Java 程序员第 42 阶段08:文档智能解析审核,大模型实现合同摘要与合规校验
目录
1. [章节概述](#1-章节概述) 2. [Drools规则引擎架构](#2-drools规则引擎架构) 3. [Drools集成配置](#3-drools集成配置) 4. [规则文件设计与编写](#4-规则文件设计与编写) 5. [规则服务实现](#5-规则服务实现) 6. [冲突检测与解决](#6-冲突检测与解决) 7. [实际代码示例](#7-实际代码示例) 8. [最佳实践](#8-最佳实践) 9. [本章小结](#9-本章小结)
1. 章节概述
1.1 规则引擎简介
Drools是Java生态中最成熟的开源规则引擎,基于Rete算法实现高效的模式匹配。在合同智能审核平台中,规则引擎承担以下核心职责:
┌─────────────────────────────────────────────────────────────────┐ │ 规则引擎核心职责 │ ├─────────────────────────────────────────────────────────────────┤ │ 1. 合规性校验 - 合同条款是否符合法规和企业政策 │ │ 2. 风险识别 - 识别高风险条款并触发预警 │ │ 3. 完整性检查 - 确保必要条款完备无遗漏 │ │ 4. 逻辑冲突检测 - 发现条款间的逻辑矛盾 │ └─────────────────────────────────────────────────────────────────┘
1.2 本章学习目标
┌─────────────────────────────────────────────────────────────────┐ │ 学习目标 │ ├─────────────────────────────────────────────────────────────────┤ │ 1. 理解Drools规则引擎的核心概念和架构 │ │ 2. 掌握Drools与Spring Boot的集成配置 │ │ 3. 设计并编写DRL规则文件 │ │ 4. 实现规则服务与审核流程集成 │ │ 5. 掌握规则冲突检测与解决策略 │ └─────────────────────────────────────────────────────────────────┘
1.3 业务场景
在合同审核流程中引入规则引擎后,整个审核流程变为:
合同录入 → 规则引擎初筛 → LLM智能摘要 → 规则深度校验 → 人工复核 → 审核通过 ↑ │ └──────── 规则触发预警 ←──────────────┘
2. Drools规则引擎架构
2.1 整体架构
┌─────────────────────────────────────────────────────────────────┐ │ Drools 规则引擎架构 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ Application │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ Stateless │ │ Stateful │ │ Rule │ │ │ │ │ │ Session │ │ Session │ │ Management │ │ │ │ │ └──────┬──────┘ └──────┬──────┘ └─────────────┘ │ │ │ └─────────┼──────────────────┼───────────────────────────────┘ │ │ │ │ │ │ ┌─────────▼──────────────────▼───────────────────────────────┐ │ │ │ KieContainer │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ KieBase │◄───│ KieModule │───►│ KieSession │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └───────────────────────────────────────────────────────────┘ │ │ │ │ │ ┌───────────────────────────▼───────────────────────────────┐ │ │ │ Inference Engine │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ │ │ Pattern │ │ Rule │ │ Agenda │ │ Working │ │ │ │ │ │ Matching │ │Evaluator │ │ Control │ │ Memory │ │ │ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │ └───────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘
2.2 核心概念
#### 2.2.1 事实(Fact)
事实是规则引擎操作的数据对象,在合同审核场景中:
// 合同事实 public class ContractFact { private String contractId; private String contractType; private BigDecimal amount; private String currency; private LocalDate signingDate; private List<PartyFact> parties; private List<ClauseFact> clauses; // ... getters and setters } // 条款事实 public class ClauseFact { private Integer number; private String type; private String content; private String riskLevel; private boolean isMandatory; // ... getters and setters }
#### 2.2.2 规则(Rule)
规则由条件(When)和动作(Then)组成:
rule "超过100万的合同需要法务审批" salience 100 activation-group "approval-group" when $contract : ContractFact(amount > 1000000) then $contract.addApprovalRequirement("NEED_LEGAL_REVIEW"); System.out.println("Contract " + $contract.getContractId() + " requires legal review"); end
2.3 Kie体系结构
Drools使用Kie(Knowledge Is Everything)体系结构管理规则生命周期:
KieModule (kmodule.xml) ├── KieBase (编译后的规则) │ └── KiePackage (规则包) └── KieSession (运行时会话) └── Working Memory (工作内存)
3. Drools集成配置
3.1 Maven依赖
<dependencies> <!-- Drools Core --> <dependency> <groupId>org.drools</groupId> <artifactId>drools-core</artifactId> <version>${drools.version}</version> </dependency> <!-- Drools Compiler --> <dependency> <groupId>org.drools</groupId> <artifactId>drools-compiler</artifactId> <version>${drools.version}</version> </dependency> <!-- Drools Decision Tables --> <dependency> <groupId>org.drools</groupId> <artifactId>drools-decisiontables</artifactId> <version>${drools.version}</version> </dependency> <!-- Drools Spring Integration --> <dependency> <groupId>org.drools</groupId> <artifactId>drools-spring</artifactId> <version>${drools.version}</version> </dependency> </dependencies>
3.2 kmodule.xml配置
<?xml version="1.0" encoding="UTF-8"?> <kmodule xmlns="http://www.drools.org/2006/10/kmodule"> <!-- 审慎型规则库 --> <kbase name="prudentRules" packages="rules.prudent"> <ksession name="prudentSession" type="stateful"/> </kbase> <!-- 标准规则库 --> <kbase name="standardRules" packages="rules.standard"> <ksession name="standardSession" type="stateful"/> </kbase> <!-- 快速规则库(仅简单校验) --> <kbase name="fastRules" packages="rules.fast"> <ksession name="fastSession" type="stateless"/> </kbase> </kmodule>
3.3 Spring配置类
@Configuration @Slf4j public class DroolsConfig { private static final String KMODULE_PATH = "classpath:/drools/kmodule.xml"; @Bean public KieContainer kieContainer() { log.info("Initializing KieContainer"); KieServices kieServices = KieServices.Factory.get(); // 从classpath加载kmodule.xml KieModule kieModule = kieServices.getResources() .newClassPathResource("drools/kmodule.xml") .getZip(); KieContainer kieContainer = kieServices.newKieContainer( kieServices.getRepository().getDefaultReleaseId()); log.info("KieContainer initialized successfully"); return kieContainer; } @Bean public KieBase prudentKieBase(KieContainer kieContainer) { return kieContainer.getKieBase("prudentRules"); } @Bean public KieBase standardKieBase(KieContainer kieContainer) { return kieContainer.getKieBase("standardRules"); } @Bean public KieSession prudentKieSession(KieBase prudentKieBase) { return prudentKieBase.newKieSession(); } @Bean public KieSession standardKieSession(KieBase standardKieBase) { return standardKieBase.newKieSession(); } @Bean public StatelessKieSession fastKieSession(KieBase fastKieBase) { return fastKieBase.newStatelessKieSession(); } }
3.4 动态规则加载
@Configuration public class DynamicDroolsConfig { private final KieServices kieServices = KieServices.Factory.get(); private volatile KieModule currentModule; @PostConstruct public void init() { reloadRules(); } public void reloadRules() { log.info("Reloading Drools rules..."); // 从数据库或文件系统加载规则 String drlContent = loadRulesFromDatabase(); KieModule newModule = createKieModule(drlContent); synchronized (this) { currentModule = newModule; } log.info("Rules reloaded successfully"); } private String loadRulesFromDatabase() { // 实现从数据库加载规则的逻辑 return ruleRepository.findActiveRulesAsDrl(); } private KieModule createKieModule(String drlContent) { KieModuleModel kieModuleModel = kieServices.newKieModuleModel(); KieBaseModel kieBaseModel = kieModuleModel.newKieBaseModel("dynamicRules") .addPackage("rules.dynamic"); kieBaseModel.newKieSessionModel("dynamicSession") .setType(KieSessionModel.KieSessionType.STATEFUL); KieFileSystem kfs = kieServices.newKieFileSystem(); kfs.writeKModuleXML(kieModuleModel.toXML()); kfs.write("src/main/resources/rules/dynamic/Rule.drl", drlContent); KieBuilder kieBuilder = kieServices.newKieBuilder(kfs); kieBuilder.buildAll(); if (kieBuilder.getResults().hasMessages(Message.Level.ERROR)) { throw new IllegalStateException( "Build errors: " + kieBuilder.getResults()); } return kieBuilder.getKieModule(); } public KieSession newDynamicSession() { if (currentModule == null) { throw new IllegalStateException("KieModule not initialized"); } return kieServices.newKieContainer(currentModule.getReleaseId()) .getKieBase("dynamicRules") .newKieSession(); } }
4. 规则文件设计与编写
4.1 DRL规则语法

#### 4.1.1 基本结构
package com.docanalysis.rules.contract import com.docanalysis.fact.ContractFact import com.docanalysis.fact.ClauseFact import com.docanalysis.fact.ViolationFact import function com.docanalysis.rules.helpers.DateHelper.after // 全局变量 global java.util.List violations; global java.util.Logger logger;
#### 4.1.2 规则定义
rule "金额超限需要审批" salience 100 activation-group "approval-requirements" agenda-group "amount-check" when $contract : ContractFact(amount > 1000000) then ViolationFact violation = new ViolationFact(); violation.setRuleId("RULE_001"); violation.setSeverity("HIGH"); violation.setMessage("合同金额超过100万,需要法务审批"); violation.setContractId($contract.getContractId()); violations.add(violation); logger.info("Rule triggered: Amount exceeds limit for contract {}", $contract.getContractId()); end
4.2 合同合规规则库
#### 4.2.1 合同基础规则
package com.docanalysis.rules.contract import com.docanalysis.fact.ContractFact; import com.docanalysis.fact.PartyFact; import com.docanalysis.fact.ViolationFact; import java.math.BigDecimal; import java.util.List; import java.util.ArrayList; global List violations; global org.slf4j.Logger logger; // 规则1: 合同必须有明确的甲方乙方 rule "合同方信息完整性校验" salience 90 when $contract : ContractFact() not (PartyFact(this memberOf $contract.getParties(), type == "PARTY_A")) then ViolationFact v = new ViolationFact(); v.setRuleId("RULE_BASIC_001"); v.setSeverity("HIGH"); v.setMessage("合同缺少甲方信息"); v.setContractId($contract.getContractId()); violations.add(v); end rule "合同必须有金额条款" salience 85 when $contract : ContractFact(amount == null || amount.compareTo(BigDecimal.ZERO) == 0) then ViolationFact v = new ViolationFact(); v.setRuleId("RULE_BASIC_002"); v.setSeverity("MEDIUM"); v.setMessage("合同缺少金额条款或金额为零"); v.setContractId($contract.getContractId()); violations.add(v); end
#### 4.2.2 金额相关规则
// 规则2: 大额合同特殊审批 rule "大额合同法务审批" salience 100 activation-group "approval-group" no-loop true when $contract : ContractFact(amount > new BigDecimal("1000000")) then ViolationFact v = new ViolationFact(); v.setRuleId("RULE_AMOUNT_001"); v.setSeverity("HIGH"); v.setMessage("合同金额超过100万,需要法务审批"); v.setContractId($contract.getContractId()); v.addRecommendation("请联系法务部门进行合同审批"); violations.add(v); logger.info("Large amount contract detected: {}", $contract.getContractId()); end rule "超大额合同CEO审批" salience 110 activation-group "approval-group" no-loop true when $contract : ContractFact(amount > new BigDecimal("5000000")) then ViolationFact v = new ViolationFact(); v.setRuleId("RULE_AMOUNT_002"); v.setSeverity("CRITICAL"); v.setMessage("合同金额超过500万,需要CEO审批"); v.setContractId($contract.getContractId()); v.addRecommendation("请联系CEO进行合同审批"); violations.add(v); end
#### 4.2.3 期限相关规则
// 规则3: 合同期限校验 rule "合同期限合理性检查" salience 80 when $contract : ContractFact( startDate != null, endDate != null, endDate.isBefore(startDate) ) then ViolationFact v = new ViolationFact(); v.setRuleId("RULE_TERM_001"); v.setSeverity("HIGH"); v.setMessage("合同结束日期早于开始日期"); v.setContractId($contract.getContractId()); violations.add(v); end rule "超长合同期限检查" salience 75 when $contract : ContractFact( startDate != null, endDate != null, java.time.temporal.ChronoUnit.DAYS.between(startDate, endDate) > 3650 ) then ViolationFact v = new ViolationFact(); v.setRuleId("RULE_TERM_002"); v.setSeverity("MEDIUM"); v.setMessage("合同期限超过10年,请确认是否合理"); v.setContractId($contract.getContractId()); violations.add(v); end
#### 4.2.4 条款类型规则
// 规则4: 必须包含条款检查 rule "必须包含保密条款" salience 70 when $contract : ContractFact() not (ClauseFact( this memberOf $contract.getClauses(), type == "CONFIDENTIALITY" )) then ViolationFact v = new ViolationFact(); v.setRuleId("RULE_CLAUSE_001"); v.setSeverity("MEDIUM"); v.setMessage("合同缺少保密条款"); v.setContractId($contract.getContractId()); v.addRecommendation("建议添加标准保密条款"); violations.add(v); end rule "必须包含违约条款" salience 70 when $contract : ContractFact() not (ClauseFact( this memberOf $contract.getClauses(), type == "BREACH_OF_CONTRACT" )) then ViolationFact v = new ViolationFact(); v.setRuleId("RULE_CLAUSE_002"); v.setSeverity("HIGH"); v.setMessage("合同缺少违约条款"); v.setContractId($contract.getContractId()); v.addRecommendation("建议添加违约责任条款"); violations.add(v); end
4.3 决策表规则
对于简单的规则,可以使用Excel决策表:
// contract_rules.xlsx | ruleId | severity | condition | message | recommendation | |---------------|-----------|------------------------------------|----------------------------|-------------------------------| | RULE_EXT_001 | HIGH | contractType == "LOAN" && amount > 500000 | 大额借贷合同需要公证 | 建议进行合同公证 | | RULE_EXT_002 | MEDIUM | contractType == "SERVICE" && !hasPaymentClause | 服务合同缺少付款条款 | 建议添加付款方式和时间条款 | | RULE_EXT_003 | HIGH | !hasForceMajeureClause | 缺少不可抗力条款 | 建议添加不可抗力条款 |
5. 规则服务实现
5.1 事实对象定义
@Data @Builder public class ContractFact { private String contractId; private String contractType; private String contractNumber; private BigDecimal amount; private String currency; @JsonFormat(pattern = "yyyy-MM-dd") private LocalDate signingDate; @JsonFormat(pattern = "yyyy-MM-dd") private LocalDate startDate; @JsonFormat(pattern = "yyyy-MM-dd") private LocalDate endDate; private List<PartyFact> parties; private List<ClauseFact> clauses; private List<String> approvalRequirements; private List<String> recommendations; public void addApprovalRequirement(String requirement) { if (this.approvalRequirements == null) { this.approvalRequirements = new ArrayList<>(); } this.approvalRequirements.add(requirement); } public void addRecommendation(String recommendation) { if (this.recommendations == null) { this.recommendations = new ArrayList<>(); } this.recommendations.add(recommendation); } } @Data @Builder public class PartyFact { private String name; private String type; // PARTY_A, PARTY_B private String address; private String representative; private String contactInfo; } @Data @Builder public class ClauseFact { private Integer number; private String type; private String title; private String content; private String riskLevel; // LOW, MEDIUM, HIGH private boolean isMandatory; } @Data @Builder public class ViolationFact { private String ruleId; private String severity; // LOW, MEDIUM, HIGH, CRITICAL private String message; private String contractId; private List<String> recommendations; public void addRecommendation(String recommendation) { if (this.recommendations == null) { this.recommendations = new ArrayList<>(); } this.recommendations.add(recommendation); } }
5.2 规则服务实现
@Service @Slf4j public class ContractRuleService { private final KieContainer kieContainer; private final RuleEventListener ruleEventListener; public ContractRuleService(KieContainer kieContainer) { this.kieContainer = kieContainer; this.ruleEventListener = new RuleEventListener(); } /** * 执行合同合规检查 */ public RuleEvaluationResult evaluate(ContractFact contract) { log.info("Starting rule evaluation for contract: {}", contract.getContractId()); List<ViolationFact> violations = new ArrayList<>(); List<String> firedRules = new ArrayList<>(); KieSession kieSession = kieContainer.newKieSession("standardSession"); try { // 注册事件监听器 kieSession.addEventListener(ruleEventListener); kieSession.addEventListener(new AgendaEventListener() { @Override public void matchCreated(org.kie.api.event.rule.MatchCreatedEvent event) { firedRules.add(event.getMatch().getRule().getName()); log.debug("Rule fired: {}", event.getMatch().getRule().getName()); } }); // 设置全局变量 kieSession.setGlobal("violations", violations); kieSession.setGlobal("logger", log); // 插入事实 kieSession.insert(contract); // 插入各方信息 if (contract.getParties() != null) { for (PartyFact party : contract.getParties()) { kieSession.insert(party); } } // 插入条款信息 if (contract.getClauses() != null) { for (ClauseFact clause : contract.getClauses()) { kieSession.insert(clause); } } // 执行规则 log.debug("Firing all rules..."); kieSession.fireAllRules(); log.info("Rule evaluation completed. Contract: {}, Violations: {}", contract.getContractId(), violations.size()); return RuleEvaluationResult.builder() .contractId(contract.getContractId()) .violations(violations) .firedRules(firedRules) .passed(violations.isEmpty()) .build(); } finally { kieSession.dispose(); } } /** * 快速规则检查(仅关键规则) */ public RuleEvaluationResult quickCheck(ContractFact contract) { log.info("Starting quick rule check for contract: {}", contract.getContractId()); List<ViolationFact> violations = new ArrayList<>(); StatelessKieSession kieSession = kieContainer.newKieSession("fastSession"); kieSession.setGlobal("violations", violations); kieSession.setGlobal("logger", log); try { kieSession.execute(contract); // 过滤仅保留HIGH和CRITICAL级别的违规 List<ViolationFact> criticalViolations = violations.stream() .filter(v -> "HIGH".equals(v.getSeverity()) || "CRITICAL".equals(v.getSeverity())) .collect(Collectors.toList()); return RuleEvaluationResult.builder() .contractId(contract.getContractId()) .violations(criticalViolations) .passed(criticalViolations.isEmpty()) .build(); } finally { kieSession.dispose(); } } } @Data @Builder public class RuleEvaluationResult { private String contractId; private List<ViolationFact> violations; private List<String> firedRules; private boolean passed; public String getOverallSeverity() { if (violations == null || violations.isEmpty()) { return "NONE"; } if (violations.stream().anyMatch(v -> "CRITICAL".equals(v.getSeverity()))) { return "CRITICAL"; } if (violations.stream().anyMatch(v -> "HIGH".equals(v.getSeverity()))) { return "HIGH"; } if (violations.stream().anyMatch(v -> "MEDIUM".equals(v.getSeverity()))) { return "MEDIUM"; } return "LOW"; } }
5.3 规则事件监听
public class RuleEventListener implements RuleRuntimeEventListener { private static final Logger log = LoggerFactory.getLogger(RuleEventListener.class); @Override public void objectInserted(org.kie.api.event.rule.ObjectInsertedEvent event) { log.debug("Object inserted: {}", event.getFact().toString()); } @Override public void objectUpdated(org.kie.api.event.rule.ObjectUpdatedEvent event) { log.debug("Object updated: {}", event.getFact().toString()); } @Override public void objectDeleted(org.kie.api.event.rule.ObjectDeletedEvent event) { log.debug("Object deleted: {}", event.getFact().toString()); } @Override public void matchCreated(org.kie.api.event.rule.MatchCreatedEvent event) { log.debug("Match created for rule: {}", event.getMatch().getRule().getName()); } @Override public void matchCancelled(org.kie.api.event.rule.MatchCancelledEvent event) { log.debug("Match cancelled for rule: {}", event.getMatch().getRule().getName()); } @Override public void beforeMatchFired(org.kie.api.event.rule.BeforeMatchFiredEvent event) { log.debug("About to fire rule: {}", event.getMatch().getRule().getName()); } @Override public void afterMatchFired(org.kie.api.event.rule.AfterMatchFiredEvent event) { log.info("Rule fired: {}", event.getMatch().getRule().getName()); } }
6. 冲突检测与解决

6.1 冲突类型
┌─────────────────────────────────────────────────────────────────┐ │ 规则冲突类型 │ ├─────────────────────────────────────────────────────────────────┤ │ 1. 优先级冲突 - 相同Salience的规则同时触发 │ │ 2. 互斥冲突 - 规则条件互斥但可能同时满足 │ │ 3. 冗余冲突 - 一条规则是另一条的特例 │ │ 4. 依赖冲突 - 规则执行顺序影响结果 │ └─────────────────────────────────────────────────────────────────┘
6.2 冲突解决策略
@Configuration public class ConflictResolutionConfig { /** * 配置激活组实现互斥 */ @Bean public AgendaEventListener agendaEventListener() { return new AgendaEventListener() { private final Set<String> activatedRules = new HashSet<>(); @Override public void matchCreated(MatchCreatedEvent event) { String ruleName = event.getMatch().getRule().getName(); ActivationGroup ag = event.getMatch().getRule().getActivationGroup(); if (ag != null && activatedRules.contains(ag.getName())) { log.warn("Activation group '{}' already activated. " + "Cancelling rule: {}", ag.getName(), ruleName); event.getMatch().cancel(); } } @Override public void afterMatchFired(AfterMatchFiredEvent event) { String ruleName = event.getMatch().getRule().getName(); ActivationGroup ag = event.getMatch().getRule().getActivationGroup(); if (ag != null) { activatedRules.add(ag.getName()); } log.info("Rule fired successfully: {}", ruleName); } }; } }
6.3 规则依赖分析
@Service @Slf4j public class RuleDependencyAnalyzer { /** * 分析规则间的依赖关系,检测潜在冲突 */ public List<RuleConflict> analyzeConflicts(List<RuleDefinition> rules) { List<RuleConflict> conflicts = new ArrayList<>(); for (int i = 0; i < rules.size(); i++) { for (int j = i + 1; j < rules.size(); j++) { RuleDefinition rule1 = rules.get(i); RuleDefinition rule2 = rules.get(j); // 检测优先级冲突 if (rule1.getSalience() == rule2.getSalience()) { if (hasOverlappingConditions(rule1, rule2)) { conflicts.add(new RuleConflict( "PRIORITY_CONFLICT", rule1.getName(), rule2.getName(), "相同优先级和重叠条件" )); } } // 检测互斥冲突 if (areMutuallyExclusive(rule1, rule2)) { conflicts.add(new RuleConflict( "MUTUAL_EXCLUSION", rule1.getName(), rule2.getName(), "规则条件互斥但可能在同一事实下触发" )); } } } return conflicts; } private boolean hasOverlappingConditions(RuleDefinition r1, RuleDefinition r2) { // 简化实现:实际需要解析DRL条件 return true; } private boolean areMutuallyExclusive(RuleDefinition r1, RuleDefinition r2) { // 简化实现:实际需要逻辑分析 return false; } } public record RuleConflict( String type, String rule1, String rule2, String description ) {}
7. 实际代码示例

7.1 Drools配置完整实现
package com.docanalysis.rules.config; import org.kie.api.KieServices; import org.kie.api.builder.KieBuilder; import org.kie.api.builder.KieFileSystem; import org.kie.api.builder.KieModule; import org.kie.api.builder.ReleaseId; import org.kie.api.builder.Results; import org.kie.api.io.ResourceType; import org.kie.api.runtime.KieContainer; import org.kie.api.runtime.KieSession; import org.kie.api.runtime.StatelessKieSession; import org.kie.api.runtime.conf.AccumulateFunction; import org.kie.internal.io.ResourceFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; /** * Drools规则引擎配置 */ @Configuration public class DroolsConfiguration { private static final Logger log = LoggerFactory.getLogger(DroolsConfiguration.class); @Value("${drools.rule.path:rules}") private String rulePath; @Value("${drools.kbase.name:defaultKieBase}") private String kbaseName; @Bean public KieServices kieServices() { return KieServices.Factory.get(); } @Bean public KieContainer kieContainer(KieServices kieServices) { log.info("Building KieContainer from rule path: {}", rulePath); KieModule kieModule = buildKieModule(kieServices); KieContainer kieContainer = kieServices.newKieContainer(kieModule.getReleaseId()); log.info("KieContainer built successfully. ReleaseId: {}", kieModule.getReleaseId()); return kieContainer; } private KieModule buildKieModule(KieServices kieServices) { KieFileSystem kfs = kieServices.newKieFileSystem(); // 加载规则文件 loadRuleFiles(kfs); // 构建kmodule.xml String kmoduleXML = createKmoduleXML(); kfs.writeKModuleXML(kmoduleXML); // 编译 KieBuilder kieBuilder = kieServices.newKieBuilder(kfs); kieBuilder.buildAll(); Results results = kieBuilder.getResults(); if (results.hasMessages(org.kie.api.builder.Message.Level.ERROR)) { log.error("KieBuilder errors: {}", results.getMessages()); throw new IllegalStateException("Failed to build KieModule: " + results); } return kieBuilder.getKieModule(); } private void loadRuleFiles(KieFileSystem kfs) { try { Path rulesDir = Path.of(rulePath); if (Files.exists(rulesDir)) { Files.walk(rulesDir) .filter(Files::isRegularFile) .filter(p -> p.toString().endsWith(".drl")) .forEach(p -> { try { String content = Files.readString(p); String relativePath = rulesDir.relativize(p).toString(); kfs.write("src/main/resources/rules/" + relativePath, content); log.info("Loaded rule file: {}", relativePath); } catch (IOException e) { log.error("Failed to load rule file: {}", p, e); } }); } else { log.warn("Rule path does not exist: {}. Using default rules.", rulePath); loadDefaultRules(kfs); } } catch (IOException e) { log.error("Failed to walk rule directory", e); loadDefaultRules(kfs); } } private void loadDefaultRules(KieFileSystem kfs) { // 默认规则 kfs.write("src/main/resources/rules/ContractBasicRules.drl", getDefaultRules()); } private String createKmoduleXML() { return """ <?xml version="1.0" encoding="UTF-8"?> <kmodule xmlns="http://www.drools.org/2006/10/kmodule"> <kbase name="%s" packages="rules"> <ksession name="defaultStatelessSession" type="stateless"/> <ksession name="defaultStatefulSession" type="stateful"/> </kbase> </kmodule> """.formatted(kbaseName); } private String getDefaultRules() { return """ package com.docanalysis.rules import com.docanalysis.fact.ContractFact import com.docanalysis.fact.ViolationFact import java.math.BigDecimal import java.util.List global List violations rule "Default Rule: Check contract amount" salience 100 when $contract : ContractFact(amount > new BigDecimal("1000000")) then ViolationFact v = new ViolationFact(); v.setRuleId("DEFAULT_001"); v.setSeverity("HIGH"); v.setMessage("Contract amount exceeds limit"); v.setContractId($contract.getContractId()); violations.add(v); end """; } @Bean public StatelessKieSession statelessKieSession(KieContainer kieContainer) { return kieContainer.newStatelessKieSession("defaultStatelessSession"); } @Bean public KieSession statefulKieSession(KieContainer kieContainer) { return kieContainer.newKieSession("defaultStatefulSession"); } }
7.2 规则服务完整实现
package com.docanalysis.rules.service; import com.docanalysis.fact.ClauseFact; import com.docanalysis.fact.ContractFact; import com.docanalysis.fact.PartyFact; import com.docanalysis.fact.ViolationFact; import org.kie.api.KieServices; import org.kie.api.runtime.KieContainer; import org.kie.api.runtime.KieSession; import org.kie.api.runtime.StatelessKieSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; /** * 合同规则评估服务 */ @Service public class ContractRuleEvaluationService { private static final Logger log = LoggerFactory.getLogger( ContractRuleEvaluationService.class); private final KieContainer kieContainer; private final KieServices kieServices; public ContractRuleEvaluationService(KieContainer kieContainer) { this.kieContainer = kieContainer; this.kieServices = KieServices.Factory.get(); } /** * 评估合同合规性 */ public ContractRuleResult evaluate(ContractFact contract) { log.info("Evaluating contract: {}", contract.getContractId()); long startTime = System.currentTimeMillis(); List<ViolationFact> violations = new ArrayList<>(); List<String> firedRules = new ArrayList<>(); // 创建有状态会话 KieSession kieSession = kieContainer.newKieSession("defaultStatefulSession"); try { // 设置全局变量 kieSession.setGlobal("violations", violations); kieSession.setGlobal("logger", log); // 插入合同事实 kieSession.insert(contract); // 插入各方信息 if (contract.getParties() != null) { for (PartyFact party : contract.getParties()) { kieSession.insert(party); } } // 插入条款信息 if (contract.getClauses() != null) { for (ClauseFact clause : contract.getClauses()) { kieSession.insert(clause); } } // 添加规则事件监听 kieSession.addEventListener(new RuleEventListener() { @Override public void afterRuleFired(String ruleName) { firedRules.add(ruleName); } }); // 执行规则 int rulesFired = kieSession.fireAllRules(); log.info("Contract {} evaluated. {} rules fired, {} violations found", contract.getContractId(), rulesFired, violations.size()); long duration = System.currentTimeMillis() - startTime; return ContractRuleResult.builder() .contractId(contract.getContractId()) .violations(violations) .firedRules(firedRules) .rulesFiredCount(rulesFired) .passed(violations.isEmpty()) .evaluationTimeMs(duration) .overallSeverity(calculateOverallSeverity(violations)) .approvalRequirements(collectApprovalRequirements(violations)) .build(); } finally { kieSession.dispose(); } } /** * 快速检查(仅关键规则) */ public ContractRuleResult quickCheck(ContractFact contract) { log.debug("Quick checking contract: {}", contract.getContractId()); List<ViolationFact> violations = new ArrayList<>(); StatelessKieSession kieSession = kieContainer.newKieSession("defaultStatelessSession"); kieSession.setGlobal("violations", violations); kieSession.setGlobal("logger", log); try { kieSession.execute(contract); // 只返回严重违规 List<ViolationFact> criticalViolations = violations.stream() .filter(v -> "HIGH".equals(v.getSeverity()) || "CRITICAL".equals(v.getSeverity())) .toList(); return ContractRuleResult.builder() .contractId(contract.getContractId()) .violations(criticalViolations) .passed(criticalViolations.isEmpty()) .overallSeverity(calculateOverallSeverity(criticalViolations)) .build(); } finally { kieSession.dispose(); } } private String calculateOverallSeverity(List<ViolationFact> violations) { if (violations.isEmpty()) { return "NONE"; } boolean hasCritical = violations.stream() .anyMatch(v -> "CRITICAL".equals(v.getSeverity())); if (hasCritical) { return "CRITICAL"; } boolean hasHigh = violations.stream() .anyMatch(v -> "HIGH".equals(v.getSeverity())); if (hasHigh) { return "HIGH"; } boolean hasMedium = violations.stream() .anyMatch(v -> "MEDIUM".equals(v.getSeverity())); if (hasMedium) { return "MEDIUM"; } return "LOW"; } private List<String> collectApprovalRequirements(List<ViolationFact> violations) { return violations.stream() .filter(v -> v.getRecommendations() != null) .flatMap(v -> v.getRecommendations().stream()) .distinct() .toList(); } /** * 规则事件监听器接口 */ public interface RuleEventListener { default void afterRuleFired(String ruleName) {} default void beforeRuleFired(String ruleName) {} } } @Data @Builder public class ContractRuleResult { private String contractId; private List<ViolationFact> violations; private List<String> firedRules; private int rulesFiredCount; private boolean passed; private long evaluationTimeMs; private String overallSeverity; private List<String> approvalRequirements; public int getViolationCount() { return violations != null ? violations.size() : 0; } public int getHighSeverityCount() { if (violations == null) return 0; return (int) violations.stream() .filter(v -> "HIGH".equals(v.getSeverity()) || "CRITICAL".equals(v.getSeverity())) .count(); } }
7.3 规则文件示例
package com.docanalysis.rules.contract import com.docanalysis.fact.ContractFact import com.docanalysis.fact.PartyFact import com.docanalysis.fact.ClauseFact import com.docanalysis.fact.ViolationFact import java.math.BigDecimal import java.util.List import java.time.LocalDate import java.time.temporal.ChronoUnit global List violations global org.slf4j.Logger logger // ==================== 基础规则 ==================== rule "检查合同方信息完整性" salience 90 when $contract : ContractFact() not (PartyFact(this memberOf $contract.getParties(), type == "PARTY_A")) then ViolationFact v = new ViolationFact(); v.setRuleId("BASIC_001"); v.setSeverity("HIGH"); v.setMessage("合同缺少甲方信息"); v.setContractId($contract.getContractId()); v.addRecommendation("请补充甲方信息"); violations.add(v); logger.warn("Contract {} missing PARTY_A", $contract.getContractId()); end rule "检查金额条款存在" salience 85 when $contract : ContractFact(amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) then ViolationFact v = new ViolationFact(); v.setRuleId("BASIC_002"); v.setSeverity("MEDIUM"); v.setMessage("合同缺少有效金额条款"); v.setContractId($contract.getContractId()); v.addRecommendation("请添加金额条款"); violations.add(v); end // ==================== 金额规则 ==================== rule "大额合同法务审批" salience 100 activation-group "approval-group" no-loop true when $contract : ContractFact(amount > new BigDecimal("1000000")) then ViolationFact v = new ViolationFact(); v.setRuleId("AMOUNT_001"); v.setSeverity("HIGH"); v.setMessage("合同金额超过100万,需要法务审批"); v.setContractId($contract.getContractId()); v.addRecommendation("请提交法务部门审批"); violations.add(v); logger.info("Contract {} requires legal review", $contract.getContractId()); end rule "超大额合同CEO审批" salience 110 activation-group "approval-group" no-loop true when $contract : ContractFact(amount > new BigDecimal("5000000")) then ViolationFact v = new ViolationFact(); v.setRuleId("AMOUNT_002"); v.setSeverity("CRITICAL"); v.setMessage("合同金额超过500万,需要CEO审批"); v.setContractId($contract.getContractId()); v.addRecommendation("请提交CEO审批"); violations.add(v); end // ==================== 期限规则 ==================== rule "合同期限校验" salience 80 when $contract : ContractFact( startDate != null, endDate != null, endDate.isBefore(startDate) ) then ViolationFact v = new ViolationFact(); v.setRuleId("TERM_001"); v.setSeverity("HIGH"); v.setMessage("合同结束日期早于开始日期"); v.setContractId($contract.getContractId()); violations.add(v); end rule "超长合同期限提醒" salience 70 when $contract : ContractFact( startDate != null, endDate != null, ChronoUnit.DAYS.between(startDate, endDate) > 3650 ) then ViolationFact v = new ViolationFact(); v.setRuleId("TERM_002"); v.setSeverity("MEDIUM"); v.setMessage("合同期限超过10年"); v.setContractId($contract.getContractId()); v.addRecommendation("请确认超长合同期限的合理性"); violations.add(v); end // ==================== 条款规则 ==================== rule "必须包含保密条款" salience 75 when $contract : ContractFact() not (ClauseFact( type == "CONFIDENTIALITY", this memberOf $contract.getClauses() )) then ViolationFact v = new ViolationFact(); v.setRuleId("CLAUSE_001"); v.setSeverity("MEDIUM"); v.setMessage("合同缺少保密条款"); v.setContractId($contract.getContractId()); v.addRecommendation("建议添加标准保密条款"); violations.add(v); end rule "必须包含违约条款" salience 75 when $contract : ContractFact() not (ClauseFact( type == "BREACH_OF_CONTRACT", this memberOf $contract.getClauses() )) then ViolationFact v = new ViolationFact(); v.setRuleId("CLAUSE_002"); v.setSeverity("HIGH"); v.setMessage("合同缺少违约条款"); v.setContractId($contract.getContractId()); v.addRecommendation("建议添加违约责任条款"); violations.add(v); end rule "必须包含争议解决条款" salience 75 when $contract : ContractFact() not (ClauseFact( type == "DISPUTE_RESOLUTION", this memberOf $contract.getClauses() )) then ViolationFact v = new ViolationFact(); v.setRuleId("CLAUSE_003"); v.setSeverity("MEDIUM"); v.setMessage("合同缺少争议解决条款"); v.setContractId($contract.getContractId()); v.addRecommendation("建议添加仲裁或诉讼条款"); violations.add(v); end
8. 最佳实践
8.1 规则设计原则
┌─────────────────────────────────────────────────────────────────┐ │ 规则设计原则 │ ├─────────────────────────────────────────────────────────────────┤ │ 1. 单一职责 - 每条规则只检查一个条件 │ │ 2. 清晰命名 - 规则名应表达其目的 │ │ 3. 合适优先级 - 使用Salience控制执行顺序 │ │ 4. 避免硬编码 - 使用全局变量或事实数据 │ │ 5. 完整日志 - 记录规则触发和执行情况 │ └─────────────────────────────────────────────────────────────────┘
8.2 性能优化
@Bean public KieSession optimizedKieSession(KieBase kieBase) { KieSessionConfiguration config = kieServices.newKieSessionConfiguration(); // 配置会话参数 config.setOption(new ClockTypeOption(ClockType.PSEUDO_CLOCK)); // 配置事件处理模式 config.setOption(new MutatisThresholdOption(10)); return kieBase.newKieSession(config, null); }
8.3 规则版本管理
@Service @Slf4j public class RuleVersionService { @Value("${drools.rule.version:1.0.0}") private String currentVersion; public void activateVersion(String version) { log.info("Activating rule version: {}", version); // 1. 验证新版本规则 validateRules(version); // 2. 切换活跃版本 ruleRepository.setActiveVersion(version); // 3. 重新加载KieContainer refreshKieContainer(); log.info("Rule version {} activated", version); } private void validateRules(String version) { // 实现规则验证逻辑 } private void refreshKieContainer() { // 实现容器刷新逻辑 } }
9. 本章小结
9.1 核心要点回顾
┌─────────────────────────────────────────────────────────────────┐ │ 本章核心要点 │ ├─────────────────────────────────────────────────────────────────┤ │ 1. Drools架构:KieContainer → KieBase → KieSession → WorkingMemory │ │ 2. 规则编写:DRL语法、when-then结构、salience、activation-group │ │ 3. Spring集成:KieContainer配置、动态规则加载 │ │ 4. 规则服务:Fact插入、规则执行、结果收集 │ │ 5. 冲突解决:优先级、激活组、议程控制 │ └─────────────────────────────────────────────────────────────────┘
9.2 实战应用
通过本章学习,您可以在合同智能审核平台中实现:
1. **合规性自动检查** - 替代人工逐条核对法规条款 2. **风险自动识别** - 高亮显示需要关注的条款 3. **审批流程自动化** - 根据规则自动触发不同审批级别 4. **规则灵活配置** - 支持热更新业务规则,无需重新部署
9.3 后续内容预告
后续章节我们将学习:
- **第9篇**:审核工作流引擎设计与实现 - **第10篇**:审核结果展示与报告生成 - **第11篇**:系统集成与性能优化
*本章完*
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)