NestJS 中间件与拦截器:请求处理流程详解
在上一篇文章中,我们介绍了 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 的微服务架构实现。
如果觉得这篇文章对你有帮助,别忘了点个赞 👍
原文地址:https://blog.csdn.net/ChengFengTech/article/details/144795251
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!