Spring Boot 优雅地实现接口参数校验( 二 )

SimpleController 增加一个抛出异常的方法 。
@RestControllerpublic class SimpleController {@PostMapping("/users")public String register(@Valid @RequestBody UserInfoIDto userInfoIDto){System.out.println("开始注册用户...");return "success";}@GetMapping("/users")public Response<UserInfoIDto> list() {UserInfoIDto userInfoIDto = new UserInfoIDto();userInfoIDto.setUsername("monday");userInfoIDto.setAge(30);userInfoIDto.setPhone("123456789");if (true) {throw new APIException(ErrorCodeEnum.ERROR);}// 为了保持数据格式统一,必须使用Response包装一下return new Response<>(userInfoIDto);}}

Spring Boot 优雅地实现接口参数校验

文章插图
 
报错返回的格式 。
Spring Boot 优雅地实现接口参数校验

文章插图
 
不报错,返回的格式 。
Spring Boot 优雅地实现接口参数校验

文章插图
 
6. 去掉接口中的Response包装@RestControllerAdvice既可以全局拦截异常也可拦截指定包下正常的返回值,可以对返回值进行修改 。
@RestControllerAdvice(basePackages = {"com.example.validator.controller"})public class ResponseControllerAdvice implements ResponseBodyAdvice<Object> {/*** 对那些方法需要包装,如果接口直接返回Response就没有必要再包装了** @param returnType* @param aClass* @return 如果为true才会执行beforeBodyWrite*/@Overridepublic boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> aClass) {return !returnType.getParameterType().equals(Response.class);}@Overridepublic Object beforeBodyWrite(Object data, MethodParameter returnType, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest request, ServerHttpResponse response) {// String类型不能直接包装,所以要进行些特别的处理if (returnType.getGenericParameterType().equals(String.class)) {ObjectMapper objectMapper = new ObjectMapper();try {// 将数据包装在Response里后,再转换为json字符串响应给前端return objectMapper.writeValueAsString(new Response<>(data));} catch (JsonProcessingException e) {throw new APIException(ErrorCodeEnum.ERROR);}}// 这里统一包装return new Response<>(data);}}@RestControllerpublic class SimpleController {@GetMapping("/users")public UserInfoIDto list() {UserInfoIDto userInfoIDto = new UserInfoIDto();userInfoIDto.setUsername("monday");userInfoIDto.setAge(30);userInfoIDto.setPhone("123456789");// 直接返回值,不需要再使用Response包装return userInfoIDto;}}
Spring Boot 优雅地实现接口参数校验

文章插图
 
7. 每个校验错误都对应不同的错误码@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.FIELD})public @interface ValidateErrorCode {/** 校验错误码 code */int value() default 100000;}@Datapublic class UserInfoIDto {@NotBlank@Email@ValidateErrorCode(value = https://www.isolves.com/it/cxkf/kj/2020-11-19/20000)private String email;@NotBlank@Pattern(regexp="^((13[0-9])|(15[^4,/D])|(18[0,3-9]))/d{8}$", message="手机号格式不正确")@ValidateErrorCode(value = 30000)private String phone;}校验异常获取注解中的错误码 。
@ExceptionHandler(MethodArgumentNotValidException.class)public Response<String> methodArgumentNotValidException(MethodArgumentNotValidException e) throws NoSuchFieldException {// 从异常对象中拿到ObjectError对象ObjectError objectError = e.getBindingResult().getAllErrors().get(0);// 参数的Class对象,等下好通过字段名称获取Field对象Class<?> parameterType = e.getParameter().getParameterType();// 拿到错误的字段名称String fieldName = e.getBindingResult().getFieldError().getField();Field field = parameterType.getDeclaredField(fieldName);// 获取Field对象上的自定义注解ValidateErrorCode annotation = field.getAnnotation(ValidateErrorCode.class);if (annotation != null) {return new Response<>(annotation.value(),objectError.getDefaultMessage());}// 然后提取错误提示信息进行返回return new Response<>(ErrorCodeEnum.VALIDATE_FAILED.getCode(), objectError.getDefaultMessage());}
Spring Boot 优雅地实现接口参数校验

文章插图
 

Spring Boot 优雅地实现接口参数校验

文章插图
 
8. 个别接口不统一包装响应有时候第三方接口回调我们的接口,我们的接口必须按照第三方定义的返回格式来,此时第三方不一定和我们自己的返回格式一样,所以要提供一种可以绕过统一包装的方式 。
@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD})public @interface NotResponseWrap {}


推荐阅读