戴老师的博客 - 规则引擎
type: channel
link: https://www.tushu.info/tag/%E8%A7%84%E5%88%99%E5%BC%95%E6%93%8E/
items: 一个规则引擎的可视化方案

一个规则引擎的可视化方案
type: item
date: 2026-07-09T04:27:44+00:00
description: 背景近期因新项目需求，可能需要引入规则引擎，因此花费了一些时间对相关技术进行了调研。通过主流搜索引擎检索“规则引擎”关键字，可以找到大量相关资料，其中绝大部分都会提到 Drools、URules、Easy-Rules 等开源项目，部分文章也指出它们采用 Groovy 脚本来实现。通过对项目需求的评估，初步判定 Groovy 脚本方案已能满足实际场景。然而，在这些资料或方案中，除 URules 外，大部分仅关注框架的性能和使用便捷性，很少探讨如何让业务人员自行定义规则的方案。URules 虽然自带可视化规则管理界面，但其界面样式难以自定义，无法与现有的后台管理界面无缝融合。经过多次尝试变换关键字检索，最终在 StackOverflow 上找到了一个探讨该问题的帖子。特此将帖子中提到的方案分享给大家，如果你也在研究同样的问题，希望本文能对你有所帮助。在介绍具体方案之前，我们先简单了解一下什么是规则引擎。什么是规则引擎？简单来说，规则引擎负责的核心逻辑是：判定某个数据或对象是否满足特定条件，并根据判定结果执行相应的动作。例如：对于刚刚在网站上完成购物的一个用户（对象）， 如果她是 "女性用户 并且（连续登录天数大于 10 天 或者 订单金额大于 200 元）" (条件.html)， 那么系统就自动给该用户发放一张优惠券（动作）。在上述场景中，规则引擎最重要的优势在于实现了“条件”表达式的配置化。如果条件表达式无法配置，程序员就需要在代码中硬编码各种 if...else... 逻辑；若条件组合特别复杂，代码将难以维护。同时，若无法配置化，每次条件的细微变更都需要修改代码并走发布流程，无法快速响应业务需求。在 Groovy 脚本方案中，上述场景可以这样实现：定义一个 Groovy 脚本：def validateCondition(args.html) { return args.用户性别 == "女性" && (args.连续登录天数 > 10 || args.订单金额 > 200.html); }通过 Java 提供的 ScriptEngineManager 对象去执行： org.codehaus.groovy groovy 3.0.7 /* * @param condition 从数据库中读出来的条件表达式 */ private Boolean validateCondition(String condition.html) { // 实际使用上，ScriptEngineManager 建议定义为单例 ScriptEngineManager engineManager = new ScriptEngineManager(); ScriptEngine engine = engineManager.getEngineByName("groovy".html); Map<String, Object> args = new HashMap<>(); args.put("用户性别", "女性".html); args.put("连续登录天数", 11.html); args.put("订单金额", 220.html); engine.eval(condition.html); return ((Invocable.html) engine).invokeFunction("validateCondition", args.html); }在上面的 Groovy 脚本中，经常需要变动的部分是 args.用户性别 == "女性" && (args.连续登录天数 > 10 || args.订单金额 > 200.html) 这个表达式。最简单的方案是在后台界面提供一个文本框，录入整个 Groovy 脚本后保存到数据库。但这种方案存在缺点：表达式的定义有一定门槛。这对程序员来说很简单，但对于未接触过编程的业务人员而言，很容易录入错误的表达式。这就引出了本文的另一个话题：如何实现 Boolean 表达式的可视化编辑？如何实现 Boolean 表达式的可视化编辑？一种方案是前端对指定的表达式进行语法解析，然后渲染成界面元素；业务人员编辑完成后，再将界面元素结构转换回表达式。然而，直接解析语法存在两个缺点：需要考虑的边界条件较多，容易因处理不当导致解析出错。限定了后端可选用的脚本语言。例如，上述方案选用的是 Groovy，其“与”运算符是 &&；假如某天引入了一种性能更好的脚本语言，其“与”运算符定义为 and，那么就需要修改大量表达式解析逻辑。另一种方案，是定义一个数据结构来描述表达式的结构：{ "all": [ { "any": [ { "gt": ["连续登录天数", 10] }, { "gt": ["订单金额", 200] } ] }, { "e…