Springboot整合Flowable

工作流引擎目前开源上可以选的就只有activitiflowablecamunda三种,当然除了activiti它们都有商用版本,flowable在6之后搞商用版了。所以暂时选用了比较稳定的开源版6.5.0。最近也是利用空闲时间研究了一下flowable的具体使用流程,总体来说还是比较简单的。

参考文档:

官网:https://tkjohn.github.io/flowable-userguide/#_introduction

Flowable介绍

Flowable是一个使用Java编写的轻量级业务流程引擎。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示例,可以使用流程与任务。

项目集成

在pom文件引入:

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

配置文件加入:

#flowable流程引擎测试
flowable:
  async-executor-activate: false
  database-schema-update: true #开启数据库结构自动同步,启动项目后会自动往数据库添加表大概七八十张

 这个方式是独立部署,逻辑是一个流程设计器->设计流程->导出流程文件xxx.bpmn2.0.xml文件->工程项目加载流程文件->调用流程相关接口(开始流程、提交、结束流程、查询流程流转、历史查询等等)->实现自己的业务逻辑。然后通过接口实现流程导入、授权、部署和更新等操作实现新流程加入。 最后要基于数据库使用配置mysql数据库连接池这些就不做说明了。

流程设计器

为了得到我们的流程部署文件(xxx.bpmn2.0.xml),我们可以安装流程设计工具或者使用一些第三方插件来做这个事情,不过我们可以利用官方提供的docker镜像来直接运行Flowable UI来实现流程设计。镜像地址:flowable/all-in-one:6.5.0,至于持久化映射就自行查阅吧。启动之访问http://localhost:28080/flowable-modeler即可,默认的用户名是:admin,密码:test,进去了之后就是编辑页面了编辑完成之后导出为xml文件就可以放在项目里面使用了,算是比较简单。

docker run -p 28080:8080 flowable/all-in-one:6.5.0

Flowable ui提供了几个web应用,用于演示及介绍Flowable项目提供的功能:

  • Flowable IDM: 身份管理应用。为所有Flowable UI应用提供单点登录认证功能,并且为拥有IDM管理员权限的用户提供了管理用户、组与权限的功能。

  • Flowable Modeler: 让具有建模权限的用户可以创建流程模型、表单、选择表与应用定义。

  • Flowable Task: 运行时任务应用。提供了启动流程实例、编辑任务表单、完成任务,以及查询流程实例与任务的功能。

  • Flowable Admin: 管理应用。让具有管理员权限的用户可以查询BPMN、DMN、Form及Content引擎,并提供了许多选项用于修改流程实例、任务、作业等。管理应用通过REST API连接至引擎,并与Flowable Task应用及Flowable REST应用一同部署。

 demo.bpmn2.0.xml:

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.flowable.org/processdef">
  <process id="demo" name="demo" isExecutable="true">
    <documentation>这个流程是一个测试流程</documentation>
    <startEvent id="startEvent1" flowable:formFieldValidation="true"></startEvent>
    <userTask id="SubmitVacation" name="提交请假" flowable:candidateUsers="staff" flowable:candidateGroups="staff" flowable:formFieldValidation="true">
      <documentation>人员提出请假申请</documentation>
    </userTask>
    <sequenceFlow id="sid-6654930C-965A-44A6-A9E4-5D4AD7195E79" sourceRef="startEvent1" targetRef="SubmitVacation"></sequenceFlow>
    <exclusiveGateway id="sid-9F0C1BD9-DCE8-4542-A299-77F47BC985CE"></exclusiveGateway>
    <userTask id="DepartmentManagerReview" name="部门经理审核" flowable:candidateUsers="dept_manger" flowable:candidateGroups="dept_manger" flowable:formFieldValidation="true">
      <documentation>由部门经理审核请假申请</documentation>
    </userTask>
    <userTask id="ProjectManagerReview" name="项目经理审核" flowable:candidateUsers="project_manger" flowable:candidateGroups="project_manger" flowable:formFieldValidation="true">
      <documentation>由项目经理审核请假申请</documentation>
    </userTask>
    <sequenceFlow id="sid-E37BFB13-BA35-4CFE-92DA-4DB34091C04C" sourceRef="SubmitVacation" targetRef="ProjectManagerReview"></sequenceFlow>
    <exclusiveGateway id="sid-970F1E93-EDBC-4D4A-876B-E7E06377638C"></exclusiveGateway>
    <exclusiveGateway id="sid-2A3E3784-673A-49E4-B3FB-E9F09486397A"></exclusiveGateway>
    <endEvent id="sid-DCA2F1E5-AC5D-4BBC-90E8-5B274F56A5D9"></endEvent>
    <endEvent id="sid-C83E28D4-423F-459B-A789-1D6211E4757E"></endEvent>
    <sequenceFlow id="sid-0FB8260C-1F08-4AF6-BF97-21B56189A556" sourceRef="ProjectManagerReview" targetRef="sid-970F1E93-EDBC-4D4A-876B-E7E06377638C"></sequenceFlow>
    <sequenceFlow id="sid-61AAC13D-9B4A-41C8-850F-ACCF423A5CA8" name="审核通过" sourceRef="sid-970F1E93-EDBC-4D4A-876B-E7E06377638C" targetRef="sid-9F0C1BD9-DCE8-4542-A299-77F47BC985CE">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${project_pass==true}]]></conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="sid-1E10E485-8FA3-4A79-9C45-5985CE6871F9" name="审核不通过" sourceRef="sid-970F1E93-EDBC-4D4A-876B-E7E06377638C" targetRef="SubmitVacation">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${project_pass==false}]]></conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="sid-6A32EEA5-051B-482F-BBC3-1A9D98DC0EF8" name="请假大于或等于三天" sourceRef="sid-9F0C1BD9-DCE8-4542-A299-77F47BC985CE" targetRef="DepartmentManagerReview">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${days >= 3}]]></conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="sid-A9646AF4-6F60-4E47-8A86-CCC363880AA8" name="请假小于三天" sourceRef="sid-9F0C1BD9-DCE8-4542-A299-77F47BC985CE" targetRef="sid-C83E28D4-423F-459B-A789-1D6211E4757E">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${days < 3}]]></conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="sid-97207CE9-A758-40B4-9D9B-8896F6CD5692" sourceRef="DepartmentManagerReview" targetRef="sid-2A3E3784-673A-49E4-B3FB-E9F09486397A"></sequenceFlow>
    <sequenceFlow id="sid-AF408E83-505D-4642-9155-5D33A782D28F" name="审核通过" sourceRef="sid-2A3E3784-673A-49E4-B3FB-E9F09486397A" targetRef="sid-DCA2F1E5-AC5D-4BBC-90E8-5B274F56A5D9">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${dept_pass==true}]]></conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="sid-1FD9C6EB-152E-486F-9F2F-B72D7CD8C9B6" name="审核不通过" sourceRef="sid-2A3E3784-673A-49E4-B3FB-E9F09486397A" targetRef="SubmitVacation">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${dept_pass==false}]]></conditionExpression>
    </sequenceFlow>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_demo">
    <bpmndi:BPMNPlane bpmnElement="demo" id="BPMNPlane_demo">
      <bpmndi:BPMNShape bpmnElement="startEvent1" id="BPMNShape_startEvent1">
        <omgdc:Bounds height="30.0" width="30.0" x="120.0" y="220.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="SubmitVacation" id="BPMNShape_SubmitVacation">
        <omgdc:Bounds height="80.0" width="100.0" x="240.0" y="195.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="sid-9F0C1BD9-DCE8-4542-A299-77F47BC985CE" id="BPMNShape_sid-9F0C1BD9-DCE8-4542-A299-77F47BC985CE">
        <omgdc:Bounds height="40.0" width="40.0" x="655.0" y="215.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="DepartmentManagerReview" id="BPMNShape_DepartmentManagerReview">
        <omgdc:Bounds height="80.0" width="100.0" x="835.0" y="195.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="ProjectManagerReview" id="BPMNShape_ProjectManagerReview">
        <omgdc:Bounds height="80.0" width="100.0" x="445.0" y="195.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="sid-970F1E93-EDBC-4D4A-876B-E7E06377638C" id="BPMNShape_sid-970F1E93-EDBC-4D4A-876B-E7E06377638C">
        <omgdc:Bounds height="40.0" width="40.0" x="475.0" y="350.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="sid-2A3E3784-673A-49E4-B3FB-E9F09486397A" id="BPMNShape_sid-2A3E3784-673A-49E4-B3FB-E9F09486397A">
        <omgdc:Bounds height="40.0" width="40.0" x="865.0" y="455.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="sid-DCA2F1E5-AC5D-4BBC-90E8-5B274F56A5D9" id="BPMNShape_sid-DCA2F1E5-AC5D-4BBC-90E8-5B274F56A5D9">
        <omgdc:Bounds height="28.0" width="28.0" x="985.0" y="461.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="sid-C83E28D4-423F-459B-A789-1D6211E4757E" id="BPMNShape_sid-C83E28D4-423F-459B-A789-1D6211E4757E">
        <omgdc:Bounds height="28.0" width="28.0" x="661.0" y="65.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="sid-1E10E485-8FA3-4A79-9C45-5985CE6871F9" id="BPMNEdge_sid-1E10E485-8FA3-4A79-9C45-5985CE6871F9">
        <omgdi:waypoint x="475.0" y="370.0"></omgdi:waypoint>
        <omgdi:waypoint x="290.0" y="370.0"></omgdi:waypoint>
        <omgdi:waypoint x="290.0" y="274.95000000000005"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="sid-A9646AF4-6F60-4E47-8A86-CCC363880AA8" id="BPMNEdge_sid-A9646AF4-6F60-4E47-8A86-CCC363880AA8">
        <omgdi:waypoint x="675.0" y="215.0"></omgdi:waypoint>
        <omgdi:waypoint x="675.0" y="92.94992723251232"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="sid-1FD9C6EB-152E-486F-9F2F-B72D7CD8C9B6" id="BPMNEdge_sid-1FD9C6EB-152E-486F-9F2F-B72D7CD8C9B6">
        <omgdi:waypoint x="865.0" y="475.0"></omgdi:waypoint>
        <omgdi:waypoint x="290.0" y="475.0"></omgdi:waypoint>
        <omgdi:waypoint x="290.0" y="274.95000000000005"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="sid-6654930C-965A-44A6-A9E4-5D4AD7195E79" id="BPMNEdge_sid-6654930C-965A-44A6-A9E4-5D4AD7195E79">
        <omgdi:waypoint x="149.9499992392744" y="235.0"></omgdi:waypoint>
        <omgdi:waypoint x="240.0" y="235.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="sid-6A32EEA5-051B-482F-BBC3-1A9D98DC0EF8" id="BPMNEdge_sid-6A32EEA5-051B-482F-BBC3-1A9D98DC0EF8">
        <omgdi:waypoint x="694.9452522606873" y="235.0"></omgdi:waypoint>
        <omgdi:waypoint x="835.0" y="235.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="sid-E37BFB13-BA35-4CFE-92DA-4DB34091C04C" id="BPMNEdge_sid-E37BFB13-BA35-4CFE-92DA-4DB34091C04C">
        <omgdi:waypoint x="339.94999999988573" y="235.0"></omgdi:waypoint>
        <omgdi:waypoint x="444.99999999995566" y="235.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="sid-61AAC13D-9B4A-41C8-850F-ACCF423A5CA8" id="BPMNEdge_sid-61AAC13D-9B4A-41C8-850F-ACCF423A5CA8">
        <omgdi:waypoint x="514.9389289678135" y="370.0"></omgdi:waypoint>
        <omgdi:waypoint x="585.0" y="370.0"></omgdi:waypoint>
        <omgdi:waypoint x="585.0" y="235.0"></omgdi:waypoint>
        <omgdi:waypoint x="655.0" y="235.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="sid-0FB8260C-1F08-4AF6-BF97-21B56189A556" id="BPMNEdge_sid-0FB8260C-1F08-4AF6-BF97-21B56189A556">
        <omgdi:waypoint x="495.0" y="274.95000000000005"></omgdi:waypoint>
        <omgdi:waypoint x="495.0" y="350.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="sid-97207CE9-A758-40B4-9D9B-8896F6CD5692" id="BPMNEdge_sid-97207CE9-A758-40B4-9D9B-8896F6CD5692">
        <omgdi:waypoint x="885.0" y="274.95000000000005"></omgdi:waypoint>
        <omgdi:waypoint x="885.0" y="455.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="sid-AF408E83-505D-4642-9155-5D33A782D28F" id="BPMNEdge_sid-AF408E83-505D-4642-9155-5D33A782D28F">
        <omgdi:waypoint x="904.941257668518" y="475.0"></omgdi:waypoint>
        <omgdi:waypoint x="985.0" y="475.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

具体使用:

我这里简单创建了一个服务和写了一些前端文件来测试:

后端代码:

package com.example.springboottest.FlowableTest.service;

import com.example.springboottest.FlowableTest.Entity.ProcessInstanceObject;
import com.example.springboottest.FlowableTest.Entity.TaskObject;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;

import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;

/**
 * @Author: liangpeng
 * @name: FlowableService  流程引擎服务
 * @Date: 2023/5/30 11:49
 */
public interface FlowableService {
    /**
     * 启动流程
     * @param processKey  流程定义key(流程图ID)
     * @param businessKey 业务key(可以为空)
     * @param map         参数键值对
     * @return 流程实例ID
     */
    ProcessInstance start(String processKey, String businessKey, Map<String, Object> map);

    /**
     * 停止流程
     * @param processInstanceId 流程实例ID
     * @param reason            终止理由
     */
    void stop(String processInstanceId, String reason);
    /**
     * 完成指定任务(推动流程运行)
     * @param taskId 任务ID
     * @param map    变量键值对
     * @throws RuntimeException 任务不存在
     */
    void complete(String taskId, Map<String, Object> map);

    /**
     * 获取指定用户的待办任务列表(创建时间倒序)
     * @param userId 用户ID
     * @return 任务列表
     */
    List<TaskObject> getTasksByUserId(String userId);
    /**
     * 获取指定用户组的待办任务列表
     * @param group 用户组
     * @return 任务列表
     */
    List<TaskObject> getTasksByGroup(String group);

    /**
     * 获取指定实例的任务列表
     * @param processInstanceId 流程实例id
     * @return 任务列表
     */
    List<TaskObject> getTasksByProcessInstanceId(String processInstanceId);

    /**
     * 获取指定任务列表中的特定任务
     * @param list        任务列表
     * @param businessKey 业务key
     * @return 任务
     */
    Task getOneByBusinessKey(List<Task> list, String businessKey);
    /**
     * 创建流程并完成第一个任务
     * @param processKey  流程定义key(流程图ID)
     * @param businessKey 业务key
     * @param map         变量键值对
     */
    Map<String,Object> startAndComplete(String processKey, String businessKey, Map<String, Object> map);

    /**
     * 退回到指定任务节点
     * @param currentTaskId 当前任务ID
     * @param targetTaskKey 目标任务节点key
     * @throws Exception 当前任务节点不存在
     */
    void backToStep(String currentTaskId, String targetTaskKey) throws Exception;

    /**
     * 返回尚未结束的流程实例
     * @param processKey 流程的名称(传入空返回所有)
     * @return 流程列表
     */
    List<ProcessInstanceObject> getProcessListByKey(String processKey);
    /**
     * 返回所有的流程实例
     *
     * @param processKey 流程的名称(传入空返回所有)
     * @return 流程列表
     */
    List<ProcessInstanceObject> getHistoryProcessList(String processKey);

    /**
     * 获取尚未结束的流程图
     * @param outputStream 流程图的输出流(传入空返回所有)
     * @param processId 流程的ID
     * @throws IOException 流无法读取或写入
     */
    Boolean getProcessChart(OutputStream outputStream, String processId) throws IOException;
}
package com.example.springboottest.FlowableTest.service.impl;
import com.example.springboottest.FlowableTest.Entity.ProcessInstanceObject;
import com.example.springboottest.FlowableTest.Entity.TaskObject;
import com.example.springboottest.FlowableTest.service.FlowableService;
import liquibase.pro.packaged.B;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.*;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import javax.annotation.Resource;
import java.io.IOException;
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: liangpeng
 * @name: FlowableServiceImpl
 * @Date: 2023/5/30 11:49
 * @description: 流程引擎实现
 */
@Service
public class FlowableServiceImpl implements FlowableService {
    private static final Logger logger = LoggerFactory.getLogger("flowable-service-log");
    @Resource
    RuntimeService runtimeService;
    @Resource
    TaskService taskService;
    @Resource
    HistoryService historyService;
    @Resource
    ProcessEngine processEngine;
    @Resource
    RepositoryService repositoryService;


    @Override
    public ProcessInstance start(String processKey, String businessKey, Map<String, Object> map) {
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processKey, businessKey, map);
        return processInstance;
    }
    @Override
    public void stop(String processInstanceId, String reason) {
        runtimeService.deleteProcessInstance(processInstanceId, reason);
    }
    @Override
    public void complete(String taskId, Map<String, Object> map) {
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        if (task == null) {
            logger.error(taskId + ":指定的任务不存在");
            throw new RuntimeException("任务不存在");
        }
        taskService.complete(taskId, map);
    }

    @Override
    public List<TaskObject> getTasksByUserId(String userId) {
        List<Task> tasks = taskService.createTaskQuery().taskAssignee(userId).orderByTaskCreateTime().desc().list();
        return taskToTaskObject(tasks);
    }
    @Override
    public List<TaskObject> getTasksByGroup(String group) {
        List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup(group).orderByTaskCreateTime().desc().list();
        return taskToTaskObject(tasks);
    }
    @Override
    public List<TaskObject> getTasksByProcessInstanceId(String processInstanceId) {
        List<Task> tasks = taskService.createTaskQuery().processInstanceId(processInstanceId).orderByTaskCreateTime().desc().list();
        return taskToTaskObject(tasks);
    }


    private List<TaskObject> taskToTaskObject(List<Task> tasks) {
        List<TaskObject> taskObjects = new ArrayList<>();
        for (Task task : tasks) {
            taskObjects.add(TaskObject.fromTask(task));
        }
        return taskObjects;
    }

    @Override
    public Task getOneByBusinessKey(List<Task> list, String businessKey) {
        Task task = null;
        for (Task t : list) {
            // 通过任务对象获取流程实例
            ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(t.getProcessInstanceId()).singleResult();
            if (businessKey.equals(pi.getBusinessKey())) {
                task = t;
            }
        }
        return task;
    }

    @Override
    public Map<String,Object> startAndComplete(String processKey, String businessKey, Map<String, Object> map) {
        Map<String,Object> out=new HashMap<>();
        ProcessInstance processInstance = start(processKey, businessKey, map);//启动流程,返回流程id
        if(ObjectUtils.isEmpty(processInstance)) return out;
        out.put("processInstance",ProcessInstanceObject.fromProcessInstance(processInstance));
        Task task = processEngine.getTaskService().createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
        taskService.complete(task.getId(), map);
        out.put("task",TaskObject.fromTask(task));
        return out;
    }

    @Override
    public void backToStep(String currentTaskId, String targetTaskKey) throws Exception {
        Task currentTask = taskService.createTaskQuery().taskId(currentTaskId).singleResult();
        if (currentTask == null) {
            logger.error(currentTaskId + ":指定的任务不存在");
            throw new Exception("当前任务节点不存在");
        }
        List<String> currentTaskKeys = new ArrayList<>();
        currentTaskKeys.add(currentTask.getTaskDefinitionKey());
        runtimeService.createChangeActivityStateBuilder().processInstanceId(currentTask.getProcessInstanceId()).moveActivityIdsToSingleActivityId(currentTaskKeys, targetTaskKey);
    }
    @Override
    public List<ProcessInstanceObject> getProcessListByKey(String processKey) {
        if (processKey == null) {
            return processToProcessObject(runtimeService.createProcessInstanceQuery().orderByStartTime().desc().list());
        }
        return processToProcessObject(runtimeService.createProcessInstanceQuery().processDefinitionKey(processKey).orderByStartTime().desc().list());
    }
    @Override
    public List<ProcessInstanceObject> getHistoryProcessList(String processKey) {
        if (processKey == null) {
            return historyProcessToProcessObject(historyService.createHistoricProcessInstanceQuery().orderByProcessInstanceStartTime().desc().list());
        }
        return historyProcessToProcessObject(historyService.createHistoricProcessInstanceQuery().orderByProcessInstanceStartTime().desc().processDefinitionKey(processKey).list());
    }
    private List<ProcessInstanceObject> processToProcessObject(List<ProcessInstance> instances) {
        List<ProcessInstanceObject> instanceObjects = new ArrayList<>();
        for (ProcessInstance instance : instances) {
            instanceObjects.add(ProcessInstanceObject.fromProcessInstance(instance));
        }
        return instanceObjects;
    }
    private List<ProcessInstanceObject> historyProcessToProcessObject(List<HistoricProcessInstance> instances) {
        List<ProcessInstanceObject> instanceObjects = new ArrayList<>();
        for (HistoricProcessInstance instance : instances) {
            instanceObjects.add(ProcessInstanceObject.fromHistoryProcessInstance(instance));
        }
        return instanceObjects;
    }

    @Override
    public Boolean getProcessChart(OutputStream out, String processId) throws IOException {
        ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult();
        //流程走完的不显示图
        if (pi == null) {
            return false;
        }
        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, "宋体", "宋体", "宋体", null, 1.0, true);
        byte[] buf = new byte[1024];
        int legth = 0;
        try {
            while ((legth = in.read(buf)) != -1) {
                out.write(buf, 0, legth);
            }
        } finally {
            if (in != null) {
                in.close();
            }
            if (out != null) {
                out.close();
            }
        }
        return true;
    }

}

package com.example.springboottest.FlowableTest.Controller;

import com.example.springboottest.FlowableTest.Entity.ProcessInstanceObject;
import com.example.springboottest.FlowableTest.service.FlowableService;
import com.example.springboottest.common.vo.result.R;
import liquibase.util.StringUtil;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping(value = "${application.admin-path}/flowable")
public class FlowableController {
    final private String PROCESS_KEY = "demo";
    @Resource
    FlowableService flowableService;


    //TODO:动态部署一个流程(xml文件)


    @GetMapping("/demo/stopProcessById")
    //@ApiOperation("停止流程实例")
    public R stopProcessById(String processInstanceId) {
        if(StringUtil.isEmpty(processInstanceId))
            throw new RuntimeException("参数不全请指定流程实例id(processInstanceId)字段");
        flowableService.stop(processInstanceId, "测试手动停止!");
        return R.buildOk("停止流程实例["+processInstanceId+"]成功!");
    }

    @GetMapping("/demo/startAndComplete")
    //@ApiOperation("开始请假流程,并提交")
    public R startAndComplete(Integer days,String processKey) {
        if(ObjectUtils.isEmpty(days)||StringUtil.isEmpty(processKey))
            throw new RuntimeException("参数不全请指定请假天数(days)和流程key(processKey)字段");
        Map<String, Object> map = new HashMap<>();
        map.put("days", days);
        Map<String, Object> objectMap = flowableService.startAndComplete(processKey, "请假", map);
        return R.buildOkData(objectMap);
    }

    @GetMapping("/demo/reComplete")
    //@ApiOperation("重新提交申请流程")
    public R reComplete(Integer days,String taskId){
        if(ObjectUtils.isEmpty(days)||StringUtil.isEmpty(taskId))
            throw new RuntimeException("参数不全请指定请假天数(days)和任务id(taskId)字段");
        Map<String, Object> map = new HashMap<>();
        map.put("project_pass", false);
        flowableService.complete(taskId, map);
        return R.buildOk("重新提交申请流程成功,项目经理审核!");
    }



    @GetMapping("/demo/project/pass")
    //@ApiOperation("项目经理审核通过")
    public R projectPass(String taskId) throws Exception {
        Map<String, Object> map = new HashMap<>();
        map.put("project_pass", true);
        flowableService.complete(taskId, map);
        return R.buildOk("项目经理审核通过成功,自动判定请假天数决定是否部门经理审核!");
    }

    @GetMapping("/demo/project/not_pass")
    //@ApiOperation("项目经理审核驳回")
    public R projectNotPass(String taskId)  {
        Map<String, Object> map = new HashMap<>();
        map.put("project_pass", false);
        flowableService.complete(taskId, map);
        return R.buildOk("项目经理审核驳回成功,请修改后重新提交申请!");
    }

    @GetMapping("/demo/dept/pass")
    //@ApiOperation("部门经理审核通过")
    public R deptPass(String taskId)  {
        Map<String, Object> map = new HashMap<>();
        map.put("dept_pass", true);
        flowableService.complete(taskId, map);
        return R.buildOk("部门经理审核通过,流程结束!");
    }

    @GetMapping("/demo/dept/not_pass")
    //@ApiOperation("部门经理审核驳回")
    public R deptNotPass(String taskId)  {
        Map<String, Object> map = new HashMap<>();
        map.put("dept_pass", false);
        flowableService.complete(taskId, map);
        return R.buildOk("部门经理审核驳回成功,请修改后重新提交申请!");
    }

    @GetMapping("/demo/process/list")
    //@ApiOperation("通过流程key获取未完成的流程实例列表")
    public R<ProcessInstanceObject> getProcessList(String processKey)  {
        if(StringUtil.isEmpty(processKey))
            throw new RuntimeException("参数不全请指定流程key(processKey)字段");
        return R.buildOkData(flowableService.getProcessListByKey(processKey));
    }
    @GetMapping("/demo/getTasksByGroup")
    //@ApiOperation("获取任务列表-指定用户组,倒叙")
    public R getTaskList(String groupId) {
        return R.buildOkData(flowableService.getTasksByGroup(groupId));
    }
    @GetMapping("/demo/getTasksByProcessInstanceId")
    //@ApiOperation("获取任务列表-指定流程实例id,倒叙")
    public R getTasksByProcessInstanceId(String processInstanceId) {
        return R.buildOkData(flowableService.getTasksByProcessInstanceId(processInstanceId));
    }

    @GetMapping("/demo/process/history/list")
   //@ApiOperation("获取已经完成的流程列表")
    public Object getHistoryProcessList() throws Exception {
        return flowableService.getHistoryProcessList(PROCESS_KEY);
    }

    @RequestMapping("/demo/process/chart/{processId}")
   // @ApiOperation("获取某个流程实例的进度状态")
    public void getProcessChart(HttpServletResponse httpServletResponse, @PathVariable("processId") String processId) throws IOException {
        ServletOutputStream outputStream = httpServletResponse.getOutputStream();
        httpServletResponse.setContentType("image/png");
        Boolean processChart = flowableService.getProcessChart(outputStream, processId);
        //return R.buildOkData(processChart);
    }
}
package com.example.springboottest.FlowableTest.Entity;

import lombok.Data;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.beans.BeanUtils;

import java.util.Date;
import java.util.Map;

/**
 *
 * 流程实体表--转换
 */

@Data
public class ProcessInstanceObject {

	String processDefinitionId;

	String processDefinitionName;

	String processDefinitionKey;

	Integer processDefinitionVersion;

	String deploymentId;

	String businessKey;

	boolean isSuspended;

	Map<String, Object> processVariables;

	String tenantId;

	String name;

	String description;

	String localizedName;

	String localizedDescription;

	Date startTime;

	Date endTime;

	Long durationInMillis;

	String endActivityId;

	String startUserId;

	String callbackId;

	String callbackType;

	String id;

	boolean suspended;

	boolean ended;

	String activityId;

	String processInstanceId;

	String parentId;

	String superExecutionId;

	String rootProcessInstanceId;

	String referenceId;

	String referenceType;

	String propagatedStageInstanceId;

	public static ProcessInstanceObject fromProcessInstance(ProcessInstance instance){
		ProcessInstanceObject object = new ProcessInstanceObject();
		BeanUtils.copyProperties(instance,object);
		return object;
	}

	public static ProcessInstanceObject fromHistoryProcessInstance(HistoricProcessInstance instance){
		ProcessInstanceObject object = new ProcessInstanceObject();
		BeanUtils.copyProperties(instance,object);
		return object;
	}
}

package com.example.springboottest.FlowableTest.Entity;

import lombok.Data;
import org.flowable.task.api.Task;
import org.springframework.beans.BeanUtils;

import java.util.Date;
import java.util.Map;

/**
 * 
 * 任务实体表--转换
 */
@Data
public class TaskObject {
	String id;

	String name;

	String description;

	int priority;

	String owner;

	String assignee;

	String processInstanceId;

	String executionId;

	String taskDefinitionId;

	String processDefinitionId;

	String scopeId;

	String subScopeId;

	String scopeType;

	String scopeDefinitionId;

	String propagatedStageInstanceId;

	Date createTime;

	String taskDefinitionKey;

	Date dueDate;

	String category;

	String parentTaskId;

	String tenantId;

	String formKey;

	Map<String, Object> taskLocalVariables;

	Map<String, Object> processVariables;

	Date claimTime;

	public static TaskObject fromTask(Task task){
		TaskObject object = new TaskObject();
		BeanUtils.copyProperties(task,object);
		return object;
	}
}
package com.example.springboottest.common.vo.result;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import org.springframework.http.HttpStatus;
import org.springframework.util.ObjectUtils;

import java.io.Serializable;


/**
 * 响应信息主体
 *
 * @param <T>
 * @author somewhere
 */
@ToString
@Accessors(chain = true)
@AllArgsConstructor
public class R<T> implements Serializable {
    private static final long serialVersionUID = 1L;

    @Getter
    @Setter
    private int code = CommonConstants.SUCCESS;

    @Getter
    @Setter
    private HttpStatus httpStatus;


    @Getter
    @Setter
    private T data;


    private String[] messages = {};


    public R() {
        super();
    }

    public R(T data) {
        super();
        this.data = data;
    }

    public R(String... msg) {
        super();
        this.messages = msg;
    }

    public R(T data, String... msg) {
        super();
        this.data = data;
        this.messages = msg;
    }

    public R(T data, int code, String... msg) {
        super();
        this.data = data;
        this.code = code;
        this.messages = msg;
    }

    public R(Throwable e) {
        super();
        setMessage(e.getMessage());
        this.code = CommonConstants.FAIL;
    }

    public static R buildOk(String... messages) {
        return new R(messages);
    }

    public static <T> R buildOkData(T data, String... messages) {
        return new R(data, messages);
    }

    public static <T> R buildFailData(T data, String... messages) {
        return new R(data, CommonConstants.FAIL, messages);
    }

    public static <T> R buildFail(String... messages) {
        return new R(null, CommonConstants.FAIL, messages);
    }

    public static <T> R build(T data, int code, String... messages) {
        return new R(data, code, messages);
    }

    public static <T> R build(int code, String... messages) {
        return new R(null, code, messages);
    }

    public String getMessage() {
        return readMessages();
    }

    public void setMessage(String message) {
        addMessage(message);
    }

    public String readMessages() {
        StringBuilder sb = new StringBuilder();
        for (String message : messages) {
            sb.append(message);
        }
        return sb.toString();
    }

    public void addMessage(String message) {
        this.messages = ObjectUtils.addObjectToArray(messages, message);
    }

}

前端页面:

<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>flowable测试</title>
	<script src="../js/jquery-3.0.0.js"> </script>
	<script src="../js/wfs.js"></script>
	<!-- <link href="js/jquery/jquery-ui.css" rel="stylesheet" type="text/css" /> -->
</head>
<body>
<h2>flowable测试</h2>

<div style="width:1200px; height:600px;float:right">
	<button id="viewProcess">查看进度,并实时查看进度和更新任务id</button>
	  info:<textarea id="viewProcessMessage" style="width:500px;height:20px;"></textarea>
	<img id="processImg" src="./default.jpg" style="width:100%; height:100%;float:left" alt="图片显示"/>
</div>
<button id="login">登录授权</button>
<br>

-----------------------------------------基本信息区域-----------------------------------------
<br>
流程key:<textarea id="processKeyTxt" style="width:100px;height:20px;"></textarea>
<button id="getProcessProcessKey">查询并更新实例id</button>
<br>
当前流程实例id:<textarea id="processIdTxt" style="width:300px;height:20px;"></textarea>
<button id="getTasksByProcessInstanceId">查询并更新任务id</button>
<button id="stopProcess">停止该流程</button>
<br>
当前任务id:<textarea id="taskIdTxt" style="width:300px;height:20px;"></textarea>
<br>
<br>
-----------------------------------------操作区域-----------------------------------------
<script>
	var mypath="http://127.0.0.1:8888/a";
	var tocken="";
	document.getElementById("processKeyTxt").value='demo';
	document.getElementById("processIdTxt").value="4555232f-fed5-11ed-bd05-0250f2000002";
	document.getElementById("taskIdTxt").value="86248e60-ff81-11ed-9e75-0250f2000002";
</script>

<script>
	/**
	 * 登录授权 获取tocken  并缓存tocken
	 */
	$("#login").click(function () {
		$.ajax({
			type: 'get',
			url: mypath + "/jwt/login",
			dataType: 'json',
			contentType: "application/json;charset=UTF-8",
			success: function (res) {
				console.log(res)
				if(res.code&&res.code==200&&res.data&&res.data.state)
				{
					tocken=res.data.token;
					window.localStorage.setItem("tocken",tocken);
					alert("授权成功!")
				}
				else
				{
					alert("授权失败,错误信息:"+JSON.stringify(res))
				}
			}
		});
	})
</script>

<script>

	var runNum=1;

	//更新任务id
	function refaushTaskId () {
		$.ajax({
			type: 'get',
			url: mypath + "/flowable/demo/getTasksByProcessInstanceId?processInstanceId="
					+document.getElementById("processIdTxt").value,
			dataType: 'json',
			contentType: "application/json;charset=UTF-8",
			headers: {
				"access_token": tocken||window.localStorage.getItem("tocken")
			},
			success: function (res) {
				console.log(res)
				if(res.code&&res.code==200&&res.data.length>0)
				{
					var idsstr="任务ids:  \n";
					for (let i = 0; i < res.data.length; i++) {
						idsstr+=res.data[i].id+"\n"
					}
					//alert("查询成功,"+idsstr);
					console.log(idsstr)
					document.getElementById("taskIdTxt").value=res.data[0].id;
				}
				else
				{
					console.log("查询失败,错误信息:"+(res.message||"该流程没有待办的任务"));
				}
			},
			error:function (res){
				//alert("请求异常,错误信息:"+(JSON.stringify(res)))
			}
		});
	}
	//更新流程实例id
	function refaushProcessInstanceIdByKey(){
		$.ajax({
			type: 'get',
			url: mypath + "/flowable/demo/process/list?processKey=" +document.getElementById("processKeyTxt").value,
			dataType: 'json',
			contentType: "application/json;charset=UTF-8",
			headers: {
				"access_token": tocken||window.localStorage.getItem("tocken")
			},
			success: function (res) {
				//console.log(res)
				if(res.code&&res.code==200&&res.data.length>0)
				{
					var idsstr="流程实例ids:  \n";
					for (let i = 0; i < res.data.length; i++) {
						idsstr+=res.data[i].id+"\n"
					}
					//alert("查询成功,"+idsstr);
					console.log(idsstr)
					document.getElementById("processIdTxt").value=res.data[0].id;
					refaushTaskId();
				}
				else
				{
					alert("查询失败:"+(res.message||"该流程没有正在运行的实例,请先点击新建请假流程!"))
				}
			},
			error:function (res){
				//alert("请求异常,错误信息:"+(JSON.stringify(res)))
			}
		});
	}


	//查询更更新流程进度
	function refaushProcessImg () {
		runNum+=1;
		var xmlhttp;
		xmlhttp=new XMLHttpRequest();
		xmlhttp.open("GET",mypath + "/flowable/demo/process/chart/"+ document.getElementById("processIdTxt").value,true);
		xmlhttp.responseType = "blob";
		xmlhttp.setRequestHeader("access_token", tocken||window.localStorage.getItem("tocken"));
		xmlhttp.onload = function(){
			console.log("xmlhttp RES",this);
			if (this.status == 200) {
				console.log("asdasdasdasdsad")
				var blob = this.response;
				if(blob.size&&blob.size>10)
				{
					var img_processImg = document.getElementById("processImg");
					// img_processImg.onload = function(e) {
					// 	window.URL.revokeObjectURL(img_processImg.src);
					// };
					img_processImg.src = window.URL.createObjectURL(blob);
					document.getElementById("viewProcessMessage").value="已经刷新次数:"+runNum;
				}
				else
				{
					document.getElementById("viewProcessMessage").value="当前流程不存在或者已结束!,已经刷新次数:"+runNum;
					document.getElementById("processImg").src="./default.jpg";
				}
			}
		}
		xmlhttp.send();
	}

	//按钮点击-根据流程key查询流程实例
	$("#getProcessProcessKey").click(function () {
		refaushProcessInstanceIdByKey();
	})
	//按钮点击-查询并更新任务id
	$("#getTasksByProcessInstanceId").click(function () {
		refaushTaskId();
	})
	//按钮点击-查看流程执行情况
	$("#viewProcess").click(function () {
		refaushProcessImg ();
		//定时器刷新
		setInterval(function (){
			refaushProcessImg();
			refaushTaskId();
		},2000)
	})
	//按钮点击-停止流程
	$("#stopProcess").click(function () {
		$.ajax({
			type: 'get',
			url: mypath + "/flowable/demo/stopProcessById?processInstanceId="
					+document.getElementById("processIdTxt").value,
			dataType: 'json',
			contentType: "application/json;charset=UTF-8",
			headers: {
				"access_token": tocken||window.localStorage.getItem("tocken")
			},
			success: function (res) {
				console.log(res)
				alert(res.message||JSON.stringify(res));
				refaushProcessImg();
			},
			error:function (res){
				//alert("请求异常,错误信息:"+(JSON.stringify(res)))
			}
		});
	})
</script>

<br>
<br>
请假人操作:
<div style="width:300px; height:50px;">
	请输入请假天数:<textarea id="qjdays" style="width:50px;height:20px;"></textarea>
	<br>
	<button id="startAndComplete">新建请假流程</button>
	<button id="reComplete">重新提交</button>
</div>
<br>
项目经理操作:
<div style="width:300px; height:50px;">
	<button id="project_pass">同意</button>
	<button id="project_not_pass">不同意</button>
</div>
<br>
部门经理操作:
<div style="width:300px; height:50px;">
	<button id="dept_pass">同意</button>
	<button id="dept_not_pass">不同意</button>
</div>
<br>


<script>
	document.getElementById("qjdays").value=3;

	/**
	 * 新建开始请假流程
	 */
	$("#startAndComplete").click(function () {
		$.ajax({
			type: 'get',
			url: mypath + "/flowable/demo/startAndComplete?days="
					+ document.getElementById("qjdays").value
					+"&processKey="+document.getElementById("processKeyTxt").value,
			dataType: 'json',
			contentType: "application/json;charset=UTF-8",
			headers: {
				"access_token": tocken||window.localStorage.getItem("tocken")
			},
			success: function (res) {
				console.log(res)
				if(res.code&&res.code==200)
				{
					alert("提交请假申请成功!")
					document.getElementById("processIdTxt").value=res.data.processInstance.id;
					document.getElementById("taskIdTxt").value=res.data.task.id;
				}
				else
				{
					alert("提交请假申请失败,错误信息:"+(res.message||JSON.stringify(res)))
				}
			},
			error:function (res){
				alert("请求异常,错误信息:"+(JSON.stringify(res)))
			}
		});
	})

	//驳回后重新提交
	$("#reComplete").click(function () {
		$.ajax({
			type: 'get',
			url: mypath + "/flowable/demo/reComplete?days="
					+ document.getElementById("qjdays").value
					+"&taskId="+ document.getElementById("taskIdTxt").value,
			dataType: 'json',
			contentType: "application/json;charset=UTF-8",
			headers: {
				"access_token": tocken||window.localStorage.getItem("tocken")
			},
			success: function (res) {
				console.log(res)
				if(res.code&&res.code==200)
				{
					alert("重新提交请假申请成功!")
					//document.getElementById("processIdTxt").value=res.data.processInstance.id;
					//document.getElementById("taskIdTxt").value=res.data.task.id;
				}
				else
				{
					alert("提交请假申请失败,错误信息:"+(res.message||JSON.stringify(res)))
				}
			},
			error:function (res){
				alert("请求异常,错误信息:"+(JSON.stringify(res)))
			}
		});
	})
</script>


<script>
	//项目经理按钮区域
	/**
	 * 通过
	 */
	$("#project_pass").click(function () {
		$.ajax({
			type: 'get',
			url: mypath + "/flowable/demo/project/pass?taskId="+ document.getElementById("taskIdTxt").value,
			dataType: 'json',
			contentType: "application/json;charset=UTF-8",
			headers: {
				"access_token": tocken||window.localStorage.getItem("tocken")
			},
			success: function (res) {
				console.log(res)
				alert(res.message||JSON.stringify(res))
				if(res.code&&res.code==200&&res.data)
				{

				}
				else
				{

				}
			},
			error:function (res){
				alert("请求异常,错误信息:"+(JSON.stringify(res)))
			}
		});
	})
	$("#project_not_pass").click(function () {
		$.ajax({
			type: 'get',
			url: mypath + "/flowable/demo/project/not_pass?taskId="+ document.getElementById("taskIdTxt").value,
			dataType: 'json',
			contentType: "application/json;charset=UTF-8",
			headers: {
				"access_token": tocken||window.localStorage.getItem("tocken")
			},
			success: function (res) {
				console.log(res)
				alert(res.message||JSON.stringify(res))
				if(res.code&&res.code==200&&res.data)
				{
				}
				else
				{
				}
			},
			error:function (res){
				alert("请求异常,错误信息:"+(JSON.stringify(res)))
			}
		});
	})
</script>

<script>
	//部门经理按钮区域
	/**
	 * 通过
	 */
	$("#dept_pass").click(function () {
		$.ajax({
			type: 'get',
			url: mypath + "/flowable/demo/dept/pass?taskId="+ document.getElementById("taskIdTxt").value,
			dataType: 'json',
			contentType: "application/json;charset=UTF-8",
			headers: {
				"access_token": tocken||window.localStorage.getItem("tocken")
			},
			success: function (res) {
				console.log(res)
				alert(res.message||JSON.stringify(res))
				if(res.code&&res.code==200&&res.data)
				{

				}
				else
				{

				}
			},
			error:function (res){
				alert("请求异常,错误信息:"+(JSON.stringify(res)))
			}
		});
	})
	$("#dept_not_pass").click(function () {
		$.ajax({
			type: 'get',
			url: mypath + "/flowable/demo/dept/not_pass?taskId="+ document.getElementById("taskIdTxt").value,
			dataType: 'json',
			contentType: "application/json;charset=UTF-8",
			headers: {
				"access_token": tocken||window.localStorage.getItem("tocken")
			},
			success: function (res) {
				console.log(res)
				alert(res.message||JSON.stringify(res))
				if(res.code&&res.code==200&&res.data)
				{
				}
				else
				{
				}
			},
			error:function (res){
				alert("请求异常,错误信息:"+(JSON.stringify(res)))
			}
		});
	})
</script>

</body>
</html>

TODO:

目前只是建的测试了一下,具体的还有很多想做的没做完的事:

常见错误 :

  • 由于升级本后回退发现版本报错:
Could not update Flowable database schema: unknown version from database: ‘6.7.1.0‘

如下两个表的版本都改为你当前pom文件里面指定的版本:

ACT_GE_PROPERTY 把这个表的所有的

ACT_ID_PROPERTY 把schema.version参数改为和你的

猜你喜欢

转载自blog.csdn.net/qq_22824481/article/details/130981767
今日推荐