SpringBoot参数校验的一些实战应用
作者:cxykk1217
在日常项目开发中,我们都知道参数验证是必不可少的一环,但是有时候为了偷懒,把参数校验交给前端开发人员去处理,这样很容易影响系统稳定性和安全性,毕竟现在有很多手段可以绕过前端,直接后端请求接口。
本文就来介绍一下在 SpringBoot
应用中怎么进行参数校验。
一、使用参数校验注解
在 SpringBoot
项目中可以引用 spring-boot-starter-validation
实现数据验证。spring-boot-starter-validation
不仅支持 JSR-303(Bean Validation 1.0)
规范,还提供了对 JSR-380(Bean Validation 2.0)
规范的全面支持。可以利用 Bean Validation 2.0
的新特性,更灵活地定义验证规则,包括对集合、嵌套对象的验证等。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>2.4.3</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
通常在实体类上的字段上使用标准的 Bean Validation
注解,以下是一些常用的参数校验注解以及相关例子。
注解名称 | 功能 |
@Null | 检查该字段为空 |
@NotNull | 不能为null |
@NotBlank | 不能为空,常用于检查空字符串 |
@NotEmpty | 不能为空,多用于检测list是否size是0 |
@Max | 该字段的值只能小于或等于该值 |
@Min | 该字段的值只能大于或等于该值 |
@Past | 检查该字段的日期是在过去 |
@Future | 检查该字段的日期是否是属于将来的日期 |
检查是否是一个有效的email地址 | |
@Pattern(regex=,flag=) | 被注释的元素必须符合指定的正则表达式 |
@Range(min=,max=,message=) | 被注释的元素必须在合适的范围内 |
@Size(min=, max=) | 检查该字段的size是否在min和max之间,可以是字符串、数组、集合、Map等 |
@Length(min=,max=) | 检查所属的字段的长度是否在min和max之间,只能用于字符串 |
@AssertTrue | 用于boolean字段,该字段只能为true |
@AssertFalse | 该字段的值只能为false |
1.1、基本用法
1.@NotNull:校验元素值不能为空,如果为空,则校验失败。
@NotNull(message = "名字不能为空") private String userName;
2.@NotBlank:校验字符串值不能为null和空字符串,必须包含至少一个非空字符即执行trim
(之后不为’‘)。如果元素为null
或者’',则验证失败。
@NotBlank(message = "昵称不能为null和空字符串") private String nickName;
3.@NotEmpty:校验集合或者数组或者字符串是否非空,通常用于集合和数组字段,需要集合和数组元素个数大于0
。也可以作用于字符串,此时校验字符串不能为null
或空串(可以是一个空格)。
@NotEmpty(message = "postIds不能为空") private Long[] postIds;
4.@Max:校验数字元素最大值。
@Max(value=100,message = "年龄最大100") private String age;
5.@Min:校验数字元素最小值。
@Min(value=18,message = "年龄最小100") private String age;
6.@Past:校验日期或时间元素是否在当前时间之前。即是否是过去时间。作用于Date
相关类型的字段。
@Past(message = "") private Date createTime;
7.@Future:校验日期或时间元素是否在当前时间之前。即是否是过去时间。作用于Date
相关类型的字段。
@Future(message = "") private Date createTime;
8.@Email:校验字符串元素是否为有效的电子邮件地址。
@Email(message = "") private String email;
9.@Pattern:根据正则表达式校验字符串元素的格式。
@Pattern(regexp = "[a-zA-Z0-9]+") private String userName;
10.@Size:校验集合元素个数或字符串的长度在指定范围内。
@Size(min = 3, max = 10, message = "长度在3到10之间") private String username;
11.@Length:校验字符串元素的长度。作用于字符串。
@Length(min = 3, max = 10, message = "长度在3到10之间") private String username;
以上只是部分注解和他们的功能,需要详细的了解需要查看源码。
1.2、用法示例
定义入参请求参数
package com.duan.pojo.vo; import com.baomidou.mybatisplus.annotation.TableField; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import javax.validation.constraints.Email; import javax.validation.constraints.NotBlank; /** * @author db * @version 1.0 * @description SysUserVO * @since 2024/6/17 */ @Data public class SysUserVO { @ApiModelProperty("部门ID") private Long deptId; @NotBlank(message = "名字不能为空") @ApiModelProperty("用户名") private String userName; @NotBlank(message = "昵称不能为null和空字符串") @ApiModelProperty("昵称") private String nickName; @ApiModelProperty("密码") private String password; @ApiModelProperty("用户性别(0男,1女") private Integer gender; @ApiModelProperty("手机号码") private String phone; @Email(message = "请填写正确的邮箱地址") @ApiModelProperty("邮箱") private String email; @ApiModelProperty("头像地址") private String avatarName; @ApiModelProperty("用户类型(0管理员,1普通用户") private Integer userType; @ApiModelProperty("状态:1启用、0禁用") private Integer status; @ApiModelProperty("备注") private String remark; }
定义mapper
package com.duan.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.duan.pojo.SysUser; import org.apache.ibatis.annotations.Mapper; @Mapper public interface UserMapper extends BaseMapper<SysUser> { }
定义接口
package com.duan.service; import com.baomidou.mybatisplus.extension.service.IService; import com.duan.pojo.SysUser; public interface UserService extends IService<SysUser> { void AddUser(SysUserVO sysUserVO); }
定义接口实现
package com.duan.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.duan.mapper.UserMapper; import com.duan.pojo.SysUser; import com.duan.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * @author db * @version 1.0 * @description UserServiceImpl * @since 2024/4/15 */ @Service public class UserServiceImpl extends ServiceImpl<UserMapper, SysUser> implements UserService { @Autowired private UserMapper userMapper; @Override public void AddUser(SysUserVO sysUserVO) { SysUser sysUser = new SysUser(); BeanUtils.copyProperties(sysUserVO,sysUser); userMapper.insert(sysUser); } }
定义controller
package com.duan.controller; import com.duan.pojo.ResponseResult; import com.duan.pojo.Result; import com.duan.pojo.SysUser; import com.duan.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author db * @version 1.0 * @description UserController * @since 2024/4/15 */ @RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @PostMapping("/addUser") public ResponseResult addUser(@RequestBody @Validated SysUserVO sysUserVO){ userService.AddUser(sysUserVO); return ResponseResult.okResult(); } }
1.3、示例测试
调用增加用户接口
注意:我们需要捕获一下 MethodArgumentNotValidException
,才能如上图显示的那样。
1.4、嵌套对象的验证
在SysUserVO
中增加一个address
的校验,即需要对嵌套对象进行校验。
package com.duan.pojo.vo; import lombok.Data; import javax.validation.constraints.NotBlank; /** * @author db * @version 1.0 * @description AddressVO * @since 2024/6/17 */ @Data public class AddressVO { @NotBlank(message = "省份不能为空") private String province; @NotBlank(message = "城市不能为空") private String city; }
AddressVO
对象如下所示:
package com.duan.pojo.vo; import lombok.Data; import javax.validation.constraints.NotBlank; /** * @author db * @version 1.0 * @description AddressVO * @since 2024/6/17 */ @Data public class AddressVO { @NotBlank(message = "省份不能为空") private String province; @NotBlank(message = "城市不能为空") private String city; }
测试
说明:为了能够进行嵌套对象验证,必须手动在
SysUserVO
实体的addressVo
字段上明确指出这个字段里面的实体需要验证,由于@Vaildated
不能作用在成员属性上,而且@Valid
能加在成员属性上,同时配合controller
中在方法参数上@Validated
或@Valid
来进行嵌套验证。
这里必须要说明一下@Validated
和@Valid
的区别
- 来源
- @Validated:Spring 框架特有的注解,是标准 JSR-303 的一个变种,提供了一个分组功能
- @Valid:标准 JSR-303 规范的标记型注解。
- 注解位置
- @Validated:作用在类上、方法上、方法参数上,不能作用于成员属性上。
- @Valid:方法、构造函数、方法参数、成员属性上。
- 分组
- @Validated:支持分组验证。
- @Valid:支持标准的 Bean 验证功能,不支持分组验证。
- 嵌套验证
- @Validated:不支持嵌套验证。
- @Valid:支持嵌套验证。
二、分组验证
同一个应用中,会出现不同的场景,比如:用户创建、用户更新、用户删除,针对不同的场景,有些字段在一个场景中需要验证,但是在另一个场景中该字段就不需要验证,我们可以选择新建不同的实体类去解决这类问题,比如:用户创建 UserCreateVO
、用户更新 UserUpdate
等,但是这样的做法会造成类的膨胀、代码的冗余。其实我们可以使用分组校验有选择的执行特定组的参数校验。定义分组校验需要注意两点:
- 定义分组必须使用接口。
- 要校验的字段必须加上分组,分组只对指定分组生效,不加分组不校验。
2.1、创建分组
创建两个分组接口,标识不同的业务场景CreateGroup用于创建时指定的分组
package com.duan.validatedGroup; /** * @author db * @version 1.0 * @description CreateUserGroup * @since 2024/6/24 */ public interface CreateUserGroup { }
UpdateGroup
用于更新时指定的分组
package com.duan.validatedGroup; /** * @author db * @version 1.0 * @description UpdateUserGroup * @since 2024/6/24 */ public interface UpdateUserGroup { }
2.2、使用分组校验
分组校验是通过在验证注解上指定 groups
属性来实现的。这个属性允许你为验证规则分配一个或多个验证组。假设用户创建时不传递用户ID
,其余的参数必传,用户更新接口必须传递用户ID
,可以不传递用户名,其他参数必须传递。
package com.duan.pojo.vo; import com.baomidou.mybatisplus.annotation.TableField; import com.duan.validatedGroup.CreateUserGroup; import com.duan.validatedGroup.UpdateUserGroup; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import javax.validation.Valid; import javax.validation.constraints.Email; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import java.util.List; /** * @author db * @version 1.0 * @description SysUserVO * @since 2024/6/17 */ @Data public class SysUserVO { @NotBlank(message = "请选择用户",groups = UpdateUserGroup.class) private Long id; @ApiModelProperty("部门ID") private Long deptId; @NotBlank(message = "名字不能为空",groups = CreateUserGroup.class) @ApiModelProperty("用户名") private String userName; @NotBlank(message = "昵称不能为null和空字符串") @ApiModelProperty("昵称") private String nickName; @ApiModelProperty("密码") private String password; @ApiModelProperty("用户性别(0男,1女") private Integer gender; @ApiModelProperty("手机号码") private String phone; @Email(message = "请填写正确的邮箱地址") @ApiModelProperty("邮箱") private String email; @ApiModelProperty("头像地址") private String avatarName; @ApiModelProperty("用户类型(0管理员,1普通用户") private Integer userType; @ApiModelProperty("状态:1启用、0禁用") private Integer status; @ApiModelProperty("备注") private String remark; @NotNull(message = "请输入地址信息") @Valid private AddressVO addressVO ; }
2.3、在Controller中使用分组
使用@Validated
注解,并指定要执行的验证。
package com.duan.controller; import com.duan.pojo.ResponseResult; import com.duan.pojo.Result; import com.duan.pojo.SysUser; import com.duan.pojo.vo.SysUserVO; import com.duan.service.UserService; import com.duan.validatedGroup.CreateUserGroup; import com.duan.validatedGroup.UpdateUserGroup; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author db * @version 1.0 * @description UserController * @since 2024/4/15 */ @RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @PostMapping("/addUser") public ResponseResult addUser(@RequestBody @Validated(value = CreateUserGroup.class) SysUserVO sysUserVO){ userService.addUser(sysUserVO); return ResponseResult.okResult(); } @PostMapping("/updateUser") public ResponseResult updateUser(@RequestBody @Validated(value = UpdateUserGroup.class) SysUserVO sysUserVO){ userService.updateUser(sysUserVO); return ResponseResult.okResult(); } }
2.4、测试
- 创建用户接口
username 不传值,即不满足 username 不能为空的条件,应该校验不通过。通过测试发现,会提示username不能为空。
- 更新用户update接口
id写成0,username
还是为空,只是报了id
不能小于1
三、自定义验证注解
在项目开发中,我们也经常使用自定义注解去完成字段校验。自定义校验注解步骤如下:
- 编写一个自定义校验注解
- 编写一个自定义的校验器
- 关联自定义的校验器和自定义的校验注解
假如:user
实体中的password
字段,格式是大于八位且包含数字大写字母小写字母,这个自定义校验怎么实现呢?
1、首先定义一个注解PasswordValid
package com.duan.anno; import com.duan.config.PasswordValidValidator; import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.*; import static java.lang.annotation.ElementType.TYPE_USE; import static java.lang.annotation.RetentionPolicy.RUNTIME; @Constraint(validatedBy = { PasswordValidValidator.class}) @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) @Retention(RUNTIME) public @interface PasswordValid { String message() default "{密码格式不对}"; Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default { }; }
2、定义一个校验器PasswordValidValidator
自定义校验器需要实现 ConstraintValidator<A extends Annotation, T>
这个接口,第一个泛型是校验注解,第二个是参数类型。
package com.duan.config; import com.duan.anno.PasswordValid; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; /** * @author db * @version 1.0 * @description PasswordValidValidator * @since 2024/6/25 */ public class PasswordValidValidator implements ConstraintValidator<PasswordValid,String> { @Override public void initialize(PasswordValid constraintAnnotation) { } @Override public boolean isValid(String value, ConstraintValidatorContext context) { if (value == null) { return false; } boolean hasUppercase = value.chars().anyMatch(Character::isUpperCase); boolean hasLowercase = value.chars().anyMatch(Character::isLowerCase); boolean hasDigit = value.chars().anyMatch(Character::isDigit); return value.length() >= 8 && hasUppercase && hasLowercase && hasDigit; } }
3、关联自定义的校验器和自定义的校验注解
当你使用 @Constraint(validatedBy = EnumValidator.class)
注解时,Java Bean Validation
的实现框架会自动发现并注册相应的验证器。
@Constraint(validatedBy = { PasswordValidValidator.class})
在SysUserVO
中使用
@PasswordValid(groups = CreateUserGroup.class) @ApiModelProperty("密码") private String password;
模拟输入password
为纯数字时,校验不通过。
代码地址:https://gitee.com/duan138/practice-code/tree/master/paramValidated
四、总结
本文我们了解和实践在 SpringBoot
项目中,怎么去使用基本的校验注解、嵌套校验、分组校验,同时又学习了怎么使用自定义校验注解,在项目中合理地使用相关校验注解,可以简化代码、提高代码可读性和可维护性。
到此这篇关于SpringBoot参数校验的文章就介绍到这了,更多相关SpringBoot参数校验内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!