在我们平时做项目的时候,经常会遇到复杂的业务逻辑,如果使用if else来实现的话,往往会很冗长,维护成本也很高。今天给大家推荐一个轻量级流程引擎
LiteFlow
,可以优雅地实现复杂的业务逻辑,本文将以电商项目中的订单价格计算为例来聊聊它的使用。
LiteFlow是一个轻量且强大的国产流程引擎框架,可用于复杂的组件化业务的编排工作。通过它我们可以把业务逻辑都定义到不同组件之中,然后使用简洁的规则文件来串联整个流程,从而实现复杂的业务逻辑。
LiteFlow主要特性如下:
@Component
定义即可。下面是使用LiteFlow来实现订单价格计算的展示页面,实现起来确实比较优雅!
LiteFlow还拥有自己的IDEA插件
LiteFlowX
,通过该插件能支持规则文件的智能提示、语法高亮、组件与规则文件之间的跳转及LiteFlow工具箱等功能,强烈建议大家安装下。
接下来我们学习下规则表达式,也就是规则文件的编写,入门表达式非常简单,但这对使用LiteFlow非常有帮助!
当我们想要依次执行a、b、c、d四个组件时,直接使用THEN
关键字即可。
<chain name="chain1">
THEN(a, b, c, d);
</chain>
如果想并行执行a、b、c三个组件的话,可以使用WHEN
关键字。
<chain name="chain1">
WHEN(a, b, c);
</chain>
如果想实现代码中的switch逻辑的话,例如通过a组件的返回结果进行判断,如果返回的是组件名称b的话则执行b组件,可以使用SWITCH
关键字。
<chain name="chain1">
SWITCH(a).to(b, c, d);
</chain>
如果想实现代码中的if逻辑的话,例如当x组件返回为true时执行a,可以使用IF
关键字。
<chain name="chain1">
IF(x, a);
</chain>
如果想实现if的三元运算符逻辑的话,例如x组件返回为true时执行a组件,返回为false时执行b组件,可以编写如下规则文件。
<chain name="chain1">
IF(x, a, b);
</chain>
如果想实现if else逻辑的话,可以使用ELSE
关键字,和上面实现效果等价。
<chain name="chain1">
IF(x, a).ELSE(b);
</chain>
如果想实现else if逻辑的话,可以使用ELIF
关键字。
<chain name="chain1">
IF(x1, a).ELIF(x2, b).ELSE(c);
</chain>
当某些流程比较复杂时,我们可以定义子流程,然后在主流程中引用,这样逻辑会比较清晰。
例如我们有如下子流程,执行C、D组件。
<chain name="subChain">
THEN(C, D);
</chain>
然后我们直接在主流程中引用子流程即可。
<chain name="mainChain">
THEN(
A, B,
subChain,
E
);
</chain>
学习完规则表达式后,我们发现LiteFlow寥寥几个关键字,就可以实现复杂的流程了。下面我们将以订单价格计算为例,实践下LiteFlow这个流程引擎框架。
pom.xml
中添加如下依赖即可;<dependency>
<groupId>com.yomahub</groupId>
<artifactId>liteflow-spring-boot-starter</artifactId>
<version>2.8.5</version>
</dependency>
application.yml
,配置好LiteFlow的规则文件路径即可;server:
port: 8580
liteflow:
#规则文件路径
rule-source: liteflow/*.el.xml
https://gitee.com/bryan31/liteflow-example
NodeComponent
并实现process()
方法,比如这里的优惠券抵扣组件,还需设置@Component
注解的名称,可以通过重写isAccess
方法来决定是否执行该组件;/**
* 优惠券抵扣计算组件
*/
@Component("couponCmp")
public class CouponCmp extends NodeComponent {
@Override
public void process() throws Exception {
PriceContext context = this.getContextBean(PriceContext.class);
/**这里Mock下根据couponId取到的优惠卷面值为15元**/
Long couponId = context.getCouponId();
BigDecimal couponPrice = new BigDecimal(15);
BigDecimal prePrice = context.getLastestPriceStep().getCurrPrice();
BigDecimal currPrice = prePrice.subtract(couponPrice);
context.addPriceStep(new PriceStepVO(PriceTypeEnum.COUPON_DISCOUNT,
couponId.toString(),
prePrice,
currPrice.subtract(prePrice),
currPrice,
PriceTypeEnum.COUPON_DISCOUNT.getName()));
}
@Override
public boolean isAccess() {
PriceContext context = this.getContextBean(PriceContext.class);
if(context.getCouponId() != null){
return true;
}else{
return false;
}
}
}
NodeSwitchComponent
并实现processSwitch()
方法;/**
* 运费条件组件
*/
@Component("postageCondCmp")
public class PostageCondCmp extends NodeSwitchComponent {
@Override
public String processSwitch() throws Exception {
PriceContext context = this.getContextBean(PriceContext.class);
//根据参数oversea来判断是否境外购,转到相应的组件
boolean oversea = context.isOversea();
if(oversea){
return "overseaPostageCmp";
}else{
return "postageCmp";
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="promotionChain">
THEN(fullCutCmp, fullDiscountCmp, rushBuyCmp);
</chain>
</flow>
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="mainChain">
THEN(
checkCmp, slotInitCmp, priceStepInitCmp,
promotionConvertCmp, memberDiscountCmp,
promotionChain, couponCmp,
SWITCH(postageCondCmp).to(postageCmp, overseaPostageCmp),
priceResultCmp, stepPrintCmp
);
</chain>
</flow>
FlowExecutor
类的执行方法即可;@Controller
public class PriceExampleController {
@Resource
private FlowExecutor flowExecutor;
@RequestMapping(value = "/submit", method = RequestMethod.POST)
@ResponseBody
public String submit(@Nullable @RequestBody String reqData) {
try {
PriceCalcReqVO req = JSON.parseObject(reqData, PriceCalcReqVO.class);
LiteflowResponse response = flowExecutor.execute2Resp("mainChain", req, PriceContext.class);
return response.getContextBean(PriceContext.class).getPrintLog();
} catch (Throwable t) {
t.printStackTrace();
return "error";
}
}
}
PriceContext
类;public class PriceContext {
/**
* 订单号
*/
private String orderNo;
/**
* 是否境外购
*/
private boolean oversea;
/**
* 商品包
*/
private List<ProductPackVO> productPackList;
/**
* 订单渠道
*/
private OrderChannelEnum orderChannel;
/**
* 会员CODE
*/
private String memberCode;
/**
* 优惠券
*/
private Long couponId;
/**
* 优惠信息
*/
private List<PromotionPackVO> promotionPackList;
/**
* 价格步骤
*/
private List<PriceStepVO> priceStepList = new ArrayList<>();
/**
* 订单原始价格
*/
private BigDecimal originalOrderPrice;
/**
* 订单最终价格
*/
private BigDecimal finalOrderPrice;
/**
* 步骤日志
*/
private String printLog;
}
slotInitCmp
组件中,我们早已从getRequestData()
方法中获取到了请求的订单参数,然后设置到了PriceContext
上下文中,流程中的其他参数和结果也存储在此了。/**
* Slot初始化组件
*/
@Component("slotInitCmp")
public class SlotInitCmp extends NodeComponent {
@Override
public void process() throws Exception {
//把主要参数冗余到slot里
PriceCalcReqVO req = this.getRequestData();
PriceContext context = this.getContextBean(PriceContext.class);
context.setOrderNo(req.getOrderNo());
context.setOversea(req.isOversea());
context.setMemberCode(req.getMemberCode());
context.setOrderChannel(req.getOrderChannel());
context.setProductPackList(req.getProductPackList());
context.setCouponId(req.getCouponId());
}
@Override
public boolean isAccess() {
PriceCalcReqVO req = this.getSlot().getRequestData();
if(req != null){
return true;
}else{
return false;
}
}
}
LiteFlow确实是一款好用的轻量级流程引擎,可以让复杂的业务逻辑变得清晰起来,便于代码维护。它的规则文件比起其他流程引擎来说,编写简单太多了,几分钟就能上手,感兴趣的朋友可以尝试下它!
官网文档:https://liteflow.yomahub.com/
https://gitee.com/dromara/liteFlow