7 小时以前 9bad721754fe8bbe2e5f459d0706e0fefac569f3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
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));
    }
 
}