From c8f755cbde9e7773a312ecf48c29264b508e1d6c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=96=AF=E7=8B=82=E7=9A=84=E7=8B=AE=E5=AD=90Li?=
<15040126243@163.com>
Date: Thu, 2 Jul 2026 14:39:10 +0800
Subject: [PATCH] =?UTF-8?q?add:=20=E6=96=B0=E5=A2=9E=E5=B7=A5=E4=BD=9C?=
=?UTF-8?q?=E6=B5=81=20LiteFlow=20=E8=A7=84=E5=88=99=E9=93=BE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/main/resources/application.yml | 9 ++
ruoyi-modules/ruoyi-workflow/pom.xml | 6 ++
.../domain/context/CompleteTaskContext.java | 44 ++++++++
.../domain/context/InstanceDeleteContext.java | 59 +++++++++++
.../domain/context/StartProcessContext.java | 70 ++++++++++++
.../domain/context/TaskOperationContext.java | 55 ++++++++++
.../complete/CompleteAutoPassComponent.java | 95 +++++++++++++++++
.../complete/CompleteExecuteComponent.java | 54 ++++++++++
.../CompleteNeedAutoPassComponent.java | 23 ++++
.../complete/CompletePrepareComponent.java | 100 ++++++++++++++++++
.../InstanceDeleteEventComponent.java | 60 +++++++++++
.../InstanceDeleteExecuteComponent.java | 50 +++++++++
.../InstanceDeleteExistsComponent.java | 24 +++++
.../instance/InstanceDeleteLoadComponent.java | 52 +++++++++
.../operation/TaskOpExecuteComponent.java | 49 +++++++++
.../operation/TaskOpLoadComponent.java | 53 ++++++++++
.../operation/TaskOpNeedNotifyComponent.java | 24 +++++
.../operation/TaskOpNotifyComponent.java | 57 ++++++++++
.../operation/TaskOpPrepareComponent.java | 57 ++++++++++
.../liteflow/start/StartExecuteComponent.java | 67 ++++++++++++
.../liteflow/start/StartExistsComponent.java | 24 +++++
.../start/StartPrepareInstanceComponent.java | 55 ++++++++++
.../start/StartPrepareRequestComponent.java | 53 ++++++++++
.../liteflow/start/StartResumeComponent.java | 56 ++++++++++
.../resources/liteflow/instance-chain.el.xml | 22 ++++
.../main/resources/liteflow/task-chain.el.xml | 61 +++++++++++
26 files changed, 1279 insertions(+)
create mode 100644 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/context/CompleteTaskContext.java
create mode 100644 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/context/InstanceDeleteContext.java
create mode 100644 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/context/StartProcessContext.java
create mode 100644 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/context/TaskOperationContext.java
create mode 100644 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/complete/CompleteAutoPassComponent.java
create mode 100644 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/complete/CompleteExecuteComponent.java
create mode 100644 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/complete/CompleteNeedAutoPassComponent.java
create mode 100644 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/complete/CompletePrepareComponent.java
create mode 100644 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/instance/InstanceDeleteEventComponent.java
create mode 100644 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/instance/InstanceDeleteExecuteComponent.java
create mode 100644 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/instance/InstanceDeleteExistsComponent.java
create mode 100644 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/instance/InstanceDeleteLoadComponent.java
create mode 100644 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/operation/TaskOpExecuteComponent.java
create mode 100644 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/operation/TaskOpLoadComponent.java
create mode 100644 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/operation/TaskOpNeedNotifyComponent.java
create mode 100644 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/operation/TaskOpNotifyComponent.java
create mode 100644 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/operation/TaskOpPrepareComponent.java
create mode 100644 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/start/StartExecuteComponent.java
create mode 100644 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/start/StartExistsComponent.java
create mode 100644 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/start/StartPrepareInstanceComponent.java
create mode 100644 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/start/StartPrepareRequestComponent.java
create mode 100644 ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/start/StartResumeComponent.java
create mode 100644 ruoyi-modules/ruoyi-workflow/src/main/resources/liteflow/instance-chain.el.xml
create mode 100644 ruoyi-modules/ruoyi-workflow/src/main/resources/liteflow/task-chain.el.xml
diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml
index 8d4c2bfb2..32b8a7429 100644
--- a/ruoyi-admin/src/main/resources/application.yml
+++ b/ruoyi-admin/src/main/resources/application.yml
@@ -245,6 +245,15 @@ warm-flow:
# 默认Authorization,如果有多个token,用逗号分隔
token-name: ${sa-token.token-name},clientid
+--- # LiteFlow 业务规则编排
+liteflow:
+ # 跟随工作流模块开关,避免禁用工作流时仍加载任务办理链
+ enable: ${warm-flow.enabled:true}
+ # 工作流模块用于编排任务办理链路
+ rule-source: classpath:liteflow/*.el.xml
+ # 开启虚拟线程
+ enable-virtual-thread: true
+
--- # mqtt 配置
# 具体配置还需查看文档
# https://mica-mqtt.dreamlu.net/guide/spring/client.html
diff --git a/ruoyi-modules/ruoyi-workflow/pom.xml b/ruoyi-modules/ruoyi-workflow/pom.xml
index 347cb897b..066eecadf 100644
--- a/ruoyi-modules/ruoyi-workflow/pom.xml
+++ b/ruoyi-modules/ruoyi-workflow/pom.xml
@@ -96,6 +96,12 @@
warm-flow-plugin-ui-sb-web
+
+
+ org.dromara
+ ruoyi-common-liteflow
+
+
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/context/CompleteTaskContext.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/context/CompleteTaskContext.java
new file mode 100644
index 000000000..c6f56a003
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/context/CompleteTaskContext.java
@@ -0,0 +1,44 @@
+package org.dromara.workflow.domain.context;
+
+import lombok.Data;
+import org.dromara.warm.flow.core.dto.FlowParams;
+import org.dromara.warm.flow.core.entity.Instance;
+import org.dromara.warm.flow.orm.entity.FlowTask;
+import org.dromara.workflow.domain.bo.CompleteTaskBo;
+
+/**
+ * 办理任务 LiteFlow 上下文。
+ *
+ * 链路内各节点通过该对象传递任务、实例、办理参数等中间结果,避免组件之间重复查询。
+ *
+ * @author may
+ */
+@Data
+public class CompleteTaskContext {
+
+ /**
+ * 办理任务入参。
+ */
+ private final CompleteTaskBo completeTaskBo;
+
+ /**
+ * 当前待办理的运行任务,由校验节点加载。
+ */
+ private FlowTask flowTask;
+
+ /**
+ * 当前任务所属流程实例,由校验节点加载。
+ */
+ private Instance instance;
+
+ /**
+ * Warm-Flow 办理参数,由参数构建节点生成并供执行、自动审批节点复用。
+ */
+ private FlowParams flowParams;
+
+ /**
+ * 流程实例是否开启自动审批,由参数构建节点从实例变量中解析。
+ */
+ private Boolean autoPass;
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/context/InstanceDeleteContext.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/context/InstanceDeleteContext.java
new file mode 100644
index 000000000..86a7a530c
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/context/InstanceDeleteContext.java
@@ -0,0 +1,59 @@
+package org.dromara.workflow.domain.context;
+
+import lombok.Data;
+import org.dromara.warm.flow.orm.entity.FlowInstance;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 删除流程实例 LiteFlow 上下文。
+ *
+ * @author may
+ */
+@Data
+public class InstanceDeleteContext {
+
+ /**
+ * 按业务 id 删除时的业务 id 集合。
+ */
+ private final List businessIds;
+
+ /**
+ * 按实例 id 删除时的实例 id 集合。
+ */
+ private final Collection instanceIds;
+
+ /**
+ * 是否删除历史实例及其历史任务数据。
+ */
+ private final boolean history;
+
+ /**
+ * 待删除流程实例。
+ */
+ private List flowInstances;
+
+ /**
+ * 实际执行删除的实例 id。
+ */
+ private List deleteInstanceIds;
+
+ /**
+ * 删除结果。
+ */
+ private boolean result;
+
+ public static InstanceDeleteContext byBusinessIds(List businessIds) {
+ return new InstanceDeleteContext(businessIds, null, false);
+ }
+
+ public static InstanceDeleteContext byInstanceIds(Collection instanceIds) {
+ return new InstanceDeleteContext(null, instanceIds, false);
+ }
+
+ public static InstanceDeleteContext byHistoryInstanceIds(Collection instanceIds) {
+ return new InstanceDeleteContext(null, instanceIds, true);
+ }
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/context/StartProcessContext.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/context/StartProcessContext.java
new file mode 100644
index 000000000..9a8ae610e
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/context/StartProcessContext.java
@@ -0,0 +1,70 @@
+package org.dromara.workflow.domain.context;
+
+import lombok.Data;
+import org.dromara.warm.flow.core.entity.Definition;
+import org.dromara.warm.flow.core.entity.Instance;
+import org.dromara.warm.flow.core.entity.Task;
+import org.dromara.warm.flow.orm.entity.FlowInstance;
+import org.dromara.workflow.api.domain.StartProcessReturnDTO;
+import org.dromara.workflow.domain.FlowInstanceBizExt;
+import org.dromara.workflow.domain.bo.StartProcessBo;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 启动流程 LiteFlow 上下文。
+ *
+ * 链路内保存启动参数、流程定义、流程实例和首个任务等中间结果。
+ *
+ * @author may
+ */
+@Data
+public class StartProcessContext {
+
+ /**
+ * 启动流程入参。
+ */
+ private final StartProcessBo startProcessBo;
+
+ /**
+ * 业务唯一标识。
+ */
+ private String businessId;
+
+ /**
+ * 流程变量。
+ */
+ private Map variables;
+
+ /**
+ * 流程实例业务扩展信息。
+ */
+ private FlowInstanceBizExt bizExt;
+
+ /**
+ * 已存在的流程实例,非空时走续提交逻辑。
+ */
+ private FlowInstance existingInstance;
+
+ /**
+ * 已发布的流程定义。
+ */
+ private Definition definition;
+
+ /**
+ * 新启动的流程实例。
+ */
+ private Instance instance;
+
+ /**
+ * 当前流程实例下生成的任务列表。
+ */
+ private List taskList;
+
+ /**
+ * 启动流程返回结果。
+ */
+ private StartProcessReturnDTO startProcessReturn;
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/context/TaskOperationContext.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/context/TaskOperationContext.java
new file mode 100644
index 000000000..73fa2de4c
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/context/TaskOperationContext.java
@@ -0,0 +1,55 @@
+package org.dromara.workflow.domain.context;
+
+import lombok.Data;
+import org.dromara.warm.flow.core.dto.FlowParams;
+import org.dromara.warm.flow.core.entity.Task;
+import org.dromara.warm.flow.orm.entity.FlowNode;
+import org.dromara.workflow.common.enums.TaskOperationEnum;
+import org.dromara.workflow.domain.bo.TaskOperationBo;
+
+/**
+ * 任务操作 LiteFlow 上下文。
+ *
+ * 用于在委派、转办、加签、减签链路中传递操作类型、任务、节点和办理参数。
+ *
+ * @author may
+ */
+@Data
+public class TaskOperationContext {
+
+ /**
+ * 任务操作入参。
+ */
+ private final TaskOperationBo taskOperationBo;
+
+ /**
+ * 操作类型编码。
+ */
+ private final String taskOperation;
+
+ /**
+ * 解析后的操作枚举。
+ */
+ private TaskOperationEnum operation;
+
+ /**
+ * Warm-Flow 操作参数。
+ */
+ private FlowParams flowParams;
+
+ /**
+ * 当前任务。
+ */
+ private Task task;
+
+ /**
+ * 当前任务对应的流程节点。
+ */
+ private FlowNode flowNode;
+
+ /**
+ * 操作执行结果。
+ */
+ private boolean result;
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/complete/CompleteAutoPassComponent.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/complete/CompleteAutoPassComponent.java
new file mode 100644
index 000000000..cd7e6521a
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/complete/CompleteAutoPassComponent.java
@@ -0,0 +1,95 @@
+package org.dromara.workflow.liteflow.complete;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeComponent;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.query.QueryBuilder;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.warm.flow.core.FlowEngine;
+import org.dromara.warm.flow.core.dto.FlowParams;
+import org.dromara.warm.flow.core.entity.User;
+import org.dromara.warm.flow.core.service.TaskService;
+import org.dromara.warm.flow.orm.entity.FlowTask;
+import org.dromara.warm.flow.orm.mapper.FlowTaskMapper;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.domain.context.CompleteTaskContext;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static org.dromara.workflow.common.constant.FlowConstant.*;
+
+/**
+ * 执行当前处理人的后续自动审批。
+ *
+ * 仅当流程实例开启自动审批时生效,用于连续跳过同一登录人负责的后续待办任务。
+ *
+ * @author may
+ */
+@ConditionalOnEnable
+@RequiredArgsConstructor
+@LiteflowComponent("completeAutoPass")
+public class CompleteAutoPassComponent extends NodeComponent {
+
+ private final TaskService taskService;
+ private final FlowTaskMapper flowTaskMapper;
+
+ @Override
+ public void process() {
+ CompleteTaskContext context = getRequestData();
+ FlowTask flowTask = context.getFlowTask();
+ autoPass(flowTask.getId(), context.getFlowParams(), flowTask.getInstanceId());
+ }
+
+ /**
+ * 递归跳过当前处理人的后续任务。
+ *
+ * @param taskId 本轮已处理任务 id,用于避免重复跳过同一任务
+ * @param flowParams 当前办理参数,会在自动审批时覆盖为自动审批消息和空抄送变量
+ * @param instanceId 流程实例 id
+ */
+ private void autoPass(Long taskId, FlowParams flowParams, Long instanceId) {
+ List flowTaskList = selectByInstId(instanceId);
+ if (CollUtil.isEmpty(flowTaskList)) {
+ return;
+ }
+ List userList = FlowEngine.userService()
+ .getByAssociateds(StreamUtils.toList(flowTaskList, FlowTask::getId));
+ if (CollUtil.isEmpty(userList)) {
+ return;
+ }
+ for (FlowTask task : flowTaskList) {
+ if (task.getId().equals(taskId)) {
+ continue;
+ }
+ // 自动审批只处理当前登录人仍是办理人的后续任务,避免替其他候选人或并行分支误审批。
+ List users = StreamUtils.filter(userList,
+ e -> ObjectUtil.equals(task.getId(), e.getAssociated()) && ObjectUtil.equal(e.getProcessedBy(), LoginHelper.getUserIdStr()));
+ if (CollUtil.isEmpty(users)) {
+ continue;
+ }
+ // 自动审批不继承人工办理时的抄送和消息通知,避免重复发送业务消息。
+ flowParams
+ .message("流程引擎自动审批!")
+ .variable(Map.of(
+ SUBMIT, false,
+ FLOW_COPY_LIST, Collections.emptyList(),
+ MESSAGE_NOTICE, StringUtils.EMPTY));
+ taskService.skip(task.getId(), flowParams);
+ // 跳过后可能生成新的后续任务,继续扫描直到当前登录人不再有可自动办理任务。
+ autoPass(task.getId(), flowParams, instanceId);
+ }
+ }
+
+ private List selectByInstId(Long instanceId) {
+ return flowTaskMapper.selectList(QueryBuilder.lambda(FlowTask.class)
+ .eq(FlowTask::getInstanceId, instanceId)
+ .build());
+ }
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/complete/CompleteExecuteComponent.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/complete/CompleteExecuteComponent.java
new file mode 100644
index 000000000..6b94bc3ec
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/complete/CompleteExecuteComponent.java
@@ -0,0 +1,54 @@
+package org.dromara.workflow.liteflow.complete;
+
+import cn.hutool.core.convert.Convert;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeComponent;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.enums.BusinessStatusEnum;
+import org.dromara.warm.flow.core.dto.FlowParams;
+import org.dromara.warm.flow.core.enums.SkipType;
+import org.dromara.warm.flow.core.service.TaskService;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.common.enums.TaskStatusEnum;
+import org.dromara.workflow.domain.bo.CompleteTaskBo;
+import org.dromara.workflow.domain.context.CompleteTaskContext;
+
+import java.util.Map;
+
+import static org.dromara.workflow.common.constant.FlowConstant.*;
+
+/**
+ * 构建办理参数并执行当前任务跳转。
+ *
+ * @author may
+ */
+@ConditionalOnEnable
+@RequiredArgsConstructor
+@LiteflowComponent("completeExecute")
+public class CompleteExecuteComponent extends NodeComponent {
+
+ private final TaskService taskService;
+
+ @Override
+ public void process() {
+ CompleteTaskContext context = getRequestData();
+ CompleteTaskBo completeTaskBo = context.getCompleteTaskBo();
+ Map variables = completeTaskBo.getVariables();
+ FlowParams flowParams = FlowParams.build()
+ .handler(completeTaskBo.getHandler())
+ .variable(variables)
+ .ignore(Convert.toBool(variables.getOrDefault(VAR_IGNORE, false)))
+ .ignoreDepute(Convert.toBool(variables.getOrDefault(VAR_IGNORE_DEPUTE, false)))
+ .ignoreCooperate(Convert.toBool(variables.getOrDefault(VAR_IGNORE_COOPERATE, false)))
+ .skipType(SkipType.PASS.getKey())
+ .message(completeTaskBo.getMessage())
+ .flowStatus(BusinessStatusEnum.WAITING.getStatus())
+ .hisStatus(TaskStatusEnum.PASS.getStatus())
+ .hisTaskExt(completeTaskBo.getFileId());
+
+ context.setFlowParams(flowParams);
+ context.setAutoPass(Convert.toBool(context.getInstance().getVariableMap().getOrDefault(AUTO_PASS, false)));
+ taskService.skip(context.getFlowTask().getId(), flowParams);
+ }
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/complete/CompleteNeedAutoPassComponent.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/complete/CompleteNeedAutoPassComponent.java
new file mode 100644
index 000000000..ba4d5ca28
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/complete/CompleteNeedAutoPassComponent.java
@@ -0,0 +1,23 @@
+package org.dromara.workflow.liteflow.complete;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeBooleanComponent;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.domain.context.CompleteTaskContext;
+
+/**
+ * 判断办理任务后是否进入自动审批分支。
+ *
+ * @author may
+ */
+@ConditionalOnEnable
+@LiteflowComponent("completeNeedAutoPass")
+public class CompleteNeedAutoPassComponent extends NodeBooleanComponent {
+
+ @Override
+ public boolean processBoolean() {
+ CompleteTaskContext context = getRequestData();
+ return Boolean.TRUE.equals(context.getAutoPass());
+ }
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/complete/CompletePrepareComponent.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/complete/CompletePrepareComponent.java
new file mode 100644
index 000000000..ec6c95045
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/complete/CompletePrepareComponent.java
@@ -0,0 +1,100 @@
+package org.dromara.workflow.liteflow.complete;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeComponent;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.enums.BusinessStatusEnum;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.warm.flow.core.entity.Instance;
+import org.dromara.warm.flow.core.service.InsService;
+import org.dromara.warm.flow.orm.entity.FlowTask;
+import org.dromara.warm.flow.orm.mapper.FlowTaskMapper;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.common.enums.TaskStatusEnum;
+import org.dromara.workflow.domain.bo.CompleteTaskBo;
+import org.dromara.workflow.domain.context.CompleteTaskContext;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.dromara.workflow.common.constant.FlowConstant.*;
+
+/**
+ * 准备任务办理上下文。
+ *
+ * 负责加载任务实例、补齐流程变量,并解析弹窗选择的后续办理人。
+ *
+ * @author may
+ */
+@ConditionalOnEnable
+@RequiredArgsConstructor
+@LiteflowComponent("completePrepare")
+public class CompletePrepareComponent extends NodeComponent {
+
+ private final FlowTaskMapper flowTaskMapper;
+ private final InsService insService;
+
+ @Override
+ public void process() {
+ CompleteTaskContext context = getRequestData();
+ CompleteTaskBo completeTaskBo = context.getCompleteTaskBo();
+ FlowTask flowTask = flowTaskMapper.selectById(completeTaskBo.getTaskId());
+ if (ObjectUtil.isNull(flowTask)) {
+ throw new ServiceException("流程任务不存在或任务已审批!");
+ }
+ Instance instance = insService.getById(flowTask.getInstanceId());
+ if (ObjectUtil.isNull(instance)) {
+ throw new ServiceException("流程实例不存在");
+ }
+ context.setFlowTask(flowTask);
+ context.setInstance(instance);
+
+ Map variables = completeTaskBo.getVariables();
+ variables.put(FLOW_COPY_LIST, completeTaskBo.getFlowCopyList());
+ variables.put(MESSAGE_TYPE, completeTaskBo.getMessageType());
+ variables.put(MESSAGE_NOTICE, completeTaskBo.getNotice());
+ if (BusinessStatusEnum.isDraftOrCancelOrBack(instance.getFlowStatus())) {
+ // 草稿、撤销、驳回状态再次办理时等同于重新提交,条件表达式通过 SUBMIT 识别提交分支。
+ variables.put(SUBMIT, true);
+ }
+ Map assigneeMap = setPopAssigneeMap(completeTaskBo.getAssigneeMap(), instance.getVariableMap());
+ if (CollUtil.isNotEmpty(assigneeMap)) {
+ variables.putAll(assigneeMap);
+ }
+ }
+
+ /**
+ * 将弹窗处理人映射成通过/退回状态下的办理人变量。
+ */
+ private Map setPopAssigneeMap(Map assigneeMap, Map variablesMap) {
+ Map map = new HashMap<>();
+ if (CollUtil.isEmpty(assigneeMap)) {
+ return map;
+ }
+ for (Map.Entry entry : assigneeMap.entrySet()) {
+ if (variablesMap.containsKey(entry.getKey())) {
+ String userIds = variablesMap.get(entry.getKey()).toString();
+ if (StringUtils.isNotBlank(userIds)) {
+ Set hashSet = CollUtil.newHashSet();
+ List popUserIds = StringUtils.str2List(entry.getValue().toString(), StringUtils.SEPARATOR, true, true);
+ List variableUserIds = StringUtils.str2List(userIds, StringUtils.SEPARATOR, true, true);
+ hashSet.addAll(popUserIds);
+ hashSet.addAll(variableUserIds);
+ // Warm-Flow 会按“状态:变量名”读取办理人,正向和退回都写入同一批候选人。
+ map.put(TaskStatusEnum.PASS.getStatus() + StringUtils.COLON + entry.getKey(), StringUtils.joinComma(hashSet));
+ map.put(TaskStatusEnum.BACK.getStatus() + StringUtils.COLON + entry.getKey(), StringUtils.joinComma(hashSet));
+ }
+ } else {
+ map.put(TaskStatusEnum.PASS.getStatus() + StringUtils.COLON + entry.getKey(), entry.getValue());
+ map.put(TaskStatusEnum.BACK.getStatus() + StringUtils.COLON + entry.getKey(), entry.getValue());
+ }
+ }
+ return map;
+ }
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/instance/InstanceDeleteEventComponent.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/instance/InstanceDeleteEventComponent.java
new file mode 100644
index 000000000..5aeb1442c
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/instance/InstanceDeleteEventComponent.java
@@ -0,0 +1,60 @@
+package org.dromara.workflow.liteflow.instance;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeComponent;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.warm.flow.core.entity.Definition;
+import org.dromara.warm.flow.core.entity.Instance;
+import org.dromara.warm.flow.core.service.DefService;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.domain.context.InstanceDeleteContext;
+import org.dromara.workflow.handler.FlowProcessEventHandler;
+
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * 校验实例删除权限并发布业务删除事件。
+ *
+ * @author may
+ */
+@ConditionalOnEnable
+@RequiredArgsConstructor
+@Slf4j
+@LiteflowComponent("instanceDeleteEvent")
+public class InstanceDeleteEventComponent extends NodeComponent {
+
+ private final DefService defService;
+ private final FlowProcessEventHandler flowProcessEventHandler;
+
+ @Override
+ public void process() {
+ InstanceDeleteContext context = getRequestData();
+ String userId = LoginHelper.getUserIdStr();
+ context.getFlowInstances().forEach(flowInstance -> {
+ if (!LoginHelper.isSuperAdmin() && !flowInstance.getCreateBy().equals(userId)) {
+ throw new ServiceException("权限不足,无法删除流程实例信息!");
+ }
+ });
+
+ Map definitionMap = StreamUtils.toMap(
+ defService.getByIds(StreamUtils.toList(context.getFlowInstances(), Instance::getDefinitionId)),
+ Definition::getId,
+ Function.identity()
+ );
+ context.getFlowInstances().forEach(instance -> {
+ Definition definition = definitionMap.get(instance.getDefinitionId());
+ if (ObjectUtil.isNull(definition)) {
+ log.warn("实例 ID: {} 对应的流程定义信息未找到,跳过删除事件触发。", instance.getId());
+ return;
+ }
+ flowProcessEventHandler.processDeleteHandler(definition.getFlowCode(), instance.getBusinessId());
+ });
+ }
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/instance/InstanceDeleteExecuteComponent.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/instance/InstanceDeleteExecuteComponent.java
new file mode 100644
index 000000000..54bea97dd
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/instance/InstanceDeleteExecuteComponent.java
@@ -0,0 +1,50 @@
+package org.dromara.workflow.liteflow.instance;
+
+import cn.hutool.core.collection.CollUtil;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeComponent;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.mybatis.core.query.QueryBuilder;
+import org.dromara.warm.flow.core.FlowEngine;
+import org.dromara.warm.flow.core.service.InsService;
+import org.dromara.warm.flow.orm.entity.FlowTask;
+import org.dromara.warm.flow.orm.mapper.FlowTaskMapper;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.domain.context.InstanceDeleteContext;
+
+import java.util.List;
+
+/**
+ * 执行流程实例删除。
+ *
+ * @author may
+ */
+@ConditionalOnEnable
+@RequiredArgsConstructor
+@LiteflowComponent("instanceDeleteExecute")
+public class InstanceDeleteExecuteComponent extends NodeComponent {
+
+ private final InsService insService;
+ private final FlowTaskMapper flowTaskMapper;
+
+ @Override
+ public void process() {
+ InstanceDeleteContext context = getRequestData();
+ if (!context.isHistory()) {
+ context.setResult(insService.remove(context.getDeleteInstanceIds()));
+ return;
+ }
+ List flowTaskList = flowTaskMapper.selectList(QueryBuilder.lambda(FlowTask.class)
+ .in(FlowTask::getInstanceId, context.getDeleteInstanceIds())
+ .build());
+ if (CollUtil.isNotEmpty(flowTaskList)) {
+ FlowEngine.userService().deleteByTaskIds(StreamUtils.toList(flowTaskList, FlowTask::getId));
+ }
+ FlowEngine.taskService().deleteByInsIds(context.getDeleteInstanceIds());
+ FlowEngine.hisTaskService().deleteByInsIds(context.getDeleteInstanceIds());
+ FlowEngine.insService().removeByIds(context.getDeleteInstanceIds());
+ context.setResult(true);
+ }
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/instance/InstanceDeleteExistsComponent.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/instance/InstanceDeleteExistsComponent.java
new file mode 100644
index 000000000..f9ac98e3e
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/instance/InstanceDeleteExistsComponent.java
@@ -0,0 +1,24 @@
+package org.dromara.workflow.liteflow.instance;
+
+import cn.hutool.core.collection.CollUtil;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeBooleanComponent;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.domain.context.InstanceDeleteContext;
+
+/**
+ * 判断删除流程实例时是否存在可执行删除的数据。
+ *
+ * @author may
+ */
+@ConditionalOnEnable
+@LiteflowComponent("instanceDeleteExists")
+public class InstanceDeleteExistsComponent extends NodeBooleanComponent {
+
+ @Override
+ public boolean processBoolean() {
+ InstanceDeleteContext context = getRequestData();
+ return CollUtil.isNotEmpty(context.getFlowInstances());
+ }
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/instance/InstanceDeleteLoadComponent.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/instance/InstanceDeleteLoadComponent.java
new file mode 100644
index 000000000..84ab78e24
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/instance/InstanceDeleteLoadComponent.java
@@ -0,0 +1,52 @@
+package org.dromara.workflow.liteflow.instance;
+
+import cn.hutool.core.collection.CollUtil;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeComponent;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.mybatis.core.query.QueryBuilder;
+import org.dromara.warm.flow.orm.entity.FlowInstance;
+import org.dromara.warm.flow.orm.mapper.FlowInstanceMapper;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.domain.context.InstanceDeleteContext;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 加载待删除流程实例。
+ *
+ * @author may
+ */
+@ConditionalOnEnable
+@RequiredArgsConstructor
+@Slf4j
+@LiteflowComponent("instanceDeleteLoad")
+public class InstanceDeleteLoadComponent extends NodeComponent {
+
+ private final FlowInstanceMapper flowInstanceMapper;
+
+ @Override
+ public void process() {
+ InstanceDeleteContext context = getRequestData();
+ List flowInstances;
+ if (CollUtil.isNotEmpty(context.getBusinessIds())) {
+ flowInstances = flowInstanceMapper.selectList(QueryBuilder.lambda(FlowInstance.class)
+ .in(FlowInstance::getBusinessId, context.getBusinessIds())
+ .build());
+ context.setDeleteInstanceIds(StreamUtils.toList(flowInstances, FlowInstance::getId));
+ } else {
+ flowInstances = flowInstanceMapper.selectByIds(context.getInstanceIds());
+ context.setDeleteInstanceIds(new ArrayList<>(context.getInstanceIds()));
+ }
+ if (CollUtil.isEmpty(flowInstances)) {
+ log.warn("未找到对应的流程实例信息,无法执行删除操作。");
+ context.setResult(false);
+ return;
+ }
+ context.setFlowInstances(flowInstances);
+ }
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/operation/TaskOpExecuteComponent.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/operation/TaskOpExecuteComponent.java
new file mode 100644
index 000000000..cb3bbb6c2
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/operation/TaskOpExecuteComponent.java
@@ -0,0 +1,49 @@
+package org.dromara.workflow.liteflow.operation;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeComponent;
+import lombok.RequiredArgsConstructor;
+import org.dromara.warm.flow.core.service.TaskService;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.common.enums.TaskStatusEnum;
+import org.dromara.workflow.domain.context.TaskOperationContext;
+
+/**
+ * 执行委派、转办、加签或减签。
+ *
+ * @author may
+ */
+@ConditionalOnEnable
+@RequiredArgsConstructor
+@LiteflowComponent("taskOpExecute")
+public class TaskOpExecuteComponent extends NodeComponent {
+
+ private final TaskService taskService;
+
+ @Override
+ public void process() {
+ TaskOperationContext context = getRequestData();
+ Long taskId = context.getTaskOperationBo().getTaskId();
+ boolean result = false;
+ switch (context.getOperation()) {
+ case DELEGATE_TASK -> {
+ context.getFlowParams().hisStatus(TaskStatusEnum.DEPUTE.getStatus());
+ result = taskService.depute(taskId, context.getFlowParams());
+ }
+ case TRANSFER_TASK -> {
+ context.getFlowParams().hisStatus(TaskStatusEnum.TRANSFER.getStatus());
+ result = taskService.transfer(taskId, context.getFlowParams());
+ }
+ case ADD_SIGNATURE -> {
+ context.getFlowParams().hisStatus(TaskStatusEnum.SIGN.getStatus());
+ result = taskService.addSignature(taskId, context.getFlowParams());
+ }
+ case REDUCTION_SIGNATURE -> {
+ context.getFlowParams().hisStatus(TaskStatusEnum.SIGN_OFF.getStatus());
+ result = taskService.reductionSignature(taskId, context.getFlowParams());
+ }
+ }
+ context.setResult(result);
+ }
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/operation/TaskOpLoadComponent.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/operation/TaskOpLoadComponent.java
new file mode 100644
index 000000000..ab5e72b8a
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/operation/TaskOpLoadComponent.java
@@ -0,0 +1,53 @@
+package org.dromara.workflow.liteflow.operation;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeComponent;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.mybatis.core.query.QueryBuilder;
+import org.dromara.warm.flow.core.entity.Task;
+import org.dromara.warm.flow.core.enums.CooperateType;
+import org.dromara.warm.flow.core.service.TaskService;
+import org.dromara.warm.flow.orm.entity.FlowNode;
+import org.dromara.warm.flow.orm.mapper.FlowNodeMapper;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.common.enums.TaskOperationEnum;
+import org.dromara.workflow.domain.context.TaskOperationContext;
+
+/**
+ * 加载任务节点并校验操作约束。
+ *
+ * @author may
+ */
+@ConditionalOnEnable
+@RequiredArgsConstructor
+@LiteflowComponent("taskOpLoad")
+public class TaskOpLoadComponent extends NodeComponent {
+
+ private final TaskService taskService;
+ private final FlowNodeMapper flowNodeMapper;
+
+ @Override
+ public void process() {
+ TaskOperationContext context = getRequestData();
+ Task task = taskService.getById(context.getTaskOperationBo().getTaskId());
+ if (ObjectUtil.isNull(task)) {
+ throw new ServiceException("任务不存在!");
+ }
+ FlowNode flowNode = flowNodeMapper.selectOne(QueryBuilder.lambda(FlowNode.class)
+ .eq(FlowNode::getNodeCode, task.getNodeCode())
+ .eq(FlowNode::getDefinitionId, task.getDefinitionId())
+ .build());
+ if (ObjectUtil.isNull(flowNode)) {
+ throw new ServiceException("流程节点不存在");
+ }
+ if ((context.getOperation() == TaskOperationEnum.ADD_SIGNATURE || context.getOperation() == TaskOperationEnum.REDUCTION_SIGNATURE)
+ && CooperateType.isOrSign(flowNode.getNodeRatio())) {
+ throw new ServiceException(task.getNodeName() + "不是会签或票签节点!");
+ }
+ context.setTask(task);
+ context.setFlowNode(flowNode);
+ }
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/operation/TaskOpNeedNotifyComponent.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/operation/TaskOpNeedNotifyComponent.java
new file mode 100644
index 000000000..f273aef56
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/operation/TaskOpNeedNotifyComponent.java
@@ -0,0 +1,24 @@
+package org.dromara.workflow.liteflow.operation;
+
+import cn.hutool.core.collection.CollUtil;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeBooleanComponent;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.domain.context.TaskOperationContext;
+
+/**
+ * 判断任务操作后是否进入消息通知分支。
+ *
+ * @author may
+ */
+@ConditionalOnEnable
+@LiteflowComponent("taskOpNeedNotify")
+public class TaskOpNeedNotifyComponent extends NodeBooleanComponent {
+
+ @Override
+ public boolean processBoolean() {
+ TaskOperationContext context = getRequestData();
+ return context.isResult() && CollUtil.isNotEmpty(context.getTaskOperationBo().getMessageType());
+ }
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/operation/TaskOpNotifyComponent.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/operation/TaskOpNotifyComponent.java
new file mode 100644
index 000000000..543f7c2ca
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/operation/TaskOpNotifyComponent.java
@@ -0,0 +1,57 @@
+package org.dromara.workflow.liteflow.operation;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeComponent;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.system.api.UserService;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.domain.bo.TaskOperationBo;
+import org.dromara.workflow.domain.context.TaskOperationContext;
+import org.dromara.workflow.service.IFlwCommonService;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.dromara.workflow.common.constant.FlowConstant.PATH_TASK_WAITING;
+
+/**
+ * 任务操作成功后发送待办消息。
+ *
+ * @author may
+ */
+@ConditionalOnEnable
+@RequiredArgsConstructor
+@LiteflowComponent("taskOpNotify")
+public class TaskOpNotifyComponent extends NodeComponent {
+
+ private final IFlwCommonService flwCommonService;
+ private final UserService userService;
+
+ @Override
+ public void process() {
+ TaskOperationContext context = getRequestData();
+ TaskOperationBo bo = context.getTaskOperationBo();
+ List userIdList = new ArrayList<>();
+ if (StringUtils.isNotBlank(bo.getUserId())) {
+ userIdList.add(Convert.toLong(bo.getUserId()));
+ }
+ if (CollUtil.isNotEmpty(bo.getUserIds())) {
+ userIdList.addAll(bo.getUserIds().stream().map(Convert::toLong).toList());
+ }
+ if (CollUtil.isEmpty(userIdList)) {
+ return;
+ }
+ // 转办、委托、加减签等运行时操作,消息接收人统一从“我的待办”进入处理。
+ flwCommonService.sendMessage(
+ bo.getMessageType(),
+ StringUtils.isNotBlank(bo.getMessage()) ? bo.getMessage() : "单据「" + context.getOperation().getDesc() + "」通知",
+ "单据「" + context.getOperation().getDesc() + "」提醒",
+ userService.selectListByIds(userIdList),
+ PATH_TASK_WAITING
+ );
+ }
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/operation/TaskOpPrepareComponent.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/operation/TaskOpPrepareComponent.java
new file mode 100644
index 000000000..d8c2863ae
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/operation/TaskOpPrepareComponent.java
@@ -0,0 +1,57 @@
+package org.dromara.workflow.liteflow.operation;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeComponent;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.ValidatorUtils;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.warm.flow.core.dto.FlowParams;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.common.enums.TaskOperationEnum;
+import org.dromara.workflow.domain.bo.TaskOperationBo;
+import org.dromara.workflow.domain.context.TaskOperationContext;
+
+import java.util.Collections;
+
+/**
+ * 准备任务操作参数。
+ *
+ * @author may
+ */
+@ConditionalOnEnable
+@Slf4j
+@LiteflowComponent("taskOpPrepare")
+public class TaskOpPrepareComponent extends NodeComponent {
+
+ @Override
+ public void process() {
+ TaskOperationContext context = getRequestData();
+ TaskOperationEnum op = TaskOperationEnum.getByCode(context.getTaskOperation());
+ if (op == null) {
+ log.error("Invalid operation type:{} ", context.getTaskOperation());
+ throw new ServiceException("Invalid operation type " + context.getTaskOperation());
+ }
+ context.setOperation(op);
+
+ TaskOperationBo bo = context.getTaskOperationBo();
+ switch (op) {
+ case DELEGATE_TASK, TRANSFER_TASK -> ValidatorUtils.validate(bo, AddGroup.class);
+ case ADD_SIGNATURE, REDUCTION_SIGNATURE -> ValidatorUtils.validate(bo, EditGroup.class);
+ }
+
+ FlowParams flowParams = FlowParams.build().message(bo.getMessage());
+ if (LoginHelper.isSuperAdmin()) {
+ flowParams.ignore(true);
+ }
+ switch (op) {
+ case DELEGATE_TASK, TRANSFER_TASK -> flowParams.addHandlers(Collections.singletonList(bo.getUserId()));
+ case ADD_SIGNATURE -> flowParams.addHandlers(bo.getUserIds());
+ case REDUCTION_SIGNATURE -> flowParams.reductionHandlers(bo.getUserIds());
+ }
+ context.setFlowParams(flowParams);
+ }
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/start/StartExecuteComponent.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/start/StartExecuteComponent.java
new file mode 100644
index 000000000..a456c8d05
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/start/StartExecuteComponent.java
@@ -0,0 +1,67 @@
+package org.dromara.workflow.liteflow.start;
+
+import cn.hutool.core.collection.CollUtil;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeComponent;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.enums.BusinessStatusEnum;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.warm.flow.core.dto.FlowParams;
+import org.dromara.warm.flow.core.entity.Instance;
+import org.dromara.warm.flow.core.entity.Task;
+import org.dromara.warm.flow.core.service.InsService;
+import org.dromara.warm.flow.core.service.TaskService;
+import org.dromara.warm.flow.orm.entity.FlowTask;
+import org.dromara.workflow.api.domain.StartProcessReturnDTO;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.domain.FlowInstanceBizExt;
+import org.dromara.workflow.domain.context.StartProcessContext;
+import org.dromara.workflow.mapper.FlwInstanceBizExtMapper;
+
+import java.util.List;
+
+/**
+ * 启动流程实例并构建返回结果。
+ *
+ * @author may
+ */
+@ConditionalOnEnable
+@RequiredArgsConstructor
+@LiteflowComponent("startExecute")
+public class StartExecuteComponent extends NodeComponent {
+
+ private final InsService insService;
+ private final TaskService taskService;
+ private final FlwInstanceBizExtMapper flwInstanceBizExtMapper;
+
+ @Override
+ public void process() {
+ StartProcessContext context = getRequestData();
+ FlowParams flowParams = FlowParams.build()
+ .handler(context.getStartProcessBo().getHandler())
+ .flowCode(context.getStartProcessBo().getFlowCode())
+ .variable(context.getVariables())
+ .flowStatus(BusinessStatusEnum.DRAFT.getStatus());
+ Instance instance = insService.start(context.getBusinessId(), flowParams);
+ context.setInstance(instance);
+ saveBizExt(context, instance);
+
+ List taskList = taskService.list(new FlowTask().setInstanceId(instance.getId()));
+ if (CollUtil.isEmpty(taskList)) {
+ throw new ServiceException("流程启动失败,未生成任务");
+ }
+ if (taskList.size() > 1) {
+ throw new ServiceException("请检查流程第一个环节是否为申请人!");
+ }
+ context.setTaskList(taskList);
+ context.setStartProcessReturn(new StartProcessReturnDTO(instance.getId(), taskList.getFirst().getId()));
+ }
+
+ private void saveBizExt(StartProcessContext context, Instance instance) {
+ FlowInstanceBizExt bizExt = context.getBizExt();
+ bizExt.setInstanceId(instance.getId());
+ bizExt.setBusinessId(instance.getBusinessId());
+ flwInstanceBizExtMapper.saveOrUpdateByInstanceId(bizExt);
+ }
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/start/StartExistsComponent.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/start/StartExistsComponent.java
new file mode 100644
index 000000000..e55ed23cd
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/start/StartExistsComponent.java
@@ -0,0 +1,24 @@
+package org.dromara.workflow.liteflow.start;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeBooleanComponent;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.domain.context.StartProcessContext;
+
+/**
+ * 判断启动流程时是否走已有实例续提交分支。
+ *
+ * @author may
+ */
+@ConditionalOnEnable
+@LiteflowComponent("startExists")
+public class StartExistsComponent extends NodeBooleanComponent {
+
+ @Override
+ public boolean processBoolean() {
+ StartProcessContext context = getRequestData();
+ return ObjectUtil.isNotNull(context.getExistingInstance());
+ }
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/start/StartPrepareInstanceComponent.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/start/StartPrepareInstanceComponent.java
new file mode 100644
index 000000000..24954b8fe
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/start/StartPrepareInstanceComponent.java
@@ -0,0 +1,55 @@
+package org.dromara.workflow.liteflow.start;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.lang.Dict;
+import cn.hutool.core.util.ObjectUtil;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeComponent;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.warm.flow.core.FlowEngine;
+import org.dromara.warm.flow.core.entity.Definition;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.domain.FlowInstanceBizExt;
+import org.dromara.workflow.domain.context.StartProcessContext;
+
+import java.util.Map;
+
+import static org.dromara.workflow.common.constant.FlowConstant.*;
+
+/**
+ * 加载流程定义并补齐启动变量。
+ *
+ * @author may
+ */
+@ConditionalOnEnable
+@LiteflowComponent("startPrepareInstance")
+public class StartPrepareInstanceComponent extends NodeComponent {
+
+ @Override
+ public void process() {
+ StartProcessContext context = getRequestData();
+ Definition definition = FlowEngine.defService().getPublishByFlowCode(context.getStartProcessBo().getFlowCode());
+ if (ObjectUtil.isNull(definition)) {
+ throw new ServiceException("流程【" + context.getStartProcessBo().getFlowCode() + "】未发布,请先在流程设计器中发布流程定义");
+ }
+ context.setDefinition(definition);
+
+ Map variables = context.getVariables();
+ Dict dict = JsonUtils.parseMap(definition.getExt());
+ boolean autoPass = !ObjectUtil.isNull(dict) && dict.getBool(AUTO_PASS);
+ variables.put(AUTO_PASS, autoPass);
+ variables.put(BUSINESS_CODE, generateBusinessCode(context.getBizExt()));
+ }
+
+ private String generateBusinessCode(FlowInstanceBizExt bizExt) {
+ if (ObjectUtil.isNull(bizExt.getBusinessCode()) || bizExt.getBusinessCode().isBlank()) {
+ // TODO: 按照自己业务规则生成编号
+ String businessCode = Convert.toStr(System.currentTimeMillis());
+ bizExt.setBusinessCode(businessCode);
+ return businessCode;
+ }
+ return bizExt.getBusinessCode();
+ }
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/start/StartPrepareRequestComponent.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/start/StartPrepareRequestComponent.java
new file mode 100644
index 000000000..05c0ac5dd
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/start/StartPrepareRequestComponent.java
@@ -0,0 +1,53 @@
+package org.dromara.workflow.liteflow.start;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeComponent;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.query.QueryBuilder;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.warm.flow.orm.entity.FlowInstance;
+import org.dromara.warm.flow.orm.mapper.FlowInstanceMapper;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.domain.context.StartProcessContext;
+
+import java.util.Map;
+
+import static org.dromara.workflow.common.constant.FlowConstant.*;
+
+/**
+ * 准备流程启动请求。
+ *
+ * @author may
+ */
+@ConditionalOnEnable
+@RequiredArgsConstructor
+@LiteflowComponent("startPrepareRequest")
+public class StartPrepareRequestComponent extends NodeComponent {
+
+ private final FlowInstanceMapper flowInstanceMapper;
+
+ @Override
+ public void process() {
+ StartProcessContext context = getRequestData();
+ String businessId = context.getStartProcessBo().getBusinessId();
+ if (StringUtils.isBlank(businessId)) {
+ throw new ServiceException("启动工作流时必须包含业务ID");
+ }
+ context.setBusinessId(businessId);
+ context.setVariables(context.getStartProcessBo().getVariables());
+ context.setBizExt(context.getStartProcessBo().getBizExt());
+
+ Map variables = context.getVariables();
+ variables.put(INITIATOR, LoginHelper.getUserIdStr());
+ variables.put(INITIATOR_DEPT_ID, LoginHelper.getDeptId());
+ variables.put(BUSINESS_ID, businessId);
+
+ FlowInstance flowInstance = flowInstanceMapper.selectOne(QueryBuilder.lambda(FlowInstance.class)
+ .eq(FlowInstance::getBusinessId, businessId)
+ .build());
+ context.setExistingInstance(flowInstance);
+ }
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/start/StartResumeComponent.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/start/StartResumeComponent.java
new file mode 100644
index 000000000..75ff4c58b
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/liteflow/start/StartResumeComponent.java
@@ -0,0 +1,56 @@
+package org.dromara.workflow.liteflow.start;
+
+import cn.hutool.core.collection.CollUtil;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeComponent;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.enums.BusinessStatusEnum;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.warm.flow.core.entity.Task;
+import org.dromara.warm.flow.core.service.InsService;
+import org.dromara.warm.flow.core.service.TaskService;
+import org.dromara.warm.flow.orm.entity.FlowTask;
+import org.dromara.workflow.api.domain.StartProcessReturnDTO;
+import org.dromara.workflow.common.ConditionalOnEnable;
+import org.dromara.workflow.domain.FlowInstanceBizExt;
+import org.dromara.workflow.domain.context.StartProcessContext;
+import org.dromara.workflow.mapper.FlwInstanceBizExtMapper;
+
+import java.util.List;
+
+/**
+ * 已存在流程实例时执行续提交。
+ *
+ * @author may
+ */
+@ConditionalOnEnable
+@RequiredArgsConstructor
+@LiteflowComponent("startResume")
+public class StartResumeComponent extends NodeComponent {
+
+ private final TaskService taskService;
+ private final InsService insService;
+ private final FlwInstanceBizExtMapper flwInstanceBizExtMapper;
+
+ @Override
+ public void process() {
+ StartProcessContext context = getRequestData();
+ BusinessStatusEnum.checkStartStatus(context.getExistingInstance().getFlowStatus());
+ List taskList = taskService.list(new FlowTask().setInstanceId(context.getExistingInstance().getId()));
+ if (CollUtil.isEmpty(taskList)) {
+ throw new ServiceException("流程实例缺少任务,请检查流程定义配置");
+ }
+ taskService.mergeVariable(context.getExistingInstance(), context.getVariables());
+ insService.updateById(context.getExistingInstance());
+ saveBizExt(context);
+ context.setStartProcessReturn(new StartProcessReturnDTO(taskList.getFirst().getInstanceId(), taskList.getFirst().getId()));
+ }
+
+ private void saveBizExt(StartProcessContext context) {
+ FlowInstanceBizExt bizExt = context.getBizExt();
+ bizExt.setInstanceId(context.getExistingInstance().getId());
+ bizExt.setBusinessId(context.getExistingInstance().getBusinessId());
+ flwInstanceBizExtMapper.saveOrUpdateByInstanceId(bizExt);
+ }
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/resources/liteflow/instance-chain.el.xml b/ruoyi-modules/ruoyi-workflow/src/main/resources/liteflow/instance-chain.el.xml
new file mode 100644
index 000000000..c37beb7d8
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/resources/liteflow/instance-chain.el.xml
@@ -0,0 +1,22 @@
+
+
+
+
+ THEN(
+ instanceDeleteLoad,
+ IF(
+ instanceDeleteExists,
+ THEN(
+ instanceDeleteEvent,
+ instanceDeleteExecute
+ ),
+ noop
+ )
+ );
+
+
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/resources/liteflow/task-chain.el.xml b/ruoyi-modules/ruoyi-workflow/src/main/resources/liteflow/task-chain.el.xml
new file mode 100644
index 000000000..772243388
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/resources/liteflow/task-chain.el.xml
@@ -0,0 +1,61 @@
+
+
+
+
+ THEN(
+ startPrepareRequest,
+ IF(
+ startExists,
+ startResume,
+ THEN(
+ startPrepareInstance,
+ startExecute
+ )
+ )
+ );
+
+
+
+
+ THEN(
+ completePrepare,
+ completeExecute,
+ IF(
+ completeNeedAutoPass,
+ completeAutoPass,
+ noop
+ )
+ );
+
+
+
+
+ THEN(
+ taskOpPrepare,
+ taskOpLoad,
+ taskOpExecute,
+ IF(
+ taskOpNeedNotify,
+ taskOpNotify,
+ noop
+ )
+ );
+
+