在之前的篇章中介绍了 拦截器的基本概念、使用方法、使用场景;
本节主要从实战层面开发一个通用功能:统一处理接口请求与响应结果
需求:
- 统一处理接口请求与响应结果
- 可选配置(部分接口如果不需要统一处理 可配置)
第一步,全局注入拦截器
首先创建一个 transform.interceptor.ts 文件,并全局注入:
typescript
/// app.module.ts
// 省略其它代码
// 主要代码
import { APP_INTERCEPTOR } from '@nestjs/core';
import { TransformInterceptor } from './common/interceptors/transform.interceptor';
@Module({
// ...
providers: [
// 全局注入拦截器,它会作用到所有路由上
{ provide: APP_INTERCEPTOR, useClass: TransformInterceptor },
],
})
export class AppModule {}
第二步,拦截器功能实现
需要注意的点,我们需要处理
- 请求参数(前置拦截器)
- 响应结果(后置拦截器)
在之前的章节中也介绍了这两个概念。
csharp
// 首先需要下载两个相关依赖
pnpm add fastify qs
transform.interceptor.ts
实现拦截器 transform.interceptor.ts 内部逻辑:
typescript
import {
NestInterceptor,
CallHandler,
ExecutionContext,
Injectable,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core'
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import type { FastifyRequest } from 'fastify'
import qs from 'qs';
import { ResponseModel } from '../mode/response.mode';
import { BYPASS_KEY } from '../decorators/bypass.decorator';
/**
* 响应拦截器
* 用于处理响应数据
* 可以用于处理响应数据,如添加响应头,添加响应体等
*/
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor {
constructor(private readonly reflector: Reflector) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<ResponseModel<T>> {
// ==========================
// 【阶段 1:控制器执行之前】
// ==========================
// 这里的代码会立即同步执行。
// 此时请求刚到达拦截器,还没进控制器。
// ✅功能1:获取是否需要跳过拦截器
const bypass = this.reflector.get<boolean>(
BYPASS_KEY,
context.getHandler(),
)
// 如果在这里直接 return 一个 Observable (例如 return of({error: 'blocked'}))
// 而不调用 next.handle(),控制器将永远不会执行(短路)。
// 调用 next.handle() 启动控制器逻辑
// 它返回一个 Observable,代表控制器未来的执行结果(流)
if (bypass)
return next.handle()
// ✅功能2:获取请求对象
const http = context.switchToHttp()
const request = http.getRequest<FastifyRequest>()
// 处理 query 参数,将数组参数转换为数组,如:?a[]=1&a[]=2 => { a: [1, 2] }
request.query = qs.parse(request.url.split('?').at(1))
// ✅功能3:调用控制器逻辑
const response$ = next.handle();
// 【阶段 2:控制器执行之后】
// ==========================
// 这里的代码不会立即执行!
// 它们被注册为 RxJS 的"操作符",只有当控制器执行完毕并产生数据时,流才会流动到这里。
return response$.pipe(
map((data) => {
console.log('data', data);
return ResponseModel.success(data);
}),
);
}
}
response.mode.ts
它是生成 响应数据 的一个构造函数;
typescript
import { HttpStatus } from "@nestjs/common";
export class ResponseModel<T = any> {
code: number;
message: string;
data?: T;
constructor(code: number, message: string, data?: T) {
this.code = code;
this.message = message;
this.data = data ?? undefined;
}
static success<T>(data?: T) {
return new ResponseModel(HttpStatus.OK, 'success', data);
}
static error(code: number, message: string) {
return new ResponseModel(code, message, null);
}
}
bypass.decorator.ts
配置:是否使用 - 拦截器统一响应数据结构功能,亦可解释为 此拦截器功能的开关
javascript
import { SetMetadata } from '@nestjs/common';
export const BYPASS_KEY = '__bypass_key__';
/**
* 当不需要转换成基础返回格式时添加该装饰器
*/
export const Bypass = () => SetMetadata(BYPASS_KEY, true);
SetMetadata
在 NestJS 中,@SetMetadata() 是一个核心装饰器,用于向路由处理器(Controller 中的方法)或控制器类附加自定义的元数据(Metadata)。简单来说,它允许你给代码"打标签",这些标签可以在运行时被读取,从而实现灵活、声明式的逻辑控制,例如权限校验、日志记录或缓存策略等。
1. 设置元数据
你可以直接在控制器或其方法上使用 @SetMetadata('key', value) 来设置元数据。
key: 一个字符串,作为元数据的唯一标识。value: 任意类型的值,是你想要存储的数据。
示例:
kotlin
import { Controller, Get, SetMetadata } from '@nestjs/common';
@Controller('cats')
export class CatsController {
// 为单个方法设置元数据
@Get()
@SetMetadata('roles', ['admin']) // key 是 'roles', value 是 ['admin']
findAll() {
return 'This action returns all cats';
}
// 也可以为整个控制器设置元数据
@SetMetadata('isPublic', true)
@Get('public')
findPublic() {
return 'This is a public route';
}
}
设置元数据的最佳实践,如上文中我们的写法,通过一个自定义装饰,为控制器 或 方法 设置。
2. 获取元数据
设置的元数据本身是静态的,它的价值在于在运行时被动态读取。这通常在 守卫(Guards) 、拦截器(Interceptors) 或 管道(Pipes) 中完成,通过注入 Reflector 辅助类来实现。
Reflector 提供了多种方法来读取元数据,最常用的是 get()。
以上文中拦截器为例,获取元数据:
typescript
// ...
import KEY_NAME from '../****'
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor {
constructor(private readonly reflector: Reflector) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<ResponseModel<T>> {
const bypass = this.reflector.get<boolean>(
KEY_NAME,
context.getHandler(), // 获取控制器方法的元数据
)
}
}
第三步,使用
测试一下,我们再users.controller.ts 中测试使用
kotlin
import { Bypass } from '~/common/decorators/bypass.decorator';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get()
@Bypass() // 过滤全局统一响应数据拦截器
findAll() {
return this.usersService.findAll();
}
}
注意:
kotlin
const bypass = this.reflector.get<boolean>(
BYPASS_KEY,
context.getHandler(), // 获取控制器方法的元数据
)
@Bypass 装饰器只能作用在 路由处理方法 上。
总结:
以上就完成了 统一处理接口请求与响应结果 功能的开发,顺便让我们熟悉了 拦截器的用法;
再次回顾一下拦截器的使用场景:
- 请求参数统一处理:格式转换
- 响应数据统一 格式化
- 响应缓存
- 超时处理
- 数据序列化/脱敏