我们在日常开发中,经常会涉及到一些数学计算,尤其是在金融交易系统中,对手续费,积分的计算更是家常便饭。今天我们以计算订单手续费为例简单介绍一下,日常开发中都是如何进行费用计算的。

写在前面的话

本篇并不是讨论在一个复杂的系统中,如何合理地设计一个高效而复杂的计费系统。只是为了日后大家在日常开发遇到同类问题时,可以多一种选择而已。

假设现在要对支付完成的订单进行手续费计算,系统支持按单笔收取,也支持按百分比收取,并且对单笔最低收取的费用有限制,对单笔上限收取的费用也有限制。

对于上面描述的问题,其实并不是十分复杂,即使是我们为了赶进度,不使用任何其他的第三方类库,硬编码实现也是可以的,无非就是多写一些 if else 而已。当然了,如果你是一个技术大牛,对一些规则引擎比较熟悉,比如 drools 规则引擎,也是可以很优雅地解决上面的问题。

但是,你们知道的,资深北漂假码农是一个假码农。我对那些复杂的业务引擎规则又不是很了解,但是呢又不想写一大堆if else 的判断。一腔热血献技术(装X)的我,从老前辈那里偷了点东西,今天就毫无保留地分享给大家。

下面就来分享下如何通过ONGL(不了解的,请自行搜索)表达式来进行费用的计算。

添加依赖

如果使用maven项目,添加下面的依赖即可,如果是其他打包方式的项目,请自行搜索如何引入第三方类库。

<dependency> <groupId>ognl</groupId> <artifactId>ognl</artifactId> <version>3.3.2</version> </dependency>

编码

编译表达式方法compile,方法接收一个String 类型的参数,即为计算的表达式。

public static Object compile(String expression){ try { return Ognl.parseExpression(expression); } catch (OgnlException e) { e.printStackTrace(); throw new RuntimeException(e); } }

执行表达式方法execute,方法接收两个参数,String类型的计算表达式,Object类型的对象图根(可以简单理解为计算表达式时需要的key-value,实际调用时传入的是Map)。

public static Object execute(String expression,Object root){ logger.info("使用OGNL计算 表达式:{} 参数:{}",expression,root); Map context = Ognl.createDefaultContext(root); try { return Ognl.getValue(compile(expression),context,root); } catch (OgnlException e) { e.printStackTrace(); throw new RuntimeException(e); } }

编写单元测试

现在我们来实现文中开头处所说到的需求,这个时候我们需要两个表达式才能满足我们的需求的,一个是计算费用的表达式:amount*percentage single,其中amount表示本次需要计算金额(如订单金额100元),percentage 表示计费的百分比(如2%),single表示单笔订单收取的费用(如每笔额外收取1.5元),另一个是单笔限额的表达式:fee<min?min:fee<max?fee:max,其中 fee 是根据第一个表达式计算出来的手续费,min表示单笔订单收取的最低费用(如一笔订单最低收取2元,不足2元按2元收取),max表示单笔订单最高收取的费用(如一笔订单最高收取15元,超出15元按15元收取)。

final static String EXPRESSION = "amount*percentage single"; final static String EXPRESSION2 = "fee<min?min:fee<max?fee:max"; @Test public void test(){ Map<String,Object> params = new HashMap<>(); //订单金额 100元 params.put("amount", BigDecimal.valueOf(100L)); //单笔订单收取百2的手续费 params.put("percentage", BigDecimal.valueOf(0.02)); //每笔额外收取1.5元 params.put("single", BigDecimal.valueOf(1.5D)); //计算费用 = 3.5 元 Object o = OgnlUtil.execute(EXPRESSION, params); //计算单笔限额 params.put("fee",o); //每笔最低费用 2 元 params.put("min",BigDecimal.valueOf(2L)); //每笔最高费用15 元 params.put("max",BigDecimal.valueOf(15L)); //计算费用 = 3.5 Object fee = OgnlUtil.execute(EXPRESSION2, params); logger.info("fee:{}",fee); }

以下为计算结果,读者可以自行修改测试数据,以验证表达式的准确性。

开发成本利润率的算法(分享一个实际开发过程中)(1)

测试用例执行结果

优化

对于相同的表达式,没有必要每次都进行编译,我们可以将表达式的编译结果进行缓存起来,最终修改如下:

private static final Map<String,Object> EXPRESSIONS = new HashMap<>(); public static Object compile(String expression){ synchronized (EXPRESSIONS){ return EXPRESSIONS.computeIfAbsent(expression,e ->{ try { return Ognl.parseExpression(expression); } catch (OgnlException ex) { ex.printStackTrace(); throw new RuntimeException(e); } }); } }

好了,本次分享就到这里,感谢大家的阅读。

,