# 攔截器
攔截器是使用 `@Injectable()` 裝飾器注解的類。攔截器實現了 `NestInterceptor` 接口。

攔截器具有一系列有用的功能,這些功能受面向切面編程(AOP)技術的啟發。它們可以:
- 在函數執行之前/之后綁定**額外的邏輯**
- 轉換從函數返回的結果
- **轉換**從函數拋出的異常
- 擴展基本函數行為
- 根據特定條件完全重寫函數 (例如, 出于緩存目的)
## 基礎
每個攔截器都有 `intercept()` 方法,它接收2個參數。 第一個是 `ExecutionContext` 實例(與守衛完全相同的對象)。 `ExecutionContext` 繼承自 `ArgumentsHost` 。 `ArgumentsHost` 是傳遞給原始處理程序的參數的一個包裝 ,它根據應用程序的類型包含不同的參數數組。你可以在[這里](https://docs.nestjs.com/exception-filters#arguments-host)讀更多關于它的內容(在異常過濾器章節中)。
## 執行上下文
通過擴展 `ArgumentsHost`,`ExecutionContext` 還添加了幾個新的幫助程序方法,這些方法提供有關當前執行過程的更多詳細信息。這些詳細信息有助于構建可以在廣泛的控制器,方法和執行上下文中使用的更通用的攔截器。`ExecutionContext` 在[此處](https://docs.nestjs.com/fundamentals/execution-contextt)了解更多信息。
## 調用處理程序
第二個參數是 `CallHandler`。如果不手動調用 `handle()` 方法,則主處理程序根本不會進行求值。這是什么意思?基本上,`CallHandler`是一個包裝執行流的對象,因此推遲了最終的處理程序執行。
比方說,有人提出了 POST `/cats` 請求。此請求指向在 `CatsController` 中定義的 `create()` 處理程序。如果在此過程中未調用攔截器的 `handle()` 方法,則 `create()` 方法不會被計算。只有 `handle()` 被調用(并且已返回值),最終方法才會被觸發。為什么?因為Nest訂閱了返回的流,并使用此流生成的值來為最終用戶創建單個響應或多個響應。而且,`handle()` 返回一個 `Observable`,這意味著它為我們提供了一組非常強大的運算符,可以幫助我們進行例如響應操作。
## 截取切面
第一個用例是使用攔截器在函數執行之前或之后添加額外的邏輯。當我們要記錄與應用程序的交互時,它很有用,例如 存儲用戶調用,異步調度事件或計算時間戳。作為一個例子,我們來創建一個簡單的例子 `LoggingInterceptor`。
> logging.interceptor.ts
```typescript
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`)),
);
}
}
```
> `NestInterceptor<T,R>` 是一個通用接口,其中 `T` 表示已處理的 `Observable<T>` 的類型(在流后面),而 `R` 表示包含在返回的 `Observable<R>` 中的值的返回類型。
> 攔截器的作用與控制器,提供程序,守衛等相同,這意味著它們可以通過構造函數注入依賴項。
由于 `handle()` 返回一個RxJS `Observable`,我們有很多種操作符可以用來操作流。在上面的例子中,我們使用了 `tap()` 運算符,該運算符在可觀察序列的正常或異常終止時調用函數。
## 綁定攔截器
為了設置攔截器, 我們使用從 `@nestjs/common` 包導入的 `@UseInterceptors()` 裝飾器。與守衛一樣, 攔截器可以是控制器范圍內的, 方法范圍內的或者全局范圍內的。
> cats.controller.ts
```typescript
@UseInterceptors(LoggingInterceptor)
export class CatsController {}
```
> `@UseInterceptors()` 裝飾器從 `@nestjs/common` 導入。
由此,`CatsController` 中定義的每個路由處理程序都將使用 `LoggingInterceptor`。當有人調用 GET `/cats` 端點時,您將在控制臺窗口中看到以下輸出:
```json
Before...
After... 1ms
```
請注意,我們傳遞的是 `LoggingInterceptor` 類型而不是實例,讓框架承擔實例化責任并啟用依賴注入。另一種可用的方法是傳遞立即創建的實例:
> cats.controller.ts
```typescript
@UseInterceptors(new LoggingInterceptor())
export class CatsController {}
```
如上所述, 上面的構造將攔截器附加到此控制器聲明的每個處理程序。如果我們決定只限制其中一個, 我們只需在**方法級別**設置攔截器。為了綁定全局攔截器, 我們使用 Nest 應用程序實例的 `useGlobalInterceptors()` 方法:
```typescript
const app = await NestFactory.create(ApplicationModule);
app.useGlobalInterceptors(new LoggingInterceptor());
```
全局攔截器用于整個應用程序、每個控制器和每個路由處理程序。在依賴注入方面, 從任何模塊外部注冊的全局攔截器 (如上面的示例中所示) 無法插入依賴項, 因為它們不屬于任何模塊。為了解決此問題, 您可以使用以下構造**直接從任何模塊**設置一個攔截器:
> app.module.ts
```typescript
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_INTERCEPTOR,
useClass: LoggingInterceptor,
},
],
})
export class AppModule {}
```
> 另一種選擇是使用[執行上下文](https://docs.nestjs.com/fundamentals/execution-context)功能。另外,useClass 并不是處理自定義提供商注冊的唯一方法。在[這里](https://docs.nestjs.com/fundamentals/custom-providers)了解更多。
## 響應映射
我們已經知道, `handle()` 返回一個 `Observable`。此流包含從路由處理程序返回的值, 因此我們可以使用 `map()` 運算符輕松地對其進行改變。
> 響應映射功能不適用于特定于庫的響應策略(禁止直接使用 `@Res()` 對象)。
讓我們創建一個 TransformInterceptor, 它將打包響應并將其分配給 data 屬性。
> transform.interceptor.ts
```typescript
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 })));
}
}
```
> `Nest` 攔截器就像使用異步 `intercept()` 方法的魅力一樣, 意思是, 如果需要,您可以毫不費力地將方法切換為異步。
之后,當有人調用GET `/cats`端點時,請求將如下所示(我們假設路由處理程序返回一個空數組 arry `[]`):
```json
{
"data": []
}
```
攔截器在創建用于整個應用程序的可重用解決方案時具有巨大的潛力。例如,我們假設我們需要將每個發生的 `null` 值轉換為空字符串 `''`。我們可以使用一行代碼并將攔截器綁定為全局代碼。由于這一點,它會被每個注冊的處理程序自動重用。
```typescript
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable()
export class ExcludeNullInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next
.handle()
.pipe(map(value => value === null ? '' : value ));
}
}
```
## 異常映射
另一個有趣的用例是利用 `catchError()` 操作符來覆蓋拋出的異常:
> exception.interceptor.ts
```typescript
import {
Injectable,
NestInterceptor,
ExecutionContext,
BadGatewayException,
CallHandler,
} from '@nestjs/common';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable()
export class ErrorsInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next
.handle()
.pipe(
catchError(err => throwError(new BadGatewayException())),
);
}
}
```
## Stream 重寫
有時我們可能希望完全阻止調用處理程序并返回不同的值 (例如, 由于性能問題而從緩存中獲取), 這是有多種原因的。一個很好的例子是**緩存攔截器**,它將使用一些TTL存儲緩存的響應。不幸的是, 這個功能需要更多的代碼并且由于簡化, 我們將僅提供簡要解釋主要概念的基本示例。
> cache.interceptor.ts
```typescript
import { 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();
}
}
```
這是一個 `CacheInterceptor`,帶有硬編碼的 `isCached` 變量和硬編碼的響應 `[]` 。我們在這里通過 `of` 運算符創建并返回了一個新的流, 因此路由處理程序**根本不會被調用**。當有人調用使用 `CacheInterceptor` 的端點時, 響應 (一個硬編碼的空數組) 將立即返回。為了創建一個通用解決方案, 您可以利用 `Reflector` 并創建自定義修飾符。反射器 `Reflector` 在守衛章節描述的很好。
## 更多操作符
使用 `RxJS` 運算符操作流的可能性為我們提供了許多功能。讓我們考慮另一個常見的用例。假設您要處理路由請求超時。如果您的端點在一段時間后未返回任何內容,則您將以錯誤響應終止。以下構造可實現此目的:
> timeout.interceptor.ts
```typescript
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, RequestTimeoutException } from '@nestjs/common';
import { Observable, throwError, TimeoutError } from 'rxjs';
import { catchError, timeout } from 'rxjs/operators';
@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
timeout(5000),
catchError(err => {
if (err instanceof TimeoutError) {
return throwError(new RequestTimeoutException());
}
return throwError(err);
}),
);
};
};
```
5秒后,請求處理將被取消。您還可以在拋出之前添加自定義邏輯`RequestTimeoutException`(例如,釋放資源)。
- 介紹
- 概述
- 第一步
- 控制器
- 提供者
- 模塊
- 中間件
- 異常過濾器
- 管道
- 守衛
- 攔截器
- 自定義裝飾器
- 基礎知識
- 自定義提供者
- 異步提供者
- 動態模塊
- 注入作用域
- 循環依賴
- 模塊參考
- 懶加載模塊
- 應用上下文
- 生命周期事件
- 跨平臺
- 測試
- 技術
- 數據庫
- Mongo
- 配置
- 驗證
- 緩存
- 序列化
- 版本控制
- 定時任務
- 隊列
- 日志
- Cookies
- 事件
- 壓縮
- 文件上傳
- 流式處理文件
- HTTP模塊
- Session(會話)
- MVC
- 性能(Fastify)
- 服務器端事件發送
- 安全
- 認證(Authentication)
- 授權(Authorization)
- 加密和散列
- Helmet
- CORS(跨域請求)
- CSRF保護
- 限速
- GraphQL
- 快速開始
- 解析器(resolvers)
- 變更(Mutations)
- 訂閱(Subscriptions)
- 標量(Scalars)
- 指令(directives)
- 接口(Interfaces)
- 聯合類型
- 枚舉(Enums)
- 字段中間件
- 映射類型
- 插件
- 復雜性
- 擴展
- CLI插件
- 生成SDL
- 其他功能
- 聯合服務
- 遷移指南
- Websocket
- 網關
- 異常過濾器
- 管道
- 守衛
- 攔截器
- 適配器
- 微服務
- 概述
- Redis
- MQTT
- NATS
- RabbitMQ
- Kafka
- gRPC
- 自定義傳輸器
- 異常過濾器
- 管道
- 守衛
- 攔截器
- 獨立應用
- Cli
- 概述
- 工作空間
- 庫
- 用法
- 腳本
- Openapi
- 介紹
- 類型和參數
- 操作
- 安全
- 映射類型
- 裝飾器
- CLI插件
- 其他特性
- 遷移指南
- 秘籍
- CRUD 生成器
- 熱重載
- MikroORM
- TypeORM
- Mongoose
- 序列化
- 路由模塊
- Swagger
- 健康檢查
- CQRS
- 文檔
- Prisma
- 靜態服務
- Nest Commander
- 問答
- Serverless
- HTTP 適配器
- 全局路由前綴
- 混合應用
- HTTPS 和多服務器
- 請求生命周期
- 常見錯誤
- 實例
- 遷移指南
- 發現
- 誰在使用Nest?