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

Nest.js技术疑问:Exception Filter能否在Service中使用?

问题解答

核心结论:Exception Filter 不能直接作用于 Service 方法级别

Nest.js 的 Exception Filter 是为HTTP/RPC 请求处理链路设计的,仅在控制器(Controller)的方法、控制器类或全局级别生效。Service 作为业务逻辑层,不属于请求处理的直接上下文,因此@UseFilters装饰器在 Service 方法上不会被 Nest 识别,完全无效。

你的代码问题分析

  1. Service 上的@UseFilters无效:你在DSDynamoDBService.putItem上添加的@UseFilters(HttpExceptionFilter)不会触发任何过滤逻辑,Nest 只会处理控制器相关的过滤器配置。
  2. 未捕获的 Promise 拒绝:在ReqResLoggerInterceptor中调用this.dsDynamoDBService.putItem()时,没有使用await,导致这个异步方法抛出的异常无法被外层的try/catch捕获,最终变成未处理的 Promise 拒绝,引发请求挂起(Node.js 中未处理的 Promise 拒绝会导致进程无响应或崩溃)。
  3. Exception Filter 的上下文不匹配:你的HttpExceptionFilter依赖 HTTP 请求/响应上下文(host.switchToHttp()),但 Service 抛出的异常如果没有传到控制器的处理链路,Filter 根本无法获取到 HTTP 上下文来处理。

修复方案

1. 移除 Service 上无效的@UseFilters

修改dynamo_db.service.ts,删掉方法上的@UseFilters装饰器:

import { DynamoDB } from 'aws-sdk';

import { DSDynamoDB } from '@document/utils/aws/dynamodb';
import { NotImplementedException, Injectable } from '@nestjs/common';

@Injectable()
export class DSDynamoDBService {
    constructor(private readonly dynamoDb: DSDynamoDB) {}

    async putItem(param?: DynamoDB.DocumentClient.PutItemInput): Promise<void> {
        try {
            await this.dynamoDb.getDynamoDBDocClient().put(param).promise();
        } catch (error) {
            throw new NotImplementedException('Update to data failed.', { cause: error, description: 'An Error occured to prevent the data to update.' });
        }
    }
}

2. 在 Interceptor 中正确捕获 Service 异常

调用异步方法时必须加await,让异常能被外层try/catch捕获:

@Injectable()
export class ReqResLoggerInterceptor implements NestInterceptor {
    constructor(/* 你的依赖注入 */) {}

    async intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
        const preventReqResLog: boolean = this.reflector.get<boolean>('preventReqResLog', context.getHandler());
        const req = context.switchToHttp().getRequest();
        const { originalUrl, method, params, query, headers, body } = req;
        const isHealthRoute: boolean = originalUrl.endsWith('health/detailed') || originalUrl.endsWith('health');
        let requestDetails;

        try {
            if (!isHealthRoute && !preventReqResLog) {
                requestDetails = {
                    originalUrl,
                    method,
                    params,
                    query,
                    headers,
                    requestId: DSRequestContextService.getCurrentReqId(),
                    body
                };
                this.logService.info(JSON.stringify(requestDetails));

                // 关键:添加 await 捕获异步异常
                await this.dsDynamoDBService.putItem({
                    TableName: this.dsConfig.aws.dynamoDB.table,
                    Item: requestDetails
                });
            }
            (req as any).isLogReqRes = !preventReqResLog;
        } catch (error) {
            this.logService.info(`Saving ${JSON.stringify(requestDetails)} failed.`);
            this.logService.error(error);
            // 如果需要将异常传递到控制器的 Filter 处理,可以重新抛出
            // throw error;
        }

        return next.handle().pipe(
            tap((data) => {
                try {
                    if (!isHealthRoute && data) {
                        data.requestId = requestDetails?.requestId;
                    }
                    if (!isHealthRoute && !preventReqResLog) {
                        this.logService.info(
                            JSON.stringify({
                                originalUrl,
                                method,
                                params,
                                query,
                                headers,
                                requestId: requestDetails?.requestId,
                                statusCode: req.res?.statusCode,
                                data
                            })
                        );
                    }
                } catch (error) {
                    this.logService.error(error);
                }
            })
        );
    }
}

3. 让 Exception Filter 正常工作

如果希望HttpExceptionFilter处理 Service 抛出的异常,需要确保异常最终传递到控制器的请求链路:

  • 在 Interceptor 的catch块中重新抛出异常(如上面代码中的注释部分),这样异常会被控制器层面的 Filter 捕获。
  • 确保HttpExceptionFilter已正确注册:
    • 全局注册(在main.ts中):
      async function bootstrap() {
        const app = await NestFactory.create(AppModule);
        app.useGlobalFilters(new HttpExceptionFilter());
        await app.listen(3000);
      }
      bootstrap();
      
    • 或者在对应的控制器上添加@UseFilters(HttpExceptionFilter)装饰器。

内容的提问来源于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.16 11:50:08