写 API 接口时,我们通常希望有个统一的结构包裹一下要返回的数据,每个接口分别去写不光繁琐,还容易不一致,所以一般会用一个统一的拦截器来实现这个功能。各种语言的不同框架基本都有对应的拦截器写法,今天分享下 nestjs 里如何编写拦截器和如何跳过拦截器。
比如普通的一个查询接口,返回的数据如:
1 2 3 4
| { "user": "xxx", "image_url": "https://xxxxx" }
|
客户端侧往往需要区分是成功还是失败,所以希望这个数据被包在 data 字段下面,再用一个同级的 "status": "success", "code": 0
来代表成功。如果是失败的,则返回 fail 、对应错误码及错误信息。
1 2 3 4 5 6 7 8
| { "code": 0, "status": "success", "data": { "user": "xxx", "image_url": "https://xxxxx" } }
|
要包装返回数据的需求非常的常见,相关的文档非常的多。对于 nestjs ,很快就查到正确的写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| import { CallHandler, ExecutionContext, Inject, Injectable, NestInterceptor, } from '@nestjs/common'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { Reflector } from '@nestjs/core';
@Injectable() export class ResponseFormatInterceptor implements NestInterceptor { constructor(@Inject(Reflector) private readonly reflector: Reflector) {}
intercept( context: ExecutionContext, next: CallHandler<any>, ): Observable<any> | Promise<Observable<any>> { return next.handle().pipe( map((data) => ({ code: 0, success: true, status: 'success', data, })), ); } }
|
对应的还要有一个错误拦截:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| import { CallHandler, ExecutionContext, Injectable, NestInterceptor, } from '@nestjs/common'; import { Observable } from 'rxjs'; import { catchError } from 'rxjs/operators';
@Injectable() export class ResponseErrorInterceptor implements NestInterceptor { intercept( context: ExecutionContext, next: CallHandler<any>, ): Observable<any> | Promise<Observable<any>> { return next.handle().pipe( catchError(async (err) => { const errorResponse = { code: (err.response && err.response.code) || -1, success: false, status: err.status || 'fail', message: err.message || '不明异常', data: err.response, };
return errorResponse; }), ); } }
|
最后再在 app.module.ts 注册这两个拦截器:
1 2 3 4 5 6 7 8 9 10
| @Module({ providers: [ { provide: APP_INTERCEPTOR, useClass: ResponseErrorInterceptor }, { provide: APP_INTERCEPTOR, useClass: ResponseFormatInterceptor }, ], }) export class AppModule {}
|
这样就可以满足需求了,但有一些接口,不希望有这个包装,比如微信推送消息的服务,推给你的服务器后需要你的服务器返回一个 ‘success’ 的文本。这时需要跳过这个全局拦截器,这个不知道是搜索姿势不对还是这个需求比较少,比较少找到相关资料。
这里使用的是装饰器 decorator 来实现:
1 2 3 4
| import { SetMetadata } from '@nestjs/common';
export const SkipInterceptor = () => SetMetadata('skipInterceptor', true);
|
这样就声明了一个装饰器,装饰器会设置一个 metadata skipInterceptor 值为 true,再修改格式化响应的拦截器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| import { CallHandler, ExecutionContext, Inject, Injectable, NestInterceptor, } from '@nestjs/common'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { Reflector } from '@nestjs/core';
@Injectable() export class ResponseFormatInterceptor implements NestInterceptor { constructor(@Inject(Reflector) private readonly reflector: Reflector) {}
intercept( context: ExecutionContext, next: CallHandler<any>, ): Observable<any> | Promise<Observable<any>> { const skipInterceptor = this.reflector.get<boolean>( 'skipInterceptor', context.getHandler(), ); if (skipInterceptor) { return next.handle(); } return next.handle().pipe( map((data) => ({ code: 0, success: true, status: 'success', data, })), ); } }
|
在 intercept 中判断是否 skipInterceptor ,是的话就直接 next ,否的话正常 pipe 里做包装。最后,使用时:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { Body, Controller, HttpCode, Post } from '@nestjs/common'; import { SkipInterceptor } from '../../decorator/skip.decorator';
@Controller('xxx') export class xxxController { constructor() {}
@Post('push') @HttpCode(200) @SkipInterceptor() async handleWechatPush(@Body() data) { return 'success'; } }
|
就可以拿到纯字符串的 success 了。