Проверка параметров в SpringBoot

Java

Введение

В работе по внутренней разработке Java некоторые проверки часто выполняются для параметров, переданных из внешнего интерфейса.В бизнесе возникает исключение или информация о проверке, когда исключение постоянно возвращается.Он полон кодов проверки, таких как как будто-иначе.Довольно многословно в коде. Например, когда пользователь регистрируется, он проверяет правильность формата мобильного телефона, длину имени пользователя и т. д. Хотя внешний интерфейс также может выполнять проверку параметров, чтобы обеспечить надежность нашего API-интерфейса и правильность конечного хранения данных, проверку параметров внутреннего интерфейса нельзя игнорировать.Hibernate ValidatorПредоставляет унифицированный и удобный способ быстрой реализации проверки параметров.

Hibernate ValidatorИспользуйте аннотации для реализации декларативной проверки. С точки зрения принципа реализации, он также основан на перехвате Spring AOP для реализации операций, связанных с проверкой.javax.validation.constraintsВ пакете определен ряд аннотаций ограничений. Некоторые часто используемые аннотации будут размещены в конце статьи. Если фреймворком проекта является SpringBoot, вspring-boot-starter-webуже включены вHibernate-validatorзависимости (Версия ).

2.3в будущих версияхspring-boot-starter-webЭта зависимость была удалена и должна быть введена вручнуюHibernate-validatorполагаться. Эта статья была обновлена ​​до2.3.3.

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

Полный код загружен на GitHub:GitHub.com/night777/val…

RequestBodyпроверка параметров

1. Создайте класс сущностей

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.*;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User {
    /**
     * 用户ID
     */
    @NotNull(message = "用户id不能为空")
    private Long userId;

    /**
     * 用户名
     */
    @NotBlank(message = "用户名不能为空")
    @Length(max = 20, message = "用户名不能超过10个字符")
    @Pattern(regexp = "^[\\u4E00-\\u9FA5A-Za-z0-9\\*]*$", message = "用户名限制:最多10字符,包含文字、字母和数字")
    private String username;

    /**
     * 密码
     */
    @NotBlank(message = "密码不能为空")
    private String password;

    /**
     * 手机号
     */
    @NotBlank(message = "手机号不能为空")
    @Pattern(regexp = "^[1][3,4,5,6,7,8,9][0-9]{9}$", message = "手机号格式有误")
    private String mobile;

    /**
     * 性别
     */
    @NotNull(message = "性别不能为空")
    private Integer sex;

    /**
     * 年龄
     */
    @NotNull(message = "年龄不能为空")
    @Min(value = 1, message = "年龄最小为1岁")
    @Max(value = 120, message = "年龄最大为120岁")
    private Integer age;

    /**
     * 邮箱
     */
    @NotBlank(message = "邮箱不能为空")
    @Email(message = "邮箱格式错误")
    private String email;

}

2. Создайте контроллер

import com.yese.pojo.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
public class UserController {

    /**
     * controller方法的入参里面在实体类前面加上@Validated
     */
    @PostMapping("/user")
    public void add(@RequestBody @Validated User user) {
        log.info("user====={}", user);
    }

}

3. Создайте глобальный обработчик исключений

import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ConstraintViolationException;
import java.util.HashMap;
import java.util.Map;
import java.util.StringJoiner;

/**
 * 全局异常处理器
 */
@RestControllerAdvice
public class GlobalExceptionHandler {
    /**
     * post方式提交json数据,参数校验失败后,会抛出一个MethodArgumentNotValidException
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Map<String, Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        // 获取所有的错误
        // List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
        // 获取错误提示
        // System.out.println(fieldErrors.get(0).getDefaultMessage());
        // 获取错误字段
        // System.out.println(fieldErrors.get(0).getField());

        // 将所有的错误提示使用";"拼接起来并返回
        StringJoiner sj = new StringJoiner(";");
        e.getBindingResult().getFieldErrors().forEach(x -> sj.add(x.getDefaultMessage()));

        // 此处通常定义一个全局相应对象返回
        Map<String, Object> map = new HashMap<>();
        // 此处状态码可以通过枚举或者常量定义
        map.put("code", 1001);
        map.put("msg", sj.toString());
        return map;
    }

    /**
     * get方式提交参数,参数校验失败后,会抛出一个ConstraintViolationException
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public Map<String, Object> handleConstraintViolationException(ConstraintViolationException e) {
        StringJoiner sj = new StringJoiner(";");
        e.getConstraintViolations().forEach(x -> sj.add(x.getMessage()));

        Map<String, Object> map = new HashMap<>();
        map.put("code", 1001);
        map.put("msg", sj.toString());
        return map;
    }

}

4. Тест

Эффект ожидаемый!

RequestParam/PathVariableпроверка параметров

    /**
     * 入参校验时,Controller上面要加:@Validated
     * @RequestParam方式绑定参数
     */
    @GetMapping("/get1")
    public void get1(@RequestParam("id") @NotNull(message = "id不能为空") Integer id,
                     @RequestParam("name") @NotBlank(message = "name不能为空") @Length(max = 10, message = "用户名不能超过10个字符") String name) {
        log.info("id:{},name:{}", id, name);
    }

    /**
     * @PathVariable方式绑定参数
     */
    @GetMapping("/get2/{id}")
    public void get2(@PathVariable("id") @Min(value = 0, message = "id最小为0") Integer id) {
        log.info("id:{}", id);
    }

@Действительный и @Проверенный

В большинстве случаев мы можем использовать аннотацию @Validated.

В случае вложенной проверки мы используем аннотацию @Valid для добавления к свойству члена. Ссылаться на:

public class Dog {
    @NotBlank(message = "狗的名字不能为空")
    private String name;
}
public class User {
	// 省略其他。。。

    // 用于嵌套校验
    @Valid
    private Dog dog;
}

Централизованное определение оперативной информации

Создайте новый в каталоге ресурсовValidationMessages.propertiesфайл со следующим содержимым:

id.NotNull=id不能为空
username.NotBlank=用户名不能为空
username.length=用户名不能超过10个字符
username.invalid=用户名限制:最多10字符,包含文字、字母和数字
password.NotBlank=密码不能为空
phone.NotBlank=手机号不能为空
phone.invalid=手机号格式不合法
sex.NotNull=性别不能为空
age.NotNull=年龄不能为空
age.min=年龄最小为1岁
age.max=年龄最大为120岁
email.NotBlank=邮箱不能为空
email.invalid=邮箱格式不合法
dogName.NotBlank=狗的名字不能为空

Измените текст исходной аннотации проверки, используя заполнители.{key}способ получитьValidationMessages.propertiesТекстовое содержимое, определенное в . Чтобы определить другую информацию о приглашении в будущем, вам нужно только централизованно определить ее в свойствах, и ее можно будет использовать повторно. Ссылка выглядит следующим образом:

Проверка пакетов

Когда один и тот же объект должен использоваться повторно, например, пользователю необходимо проверить идентификатор пользователя при обновлении, но не нужно проверять идентификатор пользователя при добавлении, а другие параметры необходимо проверять в обоих случаях, поэтому используйтеgroupsфункция. Определите два интерфейса:

import javax.validation.groups.Default;

/**
 * 用于新增时的分组校验
 */
public interface Insert extends Default {
}
import javax.validation.groups.Default;

/**
 * 用于更新时的分组校验
 */
public interface Update extends Default {
}

Измените сущность, если остальные параметры нужно проверить, группы также можно не добавлять.

public class User {
    /**
     * 用户ID
     */
    @NotNull(message = "{id.NotNull}", groups = {Update.class})
    private Long userId;
    
    /**
     * 用户名
     */
    @NotBlank(message = "{username.NotBlank}", groups = {Insert.class, Update.class})
    @Length(max = 10, message = "{username.length}")
    @Pattern(regexp = "^[\\u4E00-\\u9FA5A-Za-z0-9\\*]*$", message = "{username.invalid}")
    private String username;
}

Массив groups представляет эффективный диапазон. Изменить метод контроллера

    @PostMapping("/user")
    public void add(@RequestBody @Validated(Insert.class) User user) {
        log.info("user====={}", user);
    }

@Validated(Insert.class)Указывает, что проверка проверяет только то, что группы содержат параметры Insert.class. То есть в текущем случае userId не проверяется. Оба случая проверяются, если другие параметры объекта не имеют явно определенных групп.

При добавлении id не проверяется, а эффект следующий:

Если изменить на@Validated(Update.class)

При обновлении проверяется id, и эффект следующий:

Пользовательские аннотации проверки

1. Аннотации пользовательских ограничений

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

@Documented
@Target({ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UsernameValidator.class)
public @interface Username {

    // 默认错误消息
    String message() default "用户名不合法";

    // 分组
    Class<?>[] groups() default {};

    // 负载
    Class<? extends Payload>[] payload() default {};

}

2. ОсознайтеConstraintValidatorВалидатор ограничения записи интерфейса

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**
 * 自定义用户名验证规则
 * 用户名需要以abc开头
 */
public class UsernameValidator implements ConstraintValidator<Username, String> {

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        // 不为null才进行校验
        if (value != null) {
            return value.startsWith("abc");
        }
        return true;
    }

}

3. Используйте

    @NotBlank(message = "name不能为空")
    @Username(message = "name需要以abc开头")
    private String name;

С общими нотами

javax.validation.constraintsпод пакетом

аннотация Проверенный тип данных иллюстрировать
@Null любой тип Значение элемента аннотации проверки должно быть нулевым.
@NotNull любой тип Убедитесь, что значение элемента аннотации не равно нулю
@NotBlank нить Убедитесь, что значение элемента аннотации не равно нулю (не равно нулю, длина больше 0 после удаления первого пробела)
@NotEmpty Подтип CharSequence, Коллекция, Карта, Массив Убедитесь, что значение элемента аннотации не равно нулю и не пусто (длина строки не равна 0, размер коллекции не равен 0).
@Мин(значение=значение) Числовой тип Убедитесь, что значение элемента аннотации больше или равно значению, заданному параметром @Min.
@Макс (значение = значение) То же, что спросил @Min Убедитесь, что значение элемента аннотации меньше или равно значению, указанному @Max.
@DecimalMin (значение = значение) То же, что спросил @Min Убедитесь, что значение элемента аннотации больше или равно значению, указанному @DecimalMin
@DecimalMax (значение = значение) То же, что спросил @Min Убедитесь, что значение элемента аннотации меньше или равно значению, указанному @DecimalMax.
@Digits(целое=целые цифры, дробь=десятичные цифры) То же, что спросил @Min Подтвердите максимальное количество целых и десятичных знаков для значения элемента аннотации.
@Size(мин=нижний предел, максимум=верхний предел) Строка, коллекция, карта, массив и т. д. Убедитесь, что значение элемента аннотации находится в пределах указанного интервала минимума и максимума (включительно), например длина символа, заданный размер
@Positive Оценка положительных чисел
@PositiveOrZero Определение положительных чисел или 0 .
@Negative Оцените отрицательные числа.
@NegativeOrZero Определение отрицательных чисел или 0 .
@Past тип даты Убедитесь, что значение элемента аннотации (тип даты) предшествует текущему времени.
@Future То же, что и @Past запрос Убедитесь, что значение элемента аннотации (тип даты) позже текущего времени.
@AssertFalse Boolean Подтвердите, что значение элемента аннотации является ложным
@AssertTrue Boolean Убедитесь, что значение элемента аннотации истинно.
@Range(мин=минимум, максимум=максимум) Атомарные типы и типы-оболочки, такие как BigDecimal, BigInteger, CharSequence, byte, short, int, long Убедитесь, что значение элемента аннотации находится между минимальным и максимальным значениями.
@Email нить Убедитесь, что значением элемента аннотации является электронная почта, или вы можете указать собственный формат электронной почты с помощью регулярного выражения и флага.
@Pattern(regexp=регулярное выражение, flag=шаблон флага) нить Убедитесь, что значение элемента аннотации соответствует указанному регулярному выражению.
@Valid любой неатомарный тип Укажите объект, связанный с рекурсивной проверкой. Например, в объекте пользователя есть свойство объекта адреса. Если вы хотите вместе проверять объект адреса при проверке объекта пользователя, добавьте аннотацию @Valid к объекту адреса для каскадной проверки.

org.hibernate.validator.constraintsВ пакете определен ряд аннотаций ограничений. следующим образом:

@Range(min=, max=): Аннотированный элемент должен находиться в соответствующей области.

@Length(min=, max=): Размер аннотированной строки должен быть в пределах указанного диапазона.

@URL(protocol=,host=,port=,regexp=,flags=): Аннотированная строка должна быть допустимым URL-адресом.

@SafeHtml: определяет, является ли отправленный HTML безопасным. Например, он не может содержать javascript-скрипты и т. д.