Node.js 学习文档
1. Node 基础
1.1 基础概念
定义与特点
Node.js 是一个基于 Chrome V8 引擎 的 JavaScript 运行时环境,让 JavaScript 可以运行在服务端。
核心特点:
- 异步非阻塞 I/O
- 事件驱动架构
- 单线程模型
- 跨平台(Windows / macOS / Linux)
- 拥有丰富的 npm 生态
事件驱动(Event-Driven)
Node.js 使用 事件循环(Event Loop) 来处理并发,所有异步操作完成后通过触发"事件"来执行回调函数。
js
// 示例: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),完成后回调通知主线程,主线程无需等待。
js
// 示例: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 查询、加密等耗时操作,主线程不会被阻塞。
css
┌──────────────────────────────────────────┐
│ Node.js 进程 │
│ │
│ ┌───────────────────────────────────┐ │
│ │ JS 主线程(单线程) │ │
│ │ - 执行 JavaScript 代码 │ │
│ │ - 运行事件循环 │ │
│ │ - 处理回调函数 │ │
│ └────────────┬──────────────────────┘ │
│ │ 异步任务(I/O、加密等) │
│ ┌────────────▼──────────────────────┐ │
│ │ libuv 线程池(默认4线程) │ │
│ │ 线程1 │ 线程2 │ 线程3 │ 线程4 │ │
│ │ 文件IO DNS 加密 压缩 │ │
│ └────────────┬──────────────────────┘ │
│ │ 任务完成 → 放入事件队列 │
│ ┌────────────▼──────────────────────┐ │
│ │ 事件队列 │ │
│ │ [回调1] [回调2] [回调3] ... │ │
│ └────────────┬──────────────────────┘ │
│ │ 事件循环取出并执行 │
│ └──→ JS 主线程 │
└──────────────────────────────────────────┘
单线程的好处:
- 无需处理多线程的锁、竞态条件、死锁等问题
- 内存共享安全,无需同步原语
- 上下文切换开销极小
单线程的限制:
- CPU 密集型任务(大量计算、图像处理)会阻塞主线程,导致所有请求挂起
- 单个未捕获异常会导致整个进程崩溃
验证单线程阻塞:
js
// 示例: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 密集型任务的方案:
js
// 方案: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); // 将结果发送给主线程
}
线程池大小配置:
bash
# 通过环境变量调整 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 的核心机制,按以下阶段循环执行:
arduino
┌───────────────────────────┐
┌─>│ timers │ setTimeout / setInterval 回调
│ └─────────────┬─────────────┘
│ ┌─────────────▼─────────────┐
│ │ pending callbacks │ 上一轮延迟的 I/O 回调
│ └─────────────┬─────────────┘
│ ┌─────────────▼─────────────┐
│ │ idle, prepare │ 内部使用
│ └─────────────┬─────────────┘
│ ┌─────────────▼─────────────┐
│ │ poll │ 获取新 I/O 事件
│ └─────────────┬─────────────┘
│ ┌─────────────▼─────────────┐
│ │ check │ setImmediate 回调
│ └─────────────┬─────────────┘
│ ┌─────────────▼─────────────┐
└──│ close callbacks │ 关闭事件回调
└───────────────────────────┘
js
// 示例: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 管理多版本(推荐)
bash
# 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 的默认包管理器。
bash
# 查看 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 是项目的配置清单,记录项目信息、依赖、脚本等。
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 导出。
js
// 示例: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 };
js
// 示例: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:当前文件完整路径
js
// 示例: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"
js
// 示例: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;
}
js
// 示例: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 对比:
| 特性 | CommonJS | ES Module |
|---|---|---|
| 语法 | require / module.exports |
import / export |
| 加载时机 | 运行时(同步) | 编译时(静态分析) |
| 动态导入 | 原生支持 | import() 动态语法 |
this 指向 |
module.exports |
undefined |
| 适用场景 | Node.js 传统项目 | 现代项目、前端打包 |
内置模块
Node.js 自带的模块,无需安装,直接 require。
js
// 示例: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 |
流处理 |
url |
URL 解析 |
crypto |
加密功能 |
util |
实用工具 |
child_process |
子进程 |
第三方模块
通过 npm 安装,存放于 node_modules 目录。
bash
# 安装示例
npm install lodash
npm install axios
js
// 示例: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
自定义模块
将业务逻辑封装为模块,实现代码复用。
js
// 示例: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 };
js
// 示例:modules/custom/main.js(使用自定义模块)
const logger = require('./logger');
logger.info('应用已启动');
logger.warn('内存使用率较高');
logger.error('数据库连接失败');
1.4 npm 包管理
npm init
初始化项目,生成 package.json。
bash
# 交互式初始化(逐步填写)
npm init
# 快速初始化(全部使用默认值)
npm init -y
# 使用自定义默认值
npm config set init-author-name "Your Name"
npm config set init-license "MIT"
npm init -y
npm install
安装依赖包。
bash
# 安装所有依赖(根据 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):
json
"express": "^4.18.2" → 兼容 4.x.x,不升级主版本
"express": "~4.18.2" → 兼容 4.18.x,不升级次版本
"express": "4.18.2" → 精确版本
"express": "*" → 任意版本(不推荐)
npm scripts
在 package.json 的 scripts 字段定义可执行脚本。
json
{
"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!"
}
}
bash
# 运行脚本
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 出品,更快更稳定):
bash
# 安装 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 友好):
bash
# 安装 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 锁定版本
三者对比:
| 特性 | npm | yarn | pnpm |
|---|---|---|---|
| 安装速度 | 一般 | 快 | 最快 |
| 磁盘占用 | 高 | 高 | 低(硬链接共享) |
| lockfile | package-lock.json | yarn.lock | pnpm-lock.yaml |
| monorepo | 支持(workspaces) | 支持(workspaces) | 原生最优 |
| 安全性 | 一般 | 好 | 好 |
2. Node.js 核心模块
2.1 文件系统 fs
读取文件
js
// 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);
写入文件
js
// 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);
文件信息
js
// 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);
});
目录操作
js
// 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)用于处理大文件,避免一次性读入内存。
js
// 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
js
// 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 服务器
js
// 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');
});
请求与响应
js
// 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'));
路由处理
js
// 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 用户详情');
});
静态文件服务
js
// 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
js
// 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
可读流
js
// 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();
可写流
js
// 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
js
// 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 用于处理二进制数据(图片、文件、网络数据等),是固定大小的内存块。
js
// 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
js
// 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)
js
// 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 |
生成安全随机数 |
js
// 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
js
// 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 框架,轻量、灵活,提供路由、中间件等核心能力。
安装和使用
bash
npm install express
js
// 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');
});
路由
js
// 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;
js
// 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) => {},可以拦截、处理请求,或将控制权传递给下一个中间件。
js
// 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);
请求与响应
js
// 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);
错误处理
js
// 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 特点
| 特性 | Express | Koa |
|---|---|---|
| 中间件机制 | 线性(next 传递) | 洋葱模型(async/await) |
| 内置路由 | ✅ | ❌(需 koa-router) |
| 错误处理 | 4 参数中间件 | try/catch + ctx.status |
| 请求/响应 | req / res | ctx.request / ctx.response |
| 异步支持 | callback / Promise | 原生 async/await |
bash
npm install koa koa-router koa-bodyparser
Koa 中间件(洋葱模型)
js
// 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 路由
js
// 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 |
状态码
| 状态码 | 含义 | 场景 |
|---|---|---|
| 200 | OK | 请求成功 |
| 201 | Created | 资源创建成功 |
| 204 | No Content | 删除成功(无响应体) |
| 400 | Bad Request | 参数错误 |
| 401 | Unauthorized | 未登录/Token 无效 |
| 403 | Forbidden | 无权限 |
| 404 | Not Found | 资源不存在 |
| 409 | Conflict | 资源冲突(如重复创建) |
| 422 | Unprocessable Entity | 数据验证失败 |
| 500 | Internal Server Error | 服务器内部错误 |
参数传递
js
// 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 注释风格)
js
// 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
bash
npm install express-session
js
// 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):无状态认证,适合前后端分离。
css
Header.Payload.Signature
bash
npm install jsonwebtoken
js
// 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):
css
用户 → 客户端 → 授权服务器(GitHub)→ 返回 code
客户端 → 用 code 换 access_token
客户端 → 用 access_token 访问资源服务器(获取用户信息)
四种授权模式:
| 模式 | 适用场景 |
|---|---|
| Authorization Code | 服务端 Web 应用(最安全,推荐) |
| PKCE | 单页应用(SPA)/ 移动端 |
| Client Credentials | 服务间调用(无用户参与) |
| Resource Owner Password | 高度信任的第一方应用(不推荐) |
bash
# 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
js
// 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)
安装与连接
bash
npm install mysql2
js
// 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
js
// 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 注入攻击。
js
// 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;
}
连接池
js
// 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 约束和模型操作。
bash
npm install mongoose
Schema / Model
js
// 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;
连接数据库
js
// 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 操作
js
// 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,历史悠久、社区成熟。
bash
npm install sequelize mysql2
js
// 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 项目。
bash
npm install prisma @prisma/client
npx prisma init
prisma
// 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())
}
js
// 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 装饰器,适合面向对象风格。
bash
npm install typeorm mysql2 reflect-metadata
js
// 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);
三者对比:
| 特性 | Sequelize | Prisma | TypeORM |
|---|---|---|---|
| 数据库支持 | MySQL/PG/SQLite/MSSQL | MySQL/PG/SQLite/MongoDB | MySQL/PG/SQLite/MongoDB/MSSQL |
| TypeScript | 一般 | 最优(自动生成类型) | 好(装饰器) |
| 学习成本 | 中 | 低 | 中 |
| 迁移管理 | 手动 | prisma migrate |
typeorm migration |
| 适合场景 | 传统 Node 项目 | 现代 TS 全栈 | 企业级 OOP 项目 |
4.4 Redis 操作
Redis 是基于内存的键值存储,常用于缓存、会话、消息队列等场景。
bash
npm install redis
js
// 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)
js
// 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 };
js
// 使用缓存中间件
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);