NestJS使用自定义装饰器作为验证管道的风险与方案咨询
背景
我数据库中的某类数据结构由sections构成,本质是自定义对象组成的列表。未来sections的数量可能会持续扩展,为了尽可能让代码符合DRY原则,我希望新增、更新、删除条目对应的目标section可以通过参数动态定义。
我很快发现,直接写类似@Body() section: SectionA | SectionB | SectionC...的联合类型写法会让自动校验失效,因此我需要一个能覆盖所有section类型的统一Section DTO。要实现这一点,因为我给字段定义了多个@IsNotEmpty约束,需要动态指定当前要生效的校验规则。
之后我查到有成熟方案推荐使用class-validator的groups特性实现条件校验。
但落地这个方案时遇到了两个问题:
- 我需要自行编写自定义校验管道,实现过程大量参考了NestJS官方文档中class-validator相关的实现说明
- 我本来想覆盖已经配置好的全局校验管道,只在对应接口方法上使用自定义管道,但尝试没有成功,最后只能给每个控制器方法单独定义管道,这个权衡我可以接受,目前也没有更简便的替代方案。
但紧接着我遇到了最后一个卡点:如何在校验器中通过请求参数定义这些groups?这个问题没有现成的简单解法。
解决方案
之前也有人提过如何在验证管道内访问请求对象的问题,但没有给出能直接落地的满意解决方案。
第一种方案建议将管道作用域调整为request级别,但没有说明具体实现方式,网上能找到的相关实现方案都无法正常运行。
第二种方案是改用自定义参数装饰器执行校验,这个方案实际运行效果非常好,以下是简化后的实现代码:
export const ProfileSectionData = createParamDecorator( async (data: unknown, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); // 我不需要从请求里获取元类型,因为我明确知道转换的目标类型,有需要的话也可以动态获取 let object = plainToInstance(SectionDto, request.body); const groups = [request.params.profileSection]; let validatorOptions = { groups, ...defaultOptions }; const errors = await validate(object, validatorOptions); if (errors.length > 0) { throw new BadRequestException(); } return request.body; }, );
潜在影响?
这也是我目前的核心疑问。当初有核心开发者推荐自定义装饰器方案时,特意给出过警告:需要注意的是,如果全局、控制器类级别或者方法级别绑定了内置ValidationPipe,这种写法可能会影响ValidationPipe的正常运行逻辑。
我想知道这个警告具体指代什么问题?这个方案是否存在安全漏洞或者性能缺陷?
很明显这个方案的一个缺点是把校验逻辑写在了验证管道之外,从执行顺序和单一职责原则来看并不理想,但除了代码规范和可维护性层面的问题外,我暂时没发现其他实质性的弊端。
另外结合上述业务背景,是否存在完全不同的更优实现思路?
内容的提问来源于stack exchange,提问作者Liam Clark Gutiérrez

