响应数据统一格式化
全局拦截:后置拦截,拦截数据并统一数据格式
跳过格式化:比如文件上传等接口需要跳过格式化,借助前置拦截器和元信息实现
自定义提示语:以后置拦截器和元信息的方式实现自定义提示语 :::
1、定义拦截器
ts
// src/common/interceptors/transform.interceptor.ts
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { ApiResponse } from '../types/response.type';
import { SKIP_TRANSFORM } from '../decorators/skip-transform.decorator';
import { RESPONSE_MESSAGE } from '../decorators/response-message.decorator';
@Injectable()
export class TransformInterceptor<T>
implements NestInterceptor<T, ApiResponse<T>>
{
constructor(private reflector: Reflector) {} // 注入Reflector
intercept(
context: ExecutionContext,
next: CallHandler,
): Observable<ApiResponse<T>> {
// 前置拦截判断是否标记了跳过包装
const skip = this.reflector.get<boolean>(
SKIP_TRANSFORM,
context.getHandler(),
);
if (skip) {
return next.handle(); // 直接返回原始数据
}
const message =
this.reflector.get<string>(RESPONSE_MESSAGE, context.getHandler()) ||
'success';
// 后置拦截包装:统一响应数据格式
return next.handle().pipe(
map((data) => ({
code: 200, // 正常响应默认业务码200
message,
path: context.switchToHttp().getRequest().path,
method: context.switchToHttp().getRequest().method,
data: data ?? null, // 处理data为undefined的情况
timestamp: Date.now(),
})),
);
}
}2、定义格式化类型
ts
// src/common/types/response.type.ts
/**
* 统一响应格式
*/
export interface ApiResponse<T = any> {
code: number;
message: string;
data: T;
path: string;
method: string;
timestamp: number;
error?: string;
stack?: string;
}3、跳过格式化
定义元信息
ts
// src/common/decorators/skip-transform.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const SKIP_TRANSFORM = 'SKIP_TRANSFORM';
export const SkipTransform = () => SetMetadata(SKIP_TRANSFORM, true);4、自定义提示语
定义元信息
ts
// src/common/decorators/response-message.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const RESPONSE_MESSAGE = 'RESPONSE_MESSAGE';
export const ResponseMessage = (message: string) => SetMetadata(RESPONSE_MESSAGE, message);5、注册全局拦截器
ts
import { TransformInterceptor } from './common/interceptors/transform.interceptor';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 注册全局拦截器
app.useGlobalInterceptors(new TransformInterceptor(app.get(Reflector)));
app.setGlobalPrefix('api/v1');
await app.listen(3000);
}
bootstrap();测试
- 自定义提示语&数据格式化测试
shell
# 接口请求
http://localhost:3000/api/v1/user/2
# 在 src/user/user.controller.ts 中,给路由注入提示语元信息
import { ResponseMessage } from '../common/decorators/response-message.decorator';
@Get(':id')
@ResponseMessage('获取用户成功') # 自定义提示语
async findOne(@Param('id') id: number) {
return await this.userService.findOne(id);
}
# 结果
{
"code": 200,
"message": "获取用户成功",
"path": "/api/v1/user/2",
"method": "GET",
"data": {
"id": 2,
"username": "lisi",
"version": 1
},
"timestamp": 1765269321713
}- 跳过格式化测试
shell
# 接口请求
http://localhost:3000/api/v1/user
# 在 src/user/user.controller.ts 中,给路由注入跳过格式化元信息
import { SkipTransform } from '../common/decorators/skip-transform.decorator';
@Get()
@SkipTransform() # 跳过响应数据格式化
async findAll() {
return await this.userService.findAll();
}
# 结果
[
{
"id": 2,
"username": "lisi",
"version": 1
},
{
"id": 3,
"username": "alias",
"version": 1
}
]