JSR303数据校验和自定义校验注解

282 阅读4分钟

为了确保前端传来的数据是合法的,后端需要对客户端传来的数据进行再次校验。使用JSR303对数据进行校验步骤如下:

1. 导入相关依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2. 在实体类中需要校验的字段上添加注解,以用户名和密码为例
/**
 * 用户名
 */
@NotBlank(message = "用户名不能为空",groups = {AddGroup.class, UpdateGroup.class})
private String name;

/**
 * 密码,注意:regexp = ""中的正则表达式不需要加//
 */
@NotNull(message = "密码不能为空",groups = {AddGroup.class})
@Pattern(regexp = "^[a-zA-Z][a-zA-Z0-9_]{4,15}$",message = "必须以字母开头,长度在5-16,允许字母数字下划线")
@NotEmpty(message = "密码不能为空",groups = {AddGroup.class})
private String password;
3. 在Controller中对于需要校验的参数添加@Validated注解
public R addUser(@Validated @RequestBody UserInfo userInfo, BindingResult result){
    if (result.hasErrors()) {
        Map<String,String> map = new HashMap<>();
        //获取校验的错误结果
        result.getFieldErrors().forEach((item->{
            String message = item.getDefaultMessage();
            String field = item.getField();
            map.put(field,message);
        }));
        return R.error(400,"提交的信息不合法").put("data",map);
    }
    int res = userInfoMapper.insert(userInfo);
    if(res > 0){
        return R.ok("新增用户成功");
    }
    return R.error();
}

常用注解:

注解功能
@Email校验是否符合Email格式
@NotBlank不为null并且包含至少一个非空白字符
@NotEmpty不为null并且不为空
@NotNull不为null
@Null为null
@Pattern必须满足正则表达式
4. 对第三步进行优化,增加全局异常处理
/**
 * 集中处理所有异常
 */
@Slf4j
@RestControllerAdvice(basePackages = "com.tomla.controller")
public class GlobalExceptionControllerAdvice {

    @ExceptionHandler(value = BindException.class)
    public R HandleValidException(BindException e){
        log.error("数据校验出现问题:{},异常类型:{}",e.getMessage(),e.getClass());
        BindingResult result = e.getBindingResult();
        Map<String,String> errMap = new HashMap<>();
        //获取校验的错误结果
        result.getFieldErrors().forEach((item->{
            String message = item.getDefaultMessage();
            String field = item.getField();
            errMap.put(field,message);
        }));
        return R.error(CodeEnums.VALID_EXCEPTION.getCode(), CodeEnums.VALID_EXCEPTION.getMessage()).put("data",errMap);
    }
 
    @ExceptionHandler(value = RuntimeException.class)
    public Result HandleRuntimeException(RuntimeException e){
        log.error("系统异常:{},异常类型:{}",e.getMessage(),e.getClass());
        return Result.failed(e.getMessage());
    }

    @ExceptionHandler(value = Throwable.class)
    public R handleException(Throwable throwable){
        log.error("错误",throwable);
        return R.error(CodeEnums.UNKNOWN_EXCEPTION.getCode(), CodeEnums.UNKNOWN_EXCEPTION.getMessage());
    }
}

其中错误代码和消息提示定义为枚举类型

public enum CodeEnums {
    UNKNOWN_EXCEPTION(10000,"系统未知异常"),
    VALID_EXCEPTION(10001,"参数格式校验失败");

    private int code;
    private String message;

    CodeEnums(int code,String message){
        this.code = code;
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }
}
5. 简化Controller中的代码
@PostMapping("add")
public R addUser(@Validated({AddGroup.class}) @RequestBody UserInfo userInfo){
    int res = userInfoMapper.insert(userInfo);
    if(res > 0){
        return R.ok("新增用户成功");
    }else{
        //将异常抛出,由全局异常处理器处理
        throw new RuntimeException();
    } 
}
6. 第二步中的分组校验实现
  1. 定义空接口,作为group
//定义空接口,仅仅作为标识使用
public interface AddGroup {
}
  1. controller中使用:@Validated({xxx.class}),表示开启分组校验
  2. 实体类字段中指定所属group
@NotBlank(message = "用户名不能为空",groups = {AddGroup.class, UpdateGroup.class})
private String name;
  1. 如果一个校验注解没有指定分组,那么在第2步开启分组校验的情况下,这个校验注解不生效
7. 除了使用javax.validation.constraints包下提供的各种常用注解之外,我们可以自定义校验注解,步骤如下:
  1. 编写一个自定义的校验注解
@Documented
@Constraint(validatedBy = {ListValueConstraintValidator.class}) //指定用什么来校验,可以指定多个校验器
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ListValue {
    String message() default "{com.tomla.common.valid.ListValue.message}";

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

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

    int[]  vals() default {};
}
  1. 框架自带的配置文件如下

image.png

  • message中的消息需要写在ValidationMessages.properties配置文件中,新建同名配置文件并配置自定义的message
com.tomla.common.valid.ListValue.message=必须为指定的值
  1. 编写一个自定义的校验器,用来校验那些被自定义校验注解标注的字段
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
    private Set<Integer> set = new HashSet<>();
    @Override
    public void initialize(ListValue constraintAnnotation) {
        //获取@ListValue注解中的详细信息
        int[] vals = constraintAnnotation.vals();
        for (int val : vals) {
            set.add(val);
        }
    }
    /**
     * 判断是否校验成功
     * @param value 被注解的值(需要校验的值)
     * @param context 上下文
     * @return
     */
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        return set.contains(value);
    }
}
  1. 关联自定义的校验器和自定义的校验注解(第1步中已有完整代码 )
@Constraint(validatedBy = {ListValueConstraintValidator.class}) //可以指定多个校验器
  1. 使用自定义注解
/**
* 是否可用:0:可用,1:不可用
*/
@TableLogic(value = "0",delval = "1")
@ListValue(vals={0,1})
private Integer status;
  1. 测试自定义注解是否生效 image.png
补充:统一返回结果R类的代码如下
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.apache.http.HttpStatus;


import java.util.HashMap;

import java.util.Map;

public class R extends HashMap<String, Object> {
   private static final long serialVersionUID = 1L;

   public R setData(Object data){
      put("data",data);
      return this;
   }

   public <T> T getData(String name,TypeReference<T> typeReference){
      Object data = get(name);//map 类型
      String jsonString = JSON.toJSONString(data);

      T t = JSON.parseObject(jsonString, typeReference);

      return t;
   }

   public <T> T getData(TypeReference<T> typeReference){
      Object data = get("data");//map 类型
      String jsonString = JSON.toJSONString(data);

      T t = JSON.parseObject(jsonString, typeReference);

      return t;
   }

   public R() {
      put("code", 0);
      put("msg", "success");
   }
   
   public static R error() {
      return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "未知异常,请联系管理员");
   }
   
   public static R error(String msg) {
      return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, msg);
   }
   
   public static R error(int code, String msg) {
      R r = new R();
      r.put("code", code);
      r.put("msg", msg);
      return r;
   }

   public static R ok(String msg) {
      R r = new R();
      r.put("msg", msg);
      return r;
   }
   
   public static R ok(Map<String, Object> map) {
      R r = new R();
      r.putAll(map);
      return r;
   }
   
   public static R ok() {
      return new R();
   }

   public R put(String key, Object value) {
      super.put(key, value);
      return this;
   }
   public Integer getCode(){
      return (Integer)this.get("code");
   }

}