Saul's blog Saul's blog
首页
后端
分布式
前端
更多
分类
标签
归档
友情链接
关于
GitHub (opens new window)

Saul.J.Wu

立身之本,不在高低。
首页
后端
分布式
前端
更多
分类
标签
归档
友情链接
关于
GitHub (opens new window)
  • Java入门基础

  • Java核心基础

  • 设计模式

  • Web开发

  • SpringBoot

    • JSR303校验
      • 有什么注解关键字呢?
      • 非空字段选择哪个?例如name之类的?
      • 如何使用?
      • 自定义校验错误返回信息
      • 自定义校验结果
      • 分组校验
        • 测试,新增时指定品牌id
        • 测试,新增时,未指定分组的字段不符合规则
        • 测试修改接口校验
      • 自定义校验
        • 如何开启
        • 后期拓展
      • 验证 Service 中的方法
      • 参考链接
    • 使用hibernate-validator进行参数校验最佳实践+校验工具类
  • 微服务

  • Elasticsearch

  • 运维

  • 后端
  • SpringBoot
SaulJWu
2020-08-17

JSR303校验

# 有什么注解关键字呢?

package javax.validation.constraints;
1

在这个包下,有许多注解。

# 非空字段选择哪个?例如name之类的?

先看一下源码:

NotNull

* The annotated element must not be {@code null}.
* Accepts any type.
1
2

NotEmpty

* The annotated element must not be {@code null} nor empty.
1

NotBlank

* The annotated element must not be {@code null} and must contain at least one
* non-whitespace character. Accepts {@code CharSequence}
1
2

对比一下,只有NotBlank更适合,因为他的意思不能为空,且至少一个非空格的字符。

# 如何使用?

  • 给需要校验的字段加上@关键字
package com.elite.mall.product.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import javax.validation.constraints.NotBlank;
import java.io.Serializable;

/**
 * 品牌
 * 
 * @author Saul.J.Wu
 * @email Saul.J.Wu@gmail.com
 * @date 2020-09-24 13:20:51
 */
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * 品牌id
	 */
	@TableId
	private Long brandId;
	/**
	 * 品牌名
	 */
	@NotBlank(message = "品牌名必须提交")
	private String name;
	/**
	 * 品牌logo地址
	 */
	private String logo;
	/**
	 * 介绍
	 */
	private String descript;
	/**
	 * 显示状态[0-不显示;1-显示]
	 */
	@TableLogic(delval = "0",value = "1")
	private Integer showStatus;
	/**
	 * 检索首字母
	 */
	private String firstLetter;
	/**
	 * 排序
	 */
	private Integer sort;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
  • 在contoller的方法参数添加@Valid

就可以测试接口,校验

示例代码

/**
 * 保存
 */
@PostMapping
public CommonResult save(@Valid @RequestBody BrandEntity brand){
    brandService.save(brand);
    return CommonResult.ok();
}
1
2
3
4
5
6
7
8

springboot 2.3.x 版本无法引用 javax.validation.constraints. 下的 @NotNull 注解

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
1
2
3
4

我刚才测试的时候,发现问的注解没有作用,经过排查,原来springboot2.3.x版本开始就需要手动添加了。

# 自定义校验错误返回信息

@关键字(message = "xxxx")

就可以指定返回什么信息了

# 自定义校验结果

给校验的Bean后,紧跟着BindingResult,就可以获取到校验结果。

/**
  * 保存
  */
@PostMapping
public CommonResult save(@Valid @RequestBody BrandEntity brand, BindingResult bindingResult){
    if(bindingResult.hasErrors()){
        Map<String, String> map = new HashMap<>();
        //获取错误的校验结果
        bindingResult.getFieldErrors().forEach(item -> {
            //获取错误提示
            String message = item.getDefaultMessage();
            //获取错误属性的名字
            String field = item.getField();
            map.put(field, message);
        });
        return CommonResult.error(400, "提交的数据不合法").put("data", map);
    }
    brandService.save(brand);
    return CommonResult.ok();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

示例:

  • 请求
{"name":""}
1
  • 返回结果
{
    "msg": "提交的数据不合法",
    "code": 400,
    "data": {
        "name": "品牌名必须提交"
    }
}
1
2
3
4
5
6
7

实例2

  • 实体
package com.elite.mall.product.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import org.hibernate.validator.constraints.URL;

import javax.validation.constraints.*;
import java.io.Serializable;

/**
 * 品牌
 * 
 * @author Saul.J.Wu
 * @email Saul.J.Wu@gmail.com
 * @date 2020-09-24 13:20:51
 */
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * 品牌id
	 */
	@TableId
	private Long brandId;
	/**
	 * 品牌名
	 */
	@NotBlank(message = "品牌名必须提交")
	private String name;
	/**
	 * 品牌logo地址
	 */
	@NotEmpty
	@URL(message = "logo必须是一个合法的url地址")
	private String logo;
	/**
	 * 介绍
	 */
	private String descript;
	/**
	 * 显示状态[0-不显示;1-显示]
	 */
	@TableLogic(delval = "0",value = "1")
	private Integer showStatus;
	/**
	 * 检索首字母
	 */
	@NotEmpty
	@Pattern(regexp = "/^[a-zA-z]$/",message = "检索首字母必须是一个字母")
	private String firstLetter;
	/**
	 * 排序
	 */
	@NotNull
	@Min(value = 0,message = "排序必须大于等于0")
	private Integer sort;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
  • 请求结果
{
    "msg": "提交的数据不合法",
    "code": 400,
    "data": {
        "name": "品牌名必须提交",
        "logo": "不能为空",
        "sort": "不能为null",
        "firstLetter": "不能为空"
    }
}
1
2
3
4
5
6
7
8
9
10

# 分组校验

前言

新增和修改的校验规则不一样,例如,新增不需要品牌id,修改则需要,所以存在分组校验。

用法

  • 给注解标注什么情况需要校验
package com.elite.mall.product.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.elite.mall.common.valid.AddGroup;
import com.elite.mall.common.valid.UpdateGroup;
import lombok.Data;
import org.hibernate.validator.constraints.URL;

import javax.validation.constraints.*;
import java.io.Serializable;

/**
 * 品牌
 *
 * @author Saul.J.Wu
 * @email Saul.J.Wu@gmail.com
 * @date 2020-09-24 13:20:51
 */
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 品牌id
     */
    @NotNull(message = "修改时,必须指定品牌id", groups = {UpdateGroup.class})
    @Null(message = "新增时,不能指定品牌id", groups = {AddGroup.class})
    @TableId
    private Long brandId;

    /**
     * 品牌名
     */
    @NotBlank(message = "品牌名必须提交", groups = {AddGroup.class, UpdateGroup.class})
    private String name;

    /**
     * 品牌logo地址
     */
    @NotEmpty
    @URL(message = "logo必须是一个合法的url地址")
    private String logo;

    /**
     * 介绍
     */
    private String descript;

    /**
     * 显示状态[0-不显示;1-显示]
     */
    @TableLogic(delval = "0", value = "1")
    private Integer showStatus;

    /**
     * 检索首字母
     */
    @NotEmpty
    @Pattern(regexp = "/^[a-zA-z]$/", message = "检索首字母必须是一个字母")
    private String firstLetter;

    /**
     * 排序
     */
    @NotNull
    @Min(value = 0, message = "排序必须大于等于0")
    private Integer sort;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
  • 在controller中使用@Validated指定使用什么分组

注意:当使用了分组校验后,有些字段并没有指定分组,那么它是无效的,所以要校验的字段,必须指定分组

package com.elite.mall.product.controller;

import com.elite.mall.common.utils.CommonResult;
import com.elite.mall.common.utils.PageUtils;
import com.elite.mall.common.valid.AddGroup;
import com.elite.mall.common.valid.UpdateGroup;
import com.elite.mall.product.entity.BrandEntity;
import com.elite.mall.product.service.BrandService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.Map;

//import org.apache.shiro.authz.annotation.RequiresPermissions;


/**
 * 品牌
 *
 * @author Saul.J.Wu
 * @email Saul.J.Wu@gmail.com
 * @date 2020-09-24 13:20:51
 */
@RestController
@RequestMapping("product/brands")
public class BrandController {
    @Autowired
    private BrandService brandService;

    /**
     * 列表
     */
    @GetMapping
    //@RequiresPermissions("product:brand:list")
    public CommonResult list(@RequestParam Map<String, Object> params) {
        PageUtils page = brandService.queryPage(params);

        return CommonResult.ok().put("page", page);
    }


    /**
     * 信息
     */
    @GetMapping("/{brandId}")
    //@RequiresPermissions("product:brand:info")
    public CommonResult info(@PathVariable("brandId") Long brandId) {
        BrandEntity brand = brandService.getById(brandId);

        return CommonResult.ok().put("brand", brand);
    }

    /**
     * 保存
     */
    @PostMapping
    //@RequiresPermissions("product:brand:save")
    public CommonResult save(@Validated(AddGroup.class) @RequestBody BrandEntity brand) {
        brandService.save(brand);
        return CommonResult.ok();
    }

    /**
     * 修改
     */
    @PutMapping
    //@RequiresPermissions("product:brand:update")
    public CommonResult update(@Validated(UpdateGroup.class) @RequestBody BrandEntity brand) {
        brandService.updateById(brand);
        return CommonResult.ok();
    }

    /**
     * 删除
     */
    @DeleteMapping
    //@RequiresPermissions("product:brand:delete")
    public CommonResult delete(@RequestBody Long[] brandIds) {
        brandService.removeByIds(Arrays.asList(brandIds));

        return CommonResult.ok();
    }

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87

# 测试,新增时指定品牌id

  • 请求体
{"name":"xxx","brandId":1,"logo":"abc"}
1
  • 结果
{
    "msg": "参数格式校验失败!",
    "code": 10001,
    "data": {
        "brandId": "新增时,不能指定品牌id"
    }
}
1
2
3
4
5
6
7

# 测试,新增时,未指定分组的字段不符合规则

  • 请求体
{"name":"xxx","logo":"abc"}
1
  • 结果
{
    "msg": "success",
    "code": 200
}
1
2
3
4

居然成功了,所以当使用了分组校验后,有些字段并没有指定分组,那么它是无效的,所以要校验的字段,必须指定分组

那么现在应该为还没有用分组的字段,加上分组。

package com.elite.mall.product.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.elite.mall.common.valid.AddGroup;
import com.elite.mall.common.valid.UpdateGroup;
import lombok.Data;
import org.hibernate.validator.constraints.URL;

import javax.validation.constraints.*;
import java.io.Serializable;

/**
 * 品牌
 *
 * @author Saul.J.Wu
 * @email Saul.J.Wu@gmail.com
 * @date 2020-09-24 13:20:51
 */
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 品牌id
     */
    @NotNull(message = "修改时,必须指定品牌id", groups = {UpdateGroup.class})
    @Null(message = "新增时,不能指定品牌id", groups = {AddGroup.class})
    @TableId
    private Long brandId;

    /**
     * 品牌名
     */
    @NotBlank(message = "品牌名必须提交", groups = {AddGroup.class, UpdateGroup.class})
    private String name;

    /**
     * 品牌logo地址
     */
    @NotEmpty(groups = {AddGroup.class})
    @URL(message = "logo必须是一个合法的url地址", groups = {AddGroup.class, UpdateGroup.class})
    private String logo;

    /**
     * 介绍
     */
    private String descript;

    /**
     * 显示状态[0-不显示;1-显示]
     */
    @TableLogic(delval = "0", value = "1")
    private Integer showStatus;

    /**
     * 检索首字母
     */
    @NotEmpty(groups = {AddGroup.class})
    @Pattern(regexp = "/^[a-zA-z]$/", message = "检索首字母必须是一个字母", groups = {AddGroup.class, UpdateGroup.class})
    private String firstLetter;

    /**
     * 排序
     */
    @NotNull(groups = {AddGroup.class})
    @Min(value = 0, message = "排序必须大于等于0", groups = {AddGroup.class, UpdateGroup.class})
    private Integer sort;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72

重新测试一次,同样的请求体,返回结果不一样

{
    "msg": "参数格式校验失败!",
    "code": 10001,
    "data": {
        "logo": "logo必须是一个合法的url地址",
        "sort": "不能为null",
        "firstLetter": "不能为空"
    }
}
1
2
3
4
5
6
7
8
9

请求体

{"name":"xxx","logo":""}
1
  • 返回
{
    "msg": "参数格式校验失败!",
    "code": 10001,
    "data": {
        "logo": "不能为空",
        "sort": "不能为null",
        "firstLetter": "不能为空"
    }
}
1
2
3
4
5
6
7
8
9

# 测试修改接口校验

  • put请求
{"name":"xxx","logo":""}
1
  • 返回
{
    "msg": "参数格式校验失败!",
    "code": 10001,
    "data": {
        "brandId": "修改时,必须指定品牌id"
    }
}
1
2
3
4
5
6
7
  • put请求
{"name":"xxx","brandId":1}
1
  • 返回
{
    "msg": "success",
    "code": 200
}
1
2
3
4

说明其他字段的分组校验也生效了。

像类似@NotNull(groups = {AddGroup.class})的这种情况,只有在新增时才会校验非空,修改时则不校验,可以保存数据库原数据。

总结:分组校验,可以帮助完成复杂校验场景,没有指定分组的校验注解,在开启分组校验时,不会生效。

# 自定义校验

前景

比如品牌显示状态,0,不显示,1显示,或者其他复杂情况,可能不能只用正则表达式来校验,所以就需要自定义校验来完成业务功能

# 如何开启

  1. 编写一个自定义的校验注解
  2. 编写一个自定义的校验器
  3. 关联自定义的校验注解和校验器
  • 自定义校验注解

使用自定义注解需要导入依赖,elitemall-common/pom.xml

<dependency>
    <groupId>jakarta.validation</groupId>
    <artifactId>jakarta.validation-api</artifactId>
    <version>2.0.2</version>
    <scope>compile</scope>
</dependency>
1
2
3
4
5
6
 /**
  * 显示状态[0-不显示;1-显示]
  */
@StatusListValue(vals = {0, 1}, groups = {AddGroup.class})
@TableLogic(delval = "0", value = "1")
private Integer showStatus;
1
2
3
4
5
6
  • 关联自定义的校验注解和校验器
package com.elite.mall.common.valid;

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;

@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {StatusListValueConstraintValidator.class})
public @interface StatusListValue {

    String message() default "{com.elite.mall.common.valid.StatusListValue.message}";

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

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

    int[] vals() default {};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
  • 校验器
package com.elite.mall.common.valid;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;

public class StatusListValueConstraintValidator implements ConstraintValidator<StatusListValue, Integer> {

    private Set<Integer> set = new HashSet<>();

    /**
     * 初始化方法
     *
     * @param constraintAnnotation
     */
    @Override
    public void initialize(StatusListValue constraintAnnotation) {
        int[] vals = constraintAnnotation.vals();
        for (int val : vals) {
            //todo 非空判断
            set.add(val);
        }
    }

    /**
     * 判断校验是否成功
     *
     * @param value   需要校验的值
     * @param context 校验的上下文环境信息
     * @return
     */
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        return set.contains(value);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

测试

  • 请求体
{"name":"xxx","logo":"abc","showStatus":3}
1
  • 返回
{
    "msg": "参数格式校验失败!",
    "code": 10001,
    "data": {
        "logo": "logo必须是一个合法的url地址",
        "showStatus": "必须提交指定的值",
        "sort": "不能为null",
        "firstLetter": "不能为空"
    }
}
1
2
3
4
5
6
7
8
9
10

可见showStatus已经完成了

# 后期拓展

@Constraint(validatedBy = {StatusListValueConstraintValidator.class})
1

一个校验注解,可以由多个校验器,适配不同类型的注解

# 验证 Service 中的方法

还可以验证任何Spring组件的输入,而不是验证控制器级别的输入,我们可以使用@Validated和@Valid注释的组合来实现这一需求。

一定一定不要忘记在类上加上 Validated 注解了,这个参数可以告诉 Spring 去校验方法参数。

@Service
@Validated
publicclass PersonService {

    public void validatePerson(@Valid Person person){
        // do something
    }
}
1
2
3
4
5
6
7
8

通过测试验证:

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
publicclass PersonServiceTest {
    @Autowired
    private PersonService service;

    @Test(expected = ConstraintViolationException.class)
    public void should_throw_exception_when_person_is_not_valid() {
        Person person = new Person();
        person.setSex("Man22");
        person.setClassId("82938390");
        person.setEmail("SnailClimb");
        service.validatePerson(person);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 参考链接

如何在 Spring/Spring Boot 中做参数校验?你需要了解的都在这里! (opens new window)

帮我改善此页面 (opens new window)
#校验#JSR303
上次更新: 2020/12/24, 06:26:14
Web基础
使用hibernate-validator进行参数校验最佳实践+校验工具类

← Web基础 使用hibernate-validator进行参数校验最佳实践+校验工具类→

最近更新
01
zabbix学习笔记二
02-28
02
zabbix学习笔记一
02-10
03
Linux访问不了github
12-08
更多文章>
Theme by Vdoing | Copyright © 2020-2022 Saul.J.Wu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式