上一篇文章 描述了一些常用的drools的语法标签和一个模拟实例即发送积分的场景,这一片优化了一下代码,在此贴一下,希望有这方面使用经验的朋友多多交流沟通,指正不足。
通常而言,习惯上我们将规则放到文件系统中,比如以drl结尾的规则文件,现在我们要扩充一下,使其放到数据库中,以供多台服务器同时使用,同时依然保留文件系统的支持。
先看下一个接口:
/** * 规则接口 * @author quzishen */ public interface PointRuleEngine { /** * 初始化规则引擎 */ public void initEngine(); /** * 刷新规则引擎中的规则 */ public void refreshEnginRule(); /** * 执行规则引擎 * @param pointDomain 积分Fact */ public void executeRuleEngine(final PointDomain pointDomain); }
实现过程没有任何难度,两种方式封装过程只在于读取规则的方式不同,代码很简单:
package com.drools.demo.point; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.Reader; import java.io.StringReader; import java.util.ArrayList; import java.util.List; import org.drools.RuleBase; import org.drools.StatefulSession; import org.drools.compiler.PackageBuilder; import org.drools.spi.Activation; /** * 规则接口实现类 * * @author quzishen */ public class PointRuleEngineImpl implements PointRuleEngine { // ~~~ instance filed begin /** RuleBase */ private RuleBase ruleBase; // ~~~ instance filed end /* * (non-Javadoc) * @see com.drools.demo.point.PointRuleEngine#initEngine() */ public void initEngine() { // 设置时间格式 System.setProperty("drools.dateformat", "yyyy-MM-dd HH:mm:ss"); try { synchronized (this) { ruleBase = RuleBaseFacatory.getRuleBase(); // 优先从DB加载规则,如果没有加载到或者加载错误,则从文件系统加载 PackageBuilder backageBuilder = getPackBuilderFromDrlDB(); backageBuilder = null == backageBuilder ? getPackageBuilderFromDrlFile() : backageBuilder; ruleBase.addPackages(backageBuilder.getPackages()); } } catch (Exception e) { e.printStackTrace(); } } /* * (non-Javadoc) * @see com.drools.demo.point.PointRuleEngine#refreshEnginRule() */ public void refreshEnginRule() { ruleBase = RuleBaseFacatory.getRuleBase(); synchronized (ruleBase) { // 删除所有的添加的Package org.drools.rule.Package[] packages = ruleBase.getPackages(); for (org.drools.rule.Package pg : packages) { ruleBase.removePackage(pg.getName()); } // 重新初始化规则引擎 initEngine(); } } /* * (non-Javadoc) * @see com.drools.demo.point.PointRuleEngine#executeRuleEngine(com.drools.demo.point.PointDomain) */ public void executeRuleEngine(final PointDomain pointDomain) { if (null == ruleBase.getPackages() || 0 == ruleBase.getPackages().length) { return; } StatefulSession statefulSession = ruleBase.newStatefulSession(); statefulSession.insert(pointDomain); // fire statefulSession.fireAllRules(new org.drools.spi.AgendaFilter() { public boolean accept(Activation activation) { return !activation.getRule().getName().contains("_test"); } }); statefulSession.dispose(); } /** * 从Drl规则文件中读取规则 * * @return * @throws Exception */ private PackageBuilder getPackageBuilderFromDrlFile() { // 装载规则文件 List<Reader> readers; try { readers = buildReadersFromDrlFile(); // 装载PackageBuilder return buildPackageBuilder(readers); } catch (FileNotFoundException e) { e.printStackTrace(); return null; } catch (Exception e) { e.printStackTrace(); return null; } } /** * 从Drl规则DB中读取规则 * * @return * @throws Exception */ private PackageBuilder getPackBuilderFromDrlDB() { // 装载规则 List<Reader> readers = buildReadersFromDrlDB(); // 装载PackageBuilder try { return buildPackageBuilder(readers); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 装载db中的规则到List<Reader> * * @return */ private List<Reader> buildReadersFromDrlDB() { List<Reader> readers = new ArrayList<Reader>(); // 获取脚本 List<DroolsRuleDomain> drlRuleDomains = getRuleFromDB(); if (null == drlRuleDomains) { return readers; } for (DroolsRuleDomain droolsRuleDomain : drlRuleDomains) { String ruleContext = droolsRuleDomain.getRuleContext(); Reader br = new StringReader(ruleContext); readers.add(br); } return readers; } /** * 装载PackageBuilder * * @param readers * @return * @throws Exception */ private PackageBuilder buildPackageBuilder(List<Reader> readers) throws Exception { if (null == readers || 0 == readers.size()) { return null; } PackageBuilder backageBuilder = new PackageBuilder(); for (Reader r : readers) { backageBuilder.addPackageFromDrl(r); } // 检查脚本是否有问题 if (backageBuilder.hasErrors()) { throw new Exception(backageBuilder.getErrors().toString()); } return backageBuilder; } /** * 装载规则文件到Reader中 * * @return * @throws FileNotFoundException */ private List<Reader> buildReadersFromDrlFile() throws FileNotFoundException { // 获取脚本文件 List<String> drlFilePath = getRuleDrlFile(); // 装载脚本文件 return readRuleFromDrlFile(drlFilePath); } /** * 从规则文件中读取规则 * * @param drlFilePath 脚本文件路径 * @return * @throws FileNotFoundException */ private List<Reader> readRuleFromDrlFile(List<String> drlFilePath) throws FileNotFoundException { if (null == drlFilePath || 0 == drlFilePath.size()) { return null; } List<Reader> readers = new ArrayList<Reader>(); for (String ruleFilePath : drlFilePath) { readers.add(new FileReader(new File(ruleFilePath))); } return readers; } /** * 从数据库中获取规则脚本内容 * * @return */ private List<DroolsRuleDomain> getRuleFromDB() { // 测试代码 List<DroolsRuleDomain> droolsRuleDomains = new ArrayList<DroolsRuleDomain>(); DroolsRuleDomain d1 = new DroolsRuleDomain(); d1.setId(1); d1.setRuleContext("package com.drools.demo.point" + "/n" + "import com.drools.demo.point.PointDomain;" + "/n" + "rule birthdayPoint" + "/n" + "// 过生日,则加10分,并且将当月交易比数翻倍后再计算积分" + "/n" + "salience 100" + "/n" + "lock-on-active true" + "/n" + "when" + "/n" + "$pointDomain : PointDomain(birthDay == true)" + "/n" + "then" + "/n" + "$pointDomain.setPoint($pointDomain.getPoint()+10);" + "/n" + "$pointDomain.recordPointLog($pointDomain.getUserName(),/"birthdayPoint/");" + "/n" + "end"); d1.setRuleName("testRule"); d1.setVersion(1); droolsRuleDomains.add(d1); return droolsRuleDomains; } /** * 获取规则文件 * * @return */ private List<String> getRuleDrlFile() { List<String> drlFilePath = new ArrayList<String>(); drlFilePath .add("D:/workspace2/DroolsDemo/src/com/drools/demo/point/addpoint.drl"); drlFilePath .add("D:/workspace2/DroolsDemo/src/com/drools/demo/point/subpoint.drl"); return drlFilePath; } }
其中的getRuleFromDB() 和 getRuleDrlFile() 两个方法即可以重写以接入个人系统,现在其中编写的是测试代码。
其他的文件与上篇文章相同:
RuleBaseFacatory
package com.drools.demo.point; import org.drools.RuleBase; import org.drools.RuleBaseFactory; /** * RuleBaseFacatory 单实例RuleBase生成工具 * @author quzishen */ public class RuleBaseFacatory { private static RuleBase ruleBase; public static RuleBase getRuleBase(){ return null != ruleBase ? ruleBase : RuleBaseFactory.newRuleBase(); } }
DroolsRuleDomain
package com.drools.demo.point; /** * 规则内容domain * * @author quzishen */ public class DroolsRuleDomain { /** 数据库记录ID */ private long id; /** 规则名称 */ private String ruleName; /** 规则正文 */ private String ruleContext; /** 规则版本 */ private int version; /** 规则脚本状态 */ private int status; public long getId() { return id; } public void setId(long id) { this.id = id; } public String getRuleName() { return ruleName; } public void setRuleName(String ruleName) { this.ruleName = ruleName; } public String getRuleContext() { return ruleContext; } public void setRuleContext(String ruleContext) { this.ruleContext = ruleContext; } public int getVersion() { return version; } public void setVersion(int version) { this.version = version; } public int getStatus() { return status; } public void setStatus(int status) { this.status = status; } }
PointDomain
package com.drools.demo.point; /** * 积分计算对象 * @author quzishen */ public class PointDomain { // 用户名 private String userName; // 是否当日生日 private boolean birthDay; // 增加积分数目 private long point; // 当月购物次数 private int buyNums; // 当月退货次数 private int backNums; // 当月购物总金额 private double buyMoney; // 当月退货总金额 private double backMondy; // 当月信用卡还款次数 private int billThisMonth; /** * 记录积分发送流水,防止重复发放 * @param userName 用户名 * @param type 积分发放类型 */ public void recordPointLog(String userName, String type){ System.out.println("增加对"+userName+"的类型为"+type+"的积分操作记录."); } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public boolean isBirthDay() { return birthDay; } public void setBirthDay(boolean birthDay) { this.birthDay = birthDay; } public long getPoint() { return point; } public void setPoint(long point) { this.point = point; } public int getBuyNums() { return buyNums; } public void setBuyNums(int buyNums) { this.buyNums = buyNums; } public int getBackNums() { return backNums; } public void setBackNums(int backNums) { this.backNums = backNums; } public double getBuyMoney() { return buyMoney; } public void setBuyMoney(double buyMoney) { this.buyMoney = buyMoney; } public double getBackMondy() { return backMondy; } public void setBackMondy(double backMondy) { this.backMondy = backMondy; } public int getBillThisMonth() { return billThisMonth; } public void setBillThisMonth(int billThisMonth) { this.billThisMonth = billThisMonth; } }
addpoint.drl
package com.drools.demo.point import com.drools.demo.point.PointDomain; rule birthdayPoint // 过生日,则加10分,并且将当月交易比数翻倍后再计算积分 salience 100 lock-on-active true when $pointDomain : PointDomain(birthDay == true) then $pointDomain.setPoint($pointDomain.getPoint()+10); $pointDomain.setBuyNums($pointDomain.getBuyNums()*2); $pointDomain.setBuyMoney($pointDomain.getBuyMoney()*2); $pointDomain.setBillThisMonth($pointDomain.getBillThisMonth()*2); $pointDomain.recordPointLog($pointDomain.getUserName(),"birthdayPoint"); end rule billThisMonthPoint // 2011-01-08 - 2011-08-08每月信用卡还款3次以上,每满3笔赠送30分 salience 99 lock-on-active true date-effective "2011-01-08 23:59:59" date-expires "2011-08-08 23:59:59" when $pointDomain : PointDomain(billThisMonth >= 3) then $pointDomain.setPoint($pointDomain.getPoint()+$pointDomain.getBillThisMonth()/3*30); $pointDomain.recordPointLog($pointDomain.getUserName(),"billThisMonthPoint"); end rule buyMoneyPoint // 当月购物总金额100以上,每100元赠送10分 salience 98 lock-on-active true when $pointDomain : PointDomain(buyMoney >= 100) then $pointDomain.setPoint($pointDomain.getPoint()+ (int)$pointDomain.getBuyMoney()/100 * 10); $pointDomain.recordPointLog($pointDomain.getUserName(),"buyMoneyPoint"); end rule buyNumsPoint // 当月购物次数5次以上,每五次赠送50分 salience 97 lock-on-active true when $pointDomain : PointDomain(buyNums >= 5) then $pointDomain.setPoint($pointDomain.getPoint()+$pointDomain.getBuyNums()/5 * 50); $pointDomain.recordPointLog($pointDomain.getUserName(),"buyNumsPoint"); end rule allFitPoint // 特别的,如果全部满足了要求,则额外奖励100分 salience 96 lock-on-active true when $pointDomain:PointDomain(buyNums >= 5 && billThisMonth >= 3 && buyMoney >= 100) then $pointDomain.setPoint($pointDomain.getPoint()+ 100); $pointDomain.recordPointLog($pointDomain.getUserName(),"allFitPoint"); end
subpoint.drl 与上一篇相同,请参见上一篇,此处省略篇幅略
测试代码
Test
package com.drools.demo.point; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; public class Test { /** * @param args * @throws IOException */ public static void main(String[] args) throws IOException { PointRuleEngine pointRuleEngine = new PointRuleEngineImpl(); boolean isStart = false; while(true){ InputStream is = System.in; BufferedReader br = new BufferedReader(new InputStreamReader(is)); String input = br.readLine(); if (null != input && "s".equals(input)){ System.out.println("初始化规则引擎..."); pointRuleEngine.initEngine(); isStart = true; System.out.println("初始化规则引擎结束."); } else if ("e".equals(input)){ if (!isStart) { System.out.println("需要输入s启动"); } else { final PointDomain pointDomain = new PointDomain(); pointDomain.setUserName("hello kity"); pointDomain.setBackMondy(100d); pointDomain.setBuyMoney(500d); pointDomain.setBackNums(1); pointDomain.setBuyNums(5); pointDomain.setBillThisMonth(5); pointDomain.setBirthDay(true); pointDomain.setPoint(0l); pointRuleEngine.executeRuleEngine(pointDomain); System.out.println("执行完毕BillThisMonth:"+pointDomain.getBillThisMonth()); System.out.println("执行完毕BuyMoney:"+pointDomain.getBuyMoney()); System.out.println("执行完毕BuyNums:"+pointDomain.getBuyNums()); System.out.println("执行完毕规则引擎决定发送积分:"+pointDomain.getPoint()); } } else if ("r".equals(input)){ System.out.println("刷新规则文件..."); pointRuleEngine.refreshEnginRule(); isStart = true; System.out.println("刷新规则文件结束."); } else if ("q".equals(input)) { System.exit(0); } } } }