package cn.iocoder.yudao.module.qcreport.engine.rule;
|
|
import java.util.ArrayList;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.concurrent.ConcurrentHashMap;
|
|
/**
|
* 判定规则引擎入口。
|
* <p>
|
* 规则文本来自用户,全程只用自建的词法/语法分析与白名单函数求值,
|
* 不碰 eval、不碰脚本引擎——这是硬约束,不是风格偏好。
|
* <p>
|
* 与前端 {@code engine/rule-engine.ts} 一一对应,同一份规则两端必须算出同一结果。
|
*/
|
public final class RuleEngine {
|
|
/** 已解析的语法树缓存:同一条规则要在多行检验项上重复求值,不必反复解析 */
|
private static final Map<String, RuleNode> CACHE = new ConcurrentHashMap<>();
|
|
/**
|
* 缓存条数上限。
|
* <p>
|
* 规则来自保存下来的模板,条数天然有限;但校验接口会被反复调用,
|
* 没有上限的静态缓存等于把用户输入攒在堆里,这里到量就不再往里放。
|
*/
|
private static final int MAX_CACHE_SIZE = 512;
|
|
private RuleEngine() {
|
}
|
|
/** 解析规则文本,语法错误抛 {@link RuleSyntaxException} */
|
public static RuleNode parse(String expression) {
|
return RuleParser.parse(expression);
|
}
|
|
/** 解析并缓存,供渲染时反复求值使用 */
|
public static RuleNode compiled(String expression) {
|
if (expression == null) {
|
throw new RuleSyntaxException("规则表达式为空", 0);
|
}
|
RuleNode cached = CACHE.get(expression);
|
if (cached != null) {
|
return cached;
|
}
|
RuleNode node = RuleParser.parse(expression);
|
if (CACHE.size() < MAX_CACHE_SIZE) {
|
CACHE.put(expression, node);
|
}
|
return node;
|
}
|
|
/**
|
* 校验规则文本的语法,返回错误清单,空列表表示通过。
|
* <p>
|
* 这里<b>只查语法</b>;函数白名单交给 {@link RuleEvaluator#findUnknownFunction},
|
* 由 {@link cn.iocoder.yudao.module.qcreport.engine.QualityReportEngine#validateRules}
|
* 把两者拼成完整的保存前校验。
|
* 前端把这两步合在了 {@code validateRule} 一个函数里,但报错文案两端逐字一致——
|
* 口径不一致会变成「设计器能存、后端存不了」这种互相打架的场面。
|
*/
|
public static List<String> validate(QualityRuleDefinition rule) {
|
List<String> errors = new ArrayList<>();
|
String label = rule.label();
|
String expression = rule.expression();
|
if (expression == null || expression.trim().isEmpty()) {
|
errors.add("规则「" + label + "」的表达式为空");
|
return errors;
|
}
|
try {
|
RuleParser.parse(expression);
|
} catch (RuleSyntaxException error) {
|
errors.add("规则「" + label + "」语法错误:" + error.getMessage()
|
+ "(第 " + (error.position() + 1) + " 个字符)");
|
} catch (RuntimeException error) {
|
errors.add("规则「" + label + "」无法解析:" + error.getMessage());
|
}
|
return errors;
|
}
|
|
/** 求值,返回原始结果(可能是数字、字符串、布尔) */
|
public static Object evaluate(String expression, Object context) {
|
return RuleEvaluator.evaluateNode(parse(expression), context);
|
}
|
|
/** 求值并转成布尔判定,用于「是否合格」这类条件 */
|
public static boolean test(String expression, Object context) {
|
return RuleEvaluator.evaluateCondition(parse(expression), context);
|
}
|
|
/** 静态检查表达式里调用的函数是否在白名单内,不在则返回函数名 */
|
public static String findUnknownFunction(String expression) {
|
return RuleEvaluator.findUnknownFunction(parse(expression));
|
}
|
|
/** 表达式依赖的上下文路径,渲染前可据此检查数据是否齐备 */
|
public static List<String> listPaths(String expression) {
|
return RuleEvaluator.listPaths(parse(expression));
|
}
|
|
}
|