node.js生态技术栈

重要通知

Node.js 是一个事件驱动 I/O 服务端 JavaScript 环境,基于 Google 的 V8 引擎,V8 引擎执行 Javascript 的速度非常快,性能非常好。node.js 版本管理工具,可以在同一系统环境自由切换不同的Node.js版本。

node.js基本概况

MacOS安装配置

# 手动下载安装地址
> https://nodejs.org/dist/v12.13.1/node-v12.13.1.pkg


This package will install:
	•	Node.js v18.6.0 to /usr/local/bin/node
	•	npm v8.13.2 to /usr/local/bin/npm


This package has installed:
	•	Node.js v22.15.0 to /usr/local/bin/node
	•	npm v10.9.2 to /usr/local/bin/npm
Make sure that /usr/local/bin is in your $PATH.

CentOS安装配置

# 先安装好npm,然后替换掉镜像,安装nvm,再选择node.js版本即可
# 不建议,版本旧 yum install -y nodejs




# 下载地址 | https://nodejs.org/zh-cn/download

# Download and install nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash

# in lieu of restarting the shell
\. "$HOME/.nvm/nvm.sh"

# Download and install Node.js:
nvm install 22

# Verify the Node.js version:
node -v # Should print "v22.18.0".
nvm current # Should print "v22.18.0".

# Download and install Yarn:
corepack enable yarn

# Verify Yarn version:
yarn -v

nvm版本管理工具

node版本管理工具,其他node版本管理工具:fnm,nvs,nvm。

安装配置

注意,执行方法一后,需要关闭CMD,再次打开,执行nvm --version才生效。

> curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash # 方法一
> wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash # 方法二

# 下载提示
100 15916  100 15916    0     0  16895      0 --:--:-- --:--:-- --:--:-- 16878
=> Downloading nvm from git to '/Users/apple/.nvm'
=> Cloning into '/Users/apple/.nvm'...

window安装配置

下载地址:https://github.com/coreybutler/nvm-windows/releases 下载EXE:https://github.com/coreybutler/nvm-windows/releases/download/1.1.11/nvm-setup.exe

重大问题:容易出现nvm use切换成功,但是实际node -v并没有成功切换至需要切换的版本

  • Mac下nvm切换node版本无效:https://juejin.cn/post/6985085066531586062
> sudo rm -rf /usr/local/{bin/{node,npm},lib/node_modules/npm,lib/node,share/man/*/node.*}

> sudo rm -rf ~/.npm
> sudo rm -rf ~/.npmrc
> sudo rm -rf ~/.node-gyp

> sudo rm -rf ~/.npmrc

基础命令

# 查看nvm版本
> nvm --version

# 显示当前运行的node版本,可以简写为nvm v
> nvm version

> nvm install <version> # 安装版本12.13.1 16.15.1 18.12.0 18.16.0 20.0.0 20.10.0 20.16.0 22.0.0

> nvm ls-remote # 列出远程服务器上所有的可用版本
> nvm ls # 查看本地node版本列表
> nvm list available # 列出本地所有的可用版本

> nvm use [version] [arch] # 切换到使用指定的nodejs版本。可以指定32/64位[arch]。nvm use <arch>将继续使用所选版本,但根据提供的值切换到32/64位模式的<arch>




nvm on # 启用node.js版本管理。
nvm off # 禁用node.js版本管理(不卸载任何东西)
nvm proxy [url]: 设置用于下载的代理。留[url]空白,以查看当前的代理。设置[url]为none删除代理。
nvm node_mirror [url]:设置node镜像,默认为https://nodejs.org/dist/.。我建议设置为淘宝的镜像https://npm.taobao.org/mirrors/node/
nvm npm_mirror [url]:设置npm镜像,默认为https://github.com/npm/npm/archive/。我建议设置为淘宝的镜像https://npm.taobao.org/mirrors/npm/
nvm uninstall <version>: 卸载指定版本的nodejs。
nvm root [path]: 设置 nvm 存储node.js不同版本的目录 ,如果未设置,将使用当前目录。


npm install -g cnpm --registry=https://registry.npm.taobao.org

基础问题

  • nvm ls-remote运行后只有iojs列表

nvm 镜像地址的问题,修改对应环境变量NVM_NODEJS_ORG_MIRROR即可

> export NVM_NODEJS_ORG_MIRROR=https://npmmirror.com/mirrors/node
> nvm ls-remote
  • 切换不同node版本会导致对应npm版本不可用,需要单独安装配置

报错:Error: Cannot find module 'node_modules\npm\bin\npm-cli.js' 解压后将文件夹重命名为npm并复制到node_modules目录下 最后将npm中bin目录下的npm、npm.cmd、npx、npx.cmd复制到与node_modules目录同级目录下

卸载nvm

# 切换至安装目录, /root/.nvm,需要根据实际安装位置变化
> cd 
cd /root/ # 切换至用户目录
ls -a # 查看目录文件
rm -rf .nvm # 删除.nvm目录


# 删除配置
vim /root/.bashrc

# 删除这些配置,凡是涉及nvm的都删除,实际上不只有这两行
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm

常见问题释疑

  • Could not retrieve https://nodejs.org/dist/latest/SHASUMS256.txt.

在本地找到安装nvm的路径,在nvm文件夹下找到settings.txt,添加以下代码即可

node_mirror:npm.taobao.org/mirrors/node/
npm_mirror:npm.taobao.org/mirrors/npm/
  • nvm list available N/A

内存泄漏

# 全局环境变量,设置4G
> export NODE_OPTIONS=--max-old-space-size=4096

node.js ESM

从node.js v12.0.0开始,只要在package.json中设置了type:"module",Node 会将整个项目视为ESM规范,我们就可以直接裸写import/export。

环境变量

  • 安装 cross-env
npm install cross-env --save-dev
  • package.json
{
  "name": "",
  "version": "",
  "scripts": {
    "dev": "cross-env NODE_ENV=development nodemon main.js"
  },
  "dependencies": {},
  "devDependencies": {
    "cross-env": "^7.0.3",
  }
}
  • main.js
console.log(process.env);
console.log(process.env.NODE_ENV);

node.js设计原理

模块加载机制

Node.js 中存在 4 类模块(原生模块和3种文件模块),其加载优先级有图所示。

事件驱动模型

node.js事件循环,即基于node.js被设计为单进程单线程的基础上,由V8引擎提供的异步执行回调接口,实现高并发处理的特性。其中,几乎整体事件机制用设计模式中观察者模式实现,类似于进入一个while(true)的事件循环,直到没有事件观察者退出,每个异步事件都生成一个事件观察者,如果有事件发生就调用该回调函数。

整个事件驱动的流程

事件Event

events 模块只提供了一个对象: events.EventEmitter。EventEmitter 的核心就是事件触发与事件监听器功能的封装。

基础方法

  • addListener(event, listener):为指定事件添加一个监听器到监听器数组的尾部。
  • on(event, listener):为指定事件注册一个监听器,接受一个字符串 event 和一个回调函数。
  • once(event, listener):为指定事件注册一个单次监听器,即 监听器最多只会触发一次,触发后立刻解除该监听器。
  • removeListener(event, listener):移除指定事件的某个监听器,监听器必须是该事件已经注册过的监听器。
  • removeAllListeners([event]):移除所有事件的所有监听器, 如果指定事件,则移除指定事件的所有监听器。
  • setMaxListeners(n):默认情况下, EventEmitters 如果你添加的监听器超过 10 个就会输出警告信息。 setMaxListeners 函数用于改变监听器的默认限制的数量。
  • listeners(event):返回指定事件的监听器数组。
  • emit(event, [arg1], [arg2], [...]):按监听器的顺序执行执行每个监听器,如果事件有注册监听返回 true,否则返回 false。
  • listenerCount(emitter, event):返回指定事件的监听器数量。

事件

  • newListener(event, listener):该事件在添加新监听器时被触发。
  • removeListener(event, listener):从指定监听器数组中删除一个监听器。需要注意的是,此操作将会改变处于被删监听器之后的那些监听器的索引。
const events = require('events');
const eventEmitter = new events.EventEmitter();

// 注册事件
eventEmitter.on('sendMsg', function() { 
  console.log('事件触发'); 
}); 

// 派发事件
eventEmitter.emit('sendMsg'); 

const listenerA = function listenerA() {
  console.log('监听器 listenerA 执行。');
}
const listenerB = function listenerB() {
  console.log('监听器 listenerA 执行。');
}

// 绑定监听器
eventEmitter.addListener('connection', listenerA);
eventEmitter.addListener('connection', listenerB);

// 返回指定事件的监听器数量
console.log(eventEmitter.listenerCount('connection'));

eventEmitter.emit('connection');
eventEmitter.removeListener('connection', listenerA);

console.log(eventEmitter.listenerCount('connection'));

Buffer(缓冲区)

该类用来创建一个专门存放二进制数据的缓存区。

创建 Buffer 类

  • Buffer.alloc(size[, fill[, encoding]]): 返回一个指定大小的 Buffer 实例,如果没有设置 fill,则默认填满 0
  • Buffer.allocUnsafe(size): 返回一个指定大小的 Buffer 实例,但是它不会被初始化,所以它可能包含敏感的数据
  • Buffer.allocUnsafeSlow(size)
  • Buffer.from(array): 返回一个被 array 的值初始化的新的 Buffer 实例(传入的 array 的元素只能是数字,不然就会自动被 0 覆盖)
  • Buffer.from(arrayBuffer[, byteOffset[, length]]): 返回一个新建的与给定的 ArrayBuffer 共享同一内存的 Buffer。
  • Buffer.from(buffer): 复制传入的 Buffer 实例的数据,并返回一个新的 Buffer 实例
  • Buffer.from(string[, encoding]): 返回一个被 string 的值初始化的新的 Buffer 实例

Buffer基础操作

const buf = Buffer.alloc(256);
const buf1 = Buffer.from('name');
const buf2 = Buffer.from('ysun');
console.log(buf);
console.log('长度:', buf.length);

// 写入缓冲区
{
  buf.write('www');
  console.log('写入数据:', buf.toString());
}
// 从缓冲区读取数据
{
  console.log('读取数据:', buf1.toString());
}
// Buffer转换为JSON 对象
{
  console.log('Buffer转换为JSON 对象:', buf1.toJSON());
}
// 缓冲区合并
{
  const result = Buffer.concat([buf1, buf2]);
  console.log('合并:', result.toString());
}
// 缓冲区比较
{
  console.log('比较:', buf1.compare(buf2));
}
// 拷贝缓冲区
{
  buf1.copy(buf2, 1);
  console.log('拷贝:', buf1.toString());
}
// 缓冲区裁剪
{
  const result = buf1.slice();
  console.log('裁剪:', result.toString());
}

Stream(流)

读取流

let stream = fs.createReadStream('Reference/code.txt');
stream.setEncoding('UTF8');

let dataCtn = "";
stream.on('data', (res)=> {
  dataCtn += res;
});
stream.on('end', ()=> {
  console.log(dataCtn);
});
stream.on('error', function(err){
  console.log(err.stack);
});

写入流

let stream = fs.createWriteStream('Reference/code.txt');
  
const writeTxt = `时间戳:${Date.now()}`;
stream.write(writeTxt, 'UTF8');
stream.end();
stream.on('finish', function() {
  console.log("写入完成。");
});
stream.on('error', function(err){
  console.log(err.stack);
});

管道流

管道提供了一个输出流到输入流的机制,即从一个流中获取数据并将数据传递到另外一个流中。

const stream = fs.createReadStream('robots.txt');
const writerStream = fs.createWriteStream('Reference/code.txt');
stream.pipe(writerStream); 

链式流

链式是通过连接输出流到另外一个流并创建多个流操作链的机制。

// 压缩文件
fs.createReadStream('Reference/code.txt')
  .pipe(zlib.createGzip())
  .pipe(fs.createWriteStream('Reference/code.txt.gz'));

// 解压文件
fs.createReadStream('Reference/code.txt.gz')
  .pipe(zlib.createGunzip())
  .pipe(fs.createWriteStream('Reference/example.txt'));

文件系统 File

异步和同步

异步方法性能更高,速度更快,而且没有阻塞。

// 异步读取
fs.readFile('Reference/code.txt', function (err, data) {
  if (err) {
    return console.error(err);
  }
  console.log("异步读取: " + data.toString());
});

// 同步读取
var data = fs.readFileSync('Reference/code.txt');
console.log("同步读取: " + data.toString());

写入文件

fs.writeFile('Reference/code.txt', '文件内容',  function(err) {
  if (err) return console.error(err);
  console.log("读取写入文件");

  fs.readFile('Reference/code.txt', function (err, data) {
      if (err) return console.error(err);
      console.log("异步读取: " + data.toString());
  });
});

读取文件

const fs = require("fs");
const buf = new Buffer.alloc(1024);

fs.open('Reference/code.txt', 'r+', function(err, fd) {
	if (err) return console.error(err);

	fs.read(fd, buf, 0, buf.length, 0, function(err, bytes){
		if (err) return console.log(err);

		console.log(bytes + "字节被读取");
		console.log(buf.slice(0, bytes).toString());
	});
});

截取文件

const fs = require("fs");
const buf = new Buffer.alloc(1024);

fs.open('Reference/code.txt', 'r+', function(err, fd) {
	if (err) return console.error(err);

  // 截取文件
  fs.ftruncate(fd, 1000, function(err){
    if (err) return console.log(err);
 
    fs.read(fd, buf, 0, buf.length, 0, function(err, bytes){
      if (err) return console.log(err);

      console.log(buf.slice(0, bytes).toString());

      // 关闭文件
      fs.close(fd, function(err){
        if (err) return console.log(err);
        console.log("文件关闭成功!");
      });
    });
  });
});

文件目录

  • 创建目录:fs.mkdir(path[, options], callback)
  • 读取目录:fs.readdir(path, callback)
  • 删除目录:fs.rmdir(path, callback)

文件操作函数示例

读取文件信息

/**
 * 获取文件信息
 * @param {*} fileUrl 文件绝对路径
 * @returns 
 */
exports.getfileInfoUtil = async function getfileInfoUtil(fileUrl = "") {
  return new Promise((resolve) => {
    if (
      !fileUrl === undefined ||
      typeof fileUrl !== "string"
    ) return resolve(null);

    fs.stat(fileUrl, (err, stats) => {
      if (err) return resolve(undefined);

      resolve(stats || {});
    });
  });
}
exports.getfileInfoUtil = getfileInfoUtil;

读取CVS文件

const csv = require('csv-parser');
const fs = require('fs');


/**
 * 读取文件
 */
async function getCVSFileUtil(
  fileUrl = "", 
  format = [],
  progressCallback
) {
  const fileInfo = await getfileInfoUtil(fileUrl);

  return new Promise((resolve) => {
    if (!fileUrl) return resolve(null);

    if (
      typeof format !== "string" &&
      !Array.isArray(format)
    ) return resolve(null);

    if (!fileInfo?.size) return resolve(null);

    let size = 0;

    fs.createReadStream(fileUrl)
      .pipe(csv())
      .on('data', (data) => {
        // 这个进度值存在问题,如果用于生产环境,需要重新测试与优化
        const length = typeof data === "string" ? data.length : !data ? 0 : JSON.stringify(data).length;
        size += length;
        const progress = ((size / fileInfo.size) * 100).toFixed(2);
        if (typeof progressCallback === "function") progressCallback(progress, fileInfo.size, size);

        Array.isArray(format) ? format.push(data) : format += data;
      })
      .on('end', () => {
        resolve(format);
      })
      .on('error', () => {
        resolve(undefined);
      });
  });
}
exports.getCVSFileUtil = getCVSFileUtil;

读取JSON文件

/**
 * 读取文件数据
 * @param {*} fileUrl  文件绝对路径
 * @param {*} format 返回数据格式
 */
async function readFileUtil(fileUrl = "", format = "json") {
  return new Promise((resolve) => {
    if (!fileUrl) return resolve(null);

    fs.readFile(fileUrl, function (err, data) {
      if (err) return resolve(undefined);

      if (format === "json") {
        resolve(JSON.parse(data.toString()));
      } else {
        resolve(data.toString());
      }
    });
  });
}
exports.readFileUtil = readFileUtil;

保存数据到文件

const fs = require('fs');


/**
 * 保存数据到文件
 * @param {*} data 数据源
 * @param {*} fileName 文件名称
 * @returns 
 */
async function saveFileDataUtil(data, fileName) {
  return new Promise((resolve) => {
    if (data === undefined) return resolve(null);

    let jsonContent;
    
    try {
      jsonContent = data ? JSON.stringify(data) : data;
    } catch(e) {
      return resolve(null);
    }

    fs.writeFile(fileName, jsonContent, function (err, data) {
      if (err) return resolve(undefined);

      resolve(data);
    });
  });
}
exports.saveFileDataUtil = saveFileDataUtil;

遍历文件目录中的所有文件

const fs = require('fs');
const path = require('path');

const { merge, mergeWith } = require('lodash/object');

const { readFileUtil } = require(ERP_PATH + '/utils/file.js');




async function initDataListFunc(countLimit = 20) {
  const fileDir = ERP_DATABASE_PATH + 'list';

  const fileList = fs.readdirSync(fileDir);
  const fileUrlList = fileList.map(file => path.join(fileDir, file));

  // 复制数组,直到数组长度等于countLimit
  const copyArrayFunc = function (arrayList = [], arraySize = countLimit) {
    const _arrayList = [...arrayList];

    if (_arrayList.length >= arraySize) {
      return _arrayList.slice(0, arraySize);
    }

    _arrayList.push.apply(_arrayList, _arrayList);
    return copyArrayFunc(_arrayList);
  }

  const referList = [];

  for (let i = 0; i < fileUrlList.length; i++) {
    const fileUrl = fileUrlList[i];
    const defaultList = await readFileUtil(fileUrl);

    const copyArray = copyArrayFunc(defaultList, countLimit);

    if (i === 0) {
      referList.push.apply(referList, copyArray);
    } else {
      for (let j = 0; j < referList.length; j++) {
        const referItem = referList[j];
        const contrastItem = copyArray[j];

        referList[j] = merge(referItem, contrastItem);
      }
    }
  }

  return referList;
}

工具模块

OS 模块

提供基本的系统操作函数。

const os = require("os");

console.log('CPU字节序:' + os.endianness());
console.log('操作系统名:' + os.type());
console.log('编译时操作系统名:' + os.platform());
console.log('系统内存总量:' + os.totalmem() + " bytes.");
console.log('操作系统空闲内存量:' + os.freemem() + " bytes.");
console.log('操作系统发行版本:', os.release());
console.log('操作系统运行时间:', os.uptime());
console.log('网络接口列表:', os.networkInterfaces());

Path 模块

提供了处理和转换文件路径的工具。


Net 模块

用于底层的网络通信。提供了服务端和客户端的的操作。

服务器

const net = require('net');
var server = net.createServer(function(connection) { 
  console.log('client connected');
  connection.on('end', function() {
    console.log('客户端关闭连接');
  });
  connection.write('Hello World!\r\n');
  connection.pipe(connection);
});
server.listen(8080, function() {
  console.log('server is listening');
});

客户端

const net = require('net');
var client = net.connect({port: 8080}, function() {
  console.log('连接到服务器!');  
});
client.on('data', function(data) {
  console.log(data.toString());
  client.end();
});
client.on('end', function() { 
  console.log('断开与服务器的连接');
});

DNS 模块

DNS 模块用于解析域名。

const dns = require("dns");

dns.lookup('www.github.com', function onLookup(err, address, family) {
  console.log('ip地址:', address);
  dns.reverse(address, function (err, hostnames) {
    if (err) return console.log(err.stack);

    console.log('反向解析 ' + address + ': ' + JSON.stringify(hostnames));
  });  
});

util模块

util 是一个Node.js 核心模块,提供常用函数的集合,用于弥补核心 JavaScript 的功能 过于精简的不足。

  • util.callbackify(original):将 async 异步函数(或者一个返回值为 Promise 的函数)转换成遵循异常优先的回调风格的函数,在回调函数中,第一个参数为拒绝的原因(如果 Promise 解决,则为 null),第二个参数则是解决的值。
  • util.inherits(constructor, superConstructor):是一个实现对象间原型继承的函数。仅仅继承在原型中定义的函数,而不是继承内部创造的函数。
  • util.inspect(object,[showHidden],[depth],[colors]):一个将任意对象转换 为字符串的方法,通常用于调试和错误输出。
  • util.isArray(object):如果给定的参数 "object" 是一个数组返回 true,否则返回 false。
  • util.isRegExp(object):如果给定的参数 "object" 是一个正则表达式返回true,否则返回false。
  • util.isDate(object):如果给定的参数 "object" 是一个日期返回true,否则返回false。
const util = require('util');

多进程

操作系统进程

查看系统进程:ps -ef

  • UID 执行该进程的用户ID
  • PID 进程编号
  • PPID 该进程的父进程编号
  • C 该进程所在的CPU利用率
  • STIME 进程执行时间
  • TTY 进程相关的终端类型
  • TIME 进程所占用的CPU时间
  • CMD 创建该进程的指令

Process (进程)

提供有关当前 Node.js 进程的信息并对其进行控制。

  • process.nextTick(callback[, ...args])
console.log('start');
process.nextTick(() => {
  console.log('nextTick callback');
});
console.log('scheduled');

Child Processes (子进程)

  • 基础方法
    • child_process.exec(command[, options], callback):使用子进程执行命令,缓存子进程的输出,并将子进程的输出以回调函数参数的形式返回。
    • child_process.spawn(command[, args][, options]):使用指定的命令行参数创建新进程。
    • child_process.fork(modulePath[, args][, options]):用于在子进程中运行的模块。

Cluster (集群)

Node.js 进程集群可用于运行多个 Node.js 实例,这些实例可以在其应用程序线程之间分配工作负载。当不需要进程隔离时,请改用该worker_threads模块,它允许在单个 Node.js 实例中运行多个应用程序线程。

import cluster from 'node:cluster';

进程间通信IPC

进程间通信就是在不同进程之间传播或交换信息。其中,进程间通信(IPC,Interprocess communication)是一组编程接口,让程序员能够协调不同的进程,使之能在一个操作系统里同时运行,并相互传递、交换信息。

IPC

IPC方法包括管道(PIPE)、消息排队、旗语、共用内存以及套接字(Socket)。

IPC管道

  • 普通管道PIPE:通常有两种限制,一是单工,只能单向传输;二是只能在父子或者兄弟进程间使用。
  • 流管道s_pipe: 去除了第一种限制,为半双工,可以双向传输。
  • 命名管道:name_pipe,去除了第二种限制,可以在许多并不相关的进程之间进行通讯。

IPC实现

  • 数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几兆字节之间。
  • 共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到。
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
  • 资源共享:多个进程之间共享同样的资源。为了做到这一点,需要内核提供锁和同步机制。
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

守护进程

守护进程,即在单一进程出现异常而终止的情况下,继续重启进程的运行工作,从而提供服务。

  • PM2守护进程
  • shell脚本守护进程
  • C语言版本
void init_daemon()
{
  pid_t pid;
  int i = 0;
  if ((pid = fork()) == -1) {
    printf("Fork error !\n");
    exit(1);
  }
  if (pid != 0) {
    exit(0);        // 父进程退出
  }
  setsid();           // 子进程开启新会话, 并成为会话首进程和组长进程
  if ((pid = fork()) == -1) {
    printf("Fork error !\n");
    exit(-1);
  }
  if (pid != 0) {
    exit(0);        // 结束第一子进程, 第二子进程不再是会话首进程
                    // 避免当前会话组重新与tty连接
  }
  chdir("/tmp");      // 改变工作目录
  umask(0);           // 重设文件掩码
  for (; i < getdtablesize(); ++i) {
    close(i);        // 关闭打开的文件描述符
  }
  return;
}

Koa框架

Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。

const Koa = require('koa');
const app = new Koa();


//跨域配置  注意:nginx配置Access-Control-Allow-Origin'后会导致:The 'Access-Control-Allow-Origin' header contains multiple values ' *', 
const cors = require('koa2-cors');
app.use(cors()); 

global.router = new Router();
app.use(router.routes());

app.use(async ctx => {
  ctx.body = 'Hello World';
});

app.listen(8080);

koa-router

koa-router,即Koa的路由器中间件。

  • 动态路由
router.get('api/app/:id', async(ctx, next) => {
  console.log(ctx.params.id);

  // ctx.request.query;
  // ctx.request.body;
});

router.get('api/app/:id/:name', async(ctx, next) => {
  console.log(ctx.params.id);
  console.log(ctx.params.name);
});

koa-body

一个功能齐全的koa正文解析器中间件。支持multipart、urlencoded和json请求正文。提供与 Express 的 bodyParser 相同的功能 - multer。

const { koaBody } = require('koa-body');
// app.use(koaBody({ multipart: true }));

// 与koa-router一起使用
router.post('/users', koaBody({
  multipart: false,  //多文件上传
  jsonLimit: '1mb',  //
  formLimit: '56kb', // 10 * 1024 * 1024 * 1024,
  textLimit: '56kb',
  encoding: 'utf-8',
  urlencoded: true,
  text: true,
  json: true,
  formidable: {
    maxFields: 1000,
    maxFileSize: 200 * 1024 * 1024,  //20MB  单文件限制
    maxFieldsSize: 2 * 1024 * 1024,  //2mb  多文件限制
    uploadDir: '',
    keepExtensions: false,
    hash: '',
    multiples: true,
    onFileBegin: ()=> {}
  },
  onError: (err)=> {
    console.log('Error');
  }
}), (ctx) => {
    console.log(ctx.request.body);

    ctx.body = { status: 200, data: {} };
  }
);
  • 支持类型

    • multipart/form-data
    • application/x-www-urlencoded
    • application/json
    • application/json-patch+json
    • application/vnd.api+json
    • application/csp-report
  • koa-body限制文件大小

Error: maxFileSize exceeded, received 209762633 bytes of file data

  • 单文件
global.koaBody = require('koa-body');
app.use(koaBody());

async function upload() { 
  router.post("/upload", koaBody({
    multipart: true, 
    formidable: {
      maxFileSize: 10 * 1024 * 1024 * 1024  # 10G
    }
  }), async (ctx) => { 
    let resdt = {status: 200, msg: "success", data: {}};
    let file = ctx.request.files;
    console.log(file);
    console.log(file.content);  //{size: , path: , name: , type: , }

    await saveFile(file.content);
    ctx.body = resdt;  
    process.stdout.write(`${JSON.stringify(resdt)}\n\n`);
  }); 
}
  • 批量上传文件
global.koaBody = require('koa-body');
app.use(koaBody());

async function uploadMultiple() { 
  router.post("/upload-multiple", koaBody({
    multipart: true,
    formidable: {
      maxFileSize: 10 * 1024 * 1024 * 1024,  //10G
      maxFilesSize: 10 * 1024 * 1024 * 1024  //10G
    }
  }), async (ctx)=> { 
    let resdt = {status: 200, msg: "success", data: {}};
    let file = ctx.request.files;
    if (!file.content.length) {
      await saveFile(file.content);
    } else {
      Array.from(file.content).forEach(async (idx)=> {
        await saveFile(idx);
      }); 
    }
    ctx.body = resdt;  
  }); 
}

formidable

const formidable require('formidable');

Node.js 连接 MongoDB

MongoDB, 基于C++编写实现的分布式文件存储非关系型数据库,旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。

const mongodb = require('mongodb');
const uuid = require("uuid");

const mongo = mongodb.MongoClient;
const url = "mongodb://root:123456@106.13.47.239:27017/";

mongo.connect(url, {useNewUrlParser: true, useUnifiedTopology: true}, (err, res)=> {
  if (err) return reject(err);
  console.log(err);
});

const dbase = MongoClient.db('test'); 
const collect = dbase.collection("example");

let doc = {title: '军事战争', article_id: uuid.v1(), subtitle: '伊朗与美国之间的冲突越演越烈'};

collect.insertOne(doc, function(err, res) {
  if (err) throw err;
  console.log(res);
});

Node.js 连接 Redis

Redis,基于C语言实现的非关系型数据库,用作数据库、缓存、流引擎和消息代理的开源内存数据存储。

const redis = require('redis');

let port = 6379; 
let host = "49.235.198.78";   //〔腾讯云〕1核2G-50G | 49.235.198.78
let options = {};         
global.RedisClient  = redis.createClient(port, host, options); 

//连接异常  
RedisClient.on("error", (err)=> {  
  console.log(err);     
}); 

//字符串
RedisClient.set("sex", '男', (err, res)=> {
  if (err) throw err;
  console.log(res); 
});
RedisClient.get("sex", (err, res)=> {
  if (err) throw err;  
  console.log(res); 
});

守护进程管理器

PM2部署运维

PM2 是一个守护进程管理器,它将帮助您管理和保持您的应用程序。 性能监控、自动重启、负载均衡, 进程监控管理工具。 功效:实时监控Web界面、问题与异常跟踪、部署报告、实时日志、电子邮件和闲置通知、自定义指标监控、自定义行动中心。

官网:https://pm2.io/
官网:https://pm2.keymetrics.io/
GitHub:https://github.com/Unitech/pm2
NPM:https://www.npmjs.com/package/pm2
技术文档:https://pm2.io/docs/plus/overview/ 配置文档:https://www.jiyik.com/w/pm2/pm2-configuration-file

安装配置

安装:pnpm install pm2 -g

应用部署

健壮稳定

日志监控

安装配置

> 安装:pnpm install pm2 -g
> 版本:pm2 -v  
> update[vuersion]:npm install pm2@latest -g  
> update[in-memory]:pm2 update 

启动

> pm2 start main.js [--name] <application> -i [max]   # 开启最大多少个进程
> pm2 start main.js --watch      |-开启watch, 后续更新代码,无需重启应用即可生效
> pm2 start main.js [--port 8082]  |-配置端口号
> pm2 startup                  # 创建开机自启动命令

基础操作

> pm2 restart < app_name | namespace | id | 'all' | json_conf >  #重启应用   
> pm2 stop < app_name | namespace | id | 'all' | json_conf >     #停止应用  
> pm2 reload < app_name | namespace | id | 'all' | json_conf >   #重载代码 |-新加代码时重新加载代码  
> pm2 delete < app_name | namespace | id | 'all' | json_conf >   #删除应用 

批量操作

> pm2 reload all  
> pm2 stop all  
> pm2 restart all  
> pm2 delete all

监控管理

> pm2 list [list|ls|status]   #列出所有应用  
> pm2 monit    #查看资源消耗 | 打开实时监视器去查看资源占用情况  
> pm2 describe <id|app_name>   #查看某一个应用状态  
> pm2 logs   #查看所有日志  
> pm2 show <id|app_name>   #查看详情

日志

> pm2 logs [--raw]       # Display all processes logs in streaming  
> pm2 flush              # Empty all log files  
> pm2 reloadLogs         # Reload all logs

pm2 plus

pm2 plus    # https://app.pm2.io/  开启api访问

PM2.IO

扩展集群

pm2 scale <app name> <n>

代码示例

var pm2 = require('pm2');

pm2.connect(function(err) {
  if (err) {
    console.error(err);
    process.exit(2);
  }
  
  pm2.start({
    script    : 'app.js',         // Script to be run
    exec_mode : 'cluster',        // Allows your app to be clustered
    instances : 4,                // Optional: Scales your app by 4
    max_memory_restart : '100M'   // Optional: Restarts your app if it reaches 100Mo
  }, function(err, apps) {
    pm2.disconnect();   // Disconnects from PM2
    if (err) throw err
  });
});  

ecosystem.config.js

相关指令

生成示例配置文件:pm2 init simple 启动所有的应用程序:pm2 start ecosystem.config.js 停止所有应用程序 重启所有应用程序 重载所有应用程序 删除所有应用程序

代码示例

module.exports = {
  apps: [
    {
      name: "mywork",  //应用程序名称
      cwd: "/srv/node-app/current",  //应用程序所在的目录
      script: "bin/www",  //应用程序的脚本路径
      log_date_format: "YYYY-MM-DD HH:mm Z",  //
      error_file: "/var/log/node-app/node-app.stderr.log",  //自定义应用程序的错误日志文件
      out_file: "log/node-app.stdout.log",  //自定义应用程序日志文件
      pid_file: "pids/node-geo-api.pid",  //自定义应用程序的pid文件
      instances: 6,  //
      min_uptime: "200s",  //最小运行时间,这里设置的是60s即如果应用程序在60s内退出,pm2会认为程序异常退出,此时触发重启max_restarts设置数量
      max_restarts: 10,  //设置应用程序异常退出重启的次数,默认15次(从0开始计数)
      max_memory_restart: "1M",  //
      cron_restart: "1 0 * * *",  //定时启动,解决重启能解决的问题
      watch: false,  //是否启用监控模式,默认是false。如果设置成true,当应用程序变动时,pm2会自动重载。这里也可以设置你要监控的文件。
      merge_logs: true,  //
      exec_interpreter: "node",  //应用程序的脚本类型,这里使用的shell,默认是nodejs
      exec_mode: "fork",  //应用程序启动模式,这里设置的是cluster_mode(集群),默认是fork
      autorestart: false,  //启用/禁用应用程序崩溃或退出时自动重启
      vizion: false  //启用/禁用vizion特性(版本控制)
    }
  ]
}

工程化建设方案

Typescript

ts-node用于 node.js 的 TypeScript 执行和 REPL,具有源映射和本机 ESM 支持。

安装配置

> pnpm install -D typescript
> pnpm install -D ts-node

# Depending on configuration, you may also need these
> pnpm install -D tslib @types/node
  • 启动项目
> ts-node main.ts
  • 配置package.json
"scripts": {
  "dev": "nodemon --watch src/ -e ts --exec ts-node ./src/main.ts"
}
  • 打包程序

性能调优最佳实践

Node.js 性能平台(Node.js Performance Platform)是面向中大型 Node.js 应用提供性能监控、安全提醒、故障排查、性能优化等服务的整体性解决方案。

Node.js生态系统

Express

用于节点的快速、不拘一格、极简主义的 Web 框架。

const express = require('express');
const app = express();

// 反向代理
const { createProxyMiddleware } = require('http-proxy-middleware');
app.use(
  '/ares',
  createProxyMiddleware({
    target: 'https://erp.feierfit.com/',
    changeOrigin: true
  })
);

// 反向代理: ERP示例
/*
 * 访问网址: http://localhost:5111/#/wizard/index
 * API地址: http://localhost:5111/de-api/system/requestTimeOut
 * 实际测试环境API地址: https://bi-dev.feierfit.com/de-api/system/requestTimeOut
 */
app.use(
  '/de-api/',
  createProxyMiddleware({
    target: 'https://bi-dev.feierfit.com/de-api/',
    changeOrigin: true
  })
);

// 解决路由刷新问题
const history = require('connect-history-api-fallback');
app.use(history());

// 静态资源目录
const serveStatic = require('serve-static');
app.use(serveStatic('./erp_fe/dist'));

// 跨域问题
const cors = require('cors');
app.use(cors());


const port = 7003;
app.listen(port); 

搭建服务器

const express = require("express");
const app = express();


// 刷新问题
const history = require("connect-history-api-fallback");
app.use(history());

// 静态资源服务
const STATIC_PATH = "./server-files";
const serveStatic = require("serve-static");
app.use(serveStatic(STATIC_PATH));

// 跨域问题
const cors = require("cors");
app.use(cors());

/*--------------------------------------------------*/
const port = 7061;
app.listen(port);

console.log(`

··································${new Date()}··································
PORT: ${port}    Server is Running...
····· 
·  
http://localhost:${port}
server is starting ...
`);

/**
 * 异常机制
 */
app.on("error", (err) => {
  //err.stack
  console.log(`\n\n Express异常错误机制  \n ${err}  \n\n`);
});

request

node-fetch

语法示例

import fetch from 'node-fetch';

log4js

log4js是Node.js 中一个成熟的记录日志的第三方模块。

var log4js = require("log4js");
var logger = log4js.getLogger();
logger.level = "debug";
logger.debug("Some debug messages");

node-http-proxy

node-http-proxy是一个支持 websockets 的 HTTP 可编程代理库。它适用于实现反向代理和负载均衡器等组件。

> Mac:sudo npm i http-proxy --force
  • 代码示例
const app = new Koa();
const httpProxy = require('http-proxy');
const proxy = httpProxy.createProxyServer({});

app.use(async (ctx, next) => {
  proxy.web(req, res, {
    target: 'https://erp.feierfit.com'
  });
  await next();
});

http-proxy-middleware

nodejs中间件代理跨域

const express = require('express');
const app = express();

// 配置反向代理
const { createProxyMiddleware } = require('http-proxy-middleware');

// 反向代理路径一
const PROXY_CONFIG_001 = {
  host_name: '',
  api_paths: []
};

// 反向代理路径二
const PROXY_CONFIG_002 = {
  host_name: '',
  api_paths: []
};

// 反向代理路径三
const PROXY_CONFIG_003 = {
  host_name: '',
  api_paths: []
};

app.use(
  // '/feplugin',
  '/ares/v1/',
  createProxyMiddleware({
    router: {
      '42.192.173.202:20003'.      : 'http://42.192.173.202:20003/ares/v1/',  // 刘路浩
      'staging.localhost:3000'     : 'http://42.192.173.202:20165/ares/v1/',  // 田晨亮
      'localhost:3000/api/mock/'   : 'http://42.192.173.202:20016/ares/v1/',  // 坚森
      '/financial/'                : 'http://42.192.173.202:20005/ares/v1/'   // 炳委
    },
    target: 'https://dev.feierfit.com/',
    changeOrigin: true
  })
);

// 刷新问题
const history = require('connect-history-api-fallback');
app.use(history());

// 静态资源服务
const serveStatic = require('serve-static');
app.use(serveStatic('./fed_fe/dist'));

// 跨域问题
const cors = require('cors');
app.use(cors());


// 路由拦截
app.all('*', (req, res, next) => {
  console.log('路由:', req, res);
  next()
})

const port = 5111;
app.listen(port);
var express = require('express');
var proxy = require('http-proxy-middleware');
var app = express();

app.use('/', proxy({
  // 代理跨域目标接口
  target: 'http://www.domain2.com:8080',
  changeOrigin: true,

  // 修改响应头信息,实现跨域并允许带cookie
  onProxyRes: function(proxyRes, req, res) {
      res.header('Access-Control-Allow-Origin', 'http://www.domain1.com');
      res.header('Access-Control-Allow-Credentials', 'true');
  },

  // 修改响应信息中的cookie域名
  cookieDomainRewrite: 'www.domain1.com'  // 可以为false,表示不修改
}));

Midway

TypeORM

TypeORM 是一个ORM (opens new window)框架,它可以运行在 NodeJS、Browser、Cordova、PhoneGap、Ionic、React Native、Expo 和 Electron 平台上,可以与 TypeScript 和 JavaScript (ES5,ES6,ES7,ES8)一起使用。

cool-admin-api

cool-admin-api 是基于egg.js、typeorm、jwt等封装的api开发脚手架、快速开发api接口。

https://github.com/cool-team-official/cool-admin-midway

COOL: https://docs.cool-admin.com/ cool-admin for node是基于midwayjs开发的 http://www.midwayjs.org/ 技术选型 midwayjs,基础框架; bullmq,基于redis的任务与队列框架; TypeORM,node的orm框架,中文文档,; mysql,最流行的关系型数据库管理系统; elasticsearch,大数据处理; moleculer,一个Node.js快速、可扩展、容错的微服务框架;

网络请求:https://github.com/request/request-promise

基础工程搭建配置

node.js安装配置

下载地址: https://nodejs.org/zh-cn/download

  • window安装配置
# window版本
https://nodejs.org/dist/v18.17.1/node-v18.17.1-x64.msi

# 此电脑 -> 右键"属性" -> 高级系统设置 -> 环境变量
path -> D:\software\nodejs\

# 查看版本
node -v
npm -v

Node.js体系源码

Node.js源码分析

Koa源码分析

  • 中间件实现原理
  • 错误捕获处理原理

Koa-router源码分析

Last Updated:
Contributors: 709992523, Eshen