varList;
+
+ //业务扩展信息开始
+ /**
+ * 业务编码
+ */
+ private String businessCode;
+
+ /**
+ * 业务标题
+ */
+ private String businessTitle;
+ //业务扩展信息结束
+
}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/NodeExtVo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/NodeExtVo.java
new file mode 100644
index 000000000..5fb3380b4
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/NodeExtVo.java
@@ -0,0 +1,45 @@
+package org.dromara.workflow.domain.vo;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Node 扩展属性解析结果 VO
+ *
+ * 用于封装从扩展属性 JSON 中解析出的各类信息,包括按钮权限、抄送对象和自定义参数。
+ *
+ * @author AprilWind
+ */
+@Data
+public class NodeExtVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 按钮权限列表
+ *
+ * 根据扩展属性中 ButtonPermissionEnum 类型的数据生成,每个元素表示一个按钮及其是否勾选。
+ */
+ private List buttonPermissions;
+
+ /**
+ * 抄送对象 ID 集合
+ *
+ * 根据扩展属性中 CopySettingEnum 类型的数据生成,存储需要抄送的对象 ID
+ */
+ private Set copySettings;
+
+ /**
+ * 自定义参数 Map
+ *
+ * 根据扩展属性中 VariablesEnum 类型的数据生成,存储 key=value 格式的自定义参数
+ */
+ private Map variables;
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/TestLeaveVo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/TestLeaveVo.java
index 4c9b4e936..2f34158b0 100644
--- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/TestLeaveVo.java
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/TestLeaveVo.java
@@ -32,6 +32,12 @@ public class TestLeaveVo implements Serializable {
@ExcelProperty(value = "主键")
private Long id;
+ /**
+ * 申请编号
+ */
+ @ExcelProperty(value = "申请编号")
+ private String applyCode;
+
/**
* 请假类型
*/
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/WorkflowPermissionHandler.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/WorkflowPermissionHandler.java
index 43af0105e..e908a2085 100644
--- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/WorkflowPermissionHandler.java
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/WorkflowPermissionHandler.java
@@ -1,6 +1,7 @@
package org.dromara.workflow.handler;
import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.dto.UserDTO;
@@ -59,6 +60,6 @@ public class WorkflowPermissionHandler implements PermissionHandler {
}
String storageIds = CollUtil.join(permissions, StringUtils.SEPARATOR);
List users = flwTaskAssigneeService.fetchUsersByStorageIds(storageIds);
- return StreamUtils.toList(users, userDTO -> String.valueOf(userDTO.getUserId()));
+ return StreamUtils.toList(users, userDTO -> Convert.toStr(userDTO.getUserId()));
}
}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/WorkflowGlobalListener.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/WorkflowGlobalListener.java
index 88a4dbf5f..2390bb251 100644
--- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/WorkflowGlobalListener.java
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/WorkflowGlobalListener.java
@@ -1,33 +1,39 @@
package org.dromara.workflow.listener;
import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.enums.BusinessStatusEnum;
+import org.dromara.common.core.service.UserService;
+import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
+import org.dromara.warm.flow.core.FlowEngine;
import org.dromara.warm.flow.core.dto.FlowParams;
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.core.listener.GlobalListener;
import org.dromara.warm.flow.core.listener.ListenerVariable;
-import org.dromara.warm.flow.core.service.InsService;
import org.dromara.workflow.common.ConditionalOnEnable;
import org.dromara.workflow.common.constant.FlowConstant;
import org.dromara.workflow.common.enums.TaskStatusEnum;
import org.dromara.workflow.domain.bo.FlowCopyBo;
+import org.dromara.workflow.domain.vo.NodeExtVo;
import org.dromara.workflow.handler.FlowProcessEventHandler;
import org.dromara.workflow.service.IFlwCommonService;
import org.dromara.workflow.service.IFlwInstanceService;
+import org.dromara.workflow.service.IFlwNodeExtService;
import org.dromara.workflow.service.IFlwTaskService;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* 全局任务办理监听
@@ -41,10 +47,11 @@ import java.util.Map;
public class WorkflowGlobalListener implements GlobalListener {
private final IFlwTaskService flwTaskService;
- private final IFlwInstanceService instanceService;
+ private final IFlwInstanceService flwInstanceService;
private final FlowProcessEventHandler flowProcessEventHandler;
private final IFlwCommonService flwCommonService;
- private final InsService insService;
+ private final IFlwNodeExtService nodeExtService;
+ private final UserService userService;
/**
* 创建监听器,任务创建时执行
@@ -63,6 +70,25 @@ public class WorkflowGlobalListener implements GlobalListener {
*/
@Override
public void start(ListenerVariable listenerVariable) {
+ String ext = listenerVariable.getNode().getExt();
+ if (StringUtils.isNotBlank(ext)) {
+ NodeExtVo nodeExt = nodeExtService.parseNodeExt(ext);
+ Map variable = listenerVariable.getVariable();
+ Set copyList = nodeExt.getCopySettings();
+ if (CollUtil.isNotEmpty(copyList)) {
+ List list = StreamUtils.toList(copyList, x -> {
+ FlowCopyBo bo = new FlowCopyBo();
+ Long id = Convert.toLong(x);
+ bo.setUserId(id);
+ bo.setUserName(userService.selectUserNameById(id));
+ return bo;
+ });
+ variable.put(FlowConstant.FLOW_COPY_LIST, list);
+ }
+ if (CollUtil.isNotEmpty(nodeExt.getVariables())) {
+ variable.putAll(nodeExt.getVariables());
+ }
+ }
}
/**
@@ -125,12 +151,15 @@ public class WorkflowGlobalListener implements GlobalListener {
if (StringUtils.isNotBlank(status)) {
flowProcessEventHandler.processHandler(definition.getFlowCode(), instance, status, params, false);
}
- if (task != null && CollUtil.isNotEmpty(nextTasks) && nextTasks.size() == 1 && flwCommonService.applyNodeCode(definition.getId()).equals(nextTasks.get(0).getNodeCode())) {
- //如果为画线指定驳回 线条指定为驳回 驳回得节点为申请人节点 则修改流程状态为退回
- flowProcessEventHandler.processHandler(definition.getFlowCode(), instance, BusinessStatusEnum.BACK.getStatus(), params, false);
- //修改流程实例状态
- instance.setFlowStatus(BusinessStatusEnum.BACK.getStatus());
- insService.updateById(instance);
+ if (!BusinessStatusEnum.initialState(instance.getFlowStatus())) {
+ if (task != null && CollUtil.isNotEmpty(nextTasks) && nextTasks.size() == 1
+ && flwCommonService.applyNodeCode(definition.getId()).equals(nextTasks.get(0).getNodeCode())) {
+ // 如果为画线指定驳回 线条指定为驳回 驳回得节点为申请人节点 则修改流程状态为退回
+ flowProcessEventHandler.processHandler(definition.getFlowCode(), instance, BusinessStatusEnum.BACK.getStatus(), params, false);
+ // 修改流程实例状态
+ instance.setFlowStatus(BusinessStatusEnum.BACK.getStatus());
+ FlowEngine.insService().updateById(instance);
+ }
}
}
//发布任务事件
@@ -158,12 +187,9 @@ public class WorkflowGlobalListener implements GlobalListener {
if (variable.containsKey(FlowConstant.MESSAGE_TYPE)) {
List messageType = MapUtil.get(variable, FlowConstant.MESSAGE_TYPE, new TypeReference<>() {});
String notice = MapUtil.getStr(variable, FlowConstant.MESSAGE_NOTICE);
- // 消息通知
- if (CollUtil.isNotEmpty(messageType)) {
- flwCommonService.sendMessage(definition.getFlowName(), instance.getId(), messageType, notice);
- }
+ flwCommonService.sendMessage(definition.getFlowName(), instance.getId(), messageType, notice);
}
- insService.removeVariables(instance.getId(),
+ FlowEngine.insService().removeVariables(instance.getId(),
FlowConstant.FLOW_COPY_LIST,
FlowConstant.MESSAGE_TYPE,
FlowConstant.MESSAGE_NOTICE,
@@ -187,7 +213,7 @@ public class WorkflowGlobalListener implements GlobalListener {
if (flwTaskService.isTaskEnd(instanceId)) {
String status = BusinessStatusEnum.FINISH.getStatus();
// 更新流程状态为已完成
- instanceService.updateStatus(instanceId, status);
+ flwInstanceService.updateStatus(instanceId, status);
log.info("流程已结束,状态更新为: {}", status);
return status;
}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwInstanceBizExtMapper.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwInstanceBizExtMapper.java
new file mode 100644
index 000000000..e11613cf4
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwInstanceBizExtMapper.java
@@ -0,0 +1,61 @@
+package org.dromara.workflow.mapper;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.workflow.domain.FlowInstanceBizExt;
+
+import java.util.List;
+
+/**
+ * 流程实例业务扩展Mapper接口
+ *
+ * @author may
+ * @date 2025-08-05
+ */
+public interface FlwInstanceBizExtMapper extends BaseMapperPlus {
+
+ /**
+ * 根据 instanceId 保存或更新流程实例业务扩展
+ *
+ * @param entity 流程实例业务扩展实体
+ * @return 操作是否成功
+ */
+ default int saveOrUpdateByInstanceId(FlowInstanceBizExt entity) {
+ // 查询是否存在
+ FlowInstanceBizExt exist = this.selectOne(new LambdaQueryWrapper()
+ .eq(FlowInstanceBizExt::getInstanceId, entity.getInstanceId()));
+
+ if (ObjectUtil.isNotNull(exist)) {
+ // 存在就带上主键更新
+ entity.setId(exist.getId());
+ return updateById(entity);
+ } else {
+ // 不存在就插入
+ return insert(entity);
+ }
+ }
+
+ /**
+ * 按照流程实例ID删除单个流程实例业务扩展
+ *
+ * @param instanceId 流程实例ID
+ * @return 删除的行数
+ */
+ default int deleteByInstId(Long instanceId) {
+ return this.delete(new LambdaQueryWrapper()
+ .eq(FlowInstanceBizExt::getInstanceId, instanceId));
+ }
+
+ /**
+ * 按照流程实例ID批量删除流程实例业务扩展
+ *
+ * @param instanceIds 流程实例ID列表
+ * @return 删除的行数
+ */
+ default int deleteByInstIds(List instanceIds) {
+ return this.delete(new LambdaQueryWrapper()
+ .in(FlowInstanceBizExt::getInstanceId, instanceIds));
+ }
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwInstanceService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwInstanceService.java
index 09fed0d63..54ffe977e 100644
--- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwInstanceService.java
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwInstanceService.java
@@ -85,6 +85,14 @@ public interface IFlwInstanceService {
*/
boolean deleteByInstanceIds(List instanceIds);
+ /**
+ * 按照实例id删除已完成得流程实例
+ *
+ * @param instanceIds 删除的实例id
+ * @return 删除结果
+ */
+ boolean deleteHisByInstanceIds(List instanceIds);
+
/**
* 撤销流程
*
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwNodeExtService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwNodeExtService.java
index 959516535..2ccc6a73e 100644
--- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwNodeExtService.java
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwNodeExtService.java
@@ -1,8 +1,6 @@
package org.dromara.workflow.service;
-import org.dromara.workflow.domain.vo.ButtonPermissionVo;
-
-import java.util.List;
+import org.dromara.workflow.domain.vo.NodeExtVo;
/**
* 流程节点扩展属性 服务层
@@ -12,11 +10,23 @@ import java.util.List;
public interface IFlwNodeExtService {
/**
- * 从扩展属性构建按钮权限列表:根据 ext 中记录的权限值,标记每个按钮是否勾选
+ * 解析扩展属性 JSON 并构建 Node 扩展属性对象
+ *
+ * 根据传入的 JSON 字符串,将扩展属性分为三类:
+ * 1. ButtonPermissionEnum:解析为按钮权限列表,标记每个按钮是否勾选
+ * 2. CopySettingEnum:解析为抄送对象 ID 集合
+ * 3. VariablesEnum:解析为自定义参数 Map
+ *
+ *
示例 JSON:
+ * [
+ * {"code": "ButtonPermissionEnum", "value": "back,termination"},
+ * {"code": "CopySettingEnum", "value": "1"},
+ * {"code": "VariablesEnum", "value": "key1=value1,key2=value2"}
+ * ]
*
* @param ext 扩展属性 JSON 字符串
- * @return 按钮权限 VO 列表
+ * @return NodeExtVo 对象,封装按钮权限列表、抄送对象集合和自定义参数 Map
*/
- List buildButtonPermissionsFromExt(String ext);
+ NodeExtVo parseNodeExt(String ext);
}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskService.java
index 67279f340..f9dc45e1d 100644
--- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskService.java
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskService.java
@@ -111,11 +111,11 @@ public interface IFlwTaskService {
/**
* 获取可驳回的前置节点
*
- * @param definitionId 流程定义id
+ * @param taskId 任务id
* @param nowNodeCode 当前节点
* @return 结果
*/
- List getBackTaskNode(Long definitionId, String nowNodeCode);
+ List getBackTaskNode(Long taskId, String nowNodeCode);
/**
* 终止任务
@@ -165,6 +165,14 @@ public interface IFlwTaskService {
*/
List selectByInstId(Long instanceId);
+ /**
+ * 按照实例id查询任务
+ *
+ * @param instanceIds 列表
+ * @return 结果
+ */
+ List selectByInstIds(List instanceIds);
+
/**
* 判断流程是否已结束(即该流程实例下是否还有未完成的任务)
*
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCategoryServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCategoryServiceImpl.java
index 26d91a5b4..1e2f6c268 100644
--- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCategoryServiceImpl.java
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCategoryServiceImpl.java
@@ -1,6 +1,7 @@
package org.dromara.workflow.service.impl;
import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@@ -8,13 +9,11 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.exception.ServiceException;
-import org.dromara.common.core.utils.MapstructUtils;
-import org.dromara.common.core.utils.ObjectUtils;
-import org.dromara.common.core.utils.StringUtils;
-import org.dromara.common.core.utils.TreeBuildUtils;
+import org.dromara.common.core.utils.*;
import org.dromara.common.mybatis.helper.DataBaseHelper;
import org.dromara.warm.flow.core.service.DefService;
import org.dromara.warm.flow.orm.entity.FlowDefinition;
+import org.dromara.warm.flow.ui.service.CategoryService;
import org.dromara.workflow.common.ConditionalOnEnable;
import org.dromara.workflow.common.constant.FlowConstant;
import org.dromara.workflow.domain.FlowCategory;
@@ -38,7 +37,7 @@ import java.util.List;
@ConditionalOnEnable
@RequiredArgsConstructor
@Service
-public class FlwCategoryServiceImpl implements IFlwCategoryService {
+public class FlwCategoryServiceImpl implements IFlwCategoryService, CategoryService {
private final DefService defService;
private final FlwCategoryMapper baseMapper;
@@ -97,16 +96,31 @@ public class FlwCategoryServiceImpl implements IFlwCategoryService {
}
return TreeBuildUtils.buildMultiRoot(
categoryList,
- node -> String.valueOf(node.getCategoryId()),
- node -> String.valueOf(node.getParentId()),
+ node -> Convert.toStr(node.getCategoryId()),
+ node -> Convert.toStr(node.getParentId()),
(node, treeNode) -> treeNode
- .setId(String.valueOf(node.getCategoryId()))
- .setParentId(String.valueOf(node.getParentId()))
+ .setId(Convert.toStr(node.getCategoryId()))
+ .setParentId(Convert.toStr(node.getParentId()))
.setName(node.getCategoryName())
.setWeight(node.getOrderNum())
);
}
+ /**
+ * 工作流查询分类
+ *
+ * @return 分类树结构列表
+ */
+ @Override
+ public List queryCategory() {
+ List list = this.queryList(new FlowCategoryBo());
+ return StreamUtils.toList(list, category -> new org.dromara.warm.flow.core.dto.Tree()
+ .setId(Convert.toStr(category.getCategoryId()))
+ .setName(category.getCategoryName())
+ .setParentId(Convert.toStr(category.getParentId()))
+ );
+ }
+
/**
* 校验流程分类名称是否唯一
*
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwChartExtServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwChartExtServiceImpl.java
index dba24e419..3557b96e1 100644
--- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwChartExtServiceImpl.java
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwChartExtServiceImpl.java
@@ -12,7 +12,6 @@ import org.dromara.common.core.service.DeptService;
import org.dromara.common.core.service.DictService;
import org.dromara.common.core.service.UserService;
import org.dromara.common.core.utils.DateUtils;
-import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.warm.flow.core.dto.DefJson;
@@ -57,12 +56,8 @@ public class FlwChartExtServiceImpl implements ChartExtService {
*/
@Override
public void execute(DefJson defJson) {
- // 临时修复 后续版本将通过defjson获取流程实例ID
- String[] parts = ServletUtils.getRequest().getRequestURI().split("/");
- Long instanceId = Convert.toLong(parts[parts.length - 1]);
-
// 根据流程实例ID查询所有相关的历史任务列表
- List flowHisTasks = this.getHisTaskGroupedByNode(instanceId);
+ List flowHisTasks = this.getHisTaskGroupedByNode(defJson.getInstance().getId());
if (CollUtil.isEmpty(flowHisTasks)) {
return;
}
@@ -143,7 +138,8 @@ public class FlwChartExtServiceImpl implements ChartExtService {
"fontSize", "14px",
"zIndex", "1000",
"maxWidth", "500px",
- "overflowY", "visible",
+ "maxHeight", "300px",
+ "overflowY", "auto",
"overflowX", "hidden",
"color", "#333",
"pointerEvents", "auto",
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCommonServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCommonServiceImpl.java
index efc25a3f8..d0b53853b 100644
--- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCommonServiceImpl.java
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCommonServiceImpl.java
@@ -11,9 +11,8 @@ import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mail.utils.MailUtils;
import org.dromara.common.sse.dto.SseMessageDto;
import org.dromara.common.sse.utils.SseMessageUtils;
+import org.dromara.warm.flow.core.FlowEngine;
import org.dromara.warm.flow.core.entity.Node;
-import org.dromara.warm.flow.core.enums.SkipType;
-import org.dromara.warm.flow.core.service.NodeService;
import org.dromara.warm.flow.orm.entity.FlowTask;
import org.dromara.workflow.common.ConditionalOnEnable;
import org.dromara.workflow.common.enums.MessageTypeEnum;
@@ -21,8 +20,10 @@ import org.dromara.workflow.service.IFlwCommonService;
import org.dromara.workflow.service.IFlwTaskService;
import org.springframework.stereotype.Service;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
-import java.util.stream.Collectors;
+import java.util.Set;
/**
@@ -35,19 +36,27 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor
@Service
public class FlwCommonServiceImpl implements IFlwCommonService {
- private final NodeService nodeService;
+
+ private static final String DEFAULT_SUBJECT = "单据审批提醒";
/**
- * 发送消息
+ * 根据流程实例发送消息给当前处理人
*
* @param flowName 流程定义名称
- * @param messageType 消息类型
- * @param message 消息内容,为空则发送默认配置的消息内容
+ * @param instId 流程实例ID
+ * @param messageType 消息类型列表
+ * @param message 消息内容,为空则使用默认消息
*/
@Override
public void sendMessage(String flowName, Long instId, List messageType, String message) {
+ if (CollUtil.isNotEmpty(messageType)) {
+ return;
+ }
IFlwTaskService flwTaskService = SpringUtils.getBean(IFlwTaskService.class);
List list = flwTaskService.selectByInstId(instId);
+ if (CollUtil.isEmpty(list)) {
+ return;
+ }
if (StringUtils.isBlank(message)) {
message = "有新的【" + flowName + "】单据已经提交至您,请您及时处理。";
}
@@ -55,19 +64,25 @@ public class FlwCommonServiceImpl implements IFlwCommonService {
if (CollUtil.isEmpty(userList)) {
return;
}
- sendMessage(messageType, message, "单据审批提醒", userList);
+ sendMessage(messageType, message, DEFAULT_SUBJECT, userList);
}
/**
- * 发送消息
+ * 发送消息给指定用户列表
*
- * @param messageType 消息类型
+ * @param messageType 消息类型列表
* @param message 消息内容
* @param subject 邮件标题
- * @param userList 接收用户
+ * @param userList 接收用户列表
*/
@Override
public void sendMessage(List messageType, String message, String subject, List userList) {
+ if (CollUtil.isEmpty(messageType) || CollUtil.isEmpty(userList)) {
+ return;
+ }
+ List userIds = new ArrayList<>(StreamUtils.toSet(userList, UserDTO::getUserId));
+ Set emails = StreamUtils.toSet(userList, UserDTO::getEmail);
+
for (String code : messageType) {
MessageTypeEnum messageTypeEnum = MessageTypeEnum.getByCode(code);
if (ObjectUtil.isEmpty(messageTypeEnum)) {
@@ -76,13 +91,11 @@ public class FlwCommonServiceImpl implements IFlwCommonService {
switch (messageTypeEnum) {
case SYSTEM_MESSAGE -> {
SseMessageDto dto = new SseMessageDto();
- dto.setUserIds(StreamUtils.toList(userList, UserDTO::getUserId).stream().distinct().collect(Collectors.toList()));
+ dto.setUserIds(userIds);
dto.setMessage(message);
SseMessageUtils.publishMessage(dto);
}
- case EMAIL_MESSAGE -> {
- MailUtils.sendText(StreamUtils.join(userList, UserDTO::getEmail), subject, message);
- }
+ case EMAIL_MESSAGE -> MailUtils.sendText(emails, subject, message);
case SMS_MESSAGE -> {
//todo 短信发送
}
@@ -100,8 +113,7 @@ public class FlwCommonServiceImpl implements IFlwCommonService {
*/
@Override
public String applyNodeCode(Long definitionId) {
- Node startNode = nodeService.getStartNode(definitionId);
- Node nextNode = nodeService.getNextNode(definitionId, startNode.getNodeCode(), null, SkipType.PASS.getKey());
- return nextNode.getNodeCode();
+ List firstBetweenNode = FlowEngine.nodeService().getFirstBetweenNode(definitionId, new HashMap<>());
+ return firstBetweenNode.get(0).getNodeCode();
}
}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwDefinitionServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwDefinitionServiceImpl.java
index d19c7313d..b89a369cf 100644
--- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwDefinitionServiceImpl.java
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwDefinitionServiceImpl.java
@@ -128,7 +128,7 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
}
}
if (CollUtil.isNotEmpty(errorMsg)) {
- throw new ServiceException("节点【" + StringUtils.join(errorMsg, ",") + "】未配置办理人!");
+ throw new ServiceException("节点【{}】未配置办理人!", StringUtils.joinComma(errorMsg));
}
}
return defService.publish(id);
@@ -188,7 +188,7 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
if (CollUtil.isNotEmpty(flowDefinitions)) {
String join = StreamUtils.join(flowDefinitions, FlowDefinition::getFlowCode);
log.info("流程定义【{}】已被使用不可被删除!", join);
- throw new ServiceException("流程定义【" + join + "】已被使用不可被删除!");
+ throw new ServiceException("流程定义【{}】已被使用不可被删除!", join);
}
}
try {
@@ -231,7 +231,7 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
flowDefinition.setId(null);
flowDefinition.setTenantId(tenantId);
flowDefinition.setIsPublish(0);
- flowDefinition.setCategory(String.valueOf(flowCategory.getCategoryId()));
+ flowDefinition.setCategory(Convert.toStr(flowCategory.getCategoryId()));
int insert = flowDefinitionMapper.insert(flowDefinition);
if (insert <= 0) {
log.info("同步流程定义【{}】失败!", definition.getFlowCode());
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java
index 513ae7821..be6dfaf1a 100644
--- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java
@@ -110,7 +110,7 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
*/
@Override
public FlowInstanceVo queryByBusinessId(Long businessId) {
- FlowInstance instance = this.selectInstByBusinessId(String.valueOf(businessId));
+ FlowInstance instance = this.selectInstByBusinessId(Convert.toStr(businessId));
FlowInstanceVo instanceVo = BeanUtil.toBean(instance, FlowInstanceVo.class);
Definition definition = defService.getById(instanceVo.getDefinitionId());
instanceVo.setFlowName(definition.getFlowName());
@@ -211,18 +211,71 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
Function.identity()
);
- // 逐一触发删除事件
- instances.forEach(instance -> {
- Definition definition = definitionMap.get(instance.getDefinitionId());
- if (ObjectUtil.isNull(definition)) {
- log.warn("实例 ID: {} 对应的流程定义信息未找到,跳过删除事件触发。", instance.getId());
- return;
+ try {
+ // 逐一触发删除事件
+ instances.forEach(instance -> {
+ Definition definition = definitionMap.get(instance.getDefinitionId());
+ if (ObjectUtil.isNull(definition)) {
+ log.warn("实例 ID: {} 对应的流程定义信息未找到,跳过删除事件触发。", instance.getId());
+ return;
+ }
+ flowProcessEventHandler.processDeleteHandler(definition.getFlowCode(), instance.getBusinessId());
+ });
+ // 删除实例
+ boolean remove = insService.remove(instanceIds);
+ if (!remove) {
+ log.warn("删除流程实例失败!");
+ throw new ServiceException("删除流程实例失败");
}
- flowProcessEventHandler.processDeleteHandler(definition.getFlowCode(), instance.getBusinessId());
- });
+ } catch (Exception e) {
+ log.warn("操作失败!{}", e.getMessage());
+ throw new ServiceException(e.getMessage());
+ }
+ return true;
+ }
- // 删除实例
- return insService.remove(instanceIds);
+ /**
+ * 按照实例id删除已完成的流程实例
+ *
+ * @param instanceIds 实例id
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public boolean deleteHisByInstanceIds(List instanceIds) {
+ // 获取实例信息
+ List instances = insService.getByIds(instanceIds);
+ if (CollUtil.isEmpty(instances)) {
+ log.warn("未找到对应的流程实例信息,无法执行删除操作。");
+ return false;
+ }
+ // 获取定义信息
+ Map definitionMap = StreamUtils.toMap(
+ defService.getByIds(StreamUtils.toList(instances, Instance::getDefinitionId)),
+ Definition::getId,
+ Function.identity()
+ );
+ try {
+ // 逐一触发删除事件
+ instances.forEach(instance -> {
+ Definition definition = definitionMap.get(instance.getDefinitionId());
+ if (ObjectUtil.isNull(definition)) {
+ log.warn("实例 ID: {} 对应的流程定义信息未找到,跳过删除事件触发。", instance.getId());
+ return;
+ }
+ flowProcessEventHandler.processDeleteHandler(definition.getFlowCode(), instance.getBusinessId());
+ });
+ List flowTaskList = flwTaskService.selectByInstIds(instanceIds);
+ if (CollUtil.isNotEmpty(flowTaskList)) {
+ FlowEngine.userService().deleteByTaskIds(StreamUtils.toList(flowTaskList, FlowTask::getId));
+ }
+ FlowEngine.taskService().deleteByInsIds(instanceIds);
+ FlowEngine.hisTaskService().deleteByInsIds(instanceIds);
+ FlowEngine.insService().removeByIds(instanceIds);
+ } catch (Exception e) {
+ log.warn("操作失败!{}", e.getMessage());
+ throw new ServiceException(e.getMessage());
+ }
+ return true;
}
/**
@@ -371,6 +424,10 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
}
try {
Map variableMap = new HashMap<>(Optional.ofNullable(flowInstance.getVariableMap()).orElse(Collections.emptyMap()));
+ if (!variableMap.containsKey(bo.getKey())) {
+ log.error("变量不存在: {}", bo.getKey());
+ return false;
+ }
variableMap.put(bo.getKey(), bo.getValue());
flowInstance.setVariable(FlowEngine.jsonConvert.objToStr(variableMap));
flowInstanceMapper.updateById(flowInstance);
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwNodeExtServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwNodeExtServiceImpl.java
index fca21a367..b5f4c6deb 100644
--- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwNodeExtServiceImpl.java
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwNodeExtServiceImpl.java
@@ -1,8 +1,8 @@
package org.dromara.workflow.service.impl;
import cn.hutool.core.convert.Convert;
+import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ObjectUtil;
-import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.dto.DictTypeDTO;
@@ -13,14 +13,16 @@ import org.dromara.warm.flow.ui.service.NodeExtService;
import org.dromara.warm.flow.ui.vo.NodeExt;
import org.dromara.workflow.common.ConditionalOnEnable;
import org.dromara.workflow.common.enums.ButtonPermissionEnum;
+import org.dromara.workflow.common.enums.CopySettingEnum;
import org.dromara.workflow.common.enums.NodeExtEnum;
+import org.dromara.workflow.common.enums.VariablesEnum;
import org.dromara.workflow.domain.vo.ButtonPermissionVo;
+import org.dromara.workflow.domain.vo.NodeExtVo;
import org.dromara.workflow.service.IFlwNodeExtService;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
/**
* 流程设计器-节点扩展属性
@@ -36,14 +38,35 @@ public class FlwNodeExtServiceImpl implements NodeExtService, IFlwNodeExtService
/**
* 存储不同 dictType 对应的配置信息
*/
- private static final Map CHILD_NODE_MAP = new HashMap<>();
-
- record ButtonPermission(String label, Integer type, Boolean must, Boolean multiple) {
- }
+ private static final Map> CHILD_NODE_MAP;
static {
- CHILD_NODE_MAP.put(ButtonPermissionEnum.class.getSimpleName(),
- new ButtonPermission("权限按钮", 4, false, true));
+ CHILD_NODE_MAP = Map.of(
+ CopySettingEnum.class.getSimpleName(),
+ Map.of(
+ "label", "抄送对象",
+ "type", 2,
+ "must", false,
+ "multiple", false,
+ "desc", "设置该节点的抄送办理人"
+ ),
+ VariablesEnum.class.getSimpleName(),
+ Map.of(
+ "label", "自定义参数",
+ "type", 2,
+ "must", false,
+ "multiple", false,
+ "desc", "节点执行时可以使用的自定义参数"
+ ),
+ ButtonPermissionEnum.class.getSimpleName(),
+ Map.of(
+ "label", "权限按钮",
+ "type", 4,
+ "must", false,
+ "multiple", true,
+ "desc", "控制该节点的按钮权限"
+ )
+ );
}
private final DictService dictService;
@@ -56,6 +79,9 @@ public class FlwNodeExtServiceImpl implements NodeExtService, IFlwNodeExtService
@Override
public List getNodeExt() {
List nodeExtList = new ArrayList<>();
+ // 构建基础设置页面
+ nodeExtList.add(buildNodeExt("wf_basic_tab", "基础设置", 1,
+ List.of(CopySettingEnum.class, VariablesEnum.class)));
// 构建按钮权限页面
nodeExtList.add(buildNodeExt("wf_button_tab", "权限", 2,
List.of(ButtonPermissionEnum.class)));
@@ -105,9 +131,20 @@ public class FlwNodeExtServiceImpl implements NodeExtService, IFlwNodeExtService
return null;
}
String simpleName = enumClass.getSimpleName();
- NodeExt.ChildNode childNode = buildChildNodeMap(simpleName);
+ NodeExt.ChildNode childNode = new NodeExt.ChildNode();
+ Map map = CHILD_NODE_MAP.get(simpleName);
// 编码,此json中唯
childNode.setCode(simpleName);
+ // label名称
+ childNode.setLabel(Convert.toStr(map.get("label")));
+ // 1:输入框 2:文本域 3:下拉框 4:选择框
+ childNode.setType(Convert.toInt(map.get("type"), 1));
+ // 是否必填
+ childNode.setMust(Convert.toBool(map.get("must"), false));
+ // 是否多选
+ childNode.setMultiple(Convert.toBool(map.get("multiple"), true));
+ // 描述
+ childNode.setDesc(Convert.toStr(map.get("desc"), null));
// 字典,下拉框和复选框时用到
childNode.setDict(Arrays.stream(enumClass.getEnumConstants())
.map(NodeExtEnum.class::cast)
@@ -128,12 +165,18 @@ public class FlwNodeExtServiceImpl implements NodeExtService, IFlwNodeExtService
if (ObjectUtil.isNull(dictTypeDTO)) {
return null;
}
- NodeExt.ChildNode childNode = buildChildNodeMap(dictType);
+ NodeExt.ChildNode childNode = new NodeExt.ChildNode();
// 编码,此json中唯一
childNode.setCode(dictType);
// label名称
childNode.setLabel(dictTypeDTO.getDictName());
- // 描述
+ // 1:输入框 2:文本域 3:下拉框 4:选择框
+ childNode.setType(3);
+ // 是否必填
+ childNode.setMust(false);
+ // 是否多选
+ childNode.setMultiple(true);
+ // 描述 (可根据描述参数解析更多配置,如type,must,multiple等)
childNode.setDesc(dictTypeDTO.getRemark());
// 字典,下拉框和复选框时用到
childNode.setDict(dictService.getDictData(dictType)
@@ -144,100 +187,71 @@ public class FlwNodeExtServiceImpl implements NodeExtService, IFlwNodeExtService
}
/**
- * 根据 CHILD_NODE_MAP 中的配置信息,构建一个基本的 ChildNode 对象
- * 该方法用于设置 ChildNode 的常规属性,例如 label、type、是否必填、是否多选等
+ * 解析扩展属性 JSON 并构建 Node 扩展属性对象
+ *
+ * 根据传入的 JSON 字符串,将扩展属性分为三类:
+ * 1. ButtonPermissionEnum:解析为按钮权限列表,标记每个按钮是否勾选
+ * 2. CopySettingEnum:解析为抄送对象 ID 集合
+ * 3. VariablesEnum:解析为自定义参数 Map
*
- * @param key CHILD_NODE_MAP 的 key
- * @return 返回构建好的 ChildNode 对象
- */
- private NodeExt.ChildNode buildChildNodeMap(String key) {
- NodeExt.ChildNode childNode = new NodeExt.ChildNode();
- ButtonPermission bp = CHILD_NODE_MAP.get(key);
- if (bp == null) {
- childNode.setType(1);
- childNode.setMust(false);
- childNode.setMultiple(true);
- return childNode;
- }
- // label名称
- childNode.setLabel(bp.label());
- // 1:输入框 2:输入框 3:下拉框 4:选择框
- childNode.setType(bp.type());
- // 是否必填
- childNode.setMust(bp.must());
- // 是否多选
- childNode.setMultiple(bp.multiple());
- return childNode;
- }
-
- /**
- * 从扩展属性构建按钮权限列表:根据 ext 中记录的权限值,标记每个按钮是否勾选
+ *
示例 JSON:
+ * [
+ * {"code": "ButtonPermissionEnum", "value": "back,termination"},
+ * {"code": "CopySettingEnum", "value": "1"},
+ * {"code": "VariablesEnum", "value": "key1=value1,key2=value2"}
+ * ]
*
* @param ext 扩展属性 JSON 字符串
- * @return 按钮权限 VO 列表
+ * @return NodeExtVo 对象,封装按钮权限列表、抄送对象集合和自定义参数 Map
*/
@Override
- public List buildButtonPermissionsFromExt(String ext) {
- // 解析 ext 为 Map>,用于标记权限
- Map> permissionMap = JsonUtils.parseArray(ext, ButtonPermissionVo.class)
- .stream()
- .collect(Collectors.toMap(
- ButtonPermissionVo::getCode,
- item -> StringUtils.splitList(item.getValue()).stream()
- .map(String::trim)
- .filter(StrUtil::isNotBlank)
- .collect(Collectors.toSet()),
- (a, b) -> b,
- HashMap::new
- ));
+ public NodeExtVo parseNodeExt(String ext) {
+ NodeExtVo nodeExtVo = new NodeExtVo();
- // 构建按钮权限列表,标记哪些按钮在 permissionMap 中出现(表示已勾选)
- return buildPermissionsFromSources(permissionMap, List.of(ButtonPermissionEnum.class));
- }
+ // 解析 JSON 为 Dict 列表
+ List nodeExtMap = JsonUtils.parseArrayMap(ext);
- /**
- * 将权限映射与按钮权限来源(枚举类或字典类型)进行匹配,生成权限视图列表
- *
- * 使用说明:
- * - sources 支持传入多个来源类型,支持 NodeExtEnum 枚举类 或 字典类型字符串(dictType)
- * - 若需要扩展更多按钮权限,只需在 sources 中新增对应的枚举类或字典类型
- *
- * 示例:
- * buildPermissionsFromSources(permissionMap, List.of(ButtonPermissionEnum.class, "custom_button_dict"));
- *
- * @param permissionMap 权限映射
- * @param sources 枚举类或字典类型列表
- * @return 按钮权限视图对象列表
- */
- @SuppressWarnings("unchecked cast")
- private List buildPermissionsFromSources(Map> permissionMap, List