Today I will continue to share with you how to gracefully verify the validity of the interface parameters and how to uniformly handle the JSON format returned by the interface. Every word is dry goods, original is not easy, not easy to share.

Validation is mainly used to verify the validity of the data submitted by the user, such as whether the password is empty, whether the password conforms to the rules, whether the mailbox format is correct, etc. There are many validation frameworks, such as hibernate-Validator, which also supports internationalization, and can also customize the annotation of the validation type. This is just a simple demonstration of the validation framework’s simple integration with Spring Boot. For more information, see Hibernate-Validator.

1. pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Copy the code

2. dto

public class UserInfoIDto { private Long id; @NotBlank @Length(min=3, max=10) private String username; @NotBlank @Email private String email; @ NotBlank @ the Pattern (regexp = "^ ([0-9] (13) | (15 [^ 4 \ \ D]) | (18 [0, 3-9])) \ \ D {8} $", message =" phone number format is not correct ") private String phone; @Min(value=18) @Max(value = 200) private int age; @notblank @length (min=6, Max =12, message=" nickname ") private String nickname; // Getter & Setter }Copy the code

3. controller

import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; @RestController public class SimpleController { @PostMapping("/users") public String register(@Valid @RequestBody UserInfoIDto userInfoIDto, BindingResult result){ if (result.hasErrors()) { FieldError fieldError = result.getFieldError(); String field = fieldError.getField(); String msg = fieldError.getDefaultMessage(); return field + ":" + msg; } system.out. println(" Start registering user...") ); return "success"; }}Copy the code

4. Delete the BindingResult parameter

Each interface needs the BindingResult parameter, and each interface needs to handle error messages, so adding one parameter is not elegant, and the amount of code to handle error messages is repetitive. If you remove BindingResult parameters, the system will be an error MethodArgumentNotValidException, we only need to use the exception to capture this error, it is ok to handle omit preach BindingResult parameters.

@RestController public class SimpleController { @PostMapping("/users") public String register(@Valid @RequestBody UserInfoIDto UserInfoIDto){system.out.println (" Start registering user... ); return "success"; }}Copy the code

@RestControllerAdvice intercepts all @RestControllers

@RestControllerAdvice public class GlobalExceptionHandlerAdvice { @ExceptionHandler(MethodArgumentNotValidException.class) public String MethodArgumentNotValidException (methodArgumentNotValidException e) {/ / get ObjectError objects from the exception object ObjectError ObjectError = e.getBindingResult().getAllErrors().get(0); / / and then extract the error information returned return objectError. GetDefaultMessage (); }}Copy the code

5. Use the same return format

Error code enumeration

@getter Public enum ErrorCodeEnum {SUCCESS(1000, "successful "), FAILED(1001," FAILED "), VALIDATE_FAILED(1002, "FAILED "), ERROR(5000, "unknown ERROR "); private Integer code; private String msg; ErrorCodeEnum(int code, String msg) { this.code = code; this.msg = msg; }}Copy the code

Custom exceptions.

@Getter public class APIException extends RuntimeException { private int code; private String msg; public APIException(ErrorCodeEnum errorCodeEnum) { super(errorCodeEnum.getMsg()); this.code = errorCodeEnum.getCode(); this.msg = errorCodeEnum.getMsg(); }}Copy the code

Define the return format.

@getter public class Response<T> {private int code; private int code; /** * private String MSG; /** * private T data; public Response(T data) { this.code = ErrorCodeEnum.SUCCESS.getCode(); this.msg = ErrorCodeEnum.SUCCESS.getMsg(); this.data = data; } public Response(int code, String msg) { this.code = code; this.msg = msg; }}Copy the code

The global exception handler adds interceptions to the APIException and modifies the data format returned when the exception occurs.

@RestControllerAdvice public class GlobalExceptionHandlerAdvice { @ExceptionHandler(MethodArgumentNotValidException.class) public Response<String> MethodArgumentNotValidException (methodArgumentNotValidException e) {/ / get ObjectError objects from the exception object ObjectError ObjectError = e.getBindingResult().getAllErrors().get(0); / / and then extract the error information returned to return new Response < > (ErrorCodeEnum. VALIDATE_FAILED. GetCode (), objectError. GetDefaultMessage ()); } @ExceptionHandler(APIException.class) public Response<String> APIExceptionHandler(APIException e) { return new Response<>(e.getCode(), e.getMsg()); }}Copy the code

SimpleController adds a method that throws an exception.

@RestController public class SimpleController { @PostMapping("/users") public String register(@Valid @RequestBody UserInfoIDto UserInfoIDto){system.out.println (" Start registering user... ); 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); Return new Response<>(userInfoIDto); }}Copy the code

Error return format.

No error, return the format.

6. Remove the Response package from the interface

@restControllerAdvice Intercepts global exceptions and normal return values in a specified package. You can modify the return values.

@RestControllerAdvice(basePackages = {"com.example.validator.controller"}) public class ResponseControllerAdvice Implements ResponseBodyAdvice<Object> {/** * * * @param returnType * @param aClass * @return beforeBodyWrite */ @Override Public if true  boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<? >> aClass) { return ! returnType.getParameterType().equals(Response.class); } @Override public Object beforeBodyWrite(Object data, MethodParameter returnType, MediaType mediaType, Class<? extends HttpMessageConverter<? >> aClass, ServerHttpRequest Request, ServerHttpResponse Response) {// String type cannot be wrapped directly, So for some special processing if (returnType. GetGenericParameterType (.) the equals (String. Class)) {ObjectMapper ObjectMapper = new ObjectMapper(); Try {/ / will be packed data in the Response, and then converted to a json string Response to the front return objectMapper. WriteValueAsString (new Response < > (data)); } catch (JsonProcessingException e) { throw new APIException(ErrorCodeEnum.ERROR); Return new Response<>(data); } } @RestController public class SimpleController { @GetMapping("/users") public UserInfoIDto list() { UserInfoIDto userInfoIDto = new UserInfoIDto(); userInfoIDto.setUsername("monday"); userInfoIDto.setAge(30); userInfoIDto.setPhone("123456789"); Return userInfoIDto; return userInfoIDto; }}Copy the code

7. Each verification error corresponds to a different error code

@Retention(retentionPolicy.runtime) @Target({elementtype.field}) public @interface ValidateErrorCode {/** ValidateErrorCode */  int value() default 100000; } @Data public class UserInfoIDto { @NotBlank @Email @ValidateErrorCode(value = 20000) private String email; @ NotBlank @ the Pattern (regexp = "^ ([0-9] (13) | (15 [^ 4 \ \ D]) | (18 [0, 3-9])) \ \ D {8} $". Message =" Phone number format is incorrect ") @validATEErrorCode (value = 30000) private String phone; }Copy the code

Validation exceptions get error codes in annotations.

@ExceptionHandler(MethodArgumentNotValidException.class) public Response<String> methodArgumentNotValidException(MethodArgumentNotValidException e) throws NoSuchFieldException { // ObjectError ObjectError = lletBindingResult ().getallErrors ().get(0); Class<? Class<? > parameterType = e.getParameter().getParameterType(); String fieldName = LLDB etBindingResult().getFielderror ().getField(); Field field = parameterType.getDeclaredField(fieldName); // Get a custom annotation on the Field object. ValidateErrorCode Annotation = field.getannotation (ValidateErrorCode.class); if (annotation ! = null) { return new Response<>(annotation.value(),objectError.getDefaultMessage()); } / / and then extract the error information returned to return new Response < > (ErrorCodeEnum. VALIDATE_FAILED. GetCode (), objectError. GetDefaultMessage ()); }Copy the code

8. Individual interfaces do not uniformly wrap responses

Sometimes a third party interface calls back to our interface, and our interface must follow the return format defined by the third party, which may not be the same as our own return format, so provide a way to bypass the uniform wrapper.

@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface NotResponseWrap { } @RestController public class SimpleController { @NotResponseWrap @PostMapping("/users") public String register(@Valid @RequestBody UserInfoIDto UserInfoIDto){system.out.println (" Start registering user... ); return "success"; }}Copy the code

ResponseControllerAdvice adds an unwrapped condition that bypasses wrapping if @notresponseWrap is configured.

@Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<? >> aClass) { return ! (returnType.getParameterType().equals(Response.class) || returnType.hasMethodAnnotation(NotResponseWrap.class)); }Copy the code