Commit 9922e2fcf249293c501cae367a90c48f1ed34673

Authored by YunaiV
1 parent 6cdcde69

1. 调整 activiti 配置,使用 SQL 初始化 activi 表;

2. 实现内置的几个 Bpm 自定义分配 Script
Showing 12 changed files with 270 additions and 44 deletions
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/bpm/enums/definition/BpmTaskRuleScriptEnum.java
... ... @@ -5,7 +5,7 @@ import lombok.Getter;
5 5  
6 6 /**
7 7 * BPM 任务规则的脚本枚举
8   - * 目前暂时通过 TODO 硬编码,未来可以考虑 Groovy 动态脚本的方式
  8 + * 目前暂时通过 TODO 芋艿:硬编码,未来可以考虑 Groovy 动态脚本的方式
9 9 *
10 10 * @author 芋道源码
11 11 */
... ... @@ -13,8 +13,10 @@ import lombok.Getter;
13 13 @AllArgsConstructor
14 14 public enum BpmTaskRuleScriptEnum {
15 15  
16   - ONE(1L, ""),
17   - TWO(2L, "");
  16 + START_USER(10L, "流程发起人"),
  17 +
  18 + LEADER_X1(20L, "流程发起人的一级领导"),
  19 + LEADER_X2(21L, "流程发起人的二级领导");
18 20  
19 21 /**
20 22 * 脚本编号
... ...
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/bpm/framework/activiti/core/behavior/script/BpmTaskAssignScript.java
... ... @@ -4,6 +4,7 @@ import cn.iocoder.yudao.adminserver.modules.bpm.enums.definition.BpmTaskRuleScri
4 4 import org.activiti.engine.impl.persistence.entity.TaskEntity;
5 5  
6 6 import java.util.List;
  7 +import java.util.Set;
7 8  
8 9 /**
9 10 * Bpm 任务分配的自定义 Script 脚本
... ... @@ -22,7 +23,7 @@ public interface BpmTaskAssignScript {
22 23 * @param task 任务
23 24 * @return 候选人用户的编号数组
24 25 */
25   - List<Long> calculateTaskCandidateUsers(TaskEntity task);
  26 + Set<Long> calculateTaskCandidateUsers(TaskEntity task);
26 27  
27 28 /**
28 29 * 获得枚举值
... ...
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/bpm/framework/activiti/core/behavior/script/impl/BpmTaskAssignLeaderAbstractScript.java 0 → 100644
  1 +package cn.iocoder.yudao.adminserver.modules.bpm.framework.activiti.core.behavior.script.impl;
  2 +
  3 +import cn.iocoder.yudao.adminserver.modules.bpm.framework.activiti.core.behavior.script.BpmTaskAssignScript;
  4 +import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.dept.SysDeptDO;
  5 +import cn.iocoder.yudao.adminserver.modules.system.service.dept.SysDeptService;
  6 +import cn.iocoder.yudao.adminserver.modules.system.service.user.SysUserService;
  7 +import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.user.SysUserDO;
  8 +import org.activiti.engine.impl.persistence.entity.TaskEntity;
  9 +import org.springframework.util.Assert;
  10 +
  11 +import javax.annotation.Resource;
  12 +import java.util.Set;
  13 +
  14 +import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
  15 +import static java.util.Collections.emptySet;
  16 +
  17 +/**
  18 + * 分配给发起人的 Leader 审批的 Script 实现类
  19 + * 目前 Leader 的定义是,
  20 + *
  21 + * @author 芋道源码
  22 + */
  23 +public abstract class BpmTaskAssignLeaderAbstractScript implements BpmTaskAssignScript {
  24 +
  25 + @Resource
  26 + private SysUserService userService;
  27 + @Resource
  28 + private SysDeptService deptService;
  29 +
  30 + protected Set<Long> calculateTaskCandidateUsers(TaskEntity task, int level) {
  31 + Assert.isTrue(level > 0, "level 必须大于 0");
  32 + // 获得发起人
  33 + Long startUserId = Long.parseLong(task.getProcessInstance().getStartUserId());
  34 + // 获得对应 leve 的部门
  35 + SysDeptDO dept = null;
  36 + for (int i = 0; i < level; i++) {
  37 + // 获得 level 对应的部门
  38 + if (dept == null) {
  39 + dept = getStartUserDept(startUserId);
  40 + if (dept == null) { // 找不到发起人的部门,所以无法使用该规则
  41 + return emptySet();
  42 + }
  43 + } else {
  44 + SysDeptDO parentDept = deptService.getDept(dept.getParentId());
  45 + if (parentDept == null) { // 找不到父级部门,所以只好结束寻找。原因是:例如说,级别比较高的人,所在部门层级比较少
  46 + break;
  47 + }
  48 + dept = parentDept;
  49 + }
  50 + }
  51 + return dept.getLeaderUserId() != null ? asSet(dept.getLeaderUserId()) : emptySet();
  52 + }
  53 +
  54 + private SysDeptDO getStartUserDept(Long startUserId) {
  55 + SysUserDO startUser = userService.getUser(startUserId);
  56 + if (startUser.getDeptId() == null) { // 找不到部门,所以无法使用该规则
  57 + return null;
  58 + }
  59 + return deptService.getDept(startUser.getDeptId());
  60 + }
  61 +
  62 +}
... ...
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/bpm/framework/activiti/core/behavior/script/impl/BpmTaskAssignLeaderX1Script.java 0 → 100644
  1 +package cn.iocoder.yudao.adminserver.modules.bpm.framework.activiti.core.behavior.script.impl;
  2 +
  3 +import cn.iocoder.yudao.adminserver.modules.bpm.enums.definition.BpmTaskRuleScriptEnum;
  4 +import org.activiti.engine.impl.persistence.entity.TaskEntity;
  5 +import org.springframework.stereotype.Component;
  6 +
  7 +import java.util.Set;
  8 +
  9 +/**
  10 + * 分配给发起人的一级 Leader 审批的 Script 实现类
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +@Component
  15 +public class BpmTaskAssignLeaderX1Script extends BpmTaskAssignLeaderAbstractScript {
  16 +
  17 + @Override
  18 + public Set<Long> calculateTaskCandidateUsers(TaskEntity task) {
  19 + return calculateTaskCandidateUsers(task, 1);
  20 + }
  21 +
  22 + @Override
  23 + public BpmTaskRuleScriptEnum getEnum() {
  24 + return BpmTaskRuleScriptEnum.LEADER_X1;
  25 + }
  26 +
  27 +}
... ...
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/bpm/framework/activiti/core/behavior/script/impl/BpmTaskAssignLeaderX2Script.java 0 → 100644
  1 +package cn.iocoder.yudao.adminserver.modules.bpm.framework.activiti.core.behavior.script.impl;
  2 +
  3 +import cn.iocoder.yudao.adminserver.modules.bpm.enums.definition.BpmTaskRuleScriptEnum;
  4 +import org.activiti.engine.impl.persistence.entity.TaskEntity;
  5 +import org.springframework.stereotype.Component;
  6 +
  7 +import java.util.Set;
  8 +
  9 +/**
  10 + * 分配给发起人的二级 Leader 审批的 Script 实现类
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +@Component
  15 +public class BpmTaskAssignLeaderX2Script extends BpmTaskAssignLeaderAbstractScript {
  16 +
  17 + @Override
  18 + public Set<Long> calculateTaskCandidateUsers(TaskEntity task) {
  19 + return calculateTaskCandidateUsers(task, 2);
  20 + }
  21 +
  22 + @Override
  23 + public BpmTaskRuleScriptEnum getEnum() {
  24 + return BpmTaskRuleScriptEnum.LEADER_X2;
  25 + }
  26 +
  27 +}
... ...
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/bpm/framework/activiti/core/behavior/script/impl/BpmTaskAssignStartUserScript.java 0 → 100644
  1 +package cn.iocoder.yudao.adminserver.modules.bpm.framework.activiti.core.behavior.script.impl;
  2 +
  3 +import cn.iocoder.yudao.adminserver.modules.bpm.enums.definition.BpmTaskRuleScriptEnum;
  4 +import cn.iocoder.yudao.adminserver.modules.bpm.framework.activiti.core.behavior.script.BpmTaskAssignScript;
  5 +import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
  6 +import org.activiti.engine.impl.persistence.entity.TaskEntity;
  7 +import org.springframework.stereotype.Component;
  8 +
  9 +import java.util.Set;
  10 +
  11 +/**
  12 + * 分配给发起人审批的 Script 实现类
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +@Component
  17 +public class BpmTaskAssignStartUserScript implements BpmTaskAssignScript {
  18 +
  19 + @Override
  20 + public Set<Long> calculateTaskCandidateUsers(TaskEntity task) {
  21 + Long userId = Long.parseLong(task.getProcessInstance().getStartUserId());
  22 + return SetUtils.asSet(userId);
  23 + }
  24 +
  25 + @Override
  26 + public BpmTaskRuleScriptEnum getEnum() {
  27 + return BpmTaskRuleScriptEnum.START_USER;
  28 + }
  29 +
  30 +}
... ...
yudao-admin-server/src/main/resources/application-dev.yaml
... ... @@ -61,19 +61,6 @@ spring:
61 61 port: 6379 # 端口
62 62 database: 1 # 数据库索引
63 63  
64   - # 工作流 Activiti 配置
65   - activiti:
66   - # 1. false:默认值,activiti启动时,对比数据库表中保存的版本,如果不匹配。将抛出异常
67   - # 2. true:启动时会对数据库中所有表进行更新操作,如果表存在,不做处理,反之,自动创建表
68   - # 3. create_drop:启动时自动创建表,关闭时自动删除表
69   - # 4. drop_create:启动时,删除旧表,再创建新表
70   - database-schema-update: true
71   - # activiti7 默认不生成历史信息表,需手动设置开启
72   - db-history-used: true
73   - check-process-definitions: true
74   - #full:保存历史数据的最高级别,可保存全部流程相关细节,包括流程流转各节点参数
75   - history-level: full
76   -
77 64 --- #################### 定时任务相关配置 ####################
78 65  
79 66 # Quartz 配置项,对应 QuartzProperties 配置类
... ...
yudao-admin-server/src/main/resources/application-local.yaml
... ... @@ -61,19 +61,6 @@ spring:
61 61 port: 6379 # 端口
62 62 database: 0 # 数据库索引
63 63  
64   - # 工作流 Activiti 配置
65   - activiti:
66   - #1.false:默认值,activiti启动时,对比数据库表中保存的版本,如果不匹配。将抛出异常
67   - #2.true:启动时会对数据库中所有表进行更新操作,如果表存在,不做处理,反之,自动创建表
68   - #3.create_drop:启动时自动创建表,关闭时自动删除表
69   - #4.drop_create:启动时,删除旧表,再创建新表
70   - database-schema-update: true
71   - #activiti7默认不生成历史信息表,需手动设置开启
72   - db-history-used: true
73   - check-process-definitions: true
74   - #full:保存历史数据的最高级别,可保存全部流程相关细节,包括流程流转各节点参数
75   - history-level: full
76   -
77 64 --- #################### 定时任务相关配置 ####################
78 65  
79 66 # Quartz 配置项,对应 QuartzProperties 配置类
... ...
yudao-admin-server/src/main/resources/application.yaml
... ... @@ -20,6 +20,19 @@ spring:
20 20 write-durations-as-timestamps: true # 设置 Duration 的格式,使用时间戳
21 21 fail-on-empty-beans: false # 允许序列化无属性的 Bean
22 22  
  23 + # 工作流 Activiti 配置
  24 + activiti:
  25 + # 1.false: 默认值,activiti启动时,对比数据库表中保存的版本,如果不匹配。将抛出异常
  26 + # 2.true: 启动时会对数据库中所有表进行更新操作,如果表存在,不做处理,反之,自动创建表
  27 + # 3.create_drop: 启动时自动创建表,关闭时自动删除表
  28 + # 4.drop_create: 启动时,删除旧表,再创建新表
  29 + database-schema-update: false # 设置为 false,可通过 sql/activiti.sql 初始化
  30 + # activiti7 默认不生成历史信息表,需手动设置开启
  31 + db-history-used: true
  32 + check-process-definitions: true
  33 + # full:保存历史数据的最高级别,可保存全部流程相关细节,包括流程流转各节点参数
  34 + history-level: full
  35 +
23 36 # MyBatis Plus 的配置项
24 37 mybatis-plus:
25 38 # 在 mybatis-config/mybatis-config.xml 中设置 TODO jason:看看有没其它解决方案
... ...
yudao-admin-server/src/main/resources/processes/leave-update.bpmn renamed to yudao-admin-server/src/main/resources/processes/leave-update.bpmn_bak
yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/bpm/framework/activiti/core/behavior/BpmUserTaskActivitiBehaviorTest.java
... ... @@ -15,25 +15,19 @@ import cn.iocoder.yudao.adminserver.modules.system.service.user.SysUserService;
15 15 import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.user.SysUserDO;
16 16 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
17 17 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
18   -import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
19 18 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
20   -import cn.iocoder.yudao.framework.test.core.util.RandomUtils;
21 19 import org.activiti.engine.impl.persistence.entity.TaskEntity;
22 20 import org.junit.jupiter.api.Test;
23 21 import org.mockito.InjectMocks;
24 22 import org.mockito.Mock;
25   -import org.mockito.invocation.InvocationOnMock;
26   -import org.mockito.stubbing.Answer;
27 23  
28 24 import java.util.*;
29   -import java.util.function.Consumer;
30   -import java.util.function.Function;
31 25  
32 26 import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.*;
33 27 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  28 +import static java.util.Collections.singleton;
34 29 import static java.util.Collections.singletonList;
35 30 import static org.junit.jupiter.api.Assertions.*;
36   -import static org.mockito.ArgumentMatchers.anySet;
37 31 import static org.mockito.ArgumentMatchers.eq;
38 32 import static org.mockito.Mockito.when;
39 33  
... ... @@ -153,31 +147,31 @@ class BpmUserTaskActivitiBehaviorTest extends BaseMockitoUnitTest {
153 147 @Test
154 148 public void testCalculateTaskCandidateUsers_Script() {
155 149 // 准备参数
156   - BpmTaskAssignRuleDO rule = new BpmTaskAssignRuleDO().setOptions(asSet(1L, 2L))
  150 + BpmTaskAssignRuleDO rule = new BpmTaskAssignRuleDO().setOptions(asSet(20L, 21L))
157 151 .setType(BpmTaskAssignRuleTypeEnum.SCRIPT.getType());
158 152 // mock 方法
159 153 BpmTaskAssignScript script1 = new BpmTaskAssignScript() {
160 154  
161 155 @Override
162   - public List<Long> calculateTaskCandidateUsers(TaskEntity task) {
163   - return singletonList(11L);
  156 + public Set<Long> calculateTaskCandidateUsers(TaskEntity task) {
  157 + return singleton(11L);
164 158 }
165 159  
166 160 @Override
167 161 public BpmTaskRuleScriptEnum getEnum() {
168   - return BpmTaskRuleScriptEnum.ONE;
  162 + return BpmTaskRuleScriptEnum.LEADER_X1;
169 163 }
170 164 };
171 165 BpmTaskAssignScript script2 = new BpmTaskAssignScript() {
172 166  
173 167 @Override
174   - public List<Long> calculateTaskCandidateUsers(TaskEntity task) {
175   - return singletonList(22L);
  168 + public Set<Long> calculateTaskCandidateUsers(TaskEntity task) {
  169 + return singleton(22L);
176 170 }
177 171  
178 172 @Override
179 173 public BpmTaskRuleScriptEnum getEnum() {
180   - return BpmTaskRuleScriptEnum.TWO;
  174 + return BpmTaskRuleScriptEnum.LEADER_X2;
181 175 }
182 176 };
183 177 behavior.setScripts(Arrays.asList(script1, script2));
... ...
yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/bpm/framework/activiti/core/behavior/script/impl/BpmTaskAssignLeaderX2ScriptTest.java 0 → 100644
  1 +package cn.iocoder.yudao.adminserver.modules.bpm.framework.activiti.core.behavior.script.impl;
  2 +
  3 +import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.dept.SysDeptDO;
  4 +import cn.iocoder.yudao.adminserver.modules.system.service.dept.SysDeptService;
  5 +import cn.iocoder.yudao.adminserver.modules.system.service.user.SysUserService;
  6 +import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.user.SysUserDO;
  7 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  8 +import org.activiti.engine.impl.persistence.entity.ExecutionEntityImpl;
  9 +import org.activiti.engine.impl.persistence.entity.TaskEntity;
  10 +import org.activiti.engine.impl.persistence.entity.TaskEntityImpl;
  11 +import org.junit.jupiter.api.Test;
  12 +import org.mockito.InjectMocks;
  13 +import org.mockito.Mock;
  14 +
  15 +import javax.annotation.Resource;
  16 +
  17 +import java.util.Set;
  18 +
  19 +import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
  20 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  21 +import static org.junit.jupiter.api.Assertions.*;
  22 +import static org.mockito.ArgumentMatchers.eq;
  23 +import static org.mockito.Mockito.mock;
  24 +import static org.mockito.Mockito.when;
  25 +
  26 +class BpmTaskAssignLeaderX2ScriptTest extends BaseMockitoUnitTest {
  27 +
  28 + @InjectMocks
  29 + private BpmTaskAssignLeaderX2Script script;
  30 +
  31 + @Mock
  32 + private SysUserService userService;
  33 + @Mock
  34 + private SysDeptService deptService;
  35 +
  36 + @Test
  37 + public void testCalculateTaskCandidateUsers_noDept() {
  38 + // 准备参数
  39 + TaskEntity task = buildTaskEntity(1L);
  40 + // mock 方法(startUser)
  41 + SysUserDO startUser = randomPojo(SysUserDO.class, o -> o.setDeptId(10L));
  42 + when(userService.getUser(eq(1L))).thenReturn(startUser);
  43 +
  44 + // 调用
  45 + Set<Long> result = script.calculateTaskCandidateUsers(task);
  46 + // 断言
  47 + assertEquals(0, result.size());
  48 + }
  49 +
  50 + @Test
  51 + public void testCalculateTaskCandidateUsers_noParentDept() {
  52 + // 准备参数
  53 + TaskEntity task = buildTaskEntity(1L);
  54 + // mock 方法(startUser)
  55 + SysUserDO startUser = randomPojo(SysUserDO.class, o -> o.setDeptId(10L));
  56 + when(userService.getUser(eq(1L))).thenReturn(startUser);
  57 + SysDeptDO startUserDept = randomPojo(SysDeptDO.class, o -> o.setId(10L).setParentId(100L)
  58 + .setLeaderUserId(20L));
  59 + when(deptService.getDept(eq(10L))).thenReturn(startUserDept);
  60 +
  61 + // 调用
  62 + Set<Long> result = script.calculateTaskCandidateUsers(task);
  63 + // 断言
  64 + assertEquals(asSet(20L), result);
  65 + }
  66 +
  67 + @Test
  68 + public void testCalculateTaskCandidateUsers_existParentDept() {
  69 + // 准备参数
  70 + TaskEntity task = buildTaskEntity(1L);
  71 + // mock 方法(startUser)
  72 + SysUserDO startUser = randomPojo(SysUserDO.class, o -> o.setDeptId(10L));
  73 + when(userService.getUser(eq(1L))).thenReturn(startUser);
  74 + SysDeptDO startUserDept = randomPojo(SysDeptDO.class, o -> o.setId(10L).setParentId(100L)
  75 + .setLeaderUserId(20L));
  76 + when(deptService.getDept(eq(10L))).thenReturn(startUserDept);
  77 + // mock 方法(父 dept)
  78 + SysDeptDO parentDept = randomPojo(SysDeptDO.class, o -> o.setId(100L).setParentId(1000L)
  79 + .setLeaderUserId(200L));
  80 + when(deptService.getDept(eq(100L))).thenReturn(parentDept);
  81 +
  82 + // 调用
  83 + Set<Long> result = script.calculateTaskCandidateUsers(task);
  84 + // 断言
  85 + assertEquals(asSet(200L), result);
  86 + }
  87 +
  88 + @SuppressWarnings("SameParameterValue")
  89 + private TaskEntity buildTaskEntity(Long startUserId) {
  90 + TaskEntityImpl task = new TaskEntityImpl();
  91 + task.setProcessInstance(new ExecutionEntityImpl());
  92 + task.getProcessInstance().setStartUserId(String.valueOf(startUserId));
  93 + return task;
  94 + }
  95 +
  96 +}
... ...