springboot+activiti7+react实现模仿钉钉功能的审批流(十四、发起流程自动预判流程走向)

功能说明:类似钉钉发起流程,发起人填单子过程中,可自动预判该流程走向,方便发起人填单子时候就可以看到这个单子的审批流转情况。

注意:预判的流程走向,并不是固定的,只能根据当时的表单数据做预判,因为表单值在审批过程中是可以被修改的,修改之后流程走向会变,具体的解释请查看本系列文章的第十五章节《springboot+activiti7+react实现模仿钉钉功能的审批流(十五、流程表单操作权限设计)》

图的相关算法,可参考我的另外一篇文章《《算法4》无向图、有向图 (一、深度优先 | 广度优先 | 连通分量 | 可达性分析 | 最短路径)》

本人并未实际具体实现此功能,只是写下自己考虑实现该功能的大致实现思路,具体细节还需要打磨,以供交流,欢迎提出更优方案;话不多的,直接举一个简单栗子:

流程预判,钉钉是这样玩的:

  •  第1步 表单设计:

  • 第2步 流程设计:

  • 第3步 用户发起审批,并自动预判流程走向

条件满足 “请假天数 < 3” 走B审批:

条件不满足 “请假天数 < 3” 走C审批:

具体实现步骤:

先简化下钉钉的这个demo图,就是这个样子:

之前我的这个系列文章里面<实现仿钉钉流程设计器>章节,写过bpmn就是一个有向图结构,所以可以用有向图广度优先遍历算法解决该问题,没看过之前章节的可以先扫盲看一下。

1. 获取流程图bpmn信息

BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
这个bpmnModel对象里面,可以拿到有整个流程图信息,包括所有节点、网关、网关线上的el表达式、还有各种其他信息,找不到的可以打断点dubug查看;

2.  将activiti的bpmn流程图,转换为一个有向图结构,这里用邻接表数据结构存储(当然你用邻接矩阵也行);

用以上面钉钉的演示为例,实现该邻接表数据结构可以有两种方式:

方式1:Map实现
S: [A]
A: [G]
G: [B, C]
B: [D]
C: [D]
D: [E]
E: []

方式1:邻接表数据结构实现(推荐)
字母换数字List邻接表实现,算法实现比map结构更简单而且省了空间:
以这种做映射 0:S 1:A 2:G 3:B 4:C 5:D 6:E,结构如下:
0: [1]
1: [2]
2: [3, 4]
3: [5]
4: [5]
5: [6]
6: []
来源《算法4》一书中的有向图邻接表结构;

代码上一波:

@Test
    public void digraph() {
        /**
         * map结构存储有向图:
         * S: [A]
         * A: [G]
         * G: [B, C]
         * B: [D]
         * C: [D]
         * D: [E]
         * E: []
         * 左边key是节点,右边的value是节点的后续节点
         */
        //为了顺序,用下LinkedHashMap
        Map<String, List<String>> map = new LinkedHashMap<>();
        map.put("S", Arrays.asList("A"));
        map.put("A", Arrays.asList("G"));
        map.put("G", Arrays.asList("B", "C"));
        map.put("B", Arrays.asList("D"));
        map.put("C", Arrays.asList("D"));
        map.put("D", Arrays.asList("E"));
        map.put("E", Arrays.asList());
        //输出邻接表结构
        for (String key : map.keySet()) {
            System.out.println(key + ": " + map.get(key));
        }
        System.out.println();

        /**
         * 邻接表结构存储有向图(推荐!!!):
         * 0: [1]
         * 1: [2]
         * 2: [3, 4]
         * 3: [5]
         * 4: [5]
         * 5: [6]
         * 6: []
         * 左边既是数组下标,也是节点,右边是节点的后续节点,算法实现比map结构更简单而且省了空间
         */
        //初始化邻接表,7是节点数量,这里只是简易演示下,详细的有向图结构和算法请查阅资料
        List<Integer>[] adj = new List[7];
        for (int i = 0; i < 7; i++) {
            adj[i] = new ArrayList<>();
        }
        adj[0] = Arrays.asList(1);
        adj[1] = Arrays.asList(2);
        adj[2] = Arrays.asList(3, 4);
        adj[3] = Arrays.asList(5);
        adj[4] = Arrays.asList(5);
        adj[5] = Arrays.asList(6);
        //输出邻接表结构
        for (int i = 0; i < 7; i++) {
            System.out.println(i + ": " + adj[i]);
        }
    }

3. 模拟发起流程,构造一个Map对象formData,如:{"day": 3, "reason": "有事回家几天"},然后使用有向图的广度优先(这里主要是为了兼容并行网关,没有并行网关也可以用深度优先)遍历算法,从开始(startEvent)节点遍历节点,遇到网关时候用下面代码的gatewayJuelExpression(String el, Map<String, Object> formData)方法,判断网关节点的后续节点;备注:el表达式字符串在网关节点的出线属性上面;

4. 补充第3步,使用有向图广度优先(这里主要是为了兼容并行网关,没有并行网关也可以用深度优先)遍历算法从开始(startEvent)节点遍历节点,如从上图中的S(startEvent)节点开始走,走到A(userTask)节点,然后走到G(exclusiveGateway)节点,网关节点G这里根据el表达式和map参数判断走B(userTask)节点还是C(userTask)节点,然后走D(userTask)节点,最后走E(userTask)节点结束;

遍历网关节点出口要注意:bpmn的排他网关出口线是有顺序的,要按网关的条件顺序依次执行该方法判断,直到为true的条件成立即找到网关的出口,比如a、b、c 是网关G的3个出口线,如果不按顺序可能会走错出口,比如a、b两条线都满足el条件表达式,走错顺序就会有问题);

一个el表达式判断网关走向的代码demo(注意看注释):

/**
     * 计算Activiti排他网关(ExclusiveGateway)的条件表达式(EL表达式)的值
     * ---网关(exclusiveGateway)节点的走向判断---
     * uel表达式解析,该方法的作用是类似activiti的uel表达式,网关(exclusiveGateway)节点根据el表达式和流程变量variables判断走向, 方法可以改造为:
     * Boolean gatewayJuelExpression(String el, Map<String, Object> formData)
     * el为uel表达式,formData为activiti的Map类型的流程变量variables
     * <p>
     * demo:
     * Boolean res = gatewayJuelExpression("${day < 3}", {"day": 3, "reason": "有事回家几天"});
     * 根据el和变量经过uel解析得到的res可以判断网关的走向
     * <p>
     * 注意:bpmn的网关条件(一般网关出口至少2个以上)是有顺序的,要按网关的条件顺序依次执行该方法判断,直到为true的条件成立即找到网关的出口(下一个节点);
     * 另:bpmn网关的最后一个条件可不写条件判断表达式作为默认保底的出口,比如G网关节点有"A/B/C"3个出口节点,依次匹配判断A->B->C条件是否满足,如果匹配不成功则继续进行匹配下一优先级的条件;
     * 当3个条件都不满足时抛异常(事实上activiti就是这么干的,没有满足条件的出口就抛异常,本人就遇到这个坑,发起流程就报异常,原因是网关没有出口!);C不写表达式时,当A/B条件不满足时,默认最后这里会走C出口;
     */
    @Test
    public void gatewayJuelExpression() {
        //el表达式
        String el = "${day < 3}";
        //模拟提交activiti的变量
        Map<String, Object> formData = new HashMap();
        formData.put("day", 3);
        formData.put("reason", "有事回家几天");
        ExpressionFactory factory = new ExpressionFactoryImpl();
        SimpleContext context = new SimpleContext();
        for (Object k : formData.keySet()) {
            if (formData.get(k) != null) {
                context.setVariable(k.toString(), factory.createValueExpression(formData.get(k), formData.get(k).getClass()));
            }
        }
        ValueExpression e = factory.createValueExpression(context, el, Boolean.class);
        //el表达式和variables得到的结果
        Boolean res = (Boolean) e.getValue(context);
        //输出结果 false
        System.out.println(res);
    }

5.将上面遍历过的节点,根据节点走向(遍历顺序),关联到Node,并将Node加到一个List<Node>里面,Node是一个普通对象,里面包含userTask节点的信息(节点名、会签信息...)和审批人(审批人list...)信息;

6.将List<Node>返给前端,前端像钉钉一样,从上到下展示出来即可;

以上遍历过程中的用户节点(userTask),也适用于服务节点(serviceTask);

我之前的这个系列文章里面,写了有借助服务节点(serviceTask)实现抄送功能,有开发中需要实现抄送功能的可以借鉴;

注意事项:

钉钉的流程是比较简单的流程,都是有向无环图,图中不会存在有向环情况,所以暂时不用判断是否存在有向环情况;

若是复杂的流程图,如activiti modeler和bpmnjs设计出来的图,会有各种情况,需要注意用marked[]数组标记走过的节点,判断是否存在有向环的情况,避免遍历节点时候出现死循环或者栈溢出,如下图这种:

最后补一段 <计算Activiti用户(userTask)节点表达式(EL表达式)的值> 的代码:

/**
     * 计算Activiti用户(userTask)节点表达式(EL表达式)的值
     * ---用户(userTask)节点从流程变量中得到值---
     * uel表达式解析,该方法的作用是类似activiti的uel表达式,用户(userTask)节点根据el表达式和流程变量variables得到该节点上的值, 比如得到这个节点的审批人list,方法可以改造为:
     * List<String> assignJuelExpression(String el, Map formData)
     * el为uel表达式,formData为activiti的Map类型的流程变量variables
     * <p>
     * demo:
     * List<String> assigns = assignJuelExpression("${assigns}", {"assigns": users集合});
     * 根据el和变量经过uel解析得到的res可以判断网关的走向
     */
    @Test
    public void assignJuelExpression() {
        //el表达式
        String el = "${assigns}";
        //模拟提交activiti的变量
        List<String> users = Arrays.asList(new String[]{"admin", "lisi", "zhangsan"});
        Map<String, Object> formData = new HashMap();
        formData.put("assigns", users);
        ExpressionFactory factory = new ExpressionFactoryImpl();
        SimpleContext context = new SimpleContext();
        for (Object k : formData.keySet()) {
            if (formData.get(k) != null) {
                context.setVariable(k.toString(), factory.createValueExpression(formData.get(k), formData.get(k).getClass()));
            }
        }
        ValueExpression e = factory.createValueExpression(context, el, List.class);
        //输出结果 [admin, lisi, zhangsan]
        List<String> assigns = (List) e.getValue(context);
        System.out.println(assigns);
    }

 

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
### 回答1: 基于Spring Boot和Activiti,我们可以实现一个简单的流程审批系统。首先,需要搭建一个Spring Boot项目,并添加Activiti的相关依赖。然后,我们可以使用Activiti提供的API来定义流程发起审批和处理审批。 在项目中,我们可以通过编写BPMN 2.0的定义文件来定义流程。在该文件中,我们可以添加活动(任务),并定义活动之间的流程转条件。然后,使用Activiti的API部署该流程定义到Activiti引擎中。 在发起审批时,可以通过调用Activiti的API来启动一个流程实例。在启动流程实例时,可以设置相关的参数,如申请人、审批金额等。Activiti会根据流程定义自动创建相应的流程实例和任务。 在处理审批时,可以根据当前用户查询待办任务列表,并选择某个任务进行处理。处理任务可以包括审批通过、驳回、转交等操作。在处理任务时,可以通过Activiti的API设置相关的参数,并将任务转到下一步或退回上一步。 此外,我们还可以添加监听器来监听流程的各个状态,如流程启动、任务分配、任务完成等。通过监听器,我们可以在流程转时执行一些自定义的逻辑,如发送邮件通知、记录审批历史等。 综上所述,基于Spring Boot和Activiti,我们可以实现一个简单的流程审批系统。通过配置流程定义、发起审批和处理审批,我们可以实现审批流程自动化和可视化。同时,通过添加监听器和自定义逻辑,我们可以满足不同业务场景下的特定需求。 ### 回答2: 利用Spring Boot和Activiti可以很方便地实现流程审批功能。下面我将详细介绍如何使用Spring Boot和Activiti实现流程审批。 首先,需要在Spring Boot项目的pom.xml文件中添加Activiti的依赖。可以在Maven仓库中找到最新的Activiti版本,并将其添加到pom.xml文件中。 接下来,需要创建Activiti的配置类,以便在Spring Boot中集成Activiti。这个配置类应该继承自Activiti的ProcessEngineConfigurationConfigurer接口,并实现其configure方法。在configure方法中,我们可以进行一些Activiti的配置,比如设置数据库相关的配置、添加流程监听器等。 然后,需要创建流程定义文件。可以使用Activiti提供的图形化工具来创建流程定义文件,也可以通过编码的方式来创建。在流程定义文件中,需要定义流程的各个节点,以及节点之间的连线和流程变量。 接下来,需要编写与流程审批相关的业务逻辑代码。这些代码应该包括启动流程实例、查询待办任务、完成任务等功能。可以使用Activiti提供的API来完成这些操作。 最后,需要创建前端页面用于展示和处理审批任务。可以使用Thymeleaf等模板引擎来创建页面,并通过Ajax请求与后端进行数据交互。 综上所述,借助于Spring Boot和Activiti,我们可以快速便捷地实现流程审批功能。这种方式不仅可以提高开发效率,还有利于代码的维护和扩展。希望以上内容对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小绿豆

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值