天天看點

Drools規則引擎在支付結算對賬中的應用

什麼是Drools

Drools是Jboss公司旗下一款開源的規則引擎,是一個基于java的規則引擎,開源的,可以将複雜多變的規則從寫死中解放出來,

以規則腳本的形式存放在檔案中,使得規則的變更不需要修正代碼重新開機機器就可以立即線上上環境生效,有如下特點

1. 完整的實作了Rete算法;

2. 提供了強大的IDE Plugin開發支援;

3. 通過使用其中的DSL(Domain Specific

Language),可以實作用自然語言方式來描述業務規則,使得業務分析人員也可以看懂業務規則代碼;

4. 提供了基于WEB的BRMS——Guvnor,Guvnor提供了規則管理的知識庫,通過它可以實作規則的版本控制,及規則的線上修改與編譯,使得開發人員和系統管理人員可以線上管理業務規則。

Drools 是業務邏輯內建平台,被分為4個項目:

  1. Drools Guvnor (BRMS/BPMS):業務規則管理系統
  2. Drools Expert (rule engine):規則引擎,drools的核心部分
  3. Drools Flow (process/workflow):工作流引擎
  4. Drools Fusion (cep/temporal reasoning):事件處理

工作原理

Drools規則引擎在支付結算對賬中的應用

一個簡單的使用demo,這裡我們主要用到Drools Expert 部分。

業務場景:模拟清算對賬

輸入資料源:零花錢流水對賬資料、快錢管道對賬資料,執行業務規則,輸出平賬資料、對賬差異資料

零花錢流水對賬資料實體類

AccountOrder.java

@Data
public class AccountOrder {

    private Long puidOrderId;
    private String puid;
    private String fnPuid;
    private Long orderId;
    private Byte type;
    private Date createTime;
    private Byte busiType;
    private int transType;
    private Long transAmount;
    private Long afterBalance;
    private Long origPuidOrderId;
    private Date expireTime;
    private int orderStatus ; //0 成功
    private int checkStatus;
}
           

快錢管道對賬資料實體類

BIll99Order.java

@Data
public class BIll99Order {

    private Long outerOrderId;
    private Long orderId;
    private Date createTime;
    private int transType;
    private Long amount;
    private int orderStatus ;
    private int checkStatus; //0 未對平  1 對平
}
           

1.引入drools依賴

pom.xml

<properties>
     <drools-version>6.4.0.Final</drools-version>
</properties>

<dependency>
    <groupId>org.kie</groupId>
    <artifactId>kie-api</artifactId>
    <version>${drools-version}</version>
</dependency>
<dependency>
    <groupId>org.kie</groupId>
    <artifactId>kie-internal</artifactId>
    <version>${drools-version}</version>
</dependency>

<dependency>
    <groupId>org.kie</groupId>
    <artifactId>kie-spring</artifactId>
    <version>${drools-version}</version>
</dependency>

<dependency>
    <groupId>org.drools</groupId>
    <artifactId>drools-core</artifactId>
    <version>${drools-version}</version>
</dependency>

<dependency>
    <groupId>org.drools</groupId>
    <artifactId>drools-compiler</artifactId>
    <version>${drools-version}</version>
</dependency>
           

2.定義業務規則,可以是.drl檔案 也可以是一段符合drools規則的字元串

precheck.drl

package rules;
dialect  "java"
import com.shinyleo.drools.mode.AccountOrder;
import com.shinyleo.drools.mode.BIll99Order;

global java.util.List successCheckList;


//中繼資料定義
declare SuccessData
   orderId :Long
   checkresult:String
   checkStatus:int
    amount:Long
    status:int
end



rule "precheck"
    salience 
    when
       $accountOrder : AccountOrder(checkStatus ==  ,$transAmount:transAmount,$orderId:orderId,$status:orderStatus)
       $bill99Order : BIll99Order(checkStatus == ,outerOrderId == $orderId,amount == $transAmount,orderStatus == $status)
    then
      System.out.println("-----start rules-----" + $accountOrder.getOrderId());
      $accountOrder.setCheckStatus(); //标記為對平
      $bill99Order.setCheckStatus();
      update($accountOrder);
      update($bill99Order);
      //擷取平賬資料
      SuccessData successData = new SuccessData();
      successData.setAmount($transAmount);
      successData.setCheckresult("平賬");
      successData.setOrderId($orderId);
      //insertLogical(successData);
      insert(successData);
      //傳回
      successCheckList.add(successData);
end

rule "successData"
  when
      SuccessData(checkStatus == );
  then
     System.out.println("success increase 1 ..." );
end

query "successList"
   // 找出對平的資料
   successData:AccountOrder(checkStatus == )
end

query "errorList"
   // 找出還未對平的資料
   errorData:AccountOrder(checkStatus == )

end

query "queryOrder" (int $status,Long $orderId)
   // 找出還未對平的資料
   queryOrder:AccountOrder(checkStatus == $status,orderId == $orderId)
end
           

3.注冊規則,kmodule.xml,配置如果是多個規則,則配置如下

<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://jboss.org/kie/6.0.0/kmodule">
    <!--規則1-->
    <kbase name="precheckBase" packages="rules">
        <ksession name="precheck"/>
    </kbase>

    <!--規則2-->
    <kbase name="precheckCmd" packages="rules.cmd">
        <ksession name="cmd"/>
    </kbase>

    <!--規則3-->
    <kbase name="precheckthird" packages="rules.third">
        <ksession name="precheck-third"/>
    </kbase>
</kmodule>
           

4.定義規則引擎調用接入層

DroolService.java

/**
 * Created by zhugh on 17/1/12.
 */
public interface DroolService {

    /**
     * 根據規則檔案激活規則
     * @param ruleName
     * @return
     */
    public KieSession createKieSessionByRule(String ruleName);


    /**
     * 根據規則檔案激活規則 并裝載資料
     * @param ruleName
     * @return
     */
    public KieSession executeKieSessionByRule(String ruleName, DataBean dataBean, Map<String,Object> globalMap);


    /**
     * 根據規則檔案激活規則 并裝載資料
     * @param ruleName
     * @return
     */
    public ExecutionResults executeBatchCmd(String ruleName, DataBean dataBean, Map<String,Object> globalMap,String ... queryName);
    /**
     * 根據自定義内容建立規則
     * @param ruleContent
     * @return
     */
    public KieSession executeKieSessionByRuleContent(String ruleContent);

    /**
     * 根據自定義内容建立規則,并執行規則
     * @param ruleContent
     * @return
     */
    public KieSession executeKieSessionByRuleContent(String ruleContent,Object srcData,Object targetData,Map<String,Object> globalMap);
}
           

實作類

DroolServiceImpl.java

@Service("droolService")
public class DroolServiceImpl implements DroolService, InitializingBean {

    Logger logger = LoggerFactory.getLogger(this.getClass());

    private  KieContainer kContainer;
    @Override
    public void afterPropertiesSet() throws Exception {
        KieServices ks = KieServices.Factory.get();
        this.kContainer = ks.getKieClasspathContainer();
    }


    @Override
    public KieSession createKieSessionByRule(String ruleName) {
        try{
            return kContainer.newKieSession(ruleName);
        }catch (Exception e){
            throw new RuntimeException(e);
        }

    }

    /**
     * 資料載入
     * @param ruleName
     * @param dataBean
     * @param globalMap
     * @return
     */
    @Override
    public KieSession executeKieSessionByRule(String ruleName, DataBean dataBean, Map<String, Object> globalMap) {

        KieSession kieSession = null;
        try{
            kieSession = kContainer.newKieSession(ruleName);

            List listSrc = (List) dataBean.getSrcData();
            List listTarget = (List) dataBean.getTargetData();
            for(Object srcObj : listSrc){
                kieSession.insert(srcObj);
            }
            for(Object tarObj : listTarget){
                kieSession.insert(tarObj);
            }
            for(Map.Entry<String,Object> entry : globalMap.entrySet()){
                kieSession.setGlobal(entry.getKey(),entry.getValue());
            }
            kieSession.fireAllRules();

        }catch(Exception e){
            logger.error("executeKieSessionByRule.error:{}",e);
            throw new RuntimeException(e);
        }finally {
            if(null  != kieSession){
                kieSession.dispose();
            }
        }
        return kieSession;

    }

    /**
     * 批量資料載入
     * @param ruleName
     * @param dataBean
     * @param globalMap
     * @return
     */

    @Override
    public  ExecutionResults executeBatchCmd(String ruleName, DataBean dataBean, Map<String, Object> globalMap,String ... queryName) {
        KieSession kieSession = null;
        ExecutionResults executionResults = null;
        try{
            kieSession = kContainer.newKieSession(ruleName);
            List<Command> cmdList = new ArrayList<>();
            Command srcCmd = CommandFactory.newInsertElements((Collection) dataBean.getSrcData());
            cmdList.add(srcCmd);
            Command tarCmd = CommandFactory.newInsertElements((Collection) dataBean.getTargetData());
            cmdList.add(tarCmd);
            //全局變量裝載
            for(Map.Entry<String,Object> entry : globalMap.entrySet()){
                Command globalCmd = CommandFactory.newSetGlobal(entry.getKey(), entry.getValue(),true);
                cmdList.add(globalCmd);
            }
            //查詢裝載
            for(String query_Name : queryName){
                cmdList.add(CommandFactory.newQuery(query_Name,query_Name));
            }
            Command fireCmd = CommandFactory.newFireAllRules();
            cmdList.add(fireCmd);
            BatchExecutionCommand command = CommandFactory.newBatchExecution(cmdList);
            executionResults = kieSession.execute(command);

        }catch(Exception e){
            logger.error("executeBatchCmd.error:{}",e);
            throw new RuntimeException(e);
        }finally {
            if(null != kieSession){
                kieSession.dispose();
            }
        }
        return executionResults;
    }


/*
    @Override
    public  ExecutionResults executeBatchCmd(String ruleName, DataBean dataBean, Map<String, Object> globalMap,String ... queryName) {
        KieSession kieSession = null;
        ExecutionResults executionResults = null;
        try{
            kieSession = kContainer.newKieSession(ruleName);
            List<Command> list = new ArrayList<>();
            Command srcCmd = CommandFactory.newInsertElements((Collection) dataBean.getSrcData());
            list.add(srcCmd);
            Command tarCmd = CommandFactory.newInsertElements((Collection) dataBean.getTargetData());
            list.add(tarCmd);
            //全局變量裝載
            for(Map.Entry<String,Object> entry : globalMap.entrySet()){
                Command globalCmd = CommandFactory.newSetGlobal(entry.getKey(), entry.getValue(),true);
                list.add(globalCmd);
            }
            //查詢裝載
            for(String query_Name : queryName){
                list.add(CommandFactory.newQuery(query_Name,query_Name));
            }
            BatchExecutionCommand command = CommandFactory.newBatchExecution(list);
            executionResults = kieSession.execute(command);

        }catch(Exception e){
            logger.error("executeBatchCmd.error:{}",e);
            throw new RuntimeException(e);
        }finally {
            if(null != kieSession){
                kieSession.dispose();
            }
        }
        return executionResults;
    }
*/

    @Override
    public KieSession executeKieSessionByRuleContent(String ruleContent) {
        StatefulKnowledgeSession ksession = null;
        try {
            KnowledgeBuilder kBuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
            kBuilder.add(ResourceFactory.newByteArrayResource(ruleContent.getBytes()), ResourceType.DRL);
            if(kBuilder.hasErrors()){
                logger.error("DroolServiceImpl.getKieSessionByRuleContent.error:{}",kBuilder.getErrors().toString());
                throw new RuntimeException( kBuilder.getErrors().toString() );
            }
            KnowledgeBase knowledgeBase = KnowledgeBaseFactory.newKnowledgeBase();
            knowledgeBase.addKnowledgePackages(kBuilder.getKnowledgePackages());
            ksession = knowledgeBase.newStatefulKnowledgeSession();

        }catch (Exception e){
            logger.error("executeKieSessionByRuleContent.error:{}",e);
            throw new RuntimeException(e);
        }finally {
            if(null != ksession){
                ksession.dispose();
            }
        }
        return ksession;

    }

    @Override
    public KieSession executeKieSessionByRuleContent(String ruleContent, Object srcData, Object targetData,Map<String,Object> globalMap) {
        StatefulKnowledgeSession ksession = null;
        try {
            KnowledgeBuilder kBuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
            kBuilder.add(ResourceFactory.newByteArrayResource(ruleContent.getBytes()), ResourceType.DRL);
            if (kBuilder.hasErrors()) {
                logger.error("DroolServiceImpl.getKieSessionByRuleContent.error:{}", kBuilder.getErrors().toString());
                throw new RuntimeException(kBuilder.getErrors().toString());
            }
            KnowledgeBase knowledgeBase = KnowledgeBaseFactory.newKnowledgeBase();
            knowledgeBase.addKnowledgePackages(kBuilder.getKnowledgePackages());
            ksession = knowledgeBase.newStatefulKnowledgeSession();
            ksession.insert(srcData);
            ksession.insert(targetData);
            for (Map.Entry<String, Object> entry : globalMap.entrySet()) {
                ksession.setGlobal(entry.getKey(), entry.getValue());
            }
            ksession.fireAllRules();
        }catch (Exception e){
            logger.error("executeKieSessionByRuleContent.error:{}",e);
            throw new RuntimeException(e);
        }finally {
            if(null != ksession){
                ksession.dispose();
            }
        }
        return ksession;
    }
}
           

5.執行業務規則

@Test
public void droolsTest(){
    List<AccountOrder> srcDataList = getSrcList();
    List<BIll99Order> targetList = getTargetList();
    Map<String ,Object> globalMap = new HashMap<>();
    globalMap.put("successCheckList",new ArrayList());

    DataBean<List<AccountOrder>,List<BIll99Order>> dataBean = new DataBean(srcDataList,targetList);
    KieSession kieSession = droolService.executeKieSessionByRule("precheck",dataBean,globalMap);

    List successlist = (List) kieSession.getGlobal("successCheckList");
    System.out.println("global successlist :" + successlist.size());
    //擷取對平資料
    QueryResults querySuccessList = kieSession.getQueryResults("successList");
    System.out.println("對平資料條數:" + querySuccessList.size());
    for(QueryResultsRow qr : querySuccessList){
        AccountOrder order = (AccountOrder) qr.get("successData");
        System.out.println("對平資料明細:" + order.getOrderId());
    }

    //擷取未對平資料
    QueryResults queryErrors = kieSession.getQueryResults("errorList");
    System.out.println("未對平資料條數:" + queryErrors.size());

    for(QueryResultsRow qr : queryErrors){
        AccountOrder order = (AccountOrder) qr.get("errorData");
        System.out.println("未對平資料明細:" + order.getOrderId());
    }

    //查找資料
    QueryResults successData = kieSession.getQueryResults("queryOrder", new Object[]{new Integer(),L});
    for(QueryResultsRow qr : successData){
        AccountOrder order = (AccountOrder)qr.get("queryOrder"); //列印查詢結果
        System.out.println("查找單号為1000112345的對賬結果結果 :" + order.getOrderId() + ":" + order.getCheckStatus());
    }
    kieSession.dispose();

}

//構造資料源,實際資料源從對賬檔案中錄入
   private List<AccountOrder> getSrcList(){
        List<AccountOrder> srcDataList = new ArrayList<>();
        AccountOrder accountOrder = new AccountOrder();
        accountOrder.setOrderId(L);
        accountOrder.setTransAmount(L);
        accountOrder.setOrderStatus();
        accountOrder.setCheckStatus();

        AccountOrder accountOrder1 = new AccountOrder();
        accountOrder1.setOrderId(L);
        accountOrder1.setTransAmount(L);
        accountOrder1.setOrderStatus();
        accountOrder1.setCheckStatus();

        srcDataList.add(accountOrder);
        srcDataList.add(accountOrder1);
        return srcDataList;
    }


    private  List<BIll99Order> getTargetList(){
        List<BIll99Order> targetList = new ArrayList<>();
        BIll99Order bIll99Order = new BIll99Order();
        bIll99Order.setOuterOrderId(L);
        bIll99Order.setAmount(L);
        bIll99Order.setOrderStatus();
        bIll99Order.setCheckStatus();

        BIll99Order bIll99Order1 = new BIll99Order();
        bIll99Order1.setOuterOrderId(L);
        bIll99Order1.setAmount(L);
        bIll99Order1.setOrderStatus();
        bIll99Order1.setCheckStatus();

        targetList.add(bIll99Order);
        targetList.add(bIll99Order1);
        return targetList;
    }
           

6.輸出結果

-----start rules-----123
global successlist :1
對平資料條數:1
對平資料明細:123
未對平資料條數:1
未對平資料明細:456
           

7.工程結構

Drools規則引擎在支付結算對賬中的應用