前言
前后端分离开发,接口开发等,数据准确性校验是必不可少步骤。常规的处理方案是在数据发送前、接收后增加验证逻辑,针对参数各属性一一验证是否规范,并对错误的信息进行相应的逻辑处理。这样我们需要写很多很多的针对性强的校验逻辑。在这些校验逻辑中存在很多通用的校验例如:非空判断、值是否在枚举/数据字典范围内、数据是否满足某正则表达式规则等。
思考
如何能减少验证代码,验证代码如何统一、通用
整理后的思路
- 实体属性增加验证注解
- 注解增加各类验证参数
- 反射获取bean对象及各属性
- 获取属性上的注解及参数
- 执行注解参数对应的验证逻辑
- 返回验证结果
干货
注解类
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ValidateField {
/**
* 展示/异常提醒文本
*
* @return
*/
String fieldTxt() default "";
/**
* 是否非空
*
* @return
*/
boolean notEmpty() default false;
/**
* 正则验证
*
* @return
*/
String regex() default "";
/**
* 验证枚举
*
* @return
*/
Class<? extends Enum<?>> enumClass() default DefaultEnum.class;
/**
* 自定义业务验证逻辑
* 必须继承{@link ValidateService}接口
*
* @return
*/
Class<? extends ValidateService> validateClass() default ValidateService.class;
}
枚举默认类
public enum DefaultEnum {
;
private String code;
private String name;
DefaultEnum(String code, String name) {
this.code = code;
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
业务验证基类
public interface ValidateService {
/**
* 执行验证
* @param value
* @return
*/
boolean validate(Object value);
}
验证类
此类为验证主类。
public class ModelValidate {
private static ApplicationContext applicationContext;
/**
* bean验证
* @param model
* @return
*/
public static String validate(Object model) {
StringBuilder rtn = new StringBuilder();
try {
Field[] fields = model.getClass().getDeclaredFields();
for (Field field : fields) {
ValidateField validateField = field.getAnnotation(ValidateField.class);
if (validateField != null) {
//设置可访问,不然拿不到private
field.setAccessible(true);
Object value = field.get(model);
validateEmpty(rtn, field, validateField, value);
if (value != null) {
//正则表达式验证
validateRegex(rtn, field, validateField, value);
//枚举验证
validateEnum(rtn, field, validateField, value);
//业务逻辑验证
validateByClass(rtn, field, validateField, value);
}
}
}
} catch (Exception e) {
rtn.append("推送的类型不存在!");
}
return rtn.toString();
}
/**
* 非空验证
*
* @param rtn
* @param field
* @param validateField
* @param value
*/
private static void validateEmpty(StringBuilder rtn, Field field, ValidateField validateField, Object value) {
if (validateField.notEmpty()) {
if (value == null) {
rtn.append("字段【" + validateField.fieldTxt() + "/" + field.getName() + "】必填;");
}
}
}
/**
* 正则表达式验证
*
* @param rtn
* @param field
* @param validateField
* @param value
*/
private static void validateRegex(StringBuilder rtn, Field field, ValidateField validateField, Object value) {
//验证正则表达式
if (validateField.regex() != null && validateField.regex().length() > 0) {
if (!Pattern.matches(validateField.regex(), value.toString())) {
rtn.append("字段【" + validateField.fieldTxt() + "/" + field.getName() + "】不满足正则验证规则;");
}
}
}
/**
* 枚举验证
*
* @param rtn
* @param field
* @param validateField
* @param value
*/
private static void validateEnum(StringBuilder rtn, Field field, ValidateField validateField, Object value) {
if (validateField.enumClass() != null && !validateField.enumClass().getSimpleName().equals("DefaultEnum")) {
//此处为getCode可根据业务情况调整
Object enumObject = EnumUtils.getByMethodAndParam(validateField.enumClass(), "getCode", value);
if (enumObject == null) {
rtn.append("字段【" + validateField.fieldTxt() + "/" + field.getName() + "】不是定义枚举中的值。现在值:" + value.toString() + ",应值见:" + validateField.enumClass().getName() + ";");
}
}
}
/**
* 类业务逻辑验证
* @param rtn
* @param field
* @param validateField
* @param value
* @throws NoSuchMethodException
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
private static void validateByClass(StringBuilder rtn, Field field, ValidateField validateField, Object value){
try {
if (validateField.validateClass() != null && !validateField.validateClass().getSimpleName().equals("ValidateService")) {
String methodName = "validate";
//上线用
//找到Spring容器中类对应的bean
Object object = applicationContext.getBean(validateField.validateClass());
//执行validate方法
Method method =validateField.validateClass().getMethod(methodName,new Class[]{Object.class});
boolean validateResult = (boolean) method.invoke(object,value);
// 测试用
// Object object=validateField.validateClass().newInstance();
// Method method =validateField.validateClass().getMethod(methodName,new Class[]{Object.class});
//执行validate方法
// boolean validateResult = (boolean) method.invoke(object,value);
if (!validateResult) {
rtn.append("字段【" + validateField.fieldTxt() + "/" + field.getName() + "】不符合规则;");
}
}
}catch (Exception e){
rtn.append("字段【" + validateField.fieldTxt() + "/" + field.getName() + "】不符合规则;");
e.printStackTrace();
}
}
测试
验证实体
public class TestValidateModel implements Serializable {
/**
* 测试必填
*/
@ValidateField(notEmpty = true, fieldTxt = "非空")
private String emptyTrue;
@ValidateField(notEmpty = true, fieldTxt = "非空")
private String emptyFalse;
/**
* 测试正则(手机号)
*/
@ValidateField(notEmpty = true, regex = "^((17[0-9])|(14[0-9])|(13[0-9])|(15[^4,\\D])|(18[0,5-9]))\\d{8}$", fieldTxt = "正则手机号")
private String regexTrue;
@ValidateField(notEmpty = true, regex = "^((17[0-9])|(14[0-9])|(13[0-9])|(15[^4,\\D])|(18[0,5-9]))\\d{8}$", fieldTxt = "正则手机号")
private String regexFalse;
/**
* 测试枚举
*/
@ValidateField(notEmpty = true, enumClass = TestValidateEnum.class, fieldTxt = "枚举")
private String enumTrue;
@ValidateField(notEmpty = true, enumClass = TestValidateEnum.class, fieldTxt = "枚举")
private String enumFalse;
/**
* 测试枚举
*/
@ValidateField(notEmpty = true, validateClass = TestValidateService.class, fieldTxt = "自定义验证类")
private String classTrue;
@ValidateField(notEmpty = true, validateClass = TestValidateService.class, fieldTxt = "自定义验证类")
private String classFalse;
public String getEmptyTrue() {
return emptyTrue;
}
public void setEmptyTrue(String emptyTrue) {
this.emptyTrue = emptyTrue;
}
public String getEmptyFalse() {
return emptyFalse;
}
public void setEmptyFalse(String emptyFalse) {
this.emptyFalse = emptyFalse;
}
public String getRegexTrue() {
return regexTrue;
}
public void setRegexTrue(String regexTrue) {
this.regexTrue = regexTrue;
}
public String getRegexFalse() {
return regexFalse;
}
public void setRegexFalse(String regexFalse) {
this.regexFalse = regexFalse;
}
public String getEnumTrue() {
return enumTrue;
}
public void setEnumTrue(String enumTrue) {
this.enumTrue = enumTrue;
}
public String getEnumFalse() {
return enumFalse;
}
public void setEnumFalse(String enumFalse) {
this.enumFalse = enumFalse;
}
public String getClassTrue() {
return classTrue;
}
public void setClassTrue(String classTrue) {
this.classTrue = classTrue;
}
public String getClassFalse() {
return classFalse;
}
public void setClassFalse(String classFalse) {
this.classFalse = classFalse;
}
}
测试枚举
public enum TestValidateEnum {
A("a", "A");
private String code;
private String name;
TestValidateEnum(String code, String name) {
this.code = code;
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
测试验证业务逻辑
public class TestValidateService implements ValidateService {
@Override
public boolean validate(Object value) {
if(value.toString().equals("1")){
return true;
}
return false;
}
}
测试类
public class TestModelValidate {
public static void main(String[] args) {
TestValidateModel model = new TestValidateModel();
model.setEmptyTrue("AAA");
model.setRegexTrue("18888888888");
model.setRegexFalse("88888888888");
model.setEnumTrue("a");
model.setEnumFalse("b");
model.setClassTrue("1");
model.setClassFalse("0");
String rtn = ModelValidate.validate(model);
System.out.println(rtn);
}
}
测试结果
Connected to the target VM, address: ‘127.0.0.1:0’, transport: ‘socket’
字段【非空/emptyFalse】必填;字段【正则手机号/regexFalse】不满足正则验证规则;字段【枚举/enumFalse】不是定义枚举中的值。现在值:b,应值见:cn.pminfo.standard.push.common.test.TestValidateEnum;字段【自定义验证类/classFalse】不符合规则;
Disconnected from the target VM, address: ‘127.0.0.1:0’, transport: ‘socket’
总结
此通用类使用后,使用注解的方式在实体属性上添加验证规则,即可实现数据准确校验。此时有一个考虑,我们是否可以扩展,增加拦截器,拦截系统情况,根据请求的类型判断是否调用该验证方法。所有接口入口即可不用关心参数是否准确。
思路决定出路。