当前位置: 首页 > 工具软件 > QLExpress > 使用案例 >

使用QLExpress动态制定计算公式

童花蜂
2023-12-01

QLExpress的使用

最近公司要统计一些数据,需要定义一些统计数据的计算公式,然后通过计算公式统计数据。于是我去找了找有没有什么好用的工具。说实话,现在做开发,网上能找到许多实用的工具,而且还是开源的,非常感谢大佬们的分享。今天要说的是QLExpress。

QLExpress的简单介绍

​ QLExpress由阿里的电商业务规则、表达式(布尔组合)、特殊数学公式计算(高精度)、语法分析、脚本二次定制等强需求而设计的一门动态脚本引擎解析工具。它具有以下特性:

  1. 线程安全。
  2. 高效执行。
  3. 弱类型脚本语言。
  4. 安全控制。
  5. 代码精简,依赖最小。

简单的例子

  1. 添加依赖
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>QLExpress</artifactId>
  <version>3.2.0</version>
</dependency>
  1. 添加代码
public static void main(String[] args) throws Exception{
    String express = "2 * 3 / 2 + 4 - 5";
    ExpressRunner runner = new ExpressRunner();
    Object result = runner.execute(express,null, null, false,false);
    // 输出结果,结果为2,使用的时候还是挺方便的
    System.out.println("计算公式的结果:" + result);
}

/**
 * 以下复制于官网的说明
 * 
 * 执行一段文本
 * @param expressString 程序文本
 * @param context 执行上下文,可以扩展为包含ApplicationContext
 * @param errorList 输出的错误信息List
 * @param isCache 是否使用Cache中的指令集,建议为true
 * @param isTrace 是否输出详细的执行指令信息,建议为false
 * @param aLog 输出的log
 * @return
 * @throws Exception
 */
Object execute(String expressString, IExpressContext<String,Object> context,List<String> errorList, boolean isCache, boolean isTrace, Log aLog);

​ 当然,这只是一个简单的例子,QLExpress可不止这样。

实战场景

​ 背景之前也说过了,公式需要先设置一个计算公式,而计算公式可不是像"2 * 3 / 2 + 4 - 5"这样直接将需要计算的值直接写死在公式里的,而是像这样:“avg(item_code_f,item_code_b) +item_code_a ÷ item_code_k - 100”,item_code_f这类数据其实就是数据表里的唯一编码。

​ 计算的时候需要先把通过编码去查找对应的数据值,然后带入公式。其中要注意的是avg是我们自定义的计算符号,意为计算平均值。而且为了用户方便理解,乘法计算的符号是用×而不是*,除法则是÷而不是/,所以这些东西都需要自己去处理。

​ 于是我整理了一下思路,想出了解决的办法,下面是我写的一个demo,可能有些地方略显粗糙,但是我总不能把公司代码直接贴出来,将就着看吧 T_T。

  1. 表结构
CREATE TABLE `calculation_rules` (
  `id` varchar(32) CHARACTER SET utf8 NOT NULL COMMENT '主键',
  `module_id` varchar(32) CHARACTER SET utf8 NOT NULL COMMENT '数据板块id',
  `rule` varchar(255) CHARACTER SET utf8 NOT NULL COMMENT '计算公式',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='计算公式表';
CREATE TABLE `data_item_table` (
  `id` varchar(32) NOT NULL COMMENT '主键',
  `item_code` varchar(10) NOT NULL COMMENT '数据项编码',
  `item_name` varchar(255) NOT NULL COMMENT '数据项名称',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_item_code` (`item_code`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='数据项表';
CREATE TABLE `commodity_price` (
  `id` varchar(32) NOT NULL COMMENT '主键',
  `item_code` varchar(32) NOT NULL COMMENT '数据项编码',
  `insert_time` datetime NOT NULL COMMENT '日期',
  `amount` decimal(13,2) NOT NULL COMMENT '数量/金额,这里反正根据需求,就是一个计算要用到的值',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品价格表';
  1. 如何通过表id去查找对应的数据值,然后带入公式。
public static void main(String[] args) {
    // 统计时,会传递日期和数据板块id,数据板块id的用处就是获取计算公式
    // 假如这就是我们获取到的某一个数据板块的公式
    String str = "avg(item_code_f,item_code_b) +item_code_a ÷ item_code_k - 100";
    // 处理一下÷和×,毕竟是以*和/作为乘法除法符号的
    str = str.replace("÷","/");
    str = str.replace("×","*");
    // 定义一个正则表达式,过滤掉计算符号
    String regex = "[()*+/-]";
    Pattern p = Pattern.compile(regex);
    Matcher m = p.matcher(str);
    List<String> list = new ArrayList<>();
    // 这里注意要先转成Set然后再转成List,因为有可能计算的时候要使用一个数据项的值多次,但是其实都是同一个值
    list.addAll(Arrays.asList(m.replaceAll(" ").split(" ")).stream().filter(s->{
       // 通过流来单独过滤avg和数字
       return !s.equals("avg")&&!s.equals("")&&!isNumeric(s);
    }).collect(Collectors.toSet()));
    // 输出一下结果:[item_code_f,item_code_b,item_code_a,item_code_k],这样就成功的将数据项编码给分离出来
    System.out.println(list.toString());
    // 然后通过数据项编码可以把对应的统计日期下,对应的数据项编码的数量或者金额获取到,然后通过replace()方法替换调即可
    // 具体从数据库里取值和替换的代码省略。。。嘿嘿,偷个懒 >_<||| 
    // 最后得到大概这样的数据:avg(1000,2000)+30/2-100
    String express = "avg(1000,2000)+30/2-100";
    // 接下来就是使用QLExpress了
    ExpressRunner runner = new ExpressRunner();
    // 先定义我们需要的avg函数
    runner.addFunction("avg",new Operator(){
        @Override
        public Object executeInner(Object[] objArray) throw Exception{
            Double total=0.0;
            Double average=0.0;
            for(Double obj: objArray){
                Double num=Double.valueOf(obj.toString());
                total = total + num;
            }
            average = total/objArray.length;
            return average;
        }
    });
    // 计算结果,这里是一个重载方法,没有log
  	Object result = runner.execute(express,null, null, false,false);
    // 顺利拿到结果:1415
    Double resultNum = Double.valueOf(result.toString());
}

/**
 * 判断是否是数字
 */
public static boolean isNumeric(String str){
    Pattern pattern = Pattern.compile("[0-9]*");
    Matcher isNum = pattern.matcher(str);
    if( !isNum.matches() ){
        return false;
    }
    return true;
}

​ QLExpress使用还是挺方便的,而且也很简单,而我上面展示的也只是它的一小部分。实战是最好的老师,有兴趣的同学可以自己去敲上一段代码,另附上官网地址:QLExpress官网

 类似资料: