API拦截器全链路实战:axios与Express中间件深度解析

发布时间:2026/10/18 13:43:07
API拦截器全链路实战:axios与Express中间件深度解析 简介面向Windows平台开发者的完整全局API拦截示例工程尤其适合理解Hook机制、从事核心编程与安全研究的开发者。压缩包内共35个文件主要包含C源文件.cpp、头文件.h、工程配置.vcproj、.sln、资源脚本.rc、.ico并附带Debug目录下的.obj、.pdb、.manifest等编译产出可对照工程结构查看构建细节整个包仅246KB轻量易下载。实例以“LastMsgBoxInfoLib”动态库与“LastMsgBoxInfo”可执行程序两个子项目构成完整Demo演示如何利用全局API钩子拦截消息框相关调用进而监控和修改系统弹窗行为。源码中可学习SetWindowsHookEx等核心API的用法、DLL注入思路及消息流拦截切入点并可直接复用工程进行二次扩展。目前已有230人学习下载示例短小而技术点密集是Windows核心编程和API拦截入门的实用参考。1. 一个联调三天才发现的坑API 拦截器不是可选项做前端的人大概都经历过这种晚上后端说接口已经联调完了你一发请求就 404再看报文发现请求压根没到业务路由或者数据回来了但结构跟你撕了一下午的文档差了整整一个壳。我当时遇到的是第三种情况——接口能通但返回体里多了一层包装前端解析逻辑全崩。查了整整三天最后才定位到是网关层一个响应拦截器偷偷改了结构。从那时候起我就明白一件事API 拦截器不是联调时临时加的补丁它就是请求链路本身的一部分谁不理解这一层谁就要在半夜排查时交学费。这份完整源码就是一个把「请求从发起、拦截、改造、放行、响应再拦截」全链路做透的例子前端 axios 拦截器、后端 Express 中间件、Mock 数据拦截、日志与签名校验全都有。想跑通一次完整拦截链、想在你自己的工程里加拦截层的开发者这份能直接抄作业。2. 整体架构一次请求从发起到返回要穿过几层拦截2.1 目录结构前后端拦截器分开放但共用一套约定拿到源码先别急着 npm install花五分钟把目录看一遍后面改起来会顺手很多。整个工程就一个 npm 包没有拆 monorepo前端和后端都在同一个仓库里靠文件夹隔离api-interceptor-demo/ ├── package.json ├── index.js ├── server/ │ ├── app.js │ ├── interceptors/ │ │ ├── logger.js │ │ ├── signature.js │ │ ├── response.js │ │ ├── error.js │ │ └── index.js │ ├── routes/ │ │ ├── user.js │ │ └── health.js │ └── config.js ├── client/ │ ├── request.js │ ├── mock/ │ │ ├── user.mock.js │ │ └── index.js │ └── demo.js └── scripts/ └── smoke-test.shserver 下 interceptors 是后端拦截器集合所有拦截器都实现同一个接口下文会细讲最终在 index.js 里统一编排进 Express 应用。client 下 request.js 是 axios 实例封装mock 目录是前端本地 mock 数据源demo.js 是一个可直接 node 运行的客户端脚本用来模拟浏览器发请求。前端和后端虽然实现语言不同但拦截器的核心抽象是一样的每个拦截器负责一件事多个拦截器串成一条链前一个处理完把控制权交给下一个。理解了这条链整个项目就理解了一半。2.2 拦截器接口设计为什么所有拦截器都长一个样后端拦截器全部遵循同一个签名这是整个设计的骨架。每个拦截器是一个工厂函数返回一个 async 函数接收 ctx 和 next 两个参数// server/interceptors/demo.js // 展示拦截器统一签名实际项目中不必实现这个文件 module.exports function createDemoInterceptor(options) { // options 是配置项比如开关、白名单 return async function demoInterceptor(ctx, next) { // 请求进入时要做的事 console.log([demo] ${ctx.req.method} ${ctx.req.url} 进入拦截器); // 调用 next 放行到下一个拦截器或业务路由 await next(); // 响应返回时要做的事 console.log([demo] 响应已经发出状态码 ${ctx.res.statusCode}); }; };ctx 对象里挂了 req、res、请求开始时间、日志实例等上下文数据next 是放行动作await next() 之前是请求前逻辑之后是响应后逻辑。这样的设计让每个拦截器可以独立测试、独立开关也可以调整顺序而不影响彼此逻辑。参数说明createDemoInterceptor 的 options 一般传 enabled是否启用、whitelist跳过拦截的路径白名单、logger日志实例。拦截器内部不要直接操作 res 返回结果除非你是响应格式化拦截器——否则就破坏了单一职责排查问题时会很痛苦。2.3 请求生命周期一个请求在生产环境里到底被谁动过把这个项目的启动流程跑起来后一个带签名参数 POST 请求从进入到返回会依次经过这几个节点顺序位置拦截器 / 环节职责1Express 入口全局中间件解析 JSON body、记录请求开始时间2拦截器链logger.js打印请求方法与路径、统计耗时3拦截器链signature.js校验时间戳与签名失败直接 return 4014拦截器链response.js给 res 挂上统一成功/失败方法5拦截器链error.jstry-catch 包裹后续全部逻辑异常统一兜底6业务路由routes/user.js真正处理业务返回数据7响应返回response.js将业务数据包成{ code, msg, data }结构输出这个顺序不是随便排的日志最外层是为了记录所有请求签名必须靠前校验不通过直接拦截掉不浪费后续计算response.js 在 error.js 之前挂载是因为 error 拦截器内部也要用统一格式返回异常业务路由永远在最内层。理解了这张表你就知道了「API 拦截」四个字的全部含义后面所有代码都是在填充这张表。3. 服务端拦截链日志、签名、格式化那套完整实现3.1 入口与编排拦截器链是怎么拼起来的拦截器按顺序拼进 Express 的过程在 server/interceptors/index.js 里这里决定链的顺序。完整代码如下// server/interceptors/index.js const createLoggerInterceptor require(./logger); const createSignatureInterceptor require(./signature); const createResponseInterceptor require(./response); const createErrorInterceptor require(./error); function registerInterceptors(app, config) { // 注意顺序先日志再签名再响应格式化最后兜底异常 app.use(createLoggerInterceptor({ logger: config.logger })); app.use(createSignatureInterceptor({ enable: config.signature.enable, secret: config.signature.secret, whitelist: [/api/health] })); app.use(createResponseInterceptor()); app.use(createErrorInterceptor()); // 业务路由一定要在拦截器全部注册之后再挂载 const userRouter require(../routes/user); const healthRouter require(../routes/health); app.use(/api/user, userRouter); app.use(/api/health, healthRouter); } module.exports registerInterceptors;这段代码的关键在于 Express 中间件是从上到下顺序匹配的一旦某个拦截器不给 next()后面的所有拦截器和路由都不会执行。所以顺序决定行为签名拦截器如果发现没有签名直接 res.status(401).end()业务层根本不会感知。这里有多个拦截器对响应一次修改容易出现响应已发送后二次 setHeader 的报错后面的踩坑章节里会展开说。参数说明whitelist 是字符串数组signature.js 内部会用 req.path 做前缀匹配命中就直接放行。如果你的项目里健康检查路径变了记得同步改这里。3.2 日志拦截器记录每一个请求的耗时与状态日志是排查问题最基础的依赖也是最容易被写坏的拦截器。我在这份代码里把日志拦截器写成了独立模块核心如下// server/interceptors/logger.js module.exports function createLoggerInterceptor(options {}) { const logger options.logger || console; return async function loggerInterceptor(ctx, next) { const start Date.now(); const { method, url } ctx.req; const requestId ctx.req.headers[x-request-id] || req_${start}_${Math.random().toString(16).slice(2, 8)}; ctx.requestId requestId; // 后续所有拦截器与业务代码都可以拿它做日志关联 logger.info([${requestId}] ${method} ${url} 开始); await next(); // 放行等待后续链路执行完 const duration Date.now() - start; const status ctx.res.statusCode; logger.info([${requestId}] ${method} ${url} 结束状态 ${status}耗时 ${duration}ms); }; };设计要点有两个。第一requestId 从请求头 x-request-id 里取取不到就自己生成一个以时间戳开头的随机串这样每次请求的日志都能串成一条完整链路前端传的 requestId 后端也能接住第二await next() 前后是天然的分界线前是请求阶段后是响应阶段耗时统计放在后面可以完整包含业务处理时间。注意一个细节ctx.res.statusCode 读取的是最终状态码但如果后续拦截器里出现未捕获异常statusCode 可能还是 200所以真正的错误状态码统计应当在 error 拦截器里做二次标记。日志拦截器只负责记录不负责判断业务对错。3.3 签名拦截器校验失败直接拦截不让请求进业务层服务端 API 一定要做防伪造校验这份例子用一个简单的时间戳 HMAC 签名方案代码不依赖额外库// server/interceptors/signature.js const crypto require(crypto); module.exports function createSignatureInterceptor(options {}) { const enabled options.enable ! false; const secret options.secret || dev_secret; const whitelist options.whitelist || []; return async function signatureInterceptor(ctx, next) { if (!enabled) { return next(); } const { path } ctx.req; if (whitelist.some((prefix) path.startsWith(prefix))) { return next(); // 白名单直接放行 } const timestamp ctx.req.headers[x-timestamp]; const signature ctx.req.headers[x-signature]; const method ctx.req.method; // 时间戳超过 300 秒视为过期请求防止重放 if (!timestamp || Math.abs(Date.now() - Number(timestamp)) 300 * 1000) { ctx.res.status(401).json({ code: 401, msg: 请求时间戳无效或已过期 }); return; } // 签名内容 method path timestamp字符串拼接后做 sha256 const raw ${method}${path}${timestamp}; const expect crypto.createHmac(sha256, secret).update(raw).digest(hex); if (signature ! expect) { ctx.res.status(401).json({ code: 401, msg: 签名校验失败 }); return; } await next(); }; };签名这块最开始我用 sha1后来被安全评审打回来说强度不够改成 sha256。实践里要注意的坑是参与签名的字符串必须和客户端约定好多一个字符少一个字符结果就完全不同。比如 method 大小写、path 带不带 query都要在注释里写清楚否则前后端会互相折磨。这里的 raw 用的是method path timestamp不含 query 参数和 body。好处是实现简单坏处是 query 参数可以被篡改后重放。如果要更严格可以把 query 排序后拼进签名串代价是客户端代码跟着复杂。demo 场景用这个级别够用了。3.4 响应格式化拦截器给所有接口统一包一层壳前后端撕扯最多的地方就是返回结构。这份代码用一个拦截器给所有成功响应统一包装凡是 res.ok(data) 的调用最终输出{ code: 0, msg: ok, data: ... }// server/interceptors/response.js module.exports function createResponseInterceptor() { return async function responseInterceptor(ctx, next) { // 在 res 上挂两个便捷方法业务路由直接用 ctx.res.ok function (data, msg ok) { if (ctx.res.headersSent) { // 响应已经开始发送时直接 end避免二次 setHeader return ctx.res.end(); } ctx.res.status(200).json({ code: 0, msg, data }); }; ctx.res.fail function (code 500, msg error, data null) { if (ctx.res.headersSent) { return ctx.res.end(); } ctx.res.status(200).json({ code, msg, data }); }; await next(); }; };注意 res.fail 里 HTTP 状态码仍然返回 200只是业务 code 非 0。这是国内团队联调时很常见的约定好处是前端 fetch 不会把业务报错当成网络异常坏处是一旦前端忘了判断 code就会出现「明明报错了却走成功回调」的玄学问题。我一般会选择「HTTP 状态码与业务 code 保持一致」的策略但这份源码为了兼容更多团队习惯保留了 200 code 的模式你在二次开发时按团队约定改一处即可。headersSent 的判断是我后来补的因为出现过登录接口里 res.ok() 被调用两次的情况——业务路由自己写了一个 res.json()上面拦截器又补了一次导致 Express 直接抛 ERR_HTTP_HEADERS_SENT。加了判断后只要响应已经发出后续的格式化调用自动变成 no-op。3.5 异常兜底拦截器业务代码抛错时不再返回空白 500最内层的业务代码如果抛了异常会被 error 拦截器接住保证客户端拿到的永远是一个结构完整的 JSON而不是一个干巴巴的 500 页面// server/interceptors/error.js module.exports function createErrorInterceptor(options {}) { const logger options.logger || console; return async function errorInterceptor(ctx, next) { try { await next(); } catch (err) { logger.error([请求异常] ${ctx.req.method} ${ctx.req.url}, err); if (!ctx.res.headersSent) { ctx.res.fail(500, 服务器内部错误); } else { // 响应已经开始无法再发送 JSON只能直接断开连接 ctx.res.end(); } } }; };这里最重要的判断仍然是 headersSent。如果异常发生时响应已经发出了一部分再用 res.fail() 就会二次 setHeader这时候唯一能做的就是直接 end()告诉客户端连接被异常中断。这个「异常可能发生在写响应之后」的场景是排查线上问题时最容易忽略的后面避坑章会再提。还有一点容易忽略error 拦截器只能兜住同步和 await 链上的异常。如果业务代码里有人写了不 await 的 Promise错误就会变成 unhandledRejection这种谁都拦不住只能在入口加 process 级别的兜底监听。4. 客户端拦截与 Mock 联调没后端也能把页面跑起来4.1 axios 拦截器请求加密头与响应统一解包后端做了签名校验前端就不能只发裸请求了。client/request.js 里对 axios 做了二次封装请求拦截器负责加签名头响应拦截器负责统一解包// client/request.js const axios require(axios); const crypto require(crypto); const SECRET dev_secret; const instance axios.create({ baseURL: http://localhost:3000, timeout: 10000 }); // 请求拦截器每个请求自动附带时间戳与 HMAC 签名 instance.interceptors.request.use((config) { const timestamp Date.now().toString(); const raw ${config.method.toUpperCase()}${config.url.split(?)[0]}${timestamp}; const signature crypto.createHmac(sha256, SECRET).update(raw).digest(hex); config.headers[x-timestamp] timestamp; config.headers[x-signature] signature; return config; }, (error) Promise.reject(error)); // 响应拦截器把 { code, msg, data } 的外层壳剥掉 instance.interceptors.response.use((response) { const body response.data; if (body body.code 0) { return body.data; // 业务成功直接返回 data 给调用方 } // 业务失败把整个 body 抛出去调用方在 catch 里拿 return Promise.reject(new Error(body body.msg ? body.msg : 业务处理失败)); }, (error) { if (error.response error.response.status 401) { // 这里是签名失败的统一处理入口 return Promise.reject(new Error(请求签名校验失败请检查本地时间是否准确)); } return Promise.reject(error); }); module.exports instance;这段代码有两个关键点。第一签名串的拼接规则必须和后端 signature.js 保持一致比如config.method在 axios 里默认是小写代码里我调用了toUpperCase()后端也要按大写校验否则签名永远对不上。第二响应拦截器把 code 为 0 的响应「剥壳」后直接返回 data这样业务代码里拿到的就是干净的数据解耦效果明显。timeout 设为 10 秒是给弱网环境留的余量。如果你在公司内网调试建议改成 5 秒排错时反馈更快但不要改成 1 秒签名校验偶发的网络抖动会让你们误以为拦截器写错了这种排查最冤枉。4.2 Mock 拦截命中规则就直接造假数据请求根本不出网联调最常见的卡点是后端还没写好前端没有数据可用。这份源码的 Mock 方案是拦截器链里的一个特殊分支——请求发起前先查 Mock 规则表// client/mock/index.js const userMock require(./user.mock); // 规则表key 是「METHOD /path」的字符串value 是假数据或制造假数据的函数 const mockTable { GET /api/user/info: userMock.getUserInfo, POST /api/user/update: { code: 0, msg: ok, data: { updated: true } }, }; function matchMock(config) { const key ${(config.method || get).toUpperCase()} ${config.url.split(?)[0]}; return mockTable[key] || null; } module.exports { matchMock };这个模块不关心你怎么发请求它只做一件事根据方法的 URL 返回命中的 mock 数据。真正拦截动作发生在 request.js 里我把它设计成了「先用 mock 规则再走真实请求」的优先级模式// client/request.js 中加入 mock 开关 const { matchMock } require(./mock/index); const ENABLE_MOCK process.env.MOCK true; // 命令行里设了 MOCKtrue 才开启 instance.interceptors.request.use((config) { if (ENABLE_MOCK) { const mock matchMock(config); if (mock) { // 给 config 挂一个自定义标记响应拦截器识别到后直接走 mock 分支 config.mockData typeof mock function ? mock() : mock; } } // 这里省略前一小节的签名逻辑 return config; });把 mock 逻辑放在请求拦截器里而不是单独包一层 axios 适配器是我后来重构的结果。最初我写过一个 mocker 适配器确实更干净但团队里别人接手时理解成本太高现在改成在同一个拦截器里判断逻辑集中在请求入口附近一眼就能看明白开关在哪。使用方式非常简单MOCKtrue node client/demo.js后端没启动也能拿到数据后端联调时删掉 MOCK 环境变量即可。这个开关没有写死在代码里而是通过环境变量控制就是在生产环境误跑时能有一条后悔药。4.3 客户端调试入口demo.js 里演示了三种拦截场景demo.js 是拿来即用的调试脚本它演示了三种场景命中 mock、走真实请求成功、走真实请求但签名过期// client/demo.js const request require(./request); async function main() { // 场景一mock 命中 const userInfo await request.get(/api/user/info); console.log(mock 命中userInfo, userInfo); // 场景二真实请求 正确签名 try { const updated await request.post(/api/user/update, { nickname: new_name }); console.log(更新成功updated, updated); } catch (err) { console.error(更新失败, err.message); } // 场景三真实请求 手动伪造过期时间戳验证签名拦截 const axios require(axios); const crypto require(crypto); const fakeTimestamp (Date.now() - 600000).toString(); const raw GET/api/user/info${fakeTimestamp}; const fakeSign crypto.createHmac(sha256, dev_secret).update(raw).digest(hex); try { await axios.get(http://localhost:3000/api/user/info, { headers: { x-timestamp: fakeTimestamp, x-signature: fakeSign } }); } catch (err) { console.error(预期中的签名失败, err.response.status, err.response.data.msg); } } main();场景三值得多说一句它不是为了演示功能而是给你一个验证拦截器顺序是否正常的测试方法。如果你改了签名算法先跑一遍场景三确认服务端会返回 401 且 msg 是「签名校验失败」再跑场景二确认正确签名能通过这一正一反两个用例能挡掉八成签名改动引入的回归问题。5. 运行环境与常见排查启动命令、验证脚本、避坑记录5.1 环境准备与启动从零开始跑通需要哪些步骤源码对运行环境的要求非常低Node.js 12 及以上、npm 6 及以上即可不需要数据库不需要 Redis所有数据都是内存态模拟。首次运行按下面三步走# 第一步安装依赖 npm install # 第二步启动后端服务默认监听 3000 端口 node index.js # 第三步另开一个终端跑客户端 demo # 先跑带 mock 的版本不需要后端也能出数据 MOCKtrue node client/demo.js # 再跑不带 mock 的真实请求版本 node client/demo.jsindex.js 入口文件内容很薄只做一件事创建 Express 应用、读取配置、注册拦截器和路由、监听端口。追求的就是「一眼看完启动链路」的效果你在这个项目里学的任何套路换到你自己的工程里都能照搬。服务端启动后会看到一行日志提示当前签名开关的状态这是我在 config.js 里加的显式输出避免排查问题时搞不清自己当前跑的是哪种配置模式[config] signature.enable true, secret dev_secret [config] mock false 服务已启动: http://localhost:3000看到[config]两行日志说明配置加载正常。如果没看到大概率是 config.js 被你改坏了先检查有没有语法错误。5.2 验证拦截是否生效一条命令五分钟端到端验证启动不等于能跑通我给你写了一个完整的端到端验证脚本 scripts/smoke-test.sh它做的事依次是检查端口是否监听、用正确签名调一次真实接口、用错误签名调一次、确认四组预期日志全部出现#!/usr/bin/env bash set -e BASEhttp://localhost:3000 echo 1. 检查服务端口 curl -s $BASE/api/health || { echo 服务未启动; exit 1; } echo 2. 正确签名调用 TIMESTAMP$(date %s000) RAWGET/api/user/info${TIMESTAMP} SIGN$(echo -n $RAW | openssl dgst -sha256 -hmac dev_secret | awk {print $2}) curl -s -H x-timestamp: $TIMESTAMP -H x-signature: $SIGN $BASE/api/user/info echo echo 3. 错误签名调用 curl -s -o /dev/null -w HTTP状态码: %{http_code}\n -H x-timestamp: $TIMESTAMP -H x-signature: deadbeef $BASE/api/user/info脚本里的签名生成用了 openssl 命令它和后端 crypto 算法的结果是等价的你可以放心用。如果你在 Windows 上跑不了 bash就把 same计算逻辑手工改成 Node 一行式node -e console.log(require(crypto).createHmac(sha256,dev_secret).update(GET/api/user/infoprocess.argv[1]).digest(hex)) $(date %s000)这两个验证脚本的价值在于你改完任何拦截器代码后跑一次 smoke-test就知道哪些环节被破坏。我自己的习惯是每次改完代码先跑脚本再 postman 手测脚本过了说明基本链路是好的。5.3 配置项说明哪些参数要改、哪些打死都不要动config.js 是这份源码里唯一跟部署环境相关的文件我把常用配置整理成一张表配置项默认值说明建议port3000服务监听端口生产环境建议从环境变量注入signature.enabletrue是否开启签名校验内网调试可临时关掉signature.secretdev_secretHMAC 密钥生产环境必须换成强随机值signature.expireMs300000时间戳过期毫秒数建议 300 到 600 秒logger.levelinfo日志级别生产环境可调成 warn 减少噪音最容易被忽略的是 signature.secret我没有把它放进 .env 文件是因为这个 demo 定位是教学性质。你自己的项目里secret 一定要从环境变量或配置中心读取绝对不能硬编码进仓库否则等于把签名机制完全废掉。logger.level 控制日志的详细程度排查问题时用 info正常运行时用 warn。如果你发现日志刷屏先看是不是这个配置停在 info而不是急着改代码。5.4 避坑记录我在这个项目里踩过的五个典型坑第一个坑是 CORS 预检请求 404。现象前端浏览器访问接口控制台报 CORS 错误但用 curl 请求后端完全正常。原因浏览器发 POST 时会先发一个 OPTIONS 预检请求后端没有对 OPTIONS 做处理请求在拦截器链里被签名校验拦下来返回 401浏览器认为服务端不允许跨域。解决在拦截器注册之前加一个全局的 OPTIONS 快速放行并正确设置 Access-Control-Allow-Headers把 x-timestamp 和 x-signature 都加进去。第二个坑是响应被 JSON.stringify 了两次。现象接口返回的 data 字段是一个字符串里面又是一段 JSON前端 JSON.parse 报错。原因某个拦截器里先对对象做了 JSON.stringify后面的响应格式化又对它做了一次 stringify 把字符串转义了。解决不要在拦截器里手工 stringify统一交给 res.json() 处理stringify 只发生在数据库入库或日志输出的场景里。第三个坑是拦截器链死等请求挂起不返回。现象接口偶尔能通偶尔超时没有规律。原因某个拦截器的 next() 被 return 后后面的拦截器执行完没回到调用链比如签名拦截器里return next()写成了next()而下一层的拦截器又用了 await导致 Promise 链断掉。解决所有拦截器的放行必须统一写成return next()或await next()不要在两种风格之间混用我见过一个团队因为混用排查了整整一天。第四个坑是 headersSent 导致的二次响应报错。现象当请求已经写了一半响应时业务抛异常异常拦截器再调 res.fail()控制台报 ERR_HTTP_HEADERS_SENT。原因后端逻辑里在响应中间态时中断了业务比如流式输出时抛错。解决异常拦截器里加 headersSent 判断已经发了响应就直接 end没发才走统一格式化。这个坑在我项目里出现过两次之后我凡是写拦截器都条件反射式加这道判断。第五个坑是 mock 数据忘了关就上了生产。现象前端联调一切正常后端上线后数据死活不对有人发现返回的数据像假数据。原因客户端某条 axios 封装里 mock 开关没关闭环境变量 MOCKtrue 被带到了生产构建流程。解决mock 开关必须通过环境变量注入并在构建时注入MOCKfalse且代码里要有明显的日志输出告知当前是否处于 mock 模式避免悄无声息地活在假数据里。这五个坑每一个都是真实流血流出来的经验光看不会长记性建议你故意改错代码跑一遍亲眼看到报错现象以后定位问题时反应会快很多。6. 二次开发扩展把你的业务塞进拦截链的通用套路拿到这份源码后最常做的改造不是改签名算法而是往链路中间插入新的拦截器。先说套路先实现一个新拦截器模块再把它注册进 server/interceptors/index.js 里顺序决定作用范围。比如你公司要求所有写接口记录操作人可以写一个 audit 拦截器放在 signature.js 之后// server/interceptors/audit.js module.exports function createAuditInterceptor(options {}) { const store options.store || []; // 内存存储生产环境换成数据库 return async function auditInterceptor(ctx, next) { await next(); // 等业务执行完拿到结果再记录 const body ctx.res.body || ctx.res.locals.body; store.push({ path: ctx.req.path, method: ctx.req.method, operator: ctx.req.headers[x-operator] || unknown, time: new Date().toISOString(), status: ctx.res.statusCode, body, }); logger.info([audit] 写入一条操作记录); }; };进阶玩法是把拦截器变成一个「流量复制器」在 await next() 之前克隆请求体在 next() 之后把同样的请求异步转发到影子环境用来验证新老接口的响应差异这个场景在 API 网关和全链路压测里很常见。实现上只需要在拦截器里加一个转发函数不修改任何业务路由代码。另一个值得动手的方向是改造响应结构从「HTTP 200 业务 code」改为「REST 语义的 HTTP 状态码」你要动的是 response.js 里 res.ok 和 res.fail 的状态码逻辑以及客户端 request.js 响应拦截器的解包判断两端同时改才能保持一致。从那以后我每次往工程里加拦截器都强制走一遍同一套流程先确定放行条件白名单还是全量再确认放行方式return next 还是 await next最后用 smoke-test 脚本验证拦截器前后的日志输出没有断链。这套流程替我挡掉了至少四次线上事故。希望你也能在改这份源码时养成这个习惯希望这套拦截器设计思路对你有用。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询