介绍
在nest企业级开发项目中,必不可免的就是封装统一返回格式,否则返回格式一团糟,对应的前端也要忍不住给你唱一段rep,那么在nest中,你有两种方式来实现这个功能,一种是使用利用拦截器来实现,另一种是自己封装一个类来实现,两者皆可,不过在这里还是推荐使用类来实现,原因就是足够灵活,那么话不多说,让我们来实现下这两种方式。
拦截器实现
在nest官网中可以看到,在拦截器这一章节中,给出了响应映射这样的功能案例,并且在开篇也说明了,使用拦截器需要实现 NestInterceptor
接口类,很多道友不明白什么叫做实现 NestInterceptor
接口类,其实 NestInterceptor
就是一个ts的类型声明,nest内部对此方法已经做了处理,你想使用,就按照给出的接口声明来照做实现就行,那接下来我们就按照官网来实现下
我们在src下创建一个transform.interceptor.ts文件,并根据官网给出的示例代码先尝试一下,如下:
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
export interface Response<T> {
data: T;
}
@Injectable()
export class TransformInterceptor<T>
implements NestInterceptor<T, Response<T>>
{
intercept(
context: ExecutionContext,
next: CallHandler,
): Observable<Response<T>> {
return next.handle().pipe(map((data) => ({ data })));
}
}
然后将刚刚创建的拦截器来绑定一下,在getHello接口绑定使用:
让我们来请求一下看看现在的返回格式是什么样子的:
可以看到,现在返回格式中包了一层 data
,这样是不是跟我们常见的接口返回格式一样了呢?
但是现在还少了点东西,正常我们接口返回还有 code、message 字段,那这怎么添加呢?其实非常简单,让我们把 TransformInterceptor
稍作处理即可,如下:
这里将map返回值手动定义了一下,增加了code、message字段,注意:上面的类型也要对应增加,现在再让我们来看下返回的格式是怎样的吧,如下:
没错,这就是我们想要的结果,但是现在code、message两个字段是写死的,按理来说应该是动态的才对,假设我想返回 code: 400,message: 请求失败,这怎么办呢?很简单,我们需要在controller中返回对应的数据,然后在 TransformInterceptor
中手动接收下数据即可,现在我们getHello
接口返回的是传过来的请求参数,现在我们要额外再返回两个字段,如下所示:
第一步:controller返回对应的数据格式
让我们来看下现在的返回结果:
显然不是我们理想中的格式,接下来还有第二步,如下所示:
我们先来打印下TransformInterceptor
中data参数:
可以看到,在data中,成功打印出了我们在controller中返回的对象,那这样就好办了,我们只需要取出对应的值即可:
再来请求下接口看下:
完美!
自定义类实现(推荐)
虽然我们上面使用拦截器实现了统一 返回格式功能,但是并不是很舒服,还是有局限性,没有自定义类来拓展性高,那么接下来我们就来用自定义类来实现:
我在先创建一个result.ts文件,里面去实现我们的ResultData这样一个类:
import { HttpStatus } from '@nestjs/common';
export class ResultData {
constructor(
public code = HttpStatus.OK,
public msg?: string,
public data?: any,
) {
this.code = code;
this.msg = msg || '操作成功';
this.data = data || null;
}
static success(data?: any, msg?: string) {
return new ResultData(HttpStatus.OK, msg, data);
}
static fail(code = HttpStatus.BAD_REQUEST, msg?: string, data?: any) {
return new ResultData(code, msg, data);
}
}
接下来我们在app.controller.ts中去使用一下:
import {
Body,
Controller,
Post,
UseInterceptors,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import { AppService } from './app.service';
import { CreateUserDto } from './create-user.dto';
import { TransformInterceptor } from './transform.interceptor';
import { ResultData } from './result';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Post()
// @UseInterceptors(TransformInterceptor)
getHello(
@Body(new ValidationPipe({ transform: true })) createUserDto: CreateUserDto,
) {
return ResultData.success(createUserDto, '啦啦啦');
}
}
注意:我们在使用的时候需要注释掉我们之前编写的统一返回拦截器,否则的话,我们在 ResultData
中返回后,又回进入拦截器再次组装返回格式,接下来我们来请求一下:
可以看到,已经正确的返回了,当然了 ,我们在实际项目开发中,单单这一种封装还是有点单一的,不过其他的我们就按照我们的需求自己封装即可
注意REST API
因为我们使用的是REST API风格,所以对于post请求默认返回的是201,所以需要手动处理成200,不然前端如果只认200的话,是会报错的,可能这么一个小问题就是一上午时间(焦头烂额),
那么怎么办呢 ?
- 我们需要从
Request
请求中取出method
判断是否是post请求 - 我们需要再从
Response
中取出statusCode
判断是否等于 201
纯拦截器实现统一返回格式
纯拦截器实现统一返回格式代码修改如下:
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
HttpStatus,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Request, Response } from 'express';
export interface ResponseType<T> {
data: T;
code: number;
message: string;
}
@Injectable()
export class TransformInterceptor<T>
implements NestInterceptor<T, ResponseType<T>>
{
intercept(
context: ExecutionContext,
next: CallHandler,
): Observable<ResponseType<T>> {
// 因为nestjs使用REST API风格,对于post请求默认返回201,所以需要手动处理成200
const request = context.switchToHttp().getRequest<Request>();
const response = context.switchToHttp().getResponse<Response>();
if (
request.method === 'POST' &&
response.statusCode === HttpStatus.CREATED
) {
response.status(HttpStatus.OK);
}
return next.handle().pipe(
map((data) => {
console.log(data);
return {
data: data.data,
code: data.code,
message: data.message,
};
}),
);
}
}
至于 Request
和Response
类型,因为我们项目使用的是express,所以从 express 中导入这两个接口。
自定义实现类统一返回格式
result.ts不用修改,需要添加拦截器:
import {
Injectable,
NestInterceptor,
CallHandler,
HttpStatus,
ExecutionContext,
} from '@nestjs/common';
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { ResultData } from './result';
import { Request, Response } from 'express';
@Injectable()
export class ResponsInterceptoreBase<T> implements NestInterceptor {
intercept(
context: ExecutionContext,
next: CallHandler,
): Observable<ResultData> {
// 因为nestjs使用REST API风格,对于post请求默认返回201,所以需要手动处理成200
const request = context.switchToHttp().getRequest<Request>();
const response = context.switchToHttp().getResponse<Response>();
if (
request.method === 'POST' &&
response.statusCode === HttpStatus.CREATED
) {
response.status(HttpStatus.OK);
}
return next.handle().pipe(
map((data:ResultData) => {
return data
}),
);
}
}
在 app.controller.ts
中使用拦截器:
import {
Body,
Controller,
Post,
UseInterceptors,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import { AppService } from './app.service';
import { CreateUserDto } from './create-user.dto';
import { TransformInterceptor } from './transform.interceptor';
import { ResultData } from './result';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Post()
@UseInterceptors(TransformInterceptor)
getHello(
@Body(new ValidationPipe({ transform: true })) createUserDto: CreateUserDto,
) {
return ResultData.success(createUserDto, '啦啦啦');
}
}
我们请求一下:
这样我们就可以愉快的编写接口代码了~