乐趣区

关于java:告别if-else试试这款轻量级流程引擎吧自带IDEA插件真香

在咱们平时做我的项目的时候,常常会遇到简单的业务逻辑,如果应用 if else 来实现的话,往往会很简短,保护老本也很高。明天给大家举荐一个轻量级流程引擎LiteFlow,能够优雅地实现简单的业务逻辑,本文将以电商我的项目中的订单价格计算为例来聊聊它的应用。

SpringBoot 实战电商我的项目 mall(50k+star)地址:https://github.com/macrozheng/mall

LiteFlow 简介

LiteFlow 是一个轻量且弱小的国产流程引擎框架,可用于简单的组件化业务的编排工作。通过它咱们能够把业务逻辑都定义到不同组件之中,而后应用简洁的规定文件来串联整个流程,从而实现简单的业务逻辑。

LiteFlow 次要个性如下:

  • 组件定义对立:所有的逻辑都是组件,间接应用 Spring 原生注解 @Component 定义即可。
  • 规定轻量:基于规定文件来编排流程,学习规定表达式入门仅需 5 分钟。
  • 规定多样化:规定反对 xml、json、yml 三种规定文件写法,喜爱哪种用哪个。
  • 任意编排:同步异步混编,再简单的逻辑过程,都能轻易实现。
  • 规定能从任意中央加载:框架中提供本地文件配置源和 zk 配置源的实现,也提供了扩大接口。
  • 优雅热刷新机制:规定变动,无需重启利用,即时扭转利用的规定。
  • 反对宽泛:共事反对 SpringBoot,Spring 或其余 Java 我的项目。

上面是应用 LiteFlow 来实现订单价格计算的展现页面,实现起来的确比拟优雅!

IDEA 插件

LiteFlow 还领有本人的 IDEA 插件LiteFlowX,通过该插件能反对规定文件的智能提醒、语法高亮、组件与规定文件之间的跳转及 LiteFlow 工具箱等性能,强烈建议大家装置下。

  • 首先咱们在 IDEA 的插件市场中装置该插件;
  • 装置好 LiteFlowX 插件后,咱们代码中所定义的组件和规定文件都会显示特定的图标;
  • 当咱们编辑规定文件时,会提醒咱们曾经定义好的组件,并反对从规定文件中跳转到组件;
  • 还反对从右侧关上工具箱,快捷查看组件和规定文件。

规定表达式

接下来咱们学习下规定表达式,也就是规定文件的编写,入门表达式非常简单,但这对应用 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 这个流程引擎框架。

  • 首先咱们须要在我的项目中集成 LiteFlow,这里以 SpringBoot 利用为例,在 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
  • 这里间接应用 LiteFlow 官网的 Demo,该案例为一个价格计算引擎,模仿了电商中对订单价格的计算,并提供了简略的界面,下载地址如下:

https://gitee.com/bryan31/lit…

  • 下载实现后,间接运行 Demo,通过如下地址能够拜访测试页面:http://localhost:8580
  • 这个案例通过传入的订单数据,能计算出订单的最终价格,这里波及到会员折扣、促销优惠、优惠券抵扣、运费计算等操作,多达十几步,如果不应用流程引擎的话实现起来是非常复杂的,上面是订单价格计算中各组件执行流程图;
  • 接下来咱们来聊聊如何应用 LiteFlow 来实现这个性能,首先咱们须要定义好各个组件,一般组件须要继承 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";}
    }
}
  • 其余组件逻辑具体能够参考 demo 源码,定义好组件之后就能够通过规定文件将所有流程连接起来了,首先是促销优惠计算子流程;
<?xml version="1.0" encoding="UTF-8"?>
<flow>
    <chain name="promotionChain">
        THEN(fullCutCmp, fullDiscountCmp, rushBuyCmp);
    </chain>
</flow>
  • 而后是整个流程,大家能够比照下下面的流程图,根本能画出流程图的都能够用 LiteFlow 来实现;
<?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>
  • 最初在 Controller 中增加接口,获取传入的订单数据,而后调用 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";
        }
    }
}
  • 咱们平时在写简单代码时,前面一步常常会用到后面一步的后果,然而应用 LiteFlow 之后,组件里并没有参数传递,那么各个流程中参数是这么解决的?其实是 LiteFlow 中有个上下文的概念,流程中的所有数据都对立寄存在此,比方下面的 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/lit…

退出移动版