工作流引擎目前开源上可以选的就只有activiti
、flowable
、camunda
三种,当然除了activiti
它们都有商用版本,flowable
在6之后搞商用版了。所以暂时选用了比较稳定的开源版6.5.0。最近也是利用空闲时间研究了一下flowable的具体使用流程,总体来说还是比较简单的。
参考文档:
官网:https://tkjohn.github.io/flowable-userguide/#_introduction
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文件引入:
org.flowable
flowable-spring-boot-starter
6.5.0
配置文件加入:
#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:
这个流程是一个测试流程
人员提出请假申请
由部门经理审核请假申请
由项目经理审核请假申请
= 3}]]>
我这里简单创建了一个服务和写了一些前端文件来测试:
后端代码:
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 map);
/**
* 停止流程
* @param processInstanceId 流程实例ID
* @param reason 终止理由
*/
void stop(String processInstanceId, String reason);
/**
* 完成指定任务(推动流程运行)
* @param taskId 任务ID
* @param map 变量键值对
* @throws RuntimeException 任务不存在
*/
void complete(String taskId, Map map);
/**
* 获取指定用户的待办任务列表(创建时间倒序)
* @param userId 用户ID
* @return 任务列表
*/
List getTasksByUserId(String userId);
/**
* 获取指定用户组的待办任务列表
* @param group 用户组
* @return 任务列表
*/
List getTasksByGroup(String group);
/**
* 获取指定实例的任务列表
* @param processInstanceId 流程实例id
* @return 任务列表
*/
List getTasksByProcessInstanceId(String processInstanceId);
/**
* 获取指定任务列表中的特定任务
* @param list 任务列表
* @param businessKey 业务key
* @return 任务
*/
Task getOneByBusinessKey(List list, String businessKey);
/**
* 创建流程并完成第一个任务
* @param processKey 流程定义key(流程图ID)
* @param businessKey 业务key
* @param map 变量键值对
*/
Map startAndComplete(String processKey, String businessKey, Map map);
/**
* 退回到指定任务节点
* @param currentTaskId 当前任务ID
* @param targetTaskKey 目标任务节点key
* @throws Exception 当前任务节点不存在
*/
void backToStep(String currentTaskId, String targetTaskKey) throws Exception;
/**
* 返回尚未结束的流程实例
* @param processKey 流程的名称(传入空返回所有)
* @return 流程列表
*/
List getProcessListByKey(String processKey);
/**
* 返回所有的流程实例
*
* @param processKey 流程的名称(传入空返回所有)
* @return 流程列表
*/
List 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 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 map) {
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
if (task == null) {
logger.error(taskId + ":指定的任务不存在");
throw new RuntimeException("任务不存在");
}
taskService.complete(taskId, map);
}
@Override
public List getTasksByUserId(String userId) {
List tasks = taskService.createTaskQuery().taskAssignee(userId).orderByTaskCreateTime().desc().list();
return taskToTaskObject(tasks);
}
@Override
public List getTasksByGroup(String group) {
List tasks = taskService.createTaskQuery().taskCandidateGroup(group).orderByTaskCreateTime().desc().list();
return taskToTaskObject(tasks);
}
@Override
public List getTasksByProcessInstanceId(String processInstanceId) {
List tasks = taskService.createTaskQuery().processInstanceId(processInstanceId).orderByTaskCreateTime().desc().list();
return taskToTaskObject(tasks);
}
private List taskToTaskObject(List tasks) {
List taskObjects = new ArrayList<>();
for (Task task : tasks) {
taskObjects.add(TaskObject.fromTask(task));
}
return taskObjects;
}
@Override
public Task getOneByBusinessKey(List 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 startAndComplete(String processKey, String businessKey, Map map) {
Map 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 currentTaskKeys = new ArrayList<>();
currentTaskKeys.add(currentTask.getTaskDefinitionKey());
runtimeService.createChangeActivityStateBuilder().processInstanceId(currentTask.getProcessInstanceId()).moveActivityIdsToSingleActivityId(currentTaskKeys, targetTaskKey);
}
@Override
public List 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 getHistoryProcessList(String processKey) {
if (processKey == null) {
return historyProcessToProcessObject(historyService.createHistoricProcessInstanceQuery().orderByProcessInstanceStartTime().desc().list());
}
return historyProcessToProcessObject(historyService.createHistoricProcessInstanceQuery().orderByProcessInstanceStartTime().desc().processDefinitionKey(processKey).list());
}
private List processToProcessObject(List instances) {
List instanceObjects = new ArrayList<>();
for (ProcessInstance instance : instances) {
instanceObjects.add(ProcessInstanceObject.fromProcessInstance(instance));
}
return instanceObjects;
}
private List historyProcessToProcessObject(List instances) {
List 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 executions = runtimeService
.createExecutionQuery()
.processInstanceId(InstanceId)
.list();
//得到正在执行的Activity的Id
List activityIds = new ArrayList<>();
List flows = new ArrayList<>();
for (Execution exe : executions) {
List 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 map = new HashMap<>();
map.put("days", days);
Map 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 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 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 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 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 map = new HashMap<>();
map.put("dept_pass", false);
flowableService.complete(taskId, map);
return R.buildOk("部门经理审核驳回成功,请修改后重新提交申请!");
}
@GetMapping("/demo/process/list")
//@ApiOperation("通过流程key获取未完成的流程实例列表")
public R 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 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 taskLocalVariables;
Map 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
* @author somewhere
*/
@ToString
@Accessors(chain = true)
@AllArgsConstructor
public class R 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 R buildOkData(T data, String... messages) {
return new R(data, messages);
}
public static R buildFailData(T data, String... messages) {
return new R(data, CommonConstants.FAIL, messages);
}
public static R buildFail(String... messages) {
return new R(null, CommonConstants.FAIL, messages);
}
public static R build(T data, int code, String... messages) {
return new R(data, code, messages);
}
public static 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);
}
}
前端页面:
flowable测试
flowable测试
info:
-----------------------------------------基本信息区域-----------------------------------------
流程key:
当前流程实例id:
当前任务id:
-----------------------------------------操作区域-----------------------------------------
请假人操作:
请输入请假天数:
项目经理操作:
部门经理操作:
目前只是建的测试了一下,具体的还有很多想做的没做完的事:
resources/processes
里面,只要不冲突和重复就不会有问题,项目启动时会自动部署)