
12-流程条件控制
目标:前端任意增加对字段的匹配规则,后端得到正确的校验结果。适用于简单匹配和复杂匹配。
简单匹配:没有嵌套,多个条件只是&&的关系。eg:color = '红色' && 重量 > 10kg && producer(产地) = '中国'
复杂匹配:包括各种嵌套。
- 根据level等级来决定执行的顺序,先执行最里面的,后执行最外面的
- 同一level下的,最后一个rule的relation_outer可为空
eg0: color = '红色' && 重量 > 10kg && 产地 = '中国'
id | rule_type | level | attr_code | relation_outer | relation_inner | attr_val | sort | pId |
---|---|---|---|---|---|---|---|---|
1 | normal | 1 | color | and | = | 红色 | 1 | 0 |
2 | normal | 1 | weight | and | > | 10 | 2 | 0 |
3 | normal | 1 | producer | = | 中国 | 3 | 0 |
eg1: 产地 = '中国' && ( color = '红色' || weight > 10kg )
id | rule_type | level | attr_code | relation_outer | relation_inner | attr_val | sort | pId |
---|---|---|---|---|---|---|---|---|
1 | normal | 1 | producer | and | = | 中国 | 1 | 0 |
2 | virtually | 1 | 2 | 0 | ||||
3 | normal | 2 | color | or | = | 红色 | 1 | 2 |
4 | normal | 2 | weight | > | 10 | 2 | 2 |
eg2: (( color = '蓝色' && weight < 10kg ) || ( color = '红色' && weight > 10kg )) && 产地 = '中国'
id | rule_type | level | attr_code | relation_outer | relation_inner | attr_val | sort | pId |
---|---|---|---|---|---|---|---|---|
1 | virtually | 1 | and | 1 | 0 | |||
2 | normal | 1 | producer | = | 中国 | 2 | 0 | |
3 | virtually | 2 | or | 1 | 1 | |||
4 | virtually | 2 | 2 | 1 | ||||
5 | normal | 3 | color | and | = | 蓝色 | 1 | 3 |
6 | normal | 3 | weight | < | 10 | 2 | 3 | |
7 | normal | 3 | color | and | = | 红色 | 1 | 4 |
8 | normal | 3 | weight | > | 10 | 2 | 4 |
测试
java
package com.example.bootdemo.flow;
import com.example.bootdemo.flow.constant.RuleOperatorConstant;
import com.example.bootdemo.flow.dto.RuleDO;
import com.example.bootdemo.flow.handle.RuleEqualsOperatorHandle;
import com.example.bootdemo.flow.handle.RuleLessThanEqualsOperatorHandle;
import com.example.bootdemo.flow.handle.RuleLessThanOperatorHandle;
import com.example.bootdemo.flow.handle.RuleMoreThanEqualsOperatorHandle;
import com.example.bootdemo.flow.handle.RuleMoreThanOperatorHandle;
import com.example.bootdemo.flow.handle.RuleNotEqualsOperatorHandle;
import com.example.bootdemo.flow.handle.RuleOperatorHandle;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
public class RuleTest {
public static Map<String, RuleOperatorHandle> ruleOperatorHandleList = getRuleOperatorHandleList();
@Test
public void test1() {
List<RuleDO> ruleDOList = new ArrayList<RuleDO>(){{
add(new RuleDO(1L,0L,"normal",1, "color","红色","and","=",1,"string"));
add(new RuleDO(2L,0L,"normal",1, "weight","10","and",">",2,"int"));
add(new RuleDO(3L,0L,"normal",1, "weight","100","and","<",2,"int"));
// add(new RuleDO("size","10",null,"<=",3,"double"));
}};
Map<String, Object> valMap = new HashMap<>();
valMap.put("color","红色");
valMap.put("weight",18);
valMap.put("size",12.4);
Boolean pass = isPass(ruleDOList,valMap);
System.out.println(pass);
}
@Test
public void test2() {
List<RuleDO> ruleDOList = new ArrayList<RuleDO>();
RuleDO ruleDO1 = new RuleDO(1L,0L,"virtually",1, null,null,"and",null,1,null);
RuleDO ruleDO2 = new RuleDO(2L,0L,"normal", 1,"producer","中国",null,"=",2,"string");
RuleDO ruleDO1_1 = new RuleDO(3L,1L,"virtually", 2,null,null,"or",null,1,null);
RuleDO ruleDO1_2 = new RuleDO(4L,1L,"virtually", 2,null,null,null,null,2,null);
RuleDO ruleDO1_1_1 = new RuleDO(5L,3L,"normal", 3,"color","蓝色","and","=",1,"string");
RuleDO ruleDO1_1_2 = new RuleDO(6L,3L,"normal", 3,"weight","10",null,"<",2,"int");
RuleDO ruleDO1_2_1 = new RuleDO(7L,4L,"normal", 3,"color","红色","and","=",1,"string");
RuleDO ruleDO1_2_2 = new RuleDO(8L,4L,"normal", 3,"weight","10",null,">",2,"int");
ruleDOList.add(ruleDO1);
ruleDOList.add(ruleDO2);
ruleDOList.add(ruleDO1_1);
ruleDOList.add(ruleDO1_2);
ruleDOList.add(ruleDO1_1_1);
ruleDOList.add(ruleDO1_1_2);
ruleDOList.add(ruleDO1_2_1);
ruleDOList.add(ruleDO1_2_2);
List<RuleDO> ruleDOS = convertTreeNode(ruleDOList,0L);
System.out.println(ruleDOS);
Map<String, Object> valMap = new HashMap<>();
valMap.put("color","红色");
valMap.put("weight",18);
valMap.put("producer","中国");
Boolean pass2 = isPass2(ruleDOS,valMap);
System.out.println(pass2);
}
public static Boolean isPass2(List<RuleDO> ruleDOList, Map<String,Object> valMap){
Boolean resultByLevel = null;
for (int i = 0; i < ruleDOList.size(); i++) {
Boolean exec = null;
String relationOuter = null;
if (i > 0){
relationOuter = ruleDOList.get(i - 1).getRelationOuter();
}
RuleDO ruleDO = ruleDOList.get(i);
List<RuleDO> childRuleDOList = ruleDO.getChildRuleDOList();
if (CollectionUtils.isNotEmpty(childRuleDOList)){
exec = isPass2(childRuleDOList,valMap);
}else{
String attrCode = ruleDO.getAttrCode();
String relationInner = ruleDO.getRelationInner();
//规则的val
String rule_val_original = ruleDO.getAttrVal();
//规则的val 类型
String attrValType = ruleDO.getAttrValType();
// 商品的val
Object product_val_original = valMap.get(attrCode);
exec = ruleOperatorHandleList.get(relationInner).exec(product_val_original, rule_val_original, attrValType);
}
if (resultByLevel == null){
resultByLevel = exec;
}else{
resultByLevel = relationExec(exec,resultByLevel,relationOuter);
}
}
return resultByLevel;
}
private static List<RuleDO> convertTreeNode(List<RuleDO> list, Long parentId){
List<RuleDO> children = list.stream().filter(x -> x.getParentRuleId().equals(parentId)).collect(Collectors.toList());
List<RuleDO> successor = list.stream().filter(x -> !x.getParentRuleId().equals(parentId)).collect(Collectors.toList());
//排序
children = children.stream().sorted(Comparator.comparingInt(RuleDO::getSort)).collect(Collectors.toList());
children.forEach(x ->
{
convertTreeNode(successor, x.getId()).forEach(
y -> x.getChildRuleDOList().add(y)
);
}
);
return children;
}
public static Boolean isPass(List<RuleDO> ruleDOList, Map<String,Object> valMap){
if (CollectionUtils.isEmpty(ruleDOList)){
return true;
}
//排序
ruleDOList = ruleDOList.stream().sorted(Comparator.comparingInt(RuleDO::getSort)).collect(Collectors.toList());
Boolean resultByLevel = null;
for (int i = 0; i < ruleDOList.size(); i++) {
RuleDO ruleDO = ruleDOList.get(i);
String attrCode = ruleDO.getAttrCode();
String relationOuter = null;
if (i > 0){
relationOuter = ruleDOList.get(i - 1).getRelationOuter();
}
String relationInner = ruleDO.getRelationInner();
//规则的val
String rule_val_original = ruleDO.getAttrVal();
//规则的val 类型
String attrValType = ruleDO.getAttrValType();
// 商品的val
Object product_val_original = valMap.get(attrCode);
Boolean exec = ruleOperatorHandleList.get(relationInner).exec(product_val_original, rule_val_original, attrValType);
if (resultByLevel == null){
resultByLevel = exec;
}else{
resultByLevel = relationExec(exec,resultByLevel,relationOuter);
}
}
return resultByLevel;
}
public static Boolean relationExec(Boolean r1,Boolean r2,String relationType){
if (RuleOperatorConstant.and.equalsIgnoreCase(relationType)){
return r1 && r2;
}else if (RuleOperatorConstant.or.equalsIgnoreCase(relationType)){
return r1 || r2;
}
return true;
}
public static Map<String, RuleOperatorHandle> getRuleOperatorHandleList(){
Map<String, RuleOperatorHandle> map = new HashMap<>();
map.put("=",new RuleEqualsOperatorHandle());
map.put(">",new RuleMoreThanOperatorHandle());
map.put(">=",new RuleMoreThanEqualsOperatorHandle());
map.put("<",new RuleLessThanOperatorHandle());
map.put("<=",new RuleLessThanEqualsOperatorHandle());
map.put("!=",new RuleNotEqualsOperatorHandle());
return map;
}
}
实体
java
package com.example.bootdemo.flow.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
@Data
@NoArgsConstructor
public class RuleDO {
/**
* 主键
*/
private Long id;
/**
* 父级规则ID
*/
private Long parentRuleId;
/**
* 规则类型;normal=正常节点,virtually=虚拟节点
*/
private String ruleType;
/**
* 层级;层级越高执行优先级越高, default 1
*/
private int level;
private String attrCode;
private String attrVal;
private String relationOuter;
private String relationInner;
private Integer sort;
private String attrValType;
private List<RuleDO> childRuleDOList = new ArrayList<>();
public RuleDO(long id, Long parentRuleId, String ruleType, int level, String attrCode, String attrVal, String relationOuter, String relationInner, Integer sort, String attrValType) {
this.id = id;
this.parentRuleId = parentRuleId;
this.ruleType = ruleType;
this.level = level;
this.attrCode = attrCode;
this.attrVal = attrVal;
this.relationOuter = relationOuter;
this.relationInner = relationInner;
this.sort = sort;
this.attrValType = attrValType;
}
}
常量
java
package com.example.bootdemo.flow.constant;
public class RuleOperatorConstant {
public static final String equals_operator = "=";
public static final String more_than_operator = ">";
public static final String less_than_operator = "<";
public static final String less_than_equal_operator = "<=";
public static final String more_than_equal_operator = ">=";
public static final String not_equals_operator = "!=";
public static final String int_type = "int";
public static final String string_type = "string";
public static final String double_type = "double";
public static final String and = "and";
public static final String or = "or";
}
接口
java
public interface RuleOperatorHandle {
/**
* 操作类型
* @return
*/
String operatorType();
/**
* 执行计算
* @return
*/
Boolean exec(Object product_val_original,Object rule_val_original,String attrValType);
}
RuleEqualsOperatorHandle
java
package com.example.bootdemo.flow.handle;
import com.example.bootdemo.flow.constant.RuleOperatorConstant;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
@Slf4j
public class RuleEqualsOperatorHandle implements RuleOperatorHandle {
@Override
public String operatorType() {
return RuleOperatorConstant.equals_operator;
}
@Override
public Boolean exec(Object product_val_original, Object rule_val_original, String attrValType) {
Boolean result = true;
if (RuleOperatorConstant.int_type.equalsIgnoreCase(attrValType)) {
Integer product_val = Integer.valueOf(product_val_original.toString());
Integer rule_val = Integer.valueOf(rule_val_original.toString());
result = product_val.equals(rule_val);
} else if (RuleOperatorConstant.string_type.equalsIgnoreCase(attrValType)) {
String product_val = product_val_original.toString();
String rule_val = rule_val_original.toString();
result = product_val.equals(rule_val);
} else if (RuleOperatorConstant.double_type.equalsIgnoreCase(attrValType)) {
BigDecimal product_val = new BigDecimal(product_val_original.toString());
BigDecimal rule_val = new BigDecimal(rule_val_original.toString());
result = product_val.compareTo(rule_val) == 0;
}
log.info(product_val_original + RuleOperatorConstant.equals_operator + rule_val_original + "; --> " + result);
return result;
}
}
java
package com.example.bootdemo.flow.handle;
import com.example.bootdemo.flow.constant.RuleOperatorConstant;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
@Slf4j
public class RuleLessThanEqualsOperatorHandle implements RuleOperatorHandle{
@Override
public String operatorType() {
return RuleOperatorConstant.less_than_equal_operator;
}
@Override
public Boolean exec(Object product_val_original,Object rule_val_original,String attrValType){
Boolean result = true;
if (RuleOperatorConstant.int_type.equalsIgnoreCase(attrValType)){
Integer product_val = Integer.valueOf(product_val_original.toString());
Integer rule_val = Integer.valueOf(rule_val_original.toString());
result = product_val.compareTo(rule_val) <= 0;
}else if (RuleOperatorConstant.double_type.equalsIgnoreCase(attrValType)){
BigDecimal product_val = new BigDecimal(product_val_original.toString());
BigDecimal rule_val = new BigDecimal(rule_val_original.toString());
result = product_val.compareTo(rule_val) <= 0;
}
log.info(product_val_original + RuleOperatorConstant.less_than_equal_operator + rule_val_original + "; --> " + result);
return result;
}
}
java
package com.example.bootdemo.flow.handle;
import com.example.bootdemo.flow.constant.RuleOperatorConstant;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
@Slf4j
public class RuleLessThanOperatorHandle implements RuleOperatorHandle{
@Override
public String operatorType() {
return RuleOperatorConstant.less_than_operator;
}
@Override
public Boolean exec(Object product_val_original,Object rule_val_original,String attrValType){
Boolean result = true;
if (RuleOperatorConstant.int_type.equalsIgnoreCase(attrValType)){
Integer product_val = Integer.valueOf(product_val_original.toString());
Integer rule_val = Integer.valueOf(rule_val_original.toString());
result = product_val.compareTo(rule_val) < 0;
}else if (RuleOperatorConstant.double_type.equalsIgnoreCase(attrValType)){
BigDecimal product_val = new BigDecimal(product_val_original.toString());
BigDecimal rule_val = new BigDecimal(rule_val_original.toString());
result = product_val.compareTo(rule_val) < 0;
}
log.info(product_val_original + RuleOperatorConstant.less_than_operator + rule_val_original + "; --> " + result);
return result;
}
}
java
package com.example.bootdemo.flow.handle;
import com.example.bootdemo.flow.constant.RuleOperatorConstant;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
@Slf4j
public class RuleMoreThanEqualsOperatorHandle implements RuleOperatorHandle{
@Override
public String operatorType() {
return RuleOperatorConstant.more_than_equal_operator;
}
@Override
public Boolean exec(Object product_val_original,Object rule_val_original,String attrValType){
Boolean result = true;
if (RuleOperatorConstant.int_type.equalsIgnoreCase(attrValType)){
Integer product_val = Integer.valueOf(product_val_original.toString());
Integer rule_val = Integer.valueOf(rule_val_original.toString());
result = product_val.compareTo(rule_val) >= 0;
}else if (RuleOperatorConstant.double_type.equalsIgnoreCase(attrValType)){
BigDecimal product_val = new BigDecimal(product_val_original.toString());
BigDecimal rule_val = new BigDecimal(rule_val_original.toString());
result = product_val.compareTo(rule_val) >= 0;
}
log.info(product_val_original + RuleOperatorConstant.more_than_equal_operator + rule_val_original + "; --> " + result);
return result;
}
}
java
package com.example.bootdemo.flow.handle;
import com.example.bootdemo.flow.constant.RuleOperatorConstant;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
@Slf4j
public class RuleMoreThanOperatorHandle implements RuleOperatorHandle{
@Override
public String operatorType() {
return RuleOperatorConstant.more_than_operator;
}
@Override
public Boolean exec(Object product_val_original,Object rule_val_original,String attrValType){
Boolean result = true;
if (RuleOperatorConstant.int_type.equalsIgnoreCase(attrValType)){
Integer product_val = Integer.valueOf(product_val_original.toString());
Integer rule_val = Integer.valueOf(rule_val_original.toString());
result = product_val.compareTo(rule_val) > 0;
}else if (RuleOperatorConstant.double_type.equalsIgnoreCase(attrValType)){
BigDecimal product_val = new BigDecimal(product_val_original.toString());
BigDecimal rule_val = new BigDecimal(rule_val_original.toString());
result = product_val.compareTo(rule_val) > 0;
}
log.info(product_val_original + RuleOperatorConstant.more_than_operator + rule_val_original + "; --> " + result);
return result;
}
}
java
package com.example.bootdemo.flow.handle;
import com.example.bootdemo.flow.constant.RuleOperatorConstant;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
@Slf4j
public class RuleNotEqualsOperatorHandle implements RuleOperatorHandle{
@Override
public String operatorType() {
return RuleOperatorConstant.not_equals_operator;
}
@Override
public Boolean exec(Object product_val_original,Object rule_val_original,String attrValType){
Boolean result = true;
if (RuleOperatorConstant.int_type.equalsIgnoreCase(attrValType)){
Integer product_val = Integer.valueOf(product_val_original.toString());
Integer rule_val = Integer.valueOf(rule_val_original.toString());
result = product_val.compareTo(rule_val) != 0;
}else if (RuleOperatorConstant.double_type.equalsIgnoreCase(attrValType)){
BigDecimal product_val = new BigDecimal(product_val_original.toString());
BigDecimal rule_val = new BigDecimal(rule_val_original.toString());
result = product_val.compareTo(rule_val) != 0;
}
log.info(product_val_original + RuleOperatorConstant.not_equals_operator + rule_val_original + "; --> " + result);
return result;
}
}
sql
sql
CREATE TABLE `product_planning_rule` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`gmt_create` datetime NOT NULL COMMENT '创建时间',
`gmt_modified` datetime NOT NULL COMMENT '修改时间',
`product_planning_id` bigint(20) unsigned DEFAULT NULL COMMENT '企划ID',
`parent_rule_id` bigint(20) unsigned DEFAULT NULL COMMENT '父级规则ID',
`rule_type` varchar(50) DEFAULT NULL COMMENT '规则类型;normal=正常节点,virtually=虚拟节点',
`level` int(10) unsigned DEFAULT NULL COMMENT '层级;层级越高执行优先级越高, default 1',
`attr_code` varchar(50) DEFAULT NULL COMMENT '属性code;eg:color、size',
`relation_outer` varchar(50) DEFAULT NULL COMMENT '组外关系;or 或者 and',
`relation_inner` varchar(50) DEFAULT NULL COMMENT '组内关系;eg:=、>、<、>=、<=、≠、in',
`attr_val` varchar(200) DEFAULT NULL COMMENT '变量值',
`attr_unit` varchar(50) DEFAULT NULL COMMENT '变量单位;eg:kg、cm、CNY',
`sort` int(10) unsigned DEFAULT NULL COMMENT '同一个level下的排序',
`attr_val_type` varchar(50) DEFAULT NULL COMMENT '量值数据类型;string=字符串;int=数字;double=小数',
`is_deleted` tinyint(3) unsigned DEFAULT NULL COMMENT '逻辑删除:1-删除 0-没有删除',
`env` varchar(50) DEFAULT NULL COMMENT '环境;daily、pre、prod',
PRIMARY KEY (`id`),
KEY `idx_planning_id` (`product_planning_id`)
) ENGINE=InnoDB AUTO_INCREMENT=341 DEFAULT CHARSET=utf8mb4 COMMENT='企划规则表'