From 16a7f9309f423b37d6e1208a374380226212aae9 Mon Sep 17 00:00:00 2001
From: PIGCLOUD
Date: Tue, 9 Dec 2025 15:43:41 +0800
Subject: [PATCH] =?UTF-8?q?feat(flow):=20=E6=96=B0=E5=A2=9E=E9=A9=B3?=
=?UTF-8?q?=E5=9B=9E=E5=88=B0=E5=8F=91=E8=B5=B7=E4=BA=BA=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 在ModelUtil重构开始节点结构,添加root UserTask节点
- 新增RootTaskCreateListener处理驳回到发起人场景
- 新增RootTaskHandler解析发起人执行人
- 在ApproveServiceTask添加TO_START驳回处理逻辑
- 新增ProcessInstanceRecordVo包含驳回状态信息
- 在TaskService添加resubmitTask接口处理重新提交
- 在ProcessTaskController添加resubmitTask接口
---
.../constant/ProcessInstanceConstant.java | 10 +++
.../pigx/flow/vo/ProcessInstanceRecordVo.java | 38 ++++++++++
.../controller/ProcessTaskController.java | 19 +++++
.../pigx/flow/service/ITaskService.java | 14 ++++
.../impl/ProcessInstanceServiceImpl.java | 35 ++++++++-
.../flow/service/impl/TaskServiceImpl.java | 74 +++++++++++++++++++
.../listeners/RootTaskCreateListener.java | 66 +++++++++++++++++
.../flow/support/node/RootTaskHandler.java | 67 +++++++++++++++++
.../support/tasks/ApproveServiceTask.java | 5 ++
.../pigx/flow/support/utils/ModelUtil.java | 57 ++++++++++++--
10 files changed, 377 insertions(+), 8 deletions(-)
create mode 100644 pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/vo/ProcessInstanceRecordVo.java
create mode 100644 pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/support/listeners/RootTaskCreateListener.java
create mode 100644 pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/support/node/RootTaskHandler.java
diff --git a/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/constant/ProcessInstanceConstant.java b/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/constant/ProcessInstanceConstant.java
index c64c3f40c..2ab6f372c 100644
--- a/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/constant/ProcessInstanceConstant.java
+++ b/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/constant/ProcessInstanceConstant.java
@@ -50,6 +50,16 @@ public class ProcessInstanceConstant {
*/
public static final String USER_TASK_REFUSE_TYPE_TO_NODE = "TO_NODE";
+ /**
+ * 拒绝处理策略:驳回到发起人 审批人拒绝后,流程跳转回发起人节点,发起人可重新编辑提交
+ */
+ public static final String USER_TASK_REFUSE_TYPE_TO_START = "TO_START";
+
+ /**
+ * 流程变量:驳回到发起人标识 用于标记流程实例是否处于驳回待重新提交状态
+ */
+ public static final String REJECT_TO_STARTER_VAR = "rejectToStarter";
+
/**
* 多人审批模式:会签 所有审批人都必须同意才能通过,任何一人拒绝则不通过
*/
diff --git a/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/vo/ProcessInstanceRecordVo.java b/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/vo/ProcessInstanceRecordVo.java
new file mode 100644
index 000000000..43b39975a
--- /dev/null
+++ b/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/vo/ProcessInstanceRecordVo.java
@@ -0,0 +1,38 @@
+package com.pig4cloud.pigx.flow.vo;
+
+import com.pig4cloud.pigx.flow.entity.ProcessInstanceRecord;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 流程实例记录视图对象
+ *
+ * 继承自ProcessInstanceRecord实体类,用于扩展查询返回的额外信息。
+ * 包含流程实例的驳回状态等运行时信息。
+ *
+ *
+ * @author pigx
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class ProcessInstanceRecordVo extends ProcessInstanceRecord {
+
+ /**
+ * 是否驳回到发起人状态
+ *
+ * true表示流程已被驳回到发起人,等待发起人重新编辑提交;
+ * false或null表示流程正常运行中
+ *
+ */
+ private Boolean rejectToStarter;
+
+ /**
+ * 重新提交的任务ID
+ *
+ * 当rejectToStarter为true时,此字段保存发起人节点(root)的任务ID,
+ * 用于前端调用重新提交接口
+ *
+ */
+ private String resubmitTaskId;
+
+}
diff --git a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/controller/ProcessTaskController.java b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/controller/ProcessTaskController.java
index 62d5be7df..94f0116d7 100644
--- a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/controller/ProcessTaskController.java
+++ b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/controller/ProcessTaskController.java
@@ -159,4 +159,23 @@ public class ProcessTaskController {
return taskService.back(taskParamDto);
}
+ /**
+ * 重新提交任务(驳回到发起人后)
+ *
+ * 当流程被驳回到发起人后,发起人可使用此接口重新编辑表单并提交。
+ * 与completeTask不同:
+ * 1. 不需要填写审批意见
+ * 2. 会自动清理驳回标识
+ * 3. 提交后流程继续到下一个审批节点
+ *
+ * @param taskParamDto 重新提交参数,包含:
+ * - taskId: 发起人节点的任务ID
+ * - formData: 更新后的表单数据
+ * @return R 统一响应对象,操作成功返回提交结果
+ */
+ @PostMapping("resubmitTask")
+ public R resubmitTask(@RequestBody TaskParamDto taskParamDto) {
+ return taskService.resubmitTask(taskParamDto);
+ }
+
}
diff --git a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/ITaskService.java b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/ITaskService.java
index bff9632bd..b15de35a5 100644
--- a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/ITaskService.java
+++ b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/ITaskService.java
@@ -106,4 +106,18 @@ public interface ITaskService {
*/
R queryTaskData();
+ /**
+ * 重新提交任务(驳回到发起人后)
+ *
+ * 当流程被驳回到发起人时,发起人使用此方法重新编辑表单并提交。
+ * 与普通completeTask不同:
+ * 1. 不需要填写审批意见
+ * 2. 会清理 rejectToStarter 流程变量
+ * 3. 完成后流程继续到下一个审批节点
+ *
+ * @param taskParamDto 任务参数,包含taskId和更新后的formData
+ * @return 操作结果
+ */
+ R resubmitTask(TaskParamDto taskParamDto);
+
}
diff --git a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/ProcessInstanceServiceImpl.java b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/ProcessInstanceServiceImpl.java
index 00fdd60e8..e3b09edd1 100644
--- a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/ProcessInstanceServiceImpl.java
+++ b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/ProcessInstanceServiceImpl.java
@@ -29,6 +29,7 @@ import com.pig4cloud.pigx.flow.support.utils.NodeUtil;
import com.pig4cloud.pigx.flow.vo.NodeFormatParamVo;
import com.pig4cloud.pigx.flow.vo.NodeVo;
import com.pig4cloud.pigx.flow.vo.ProcessCopyVo;
+import com.pig4cloud.pigx.flow.vo.ProcessInstanceRecordVo;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@@ -364,7 +365,39 @@ public class ProcessInstanceServiceImpl implements IProcessInstanceService {
Page instanceRecordPage = processInstanceRecordService.lambdaQuery().eq(ProcessInstanceRecord::getUserId, userId).eq(ProcessInstanceRecord::getStatus, taskQueryParamDto.getStatus()).between(ArrayUtil.isNotEmpty(taskQueryParamDto.getTaskTime()), ProcessInstanceRecord::getCreateTime, ArrayUtil.isNotEmpty(taskQueryParamDto.getTaskTime()) ? taskQueryParamDto.getTaskTime()[0] : null, ArrayUtil.isNotEmpty(taskQueryParamDto.getTaskTime()) ? taskQueryParamDto.getTaskTime()[1] : null).orderByDesc(ProcessInstanceRecord::getCreateTime).page(new Page<>(taskQueryParamDto.getCurrent(), taskQueryParamDto.getSize()));
- return R.ok(instanceRecordPage);
+ // 转换为 VO 并添加驳回状态信息
+ List voList = BeanUtil.copyToList(instanceRecordPage.getRecords(), ProcessInstanceRecordVo.class);
+
+ for (ProcessInstanceRecordVo vo : voList) {
+ try {
+ // 从 Flowable 变量中获取 rejectToStarter 状态
+ Object rejectVar = runtimeService.getVariable(
+ vo.getProcessInstanceId(),
+ "rejectToStarter"
+ );
+ vo.setRejectToStarter(Boolean.TRUE.equals(rejectVar));
+
+ // 如果是驳回状态,获取 root 任务 ID 用于重新提交
+ if (Boolean.TRUE.equals(vo.getRejectToStarter())) {
+ Task task = taskService.createTaskQuery()
+ .processInstanceId(vo.getProcessInstanceId())
+ .taskDefinitionKey("root")
+ .singleResult();
+ if (task != null) {
+ vo.setResubmitTaskId(task.getId());
+ }
+ }
+ } catch (Exception e) {
+ // 流程可能已结束,设置为 false
+ vo.setRejectToStarter(false);
+ }
+ }
+
+ // 构建返回的分页对象
+ Page resultPage = new Page<>(instanceRecordPage.getCurrent(), instanceRecordPage.getSize(), instanceRecordPage.getTotal());
+ resultPage.setRecords(voList);
+
+ return R.ok(resultPage);
}
/**
diff --git a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/TaskServiceImpl.java b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/TaskServiceImpl.java
index de2f5b541..daf3163fd 100644
--- a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/TaskServiceImpl.java
+++ b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/TaskServiceImpl.java
@@ -10,6 +10,7 @@ import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder;
import com.pig4cloud.pigx.common.security.util.SecurityUtils;
import com.pig4cloud.pigx.flow.constant.NodeStatusEnum;
+import com.pig4cloud.pigx.flow.constant.ProcessInstanceConstant;
import com.pig4cloud.pigx.flow.dto.IndexPageStatistics;
import com.pig4cloud.pigx.flow.dto.Node;
import com.pig4cloud.pigx.flow.dto.TaskParamDto;
@@ -483,6 +484,79 @@ public class TaskServiceImpl implements ITaskService {
return flowableEngineService.back(taskParamDto);
}
+ /**
+ * 重新提交任务(驳回到发起人后)
+ *
+ * 当流程被驳回到发起人时,发起人使用此方法重新编辑表单并提交。
+ * 该方法会:
+ * 1. 验证任务存在且属于当前用户
+ * 2. 验证任务是 root 节点任务
+ * 3. 更新表单数据
+ * 4. 清理 rejectToStarter 流程变量
+ * 5. 完成任务,流程继续到下一个审批节点
+ *
+ *
+ * @param taskParamDto 任务参数,包含taskId和更新后的formData
+ * @return R 操作结果
+ */
+ @Override
+ @SneakyThrows
+ @Transactional(rollbackFor = Exception.class)
+ public R resubmitTask(TaskParamDto taskParamDto) {
+ long userId = SecurityUtils.getUser().getId();
+
+ // 1. 获取任务并验证
+ Task task = taskService.createTaskQuery()
+ .taskId(taskParamDto.getTaskId())
+ .taskTenantId(TenantContextHolder.getTenantId().toString())
+ .taskAssignee(String.valueOf(userId))
+ .singleResult();
+
+ if (task == null) {
+ return R.failed("任务不存在或无权限操作");
+ }
+
+ // 2. 验证是 root 任务(发起人节点)
+ if (!StrUtil.equals(task.getTaskDefinitionKey(), "root")) {
+ return R.failed("非重新提交任务,无法操作");
+ }
+
+ String processInstanceId = task.getProcessInstanceId();
+
+ // 3. 更新表单数据
+ if (CollUtil.isNotEmpty(taskParamDto.getFormData())) {
+ runtimeService.setVariables(task.getExecutionId(), taskParamDto.getFormData());
+
+ // 同步更新到 ProcessInstanceRecord
+ ProcessInstanceRecord processInstanceRecord = processInstanceRecordService.lambdaQuery()
+ .eq(ProcessInstanceRecord::getProcessInstanceId, processInstanceId)
+ .one();
+
+ if (processInstanceRecord != null) {
+ Map existingFormData = new HashMap<>();
+ if (StrUtil.isNotBlank(processInstanceRecord.getFormData())) {
+ existingFormData = objectMapper.readValue(processInstanceRecord.getFormData(),
+ new TypeReference
+ *
+ * 判断逻辑:
+ * 通过检查流程变量 rejectToStarter 是否为 true 来判断是否是驳回场景。
+ * 该变量在 ApproveServiceTask 处理驳回到发起人时设置。
+ *
+ *
+ * @author pigx
+ * @see com.pig4cloud.pigx.flow.support.tasks.ApproveServiceTask
+ */
+@Slf4j
+public class RootTaskCreateListener implements TaskListener {
+
+ /**
+ * 任务创建事件处理方法
+ *
+ * 当root任务被创建时触发。根据是否是驳回场景决定是否自动完成任务。
+ *
+ *
+ * @param delegateTask 委派的任务对象
+ */
+ @Override
+ public void notify(DelegateTask delegateTask) {
+ RuntimeService runtimeService = SpringUtil.getBean(RuntimeService.class);
+ TaskService taskService = SpringUtil.getBean(TaskService.class);
+
+ String processInstanceId = delegateTask.getProcessInstanceId();
+ String taskId = delegateTask.getId();
+ String assignee = delegateTask.getAssignee();
+
+ log.debug("发起人任务创建 - 任务ID: {}, 执行人: {}, 流程实例ID: {}", taskId, assignee, processInstanceId);
+
+ // 检查是否是驳回场景
+ Object rejectToStarter = runtimeService.getVariable(processInstanceId, "rejectToStarter");
+
+ if (!Boolean.TRUE.equals(rejectToStarter)) {
+ // 非驳回场景:自动完成 root 任务,让流程继续到下一个节点
+ log.debug("非驳回场景,自动完成发起人任务 - 任务ID: {}", taskId);
+ taskService.complete(taskId);
+ }
+ else {
+ // 驳回场景:保留任务给发起人处理
+ // 发起人可以在待办任务列表中看到这个任务,并进行编辑重新提交
+ log.info("驳回场景,保留任务给发起人 - 任务ID: {}, 执行人: {}", taskId, assignee);
+ }
+ }
+
+}
diff --git a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/support/node/RootTaskHandler.java b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/support/node/RootTaskHandler.java
new file mode 100644
index 000000000..c2da4e999
--- /dev/null
+++ b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/support/node/RootTaskHandler.java
@@ -0,0 +1,67 @@
+package com.pig4cloud.pigx.flow.support.node;
+
+import cn.hutool.extra.spring.SpringUtil;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.pig4cloud.pigx.flow.constant.ProcessInstanceConstant;
+import com.pig4cloud.pigx.flow.dto.NodeUser;
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.engine.delegate.DelegateExecution;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * 发起人任务处理器
+ *
+ * 用于处理驳回到发起人场景下,发起人任务的执行人解析。
+ * 该处理器作为Spring Bean注入,在BPMN模型中通过EL表达式调用。
+ *
+ *
+ * 使用方式:
+ * 在UserTask的assignee属性中配置:${rootTaskHandler.resolveAssignee(execution)}
+ *
+ *
+ * @author pigx
+ */
+@Component("rootTaskHandler")
+@Slf4j
+public class RootTaskHandler {
+
+ /**
+ * 解析发起人作为任务执行人
+ *
+ * 从流程变量中获取发起人信息(root变量),返回发起人的用户ID作为任务执行人。
+ * 该方法在任务创建时由Flowable引擎通过EL表达式调用。
+ *
+ *
+ * @param execution 流程执行上下文,包含流程变量等信息
+ * @return 发起人用户ID字符串,如果未找到则返回默认空执行人标识
+ */
+ public String resolveAssignee(DelegateExecution execution) {
+ // 从流程变量中获取发起人信息
+ Object rootUserObj = execution.getVariable("root");
+ if (rootUserObj == null) {
+ log.warn("未找到发起人信息,流程实例ID: {}", execution.getProcessInstanceId());
+ return ProcessInstanceConstant.DEFAULT_EMPTY_ASSIGN.toString();
+ }
+
+ ObjectMapper objectMapper = SpringUtil.getBean(ObjectMapper.class);
+ try {
+ // 发起人信息存储为List格式
+ List nodeUsers = objectMapper.readValue(objectMapper.writeValueAsString(rootUserObj),
+ new TypeReference>() {
+ });
+ if (!nodeUsers.isEmpty()) {
+ Long userId = nodeUsers.get(0).getId();
+ log.debug("解析发起人成功,用户ID: {}", userId);
+ return userId.toString();
+ }
+ }
+ catch (Exception e) {
+ log.error("解析发起人信息失败,流程实例ID: {}", execution.getProcessInstanceId(), e);
+ }
+ return ProcessInstanceConstant.DEFAULT_EMPTY_ASSIGN.toString();
+ }
+
+}
diff --git a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/support/tasks/ApproveServiceTask.java b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/support/tasks/ApproveServiceTask.java
index f1d2e4ad1..7a0a22db1 100644
--- a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/support/tasks/ApproveServiceTask.java
+++ b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/support/tasks/ApproveServiceTask.java
@@ -105,6 +105,11 @@ public class ApproveServiceTask implements JavaDelegate {
// 根据拒绝设置重定向到特定节点
stateChangeBuilder.moveActivityIdTo(currentActivityId, refuseConfig.getNodeId()).changeState();
}
+ else if (StrUtil.equals(rejectionHandler, "TO_START")) {
+ // 驳回到发起人:设置驳回标识变量,跳转到 root 节点
+ runtimeService.setVariable(processInstanceId, "rejectToStarter", true);
+ stateChangeBuilder.moveActivityIdTo(currentActivityId, "root").changeState();
+ }
else {
// 默认行为:重定向到结束节点
stateChangeBuilder.moveActivityIdTo(currentActivityId, "end").changeState();
diff --git a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/support/utils/ModelUtil.java b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/support/utils/ModelUtil.java
index a126071a2..d9fdd3d89 100644
--- a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/support/utils/ModelUtil.java
+++ b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/support/utils/ModelUtil.java
@@ -13,6 +13,7 @@ import com.pig4cloud.pigx.flow.service.IProcessNodeDataService;
import com.pig4cloud.pigx.flow.support.expression.condition.NodeExpressionStrategyFactory;
import com.pig4cloud.pigx.flow.support.listeners.ApprovalCreateListener;
import com.pig4cloud.pigx.flow.support.listeners.FlowProcessEventListener;
+import com.pig4cloud.pigx.flow.support.listeners.RootTaskCreateListener;
import com.pig4cloud.pigx.flow.support.tasks.ApproveServiceTask;
import com.pig4cloud.pigx.flow.support.tasks.CopyServiceTask;
import lombok.extern.slf4j.Slf4j;
@@ -352,22 +353,53 @@ public class ModelUtil {
/**
* 构建开始节点
*
- * 创建BPMN流程的开始事件节点。
- * 每个流程必须有且只有一个开始节点。
+ * 创建BPMN流程的开始事件节点和发起人任务节点。
+ * 流程结构:StartEvent(start) → UserTask(root) → 第一个审批节点
+ *
+ *
+ * 发起人任务节点的作用:
+ *
+ * - 正常发起流程时,通过RootTaskCreateListener自动完成,流程继续到下一个节点
+ * - 驳回到发起人时,任务保留给发起人,发起人可以在待办任务中编辑并重新提交
+ *
+ *
*
* @param node 前端传输的开始节点数据
- * @return 包含开始事件的流程元素列表
+ * @return 包含开始事件和发起人任务的流程元素列表
*/
private static List buildStartNode(Node node) {
List flowElementList = new ArrayList<>();
+ // 1. 创建真正的开始事件(ID 为 start)
StartEvent startEvent = new StartEvent();
- startEvent.setId(node.getId());
- startEvent.setName(node.getName());
-
+ startEvent.setId("start");
+ startEvent.setName("开始");
flowElementList.add(startEvent);
+ // 2. 创建发起人任务节点(ID 保持为 root)
+ // 配置任务监听器,用于处理驳回场景
+ FlowableListener createListener = new FlowableListener();
+ createListener.setImplementation(RootTaskCreateListener.class.getCanonicalName());
+ createListener.setImplementationType("class");
+ createListener.setEvent("create");
+
+ UserTask rootTask = new UserTask();
+ rootTask.setId(node.getId()); // 保持 "root" 作为 ID
+ rootTask.setName(node.getName());
+ // 动态获取发起人作为执行人
+ rootTask.setAssignee("${rootTaskHandler.resolveAssignee(execution)}");
+
+ List taskListeners = new ArrayList<>();
+ taskListeners.add(createListener);
+ rootTask.setTaskListeners(taskListeners);
+
+ flowElementList.add(rootTask);
+
+ // 3. 更新节点的 head/tail 信息
+ node.setHeadId("start"); // 头部是 StartEvent
+ node.setTailId(node.getId()); // 尾部是 root UserTask
+
return flowElementList;
}
@@ -655,7 +687,11 @@ public class ModelUtil {
/**
* 创建节点内部连接线
*
- * 创建节点内部的连接线,例如审批节点与其后续服务任务之间的连接。
+ * 创建节点内部的连接线,例如:
+ *
+ * - 开始节点:start → root(StartEvent到发起人UserTask)
+ * - 审批节点:审批任务 → 服务任务
+ *
*
* @param node 节点数据
* @param flowId 流程定义ID
@@ -673,6 +709,13 @@ public class ModelUtil {
return sequenceFlowList;
}
+ // 开始节点:创建 start → root 的连线
+ if (node.getType() == NodeTypeEnum.ROOT.getValue().intValue()) {
+ SequenceFlow sequenceFlow = buildSingleSequenceFlow("start", nodeId, null, "start->root");
+ sequenceFlowList.add(sequenceFlow);
+ }
+
+ // 审批节点:创建审批任务到服务任务的连线
if (node.getType() == NodeTypeEnum.APPROVAL.getValue().intValue()) {
String gatewayId = StrUtil.format("approve_service_task_{}", nodeId);