AOP
(Aspect Oriented Programming),即面向切面编程,是
NestJS
框架中的重要内容之一。
利用AOP
能够对业务逻辑的各个部分例如:权限控制,日志统计,性能分析,异常处理等进行隔离,从而下降各部分的耦合度,提升程序的可维护性。sql
NestJS
框架中体现AOP
思想的部分有:Middleware
(中间件), Guard
(守卫器),Pipe
(管道),Exception filter
(异常过滤器)等,固然还有咱们今天的主角:Interceptor
(拦截器)。express
首先咱们看一下拦截器在NestJS中的三种使用方式:编程
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new SomeInterceptor());
复制代码
@UseInterceptors(SomeInterceptor)
export class SomeController {}
复制代码
export class SomeController {
@UseInterceptors(SomeInterceptor)
@Get()
routeHandler(){
// 执行路由函数
}
}
复制代码
下面咱们经过一些例子来看一下拦截器具体有哪些使用场景:缓存
routeHandler
执行以前或以后添加额外的逻辑:LoggingInterceptor下面这个例子能够计算出routeHandler
的执行时间,这是因为程序的执行顺序是 拦截器 =》路由执行 =》拦截器。app
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('Before...');
// 保存路由执行前的时间
const now = Date.now();
return next
.handle()
.pipe(
// 计算出这个路由的执行时间
tap(() => console.log(`After... ${Date.now() - now}ms`)),
);
}
}
复制代码
咱们知道,中间件也能够在路由执行以前添加额外的逻辑。而拦截器与中间件的主要区别之一就在于拦截器不仅能路由执行以前,也能在执行以后添加逻辑。框架
routeHandler
的返回结果进行转化: PaginateInterceptor下面例子中,咱们展现了拦截器的另外一个重要应用,对返回的结果进行转化。当routeHandler
返回分页列表
和总条数
时,拦截器能够将结果进行格式化:async
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'
import { Request } from 'express'
import { Observable } from 'rxjs'
import { map } from 'rxjs/operators'
@Injectable()
export class PaginateInterceptor implements NestInterceptor {
public intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
// 规定返回的数据格式必须为[分页列表,总条数]
map((data: [any[], number]) => {
const req: Request = context.switchToHttp().getRequest()
const query = req.query
// 判断是否一个分页请求
const isPaginateRequest = req.method === 'GET' && query.current && query.size
// 判断data是否符合格式
const isValidData = Array.isArray(data) && data.length === 2 && Array.isArray(data[0])
if (isValidData && isPaginateRequest) {
const [list, total] = data
return {
data: list,
meta: { total, size: query.size, current: query.current },
status: 'succ',
}
}
return data
}),
)
}
}
复制代码
routeHandler
抛出的异常进行处理: TypeormExceptionInterceptor若是你使用的ORM是TypeOrm
的话,也许你会接触过TypeOrm
抛出的EntityNotFoundError
异常。这个异常是因为sql
语句执行时找不到对应的行时抛出的错误。函数
在下面的例子里拦截器捕获到了TypeOrm
抛出的EntityNotFoundError
异常后,改成抛出咱们自定义的EntityNoFoundException
(关于自定义异常,可参考另外一篇文章基于@nestjs/swagger,封装自定义异常响应的装饰器))。post
import { EntityNoFoundException } from '@common/exception/common.exception'
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'
import { Observable, throwError } from 'rxjs'
import { catchError } from 'rxjs/operators'
@Injectable()
export class TypeOrmExceptionInterceptor implements NestInterceptor {
public intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
catchError(err => {
if (err.name === 'EntityNotFound') {
return throwError(new EntityNoFoundException())
}
return throwError(err)
}),
)
}
}
复制代码
看到这里,各位看官可能有个疑问:拦截器和异常过滤器有什么差异? 首先,时机不一样,拦截器的执行顺序在异常过滤器以前,这意味着拦截器抛出的错误,最后可经由过滤器处理;其次,对象不一样,拦截器捕获的是routeHandler
抛出的全部异常,而异常过滤器可经过@Catch(SomeException)来捕获特定的异常。性能
routeHandler
的行为:CacheInterceptorimport { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable, of } from 'rxjs';
@Injectable()
export class CacheInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const isCached = true;
if (isCached) {
return of([]);
}
return next.handle();
}
}
复制代码
这里例子里,当命中缓存时,经过return of([]);
语句直接返回告终果,而不走routeHandler
的逻辑。
routeHandler
,为routeHandler
添加额外功能:BindRoleToUserInterceptor在业务上,有时咱们须要在用户调用某些接口后,对用户执行一些额外操做,好比添加标签,或者添加角色。这个时候,就能够经过拦截器来实现这个功能。下面这个例子里,拦截器发挥实现是在某个接口调用成功后,给用户绑定上角色的功能,
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'
import { User } from '@src/auth/user/user.entity'
import { Observable } from 'rxjs'
import { tap } from 'rxjs/operators'
import { getConnection } from 'typeorm'
/** * 用于给用户绑定角色 */
@Injectable()
export class BindRoleToUserInterceptor implements NestInterceptor {
public intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
tap(async () => {
const req = context.switchToHttp().getRequest()
await this.bindRoleToUser(req.roleId, req.user.id)
}),
)
}
/** * 这里假定用户和角色是多对多的关系,此处省略User表和Role表的结构 */
public async bindRoleToUser(roleId: number, userId: number) {
await getConnection()
.createQueryBuilder()
.relation(User, 'roles')
.of(userId)
.add(roleId)
}
}
复制代码
当有多个接口都有相似逻辑的时候,使用拦截器就实现代码的复用,并与接口的主要功能分隔开,实现AOP
。
经过以上几个例子,咱们能够总结出拦截器的几个做用:
routeHandler
执行以前或以后添加额外的逻辑routeHandler
的返回结果进行转化routeHandler
抛出的异常进行处理routeHandler
的行为routeHandler
,为routeHandler
添加额外功能