NestJS:如何在NestInterceptor中更新请求/响应状态?
NestJS拦截器异步更新请求状态:多请求场景下的正确性与注意事项
核心问题解答
多个异步请求(如A、B、C)同时处理时,Interceptor能正确为每个请求更新对应的requestDetails.requestState。原因是:
每次请求进入intercept方法时,都会创建一个全新的requestDetails实例——这个变量是当前请求上下文的局部变量,不会与其他请求的实例共享。每个请求的拦截逻辑都是独立执行的,不存在变量污染的情况,因此不会出现A请求的状态被错误更新到B请求记录的问题。
关键注意事项
1. 完善错误场景的状态更新
目前代码仅在日志层面处理了数据库操作的错误,但未同步更新请求状态为ERROR:
- 在初始化
INPROGRESS状态的数据库写入失败时,应在catch块中修改requestDetails.requestState = RequestDetails.ERROR,再重新执行数据库写入。 - 在响应成功后的
COMPLETE状态写入失败时,同样需要将状态设为ERROR并持久化,避免审计记录停留在INPROGRESS状态。
示例代码调整:
// 初始化INPROGRESS时的错误处理 (async () => { try { await this.dsDynamoDBService.putItem({ TableName: this.dsConfig.aws.dynamoDB.table, Item: requestDetails }); } catch (error) { this.logService.error(error); // 更新状态为ERROR并写入 requestDetails.requestState = RequestDetails.ERROR; await this.dsDynamoDBService.putItem({ TableName: this.dsConfig.aws.dynamoDB.table, Item: requestDetails }).catch(err => this.logService.error(err)); } })();
2. 用RxJS操作符替代嵌套异步函数
NestJS拦截器基于RxJS实现,使用Observable操作符处理异步逻辑更贴合框架设计,也能更好地控制流的错误与完成:
- 可以将DynamoDB的
putItem转换为Observable(利用from操作符),避免使用立即执行的异步函数。 - 结合
tap、catchError等操作符,统一处理请求成功、失败的状态更新。
示例代码:
import { from, throwError } from 'rxjs'; import { tap, catchError } from 'rxjs/operators'; // ... 其他代码 return next.handle().pipe( tap(() => { if (!isHealthRoute && !preventReqResLog) { requestDetails.requestState = RequestDetails.COMPLETE; from(this.dsDynamoDBService.putItem({ TableName: this.dsConfig.aws.dynamoDB.table, Item: requestDetails })).subscribe({ error: err => { this.logService.error(err); // 写入失败时更新为ERROR requestDetails.requestState = RequestDetails.ERROR; from(this.dsDynamoDBService.putItem({ TableName: this.dsConfig.aws.dynamoDB.table, Item: requestDetails })).subscribe(); } }); } }), catchError(err => { // 请求处理失败时更新状态为ERROR if (!isHealthRoute && !preventReqResLog) { requestDetails.requestState = RequestDetails.ERROR; from(this.dsDynamoDBService.putItem({ TableName: this.dsConfig.aws.dynamoDB.table, Item: requestDetails })).subscribe({ error: dbErr => this.logService.error(dbErr) }); } return throwError(() => err); }) );
3. 确保Request ID的唯一性
当前使用headers.awsrequestid作为审计记录的id,需确认该ID全局唯一。若存在重复风险,建议生成自定义唯一ID(如UUID)并绑定到请求上下文:
import { v4 as uuidv4 } from 'uuid'; // 在intercept方法开头生成唯一ID const requestId = uuidv4(); // 存入请求上下文 (req as any).requestId = requestId; requestDetails.id = requestId;
4. 正确获取响应状态码
当前代码提前获取statusCode时,请求尚未处理完成,状态码可能是默认值(如200)。应在请求处理完成后(如tap操作符内)重新获取实际状态码:
tap(() => { const actualStatusCode = context.switchToHttp().getResponse().statusCode; // 使用actualStatusCode更新审计记录或日志 })
5. 避免主请求性能被审计逻辑阻塞
两次数据库写入操作会增加请求的响应时间,若请求量较大,建议:
- 将审计日志写入逻辑放入异步队列(如BullMQ),后台批量处理。
- 使用DynamoDB的批量写入API减少请求次数。
内容的提问来源于stack exchange,提问作者Antonio Pavicevac-Ortiz
相关产品推荐
相关产品推荐

