重新学习前端之Node.js

2 阅读10分钟

Node.js

一、Node.js 基础与模块系统

1. Node.js 是什么?

定义 Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境(Runtime),它使得 JavaScript 可以在服务端运行,而不再局限于浏览器环境。

原理 Node.js 由 C++ 编写的底层核心和 JavaScript 编写的高层 API 组成。其架构分为四层:

┌─────────────────────────────────────────────────┐
│               Node.js API (JavaScript)          │  ← 用户层
├─────────────────────────────────────────────────┤
│              Node.js Bindings (C++)             │  ← 绑定层
├─────────────────────────────────────────────────┤
│              V8 引擎 + Libuv + ...              │  ← 核心层
├─────────────────────────────────────────────────┤
│              操作系统 (Linux/Windows/Mac)        │  ← 系统层
└─────────────────────────────────────────────────┘

核心组成部分

  • V8 引擎: Google 开发的 JavaScript 引擎,负责 JS 代码的编译和执行
  • Libuv: 跨平台的异步 I/O 库,提供事件循环和线程池
  • C-ares: 处理 DNS 查询
  • OpenSSL: 提供加密和 TLS 支持
  • HTTP Parser: 解析 HTTP 协议
  • Zlib: 提供压缩功能

示例:创建简单的 HTTP 服务器

const http = require('http');

const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Hello, Node.js!\n');
});

server.listen(3000, () => {
  console.log('Server running at http://localhost:3000/');
});

常见误区

  • ❌ Node.js 是一个框架 → ✅ Node.js 是运行时环境,不是框架
  • ❌ Node.js 是多线程的 → ✅ Node.js 主线程是单线程的,但底层 Libuv 使用线程池处理部分 I/O
  • ❌ Node.js 只适合 I/O 密集型 → ✅ 虽然擅长 I/O 密集型,但通过 Worker Threads 也能处理 CPU 密集型任务

2. Node.js 特点

核心特点

特点说明原理
单线程主线程只有一个,避免多线程上下文切换开销V8 引擎单线程执行 JS 代码
非阻塞 I/OI/O 操作不会阻塞主线程Libuv 事件循环 + 线程池
事件驱动通过事件循环处理异步操作事件驱动架构
异步编程大量使用回调、Promise、async/await避免阻塞,提高并发能力
跨平台支持 Windows、macOS、LinuxLibuv 跨平台抽象层
丰富的生态系统NPM 拥有全球最大的包仓库package.json + registry

优势场景

  • I/O 密集型应用:Web 服务器、API 网关、代理服务器
  • 实时应用:聊天室、在线游戏、实时数据分析
  • 工具链:构建工具(Webpack、Vite)、脚手架(CLI)
  • 微服务:轻量级、快速启动、适合容器化部署

劣势场景

  • CPU 密集型任务:图像处理、视频编码、机器学习(可通过 Worker Threads 缓解)

3. Node.js 应用场景

主要应用场景

场景说明技术栈示例
Web 服务器构建 RESTful API、GraphQL 服务Express、Koa、Fastify
SSR(服务端渲染)提升首屏加载速度和 SEONext.js、Nuxt.js
实时应用WebSocket 实时通信Socket.io、ws
构建工具前端工程化Webpack、Vite、Rollup
CLI 工具命令行工具开发Commander、Inquirer
微服务轻量级服务拆分NestJS、Fastify
自动化脚本文件处理、部署脚本fs、child_process
中间层(BFF)聚合多个后端 APIGraphQL、REST 代理
流式处理大文件处理、数据转换Stream API

SSR 示例(Nuxt.js)

// nuxt.config.js
export default {
  target: 'server',
  ssr: true,
  // 服务端渲染,提升首屏加载速度和 SEO
}

BFF 示例

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

app.get('/api/user-data', async (req, res) => {
  // 聚合多个后端服务
  const [user, orders, notifications] = await Promise.all([
    axios.get('http://user-service/api/user'),
    axios.get('http://order-service/api/orders'),
    axios.get('http://notification-service/api/notifications')
  ]);
  res.json({ user: user.data, orders: orders.data, notifications: notifications.data });
});

4. Node.js 模块系统

定义 Node.js 模块系统遵循 CommonJS 规范,将代码组织为独立的模块,每个模块有自己的作用域,通过 require 加载,通过 module.exports 导出。

模块类型

类型说明示例
核心模块Node.js 内置模块,无需安装fs, http, path, events
文件模块用户创建的本地模块./utils, ../config/db
第三方模块NPM 安装的模块express, lodash, axios

模块加载优先级

  1. 核心模块(优先级最高)
  2. NPM 安装的模块(node_modules
  3. 本地文件模块(需要路径标识)

模块包装器 Node.js 在执行模块前会将其包装在一个函数中:

(function(exports, require, module, __filename, __dirname) {
  // 模块的实际代码
  const fs = require('fs');
  module.exports = { readFile: fs.readFile };
});

这解释了为什么每个模块中的 varletconst 声明都是局部变量,不会污染全局作用域。

示例

// math.js - 模块定义
function add(a, b) { return a + b; }
function subtract(a, b) { return a - b; }
module.exports = { add, subtract };

// app.js - 模块使用
const math = require('./math');
console.log(math.add(1, 2)); // 3

5. CommonJS 规范

定义 CommonJS 是 JavaScript 的模块规范之一,最初为服务端 JavaScript 设计。Node.js 采用了 CommonJS 规范的大部分特性。

核心 API

  • require(id): 加载模块
  • module.exports: 导出模块内容
  • exports: module.exports 的引用

特性

  • 同步加载: require 是同步的,会阻塞后续代码执行
  • 运行时加载: 在代码运行时确定依赖关系
  • 值拷贝: 导出的是值的拷贝(对象是引用拷贝)
  • 缓存机制: 模块加载后会被缓存,再次加载直接返回缓存
// a.js
let count = 0;
function increment() { count++; return count; }
module.exports = { increment, getCount: () => count };

// b.js
const { increment, getCount } = require('./a');
console.log(increment()); // 1
console.log(getCount());  // 1
// 导出的是值的拷贝,但对象/函数是引用

与 ES Modules 对比

特性CommonJSES Modules
语法require / module.exportsimport / export
加载方式同步加载异步加载(静态分析)
加载时机运行时编译时(静态解析)
输出值值的拷贝值的引用(动态绑定)
this 指向指向当前模块undefined
Tree Shaking不支持支持
顶层 await不支持支持

6. require 原理

加载流程 require 的完整加载过程分为五个阶段:

require(id)
  ↓
1. 路径解析 (Module._resolveFilename)
  ↓
2. 缓存检查 (Module._cache)
  ↓
3. 创建模块实例 (new Module)
  ↓
4. 加载模块 (module.load)
  ↓
5. 编译执行 (module._compile)
  ↓
返回 module.exports

源码简化分析

// Node.js 内部简化实现
Module.prototype.require = function(id) {
  // 1. 解析路径
  const filename = Module._resolveFilename(id, this);
  
  // 2. 检查缓存
  if (Module._cache[filename]) {
    return Module._cache[filename].exports;
  }
  
  // 3. 创建新模块
  const module = new Module(filename);
  Module._cache[filename] = module;
  
  // 4. 加载模块
  module.load(filename);
  
  // 5. 返回导出
  return module.exports;
};

路径解析规则

  • 核心模块:直接返回模块名(如 fs
  • 相对路径:拼接当前文件所在目录
  • 绝对路径:直接使用
  • 无路径标识:在 node_modules 中查找

7. module.exports 与 exports

关系说明

  • exportsmodule.exports 的引用
  • 最终导出的是 module.exports
  • exports 的属性赋值等价于对 module.exports 赋值
  • 直接给 exports 重新赋值会断开引用关系
// 正确用法 1:给 exports 添加属性
exports.name = 'Node.js';
exports.version = '18';
// 等价于 module.exports.name = 'Node.js';

// 正确用法 2:直接赋值 module.exports
module.exports = {
  name: 'Node.js',
  version: '18'
};

// ❌ 错误用法:给 exports 重新赋值
exports = { name: 'Node.js' }; // 无效!断开了与 module.exports 的引用

引用关系图解

初始状态:
exports ──→ {} ←── module.exportsexports 添加属性:
exports.name = 'xxx'
exports ──→ { name: 'xxx' } ←── module.exportsexports 重新赋值(断开引用):
exports = {}
exports ──→ {}           (新的空对象)
           { name: 'xxx' } ←── module.exports  (最终导出的)

8. 模块缓存

原理 Node.js 会缓存已加载的模块,再次 require 时直接返回缓存,不会重新执行模块代码。缓存存储在 require.cache 中。

// a.js
console.log('a.js 被加载');
module.exports = { count: 0 };

// b.js
const a1 = require('./a'); // 输出: a.js 被加载
const a2 = require('./a'); // 无输出,使用缓存

console.log(a1 === a2);    // true,同一个对象

清除缓存

// 清除指定模块缓存
delete require.cache[require.resolve('./a')];

// 清除所有缓存
Object.keys(require.cache).forEach(key => {
  delete require.cache[key];
});

使用场景

  • 配置文件热更新
  • 测试用例隔离
  • 插件系统动态加载

9. 模块加载机制

文件查找规则require('./utils') 时,Node.js 按以下顺序查找:

  1. ./utils (精确匹配文件)
  2. ./utils.js
  3. ./utils.json
  4. ./utils.node (C++ 扩展)
  5. ./utils/index.js (作为目录,查找 index 文件)

目录模块查找 如果 utils 是目录:

  1. 查找 ./utils/package.json 中的 main 字段
  2. 查找 ./utils/index.js
  3. 查找 ./utils/index.json
  4. 查找 ./utils/index.node

node_modules 查找require('express') 时,从当前目录开始向上逐层查找:

./node_modules/express
../node_modules/express
../../node_modules/express
../../../node_modules/express
...

10. 循环依赖

问题描述 当模块 A 引用模块 B,模块 B 又引用模块 A 时,形成循环依赖。

// a.js
const b = require('./b');
console.log('在 a.js 中,b 的值为:', b);
module.exports = { name: 'a' };

// b.js
const a = require('./a');
console.log('在 b.js 中,a 的值为:', a);
module.exports = { name: 'b' };

// main.js
require('./a');
// 输出:
// 在 b.js 中,a 的值为: {}  (空对象,a.js 尚未执行完)
// 在 a.js 中,b 的值为: { name: 'b' }

解决策略

  1. 推迟引用: 在函数内部 require
// a.js
module.exports = {
  getName() {
    const b = require('./b'); // 延迟加载
    return b.name;
  }
};
  1. 抽取公共模块: 将共同依赖抽到独立模块
  2. 重新设计架构: 消除循环依赖,改为单向依赖

二、Buffer 与 Stream

11. Buffer

定义 Buffer 是 Node.js 用于处理二进制数据的类数组对象。在 JavaScript 原生不支持二进制数据时,Buffer 填补了这一空白。

特点

  • 大小固定,创建后不能调整
  • 内存分配在 V8 堆外(由 C++ 层管理)
  • 基于 TypedArray(Uint8Array)

创建方式

// 从字符串创建
const buf1 = Buffer.from('Hello', 'utf-8');

// 从数组创建
const buf2 = Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]);

// 分配指定大小(已废弃旧 API,不推荐使用 allocUnsafe)
const buf3 = Buffer.alloc(10);      // 安全初始化(清零)
const buf4 = Buffer.allocUnsafe(10); // 快速但不安全(可能包含旧数据)

// 拼接 Buffer
const combined = Buffer.concat([buf1, buf2]);

常见操作

const buf = Buffer.from('Node.js');

// 转字符串
console.log(buf.toString('utf-8'));  // 'Node.js'
console.log(buf.toString('hex'));    // '4e6f64652e6a73'
console.log(buf.toString('base64')); // 'Tm9kZS5qcw=='

// 读取/写入
buf.writeUInt8(65, 0);      // 在位置 0 写入 65 ('A')
console.log(buf.readUInt8(0)); // 65

// 比较
Buffer.compare(buf1, buf2); // 返回 -1, 0, 1
buf.equals(buf2);           // 返回 boolean

应用场景

  • 文件读写
  • 网络数据传输
  • 加密/解密
  • 图片/视频处理

12. Stream

定义 Stream(流)是 Node.js 中处理流式数据的抽象接口,用于连续读取或写入大量数据,而不需要将全部数据加载到内存中。

流的类型

类型说明示例
Readable可读流,数据源fs.createReadStream
Writable可写流,数据目标fs.createWriteStream
Duplex双工流,既可读又可写net.Socket
Transform转换流,读取时可修改数据zlib.createGzip

可读流示例

const fs = require('fs');

// 方式 1:事件监听
const readStream = fs.createReadStream('./large-file.txt', {
  highWaterMark: 64 * 1024, // 64KB 缓冲区
  encoding: 'utf-8'
});

readStream.on('data', chunk => {
  console.log(`接收到 ${chunk.length} 字节`);
});

readStream.on('end', () => {
  console.log('读取完成');
});

// 方式 2:异步迭代
(async () => {
  for await (const chunk of readStream) {
    console.log(chunk);
  }
})();

可写流示例

const writeStream = fs.createWriteStream('./output.txt');

writeStream.write('Hello ');
writeStream.write('World');
writeStream.end(); // 结束写入

writeStream.on('finish', () => {
  console.log('写入完成');
});

管道(Pipe)

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

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

// pipe 自动处理背压(backpressure)

背压(Backpressure)处理 当写入速度跟不上读取速度时,会产生背压:

const fs = require('fs');

const readStream = fs.createReadStream('./large.txt');
const writeStream = fs.createWriteStream('./output.txt');

// 手动处理背压
readStream.on('data', chunk => {
  const shouldContinue = writeStream.write(chunk);
  if (!shouldContinue) {
    readStream.pause();    // 暂停读取
    console.log('背压:暂停读取');
  }
});

writeStream.on('drain', () => {
  readStream.resume();     // 缓冲区清空,恢复读取
  console.log('背压解除:恢复读取');
});

三、异步 I/O 与事件循环

13. 异步 I/O

定义 异步 I/O 是指在执行 I/O 操作(文件读写、网络请求、数据库查询)时,不会阻塞主线程,而是将操作交给底层线程池或系统 API,完成后通过回调通知主线程。

同步 vs 异步

const fs = require('fs');

// 同步(阻塞)
const data = fs.readFileSync('./file.txt', 'utf-8');
console.log(data); // 等待文件读取完成

// 异步(非阻塞)
fs.readFile('./file.txt', 'utf-8', (err, data) => {
  console.log(data); // 读取完成后执行
});
console.log('继续执行其他代码'); // 不会等待

底层实现

  • 文件 I/O: 使用 Libuv 线程池(默认 4 个线程)
  • 网络 I/O: 使用操作系统 epoll/kqueue/IOCP(非阻塞系统调用)
  • DNS: 使用 C-ares 或线程池

14. Node.js 事件循环

定义 事件循环(Event Loop)是 Node.js 实现非阻塞 I/O 的核心机制。它是一个无限循环,不断检查是否有待处理的任务,并按阶段执行。

事件循环流程图

   ┌───────────────────────────┐
┌─>│        timers             │  ← setTimeout, setInterval
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │  ← 上一轮未执行的 I/O 回调
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │  ← 内部使用
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │           poll            │  ← I/O 回调、fs.readFile 等
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │           check           │  ← setImmediate
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──│      close callbacks      │  ← close 事件回调
   └───────────────────────────┘

事件循环核心要点

  • 事件循环是单线程的
  • 每个阶段都有一个 FIFO 回调队列
  • 只有队列为空或达到系统限制时,才会进入下一阶段
  • process.nextTickPromise.then 在每个阶段结束后立即执行

15. 事件循环各阶段详解

timers 阶段
  • 时机: 事件循环的第一阶段
  • 执行: setTimeoutsetInterval 的回调
  • 注意: 延迟时间是最小等待时间,实际执行时间可能更长(受系统负载影响)
setTimeout(() => {
  console.log('setTimeout 回调');
}, 100);
// 至少等待 100ms 后在 timers 阶段执行
pending callbacks 阶段
  • 执行: 上一轮循环中未执行的 I/O 回调
  • 场景: 网络错误回调、TCP 连接异常等
idle, prepare 阶段
  • 用途: Node.js 内部使用
  • 开发者一般不直接涉及
poll 阶段
  • 最重要的阶段
  • 功能:
    1. 执行 I/O 回调(fs.readFilehttp.request 等)
    2. 检查 timers 队列,如果到期则回到 timers 阶段
  • 行为:
    • 如果 poll 队列不为空:同步执行所有回调
    • 如果 poll 队列为空:
      • 如果有 setImmediate: 立即进入 check 阶段
      • 如果没有 setImmediate: 等待新的 I/O 回调
fs.readFile('./file.txt', 'utf-8', (err, data) => {
  // 在 poll 阶段执行
  console.log(data);
});
check 阶段
  • 执行: setImmediate 的回调
  • 特点: 在 poll 阶段结束后立即执行
setImmediate(() => {
  console.log('setImmediate 回调');
});
close callbacks 阶段
  • 执行: 关闭事件的回调(如 socket.on('close')
const server = net.createServer();
server.on('close', () => {
  // 在 close callbacks 阶段执行
  console.log('服务器已关闭');
});

16. setImmediate

定义 setImmediate 将回调添加到事件循环的 check 阶段,在当前轮次的 I/O 操作完成后执行。

console.log('start');

setImmediate(() => {
  console.log('setImmediate');
});

console.log('end');

// 输出: start → end → setImmediate

与 setTimeout(fn, 0) 的区别

  • setImmediate: 在 poll 阶段后执行(确定性更高)
  • setTimeout(fn, 0): 在 timers 阶段执行(至少等待 1ms)

17. process.nextTick

定义 process.nextTick 将回调添加到当前阶段的末尾,在下一个阶段开始前执行。它不属于事件循环的任何阶段,优先级最高。

console.log('start');

process.nextTick(() => {
  console.log('nextTick');
});

console.log('end');

// 输出: start → end → nextTick

递归调用可能导致阻塞

// ❌ 危险:阻塞事件循环
function block() {
  process.nextTick(block);
}
block(); // 永远无法进入下一阶段

18. setImmediate vs setTimeout

对比项setTimeout(fn, 0)setImmediate(fn)
执行阶段timers 阶段check 阶段
最小延迟约 1ms当前轮次 I/O 后
确定性较低(受系统影响)较高
适用场景需要延迟执行I/O 后立即执行

执行顺序测试

// 在主模块中执行(结果不确定)
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
// 可能: timeout → immediate
// 也可能: immediate → timeout

// 在 I/O 回调中执行(结果确定)
const fs = require('fs');
fs.readFile('./file.txt', () => {
  setTimeout(() => console.log('timeout'), 0);
  setImmediate(() => console.log('immediate'));
});
// 永远是: immediate → timeout

19. process.nextTick vs setImmediate

对比项process.nextTicksetImmediate
执行时机当前阶段结束、下一阶段开始前check 阶段
优先级最高较低
递归风险容易阻塞事件循环不会阻塞
适用场景需要立即执行,但非同步I/O 回调后执行

执行顺序

setTimeout(() => console.log('setTimeout'), 0);
setImmediate(() => console.log('setImmediate'));
process.nextTick(() => console.log('nextTick'));
Promise.resolve().then(() => console.log('Promise'));

// 输出: nextTick → Promise → setTimeout/setImmediate

20. Node.js 与浏览器事件循环的区别

对比项Node.js浏览器
事件循环模型6 个阶段(Libuv)宏任务 + 微任务
微任务处理每个阶段结束后当前宏任务结束后
task 概念无明确 task 概念宏任务(task)
特有 APIsetImmediateprocess.nextTickMessageChannelrequestAnimationFrame
I/O 模型Libuv 线程池 + epoll浏览器事件队列

浏览器事件循环

宏任务队列(MacroTask)
  ├─ setTimeout
  ├─ setInterval
  ├─ setImmediate(仅 IE)
  ├─ I/O
  └─ UI 渲染

微任务队列(MicroTask)
  ├─ Promise.then/catch/finally
  ├─ MutationObserver
  ├─ queueMicrotask
  └─ process.nextTickNode.js 独有)

四、文件系统操作

21. fs 模块

定义 fs(File System)模块是 Node.js 提供的文件系统操作核心模块,支持同步和异步两种方式。

导入方式

const fs = require('fs');
const fsPromises = require('fs/promises');

22. fs.readFile

功能: 异步读取文件内容

// 回调风格
fs.readFile('./file.txt', 'utf-8', (err, data) => {
  if (err) {
    console.error('读取失败:', err);
    return;
  }
  console.log(data);
});

// Promise 风格
const fsPromises = require('fs/promises');
async function read() {
  try {
    const data = await fsPromises.readFile('./file.txt', 'utf-8');
    console.log(data);
  } catch (err) {
    console.error(err);
  }
}

23. fs.writeFile

功能: 异步写入文件(覆盖写入)

fs.writeFile('./output.txt', 'Hello World', 'utf-8', (err) => {
  if (err) throw err;
  console.log('写入成功');
});

// 追加模式
fs.writeFile('./output.txt', 'New Content', { flag: 'a' }, (err) => {
  // ...
});

24. fs.appendFile

功能: 追加内容到文件末尾

fs.appendFile('./log.txt', 'New log entry\n', (err) => {
  if (err) throw err;
  console.log('追加成功');
});

25. fs.unlink

功能: 删除文件

fs.unlink('./temp.txt', (err) => {
  if (err) throw err;
  console.log('文件已删除');
});

26. fs.mkdir / fs.rmdir

// 创建目录
fs.mkdir('./new-dir', { recursive: true }, (err) => {
  // recursive: true 时,父目录不存在会自动创建
});

// 删除目录(Node.js 14.14+ 推荐使用 fs.rm)
fs.rm('./new-dir', { recursive: true }, (err) => {
  // 删除目录及其内容
});

27. fs.readdir

功能: 读取目录内容

fs.readdir('./my-dir', (err, files) => {
  if (err) throw err;
  console.log(files); // ['file1.txt', 'file2.js', ...]
});

// 递归读取子目录
fs.readdir('./my-dir', { recursive: true }, (err, files) => {
  console.log(files);
});

28. fs.stat

功能: 获取文件/目录信息

fs.stat('./file.txt', (err, stats) => {
  if (err) throw err;
  console.log('文件大小:', stats.size);
  console.log('是否是文件:', stats.isFile());
  console.log('是否是目录:', stats.isDirectory());
  console.log('创建时间:', stats.birthtime);
  console.log('修改时间:', stats.mtime);
});

29. fs.watch

功能: 监听文件/目录变化

const watcher = fs.watch('./my-dir', (eventType, filename) => {
  console.log(`文件 ${filename} 发生了 ${eventType} 事件`);
});

watcher.on('error', (err) => {
  console.error('监听错误:', err);
});

// 停止监听
// watcher.close();

注意: fs.watch 在不同操作系统上行为可能不一致,生产环境推荐使用 chokidar 库。


30. 同步与异步文件操作

特性同步操作异步操作
方法名readFileSync, writeFileSyncreadFile, writeFile
阻塞阻塞主线程不阻塞
性能
适用场景启动时读取配置、脚本服务器运行时
// ❌ 不推荐:阻塞主线程
const data = fs.readFileSync('./config.json', 'utf-8');

// ✅ 推荐:异步操作
fs.readFile('./config.json', 'utf-8', (err, data) => {
  // 处理数据
});

31. 文件描述符

定义 文件描述符(File Descriptor)是操作系统为打开的文件分配的非负整数标识符。

// 打开文件获取描述符
fs.open('./file.txt', 'r', (err, fd) => {
  if (err) throw err;
  console.log('文件描述符:', fd); // 例如: 3
  
  // 使用描述符读取
  const buffer = Buffer.alloc(1024);
  fs.read(fd, buffer, 0, buffer.length, 0, (err, bytesRead) => {
    console.log('读取字节数:', bytesRead);
    fs.close(fd, () => {}); // 关闭文件
  });
});

注意事项

  • 必须在使用后调用 fs.close() 关闭,否则会导致文件描述符泄漏
  • 操作系统对打开的文件描述符数量有限制

五、HTTP 服务器

32. http 模块

定义 http 模块是 Node.js 内置的 HTTP 服务器和客户端模块,用于创建 Web 服务器和发起 HTTP 请求。


33. 创建 HTTP 服务器

const http = require('http');

const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/html' });
  res.end('<h1>Hello World</h1>');
});

server.listen(3000, 'localhost', () => {
  console.log('Server: http://localhost:3000');
});

34. request 对象

request 对象(IncomingMessage)包含客户端请求信息:

const http = require('http');

http.createServer((req, res) => {
  console.log('请求方法:', req.method);           // GET, POST, ...
  console.log('请求 URL:', req.url);              // /api/users
  console.log('请求头:', req.headers);            // { host: 'localhost:3000', ... }
  console.log('HTTP 版本:', req.httpVersion);     // 1.1
  
  // 解析 URL 参数
  const url = require('url');
  const parsed = url.parse(req.url, true);
  console.log('查询参数:', parsed.query);
  
  // 接收 POST 数据
  let body = '';
  req.on('data', chunk => { body += chunk; });
  req.on('end', () => {
    console.log('请求体:', body);
  });
}).listen(3000);

35. response 对象

response 对象(ServerResponse)用于向客户端发送响应:

http.createServer((req, res) => {
  // 设置状态码和响应头
  res.writeHead(200, {
    'Content-Type': 'application/json',
    'Access-Control-Allow-Origin': '*',
  });
  
  // 发送响应
  res.write('Partial content ');
  res.end(JSON.stringify({ status: 'ok' }));
  
  // 设置状态码(单独)
  res.statusCode = 404;
  res.statusMessage = 'Not Found';
}).listen(3000);

36. 路由实现

简单路由

const http = require('http');
const url = require('url');

const routes = {
  'GET /': (req, res) => res.end('Home'),
  'GET /users': (req, res) => res.end('Users List'),
  'POST /users': (req, res) => res.end('Create User'),
};

http.createServer((req, res) => {
  const pathname = url.parse(req.url).pathname;
  const key = `${req.method} ${pathname}`;
  
  if (routes[key]) {
    routes[key](req, res);
  } else {
    res.writeHead(404);
    res.end('Not Found');
  }
}).listen(3000);

37. 静态文件服务

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

const mimeTypes = {
  '.html': 'text/html',
  '.css': 'text/css',
  '.js': 'application/javascript',
  '.png': 'image/png',
  '.json': 'application/json',
};

http.createServer((req, res) => {
  let filePath = '.' + req.url;
  if (filePath === './') filePath = './index.html';
  
  const extname = path.extname(filePath);
  const contentType = mimeTypes[extname] || 'application/octet-stream';
  
  fs.readFile(filePath, (err, data) => {
    if (err) {
      res.writeHead(404);
      res.end('File not found');
    } else {
      res.writeHead(200, { 'Content-Type': contentType });
      res.end(data);
    }
  });
}).listen(3000);

六、Express/Koa 框架

38. Express

定义 Express 是一个轻量级的 Node.js Web 框架,提供了路由、中间件、模板引擎等功能。

基本使用

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

// 中间件
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// 路由
app.get('/users', (req, res) => {
  res.json([{ id: 1, name: 'Alice' }]);
});

app.post('/users', (req, res) => {
  console.log(req.body);
  res.status(201).json({ message: 'User created' });
});

// 启动
app.listen(3000, () => {
  console.log('Express server started');
});

39. Express 路由

路由定义

const express = require('express');
const router = express.Router();

// 基本路由
router.get('/users', (req, res) => { /* ... */ });
router.post('/users', (req, res) => { /* ... */ });
router.put('/users/:id', (req, res) => { /* ... */ });
router.delete('/users/:id', (req, res) => { /* ... */ });

// 路由参数
router.get('/users/:id', (req, res) => {
  console.log(req.params.id);    // 路径参数
  console.log(req.query.page);   // 查询参数
  res.json({ id: req.params.id });
});

// 挂载路由
app.use('/api', router);

40. Express 中间件

中间件类型

// 应用级中间件
app.use((req, res, next) => {
  console.log(`${req.method} ${req.url}`);
  next();
});

// 路由级中间件
router.use((req, res, next) => {
  // 特定路由前的中间件
  next();
});

// 错误处理中间件(必须有 4 个参数)
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ error: 'Internal Server Error' });
});

// 内置中间件
app.use(express.static('public'));
app.use(express.json());

// 第三方中间件
app.use(require('cors')());
app.use(require('morgan')('dev'));

中间件执行顺序

app.use((req, res, next) => {
  console.log('1. 日志中间件');
  next();
});

app.get('/users', (req, res, next) => {
  console.log('2. 路由中间件');
  next();
}, (req, res) => {
  console.log('3. 响应中间件');
  res.json({ users: [] });
});

// 输出: 1. 日志中间件 → 2. 路由中间件 → 3. 响应中间件

41. Express 错误处理

// 方式 1:错误处理中间件
app.use((err, req, res, next) => {
  if (err.type === 'entity.parse.failed') {
    return res.status(400).json({ error: 'Invalid JSON' });
  }
  res.status(500).json({ error: 'Server Error' });
});

// 方式 2:异步路由错误处理
app.get('/users', async (req, res, next) => {
  try {
    const users = await db.find();
    res.json(users);
  } catch (err) {
    next(err); // 交给错误处理中间件
  }
});

// 方式 3:express-async-errors 包
require('express-async-errors');
app.get('/users', async (req, res) => {
  const users = await db.find(); // 自动捕获异常
  res.json(users);
});

42. Koa

定义 Koa 是 Express 原班人马打造的新一代 Node.js Web 框架,基于 ES6 Generator/async-await,提供更优雅的异步处理。

基本使用

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

// 中间件
app.use(async (ctx, next) => {
  console.log('请求开始');
  await next();
  console.log('请求结束');
});

app.use(async (ctx) => {
  ctx.body = { message: 'Hello Koa' };
});

app.listen(3000);

43. Koa 中间件

洋葱模型 Koa 的中间件采用"洋葱模型",请求从外向内穿过中间件,响应从内向外返回。

app.use(async (ctx, next) => {
  console.log('1. 开始');
  await next();
  console.log('1. 结束');
});

app.use(async (ctx, next) => {
  console.log('2. 开始');
  await next();
  console.log('2. 结束');
});

app.use(async (ctx) => {
  console.log('3. 响应');
  ctx.body = 'Hello';
});

// 输出:
// 1. 开始
// 2. 开始
// 3. 响应
// 2. 结束
// 1. 结束

44. Koa 与 Express 的区别

对比项ExpressKoa
异步处理回调函数async/await
中间件模型线性模型洋葱模型
内置功能路由、模板、静态文件仅核心功能
错误处理try-catch 或回调try-catch
context 对象req, res 分离ctx 统一封装
体积较大更小
生态更成熟逐渐完善
响应设置res.send(), res.json()ctx.body = ...

选择策略

  • 选 Express:项目需要快速开发、需要丰富的中间件生态、团队熟悉 Express
  • 选 Koa:需要更优雅的异步处理、喜欢轻量级框架、需要洋葱模型能力

45. koa-router

const Koa = require('koa');
const Router = require('koa-router');

const app = new Koa();
const router = new Router();

router.get('/users', async (ctx) => {
  ctx.body = [{ id: 1, name: 'Alice' }];
});

router.post('/users', async (ctx) => {
  ctx.body = { message: 'Created', user: ctx.request.body };
  ctx.status = 201;
});

router.get('/users/:id', async (ctx) => {
  ctx.body = { id: ctx.params.id };
});

app.use(router.routes()).use(router.allowedMethods());
app.listen(3000);

46. koa-bodyparser

const Koa = require('koa');
const bodyParser = require('koa-bodyparser');

const app = new Koa();

app.use(bodyParser());

app.use(async (ctx) => {
  console.log(ctx.request.body); // 解析后的请求体
  ctx.body = { received: ctx.request.body };
});

47. koa-static

const Koa = require('koa');
const serve = require('koa-static');

const app = new Koa();

// 静态文件服务
app.use(serve('./public'));

app.listen(3000);
// http://localhost:3000/style.css → ./public/style.css

七、数据库操作

48. MySQL 连接

const mysql = require('mysql2/promise');

async function connect() {
  const connection = await mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: 'password',
    database: 'mydb'
  });
  return connection;
}

// 使用连接池
const pool = mysql.createPool({
  host: 'localhost',
  user: 'root',
  password: 'password',
  database: 'mydb',
  waitForConnections: true,
  connectionLimit: 10,
  queueLimit: 0
});

49. MySQL CRUD

// 查询
const [rows] = await connection.query('SELECT * FROM users WHERE age > ?', [18]);

// 插入
const [result] = await connection.query(
  'INSERT INTO users (name, email) VALUES (?, ?)',
  ['Alice', 'alice@example.com']
);
console.log(result.insertId);

// 更新
await connection.query(
  'UPDATE users SET name = ? WHERE id = ?',
  ['Bob', 1]
);

// 删除
await connection.query('DELETE FROM users WHERE id = ?', [1]);

50. MySQL 事务

async function transfer(fromId, toId, amount) {
  const connection = await mysql.createConnection({ /* ... */ });
  
  try {
    await connection.beginTransaction();
    
    await connection.query(
      'UPDATE accounts SET balance = balance - ? WHERE id = ?',
      [amount, fromId]
    );
    
    await connection.query(
      'UPDATE accounts SET balance = balance + ? WHERE id = ?',
      [amount, toId]
    );
    
    await connection.commit();
    console.log('转账成功');
  } catch (err) {
    await connection.rollback();
    console.error('转账失败:', err);
  } finally {
    connection.end();
  }
}

51. MongoDB 连接与 CRUD

const { MongoClient } = require('mongodb');

const client = new MongoClient('mongodb://localhost:27017');

async function run() {
  await client.connect();
  const db = client.db('mydb');
  const collection = db.collection('users');
  
  // 创建
  await collection.insertOne({ name: 'Alice', age: 25 });
  
  // 查询
  const user = await collection.findOne({ name: 'Alice' });
  const users = await collection.find({ age: { $gt: 18 } }).toArray();
  
  // 更新
  await collection.updateOne(
    { name: 'Alice' },
    { $set: { age: 26 } }
  );
  
  // 删除
  await collection.deleteOne({ name: 'Alice' });
}

52. Mongoose

const mongoose = require('mongoose');

// Schema 定义
const userSchema = new mongoose.Schema({
  name: { type: String, required: true },
  email: { type: String, unique: true },
  age: { type: Number, min: 0 },
  createdAt: { type: Date, default: Date.now }
});

// 虚拟属性
userSchema.virtual('birthYear').get(function() {
  return new Date().getFullYear() - this.age;
});

// Model
const User = mongoose.model('User', userSchema);

// CRUD
async function run() {
  await mongoose.connect('mongodb://localhost:27017/mydb');
  
  const user = await User.create({ name: 'Alice', email: 'a@b.com', age: 25 });
  const found = await User.findOne({ name: 'Alice' });
  await User.updateOne({ _id: user._id }, { age: 26 });
  await User.deleteOne({ _id: user._id });
}

53. Redis

连接与基本操作

const redis = require('redis');
const client = redis.createClient({ url: 'redis://localhost:6379' });

async function run() {
  await client.connect();
  
  // String
  await client.set('name', 'Alice');
  const name = await client.get('name');
  
  // Hash
  await client.hSet('user:1', { name: 'Alice', age: 25 });
  const user = await client.hGetAll('user:1');
  
  // List
  await client.lPush('todos', 'task1', 'task2');
  const todos = await client.lRange('todos', 0, -1);
  
  // Set
  await client.sAdd('tags', 'nodejs', 'javascript');
  const tags = await client.sMembers('tags');
  
  // 设置过期时间
  await client.setEx('temp', 60, 'value'); // 60秒后过期
}

Redis 缓存策略

// Cache-Aside 模式
async function getUser(id) {
  // 1. 先查缓存
  const cached = await client.get(`user:${id}`);
  if (cached) {
    return JSON.parse(cached);
  }
  
  // 2. 查数据库
  const user = await db.query('SELECT * FROM users WHERE id = ?', [id]);
  
  // 3. 写入缓存
  await client.setEx(`user:${id}`, 3600, JSON.stringify(user));
  
  return user;
}

Redis 数据类型

类型说明常用命令
String字符串SET, GET, INCR
Hash哈希表HSET, HGET, HGETALL
List链表LPUSH, RPUSH, LPOP
Set无序集合SADD, SMEMBERS, SISMEMBER
ZSet有序集合ZADD, ZRANGE, ZSCORE

八、NPM 包管理

54. NPM

定义 NPM(Node Package Manager)是 Node.js 的包管理工具,用于安装、发布、管理 JavaScript 包。


55. package.json

{
  "name": "my-app",
  "version": "1.0.0",
  "description": "My Node.js App",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "dev": "nodemon index.js",
    "test": "jest",
    "build": "webpack --mode production"
  },
  "dependencies": {
    "express": "^4.18.0",
    "mongoose": "^7.0.0"
  },
  "devDependencies": {
    "jest": "^29.0.0",
    "nodemon": "^3.0.0"
  },
  "engines": {
    "node": ">=18.0.0"
  }
}

版本前缀说明

前缀含义示例
^允许小版本升级^4.18.04.19.0
~允许补丁版本升级~4.18.04.18.1
*任意版本* → 最新版
无前缀锁定版本4.18.0 → 仅 4.18.0

56. npm install

# 安装所有依赖
npm install

# 安装指定包
npm install express

# 安装为开发依赖
npm install -D jest

# 全局安装
npm install -g nodemon

# 精确版本安装
npm install express@4.17.1

安装机制

  1. 依赖解析: 读取 package.json,解析依赖树
  2. 注册表查询: 从 NPM registry 下载包
  3. 缓存: 下载的包缓存到 ~/.npm/_cacache
  4. 扁平化安装: npm 5+ 尽量将依赖安装到根 node_modules
  5. 生成 lock 文件: 锁定依赖版本

57. npm scripts

{
  "scripts": {
    "start": "node index.js",
    "dev": "NODE_ENV=development nodemon index.js",
    "build": "tsc",
    "test": "jest --coverage",
    "lint": "eslint .",
    "prestart": "npm run build",
    "posttest": "npm run lint"
  }
}

执行

npm run dev
npm run test

pre/post 钩子

npm run start
# 自动执行: prestart → start → poststart

58. npm vs yarn vs pnpm

对比项npmyarnpnpm
速度较慢最快
磁盘空间每个项目独立安装全局缓存内容寻址存储
依赖管理扁平化(幻影依赖)扁平化(strict)严格隔离
Lock 文件package-lock.jsonyarn.lockpnpm-lock.yaml
离线安装部分支持支持支持
工作区支持支持支持

59. 发布包

# 1. 登录
npm login

# 2. 版本号更新
npm version patch  # 1.0.0 → 1.0.1
npm version minor  # 1.0.0 → 1.1.0
npm version major  # 1.0.0 → 2.0.0

# 3. 发布
npm publish

# 4. 撤销发布(72小时内)
npm unpublish my-package@1.0.0

60. npx

# 执行本地安装的 CLI 工具
npx jest

# 临时执行(不安装)
npx create-react-app my-app

# 指定版本
npx typescript@5.0.0 tsc --version

九、中间件机制

61. 中间件

定义 中间件(Middleware)是位于请求和响应之间的处理函数,可以访问请求对象、响应对象和 next 函数。

// 基本中间件结构
function middleware(req, res, next) {
  // 请求处理
  console.log(`${req.method} ${req.url}`);
  
  // 传递到下一个中间件
  next();
  
  // 响应后处理(可选)
}

62. 中间件原理

Express 中间件实现原理

// 简化版 Express 中间件机制
class Application {
  constructor() {
    this.middlewares = [];
  }
  
  use(fn) {
    this.middlewares.push(fn);
  }
  
  handle(req, res) {
    let index = 0;
    
    const next = () => {
      if (index >= this.middlewares.length) return;
      const middleware = this.middlewares[index++];
      middleware(req, res, next);
    };
    
    next();
  }
}

63. 中间件执行顺序

中间件按注册顺序依次执行,先注册的先执行。

// 顺序:A → B → C → B(返回) → A(返回)
app.use((req, res, next) => {
  console.log('A 前置');
  next();
  console.log('A 后置');
});

app.use((req, res, next) => {
  console.log('B 前置');
  next();
  console.log('B 后置');
});

app.use((req, res) => {
  console.log('C 处理');
  res.send('done');
});

64. 中间件类型

类型说明示例
应用级全局生效app.use()
路由级特定路由生效router.use()
错误处理4 个参数(err, req, res, next) => {}
内置Express/Koa 内置express.json()
第三方NPM 安装cors(), morgan()

十、进程与线程

65. 进程 vs 线程

对比项进程线程
定义程序的一次执行实例进程内的执行单元
资源独立内存空间共享进程内存
开销大(创建/切换)
通信IPC(进程间通信)共享内存
隔离性
Node.jschild_process, clusterworker_threads

66. child_process

spawn - 启动子进程

const { spawn } = require('child_process');

const child = spawn('node', ['worker.js'], {
  stdio: ['pipe', 'pipe', 'pipe']
});

child.stdout.on('data', (data) => {
  console.log(`子进程输出: ${data}`);
});

child.on('close', (code) => {
  console.log(`子进程退出码: ${code}`);
});

exec - 执行命令

const { exec } = require('child_process');

exec('ls -la', (error, stdout, stderr) => {
  if (error) {
    console.error(`执行错误: ${error}`);
    return;
  }
  console.log(`stdout: ${stdout}`);
});

execFile - 执行可执行文件

const { execFile } = require('child_process');

execFile('./script.sh', ['arg1', 'arg2'], (error, stdout) => {
  console.log(stdout);
});

fork - 创建 Node.js 子进程(支持进程间通信)

const { fork } = require('child_process');

const child = fork('./worker.js');

// 发送消息
child.send({ type: 'start', data: 'hello' });

// 接收消息
child.on('message', (msg) => {
  console.log('收到子进程消息:', msg);
});

// worker.js
process.on('message', (msg) => {
  console.log('收到父进程消息:', msg);
  process.send({ type: 'response', data: 'world' });
});

67. cluster 模块

定义 cluster 模块用于创建共享服务器端口的子进程(工作进程),充分利用多核 CPU。

const cluster = require('cluster');
const http = require('http');
const os = require('os');

const numCPUs = os.cpus().length;

if (cluster.isMaster) {
  console.log(`主进程 ${process.pid} 启动`);
  
  // 创建工作进程
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
  
  cluster.on('exit', (worker, code, signal) => {
    console.log(`工作进程 ${worker.process.pid} 退出`);
    cluster.fork(); // 重启
  });
} else {
  // 工作进程
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end(`工作进程 ${process.pid} 处理请求`);
  }).listen(3000);
  
  console.log(`工作进程 ${process.pid} 启动`);
}

68. worker_threads

定义 worker_threads 模块用于在 Node.js 中创建真正的多线程,适合 CPU 密集型任务。

const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');

if (isMainThread) {
  // 主线程
  const worker = new Worker(__filename, {
    workerData: { n: 40 }
  });
  
  worker.on('message', (result) => {
    console.log('计算结果:', result);
  });
  
  worker.on('error', (err) => console.error(err));
} else {
  // 工作线程
  function fibonacci(n) {
    return n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
  }
  
  const result = fibonacci(workerData.n);
  parentPort.postMessage(result);
}

69. 进程通信

方式适用场景示例
process.send()fork 创建的子进程child.send() / process.on('message')
IPC 通道子进程间通信共享管道
共享内存worker_threadsSharedArrayBuffer
消息传递worker_threadsparentPort.postMessage()
信号进程控制process.kill(pid, 'SIGTERM')
文件/数据库任意进程通过文件系统

70. 守护进程

实现方式

  1. 使用第三方工具: PM2、forever
  2. 使用 systemd: Linux 系统服务
  3. Docker: 容器化部署

PM2 使用

# 启动应用
pm2 start app.js --name my-app

# 集群模式
pm2 start app.js -i max

# 查看状态
pm2 status

# 监控
pm2 monit

# 重启
pm2 restart my-app

# 保存进程列表
pm2 save

# 生成启动脚本
pm2 startup

十一、性能优化与调试

71. Node.js 性能优化

优化策略

策略说明实施方法
缓存减少重复计算Redis、内存缓存
连接池复用数据库连接mysql2 pool
异步处理非阻塞 I/OPromise/async-await
集群多核利用cluster 模块
压缩减少传输大小gzip、brotli
流式处理减少内存占用Stream API
限流防止过载rate limiting

代码示例

// 使用 gzip 压缩
const compression = require('compression');
app.use(compression());

// 使用连接池
const pool = mysql.createPool({
  connectionLimit: 10,
  host: 'localhost',
  user: 'root',
  database: 'mydb'
});

72. 内存管理

V8 内存限制

  • 64 位系统:约 1.4GB
  • 32 位系统:约 0.7GB
  • 可通过 --max-old-space-size 调整
node --max-old-space-size=4096 app.js  # 4GB

内存分区

V8 内存
├── New Space(新生代): 新分配的对象,快速 GC
├── Old Space(老生代): 长期存活的对象
├── Large Object Space: 大对象
├── Code Space: 编译后的代码
└── Map Space: 对象结构信息

73. 内存泄漏排查

常见原因

  1. 全局变量: 未声明的变量变成全局
  2. 闭包: 引用未释放
  3. 事件监听器: 未移除的监听器
  4. 缓存无限增长: 无限制的缓存

排查方法

// 方法 1:使用 Chrome DevTools
// 1. 启动: node --inspect app.js
// 2. 打开 chrome://inspect
// 3. Memory 面板拍照快照

// 方法 2:heapdump
const heapdump = require('heapdump');
setInterval(() => {
  heapdump.writeSnapshot(`./heap-${Date.now()}.heapsnapshot`);
}, 60000);

// 方法 3:node --heapsnapshot-signal
// node --heapsnapshot-signal=SIGUSR2 app.js
// kill -USR2 <pid>

74. CPU 分析

# 使用 clinic.js
npx clinic doctor -- node app.js

# 使用 0x
npx 0x app.js

# 使用 --prof
node --prof app.js
node --prof-process isolate-*.log > profile.txt

75. 调试方式

node inspect

node inspect app.js

Chrome DevTools

node --inspect app.js
# 打开 chrome://inspect 进行调试

debugger 关键字

function calculate(a, b) {
  debugger; // 断点
  return a + b;
}

console 调试

console.log('普通日志');
console.error('错误日志');
console.warn('警告日志');
console.time('timer');
// ... 代码 ...
console.timeEnd('timer');
console.table([{ name: 'Alice', age: 25 }]);

76. 错误处理

错误类型

// 1. 同步错误
try {
  JSON.parse('invalid');
} catch (err) {
  console.error(err);
}

// 2. 异步错误(回调)
fs.readFile('./nonexistent', (err, data) => {
  if (err) {
    console.error(err);
    return;
  }
});

// 3. Promise 错误
someAsync().catch(err => console.error(err));

全局错误捕获

// 未捕获的同步异常
process.on('uncaughtException', (err) => {
  console.error('Uncaught Exception:', err);
  // 记录日志、清理资源,然后退出
  process.exit(1);
});

// 未处理的 Promise 拒绝
process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled Rejection at:', promise, 'reason:', reason);
});

77. 日志管理 - Winston

const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' }),
    new winston.transports.Console({
      format: winston.format.simple()
    })
  ]
});

logger.info('Server started');
logger.error('Database connection failed');

十二、实战应用

78. JWT 鉴权

定义 JWT(JSON Web Token)是一种用于身份验证的开放标准,由三部分组成:Header、Payload、Signature。

实现

const jwt = require('jsonwebtoken');

// 生成 Token
function generateToken(user) {
  return jwt.sign(
    { id: user.id, role: user.role },
    process.env.JWT_SECRET,
    { expiresIn: '7d' }
  );
}

// 验证中间件
function authMiddleware(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];
  
  if (!token) {
    return res.status(401).json({ error: '未提供 Token' });
  }
  
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (err) {
    return res.status(403).json({ error: 'Token 无效' });
  }
}

// 路由使用
app.post('/login', (req, res) => {
  const user = { id: 1, role: 'admin' };
  const token = generateToken(user);
  res.json({ token });
});

app.get('/profile', authMiddleware, (req, res) => {
  res.json({ user: req.user });
});

79. 文件上传

使用 multer

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

// 配置存储
const storage = multer.diskStorage({
  destination: './uploads/',
  filename: (req, file, cb) => {
    cb(null, `${Date.now()}-${file.originalname}`);
  }
});

// 文件过滤
const fileFilter = (req, file, cb) => {
  const allowed = ['.jpg', '.jpeg', '.png', '.gif'];
  const ext = path.extname(file.originalname).toLowerCase();
  if (allowed.includes(ext)) {
    cb(null, true);
  } else {
    cb(new Error('不支持的文件类型'), false);
  }
};

const upload = multer({
  storage,
  fileFilter,
  limits: { fileSize: 5 * 1024 * 1024 } // 5MB
});

// 路由
app.post('/upload', upload.single('file'), (req, res) => {
  res.json({
    message: '上传成功',
    file: req.file
  });
});

// 多文件上传
app.post('/uploads', upload.array('files', 5), (req, res) => {
  res.json({ files: req.files });
});

80. 分页功能设计

// 分页查询
app.get('/users', async (req, res) => {
  const page = parseInt(req.query.page) || 1;
  const limit = parseInt(req.query.limit) || 10;
  const offset = (page - 1) * limit;
  
  const { count, rows } = await User.findAndCountAll({
    limit,
    offset,
    order: [['createdAt', 'DESC']]
  });
  
  res.json({
    data: rows,
    pagination: {
      total: count,
      page,
      limit,
      totalPages: Math.ceil(count / limit),
      hasNextPage: page < Math.ceil(count / limit),
      hasPrevPage: page > 1
    }
  });
});

分页优化

-- 大数据量优化:游标分页
SELECT * FROM users 
WHERE id < ? 
ORDER BY id DESC 
LIMIT 10;

附录:高频考点总结

事件循环执行顺序

console.log('1');

setTimeout(() => console.log('2'), 0);
setImmediate(() => console.log('3'));
process.nextTick(() => console.log('4'));
Promise.resolve().then(() => console.log('5'));

console.log('6');

// 输出:1 → 6 → 4 → 5 → 2/3(2和3顺序不确定,但在主模块中)