boot-admin开源项目中有关后端参数校验的最佳实践

这篇具有很好参考价值的文章主要介绍了boot-admin开源项目中有关后端参数校验的最佳实践。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

我们在项目开发中,经常会对一些参数进行校验,比如非空校验、长度校验,以及定制的业务校验规则等,如果使用if/else语句来对请求的每一个参数一一校验,就会出现大量与业务逻辑无关的代码,繁重不堪且繁琐的校验,会大大降低我们的工作效率,而且准确性也无法保证。为保证数据的正确性、完整性,前后端都需要进行数据检验。本文对开源 boot-admin 项目的后端校验实践进行总结,以飨码友。
boot-admin 是一款采用前后端分离模式、基于 SpringCloud 微服务架构的SaaS后台管理框架。系统内置基础管理、权限管理、运行管理、定义管理、代码生成器和办公管理6个功能模块,集成分布式事务 Seata、工作流引擎 Flowable、业务规则引擎 Drools、后台作业调度框架 Quartz 等,技术栈包括 Mybatis-plus、Redis、Nacos、Seata、Flowable、Drools、Quartz、SpringCloud、Springboot Admin Gateway、Liquibase、jwt、Openfeign、I18n等。

项目源码仓库github
项目源码仓库gitee

引入Maven依赖

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>

参数校验实践

定义校验对象

@Data
/** 组合校验注解(方式1) **/
@OverallValid(value = "check1" ,message="女士不得小于16岁。")
@OverallValid(value = "check2" ,message="男士不得小于18岁。")
public class User {
    //字符个数检测(内置注解)
    @Size(min = 1,max = 10,message = "姓名长度必须为1到10")
    //占用空间长度检测(自定义注解)
    @StringLength(min = 1,max = 12,message = "姓名的保存长度不允许超过12个字节。")
    private String name;
    //利用枚举类检测(自定义注解)
    @EnumValid(target = SexEnum.class, message = "性别的取值范围是【1】和【2】")
    private String sex;
    //注意 @NotNull @NotEmpty @NotBlank 的区别
    @NotBlank(message = "姓氏是必填项。")
    private String firstName;

    @Min(value = 10,message = "年龄最小为10")
    @Max(value = 100,message = "年龄最大为100")
    private Integer age;

    @Past(message = "出生时间必须为过去时间")
    private Date birth;

    @NotEmpty(message = "兴趣不能为空")
    private List<String> interest;

    //嵌套检测
    @Valid
    private List<User> children;
    @Valid
    private User father;
    @Valid
    private User mother;

    /** 组合校验(方式2) **/
    @BooleanValid(message = "男性年龄需在60岁以下")
    public boolean getValid1(){
        if(sex.equalsIgnoreCase("1") && age >= 60 ){
            return false;
        }
        return true;
    }
    /** 组合校验(方式2) **/
    @BooleanValid(message = "女性年龄需在55岁以下")
    public boolean getValid2(){
        if(sex.equalsIgnoreCase("2") && age >= 55 ){
            return false;
        }
        return true;
    }
    /** 组合校验(方式1)方法 **/
    public boolean check1(){
        if(sex.equalsIgnoreCase("2") && age < 16 ){
            return false;
        }
        return true;
    }
    /** 组合校验(方式1)方法 **/
    public boolean check2(){
        if(sex.equalsIgnoreCase("1") && age < 18 ){
            return false;
        }
        return true;
    }
}

相关枚举类:

public enum SexEnum {
    男("1"),女("2");
    private final String value;
    SexEnum(String value) {
        this.value = value;
    }
    public String getValue() {
        return value;
    }
}

参数校验(在 Controller 中使用)

@RestController
@RequestMapping("/api/system")
@Slf4j
public class DemoController {
    //注入校验信息采集器
    @Resource
    private FormValidator formValidator;

    @PostMapping("/free/user/check")
    public ResultDTO check(@Valid @RequestBody User user, BindingResult bindingResult, HttpServletRequest request) throws Exception{
        /** 参数校验 **/
        if (bindingResult.hasErrors()) {
            return formValidator.generateMessage(bindingResult);
        }
        /** 继续执行业务逻辑 **/
        return ResultDTO.success();
    }
}

在Controller中使用的校验结果信息采集器实现

接口定义:

public interface FormValidator {
    ResultDTO generateMessage(BindingResult bindingResult) throws Exception;
}

类实现:

@Service
@Slf4j
public class FormValidatorImpl implements FormValidator {
    @Override
    public ResultDTO generateMessage(BindingResult bindingResult) throws Exception {
        String msg = this.getMessage(bindingResult);
        return ResultDTO.failureCustom(msg);
    }
    /**
     * 生成校验结果
     * @param bindingResult
     * @return
     */
    private String getMessage(BindingResult bindingResult){
        log.info(bindingResult.toString());
        List<ObjectError> objectErrorList=bindingResult.getAllErrors();
        String msg= this.getFormValidErrsMsgNoBr(objectErrorList);
        log.info(msg);
        return msg;
    }
    private String getFormValidErrsMsgNoBr(List<ObjectError> objectErrorList) {
        if (objectErrorList==null) {
            return "";
        }
        StringBuffer csv = new StringBuffer();
        csv.append("数据验证未通过:[");
        for (int i = 0; i < objectErrorList.size(); i++){
            if (i > 0){
                csv.append("],[");
            }
            csv.append(objectErrorList.get(i).getDefaultMessage());
        }
        csv.append("]");
        return csv.toString();
    }
}

相关注解介绍

JSR-303 规范常用注解

以下列举常用内置注解,可直接使用。

注解 描述
@Valid 对po实体尽心校验
@AssertFalse 所注解的元素必须是Boolean类型,且值为false
@AssertTrue 所注解的元素必须是Boolean类型,且值为true
@DecimalMax 所注解的元素必须是数字,且值小于等于给定的值
@DecimalMin 所注解的元素必须是数字,且值大于等于给定的值
@Digits 所注解的元素必须是数字,且值必须是指定的位数
@Future 所注解的元素必须是将来某个日期
@Max 所注解的元素必须是数字,且值小于等于给定的值
@Min 所注解的元素必须是数字,且值大于等于给定的值
@Range 所注解的元素需在指定范围区间内
@NotNull 所注解的元素值不能为null
@NotBlank 所注解的元素值有内容
@Null 所注解的元素值为null
@Past 所注解的元素必须是某个过去的日期
@PastOrPresent 所注解的元素必须是过去某个或现在日期
@Pattern 所注解的元素必须满足给定的正则表达式
@Size 所注解的元素必须是String、集合或数组,且长度大小需保证在给定范围之内
@Email 所注解的元素需满足Email格式

自定义注解

仅仅使用内置的注解,无法满足复杂的业务需求,故扩展下面几个自定义注解。

UTF-8 字符串长度校验

对字符串长度的校验目的,一般是用于保证数据表字段可以容纳,当字符串内容是中文时,内置的 @Size 是不适用的,此时就需要自行扩展 UTF-8 字符串长度校验。
注解类:

@Target( {
        METHOD,
        FIELD,
        ANNOTATION_TYPE,
        CONSTRUCTOR,
        PARAMETER
})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {StringLengthValidator.class})
public @interface StringLength {
    int max() default 4000;
    int min() default 0;
    String message() default "字符串长度不符合要求。";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

注解类实现:

@Slf4j
public class StringLengthValidator implements ConstraintValidator<StringLength, String> {
    private int max;
    private int min;
    @Override
    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
        try {
            if(StringUtils.isBlank(value)){
                if(min > 0){
                    return false;
                }else {
                    return true;
                }
            }
            byte[] tmpbyte = value.getBytes("UTF-8");
            int length = tmpbyte.length;
            if(length < min || length > max){
                return false;
            }
            return true;
        }catch (Exception ex){
            log.error("注解校验StringLength发生异常。");
            log.error(ex.getMessage(),ex);
            return false;
        }
    }
    @Override
    public void initialize(StringLength constraintAnnotation) {
        max = constraintAnnotation.max();
        min = constraintAnnotation.min();
    }
}

手机号码校验

注解类:

@Target( {
        METHOD,
        FIELD,
        ANNOTATION_TYPE,
        CONSTRUCTOR,
        PARAMETER
})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {MobileValidator.class})
public @interface Mobile {
    String regexp() default "";
    String message() default "手机号码格式不正确";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

注解类实现:

public class MobileValidator implements ConstraintValidator<Mobile, String> {
    /**
     * 手机号的正则表达式.
     */
    private static Pattern pattern = Pattern.compile(
            "^0?(13[0-9]|14[0-9]|15[0-9]|16[0-9]|17[0-9]|18[0-9]|19[0-9])[0-9]{8}$");
    @Override
    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
        Matcher m = pattern.matcher(value);
        return m.matches();
    }
    @Override
    public void initialize(Mobile constraintAnnotation) {}
}

这里对手机号码的校验使用了正则表达式,也可以直接使用内置注解 @Pattern 定义校验规则。

枚举类整数值校验

有时需要校验参数值必须是系统定义的枚举值(整数值),此时需要扩展以下注解。
注解类:

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {EnumIntegerValidator.class})
public @interface EnumIntegerValid {
    String message() default "";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
    /**     * 目标枚举类     */
    Class<?> target() default Class.class;
    /**     * 是否忽略空值     */
    boolean ignoreEmpty() default true;
}

注解类实现:

@Slf4j
public class EnumIntegerValidator implements ConstraintValidator<EnumIntegerValid, Integer> {
    /** 枚举校验注解 */
    private EnumIntegerValid annotation;
    @Override
    public void initialize(EnumIntegerValid constraintAnnotation) {
        annotation = constraintAnnotation;
    }
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext constraintValidatorContext) {
        boolean result = false;
        Class<?> cls = annotation.target();
        boolean ignoreEmpty = annotation.ignoreEmpty();
        // target为枚举,并且value有值,或者不忽视空值,才进行校验
        if (cls.isEnum() && value != null) {
            Object[] objects = cls.getEnumConstants();
            try {
                Method method = cls.getMethod("getValue");
                for (Object obj : objects) {
                    Object code = method.invoke(obj);
                    if (value.compareTo((Integer) code) == 0) {
                        result = true;
                        break;
                    }
                }
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                log.warn("EnumValidator call isValid() method exception.");
                result = false;
            }
        } else {
            result = true;
        }
        return result;
    }
}

枚举类字符串校验

有时需要校验参数值必须是系统定义的枚举值(字符串),此时需要扩展以下注解。
注解类:

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {EnumValidator.class})
public @interface EnumValid {
    String message() default "";
       Class<?>[] groups() default {};
       Class<? extends Payload>[] payload() default {};
       /**     * 目标枚举类     */
       Class<?> target() default Class.class;
       /**     * 是否忽略空值     */
       boolean ignoreEmpty() default true;
}

注解类实现:

@Slf4j
public class EnumValidator implements ConstraintValidator<EnumValid, String> {
    /** 枚举校验注解 */
    private EnumValid annotation;
    @Override
    public void initialize(EnumValid constraintAnnotation) {
        annotation = constraintAnnotation;
    }
    @Override
    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
        boolean result = false;
        Class<?> cls = annotation.target();
        boolean ignoreEmpty = annotation.ignoreEmpty();
        // target为枚举,并且value有值,或者不忽视空值,才进行校验
        boolean fitCheck = cls.isEnum() && (isNotEmpty(value) || !ignoreEmpty);
        if (fitCheck) {
            Object[] objects = cls.getEnumConstants();
            try {
                Method method = cls.getMethod("getValue");
                for (Object obj : objects) {
                    Object code = method.invoke(obj);
                    if (value.equals(code.toString())) {
                        result = true;
                        break;
                    }
                }
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                log.warn("EnumValidator call isValid() method exception.");
                result = false;
            }
        } else {
            result = true;
        }
        return result;
    }
}

Bean 内多属性组合校验(组合校验)

此类校验一般属于业务逻辑校验,常常要求多个属性符合一定的逻辑设定。此时需要在Bean中编写校验方法,并在类定义前面添加自定义注解 @OverallValid 或者在方法前面加上自定义注解 @BooleanValid

方式1:

注解在类定义前面,类方法要求:

  1. 方法的可访问属性:public
  2. 方法的返回类型: boolean
    @OverallValid注解类:
@Target({METHOD, FIELD,TYPE})
@Retention(RUNTIME)
@Repeatable(OverallValids.class)
@Documented
@Constraint(validatedBy = {OverallValidImpl.class})
public @interface OverallValid {
    String value() default "overallValid";
    String message() default "组合校验未通过。";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

上面注解要求可重复使用,使用了 @Repeatable(OverallValids.class),OverallValids 代码如下:

@Target({METHOD, FIELD,TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OverallValids {
    OverallValid[] value();
}

使用注入的方法名,通过反射执行该方法,得到校验结果。注解实现如下:

@Slf4j
public class OverallValidImpl implements ConstraintValidator<OverallValid, Object> {
    private String functionName;
    @Override
    public void initialize(OverallValid overallValid) {
        functionName = overallValid.value();
    }
    @Override
    public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
        try {
            //得到方法对象
            Method checkMethod = o.getClass().getMethod(functionName);
            //调用方法,得到返回值
            Object checkRet = checkMethod.invoke(o);
            return Boolean.valueOf(checkRet.toString());
        }catch (Exception ex){
            log.error("综合校验异常。");
            log.error(ex.getMessage(),ex);
        }
        return false;
    }
}

方式2:

注解在方法前面,类方法要求:

  1. 方法的可访问属性:public
  2. 方法的返回类型: boolean
  3. 方法名称格式:get+首字母大写驼峰,如 getValid1

@BooleanValid注解类:

@Target( {
        METHOD,
        FIELD,
        ANNOTATION_TYPE,
        CONSTRUCTOR,
        PARAMETER
})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {BooleanValidImpl.class})
public @interface BooleanValid {
    boolean value() default true;
    String message() default "综合校验未通过。";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

类实现:

@Slf4j
public class BooleanValidImpl implements ConstraintValidator<BooleanValid, Boolean> {
    @Override
    public boolean isValid(Boolean value, ConstraintValidatorContext constraintValidatorContext) {
        return value;
    }
    @Override
    public void initialize(BooleanValid constraintAnnotation) {
    }
}

嵌套校验

在成员属性上加注解 @Valid ,意味着对该成员属性进行嵌套校验,校验规则按该成员的内部校验注解执行。文章来源地址https://www.toymoban.com/news/detail-435864.html

到了这里,关于boot-admin开源项目中有关后端参数校验的最佳实践的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • boot-admin整合flowable官方editor-app源码进行BPMN2-0建模(续)

    boot-admin整合flowable官方editor-app源码进行BPMN2-0建模(续) 书接上回 项目源码仓库github 项目源码仓库gitee boot-admin 是一款采用前后端分离模式、基于SpringCloud微服务架构的SaaS后台管理框架。系统内置基础管理、权限管理、运行管理、定义管理、代码生成器和办公管理6个功能模

    2023年04月21日
    浏览(47)
  • ELADMIN - 免费开源 admin 后台管理系统,基于 Spring Boot 和 Vue ,包含前端和后端源码

    一款简单好用、功能强大的 admin 管理系统,包含前端和后端源码,分享给大家。 ELADMIN 是一款基于 Spring Boot、Jpa 或 Mybatis-Plus、 Spring Security、Redis、Vue 的前后端分离的后台管理系统。 ELADMIN 的作者在 Github 和 Gitee 上看了很多的项目,发现大多数都是基于 Mybatis , 而基于 Sp

    2024年02月04日
    浏览(56)
  • Spring Boot中参数校验

    为了保证数据的正确性、完整性,前后端都需要进行数据检验。作为一名后端开发工程师,不能仅仅依靠前端来校验数据,我们还需要对接口请求的参数进行后端的校验。最常见的做法就是通过if/else语句来对请求的每一个参数一一校验,当很多参数需要校验的时候,if/else语

    2023年04月16日
    浏览(37)
  • Spring Boot 使用validation校验参数

    在看公司代码的时候,发现是用了 Spring Boot Validation 去检验参数的,但是后面又在代码里去检验参数去了,而且这个 Spring Boot Validation 校验好像并不生效。于是自己摸索研究了一下。 虽然项目使用的校验都是 javax.validation ,但是不引入这个依赖他是真的不生效。 gradle如下:

    2024年01月25日
    浏览(44)
  • spring-boot 请求参数校验:注解 @Validated 的使用、手动校验、自定义校验

    spring-boot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。 spring-boot已经引入了基础包,所以直接使用就可以。 在属性上添加校验注解: 在Controller上添加 @Validated 注解 校验未通过时,可能看到: 在 @Validated 后面紧跟着追加BindingResult,

    2023年04月16日
    浏览(106)
  • spring boot3参数校验基本用法

    ⛰️个人主页:      蒾酒 🔥系列专栏: 《spring boot实战》 🌊山高路远,行路漫漫,终有归途。 目录 前置条件 前言 导入依赖 使用介绍 配置检验规则 开启校验 使用注意 全局异常捕获返回友好提示信息 常用的校验规则注解 使用技巧 已经初始化好一个spring boot项目且版本为

    2024年02月21日
    浏览(45)
  • spring boot实现实体类参数自定义校验

    安装依赖项 1、新建实体类 2、新建验证类 3、在控制器中 3.1 首先写入方法 @InitBinder注解的作用是在控制器方法执行之前,先执行有 @InitBinder注解的方法,使用WebDataBinder 把新建的验证规则绑定 3.2 在控制器接口参数中

    2024年02月12日
    浏览(37)
  • Spring Boot使用 Hibernate-Validator校验参数时的长度校验

    今天在使用Validator框架数据验证的时候碰到了三个类似的注解,都是用来限制长度,但是用法上有区别:  @Size是一个Bean验证注释,用于验证关联的String具有的长度受最小值和最大值限制的值.  @Length是一个Hibernate特定的注释,与@Size具有相同的含义; 两者的区别: ​ 用@length限

    2024年02月14日
    浏览(41)
  • 参数校验: spring-boot-starter-validation

    2024年01月21日
    浏览(51)
  • 如何在Spring Boot中优雅地进行参数校验

    在平时的开发工作中,我们通常需要对接口进行参数格式验证。当参数个数较少(个数小于3)时,可以使用 if ... else ... 手动进行参数验证。当参数个数大于3个时,使用 if ... else ... 进行参数验证就会让代码显得臃肿,这个时候推荐使用注解来进行参数验证。 在Java中,注解

    2024年01月17日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包