SpringBoot实现数据格式校验

一、简单使用

1.1 准备

JSR303检验协议,javax.validation.constraints包下注解实现。
导包:

<dependency>
      <groupId>javax.validation</groupId>
      <artifactId>validation-api</artifactId>
      <version>1.1.0.Final</version>
    </dependency>

1.2 实现

  1. 给bean添加校验注解:javax.validation.constraints,并定义自己的message提示。
@NotBlank(message = "若不满足返回的错误描述")
private String name;
  1. 控制层开启校验功能,若不加@Validated,则实体类上的校验注解无效。
    @RequestMapping("/save")
    public R save(@Validated @RequestBody BrandEntity brand){
        //业务处理
        return R.ok();
    }

1.3 注解扩展

@NotNull(message = "字段值不能为空,Map 和 Array 对象不能是 null, 但可以是空集(size = 0)")
@NotEmpty(message = "Map 和 Array 对象不能是 null 并且相关对象的 size 大于 0")
@NotBlank(message = "String 不是 null 且去除两端空白字符后的长度(trimmed length)大于 0")
@Max(value = 20,message = "最大长度为20,相应的还有@Min(value)")
@Size(max=10,min=5,message = "字段长度要在5-10之间")
@Pattern(regexp = "正则表达式",message = "不满足正则表达式")
@AssertTrue(message = "字段为true才能通过,针对布尔类型")
@Future(message = "时间在当前时间之后才可以通过,相应的还有@Past")
@DecimalMin(value)  被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value)  被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Digits(integer,fraction)  被注释的元素必须是一个数字,其值必须在可接受的范围内
@Email 被注释的元素必须是电子邮件地址
@Length 被注释的字符串的大小必须在指定的范围内
@Range  被注释的元素必须在合适的范围内

二、接受返回的错误信息

  1. 给校验的bean后紧跟一个BindingResult,就可以获取到校验的结果。
    @RequestMapping("/save")
    public R save(@Validated @RequestBody BrandEntity brand,BindingResult result){
        //业务处理
        return R.ok();
    }
  1. 对错误结果进行处理
    @RequestMapping("/save")
    public R save(@Validated @RequestBody BrandEntity brand,BindingResult result){
        if (result.hasErrors()){
            Map<String,String> errmap = new HashMap<>();
            //.getFieldErrors()方法获取错误的校验结果
            result.getFieldErrors().forEach((item)->{
                //item为FieldError类对象,getDefaultMessage获取错误描述
                String message = item.getDefaultMessage();
                //获取错误的属性的名字
                String field = item.getField();
                errmap.put(field,message);
            });
            return R.error(400,"输入数据不合法").put("data",errmap);
        }else {
            //业务处理 
        }
        return R.ok();
    }

三、统一的异常处理

3.1 准备

  1. 导入依赖
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>5.3.3</version>
            <scope>compile</scope>
        </dependency>
  1. @ControllerAdvice注解
    ControllerAdvice是Spring3.2提供的新注解,它是一个Controller增强器,可对controller中被 @RequestMapping注解的方法加一些逻辑处理。最常用的就是异常处理

3.2 使用

  1. 设置需要作用的包
@ControllerAdvice(basePackages = "com.fangk.mall.product.controller")
public class FangkmallExceptionContrpllerAdvice {
}
  1. 普通controller层方法不处理数据校验错误返回信息,直接向上抛出
    @RequestMapping("/save")
    public R save(@Validated @RequestBody BrandEntity brand/*,BindingResult result*/){
        //只有业务处理了,简洁清晰
        return R.ok();
    }
  1. 书写controllerAdvice代码
@Slf4j
//@ResponseBody
//@ControllerAdvice(basePackages = "com.fangk.mall.product.controller")
@RestControllerAdvice(basePackages = "com.fangk.mall.product.controller")
public class FangkmallExceptionContrpllerAdvice {
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public R handleVaildException(MethodArgumentNotValidException e){
        log.error("数据校验出现问题{},异常类型{}",e.getMessage(),e.getClass());
        BindingResult bindingResult = e.getBindingResult();
        Map<String,String> errorMap = new HashMap<>();
        bindingResult.getFieldErrors().forEach((item)->{
            errorMap.put(item.getField(),item.getDefaultMessage());
        });
        return R.error(400,"数据校验出现问题").put("data",errorMap);
    }
}
  • Controller里面的方法抛出了MethodArgumentNotValidException异常就会执行上面的这个方法来进行异常处理。
  • 该controller中可以有多个处理不同异常的方法,按照顺序匹配。兜底的可以选择Exception。
  • ResponseBody和ControllerAdvice注解可以合并为RestControllerAdvice。
  1. 业务代码实现中,controller中可以根据场景向上抛出不同的异常供active进行处理。

四、枚举定时码值映射

public enum BizCodeEnume {
    UNKNOW_EXCEPTION(10000,"系统未知异常"),
    VAILD_EXCEPTION(10001,"参数格式校验失败");
    private int code;
    private String msg;
    BizCodeEnume(int code,String msg){
        this.code = code;
        this.msg = msg; }
    public int getCode() { return code; }
    public String getMsg() { return msg; }
}
//这样使用
return R.error(BizCodeEnume.VAILD_EXCEPTION.getCode(),
BizCodeEnume.VAILD_EXCEPTION.getMsg()).put("data",errorMap);

五、分组校验

5.1 为什么要用

一个实体类bean可能会需要应对不同的接口请求,每个接口对于字段的格式校验会出现不同要求。比如id字段,新增时因为自增序列所以不需要传输,而修改时则必传。
为了不重复创建相同字段而校验注解不同的bean,所以javax.validation.constraints的格式校验注解中为我们提供了groups参数。

5.2 如何使用

  1. 校验注解中加入groups参数
    @NotEmpty(groups={AddGroup.class})
    @Pattern(regexp="^[a-zA-Z]$",message = "检索首字母必须是一个字母",groups={AddGroup.class,UpdateGroup.class})
    private String firstLetter;
  • AddGroup新增时,需要校验非空和正则。
  • UpdateGroup更新时,只需校验正则。
  • groups参数格式为Class<?>[] groups() default { };,故定义个空接口即可。
  1. 业务层增加@Validated
    @RequestMapping("/save")
    public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand/*,BindingResult result*/){
        //只有业务处理了,简洁清晰
        return R.ok();
    }
  • 默认没有指定分组校验的注解,在分组校验情况下不生效。
  • Validated({AddGroup.class},只有校验注解中有AddGroup的才会生效,其他不生效。

六、自定义校验注解

6.1 编写一个自定义的校验注解

@Documented
//指定校验器,此处若不指定,则需在初始化时指定
@Constraint(validatedBy = { ListValueConstraintValidator.class })
//表明注解可以使用的地方
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
    String message() default "{com.atguigu.common.valid.ListValue.message}";
    Class<?>[] groups() default { };
    Class<? extends Payload>[] payload() default { };
    int[] vals() default { };
}

6.2 编写一个自定义的校验器

public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
    private Set<Integer> set = new HashSet<>();
    //初始化方法
    @Override
    public void initialize(ListValue constraintAnnotation) {
        int[] vals = constraintAnnotation.vals();
        for (int val : vals) {
            set.add(val);
        }
    }
    //判断是否校验成功
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        return set.contains(value);
    }
}

6.3 关联自定义的校验注解和校验器

@Documented
@Constraint(validatedBy = { ListValueConstraintValidator.class【可以指定多个不同的校验器,适配不同类型校验】 })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容