Flowable入门讲解
一、springboot项目整合Flowable6.4.2
1、导入相关做标:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.monkey_wang</groupId>
<artifactId>monkey_wang</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<flowable.version>6.4.2</flowable.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!--<scope>runtime</scope>-->
</dependency>
<!--web开发的起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 工作流 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>${flowable.version}</version>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-json-converter</artifactId>
<version>${flowable.version}</version>
</dependency>
</dependencies>
</project>
2、配置文件中加入一下属性:
server.port=89
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.113.35:3306/flow
spring.datasource.username=root
spring.datasource.password=root
#关闭定时任务JOB
flowable.async-executor-activate=false
#将databaseSchemaUpdate设置为true。当Flowable发现库与数据库表结构不一致时,会自动将数据库表结构升级至新版本。
flowable.database-schema-update=true
二、部署定义好的流程
(1)、如果你的流程设计器和你当前的项目连的是同一个数据库的话:
controller层:
/**
* 部署流程
*/
@GetMapping(value = "/deployByKey")
@ApiOperationSupport(order = 4)
@ApiOperation(value = "部署流程", notes = "部署流程")
public R<String> deployByKey(@ApiParam(value = "流程定义的key", required = true) @RequestParam String processDefinitionKey) {
try {
processService.deployByKey(processDefinitionKey);
return R.data("部署成功。");
} catch (Exception e) {
e.printStackTrace();
return R.fail("服务器异常!");
}
}
service层:
//需要注入的Flowable相关service:
private final ModelRepository modelRepository;
private final RepositoryService repositoryService;
@Override
public void deployByKey(String processDefinitionKey) {
List<Model> models = modelRepository.findByKeyAndType(processDefinitionKey, 0);
if (models.size() > 0) {
for (Model model : models) {
String id = model.getId();
this.deployModel(id, "flow_2", null);
}
}
}
public boolean deployModel(String modelId, String category, List<String> tenantIdList) {
FlowModel model = this.getById(modelId);
if (model == null) {
throw new ServiceException("No model found with the given id: " + modelId);
}
byte[] bytes = getBpmnXml(model);
String processName = model.getName();
if (!StringUtil.endsWithIgnoreCase(processName, FlowEngineConstant.SUFFIX)) {
processName += FlowEngineConstant.SUFFIX;
}
String finalProcessName = processName;
if (Func.isNotEmpty(tenantIdList)) {
tenantIdList.forEach(tenantId -> {
Deployment deployment = repositoryService.createDeployment().addBytes(finalProcessName, bytes).name(model.getName()).key(model.getModelKey()).tenantId(tenantId).deploy();
deploy(deployment, category);
});
} else {
Deployment deployment = repositoryService.createDeployment().addBytes(finalProcessName, bytes).name(model.getName()).key(model.getModelKey()).deploy();
deploy(deployment, category);
}
return true;
}
(2)、如果你的流程设计器和项目连的不是一个库:
首先你需要把你的流程图下载下来保存到本地,然后利用代码找到这个bpmn文件进行部署。
service层代码:
/**
* 部署流程
* filePath 文件路径 name 流程名字
*/
public Map<String, Object> deploymentFlow(String filePath, String name) {
try {
DeploymentBuilder deploymentBuilder = repositoryService.createDeployment()
.addClasspathResource(filePath).name(name);
Deployment deployment = deploymentBuilder.deploy();
logger.info("成功:部署工作流程:" + filePath);
//acr_re_deployment表的id
String id = deployment.getId();
ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery();
//搜索条件deploymentId
query.deploymentId(id);
//最新版本过滤
query.latestVersion();
//查询
ProcessDefinition definition = query.singleResult();
//act_re_procdef表的key和id
String key = definition.getKey();
String definitionId = definition.getId();
Map<String, Object> map = new HashMap<>();
map.put("流程定义的key", key);
map.put("流程定义的的id", definitionId);
return map;
} catch (Exception e) {
logger.error("失败:部署工作流:" + e);
return null;
}
}
部署完成后,数据会保存在act_re_procdef和act_re_deployment表中
三、启动流程
注意: 后边讲解中会频繁调用RuntimeService、TaskService、HistoryService;这仨都是Flowable包下的服务类
- RuntimeService :用于查询运行中的流程实例相关信息。
- TaskService:用于查询代办、代签、签收、办理等操作。
- HistoryService:顾名思义,查询流程相关历史数据。
@PostMapping("/start")
@ApiOperationSupport(order = 1)
@ApiOperation(value = "启动流程", notes = "启动流程")
@Transactional(rollbackFor = Exception.class)
public R start(@ApiParam(value = "key", required = true) @RequestParam String key) {
try {
//构建流程启动需要的参数信息
Map<String, Object> param = new HashMap<>();
//param内封装的参数根据流程定义时的来
//举个简单的栗子:比如流程线上定义了不同请假天数走不同的人审批;
//请假天数参数名为day
Integer day = 1;
param.put("day", day);
// 设置流程启动用户
identityService.setAuthenticatedUserId(AuthUtil.getUserId().toString());
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(key, param);
String processInstanceId = processInstance.getProcessInstanceId();
return R.data(processInstanceId);
} catch (Exception e) {
e.printStackTrace();
return R.fail("启动失败!");
}
}
启动完成之后,运行中的流程数据会保存到act_ru_actinst、act_ru_execution、act_ru_identitylink、act_ru_task
-
act_ru_actinst:存储运行中各个节点的信息:
-
act_ru_identitylink: 存储候选用户、处理人相关数据
-
act_ru_task :存储审批代办相关信息
四、查询审批代办
@GetMapping("/getTask")
@ApiOperationSupport(order = 2)
@ApiOperation(value = "获取代办", notes = "获取代办")
@Transactional(rollbackFor = Exception.class)
public R getTask(@ApiParam(value = "processInstanceId", required = true) @RequestParam String processInstanceId) {
//获取当前登录用户ID
Long userId = AuthUtil.getUserId();
//根据流程实例id和当前登录用户的id查询审批代办
//注意: taskCandidateOrAssigned 表示不管是任务的候选人还是直接分配的人都可以查到
//生产环境中可以不用结合流程实例查询;具体怎么查要结合具体业务来定。
List<Task> tasks = taskService.createTaskQuery().processInstanceId(processInstanceId).taskCandidateOrAssigned(userId.toString()).list();
if (!tasks.isEmpty()) {
List<Map<String, Object>> list = new ArrayList<>();
for (Task task : tasks) {
Map<String, Object> map = new HashMap<>();
//代办id
String taskId = task.getId();
//代办节点名称
String taskName = task.getName();
//创建时间
Date createTime = task.getCreateTime();
map.put("taskId", taskId);
map.put("taskName", taskName);
map.put("createTime", createTime);
list.add(map);
}
return R.data(list);
}
return R.data("当前用户在当前流程实例中并无待办!");
}
五、审批并跳转流程:
@GetMapping("/audit")
@ApiOperationSupport(order = 2)
@ApiOperation(value = "审批并跳转流程", notes = "审批并跳转流程")
@Transactional(rollbackFor = Exception.class)
public R audit(@ApiParam(value = "taskId", required = true) @RequestParam String taskId) {
try {
//构建参数
Map<String, Object> param = new HashMap<>();
/*
这个参数和启动流程时讲的参数是一个用法,这里不做过多的描述
参数详情可以在表act_hi_varinst看到
需要注意的是,它们都是全局的参数;可以通过
historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstanceId).list()或
processInstance.getProcessVariables()获取
*/
//跳转流程
taskService.complete(taskId, param);
return R.data("已审批");
} catch (Exception e) {
e.printStackTrace();
return R.fail("跳转失败!");
}
}
ps : 流程跳转成功只后或流程结束后;对应的act_ru_task、act_ru_actinst等act_ru_打头的表数据会被删除。想要获取流程相关历史数据可以到act_hi_打头的表中查询,其结构和act_ru__打头的表结构大差不差甚至可以说相同;这样做主要是为了查询审批代办等运行中的流程数据时尽可能快。
六、转办
1、如果是候选人(candidaUser)发起的转办:
@Override
@Transactional(rollbackFor = Exception.class)
public void turnTaskToOthers(String taskId, String userId) {
Task task = taskService.createTaskQuery(taskId).taskId().singleResult();
if (task == null) {
return;
}
BladeUser user = AuthUtil.getUser();
String userNickName = user.getNickName();
User user1 = userMapper.selectById(Long.parseLong(userId));
String realName = user1.getRealName();
String comment = userNickName + "把该审批转办给了" + realName;
//生成历史记录
TaskEntity task1 = (TaskEntity) taskService.newTask(UUID.randomUUID().toString());
task1.setParentTaskId(task.getParentTaskId());
task1.setName(task.getName());
task1.setAssignee(user.getUserId().toString());
task1.setProcessInstanceId(task.getProcessInstanceId());
task1.setProcessDefinitionId(task.getProcessDefinitionId());
task1.setCategory(task.getCategory());
task1.setTaskDefinitionKey(task.getTaskDefinitionKey());
task1.setTaskDefinitionId(task.getTaskDefinitionId());
taskService.saveTask(task1);
taskService.addComment(task1.getId(), processInstanceId, comment);
taskService.complete(task1.getId());
//转办
String taskId = task.getId();
Boolean isAdmin = this.booleanAdmin();
if (!isAdmin) {
taskService.deleteCandidateUser(taskId, AuthUtil.getUserId().toString());
}
taskService.addCandidateUser(taskId, userId);
}
2、如果是签收人(assign)发起的代办:
@Override
@Transactional(rollbackFor = Exception.class)
public void turnTaskToOthers(String taskId, String userId) {
//获取源task
Task task = taskService.createTaskQuery(taskId).taskId().singleResult();
if (task == null) {
return;
}
//获取流程名称
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(tas.getProcessInstanceId).singleResult();
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionKey(processInstance.getProcessDefinitionKey()).latestVersion().singleResult();
//获取任务接受用户账号用于发送通知
User user = userService.getById(Long.parseLong(userId));
String userAccount = user.getAccount();
//获取任务源用户姓名
BladeUser firstUser = AuthUtil.getUser();
String userNickName = firstUser.getNickName();
//获取任务名称
String comment = userNickName + "把该审批转办给了" + user.getRealName();
//生成历史记录
TaskEntity task1 = (TaskEntity) taskService.newTask(UUID.randomUUID().toString());
task1.setParentTaskId(task.getParentTaskId());
task1.setName(task.getName());
task1.setAssignee(task.getAssignee());
task1.setProcessInstanceId(task.getProcessInstanceId());
task1.setProcessDefinitionId(task.getProcessDefinitionId());
task1.setCategory(task.getCategory());
task1.setTaskDefinitionKey(task.getTaskDefinitionKey());
task1.setTaskDefinitionId(task.getTaskDefinitionId());
taskService.saveTask(task1);
taskService.addComment(task1.getId(), processInstanceId, comment);
taskService.complete(task1.getId());
//转办
taskService.unclaim(taskId);
taskService.claim(taskId, userId);
}
七、强制终止流程(作废)
@Override
public void stopProcessInstanceById(String processInstanceId) {
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
if (processInstance != null) {
//1、获取终止节点
List<EndEvent> endNodes = findEndFlowElement(processInstance.getProcessDefinitionId());
String endId = endNodes.get(0).getId();
List<Execution> executions = runtimeService.createExecutionQuery().parentId(processInstanceId).list();
List<String> executionIds = new ArrayList<>();
executions.forEach(execution -> executionIds.add(execution.getId()));
runtimeService.createChangeActivityStateBuilder().moveExecutionsToSingleActivityId(executionIds, endId).changeState();
}
}
八、删除流程数据
@Override
public boolean deleteProcessInstance(String processInstanceId, String deleteReason) {
//判断流程是否结束
boolean finished = isFinished(processInstanceId);
//删除运行中的流程实例
if (!finished) {
runtimeService.deleteProcessInstance(processInstanceId, deleteReason);
}
//删除历史实例
historyService.deleteHistoricProcessInstance(processInstanceId);
return true;
}
private boolean isFinished(String processInstanceId) {
return historyService.createHistoricProcessInstanceQuery().finished()
.processInstanceId(processInstanceId).count() > 0;
}
九、流程跳转信息和节点进程图
流程跳转信息:
@Override
public List<BladeFlow> historyFlowList(String processInstanceId, String startActivityId, String endActivityId) {
List<BladeFlow> flowList = new LinkedList<>();
List<HistoricActivityInstance> historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().orderByHistoricActivityInstanceEndTime().asc().list();
boolean start = false;
Map<String, Integer> activityMap = new HashMap<>(16);
for (int i = 0; i < historicActivityInstanceList.size(); i++) {
HistoricActivityInstance historicActivityInstance = historicActivityInstanceList.get(i);
if (historicActivityInstance.getActivityName() == null) {
continue;
}
// 过滤开始节点前的节点
if (StringUtil.isNotBlank(startActivityId) && startActivityId.equals(historicActivityInstance.getActivityId())) {
start = true;
}
if (StringUtil.isNotBlank(startActivityId) && !start) {
continue;
}
// 显示开始节点和结束节点,并且执行人不为空的任务
if ("userTask".equals(historicActivityInstance.getActivityType())
|| FlowEngineConstant.START_EVENT.equals(historicActivityInstance.getActivityType())
|| FlowEngineConstant.END_EVENT.equals(historicActivityInstance.getActivityType())) {
// 给节点增加序号
Integer activityNum = activityMap.get(historicActivityInstance.getActivityId());
if (activityNum == null) {
activityMap.put(historicActivityInstance.getActivityId(), activityMap.size());
}
BladeFlow flow = new BladeFlow();
flow.setHistoryActivityId(historicActivityInstance.getActivityId());
flow.setHistoryActivityName(historicActivityInstance.getActivityName());
flow.setCreateTime(historicActivityInstance.getStartTime());
Date endTime = historicActivityInstance.getEndTime();
if (endTime != null) {
flow.setFlag("ok");
flow.setPass(true);
}
flow.setEndTime(endTime);
String durationTime = DateUtil.secondToTime(Func.toLong(historicActivityInstance.getDurationInMillis(), 0L) / 1000);
flow.setHistoryActivityDurationTime(durationTime);
// 获取流程发起人名称
this.getStartUser(historicActivityInstance, flow, processInstanceId);
// 获取任务执行人名称
if (StringUtil.isNotBlank(historicActivityInstance.getAssignee())) {
this.getTaskAssign(historicActivityInstance, flow);
} else {
this.setCandidateUsers(historicActivityInstance, flow);
}
if (!StringUtil.isEmpty(flow.getTaskId()) && StringUtil.isEmpty(flow.getAssigneeName())) {
continue;
}
// 获取意见评论内容
if (StringUtil.isNotBlank(historicActivityInstance.getTaskId())) {
List<Comment> commentList = taskService.getTaskComments(historicActivityInstance.getTaskId());
if (commentList.size() > 0) {
flow.setComment(commentList.get(0).getFullMessage());
}
}
flowList.add(flow);
}
// 过滤结束节点后的节点
if (StringUtils.isNotBlank(endActivityId) && endActivityId.equals(historicActivityInstance.getActivityId())) {
boolean temp = false;
Integer activityNum = activityMap.get(historicActivityInstance.getActivityId());
// 该活动节点,后续节点是否在结束节点之前,在后续节点中是否存在
for (int j = i + 1; j < historicActivityInstanceList.size(); j++) {
HistoricActivityInstance hi = historicActivityInstanceList.get(j);
Integer activityNumA = activityMap.get(hi.getActivityId());
boolean numberTemp = activityNumA != null && activityNumA < activityNum;
boolean equalsTemp = StringUtils.equals(hi.getActivityId(), historicActivityInstance.getActivityId());
if (numberTemp || equalsTemp) {
temp = true;
}
}
if (!temp) {
break;
}
}
}
return flowList;
}
private void getStartUser(HistoricActivityInstance historicActivityInstance, BladeFlow flow, String processInstanceId) {
if (FlowEngineConstant.START_EVENT.equals(historicActivityInstance.getActivityType())) {
List<HistoricProcessInstance> processInstanceList = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).orderByProcessInstanceStartTime().asc().list();
if (processInstanceList.size() > 0) {
if (StringUtil.isNotBlank(processInstanceList.get(0).getStartUserId())) {
String taskUser = processInstanceList.get(0).getStartUserId();
User user = UserCache.getUser(TaskUtil.getUserId(taskUser));
if (user != null) {
flow.setAssignee(historicActivityInstance.getAssignee());
flow.setAssigneeName(user.getName());
}
}
}
}
}
private void setCandidateUsers(HistoricActivityInstance historicActivityInstance, BladeFlow flow) {
String taskId = historicActivityInstance.getTaskId();
if (StringUtils.isNotEmpty(taskId)) {
flow.setTaskId(taskId);
List<Task> list = taskService.createTaskQuery().taskId(taskId).list();
if (list.isEmpty()) {
return;
}
List<IdentityLink> identityLinksForTask = taskService.getIdentityLinksForTask(taskId);
if (!identityLinksForTask.isEmpty()) {
List<String> names = new ArrayList<>();
for (IdentityLink identityLink : identityLinksForTask) {
String type = identityLink.getType();
String userId = identityLink.getUserId();
if (StringUtils.isNotEmpty(type) && "candidate".equals(type) && StringUtils.isNotEmpty(userId)) {
User user = userMapper.selectById(Long.parseLong(userId));
if (user != null) {
String realName = user.getRealName();
if (StringUtils.isNotEmpty(realName)) {
names.add(realName);
}
}
}
}
if (!names.isEmpty()) {
String substring = names.toString().substring(1, names.toString().length() - 1);
flow.setAssigneeName(substring);
}
}
}
}
节点进程图:
/**
* 根据流程节点绘图
*
* @param processInstanceId 流程实例id
* @param httpServletResponse http响应
*/
private void diagram(String processInstanceId, HttpServletResponse httpServletResponse) {
// 获得当前活动的节点
String processDefinitionId;
// 如果流程已经结束,则得到结束节点
if (this.isFinished(processInstanceId)) {
HistoricProcessInstance pi = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
processDefinitionId = pi.getProcessDefinitionId();
} else {
// 如果流程没有结束,则取当前活动节点
// 根据流程实例ID获得当前处于活动状态的ActivityId合集
ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
processDefinitionId = pi.getProcessDefinitionId();
}
List<String> highLightedActivities = new ArrayList<>();
List<String> flows = new ArrayList<>();
// 获得活动的节点
List<HistoricActivityInstance> highLightedActivityList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().desc().list();
if (!highLightedActivityList.isEmpty()) {
for (HistoricActivityInstance activityInstance : highLightedActivityList) {
highLightedActivities.add(activityInstance.getActivityId());
if ("sequenceFlow".equals(activityInstance.getActivityType())) {
flows.add(activityInstance.getActivityId());
}
}
}
// 获取流程图
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
ProcessEngineConfiguration engConf = processEngine.getProcessEngineConfiguration();
ProcessDiagramGenerator diagramGenerator = engConf.getProcessDiagramGenerator();
InputStream in = diagramGenerator.generateDiagram(bpmnModel, "bmp", highLightedActivities, flows, engConf.getActivityFontName(),
engConf.getLabelFontName(), engConf.getAnnotationFontName(), engConf.getClassLoader(), 1.0, true);
OutputStream out = null;
byte[] buf = new byte[1024];
int length;
try {
out = httpServletResponse.getOutputStream();
while ((length = in.read(buf)) != -1) {
out.write(buf, 0, length);
}
} catch (IOException e) {
log.error("操作异常", e);
} finally {
IoUtil.closeSilently(out);
IoUtil.closeSilently(in);
}
}
/**
* 是否已完结
*
* @param processInstanceId 流程实例id
* @return bool
*/
private boolean isFinished(String processInstanceId) {
return historyService.createHistoricProcessInstanceQuery().finished()
.processInstanceId(processInstanceId).count() > 0;
}
效果图:
十、流程节点回退
类似于回滚,只能回退到用户任务节点;
举个简单的栗子:
流程:开始→A审批→B审批→C审批→结束
如果现在到B审批了,则只能回退到A审批;如果现在到了C审批,则可以回退到A审批或B审批
注:回退成功后,会生成新的代办(类似于事务回滚)。
@Override
public void rollBackProcess(String proInstanceId, List<String> currTaskKeys, String targetKey) {
runtimeService.createChangeActivityStateBuilder()
.processInstanceId(proInstanceId)
.moveActivityIdsToSingleActivityId(currTaskKeys, targetKey)
.changeState();
}