🐱 NestJS 高级¶
请求处理管线核心能力。依据 NestJS 官方文档。本页讲高级:管道(Pipe)、守卫(Guard)、拦截器(Interceptor)、异常过滤器(Filter)、中间件(Middleware)、自定义装饰器、执行顺序。
1. 请求处理顺序(重要)¶
中间件 Middleware → 守卫 Guard → 拦截器 Interceptor(before) → 管道 Pipe
→ 控制器方法 → 服务 → 拦截器 Interceptor(after) → 异常过滤器 Filter(出错时)
执行顺序决定了能力边界
- 想在"进控制器前"就拦掉未登录 → 用 Guard(最适合鉴权)。
- 想改请求参数/做类型转换 → 用 Pipe(最适合校验/转换)。
- 想统一包装响应、记耗时 → 用 Interceptor(最适合响应处理)。
- 想统一错误处理 → Exception Filter。
- 中间件在最外层,适合 CORS、日志等"所有请求通用"的逻辑。别把鉴权放中间件又放 Guard,职责重叠。
2. 管道 Pipe(输入校验 + 转换)¶
@Injectable() export class ParseIntPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
const n = parseInt(value, 10)
if (isNaN(n)) throw new BadRequestException('id 必须是数字')
return n
}
}
ValidationPipe(配合 class-validator)、ParseIntPipe/ParseUUIDPipe/ParseBoolPipe/ParseArrayPipe、DefaultValuePipe。
ValidationPipe 配置陷阱
- 没开
whitelist:true时,前端多传的字段(如isAdmin:true)会被原样写进实体 → 批量赋值漏洞(越权提权)。 - 没开
transform:true时,@Query('page') page: number仍是字符串,类型转换不生效。 forbidNonWhitelisted:true可让多余字段直接 400,比默默忽略更安全。- 别忘了在
main.ts全局useGlobalPipes,否则每个路由要手动@UsePipes。
- 全局启用校验:
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }))// DTO 校验 export class CreateUserDto { @IsEmail() email: string @IsInt() @Min(0) age: number }
3. 守卫 Guard(鉴权 / 授权)¶
@Injectable() export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const req = context.switchToHttp().getRequest()
return !!req.user // false → 403/401
}
}
// 使用
@UseGuards(AuthGuard)
@Get('profile')
getProfile() {}
@Injectable() export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(ctx: ExecutionContext) {
const roles = this.reflector.get<string[]>('roles', ctx.getHandler())
const user = ctx.switchToHttp().getRequest().user
return roles?.includes(user.role)
}
}
// 自定义角色装饰器
export const Roles = (...roles: string[]) => SetMetadata('roles', roles)
Guard 鉴权坑
- 守卫
canActivate返回false时 Nest 默认抛ForbiddenException(403);若想区分"未登录",应在守卫内判断无 token 时抛UnauthorizedException(401)。 - 角色校验别只信前端传的
role字段——role必须从 JWT/服务端 Session 取,绝不能用客户端请求体里的role。 - 全局守卫要对登录/公开接口做白名单放行(如
/auth/login),否则所有接口都要 token,死循环。
4. 拦截器 Interceptor(转换 / 日志 / 缓存)¶
@Injectable() export class TransformInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(map(data => ({ data, code: 0 }))) // 统一响应
}
}
// 超时拦截
@Injectable() export class TimeoutInterceptor implements NestInterceptor {
intercept(_, next) { return next.handle().pipe(timeout(5000)) }
}
Interceptor 注意点
next.handle()返回的是Observable,拦截器是 RxJS 流。用map改数据、catchError处理异常、timeout设超时。- 统一响应拦截器(
{code:0, data})会和 Exception Filter 冲突:异常走 Filter,正常走 Interceptor,两边响应格式要约定一致,否则前端解析崩溃。 - 超时拦截器抛
TimeoutError,要配 Filter 转成 504。
5. 异常过滤器 Exception Filter¶
@Catch(HttpException)
@Injectable() export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp()
const res = ctx.getResponse()
const status = exception.getStatus()
res.status(status).json({ code: status, msg: exception.message })
}
}
// 全局
app.useGlobalFilters(new HttpExceptionFilter())
throw new BadRequestException() / NotFoundException() / ForbiddenException()。
- 业务统一错误码在过滤器里收敛。
异常处理别吞错
- 永远抛 Nest 的 HttpException 而非原生 Error:原生 Error 会被当成 500,且信息可能泄露内部细节给前端。
- 全局 Filter 要区分"已知业务错误"(透传 message)和"未知错误"(记日志、返回通用 500,别把堆栈/SQL 错误返给前端——信息泄露)。
- 生产环境不要把
exception.stack写进响应体。
6. 中间件 Middleware¶
@Injectable() export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log(`${req.method} ${req.originalUrl}`)
next()
}
}
// 在模块 configure 注册(或全局 app.use)
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggerMiddleware).forRoutes('cats')
}
}
7. 自定义装饰器¶
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => ctx.switchToHttp().getRequest().user
)
// 使用
@Get('profile') getProfile(@User() user: UserEntity) {}