Сегодня я продолжу делиться с вами тем, как элегантно проверить правильность параметров интерфейса на работе и как единообразно обрабатывать формат json, возвращаемый интерфейсом. Каждое слово — галантерея, оригинальность непроста, делиться не просто.
Кроме того, я также разобрал некоторые материалы по Java, и вы можете получить их сами, если они вам нужны!Наиболее полное исследование отмечает Dachang Zhenti + Microservices + MySQL + Distributed + SSM Framework + Java + Redis + Структура данных и алгоритм + Сеть + Linux + Spring Bucket Family + JVM + High Concurrency + Major Learning Thinking Brain Maps + Сборник интервью
Валидация в основном предназначена для проверки законности данных, представленных пользователем, например, является ли она пустой, соответствует ли пароль правилам, является ли формат электронной почты правильным и т. д. Существует множество платформ проверки, и наиболее часто используемой является hibernate-validator, который также поддерживает интернационализацию, вы также можете настроить аннотацию типа проверки.Вот просто демонстрация простой интеграции среды проверки в Spring Boot.Для получения дополнительной информации обратитесь к hibernate-validator.
1. pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2. dto
public class UserInfoIDto {
private Long id;
@NotBlank
@Length(min=3, max=10)
private String username;
@NotBlank
@Email
private String email;
@NotBlank
@Pattern(regexp="^((13[0-9])|(15[^4,\\D])|(18[0,3-9]))\\d{8}$", message="手机号格式不正确")
private String phone;
@Min(value=18)
@Max(value = 200)
private int age;
@NotBlank
@Length(min=6, max=12, message="昵称长度为6到12位")
private String nickname;
// Getter & Setter
}
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("开始注册用户...");
return "success";
}
}
4. Удалите параметр BindingResult.
Для каждого интерфейса требуется параметр BindingResult, и каждый интерфейс должен обрабатывать информацию об ошибках, поэтому добавление параметра не является элегантным, а объем кода для обработки информации об ошибках также очень повторяющийся. Если параметр BindingResult будет удален, система сообщит об ошибке MethodArgumentNotValidException.Нам нужно использовать только глобальное исключение, чтобы поймать ошибку, а параметр BindingResult можно опустить после обработки.
@RestController
public class SimpleController {
@PostMapping("/users")
public String register(@Valid @RequestBody UserInfoIDto userInfoIDto){
System.out.println("开始注册用户...");
return "success";
}
}
@RestControllerAdvice используется для перехвата всех @RestController
@RestControllerAdvice
public class GlobalExceptionHandlerAdvice {
@ExceptionHandler(MethodArgumentNotValidException.class)
public String methodArgumentNotValidException(MethodArgumentNotValidException e) {
// 从异常对象中拿到ObjectError对象
ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
// 然后提取错误提示信息进行返回
return objectError.getDefaultMessage();
}
}
5. Единый формат возврата
Перечисление кодов ошибок
@Getter
public enum ErrorCodeEnum {
SUCCESS(1000, "成功"),
FAILED(1001, "响应失败"),
VALIDATE_FAILED(1002, "参数校验失败"),
ERROR(5000, "未知错误");
private Integer code;
private String msg;
ErrorCodeEnum(int code, String msg) {
this.code = code;
this.msg = msg;
}
}
Пользовательское исключение.
@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();
}
}
Определите формат возврата.
@Getter
public class Response<T> {
/**
* 状态码,比如1000代表响应成功
*/
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;
}
}
Глобальный обработчик исключений усиливает перехват APIException и изменяет формат данных, возвращаемых при возникновении исключения.
@RestControllerAdvice
public class GlobalExceptionHandlerAdvice {
@ExceptionHandler(MethodArgumentNotValidException.class)
public Response<String> methodArgumentNotValidException(MethodArgumentNotValidException e) {
// 从异常对象中拿到ObjectError对象
ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
// 然后提取错误提示信息进行返回
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());
}
}
SimpleController добавляет метод, который генерирует исключение.
@RestController
public 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);
}
}
Формат возврата ошибки.
Об ошибке не сообщается, формат возвращается.
6. Удалите оболочку Response в интерфейсе
@RestControllerAdvice может глобально перехватывать как исключения, так и обычные возвращаемые значения в указанном пакете, а также может изменять возвращаемое значение.
@RestControllerAdvice(basePackages = {"com.example.validator.controller"})
public class ResponseControllerAdvice implements ResponseBodyAdvice<Object> {
/**
* 对那些方法需要包装,如果接口直接返回Response就没有必要再包装了
*
* @param returnType
* @param aClass
* @return 如果为true才会执行beforeBodyWrite
*/
@Override
public 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类型不能直接包装,所以要进行些特别的处理
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);
}
}
@RestController
public class SimpleController {
@GetMapping("/users")
public UserInfoIDto list() {
UserInfoIDto userInfoIDto = new UserInfoIDto();
userInfoIDto.setUsername("monday");
userInfoIDto.setAge(30);
userInfoIDto.setPhone("123456789");
// 直接返回值,不需要再使用Response包装
return userInfoIDto;
}
}
7. Каждой ошибке проверки соответствует свой код ошибки.
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface ValidateErrorCode {
/** 校验错误码 code */
int value() default 100000;
}
@Data
public class UserInfoIDto {
@NotBlank
@Email
@ValidateErrorCode(value = 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());
}
8. Отдельные интерфейсы не всегда упаковывают ответы
Иногда сторонний интерфейс вызывает наш интерфейс, и наш интерфейс должен следовать формату возврата, определенному третьей стороной.В настоящее время третья сторона не обязательно совпадает с нашим собственным форматом возврата, поэтому нам нужно предоставить способ обойти унифицированную упаковку.
@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("开始注册用户...");
return "success";
}
}
ResponseControllerAdvice добавляет условие отсутствия упаковки, и упаковка пропускается, если настроена аннотация @NotResponseWrap.
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> aClass) {
return !(returnType.getParameterType().equals(Response.class) || returnType.hasMethodAnnotation(NotResponseWrap.class));
}