前后端双端数据校验(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