当前位置:网站首页>多字段关联校验
多字段关联校验
2022-08-09 05:12:00 【愤怒的苹果ext】
背景
- 我们用了Spring框架后,在校验前端参数的时候,一般会使用
@NotNull
、@NotBlank
等注解,这样就不用写业务代码,判断这个字段了,省力,又简洁优雅,对真实业务处理的代码无侵入。 - 但是多个字段关联的业务就比较麻烦,比如下列场景。
public class TestValidate {
/**
* 姓名
**/
@NotBlank(message = "姓名必填")
private String name;
/*** 是否有房 0 - 没房子 1 - 有房子 */
@NotNull(message = "是否有房必填")
private Integer isHaveHours;
/***
* 房子面积
* */
private Integer hoursAreas;
......get/set方法 省略.....
- 如上代码所示,当选择没有房子的时候,
hoursAreas
房子面积有可能是不必校验的,isHaveHours
为1就需要校验不为空。每次都在业务代码上加if
或者断言
我觉得不舒服,所以想办法抽出来。 - 我在网上查到使用
DefaultGroupSequenceProvider
的办法,但好像又太依赖分组了,个人感觉不太合适。 - 我最终选择的是
AOP
的方式。
代码实现
- 我们可以提取共性,做成一个注解。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConditionalValidateField {
/***
* 关联字段
* */
String relationField();
/***
* 要执行的的校验动作
* */
int action();
/** 该字段的值为**/
String value();
/***
* 异常信息
* */
String message() default "";
}
- 再写一个需要拦截的标记注解(感觉这个可以扩展
分组
功能)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConditionalValidate {
}
- 然后AOP拦截这个注解,处理参数,校验参数。
- 解析参数使用的是
SPEL
@Aspect
@Component
public class ConditionalValidateAspect {
//将方法参数纳入Spring管理
private final LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
//解析spel表达式
private final ExpressionParser parser = new SpelExpressionParser();
@Before("@annotation(conditionalValidate)")
public void doBefore(JoinPoint joinPoint, ConditionalValidate conditionalValidate) throws Throwable {
//获取参数对象数组
Object[] args = joinPoint.getArgs();
Assert.notEmpty(args, "没有参数");
Assert.isTrue(args.length <= 1, "只能有一个参数");
//获取方法
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
//获取方法参数名
String[] params = discoverer.getParameterNames(method);
//将参数纳入Spring管理
EvaluationContext context = new StandardEvaluationContext();
for (int len = 0; len < params.length; len++) {
context.setVariable(params[len], args[len]);
}
Object firstParams = args[0];
if (!StringUtils.isEmpty(firstParams)) {
List<Field> allFields = getAllFields(firstParams);
// 把要校验的找到
List<ConditionalValidateFieldInfo> validateFieldList = new ArrayList<>();
// 字段类型
Map<String, Class> fieldClzMap = new HashMap<>();
allFields.forEach(field -> {
ConditionalValidateField conditionalValidateField = AnnotationUtils.findAnnotation(field, ConditionalValidateField.class);
String fieldName = field.getName();
if (!StringUtils.isEmpty(conditionalValidateField)) {
validateFieldList.add(new ConditionalValidateFieldInfo(fieldName, conditionalValidateField));
}
fieldClzMap.put(fieldName, field.getType());
});
// 执行校验动作,这块要分很多种情况处理
validateFieldList.forEach(conditionalValidateFieldInfo -> {
if (!StringUtils.isEmpty(conditionalValidateFieldInfo)) {
ConditionalValidateField conditionalValidateField = conditionalValidateFieldInfo.getConditionalValidateField();
//TODO 这个地方可以使用策略模式优化下,共性的地方用模板方法
// 如果是相等 执行校验
if (ValidateFieldAction.IF_EQ_NOT_NULL == conditionalValidateField.action()) {
// 判断该字段类型
Class originalClz = fieldClzMap.get(conditionalValidateFieldInfo.getFieldName());
//TODO 只写了Integer类型的
if (Integer.class.getSimpleName().equals(originalClz.getSimpleName())) {
Expression expression = parser.parseExpression("#" + params[0] + "." + conditionalValidateFieldInfo.getFieldName());
Integer originalValue = expression.getValue(context, Integer.class);
if (!StringUtils.isEmpty(conditionalValidateField.value())) {
// 如果是相等的
if (Integer.valueOf(conditionalValidateField.value()).equals(originalValue)) {
Expression relationExpression = parser.parseExpression("#" + params[0] + "." + conditionalValidateField.relationField());
String relationField = conditionalValidateField.relationField();
Object value = relationExpression.getValue(context, fieldClzMap.get(relationField));
Assert.isTrue(!StringUtils.isEmpty(value), conditionalValidateField.message());
}
} else {
// 为空的情况,有可能要求原字段为空,关联字段不能为空的情况;判断都是空就校验
if (StringUtils.isEmpty(conditionalValidateField.value()) && StringUtils.isEmpty(originalValue)) {
Expression relationExpression = parser.parseExpression("#" + params[0] + "." + conditionalValidateField.relationField());
String relationField = conditionalValidateField.relationField();
Object value = relationExpression.getValue(context, fieldClzMap.get(relationField));
Assert.isTrue(!StringUtils.isEmpty(value), conditionalValidateField.message());
}
}
}
}
}
});
}
}
public static List<Field> getAllFields(Object object) {
Class clazz = object.getClass();
List<Field> fieldList = new ArrayList<>();
while (clazz != null) {
fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
clazz = clazz.getSuperclass();
}
return fieldList;
}
/***
* 封装字段信息
* */
public class ConditionalValidateFieldInfo {
private String fieldName;
private ConditionalValidateField conditionalValidateField;
public ConditionalValidateFieldInfo(String fieldName, ConditionalValidateField conditionalValidateField) {
this.fieldName = fieldName;
this.conditionalValidateField = conditionalValidateField;
}
public String getFieldName() {
return fieldName;
}
public void setFieldName(String fieldName) {
this.fieldName = fieldName;
}
public ConditionalValidateField getConditionalValidateField() {
return conditionalValidateField;
}
public void setConditionalValidateField(ConditionalValidateField conditionalValidateField) {
this.conditionalValidateField = conditionalValidateField;
}
}
}
- 参数和Controller调用层。
public class TestValidate {
/**
* 姓名
**/
@NotBlank(message = "姓名必填")
private String name;
/*** 是否有房 0 - 没房子 1 - 有房子 */
@NotNull(message = "是否有房必填")
@ConditionalValidateField(relationField = "hoursAreas", value = "1",
action = ValidateFieldAction.IF_EQ_NOT_NULL,
message = "有房子,房子面积必填")
private Integer isHaveHours;
/***
* 房子面积
* */
private Integer hoursAreas;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getIsHaveHours() {
return isHaveHours;
}
public void setIsHaveHours(Integer isHaveHours) {
this.isHaveHours = isHaveHours;
}
public Integer getHoursAreas() {
return hoursAreas;
}
public void setHoursAreas(Integer hoursAreas) {
this.hoursAreas = hoursAreas;
}
}
//ValidateController
@RequestMapping("/test")
@ConditionalValidate
public String test(@Validated TestValidate testValidate) {
return "success";
}
最终效果
isHaveHours
为1时才校验。如下图所示。
结语和代码地址
- 大体思路就是这样,代码还很粗糙,有的地方需要设计模式优化下。代码地址在https://gitee.com/apple_1030907690/spring-boot-kubernetes/tree/v1.0.2/,我会持续优化下,如果对您有帮助,希望您给个三连,如果没有,点个赞也行。
更新
2022年3月16日21:34:10 一个字段支持重复注解(@Repeatable)
- 一个字段多个条件:比如
isHaveHours
为1,hoursAreas
不能为空,为0,time
参数不能为空。 - 地址 :https://gitee.com/apple_1030907690/spring-boot-kubernetes/tree/v1.0.4
2022年4月14日 切换代码地址,发布到GitHub仓库
源码地址:https://github.com/1030907690/spring-boot-kubernetes/tree/v1.0.1
Spring Boot直接引入
<repositories>
<repository>
<id>maven-repository-main</id>
<url>https://raw.github.com/1030907690/maven-repository/main/</url>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</snapshots>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.conditional.validate</groupId>
<artifactId>conditional-validate-starter</artifactId>
<version>${version}</version>
</dependency>
</dependencies>
在settings.xml配置过
<mirror>
要注意下,会有问题(这个相当于作用域表示全部),比如
<mirror>
<id>aliyunmaven</id>
<mirrorOf>public</mirrorOf>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
应该设置为<mirrorOf>public</mirrorOf>
,否则拉取GitHub
的包会失败,具体看https://developer.aliyun.com/mvn/guide
边栏推荐
- Still don't know what business intelligence (BI) is?After reading this article, you will understand
- 进程和计划任务管理
- 面向6G的欠采样相移键控可见光调制方案
- The development trend of software testing
- php write online remote file to temp file
- 剑指Offer-二叉树路径问题总结
- 算法---优美的排列(Kotlin)
- Nacos源码安装
- Pycharm Debug调试使用+代码调试理解
- 【Harmony OS】【ArkUI】ets开发 图形与动画绘制
猜你喜欢
C Advanced - Program Compilation (Preprocessing) + Linking
C进阶 - 程序的编译(预处理操作) + 链接
Pycharm Debug调试使用+代码调试理解
还不了解什么是商业智能(BI)?看完这篇文章就懂了
机器人大赛总结
【Harmony OS】【ArkUI】ets开发 基础页面布局与数据连接
华为鲲鹏生态培训试题
10.LoadRunner2022社区版安装
JDBC_PreparedStatement预编译对象
could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarExcept
随机推荐
保存Simulink仿真模型为图片或者PDF的方法
关于光电门的使用
[UNR #6 A] Noodle-based road (shortest path)
通讯录(文件版)(C语言)(VS)
数据库设计---三范式和反范式设计
STM32Cube学习笔记(delay)
p67 mixin 混入/混合:两个组件共享一个配置
【Harmony OS】【ARK UI】Custom popup
数字化时代,企业为什么需要商业智能BI
【基于富瀚6630使用/dev/fb0显示设备和TDE模块渲染bmp图像】
【luogu U142356】勇者的后缀(SA)(主席树)(二分)
Timing Constraint Basics
aur安装报错一个或多个文件没有通过有效性检查!
【Harmony OS】【ARK UI】轻量级数据存储
电气规则
二分搜索篇
软件测试的发展趋势
如何选型APS系统,还需明确这七大关键因素
滑动窗口篇
华为鲲鹏生态培训试题