SpringBoot 工作流引擎初体验 原创

新一代程序猿
发布于 2022-1-20 18:53
浏览
0收藏

春节不停更,此文正在参加「星光计划-春节更帖活动

Flowable是什么

Flowable是一个使用Java编写的轻量级业务流程引擎。Flowable流程引擎可用于部署BPMN 2.0流程定义(用于定义流程的行业XML标准), 创建这些流程定义的流程实例,进行查询,访问运行中或历史的流程实例与相关数据,等等。这个章节将用一个可以在你自己的开发环境中使用的例子,逐步介绍各种概念与API。

Flowable可以十分灵活地加入你的应用/服务/构架。可以将JAR形式发布的Flowable库加入应用或服务,来嵌入引擎。 以JAR形式发布使Flowable可以轻易加入任何Java环境:Java SE;Tomcat、Jetty或Spring之类的servlet容器;JBoss或WebSphere之类的Java EE服务器,等等。 另外,也可以使用Flowable REST API进行HTTP调用。也有许多Flowable应用(Flowable Modeler, Flowable Admin, Flowable IDM 与 Flowable Task),提供了直接可用的UI示例,可以使用流程与任务。

Flowable命名规则:

ACT_RE_* :’ RE ’表示repository(存储)。RepositoryService接口操作的表。带此前缀的表包含的是静态信息,如,流程定义,流程的资源(图片,规则等)。
ACT_RU_* :’ RU ’表示runtime。这是运行时的表存储着流程变量,用户任务,变量,职责(job)等运行时的数据。flowable只存储实例执行期间的运行时数据,当流程实例结束时,将删除这些记录。这就保证了这些运行时的表小且快。
ACT_ID_* : ’ ID ’表示identity(组织机构)。这些表包含标识的信息,如用户,用户组,等等。
ACT_HI_* : ’ HI ’表示history。就是这些表包含着历史的相关数据,如结束的流程实例,变量,任务,等等。
ACT_GE_* : 普通数据,各种情况都使用的数据

注意:Springboot 启动后表会自动创建

34张表说明

表名 表说明
ACT_GE_BYTEARRAY 通用的流程定义和流程资源
ACT_GE_PROPERTY 系统相关属性
ACT_HI_ACTINST 历史的流程实例
ACT_HI_ATTACHMENT 历史的流程附件
ACT_HI_COMMENT 历史的说明性信息
ACT_HI_DETAIL 历史的流程运行中的细节信息
ACT_HI_IDENTITYLINK 历史的流程运行过程中用户关系
ACT_HI_PROCINST 历史的流程实例
ACT_HI_TASKINST 历史的任务实例
ACT_HI_VARINST 历史的流程运行中的变量信息
ACT_ID_BYTEARRAY 二进制数据表
ACT_ID_GROUP 用户组信息表
ACT_ID_INFO 用户信息详情表
ACT_ID_MEMBERSHIP 人与组关系表
ACT_ID_PRIV 权限表
ACT_ID_PRIV_MAPPING 用户或组权限关系表
ACT_ID_PROPERTY 属性表
ACT_ID_TOKEN 系统登录日志表
ACT_ID_USER 用户表
ACT_RE_DEPLOYMENT 部署单元信息
ACT_RE_MODEL 模型信息
ACT_RE_PROCDEF 已部署的流程定义
ACT_RU_DEADLETTER_JOB 正在运行的任务表
ACT_RU_EVENT_SUBSCR 运行时事件
ACT_RU_EXECUTION 运行时流程执行实例
ACT_RU_HISTORY_JOB 历史作业表
ACT_RU_IDENTITYLINK 运行时用户关系信息
ACT_RU_JOB 运行时作业表
ACT_RU_SUSPENDED_JOB 暂停作业表
ACT_RU_TASK 运行时任务表
ACT_RU_TIMER_JOB 定时作业表
ACT_RU_VARIABLE 运行时变量表
ACT_EVT_LOG 事件日志表
ACT_PROCDEF_INFO 流程定义信息

BPMN 2.0

业务流程模型注解(Business Process Modeling Notation - BPMN)是 业务流程模型的一种标准图形注解。这个标准 是由对象管理组(Object Management Group - OMG)维护的。

基本上,BPMN规范定义了任务看起来怎样的,哪些结构可以 与其他进行连接,等等。这就意味着 意思不会被误解。

标准的早期版本(1.2版以及之前)仅仅限制在模型上, 目标是在所有的利益相关者之间形成通用的理解, 在文档,讨论和实现业务流程之上。 BPMN标准证明了它自己,现在市场上许多建模工具 都使用了BPMN标准中的元素和结构。 实际上,现在的jPDL设计器也使用了 BPMN元素。

BPMN规范的2.0版本,当前已经处于最终阶段了, 已经计划不就就会完成,允许添加精确的技术细节 在BPMN的图形和元素中, 同时制定BPMN元素的执行语法。 通过使用XML语言来指定业务流程的可执行语法, BPMN规范已经演变为业务流程的语言, 可以执行在任何兼容BPMN2的流程引擎中, 同时依然可以使用强大的图形注解。

使用BPMN2.0定义的工作流

BPMN2.0定义工作流的方式有很多种:

  • idea下载 actibBPM插件完成BPMN2.0的定义,下载后新建一个.bpmn文件进行工作流的定义,但是有中文乱码存在不推荐

SpringBoot 工作流引擎初体验-鸿蒙开发者社区

  • Flowable官网开发了工作流设计器
    SpringBoot 工作流引擎初体验-鸿蒙开发者社区

工作流设计器:https://github.com/flowable/flowable-engine/releases/download/flowable-6.4.0/flowable-6.4.0.zip

下载后将flowable-6.4.0.zip进行解压;
把wars目录下的war包复制到你安装的Tomcat8.0中的webapps目录下。
启动Tomcat8.0通过http://localhost:8080/flowable-modeler进行访问
账号:admin/test

项目实战

  • 创建流程
    SpringBoot 工作流引擎初体验-鸿蒙开发者社区
    经历妥妥拽拽形成上图的工作流。 妥妥拽拽过程忽略

  • 导入下载后的工作流文件
    SpringBoot 工作流引擎初体验-鸿蒙开发者社区

工作流文件为什么放在这个目录????
源码中规定了在processes下,并且规定了格式
SpringBoot 工作流引擎初体验-鸿蒙开发者社区

  • 配置数据源
server:
  port: 8090

spring:
  datasource:
    url: jdbc:mysql://tlshopdb.com:33806/act5?useUnicode=true&characterEncoding=utf8&nullCatalogMeansCurrent=true&useSSL=false&useLegacyDatetimeCode=false&serverTimezone=UTC
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: root
flowable:
  #关闭定时任务JOB
  async-executor-activate: false
## 开启flowablesql打印
logging:
  level:
    org:
      flowable:
        ui:
          modeler:
            domain:
              Model: debug
knife4j:
  enable: true

  • 编写ActController文件体验工作流
package com.jackgreek.actspringboot.controller;

import com.jackgreek.actspringboot.pojo.Evection;
import io.swagger.annotations.ApiOperation;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.*;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.image.ProcessDiagramGenerator;
import org.flowable.task.api.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author: jackgeeks
 * @ProjectName: demo
 * @Package: com.example.demo.controller
 * @ClassName: ActController
 * @Description: @todo
 * @CreateDate: 2022/1/3 20:55
 * @Version: 1.0
 */
    @RestController
    @RequestMapping("flowable")
    public  class ActController {

        @Autowired
        //提供了很多启动流程的API,并且全部的命名规则为startProcessInstanceByXX
        private RuntimeService runtimeService;
        @Autowired
        //任务服务
        private TaskService taskService;
        @Autowired
        //历史流程实例查询
        private HistoryService historyService;
        @Autowired
        //流程图管理
        private RepositoryService repositoryService;
        @Autowired
        private ProcessEngine processEngine;

        @Autowired
        //内置用户操作
        private  IdentityService identityService;

        /**
         * 创建流程
         *
         * @param userId
         * @param days
         * @return
         */
        @GetMapping("addExpense")
        @ApiOperation("创建流程")
        public String addExpense(String userId, String days) {
            identityService.setAuthenticatedUserId(userId);
//            设置流程变量
            Evection evection = new Evection();
//           设置出差日期
            evection.setNum(days);
//           把流程变量的pojo放入map
            Map<String, Object> map = new HashMap<>();
            map.put("evection", evection);
            ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myLeave",map);
            // 查询当前任务
            Task currentTask = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
            //taskService.setAssignee(currentTask.getId(),null);
            // 申明任务
            taskService.claim(currentTask.getId(), userId);
            System.out.println(processInstance);
            return "提交成功,流程ID为:" + processInstance.getId();
        }

        /**
         * processDefinitionId
         *
         * @param processDefinitionId
         * @return
         */
        @GetMapping("QueryByProcessInstanceId")
        @ApiOperation("根据流程实例ID查询待办任务")
        public Object QueryByProcessInstanceId(String processDefinitionId) {
            List<Task> tasks =  taskService.createTaskQuery().processInstanceId(processDefinitionId).list();
            return tasks.toString();
        }

        /**
         * 通过/拒绝任务
         *
         * @param taskId
         * @param approved 1 :true  2:false
         * @return
         */
        @GetMapping("apply")
        @ApiOperation("通过/拒绝任务")
        public String apply(String taskId, String approved) {
            Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
            if (task == null) {
                return "流程不存在";
            }
            Map<String, Object> variables = new HashMap<>();
            Boolean apply = approved.equals("1") ? true : false;
            variables.put("approved", apply);
            taskService.complete(taskId, variables);
            return "审批是否通过:" + approved;

        }

        /**
         * 查看历史流程记录
         *
         * @param processInstanceId
         * @return
         */
        @GetMapping("historyList")
        @ApiOperation("查看历史流程记录")
        public Object getHistoryList(String processInstanceId) {
            List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery()
                    .processInstanceId(processInstanceId).finished().orderByHistoricActivityInstanceEndTime().asc().list();
            for(HistoricActivityInstance hai:historicActivityInstances){
                System.out.println("活动ID:"+hai.getId());
                System.out.println("流程实例ID:"+hai.getProcessInstanceId());
                System.out.println("活动名称:"+hai.getActivityName());
                System.out.println("办理人:"+hai.getAssignee());
                System.out.println("开始时间:"+hai.getStartTime());
                System.out.println("结束时间:"+hai.getEndTime());
                System.out.println("=================================");
            }
            return historicActivityInstances;
        }

        /**
         * 驳回流程实例
         *
         * @param taskId
         * @param targetTaskKey
         * @return
         */
        @GetMapping("rollbask")
        public String rollbaskTask(String taskId, String targetTaskKey) {
            Task currentTask = taskService.createTaskQuery().taskId(taskId).singleResult();
            if (currentTask == null) {
                return "节点不存在";
            }
            List<String> key = new ArrayList<>();
            key.add(currentTask.getTaskDefinitionKey());


            runtimeService.createChangeActivityStateBuilder()
                    .processInstanceId(currentTask.getProcessInstanceId())
                    .moveActivityIdsToSingleActivityId(key, targetTaskKey)
                    .changeState();
            return "驳回成功...";
        }


        /**
         * 终止流程实例
         *
         * @param processInstanceId
         */
        public String deleteProcessInstanceById(String processInstanceId) {
            // ""这个参数本来可以写删除原因
            runtimeService.deleteProcessInstance(processInstanceId, "");
            return "终止流程实例成功";
        }


        /**
         * 挂起流程实例
         *
         * @param processInstanceId 当前流程实例id
         */
        @GetMapping("hangUp")
        public String handUpProcessInstance(String processInstanceId) {
            runtimeService.suspendProcessInstanceById(processInstanceId);
            return "挂起流程成功...";
        }

        /**
         * 恢复(唤醒)被挂起的流程实例
         *
         * @param processInstanceId 流程实例id
         */
        @GetMapping("recovery")
        public String activateProcessInstance(String processInstanceId) {
            runtimeService.activateProcessInstanceById(processInstanceId);
            return "恢复流程成功...";
        }


        /**
         * 判断传入流程实例在运行中是否存在
         *
         * @param processInstanceId
         * @return
         */
        @GetMapping("isExist/running")
        public Boolean isExistProcIntRunning(String processInstanceId) {
            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
            if (processInstance == null) {
                return false;
            }
            return true;
        }

        /**
         * 判断流程实例在历史记录中是否存在
         * @param processInstanceId
         * @return
         */
        @GetMapping("isExist/history")
        public Boolean isExistProcInHistory(String processInstanceId) {
            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
            if (historicProcessInstance == null) {
                return false;
            }
            return true;
        }


        /**
         * 我发起的流程实例列表
         *
         * @param userId
         * @return 流程实例列表
         */
        @GetMapping("myTasks")
        public List<HistoricProcessInstance> getMyStartProcint(String userId) {
            List<HistoricProcessInstance> list = historyService
                    .createHistoricProcessInstanceQuery()
                    .startedBy(userId)
                    .orderByProcessInstanceStartTime()
                    .asc()
                    .list();
            return list;
        }


        /**
         * 查询流程图
         *
         * @param httpServletResponse
         * @param processId
         * @throws Exception
         */
        @RequestMapping(value = "processDiagram")
        public void genProcessDiagram(HttpServletResponse httpServletResponse, String processId) throws Exception {
            ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult();

            //流程走完的不显示图
            if (pi == null) {
                return;
            }
            Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
            //使用流程实例ID,查询正在执行的执行对象表,返回流程实例对象
            String InstanceId = task.getProcessInstanceId();
            List<Execution> executions = runtimeService
                    .createExecutionQuery()
                    .processInstanceId(InstanceId)
                    .list();

            //得到正在执行的Activity的Id
            List<String> activityIds = new ArrayList<>();
            List<String> flows = new ArrayList<>();
            for (Execution exe : executions) {
                List<String> ids = runtimeService.getActiveActivityIds(exe.getId());
                activityIds.addAll(ids);
            }

            //获取流程图
            BpmnModel bpmnModel = repositoryService.getBpmnModel(pi.getProcessDefinitionId());
            ProcessEngineConfiguration engconf = processEngine.getProcessEngineConfiguration();
            ProcessDiagramGenerator diagramGenerator = engconf.getProcessDiagramGenerator();
            InputStream in = diagramGenerator.generateDiagram(bpmnModel, "png", activityIds, flows, engconf.getActivityFontName(), engconf.getLabelFontName(), engconf.getAnnotationFontName(), engconf.getClassLoader(), 1.0);
            OutputStream out = null;
            byte[] buf = new byte[1024];
            int legth = 0;
            try {
                out = httpServletResponse.getOutputStream();
                while ((legth = in.read(buf)) != -1) {
                    out.write(buf, 0, legth);
                }
            } finally {
                if (in != null) {
                    in.close();
                }
                if (out != null) {
                    out.close();
                }
            }
        }

    }



©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
分类
已于2022-1-20 18:54:33修改
收藏
回复
举报
回复
    相关推荐