SpringBoot 2.X整合JSR303服务端数据校验

    技术2023-10-08  81

    SpringBoot 2.X整合JSR303服务端数据校验

    一、JSR-303简介

    JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation,注重于服务端数据校验,官方参考实现是Hibernate Validator。

    Bean Validation 中内置的 constraint如下:

    二、JSR-303基本校验

    声明 constraint 的 JavaBean【User.java】 package com.hf.boot.jsr303.entity; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.URL; import javax.validation.constraints.*; import java.io.Serializable; /** * @Copyright (C), 2016-2020 HF * @ClassName: User * @Author: hf * @Date: 2020/7/2 13:40 * @Description: 用户实体信息 */ @ApiModel(value = "User对象") @Data @Accessors(chain = true) @NoArgsConstructor public class User implements Serializable { @ApiModelProperty(name = "userId", value = "用户id") private Integer userId; @NotEmpty(message = "用户名称必须填写") @ApiModelProperty(name = "userName", value = "用户名称") private String userName; @NotEmpty(message = "登录名称必须填写") @Length(min = 6,max = 12,message = "登录名称长度在6-12位") @ApiModelProperty(name = "loginName", value = "登录名称") private String loginName; @NotEmpty(message = "登录密码不得为Null") @Pattern(regexp = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,18}$", message = "密码必须包含数字和字母、且在6-18位之间") @ApiModelProperty(name = "password", value = "登录密码") private String password; @NotNull(message = "Icon头像字段必须填写") @URL(message = "Icon链接符合规范") @ApiModelProperty(name = "icon", value = "头像") private String icon; @NotNull(message = "用户邮箱必须填写") @Email @ApiModelProperty(name = "email", value = "邮箱") private String email; @ApiModelProperty(name = "phone", value = "手机号") private String phone; @ApiModelProperty(name = "state", value = "是否启用 0-启用 1-不启用", example = "0") private int state; @Min(0) @Max(10000) @NotEmpty @ApiModelProperty(name = "sort", value = "排序字段", example = "0") private int sort; @ApiModelProperty(name = "createTime", value = "注册时间") private String createTime; } 使用@Valid开启校验功能、并获取校验结果。 /** * 功能描述: * 〈 * 用户保存,标注@Valid注解开启校验,给校验的Bean后面紧跟一个BindingResult就可以获取校验的结果。 * 〉 * * @className: JSR303Controller * @author: hf * @version: 1.0.0 * @date: 2020/7/3 11:37 * @param: [user 用户实体, result 错误消息体] * @resp: java.lang.Object */ @PostMapping("/saveUser") public Object saveUser(@RequestBody @Valid User user, BindingResult result) { HashMap<String, String> errorMap = new HashMap<>(); result.getFieldErrors().forEach(item -> { //1.2 获取错误消息 String message = item.getDefaultMessage(); //1.3 获取错误的字段名 String field = item.getField(); errorMap.put(field, message); }); if (errorMap.size() > 0) { return Resp.fail(errorMap); } userService.saveUser(user); return Resp.ok(); } 测试校验。

    三、JSR-303自定义校验

    创建自定义注解 package com.hf.boot.jsr303.annotation; import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.*; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * @Copyright (C), 2016-2020 HF * @ClassName: ListState * @Author: hf * @Date: 2020/7/3 13:39 * @Description: 自定义校验注解-校验用户状态信息 */ @Documented @Constraint(validatedBy = {ListStateConstraintValidator.class}) @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) @Retention(RUNTIME) public @interface ListState { /** * 默认的错误消息【需要新建ValidationMessages.properties文件并配置】 */ String message() default "{com.hf.boot.jsr303.annotation.ListState.message}"; /** * 分组配置 */ Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; /** * 默认传递的值 */ int[] values() default {}; } 自定义校验器 package com.hf.boot.jsr303.annotation; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import java.util.HashSet; import java.util.Set; /** * @Copyright (C), 2016-2020 HF * @ClassName: ListStateConstraintValidator * @Author: hf * @Date: 2020/7/3 13:41 * @Description: 自定义校验校验器、定义规则 */ public class ListStateConstraintValidator implements ConstraintValidator<ListState, Integer> { private Set<Integer> set = new HashSet<>(); @Override public void initialize(ListState constraintAnnotation) { //获取注解上可以填的值 final int[] values = constraintAnnotation.values(); for (int val : values) { set.add(val); } } /** * 功能描述: * 〈 * 具体的校验规则定义 * 〉 * * @className: ListStateConstraintValidator * @author: hf * @version: 1.0.0 * @date: 2020/7/3 13:50 * @param: [value 需要校验的值, context 上下文对象] * @resp: boolean */ @Override public boolean isValid(Integer value, ConstraintValidatorContext context) { return set.contains(value); } } 使用注解 @ListState(values = {0, 1}) @ApiModelProperty(name = "state", value = "是否启用 0-启用 1-不启用", example = "0") private int state; 测试注解。

    四、JSR-303分组校验

    创建分组【SaveGroup.java,UpdateGroup.java】 package com.hf.boot.jsr303.valid; /** * @Copyright (C), 2016-2020 HF * @ClassName: SaveGroup * @Author: hf * @Date: 2020/7/3 14:14 * @Description: 用于数据保存-分组 */ public interface SaveGroup { } package com.hf.boot.jsr303.valid; /** * @Copyright (C), 2016-2020 HF * @ClassName: UpdateGroup * @Author: hf * @Date: 2020/7/3 14:15 * @Description: 用于数据修改-分组 */ public interface UpdateGroup { } 标注分组 /** * 当用户新增时 userId必须为空、修改时 userId 不得为空 * * 注: 当区分groups时、未标注groups的字段将不再校验 */ @NotNull(message = "用户修改时【userId】不得为空", groups = {UpdateGroup.class}) @Null(message = "用户新增【userId】必须为空", groups = {SaveGroup.class}) @ApiModelProperty(name = "userId", value = "用户id") private Integer userId; 标注@Validated注解开启分组校验功能 public Object saveUser(@RequestBody @Validated(SaveGroup.class) User user, BindingResult result) { //...... return Resp.ok(); } public Object updateUser(@RequestBody @Validated(UpdateGroup.class) User user, BindingResult result) { //...... return Resp.ok(); } 测试校验

    五、JSR-303 全局异常处理

    新建异常处理类【JSR303ExceptionControllerAdvice.java】 package com.hf.boot.jsr303.exception; import com.hf.boot.api.bean.Resp; import com.hf.boot.api.constant.SysStatusCodeConstant; import org.springframework.validation.BindingResult; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import java.util.HashMap; /** * @Copyright (C), 2016-2020 HF * @ClassName: JSR303ExceptionControllerAdvice * @Author: hf * @Date: 2020/7/2 11:16 * @Description: JSR303 系统异常 全局处理 */ @RestControllerAdvice(basePackages = "com.hf.boot.jsr303.controller") public class JSR303ExceptionControllerAdvice { /** * 功能描述: * 〈 * 处理全局数据校验异常信息 * 〉 * * @className: JSR303ExceptionControllerAdvice * @author: hf * @version: 1.0.0 * @date: 2020/7/3 15:17 * @param: [e 参数校验异常] * @resp: java.lang.Object */ @ExceptionHandler(value = MethodArgumentNotValidException.class) public Object handleValidException(MethodArgumentNotValidException e) { BindingResult bindingResult = e.getBindingResult(); HashMap<String, Object> errorMap = new HashMap<>(); //1.1 获取校验的错误结果 bindingResult.getFieldErrors().forEach(item -> { //1.2 获取错误消息 String message = item.getDefaultMessage(); //1.3 获取错误的字段名 String field = item.getField(); errorMap.put(field, message); }); return Resp.fail(SysStatusCodeConstant.CHECK_ERROR_CODE, "fail", errorMap); } /** * 功能描述: * 〈 * 处理其它的异常 * 〉 * * @className: JSR303ExceptionControllerAdvice * @author: hf * @version: 1.0.0 * @date: 2020/7/3 15:21 * @param: [throwable] * @resp: java.lang.Object */ @ExceptionHandler(value = Throwable.class) public Object handleException(Throwable throwable) { return Resp.fail(SysStatusCodeConstant.NO_KNOW_CODE, "未知异常、请联系管理员【" + throwable.getMessage() + "】"); } } 控制层只需关注正常调用即可。 public Object updateUser(@RequestBody @Validated(UpdateGroup.class) User user) { //不需要再指定BindingResult,关注正常逻辑接口调用即可...... return Resp.ok(); }

    六、示例源码地址

    https://gitee.com/yx971072369/boot-s/tree/master/boot-jsr303
    Processed: 0.026, SQL: 9