基于Java实现的简易规则引擎(日常开发难点记录)

深入理解业务管理系统中的规则引擎实现

一、背景介绍

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

二、核心思路分析

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%)

五、总结

通过以上分析,我们可以看到这套规则引擎实现方案具有以下特点:

  1. 灵活性:支持任意复杂的规则组合
  2. 可扩展性:方便添加新的规则类型和逻辑
  3. 可视化:支持前端可视化配置规则树
  4. 复用性:规则可以被多个规则组复用

在实际应用中,我们可以根据具体业务需求,灵活配置各种判定规则,提高系统的灵活性和可维护性。

六、优化建议

  1. 性能优化:递归处理在规则树层级较深时可能存在性能问题,可以考虑使用迭代方式替代递归
  2. 事务管理:在批量保存规则节点时,建议添加事务管理,确保数据一致性
  3. 缓存机制:对于频繁使用的规则配置,可以添加缓存机制,减少数据库查询
  4. 规则验证:在保存规则前添加更严格的验证逻辑,确保规则配置的合法性
typescript 复制代码
// 优化建议:添加事务管理
@Transactional(rollbackFor = Exception.class)
public Integer saveNode(List<RuleNodeDTO> judgeGroup, String ruleGroupId, List<MonRuleDefinitions> ruleDefinitions) {
    // ... 原有代码
}

通过以上优化,可以进一步提高规则引擎的性能和可靠性。

相关推荐
tkevinjd2 小时前
net1(Java中的网络编程、TCP的三次握手与四次挥手)
java
Codelinghu2 小时前
「 LLM实战 - 企业 」构建企业级RAG系统:基于Milvus向量数据库的高效检索实践
人工智能·后端·llm
J2虾虾2 小时前
Java使用的可以使用的脚本执行引擎
java·开发语言·脚本执行
老马识途2.02 小时前
java处理接口返回的json数据步骤 包括重试处理,异常抛出,日志打印,注意事项
java·开发语言
d***81722 小时前
springboot 修复 Spring Framework 特定条件下目录遍历漏洞(CVE-2024-38819)
spring boot·后端·spring
2***d8852 小时前
Spring Boot中的404错误:原因、影响及处理策略
java·spring boot·后端
c***69302 小时前
Springboot项目:使用MockMvc测试get和post接口(含单个和多个请求参数场景)
java·spring boot·后端
6***A6632 小时前
Springboot中SLF4J详解
java·spring boot·后端
五阿哥永琪2 小时前
Hutool中常用的工具类&真实项目的黄金组合
java