中间件统一做三件事:鉴权、日志、异常兜底的实际写法

2026-09-18 89 浏览 0 评论 0 字

Express 中间件把跨接口的横切逻辑抽离成可复用的能力,鉴权、日志、异常兜底是后端最典型的三件事。本文讲解中间件洋葱模型的工作原理,演示如何用鉴权拦截未登录请求、用日志记录全程耗时、用错误处理中间件统一兜底异常,让新接口一挂上路就自动获得完整防护。

问题背景

每个接口都要做鉴权、都要打日志、都要处理异常,如果这些逻辑在每个路由里复制一遍,代码会又臭又长,而且很容易出现"这个接口鉴权忘了加""那个接口异常没兜住"的疏漏。中间件(Middleware)就是用来把这些 跨接口的横切逻辑 抽出来,统一走一遍的。

原理说明

中间件的本质是 Express 里的 洋葱模型 :一个请求会依次穿过一系列中间件函数,每个函数可以执行逻辑、修改请求对象,然后调用 next() 把控制权交给下一个,也可以直接 res 响应来短路流程。以 req → m1 → m2 → m3 → handler → m3 → m2 → m1 → res 的形式回包。

三件事的定位:

  • 鉴权中间件:在 最前面 拦截未登录/无权限的请求,直接短路,别让请求往下走。
  • 日志中间件:在请求进来时记录,响应结束时记录耗时和状态码。
  • 异常兜底:放在 最后面 ,捕获前面所有中间件和路由抛出的错误,统一转成响应结构,避免崩溃。

示例代码

鉴权中间件:

// middlewares/auth.js
module.exports = function auth(req, res, next) {
  const token = req.headers['authorization'];
  const user = verifyToken(token);   // 解析并校验 token
  if (!user) {
    return res.status(401).json({ code: 401, msg: '未登录或登录已过期' });
  }
  req.user = user;   // 挂到请求对象,后续路由可以读
  next();
};

日志中间件:

// middlewares/logger.js
module.exports = function logger(req, res, next) {
  const start = Date.now();
  // 响应结束时记录耗时
  res.on('finish', () => {
    const cost = Date.now() - start;
    console.log(`${req.method} ${req.originalUrl} ${res.statusCode} ${cost}ms`);
  });
  next();
};

异常兜底:

// middlewares/errorHandler.js
module.exports = function errorHandler(err, req, res, next) {
  // 有四个参数,Express 会识别为错误处理中间件
  console.error('接口异常:', err);
  const status = err.status || 500;
  res.status(status).json({ code: status, msg: err.message || '服务器开小差了' });
};

在 app.js 组装:

const express = require('express');
const auth = require('./middlewares/auth');
const logger = require('./middlewares/logger');
const errorHandler = require('./middlewares/errorHandler');
const routes = require('./routes');

const app = express();
app.use(express.json());
app.use(logger);              // 日志放最前
app.use('/api', auth, routes); // 鉴权拦 /api 下的请求
app.use(errorHandler);        // 异常兜底放最后
module.exports = app;

常见坑与解决方案

  • 坑一:错误中间件的函数 少写了一个参数 (只写 3 个),Express 就不会当它是错误中间件,异常直接崩。解法:必须是 (err, req, res, next) 四参数,缺一不可。
  • 坑二:鉴权放错了位置,放到了路由之后,导致未登录也能进业务。解法:鉴权必须挂在具体路由前缀前,用 app.use('/api', auth, routes) 这种形式。
  • 坑三:路由里有异常但不 next(err) ,错误兜底压根接不到。解法:异步路由里用 try/catch 包裹并 next(err) ,或统一封装一个异步包装函数。
  • 坑四:日志中间件在 next() 前 res.status() 又去读,此时还没写入。解法:在 res.on('finish') 事件里取,那才是真正写完了。

小结

中间件是把横切逻辑从业务里剥离出来的利器。鉴权卡入口、日志记全程、异常兜底,三条线各司其职,任何新接口只要挂在路由组下,就自动享受到这三个能力,再也不用担心漏写。接下来要聊的全局配置,能把中间件里的常量读到一个统一的地方去。

FAQ 常见问题

Q:错误处理中间件为什么写了却不生效? A:最典型的原因是把参数写错了。Express 判断一个中间件是不是错误处理中间件,靠的是函数签名的四参数 (err, req, res, next) ,少了任何一个参数(尤其漏了 err )就不会被当成错误兜底,异常会直接冒泡崩进程。

Q:鉴权中间件到底该放在哪一层? A:放在具体路由前缀之前,用 app.use('/api', auth, routes) 这种方式,这样 /api 下的所有请求都会先过鉴权,未登录的根本进不了业务,才能从入口拦住。

Q:日志中间件里为什么拿到的状态码不对? A:因为响应在调用 next() 时还没真正写完。要在 res.on('finish') 事件里取状态码和耗时,那才是响应真正落盘的时刻。


发布评论

发布评论前请先 登录。
0 评论
点赞
收藏

评论列表 0

暂无评论