《Techporters架构搭建》-Day05 属性校验

源码地址:请看day05

前言

在项目开发过程中,经常遇到需要对传递的参数进行校验,比如某个参数字段是否为空、值的取值是否在约定范围、格式是否合法等等,最原始的写法,通过if判断

    @PostMapping
    public Result<Long> save(@RequestBody SystemUserDto systemUserDto) {
        if(systemUserDto.getUserName!=null){
           throw new BusinessException("用户名称不能为空");
        }
        if(systemUserDto.getMobile!=null){
           throw new BusinessException("用户电话不能为空");
        }
        if(systemUserDto.getMobile().length()>11){
           throw new BusinessException("用户电话长度不能超过11位");
        }
         return Result.ok(id);
    }

还有通过Spring框架提供的Assert类,它能够帮助我们确保方法参数符合预期,如果不符合会抛出IllegalArgumentException,然后通过全局异常捕获将错误按照统一格式返给前端。
示例:校验字符串非空

import org.springframework.util.Assert;

public void checkString(String input) {
    // 校验字符串非空,如果为空则抛出异常
    Assert.hasText(input, "输入字符串不能为空");
}

常用的一些方法

Assert.notNull(Object object,"object is required");  // 对象非空
Assert.isTrue(Object object,"object must be true");  // 对象必须为true
Assert.notEmpty(Collection collection,"collection must not be empty");  // 集合不能为空
Assert.hasLength(String text,"text must be specified");  // 字符不为null且字符长度不为0
Assert.hasText(String text,"text must not be empty");  // text不为null且必须至少包含一个非空的字符
Assert.isInstanceOf(Class class, Object object,"class must be of type[class]");  // object必须为class指定的类

最后一种就是使用@Valid 注解和 @Validated 注解,首先我们要了解这两个注解的区别,然后着重描述一下@Validated 用法
1.来源不同

  • @Validated:是Spring框架特有的注解,属于Spring的一部分,也是JSR 303的一个变种。它提供了一些 @Valid
    所没有的额外功能,比如分组验证。
  • @Valid:Java EE提供的标准注解,它是JSR
    303规范的一部分,主要用于Hibernate Validation等场景。

2.注解位置

  • @Validated : 用在类、方法和方法参数上,但不能用于成员属性。
  • @Valid:可以用在方法、构造函数、方法参数和成员属性上。

3.是否支持分组

  • @Validated:支持分组验证,可以更细致地控制验证过程。此外,由于它是Spring专有的,因此可以更好地与Spring的其他功能(如Spring的依赖注入)集成。
  • @Valid:主要支持标准的Bean验证功能,不支持分组验证。

4.嵌套验证

  • @Validated :不支持嵌套验证。
  • @Valid:支持嵌套验证,可以嵌套验证对象内部的属性。

@Validated基础用法

① 在根目录build.gradle引入校验的依赖包

springValidationVersion = '3.3.2'
//Validation参数校验
implementation "org.springframework.boot:spring-boot-starter-validation:${springValidationVersion}"

② 在对象需要校验属性上添加注解,比如我要验证账号不能为空

@NotBlank(message = "用户账号不能为空")
private String username;

其它类型的注解

注解验证的数据类型描述
@NotNull任意类型验证属性不能为null
@NotBlank字符串验证字符串属性不能为空且长度必须大于0
@Size(min,max )CharSequence
Collection
Map
Array
字符串:字符串长度必须在指定的范围内
Collection:集合大小必须在指定的范围内
Map:map的大小必须在指定的范围内
Array:数组长度必须在指定的范围内
@Min整型类型验证数字属性的最小值
@Max整型类型验证数字属性的最大值
@DecimalMin数字类型验证数字属性的最小值(包括小数)
@DecimalMax数字类型验证数字属性的最大值(包括小数)
@Digits(integer,fraction)数字类型 验证数字属性的整数位数和小数位数
@Email字符串类型验证字符串属性是否符合Email格式
@Pattern字符串验证字符串属性是否符合指定的正则表达式
@Positive数字类型验证数值为正数
@PositiveOrZero数字类型验证数值为正数或0
@Negative数字类型验证数值为负数
@NegativeOrZero数字类型验证数值为负数或0
@AssertTrue布尔类型参数值必须为 true
@AssertFalse布尔类型参数值必须为 false
@Past时间类型(Date)参数值为时间,且必须小于 当前时间
@PastOrPresent时间类型(Date)参数值为时间,且必须小于或等于 当前时间
@Future时间类型(Date)参数值为时间,且必须大于 当前时间
@FutureOrPresent时间类型(Date)参数值为时间,且必须大于或等于 当前日期

③ 在接口校验的对象前面添加上 @Validated 注解

@PostMapping
public Result<Long> save(@RequestBody @Validated SystemUserDto systemUserDto) {
  Long id=systemUserService.createUser(systemUserDto);
  return Result.ok(id);
}

@PutMapping
public Result<Boolean> update(@RequestBody @Validated SystemUserDto systemUserDto) {
  systemUserService.updateUser(systemUserDto);
  return Result.ok(true);
}

④通过Apifox测试,可以看出返回的值并不能看出问题,因此下一步结构化一下异常显示
在这里插入图片描述
后端报错

Resolved [org.springframework.web.bind.MethodArgumentNotValidException:
	Validation failed for argument [0] in public com.tps.cloud.response.Result<java.lang.Long>
	com.tps.cloud.system.controller.SystemUserController.save(com.tps.cloud.system.dto.SystemUserDto): [Field error in object 'systemUserDto' on field 'username': rejected value []; 
	codes [NotBlank.systemUserDto.username,NotBlank.username,NotBlank.java.lang.String,NotBlank]; 
	arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [systemUserDto.username,username]; arguments []; 
	default message [username]]; default message [用户账号不能为空]] ]

⑤ 给前端返回结构化错误提示,需要配置全局异常捕获类GlobalExceptionHandler中添加捕获MethodArgumentNotValidException的方法

/**
 * validation Exception
 * @param exception
 * @return Result 
*/
@ExceptionHandler({ MethodArgumentNotValidException.class })
public Result handleBodyValidException(MethodArgumentNotValidException exception) {
	FieldError fieldError = exception.getBindingResult().getFieldError();
    return Result.failed(String.format("%s", fieldError.getDefaultMessage()));
}

通过org.springframework.boot.autoconfigure.AutoConfiguration.imports完成自动配置注册
在这里插入图片描述
⑥ 通过Apifox测试,可以看出返回的值已经结构化
在这里插入图片描述

集合校验

分组校验

分组验证是为了在不同的验证场景下能够对对象的属性进行灵活地验证,从而提高验证的精细度和适用性。一般我们在对同一个对象进行保存或修改时,会使用同一个类作为入参。那么在创建时需要校验某个字段,但是更新的时候不需要校验,这个时候就需要用到分组校验了。
对于定义分组有两点要特别注意:

  • 定义分组必须使用接口
  • 要校验字段上必须加上分组,分组只对指定分组生效,不加分组不校验

① 创建分组
用于创建时指定分组:

package com.tps.cloud.group;

public interface AddGroup {

}

用于更新时指定分组:

package com.tps.cloud.group;

public interface UpdateGroup {

}

② 在实体类上添加注解,我们只添加了AddGroup.class 分组

/**
* 用户账号
*/
@NotBlank(message = "用户账号不能为空",groups = {AddGroup.class})
private String username;
/**
* 密码
*/
@NotBlank(message = "密码不能为空",groups = {AddGroup.class})
private String password;

③ 在新增接口上添加分组,更新不添加分组,通过Apifox测试

@PostMapping
public Result<Long> save(@RequestBody @Validated({AddGroup.class}) SystemUserDto systemUserDto) {
    Long id=systemUserService.createUser(systemUserDto);
    return Result.ok(id);
}

@PutMapping
public Result<Boolean> update(@RequestBody @Validated SystemUserDto systemUserDto) {
   systemUserService.updateUser(systemUserDto);
   return Result.ok(true);
}

保存调用
更新调用
④在实体类以及对应接口上添加UpdateGroup.class分组,通过Apifox测试

    /**
     * 用户账号
     */
    @NotBlank(message = "用户账号不能为空",groups = {AddGroup.class,UpdateGroup.class})
    private String username;
    /**
     * 密码
     */
    @NotBlank(message = "密码不能为空",groups = {AddGroup.class,UpdateGroup.class})
    private String password;
    @PutMapping
    public Result<Boolean> update(@RequestBody @Validated({UpdateGroup.class}) SystemUserDto systemUserDto) {
        systemUserService.updateUser(systemUserDto);
        return Result.ok(true);
    }

在这里插入图片描述

嵌套校验

嵌套校验(Nested Validation) 指的是在验证对象时,对对象内部包含的其他对象进行递归验证的过程。当一个对象中包含另一个对象作为属性,并且需要对这个被包含的对象也进行验证时,就需要进行嵌套校验。
嵌套属性指的是在一个对象中包含另一个对象作为其属性的情况。换句话说,当一个对象的属性本身又是一个对象,那么这些被包含的对象就可以称为嵌套属性。
我们继续以保存用户接口为例:
① 创建SystemDeptDto类,并添加验证注解,现在把SystemDeptDto作为SystemUserDto的嵌套属性

package com.tps.cloud.system.dto;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.tps.cloud.entity.TenantEntity;
import com.tps.cloud.group.AddGroup;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 部门传输类
 */
@Data
public class SystemDeptDto{

    /**
     * 部门id
     */
    @TableId
    private Long id;
    /**
     * 部门名称
     */
    @NotBlank(message = "部门名称不能为空",groups = {AddGroup.class})
    private String name;
    /**
     * 父部门id
     */
    private Long parentId;
    /**
     * 显示顺序
     */
    private Integer sort;
    /**
     * 负责人id
     */
    private Long leaderUserId;
    /**
     * 联系电话
     */
    private String phone;
    /**
     * 邮箱
     */
    private String email;
    /**
     * 部门状态(0正常 1停用)
     */
     @Valid
    private Integer status;
}

public class SystemUserDto  {
	...省略前面属性

    /**
     * 部门
     */
    private SystemDeptDto systemDept;
}
@PostMapping
public Result<Long> save(@RequestBody @Validated SystemUserDto systemUserDto) {
    Long id=systemUserService.createUser(systemUserDto);
    return Result.ok(id);
}

② 测试
在这里插入图片描述

自定义校验器

有时,内置的校验器无法满足您的需求。例如,您可能需要验证用户名是否唯一,这需要访问数据库。在这种情况下,您可以定义自己的校验器。

要定义自定义校验器,请创建一个实现 javax.validation.ConstraintValidator 接口的类。在下面的示例中,我们将创建一个用于验证用户名是否唯一的校验器:
① 创建校验器UniqueUsernameValidatorConstraintValidator包含以下两种方法:

  • 初始化方法 initialize:这个方法在验证器的生命周期中仅被调用一次。它传递了与验证器关联的注解实例,允许验证器从注解实例中提取和存储配置详情。
  • 验证方法isValid: 这是实现验证逻辑的地方。这个方法对于每个要验证的值都会被调用,并返回一个布尔值,表示数据是否符合约束条件。
package com.tps.cloud.system.constraint;

import com.tps.cloud.system.service.SystemUserService;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import lombok.AllArgsConstructor;


@AllArgsConstructor
public class UniqueUsernameValidator implements ConstraintValidator<UniqueUsername, String> {

    private final SystemUserService systemUserService;
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) {
            return true;
        }
        return systemUserService.findByUsername(value) == null;
    }
}

② 创建一个自定义注解UniqueUsername,以便在代码中使用

package com.tps.cloud.system.constraint;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {UniqueUsernameValidator.class})
public @interface UniqueUsername {
    String message() default "用户名已存在";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

② 在用户账号属性上添加改注解

public class SystemUserDto  {
	...
    /**
     * 用户账号
     */
    @NotBlank(message = "用户账号不能为空",groups = {AddGroup.class,UpdateGroup.class})
    @UniqueUsername
    private String username;
    ...
}

③ 测试接口
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值