node.js生态技术栈
重要通知
Node.js 是一个事件驱动 I/O 服务端 JavaScript 环境,基于 Google 的 V8 引擎,V8 引擎执行 Javascript 的速度非常快,性能非常好。node.js 版本管理工具,可以在同一系统环境自由切换不同的Node.js版本。
node.js基本概况
- 官网:https://nodejs.org
- 下载历史版本:https://nodejs.org/dist/
- 下载:https://nodejs.org/zh-cn/download
- 官方API: https://nodejs.org/dist/latest-v18.x/docs/api/
- 中文API文档: http://nodejs.cn/api/
- github:https://github.com/nodejs/node
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。
- 菜鸟教程:https://www.runoob.com/w3cnote/nvm-manager-node-versions.html
- nvm-windows 最新安装包:https://github.com/coreybutler/nvm-windows/releases
- GitHub:https://github.com/nvm-sh/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)的事件循环,直到没有事件观察者退出,每个异步事件都生成一个事件观察者,如果有事件发生就调用该回调函数。
- 事件循环: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;
}
工具模块
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 开发领域中的一个更小、更富有表现力、更健壮的基石。
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。
代码示例
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 框架。
connect-history-api-fallback:https://github.com/bripkens/connect-history-api-fallback
代码示例
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源码分析
- 中间件实现原理
- 错误捕获处理原理