1. 程式人生 > >規則引擎 - drools 使用講解(簡單版) - Java

規則引擎 - drools 使用講解(簡單版) - Java

drools規則引擎

專案連結

現狀:

  1. 運維同學(各種同學)通過後臺管理介面直接配置相關規則,這裡是通過輸入框、下拉框等完成輸入的,非常簡單;
  2. 規則配置完畢後,前端請求後端,此時服務端根據引數(即規則)生成drl規則檔案;
  3. 使用者側有相關請求到達時,服務端載入規則檔案(可能是多個,一般一個廣告、活動對應一個規則檔案),並通過引擎去檢查當前使用者各種狀態是否滿足規則檔案;
  4. 將所有滿足的規則檔案對應的廣告、活動進行下發,同時更新使用者資料;
  5. 完成整個drools相關流程;

關於

drools是一款標準、效率高、速度快的開源規則引擎,基於ReteOO演算法,目前主要應用場景在廣告、活動下發等領域非常多,比如APP的活動下發,通常都是有很多條件限制的,且各種活動層出不窮,無法程式碼窮舉,而如果每次為了一個活動重新發版上線,顯然是不合理的,因此通過drools將活動中變的部分抽象為一個個單獨的規則檔案,來遮蔽這部分的變化,使得系統不需要從程式碼層面做出改變,當然了為了更加極致的抽象,通常還需要對規則中的一些可配條件(大於、小於、等於、範圍、次數等)也提取到資料庫中,這樣在現有規則不滿足要求時,可以直接通過更改資料庫的對應規則表來完善,同樣不需要改程式碼;

我們當時的需求主要就是廣告、活動下發規則比較多,廣告也是各式各樣,因此去調研了drools,對drools也沒有過多的挖掘其更多特性,因此還需要大家的指點;

drools簡單使用

服務端專案中使用drools的幾個基本步驟;

step 1 -- 新增相關依賴到maven pom.xml

<dependency>
    <groupId>org.drools</groupId>
    <artifactId>drools-core</artifactId>
    <version>6.4.0.Final</version>
</dependency>
<dependency>
    <groupId>org.drools</groupId>
    <artifactId>drools-compiler</artifactId>
    <version>6.4.0.Final</version>
</dependency>

step 2 -- 建立實體類載入規則檔案

public class CarIllegalRules extends BaseRules{
    
    public static void main(String[] args) {
        try {
            KieServices ks = KieServices.Factory.get();  
                    KieContainer kContainer = ks.getKieClasspathContainer();  
                    KieSession ksession = kContainer.newKieSession("ksession-rules");
            
                CarIllegalRules carIllegalRules = new CarIllegalRules(10,500,10);
                ksession.insert(carIllegalRules);
                ksession.fireAllRules();
                System.out.println(carIllegalRules.isCan_push()+","+carIllegalRules.getContent());    
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    private int illegal_count;
    
    private int illegal_money;
    
    private int illegal_points;

    public CarIllegalRules(int illegal_count, int illegal_money, int illegal_points) {
        super();
        this.illegal_count = illegal_count;
        this.illegal_money = illegal_money;
        this.illegal_points = illegal_points;
        this.param_value = "illegal_count,illegal_money,illegal_points";
    }

    @Override
    public String toString() {
        return "CarIllegalRules [illegal_count=" + illegal_count + ", illegal_money=" + illegal_money
                + ", illegal_points=" + illegal_points + ", can_push=" + can_push + ", content=" + content + ", tts="
                + tts + "]";
    }

    public int getIllegal_count() {
        return illegal_count;
    }

    public void setIllegal_count(int illegal_count) {
        this.illegal_count = illegal_count;
    }

    public int getIllegal_money() {
        return illegal_money;
    }

    public void setIllegal_money(int illegal_money) {
        this.illegal_money = illegal_money;
    }

    public int getIllegal_points() {
        return illegal_points;
    }

    public void setIllegal_points(int illegal_points) {
        this.illegal_points = illegal_points;
    }
}

PS:main函式是用來測試這個類的;

step 3 -- 建立DSLUtils類去執行相應規則

public class DSLUtil {
    public static void fireRules(File file, Object rules) {
        try {
            KieServices kieServices = KieServices.Factory.get();
            KieFileSystem kfs = kieServices.newKieFileSystem();
            Resource resource = kieServices.getResources().newFileSystemResource(file);
            fire(rules, kieServices, kfs, resource);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void fireRules(String urlStr, Object rules) {
        try {
            KieServices kieServices = KieServices.Factory.get();
            KieFileSystem kfs = kieServices.newKieFileSystem();
            Resource resource = kieServices.getResources().newFileSystemResource(FileUtil.getFileFromUrl(urlStr));
            fire(rules, kieServices, kfs, resource);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    private static void fire(Object commonRules, KieServices kieServices, KieFileSystem kfs, Resource resource)
            throws Exception {
        resource.setResourceType(ResourceType.DRL);
        kfs.write(resource);
        KieBuilder kieBuilder = kieServices.newKieBuilder(kfs).buildAll();
        if (kieBuilder.getResults().getMessages(Message.Level.ERROR).size() > 0) {
            throw new Exception();
        }
        KieContainer kieContainer = kieServices.newKieContainer(kieServices.getRepository().getDefaultReleaseId());
        KieBase kBase = kieContainer.getKieBase();
        KieSession ksession = kBase.newKieSession();
        ksession.insert(commonRules);
        ksession.fireAllRules();
    }
}

step 4 -- 建立一個類去生成規則檔案

比如生成 music.drl 的音樂規則檔案,這一步是可選的,區別在於規則檔案的生成是程式碼生成,還是人工生成,我們的專案中是運維同學在後臺管理介面通過一些圖形化輸入框輸入一些指定引數,而生成規則檔案是服務端程式碼生成的,因此有了這部分,比較實用,一方面可以降低生成規則檔案的門檻,任何人都可以做,另一方面也避免了人工出錯的可能;

public class ActivityUtil {
    /**
     * rule template string
     */
    private static String template = 
        "package com.aispeech.dsl\r\n\r\n" + 
        "import {entity_package_path};\r\n\r\n" +
        "import {entity_package_path}.*;\r\n\r\n" +
        "rule \"{rule_name}\"\r\n\r\n" +
        "when\r\n" +
        "\t{instance_name}:{class_name}({rules})\r\n" +
        "then\r\n" +
        "\t{do}\r\n" +
        "end";
    
    private static final String AND = " && ";
    private static final String OR = " || ";

    /**
     * get business rule file xxx.drl
     * @param carActivity user info entity
     * @param clazz entity class
     * @return
     */
    public static File createBusinessRuleFile(Car_activity carActivity, Class clazz, String[] param_texts, String[] param_values) {
        String ruleStr = template;
        String entity_package_path = (clazz+"").substring(6);
        String rule_name = "rule_"+carActivity.getId();
        String class_name = (clazz+"").substring((clazz+"").lastIndexOf(".")+1);
        String instance_name = class_name.toLowerCase();
        
        String rules = "";
        JSONArray conditionArray = JSONArray.parseArray(carActivity.getAim_condition());
        for(int i=0;i<conditionArray.size();i++) {
            JSONObject condition = conditionArray.getJSONObject(i);
            rules += "\r\n\t\t("+condition.getString("param")+condition.getString("operator")+condition.getString("value")+")" + AND;
        }
        rules = rules.length()>0?rules.substring(0, rules.lastIndexOf(AND)):rules;
        
        for (String param_value : param_values) {
            rules += "\r\n\t\t,"+param_value.toLowerCase()+":"+param_value;
        }
        
        String content = JSONObject.parseObject(carActivity.getContent()).getString("content");
        String tts = carActivity.getTts();
        for (int i=0;i<param_texts.length;i++) {
            content = content.replace("#"+param_texts[i]+"#", "\"+"+param_values[i]+"+\"");
            tts = tts.replace("#"+param_texts[i]+"#",  "\"+"+param_values[i]+"+\"");
        }
        String _do = instance_name+".setCan_push(true);";
        _do += "\r\n\t" + instance_name+".setContent(\""+content+"\");";
        _do += "\r\n\t" + instance_name+".setTts(\""+tts+"\");";
        
        return returnFile(ruleStr, entity_package_path, rule_name, class_name, instance_name, _do, rules);
    }

    /**
     * @param ruleStr
     * @param entity_package_path
     * @param rule_name
     * @param class_name
     * @param instance_name
     * @param _do
     * @param rules
     * @return
     */
    private static File returnFile(String ruleStr, String entity_package_path, String rule_name, String class_name,
            String instance_name, String _do, String rules) {
        ruleStr = ruleStr.replace("{entity_package_path}", entity_package_path)
                .replace("{rule_name}", rule_name)
                .replace("{class_name}", class_name)
                .replace("{instance_name}", instance_name)
                .replace("{do}", _do)
                .replace("{rules}", rules);
        System.out.println(ruleStr);
        return FileUtil.getFileFromText(rule_name, ".drl", ruleStr);
    }
}

step 4.1 -- 通過字串建立檔案,給上一步用的函式

public static File getFileFromText(String tempFileName, String fileTail, String text) {
    try {
        File file = File.createTempFile(tempFileName, fileTail);
        FileOutputStream fos = new FileOutputStream(file);
        fos.write(text.getBytes());
        if(fos!=null){
            fos.close();
        }
        return file;
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}

step 5 -- 規則檔案載入,並用以檢查當前使用者是否滿足下發規則條件

BaseRules baseRules = new CarIllegalRules(count, money, points);
if(baseRules!=null) {
    logger.info("before fire rules:"+baseRules);
    DSLUtil.fireRules(ActivityUtil.createBusinessRuleFile(car_activity, baseRules.getClass(), 
        baseRules.getParam_text().split(","), baseRules.getParam_value().split(",")), baseRules);
    logger.info("after fire rules:"+baseRules);
    if(baseRules.isCan_push()) {
            //In here, the rules are used to judge the success of the entity, and you can do something!!!
        }
}

小結

本文通過對drools的簡單使用步驟的講解,為大家展示了drools最簡單的使用方式,而它能做到的遠遠不止看到的這些,但是基本框架是這樣,大家可以嘗試挖掘規則檔案的一些黑操作,可以對多變的業務進行極致的抽象,再也不用為了這些重新發版啦,LOL;

PS:想深入瞭解的同學還是要去看看Rete演算法、drools的推理機制等等,本文主要從該引擎的入門出發哈;

最後

大家可以到我的Github上看看有沒有其他需要的東西,目前主要是自己做的機器學習專案、Python各種指令碼工具、資料分析挖掘專案以及Follow的大佬、Fork的專案等:https://github.com/NemoHoHal