SpringBoot参数校验

在日常项目开发中,我们都知道参数验证是必不可少的一环,但是有时候为了偷懒,把参数校验交给前端开发人员去处理,这样很容易影响系统稳定性和安全性,毕竟现在有很多手段可以绕过前端,直接后端请求接口。

本文就来介绍一下在 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

检查是否是一个有效的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 的区别

  1. 来源
  • @Validated:Spring 框架特有的注解,是标准 JSR-303 的一个变种,提供了一个分组功能
  • @Valid:标准 JSR-303 规范的标记型注解。
  1. 注解位置
  • @Validated:作用在类上、方法上、方法参数上,不能作用于成员属性上。
  • @Valid:方法、构造函数、方法参数、成员属性上。
  1. 分组
  • @Validated:支持分组验证。
  • @Valid:支持标准的 Bean 验证功能,不支持分组验证。
  1. 嵌套验证
  • @Validated:不支持嵌套验证。
  • @Valid:支持嵌套验证。

二、分组验证

同一个应用中,会出现不同的场景,比如:用户创建、用户更新、用户删除,针对不同的场景,有些字段在一个场景中需要验证,但是在另一个场景中该字段就不需要验证,我们可以选择新建不同的实体类去解决这类问题,比如:用户创建 UserCreateVO、用户更新 UserUpdate 等,但是这样的做法会造成类的膨胀、代码的冗余。其实我们可以使用分组校验有选择的执行特定组的参数校验。定义分组校验需要注意两点:

  1. 定义分组必须使用接口。
  2. 要校验的字段必须加上分组,分组只对指定分组生效,不加分组不校验。
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

三、自定义验证注解

在项目开发中,我们也经常使用自定义注解去完成字段校验。自定义校验注解步骤如下:

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

假如: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 项目中,怎么去使用基本的校验注解、嵌套校验、分组校验,同时又学习了怎么使用自定义校验注解,在项目中合理地使用相关校验注解,可以简化代码、提高代码可读性和可维护性。


改变你能改变的,接受你不能改变的,关注公众号:程序员康康,一起成长,共同进步。

  • 13
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值