From e21c262bd7e3ed687b5aa1f640ecdec7066dd352 Mon Sep 17 00:00:00 2001 From: kyle <573984425@qq.com> Date: Tue, 9 Jan 2024 09:43:48 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E6=9F=A5?= =?UTF-8?q?=E6=89=BE=E5=80=99=E9=80=89=E4=BA=BA=E7=9A=84=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../candidate/vo/BpmTaskCandidateVO.java | 27 ++ .../BpmCandidateProcessorConfiguration.java | 53 ++++ .../candidate/BpmCandidateSourceInfo.java | 44 ++++ .../BpmCandidateSourceInfoProcessor.java | 52 ++++ .../BpmCandidateSourceInfoProcessorChain.java | 105 ++++++++ ...didateAdminUserApiSourceInfoProcessor.java | 31 +++ ...pmCandidateDeptApiSourceInfoProcessor.java | 49 ++++ ...pmCandidatePostApiSourceInfoProcessor.java | 39 +++ ...pmCandidateRoleApiSourceInfoProcessor.java | 36 +++ ...CandidateScriptApiSourceInfoProcessor.java | 73 ++++++ ...didateUserGroupApiSourceInfoProcessor.java | 40 +++ ...CandidateSourceInfoProcessorChainTest.java | 248 ++++++++++++++++++ 12 files changed, 797 insertions(+) create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/candidate/vo/BpmTaskCandidateVO.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/config/BpmCandidateProcessorConfiguration.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/BpmCandidateSourceInfo.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/BpmCandidateSourceInfoProcessor.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/BpmCandidateSourceInfoProcessorChain.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateAdminUserApiSourceInfoProcessor.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateDeptApiSourceInfoProcessor.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidatePostApiSourceInfoProcessor.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateRoleApiSourceInfoProcessor.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateScriptApiSourceInfoProcessor.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateUserGroupApiSourceInfoProcessor.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/service/candidate/BpmCandidateSourceInfoProcessorChainTest.java diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/candidate/vo/BpmTaskCandidateVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/candidate/vo/BpmTaskCandidateVO.java new file mode 100644 index 000000000..37e877c54 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/candidate/vo/BpmTaskCandidateVO.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.bpm.controller.admin.candidate.vo; + +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleBaseVO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.util.Set; + +/** + * 流程任务分配规则 Base VO,提供给添加、修改、详细的子 VO 使用 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 + * + * @see BpmTaskAssignRuleBaseVO + */ +@Data +public class BpmTaskCandidateVO { + + @Schema(description = "规则类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "bpm_task_assign_rule_type") + @NotNull(message = "规则类型不能为空") + private Integer type; + + @Schema(description = "规则值数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3") + @NotNull(message = "规则值数组不能为空") + private Set options; + +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/config/BpmCandidateProcessorConfiguration.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/config/BpmCandidateProcessorConfiguration.java new file mode 100644 index 000000000..b9fde2436 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/config/BpmCandidateProcessorConfiguration.java @@ -0,0 +1,53 @@ +package cn.iocoder.yudao.module.bpm.framework.bpm.config; + +import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.BpmTaskAssignScript; +import cn.iocoder.yudao.module.bpm.service.candidate.sourceInfoProcessor.*; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +/** + * BPM 通用的 Configuration 配置类,提供给 Activiti 和 Flowable + * @author kyle + */ +@Configuration(proxyBeanMethods = false) +public class BpmCandidateProcessorConfiguration { + @Bean + public BpmCandidateAdminUserApiSourceInfoProcessor bpmCandidateAdminUserApiSourceInfoProcessor() { + return new BpmCandidateAdminUserApiSourceInfoProcessor(); + } + + @Bean + public BpmCandidateDeptApiSourceInfoProcessor bpmCandidateDeptApiSourceInfoProcessor() { + return new BpmCandidateDeptApiSourceInfoProcessor(); + } + + @Bean + public BpmCandidatePostApiSourceInfoProcessor bpmCandidatePostApiSourceInfoProcessor() { + return new BpmCandidatePostApiSourceInfoProcessor(); + } + + @Bean + public BpmCandidateRoleApiSourceInfoProcessor bpmCandidateRoleApiSourceInfoProcessor() { + return new BpmCandidateRoleApiSourceInfoProcessor(); + } + + @Bean + public BpmCandidateUserGroupApiSourceInfoProcessor bpmCandidateUserGroupApiSourceInfoProcessor() { + return new BpmCandidateUserGroupApiSourceInfoProcessor(); + } + + /** + * 可以自己定制脚本,然后通过这里设置到处理器里面去 + * @param scriptsOp + * @return + */ + @Bean + public BpmCandidateScriptApiSourceInfoProcessor bpmCandidateScriptApiSourceInfoProcessor(ObjectProvider scriptsOp) { + BpmCandidateScriptApiSourceInfoProcessor bpmCandidateScriptApiSourceInfoProcessor = new BpmCandidateScriptApiSourceInfoProcessor(); + bpmCandidateScriptApiSourceInfoProcessor.setScripts(scriptsOp); + return bpmCandidateScriptApiSourceInfoProcessor; + } +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/BpmCandidateSourceInfo.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/BpmCandidateSourceInfo.java new file mode 100644 index 000000000..1280a53e5 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/BpmCandidateSourceInfo.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.bpm.service.candidate; + +import cn.iocoder.yudao.module.bpm.controller.admin.candidate.vo.BpmTaskCandidateVO; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleBaseVO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.flowable.engine.delegate.DelegateExecution; + +import java.util.HashSet; +import java.util.Set; + +/** + * 获取候选人信息 + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +public class BpmCandidateSourceInfo { + + @Schema(description = "流程id") + private String processInstanceId; + + @Schema(description = "当前任务ID") + private String taskId; + + /** + * 通过这些规则,生成最终需要生成的用户 + */ + @Schema(description = "当前任务预选规则") + private Set rules; + + @Schema(description = "源执行流程") + private DelegateExecution execution; + + public void addRule(BpmTaskCandidateVO vo) { + assert vo != null; + if (rules == null) { + rules = new HashSet<>(); + } + rules.add(vo); + } +} \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/BpmCandidateSourceInfoProcessor.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/BpmCandidateSourceInfoProcessor.java new file mode 100644 index 000000000..52a8adaaf --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/BpmCandidateSourceInfoProcessor.java @@ -0,0 +1,52 @@ +package cn.iocoder.yudao.module.bpm.service.candidate; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.module.bpm.controller.admin.candidate.vo.BpmTaskCandidateVO; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public interface BpmCandidateSourceInfoProcessor { + /** + * 获取该处理器支持的类型 + * 来自 {@link BpmTaskAssignRuleTypeEnum} + * + * @return + */ + Set getSupportedTypes(); + + /** + * 对规则和人员做校验 + * + * @param type 规则 + * @param options 人员id + */ + void validRuleOptions(Integer type, Set options); + + /** + * 默认的处理 + * 如果想去操作所有的规则,则可以覆盖此方法 + * + * @param request + * @param chain + * @return 必须包含的是用户ID,而不是其他的ID + * @throws Exception + */ + default Set process(BpmCandidateSourceInfo request, BpmCandidateSourceInfoProcessorChain chain) throws Exception { + Set rules = request.getRules(); + Set results = new HashSet<>(); + for (BpmTaskCandidateVO rule : rules) { + // 每个处理器都有机会处理自己支持的事件 + if (CollUtil.contains(getSupportedTypes(), rule.getType())) { + results.addAll(doProcess(request, rule)); + } + } + return results; + } + + default Set doProcess(BpmCandidateSourceInfo request, BpmTaskCandidateVO rule) { + return Collections.emptySet(); + } +} \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/BpmCandidateSourceInfoProcessorChain.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/BpmCandidateSourceInfoProcessorChain.java new file mode 100644 index 000000000..b33fd7356 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/BpmCandidateSourceInfoProcessorChain.java @@ -0,0 +1,105 @@ +package cn.iocoder.yudao.module.bpm.service.candidate; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.module.bpm.controller.admin.candidate.vo.BpmTaskCandidateVO; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import org.flowable.engine.delegate.DelegateExecution; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.*; +import java.util.stream.Collectors; + +@Service +public class BpmCandidateSourceInfoProcessorChain { + + // 保存处理节点 + + private List processorList; + @Resource + private AdminUserApi adminUserApi; + + /** + * 可添加其他处理器 + * + * @param processorOp + * @return + */ + @Resource + // 动态扩展处理节点 + public BpmCandidateSourceInfoProcessorChain addProcessor(ObjectProvider processorOp) { + List processor = processorOp.orderedStream().collect(Collectors.toList()); + if (null == processorList) { + processorList = new ArrayList<>(processor.size()); + } + processorList.addAll(processor); + return this; + } + + // 获取处理器处理 + public Set process(BpmCandidateSourceInfo sourceInfo) throws Exception { + // Verify our parameters + if (sourceInfo == null) { + throw new IllegalArgumentException(); + } + for (BpmCandidateSourceInfoProcessor processor : processorList) { + try { + for (BpmTaskCandidateVO vo : sourceInfo.getRules()) { + processor.validRuleOptions(vo.getType(), vo.getOptions()); + } + } catch (Exception e) { + e.printStackTrace(); + throw e; + } + } + Set saveResult = Collections.emptySet(); + Exception saveException = null; + for (BpmCandidateSourceInfoProcessor processor : processorList) { + try { + saveResult = processor.process(sourceInfo, this); + if (CollUtil.isNotEmpty(saveResult)) { + removeDisableUsers(saveResult); + break; + } + } catch (Exception e) { + saveException = e; + break; + } + } + // Return the exception or result state from the last execute() + if ((saveException != null)) { + throw saveException; + } else { + return (saveResult); + } + } + + public Set calculateTaskCandidateUsers(DelegateExecution execution, BpmCandidateSourceInfo sourceInfo) { + sourceInfo.setExecution(execution); + Set results = Collections.emptySet(); + try { + results = process(sourceInfo); + } catch (Exception e) { + e.printStackTrace(); + } + return results; + } + + /** + * 移除禁用用户 + * @param assigneeUserIds + */ + public void removeDisableUsers(Set assigneeUserIds) { + if (CollUtil.isEmpty(assigneeUserIds)) { + return; + } + Map userMap = adminUserApi.getUserMap(assigneeUserIds); + assigneeUserIds.removeIf(id -> { + AdminUserRespDTO user = userMap.get(id); + return user == null || !CommonStatusEnum.ENABLE.getStatus().equals(user.getStatus()); + }); + } +} \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateAdminUserApiSourceInfoProcessor.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateAdminUserApiSourceInfoProcessor.java new file mode 100644 index 000000000..5be8fd291 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateAdminUserApiSourceInfoProcessor.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.bpm.service.candidate.sourceInfoProcessor; + +import cn.iocoder.yudao.framework.common.util.collection.SetUtils; +import cn.iocoder.yudao.module.bpm.controller.admin.candidate.vo.BpmTaskCandidateVO; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum; +import cn.iocoder.yudao.module.bpm.service.candidate.BpmCandidateSourceInfo; +import cn.iocoder.yudao.module.bpm.service.candidate.BpmCandidateSourceInfoProcessor; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; + +import javax.annotation.Resource; +import java.util.Set; + +public class BpmCandidateAdminUserApiSourceInfoProcessor implements BpmCandidateSourceInfoProcessor { + @Resource + private AdminUserApi api; + + @Override + public Set getSupportedTypes() { + return SetUtils.asSet(BpmTaskAssignRuleTypeEnum.USER.getType()); + } + + @Override + public void validRuleOptions(Integer type, Set options) { + api.validateUserList(options); + } + + @Override + public Set doProcess(BpmCandidateSourceInfo request, BpmTaskCandidateVO rule) { + return rule.getOptions(); + } +} \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateDeptApiSourceInfoProcessor.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateDeptApiSourceInfoProcessor.java new file mode 100644 index 000000000..73c79b524 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateDeptApiSourceInfoProcessor.java @@ -0,0 +1,49 @@ +package cn.iocoder.yudao.module.bpm.service.candidate.sourceInfoProcessor; + +import cn.iocoder.yudao.framework.common.util.collection.SetUtils; +import cn.iocoder.yudao.module.bpm.controller.admin.candidate.vo.BpmTaskCandidateVO; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum; +import cn.iocoder.yudao.module.bpm.service.candidate.BpmCandidateSourceInfo; +import cn.iocoder.yudao.module.bpm.service.candidate.BpmCandidateSourceInfoProcessor; +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 javax.annotation.Resource; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; + +public class BpmCandidateDeptApiSourceInfoProcessor implements BpmCandidateSourceInfoProcessor { + @Resource + private DeptApi api; + @Resource + private AdminUserApi adminUserApi; + + @Override + public Set getSupportedTypes() { + return SetUtils.asSet(BpmTaskAssignRuleTypeEnum.DEPT_MEMBER.getType(), + BpmTaskAssignRuleTypeEnum.DEPT_LEADER.getType()); + } + + @Override + public void validRuleOptions(Integer type, Set options) { + api.validateDeptList(options); + } + + @Override + public Set doProcess(BpmCandidateSourceInfo request, BpmTaskCandidateVO rule) { + if (Objects.equals(BpmTaskAssignRuleTypeEnum.DEPT_MEMBER.getType(), rule.getType())) { + List users = adminUserApi.getUserListByDeptIds(rule.getOptions()); + return convertSet(users, AdminUserRespDTO::getId); + } else if (Objects.equals(BpmTaskAssignRuleTypeEnum.DEPT_LEADER.getType(), rule.getType())) { + List depts = api.getDeptList(rule.getOptions()); + return convertSet(depts, DeptRespDTO::getLeaderUserId); + } + return Collections.emptySet(); + } +} \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidatePostApiSourceInfoProcessor.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidatePostApiSourceInfoProcessor.java new file mode 100644 index 000000000..15abe86bc --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidatePostApiSourceInfoProcessor.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.bpm.service.candidate.sourceInfoProcessor; + +import cn.iocoder.yudao.framework.common.util.collection.SetUtils; +import cn.iocoder.yudao.module.bpm.controller.admin.candidate.vo.BpmTaskCandidateVO; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum; +import cn.iocoder.yudao.module.bpm.service.candidate.BpmCandidateSourceInfo; +import cn.iocoder.yudao.module.bpm.service.candidate.BpmCandidateSourceInfoProcessor; +import cn.iocoder.yudao.module.system.api.dept.PostApi; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Set; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; + +public class BpmCandidatePostApiSourceInfoProcessor implements BpmCandidateSourceInfoProcessor { + @Resource + private PostApi api; + @Resource + private AdminUserApi adminUserApi; + + @Override + public Set getSupportedTypes() { + return SetUtils.asSet(BpmTaskAssignRuleTypeEnum.POST.getType()); + } + + @Override + public void validRuleOptions(Integer type, Set options) { + api.validPostList(options); + } + + @Override + public Set doProcess(BpmCandidateSourceInfo request, BpmTaskCandidateVO rule) { + List users = adminUserApi.getUserListByPostIds(rule.getOptions()); + return convertSet(users, AdminUserRespDTO::getId); + } +} \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateRoleApiSourceInfoProcessor.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateRoleApiSourceInfoProcessor.java new file mode 100644 index 000000000..0e0ef6903 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateRoleApiSourceInfoProcessor.java @@ -0,0 +1,36 @@ +package cn.iocoder.yudao.module.bpm.service.candidate.sourceInfoProcessor; + +import cn.iocoder.yudao.framework.common.util.collection.SetUtils; +import cn.iocoder.yudao.module.bpm.controller.admin.candidate.vo.BpmTaskCandidateVO; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum; +import cn.iocoder.yudao.module.bpm.service.candidate.BpmCandidateSourceInfo; +import cn.iocoder.yudao.module.bpm.service.candidate.BpmCandidateSourceInfoProcessor; +import cn.iocoder.yudao.module.system.api.permission.PermissionApi; +import cn.iocoder.yudao.module.system.api.permission.RoleApi; + +import javax.annotation.Resource; +import java.util.Set; + +public class BpmCandidateRoleApiSourceInfoProcessor implements BpmCandidateSourceInfoProcessor { + @Resource + private RoleApi api; + + @Resource + private PermissionApi permissionApi; + + @Override + public Set getSupportedTypes() { + return SetUtils.asSet(BpmTaskAssignRuleTypeEnum.ROLE.getType()); + } + + @Override + public void validRuleOptions(Integer type, Set options) { + api.validRoleList(options); + } + + @Override + public Set doProcess(BpmCandidateSourceInfo request, BpmTaskCandidateVO rule) { + return permissionApi.getUserRoleIdListByRoleIds(rule.getOptions()); + } + +} \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateScriptApiSourceInfoProcessor.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateScriptApiSourceInfoProcessor.java new file mode 100644 index 000000000..b14ca2a1b --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateScriptApiSourceInfoProcessor.java @@ -0,0 +1,73 @@ +package cn.iocoder.yudao.module.bpm.service.candidate.sourceInfoProcessor; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.collection.SetUtils; +import cn.iocoder.yudao.module.bpm.controller.admin.candidate.vo.BpmTaskCandidateVO; +import cn.iocoder.yudao.module.bpm.enums.DictTypeConstants; +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.candidate.BpmCandidateSourceInfo; +import cn.iocoder.yudao.module.bpm.service.candidate.BpmCandidateSourceInfoProcessor; +import cn.iocoder.yudao.module.system.api.dict.DictDataApi; +import org.flowable.engine.delegate.DelegateExecution; +import org.springframework.beans.factory.ObjectProvider; + +import javax.annotation.Resource; +import java.util.*; +import java.util.stream.Collectors; + +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.module.bpm.enums.ErrorCodeConstants.TASK_ASSIGN_SCRIPT_NOT_EXISTS; + +public class BpmCandidateScriptApiSourceInfoProcessor implements BpmCandidateSourceInfoProcessor { + @Resource + private DictDataApi dictDataApi; + + /** + * 任务分配脚本 + */ + private Map scriptMap = Collections.emptyMap(); + + public void setScripts(ObjectProvider scriptsOp) { + List scripts = scriptsOp.orderedStream().collect(Collectors.toList()); + setScripts(scripts); + } + + public void setScripts(List scripts) { + this.scriptMap = convertMap(scripts, script -> script.getEnum().getId()); + } + + @Override + public Set getSupportedTypes() { + return SetUtils.asSet(BpmTaskAssignRuleTypeEnum.SCRIPT.getType()); + } + + @Override + public void validRuleOptions(Integer type, Set options) { + dictDataApi.validateDictDataList(DictTypeConstants.TASK_ASSIGN_SCRIPT, + CollectionUtils.convertSet(options, String::valueOf)); + } + + @Override + public Set doProcess(BpmCandidateSourceInfo request, BpmTaskCandidateVO rule) { + return calculateTaskCandidateUsersByScript(request.getExecution(), rule.getOptions()); + } + + private Set calculateTaskCandidateUsersByScript(DelegateExecution execution, Set options) { + // 获得对应的脚本 + List scripts = new ArrayList<>(options.size()); + options.forEach(id -> { + BpmTaskAssignScript script = scriptMap.get(id); + if (script == null) { + throw exception(TASK_ASSIGN_SCRIPT_NOT_EXISTS, id); + } + scripts.add(script); + }); + // 逐个计算任务 + Set userIds = new HashSet<>(); + scripts.forEach(script -> CollUtil.addAll(userIds, script.calculateTaskCandidateUsers(execution))); + return userIds; + } +} \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateUserGroupApiSourceInfoProcessor.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateUserGroupApiSourceInfoProcessor.java new file mode 100644 index 000000000..076d1693d --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/candidate/sourceInfoProcessor/BpmCandidateUserGroupApiSourceInfoProcessor.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.bpm.service.candidate.sourceInfoProcessor; + +import cn.iocoder.yudao.framework.common.util.collection.SetUtils; +import cn.iocoder.yudao.module.bpm.controller.admin.candidate.vo.BpmTaskCandidateVO; +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.service.candidate.BpmCandidateSourceInfo; +import cn.iocoder.yudao.module.bpm.service.candidate.BpmCandidateSourceInfoProcessor; +import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService; + +import javax.annotation.Resource; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class BpmCandidateUserGroupApiSourceInfoProcessor implements BpmCandidateSourceInfoProcessor { + @Resource + private BpmUserGroupService api; + @Resource + private BpmUserGroupService userGroupService; + + @Override + public Set getSupportedTypes() { + return SetUtils.asSet(BpmTaskAssignRuleTypeEnum.USER_GROUP.getType()); + } + + @Override + public void validRuleOptions(Integer type, Set options) { + api.validUserGroups(options); + } + + @Override + public Set doProcess(BpmCandidateSourceInfo request, BpmTaskCandidateVO rule) { + List userGroups = userGroupService.getUserGroupList(rule.getOptions()); + Set userIds = new HashSet<>(); + userGroups.forEach(group -> userIds.addAll(group.getMemberUserIds())); + return userIds; + } + +} \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/service/candidate/BpmCandidateSourceInfoProcessorChainTest.java b/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/service/candidate/BpmCandidateSourceInfoProcessorChainTest.java new file mode 100644 index 000000000..1af972d56 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/service/candidate/BpmCandidateSourceInfoProcessorChainTest.java @@ -0,0 +1,248 @@ +package cn.iocoder.yudao.module.bpm.service.candidate; + +import cn.hutool.core.map.MapUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.bpm.controller.admin.candidate.vo.BpmTaskCandidateVO; +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.enums.definition.BpmTaskRuleScriptEnum; +import cn.iocoder.yudao.module.bpm.framework.bpm.config.BpmCandidateProcessorConfiguration; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.BpmTaskAssignScript; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.impl.BpmTaskAssignLeaderX1Script; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.impl.BpmTaskAssignLeaderX2Script; +import cn.iocoder.yudao.module.bpm.service.candidate.BpmCandidateSourceInfo; +import cn.iocoder.yudao.module.bpm.service.candidate.BpmCandidateSourceInfoProcessorChain; +import cn.iocoder.yudao.module.bpm.service.candidate.sourceInfoProcessor.BpmCandidateScriptApiSourceInfoProcessor; +import cn.iocoder.yudao.module.bpm.service.definition.BpmTaskAssignRuleServiceImpl; +import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService; +import cn.iocoder.yudao.module.system.api.dept.DeptApi; +import cn.iocoder.yudao.module.system.api.dept.PostApi; +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; +import cn.iocoder.yudao.module.system.api.dict.DictDataApi; +import cn.iocoder.yudao.module.system.api.permission.PermissionApi; +import cn.iocoder.yudao.module.system.api.permission.RoleApi; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import org.flowable.engine.delegate.DelegateExecution; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; + +import javax.annotation.Resource; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static java.util.Collections.singleton; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +@Import({BpmCandidateSourceInfoProcessorChain.class, BpmCandidateProcessorConfiguration.class, + BpmCandidateScriptApiSourceInfoProcessor.class, BpmTaskAssignLeaderX1Script.class, + BpmTaskAssignLeaderX2Script.class}) +public class BpmCandidateSourceInfoProcessorChainTest extends BaseDbUnitTest { + @Resource + private BpmCandidateSourceInfoProcessorChain processorChain; + + @MockBean + private BpmUserGroupService userGroupService; + @MockBean + private DeptApi deptApi; + @MockBean + private AdminUserApi adminUserApi; + @MockBean + private PermissionApi permissionApi; + @MockBean + private RoleApi roleApi; + @MockBean + private PostApi postApi; + @MockBean + private DictDataApi dictDataApi; + @Resource + private BpmCandidateScriptApiSourceInfoProcessor bpmCandidateScriptApiSourceInfoProcessor; + + @Test + public void testCalculateTaskCandidateUsers_Role() { + // 准备参数 + BpmTaskCandidateVO rule = new BpmTaskCandidateVO().setOptions(asSet(1L, 2L)) + .setType(BpmTaskAssignRuleTypeEnum.ROLE.getType()); + // mock 方法 + when(permissionApi.getUserRoleIdListByRoleIds(eq(rule.getOptions()))) + .thenReturn(asSet(11L, 22L)); + mockGetUserMap(asSet(11L, 22L)); + + // 调用 + BpmCandidateSourceInfo sourceInfo = new BpmCandidateSourceInfo(); + sourceInfo.addRule(rule); + Set results = processorChain.calculateTaskCandidateUsers(null, sourceInfo); + // 断言 + assertEquals(asSet(11L, 22L), results); + } + + @Test + public void testCalculateTaskCandidateUsers_DeptMember() { + // 准备参数 + BpmTaskCandidateVO rule = new BpmTaskCandidateVO().setOptions(asSet(1L, 2L)) + .setType(BpmTaskAssignRuleTypeEnum.DEPT_MEMBER.getType()); + // mock 方法 + List users = CollectionUtils.convertList(asSet(11L, 22L), + id -> new AdminUserRespDTO().setId(id)); + when(adminUserApi.getUserListByDeptIds(eq(rule.getOptions()))).thenReturn(users); + mockGetUserMap(asSet(11L, 22L)); + + // 调用 + BpmCandidateSourceInfo sourceInfo = new BpmCandidateSourceInfo(); + sourceInfo.addRule(rule); + Set results = processorChain.calculateTaskCandidateUsers(null, sourceInfo); + // 断言 + assertEquals(asSet(11L, 22L), results); + } + + @Test + public void testCalculateTaskCandidateUsers_DeptLeader() { + // 准备参数 + BpmTaskCandidateVO rule = new BpmTaskCandidateVO().setOptions(asSet(1L, 2L)) + .setType(BpmTaskAssignRuleTypeEnum.DEPT_LEADER.getType()); + // mock 方法 + DeptRespDTO dept1 = randomPojo(DeptRespDTO.class, o -> o.setLeaderUserId(11L)); + DeptRespDTO dept2 = randomPojo(DeptRespDTO.class, o -> o.setLeaderUserId(22L)); + when(deptApi.getDeptList(eq(rule.getOptions()))).thenReturn(Arrays.asList(dept1, dept2)); + mockGetUserMap(asSet(11L, 22L)); + + // 调用 + BpmCandidateSourceInfo sourceInfo = new BpmCandidateSourceInfo(); + sourceInfo.addRule(rule); + Set results = processorChain.calculateTaskCandidateUsers(null, sourceInfo); + // 断言 + assertEquals(asSet(11L, 22L), results); + } + + @Test + public void testCalculateTaskCandidateUsers_Post() { + // 准备参数 + BpmTaskCandidateVO rule = new BpmTaskCandidateVO().setOptions(asSet(1L, 2L)) + .setType(BpmTaskAssignRuleTypeEnum.POST.getType()); + // mock 方法 + List users = CollectionUtils.convertList(asSet(11L, 22L), + id -> new AdminUserRespDTO().setId(id)); + when(adminUserApi.getUserListByPostIds(eq(rule.getOptions()))).thenReturn(users); + mockGetUserMap(asSet(11L, 22L)); + + // 调用 + BpmCandidateSourceInfo sourceInfo = new BpmCandidateSourceInfo(); + sourceInfo.addRule(rule); + Set results = processorChain.calculateTaskCandidateUsers(null, sourceInfo); + // 断言 + assertEquals(asSet(11L, 22L), results); + } + + @Test + public void testCalculateTaskCandidateUsers_User() { + // 准备参数 + BpmTaskCandidateVO rule = new BpmTaskCandidateVO().setOptions(asSet(1L, 2L)) + .setType(BpmTaskAssignRuleTypeEnum.USER.getType()); + // mock 方法 + mockGetUserMap(asSet(1L, 2L)); + + // 调用 + BpmCandidateSourceInfo sourceInfo = new BpmCandidateSourceInfo(); + sourceInfo.addRule(rule); + Set results = processorChain.calculateTaskCandidateUsers(null, sourceInfo); + // 断言 + assertEquals(asSet(1L, 2L), results); + } + + @Test + public void testCalculateTaskCandidateUsers_UserGroup() { + // 准备参数 + BpmTaskCandidateVO rule = new BpmTaskCandidateVO().setOptions(asSet(1L, 2L)) + .setType(BpmTaskAssignRuleTypeEnum.USER_GROUP.getType()); + // mock 方法 + BpmUserGroupDO userGroup1 = randomPojo(BpmUserGroupDO.class, o -> o.setMemberUserIds(asSet(11L, 12L))); + BpmUserGroupDO userGroup2 = randomPojo(BpmUserGroupDO.class, o -> o.setMemberUserIds(asSet(21L, 22L))); + when(userGroupService.getUserGroupList(eq(rule.getOptions()))).thenReturn(Arrays.asList(userGroup1, userGroup2)); + mockGetUserMap(asSet(11L, 12L, 21L, 22L)); + + // 调用 + BpmCandidateSourceInfo sourceInfo = new BpmCandidateSourceInfo(); + sourceInfo.addRule(rule); + Set results = processorChain.calculateTaskCandidateUsers(null, sourceInfo); + // 断言 + assertEquals(asSet(11L, 12L, 21L, 22L), results); + } + + private void mockGetUserMap(Set assigneeUserIds) { + Map userMap = CollectionUtils.convertMap(assigneeUserIds, id -> id, + id -> new AdminUserRespDTO().setId(id).setStatus(CommonStatusEnum.ENABLE.getStatus())); + when(adminUserApi.getUserMap(eq(assigneeUserIds))).thenReturn(userMap); + } + + @Test + public void testCalculateTaskCandidateUsers_Script() { + // 准备参数 + BpmTaskCandidateVO rule = new BpmTaskCandidateVO().setOptions(asSet(20L, 21L)) + .setType(BpmTaskAssignRuleTypeEnum.SCRIPT.getType()); + // mock 方法 + BpmTaskAssignScript script1 = new BpmTaskAssignScript() { + + @Override + public Set calculateTaskCandidateUsers(DelegateExecution task) { + return singleton(11L); + } + + @Override + public BpmTaskRuleScriptEnum getEnum() { + return BpmTaskRuleScriptEnum.LEADER_X1; + } + }; + BpmTaskAssignScript script2 = new BpmTaskAssignScript() { + + @Override + public Set calculateTaskCandidateUsers(DelegateExecution task) { + return singleton(22L); + } + + @Override + public BpmTaskRuleScriptEnum getEnum() { + return BpmTaskRuleScriptEnum.LEADER_X2; + } + }; + bpmCandidateScriptApiSourceInfoProcessor.setScripts(Arrays.asList(script1, script2)); + mockGetUserMap(asSet(11L, 22L)); + + // 调用 + BpmCandidateSourceInfo sourceInfo = new BpmCandidateSourceInfo(); + sourceInfo.addRule(rule); + Set results = processorChain.calculateTaskCandidateUsers(null, sourceInfo); + // 断言 + assertEquals(asSet(11L, 22L), results); + } + + @Test + public void testRemoveDisableUsers() { + // 准备参数. 1L 可以找到;2L 是禁用的;3L 找不到 + Set assigneeUserIds = asSet(1L, 2L, 3L); + // mock 方法 + AdminUserRespDTO user1 = randomPojo(AdminUserRespDTO.class, o -> o.setId(1L) + .setStatus(CommonStatusEnum.ENABLE.getStatus())); + AdminUserRespDTO user2 = randomPojo(AdminUserRespDTO.class, o -> o.setId(2L) + .setStatus(CommonStatusEnum.DISABLE.getStatus())); + Map userMap = MapUtil.builder(user1.getId(), user1) + .put(user2.getId(), user2).build(); + when(adminUserApi.getUserMap(eq(assigneeUserIds))).thenReturn(userMap); + + // 调用 + processorChain.removeDisableUsers(assigneeUserIds); + // 断言 + assertEquals(asSet(1L), assigneeUserIds); + } + +} \ No newline at end of file