Skip to content

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

window安装配置 ​

# window版本
https://nodejs.org/dist/v18.17.1/node-v18.17.1-x64.msi

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

# 查看版本
node -v
npm -v

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安装配置 ​

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

> 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

常见问题释疑 ​

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)的事件循环,直到没有事件观察者退出,每个异步事件都生成一个事件观察者,如果有事件发生就调用该回调函数。

  • 事件循环:https://nodejs.org/en/learn/asynchronous-work/event-loop-timers-and-nexttick

  • 在事件驱动模型中,会生成一个主循环来监听事件,当检测到事件时触发回调函数。

整个事件驱动的流程

事件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;
}

图片处理 ​

GraphicsMagick:http://www.graphicsmagick.org/ ImageMagick:https://imagemagick.org/index.php

Excel表单处理 ​

依赖库 ​

  • js-xlsx:
  • excel-export:

常见的npm依赖模块 ​

js-xlsx: 目前 Github 上 star 数量最多的处理 Excel 的库,支持解析多种格式表格XLSX / XLSM / XLSB / XLS / CSV,解析采用纯js实现,写入需要依赖nodejs或者FileSaver.js实现生成写入Excel,可以生成子表Excel,功能强大,但上手难度稍大。不提供基础设置Excel表格api例单元格宽度,文档有些乱,不适合快速上手; node-xlsx: 基于Node.js解析excel文件数据及生成excel文件,仅支持xlsx格式文件; excel-parser: 基于Node.js解析excel文件数据,支持xls及xlsx格式文件,需要依赖python,太重不太实用; excel-export : 基于Node.js将数据生成导出excel文件,生成文件格式为xlsx,可以设置单元格宽度,API容易上手,无法生成worksheet字表,比较单一,基本功能可以基本满足; node-xlrd: 基于node.js从excel文件中提取数据,仅支持xls格式文件,不支持xlsx,有点过时,常用的都是XLSX 格式。

实践 ​

通过以上分析对比,本人比较推崇js-xlsx、excel-export来读写Excel文件,可以结合使用js-xlsx解析Excel、excel-export生成,效果更加,接下来分别实践js-xlsx、excel-export。

JSON ​

JSONstream模块 ​

JSONstream旨在解析单个巨大的JSON对象,而不是许多JSON对象

视频 ​

const fs = require("fs");
const zlib = require('zlib');


module.exports = async function() {
  console.log("|-----" + "video视频流" + "-----FILE_URL: " + __filename);

  //业务模型
  await init();

}

async function init() {
  router.all("/video", async (ctx, next) => {
    let resdt = {status: 200, msg: "success", data: {}};
    console.log(ctx.request.method);

    ctx.set('Content-Type', 'video/mp4');

    let readerStream = fs.createReadStream('D:/devpt/project/server/item/video/material/download.mp4');

    readerStream.pipe(ctx.end);

    //ctx.body = ctx.res; 
    readerStream.on('end', function() {
      readerStream.end();
    });


    //process.stdout.write(`${JSON.stringify(resdt)}\n\n`);
  });

}

//去读视频流
async function Stream() {
  let readerStream = fs.createReadStream('D:/devpt/project/server/item/video/material/download.mp4');
  readerStream.setEncoding('UTF8');

  let promise = new Promise((resolve, reject)=> {
    let dataCtn = "";
    readerStream.on('data', (res)=> {
      //dataCtn.push(res);
      dataCtn += res;
    });
    readerStream.on('end', ()=> {
      resolve(dataCtn);
    });
    readerStream.on('error', function(err){
      reject(err);
    });    
  });

  return promise;
}

工具模块 ​

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('断开与服务器的连接');
});

node.js检测端口是否占用 ​

var net = require('net')
 
// 检测端口是否被占用
function portIsOccupied (port) {
 // 创建服务并监听该端口
 var server = net.createServer().listen(port)
 
 server.on('listening', function () { // 执行这块代码说明端口未被占用
 server.close() // 关闭服务
 console.log('The port【' + port + '】 is available.') // 控制台输出信息
 })
 
 server.on('error', function (err) {
 if (err.code === 'EADDRINUSE') { // 端口已经被使用
  console.log('The port【' + port + '】 is occupied, please change other port.')
 }
 })
}
 
// 执行
portIsOccupied(1987);

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 开发领域中的一个更小、更富有表现力、更健壮的基石。

  • 中文网站:https://koa.bootcss.com/

  • github:https://github.com/koajs/koa

  • 主要特征

    • 封装node http server、创建Koa类构造函数
    • 构造request、response、context对象
    • 中间件调度机制和剥洋葱模型的实现, next(机制)
    • 错误捕获和错误处理
  • 代码示例

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。

  • GitHub:https://github.com/koajs/koa-body

  • 代码示例

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); 
});

守护进程管理器 ​

进程守护forever

进程守护nodemon

进程守护supervisor

进程守护supervisor

PM2部署运维 ​

安装配置 ​

  • 安装: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                  # 创建开机自启动命令

开启自启动 ​

# 启动你的应用(main.js),建议指定名称便于管理
pm2 start main.js --name "my-app"  # --name 可选,推荐添加

# 查看进程状态,确保应用正常运行
pm2 list  # 或 pm2 ls

# 生成启动脚本(无需sudo执行,会自动检测系统)
pm2 startup

# 生成以下内容
[PM2] Init System found: launchd
[PM2] To setup the Startup Script, copy/paste the following command:
sudo env PATH=$PATH:/Users/ysun/.nvm/versions/node/v22.15.0/bin /usr/local/share/.config/yarn/global/node_modules/pm2/bin/pm2 startup launchd -u ysun --hp /Users/ysun



# 复制终端输出的命令并执行(示例,以实际输出为准)
sudo env PATH=$PATH:/usr/local/node/bin pm2 startup systemd -u root --hp /root

# 说明:
# - 此命令会为systemd(Ubuntu 16+/CentOS 7+等主流系统)生成服务文件
# - 非root用户需将 -u 后的参数改为你的用户名,--hp 改为你的主目录路径







# 保存当前所有pm2管理的进程,生成/root/.pm2/dump.pm2文件
pm2 save  

[PM2] Saving current process list...
[PM2] Successfully saved in /Users/ysun/.pm2/dump.pm2
# 系统重启时,pm2会自动读取此文件恢复所有进程

基础操作 ​

> 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 ​

安装配置 ​

> 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 ​

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 ​

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

node-http-proxy ​

> 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 ​

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)一起使用。

  • 官网:https://typeorm.biunav.com/

cool-admin-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

插件列表 ​

bagpipe ​

GitHub: https://github.com/JacksonTian/bagpipehttps://github.com/JacksonTian/bagpipe/blob/master/README_CN.md

Elasticsearch ​

Elasticsearch是一个基于Lucene的搜索服务器 官网:https://www.elastic.co/

fontmin ​

log4js ​

shelljs ​

GitHub:https://github.com/shelljs/shelljs

siege ​

yallist ​

imagemin ​

https://github.com/imagemin/imagemin

命令行工具 ​

开发第三方插件或构建工具时使用

single-line-log

限制同一行打印日志:single-line-log const figlet = require('figlet')

chalk

命令行颜色插件 NPM: https://www.npmjs.com/package/chalk

const chalk = require('chalk');
console.log(chalk.blue('Hello world!'));

inpuirer

ora

GitHub: https://github.com/Marak/colors.js

NPM: https://www.npmjs.com/package/commander API: http://tj.github.io/commander.js/

Released under the MIT License.