filter和Interceptor




解释
🧠 Interceptor vs Filter:一张表讲清楚
| Interceptor | Filter | |
|---|---|---|
| 捕获什么 | ✅ 正常返回值 | ❌ 抛出的异常 |
| 触发条件 | Controller 正常 return | Service/Controller throw |
| 改造方向 | 给返回值包一层 {code,msg,data} |
把异常转成 {code,msg,data} |
| NestJS 接口 | implements NestInterceptor |
implements ExceptionFilter |
| 注册方式 | app.useGlobalInterceptors(...) |
app.useGlobalFilters(...) |
| 类比 | 工厂质检员(产品好的包装一下) | 保安(有人闹事就控制住) |
📌 完整执行流程图
kotlin
HTTP 请求进来
│
▼
Guard(鉴权)
│
▼
Pipe(参数校验)
│
▼
Controller / Service
│
├─ return ✅ ──▶ TransformInterceptor.pipe(map) ──▶ {code:0, msg:"ok", data:xxx}
│
└─ throw ❌ ──▶ AllExceptionsFilter ──▶ {code:4xx, msg:"xxx", data:null}
前端现在可以统一写:
js
// axios 响应拦截器
response => {
if (response.data.code === 0) {
return response.data.data; // 成功:取 data
} else {
throw new Error(response.data.msg); // 失败:统一抛
}
}
AOP 日志

✅ 日志全链路搞定!.log 文件自动生成
📁 生成的文件
lua
nest-1/
└── logs/
└── app-2026-09-09.log ← 每天自动切分
📋 日志文件内容一览
ini
[2026-09-09 06:19:57] [INFO] → GET /role/1 params={"id":"1"}
[2026-09-09 06:19:57] [INFO] ← GET /role/1 ✅ 9ms | return={"code":0,"msg":"ok","data":{"id":1,"name":"admin"}}
[2026-09-09 06:19:57] [INFO] → POST /user body={"name":"Dave","age":28,"roleId":1}
[2026-09-09 06:19:57] [INFO] ← POST /user ✅ 33ms | return={"code":0,"msg":"ok","data":{"id":7,"name":"Dave","age":28}}
[2026-09-09 06:19:57] [INFO] → POST /user body={"name":"X","age":99,"roleId":999}
[2026-09-09 06:19:57] [ERROR] ← POST /user ❌ 5ms | Role #999 does not exist
🔑 每个字段的含义
| 字段 | 示例 | 说明 |
|---|---|---|
[2026-09-09 06:19:57] |
时间戳 | 精确到秒 |
[INFO] / [ERROR] |
日志级别 | 成功=绿色 INFO,失败=红色 ERROR |
→ |
请求方向 | 请求进来 |
← |
响应方向 | 响应出去 |
params=... / body=... |
请求参数 | URL 参数 + body(自动脱敏 password) |
✅ 9ms |
成功耗时 | 毫秒级 |
❌ 5ms |
失败耗时 | 异常也会记录耗时 |
return=... |
返回值预览 | 最多 100 字符 |
🧠 LoggingInterceptor 的执行逻辑(RxJS 拆解)
ts
return next.handle().pipe(
// tap = "偷看一眼",数据流继续传,不改造
tap((data) => {
// 成功:算耗时 + 记录
log(`← ${method} ${url} ✅ ${duration}ms | return=${preview}`);
}),
// catchError = "抓异常",记录后重新抛出去
catchError((err) => {
log(`← ${method} ${url} ❌ ${duration}ms | ${err.message}`, 'ERROR');
return throwError(() => err); // 继续抛,让 Filter 处理
}),
);
| RxJS 操作符 | 作用 | 类比 |
|---|---|---|
tap |
副作用,不改造数据 | 工厂质检员看了一眼产品,没动手 |
map |
改造数据 | 工厂质检员把产品包装好 |
catchError |
捕获异常 | 工厂质检员发现次品,记录后扔回流水线 |
🏗️ 现在你的项目有 3 个 Interceptor + 1 个 Filter
css
请求进来
│
▼
┌───────────────────────────────────────┐
│ LoggingInterceptor(外层) │ → 记录请求开始
│ tap + catchError │ ← 记录响应/异常+耗时
└───────────────┬───────────────────────┘
▼
┌───────────────────────────────────────┐
│ TransformInterceptor(内层) │ → 不做事
│ pipe(map) │ ← 包一层 {code:0,msg:"ok",data}
└───────────────┬───────────────────────┘
▼
Controller / Service
│
┌──────────┴──────────┐
▼ ▼
return ✅ throw ❌
│ │
│ ┌──────┴──────┐
│ │ AllExceptionsFilter
│ │ 格式化 + 错误日志
│ └─────────────┘
▼ ▼
HTTP 200 HTTP 4xx/5xx
{code:0,...} {code:400,...}
📌 一句话总结
LoggingInterceptor 利用 RxJS 的 tap + catchError,不改造数据,只做"副作用"(写日志)。 和 TransformInterceptor(改造数据)配合,一个记录、一个格式化,互不干扰。日志同时输出到控制台 (彩色)和
logs/app-YYYY-MM-DD.log文件,每天自动切分------生产环境可以加个 logrotate 定时归档,目前体量小直接用就行。