如何为不同接口的FilterDto指定对应数据库的校验规则?
动态选择数据库校验映射规则的实现方案
方案一:通过自定义校验注解传递映射规则
这种方案直接在控制器参数上指定对应接口的映射枚举,代码直观,耦合度低,适合接口数量较少的场景。
步骤1:改造自定义校验注解
修改@FilterValidatorBehaviour注解,添加mapping属性用于指定映射枚举:
@Target({ElementType.TYPE, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = FilterValidator.class) public @interface FilterValidatorBehaviour { String message() default "无效的过滤条件"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; // 新增:指定要使用的数据库映射枚举 MappingEnum mapping() default MappingEnum.DB1_MAPPING; }
步骤2:更新校验器实现类
在校验器的initialize方法中获取注解指定的映射枚举,用于后续校验逻辑:
public class FilterValidator implements ConstraintValidator<FilterValidatorBehaviour, FilterDto> { private MappingEnum targetMapping; @Override public void initialize(FilterValidatorBehaviour constraintAnnotation) { // 保存注解中指定的映射枚举 this.targetMapping = constraintAnnotation.mapping(); } @Override public boolean isValid(FilterDto filterDto, ConstraintValidatorContext ctx) { // 校验字段名是否为空 if (filterDto.getName() == null) { ctx.disableDefaultConstraintViolation(); ctx.buildConstraintViolationWithTemplate("过滤字段名不能为空") .addConstraintViolation(); return false; } // 获取当前映射对应的字段类型配置 DataBaseTypeObject typeObject = filterDto.getObjectWithFieldName(targetMapping); if (typeObject == null) { ctx.disableDefaultConstraintViolation(); ctx.buildConstraintViolationWithTemplate("无效的过滤字段:" + filterDto.getName()) .addConstraintViolation(); return false; } // 校验值的类型是否匹配 return typeObject.getType().equals(filterDto.getValue().getClass()); } }
步骤3:修改FilterDto类
移除类上的@FilterValidatorBehaviour注解,改为在控制器参数上动态指定:
@Getter @Setter public class FilterDto { String name; String operation; String value; @JsonIgnore public DataBaseTypeObject getObjectWithFieldName(MappingEnum mapping) { return mapping.getMappingMap().get(name); } }
步骤4:更新控制器接口
在两个接口的List<FilterDto>参数上,添加带指定映射的注解:
// /v1/abc接口使用DB1映射规则 @PostMapping(path = "/v1/abc") public PageableDto getAllSomething( @RequestAttribute UserClaim userClaim, @Valid SearchParametersDto searchParametersDto, @RequestBody @Valid @FilterValidatorBehaviour(mapping = MappingEnum.DB1_MAPPING) List<FilterDto> filters) { // 业务逻辑实现 } // /v1/xyz接口使用DB2映射规则 @PostMapping(path = "/v1/xyz") public PageableDto getAllAnotherThing( @RequestAttribute UserClaim userClaim, @Valid SearchParametersDto searchParametersDto, @RequestBody @Valid @FilterValidatorBehaviour(mapping = MappingEnum.DB2_MAPPING) List<FilterDto> filters) { // 业务逻辑实现 }
方案二:通过请求拦截器传递映射规则
这种方案通过拦截器根据请求路径自动设置映射规则,适合接口数量较多或路径有规律的场景。
步骤1:实现请求拦截器
根据请求URI设置对应的映射枚举到请求属性中:
public class DbMappingInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String uri = request.getRequestURI(); if ("/v1/abc".equals(uri)) { request.setAttribute("DB_MAPPING", MappingEnum.DB1_MAPPING); } else if ("/v1/xyz".equals(uri)) { request.setAttribute("DB_MAPPING", MappingEnum.DB2_MAPPING); } return true; } }
步骤2:注册拦截器
在Web配置类中注册拦截器,指定拦截的路径:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new DbMappingInterceptor()) .addPathPatterns("/v1/abc", "/v1/xyz"); } }
步骤3:更新校验器实现类
注入HttpServletRequest,从请求属性中获取映射枚举进行校验:
public class FilterValidator implements ConstraintValidator<FilterValidatorBehaviour, FilterDto> { @Autowired private HttpServletRequest request; @Override public boolean isValid(FilterDto filterDto, ConstraintValidatorContext ctx) { // 获取请求中设置的映射枚举,默认使用DB1 MappingEnum targetMapping = (MappingEnum) request.getAttribute("DB_MAPPING"); if (targetMapping == null) { targetMapping = MappingEnum.DB1_MAPPING; } // 校验字段名是否为空 if (filterDto.getName() == null) { ctx.disableDefaultConstraintViolation(); ctx.buildConstraintViolationWithTemplate("过滤字段名不能为空") .addConstraintViolation(); return false; } // 获取当前映射对应的字段类型配置 DataBaseTypeObject typeObject = filterDto.getObjectWithFieldName(targetMapping); if (typeObject == null) { ctx.disableDefaultConstraintViolation(); ctx.buildConstraintViolationWithTemplate("无效的过滤字段:" + filterDto.getName()) .addConstraintViolation(); return false; } // 校验值的类型是否匹配 return typeObject.getType().equals(filterDto.getValue().getClass()); } }
步骤4:恢复FilterDto类的注解
保留FilterDto类上的@FilterValidatorBehaviour注解:
@Getter @Setter @FilterValidatorBehaviour public class FilterDto { String name; String operation; String value; @JsonIgnore public DataBaseTypeObject getObjectWithFieldName(MappingEnum mapping) { return mapping.getMappingMap().get(name); } }
步骤5:控制器接口无需修改
控制器接口保持原有写法即可,拦截器会自动处理映射规则的传递:
@PostMapping(path = "/v1/abc") public PageableDto getAllSomething( @RequestAttribute UserClaim userClaim, @Valid SearchParametersDto searchParametersDto, @RequestBody @Valid List<FilterDto> filters) { // 业务逻辑实现 } @PostMapping(path = "/v1/xyz") public PageableDto getAllAnotherThing( @RequestAttribute UserClaim userClaim, @Valid SearchParametersDto searchParametersDto, @RequestBody @Valid List<FilterDto> filters) { // 业务逻辑实现 }
内容的提问来源于stack exchange,提问作者Sunny Khatik
相关产品推荐
相关产品推荐

