当涉及到系统有多种流程需要审批时,纯代码实现简直就是噩梦,不灵活、高耦合、难维护、难扩展等等。在公司旧 erp 系统我是深有体会,维护这个项目的人都离职了,偶尔有 bug 不得不啃这个屎山。幸好公司觉得维护这个项目成本太高,重构了该项目。于是工作流程被安排上了,其中好处就不多说了。下面是对 flowable 工作流的基础操作
flowable 主要学习技巧: 跟踪观察流程执行每一步 sql 及对应表的数据变化,流程相关表信息可以自己百度
设置处理人
指定处理人: 既在 bpmn xml 中指定具体任务的处理人,在集成自己系统体系时,一般都是使用系统唯一用户编号、角色编号、部门编号等
动态处理人: 既在 bpmn xml 中使用动态变量标识处理人,在运行流程的时候设置这些动态变量
指定用户
指定用户,如指定 zhangsan
<bpmn2:userTask id="task1" name="指定用户" flowable:assignee="zhangsan"></bpmn2:userTask>
动态用户,如启动流程时候设置动态变量 user="1001",即 1001 为任务处理人
<bpmn2:userTask id="task1" name="动态用户" flowable:assignee="${user}"></bpmn2:userTask>
// 在启动流程的时候设置变量
String key = "test";
Map<String, Object> variables = new HashMap<>();
variables.put("user", "1001");
runtimeService.startProcessInstanceByKey(key, variables);
候选用户
指定候选用户,如指定 zhangsan 和李四
<bpmn2:userTask id="test2" name="指定候选用户审批" flowable:candidateUsers="zhangsan,lisi">
<bpmn2:incoming>Flow_0k1j90n</bpmn2:incoming>
<bpmn2:outgoing>Flow_1mxru1o</bpmn2:outgoing>s
</bpmn2:userTask>
动态候选用户,如启动流程时候设置动态变量 users="u1001,u1002",即 u1001 和 u1002 都可以处理该任务
<bpmn2:userTask id="task1" name="动态候选用户" flowable:candidateUsers="${users}"></bpmn2:userTask>
// 在启动流程的时候设置变量
String key = "test";
Map<String, Object> variables = new HashMap<>();
variables.put("users", "u1001,u1002");
runtimeService.startProcessInstanceByKey(key,variables);
候选组
指定候选组,如指定部门、指定角色、指定用户组 、指定职务,如指定 dept_1 部门可以处理该任务
<bpmn2:userTask id="deptTask1" name="指定候选组" flowable:candidateGroups="dept_1"></bpmn2:userTask>
动态候选组,如启动流程时候设置动态变量 groups="g1001,g1002",即 g1001,g1002 都可以处理该任务
<bpmn2:userTask id="task1" name="动态候选组" flowable:candidateUsers="${groups}"></bpmn2:userTask>
// 在启动流程的时候设置变量
String key = "test";
Map<String, Object> variables = new HashMap<>();
variables.put("groups", "g1001,g1002");
runtimeService.startProcessInstanceByKey(key,variables);
Bpmn 操作
模型 ID 获取 bpmnBytes
repositoryService.getModelEditorSource(modelId)
bpmnBytes 转 BpmnModel
BpmnXMLConverter converter = new BpmnXMLConverter();
BpmnModel bpmnModel = converter.convertToBpmnModel(new BytesStreamSource(bpmnBytes), false, false);
BpmnModel 转 bpmnXml string
BpmnXMLConverter converter = new BpmnXMLConverter();
String bpmnXml = new String(converter.convertToXML(bpmnModel));
获取 BpmnModel 指定类型元素信息集合
/**
* 获取BpmnModel指定类型的流程元素信息集合
*
* @param bpmnModel BpmnModel对象
* @param clazz 类型 如{@link UserTask}、{@link Gateway} 等
* @return 元素信息集合
*/
public <T extends FlowElement> List<T> getBpmnModelElements(BpmnModel bpmnModel, Class<T> clazz) {
List<T> result = new ArrayList<>();
bpmnModel.getProcesses().forEach(process -> {
process.getFlowElements().forEach(flowElement -> {
if (flowElement.getClass().isAssignableFrom(clazz)) {
result.add((T) flowElement);
}
});
});
return result;
}
获取 BpmnModel 指定流程元素 ID 元素信息
/**
* 获取BpmnModel 指定流程元素ID的流程元素信息
*
* @param bpmnModel BpmnModel对象
* @param flowElementId 元素 ID
* @return 元素信息
*/
public FlowElement getFlowElementById(BpmnModel model, String flowElementId) {
Process process = model.getMainProcess();
return process.getFlowElement(flowElementId);
}
流程操作
新增流程模型
// 保存流程定义 表act_re_model
Model model = repositoryService.newModel();
repositoryService.saveModel(model);
// 保存 BPMN XML 表act_ge_bytearray
repositoryService.addModelEditorSource(model.getId(), StrUtil.utf8Bytes(bpmnXml));
部署流程模型
// 通过模型ID获取流程模型
Model model = repositoryService.getModel(modelId);
// 通过模型ID获取bpmnBytes
byte[] bpmnBytes = repositoryService.getModelEditorSource(model.getId());
// 部署流程,涉及主要表act_re_procdef、act_re_deployment、act_ge_bytearray
Deployment deploy = repositoryService.createDeployment()
.key(model.getKey()).name(model.getName()).category(model.getCategory())
.addBytes(model.getKey() + BpmnModelConstants.BPMN_FILE_SUFFIX, bpmnBytes)
.disableSchemaValidation() // 禁用 XML Schema 验证
.deploy();
查询流程模型
/**
* 分页查询流程模型
*/
private void queryModelPage(String tenantId, String key, String name, String category, Integer pageNo, Integer pageSize) {
ModelQuery modelQuery = repositoryService.createModelQuery();
if (StrUtil.isNotBlank(tenantId)) {
modelQuery.modelTenantId(tenantId);
}
if (StrUtil.isNotBlank(key)) {
modelQuery.modelKey(key);
}
if (StrUtil.isNotBlank(name)) {
modelQuery.modelNameLike("%" + name + "%"); // 模糊匹配
}
if (StrUtil.isNotBlank(category)) {
modelQuery.modelCategory(category);
}
// 执行查询
long count = modelQuery.count();
if (count == 0) {
log.info("分页查询流程模型:null");
return;
}
List<Model> models = modelQuery
.orderByCreateTime().desc()
.listPage((pageNo - 1) * pageSize, pageSize);
log.info("分页查询流程模型:{}", JSONUtil.toJsonStr(models));
}
发起流程
/**
* 发起流程 通过key
*
* @param userId 发起人
* @param key 流程key
* @param businessKey 业务key 根据需要可不传
* @param variables 流程变量
*/
private void startProcessByKey(String userId, String key, String businessKey, Map<String, Object> variables) {
// 设置流程发起人上下文,内部执行的时候会把流程发起人存入流程实例表act_hi_procinst START_USER_ID_字段中
// 可以web过滤器进行处理,而不用在这里手动处理
Authentication.setAuthenticatedUserId(userId);
ProcessInstance instance = runtimeService.startProcessInstanceByKey(key, businessKey, variables);
log.info("流程实例ID:{},流程定义ID:{},业务标识:{}", instance.getId(), instance.getProcessDefinitionId(), instance.getBusinessKey());
}
查询流程实例
/**
* 查询流程实例
*
* @param tenantId 租户ID
* @param userId 发起人
* @param processInstanceName 流程实例名称
* @param processDefinitionId 流程定义ID
* @param businessKey 业务key
* @param category 分类
* @param startTime 流程开始时间 start
* @param endTime 流程开始时间 end
* @param pageNo 第几页
* @param pageSize 每页条数
*/
private void queryProcessInstance(String tenantId, String userId, String processInstanceName,
String processDefinitionId, String businessKey, String category,
Date startTime, Date endTime, Integer pageNo, Integer pageSize) {
HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery()
.includeProcessVariables()// 左关联ACT_HI_VARINST历史流程变量
.orderByProcessInstanceStartTime().desc();
if (StrUtil.isNotBlank(tenantId)) {
processInstanceQuery.processInstanceTenantId(tenantId);
}
// 发起人
if (StrUtil.isNotBlank(userId)) {
processInstanceQuery.startedBy(userId);
}
if (StrUtil.isNotBlank(processInstanceName)) {
processInstanceQuery.processInstanceNameLike("%" + processInstanceName + "%");
}
if (StrUtil.isNotBlank(processDefinitionId)) {
processInstanceQuery.processDefinitionId("%" + processDefinitionId + "%");
}
if (StrUtil.isNotBlank(businessKey)) {
processInstanceQuery.processInstanceBusinessKey(businessKey);
}
if (StrUtil.isNotBlank(category)) {
processInstanceQuery.processDefinitionCategory(category);
}
if (ObjectUtil.isNotNull(startTime) && ObjectUtil.isNotNull(endTime)) {
// yyyy-MM-dd HH:mm:ss
processInstanceQuery.startedAfter(startTime);
processInstanceQuery.startedBefore(endTime);
}
// 查询数量
long processInstanceCount = processInstanceQuery.count();
if (processInstanceCount == 0) {
log.info("分页查询流程实例:null");
return;
}
// 查询列表
List<HistoricProcessInstance> processInstances = processInstanceQuery.listPage((pageNo - 1) * pageSize, pageSize);
log.info("分页查询流程实例:{}", JSONUtil.toJsonStr(processInstances));
}
查询待办任务
/**
* 查询我的待办任务列表
*
* @param userId 处理人
* @param businessKey 业务key
* @return 待办任务列表
*/
private List<Task> queryMyTodoTask(String userId, String deptId, String roleId, String positionId, String userGroupId, String businessKey) {
// 查询待办事项列表
TaskQuery taskQuery = taskService.createTaskQuery()
.active() // 未挂起状态
.includeProcessVariables()
.orderByTaskCreateTime().desc();
if (StrUtil.isNotBlank(userId)) {
taskQuery.taskCandidateOrAssigned(userId);
}
//部门id dept:1、角色id role:1、职务id position:1 、用户组 userGroup:1
List<String> candidateGroups = new ArrayList<>();
if (StrUtil.isNotBlank(deptId)) {
candidateGroups.add(deptId);
}
if (StrUtil.isNotBlank(roleId)) {
candidateGroups.add(roleId);
}
if (StrUtil.isNotBlank(positionId)) {
candidateGroups.add(positionId);
}
if (StrUtil.isNotBlank(userGroupId)) {
candidateGroups.add(userGroupId);
}
if (candidateGroups.size() > 0) {
taskQuery.taskCandidateGroupIn(candidateGroups);
}
// 业务key
if (StrUtil.isNotBlank(businessKey)) {
taskQuery.processInstanceBusinessKey(businessKey);
}
List<Task> list = taskQuery.list();
log.info("我的待办任务列表:{}", JSONUtil.toJsonStr(list));
return list;
}
查询已办任务
/**
* 查询我的已办任务列表
*
* @param userId 处理人
* @param businessKey 业务key
* @return 已办任务列表
*/
private List<HistoricTaskInstance> queryMyFinishTask(String userId, String businessKey) {
HistoricTaskInstanceQuery historicTaskInstanceQuery = historyService.createHistoricTaskInstanceQuery()
.finished() // 已完成
.taskAssignee(userId) // 分配给自己
.includeTaskLocalVariables()
.orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序
// 业务key
if (StrUtil.isNotBlank(businessKey)) {
historicTaskInstanceQuery.processInstanceBusinessKey(businessKey);
}
List<HistoricTaskInstance> list = historicTaskInstanceQuery.list();
log.info("我的已办任务列表:{}", JSONUtil.toJsonStr(list));
return list;
}
查询审批记录
/**
* 查询审批记录
*/
@Test
public void queryHistoryTaskByProcessInstanceId() {
log.info("根据流程实例查询历史任务(其实就是审批进度)start");
String processInstanceId = "4daf22f1-38ee-11ef-bcaa-00ff0e3fbc3b";
List<HistoricTaskInstance> historyTasks = historyService.createHistoricTaskInstanceQuery()
.includeTaskLocalVariables()// 查询出任务本地变量
.processInstanceId(processInstanceId)
.orderByHistoricTaskInstanceStartTime().desc() // 创建时间倒序
.list();
log.info("实例任务:{}", JSONUtil.toJsonStr(historyTasks));
List<Comment> taskComments = taskService.getProcessInstanceComments(processInstanceId);
log.info("实例评论:{}", JSONUtil.toJsonStr(taskComments));
for (HistoricTaskInstance historyTask : historyTasks) {
// 任务所匹配评论
for (Comment taskComment : taskComments) {
if (taskComment.getTaskId().equals(taskComment.getTaskId())) {
log.info("任务所匹配评论{}", JSONUtil.toJsonStr(taskComment));
}
}
//select * from ACT_HI_COMMENT where TASK_ID_ = ? and TYPE_ = 'comment' order by TIME_ desc
// 默认类型为comment,我们插入指定了类型所以查询出来应该没有
List<Comment> taskComments1 = taskService.getTaskComments(historyTask.getId());
log.info("任务评论:{}", JSONUtil.toJsonStr(taskComments1));
}
log.info("根据流程实例查询历史任务(其实就是审批进度)end");
}
处理任务
审核通过任务
// 查询审核的任务
Task task = taskService.createTaskQuery().taskId(taskId).includeTaskLocalVariables().singleResult();
// 查询任务对应流程实例
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
.processInstanceId(task.getProcessInstanceId())
.includeProcessVariables()
.singleResult();
// 添加审核评论
taskService.addComment(taskId, processInstance.getId(),
BpmCommentTypeEnum.APPROVE.getType(), BpmCommentTypeEnum.APPROVE.formatComment(reason));
// 认领任务 (效果类似于setAssignee方法)
taskService.claim(taskId, userId);
// 去完成任务
if (CollUtil.isNotEmpty(variables)) { // 是否有变量
taskService.complete(taskId, variables, true);
} else {
taskService.complete(taskId);
}
审核不通过任务
// 查询审核的任务
Task task = taskService.createTaskQuery().taskId(taskId).includeTaskLocalVariables().singleResult();
// 查询任务对应流程实例
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
.processInstanceId(task.getProcessInstanceId())
.includeProcessVariables()
.singleResult();
// 添加审核评论
taskService.addComment(taskId, processInstance.getId(),
BpmCommentTypeEnum.REJECT.getType(), BpmCommentTypeEnum.REJECT.formatComment(reason));
// 认领任务 (效果类似于setAssignee方法)
taskService.claim(taskId, userId);
// 去完成任务
if (CollUtil.isNotEmpty(variables)) { // 是否有变量
taskService.complete(taskId, variables, true);
} else {
taskService.complete(taskId);
}
// 删除流程实例
runtimeService.deleteProcessInstance(processDefinitionId, reason);
委托任务
这个审批自己一时把握不了,先给上级处理审批,如果上级处理通过。在把任务还给自己,如果上级处理驳回,直接结束审批流程
/**
* 委托任务
*
* @param userId 委托操作人
* @param taskId 任务ID
* @param delegateUserId 委托接收人
* @param reason 委托原因
*/
public void delegateTask(String userId, String taskId, String delegateUserId, String reason) {
// 查询审核的任务
Task task = taskService.createTaskQuery().taskId(taskId).includeTaskLocalVariables().singleResult();;
if (task == null) {
throw new RuntimeException("任务不存在");
}
if (task.getAssignee() != null && !Objects.equals(task.getAssignee(), userId)) {
throw new RuntimeException("该任务的审批人不是你");
}
// todo 1.2 校验目标用户存在
// 添加委托意见
taskService.addComment(taskId, task.getProcessInstanceId(), BpmCommentTypeEnum.DELEGATE_START.getType(),
BpmCommentTypeEnum.DELEGATE_START.formatComment(userId, delegateUserId, reason));
// 认领任务(效果类似于setAssignee方法)
taskService.claim(taskId, userId);
// 设置任务所有者 (owner) 为原任务的处理人,即当前认领任务的人即发起委托的人
// taskService.setOwner(taskId, task.getAssignee());
// 设置任务所有者 (owner) 为即当前认领任务的人即发起委托的人
taskService.setOwner(taskId, userId);
// 执行委派,将任务委派给 delegateUser
/**
* 底层调用
* task.setDelegationState(DelegationState.PENDING);
* TaskHelper.changeTaskAssignee(task, userId);
* ACT_RU_TASK 表中 assignee = owner ,DELEGATION_ = PENDING
*/
taskService.delegateTask(taskId, delegateUserId);
}
/**
* 审核委托任务
*/
// 查询审核的任务
Task task = taskService.createTaskQuery().taskId(taskId).includeTaskLocalVariables().singleResult();
// 查询任务对应流程实例
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
.processInstanceId(task.getProcessInstanceId())
.includeProcessVariables()
.singleResult();
// 委托的任务状态为PENDING
if (DelegationState.PENDING.equals(task.getDelegationState())) {
// 任务所有者,发起委托的人
String owner = task.getOwner();
Assert.notNull(owner, "发起委托的人找不到,需要检查数据");
// 添加审批意见
taskService.addComment(taskId, task.getProcessInstanceId(), BpmCommentTypeEnum.DELEGATE_END.getType(),
BpmCommentTypeEnum.DELEGATE_END.formatComment(userId, task.getOwner(), reason));
/**
* 底层调用
* task.setDelegationState(DelegationState.RESOLVED);
* TaskHelper.changeTaskAssignee(task, task.getOwner())
*
* ACT_RU_TASK 表中 assignee = owner ,DELEGATION_ = RESOLVED
*/
taskService.resolveTask(taskId);
}
转办任务
将任务交给其他人处理
/**
* 转办任务
*
* @param userId 转办操作人
* @param taskId 任务ID
* @param assigneeUserId 转办接收人
* @param reason 转办原因
*/
public void transferTask(String userId, String taskId, String assigneeUserId, String reason) {
// 查询任务
Task task = taskService.createTaskQuery().taskId(taskId).includeTaskLocalVariables().singleResult();
if (task == null) {
throw new RuntimeException("任务不存在");
}
if (task.getAssignee() != null && !Objects.equals(task.getAssignee(), userId)) {
throw new RuntimeException("该任务的审批人不是你");
}
if ((task.getAssignee() != null && task.getAssignee().equals(assigneeUserId)) || (userId.equals(assigneeUserId))) {
throw new RuntimeException("任务转办失败,转办人不能同一人");
}
// 添加转办评论
taskService.addComment(taskId, task.getProcessInstanceId(), BpmCommentTypeEnum.TRANSFER.getType(),
BpmCommentTypeEnum.TRANSFER.formatComment(userId, assigneeUserId, reason));
// 设置任务所有者
// taskService.setOwner(taskId, task.getAssignee());
taskService.setOwner(taskId, userId);
// 将任务转派给 assigneeUserId
taskService.setAssignee(taskId, assigneeUserId);
}