如何在Spring Boot+OpenAPI场景下实现用户信息差异化验证?
差异化验证实现方案
针对注册需验证密码非空、更新无需验证密码的场景,且无法使用JSR-303,以下是几种基于Spring Validator的可行实现方式:
方案一:为不同请求绑定专属验证器(推荐)
通过为Controller中不同方法的参数指定唯一名称,再用@InitBinder为对应参数绑定专属验证器,实现场景分离,职责更清晰。
步骤1:创建场景专属验证器
注册场景验证器:
@Component public class UserSignUpValidator extends AbstractValidator { @Override public boolean supports(Class<?> clazz) { return User.class.equals(clazz); } @Override public void validate(Object target, Errors errors) { rejectIfBlank(errors, "username"); rejectIfBlank(errors, "password"); // 注册强制验证密码非空 rejectIfBlank(errors, "name"); rejectIfNull(errors,"phone"); rejectIfBadPhone(errors, "phone"); rejectIfNull(errors,"email"); rejectIfBadEmail(errors,"email"); } }
更新场景验证器:
@Component public class UserUpdateValidator extends AbstractValidator { @Override public boolean supports(Class<?> clazz) { return User.class.equals(clazz); } @Override public void validate(Object target, Errors errors) { rejectIfBlank(errors, "username"); // 更新场景跳过密码验证 rejectIfBlank(errors, "name"); rejectIfNull(errors,"phone"); rejectIfBadPhone(errors, "phone"); rejectIfNull(errors,"email"); rejectIfBadEmail(errors,"email"); } }
步骤2:修改Controller绑定验证器
为不同方法的参数指定唯一名称,通过@InitBinder对应绑定:
@RestController @RequiredArgsConstructor public class UserController implements UserApi { private final UserService userService; private final UserSignUpValidator signUpValidator; private final UserUpdateValidator updateValidator; @Override public ResponseEntity<Token> signUpUser(@Valid @RequestBody User signUpUser) { return ResponseEntity.ok(userService.signUp(signUpUser)); } @Override public ResponseEntity<Void> updateUser(@Valid @RequestBody User updateUser) { userService.update(updateUser); return ResponseEntity.ok(); } // 为注册参数绑定注册验证器 @InitBinder("signUpUser") protected void initSignUpBinder(WebDataBinder binder) { binder.addValidators(signUpValidator); } // 为更新参数绑定更新验证器 @InitBinder("updateUser") protected void initUpdateBinder(WebDataBinder binder) { binder.addValidators(updateValidator); } }
方案二:在统一验证器中根据请求场景判断逻辑
通过RequestContextHolder获取当前请求信息,判断是注册还是更新场景,动态执行验证逻辑,适合简单场景快速实现。
@Component public class UserValidator extends AbstractValidator { @Override public boolean supports(Class<?> clazz) { return User.class.equals(clazz); } @Override public void validate(Object target, Errors errors) { // 通用验证逻辑 rejectIfBlank(errors, "username"); rejectIfBlank(errors, "name"); rejectIfNull(errors,"phone"); rejectIfBadPhone(errors, "phone"); rejectIfNull(errors,"email"); rejectIfBadEmail(errors,"email"); // 根据请求信息判断场景(替换为你的实际接口路径和请求方法) RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); if (requestAttributes instanceof ServletRequestAttributes servletRequestAttributes) { HttpServletRequest request = servletRequestAttributes.getRequest(); boolean isSignUp = "/api/sign-up".equals(request.getRequestURI()) && HttpMethod.POST.name().equals(request.getMethod()); if (isSignUp) { rejectIfBlank(errors, "password"); // 注册场景验证密码 } } } }
方案三:自定义场景注解传递验证标识
通过自定义注解标记参数的验证场景,在验证器中读取注解信息执行对应逻辑,灵活性更高。
步骤1:创建场景注解
@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface ValidationScenario { enum Type { SIGN_UP, UPDATE } Type value(); }
步骤2:Controller方法标记注解
@Override public ResponseEntity<Token> signUpUser(@Valid @ValidationScenario(Type.SIGN_UP) @RequestBody User user) { return ResponseEntity.ok(userService.signUp(user)); } @Override public ResponseEntity<Void> updateUser(@Valid @ValidationScenario(Type.UPDATE) @RequestBody User user) { userService.update(user); return ResponseEntity.ok(); }
步骤3:验证器中读取注解执行逻辑
@Component public class UserValidator extends AbstractValidator { @Override public boolean supports(Class<?> clazz) { return User.class.equals(clazz); } @Override public void validate(Object target, Errors errors) { // 通用验证逻辑 rejectIfBlank(errors, "username"); rejectIfBlank(errors, "name"); rejectIfNull(errors,"phone"); rejectIfBadPhone(errors, "phone"); rejectIfNull(errors,"email"); rejectIfBadEmail(errors,"email"); // 获取当前请求的HandlerMethod,读取参数注解 RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); if (requestAttributes instanceof ServletRequestAttributes servletRequestAttributes) { HttpServletRequest request = servletRequestAttributes.getRequest(); HandlerMethod handlerMethod = (HandlerMethod) request.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE); if (handlerMethod != null) { for (MethodParameter param : handlerMethod.getMethodParameters()) { if (param.getParameterType().equals(User.class)) { ValidationScenario scenario = param.getParameterAnnotation(ValidationScenario.class); if (scenario != null && scenario.value() == ValidationScenario.Type.SIGN_UP) { rejectIfBlank(errors, "password"); } break; } } } } } }
内容的提问来源于stack exchange,提问作者Aleksey
相关产品推荐
相关产品推荐

