Springboot естественно поддерживает элегантную проверку параметров с помощью hibernate validation, если ее не использовать, то можно только вручную проверять параметры один за другим следующим способом, что не только некрасиво, но и очень неудобно в использовании:
if(StringUtils.isEmpty(userName)){
throw new RuntimeException("用户名不能为空");
}
Далее будет представлено основное использование проверки гибернации.
1. Введите зависимости
Были проведены эксперименты springboot 2.4.1 Здесь введены следующие зависимости:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.6.Final</version>
</dependency>
</dependencies>
2. Проверка основных параметров запроса
В вызове запроса Spring MVC есть параметр id (целочисленный тип) следующим образом, что мне делать, если он не может быть пустым
- Добавьте аннотацию @Validated в контроллер.
- Добавьте @NotNull (сообщение = «идентификатор пользователя не может быть нулевым») перед полями, которые необходимо проверить.
- Определите глобальный класс обработки исключений и настройте возвращаемый результат.
@RestControllerAdvice
@Slf4j
public class ValidationAdvice {
@ExceptionHandler(Exception.class)
@ResponseBody
public WrapperResult handler(Exception e) {
//获取异常信息,获取异常堆栈的完整异常信息
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
//日志输出异常详情
log.error(sw.toString());
return WrapperResult.faild("服务异常,请稍后再试");
}
@ExceptionHandler(ConstraintViolationException.class)
@ResponseBody
public WrapperResult handler(ConstraintViolationException e) {
StringBuffer errorMsg = new StringBuffer();
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
violations.forEach(x -> errorMsg.append(x.getMessage()).append(";"));
return WrapperResult.faild(errorMsg.toString());
}
}
Код уровня контроллера выглядит следующим образом:
@RestController
@Slf4j
@RequestMapping("/user")
@Validated
public class UserController {
/**
* 根据id查询用户信息
*
* @param id
* @return
*/
@GetMapping
public WrapperResult<UserModel> findUser(@NotNull(message = "用户id不能为空")
@RequestParam(value = "id")
String id) {
return WrapperResult.success(new UserModel());
}
}
При запросе 127.0.0.1:8080/user?id= будет возвращен результат
{
"status": 1,
"data": "用户id不能为空;",
"msg": "FAIL",
"success": false
}
В-третьих, проверка параметров объекта
Выше приведен запрос GET, а ниже описан запрос POST, проверка параметров в объекте запроса.
1. Добавьте аннотацию @Validated в класс Controller.
@RestController
@Slf4j
@RequestMapping("/user")
**@Validated**
public class UserController {
}
2. Добавьте аннотацию @Validated перед параметрами метода запроса POST.
@PostMapping("/mobile-regist")
public WrapperResult<Boolean> mobileRegit(@Validated @RequestBody UserModel userModel) {
return WrapperResult.success(true);
}
3. Добавьте захват исключения проверки параметров объекта в класс ValidationAdvice, описанный выше.
//处理校验异常,对于对象类型的数据的校验异常
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public WrapperResult handler(MethodArgumentNotValidException e) {
StringBuffer sb = new StringBuffer();
List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
allErrors.forEach(msg -> sb.append(msg.getDefaultMessage()).append(";"));
return WrapperResult.faild(sb.toString());
}
Класс UserModel определяется следующим образом:
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class UserModel {
@NotEmpty(message = "姓名不能为空")
private String name;
@NotEmpty(message = "手机号不能为空")
// @Mobile(message = "手机号格式不正确")
private String mobile;
@NotEmpty(message = "电子邮箱不能为空")
@Email(message = "电子邮箱格式不正确")
private String email;
private String password;
private String address;
@NotNull(message = "年龄不能为空")
@Min(value = 12, message = "允许注册年龄最小为12岁")
@Max(value = 24, message = "允许年龄最大为24岁")
private Integer age;
@NotEmpty(message = "联系人不允许为空")
@Size(min = 1, max = 3, message = "联系人长度只允许1到3之间")
private List<String> contacts;
}
Если запрос POST выглядит так
{
"name":"",
"mobile":"12666666666",
"email":"",
"password":"",
"address":"",
"age": null,
"contacts":[
]
}
Будут возвращены следующие настраиваемые результаты возврата:
{
"status": 1,
"data": "电子邮箱不能为空;联系人长度只允许1到3之间;年龄不能为空;联系人不允许为空;姓名不能为空;手机号格式不正确;",
"msg": "FAIL",
"success": false
}
4. Пользовательский валидатор
Аннотации, такие как @NotNull и @Email, являются встроенными аннотациями при проверке гибернации. Мы хотим разработать аннотации с теми же функциями, что и аннотации @Email. Как это сделать, например, @Mobile, его использование будет точно таким же, как @ Электронное письмо.
Сначала определите класс инструмента для хранения двух постоянных значений ValidationUtil.
public class ValidationUtil {
//手机号校验正则
public static final String MOBILE_REGX = "^[1][3-9][0-9]{9}$";
public static final String MOBILE_MSG = "手机号格式错误";
}
1. Определите аннотацию Mobile
Конкретный код может относиться к реализации @Email, просто измените имя электронной почты на Mobile, как показано ниже:
@Documented
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
public @interface Mobile {
String message() default ValidationUtil.MOBILE_MSG;
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String regexp() default ValidationUtil.MOBILE_REGX;
Pattern.Flag[] flags() default {};
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
public @interface List {
Mobile[] value();
}
}
2. Определите MobileValidator для реализации логики проверки параметров.
public class MobileValidator implements ConstraintValidator<Mobile, String> {
private String regexp;
@Override
public void initialize(Mobile constraintAnnotation) {
//获取校验的手机号的格式
this.regexp = constraintAnnotation.regexp();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (!StringUtils.hasText(value)) {
return true;
}
return value.matches(regexp);
}
}
3. Используйте тот же метод, что и @Email
не буду вдаваться в подробности
Пять, групповая проверка
Предполагая сценарий регистрации пользователя, существует три способа регистрации пользователя.
- Регистрация имени пользователя + графического проверочного кода
- Почтовый ящик + регистрация подтверждения почтового ящика
- Мобильный номер + регистрация кода подтверждения по СМС
За исключением различных способов регистрации пользователя, остальная информация о пользователе в основном одинакова.Три интерфейса открываются на серверной части, соответствующие трем методам регистрации.В теле запроса мы используем модель для инкапсуляции всей вышеуказанной информации, включая пользователя. имя, адрес электронной почты, номер мобильного телефона и другая информация, при вызове разных интерфейсов параметры, которые необходимо проверить в модели, разные:
При регистрации с именем пользователя адрес электронной почты и номер мобильного телефона могут быть пустыми, но имя пользователя не может быть пустым, при регистрации через электронную почту адрес электронной почты не может быть пустым, но имя пользователя и номер мобильного телефона могут быть пустыми; …
Пакетная четность имеет дело именно с этой ситуацией.
1. Сначала определите три интерфейса для представления трех групповых категорий.
public interface ValidEmail {
}
public interface ValidMobile {
}
public interface ValidUserName {
}
2. Назовите категорию группы в классе сущностей UserModel.
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class UserModel {
@NotEmpty(message = "姓名不能为空", groups = {ValidUserName.class})
@UserName(groups = {ValidUserName.class})
private String name;
@NotEmpty(message = "手机号不能为空", groups = {ValidMobile.class})
@Mobile(groups = {ValidMobile.class})
private String mobile;
@NotEmpty(message = "电子邮箱不能为空", groups = {ValidEmail.class})
@Email(message = "电子邮箱格式不正确", groups = {ValidEmail.class})
private String email;
private String password;
private String address;
@NotNull(message = "年龄不能为空")
@Min(value = 12, message = "允许注册年龄最小为12岁", groups = {ValidEmail.class,ValidMobile.class,ValidUserName.class})
@Max(value = 24, message = "允许年龄最大为24岁",groups = {ValidEmail.class,ValidMobile.class,ValidUserName.class})
private Integer age;
@NotEmpty(message = "联系人不允许为空",groups = {ValidEmail.class,ValidMobile.class,ValidUserName.class})
@Size(min = 1, max = 3, message = "联系人长度只允许1到3之间",groups = {ValidEmail.class,ValidMobile.class,ValidUserName.class})
private List<String> contacts;
}
3. Назовите группу проверки в методе Controller.
/**
* 手机号注册
*
* @param userModel
* @return
*/
@PostMapping("/mobile-regist")
public WrapperResult<Boolean> mobileRegit(@Validated(ValidMobile.class) @RequestBody UserModel userModel) {
return WrapperResult.success(true);
}
В это время сделайте следующий запрос:
POST http://127.0.0.1:8080/user/mobile-regist
{
"mobile":"12666666666",
"password":"",
"address":"",
"age": null,
"contacts":[
]
}
вернет результат:
{
"status": 1,
"data": "联系人长度只允许1到3之间;手机号格式错误;联系人不允许为空;",
"msg": "FAIL",
"success": false
}
Поля электронной почты и имени пользователя не передаются в запросе, и эти два поля не проверяются в результате, что соответствует ожидаемому результату.
6. Ручная калибровка
Ручная проверка здесь заключается не в том, чтобы использовать if/else для выполнения простой ручной проверки, а в том, чтобы использовать инструмент проверки, который поставляется с валидацией, для выполнения проверки атрибутов объектов сущностей, аннотированных с помощью @NotNull и других аннотаций.
Сначала получите объект проверки:
private static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
1. Полная проверка атрибутов
/**
* 验证某个对象所有字段
*
* @param obj
* @param <T>
* @return
*/
public static <T> ValidationResult validateEntity(T obj) {
ValidationResult result = new ValidationResult();
Set<ConstraintViolation<T>> set = validator.validate(obj, Default.class);
if (!CollectionUtils.isEmpty(set)) {
result.setHasErrors(true);
Map<String, String> errorMsg = new HashMap<>();
for (ConstraintViolation<T> cv : set) {
errorMsg.put(cv.getPropertyPath().toString(), cv.getMessage());
}
result.setErrorMsg(errorMsg);
}
return result;
}
2. Отдельная проверка поля
/**
* 验证某个对象某个字段
*
* @param obj
* @param propertyName
* @param <T>
* @return
*/
public static <T> ValidationResult validateProperty(T obj, String propertyName) {
ValidationResult result = new ValidationResult();
Set<ConstraintViolation<T>> set = validator.validateProperty(obj, propertyName, Default.class);
if (!CollectionUtils.isEmpty(set)) {
result.setHasErrors(true);
Map<String, String> errorMsg = new HashMap<>();
for (ConstraintViolation<T> cv : set) {
errorMsg.put(propertyName, cv.getMessage());
}
result.setErrorMsg(errorMsg);
}
return result;
}
ValidationResult определяется следующим образом:
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class ValidationResult {
private Boolean hasErrors;
private Map<String, String> errorMsg;
}
Семь, проверка загрузки файла
1. Проверка загрузки файла в контейнере tomcat
В проверке загрузки файлов в архитектуре springboot+tomcat, если уже доступна следующая конфигурация:
spring:
servlet:
multipart:
max-file-size: 1MB
max-request-size: 1MB
Это означает, что разрешено загружать только файлы размером менее 1 МБ. Если обработчик исключений не указан, по умолчанию будет сообщено об интерфейсе 400. Добавьте следующий код в класс ValidationAdvice, чтобы настроить возвращаемый результат:
//文件上传文件大小超出限制
@ExceptionHandler(MaxUploadSizeExceededException.class)
@ResponseBody
public WrapperResult<Map<String,Object>> fileSizeException(MaxUploadSizeExceededException exception) {
log.error("文件太大,上传失败",exception);
return WrapperResult.faild("只允许上传不大于"+exception.getMaxUploadSize()+"的文件");
}
2. Другие контейнеры
Метод в 1 в контейнере Jetty может дать сбой и не проверен, в контейнере undertow точно не сработает, и он проверен. Ведь поддонный контейнер не до конца полируется спринг-ботинок, поэтому использовать его на данном этапе не рекомендуется.
8. Приложение
1. Аннотированное описание всех правил проверки
2. Пример аннотации правила проверки
// 空和非空检查: @Null、@NotNull、@NotBlank、@NotEmpty
@Null(message = "验证是否为 null")
private Integer isNull;
@NotNull(message = "验证是否不为 null, 但无法查检长度为0的空字符串")
private Integer id;
@NotBlank(message = "检查字符串是不是为 null,以及去除空格后长度是否大于0")
private String name;
@NotEmpty(message = "检查是否为 NULL 或者是 EMPTY")
private List<String> stringList;
// Boolean值检查: @AssertTrue、@AssertFalse
@AssertTrue(message = " 验证 Boolean参数是否为 true")
private Boolean isTrue;
@AssertFalse(message = "验证 Boolean 参数是否为 false ")
private Boolean isFalse;
// 长度检查: @Size、@Length
@Size(min = 1, max = 2, message = "验证(Array,Collection,Map,String)长度是否在给定范围内")
private List<Integer> integerList;
@Length(min = 8, max = 30, message = "验证字符串长度是否在给定范围内")
private String address;
// 日期检查: @Future、@FutureOrPresent、@Past、@PastOrPresent
@Future(message = "验证日期是否在当前时间之后")
private Date futureDate;
@FutureOrPresent(message = "验证日期是否为当前时间或之后")
private Date futureOrPresentDate;
@Past(message = "验证日期是否在当前时间之前")
private Date pastDate;
@PastOrPresent(message = "验证日期是否为当前时间或之前")
private Date pastOrPresentDate;
// 其它检查: @Email、@CreditCardNumber、@URL、@Pattern、
@ScriptAssert、@UniqueElements
@Email(message = "校验是否为正确的邮箱格式")
private String email;
@CreditCardNumber(message = "校验是否为正确的信用卡号")
private String creditCardNumber;
@URL(protocol = "http", host = "127.0.0.1", port = 8080, message= "校验是否为正确的URL地址")
private String url;
@Pattern(regexp = "^1[3|4|5|7|8][0-9]{9}$", message = "正则校验是否为正确的手机号")
private String phone;
// 对关联对象元素进行递归校验检查
@Valid
@UniqueElements(message = "校验集合中的元素是否唯一")
private List<CalendarEvent> calendarEvent;
@Data
@ScriptAssert(lang = "javascript", script ="_this.startDate.before(_this.endDate)",message = "通过脚本表达式校验参数")
private class CalendarEvent {
private Date startDate;
private Date endDate;
}
// 数值检查: @Min、@Max、@Range、@DecimalMin、@DecimalMax、@Digits
@Min(value = 0, message = "验证数值是否大于等于指定值")
@Max(value = 100, message = "验证数值是否小于等于指定值")
@Range(min = 0, max = 100, message = "验证数值是否在指定值区间范围内")
private Integer score;
@DecimalMin(value = "10.01", inclusive = false, message = "验证数值是否大于等于指定值")
@DecimalMax(value = "199.99", message = "验证数值是否小于等于指定值")
@Digits(integer = 3, fraction = 2, message = "限制整数位最多为3,小数位最多为2")
private BigDecimal money;