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
添加額外功能