强大的QLExpress

一只会飞的鱼儿 1年前 ⋅ 3964 阅读
ad

背景:

最近上头让研究各种物流的运费计算,并获取最优配送物流方案,无意之间搜索到了QLExpress,它是阿里的一个组件,一种规则引擎的算法,QLExpress在阿里集团内部有很强的影响力,被广泛应用在电商场景中,在业务灵活多变的场景下常常需要规则引擎的支持。所以我就动手自己实现了一下

GitHub地址:https://github.com/alibaba/QLExpress

一、优势

QLExpress脚本引擎被广泛应用在阿里的电商业务场景,具有以下的一些特性:

1、线程安全,引擎运算过程中的产生的临时变量都是threadlocal类型。
2、高效执行,比较耗时的脚本编译过程可以缓存在本地机器,运行时的临时变量创建采用了缓冲池的技术,和groovy性能相当。
3、弱类型脚本语言,和groovy,javascript语法类似,虽然比强类型脚本语言要慢一些,但是使业务的灵活度大大增强。
4、安全控制,可以通过设置相关运行参数,预防死循环、高危系统api调用等情况。
5、代码精简,依赖最小,250k的jar包适合所有java的运行环境,在android系统的低端pos机也得到广泛运用。

二、引入依赖的jar

<!-- QLExpress -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>QLExpress</artifactId>
<version>${qlexpress.version}</version>
</dependency>
<qlexpress.version>3.2.0</qlexpress.version>

用到的版本是3.2.0

三、用法

操作符和java对象操作:

//支持 +,-,*,/,<,>,<=,>=,==,!=,<>【等同于!=】,%,mod【取模等同于%】,++,--,
//in【类似sql】,like【sql语法】,&&,||,!,等操作符
//支持for,break、continue、if then else 等标准的程序控制逻辑
n=10;
for(sum=0,i=0;i<n;i++){
sum=sum+i;
}
return sum;

//逻辑三元操作
a=1;
b=2;
maxnum = a>b?a:b;

和java语法相比,要避免的一些ql写法错误

不支持try{}catch{}
不支持java8的lambda表达式
不支持for循环集合操作for (GRCRouteLineResultDTO item : list)
弱类型语言,请不要定义类型声明,更不要用Templete(Map<String,List>之类的)
array的声明不一样
min,max,round,print,println,like,in 都是系统默认函数的关键字,请不要作为变量名
QL用法:

是否符合 = 1;运费 = 0;如果 (30 < 重量 ) 则
{ 是否符合 = 0; return 是否符合;}
最长边 = 长 > 宽 ? 长 : 宽;
最长边 = 最长边 > 高 ? 最长边 : 高;
最短边 = 长 < 宽 ? 长 : 宽;
最短边 = 最短边 < 高 ? 最短边 : 高;
中间边 = (长 + 宽 + 高) - 最长边 - 最短边;
围长 = (中间边 + 最短边) * 2 + 最长边;
如果 (围长 > 300) 则
{ 是否符合 = 0; return 是否符合;}
如果 (最长边 > 175) 则
{ 是否符合 = 0; return 是否符合;}

java代码调用:

public class DPDRuleTest {
   @Test
   public void DPDRuleTest() throws Exception {
    List<String> ruleFileNames = new ArrayList<String>();
    ruleFileNames.add("rule/GB/DPD/DPD_UK.ql");
    for (int i = 0; i < ruleFileNames.size(); i++) {
       String script = getResourceAsStream(ruleFileNames.get(i));
       ExpressRunner runner = new ExpressRunner(false, false);
       runner.addOperatorWithAlias("如果", "if", null);
       runner.addOperatorWithAlias("则", "then", null);
       runner.addOperatorWithAlias("否则", "else", null);
       IExpressContext<String, Object> context = new  
       DefaultContext<String, Object>();
       try {
         context.put("长", 20);
         context.put("宽", 20);
         context.put("高", 20);
         context.put("重量", 10);
         context.put("COUNTRY","IS");
         runner.execute(script, context, null, true, false);
         if (String.valueOf(context.get("是否符合")).equals("1")) {
           System.out.println("文件名称:" + ruleFileNames.get(i));
           System.out.println("最长边:" + context.get("最长边"));
           System.out.println("中间边:" + context.get("中间边"));
           System.out.println("最短边:" + context.get("最短边"));
           System.out.println("是否符合:" + context.get("是否符合"));
           System.out.println("运费:" + context.get("运费")); }
      } catch (Exception e) {
         e.printStackTrace();
        //Assert.assertTrue(e.toString().contains("at line 7"));
      }
   }
  }
  public static String getResourceAsStream(String path) throws Exception {
     InputStream in = 
       Thread.currentThread().getContextClassLoader()
       .getResourceAsStream(path);
     if (in == null) {
     throw new Exception("classLoader中找不到资源文件:" + path);
     }
     BufferedReader reader = new BufferedReader(new 
     InputStreamReader(in, "utf-8"));
     StringBuilder builder = new StringBuilder();
     String tmpStr = null; 
     while ((tmpStr = reader.readLine()) != null) {
       builder.append(tmpStr).append("\n");
     }
     reader.close();
     in.close();
     return builder.toString();
   }
}

 

各种规则定义QL:

规则ql文件

也可以把这些规则全都放到数据库字段里面保存起来,并且页面化,进行可配置操作,达到方便配置的效果。

如下就是我们项目中一个规则引擎表设计:

 

关于Webfunny

Webfunny专注于前端监控系统,前端埋点系统的研发。 致力于帮助开发者快速定位问题,帮助企业用数据驱动业务,实现业务数据的快速增长。支持H5/Web/PC前端、微信小程序、支付宝小程序、UniApp和Taro等跨平台框架。实时监控前端网页、前端数据分析、错误统计分析监控和BUG预警,第一时间报警,快速修复BUG!支持私有化部署,Docker容器化部署,可支持千万级PV的日活量!

  点赞 0   收藏 0
  • 一只会飞的鱼儿
    共发布53篇文章 获得8个收藏
全部评论: 0