在上一篇文章中,我们介绍了 NestJS 的认证与授权实现。本文将深入探讨 NestJS 的请求处理流程,包括中间件、拦截器、管道和异常过滤器的使用。
请求生命周期
在 NestJS 中,请求处理流程按以下顺序执行:
- 中间件(Middleware)
- 守卫(Guards)
- 拦截器(Interceptors)- 前置处理
- 管道(Pipes)
- 控制器(Controller)
- 服务(Service)
- 拦截器(Interceptors)- 后置处理
- 异常过滤器(Exception Filters)
中间件实现
1. 函数式中间件
// src/common/middleware/logger.middleware.ts
import { Request, Response, NextFunction } from 'express';export function loggerMiddleware(req: Request, res: Response, next: NextFunction) {const { method, originalUrl, ip } = req;const userAgent = req.get('user-agent') || '';console.log(`[${method}] ${originalUrl} - ${ip} - ${userAgent}`);// 记录请求开始时间const start = Date.now();// 响应结束后记录耗时res.on('finish', () => {const duration = Date.now() - start;console.log(`[${method}] ${originalUrl} - ${res.statusCode} - ${duration}ms`);});next();
}
2. 类中间件
// src/common/middleware/auth.middleware.ts
import { Injectable, NestMiddleware, UnauthorizedException } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { JwtService } from '@nestjs/jwt';@Injectable()
export class AuthMiddleware implements NestMiddleware {constructor(private jwtService: JwtService) {}async use(req: Request, res: Response, next: NextFunction) {const authHeader = req.headers.authorization;if (!authHeader) {next();return;}try {const token = authHeader.split(' ')[1];const payload = await this.jwtService.verifyAsync(token);req['user'] = payload;next();} catch (error) {throw new UnauthorizedException('Invalid token');}}
}
3. 全局中间件
// src/app.module.ts
import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
import { AuthMiddleware } from './common/middleware/auth.middleware';
import { loggerMiddleware } from './common/middleware/logger.middleware';@Module({// ... 其他配置
})
export class AppModule implements NestModule {configure(consumer: MiddlewareConsumer) {consumer.apply(loggerMiddleware, AuthMiddleware).exclude({ path: 'auth/login', method: RequestMethod.POST },{ path: 'auth/register', method: RequestMethod.POST },).forRoutes('*');}
}
拦截器实现
1. 响应转换拦截器
// src/common/interceptors/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;meta: {timestamp: string;status: number;message: string;};
}@Injectable()
export class TransformInterceptor<T>implements NestInterceptor<T, Response<T>> {intercept(context: ExecutionContext,next: CallHandler,): Observable<Response<T>> {const ctx = context.switchToHttp();const response = ctx.getResponse();return next.handle().pipe(map(data => ({data,meta: {timestamp: new Date().toISOString(),status: response.statusCode,message: 'Success',},})),);}
}
2. 缓存拦截器
// src/common/interceptors/cache.interceptor.ts
import {Injectable,NestInterceptor,ExecutionContext,CallHandler,
} from '@nestjs/common';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';
import { RedisService } from '../services/redis.service';@Injectable()
export class CacheInterceptor implements NestInterceptor {constructor(private redisService: RedisService) {}async intercept(context: ExecutionContext,next: CallHandler,): Promise<Observable<any>> {const request = context.switchToHttp().getRequest();const cacheKey = `cache:${request.url}`;// 尝试从缓存获取数据const cachedData = await this.redisService.get(cacheKey);if (cachedData) {return of(JSON.parse(cachedData));}// 如果没有缓存,执行请求并缓存结果return next.handle().pipe(tap(async response => {await this.redisService.set(cacheKey,JSON.stringify(response),60 * 5, // 5分钟缓存);}),);}
}
3. 性能监控拦截器
// src/common/interceptors/logging.interceptor.ts
import {Injectable,NestInterceptor,ExecutionContext,CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { PrometheusService } from '../services/prometheus.service';@Injectable()
export class LoggingInterceptor implements NestInterceptor {constructor(private prometheusService: PrometheusService) {}intercept(context: ExecutionContext, next: CallHandler): Observable<any> {const request = context.switchToHttp().getRequest();const { method, url } = request;const start = Date.now();return next.handle().pipe(tap({next: () => {const duration = Date.now() - start;// 记录请求耗时this.prometheusService.recordHttpRequestDuration(method,url,duration,);},error: error => {const duration = Date.now() - start;// 记录错误请求this.prometheusService.recordHttpRequestError(method,url,error.status,duration,);},}),);}
}
管道实现
1. 验证管道
// src/common/pipes/validation.pipe.ts
import {PipeTransform,Injectable,ArgumentMetadata,BadRequestException,
} from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';@Injectable()
export class ValidationPipe implements PipeTransform<any> {async transform(value: any, { metatype }: ArgumentMetadata) {if (!metatype || !this.toValidate(metatype)) {return value;}const object = plainToClass(metatype, value);const errors = await validate(object);if (errors.length > 0) {const messages = errors.map(error => ({property: error.property,constraints: error.constraints,}));throw new BadRequestException({message: 'Validation failed',errors: messages,});}return value;}private toValidate(metatype: Function): boolean {const types: Function[] = [String, Boolean, Number, Array, Object];return !types.includes(metatype);}
}
2. 转换管道
// src/common/pipes/parse-int.pipe.ts
import {PipeTransform,Injectable,ArgumentMetadata,BadRequestException,
} from '@nestjs/common';@Injectable()
export class ParseIntPipe implements PipeTransform<string, number> {transform(value: string, metadata: ArgumentMetadata): number {const val = parseInt(value, 10);if (isNaN(val)) {throw new BadRequestException(`Validation failed. "${value}" is not an integer.`,);}return val;}
}// src/common/pipes/parse-boolean.pipe.ts
@Injectable()
export class ParseBooleanPipe implements PipeTransform<string, boolean> {transform(value: string, metadata: ArgumentMetadata): boolean {if (value === 'true') return true;if (value === 'false') return false;throw new BadRequestException(`Validation failed. "${value}" is not a boolean.`,);}
}
异常过滤器
1. 全局异常过滤器
// src/common/filters/http-exception.filter.ts
import {ExceptionFilter,Catch,ArgumentsHost,HttpException,HttpStatus,
} from '@nestjs/common';
import { Request, Response } from 'express';
import { LoggerService } from '../services/logger.service';@Catch()
export class HttpExceptionFilter implements ExceptionFilter {constructor(private logger: LoggerService) {}catch(exception: unknown, host: ArgumentsHost) {const ctx = host.switchToHttp();const response = ctx.getResponse<Response>();const request = ctx.getRequest<Request>();const status =exception instanceof HttpException? exception.getStatus(): HttpStatus.INTERNAL_SERVER_ERROR;const message =exception instanceof HttpException? exception.getResponse(): 'Internal server error';// 记录错误日志this.logger.error(`${request.method} ${request.url}`,exception instanceof Error ? exception.stack : 'Unknown error','HttpExceptionFilter',);response.status(status).json({statusCode: status,timestamp: new Date().toISOString(),path: request.url,message,});}
}
2. 业务异常过滤器
// src/common/filters/business-exception.filter.ts
import { Catch, ArgumentsHost, HttpStatus } from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';
import { BusinessException } from '../exceptions/business.exception';
import { LoggerService } from '../services/logger.service';@Catch(BusinessException)
export class BusinessExceptionFilter extends BaseExceptionFilter {constructor(private logger: LoggerService) {super();}catch(exception: BusinessException, host: ArgumentsHost) {const ctx = host.switchToHttp();const response = ctx.getResponse();const request = ctx.getRequest();const status = HttpStatus.BAD_REQUEST;// 记录业务错误日志this.logger.warn(`Business Exception: ${exception.message}`,exception.stack,'BusinessExceptionFilter',);response.status(status).json({statusCode: status,timestamp: new Date().toISOString(),path: request.url,error: 'Business Error',message: exception.message,code: exception.code,});}
}
实践应用
1. 请求链路追踪
// src/common/middleware/trace.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { v4 as uuidv4 } from 'uuid';@Injectable()
export class TraceMiddleware implements NestMiddleware {use(req: Request, res: Response, next: NextFunction) {const traceId = req.headers['x-trace-id'] || uuidv4();req['traceId'] = traceId;res.setHeader('X-Trace-Id', traceId);next();}
}// src/common/interceptors/trace.interceptor.ts
@Injectable()
export class TraceInterceptor implements NestInterceptor {constructor(private logger: LoggerService) {}intercept(context: ExecutionContext, next: CallHandler): Observable<any> {const request = context.switchToHttp().getRequest();const traceId = request['traceId'];return next.handle().pipe(tap(data => {this.logger.log(`[${traceId}] Response data: ${JSON.stringify(data)}`,'TraceInterceptor',);}),);}
}
2. 请求速率限制
// src/common/guards/throttler.guard.ts
import { Injectable } from '@nestjs/common';
import { ThrottlerGuard } from '@nestjs/throttler';@Injectable()
export class CustomThrottlerGuard extends ThrottlerGuard {protected getTracker(req: Record<string, any>): string {return req.ips.length ? req.ips[0] : req.ip;}
}// src/app.module.ts
import { Module } from '@nestjs/common';
import { ThrottlerModule } from '@nestjs/throttler';@Module({imports: [ThrottlerModule.forRoot({ttl: 60,limit: 10,}),],
})
export class AppModule {}
3. API 版本控制
// src/common/decorators/api-version.decorator.ts
import { SetMetadata } from '@nestjs/common';export const API_VERSION = 'api_version';
export const ApiVersion = (version: string) => SetMetadata(API_VERSION, version);// src/common/guards/api-version.guard.ts
@Injectable()
export class ApiVersionGuard implements CanActivate {constructor(private reflector: Reflector) {}canActivate(context: ExecutionContext): boolean {const version = this.reflector.get<string>(API_VERSION,context.getHandler(),);if (!version) {return true;}const request = context.switchToHttp().getRequest();const requestVersion = request.headers['api-version'];return version === requestVersion;}
}
写在最后
本文详细介绍了 NestJS 中的请求处理流程和各个组件的实现:
- 中间件的不同实现方式
- 拦截器的应用场景
- 管道的数据转换和验证
- 异常过滤器的错误处理
- 实际应用案例
在下一篇文章中,我们将探讨 NestJS 的微服务架构实现。
如果觉得这篇文章对你有帮助,别忘了点个赞 👍