Fel表达式实践
项目背景
订单完成后,会由交易系统推送实时MQ消息给订单清算系统,告诉清算系统此订单交易完成,可以进行给商家结算等后续操作。
财务要求在交易推送订单到清算系统时和订单清算系统接收到订单消息后,需要按照财务给定的校验公式,验证交易推送的数据是否正确。比如下面的财务公式:
- 商品原价 = 商品活动价 + 活动价补贴
- 在线支付=微信+支付宝+QQ钱包+会员卡支付+翼支付
- 用户实付=在线支付+现金
- 订单总额=在线支付金额+会员卡优惠+代金券优惠+公司活动补贴+商家活动价补贴+现金支付
财务同学明确告知,随着支付网关后续接入更多支付渠道整体公式都要随着变化。另外营销手段的丰富化(拼团返现等),相应结算相关的金额都要纳入财务公式计算和校验中。故公式整体是不断变化的。
方案
金额单元参数化
为了应对后续财务相关字段的随时扩充,项目组组长确定使用大Json,单元参数形式表示非订单基础信息。形如:
[{"K":10112,"V":"2100"},{"K":10113,"V":"10"},{"K":10115,"V":"2300"},{"K":10117,"V":"0"}]
具体Key字段(比如:10112、10113、10115)含义由枚举形式维护。并人为规定10000~20000段为金额相关字段。
字典表或配置中心存储校验公式
校验公式前期是存储在MySQL的一张字典表里。后期有计划迁移到架构部维护的统一配置中心里。部分校验公式形如下:
1. p10125-(p10101+p10106+p10108+p10131+p10113)
2. p10101-(p10102+p10103+p10104+p10105+p10130)
3. p10108-(p10109+p10110+p10111+p10133)
4. p10125-(p10131+p10124)
5. p10131-(p10112+p10132)
6. p10107-(p10132+p10133)
7. p10117-(p10134+p10135+p10136+p10140+p10137+p10138+p10139)
其实,通过看上面一大堆公式,也可以看出 使用这种编码化的单元参数,可读性太差了!!
公式校验
在订单清算系统接收到到订单MQ消息时,会遍历消息体内全部金额相关字段写入到FelContext内,为后面计算做准备。
校验前准备:
private FelContext getFelContext(OrderSalary orderSalary, FelEngine fel) {
FelContext ctx = fel.getContext();
ParamEnum[] enums = ParamEnum.values();
for (ParamEnum paramEnum : enums) {
if (paramEnum.getCode() < 20000) {
// 获取对应的Value值
String value = ParamapUtil.getSingleton().getEntityParamValueByParamID(orderSalary, paramEnum.getCode()) == null ? "0" : ParamapUtil.getSingleton().getEntityParamValueByParamID(orderSalary, paramEnum.getCode());
// 以 p11002 , 2000 的形式写入FelContext
ctx.set("p" + paramEnum.getCode(), Long.parseLong(value));
}
}
return ctx;
}
公式校验: 比如 ( 用户实付 - (在线支付+现金) = 0 )
// checkValue为全部检验公式,是从字典表或者配置中心获取的
for (String check : checkValue) {
Expression exp = fel.compile(check, ctx);
Object result = exp.eval(ctx);
Long checkNumber = NumberUtil.toLong(result);
if (checkNumber != 0) {
logger.error("orderId={} 规则校验错误:{}", new Object[]{orderSalary.getOrderId(), check});
return Boolean.FALSE;
}
}
其他场景
比如计算本单实发,也就是本单实际给商家结算金额,就可以表达式引擎。我们公司旧的计算规则:
本单实发=结算金额-退款金额+商家补贴撤回+现金退款-现金支付-商家信息服务费-商家承税;
最后
还是想吐槽这种编码形式的单元参数设计,可读性太差了。人为去维护code和具体含义的枚举,一旦维护乱了,后面的人就再也不知道这个编码的含义了。
加油