第2-4-10章 規則引擎Drools實戰(3)-保險產品准入規則

2022-12-01 09:00:26

9.3 保險產品准入規則

全套程式碼及資料全部完整提供,點此處下載

9.3.1 決策表

前面我們編寫的規則檔案都是drl形式的檔案,Drools除了支援drl形式的檔案外還支援xls格式的檔案(即Excel檔案)。這種xls格式的檔案通常稱為決策表(decision table)。

決策表(decision table)是一個「精確而緊湊的」表示條件邏輯的方式,非常適合商業級別的規則。決策表與現有的drl檔案可以無縫替換。Drools提供了相應的API可以將xls檔案編譯為drl格式的字串。

一個決策表的例子如下:

決策表語法:

關鍵字 說明 是否必須
RuleSet 相當於drl檔案中的package 必須,只能有一個。如果沒有設定RuleSet對應的值則使用預設值rule_table
Sequential 取值為Boolean型別。true表示規則按照表格自上到下的順序執行,false表示亂序 可選
Import 相當於drl檔案中的import,如果引入多個類則類之間用逗號分隔 可選
Variables 相當於drl檔案中的global,用於定義全域性變數,如果有多個全域性變數則中間用逗號分隔 可選
RuleTable 它指示了後面將會有一批rule,RuleTable的名稱將會作為以後生成rule的字首 必須
CONDITION 規則條件關鍵字,相當於drl檔案中的when。下面兩行則表示 LHS 部分,第三行則為註釋行,不計為規則部分,從第四行開始,每一行表示一條規則 每個規則表至少有一個
ACTION 規則結果關鍵字,相當於drl檔案中的then 每個規則表至少有一個
NO-LOOP 相當於drl檔案中的no-loop 可選
AGENDA-GROUP 相當於drl檔案中的agenda-group 可選

在決策表中還經常使用到預留位置,語法為$後面加數位,用於替換每條規則中設定的具體值。

上面的決策表例子轉換為drl格式的規則檔案內容如下:

package rules;

import com.itheima.drools.entity.PersonInfoEntity;
import java.util.List;
global java.util.List listRules;

rule "personCheck_10"
	salience 65535
	agenda-group "sign"
	when
		$person : PersonInfoEntity(sex != "男")
	then
		listRules.add("性別不對");
end

rule "personCheck_11"
	salience 65534
	agenda-group "sign"
	when
		$person : PersonInfoEntity(age < 22 || age > 25)
	then
		listRules.add("年齡不合適");
end

rule "personCheck_12"
	salience 65533
	agenda-group "sign"
	when
		$person : PersonInfoEntity(salary < 10000)
	then
		listRules.add("工資太低了");
end

要進行決策表相關操作,需要匯入如下maven座標:

<dependency>
    <groupId>org.drools</groupId>
    <artifactId>drools-decisiontables</artifactId>
    <version>7.10.0.Final</version>
</dependency>

通過下圖可以發現,由於maven的依賴傳遞特性在匯入drools-decisiontables座標後,drools-core和drools-compiler等座標也被傳遞了過來

Drools提供的將xls檔案編譯為drl格式字串的API如下:

String realPath = "C:\\testRule.xls";//指定決策表xls檔案的磁碟路徑
File file = new File(realPath);
InputStream is = new FileInputStream(file);
SpreadsheetCompiler compiler = new SpreadsheetCompiler();
String drl = compiler.compile(is, InputType.XLS);

Drools還提供了基於drl格式字串建立KieSession的API:

KieHelper kieHelper = new KieHelper();
kieHelper.addContent(drl, ResourceType.DRL);
KieSession session = kieHelper.build().newKieSession();

基於決策表的入門案例:

第一步:建立maven工程drools_decisiontable_demo並設定pom.xml檔案

<dependency>
    <groupId>org.drools</groupId>
    <artifactId>drools-decisiontables</artifactId>
    <version>7.10.0.Final</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

第二步:建立實體類PersonInfoEntity

package com.itheima.drools.entity;

public class PersonInfoEntity {
    private String sex;
    private int age;
    private double salary;

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
}

第三步:建立xls規則檔案(可以直接使用資料中提供的testRule.xls檔案)

第四步:建立單元測試

@Test
public void test1() throws Exception{
    String realPath = "d:\\testRule.xls";//指定決策表xls檔案的磁碟路徑
    File file = new File(realPath);
    InputStream is = new FileInputStream(file);
    SpreadsheetCompiler compiler = new SpreadsheetCompiler();
    String drl = compiler.compile(is, InputType.XLS);

    System.out.println(drl);
    KieHelper kieHelper = new KieHelper();
    kieHelper.addContent(drl, ResourceType.DRL);
    KieSession session = kieHelper.build().newKieSession();

    PersonInfoEntity personInfoEntity = new PersonInfoEntity();
    personInfoEntity.setSex("男");
    personInfoEntity.setAge(35);
    personInfoEntity.setSalary(1000);

    List<String> list = new ArrayList<String>();
    session.setGlobal("listRules",list);

    session.insert(personInfoEntity);
    
    session.getAgenda().getAgendaGroup("sign").setFocus();
    
    session.fireAllRules();

    for (String s : list) {
        System.out.println(s);
    }
    session.dispose();
}

9.3.2 規則介紹

各保險公司針對人身、財產推出了不同的保險產品,作為商業保險公司,篩選出符合公司利益最大化的客戶是非常重要的,即各保險產品的准入人群是不同的,也就是說保險公司會針對不同的人群特徵,制定不同的產品繳費和賠付規則。

我們來看一下某保險產品准入規則的簡化版,當不滿足以下規則時,系統模組需要返回准入失敗標識和失敗原因

規則1:  保險公司是:PICC
規則2:  銷售區域是:北京、天津
規則3:  投保人年齡:0 ~ 17歲
規則4:  保險期間是:20年、25年、30年
規則5:  繳費方式是:躉交(一次性交清)或年交
規則6:  保險期與交費期規則一:保險期間為20年期交費期間最長10年交且不能選擇[躉交]
規則7:  保險期與交費期規則二:保險期間為25年期交費期間最長15年交且不能選擇[躉交]
規則8:  保險期與交費期規則三:保險期間為30年期交費期間最長20年交且不能選擇[躉交]
規則9:  被保人要求:(投保年齡+保險期間)不得大於40週歲
規則10: 保險金額規則:投保時約定,最低為5萬元,超過部分必須為1000元的整數倍
規則11: 出單基本保額限額規則:線上出單基本保額限額62.5萬元,超62.5萬元需配合契調轉線下出單

在本案例中規則檔案是一個Excel檔案,業務人員可以直接更改這個檔案中指標的值,系統不需要做任何變更。

9.3.3 實現步驟

本案例還是基於Spring Boot整合Drools的架構來實現。

第一步:建立maven工程insuranceInfoCheck並設定pom.xml檔案

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starters</artifactId>
        <version>2.0.6.RELEASE</version>
    </parent>
    <groupId>com.itheima</groupId>
    <artifactId>insuranceInfoCheck</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>
        <!--drools規則引擎-->
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-core</artifactId>
            <version>7.6.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-compiler</artifactId>
            <version>7.6.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-templates</artifactId>
            <version>7.6.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.kie</groupId>
            <artifactId>kie-api</artifactId>
            <version>7.6.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.kie</groupId>
            <artifactId>kie-spring</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-tx</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-beans</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-core</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-context</artifactId>
                </exclusion>
            </exclusions>
            <version>7.6.0.Final</version>
        </dependency>
    </dependencies>
    <build>
        <finalName>${project.artifactId}</finalName>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.*</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

第二步:建立/resources/application.yml檔案

server:
  port: 8080
spring:
  application:
    name: insuranceInfoCheck

第三步:建立實體類InsuranceInfo

package com.itheima.drools.entity;

/**
 * 保險資訊
 */
public class InsuranceInfo {
    private String param1;//保險公司
    private String param2;//方案程式碼
    private String param3;//渠道號
    private String param4;//銷售區域
    private String param5;//投保年齡
    private String param6;//保險期間
    private String param7;//繳費期間
    private String param8;//繳費方式
    private String param9;//保障型別
    private String param10;//等待期
    private String param11;//猶豫期
    private String param12;//職業型別
    private String param13;//保額限制
    private String param14;//免賠額
    private String param15;//主險保額
    private String param16;//主險保費
    private String param17;//附加險保額
    private String param18;//附加險保費
    private String param19;//與投保人關係
    private String param20;//與被保人關係
    private String param21;//性別
    private String param22;//證件
    private String param23;//保費
    private String param24;//保額

    //getter setter省略
}

第四步:建立決策表檔案(也可以直接使用實戰資料中提供的insuranceInfoCheck.xls檔案)

第五步:封裝工具類KieSessionUtils

package com.itheima.drools.utils;

import com.itheima.drools.entity.InsuranceInfo;
import com.itheima.drools.entity.PersonInfoEntity;
import org.drools.decisiontable.InputType;
import org.drools.decisiontable.SpreadsheetCompiler;
import org.kie.api.builder.Message;
import org.kie.api.builder.Results;
import org.kie.api.io.ResourceType;
import org.kie.api.runtime.KieSession;
import org.kie.internal.utils.KieHelper;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

public class KieSessionUtils {
    private KieSessionUtils() {

    }
    // 把xls檔案解析為String
    public static String getDRL (String realPath) throws FileNotFoundException {
        File file = new File(realPath); // 例如:C:\\abc.xls
        InputStream is = new FileInputStream(file);
        SpreadsheetCompiler compiler = new SpreadsheetCompiler();
        String drl = compiler.compile(is, InputType.XLS);
        System.out.println(drl);
        return drl;
    }

    // drl為含有內容的字串
    public static KieSession createKieSessionFromDRL(String drl) throws Exception{
        KieHelper kieHelper = new KieHelper();
        kieHelper.addContent(drl, ResourceType.DRL);
        Results results = kieHelper.verify();
        if (results.hasMessages(Message.Level.WARNING, Message.Level.ERROR)) {
            List<Message> messages = results.getMessages(Message.Level.WARNING, Message.Level.ERROR);
            for (Message message : messages) {
                System.out.println("Error: "+message.getText());
            }
            // throw new IllegalStateException("Compilation errors were found. Check the logs.");
        }
        return kieHelper.build().newKieSession();
    }

    // realPath為Excel檔案絕對路徑
    public static KieSession getKieSessionFromXLS(String realPath) throws Exception {
        return createKieSessionFromDRL(getDRL(realPath));
    }
}

第六步:建立RuleService類

package com.itheima.drools.service;

import com.itheima.drools.entity.InsuranceInfo;
import com.itheima.drools.utils.KieSessionUtils;
import org.kie.api.runtime.KieSession;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;

@Service
public class RuleService {
    public List<String> insuranceInfoCheck(InsuranceInfo insuranceInfo) throws Exception{
        KieSession session = KieSessionUtils.getKieSessionFromXLS("D:\\rules.xls");
        session.getAgenda().getAgendaGroup("sign").setFocus();

        session.insert(insuranceInfo);

        List<String> listRules = new ArrayList<>();
        session.setGlobal("listRules", listRules);

        session.fireAllRules();
        
        return listRules;
    }
}

第七步:建立RuleController類

package com.itheima.drools.controller;

import com.itheima.drools.entity.InsuranceInfo;
import com.itheima.drools.service.RuleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/rule")
public class RuleController {
    @Autowired
    private RuleService ruleService;

    @RequestMapping("/insuranceInfoCheck")
    public Map insuranceInfoCheck(){
        Map map = new HashMap();
        
        //模擬資料,實際應為頁面傳遞過來
        InsuranceInfo insuranceInfo = new InsuranceInfo();
        insuranceInfo.setParam1("picc");
        insuranceInfo.setParam4("上海");
        insuranceInfo.setParam5("101");
        insuranceInfo.setParam6("12");
        insuranceInfo.setParam7("222");
        insuranceInfo.setParam8("1");
        insuranceInfo.setParam13("3");
        
        try {
            List<String> list = ruleService.insuranceInfoCheck(insuranceInfo);
            if(list != null && list.size() > 0){
                map.put("checkResult",false);
                map.put("msg","准入失敗");
                map.put("detail",list);
            }else{
                map.put("checkResult",true);
                map.put("msg","准入成功");
            }
            return map;
        } catch (Exception e) {
            e.printStackTrace();
            map.put("checkResult",false);
            map.put("msg","未知錯誤");
            return map;
        }
    }
}

第八步:建立啟動類DroolsApplication

package com.itheima.drools;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DroolsApplication {
    public static void main(String[] args) {
        SpringApplication.run(DroolsApplication.class);
    }
}

全套程式碼及資料全部完整提供,點此處下載