diff --git a/pom.xml b/pom.xml index d2550307..d258993e 100644 --- a/pom.xml +++ b/pom.xml @@ -55,7 +55,7 @@ 8.7.2-20250603 - 1.7.3 + 1.7.4 2.3.0 diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/dubbo/RemoteUserServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/dubbo/RemoteUserServiceImpl.java index aad3b1a3..23fd16b4 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/dubbo/RemoteUserServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/dubbo/RemoteUserServiceImpl.java @@ -312,7 +312,10 @@ public class RemoteUserServiceImpl implements RemoteUserService { return new ArrayList<>(); } List list = userMapper.selectVoList(new LambdaQueryWrapper() - .select(SysUser::getUserId, SysUser::getUserName, SysUser::getNickName, SysUser::getEmail, SysUser::getPhonenumber) + .select(SysUser::getUserId, SysUser::getDeptId, SysUser::getUserName, + SysUser::getNickName, SysUser::getUserType, SysUser::getEmail, + SysUser::getPhonenumber, SysUser::getSex, SysUser::getStatus, + SysUser::getCreateTime) .eq(SysUser::getStatus, SystemConstants.NORMAL) .in(SysUser::getUserId, userIds)); return MapstructUtils.convert(list, RemoteUserVo.class); diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/constant/FlowConstant.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/constant/FlowConstant.java index f3290b84..aaa640bf 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/constant/FlowConstant.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/constant/FlowConstant.java @@ -73,4 +73,9 @@ public interface FlowConstant { */ String MESSAGE_NOTICE = "messageNotice"; + /** + * 任务状态 + */ + String WF_TASK_STATUS = "wf_task_status"; + } diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/CategoryNameTranslationImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/CategoryNameTranslationImpl.java index 8c73b59e..883a967a 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/CategoryNameTranslationImpl.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/CategoryNameTranslationImpl.java @@ -26,12 +26,6 @@ public class CategoryNameTranslationImpl implements TranslationInterface @Override public String translation(Object key, String other) { - Long id = null; - if (key instanceof String categoryId) { - id = Convert.toLong(categoryId); - } else if (key instanceof Long categoryId) { - id = categoryId; - } - return flwCategoryService.selectCategoryNameById(id); + return flwCategoryService.selectCategoryNameById(Convert.toLong(key)); } } 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 new file mode 100644 index 00000000..ba4319b8 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwChartExtServiceImpl.java @@ -0,0 +1,251 @@ +package org.dromara.workflow.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.dubbo.config.annotation.DubboReference; +import org.dromara.common.core.service.DictService; +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.system.api.RemoteDeptService; +import org.dromara.system.api.RemoteUserService; +import org.dromara.system.api.domain.vo.RemoteUserVo; +import org.dromara.warm.flow.core.dto.DefJson; +import org.dromara.warm.flow.core.dto.NodeJson; +import org.dromara.warm.flow.core.dto.PromptContent; +import org.dromara.warm.flow.core.enums.NodeType; +import org.dromara.warm.flow.core.utils.MapUtil; +import org.dromara.warm.flow.orm.entity.FlowHisTask; +import org.dromara.warm.flow.orm.mapper.FlowHisTaskMapper; +import org.dromara.warm.flow.ui.service.ChartExtService; +import org.dromara.workflow.common.ConditionalOnEnable; +import org.dromara.workflow.common.constant.FlowConstant; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; + +/** + * 流程图提示信息 + * + * @author AprilWind + */ +@ConditionalOnEnable +@Slf4j +@RequiredArgsConstructor +@Service +public class FlwChartExtServiceImpl implements ChartExtService { + + private final FlowHisTaskMapper flowHisTaskMapper; + private final DictService dictService; + + @DubboReference + private RemoteUserService remoteUserService; + @DubboReference + private RemoteDeptService remoteDeptService; + + /** + * 设置流程图提示信息 + * + * @param defJson 流程定义json对象 + */ + @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); + if (CollUtil.isEmpty(flowHisTasks)) { + return; + } + + // 按节点编号(nodeCode)对历史任务进行分组 + Map> groupedByNode = StreamUtils.groupByKey(flowHisTasks, FlowHisTask::getNodeCode); + + // 批量查询所有审批人的用户信息 + List userDTOList = remoteUserService.selectListByIds(StreamUtils.toList(flowHisTasks, e -> Convert.toLong(e.getApprover()))); + + // 将查询到的用户列表转换为以用户ID为key的映射 + Map userMap = StreamUtils.toIdentityMap(userDTOList, RemoteUserVo::getUserId); + + Map dictType = dictService.getAllDictByDictType(FlowConstant.WF_TASK_STATUS); + + // 遍历流程定义中的每个节点,调用处理方法,将对应节点的任务列表及用户信息传入,生成扩展提示内容 + for (NodeJson nodeJson : defJson.getNodeList()) { + // 获取当前节点对应的历史任务列表,如果没有则返回空列表避免空指针 + List taskList = groupedByNode.get(nodeJson.getNodeCode()); + if (CollUtil.isEmpty(taskList)) { + continue; + } + // 处理当前节点的扩展信息,包括构建审批人提示内容等 + this.processNodeExtInfo(nodeJson, taskList, userMap, dictType); + } + } + + /** + * 初始化流程图提示信息 + * + * @param defJson 流程定义json对象 + */ + @Override + public void initPromptContent(DefJson defJson) { + defJson.setTopText("流程名称: " + defJson.getFlowName()); + defJson.getNodeList().forEach(nodeJson -> { + nodeJson.setPromptContent( + new PromptContent() + // 提示信息 + .setInfo( + CollUtil.newArrayList( + new PromptContent.InfoItem() + .setPrefix("任务名称: ") + .setContent(nodeJson.getNodeName()) + .setContentStyle(Map.of( + "border", "1px solid #d1e9ff", + "backgroundColor", "#e8f4ff", + "padding", "4px 8px", + "borderRadius", "4px" + )) + .setRowStyle(Map.of( + "fontWeight", "bold", + "margin", "0 0 6px 0", + "padding", "0 0 8px 0", + "borderBottom", "1px solid #ccc" + )) + ) + ) + // 弹窗样式 + .setDialogStyle(MapUtil.mergeAll( + "position", "absolute", + "backgroundColor", "#fff", + "border", "1px solid #ccc", + "borderRadius", "4px", + "boxShadow", "0 2px 8px rgba(0, 0, 0, 0.15)", + "padding", "8px 12px", + "fontSize", "14px", + "zIndex", "1000", + "maxWidth", "500px", + "overflowY", "visible", + "overflowX", "hidden", + "color", "#333", + "pointerEvents", "auto", + "scrollbarWidth", "thin" + )) + ); + }); + } + + /** + * 处理节点的扩展信息,构建用于流程图悬浮提示的内容 + * + * @param nodeJson 当前节点对象 + * @param taskList 当前节点对应的历史审批任务列表 + */ + private void processNodeExtInfo(NodeJson nodeJson, List taskList, Map userMap, Map dictType) { + + // 获取节点提示内容对象中的 info 列表,用于追加提示项 + List info = nodeJson.getPromptContent().getInfo(); + + // 遍历所有任务记录,构建提示内容 + for (FlowHisTask task : taskList) { + RemoteUserVo userDTO = userMap.get(Convert.toLong(task.getApprover())); + if (ObjectUtil.isEmpty(userDTO)) { + continue; + } + + // 查询用户所属部门名称 + String deptName = remoteDeptService.selectDeptNameByIds(Convert.toStr(userDTO.getDeptId())); + + // 添加标题项,如:👤 张三(市场部) + info.add(new PromptContent.InfoItem() + .setPrefix(StringUtils.format("👥 {}({})", userDTO.getNickName(), deptName)) + .setPrefixStyle(Map.of( + "fontWeight", "bold", + "fontSize", "15px", + "color", "#333" + )) + .setRowStyle(Map.of( + "margin", "8px 0", + "borderBottom", "1px dashed #ccc" + )) + ); + + // 添加具体信息项:账号、耗时、时间 + info.add(buildInfoItem("用户账号", userDTO.getUserName())); + info.add(buildInfoItem("审批状态", dictType.get(task.getFlowStatus()))); + info.add(buildInfoItem("审批耗时", DateUtils.getTimeDifference(task.getUpdateTime(), task.getCreateTime()))); + info.add(buildInfoItem("办理时间", DateUtils.formatDateTime(task.getUpdateTime()))); + } + } + + /** + * 构建单条提示内容对象 InfoItem,用于悬浮窗显示(key: value) + * + * @param key 字段名(作为前缀) + * @param value 字段值 + * @return 提示项对象 + */ + private PromptContent.InfoItem buildInfoItem(String key, String value) { + return new PromptContent.InfoItem() + // 前缀 + .setPrefix(key + ": ") + // 前缀样式 + .setPrefixStyle(Map.of( + "textAlign", "right", + "color", "#444", + "userSelect", "none", + "display", "inline-block", + "width", "100px", + "paddingRight", "8px", + "fontWeight", "500", + "fontSize", "14px", + "lineHeight", "24px", + "verticalAlign", "middle" + )) + // 内容 + .setContent(value) + // 内容样式 + .setContentStyle(Map.of( + "backgroundColor", "#f7faff", + "color", "#005cbf", + "padding", "4px 8px", + "fontSize", "14px", + "borderRadius", "4px", + "whiteSpace", "normal", + "border", "1px solid #d0e5ff", + "userSelect", "text", + "lineHeight", "20px" + )) + // 行样式 + .setRowStyle(Map.of( + "color", "#222", + "alignItems", "center", + "display", "flex", + "marginBottom", "6px", + "fontWeight", "400", + "fontSize", "14px" + )); + } + + /** + * 根据流程实例ID获取历史任务列表 + * + * @param instanceId 流程实例ID + * @return 历史任务列表 + */ + public List getHisTaskGroupedByNode(Long instanceId) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(FlowHisTask::getInstanceId, instanceId) + .eq(FlowHisTask::getNodeType, NodeType.BETWEEN.getKey()) + .orderByDesc(FlowHisTask::getCreateTime, FlowHisTask::getUpdateTime); + return flowHisTaskMapper.selectList(wrapper); + } + +} 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 b86d0f36..0c1f21b7 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 @@ -304,9 +304,9 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService { } //历史任务 LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); - wrapper.eq(FlowHisTask::getInstanceId, instanceId); - wrapper.eq(FlowHisTask::getNodeType, NodeType.BETWEEN.getKey()); - wrapper.orderByDesc(FlowHisTask::getCreateTime).orderByDesc(FlowHisTask::getUpdateTime); + wrapper.eq(FlowHisTask::getInstanceId, instanceId) + .eq(FlowHisTask::getNodeType, NodeType.BETWEEN.getKey()) + .orderByDesc(FlowHisTask::getCreateTime, FlowHisTask::getUpdateTime); List flowHisTasks = flowHisTaskMapper.selectList(wrapper); if (CollUtil.isNotEmpty(flowHisTasks)) { list.addAll(BeanUtil.copyToList(flowHisTasks, FlowHisTaskVo.class)); diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskAssigneeServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskAssigneeServiceImpl.java index 51f730c2..ccb5106e 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskAssigneeServiceImpl.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskAssigneeServiceImpl.java @@ -2,6 +2,7 @@ package org.dromara.workflow.service.impl; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; import cn.hutool.core.lang.Pair; import cn.hutool.core.util.StrUtil; import lombok.RequiredArgsConstructor; @@ -249,10 +250,10 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand try { String[] parts = storageId.split(StrUtil.COLON, 2); if (parts.length < 2) { - return Pair.of(TaskAssigneeEnum.USER, Long.valueOf(parts[0])); + return Pair.of(TaskAssigneeEnum.USER, Convert.toLong(parts[0])); } else { TaskAssigneeEnum type = TaskAssigneeEnum.fromCode(parts[0] + StrUtil.COLON); - return Pair.of(type, Long.valueOf(parts[1])); + return Pair.of(type, Convert.toLong(parts[1])); } } catch (Exception e) { log.warn("解析 storageId 失败,格式非法:{},错误信息:{}", storageId, e.getMessage()); diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java index 07db03dd..1e4972de 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java @@ -534,7 +534,10 @@ public class FlwTaskServiceImpl implements IFlwTaskService { //构建以下节点数据 List buildNextTaskList = StreamUtils.toList(nextNodeList, node -> taskService.addTask(node, instance, definition, FlowParams.build())); //办理人变量替换 - ExpressionUtil.evalVariable(buildNextTaskList, mergeVariable); + ExpressionUtil.evalVariable(buildNextTaskList, + FlowParams.build() + .variable(mergeVariable) + ); for (FlowNode flowNode : nextFlowNodes) { buildNextTaskList.stream().filter(t -> t.getNodeCode().equals(flowNode.getNodeCode())).findFirst().ifPresent(t -> { if (CollUtil.isNotEmpty(t.getPermissionList())) { @@ -718,7 +721,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService { for (Map.Entry> entry : listMap.entrySet()) { List value = entry.getValue(); if (CollUtil.isNotEmpty(value)) { - List userDtoList = remoteUserService.selectListByIds(StreamUtils.toList(value, e -> Long.valueOf(e.getProcessedBy()))); + List userDtoList = remoteUserService.selectListByIds(StreamUtils.toList(value, e -> Convert.toLong(e.getProcessedBy()))); map.put(entry.getKey(), userDtoList); } } @@ -737,7 +740,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService { if (CollUtil.isEmpty(userList)) { return Collections.emptyList(); } - return remoteUserService.selectListByIds(StreamUtils.toList(userList, e -> Long.valueOf(e.getProcessedBy()))); + return remoteUserService.selectListByIds(StreamUtils.toList(userList, e -> Convert.toLong(e.getProcessedBy()))); } /** diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java index 0b227a4d..4cb2f4c4 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java @@ -147,7 +147,7 @@ public class TestLeaveServiceImpl implements ITestLeaveService { public void processHandler(ProcessEvent processEvent) { TenantHelper.dynamic(processEvent.getTenantId(), () -> { log.info("当前任务执行了{}", processEvent.toString()); - TestLeave testLeave = baseMapper.selectById(Long.valueOf(processEvent.getBusinessId())); + TestLeave testLeave = baseMapper.selectById(Convert.toLong(processEvent.getBusinessId())); testLeave.setStatus(processEvent.getStatus()); // 用于例如审批附件 审批意见等 存储到业务表内 自行根据业务实现存储流程 Map params = processEvent.getParams(); diff --git a/script/sql/ry-workflow.sql b/script/sql/ry-workflow.sql index 23893ddb..68557aa1 100644 --- a/script/sql/ry-workflow.sql +++ b/script/sql/ry-workflow.sql @@ -29,7 +29,7 @@ CREATE TABLE `flow_node` `definition_id` bigint NOT NULL COMMENT '流程定义id', `node_code` varchar(100) NOT NULL COMMENT '流程节点编码', `node_name` varchar(100) DEFAULT NULL COMMENT '流程节点名称', - `permission_flag` varchar(200) DEFAULT NULL COMMENT '权限标识(权限类型:权限标识,可以多个,用逗号隔开)', + `permission_flag` varchar(200) DEFAULT NULL COMMENT '权限标识(权限类型:权限标识,可以多个,用@@隔开)', `node_ratio` decimal(6, 3) DEFAULT NULL COMMENT '流程签署比例值', `coordinate` varchar(100) DEFAULT NULL COMMENT '坐标', `any_node_skip` varchar(100) DEFAULT NULL COMMENT '任意结点跳转', @@ -42,7 +42,7 @@ CREATE TABLE `flow_node` `version` varchar(20) NOT NULL COMMENT '版本', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', - `ext` text COMMENT '扩展属性', + `ext` text COMMENT '节点扩展属性', `del_flag` char(1) DEFAULT '0' COMMENT '删除标志', `tenant_id` varchar(40) DEFAULT NULL COMMENT '租户id', PRIMARY KEY (`id`) USING BTREE @@ -108,25 +108,25 @@ CREATE TABLE `flow_task` CREATE TABLE `flow_his_task` ( - `id` bigint(20) NOT NULL COMMENT '主键id', - `definition_id` bigint(20) NOT NULL COMMENT '对应flow_definition表的id', - `instance_id` bigint(20) NOT NULL COMMENT '对应flow_instance表的id', - `task_id` bigint(20) NOT NULL COMMENT '对应flow_task表的id', + `id` bigint(20) NOT NULL COMMENT '主键id', + `definition_id` bigint(20) NOT NULL COMMENT '对应flow_definition表的id', + `instance_id` bigint(20) NOT NULL COMMENT '对应flow_instance表的id', + `task_id` bigint(20) NOT NULL COMMENT '对应flow_task表的id', `node_code` varchar(100) DEFAULT NULL COMMENT '开始节点编码', `node_name` varchar(100) DEFAULT NULL COMMENT '开始节点名称', `node_type` tinyint(1) DEFAULT NULL COMMENT '开始节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)', `target_node_code` varchar(200) DEFAULT NULL COMMENT '目标节点编码', `target_node_name` varchar(200) DEFAULT NULL COMMENT '结束节点名称', `approver` varchar(40) DEFAULT NULL COMMENT '审批者', - `cooperate_type` tinyint(1) NOT NULL DEFAULT '0' COMMENT '协作方式(1审批 2转办 3委派 4会签 5票签 6加签 7减签)', + `cooperate_type` tinyint(1) NOT NULL DEFAULT '0' COMMENT '协作方式(1审批 2转办 3委派 4会签 5票签 6加签 7减签)', `collaborator` varchar(40) DEFAULT NULL COMMENT '协作人', - `skip_type` varchar(10) NOT NULL COMMENT '流转类型(PASS通过 REJECT退回 NONE无动作)', - `flow_status` varchar(20) NOT NULL COMMENT '流程状态(0待提交 1审批中 2审批通过 4终止 5作废 6撤销 8已完成 9已退回 10失效 11拿回)', + `skip_type` varchar(10) NOT NULL COMMENT '流转类型(PASS通过 REJECT退回 NONE无动作)', + `flow_status` varchar(20) NOT NULL COMMENT '流程状态(0待提交 1审批中 2审批通过 4终止 5作废 6撤销 8已完成 9已退回 10失效 11拿回)', `form_custom` char(1) DEFAULT 'N' COMMENT '审批表单是否自定义(Y是 N否)', `form_path` varchar(100) DEFAULT NULL COMMENT '审批表单路径', `message` varchar(500) DEFAULT NULL COMMENT '审批意见', `variable` TEXT DEFAULT NULL COMMENT '任务变量', - `ext` varchar(500) DEFAULT NULL COMMENT '业务详情 存业务表对象json字符串', + `ext` TEXT DEFAULT NULL COMMENT '业务详情 存业务表对象json字符串', `create_time` datetime DEFAULT NULL COMMENT '任务开始时间', `update_time` datetime DEFAULT NULL COMMENT '审批完成时间', `del_flag` char(1) DEFAULT '0' COMMENT '删除标志',