-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
255 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,251 @@ | ||
--- | ||
title: 规则表达式的临时方案 | ||
description: 规则表达式的临时方案 | ||
--- | ||
|
||
# 规则表达式的临时方案 | ||
|
||
## 场景介绍 | ||
|
||
由于公司的客户对于市面上买卖丧葬用品的价格不透明,AB价格交易的情况有杜绝倾向,所以和公司商讨一套系统,用于管理这些用品及其交易情况, | ||
有需要对订单进行管理,同时订单涉及到订单的物品,订单物品的类型等,用户在下单的时候,需要对下单的物品进行提醒, | ||
告知物品的价格过高,明显不合理。那么就有一个面板需要对物品的类型进行管控,这一类的物品价格必须满足后台设置的给定值, | ||
否则就给予预警。 | ||
|
||
那么后台对于预警规则的设置就需要一套规则,正常的思路就是集成规则引擎的框架,用于管理各类规则,但是项目给的时间少,要求尽快开发出来, | ||
对于开发人员而言,还需要去调研规则引擎的选类,学习框架的使用,时间上面不允许,而且集成框架可能会对原有的老系统的资源, | ||
有一定的影响。我们研发只能是自己规定设计一套勉强能用的方案。 | ||
|
||
<img src="../img/预警规则页面.png" style="zoom: 70%;" /> | ||
|
||
## 前后端约定 | ||
|
||
首先需要和前后端约定,对于这个临时的规则的传参需要怎么设计: | ||
|
||
1. 首先前端需要按照要求传字符串,要求如代码注释 | ||
2. 后端根据算法解析规则,判断价格是否超出预警阈值 | ||
3. 数据库使用一个字段存储规则,使用`JSON`类型 | ||
|
||
=== "规则传递公共参数" | ||
|
||
```java | ||
@Data | ||
@AllArgsConstructor | ||
public class RuleScript { | ||
/** | ||
* 运算的类型 < <= > >= == 如<= | ||
*/ | ||
private String symbol; | ||
|
||
/** | ||
* 具体的数值 如20000.00 | ||
*/ | ||
private BigDecimal value; | ||
|
||
/** | ||
* 与后一个条件的关联关系 如或 | ||
*/ | ||
private RuleConditionEnum condition; | ||
|
||
/**这个值前端不给值,后端零时使用*/ | ||
private Boolean temp; | ||
} | ||
``` | ||
|
||
=== "条件之间的连接" | ||
|
||
```java | ||
@Getter | ||
@AllArgsConstructor | ||
public enum RuleConditionEnum { | ||
OR(1, "或"), | ||
AND(2, "且"); | ||
private final Integer value; | ||
private final String label; | ||
} | ||
``` | ||
|
||
## 算法设计 | ||
|
||
我们后端就需要对前端传递的数据,一个`List<RuleScript>`进行解析、使用。 | ||
|
||
### 1️⃣对数据分组计算 | ||
|
||
- 首先遍历所有规则,计算物品价格是否满足当前的预警值(`true` 或 `false`),并将它存储在临时变量中(无需从前端传递) | ||
- 将连续的 `AND` 条件视为一组,并通过 `OR` 条件分隔这些组。然后,对每一组执行逻辑 `AND` 操作以确定该组的最终布尔值。 | ||
|
||
|
||
我们先操作第一条和第二条 | ||
|
||
```java | ||
private List<List<RuleScript>> groupScripts(List<RuleScript> ruleScripts) { | ||
List<List<RuleScript>> groups = new ArrayList<>(); | ||
int i = 0; | ||
int size = ruleScripts.size(); | ||
|
||
while (i < size) { | ||
if (ruleScripts.get(i).getCondition() == RuleConditionEnum.AND) { | ||
List<RuleScript> group = new ArrayList<>(); | ||
// 把所有连续的AND加入组 | ||
while (i < size && ruleScripts.get(i).getCondition() == RuleConditionEnum.AND) { | ||
group.add(ruleScripts.get(i)); | ||
i++; | ||
} | ||
// 如果还有元素,把下一个元素(可能是OR)加入组 | ||
if (i < size) { | ||
group.add(ruleScripts.get(i)); | ||
i++; | ||
} | ||
groups.add(group); | ||
} else if (Objects.isNull(ruleScripts.get(i).getCondition()) | ||
|| ruleScripts.get(i).getCondition() == RuleConditionEnum.OR) { | ||
List<RuleScript> group = new ArrayList<>(); | ||
group.add(ruleScripts.get(i)); | ||
groups.add(group); | ||
// 如果有下一个元素,下一个元素单独成组 | ||
if (i < size - 1) { | ||
List<RuleScript> nextGroup = new ArrayList<>(); | ||
nextGroup.add(ruleScripts.get(i + 1)); | ||
groups.add(nextGroup); | ||
i += 2; | ||
} else { | ||
i++; | ||
} | ||
} else { | ||
i++; | ||
} | ||
} | ||
return groups; | ||
} | ||
``` | ||
|
||
### 2️⃣计算最终结果 | ||
|
||
- 最后,对所有组应用逻辑 `OR` 操作,以决定整个条件列表的最终布尔结果,从而判断是否需要触发预警。 | ||
|
||
```java | ||
private boolean calculateFinalResult(List<List<RuleScript>> groups) { | ||
// 对每个组进行逻辑与操作,并收集结果 | ||
List<Boolean> bools = groups.stream() | ||
.map(group -> group.stream() | ||
.allMatch(script -> script.getTemp() != null && script.getTemp())) | ||
.collect(Collectors.toList()); | ||
|
||
// 对所有组的结果进行逻辑或操作 | ||
return bools.stream().anyMatch(result -> result); | ||
} | ||
``` | ||
|
||
### 3️⃣每一条规则的计算和对前端规则的校验 | ||
|
||
- 那么每一条规则的计算如何避免复杂性,可以使用`枚举`的方式 | ||
- 为了保证前端传递参数的准确性,我们还需要对规则进行校验 | ||
|
||
=== "规则枚举" | ||
|
||
```java | ||
@Getter | ||
@AllArgsConstructor | ||
public enum JudgeEnum { | ||
ge(">=", "大于等于") { | ||
@Override | ||
public boolean compare(BigDecimal price, BigDecimal value) { | ||
return price.compareTo(value) >= 0; | ||
} | ||
}, | ||
gt(">", "大于") { | ||
@Override | ||
public boolean compare(BigDecimal price, BigDecimal value) { | ||
return price.compareTo(value) > 0; | ||
} | ||
}, | ||
eq("==", "等于") { | ||
@Override | ||
public boolean compare(BigDecimal price, BigDecimal value) { | ||
return price.compareTo(value) == 0; | ||
} | ||
}, | ||
le("<=", "小于等于") { | ||
@Override | ||
public boolean compare(BigDecimal price, BigDecimal value) { | ||
return price.compareTo(value) <= 0; | ||
} | ||
}, | ||
lt("<", "小于") { | ||
@Override | ||
public boolean compare(BigDecimal price, BigDecimal value) { | ||
return price.compareTo(value) < 0; | ||
} | ||
}; | ||
private final String value; | ||
private final String label; | ||
|
||
/** | ||
* 根据 value 获取对应的枚举值 | ||
*/ | ||
public static JudgeEnum getByValue(String value) { | ||
for (JudgeEnum judgeEnum : JudgeEnum.values()) { | ||
if (judgeEnum.getValue().equals(value)) { | ||
return judgeEnum; | ||
} | ||
} | ||
return null; | ||
} | ||
|
||
public abstract boolean compare(BigDecimal price, BigDecimal value); | ||
} | ||
``` | ||
|
||
=== "校验与判断" | ||
|
||
```java | ||
private boolean takeRulesEffect(List<RuleScript> ruleScripts, BigDecimal price) { | ||
for (RuleScript ruleScript : ruleScripts) { | ||
if (StrUtil.isNotBlank(ruleScript.getSymbol())) { | ||
// 通过枚举值获取对应的比较逻辑 | ||
JudgeEnum judgeEnum = JudgeEnum.getByValue(ruleScript.getSymbol()); | ||
if (Objects.isNull(judgeEnum)) { | ||
throw new RuntimeException("给予的规则不匹配,请重新设置"); | ||
} | ||
// 使用枚举类中的比较逻辑 | ||
boolean hasWarn = judgeEnum.compare(price, ruleScript.getValue()); | ||
ruleScript.setTemp(hasWarn); | ||
} else { | ||
ruleScript.setTemp(false); | ||
} | ||
} | ||
return calculateFinalResult(groupScripts(ruleScripts)); | ||
} | ||
``` | ||
|
||
### 4️⃣验算 | ||
|
||
写一个测试类,可以看到结果符合第四条和第五条的规则,返回`true`,可以多试几次,自己口算试试看是否是对的。 | ||
|
||
```java | ||
public static void main(String[] args) { | ||
BigDecimal price = new BigDecimal("2000.00"); | ||
|
||
List<RuleScript> ruleScripts = Lists.newArrayList(); | ||
ruleScripts.add(new RuleScript(">", new BigDecimal("100.00"), RuleConditionEnum.AND, null)); | ||
ruleScripts.add(new RuleScript("<", new BigDecimal("200.00"), RuleConditionEnum.OR, null)); | ||
ruleScripts.add(new RuleScript(">=", new BigDecimal("2000.00"), RuleConditionEnum.AND, null)); | ||
ruleScripts.add(new RuleScript("<", new BigDecimal("3000.00"), RuleConditionEnum.OR, null)); | ||
ruleScripts.add(new RuleScript(">=", new BigDecimal("5000.00"), RuleConditionEnum.AND, null)); | ||
ruleScripts.add(new RuleScript("<=", new BigDecimal("7000.00"), RuleConditionEnum.AND, null)); | ||
ruleScripts.add(new RuleScript(">", new BigDecimal("8000.00"), RuleConditionEnum.AND, null)); | ||
ruleScripts.add(new RuleScript("<", new BigDecimal("9000.00"), RuleConditionEnum.OR, null)); | ||
ruleScripts.add(new RuleScript(">", new BigDecimal("9000.00"), RuleConditionEnum.AND, null)); | ||
ruleScripts.add(new RuleScript("<=", new BigDecimal("10000.00"), RuleConditionEnum.AND, null)); | ||
|
||
System.out.println(takeRulesEffect(ruleScripts, price)); | ||
} | ||
``` | ||
|
||
<img src="../img/预警规则计算结果.png" style="zoom: 70%;" /> | ||
|
||
## 总结 | ||
|
||
没有过多的文字介绍,代码本身就是一种语言,它自己会说话传递信息,我就不多嘴了。这样的一种东西只能是临时使用, | ||
它的优点就是轻,快,但是和规则引擎相比,业务代码要和计算逻辑耦合,无法使业务逻辑与这代码分离,也不便于维护和更新, | ||
对于扩展和更复杂的逻辑处理,更是不可能,注定只是一种临时方案应付一下,但是应付的能力还是要有,故记录一下。 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters