目录

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篇**:系统集成与性能优化

*本章完*

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐