第 181 章:中间件(Middleware)
学习目标
- 理解 Middleware 的作用
- 掌握函数式与类式中间件
- 学会常用场景(请求日志、CORS、压缩)
- 理解中间件执行顺序
一、什么是 Middleware
Middleware 是请求进入路由处理器之前执行的函数,可以访问 request 和 response。
二、函数式中间件
2.1 定义
typescript
// logger.middleware.ts
import { Request, Response, NextFunction } from 'express';
export function logger(req: Request, res: Response, next: NextFunction) {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next();
}2.2 使用
typescript
// app.module.ts
@Module({
imports: [],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(logger).forRoutes('*'); // 所有路由
}
}三、类式中间件
3.1 定义
typescript
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next();
}
}3.2 注册
typescript
@Module({})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggerMiddleware).forRoutes('*');
}
}提示
类式中间件可以注入依赖(@Injectable()),适合复杂场景。
四、路由匹配
4.1 路径匹配
typescript
consumer.apply(LoggerMiddleware).forRoutes('users'); // /users
consumer.apply(LoggerMiddleware).forRoutes('users/*'); // /users/xxx
consumer.apply(LoggerMiddleware).forRoutes('*'); // 所有4.2 控制器匹配
typescript
consumer.apply(AuthMiddleware).forRoutes(UserController);
consumer.apply(LoggerMiddleware).forRoutes(UserController, OrderController);4.3 指定方法
typescript
import { RequestMethod } from '@nestjs/common';
consumer
.apply(AuthMiddleware)
.forRoutes({ path: 'users', method: RequestMethod.GET });4.4 排除路由
typescript
consumer
.apply(LoggerMiddleware)
.exclude(
{ path: 'health', method: RequestMethod.GET },
'auth/login',
)
.forRoutes('*');五、多个中间件
5.1 顺序执行
typescript
consumer.apply(Middleware1, Middleware2, Middleware3).forRoutes('*');
// 执行顺序:Middleware1 → Middleware2 → Middleware3 → Controller5.2 不同路径不同中间件
typescript
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes('*')
.apply(AuthMiddleware)
.forRoutes('users/*')
.apply(AdminMiddleware)
.forRoutes('admin/*');
}六、常用中间件实战
6.1 请求 ID 中间件
typescript
import { v4 as uuidv4 } from 'uuid';
@Injectable()
export class RequestIdMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const id = req.headers['x-request-id'] || uuidv4();
req['requestId'] = id;
res.setHeader('X-Request-Id', id);
next();
}
}
// 在 Controller 中使用
@Get()
findAll(@Req() req: Request) {
console.log(req['requestId']);
}6.2 请求体大小限制
typescript
@Injectable()
export class BodySizeMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const contentLength = parseInt(req.headers['content-length'] || '0', 10);
if (contentLength > 1024 * 1024) { // 1MB
return res.status(413).json({ message: '请求体过大' });
}
next();
}
}6.3 简易 API Key 校验
typescript
@Injectable()
export class ApiKeyMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const apiKey = req.headers['x-api-key'];
if (apiKey !== process.env.API_KEY) {
return res.status(401).json({ message: 'Invalid API Key' });
}
next();
}
}6.4 简易访问日志
typescript
@Injectable()
export class AccessLogMiddleware implements NestMiddleware {
private logger = new Logger('HTTP');
use(req: Request, res: Response, next: NextFunction) {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
this.logger.log(
`${req.method} ${req.url} ${res.statusCode} ${duration}ms`,
);
});
next();
}
}6.5 简易 Rate Limit
typescript
@Injectable()
export class RateLimitMiddleware implements NestMiddleware {
private visits = new Map<string, number[]>();
use(req: Request, res: Response, next: NextFunction) {
const ip = req.ip;
const now = Date.now();
const window = 60_000; // 1 分钟
const limit = 100;
const times = (this.visits.get(ip) || []).filter(t => now - t < window);
if (times.length >= limit) {
return res.status(429).json({ message: 'Too many requests' });
}
times.push(now);
this.visits.set(ip, times);
res.setHeader('X-RateLimit-Limit', String(limit));
res.setHeader('X-RateLimit-Remaining', String(limit - times.length));
next();
}
}七、全局中间件 vs 路由中间件
| 方式 | 用法 |
|---|---|
| 路由级 | consumer.apply(...).forRoutes(...) |
| 全局(注册) | app.use(LoggerMiddleware) |
| 全局(类) | APP_MIDDLEWARE Provider |
7.1 全局函数中间件
typescript
// main.ts
const app = await NestFactory.create(AppModule);
app.use(cors()); // cors 库
app.use(helmet()); // helmet 库
app.use(compression()); // compression 库
app.use(express.json({ limit: '1mb' }));7.2 全局类中间件
typescript
@Module({
providers: [
{
provide: APP_MIDDLEWARE,
useClass: LoggerMiddleware,
},
],
})
export class AppModule {}八、第三方中间件
8.1 CORS
bash
pnpm add corstypescript
// main.ts
import cors from 'cors';
app.use(cors({
origin: ['http://localhost:5173'],
credentials: true,
}));8.2 Helmet(安全头)
bash
pnpm add helmettypescript
// main.ts
import helmet from 'helmet';
app.use(helmet());8.3 Compression(压缩)
bash
pnpm add compressiontypescript
// main.ts
import compression from 'compression';
app.use(compression());8.4 Cookie Parser
bash
pnpm add cookie-parsertypescript
// main.ts
import cookieParser from 'cookie-parser';
app.use(cookieParser('secret'));九、中间件 vs Guard vs Interceptor
| 类型 | 时机 | 可访问 |
|---|---|---|
| Middleware | 路由前 | req, res, next |
| Guard | 路由前 | ExecutionContext |
| Interceptor | 路由前后 | ExecutionContext, RxJS |
| 功能 | 推荐 |
|---|---|
| 请求日志 | Middleware |
| 鉴权 | Guard |
| 响应转换 | Interceptor |
| 请求体解析 | Middleware |
十、本章小结
| 用法 | 关键 |
|---|---|
| 函数中间件 | app.use(fn) |
| 类中间件 | consumer.apply(Middleware).forRoutes() |
| 路径匹配 | 'users/*' / '*' |
| 方法匹配 | RequestMethod.GET |
| 全局注册 | APP_MIDDLEWARE |
| 常用三方 | cors / helmet / compression |
动手练习
- 实现一个请求日志中间件
- 写一个 RateLimit 中间件,限制 60s 内最多 100 次
- 用 helmet + cors + compression 配置 main.ts
- 实现一个请求 ID 中间件