Node 实践
约 1224 字大约 4 分钟
nodepractice
2026-04-08
目录:
把 Node 用在生产环境时,框架本身只是骨架,真正决定可靠性的是这些"实践层"的细节:鉴权、自实现常见框架核心、网络转发、加密签名等。
JWT 权限认证原理
JWT (JSON Web Token) 是一种无状态的鉴权方案,token 由三段 Base64Url 字符串以 . 拼接:
xxxxx.yyyyy.zzzzz
↑ ↑ ↑
Header Payload Signature- Header:
{ "alg": "HS256", "typ": "JWT" } - Payload:业务声明 (
sub,iat,exp, 自定义字段) - Signature:
HMACSHA256(base64url(header) + '.' + base64url(payload), secret)
工作流程:
- 客户端登录,服务端校验账号密码后签发 JWT
- 客户端把 JWT 放在
Authorization: Bearer xxx中随请求发送 - 服务端只需用密钥验签并检查
exp,无需查库
import jwt from 'jsonwebtoken'
const token = jwt.sign({ uid: 1 }, 'secret', { expiresIn: '2h' })
const payload = jwt.verify(token, 'secret')优点:横向扩展友好、移动端 / 跨域友好;缺点:无法主动作废(需配合短期 token + refresh token,或维护黑名单)。
手写 Express 核心
Express 的核心其实只有三件事:路由表、中间件链、请求响应增强。
import http from 'node:http'
class App {
constructor() { this.stack = [] }
use(path, fn) {
if (typeof path === 'function') { fn = path; path = '/' }
this.stack.push({ path, method: 'ALL', fn })
}
get(path, fn) { this.stack.push({ path, method: 'GET', fn }) }
post(path, fn) { this.stack.push({ path, method: 'POST', fn }) }
handle(req, res) {
let i = 0
const next = (err) => {
const layer = this.stack[i++]
if (!layer) return res.end(err ? String(err) : 'Not Found')
const matchPath = req.url.startsWith(layer.path)
const matchMethod = layer.method === 'ALL' || layer.method === req.method
if (matchPath && matchMethod) {
try { layer.fn(req, res, next) } catch (e) { next(e) }
} else next(err)
}
next()
}
listen(port) { http.createServer(this.handle.bind(this)).listen(port) }
}关键点:中间件按数组顺序调用,next 是显式触发下一个的钩子;错误中间件通过 (err, req, res, next) 四参签名识别。
手写 Koa2 核心
Koa 抛弃了回调式 next,改用 async 函数 + 洋葱模型:每个中间件 await next() 之后还能继续执行后置逻辑。
import http from 'node:http'
class Koa {
constructor() { this.middleware = [] }
use(fn) { this.middleware.push(fn) }
compose(ctx) {
const dispatch = (i) => {
const fn = this.middleware[i]
if (!fn) return Promise.resolve()
return Promise.resolve(fn(ctx, () => dispatch(i + 1)))
}
return dispatch(0)
}
listen(port) {
http.createServer((req, res) => {
const ctx = { req, res, body: '' }
this.compose(ctx)
.then(() => res.end(ctx.body))
.catch((err) => { res.statusCode = 500; res.end(err.message) })
}).listen(port)
}
}洋葱模型示意:
→ middleware1 in
→ middleware2 in
→ middleware3
→ middleware2 out
→ middleware1 out正是这个特性让日志、计时、错误捕获等"环绕"逻辑可以一行写完。
多语言(i18n)
国际化的核心是根据请求识别语言 → 查文案表 → 替换变量。
请求语言识别优先级(典型):
- URL 参数:
?lang=zh-CN - Cookie:
lang=en - 请求头:
Accept-Language: zh-CN,en;q=0.8 - 默认 fallback
文案组织:
locales/
zh-CN.json
en-US.jsonconst dict = { 'zh-CN': { hello: '你好 {name}' }, 'en-US': { hello: 'Hello {name}' } }
const t = (lang, key, vars = {}) =>
dict[lang][key].replace(/\{(\w+)\}/g, (_, k) => vars[k])生产环境推荐 i18next + 后端中间件,支持复数、上下文、命名空间、按需加载等。
防盗链
盗链是指他人页面直接引用你的资源(图片 / 视频)造成带宽损失。Node 端实现思路:
app.use((req, res, next) => {
const referer = req.headers.referer || ''
const host = req.headers.host
if (referer && !referer.includes(host)) {
return res.redirect('/forbidden.png') // 或直接 403
}
next()
})更稳的做法是配合 Nginx valid_referers 或对资源签名(带 token + expire 的临时 URL)。
正向代理与反向代理
| 维度 | 正向代理 | 反向代理 |
|---|---|---|
| 代理对象 | 客户端 | 服务端 |
| 客户端是否感知 | 是(手动配置) | 否 |
| 典型用途 | 翻墙 / 抓包 / 隐藏客户端 IP | 负载均衡 / SSL 卸载 / 静态资源缓存 |
| 工具 | Charles、Squid | Nginx、Caddy、Node http-proxy |
Node 实现一个最小反向代理:
import http from 'node:http'
import httpProxy from 'http-proxy'
const proxy = httpProxy.createProxyServer()
http.createServer((req, res) => {
proxy.web(req, res, { target: 'http://localhost:3000' })
}).listen(80)加密和签名算法
Node 内置 node:crypto 模块覆盖了常见的密码学需求。
对称加密(同一把密钥加解密,速度快):AES 是首选,推荐 aes-256-gcm,自带认证。
import { createCipheriv, randomBytes } from 'node:crypto'
const key = randomBytes(32)
const iv = randomBytes(12)
const cipher = createCipheriv('aes-256-gcm', key, iv)
const enc = Buffer.concat([cipher.update('hello'), cipher.final()])
const tag = cipher.getAuthTag()非对称加密(公钥加密 / 私钥解密,或反之做签名):RSA、ECDSA、Ed25519。常用于身份认证、SSL 握手、JWT 的 RS256。
哈希(不可逆):SHA-256 用于完整性校验;密码存储不要直接 SHA,要用 bcrypt / scrypt / argon2,自带 salt 与慢哈希。
import { scryptSync, randomBytes } from 'node:crypto'
const salt = randomBytes(16).toString('hex')
const hash = scryptSync('password', salt, 64).toString('hex')
// 存库格式:`${salt}:${hash}`HMAC(带密钥的哈希):用于消息签名、Webhook 验签、JWT HS256。
签名算法选型经验:
- 想"防篡改 + 共享密钥"用 HMAC
- 想"防篡改 + 公钥分发"用 RSA / ECDSA / EdDSA
- 想"加密通信"用 AES (+ TLS)
- 想"存储用户密码"用 bcrypt / argon2
小结
Node 实践层的难点不在 API,而在心智模型:洋葱模型如何串中间件、JWT 如何在无状态下完成鉴权、加密算法的选型等等。把这些手写一遍是吃透它们最快的路径。
