Nest.js技术疑问:Exception Filter能否在Service中使用?
问题解答
核心结论:Exception Filter 不能直接作用于 Service 方法级别
Nest.js 的 Exception Filter 是为HTTP/RPC 请求处理链路设计的,仅在控制器(Controller)的方法、控制器类或全局级别生效。Service 作为业务逻辑层,不属于请求处理的直接上下文,因此@UseFilters装饰器在 Service 方法上不会被 Nest 识别,完全无效。
你的代码问题分析
- Service 上的
@UseFilters无效:你在DSDynamoDBService.putItem上添加的@UseFilters(HttpExceptionFilter)不会触发任何过滤逻辑,Nest 只会处理控制器相关的过滤器配置。 - 未捕获的 Promise 拒绝:在
ReqResLoggerInterceptor中调用this.dsDynamoDBService.putItem()时,没有使用await,导致这个异步方法抛出的异常无法被外层的try/catch捕获,最终变成未处理的 Promise 拒绝,引发请求挂起(Node.js 中未处理的 Promise 拒绝会导致进程无响应或崩溃)。 - 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
相关产品推荐
相关产品推荐

