前后端双端数据校验(vue-Element、Bootstrap、JSR303校验规范)

参数校验

在我们开发中,参数校验时必不可少的一环,一般来说,数据都需要进行双端(前+后)校验

一、前端校验(不同的前端框架得到校验写法都不同):

1、vue-elementui校验方式(官方文档非常详细)

1、在对应的el-form进行绑定 :rules="dataRule"     
2、如下写法:
dataRule: {
        参数名: [{ required: true, message: "xxx不能为空", trigger: "blur" }],
        参数名: [
          {
            validator: (rule, value, callback) => {
              if (value === "") {
                callback(new Error("请输入xxx"));
              } else if (!/^[a-zA-Z]$/.test(value)) {
                callback(new Error("请输入合法的参数"));
              } else {
                callback();
              }
            },
            trigger: "blur"
          }
        ],
        参数名: [
          {
            validator: (rule, value, callback) => {
              if (value === "") {
                callback(new Error("请输入xxx"));
              } else if (!Number.isInteger(value)) {
                callback(new Error("请输入数字值"));
              } else {
                if (value < 0) {
                  callback(new Error("必须>=0"));
                } else {
                  callback();
                }
              }
            },
            trigger: "blur"
          }
        ]
      }

2、bootstarp校验(官方文档写的非常详细)

        //表单验证参数
        var validparam = {
            message: 'This value is not valid',
            feedbackIcons: {
                valid: 'glyphicon glyphicon-ok',
                invalid: 'glyphicon glyphicon-remove',
                validating: 'glyphicon glyphicon-refresh'
            },
            excluded: [':disabled'],
            fields: {
                参数名: {
                    message: '名称验证失败',
                    validators: {
                        notEmpty: {
                            message: '名称不能为空'
                        },
                        stringLength: {/*长度提示*/
                            min: 2,
                            max: 60,
                            message: '名称长度必须在2到60之间'
                        }
                    }
                },
                ......
     
            }
        };

二、后端校验:遵循JSR303校验来处理

1、常用注解(源码位置)

基本解释:图来自https://blog.csdn.net/weixin_44440642/article/details/106335653

2、基本简单用法可参考 https://blog.csdn.net/weixin_44440642/article/details/106335653

3、项目中一般咋用?

3.1、添加对应注解至modal

3.2、在所需要校验参数的方法加上 @Valid注解,该注解是由javax.validation 提供的

// 添加的方法 
@RequestMapping(value = "/insert")
    public Object insert(@Valid @RequestBody PmsBrand data) {
        RiestRetData riestRetData = new RiestRetData();
        brandService.insert(data);
        riestRetData.SetRetCodeMsg(0, "success");
        return riestRetData;
    }
// 修改的方法
    @RequestMapping(value = "/update")
    public Object update(@Valid @RequestBody PmsBrand data) {
        RiestRetData riestRetData = new RiestRetData();
        brandService.update(data);
        riestRetData.SetRetCodeMsg(0, "success");
        return riestRetData;
    }

3.3、自定义校验异常拦截方法

package com.riest.product.exception;

import exception.validtype.RiestCodeTypes;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import utils.RiestRetData;

import java.util.HashMap;
import java.util.Map;

/**
 * ClassName:PropertyValidException
 * Describe:controller参数校验异常拦截
 * Author:DGJ
 * Data:2021/1/1 18:19
 *
 * RiestRetData:统一返回对象,没什么好说的,自己随便定义
 * RiestCodeTypes:系统返回code,一般都是用枚举类型,很简单,自己随便定义
 */
@Slf4j
@RestControllerAdvice(basePackages = "com.riest.product.controller")
public class PropertyValidException {

    /**
     * 参数校验异常会调用该handler处理
     * @param e MethodArgumentNotValidException 
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    public RiestRetData handleVaildException(MethodArgumentNotValidException e) {
        log.error("数据校验出问题{}", "异常类型", e.getMessage(), e.getClass());
        RiestRetData retData = new RiestRetData();
        BindingResult bindingResult = e.getBindingResult();
        Map<String, String> map = new HashMap<>();
        bindingResult.getFieldErrors().forEach((fieldError) -> {
            map.put(fieldError.getField(), fieldError.getDefaultMessage());
        });
        retData.put("data", map);
        retData.SetRetCodeMsg(RiestCodeTypes.ATTRIBUTE_CHECK_ERROR_CODE.getCode(), 			RiestCodeTypes.ATTRIBUTE_CHECK_ERROR_CODE.getMessage());
        return retData;
    }

}

3.4、测试

http://localhost:81/api/product/brand/insert

可以看到:返回结果和我们预想的一致

4、分组校验

需求场景:同一个参数的校验场景不同,比如,insert时需要校验name != null,不能有id ;而修改时我们不需要update name,而id != null

针对上述场景:显然之前的方法不太能满足要求,这时候我们就需要分组校验了

我们以insert 和 update 为例

4.1、需添加两个空接口

package valid;

/**
 * ClassName:insertGroup
 * Describe:
 * Author:DGJ
 * Data:2021/1/4 18:55
 */
public interface insertGroup {}
package valid;

/**
 * ClassName:updateGroup
 * Describe:
 * Author:DGJ
 * Data:2021/1/4 18:55
 */
public interface updateGroup {}

4.2、修改controller方法(指定该方法参数校验走哪个分组:@Validated(insertGroup.class))

@Validated :import org.springframework.validation.annotation.Validated;

    @RequestMapping(value = "/insertexample")
    public Object insertExample(@Validated(insertGroup.class) @RequestBody PmsBrand data) {
        RiestRetData riestRetData = new RiestRetData();
        brandService.insert(data);
        riestRetData.SetRetCodeMsg(0, "success");
        return riestRetData;
    }

    @RequestMapping(value = "/updateexample")
    public Object updateExample(@Validated(updateGroup.class) @RequestBody PmsBrand data) {
        RiestRetData riestRetData = new RiestRetData();
        brandService.update(data);
        riestRetData.SetRetCodeMsg(0, "success");
        return riestRetData;
    }

4.3、修改modal

详情请看注释

public class PmsBrand {

    @NotNull(message = "update需指定id",groups = {updateGroup.class})
    @Null(message = "insert不需要指定id",groups = {insertGroup.class})
    private Long brandId;

    //insert和update都必须指定name
    @NotBlank(message = "name 不能为空",groups = {insertGroup.class,updateGroup.class})
    private String name;


    // 添加时必须指定logo,且必须是一个合法的URL
    // 修改时URL可以为空,但是如果有,必须是一个合法的URL
    @NotBlank(message = "logo 地址不能为空",groups = {insertGroup.class})
    @URL(message = "logo 必须是一个URL", groups = {insertGroup.class,updateGroup.class})
    private String logo;

    // 添加时必须指定firstLetter,且必须是一个符合正则的要求的字符串
    // 修改时firstLetter可以为空,但是如果有,必须是符合正则的要求的字符串
    @NotNull(message = "insert需指定firstLetter",groups = {insertGroup.class})
    @Pattern(regexp="^[a-zA-Z]$",message = "检索字段只能是a-z或者A-Z中的一个字母",groups = {insertGroup.class,updateGroup.class})
    private String firstLetter;


    @NotNull(groups = {insertGroup.class})
    @Min(value = 0,message = "最小值为0")
    private Integer sort;
    
    ......
}

4.4、测试

如图:测试insert

http://localhost:81/api/product/brand/insertexample

如图:测试update

http://localhost:81/api/product/brand/updateexample

5、自定义注解实现校验

需求场景:我们开发中经常会有一个字段来做数据字典,该字段的值是固定的,比如:1:成功2:失败3:未知,这时候,已有的注解不能满足需求,这时候就需要自定义注解

5.1、首先我们看看原有的注解咋定义的?

5.2、自定义注解

package valid;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.List;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * ClassName:ListValue
 * Describe: int[] 要和我们属性值的类型对应
 * Author:DGJ
 * Data:2021/1/4 20:09
 */
@Documented
@Constraint(validatedBy = {ListValueConstraintValidator.class})
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
public @interface ListValue {
    String message() default "{valid.ListValue.message}";

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

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

    int[] fixVals() default { };
}

5.3、自定义注解解析器

package valid;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;

/**
 * ClassName:ListValueConstraintValidator
 * Describe:写的不严谨
 * Author:DGJ
 * Data:2021/1/4 20:11
 */
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {

    //存要校验的数据
    private Set<Integer> set  = new HashSet<Integer>();


    //拿到要校验的数据
    @Override
    public void initialize(ListValue constraintAnnotation) {
        int[] ints = constraintAnnotation.fixVals();
        for (int anInt : ints) {
            set.add(anInt);
        }
    }

    // 判断是否符合规则
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        return set.contains(value);
    }
}

5.3、修改modal

    @NotNull(message = "showStatus不能为空",groups = {insertGroup.class})
    @ListValue(fixVals = {1,2,3},groups = {updateGroup.class,insertGroup.class})
    private Integer showStatus;

5.4、resource目录下加

ValidationMessages.properties
# 必须提交指定的值
valid.ListValue.message=\u5FC5\u987B\u63D0\u4EA4\u6307\u5B9A\u7684\u503C

5.5、测试

update

http://localhost:81/api/product/brand/updateexample

insert

http://localhost:81/api/product/brand/insertexample

6、我们项目中怎么用?

因为我们做的都是多租户系统,我们公司自己改的 mbggenerated,生成的sql会将不同租户的数据隔离 ,modal校验注解都会在生成时添加,且会生成符合我们业务的controller、service、dao

posted @ 2021-01-08 21:24  一个努力的人QAQ  阅读(422)  评论(0编辑  收藏  举报