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 + ) + ); + +