1. 程式人生 > >QLExpress 規則引擎使用介紹

QLExpress 規則引擎使用介紹

extend ati result ret ali 使用介紹 ger 增加 替換

一個輕量級的類java語法規則引擎,作為一個嵌入式規則引擎在業務系統中使用。讓業務規則定義簡便而不失靈活。讓業務人員就可以定義業務規則。支持標準的JAVA語法,還可以支持自定義操作符號、操作符號重載、函數定義、宏定義、數據延遲加載等

QLExpress的特性

1、編譯執行:

編譯生成基礎指令後執行,性能能得到基本保障。執行過程:單詞分解-->單詞類型分析-->語法分析-->生成運行期指令集合-->執行生成的指令集合

runner.execute("10 * 10 + 1 + 2 * 3 + 5 * 2", null, true,null); 
最後生成的指令:
	1:LoadData 10
	2:LoadData 10
	3:OP : * OPNUMBER[2] 
	4:LoadData 1
	5:OP : + OPNUMBER[2] 
	6:LoadData 2
	7:LoadData 3
	8:OP : * OPNUMBER[2] 
	9:OP : + OPNUMBER[2] 
	10:LoadData 5
	11:LoadData 2
	12:OP : * OPNUMBER[2] 
	13:OP : + OPNUMBER[2]

2、支持標準的java語法、JAVA運算符號和關鍵字

import:引入一個包或者類,例如:import java.util.*;需要放在腳本的最前面
new:創建一個對象,例如:new ArrayList();
for:操作符號
if:操作符號
then:操作符號
else:操作符號
break: 終止循環
continue: 績效循環
return: 返回
     A、四則運算 : 10 * 10 + 1 + 2 * 3 + 5 * 2
     B、boolean運算: 3 > 2 and 2 > 3
     C、創建對象,對象方法調用,靜態方法調用:new com.ql.util.express.test.BeanExample("張三").unionName("李四")
     D、變量賦值:a = 3 + 5
     E、支持 in,max,min:  (a in (1,2,4)) and (b in("abc","bcd","efg"))

3、自定義的關鍵字

include:在一個表達式中引入其它表達式。例如: include com.taobao.upp.b; 資源的轉載可以自定義接口IExpressResourceLoader來實現,缺省是從文件中裝載
[]:匿名創建數組.int[][] abc = [[11,12,13],[21,22,23]];
NewMap:創建HashMap. Map abc = NewMap(1:1,2:2);Map abc = NewMap("a":1,"b":2)
NewList:串接ArrayList.List abc = NewList(1,2,3);
exportDef: 將局部變量轉換為全局變量,例如:exportDef long userId
alias:創建別名,例如: alias 用戶ID user.userId
exportAlias: 創建別名,並轉換為全局別名
macro: 定義宏,例如: macro 降級  {level = level - 1}
function: 定義函數,例如: function add(int a,int b){  return a+b; };
in: 操作符號,例如: 3 in (3,4,5)
mod:操作符號,例如:  7 mod 3 
like:操作符號,例如: "abc" like ‘ab%‘

4、自定義的系統函數,後續還會不斷的添加

   max:取最大值max(3,4,5)
   min:最最小值min(2,9,1)
   round:四舍五入round(19.08,1)
   print:輸出信息不換行print("abc")
   println:輸出信息並換行 println("abc")

5、提供表達式上下文,屬性的值不需要在初始的時候全部加入,而是在表達式計算的時候,需要什麽信息才通過上下文接口獲取。

避免因為不知道計算的需求,而在上下文中把可能需要的數據都加入。
runner.execute("三星賣家 and 消保用戶",errorList,true,expressContext) "三星賣家"和"消保用戶"的屬性是在需要的時候通過接口去獲取。

6、可以將計算結果直接存儲到上下文中供後續業務使用。例如:

  runner.execute("c = 1000 + 2000",errorList,true,expressContext); 
  則在expressContext中會增加一個屬性c=3000,也可以在expressContext實現直接的數據庫操作等。

7、支持高精度浮點運算,只需要在創建執行引擎的時候指定參數即可:new ExpressRunner(true,false);
8、可以將Class和Spring對象的方法映射為表達式計算中的別名,方便其他業務人員的立即和配置。例如

     將 Math.abs() 映射為 "取絕對值"。
      runner.addFunctionOfClassMethod("取絕對值", Math.class.getName(), "abs",new String[] { "double" }, null); 
      runner.execute("取絕對值(-5.0)",null,true,null); 

9、可以為已經存在的boolean運算操作符號設置別名,增加錯誤信息同步輸出,在計算結果為false的時候,同時返回錯誤信息,減少業務系統相關的處理代碼

   runner.addOperatorWithAlias("屬於", "in", "用戶$1不在允許的範圍")。
   用戶自定義的函數同樣也可以設置錯誤信息:例如: 
   runner.addFunctionOfClassMethod("isOk", BeanExample?.class.getName(),"isOk", new String[] { "String" }, "$1 不是VIP用戶"); 
  則在調用:
     List errorList = new ArrayList?(); 
     Object result =runner.execute("( (1+1) 屬於 (4,3,5)) and isOk("玄難")",errorList,true,null); 
     執行結果 result = false.同時在errorList中還會返回2個錯誤原因: 
         1、"用戶 2 不在允許的範圍"
         2、玄難 不是VIP用戶 

10、可以自定義函數,自定一個操作函數 group

class GroupOperator extends Operator {
	public GroupOperator(String aName) {
		this.name= aName;
	}
	public Object executeInner(Object[] list)throws Exception {
		Object result = new Integer(0);
		for (int i = 0; i < list.length; i++) {
			result = OperatorOfNumber.Add.execute(result, list[i]);
		}
		return result;
	}
}

則執行:

     runner.addFunction("累加", new GroupOperator("累加"));
     runner.addFunction("group", new GroupOperator("group"));
    //則執行:group(2,3,4)  = 9 ,累加(1,2,3)+累加(4,5,6)=21

11、可以自定操作符號。自定義的操作符號優先級設置為最高。例如自定一個操作函數 love:

class LoveOperator extends Operator {	
	public LoveOperator(String aName) {
		this.name= aName;
	}
	public Object executeInner(Object[] list)
			throws Exception {
		String op1 = list[0].toString();
		String op2 = list[1].toString();
		String result = op2 +"{" + op1 + "}" + op2;		
		return result;
	}
}

然後增加到運算引擎:

 runner.addOperator("love", new LoveOperator("love"));
    //則執行:‘a‘ love ‘b‘ love ‘c‘ love ‘d‘ = "d{c{b{a}b}c}d"

12、可以重載已有的操作符號。例如替換“+”的執行邏輯。參見:com.ql.util.express.test.ReplaceOperatorTest
13、可以延遲運算需要的數據
14、一個腳本可以調用其它腳本定義的宏和函數.參見com.ql.util.express.test.DefineTest
15、可以類似VB的語法來使用操作符號和函數。print abc; 等價於 print(abc).參見 com.ql.util.express.test.OpCallTest
16、支持類定義
17、對 in 操作支持後面的是一個數組或者List變量義

最典型的應用場景



在業務系統中存在一些邏輯判斷,例如"商品A"只能是三星賣家,而且已經開通淘寶店鋪的用戶才能訂購。
那麽我們期望業務人員看到的配置信息就是:"三星賣家 而且 已經開店"
則通過下列步驟可能實現這個功能:

1、定義一個用戶信息對象:

class UserInfo {
	long id;
	long tag;
        String name;

	public UserInfo(long aId,String aName, long aUserTag) {
		this.id = aId;
		this.tag = aUserTag;
		this.name = aName;
	}
        public String getName(){
    	   return name;
        }
	public long getUserId() {
		return id;
	}

	public long getUserTag() {
		return tag;
	}
}

2、定義兩個基礎的功能函數:

   /**
     * 判斷一個用戶TAG的第X位是否為1。這個的demo,其實現合理性不考慮
     * @param user
     * @param tagBitIndex
     * @return
     */
    public boolean userTagJudge(UserInfo user,int tagBitIndex){
    	boolean r =  (user.getUserTag() & ((long)Math.pow(2, tagBitIndex))) > 0;
    	return r;
    }
	
	/**
	 * 判斷一個用戶是否訂購過某個商品
	 * @param user
	 * @param goodsId
	 * @return
	 */
	public boolean hasOrderGoods(UserInfo user,long goodsId){
		//隨機模擬一個
		if(user.getUserId() % 2 == 1){
			return true;
		}else{
			return false;
		}
	}

3、初始化語句執行器

	public void initial() throws Exception{
		runner.addOperatorWithAlias("而且","and",null);
		runner.addFunctionOfClassMethod("userTagJudge", Demo.class.getName(), "userTagJudge",
                            new String[] {UserInfo.class.getName(),"int"}, "你不是三星賣家");
		runner.addFunctionOfClassMethod("hasOrderGoods", Demo.class.getName(), "hasOrderGoods",
                            new String[] {UserInfo.class.getName(),"long"}, "你沒有開通淘寶店鋪");
		runner.addMacro("三星賣家", "userTagJudge(userInfo,3)");//3表示三星賣家的標誌位
		runner.addMacro("已經開店", "hasOrderGoods(userInfo,100)");//100表示旺鋪商品的ID
	}

4、定義一個邏輯判斷函數:

	/**
	 * 判斷邏輯執行函數
	 * @param userInfo
	 * @param expression
	 * @return
	 * @throws Exception
	 */
	public String hasPermission(UserInfo userInfo,String expression) throws Exception {			
        	IExpressContext<String,Object> expressContext = new DefaultContext<String,Object>();
    		expressContext.put("userInfo",userInfo);
    		List<String> errorInfo = new ArrayList<String>();
            Boolean result = (Boolean)runner.execute(expression, expressContext, errorInfo, true, false);
            String resultStr ="";
            if(result.booleanValue() == true){
            	resultStr = "可以訂購此商品";
            }else{
              for(int i=0;i<errorInfo.size();i++){
            	  if(i > 0){
            		  resultStr  = resultStr + "," ;
            	  }
            	  resultStr  = resultStr + errorInfo.get(i);
              }
              resultStr = resultStr  + ",所以不能訂購此商品";
            }
            return "親愛的" + userInfo.getName() + " : " + resultStr;
    }	

5、調用執行器執行判斷邏輯:

	public static void main(String args[]) throws Exception{
		Demo demo = new Demo();
		demo.initial();
		System.out.println(demo.hasPermission(new UserInfo(100,"xuannan",7),  "三星賣家   而且   已經開店"));
		System.out.println(demo.hasPermission(new UserInfo(101,"qianghui",8), "三星賣家   而且   已經開店"));
		System.out.println(demo.hasPermission(new UserInfo(100,"張三",8), "三星賣家 and 已經開店"));
		System.out.println(demo.hasPermission(new UserInfo(100,"李四",7), "三星賣家 and 已經開店"));

參考資料

http://code.taobao.org/p/QLExpress/wiki/index/

QLExpress 規則引擎使用介紹