深入理解业务管理系统中的规则引擎实现
一、背景介绍
这段时间工作上,需要开发一个功能,可以动态选择表和字段形成一条条规则,然后规则又可以进行不通的组合,比如三条中至少满足一条并且满足另外一条这种刁钻的规则条件,具体功能描述可以看图。当时还真是为了实现这个功能考虑到头大,他难点在于既要实现规则的判断,又要满足下面条件的二次判断,好在也在同事的帮助下想出了思路,这里和大家分享下,后续类似地方都可以参考这个思路。

二、核心思路分析
1. 问题分析
在系统中,我们需要实现以下功能:
- 支持灵活配置各种判定规则
- 支持规则之间的逻辑组合(AND/OR)
- 支持规则组的概念(设置最小 / 最大满足数量)
- 支持可视化配置规则树
2. 设计思路
为了解决这些问题,我们采用了以下设计思路:
(1)数据模型设计
将规则系统分为两个核心部分:
- 规则定义:存储具体的规则内容(如血压 > 140mmHg)
- 规则配置:存储规则之间的逻辑关系(如规则 A AND 规则 B)
(2)树形结构设计
使用树形结构来表示规则之间的逻辑关系,支持三种节点类型:
- RULE:具体的规则节点
- OPERATOR:逻辑运算符节点(AND/OR)
- GROUP:规则组节点
(3)递归处理机制
使用递归算法来处理树形结构,实现规则树的转换和解析。
三、核心代码讲解
1. 数据模型定义
(1)规则定义表(MonRuleDefinitions)
less
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("MON_RULE_DEFINITIONS")
@ApiModel(value = "MonRuleDefinitions对象", description = "判定规则定义表")
public class MonRuleDefinitions extends Model<MonRuleDefinitions> {
@ApiModelProperty(value = "主键")
@TableId(value = "ID", type = IdType.ASSIGN_ID)
private String id;
@ApiModelProperty(value = "规则组id")
@TableField("RULE_GROUP_ID")
private String ruleGroupId;
@ApiModelProperty(value = "来源字典编码【字典】;table_name")
@TableField("FROM_DICT_CODE")
private String fromDictCode;
@ApiModelProperty(value = "具体字段编码")
@TableField("SPECIFIC_FIELD_CODE")
private String specificFieldCode;
@ApiModelProperty(value = "匹配值代码【字典】;more_range")
@TableField("MATCH_TYPE_CODE")
private String matchTypeCode;
@ApiModelProperty(value = "指标值")
@TableField("ITEM_VALUE")
private String itemValue;
// ... 其他字段
}
代码分析:
- 使用 MyBatis-Plus 的注解来映射数据库表
- 每个规则定义包含来源、字段、匹配类型、指标值等信息
- 支持多种匹配类型(如大于、小于、等于)
(2)规则配置表(MonBaseRuleConfig)
less
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("MON_RULE_CONFIG")
@ApiModel(value = "MonBaseRuleConfig对象", description = "判定规则配置表")
public class MonBaseRuleConfig extends Model<MonBaseRuleConfig> {
@ApiModelProperty(value = "主键")
@TableId(value = "ID", type = IdType.ASSIGN_ID)
private String id;
@ApiModelProperty(value = "规则类型 1-疾病分级 2-患者管理 3-质控")
@TableField("RULE_TYPE")
private String ruleType;
@ApiModelProperty(value = "规则组id")
@TableField("RULE_GROUP_ID")
private String ruleGroupId;
@ApiModelProperty(value = "父节点ID,用于构建树结构")
@TableField("PARENT_NODE_ID")
private String parentNodeId;
@ApiModelProperty(value = "节点类型(RULE、OPERATOR、GROUP)")
@TableField("NODE_TYPE")
private String nodeType;
@ApiModelProperty(value = "逻辑运算符(AND、OR);more_range")
@TableField("OPERATOR")
private String operator;
@ApiModelProperty(value = "如果节点是规则,则关联规则ID")
@TableField("RULE_ID")
private String ruleId;
@ApiModelProperty(value = "对于GROUP类型,最少满足的规则数量;item_result_unit_code")
@TableField("MIN_COUNT")
private String minCount;
@ApiModelProperty(value = "对于GROUP类型,最多满足的规则数量")
@TableField("MAX_COUNT")
private String maxCount;
// ... 其他字段
}
代码分析:
- 支持三种节点类型:RULE、OPERATOR、GROUP
- 使用 parentNodeId 来构建树形结构
- GROUP 类型支持设置最小 / 最大满足数量
2. 规则节点工具类(RuleNodeUtil)
(1)递归转换规则节点
scss
public static void convertAndCollectNodes(RuleNodeDTO currentNode, String ruleGroupId,
String parentNodeId, List<MonBaseRuleConfig> allNodes) {
if ("RULE".equals(currentNode.getNodeType())&¤tNode.getRuleId() == null) {
throw new BaseException(998,"规则节点缺少规则ID");
}
// 转换当前DTO为数据库实体
MonBaseRuleConfig dbNode = new MonBaseRuleConfig();
BeanUtil.copyProperties(currentNode, dbNode);
dbNode.setRuleGroupId(ruleGroupId);
// 特殊处理GROUP类型
if ("GROUP".equals(currentNode.getNodeType())) {
dbNode.setRuleId(String.join(",",(List<String>) currentNode.getRuleId()));
if ("min".equals(currentNode.getCountType())){
dbNode.setMinCount(currentNode.getCount());
dbNode.setMaxCount("");
}else {
dbNode.setMaxCount(currentNode.getCount());
dbNode.setMinCount("");
}
}
allNodes.add(dbNode);
// 递归处理子节点
if (!CollectionUtils.isEmpty(currentNode.getChildren())) {
for (RuleNodeDTO child : currentNode.getChildren()) {
convertAndCollectNodes(child, ruleGroupId, currentNode.getId(), allNodes);
}
}
}
代码分析:
- 递归处理规则树,将前端传递的 DTO 转换为数据库实体
- 特殊处理 GROUP 类型,支持设置最小 / 最大满足数量
- 验证规则节点是否缺少规则 ID
(2)构建规则树
scss
public static RuleNodeDTO buildNode(MonBaseRuleConfig currentNode, Map<String, List<MonBaseRuleConfig>> parentMap) {
// 转换为DTO
RuleNodeDTO nodeDTO = BeanUtil.copyProperties(currentNode,RuleNodeDTO.class);
// 获取当前节点的子节点列表
List<MonBaseRuleConfig> children = parentMap.getOrDefault(currentNode.getId(), new ArrayList<>());
// 递归构建子节点
List<RuleNodeDTO> childDTOs = children.stream()
.map(child -> buildNode(child, parentMap))
.collect(Collectors.toList());
// GROUP节点特殊处理
if ("GROUP".equals(currentNode.getNodeType())) {
if (!childDTOs.isEmpty()) {
List<Object> ruleIds = childDTOs.stream().map(RuleNodeDTO::getRuleId).collect(Collectors.toList());
RuleNodeDTO ruleNodeDTO = childDTOs.get(0);
ruleNodeDTO.setRuleId(ruleIds);
nodeDTO.setChildren(Collections.singletonList(ruleNodeDTO));
}else {
nodeDTO.setChildren(childDTOs);
}
} else {
// 非GROUP节点保留完整嵌套结构
nodeDTO.setChildren(childDTOs);
}
return nodeDTO;
}
代码分析:
- 递归构建规则树,将数据库实体转换为前端需要的 DTO
- 特殊处理 GROUP 类型,合并子节点的规则 ID
- 保留完整的嵌套结构,方便前端展示
3. 规则配置服务实现(MonBaseRuleConfigServiceImpl)
(1)保存规则节点
scss
public Integer saveNode(List<RuleNodeDTO> judgeGroup, String ruleGroupId, List<MonRuleDefinitions> ruleDefinitions) {
if (CollectionUtils.isEmpty(judgeGroup)) {
return 0;
}
// 收集所有转换后的数据库实体
List<MonBaseRuleConfig> allNodes = new ArrayList<>();
// 处理顶级节点(递归处理所有子节点)
for (RuleNodeDTO rootNode : judgeGroup) {
RuleNodeUtil.convertAndCollectNodes(rootNode, ruleGroupId, null, allNodes);
}
// 批量插入数据库
if (!CollectionUtils.isEmpty(allNodes)) {
// 删除旧的规则配置
List<MonBaseRuleConfig> list = this.lambdaQuery()
.eq(MonBaseRuleConfig::getRuleGroupId, ruleGroupId)
.list();
list.removeIf(judge ->allNodes.stream()
.map(MonBaseRuleConfig::getId).collect(Collectors.toList()).contains(judge.getId()));
List<String> monConfigDel = list.stream().map(MonBaseRuleConfig::getId).collect(Collectors.toList());
this.removeByIds(monConfigDel);
// 保存新的规则配置
this.saveOrUpdateBatch(allNodes);
}
return allNodes.size();
}
代码分析:
- 批量保存规则节点,支持新增和更新
- 先删除旧的规则配置,再保存新的规则配置
- 支持规则组的概念,方便管理多个规则
四、使用场景
1. 疾病分级判定
scss
高血压分级 = (收缩压 > 140mmHg OR 舒张压 > 90mmHg) AND 年龄 > 65岁
2. 患者管理规则
scss
高危患者 = (糖尿病 AND 血压 > 130/80mmHg) OR (冠心病 AND 血脂异常)
3. 医疗质量控制
scss
质控合格 = (病历书写完整率 > 95%) AND (随访及时率 > 90%) AND (患者满意度 > 85%)
五、总结
通过以上分析,我们可以看到这套规则引擎实现方案具有以下特点:
- 灵活性:支持任意复杂的规则组合
- 可扩展性:方便添加新的规则类型和逻辑
- 可视化:支持前端可视化配置规则树
- 复用性:规则可以被多个规则组复用
在实际应用中,我们可以根据具体业务需求,灵活配置各种判定规则,提高系统的灵活性和可维护性。
六、优化建议
- 性能优化:递归处理在规则树层级较深时可能存在性能问题,可以考虑使用迭代方式替代递归
- 事务管理:在批量保存规则节点时,建议添加事务管理,确保数据一致性
- 缓存机制:对于频繁使用的规则配置,可以添加缓存机制,减少数据库查询
- 规则验证:在保存规则前添加更严格的验证逻辑,确保规则配置的合法性
typescript
// 优化建议:添加事务管理
@Transactional(rollbackFor = Exception.class)
public Integer saveNode(List<RuleNodeDTO> judgeGroup, String ruleGroupId, List<MonRuleDefinitions> ruleDefinitions) {
// ... 原有代码
}
通过以上优化,可以进一步提高规则引擎的性能和可靠性。