SpringBoot集成Flowable实践

简介: 实际上是定义一个表单form表,一个deploy_form表。form存放动态表单json,deploy_form表建立流程模型部署信息与表单信息的关系。流程模型挂载表单实际上就是往deply_form存放模型挂载的表单信息。 目的为了当通过模型启动流程实例的时候,将流程模型(部署id)对应的动态表单json取出来。

1、引入依赖

        <flowable.version>6.7.2</flowable.version>

        <dependency>
            <groupId>org.flowable</groupId>
            <artifactId>flowable-engine</artifactId>
        </dependency>

        <dependency>
            <groupId>org.flowable</groupId>
            <artifactId>flowable-spring-boot-starter-basic</artifactId>
        </dependency>

2、flowable核心概念解释

flowable核心概念和表解释

由于flowable-starter 内置集成了repositoryService,historyService,runtimeService等等因此直接注入到业务类上即可

在这里插入图片描述

3、Flowable实践

3.1、部署流程模型

前端上传的是xml文件字符串,转io流

/**
     * 导入流程文件
     *
     * @param name
     * @param category
     * @param in
     */
    @Override
    public void importFile(String name, String category, InputStream in) {
        // 部署流程定义文件
        // 1、部署信息存储
        // 2、存储bpmn文件 和  bpmn.png
        // 3、存储流程定义基本信息
        Deployment deploy = repositoryService.createDeployment()
                .addInputStream(name + BPMN_FILE_SUFFIX, in).name(name).category(category)
                .deploy();
        // 通过部署id,获取部署完成的流程定义
        ProcessDefinition definition = repositoryService.createProcessDefinitionQuery()
                .deploymentId(deploy.getId()).singleResult();
        // 设置流程定义分类
        repositoryService.setProcessDefinitionCategory(definition.getId(), category);
    }

涉及以下几张表
act_re_deployment:会有一条部署记录,记录此次部署的基本信息
act_ge_bytearray:有两条记录,记录的是本次上传的bpmn文件和对应的图片文件,每条记录都有act_re_deployment表的外键关联
act_re_procdef:有一条记录,记录的是该bpmn文件包含的基本信息,包含act_re_deployment表外键

发生更新,会生成一条新的部署记录,和新的流程模型定义记录

3.2、流程模型挂载表单

实际上是定义一个表单form表,一个deploy_form表。form存放动态表单json,deploy_form表建立流程模型部署信息与表单信息的关系。
流程模型挂载表单实际上就是往deply_form存放模型挂载的表单信息。 目的为了当通过模型启动流程实例的时候,将流程模型(部署id)对应的动态表单json取出来。

3.3、启动流程

1、启动流程需要,提交流程定义id,和表单提供的变量map
2、申请人启动流程,那么自动完成申请任务,追加任务处理人,处理意见。通过taskService.complete完成任务
3、注意:第一,需要将流程发起者存储,以便后续取出。第二,一般情况下,任务只有最新的待完成,开在ru_task表看到。第三,ru相关表只会存储运行的流程数据,流程结束便会删除

 /**
     * 根据流程定义ID启动流程实例
     *
     * @param procDefId 流程定义Id
     * @param variables 流程变量
     * @return
     */
    @Override
    public AjaxResult startProcessInstanceById(String procDefId, Map<String, Object> variables) {
        try {
            // 流程定义被挂起,将无法激活流程
            ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(procDefId)
                    .latestVersion().singleResult();
            if (Objects.nonNull(processDefinition) && processDefinition.isSuspended()) {
                return AjaxResult.error("流程已被挂起,请先激活流程");
            }

            // 设置流程发起人Id到流程中
            SysUser sysUser = SecurityUtils.getLoginUser().getUser();
            // 设置流程发起者  设置前者可以在历史流程实例中查询到,设置后置可以在表单变量中获取流程发起者
            identityService.setAuthenticatedUserId(sysUser.getUserId().toString());
            variables.put(ProcessConstants.PROCESS_INITIATOR, sysUser.getUserId().toString());

            // 给第一步申请人节点设置任务执行人和意见,并且完成任务
            ProcessInstance processInstance = runtimeService.startProcessInstanceById(procDefId, variables);
            Task task = taskService.createTaskQuery().processInstanceId(processInstance.getProcessInstanceId()).singleResult();
            if (Objects.nonNull(task)) {
                taskService.addComment(task.getId(), processInstance.getProcessInstanceId(), FlowComment.NORMAL.getType(), sysUser.getNickName() + "发起流程申请");
                taskService.setAssignee(task.getId(), sysUser.getUserId().toString());
                taskService.complete(task.getId(), variables);
            }
            return AjaxResult.success("流程启动成功");
        } catch (Exception e) {
            e.printStackTrace();
            return AjaxResult.error("流程启动错误");
        }
    }

涉及表

act_ru_execution:插入一条记录,记录这个流程定义的执行实例,其中id和proc_inst_id相同都是流程执行实例id,也就是本次执行这个流程定义的id,包含流程定义的id外键
act_ru_task:插入一条记录,记录的是第一个任务的信息,也就是开始执行第一个任务。包括act_ru_execution表中的execution_id外键和proc_inst_id外键,也就是本次执行实例id
act_hi_procinst:插入一条记录,记录的是本次执行实例的历史记录
act_hi_taskinst:插入一条记录,记录的是本次任务的历史记录

3.4、获取当前用户待处理的任务

/**
     * 代办任务列表
     *
     * @param pageNum  当前页码
     * @param pageSize 每页条数
     * @return
     */
    @Override
    public AjaxResult todoList(Integer pageNum, Integer pageSize) {
        Page<FlowTaskDto> page = new Page<>();
        Long userId = SecurityUtils.getLoginUser().getUser().getUserId();
        // 获取当前用户的待处理的任务
        TaskQuery taskQuery = taskService.createTaskQuery()
                .active().includeProcessVariables()
                .taskAssignee(userId.toString())
                .orderByTaskCreateTime().desc();
        page.setTotal(taskQuery.count());
        List<Task> taskList = taskQuery.listPage(pageSize * (pageNum - 1), pageSize);
        
        List<FlowTaskDto> flowList = new ArrayList<>();
        for (Task task : taskList) {
            FlowTaskDto flowTask = new FlowTaskDto();
            // 当前流程信息
            flowTask.setTaskId(task.getId());
            flowTask.setTaskDefKey(task.getTaskDefinitionKey());
            flowTask.setCreateTime(task.getCreateTime());
            flowTask.setProcDefId(task.getProcessDefinitionId());
            flowTask.setExecutionId(task.getExecutionId());
            flowTask.setTaskName(task.getName());
            
            // 流程定义信息
            ProcessDefinition pd = repositoryService.createProcessDefinitionQuery()
                    .processDefinitionId(task.getProcessDefinitionId()).singleResult();
            flowTask.setDeployId(pd.getDeploymentId());
            flowTask.setProcDefName(pd.getName());
            flowTask.setProcDefVersion(pd.getVersion());
            flowTask.setProcInsId(task.getProcessInstanceId());

            // 流程发起人信息
            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                    .processInstanceId(task.getProcessInstanceId()).singleResult();
            SysUser startUser = sysUserService.selectUserById(Long.parseLong(historicProcessInstance.getStartUserId()));
            
            flowTask.setStartUserId(startUser.getNickName());
            flowTask.setStartUserName(startUser.getNickName());
            flowTask.setStartDeptName(startUser.getDept().getDeptName());
            flowList.add(flowTask);
        }

        page.setRecords(flowList);
        return AjaxResult.success(page);
    }

3.5、审批待处理的任务并设置审批意见

taskId:待处理任务节点

/**
     * 完成任务
     *
     * @param taskVo 请求实体参数
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public AjaxResult complete(FlowTaskVo taskVo) {
        Task task = taskService.createTaskQuery().taskId(taskVo.getTaskId()).singleResult();
        if (Objects.isNull(task)) {
            return AjaxResult.error("任务不存在");
        }
        if (DelegationState.PENDING.equals(task.getDelegationState())) {
            // 完成审批
            taskService.addComment(taskVo.getTaskId(), taskVo.getInstanceId(), FlowComment.DELEGATE.getType(), taskVo.getComment());
            taskService.resolveTask(taskVo.getTaskId(), taskVo.getValues());
        } else {
            // 完成审批
            taskService.addComment(taskVo.getTaskId(), taskVo.getInstanceId(), FlowComment.NORMAL.getType(), taskVo.getComment());
            Long userId = SecurityUtils.getLoginUser().getUser().getUserId();
            taskService.setAssignee(taskVo.getTaskId(), userId.toString());
            taskService.complete(taskVo.getTaskId(), taskVo.getValues());
        }
        return AjaxResult.success();
    }

涉及表

act_ru_variable:插入变量信息,包含本次流程执行实例的两个id外键,但不包括任务的id,由于setVariable方法设置的是全局变量,也就是整个流程都会有效的变量
act_ru_task:表中审批人的记录被删除,新插入审批人乙的任务记录
act_ru_execution:活动记录并无删除,而是将正在执行的任务变成审批人乙
act_hi_var_inst:插入流程实例的历史记录
act_hi_taskinst:插入任务的历史记录

如果该节点后面没有任务节点,那么此时ru数据表关于当前流程实例的数据将全部被清空

3.6、前端显示当前正在执行的流程图

1、读取流程模型xml

/**
     * 读取xml
     *
     * @param deployId
     * @return
     */
    @Override
    public AjaxResult readXml(String deployId) throws IOException {
        ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().deploymentId(deployId).singleResult();
        // 通过部署id,指定名称,读取指定的资源为流
        InputStream inputStream = repositoryService.getResourceAsStream(definition.getDeploymentId(), definition.getResourceName());
        // 将xml文件流,转字符串
        String result = IOUtils.toString(inputStream, StandardCharsets.UTF_8.name());
        return AjaxResult.success("", result);
    }

2、获取流程实例的历史活动节点(未执行到的将不会被取到)

/**
     * 获取流程执行过程
     *
     * @param procInsId 流程实例id
     * @return
     */
    @Override
    public AjaxResult getFlowViewer(String procInsId, String executionId) {
        List<FlowViewerDto> flowViewerList = new ArrayList<>();

        // 获取流程实例全部的历史活动节点
        List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(procInsId).list();

        for (HistoricActivityInstance finishedActivityInstance : historicActivityInstances) {
            // 将sequenceFlow节点不交由前端渲染
            if (!"sequenceFlow".equals(finishedActivityInstance.getActivityType())) {
                FlowViewerDto flowViewerDto = new FlowViewerDto();
                flowViewerDto.setKey(finishedActivityInstance.getActivityId());
                // 根据流程节点处理时间校验改节点是否已完成
                flowViewerDto.setCompleted(!Objects.isNull(finishedActivityInstance.getEndTime()));
                flowViewerList.add(flowViewerDto);
            }
        }
        return AjaxResult.success(flowViewerList);
    }

前端bpmn-modeler 读取xml,遍历流程节点,将当前历史活动节点全部高亮即可。

对应前端代码,data:xml

// 让图能自适应屏幕
    fitViewport() {
      this.zoom = this.modeler.get('canvas').zoom('fit-viewport')
      const bbox = document.querySelector('.flow-containers .viewport').getBBox()
      const currentViewbox = this.modeler.get('canvas').viewbox()
      const elementMid = {
        x: bbox.x + bbox.width / 2 - 65,
        y: bbox.y + bbox.height / 2
      }
      this.modeler.get('canvas').viewbox({
        x: elementMid.x - currentViewbox.width / 2,
        y: elementMid.y - currentViewbox.height / 2,
        width: currentViewbox.width,
        height: currentViewbox.height
      })
      this.zoom = bbox.width / currentViewbox.width * 1.8
    },
    // 放大缩小
    zoomViewport(zoomIn = true) {
      this.zoom = this.modeler.get('canvas').zoom()
      this.zoom += (zoomIn ? 0.1 : -0.1)
      this.modeler.get('canvas').zoom(this.zoom)
    },
    async createNewDiagram(data) {
      // 将字符串转换成图显示出来
      // data = data.replace(/<!\[CDATA\[(.+?)]]>/g, '&lt;![CDATA[$1]]&gt;')
      data = data.replace(/<!\[CDATA\[(.+?)]]>/g, function(match, str) {
        return str.replace(/</g, '&lt;')
      })
      try {
        await this.modeler.importXML(data)
        this.adjustPalette()
        this.fitViewport()
        if (this.taskList !==undefined && this.taskList.length > 0 ) {
          this.fillColor()
        }
      } catch (err) {
        console.error(err.message, err.warnings)
      }
    },
    fillColor() {
      const canvas = this.modeler.get('canvas')
      this.modeler._definitions.rootElements[0].flowElements.forEach(n => {
        const completeTask = this.taskList.find(m => m.key === n.id)
        const todoTask = this.taskList.find(m => !m.completed)
        const endTask = this.taskList[this.taskList.length - 1]
        if (n.$type === 'bpmn:UserTask') {
          if (completeTask) {
            canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
            n.outgoing?.forEach(nn => {
              const targetTask = this.taskList.find(m => m.key === nn.targetRef.id)
              if (targetTask) {
                if (todoTask && completeTask.key === todoTask.key && !todoTask.completed){
                  canvas.addMarker(nn.id, todoTask.completed ? 'highlight' : 'highlight-todo')
                  canvas.addMarker(nn.targetRef.id, todoTask.completed ? 'highlight' : 'highlight-todo')
                }else {
                  canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
                  canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
                }
              }
            })
          }
        }
        // 排他网关
        else if (n.$type === 'bpmn:ExclusiveGateway') {
          if (completeTask) {
            canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
            n.outgoing?.forEach(nn => {
              const targetTask = this.taskList.find(m => m.key === nn.targetRef.id)
              if (targetTask) {

                canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
                canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
              }

            })
          }

        }
        // 并行网关
        else if (n.$type === 'bpmn:ParallelGateway') {
          if (completeTask) {
            canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
            n.outgoing?.forEach(nn => {
              debugger
              const targetTask = this.taskList.find(m => m.key === nn.targetRef.id)
              if (targetTask) {
                canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
                canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
              }
            })
          }
        }
        else if (n.$type === 'bpmn:StartEvent') {
          n.outgoing.forEach(nn => {
            const completeTask = this.taskList.find(m => m.key === nn.targetRef.id)
            if (completeTask) {
              canvas.addMarker(nn.id, 'highlight')
              canvas.addMarker(n.id, 'highlight')
              return
            }
          })
        }
        else if (n.$type === 'bpmn:EndEvent') {
          if (endTask.key === n.id && endTask.completed) {
            canvas.addMarker(n.id, 'highlight')
            return
          }
        }
      })
    },

3.7、挂载流程定义

一旦流程定义被挂载,那么就不能通过该定义启动流程实例。
挂载本质上就是修改suspend为2

/**
     * 流程定义id  激活或挂起流程定义
     * 注意:流程定义一旦被挂起,那么就不应该通过该流程定义启动流程实例
     * @param state    状态
     * @param deployId 流程部署ID
     */
    @Override
    public void updateState(Integer state, String deployId) {
        // 通过流程定义id挂起或激活流程定义
        ProcessDefinition procDef = repositoryService.createProcessDefinitionQuery().deploymentId(deployId).singleResult();
        // 激活
        if (state == 1) {
            repositoryService.activateProcessDefinitionById(procDef.getId(), true, null);
        }
        // 挂起
        if (state == 2) {
            repositoryService.suspendProcessDefinitionById(procDef.getId(), true, null);
        }
    }

3.8、挂载流程实例

/**
     * 激活或挂起流程实例
     *
     * @param state      状态
     * @param instanceId 流程实例ID
     */
    @Override
    public void updateState(Integer state, String instanceId) {

        // 激活
        if (state == 1) {
            runtimeService.activateProcessInstanceById(instanceId);
        }
        // 挂起
        if (state == 2) {
            runtimeService.suspendProcessInstanceById(instanceId);
        }
    }

3.9、删除流程模型

/**
     * 删除流程部署  连带删除procdef,bytearray
     *
     * @param deployId 流程部署ID act_ge_bytearray 表中 deployment_id值
     */
    @Override
    public void delete(String deployId) {
        // true 允许级联删除 ,不设置会导致数据库外键关联异常
        repositoryService.deleteDeployment(deployId, true);
    }

任务驳回,退回,签收,取消签收,委派,转办有时间写。

相关文章
|
3天前
|
Java 测试技术 持续交付
自动化测试实践:从单元测试到集成测试
【6月更文挑战第28天】-单元测试:聚焦代码最小单元,确保每个函数或模块按预期工作。使用测试框架(如JUnit, unittest),编写覆盖所有功能和边界的测试用例,持续集成确保每次变更后自动测试。 - 集成测试:关注模块间交互,检查协同工作。选择集成策略,编写集成测试用例,模拟真实环境执行测试,整合到CI/CD流程以持续验证软件稳定性。 自动化测试提升软件质量,降低成本,加速开发周期,是现代软件开发不可或缺的部分。
|
2天前
|
人工智能 自然语言处理 数据挖掘
利用AI集成工具提升工作效率的实践经验
随着人工智能技术的蓬勃发展,以及当今数字化快速发展的时代,人工智能的运用已经渗透到各个行业和工作领域中,大语言模型在自然语言处理领域的应用也愈发广泛,而且市面上涌现出一批AI集成工具,比如Langchain、Dify、llamaIndex、fastgpt、百炼等,它们为开发者提供了强大的支持和便利,极大地提升了AI模型的构建和管理效率。作为一名热衷于利用新技术提高工作效率的开发者,我也积极尝试将这些工具融入到我的日常工作中,以期望提升工作效率和质量,下面我将分享我是如何使用AI集成工具来提升工作效率的,以及实践经验和心得。
15 1
利用AI集成工具提升工作效率的实践经验
|
2天前
|
消息中间件 Java API
Spring Boot与JMS消息中间件的集成
Spring Boot与JMS消息中间件的集成
|
2天前
|
监控 Java 数据库
Spring Boot与Spring Batch的深度集成
Spring Boot与Spring Batch的深度集成
|
2天前
|
缓存 NoSQL Java
Spring Boot中集成Redis实现缓存功能
Spring Boot中集成Redis实现缓存功能
|
2天前
|
Java API Maven
Spring Boot中如何集成GraphQL
Spring Boot中如何集成GraphQL
|
2天前
|
缓存 NoSQL Java
Spring Boot与Redis集成的最佳实践
Spring Boot与Redis集成的最佳实践
|
2天前
|
消息中间件 Java Spring
Spring Boot与RabbitMQ的集成应用
Spring Boot与RabbitMQ的集成应用
|
2天前
|
存储 搜索推荐 Java
Spring Boot与Elasticsearch的集成应用
Spring Boot与Elasticsearch的集成应用
|
2天前
|
网络协议 Java 微服务
Spring Boot中集成RSocket实现面向服务的通信
Spring Boot中集成RSocket实现面向服务的通信