基于Java實現(xiàn)的簡易規(guī)則引擎
一、背景介紹
這段時間工作上,需要開發(fā)一個功能,可以動態(tài)選擇表和字段形成一條條規(guī)則,然后規(guī)則又可以進行不通的組合,比如三條中至少滿足一條并且滿足另外一條這種刁鉆的規(guī)則條件,具體功能描述可以看圖。當時還真是為了實現(xiàn)這個功能考慮到頭大,他難點在于既要實現(xiàn)規(guī)則的判斷,又要滿足下面條件的二次判斷,好在也在同事的幫助下想出了思路,這里和大家分享下,后續(xù)類似地方都可以參考這個思路。

二、核心思路分析
1. 問題分析
在系統(tǒng)中,我們需要實現(xiàn)以下功能:
- 支持靈活配置各種判定規(guī)則
- 支持規(guī)則之間的邏輯組合(AND/OR)
- 支持規(guī)則組的概念(設置最小 / 最大滿足數(shù)量)
- 支持可視化配置規(guī)則樹
2. 設計思路
為了解決這些問題,我們采用了以下設計思路:
(1)數(shù)據(jù)模型設計
將規(guī)則系統(tǒng)分為兩個核心部分:
- 規(guī)則定義:存儲具體的規(guī)則內(nèi)容(如血壓 > 140mmHg)
- 規(guī)則配置:存儲規(guī)則之間的邏輯關系(如規(guī)則 A AND 規(guī)則 B)
(2)樹形結(jié)構(gòu)設計
使用樹形結(jié)構(gòu)來表示規(guī)則之間的邏輯關系,支持三種節(jié)點類型:
- RULE:具體的規(guī)則節(jié)點
- OPERATOR:邏輯運算符節(jié)點(AND/OR)
- GROUP:規(guī)則組節(jié)點
(3)遞歸處理機制
使用遞歸算法來處理樹形結(jié)構(gòu),實現(xiàn)規(guī)則樹的轉(zhuǎn)換和解析。
三、核心代碼講解
1. 數(shù)據(jù)模型定義
(1)規(guī)則定義表(MonRuleDefinitions)
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("MON_RULE_DEFINITIONS")
@ApiModel(value = "MonRuleDefinitions對象", description = "判定規(guī)則定義表")
public class MonRuleDefinitions extends Model<MonRuleDefinitions> {
@ApiModelProperty(value = "主鍵")
@TableId(value = "ID", type = IdType.ASSIGN_ID)
private String id;
@ApiModelProperty(value = "規(guī)則組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 的注解來映射數(shù)據(jù)庫表
- 每個規(guī)則定義包含來源、字段、匹配類型、指標值等信息
- 支持多種匹配類型(如大于、小于、等于)
(2)規(guī)則配置表(MonBaseRuleConfig)
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("MON_RULE_CONFIG")
@ApiModel(value = "MonBaseRuleConfig對象", description = "判定規(guī)則配置表")
public class MonBaseRuleConfig extends Model<MonBaseRuleConfig> {
@ApiModelProperty(value = "主鍵")
@TableId(value = "ID", type = IdType.ASSIGN_ID)
private String id;
@ApiModelProperty(value = "規(guī)則類型 1-疾病分級 2-患者管理 3-質(zhì)控")
@TableField("RULE_TYPE")
private String ruleType;
@ApiModelProperty(value = "規(guī)則組id")
@TableField("RULE_GROUP_ID")
private String ruleGroupId;
@ApiModelProperty(value = "父節(jié)點ID,用于構(gòu)建樹結(jié)構(gòu)")
@TableField("PARENT_NODE_ID")
private String parentNodeId;
@ApiModelProperty(value = "節(jié)點類型(RULE、OPERATOR、GROUP)")
@TableField("NODE_TYPE")
private String nodeType;
@ApiModelProperty(value = "邏輯運算符(AND、OR);more_range")
@TableField("OPERATOR")
private String operator;
@ApiModelProperty(value = "如果節(jié)點是規(guī)則,則關聯(lián)規(guī)則ID")
@TableField("RULE_ID")
private String ruleId;
@ApiModelProperty(value = "對于GROUP類型,最少滿足的規(guī)則數(shù)量;item_result_unit_code")
@TableField("MIN_COUNT")
private String minCount;
@ApiModelProperty(value = "對于GROUP類型,最多滿足的規(guī)則數(shù)量")
@TableField("MAX_COUNT")
private String maxCount;
// ... 其他字段
}
代碼分析:
- 支持三種節(jié)點類型:RULE、OPERATOR、GROUP
- 使用 parentNodeId 來構(gòu)建樹形結(jié)構(gòu)
- GROUP 類型支持設置最小 / 最大滿足數(shù)量
2. 規(guī)則節(jié)點工具類(RuleNodeUtil)
(1)遞歸轉(zhuǎn)換規(guī)則節(jié)點
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,"規(guī)則節(jié)點缺少規(guī)則ID");
}
// 轉(zhuǎn)換當前DTO為數(shù)據(jù)庫實體
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);
// 遞歸處理子節(jié)點
if (!CollectionUtils.isEmpty(currentNode.getChildren())) {
for (RuleNodeDTO child : currentNode.getChildren()) {
convertAndCollectNodes(child, ruleGroupId, currentNode.getId(), allNodes);
}
}
}
代碼分析:
- 遞歸處理規(guī)則樹,將前端傳遞的 DTO 轉(zhuǎn)換為數(shù)據(jù)庫實體
- 特殊處理 GROUP 類型,支持設置最小 / 最大滿足數(shù)量
- 驗證規(guī)則節(jié)點是否缺少規(guī)則 ID
(2)構(gòu)建規(guī)則樹
public static RuleNodeDTO buildNode(MonBaseRuleConfig currentNode, Map<String, List<MonBaseRuleConfig>> parentMap) {
// 轉(zhuǎn)換為DTO
RuleNodeDTO nodeDTO = BeanUtil.copyProperties(currentNode,RuleNodeDTO.class);
// 獲取當前節(jié)點的子節(jié)點列表
List<MonBaseRuleConfig> children = parentMap.getOrDefault(currentNode.getId(), new ArrayList<>());
// 遞歸構(gòu)建子節(jié)點
List<RuleNodeDTO> childDTOs = children.stream()
.map(child -> buildNode(child, parentMap))
.collect(Collectors.toList());
// GROUP節(jié)點特殊處理
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節(jié)點保留完整嵌套結(jié)構(gòu)
nodeDTO.setChildren(childDTOs);
}
return nodeDTO;
}
代碼分析:
- 遞歸構(gòu)建規(guī)則樹,將數(shù)據(jù)庫實體轉(zhuǎn)換為前端需要的 DTO
- 特殊處理 GROUP 類型,合并子節(jié)點的規(guī)則 ID
- 保留完整的嵌套結(jié)構(gòu),方便前端展示
3. 規(guī)則配置服務實現(xiàn)(MonBaseRuleConfigServiceImpl)
(1)保存規(guī)則節(jié)點
public Integer saveNode(List<RuleNodeDTO> judgeGroup, String ruleGroupId, List<MonRuleDefinitions> ruleDefinitions) {
if (CollectionUtils.isEmpty(judgeGroup)) {
return 0;
}
// 收集所有轉(zhuǎn)換后的數(shù)據(jù)庫實體
List<MonBaseRuleConfig> allNodes = new ArrayList<>();
// 處理頂級節(jié)點(遞歸處理所有子節(jié)點)
for (RuleNodeDTO rootNode : judgeGroup) {
RuleNodeUtil.convertAndCollectNodes(rootNode, ruleGroupId, null, allNodes);
}
// 批量插入數(shù)據(jù)庫
if (!CollectionUtils.isEmpty(allNodes)) {
// 刪除舊的規(guī)則配置
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);
// 保存新的規(guī)則配置
this.saveOrUpdateBatch(allNodes);
}
return allNodes.size();
}
代碼分析:
- 批量保存規(guī)則節(jié)點,支持新增和更新
- 先刪除舊的規(guī)則配置,再保存新的規(guī)則配置
- 支持規(guī)則組的概念,方便管理多個規(guī)則
四、使用場景
1. 疾病分級判定
高血壓分級 = (收縮壓 > 140mmHg OR 舒張壓 > 90mmHg) AND 年齡 > 65歲
2. 患者管理規(guī)則
高危患者 = (糖尿病 AND 血壓 > 130/80mmHg) OR (冠心病 AND 血脂異常)
3. 醫(yī)療質(zhì)量控制
質(zhì)控合格 = (病歷書寫完整率 > 95%) AND (隨訪及時率 > 90%) AND (患者滿意度 > 85%)
五、總結(jié)
通過以上分析,我們可以看到這套規(guī)則引擎實現(xiàn)方案具有以下特點:
- 靈活性:支持任意復雜的規(guī)則組合
- 可擴展性:方便添加新的規(guī)則類型和邏輯
- 可視化:支持前端可視化配置規(guī)則樹
- 復用性:規(guī)則可以被多個規(guī)則組復用
在實際應用中,我們可以根據(jù)具體業(yè)務需求,靈活配置各種判定規(guī)則,提高系統(tǒng)的靈活性和可維護性。
六、優(yōu)化建議
- 性能優(yōu)化:遞歸處理在規(guī)則樹層級較深時可能存在性能問題,可以考慮使用迭代方式替代遞歸
- 事務管理:在批量保存規(guī)則節(jié)點時,建議添加事務管理,確保數(shù)據(jù)一致性
- 緩存機制:對于頻繁使用的規(guī)則配置,可以添加緩存機制,減少數(shù)據(jù)庫查詢
- 規(guī)則驗證:在保存規(guī)則前添加更嚴格的驗證邏輯,確保規(guī)則配置的合法性
// 優(yōu)化建議:添加事務管理
@Transactional(rollbackFor = Exception.class)
public Integer saveNode(List<RuleNodeDTO> judgeGroup, String ruleGroupId, List<MonRuleDefinitions> ruleDefinitions) {
// ... 原有代碼
}
通過以上優(yōu)化,可以進一步提高規(guī)則引擎的性能和可靠性。
到此這篇關于基于Java實現(xiàn)的簡易規(guī)則引擎的文章就介紹到這了,更多相關Java 規(guī)則引擎內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
使用WebUploader實現(xiàn)分片斷點上傳文件功能(二)
這篇文章主要為大家詳細介紹了使用WebUploader實現(xiàn)分片斷點上傳文件功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-01-01
關于idea引入spring boot <parent></parent>父依賴標紅問題
這篇文章主要介紹了idea引入spring boot <parent></parent>父依賴標紅問題,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-10-10
通過maven給沒有pom文件的jar包生成pom文件,maven項目引入本地jar包方式
這篇文章主要介紹了通過maven給沒有pom文件的jar包生成pom文件,maven項目引入本地jar包方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-05-05

