Commit 41b9ab2ba53b46d23cbc78b194639e8df70b23c2

Authored by YunaiV
1 parent 8585e057

【代码评审】BPM:review 快搭的实现

Showing 27 changed files with 78 additions and 89 deletions
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmApproveMethodEnum.java
... ... @@ -4,7 +4,6 @@ import cn.hutool.core.util.ArrayUtil;
4 4 import lombok.AllArgsConstructor;
5 5 import lombok.Getter;
6 6  
7   -// TODO @芋艿:审批方式的名字,可能要看下;
8 7 /**
9 8 * BPM 多人审批方式的枚举
10 9 *
... ...
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmFieldPermissionEnum.java
... ... @@ -13,6 +13,7 @@ import lombok.Getter;
13 13 @AllArgsConstructor
14 14 public enum BpmFieldPermissionEnum {
15 15  
  16 + // TODO @jason:这个顺序要不要改下,和页面保持一致;只读(1)、编辑(2)、隐藏(3)
16 17 WRITE(1, "可编辑"),
17 18 READ(2, "只读"),
18 19 NONE(3, "隐藏");
... ...
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java
... ... @@ -13,8 +13,10 @@ import lombok.Getter;
13 13 @AllArgsConstructor
14 14 public enum BpmUserTaskRejectHandlerType {
15 15  
  16 + // TODO @jason:是不是收敛成 2 个:FINISH_PROCESS => 1. 直接结束流程;RETURN_PRE_USER_TASK => 2. 驳回到指定节点(RETURN_USER_TASK【去掉 PRE】)
16 17 FINISH_PROCESS(1, "终止流程"),
17 18 RETURN_PRE_USER_TASK(2, "驳回到指定任务节点"),
  19 +
18 20 FINISH_PROCESS_BY_REJECT_NUMBER(3, "按拒绝人数终止流程"), // 用于会签
19 21 FINISH_TASK(4, "结束任务"); // 待实现,可能会用于意见分支
20 22  
... ... @@ -24,4 +26,5 @@ public enum BpmUserTaskRejectHandlerType {
24 26 public static BpmUserTaskRejectHandlerType typeOf(Integer type) {
25 27 return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
26 28 }
  29 +
27 30 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java
... ... @@ -149,6 +149,7 @@ public class BpmModelController {
149 149  
150 150 // ========== 仿钉钉/飞书的精简模型 =========
151 151  
  152 + // TODO @jason:modelId => id 哈。一般属于自己的模块,可以简化命名。
152 153 @GetMapping("/simple/get")
153 154 @Operation(summary = "获得仿钉钉流程设计模型")
154 155 @Parameter(name = "modelId", description = "流程模型编号", required = true, example = "a2c5eee0-eb6c-11ee-abf4-0c37967c420a")
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java
... ... @@ -43,6 +43,7 @@ public class BpmSimpleModelNodeVO {
43 43 @Schema(description = "节点的属性")
44 44 private Map<String, Object> attributes; // TODO @jason:建议是字段分拆下;类似说:
45 45  
  46 + // TODO @jason:看看是不是可以简化;
46 47 /**
47 48 * 附加节点 Id, 该节点不从前端传入。 由程序生成. 由于当个节点无法完成功能。 需要附加节点来完成。
48 49 * 例如: 会签时需要按拒绝人数来终止流程。 需要 userTask + ServiceTask 两个节点配合完成。 serviceTask 由后端生成。
... ... @@ -52,12 +53,13 @@ public class BpmSimpleModelNodeVO {
52 53  
53 54 // Map<String, Integer> formPermissions; 表单权限;仅发起、审批、抄送节点会使用
54 55 // Integer approveMethod; 审批方式;仅审批节点会使用
55   - // TODO @jason 后面和前端一起调整一下
56   - // TODO @芋艿:审批人的选择;
57   - // TODO @芋艿:没有人的策略?
58   - // TODO @芋艿:审批拒绝的策略?
59   - // TODO @芋艿:配置的可操作列表?
60   - // TODO @芋艿:超时配置;要支持指定时间点、指定时间间隔;
  56 + // TODO @jason 后面和前端一起调整一下;下面的 ①、②、③ 是优先级
  57 + // TODO @芋艿:① 审批人的选择;
  58 + // TODO @芋艿:⑥ 没有人的策略?
  59 + // TODO @芋艿:② 审批拒绝的策略?
  60 + // TODO @芋艿:③ 配置的可操作列表?(操作权限)
  61 + // TODO @芋艿:④ 表单的权限列表?
  62 + // TODO @芋艿:⑨ 超时配置;要支持指定时间点、指定时间间隔;
61 63 // TODO @芋艿:条件;建议可以固化的一些选项;然后有个表达式兜底;要支持
62 64  
63 65 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java
... ... @@ -11,6 +11,7 @@ import lombok.Data;
11 11 @Data
12 12 public class BpmSimpleModelUpdateReqVO {
13 13  
  14 + // TODO @jason:=> id
14 15 @Schema(description = "流程模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
15 16 @NotEmpty(message = "流程模型编号不能为空")
16 17 private String modelId; // 对应 Flowable act_re_model 表 ID_ 字段
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java
... ... @@ -28,9 +28,6 @@ public class BpmTaskCandidateStartUserStrategy implements BpmTaskCandidateStrate
28 28 return BpmTaskCandidateStrategyEnum.START_USER;
29 29 }
30 30  
31   - /**
32   - * 无需校验参数
33   - */
34 31 @Override
35 32 public void validateParam(String param) {}
36 33  
... ... @@ -40,11 +37,9 @@ public class BpmTaskCandidateStartUserStrategy implements BpmTaskCandidateStrate
40 37 return SetUtils.asSet(Long.valueOf(startUserId));
41 38 }
42 39  
43   - /**
44   - * 不需要参数
45   - */
46 40 @Override
47 41 public boolean isParamRequired() {
48 42 return false;
49 43 }
  44 +
50 45 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/CopyUserDelegate.java
1 1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.custom.delegate;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
4 5 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceCopyService;
5 6 import jakarta.annotation.Resource;
... ... @@ -10,23 +11,29 @@ import org.springframework.stereotype.Service;
10 11  
11 12 import java.util.Set;
12 13  
  14 +// TODO @jason:类名可以改成 BpmCopyTaskDelegate
13 15 /**
14   - * 处理抄送用户的代理
  16 + * 处理抄送用户的 {@link JavaDelegate} 的实现类
15 17 *
16 18 * @author jason
17 19 */
18   -@Service
  20 +@Service // TODO @jason:这种注解,建议用 @Component
19 21 public class CopyUserDelegate implements JavaDelegate {
20 22  
21 23 @Resource
22 24 private BpmTaskCandidateInvoker taskCandidateInvoker;
  25 +
23 26 @Resource
24 27 private BpmProcessInstanceCopyService processInstanceCopyService;
25 28  
26 29 @Override
27 30 public void execute(DelegateExecution execution) {
28   - // TODO @芋艿:可能要考虑,系统抄送,没有 taskId 的情况。
  31 + // 1. 获得抄送人
29 32 Set<Long> userIds = taskCandidateInvoker.calculateUsers(execution);
  33 + if (CollUtil.isEmpty(userIds)) {
  34 + return;
  35 + }
  36 + // 2. 执行抄送
30 37 FlowElement currentFlowElement = execution.getCurrentFlowElement();
31 38 processInstanceCopyService.createProcessInstanceCopy(userIds, execution.getProcessInstanceId(),
32 39 currentFlowElement.getId(), currentFlowElement.getName());
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/MultiInstanceServiceTaskDelegate.java
... ... @@ -10,6 +10,7 @@ import org.flowable.engine.delegate.DelegateExecution;
10 10 import org.flowable.engine.delegate.JavaDelegate;
11 11 import org.springframework.stereotype.Component;
12 12  
  13 +// TODO @jason:微信已经讨论,简化哈
13 14 /**
14 15 * 处理会签 Service Task 代理
15 16 *
... ... @@ -25,7 +26,7 @@ public class MultiInstanceServiceTaskDelegate implements JavaDelegate {
25 26 public void execute(DelegateExecution execution) {
26 27  
27 28 String attachUserTaskId = BpmnModelUtils.parseExtensionElement(execution.getCurrentFlowElement(),
28   - BpmnModelConstants.SERVICE_TASK_ATTACH_USER_TASK_ID);
  29 + BpmnModelConstants.SERVICE_TASK_ATTACH_USER_TASK_ID); // TODO @jason:上面不需要加空行哈;
29 30 Assert.notNull(attachUserTaskId, "附属的用户任务 Id 不能为空");
30 31 // 获取会签任务是否被拒绝
31 32 Boolean userTaskRejected = execution.getVariable(String.format("%s_reject", attachUserTaskId), Boolean.class);
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/expression/CompleteByRejectCountExpression.java
... ... @@ -19,6 +19,7 @@ import static cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum.
19 19 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_APPROVE_METHOD;
20 20 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_APPROVE_RATIO;
21 21  
  22 +// TODO @jason:微信已经讨论,简化哈
22 23 /**
23 24 * 按拒绝人数计算会签的完成条件的流程表达式实现
24 25 *
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/el/VariableConvertByTypeExpressionFunction.java
... ... @@ -4,6 +4,7 @@ import org.flowable.common.engine.api.variable.VariableContainer;
4 4 import org.flowable.common.engine.impl.el.function.AbstractFlowableVariableExpressionFunction;
5 5 import org.springframework.stereotype.Component;
6 6  
  7 +// TODO @jason:这个自定义转换的原因是啥呀?
7 8 /**
8 9 * 根据流程变量 variable 的类型, 转换参数的值
9 10 *
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java
... ... @@ -40,6 +40,7 @@ public interface BpmnModelConstants {
40 40 */
41 41 String USER_TASK_TIMEOUT_HANDLER_ACTION = "timeoutAction";
42 42  
  43 + // TODO @jason:1)是不是上面的 timeoutAction 改成 timeoutHandler;2)rejectHandlerType 改成 rejectHandler 哇?
43 44 /**
44 45 * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝处理类型
45 46 */
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java
... ... @@ -73,6 +73,7 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
73 73 });
74 74 }
75 75  
  76 + // TODO @jason:这块如果不需要,可以删除掉~~~
76 77 // @Override
77 78 // protected void activityMessageReceived(FlowableMessageEvent event) {
78 79 // BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(event.getProcessDefinitionId());
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java
... ... @@ -29,6 +29,7 @@ import org.springframework.stereotype.Component;
29 29 import java.util.List;
30 30 import java.util.Set;
31 31  
  32 +// TODO @芋艿:这块需要仔细再瞅瞅
32 33 /**
33 34 * 监听定时器触发事件
34 35 *
... ... @@ -96,7 +97,6 @@ public class BpmTimerFiredEventListener extends AbstractFlowableEngineEventListe
96 97 BpmTaskApproveReqVO req = new BpmTaskApproveReqVO().setId(task.getId())
97 98 .setReason("超时系统自动同意");
98 99 bpmTaskService.approveTask(Long.parseLong(task.getAssignee()), req);
99   -
100 100 }
101 101 // 自动拒绝
102 102 if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.AUTO_REJECT) {
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/producer/task/TodoTaskReminderProducer.java
... ... @@ -7,6 +7,7 @@ import org.springframework.context.ApplicationContext;
7 7 import org.springframework.stereotype.Component;
8 8 import org.springframework.validation.annotation.Validated;
9 9  
  10 +// TODO @jason:建议直接调用 BpmMessageService 哈;更简化一点~
10 11 /**
11 12 * 待办任务提醒 Producer
12 13 *
... ... @@ -22,4 +23,5 @@ public class TodoTaskReminderProducer {
22 23 public void sendReminderMessage(@Valid TodoTaskReminderMessage message) {
23 24 applicationContext.publishEvent(message);
24 25 }
  26 +
25 27 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelConditionGroups.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simplemodel/SimpleModelConditionGroups.java
1   -package cn.iocoder.yudao.module.bpm.framework.flowable.core.simple;
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.simplemodel;
2 2  
3 3 import lombok.Data;
4 4  
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelUserTaskConfig.java renamed to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simplemodel/SimpleModelUserTaskConfig.java
1   -package cn.iocoder.yudao.module.bpm.framework.flowable.core.simple;
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.core.simplemodel;
2 2  
3 3 import cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum;
4 4 import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType;
... ... @@ -18,12 +18,11 @@ public class SimpleModelUserTaskConfig {
18 18 /**
19 19 * 候选人策略
20 20 */
21   - private Integer candidateStrategy;
22   -
  21 + private Integer candidateStrategy;
23 22 /**
24 23 * 候选人参数
25 24 */
26   - private String candidateParam;
  25 + private String candidateParam;
27 26  
28 27 /**
29 28 * 字段权限
... ... @@ -34,11 +33,11 @@ public class SimpleModelUserTaskConfig {
34 33 * 审批方式 {@link BpmApproveMethodEnum }
35 34 */
36 35 private Integer approveMethod;
37   -
38 36 /**
39 37 * 通过比例 当审批方式为 多人会签(按通过比例) 需设置
40 38 */
41 39 private Integer approveRatio;
  40 +
42 41 /**
43 42 * 超时处理
44 43 */
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnFormUtils.java
... ... @@ -15,7 +15,7 @@ import java.util.Map;
15 15  
16 16 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE;
17 17  
18   -
  18 +// TODO @芋艿:这块去研究下!
19 19 /**
20 20 * Bpmn 流程表单相关工具方法
21 21 *
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
... ... @@ -27,6 +27,7 @@ public class BpmnModelUtils {
27 27 // TODO @芋艿 尝试从 ExtensionElement 取. 后续相关扩展是否都可以 存 extensionElement。 如表单权限。 按钮权限
28 28 if (candidateStrategy == null) {
29 29 ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY));
  30 + // TODO @jason:这里可以改成 element != null 看着会简单点 element != null ? NumberUtils.parseInt(element.getElementText()) : null;
30 31 candidateStrategy = NumberUtils.parseInt(Optional.ofNullable(element).map(ExtensionElement::getElementText).orElse(null));
31 32 }
32 33 return candidateStrategy;
... ... @@ -37,6 +38,7 @@ public class BpmnModelUtils {
37 38 BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM);
38 39 if (candidateParam == null) {
39 40 ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_PARAM));
  41 + // TODO @jason:这里可以改成 element != null 看着会简单点 element != null ? element.getElementText() : null;
40 42 candidateParam = Optional.ofNullable(element).map(ExtensionElement::getElementText).orElse(null);
41 43 }
42 44 return candidateParam;
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
... ... @@ -12,9 +12,9 @@ import cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum;
12 12 import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModeConditionType;
13 13 import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType;
14 14 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
15   -import cn.iocoder.yudao.module.bpm.framework.flowable.core.simple.SimpleModelConditionGroups;
16   -import cn.iocoder.yudao.module.bpm.framework.flowable.core.simple.SimpleModelUserTaskConfig;
17   -import cn.iocoder.yudao.module.bpm.framework.flowable.core.simple.SimpleModelUserTaskConfig.RejectHandler;
  15 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.simplemodel.SimpleModelConditionGroups;
  16 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.simplemodel.SimpleModelUserTaskConfig;
  17 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.simplemodel.SimpleModelUserTaskConfig.RejectHandler;
18 18 import org.flowable.bpmn.BpmnAutoLayout;
19 19 import org.flowable.bpmn.model.Process;
20 20 import org.flowable.bpmn.model.*;
... ... @@ -253,33 +253,26 @@ public class SimpleModelUtils {
253 253 return sequenceFlow;
254 254 }
255 255  
256   - // TODO-DONE @jason:要不改成 recursionNode 递归节点,然后把 build 名字让出来,专门用于构建各种 Node
257   - // @芋艿 改成了 traverseNodeToBuildFlowNode, 连线的叫 traverseNodeToBuildSequenceFlow
258   - // TODO-DONE @jason:node 改成 node,process 改成 process;更符合递归的感觉哈,处理当前节点
  256 + // TODO @芋艿 改成了 traverseNodeToBuildFlowNode, 连线的叫 traverseNodeToBuildSequenceFlow
259 257 private static void traverseNodeToBuildFlowNode(BpmSimpleModelNodeVO node, Process process) {
260 258 // 判断是否有效节点
261   - // TODO-DONE @jason:是不是写个 isValidNode 方法:判断是否为有效节点;
262 259 if (!isValidNode(node)) {
263 260 return;
264 261 }
265 262 BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType());
266 263 Assert.notNull(nodeType, "模型节点类型不支持");
267 264  
268   - // TODO-DONE @jason:要不抽个 buildNode 方法,然后返回一个 List<FlowElement>,之后这个方法 addFlowElement;原因是,让当前这个方法,有主干逻辑;不然现在太长了;
269 265 List<FlowElement> flowElements = buildFlowNode(node, nodeType);
270 266 flowElements.forEach(process::addFlowElement);
271 267  
272 268 // 如果不是网关类型的接口, 并且chileNode为空退出
273   - // TODO-DONE @jason:建议这个判断去掉,可以更简洁一点;因为往下走;如果不成功,本身也就会结束哈;主要是,这里多了一个这样的判断,增加了理解成本;
274 269 // 如果是“分支”节点,则递归处理条件
275 270 if (BpmSimpleModelNodeType.isBranchNode(node.getType())
276 271 && ArrayUtil.isNotEmpty(node.getConditionNodes())) {
277   - // TODO-DONE @jason:可以搞成 stream 写成一行哈
278 272 node.getConditionNodes().forEach(item -> traverseNodeToBuildFlowNode(item.getChildNode(), process));
279 273 }
280 274  
281 275 // 如果有“子”节点,则递归处理子节点
282   - // TODO-DONE @jason:这个,是不是不写判断,直接继续调用;因为本身 buildAndAddBpmnFlowNode 就会最开始判断了哈,就不重复判断了;
283 276 traverseNodeToBuildFlowNode(node.getChildNode(), process);
284 277 }
285 278  
... ... @@ -291,16 +284,13 @@ public class SimpleModelUtils {
291 284 List<FlowElement> list = new ArrayList<>();
292 285 switch (nodeType) {
293 286 case START_NODE: {
294   - // TODO-DONE @jason:每个 nodeType,buildXXX 方法要不更明确,并且去掉 Bpmn;
295 287 // @芋艿 改成 convert 是不是好理解一点
296 288 StartEvent startEvent = convertStartNode(node);
297 289 list.add(startEvent);
298 290 break;
299 291 }
300 292 case APPROVE_NODE: {
301   - // TODO-DONE @jason:这个,搞成一个 buildUserTask,然后把下面这 2 种节点,搞在一起实现类;这样 buildNode 里面可以更简洁;
302   - // TODO-DONE @jason:这里还有个想法,是不是可以所有的都叫 buildXXXNode,然后里面有一些是 bpmn 相关的构建,叫做 buildBpmnUserTask,用于区分;
303   - // @芋艿 改成 convertXXXNode, , 方面里面使用 buildBpmnXXXNode. 是否更好理解
  293 + // TODO @芋艿 改成 convertXXXNode, , 方面里面使用 buildBpmnXXXNode. 是否更好理解
304 294 // 转换审批节点
305 295 List<FlowElement> flowElements = convertApproveNode(node);
306 296 list.addAll(flowElements);
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java
... ... @@ -51,31 +51,6 @@ public interface BpmModelService {
51 51 byte[] getModelBpmnXML(String id);
52 52  
53 53 /**
54   - * 保存流程模型的 BPMN XML
55   - *
56   - * @param id 编号
57   - * @param xmlBytes BPMN XML bytes
58   - */
59   - // TODO @芋艿:感觉可以不修改这个方法,而是额外加一个方法;传入 id,bpmn,json;
60   - void saveModelBpmnXml(String id, byte[] xmlBytes);
61   -
62   - /**
63   - * 获得仿钉钉快搭模型的 JSON 数据
64   - *
65   - * @param id 编号
66   - * @return JSON bytes
67   - */
68   - byte[] getModelSimpleJson(String id);
69   -
70   - /**
71   - * 保存仿钉钉快搭模型的 JSON 数据
72   - *
73   - * @param id 编号
74   - * @param jsonBytes JSON bytes
75   - */
76   - void saveModelSimpleJson(String id, byte[] jsonBytes);
77   -
78   - /**
79 54 * 修改流程模型
80 55 *
81 56 * @param updateReqVO 更新信息
... ... @@ -129,6 +104,6 @@ public interface BpmModelService {
129 104 */
130 105 void updateSimpleModel(@Valid BpmSimpleModelUpdateReqVO reqVO);
131 106  
132   - // TODO @jason:另外个问题,因为是存储到 modelExtra 里,那需要 deploy 存储出快照。和 bpmn xml 一样。目前我想到的,就是存储到 BpmProcessDefinitionInfoDO 加一个 simple_model 字段,text 类型。可以看看还有啥方案?
  107 + // TODO @jason:另外个问题,因为是存储到 modelExtra 里,那需要 deploy 存储出快照。和 bpmn xml 一样。目前我想到的,就是存储到 BpmProcessDefinitionInfoDO 加一个 simple_model 字段,text 类型。可以看看还有啥方案?【重要】
133 108  
134 109 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java
... ... @@ -107,7 +107,7 @@ public class BpmModelServiceImpl implements BpmModelService {
107 107 // 保存流程定义
108 108 repositoryService.saveModel(model);
109 109 // 保存 BPMN XML
110   - saveModelBpmnXml(model.getId(), StrUtil.utf8Bytes(bpmnXml));
  110 + saveModelBpmnXml(model.getId(), bpmnXml);
111 111 return model.getId();
112 112 }
113 113  
... ... @@ -125,7 +125,7 @@ public class BpmModelServiceImpl implements BpmModelService {
125 125 // 更新模型
126 126 repositoryService.saveModel(model);
127 127 // 更新 BPMN XML
128   - saveModelBpmnXml(model.getId(), StrUtil.utf8Bytes(updateReqVO.getBpmnXml()));
  128 + saveModelBpmnXml(model.getId(), updateReqVO.getBpmnXml());
129 129 }
130 130  
131 131 @Override
... ... @@ -218,23 +218,24 @@ public class BpmModelServiceImpl implements BpmModelService {
218 218 if (model == null) {
219 219 throw exception(MODEL_NOT_EXISTS);
220 220 }
221   - // 通过 ACT_RE_MODEL 表 EDITOR_SOURCE_EXTRA_VALUE_ID_ 获取 仿钉钉快搭模型的JSON 数据
  221 + // 通过 ACT_RE_MODEL 表 EDITOR_SOURCE_EXTRA_VALUE_ID_ ,获取仿钉钉快搭模型的 JSON 数据
222 222 byte[] jsonBytes = getModelSimpleJson(model.getId());
223 223 return JsonUtils.parseObject(jsonBytes, BpmSimpleModelNodeVO.class);
224 224 }
225 225  
226 226 @Override
227 227 public void updateSimpleModel(BpmSimpleModelUpdateReqVO reqVO) {
228   - // 1.1 校验流程模型存在
  228 + // 1. 校验流程模型存在
229 229 Model model = getModel(reqVO.getModelId());
230 230 if (model == null) {
231 231 throw exception(MODEL_NOT_EXISTS);
232 232 }
233   - // 1.2 JSON 转换成 bpmnModel
  233 +
  234 + // 2.1 JSON 转换成 bpmnModel
234 235 BpmnModel bpmnModel = SimpleModelUtils.buildBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModel());
235   - // 2.1 保存 Bpmn XML
236   - saveModelBpmnXml(model.getId(), StrUtil.utf8Bytes(BpmnModelUtils.getBpmnXml(bpmnModel)));
237   - // 2.2 保存 JSON 数据
  236 + // 2.2 保存 Bpmn XML
  237 + saveModelBpmnXml(model.getId(), BpmnModelUtils.getBpmnXml(bpmnModel));
  238 + // 2.3 保存 JSON 数据
238 239 saveModelSimpleJson(model.getId(), JsonUtils.toJsonByte(reqVO.getSimpleModel()));
239 240 }
240 241  
... ... @@ -266,21 +267,18 @@ public class BpmModelServiceImpl implements BpmModelService {
266 267 }
267 268 }
268 269  
269   - @Override
270   - public void saveModelBpmnXml(String id, byte[] xmlBytes) {
271   - if (ArrayUtil.isEmpty(xmlBytes)) {
  270 + private void saveModelBpmnXml(String id, String bpmnXml) {
  271 + if (StrUtil.isEmpty(bpmnXml)) {
272 272 return;
273 273 }
274   - repositoryService.addModelEditorSource(id, xmlBytes);
  274 + repositoryService.addModelEditorSource(id, StrUtil.utf8Bytes(bpmnXml));
275 275 }
276 276  
277   - @Override
278   - public byte[] getModelSimpleJson(String id) {
  277 + private byte[] getModelSimpleJson(String id) {
279 278 return repositoryService.getModelEditorSourceExtra(id);
280 279 }
281 280  
282   - @Override
283   - public void saveModelSimpleJson(String id, byte[] jsonBytes) {
  281 + private void saveModelSimpleJson(String id, byte[] jsonBytes) {
284 282 if (ArrayUtil.isEmpty(jsonBytes)) {
285 283 return;
286 284 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java
... ... @@ -13,6 +13,8 @@ import java.util.Collection;
13 13 */
14 14 public interface BpmProcessInstanceCopyService {
15 15  
  16 + // TODO @jason:要不把 createProcessInstanceCopy 搞 2 个方法,一个方法参数是之前的 userIds、taskId;一个方法是现在 userIds、processInstanceId、taskId、taskName;
  17 +
16 18 /**
17 19 * 流程实例的抄送
18 20 *
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java
... ... @@ -127,23 +127,23 @@ public interface BpmProcessInstanceService {
127 127 void updateProcessInstanceWhenCancel(FlowableCancelledEvent event);
128 128  
129 129 /**
130   - * 更新 ProcessInstance 拓展记录为完成
  130 + * 更新 ProcessInstance 为完成
131 131 *
132 132 * @param instance 流程任务
133 133 */
134 134 void updateProcessInstanceWhenApprove(ProcessInstance instance);
135 135  
136 136 /**
137   - * 更新 ProcessInstance 拓展记录为不通过
  137 + * 更新 ProcessInstance 为不通过
138 138 *
139 139 * @param id 流程编号
140   - * @param currentActivityId 当前的活动Id
  140 + * @param currentActivityId 当前的活动编号
141 141 * @param reason 理由。例如说,审批不通过时,需要传递该值
142 142 */
143 143 void updateProcessInstanceReject(String id, String currentActivityId, String reason);
144 144  
145 145 /**
146   - * 当流程结束时候。 更新 ProcessInstance
  146 + * 当流程结束时候,更新 ProcessInstance 为通过
147 147 *
148 148 * @param instance 流程任务
149 149 */
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
1   -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.EndEvent; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" /> * * HistoricProcessInstance & ProcessInstance 的关系: * 1. <a href=" https://my.oschina.net/843294669/blog/71902" /> * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List<ProcessInstance> getProcessInstances(Set<String> ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List<HistoricProcessInstance> processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map<String, Object> variables, String businessKey, Map<String, List<Long>> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map<String, List<Long>> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 管理员取消,不用校验是否为自己的 AdminUserRespDTO user = adminUserApi.getUser(userId); // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void updateProcessInstanceWhenCancel(FlowableCancelledEvent event) { // // 1. 判断是否为 Reject 不通过。如果是,则不进行更新. 这种情况不会发生了。 拒绝时候不会删除流程 // // 因为,updateProcessInstanceReject 方法(审批不通过),已经进行更新了 // if (BpmDeleteReasonEnum.isRejectReason((String) event.getCause())) { // return; // } // 1. 更新流程实例 status runtimeService.setVariable(event.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); // 2. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.CANCEL.getStatus())); } @Override public void updateProcessInstanceWhenApprove(ProcessInstance instance) { // 1. 更新流程实例 status runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.APPROVE.getStatus()); // 2. 发送流程被【通过】的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); // 3. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.APPROVE.getStatus())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceReject(String id, String currentActivityId, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); // 2. 跳转到流程结束 EndEvent 节点, 结束流程 ProcessInstance processInstance = getProcessInstance(id); BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()); EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel); Assert.notNull(endEvent, "结束节点不能为空"); runtimeService.createChangeActivityStateBuilder() .processInstanceId(id) .moveActivityIdTo(currentActivityId, // 当前节点 endEvent.getId()) // 结束节点 .changeState(); // 3. 发送流程被【不通过】的消息 messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(processInstance, reason)); // 4. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.REJECT.getStatus())); } @Override public void updateProcessInstanceWhenCompleted(ProcessInstance instance) { Integer status = (Integer) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); // 当流程状态还是审批状态中, 更新为审批通过 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { updateProcessInstanceWhenApprove(instance); } // 审批不通过状态。已经在 updateProcessInstanceReject 处理 } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } }
2 1 \ No newline at end of file
  2 +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.EndEvent; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" /> * * HistoricProcessInstance & ProcessInstance 的关系: * 1. <a href=" https://my.oschina.net/843294669/blog/71902" /> * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List<ProcessInstance> getProcessInstances(Set<String> ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List<HistoricProcessInstance> processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map<String, Object> variables, String businessKey, Map<String, List<Long>> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map<String, List<Long>> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 管理员取消,不用校验是否为自己的 AdminUserRespDTO user = adminUserApi.getUser(userId); // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void updateProcessInstanceWhenCancel(FlowableCancelledEvent event) { // // 1. 判断是否为 Reject 不通过。如果是,则不进行更新. 这种情况不会发生了。 拒绝时候不会删除流程 // // 因为,updateProcessInstanceReject 方法(审批不通过),已经进行更新了 // if (BpmDeleteReasonEnum.isRejectReason((String) event.getCause())) { // return; // } // 1. 更新流程实例 status runtimeService.setVariable(event.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); // 2. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.CANCEL.getStatus())); } @Override public void updateProcessInstanceWhenApprove(ProcessInstance instance) { // 1. 更新流程实例 status runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.APPROVE.getStatus()); // 2. 发送流程被【通过】的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); // 3. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.APPROVE.getStatus())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceReject(String id, String currentActivityId, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); // 2. 跳转到流程结束 EndEvent 节点,结束流程 // TODO @jason:需要测试下,如果这么走。当前其它审批任务,会不会结束,以及它们的状态是什么?!【重要】 ProcessInstance processInstance = getProcessInstance(id); BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()); EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel); Assert.notNull(endEvent, "结束节点不能为空"); runtimeService.createChangeActivityStateBuilder() .processInstanceId(id) .moveActivityIdTo(currentActivityId, endEvent.getId()) // 当前节点 => 结束节点 .changeState(); // 3. 发送流程被【不通过】的消息 messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(processInstance, reason)); // 4. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.REJECT.getStatus())); } @Override public void updateProcessInstanceWhenCompleted(ProcessInstance instance) { Integer status = (Integer) instance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); // 当流程状态还是审批状态中, 更新为审批通过 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { updateProcessInstanceWhenApprove(instance); } // 审批不通过状态。已经在 updateProcessInstanceReject 处理 } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } }
3 3 \ No newline at end of file
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java
... ... @@ -129,6 +129,7 @@ public interface BpmTaskService {
129 129 */
130 130 Task getTask(String id);
131 131  
  132 + // TODO @jason:jason:这个貌似可以去掉了。
132 133 /**
133 134 * 根据条件查询已经分配的用户任务列表
134 135 * @param processInstanceId 流程实例编号,不允许为空
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
... ... @@ -335,14 +335,18 @@ public class BpmTaskServiceImpl implements BpmTaskService {
335 335 // 2.2 添加评论
336 336 taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(),
337 337 BpmCommentTypeEnum.REJECT.formatComment(reqVO.getReason()));
  338 +
338 339 // 3.1 解析用户任务的拒绝处理类型
339 340 BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId());
  341 + // TODO @jason:342 到 344 最好抽象一个方法出来哈。放在 BpmnModelUtils,参照类似 parseCandidateStrategy
340 342 UserTask flowElement = (UserTask) BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
341 343 Integer rejectHandlerType = NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_REJECT_HANDLER_TYPE));
342 344 BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType);
343   - // 3.2 类型为驳回到指定的任务节点
  345 + // 3.2 类型为驳回到指定的任务节点 TODO @jason:下面这种判断,最好是 JSON
344 346 if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_PRE_USER_TASK) {
  347 + // TODO @jason:348 最好抽象一个方法出来哈。放在 BpmnModelUtils,参照类似 parseCandidateStrategy
345 348 String returnTaskId = BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_REJECT_RETURN_TASK_ID);
  349 + // TODO @jason:这里如果找不到,直接抛出系统异常;因为说白了,已经不是业务异常啦。
346 350 if (returnTaskId == null) {
347 351 throw exception(TASK_RETURN_NOT_ASSIGN_TARGET_TASK_ID);
348 352 }
... ... @@ -351,6 +355,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
351 355 returnTask(userId, returnReq);
352 356 return;
353 357 } else if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.FINISH_PROCESS_BY_REJECT_NUMBER) {
  358 + // TODO @jason:微信沟通,去掉类似的逻辑;
354 359 // 3.3 按拒绝人数终止流程
355 360 if (!flowElement.hasMultiInstanceLoopCharacteristics()) {
356 361 log.error("[rejectTask] 按拒绝人数终止流程类型,只能用于会签任务. 当前任务【{}】不是会签任务", task.getId());
... ... @@ -362,7 +367,8 @@ public class BpmTaskServiceImpl implements BpmTaskService {
362 367 return;
363 368 }
364 369 // 3.4 其他情况 终止流程。
365   - processInstanceService.updateProcessInstanceReject(instance.getProcessInstanceId(), task.getTaskDefinitionKey(), reqVO.getReason());
  370 + processInstanceService.updateProcessInstanceReject(instance.getProcessInstanceId(),
  371 + task.getTaskDefinitionKey(), reqVO.getReason());
366 372 }
367 373  
368 374 /**
... ...