NestJS拦截器(Interceptor) #

什么是拦截器? #

拦截器是用@Injectable()装饰器装饰的类,它实现了NestInterceptor接口。拦截器可以在方法执行前后添加额外逻辑,类似于AOP(面向切面编程)的概念。

拦截器的用途 #

  • 方法执行前后添加额外逻辑
  • 转换方法返回的结果
  • 转换方法抛出的异常
  • 扩展方法行为
  • 缓存响应

创建拦截器 #

基本结构 #

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`)),
      );
  }
}

执行流程 #

text
请求 → 守卫 → 拦截器(前) → 管道 → 控制器 → 拦截器(后) → 响应

应用拦截器 #

方法级别 #

typescript
import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { LoggingInterceptor } from './logging.interceptor';

@Controller('users')
export class UsersController {
  @Get()
  @UseInterceptors(LoggingInterceptor)
  findAll() {
    return 'All users';
  }
}

控制器级别 #

typescript
@Controller('users')
@UseInterceptors(LoggingInterceptor)
export class UsersController {}

全局级别 #

typescript
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new LoggingInterceptor());

响应转换拦截器 #

统一响应格式 #

typescript
import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

export interface Response<T> {
  code: number;
  message: string;
  data: T;
  timestamp: string;
}

@Injectable()
export class TransformInterceptor<T>
  implements NestInterceptor<T, Response<T>>
{
  intercept(
    context: ExecutionContext,
    next: CallHandler,
  ): Observable<Response<T>> {
    return next.handle().pipe(
      map(data => ({
        code: 200,
        message: 'Success',
        data,
        timestamp: new Date().toISOString(),
      })),
    );
  }
}

使用响应转换 #

typescript
@Get()
findAll() {
  return [{ id: 1, name: 'John' }];
}

响应:

json
{
  "code": 200,
  "message": "Success",
  "data": [{ "id": 1, "name": "John" }],
  "timestamp": "2024-01-01T00:00:00.000Z"
}

日志拦截器 #

详细日志 #

typescript
import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
  Logger,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  private readonly logger = new Logger(LoggingInterceptor.name);

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest();
    const { method, url, body, query, params } = request;
    const now = Date.now();

    this.logger.log(
      `Request: ${method} ${url}`,
      JSON.stringify({ body, query, params }),
    );

    return next.handle().pipe(
      tap({
        next: data => {
          const response = context.switchToHttp().getResponse();
          this.logger.log(
            `Response: ${method} ${url} ${response.statusCode} ${Date.now() - now}ms`,
          );
        },
        error: error => {
          this.logger.error(
            `Error: ${method} ${url} ${Date.now() - now}ms`,
            error.stack,
          );
        },
      }),
    );
  }
}

异常映射拦截器 #

转换异常 #

typescript
import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
  NotFoundError,
} from '@nestjs/common';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';

@Injectable()
export class NotFoundInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      catchError(error => {
        if (error instanceof NotFoundError) {
          return throwError(() => new NotFoundException(error.message));
        }
        return throwError(() => error);
      }),
    );
  }
}

缓存拦截器 #

简单缓存 #

typescript
import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from '@nestjs/common';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class CacheInterceptor implements NestInterceptor {
  private cache = new Map<string, any>();

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest();
    const key = `${request.method}:${request.url}`;

    if (this.cache.has(key)) {
      return of(this.cache.get(key));
    }

    return next.handle().pipe(
      tap(data => {
        this.cache.set(key, data);
      }),
    );
  }
}

使用@nestjs/cache-manager #

typescript
import { CacheInterceptor, CacheKey, CacheTTL } from '@nestjs/cache-manager';

@Controller('users')
@UseInterceptors(CacheInterceptor)
export class UsersController {
  @Get()
  @CacheKey('users_all')
  @CacheTTL(60)
  findAll() {
    return this.usersService.findAll();
  }
}

超时拦截器 #

typescript
import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
  RequestTimeoutException,
} from '@nestjs/common';
import { Observable, throwError, TimeoutError } from 'rxjs';
import { timeout, catchError } from 'rxjs/operators';

@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
  constructor(private readonly timeoutMs: number = 5000) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      timeout(this.timeoutMs),
      catchError(error => {
        if (error instanceof TimeoutError) {
          return throwError(
            () => new RequestTimeoutException('Request timeout'),
          );
        }
        return throwError(() => error);
      }),
    );
  }
}

性能监控拦截器 #

typescript
import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class PerformanceInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const startTime = Date.now();
    const request = context.switchToHttp().getRequest();

    return next.handle().pipe(
      tap(() => {
        const duration = Date.now() - startTime;
        const { method, url } = request;

        if (duration > 1000) {
          console.warn(`Slow request: ${method} ${url} took ${duration}ms`);
        }
      }),
    );
  }
}

文件流拦截器 #

typescript
import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { StreamableFile } from '@nestjs/common';

@Injectable()
export class FileInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const response = context.switchToHttp().getResponse();

    return next.handle().pipe(
      map(data => {
        if (data instanceof StreamableFile) {
          response.setHeader('Content-Type', 'application/octet-stream');
        }
        return data;
      }),
    );
  }
}

多个拦截器 #

typescript
@Controller('users')
@UseInterceptors(LoggingInterceptor, TransformInterceptor, CacheInterceptor)
export class UsersController {
  @Get()
  findAll() {
    return this.usersService.findAll();
  }
}

执行顺序:

text
LoggingInterceptor(前) → TransformInterceptor(前) → CacheInterceptor(前) → 
控制器 → 
CacheInterceptor(后) → TransformInterceptor(后) → LoggingInterceptor(后)

条件拦截器 #

typescript
import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class ConditionalInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest();
    const shouldCache = request.method === 'GET';

    if (shouldCache) {
      // 应用缓存逻辑
    }

    return next.handle();
  }
}

拦截器最佳实践 #

1. 单一职责 #

每个拦截器只做一件事:

typescript
// 日志拦截器只负责日志
@Injectable()
export class LoggingInterceptor implements NestInterceptor {}

// 转换拦截器只负责转换
@Injectable()
export class TransformInterceptor implements NestInterceptor {}

2. 使用装饰器配置 #

typescript
import { SetMetadata } from '@nestjs/common';

export const CACHE_KEY = 'cache_key';
export const CacheConfig = (key: string, ttl: number = 60) =>
  SetMetadata(CACHE_KEY, { key, ttl });

@Get()
@CacheConfig('users_all', 120)
findAll() {}

3. 错误处理 #

typescript
@Injectable()
export class ErrorHandlingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      catchError(error => {
        // 统一错误处理
        return throwError(() => error);
      }),
    );
  }
}

总结 #

本章学习了NestJS拦截器:

  • 拦截器的概念和用途
  • 响应转换拦截器
  • 日志拦截器
  • 缓存拦截器
  • 超时拦截器
  • 拦截器最佳实践

接下来,让我们学习 TypeORM集成

最后更新:2026-03-28