工作流 Flowable 任务自定义 Script 脚本 相关实现

This commit is contained in:
jason 2022-02-17 17:14:46 +08:00
parent 41e4283f99
commit e01acfb18e
13 changed files with 307 additions and 54 deletions

View File

@ -1,7 +1,14 @@
package cn.iocoder.yudao.framework.flowable.core.util;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.common.engine.impl.identity.Authentication;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class FlowableUtils {
public static void setAuthenticatedUserId(Long userId) {
@ -11,4 +18,45 @@ public class FlowableUtils {
public static void clearAuthenticatedUserId() {
Authentication.setAuthenticatedUserId(null);
}
/**
* 获得 BPMN 流程中指定的元素们
*
* @param model
* @param clazz 指定元素例如说{@link org.flowable.bpmn.model.UserTask}{@link org.flowable.bpmn.model.Gateway} 等等
* @return 元素们
*/
public static <T extends FlowElement> List<T> getBpmnModelElements(BpmnModel model, Class<T> clazz) {
List<T> result = new ArrayList<>();
model.getProcesses().forEach(process -> {
process.getFlowElements().forEach(flowElement -> {
if (flowElement.getClass().isAssignableFrom(clazz)) {
result.add((T) flowElement);
}
});
});
return result;
}
/**
* 比较 两个bpmnModel 是否相同
* @param oldModel 老的bpmn model
* @param newModel 新的bpmn model
*/
public static boolean equals(BpmnModel oldModel, BpmnModel newModel) {
// 由于 BpmnModel 未提供 equals 方法所以只能转成字节数组进行比较
return Arrays.equals(getBpmnBytes(oldModel), getBpmnBytes(newModel));
}
/**
* bpmnModel 转换成 byte[]
* @param model bpmnModel
*/
public static byte[] getBpmnBytes(BpmnModel model) {
if (model == null) {
return new byte[0];
}
BpmnXMLConverter converter = new BpmnXMLConverter();
return converter.convertToXML(model);
}
}

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.config;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.BpmActivityBehaviorFactory;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.BpmTaskAssignScript;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmProcessInstanceEventListener;
import cn.iocoder.yudao.module.bpm.service.definition.BpmTaskAssignRuleService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService;
@ -27,8 +28,9 @@ import java.util.List;
public class BpmFlowableConfiguration {
/**
* 将自定义 listener 加入全局listener
* Flowable engines 自定义的配置
* @param listeners 自定义 listener
* @param bpmActivityBehaviorFactory 自定义的 ActivityBehaviorFactory 实现
*/
@Bean
public EngineConfigurationConfigurer<SpringProcessEngineConfiguration> addCustomerListenerConfigurer (ObjectProvider<FlowableEventListener> listeners,
@ -51,14 +53,15 @@ public class BpmFlowableConfiguration {
BpmUserGroupService userGroupService,
PermissionApi permissionApi,
DeptApi deptApi,
AdminUserApi adminUserApi) {
AdminUserApi adminUserApi,
List<BpmTaskAssignScript> scripts) {
BpmActivityBehaviorFactory bpmActivityBehaviorFactory = new BpmActivityBehaviorFactory();
bpmActivityBehaviorFactory.setBpmTaskRuleService(taskRuleService);
bpmActivityBehaviorFactory.setUserGroupService(userGroupService);
bpmActivityBehaviorFactory.setAdminUserApi(adminUserApi);
bpmActivityBehaviorFactory.setPermissionApi(permissionApi);
bpmActivityBehaviorFactory.setDeptApi(deptApi);
// bpmActivityBehaviorFactory.setScripts(scripts);
bpmActivityBehaviorFactory.setScripts(scripts);
return bpmActivityBehaviorFactory;
}
}

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.BpmTaskAssignScript;
import cn.iocoder.yudao.module.bpm.service.definition.BpmTaskAssignRuleService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
@ -13,6 +14,8 @@ import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
import org.flowable.engine.impl.bpmn.parser.factory.DefaultActivityBehaviorFactory;
import java.util.List;
/**
* 自定义的 ActivityBehaviorFactory 实现类目的如下
* 1. 自定义 {@link #createUserTaskActivityBehavior(UserTask)}实现自定义的流程任务的 assignee 负责人的分配
@ -35,6 +38,8 @@ public class BpmActivityBehaviorFactory extends DefaultActivityBehaviorFactory {
private DeptApi deptApi;
@Setter
private AdminUserApi adminUserApi;
@Setter
private List<BpmTaskAssignScript> scripts;
@Override
public UserTaskActivityBehavior createUserTaskActivityBehavior(UserTask userTask) {
@ -44,8 +49,7 @@ public class BpmActivityBehaviorFactory extends DefaultActivityBehaviorFactory {
userTaskActivityBehavior.setDeptApi(deptApi);
userTaskActivityBehavior.setUserGroupService(userGroupService);
userTaskActivityBehavior.setAdminUserApi(adminUserApi);
//TODO
//userTaskActivityBehavior.setScripts(scripts);
userTaskActivityBehavior.setScripts(scripts);
return userTaskActivityBehavior;
}
}

View File

@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.BpmTaskAssignScript;
import cn.iocoder.yudao.module.bpm.service.definition.BpmTaskAssignRuleService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
@ -30,8 +31,10 @@ import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.TASK_ASSIGN_SCRIPT_NOT_EXISTS;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.TASK_CREATE_FAIL_NO_CANDIDATE_USER;
/**
@ -56,9 +59,17 @@ public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior {
@Setter
private PermissionApi permissionApi;
/**
* 任务分配脚本
*/
private Map<Long, BpmTaskAssignScript> scriptMap = Collections.emptyMap();
public BpmUserTaskActivityBehavior(UserTask userTask) {
super(userTask);
}
public void setScripts(List<BpmTaskAssignScript> scripts) {
this.scriptMap = convertMap(scripts, script -> script.getEnum().getId());
}
@Override
protected void handleAssignments(TaskService taskService, String assignee, String owner, List<String> candidateUsers, List<String> candidateGroups, TaskEntity task, ExpressionManager expressionManager, DelegateExecution execution, ProcessEngineConfigurationImpl processEngineConfiguration) {
@ -100,10 +111,9 @@ public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior {
assigneeUserIds = calculateTaskCandidateUsersByUser(task, rule);
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.USER_GROUP.getType(), rule.getType())) {
assigneeUserIds = calculateTaskCandidateUsersByUserGroup(task, rule);
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.SCRIPT.getType(), rule.getType())) {
assigneeUserIds = calculateTaskCandidateUsersByScript(task, rule);
}
// else if (Objects.equals(BpmTaskAssignRuleTypeEnum.SCRIPT.getType(), rule.getType())) {
// assigneeUserIds = calculateTaskCandidateUsersByScript(task, rule);
// }
// 移除被禁用的用户
removeDisableUsers(assigneeUserIds);
@ -146,6 +156,22 @@ public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior {
return userIds;
}
private Set<Long> calculateTaskCandidateUsersByScript(TaskEntity task, BpmTaskAssignRuleDO rule) {
// 获得对应的脚本
List<BpmTaskAssignScript> scripts = new ArrayList<>(rule.getOptions().size());
rule.getOptions().forEach(id -> {
BpmTaskAssignScript script = scriptMap.get(id);
if (script == null) {
throw exception(TASK_ASSIGN_SCRIPT_NOT_EXISTS, id);
}
scripts.add(script);
});
// 逐个计算任务
Set<Long> userIds = new HashSet<>();
scripts.forEach(script -> CollUtil.addAll(userIds, script.calculateTaskCandidateUsers(task)));
return userIds;
}
private Long chooseTaskAssignee(Set<Long> candidateUserIds) {
// TODO 芋艿未来可以优化下改成轮询的策略
int index = RandomUtil.randomInt(candidateUserIds.size());

View File

@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskRuleScriptEnum;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import java.util.Set;
/**
* Bpm 任务分配的自定义 Script 脚本
* 使用场景
* 1. 设置审批人为发起人
* 2. 设置审批人为发起人的 Leader
* 3. 甚至审批人为发起人的 Leader Leader
*
* @author 芋道源码
*/
public interface BpmTaskAssignScript {
/**
* 基于流程任务获得任务的候选用户们
*
* @param task 任务
* @return 候选人用户的编号数组
*/
Set<Long> calculateTaskCandidateUsers(TaskEntity task);
/**
* 获得枚举值
*
* @return 枚举值
*/
BpmTaskRuleScriptEnum getEnum();
}

View File

@ -0,0 +1,71 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.impl;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.BpmTaskAssignScript;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.springframework.context.annotation.Lazy;
import org.springframework.util.Assert;
import javax.annotation.Resource;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
import static java.util.Collections.emptySet;
/**
* 分配给发起人的 Leader 审批的 Script 实现类
* 目前 Leader 的定义是
*
* @author 芋道源码
*/
public abstract class BpmTaskAssignLeaderAbstractScript implements BpmTaskAssignScript {
@Resource
private AdminUserApi adminUserApi;
@Resource
private DeptApi deptApi;
@Resource
@Lazy // 解决循环依赖
private BpmProcessInstanceService bpmProcessInstanceService;
protected Set<Long> calculateTaskCandidateUsers(TaskEntity task, int level) {
Assert.isTrue(level > 0, "level 必须大于 0");
// 获得发起人
ProcessInstance processInstance = bpmProcessInstanceService.getProcessInstance(task.getProcessInstanceId());
Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId());
// 获得对应 leve 的部门
DeptRespDTO dept = null;
for (int i = 0; i < level; i++) {
// 获得 level 对应的部门
if (dept == null) {
dept = getStartUserDept(startUserId);
if (dept == null) { // 找不到发起人的部门所以无法使用该规则
return emptySet();
}
} else {
DeptRespDTO parentDept = deptApi.getDept(dept.getParentId());
if (parentDept == null) { // 找不到父级部门所以只好结束寻找原因是例如说级别比较高的人所在部门层级比较少
break;
}
dept = parentDept;
}
}
return dept.getLeaderUserId() != null ? asSet(dept.getLeaderUserId()) : emptySet();
}
private DeptRespDTO getStartUserDept(Long startUserId) {
AdminUserRespDTO startUser = adminUserApi.getUser(startUserId);
if (startUser.getDeptId() == null) { // 找不到部门所以无法使用该规则
return null;
}
return deptApi.getDept(startUser.getDeptId());
}
}

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.impl;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskRuleScriptEnum;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.springframework.stereotype.Component;
import java.util.Set;
/**
* 分配给发起人的一级 Leader 审批的 Script 实现类
*
* @author 芋道源码
*/
@Component
public class BpmTaskAssignLeaderX1Script extends BpmTaskAssignLeaderAbstractScript {
@Override
public Set<Long> calculateTaskCandidateUsers(TaskEntity task) {
return calculateTaskCandidateUsers(task, 1);
}
@Override
public BpmTaskRuleScriptEnum getEnum() {
return BpmTaskRuleScriptEnum.LEADER_X1;
}
}

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.impl;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskRuleScriptEnum;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.springframework.stereotype.Component;
import java.util.Set;
/**
* 分配给发起人的二级 Leader 审批的 Script 实现类
*
* @author 芋道源码
*/
@Component
public class BpmTaskAssignLeaderX2Script extends BpmTaskAssignLeaderAbstractScript {
@Override
public Set<Long> calculateTaskCandidateUsers(TaskEntity task) {
return calculateTaskCandidateUsers(task, 2);
}
@Override
public BpmTaskRuleScriptEnum getEnum() {
return BpmTaskRuleScriptEnum.LEADER_X2;
}
}

View File

@ -0,0 +1,41 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.impl;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskRuleScriptEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.BpmTaskAssignScript;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Set;
/**
* 分配给发起人审批的 Script 实现类
*
* @author 芋道源码
*/
@Component
public class BpmTaskAssignStartUserScript implements BpmTaskAssignScript {
@Resource
@Lazy // 解决循环依赖
private BpmProcessInstanceService bpmProcessInstanceService;
@Override
public Set<Long> calculateTaskCandidateUsers(TaskEntity task) {
ProcessInstance processInstance = bpmProcessInstanceService.getProcessInstance(task.getProcessInstanceId());
Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId());
return SetUtils.asSet(startUserId);
}
@Override
public BpmTaskRuleScriptEnum getEnum() {
return BpmTaskRuleScriptEnum.START_USER;
}
}

View File

@ -5,6 +5,7 @@ import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.PageUtils;
import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionListReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageItemRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageReqVO;
@ -198,7 +199,7 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
BpmnModel newModel = buildBpmnModel(createReqDTO.getBpmnBytes());
BpmnModel oldModel = getBpmnModel(oldProcessDefinition.getId());
//TODO 貌似 flowable 不修改这个也不同需要看看 sourceSystemId 不同
if (equals(oldModel, newModel)) {
if (FlowableUtils.equals(oldModel, newModel)) {
return false;
}
// 最终发现都一致则返回 true
@ -216,19 +217,8 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
BpmnXMLConverter converter = new BpmnXMLConverter();
return converter.convertToBpmnModel(new BytesStreamSource(bpmnBytes), true, true);
}
//TODO 移出去
public boolean equals(BpmnModel oldModel, BpmnModel newModel) {
// 由于 BpmnModel 未提供 equals 方法所以只能转成字节数组进行比较
return Arrays.equals(getBpmnBytes(oldModel), getBpmnBytes(newModel));
}
//TODO 移出去
public byte[] getBpmnBytes(BpmnModel model) {
if (model == null) {
return new byte[0];
}
BpmnXMLConverter converter = new BpmnXMLConverter();
return converter.convertToXML(model);
}
@Override
public BpmProcessDefinitionExtDO getProcessDefinitionExt(String id) {

View File

@ -5,6 +5,7 @@ import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleCreateReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleUpdateReqVO;
@ -87,37 +88,15 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService{
if (model == null) {
return Collections.emptyList();
}
// 获得用户任务只有用户任务才可以设置分配规则
List<UserTask> userTasks = getBpmnModelElements(model, UserTask.class);
List<UserTask> userTasks = FlowableUtils.getBpmnModelElements(model, UserTask.class);
if (CollUtil.isEmpty(userTasks)) {
return Collections.emptyList();
}
// 转换数据
return BpmTaskAssignRuleConvert.INSTANCE.convertList(userTasks, rules);
}
/**
* TODO 需要移出去
* 获得 BPMN 流程中指定的元素们
*
* @param model
* @param clazz 指定元素例如说{@link org.flowable.bpmn.model.UserTask}{@link org.flowable.bpmn.model.Gateway} 等等
* @return 元素们
*/
public static <T extends FlowElement> List<T> getBpmnModelElements(BpmnModel model, Class<T> clazz) {
List<T> result = new ArrayList<>();
model.getProcesses().forEach(process -> {
process.getFlowElements().forEach(flowElement -> {
if (flowElement.getClass().isAssignableFrom(clazz)) {
result.add((T) flowElement);
}
});
});
return result;
}
@Override
public Long createTaskAssignRule(@Valid BpmTaskAssignRuleCreateReqVO reqVO) {
// 校验参数

View File

@ -106,6 +106,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
}
@Override
@Transactional(rollbackFor = Exception.class)
public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) {
// 获得流程定义
ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId());

View File

@ -25,6 +25,7 @@ import org.flowable.task.api.TaskQuery;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.task.api.history.HistoricTaskInstanceQuery;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
@ -167,6 +168,7 @@ public class BpmTaskServiceImpl implements BpmTaskService{
}
@Override
@Transactional(rollbackFor = Exception.class)
public void approveTask(Long userId, @Valid BpmTaskApproveReqVO reqVO) {
// 校验任务存在
Task task = checkTask(userId, reqVO.getId());
@ -184,6 +186,7 @@ public class BpmTaskServiceImpl implements BpmTaskService{
}
@Override
@Transactional(rollbackFor = Exception.class)
public void rejectTask(Long userId, @Valid BpmTaskRejectReqVO reqVO) {
Task task = checkTask(userId, reqVO.getId());
// 校验流程实例存在
@ -234,16 +237,15 @@ public class BpmTaskServiceImpl implements BpmTaskService{
.setAssigneeUserId(NumberUtils.parseLong(task.getAssignee()))
.setTaskId(task.getId());
taskExtMapper.updateByTaskId(taskExtDO);
//TODO 创建任务时候 也会调用 updateTaskExtAssign processInstance 名称为空 校验不通过
// 发送通知在事务提交时批量执行操作所以直接查询会无法查询到 ProcessInstance所以这里是通过监听事务的提交来实现
// TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
// @Override
// public void afterCommit() {
// ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId());
// AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId()));
// messageService.sendMessageWhenTaskAssigned(BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task));
// }
// });
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId());
AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId()));
messageService.sendMessageWhenTaskAssigned(BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task));
}
});
}
/**