springboot-validation
2022/1/9 6:07:17
本文主要是介绍springboot-validation,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
1. 引入
在项目中,大部分的工作就是前后端请求的交互,接口的编写。
接口编写就不得不做很多的参数校验,通常在业务代码之前,就要做很多很多的显示参数校验,造成代码冗余。
springboot-validation提供了优雅的参数校验,入参通常都是实体类,在实体类字段上加上对应的注解,就可以在方法之前进行参数校验,校验不通过,是不会进入方法的
springboot-validation的底层是hibernate-validation
2. 基本使用
2.1 引入依赖
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.5.2</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- springboot validation --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>2.10.8</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.6.0</version> </dependency> </dependencies>
2.2 基本使用
以登录接口为例,需要登录用户vo,包含登录需要的用户名和密码,要求都不能为空
LoginUserVo
@Data @NoArgsConstructor @AllArgsConstructor public class LoginUserVo implements Serializable { private static final long serialVersionUID = -5331320733431220933L; @NotBlank(message = "用户名不能为空") // 非空,message为错误的提示信息 private String username; @NotBlank(message = "密码不能为空") // 非空 private String passwrod; }
UserController
@RestController @RequestMapping("/user") public class UserController { /** * 加上@Validated 注解,会自动对LoginUserVo中加了注解的字段进行校验 */ @PostMapping("/login") public String login(@Validated LoginUserVo loginUserVo) { return "登录成功"; } }
测试
使用PostMan进行测试
Post /user/login
正常输入
如果留空,就会报错
2.3 注解介绍
内置校验注解包含很多,包括如下
注解 | 校验功能 |
---|---|
@Null | 被注释的元素必须为 null |
@NotNull | 被注释的元素必须不为 null |
@NotBlank | 验证字符串非null,且长度必须大于0 |
@NotEmpty | 被注释的字符串的必须非空 |
@Length(min=,max=) | 被注释的字符串的大小必须在指定的范围内 |
@Size(max=, min=) | 被注释的元素的大小必须在指定的范围内 |
@Range(min=,max=,message=) | 被注释的元素必须在合适的范围内 |
被注释的元素必须是电子邮箱地址 | |
@AssertTrue | 被注释的元素必须为 true |
@AssertFalse | 被注释的元素必须为 false |
@Min(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@Max(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@DecimalMin(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@DecimalMax(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@Digits(integer, fraction) | 被注释的元素必须是一个数字,其值必须在可接受的范围内 |
@Past | 被注释的元素必须是一个过去的日期 |
@Future | 被注释的元素必须是一个将来的日期 |
@FutureOrPresent | 当前或将来时间 |
@Pattern(regex=,flag=) | 被注释的元素必须符合指定的正则表达式 |
@Negative | 负数(不包括0) |
@NegativeOrZero | 负数或0 |
@PositiveOrZero | 正数或0 |
2.4 返回值完善
刚刚的案例大家也看到了,如果没有一个统一的返回值格式,返回的数据是不容易读,并且不容易解析的。所以需要构造统一的返回结果
Result
@Data @NoArgsConstructor @AllArgsConstructor @Builder public class Result<T> { private int status; private T data; private String errorMsg; }
ResultBuilder
public interface ResultBuilder { /** * 成功的构造 * @param data 数据 * @return Result */ default Result<?> success(Object data) { return Result.builder() .status(HttpStatus.OK.value()).data(data) .build(); } /** * 404的构造 * @param errorMsg 错误信息 * @return Result */ default Result<?> notFound(String errorMsg) { return Result.builder() .status(HttpStatus.NOT_FOUND.value()) .errorMsg(errorMsg) .build(); } /** * 500的构造 * @param errorMsg 错误信息 * @return Result */ default Result<?> internalServerError(String errorMsg) { return Result.builder() .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) .errorMsg(errorMsg) .build(); } }
UserController
改造
@RestController @RequestMapping("/user") public class UserController implements ResultBuilder { /** * 加上@Validated 注解,会自动对LoginUserVo中加了注解的字段进行校验 */ @PostMapping("/login") public Result<?> login(@RequestBody @Validated LoginUserVo loginUserVo) { return success("登录成功"); } }
2.5 统一异常处理
刚刚看到,校验错误的异常只会在控制台打印。返回值只有400的提示,没有明显的自定义提示
@RestControllerAdvice // 返回json public class GlobExceptionHandler implements ResultBuilder { @ExceptionHandler(value = BindException.class) public Result<?> bindExceptionHandler(BindException ex) { // 获取所有错误信息,拼接 List<FieldError> fieldErrors = ex.getBindingResult().getFieldErrors(); String errorMsg = fieldErrors.stream() .map(fieldError -> fieldError.getField() + ":" + fieldError.getDefaultMessage()) .collect(Collectors.joining(",")); // 返回统一处理类 return internalServerError(errorMsg); } }
再次访问接口,可以看到明显的错误提示
3. 分组校验
如果同一个参数,在不同的场景下应用不同的校验规则,那么就需要用到分组校验了,再不同的场景下,使用不同的分组。
比如:用户新注册的时候,还没有起名字,可以允许nickName为空,由系统自动生产。但是如果更新用户信息,那么需要保证nickName不为空
实现步骤
- 定义一个分组类
- 再校验注解上添加
goroups
属性指定分组 Controlelr
方法的@Validated
注解添加分组类
UpdateUser
public interface UpdateUser extends Default { }
UserVo
@Data @NoArgsConstructor @AllArgsConstructor public class UserVo implements Serializable { @NotBlank(message = "更新记录id不能为空", groups = UpdateUser.class) // 更新是保证ID不为空,根据ID更新 private String id; @NotBlank(message = "用户名不能为空") private String username; @NotBlank(message = "密码不能为空") private String password; @NotBlank(message = "昵称不能为空", groups = UpdateUser.class) // 更新时保证nickName不为空 private String nickName; }
UserController
改造
@RestController @RequestMapping("/user") public class UserController implements ResultBuilder { /** * 加上@Validated 注解,会自动对LoginUserVo中加了注解的字段进行校验 */ @PostMapping("/login") public Result<?> login(@RequestBody @Validated LoginUserVo loginUserVo) { return success("登录成功"); } /** * 新增用户,使用默认的分组校验 */ @PostMapping public Result<?> insertUser(@RequestBody @Validated UserVo userVo) { return success("新增成功"); } /** * 更新用户时使用 更新用户 的校验分组 * 包含分组内的校验规则和默认的校验规则 */ @PutMapping public Result<?> updateUser(@RequestBody @Validated(UpdateUser.class) UserVo userVo) { return success("新增成功"); } }
4. 递归校验
如果一个对象中包含另外一个对象,而且内层对象也需要校验,此时就会用到递归校验。再需要校验的内部对象上加注解@Valid
即可实现,对集合对象同样使用
比如,添加订单,订单包含订单主表和订单详情表,要求订单主表的name和订单详情表中的商品name不能为空,而且必须同时满足
实现流程
- 准备
OrderVo
和OrderDetailVo
- 在
OrderVo
上加上校验注解,完成一层属性校验 - 在
OrderVo
中的OrderDetailVo
上加上@valid注解,并且在OrderDetailVo
的属性上加上校验注解 - 编写Controller测试,即可完成递归校验
OrderVo
@Data @NoArgsConstructor @AllArgsConstructor public class OrderVo implements Serializable { private static final long serialVersionUID = 8015529308743266490L; private String id; @NotBlank(message = "订单名称不能为空") private String orderName; private Double price; @Valid private List<OrderDetailVo> orderDetailVoList; }
OrderDetailVo
/** * @author HLH * @description 订单详情vo * @email 17703595860@163.com * @date 2021/8/11 上午9:43 */ @Data @NoArgsConstructor @AllArgsConstructor public class OrderDetailVo implements Serializable { private static final long serialVersionUID = 8529818233485384618L; private String id; private String orderId; @NotBlank(message = "订单中商品id不能为空") private String goodsId; @NotBlank(message = "订单中商品名称不能为空") private String goodsName; private String goodsPrice; }
OrderController
/** * @author HLH * @description 订单模拟Controller * @email 17703595860@163.com * @date 2021/8/11 上午9:46 */ @RestController @RequestMapping("/order") public class OrderController implements ResultBuilder { @PostMapping public Result<?> insertOrder(@RequestBody @Validated OrderVo orderVo) { return success("新增订单成功"); } }
测试
Post /order 内部对象数据不完整,正常跑出错误
5. 自定义注解校验
SpringBoot的validation为我们提供了很多的校验规则注解,几乎可以满足我们日常开发中的绝大多数场景。但是,再特殊场景下,我们还是需要自定义一些校验规则注解,实现自定一校验。
一个好的框架一定是方便扩展的。SpringValidation允许用户自定义校验。
实现步骤
- 自定义校验注解
- 编写校验处理类
场景:
登录用户的密码必须是4-16位,并且必须同时包含数字,大写字母,小写,特殊字符
5.1 自定义校验注解
PasswordPatten
@Documented @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) @Retention(RUNTIME) @Repeatable(PasswordPatten.List.class) @Constraint(validatedBy = {PasswordPattenValidator.class}) public @interface PasswordPatten { String message() default "密码格式错误,必须位4-16位,并且包含数字,大写字母,小写字母,特殊字符"; Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default { }; @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) @Retention(RUNTIME) @Documented public @interface List { PasswordPatten[] value(); } }
5.2 自定义检验处理类
PasswordPattenValidator
public class PasswordPattenValidator implements ConstraintValidator<PasswordPatten, String> { /** * 重写校验方法 * @param value 值 * @param context 上下文 * @return 是否校验通过,true通过,false不通过 */ @Override public boolean isValid(String value, ConstraintValidatorContext context) { if (StringUtils.isBlank(value)) { return false; } return validatePassword(value); } /** * 校验方法,校验规则 * @param password 值 * @return 是否符合规则,true通过,false不通过 */ private boolean validatePassword(String password) { return StringUtils.containsAny(password, "-_+=,.?~!@#$%^&*()") && StringUtils.containsAny(password, "abcdefghijklmnopqrstuvwxyz") && StringUtils.containsAny(password, "ABCDEFGHIJKLMNOPQRSTUVWXYZ") && StringUtils.containsAny(password, "0123456789"); } }
5.3 测试
LoginUserVo
/** * @author HLH * @description 登录接口需要的用户名密码封装的vo * @email 17703595860@163.com * @date Created in 2021/8/10 下午10:18 */ @Data @NoArgsConstructor @AllArgsConstructor public class LoginUserVo implements Serializable { private static final long serialVersionUID = -5331320733431220933L; @NotBlank(message = "用户名不能为空") // 非空,message为错误的提示信息 private String username; @NotBlank(message = "密码不能为空") // 非空 @PasswordPatten // 密码自定义校验 private String password; }
UserController
修改登录接口
/** * 加上@Validated 注解,会自动对LoginUserVo中加了注解的字段进行校验 */ @PostMapping("/login") public Result<?> login(@RequestBody @Validated LoginUserVo loginUserVo) { return success("登录成功"); }
PostMan测试
Post /user/login 错误密码示例
这篇关于springboot-validation的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-12-27OpenFeign服务间调用学习入门
- 2024-12-27OpenFeign服务间调用学习入门
- 2024-12-27OpenFeign学习入门:轻松掌握微服务通信
- 2024-12-27OpenFeign学习入门:轻松掌握微服务间的HTTP请求
- 2024-12-27JDK17新特性学习入门:简洁教程带你轻松上手
- 2024-12-27JMeter传递token学习入门教程
- 2024-12-27JMeter压测学习入门指南
- 2024-12-27JWT单点登录学习入门指南
- 2024-12-27JWT单点登录原理学习入门
- 2024-12-27JWT单点登录原理学习入门