最近公司要统计一些数据,需要定义一些统计数据的计算公式,然后通过计算公式统计数据。于是我去找了找有没有什么好用的工具。说实话,现在做开发,网上能找到许多实用的工具,而且还是开源的,非常感谢大佬们的分享。今天要说的是QLExpress。
QLExpress由阿里的电商业务规则、表达式(布尔组合)、特殊数学公式计算(高精度)、语法分析、脚本二次定制等强需求而设计的一门动态脚本引擎解析工具。它具有以下特性:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>QLExpress</artifactId>
<version>3.2.0</version>
</dependency>
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。
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='商品价格表';
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官网。