拦截器与管道
概念
- 拦截器(Interceptor):在请求处理前后插入逻辑,用于响应转换、日志、缓存等
- 管道(Pipe):在请求到达 Controller 之前,对参数做校验和转换
拦截器(Interceptor)
实现 NestInterceptor 接口:
typescript
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'
import { Observable, map } from 'rxjs'
import { tap } from 'rxjs/operators'
@Injectable()
export class TransformInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const now = Date.now()
return next.handle().pipe(
tap(() => console.log(`耗时 ${Date.now() - now}ms`)),
map(data => ({
code: 0,
message: 'success',
data,
timestamp: new Date().toISOString(),
})),
)
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
常用场景
统一响应格式:
typescript
// 原始返回 { name: 'John' }
// 拦截后返回 { code: 0, data: { name: 'John' }, message: 'success' }1
2
2
请求日志:
typescript
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const req = context.switchToHttp().getRequest()
console.log(`${req.method} ${req.url} - ${req.ip}`)
return next.handle()
}
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
缓存:
typescript
@Injectable()
export class CacheInterceptor implements NestInterceptor {
constructor(private cache: CacheService) {}
async intercept(context: ExecutionContext, next: CallHandler) {
const key = context.switchToHttp().getRequest().url
const cached = await this.cache.get(key)
if (cached) return of(cached)
return next.handle().pipe(
tap(data => this.cache.set(key, data, 60))
)
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
管道(Pipe)
实现 PipeTransform 接口:
typescript
import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common'
@Injectable()
export class ParseIntPipe implements PipeTransform {
transform(value: string) {
const val = parseInt(value, 10)
if (isNaN(val)) throw new BadRequestException('必须是数字')
return val
}
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
ValidationPipe
最常用的内置管道,配合 class-validator 自动校验:
typescript
// 全局启用
app.useGlobalPipes(new ValidationPipe({
whitelist: true, // 自动剥离未定义的属性
forbidNonWhitelisted: true, // 遇到未定义属性抛错
transform: true, // 自动转换类型
}))
// DTO 定义
export class CreateUserDto {
@IsString()
@MinLength(2)
@MaxLength(20)
name: string
@IsEmail()
email: string
@IsInt()
@Min(0)
@Max(150)
age: number
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
自定义管道
typescript
@Injectable()
export class ZodValidationPipe implements PipeTransform {
constructor(private schema: ZodSchema) {}
transform(value: any) {
const result = this.schema.safeParse(value)
if (!result.success) {
throw new BadRequestException(result.error.issues)
}
return result.data
}
}
// 使用
@Post()
create(@Body(new ZodValidationPipe(createUserSchema)) dto: CreateUserDto) {}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
执行顺序
Request → 中间件 → 守卫 → 拦截器(前) → 管道 → Handler → 拦截器(后)1
拦截器可以在方法执行前后都有处理逻辑,管道只在执行前。
面试常问
- 拦截器和中间件的核心区别?执行顺序?
- ValidationPipe 的
whitelist: true作用是什么? - 如何实现统一响应格式包装?
