You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 06:35:18