Node.js 知识点梳理与实战代码

20 阅读12分钟

Node.js 学习文档


1. Node 基础

1.1 基础概念

定义与特点

Node.js 是一个基于 Chrome V8 引擎 的 JavaScript 运行时环境,让 JavaScript 可以运行在服务端。

核心特点:

  • 异步非阻塞 I/O
  • 事件驱动架构
  • 单线程模型
  • 跨平台(Windows / macOS / Linux)
  • 拥有丰富的 npm 生态

事件驱动(Event-Driven)

Node.js 使用 事件循环(Event Loop) 来处理并发,所有异步操作完成后通过触发"事件"来执行回调函数。

// 示例:events/event-driven.js
const EventEmitter = require('events');

const emitter = new EventEmitter();

// 注册事件监听
emitter.on('greet', (name) => {
  console.log(`Hello, ${name}!`);
});

// 触发事件
emitter.emit('greet', 'Node.js');
// 输出:Hello, Node.js!

非阻塞 I/O(Non-Blocking I/O)

传统同步模型中,I/O 操作(读文件、网络请求)会阻塞线程。Node.js 将 I/O 操作交给底层(libuv),完成后回调通知主线程,主线程无需等待。

// 示例:basic/non-blocking.js
const fs = require('fs');

console.log('1. 开始读取文件');

// 非阻塞读取
fs.readFile('./package.json', 'utf8', (err, data) => {
  if (err) {
    console.error('读取失败:', err.message);
    return;
  }
  console.log('3. 文件内容长度:', data.length);
});

console.log('2. 读取请求已发出,继续执行其他代码');

// 输出顺序:1 → 2 → 3(不会阻塞)

单线程模型

核心理解: Node.js 的"单线程"指的是 JavaScript 执行线程只有一个。但 Node.js 底层的 libuv 库维护了一个线程池(默认 4 个线程),专门处理文件 I/O、DNS 查询、加密等耗时操作,主线程不会被阻塞。

         ┌──────────────────────────────────────────┐
         │           Node.js 进程                    │
         │                                          │
         │  ┌───────────────────────────────────┐   │
         │  │     JS 主线程(单线程)             │   │
         │  │  - 执行 JavaScript 代码             │   │
         │  │  - 运行事件循环                     │   │
         │  │  - 处理回调函数                     │   │
         │  └────────────┬──────────────────────┘   │
         │               │ 异步任务(I/O、加密等)    │
         │  ┌────────────▼──────────────────────┐   │
         │  │       libuv 线程池(默认4线程)     │   │
         │  │  线程1 │ 线程2 │ 线程3 │ 线程4     │   │
         │  │  文件IO  DNS   加密    压缩         │   │
         │  └────────────┬──────────────────────┘   │
         │               │ 任务完成 → 放入事件队列    │
         │  ┌────────────▼──────────────────────┐   │
         │  │           事件队列                 │   │
         │  │  [回调1] [回调2] [回调3] ...       │   │
         │  └────────────┬──────────────────────┘   │
         │               │ 事件循环取出并执行          │
         │               └──→ JS 主线程              │
         └──────────────────────────────────────────┘

单线程的好处:

  • 无需处理多线程的锁、竞态条件、死锁等问题
  • 内存共享安全,无需同步原语
  • 上下文切换开销极小

单线程的限制:

  • CPU 密集型任务(大量计算、图像处理)会阻塞主线程,导致所有请求挂起
  • 单个未捕获异常会导致整个进程崩溃

验证单线程阻塞:

// 示例:basic/single-thread.js
const http = require('http');

const server = http.createServer((req, res) => {
  if (req.url === '/block') {
    // 模拟 CPU 密集型阻塞(3秒计算)
    const start = Date.now();
    while (Date.now() - start < 3000) {} // 死循环阻塞主线程
    res.end('阻塞结束');
  } else {
    res.end('正常响应');
  }
});

server.listen(3000, () => {
  console.log('服务启动: http://localhost:3000');
  console.log('访问 /block 会阻塞所有其他请求 3 秒');
});
// 测试:同时访问 /block 和 /,会发现 / 也被阻塞

解决 CPU 密集型任务的方案:

// 方案:worker_threads(Node.js 10.5+)
// 示例:basic/worker-demo.js
const { Worker, isMainThread, parentPort } = require('worker_threads');

// 执行流程:
// 1. node worker-demo.js → 主线程启动,isMainThread = true → 进入 if 分支
// 2. new Worker(__filename) → 用同一个文件再开一个新线程
// 3. 新线程中重新执行本文件,此时 isMainThread = false → 进入 else 分支
// 4. Worker 计算完成后通过 parentPort.postMessage 发消息给主线程
// 5. 主线程 worker.on('message') 收到消息并打印结果

if (isMainThread) {
  // ← 主线程执行此分支(node worker-demo.js 时)
  const worker = new Worker(__filename); // 让同一文件在新线程中运行
  worker.on('message', (result) => {
    console.log('计算结果:', result); // 主线程不阻塞
  });
  console.log('主线程继续运行,不被 Worker 阻塞');
} else {
  // ← Worker 线程执行此分支(被 new Worker(__filename) 启动时)
  let sum = 0;
  for (let i = 0; i < 1e8; i++) sum += i;
  parentPort.postMessage(sum); // 将结果发送给主线程
}

线程池大小配置:

# 通过环境变量调整 libuv 线程池大小(最大 1024)
UV_THREADPOOL_SIZE=8 node app.js

哪些操作走线程池,哪些走系统内核:

操作类型处理方式
文件读写(fs)libuv 线程池
DNS 解析libuv 线程池
crypto 加密libuv 线程池
zlib 压缩libuv 线程池
TCP/UDP 网络系统内核异步(epoll/kqueue/IOCP)
HTTP 请求系统内核异步

事件循环(Event Loop)

事件循环是 Node.js 实现非阻塞 I/O 的核心机制,按以下阶段循环执行:

   ┌───────────────────────────┐
┌─>│        timers             │  setTimeout / setInterval 回调
│  └─────────────┬─────────────┘
│  ┌─────────────▼─────────────┐
│  │     pending callbacks     │  上一轮延迟的 I/O 回调
│  └─────────────┬─────────────┘
│  ┌─────────────▼─────────────┐
│  │       idle, prepare       │  内部使用
│  └─────────────┬─────────────┘
│  ┌─────────────▼─────────────┐
│  │           poll            │  获取新 I/O 事件
│  └─────────────┬─────────────┘
│  ┌─────────────▼─────────────┐
│  │           check           │  setImmediate 回调
│  └─────────────┬─────────────┘
│  ┌─────────────▼─────────────┐
└──│      close callbacks      │  关闭事件回调
   └───────────────────────────┘
// 示例:basic/event-loop.js
console.log('1. 同步代码开始');

setTimeout(() => console.log('4. setTimeout'), 0);

Promise.resolve().then(() => console.log('3. Promise microtask'));

setImmediate(() => console.log('5. setImmediate'));

console.log('2. 同步代码结束');

// 输出顺序:1 → 2 → 3 → 5 → 4
// 微任务(Promise)优先于宏任务(setTimeout、setImmediate)
// setImmediate 与 setTimeout(0) 的执行先后取决于运行上下文,通常 setImmediate 更快

1.2 安装和配置

Node.js 安装

方式一:官网下载

  • 访问 nodejs.org
  • 推荐下载 LTS 版本(长期支持)

方式二:nvm 管理多版本(推荐)

# Windows 使用 nvm-windows
# 安装后使用:
nvm install 20       # 安装 Node.js 20
nvm use 20           # 切换版本
nvm list             # 查看已安装版本

# 验证安装
node -v              # 查看 Node 版本
npm -v               # 查看 npm 版本

npm 使用

npm(Node Package Manager)是 Node.js 的默认包管理器。

# 查看 npm 版本
npm -v

# 查看全局安装路径
npm root -g

# 更新 npm 自身
npm install -g npm@latest

# 配置镜像源(国内加速)
npm config set registry https://registry.npmmirror.com

# 查看当前镜像
npm config get registry

package.json

package.json 是项目的配置清单,记录项目信息、依赖、脚本等。

// 示例:package.json
{
  "name": "node-demo",
  "version": "1.0.0",
  "description": "Node.js 学习示例",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "dev": "nodemon index.js",
    "test": "node test/index.test.js"
  },
  "keywords": ["nodejs", "demo"],
  "author": "Your Name",
  "license": "MIT",
  "dependencies": {
    "express": "^4.18.2"
  },
  "devDependencies": {
    "nodemon": "^3.0.0"
  }
}

字段说明:

字段说明
name包名(小写,无空格)
version版本号(遵循 semver:主.次.补丁)
main入口文件
scripts自定义脚本命令
dependencies生产依赖
devDependencies开发依赖(不打包进生产)

1.3 模块系统

CommonJS 模块(CJS)

Node.js 默认模块系统,使用 require 引入、module.exports 导出。

// 示例:modules/cjs/math.js(导出)
const PI = 3.14159;

function add(a, b) {
  return a + b;
}

function multiply(a, b) {
  return a * b;
}

// 导出多个
module.exports = { PI, add, multiply };
// 示例:modules/cjs/main.js(引入)
const { PI, add, multiply } = require('./math');

console.log('PI =', PI);
console.log('3 + 4 =', add(3, 4));
console.log('3 × 4 =', multiply(3, 4));

特性:

  • 同步加载(适合服务端)
  • require 有缓存机制,同一模块只加载一次
  • __dirname:当前文件所在目录
  • __filename:当前文件完整路径
// 示例:modules/cjs/path-demo.js
console.log('当前目录:', __dirname);
console.log('当前文件:', __filename);

ES Module(ESM)

ES6 标准模块系统,使用 import / export,Node.js 12+ 支持。

启用方式:

  • 文件扩展名改为 .mjs,或
  • package.json 中设置 "type": "module"
// 示例:modules/esm/math.mjs(导出)
export const PI = 3.14159;

export function add(a, b) {
  return a + b;
}

// 默认导出
export default function subtract(a, b) {
  return a - b;
}
// 示例:modules/esm/main.mjs(引入)
import subtract, { PI, add } from './math.mjs';

console.log('PI =', PI);
console.log('3 + 4 =', add(3, 4));
console.log('10 - 3 =', subtract(10, 3));

// 动态导入
const { multiply } = await import('./math.mjs').catch(() => ({ multiply: null }));

CJS vs ESM 对比:

特性CommonJSES Module
语法require / module.exportsimport / export
加载时机运行时(同步)编译时(静态分析)
动态导入原生支持import() 动态语法
this 指向module.exportsundefined
适用场景Node.js 传统项目现代项目、前端打包

内置模块

Node.js 自带的模块,无需安装,直接 require

// 示例:modules/builtin/builtin-demo.js
const path = require('path');
const os = require('os');
const fs = require('fs');
const { URL } = require('url');

// path:路径处理
console.log('--- path ---');
console.log(path.join('/user', 'data', 'file.txt'));     // /user/data/file.txt
console.log(path.extname('index.html'));                  // .html
console.log(path.basename('/user/data/file.txt'));        // file.txt
console.log(path.dirname('/user/data/file.txt'));         // /user/data
console.log(path.resolve('src', 'index.js'));             // 绝对路径

// os:操作系统信息
console.log('\n--- os ---');
console.log('平台:', os.platform());
console.log('CPU核心数:', os.cpus().length);
console.log('总内存(GB):', (os.totalmem() / 1024 ** 3).toFixed(2));
console.log('空闲内存(GB):', (os.freemem() / 1024 ** 3).toFixed(2));
console.log('主机名:', os.hostname());

// url:URL 解析
console.log('\n--- url ---');
const myUrl = new URL('https://example.com:8080/path?name=node&v=20#section');
console.log('协议:', myUrl.protocol);
console.log('主机:', myUrl.host);
console.log('路径:', myUrl.pathname);
console.log('参数name:', myUrl.searchParams.get('name'));

常用内置模块:

模块用途
fs文件系统操作
path路径处理
http / https创建 HTTP 服务
os操作系统信息
events事件发射器
stream流处理
urlURL 解析
crypto加密功能
util实用工具
child_process子进程

第三方模块

通过 npm 安装,存放于 node_modules 目录。

# 安装示例
npm install lodash
npm install axios
// 示例:modules/third-party/third-party-demo.js
// 需先执行:npm install lodash
const _ = require('lodash');

const arr = [1, 2, 3, 4, 5];
console.log('sum:', _.sum(arr));           // 15
console.log('max:', _.max(arr));           // 5
console.log('chunk:', _.chunk(arr, 2));    // [[1,2],[3,4],[5]]

const obj = { a: 1, b: { c: 2 } };
const clone = _.cloneDeep(obj);
clone.b.c = 99;
console.log('原始:', obj.b.c);            // 2(深拷贝不影响原对象)
console.log('克隆:', clone.b.c);          // 99

自定义模块

将业务逻辑封装为模块,实现代码复用。

// 示例:modules/custom/logger.js(自定义日志模块)
const LOG_LEVELS = { INFO: 'INFO', WARN: 'WARN', ERROR: 'ERROR' };

function formatMessage(level, message) {
  const time = new Date().toISOString();
  return `[${time}] [${level}] ${message}`;
}

function info(message) {
  console.log(formatMessage(LOG_LEVELS.INFO, message));
}

function warn(message) {
  console.warn(formatMessage(LOG_LEVELS.WARN, message));
}

function error(message) {
  console.error(formatMessage(LOG_LEVELS.ERROR, message));
}

module.exports = { info, warn, error };
// 示例:modules/custom/main.js(使用自定义模块)
const logger = require('./logger');

logger.info('应用已启动');
logger.warn('内存使用率较高');
logger.error('数据库连接失败');

1.4 npm 包管理

npm init

初始化项目,生成 package.json

# 交互式初始化(逐步填写)
npm init

# 快速初始化(全部使用默认值)
npm init -y

# 使用自定义默认值
npm config set init-author-name "Your Name"
npm config set init-license "MIT"
npm init -y

npm install

安装依赖包。

# 安装所有依赖(根据 package.json)
npm install
npm i                            # 简写

# 安装指定包(生产依赖)
npm install express
npm install express@4.18.2       # 指定版本
npm install express@latest       # 最新版

# 安装为开发依赖(不打包生产)
npm install nodemon --save-dev
npm install nodemon -D           # 简写

# 全局安装(命令行工具)
npm install -g nodemon
npm install -g typescript

# 卸载包
npm uninstall express
npm un express                   # 简写

# 更新包
npm update express               # 更新到兼容最新版
npm update                       # 更新所有

# 查看已安装包
npm list
npm list --depth=0               # 只看顶层依赖
npm list -g --depth=0            # 全局已安装

版本符号说明(semver):

"express": "^4.18.2"   → 兼容 4.x.x,不升级主版本
"express": "~4.18.2"   → 兼容 4.18.x,不升级次版本
"express": "4.18.2"    → 精确版本
"express": "*"         → 任意版本(不推荐)

npm scripts

package.jsonscripts 字段定义可执行脚本。

{
  "scripts": {
    "start": "node src/index.js",
    "dev": "nodemon src/index.js",
    "build": "tsc",
    "test": "node test/index.test.js",
    "lint": "eslint src/**/*.js",
    "clean": "rm -rf dist",
    "prebuild": "npm run clean",
    "postbuild": "echo Build complete!"
  }
}
# 运行脚本
npm run start
npm start           # start / stop / test 可省略 run
npm test
npm run dev

# 传递参数(-- 后的参数会传入脚本)
npm run dev -- --port 3000

# 查看所有脚本
npm run

生命周期钩子:

  • pre<script>:在指定脚本前自动执行(如 prebuild
  • post<script>:在指定脚本后自动执行(如 postbuild

yarn / pnpm

yarn(Facebook 出品,更快更稳定):

# 安装 yarn
npm install -g yarn

# 常用命令对比
yarn init          # = npm init
yarn               # = npm install
yarn add express   # = npm install express
yarn add -D nodemon # = npm install -D nodemon
yarn remove express # = npm uninstall express
yarn run dev       # = npm run dev
yarn global add nodemon  # = npm install -g nodemon

# yarn.lock 锁定版本(提交到 git)

pnpm(磁盘高效,monorepo 友好):

# 安装 pnpm
npm install -g pnpm

# 常用命令对比
pnpm init          # = npm init
pnpm install       # = npm install
pnpm add express   # = npm install express
pnpm add -D nodemon # = npm install -D nodemon
pnpm remove express # = npm uninstall express
pnpm run dev       # = npm run dev

# pnpm-lock.yaml 锁定版本

三者对比:

特性npmyarnpnpm
安装速度一般最快
磁盘占用低(硬链接共享)
lockfilepackage-lock.jsonyarn.lockpnpm-lock.yaml
monorepo支持(workspaces)支持(workspaces)原生最优
安全性一般

2. Node.js 核心模块

2.1 文件系统 fs

读取文件
// core/fs/read-file.js
const fs = require('fs');
const path = require('path');

const filePath = path.join(__dirname, 'demo.txt');

// 异步读取(推荐)
fs.readFile(filePath, 'utf8', (err, data) => {
  if (err) return console.error('读取失败:', err.message);
  console.log('异步读取:', data);
});

// 同步读取(会阻塞,慎用)
try {
  const data = fs.readFileSync(filePath, 'utf8');
  console.log('同步读取:', data);
} catch (err) {
  console.error('读取失败:', err.message);
}

// Promise 方式(推荐,Node.js 10+)
const fsPromises = require('fs').promises;

async function readDemo() {
  const data = await fsPromises.readFile(filePath, 'utf8');
  console.log('Promise读取:', data);
}
readDemo().catch(console.error);
写入文件
// core/fs/write-file.js
const fs = require('fs');
const path = require('path');

const filePath = path.join(__dirname, 'output.txt');

// 写入文件(覆盖)
fs.writeFile(filePath, 'Hello Node.js\n', 'utf8', (err) => {
  if (err) return console.error('写入失败:', err.message);
  console.log('写入成功');
});

// 追加内容
fs.appendFile(filePath, '追加一行\n', 'utf8', (err) => {
  if (err) return console.error('追加失败:', err.message);
  console.log('追加成功');
});

// 同步写入
fs.writeFileSync(filePath, '同步写入内容\n', 'utf8');

// Promise 方式
const { writeFile, appendFile } = require('fs').promises;

async function writeDemo() {
  await writeFile(filePath, '覆盖内容\n', 'utf8');
  await appendFile(filePath, '追加内容\n', 'utf8');
  console.log('Promise写入完成');
}
writeDemo().catch(console.error);
文件信息
// core/fs/file-stat.js
const fs = require('fs');
const path = require('path');

const filePath = path.join(__dirname, 'demo.txt');

fs.stat(filePath, (err, stats) => {
  if (err) return console.error(err.message);

  console.log('是否文件:', stats.isFile());
  console.log('是否目录:', stats.isDirectory());
  console.log('文件大小(字节):', stats.size);
  console.log('创建时间:', stats.birthtime.toLocaleString());
  console.log('修改时间:', stats.mtime.toLocaleString());
});

// 检查文件/目录是否存在
fs.access(filePath, fs.constants.F_OK, (err) => {
  console.log('文件存在:', !err);
});
目录操作
// core/fs/dir-ops.js
const fs = require('fs');
const path = require('path');

const dirPath = path.join(__dirname, 'testdir');

// 创建目录
fs.mkdir(dirPath, { recursive: true }, (err) => {
  if (err) return console.error('创建目录失败:', err.message);
  console.log('目录创建成功');
});

// 读取目录内容
fs.readdir(__dirname, (err, files) => {
  if (err) return console.error(err.message);
  console.log('目录内容:', files);
});

// 读取目录内容(带文件类型)
fs.readdir(__dirname, { withFileTypes: true }, (err, entries) => {
  if (err) return console.error(err.message);
  entries.forEach((entry) => {
    const type = entry.isDirectory() ? '[目录]' : '[文件]';
    console.log(type, entry.name);
  });
});

// 删除目录(Node.js 14.14+)
fs.rm(dirPath, { recursive: true, force: true }, (err) => {
  if (err) return console.error(err.message);
  console.log('目录删除成功');
});
文件流

流(Stream)用于处理大文件,避免一次性读入内存。

// core/fs/file-stream.js
const fs = require('fs');
const path = require('path');

const src = path.join(__dirname, 'large.txt');
const dest = path.join(__dirname, 'copy.txt');

// 可读流
const readable = fs.createReadStream(src, { encoding: 'utf8', highWaterMark: 64 * 1024 });

readable.on('data', (chunk) => {
  process.stdout.write(`收到 ${chunk.length} 字节\n`);
});
readable.on('end', () => console.log('读取完毕'));
readable.on('error', (err) => console.error('读取错误:', err.message));

// 可写流
const writable = fs.createWriteStream(dest);
writable.write('第一行\n');
writable.write('第二行\n');
writable.end('写入结束\n');
writable.on('finish', () => console.log('写入完毕'));

// 管道:直接从可读流复制到可写流(推荐用于文件复制)
fs.createReadStream(src)
  .pipe(fs.createWriteStream(dest))
  .on('finish', () => console.log('文件复制完成'));

2.2 路径处理 path

// core/path/path-demo.js
const path = require('path');

// path.join:拼接路径(自动处理分隔符)
console.log(path.join('user', 'data', 'file.txt'));   // user/data/file.txt
console.log(path.join('/user', '../data', 'file.txt')); // /data/file.txt
// 注意:join 不会将相对路径转为绝对路径

// path.resolve:解析为绝对路径(从右向左,遇到 / 停止)
console.log(path.resolve('src', 'index.js'));          // /当前工作目录/src/index.js
console.log(path.resolve('/base', 'src', 'index.js')); // /base/src/index.js
console.log(path.resolve('/base', '/abs', 'file.js')); // /abs/file.js(遇到/开头停止)

// join vs resolve 区别
// join:纯字符串拼接,不关心当前目录
// resolve:从 process.cwd() 出发,等同于 cd 命令逐步进入

// path.dirname:获取目录部分
console.log(path.dirname('/user/data/file.txt'));      // /user/data

// path.basename:获取文件名
console.log(path.basename('/user/data/file.txt'));     // file.txt
console.log(path.basename('/user/data/file.txt', '.txt')); // file(去掉扩展名)

// path.extname:获取扩展名
console.log(path.extname('index.html'));               // .html
console.log(path.extname('archive.tar.gz'));           // .gz

// path.parse:解析路径为对象
const parsed = path.parse('/user/data/file.txt');
console.log(parsed);
// { root: '/', dir: '/user/data', base: 'file.txt', ext: '.txt', name: 'file' }

// path.format:对象转路径字符串(parse 的反操作)
console.log(path.format({ dir: '/user/data', name: 'file', ext: '.txt' }));
// /user/data/file.txt

// path.isAbsolute:判断是否绝对路径
console.log(path.isAbsolute('/user/data'));  // true
console.log(path.isAbsolute('src/index'));   // false

// path.relative:计算相对路径
console.log(path.relative('/data/from', '/data/to/file.txt')); // ../to/file.txt

// 跨平台路径分隔符
console.log(path.sep);    // POSIX: /   Windows: \
console.log(path.delimiter); // POSIX: :   Windows: ;

2.3 http 模块

创建 HTTP 服务器
// core/http/basic-server.js
const http = require('http');

const server = http.createServer((req, res) => {
  // req: IncomingMessage(请求对象)
  // res: ServerResponse(响应对象)
  res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
  res.end('Hello Node.js HTTP Server!');
});

server.listen(3000, () => {
  console.log('服务器运行在 http://localhost:3000');
});
请求与响应
// core/http/req-res.js
const http = require('http');

const server = http.createServer((req, res) => {
  // 请求信息
  console.log('方法:', req.method);      // GET POST PUT ...
  console.log('路径:', req.url);         // /api/users?page=1
  console.log('请求头:', req.headers);

  // 获取 POST 请求体
  if (req.method === 'POST') {
    let body = '';
    req.on('data', (chunk) => { body += chunk; });
    req.on('end', () => {
      console.log('请求体:', body);
      const data = JSON.parse(body);

      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ received: data, ok: true }));
    });
    return;
  }

  // 响应状态码和头
  res.statusCode = 200;
  res.setHeader('Content-Type', 'application/json');
  res.setHeader('X-Powered-By', 'Node.js');
  res.end(JSON.stringify({ message: 'ok' }));
});

server.listen(3000, () => console.log('http://localhost:3000'));
路由处理
// core/http/router.js
const http = require('http');
const { URL } = require('url');

const BASE = 'http://localhost:3000';

function router(req, res) {
  const { pathname, searchParams } = new URL(req.url, BASE);
  const method = req.method;

  res.setHeader('Content-Type', 'application/json');

  if (method === 'GET' && pathname === '/') {
    res.end(JSON.stringify({ page: 'home' }));

  } else if (method === 'GET' && pathname === '/users') {
    const page = searchParams.get('page') || 1;
    res.end(JSON.stringify({ users: [], page: Number(page) }));

  } else if (method === 'GET' && pathname.startsWith('/users/')) {
    const id = pathname.split('/')[2];
    res.end(JSON.stringify({ user: { id } }));

  } else {
    res.writeHead(404);
    res.end(JSON.stringify({ error: 'Not Found' }));
  }
}

http.createServer(router).listen(3000, () => {
  console.log('路由服务器运行在 http://localhost:3000');
  console.log('  GET /          首页');
  console.log('  GET /users     用户列表');
  console.log('  GET /users/1   用户详情');
});
静态文件服务
// core/http/static-server.js
const http = require('http');
const fs = require('fs');
const path = require('path');

const STATIC_DIR = path.join(__dirname, 'public');

const MIME_TYPES = {
  '.html': 'text/html; charset=utf-8',
  '.css':  'text/css',
  '.js':   'application/javascript',
  '.json': 'application/json',
  '.png':  'image/png',
  '.jpg':  'image/jpeg',
  '.svg':  'image/svg+xml',
  '.ico':  'image/x-icon',
};

http.createServer((req, res) => {
  // 防止路径穿越攻击
  const safePath = path.normalize(req.url).replace(/^(\.\.[/\\])+/, '');
  const filePath = path.join(STATIC_DIR, safePath === '/' ? 'index.html' : safePath);
  const ext = path.extname(filePath);
  const contentType = MIME_TYPES[ext] || 'application/octet-stream';

  fs.readFile(filePath, (err, data) => {
    if (err) {
      res.writeHead(404, { 'Content-Type': 'text/plain' });
      res.end('404 Not Found');
      return;
    }
    res.writeHead(200, { 'Content-Type': contentType });
    res.end(data);
  });
}).listen(3000, () => {
  console.log('静态文件服务 http://localhost:3000');
  console.log('根目录:', STATIC_DIR);
});

2.4 事件 events

// core/events/events-demo.js
const EventEmitter = require('events');

class TaskQueue extends EventEmitter {
  constructor() {
    super();
    this.tasks = [];
  }

  add(task) {
    this.tasks.push(task);
    this.emit('task:added', task);
  }

  run() {
    if (this.tasks.length === 0) {
      this.emit('empty');
      return;
    }
    const task = this.tasks.shift();
    this.emit('task:start', task);
    // 模拟异步执行
    setTimeout(() => {
      this.emit('task:done', task);
      this.run(); // 继续处理下一个
    }, 100);
  }
}

const queue = new TaskQueue();

// on:持续监听
queue.on('task:added', (task) => console.log('新增任务:', task));
queue.on('task:start', (task) => console.log('开始执行:', task));
queue.on('task:done', (task) => console.log('任务完成:', task));

// once:只触发一次
queue.once('empty', () => console.log('队列清空'));

queue.add('task-A');
queue.add('task-B');
queue.add('task-C');
queue.run();

// 移除监听器
function onDone(task) { console.log('外部监听 done:', task); }
queue.on('task:done', onDone);
queue.off('task:done', onDone); // removeListener 的别名

// 查看监听器数量
console.log('task:done 监听器数:', queue.listenerCount('task:done'));

// 最大监听器数量(超出会警告,默认10)
queue.setMaxListeners(20);

// 错误事件(必须监听,否则会抛出异常)
queue.on('error', (err) => console.error('错误:', err.message));

2.5 流 Stream

可读流
// core/stream/readable.js
const { Readable } = require('stream');

// 自定义可读流
class NumberStream extends Readable {
  constructor(max) {
    super({ objectMode: false });
    this.current = 1;
    this.max = max;
  }

  _read() {
    if (this.current <= this.max) {
      this.push(`number: ${this.current}\n`);
      this.current++;
    } else {
      this.push(null); // null 表示流结束
    }
  }
}

const stream = new NumberStream(5);

// 方式1:data 事件
stream.on('data', (chunk) => process.stdout.write(chunk));
stream.on('end', () => console.log('流结束'));

// 方式2:for await(推荐,Node.js 10+)
async function readStream() {
  const s = new NumberStream(5);
  for await (const chunk of s) {
    process.stdout.write(chunk);
  }
  console.log('流结束');
}
readStream();
可写流
// core/stream/writable.js
const { Writable } = require('stream');

// 自定义可写流(写入内存)
class MemoryWritable extends Writable {
  constructor() {
    super();
    this.buffer = [];
  }

  _write(chunk, encoding, callback) {
    this.buffer.push(chunk.toString());
    callback(); // 必须调用,告知可以继续写入
  }

  getContent() {
    return this.buffer.join('');
  }
}

const writer = new MemoryWritable();

writer.write('第一段内容\n');
writer.write('第二段内容\n');
writer.end('结束\n');

writer.on('finish', () => {
  console.log('写入完成,内容:');
  console.log(writer.getContent());
});
管道 pipe
// core/stream/pipe-demo.js
const fs = require('fs');
const zlib = require('zlib');
const path = require('path');

const src  = path.join(__dirname, 'input.txt');
const dest = path.join(__dirname, 'input.txt.gz');

// pipe:将可读流连接到可写流
// 读取文件 → gzip 压缩 → 写入文件
fs.createReadStream(src)
  .pipe(zlib.createGzip())
  .pipe(fs.createWriteStream(dest))
  .on('finish', () => console.log('压缩完成:', dest));

// pipeline(推荐,自动处理错误和清理)
const { pipeline } = require('stream');

pipeline(
  fs.createReadStream(src),
  zlib.createGzip(),
  fs.createWriteStream(dest),
  (err) => {
    if (err) console.error('管道错误:', err.message);
    else console.log('pipeline 压缩完成');
  }
);

2.6 Buffer

Buffer 用于处理二进制数据(图片、文件、网络数据等),是固定大小的内存块。

// core/buffer/buffer-demo.js

// 创建 Buffer
const buf1 = Buffer.alloc(10);             // 10字节,全零
const buf2 = Buffer.alloc(10, 0xff);       // 10字节,填充0xff
const buf3 = Buffer.from('Hello Node.js'); // 从字符串创建(UTF-8)
const buf4 = Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]); // 从字节数组

console.log('buf3 长度:', buf3.length);       // 13
console.log('buf3 内容:', buf3.toString());   // Hello Node.js
console.log('buf3 十六进制:', buf3.toString('hex'));  // 48656c6c6f...
console.log('buf3 base64:', buf3.toString('base64')); // SGVsbG8gTm9kZS5qcw==

// 读写 Buffer
const buf = Buffer.alloc(4);
buf.writeUInt8(255, 0);      // 在偏移0写入无符号8位整数
buf.writeUInt16BE(1000, 1);  // 在偏移1写入大端序16位整数
buf.writeUInt8(42, 3);
console.log('buf:', buf);

// 切片(与原 Buffer 共享内存)
const slice = buf3.subarray(0, 5);
console.log('切片:', slice.toString()); // Hello

// 合并 Buffer
const combined = Buffer.concat([buf3, Buffer.from(' World!')]);
console.log('合并:', combined.toString()); // Hello Node.js World!

// 比较
console.log('相等:', buf3.equals(Buffer.from('Hello Node.js'))); // true

// Buffer 与字符串互转
const str = 'Node.js 中文';
const encoded = Buffer.from(str, 'utf8');
const decoded = encoded.toString('utf8');
console.log('编码字节数:', encoded.length); // 中文3字节/字符
console.log('解码:', decoded);

// Buffer 转 JSON
console.log(JSON.stringify(Buffer.from('abc')));
// {"type":"Buffer","data":[97,98,99]}

2.7 其他核心模块

url
// core/others/url-demo.js
const { URL, URLSearchParams } = require('url');

const myUrl = new URL('https://user:pass@example.com:8080/api/users?page=1&limit=10#top');

console.log('href:    ', myUrl.href);
console.log('protocol:', myUrl.protocol); // https:
console.log('username:', myUrl.username); // user
console.log('host:    ', myUrl.host);     // example.com:8080
console.log('hostname:', myUrl.hostname); // example.com
console.log('port:    ', myUrl.port);     // 8080
console.log('pathname:', myUrl.pathname); // /api/users
console.log('search:  ', myUrl.search);  // ?page=1&limit=10
console.log('hash:    ', myUrl.hash);    // #top

// 操作查询参数
myUrl.searchParams.set('page', '2');
myUrl.searchParams.append('sort', 'name');
myUrl.searchParams.delete('limit');
console.log('新 URL:', myUrl.toString());

// URLSearchParams 单独使用
const params = new URLSearchParams('a=1&b=2&b=3');
console.log('a:', params.get('a'));      // 1
console.log('b all:', params.getAll('b')); // ['2','3']
params.forEach((val, key) => console.log(key, '->', val));
querystring(旧版,建议用 URLSearchParams)
// core/others/querystring-demo.js
const qs = require('querystring');

// 解析查询字符串
const parsed = qs.parse('name=node&version=20&tags=js&tags=server');
console.log(parsed);
// { name: 'node', version: '20', tags: ['js', 'server'] }

// 序列化对象为查询字符串
const str = qs.stringify({ name: 'node', version: 20, tags: ['js', 'server'] });
console.log(str); // name=node&version=20&tags=js&tags=server
crypto

crypto 是 Node.js 内置的加密模块,提供哈希、HMAC、对称加密/解密、随机数生成等功能,底层基于 OpenSSL 实现。

常用功能:

功能方法说明
哈希摘要createHash单向不可逆,用于完整性校验
消息认证码createHmac带密钥的哈希,单向不可逆
对称加密createCipheriv可逆,需配合 createDecipheriv 解密
随机字节randomBytes生成安全随机数
// core/others/crypto-demo.js
const crypto = require('crypto');

// MD5 哈希(不推荐用于密码,仅用于校验)
const md5 = crypto.createHash('md5').update('hello').digest('hex');
console.log('MD5:', md5);

// SHA-256 哈希
const sha256 = crypto.createHash('sha256').update('hello').digest('hex');
console.log('SHA256:', sha256);

// HMAC(消息认证码,需要密钥)
const hmac = crypto.createHmac('sha256', 'secret-key').update('hello').digest('hex');
console.log('HMAC:', hmac);

// 随机字节(用于生成 token)
const token = crypto.randomBytes(32).toString('hex');
console.log('随机token:', token);

// 对称加密 AES-256-CBC
const ALGORITHM = 'aes-256-cbc';
const KEY = crypto.randomBytes(32);  // 32字节密钥
const IV  = crypto.randomBytes(16);  // 16字节初始向量

function encrypt(text) {
  // 创建加密器,使用相同的算法、密钥和初始向量
  const cipher = crypto.createCipheriv(ALGORITHM, KEY, IV);
  // update() 处理明文,final() 处理剩余填充块,concat 合并后转十六进制字符串
  return Buffer.concat([cipher.update(text, 'utf8'), cipher.final()]).toString('hex');
}

function decrypt(encrypted) {
  // 创建解密器,必须使用与加密时完全相同的算法、密钥和初始向量
  const decipher = crypto.createDecipheriv(ALGORITHM, KEY, IV);
  // 将十六进制密文还原为 Buffer,update() + final() 完成解密并转为字符串
  return Buffer.concat([
    decipher.update(Buffer.from(encrypted, 'hex')),
    decipher.final()
  ]).toString('utf8');
}

const plaintext  = 'Node.js 加密示例';
const ciphertext = encrypt(plaintext);
const decrypted  = decrypt(ciphertext);
console.log('原文:', plaintext);
console.log('密文:', ciphertext);
console.log('解密:', decrypted);
util
// core/others/util-demo.js
const util = require('util');
const fs = require('fs');

// util.promisify:将 callback 风格函数转为 Promise
const readFile = util.promisify(fs.readFile);

async function demo() {
  const data = await readFile(__filename, 'utf8');
  console.log('文件行数:', data.split('\n').length);
}
demo();

// util.format:格式化字符串
console.log(util.format('Hello %s, you are %d years old', 'Node', 20));
// Hello Node, you are 20 years old
console.log(util.format('Object: %j', { a: 1, b: 2 }));
// Object: {"a":1,"b":2}

// util.inspect:将对象转为可读字符串
// 比 JSON.stringify 更强:支持循环引用、函数、Symbol、Map/Set、自定义颜色、控制嵌套深度
const obj = { a: 1, b: [1, 2, 3], c: { d: true } };
console.log(util.inspect(obj, { depth: null, colors: true }));

// util.isDeepStrictEqual:深度严格相等比较
console.log(util.isDeepStrictEqual({ a: 1 }, { a: 1 })); // true
console.log(util.isDeepStrictEqual({ a: 1 }, { a: '1' })); // false(严格模式)

// util.deprecate:标记函数为废弃
const oldFn = util.deprecate(
  () => console.log('旧函数'),
  '请使用 newFn 代替'
);
oldFn(); // 调用时会打印废弃警告

3. Web 框架

3.1 Express 框架

Express 是 Node.js 最流行的 Web 框架,轻量、灵活,提供路由、中间件等核心能力。

安装和使用
npm install express
// express/basic.js
const express = require('express');
const app = express();

// 解析 JSON 请求体
app.use(express.json());
// 解析 URL 编码请求体(表单)
app.use(express.urlencoded({ extended: true }));

app.get('/', (req, res) => {
  res.send('Hello Express!');
});

app.listen(3000, () => {
  console.log('Express 服务启动: http://localhost:3000');
});
路由
// express/router.js
const express = require('express');
const router = express.Router();

// GET 列表
router.get('/users', (req, res) => {
  const { page = 1, limit = 10 } = req.query;
  res.json({ users: [], page: Number(page), limit: Number(limit) });
});

// GET 详情(路由参数)
router.get('/users/:id', (req, res) => {
  const { id } = req.params;
  res.json({ user: { id } });
});

// POST 创建
router.post('/users', (req, res) => {
  const { name, email } = req.body;
  res.status(201).json({ user: { id: Date.now(), name, email } });
});

// PUT 更新
router.put('/users/:id', (req, res) => {
  const { id } = req.params;
  const { name, email } = req.body;
  res.json({ user: { id, name, email } });
});

// DELETE 删除
router.delete('/users/:id', (req, res) => {
  const { id } = req.params;
  res.json({ deleted: id });
});

module.exports = router;
// express/app.js 中挂载路由
const express = require('express');
const userRouter = require('./router');

const app = express();
app.use(express.json());
app.use('/api', userRouter);

app.listen(3000);
中间件

中间件是一个函数 (req, res, next) => {},可以拦截、处理请求,或将控制权传递给下一个中间件。

// express/middleware.js
const express = require('express');
const app = express();

// 应用级中间件:每个请求都执行
app.use((req, res, next) => {
  console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
  next(); // 必须调用,否则请求挂起
});

// 路由级中间件:只对指定路由生效
function authMiddleware(req, res, next) {
  const token = req.headers['authorization'];
  if (!token) {
    return res.status(401).json({ error: '未授权' });
  }
  next();
}

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

// 第三方中间件示例
// npm install cors morgan
const cors = require('cors');
const morgan = require('morgan');
app.use(cors());           // 跨域处理
app.use(morgan('dev'));    // 请求日志

app.listen(3000);
请求与响应
// express/req-res.js
const express = require('express');
const app = express();
app.use(express.json());

app.post('/demo', (req, res) => {
  // 请求
  console.log(req.params);    // 路由参数 /users/:id
  console.log(req.query);     // 查询参数 ?page=1
  console.log(req.body);      // 请求体(需中间件解析)
  console.log(req.headers);   // 请求头
  console.log(req.ip);        // 客户端 IP

  // 响应
  res.status(200)             // 设置状态码
    .set('X-Custom', 'value') // 设置响应头
    .json({ ok: true });      // 发送 JSON(自动设置 Content-Type)

  // 其他响应方式
  // res.send('文本')          // 文本/HTML
  // res.sendFile('/path/to/file')  // 发送文件
  // res.redirect('/other')   // 重定向
  // res.download('/file')    // 触发下载
});

app.listen(3000);
错误处理
// express/error-handler.js
const express = require('express');
const app = express();
app.use(express.json());

// 自定义错误类
class AppError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
  }
}

app.get('/users/:id', (req, res, next) => {
  const { id } = req.params;
  if (id === '0') {
    return next(new AppError('用户不存在', 404));
  }
  res.json({ user: { id } });
});

// 异步路由的错误捕获(推荐封装 asyncHandler)
function asyncHandler(fn) {
  return (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);
}

app.get('/async', asyncHandler(async (req, res) => {
  // 异步操作,错误自动传递给错误处理中间件
  throw new AppError('演示错误', 500);
}));

// 错误处理中间件(4个参数,必须放最后)
app.use((err, req, res, next) => {
  const statusCode = err.statusCode || 500;
  res.status(statusCode).json({
    error: err.message || '服务器内部错误',
  });
});

app.listen(3000);

3.2 Koa 框架

Koa 由 Express 原班人马打造,更现代、更精简,核心只有中间件机制,无内置路由。

Koa 特点
特性ExpressKoa
中间件机制线性(next 传递)洋葱模型(async/await)
内置路由❌(需 koa-router)
错误处理4 参数中间件try/catch + ctx.status
请求/响应req / resctx.request / ctx.response
异步支持callback / Promise原生 async/await
npm install koa koa-router koa-bodyparser
Koa 中间件(洋葱模型)
// koa/middleware.js
const Koa = require('koa');
const app = new 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('处理请求');
  ctx.body = { ok: true };
});

// 输出顺序:中间件1进入 → 中间件2进入 → 处理请求 → 中间件2返回 → 中间件1返回
app.listen(3000);
Koa 路由
// koa/app.js
const Koa = require('koa');
const Router = require('koa-router');
const bodyParser = require('koa-bodyparser');

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

app.use(bodyParser());

// 错误处理(放最外层)
app.use(async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    ctx.status = err.status || 500;
    ctx.body = { error: err.message };
  }
});

router.get('/users', async (ctx) => {
  const { page = 1 } = ctx.query;
  ctx.body = { users: [], page: Number(page) };
});

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

router.post('/users', async (ctx) => {
  const { name, email } = ctx.request.body;
  ctx.status = 201;
  ctx.body = { user: { id: Date.now(), name, email } };
});

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

app.listen(3000, () => console.log('Koa 服务启动: http://localhost:3000'));

3.3 RESTful API 设计

HTTP 方法
方法语义示例
GET获取资源GET /users / GET /users/1
POST创建资源POST /users
PUT全量更新PUT /users/1
PATCH部分更新PATCH /users/1
DELETE删除资源DELETE /users/1
状态码
状态码含义场景
200OK请求成功
201Created资源创建成功
204No Content删除成功(无响应体)
400Bad Request参数错误
401Unauthorized未登录/Token 无效
403Forbidden无权限
404Not Found资源不存在
409Conflict资源冲突(如重复创建)
422Unprocessable Entity数据验证失败
500Internal Server Error服务器内部错误
参数传递
// restful/param-demo.js
const express = require('express');
const app = express();
app.use(express.json());

// 1. 路由参数(资源 ID):/users/:id
app.get('/users/:id', (req, res) => {
  const { id } = req.params;      // "123"
  res.json({ id });
});

// 2. 查询参数(过滤/分页/排序):/users?page=1&sort=name
app.get('/users', (req, res) => {
  const { page = 1, limit = 10, sort = 'id' } = req.query;
  res.json({ page: Number(page), limit: Number(limit), sort });
});

// 3. 请求体(创建/更新数据)
app.post('/users', (req, res) => {
  const { name, email } = req.body;
  res.status(201).json({ user: { name, email } });
});

// 4. 请求头(认证、版本等)
app.get('/profile', (req, res) => {
  const token = req.headers['authorization']; // "Bearer xxx"
  const version = req.headers['api-version']; // "v2"
  res.json({ token, version });
});

app.listen(3000);
API 文档(JSDoc 注释风格)
// restful/api-docs.js
/**
 * @route   GET /api/users
 * @desc    获取用户列表
 * @access  Public
 * @query   {number} page - 页码,默认 1
 * @query   {number} limit - 每页条数,默认 10
 * @returns {object} { users: User[], total: number, page: number }
 */

/**
 * @route   POST /api/users
 * @desc    创建用户
 * @access  Private(需 Bearer Token)
 * @body    {string} name - 用户名(必填)
 * @body    {string} email - 邮箱(必填)
 * @returns {object} { user: User }
 * @error   400 - 参数缺失
 * @error   409 - 邮箱已存在
 */

3.4 身份认证

Cookie 与 Session
npm install express-session
// auth/session-demo.js
const express = require('express');
const session = require('express-session');

const app = express();
app.use(express.json());
app.use(session({
  secret: 'my-secret-key',   // 签名密钥
  resave: false,
  saveUninitialized: false,
  cookie: {
    maxAge: 1000 * 60 * 60,  // 1小时
    httpOnly: true,           // 禁止 JS 访问
    secure: false,            // 生产环境设为 true(HTTPS)
  }
}));

// 登录
app.post('/login', (req, res) => {
  const { username, password } = req.body;
  if (username === 'admin' && password === '123456') {
    req.session.user = { username, role: 'admin' };
    return res.json({ ok: true });
  }
  res.status(401).json({ error: '用户名或密码错误' });
});

// 受保护路由
app.get('/profile', (req, res) => {
  if (!req.session.user) {
    return res.status(401).json({ error: '请先登录' });
  }
  res.json({ user: req.session.user });
});

// 退出
app.post('/logout', (req, res) => {
  req.session.destroy();
  res.json({ ok: true });
});

app.listen(3000);
JWT

JWT(JSON Web Token):无状态认证,适合前后端分离。

Header.Payload.Signature
npm install jsonwebtoken
// auth/jwt-demo.js
const express = require('express');
const jwt = require('jsonwebtoken');

const app = express();
app.use(express.json());

const JWT_SECRET = 'my-jwt-secret';
const JWT_EXPIRES = '7d';

// 登录 → 签发 Token
app.post('/login', (req, res) => {
  const { username, password } = req.body;
  if (username === 'admin' && password === '123456') {
    const token = jwt.sign(
      { id: 1, username, role: 'admin' }, // payload
      JWT_SECRET,
      { expiresIn: JWT_EXPIRES }
    );
    return res.json({ token });
  }
  res.status(401).json({ error: '用户名或密码错误' });
});

// JWT 验证中间件
function jwtAuth(req, res, next) {
  const auth = req.headers['authorization'];
  if (!auth || !auth.startsWith('Bearer ')) {
    return res.status(401).json({ error: '缺少 Token' });
  }
  try {
    const payload = jwt.verify(auth.slice(7), JWT_SECRET);
    req.user = payload;
    next();
  } catch (err) {
    res.status(401).json({ error: 'Token 无效或已过期' });
  }
}

// 受保护路由
app.get('/profile', jwtAuth, (req, res) => {
  res.json({ user: req.user });
});

app.listen(3000);
OAuth 2.0(概念说明)

OAuth 2.0 是一个授权框架,允许第三方应用获取用户资源的有限访问权限,常见于"使用 GitHub / Google 登录"场景。

核心流程(Authorization Code Flow):

用户 → 客户端 → 授权服务器(GitHub)→ 返回 code
客户端 → 用 code 换 access_token
客户端 → 用 access_token 访问资源服务器(获取用户信息)

四种授权模式:

模式适用场景
Authorization Code服务端 Web 应用(最安全,推荐)
PKCE单页应用(SPA)/ 移动端
Client Credentials服务间调用(无用户参与)
Resource Owner Password高度信任的第一方应用(不推荐)
# Passport.js 是 Node.js 最流行的 OAuth 库
npm install passport passport-github2

# example:  auth/auth2.0.js 
---

### 3.5 文件上传(Multer)

Multer 是 Express 的文件上传中间件,基于 `multipart/form-data`。

```bash
npm install multer
// upload/upload-demo.js
const express = require('express');
const multer = require('multer');
const path = require('path');

const app = express();

// 配置存储
const storage = multer.diskStorage({
  destination(req, file, cb) {
    cb(null, 'uploads/');          // 存储目录(需提前创建)
  },
  filename(req, file, cb) {
    const ext = path.extname(file.originalname);
    const name = `${Date.now()}-${Math.random().toString(36).slice(2)}${ext}`;
    cb(null, name);
  }
});

// 文件类型过滤
function fileFilter(req, file, cb) {
  const allowed = ['.jpg', '.jpeg', '.png', '.gif', '.pdf'];
  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
});

// 单文件上传(字段名 file)
app.post('/upload/single', upload.single('file'), (req, res) => {
  if (!req.file) return res.status(400).json({ error: '请选择文件' });
  res.json({
    filename: req.file.filename,
    size: req.file.size,
    mimetype: req.file.mimetype,
    url: `/uploads/${req.file.filename}`
  });
});

// 多文件上传(最多 5 个)
app.post('/upload/multiple', upload.array('files', 5), (req, res) => {
  const files = req.files.map(f => ({
    filename: f.filename,
    size: f.size,
    url: `/uploads/${f.filename}`
  }));
  res.json({ files });
});

// 静态访问上传文件
app.use('/uploads', express.static('uploads'));

// 上传错误处理
app.use((err, req, res, next) => {
  if (err instanceof multer.MulterError) {
    return res.status(400).json({ error: `上传错误: ${err.message}` });
  }
  res.status(500).json({ error: err.message });
});

app.listen(3000, () => console.log('文件上传服务: http://localhost:3000'));

4. 数据库

4.1 MySQL 操作(mysql2)

安装与连接
npm install mysql2
// db/mysql/connection.js
const mysql = require('mysql2/promise');

// 创建连接池(推荐,自动管理连接复用)
const pool = mysql.createPool({
  host: 'localhost',
  port: 3306,
  user: 'root',
  password: 'your_password',
  database: 'test_db',
  waitForConnections: true,
  connectionLimit: 10,     // 最大连接数
  queueLimit: 0
});

module.exports = pool;
执行 SQL
// db/mysql/query-demo.js
const pool = require('./connection');

async function demo() {
  // 查询
  const [rows] = await pool.query('SELECT * FROM users WHERE id = ?', [1]);
  console.log('查询结果:', rows);

  // 插入
  const [result] = await pool.query(
    'INSERT INTO users (name, email) VALUES (?, ?)',
    ['张三', 'zhangsan@example.com']
  );
  console.log('新增 ID:', result.insertId);

  // 更新
  const [updateResult] = await pool.query(
    'UPDATE users SET name = ? WHERE id = ?',
    ['李四', 1]
  );
  console.log('影响行数:', updateResult.affectedRows);

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

demo().catch(console.error);
参数化查询

参数化查询(Prepared Statement)将 SQL 与数据分离,防止 SQL 注入攻击

// db/mysql/prepared.js
const pool = require('./connection');

// ❌ 危险:拼接字符串(SQL 注入漏洞)
// pool.query(`SELECT * FROM users WHERE name = '${userInput}'`);

// ✅ 安全:参数化查询,? 占位符由驱动处理转义
async function findUser(name) {
  const [rows] = await pool.query(
    'SELECT * FROM users WHERE name = ?',
    [name]
  );
  return rows;
}

// 批量插入
async function batchInsert(users) {
  const values = users.map(u => [u.name, u.email]);
  const [result] = await pool.query(
    'INSERT INTO users (name, email) VALUES ?',
    [values]
  );
  return result.affectedRows;
}
连接池
// db/mysql/pool-demo.js
const pool = require('./connection');

// 手动获取连接(事务场景)
async function transfer(fromId, toId, amount) {
  const conn = await pool.getConnection();
  try {
    await conn.beginTransaction();

    await conn.query('UPDATE accounts SET balance = balance - ? WHERE id = ?', [amount, fromId]);
    await conn.query('UPDATE accounts SET balance = balance + ? WHERE id = ?', [amount, toId]);

    await conn.commit();
    console.log('转账成功');
  } catch (err) {
    await conn.rollback();
    console.error('转账失败,已回滚:', err.message);
    throw err;
  } finally {
    conn.release(); // 归还连接到池
  }
}

4.2 MongoDB(Mongoose)

Mongoose 是 MongoDB 的 ODM(对象文档映射)库,提供 Schema 约束和模型操作。

npm install mongoose
Schema / Model
// db/mongo/models/User.js
const mongoose = require('mongoose');

// Schema:定义文档结构和约束
const userSchema = new mongoose.Schema({
  name:      { type: String, required: true, trim: true },
  email:     { type: String, required: true, unique: true, lowercase: true },
  age:       { type: Number, min: 0, max: 150 },
  role:      { type: String, enum: ['user', 'admin'], default: 'user' },
  createdAt: { type: Date, default: Date.now }
});

// Model:对应 MongoDB 中的 users 集合
const User = mongoose.model('User', userSchema);

module.exports = User;
连接数据库
// db/mongo/connection.js
const mongoose = require('mongoose');

async function connect() {
  await mongoose.connect('mongodb://localhost:27017/test_db', {
    // Mongoose 6+ 无需再传 useNewUrlParser 等选项
  });
  console.log('MongoDB 连接成功');
}

// 监听连接事件
mongoose.connection.on('disconnected', () => console.warn('MongoDB 断开连接'));
mongoose.connection.on('error', (err) => console.error('MongoDB 错误:', err));

module.exports = { connect };
CRUD 操作
// db/mongo/crud-demo.js
const mongoose = require('mongoose');
const User = require('./models/User');

async function demo() {
  await mongoose.connect('mongodb://localhost:27017/test_db');

  // 创建
  const user = await User.create({ name: '张三', email: 'zs@example.com', age: 25 });
  console.log('创建:', user._id);

  // 查询
  const users = await User.find({ role: 'user' }).select('name email').limit(10);
  const one   = await User.findById(user._id);
  const byEmail = await User.findOne({ email: 'zs@example.com' });

  // 更新
  await User.findByIdAndUpdate(user._id, { age: 26 }, { new: true }); // new: true 返回更新后文档

  // 删除
  await User.findByIdAndDelete(user._id);

  // 统计
  const count = await User.countDocuments({ role: 'user' });
  console.log('用户数:', count);

  await mongoose.disconnect();
}

demo().catch(console.error);

4.3 ORM 框架

ORM(对象关系映射)将数据库表映射为 JavaScript 对象,避免手写 SQL。

Sequelize

适合 MySQL / PostgreSQL / SQLite,历史悠久、社区成熟。

npm install sequelize mysql2
// db/orm/sequelize-demo.js
const { Sequelize, DataTypes } = require('sequelize');

const sequelize = new Sequelize('test_db', 'root', 'password', {
  host: 'localhost',
  dialect: 'mysql',
  logging: false,
});

// 定义模型
const User = sequelize.define('User', {
  name:  { type: DataTypes.STRING, allowNull: false },
  email: { type: DataTypes.STRING, unique: true, allowNull: false },
  age:   { type: DataTypes.INTEGER },
}, { tableName: 'users' });

async function demo() {
  await sequelize.authenticate();    // 测试连接
  await sequelize.sync({ alter: true }); // 同步表结构

  const user = await User.create({ name: '张三', email: 'zs@example.com', age: 25 });
  const found = await User.findOne({ where: { email: 'zs@example.com' } });
  await User.update({ age: 26 }, { where: { id: user.id } });
  await User.destroy({ where: { id: user.id } });
}

demo().catch(console.error);
Prisma

现代 ORM,类型安全,自动生成类型定义,适合 TypeScript 项目。

npm install prisma @prisma/client
npx prisma init
// prisma/schema.prisma
datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model User {
  id        Int      @id @default(autoincrement())
  name      String
  email     String   @unique
  age       Int?
  createdAt DateTime @default(now())
}
// db/orm/prisma-demo.js
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

async function demo() {
  const user  = await prisma.user.create({ data: { name: '张三', email: 'zs@example.com' } });
  const users = await prisma.user.findMany({ where: { age: { gte: 18 } } });
  await prisma.user.update({ where: { id: user.id }, data: { age: 26 } });
  await prisma.user.delete({ where: { id: user.id } });

  await prisma.$disconnect();
}

demo().catch(console.error);
TypeORM

支持 TypeScript 装饰器,适合面向对象风格。

npm install typeorm mysql2 reflect-metadata
// db/orm/typeorm-demo.js
const { DataSource, Entity, PrimaryGeneratedColumn, Column } = require('typeorm');

// 定义实体
@Entity('users')
class User {
  @PrimaryGeneratedColumn()
  id;

  @Column()
  name;

  @Column({ unique: true })
  email;
}

const AppDataSource = new DataSource({
  type: 'mysql',
  host: 'localhost',
  port: 3306,
  username: 'root',
  password: 'password',
  database: 'test_db',
  entities: [User],
  synchronize: true,  // 自动同步表结构(生产环境关闭)
});

async function demo() {
  await AppDataSource.initialize();
  const repo = AppDataSource.getRepository(User);

  const user  = repo.create({ name: '张三', email: 'zs@example.com' });
  await repo.save(user);
  const found = await repo.findOneBy({ email: 'zs@example.com' });
  await repo.remove(found);

  await AppDataSource.destroy();
}

demo().catch(console.error);

三者对比:

特性SequelizePrismaTypeORM
数据库支持MySQL/PG/SQLite/MSSQLMySQL/PG/SQLite/MongoDBMySQL/PG/SQLite/MongoDB/MSSQL
TypeScript一般最优(自动生成类型)好(装饰器)
学习成本
迁移管理手动prisma migratetypeorm migration
适合场景传统 Node 项目现代 TS 全栈企业级 OOP 项目

4.4 Redis 操作

Redis 是基于内存的键值存储,常用于缓存、会话、消息队列等场景。

npm install redis
// db/redis/redis-demo.js
const { createClient } = require('redis');

const client = createClient({
  url: 'redis://localhost:6379'
  // password: 'your_password'  // 有密码时配置
});

client.on('error', (err) => console.error('Redis 错误:', err));

async function demo() {
  await client.connect();

  // 字符串:缓存操作
  await client.set('name', 'Node.js');
  await client.set('counter', '0');
  await client.expire('name', 60);           // 60 秒过期
  const name = await client.get('name');
  console.log('name:', name);                // Node.js

  // 自增(计数器)
  await client.incr('counter');
  await client.incrBy('counter', 5);

  // 哈希:存储对象
  await client.hSet('user:1', { name: '张三', age: '25', role: 'admin' });
  const user = await client.hGetAll('user:1');
  console.log('user:', user);

  // 列表:消息队列
  await client.lPush('queue', 'task1', 'task2');
  const task = await client.rPop('queue');   // 从右侧取出
  console.log('task:', task);

  // Set:去重集合
  await client.sAdd('tags', 'node', 'js', 'node'); // 自动去重
  const tags = await client.sMembers('tags');
  console.log('tags:', tags);

  // 检查 key 是否存在
  const exists = await client.exists('name');
  console.log('exists:', exists); // 1

  // 删除 key
  await client.del('name');

  await client.disconnect();
}

demo().catch(console.error);
缓存实战(Express + Redis)
// db/redis/cache-middleware.js
const { createClient } = require('redis');

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

// 缓存中间件(GET 请求缓存 60 秒)
function cache(ttl = 60) {
  return async (req, res, next) => {
    const key = `cache:${req.originalUrl}`;
    const cached = await redis.get(key);
    if (cached) {
      return res.json(JSON.parse(cached)); // 命中缓存
    }
    // 劫持 res.json,写入缓存后再响应
    const originJson = res.json.bind(res);
    res.json = async (data) => {
      await redis.setEx(key, ttl, JSON.stringify(data));
      originJson(data);
    };
    next();
  };
}

module.exports = { cache, redis };
// 使用缓存中间件
const express = require('express');
const { cache } = require('./cache-middleware');

const app = express();

app.get('/users', cache(30), async (req, res) => {
  // 首次请求查数据库,结果缓存 30 秒
  const users = [{ id: 1, name: '张三' }]; // 模拟 DB 查询
  res.json({ users });
});

app.listen(3000);