Spring Boot参数校验-简单有效的数据验证

Spring Boot 简单有效的数据验证

概要

在现代软件应用的开发中,参数校验对于确保数据的完整性和安全性至关重要。

了不起最近和一个前端实习生联调接口发现,参数校验确实给到前端展示和处理的诸多便利。

Spring Boot作为一个流行的Java框架,提供了多种参数校验的方式。

我们将通过具体的代码实例来演示每种校验方式的用法和效果。

1 参数校验的重要性

在开发过程中,参数校验是确保数据的完整性和安全性的重要环节。

以下是一些原因说明为什么参数校验是必要的:

1.1 数据完整性

参数校验可以防止无效或错误的数据进入系统。

通过校验前端输入的参数,我们可以确保数据的完整性,避免因为缺少必要的信息而导致程序错误或异常。

1.2 安全性

参数校验可以防止恶意用户提交危险的数据。

例如,对于密码字段,我们可以通过校验规则要求用户输入至少8个字符、包含字母和数字等,以增加密码的强度,提高系统的安全性。

1.3 用户体验

参数校验可以帮助我们捕获输入错误,并向用户提供友好的错误提示。

通过及时地反馈错误信息,用户可以更快地发现和纠正输入错误,提升用户体验。

特别是在前后端接口联调时,前端传参错误很快能得到异常提示,就大大提升了联调效率。

2. Spring Boot参数校验的基本原理

在Spring Boot中,参数校验是通过JSR 303规范的Bean Validation实现的。

它基于注解和反射机制,可以轻松地在实体类的字段上进行声明式的校验规则定义。

当请求到达控制器方法时,Spring Boot会自动根据定义的校验规则执行校验操作,并返回校验结果。

2.1. 导入必需的包

在使用参数校验功能时,我们需要导入相关的依赖包。在 pom.xml文件中添加以下依赖:

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

3. 校验方式一:注解方式

注解方式是最常用和方便的参数校验方式。

Spring Boot提供了多种内置的校验注解,包括 @NotNull、@NotBlank、@NotEmpty、@Size、@Pattern和 @Valid等。

3.1. @NotNull、@NotBlank 和 @NotEmpty

@NotNull注解用于检查字段是否为null
@NotBlank注解用于检查字段是否不为空且长度大于0
@NotEmpty注解用于检查字段是否不为空

@Data
public class UserDto {
  @NotNull(message = "用户名不能为空")
  private String username;

  @NotBlank(message = "密码不能为空")
  private String password;

  @NotEmpty(message = "邮箱不能为空")
  private String email;
}

在上述示例中,我们使用了 @NotNull注解来确保 username字段不为null,使用了 @NotBlank注解来确保 password字段不为空且长度大于0,使用了 @NotEmpty注解来确保 email字段不为空。

通过为注解提供错误提示信息,我们可以在校验失败时向用户提供友好的错误提示。

3.2. @Size

@Size注解用于检查字段的长度是否在指定范围内。

@Data
public class UserDto{
    @Size(min = 2, max = 20, message = "用户名长度必须在2到20个字符之间")
    private String username;
}

在上述示例中,我们使用了 @Size注解来确保 username字段的长度在2到20个字符之间。

如果长度不符合指定范围,校验将失败,并返回指定的错误提示信息。

3.3. @Pattern

@Pattern注解可以用于检查字段是否符合指定的正则表达式。

public class UserDto{
    @Pattern(regexp = "^[A-Za-z0-9]+$", message = "用户名只能包含字母和数字")
     private String username;
}

在上述示例中,我们使用了 @Pattern注解来确保 username字段只包含字母和数字。

如果字段中包含其他字符,校验将失败,并返回指定的错误提示信息。

3.4. @Valid

@Valid注解用于标记一个嵌套对象,表示需要对该对象进行递归校验。

public class UserDto {
 @Valid
 private AddressDto address;
}

public class AddressDto {
   @NotBlank(message = "地址不能为空")
   private String street;
}

在上述示例中,我们使用了 @Valid注解来标记 UserDto中的 AddressDto对象,表示需要对 AddressDto对象进行递归校验。

在校验过程中,会同时校验 AddressDto对象中的 street字段是否为空。

3.5. 复杂逻辑的参数校验

有时候,我们需要对多个字段进行复杂的逻辑校验,例如需要两个字段相互比较或执行自定义的校验逻辑。

在这种情况下,我们可以使用自定义的校验器(Validator)来实现。


public class UserDto {
  @NotNull(message = "起始日期不能为空")
  private LocalDate startDate;

  @NotNull(message = "结束日期不能为空")
  private LocalDate endDate;

   @AssertTrue(message = "结束日期必须晚于起始日期")
  private boolean isEndDateAfterStartDate(){
if (startDate == null || endDate == null) {
   return true;
        }
   return  endDate.isAfter(startDate);
    }
}

在上述示例中,我们使用了 @AssertTrue注解来标记自定义的校验方法 isEndDateAfterStartDate()。该方法检查 endDate是否晚于 startDate,如果校验失败,将返回指定的错误提示信息。

4. 校验方式二:自定义校验注解

除了使用Spring Boot提供的内置注解外,我们还可以自定义校验注解来满足特定的校验需求。

4.1. 自定义注解

首先,我们需要自定义一个注解,并指定它的校验器。

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {EmailValidator.class})
public @interface Email {
String message()default "邮箱格式不正确";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

在上述示例中,我们定义了一个名为 Email的注解,并指定了它的校验器 EmailValidator。

4.2. 自定义校验器

接下来,我们需要实现校验器的逻辑。

public class EmailValidator implements ConstraintValidator<Email, String> {
@Override
public void initialize(Email constraintAnnotation){
    }

@Override
public boolean isValid(String value, ConstraintValidatorContext context){
// 在这里编写自定义的校验逻辑
// 返回true表示校验通过,返回false表示校验失败
    }
}

在上述示例中,我们实现了 EmailValidator类,并实现了 isValid方法来定义自定义校验逻辑。

4.3. 使用自定义注解

最后,我们可以在实体类中使用自定义注解进行参数校验。

public class UserDto{ 
@Email
private String email;

}

在上述示例中,我们在 UserDto类的 email字段上使用了自定义的 @Email注解。

当校验发生时,将会调用 EmailValidator中的 isValid方法来执行自定义的校验逻辑。

5. 控制器中的参数校验

在Spring Boot中,我们可以在控制器中使用参数校验来验证请求中的参数。

@RestController
@RequestMapping("/api/users")
public class UserController{

@PostMapping
public ResponseEntity createUser(@Valid @RequestBody UserDto userDto){
// 参数校验通过,执行业务逻辑
    }
}

在上述示例中,我们在 createUser方法中使用了 @Valid注解来对 UserDto对象进行参数校验。如果校验失败,将会返回带有错误信息的响应。

6.拓展应用

自定义注解+枚举值
6.1 定义枚举值

package com.bry.advert.enums;

/**
 * 回传类型:1有效、2无效、3历史成交、4正向、5负向
 */
public enum FeedbackTypeEnum {

    EFFECTIVE_WECHART_ADD_SUCCESS(1, "用户添加企微成功"),
    WECHART_FIRST_MESSAGE(2, "微信用户首次信息"),

    WECHART_THREE_OPEN(3, "三次开口"),
    STOCK_CUSTOMER(4, "股民"),

    SMALL_ORDER_PAY(5, "小单产品支付"),

    BIG_ORDER_PAY(6, "大单产品支付"),

    HISTORY_SMALL_ORDER_PAY(7, "历史已开大单正常客户"),

    HISTORY_BIG_ORDER_PAY(8, "历史已开大单正常客户"),

    ;


    private final Integer code;
    private final String desc;

    public Integer getCode() {
        return code;
    }

    public String getDesc() {
        return desc;
    }

    FeedbackTypeEnum(Integer code, String desc) {
        this.code = code;
        this.desc = desc;
    }


    /**
     * 第三方类型
     */

    public static PlatformType getEnum(Integer code) {
        for (PlatformType enums : PlatformType.values()) {
            if (enums.getCode().equals(code)) {
                return enums;
            }
        }
        return null;
    }


    /**
     * 校验是否是该枚举-
     * **该方法一定不能删除 用于校验参数**
     *
     * @param code 枚举字符串
     * @return true or false
     */
    public static boolean isValidEnum(Integer code) {
        for (FeedbackTypeEnum myEnum : FeedbackTypeEnum.values()) {
            if (myEnum.getCode().equals(code)) {
                return true;
            }
        }
        return false;
    }
}

6.2自定义参数校验注解

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EnumValueCheck.Validator.class)
public @interface EnumValueCheck {

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

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

    /**
     * 枚举类
     */
    Class<? extends Enum<?>> enumClass();

    /**
     * 枚举校验的方法
     */
    String enumMethod() default "isValidEnum";

    /**
     * 默认提示文字
     */
    String message() default "enum.value.invalid";

    boolean allowNull() default false;

    //内部校验器
    class Validator implements ConstraintValidator<EnumValueCheck, Object> {
        private Class<? extends Enum<?>> enumClass;
        private String enumMethod;
        private boolean allowNull;

        @Override
        public void initialize(EnumValueCheck enumValue) {
            enumMethod = enumValue.enumMethod();
            enumClass = enumValue.enumClass();
            allowNull = enumValue.allowNull();
        }

        @Override
        public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
            if (null == value) {
                return allowNull;
            }
            if (null == enumClass || null == enumMethod) {
                return Boolean.TRUE;
            }
            Class<?> valueClass = value.getClass();
            try {
                Method method = enumClass.getMethod(enumMethod, valueClass);
                if (!Boolean.TYPE.equals(method.getReturnType()) && !Boolean.class.equals(method.getReturnType())) {
                    throw new BusinessException(BusinessErrorEnum.PARAM_ERROR.getCode(),
                            String.valueOf(String.format("%s method return is not boolean type in the %s class", enumMethod, enumClass)));
                }
                if (!Modifier.isStatic(method.getModifiers())) {
                    throw new BusinessException(BusinessErrorEnum.PARAM_ERROR.getCode(),
                            String.valueOf(String.format("%s method is not static method in the %s class", enumMethod, enumClass)));
                }
                Boolean result = (Boolean) method.invoke(null, value);
                return result == null ? Boolean.FALSE : result;
            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                throw new BusinessException(BusinessErrorEnum.PARAM_ERROR.getCode(),
                        String.valueOf(String.format("%s method return is not boolean type in the %s class", enumMethod, enumClass)));
            } catch (NoSuchMethodException | SecurityException e) {
                throw new BusinessException(BusinessErrorEnum.PARAM_ERROR.getCode(),
                        String.valueOf(String.format("This %s(%s) method does not exist in the %s", enumMethod, valueClass, enumClass)),
                        e);
            }
        }

    }
}

6.3 自定义注解的使用

@Data
public class FeedbackResourceRequest {

    @ApiModelProperty(value = "unionId 唯一", required = true)
    @NotNull(message = "unionId 不能为空")
    private String unionId;

    @ApiModelProperty(value = "回传类型:1有效、2无效、3历史成交、4正向、5负向", required = true)
    @EnumValueCheck(enumClass = FeedbackTypeEnum.class, message = "邀请类型错误")
    private Integer feedbackType;
}

结论

Spring Boot中参数校验的多种实现方式,包括注解方式和自定义校验注解方式。

我们通过具体的代码示例演示了每种校验方式的用法和效果。

相信你肯定能应用好参数校验,将一些简单的逻辑校验使用注解,稍稍复杂的使用校验器,复杂业务的校验可以再具体的业务代码中实现。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值