From a6c21ac7d7f1cd24fa4c70814b362b6a8c5e56eb Mon Sep 17 00:00:00 2001 From: AprilWind <2100166581@qq.com> Date: Mon, 30 Mar 2026 17:46:19 +0800 Subject: [PATCH] =?UTF-8?q?update=20=E5=A2=9E=E5=8A=A0=E9=83=A8=E9=97=A8Ex?= =?UTF-8?q?cel=E8=BD=AC=E6=8D=A2=E5=A4=84=E7=90=86=E5=92=8C=E4=B8=8B?= =?UTF-8?q?=E6=8B=89=E9=80=89=E9=A1=B9=E6=95=B0=E6=8D=AE=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-common/ruoyi-common-bom/pom.xml | 2 + .../common/core/utils/TreeBuildUtils.java | 47 ++++++- .../system/domain/vo/SysUserExportVo.java | 8 +- .../system/domain/vo/SysUserImportVo.java | 6 +- .../system/listener/DeptExcelConverter.java | 121 ++++++++++++++++++ .../system/listener/DeptExcelOptions.java | 38 ++++++ 6 files changed, 219 insertions(+), 3 deletions(-) create mode 100644 ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/listener/DeptExcelConverter.java create mode 100644 ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/listener/DeptExcelOptions.java diff --git a/ruoyi-common/ruoyi-common-bom/pom.xml b/ruoyi-common/ruoyi-common-bom/pom.xml index 9eb04dd77..b60c35f40 100644 --- a/ruoyi-common/ruoyi-common-bom/pom.xml +++ b/ruoyi-common/ruoyi-common-bom/pom.xml @@ -103,6 +103,7 @@ ${revision} + org.dromara ruoyi-common-social @@ -151,6 +152,7 @@ ${revision} + org.dromara ruoyi-common-mqtt diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/TreeBuildUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/TreeBuildUtils.java index 491559d90..43390b13f 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/TreeBuildUtils.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/TreeBuildUtils.java @@ -5,10 +5,12 @@ import cn.hutool.core.lang.tree.Tree; import cn.hutool.core.lang.tree.TreeNodeConfig; import cn.hutool.core.lang.tree.TreeUtil; import cn.hutool.core.lang.tree.parser.NodeParser; +import cn.hutool.core.util.StrUtil; import lombok.AccessLevel; import lombok.NoArgsConstructor; import org.dromara.common.core.utils.reflect.ReflectUtils; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -41,7 +43,7 @@ public class TreeBuildUtils extends TreeUtil { * @param 输入节点的类型 * @return 构建好的树形结构列表 */ - public static List build(List items, K parentId, Function classifier, BiConsumer>> action) { + public static List build(List items, K parentId, Function classifier, BiConsumer>> action) { // 构建动态规划表 (依据父ID分组) Map> nodeTreeMaps = items.stream().collect(Collectors.groupingBy(classifier)); // 回溯构建各级节点关系 @@ -124,6 +126,20 @@ public class TreeBuildUtils extends TreeUtil { .collect(Collectors.toList()); } + /** + * 构建树节点路径 Map:路径为 key,节点为 value + * + * @param trees 树结构 + * @param joiner 拼接符 / - _ + * @param fieldGetter 路径拼接字段(Tree::getName / Tree::getId) + * @return Map<拼接路径, 原始Tree节点> + */ + public static Map> buildTreeNodeMap(List> trees, String joiner, Function, CharSequence> fieldGetter) { + Map> nodeMap = new LinkedHashMap<>(); + doBuildTreeNodeMap(trees, "", joiner, fieldGetter, nodeMap); + return nodeMap; + } + /** * 获取指定节点下的所有叶子节点 * @@ -141,4 +157,33 @@ public class TreeBuildUtils extends TreeUtil { } } + /** + * 递归构建【路径为key,节点为value】的Map + *

深度优先遍历树结构,将拼接好的路径作为key,原始Tree节点作为value

+ * + * @param trees 当前层级的节点列表 + * @param parentPath 父节点已拼接好的路径 + * @param joiner 路径拼接符,如 "/"、"-"、"_" + * @param fieldGetter 用于拼接路径的节点字段获取器 + * @param nodeMap 存放最终结果的有序Map(路径->Tree节点) + * @param 树节点ID的类型 + */ + private static void doBuildTreeNodeMap(List> trees, String parentPath, String joiner, Function, CharSequence> fieldGetter, Map> nodeMap) { + if (CollUtil.isEmpty(trees)) { + return; + } + for (Tree tree : trees) { + CharSequence field = fieldGetter.apply(tree); + if (StrUtil.isEmpty(field)) { + continue; + } + // 拼接路径作为 KEY + String currentPath = StrUtil.isEmpty(parentPath) ? field.toString() : parentPath + joiner + field; + // 路径 = key,节点 = value + nodeMap.put(currentPath, tree); + // 递归子节点 + doBuildTreeNodeMap(tree.getChildren(), currentPath, joiner, fieldGetter, nodeMap); + } + } + } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserExportVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserExportVo.java index 528851111..13b563f91 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserExportVo.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserExportVo.java @@ -5,6 +5,7 @@ import lombok.NoArgsConstructor; import org.apache.fesod.sheet.annotation.ExcelProperty; import org.dromara.common.excel.annotation.ExcelDictFormat; import org.dromara.common.excel.convert.ExcelDictConvert; +import org.dromara.system.listener.DeptExcelConverter; import java.io.Serial; import java.io.Serializable; @@ -15,7 +16,6 @@ import java.util.Date; * * @author Lion Li */ - @Data @NoArgsConstructor public class SysUserExportVo implements Serializable { @@ -35,6 +35,12 @@ public class SysUserExportVo implements Serializable { @ExcelProperty(value = "用户账号") private String userName; + /** + * 部门ID + */ + @ExcelProperty(value = "部门名称", converter = DeptExcelConverter.class) + private Long deptId; + /** * 用户昵称 */ diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserImportVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserImportVo.java index 41544d491..b25e90d08 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserImportVo.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysUserImportVo.java @@ -4,7 +4,10 @@ import lombok.Data; import lombok.NoArgsConstructor; import org.apache.fesod.sheet.annotation.ExcelProperty; import org.dromara.common.excel.annotation.ExcelDictFormat; +import org.dromara.common.excel.annotation.ExcelDynamicOptions; import org.dromara.common.excel.convert.ExcelDictConvert; +import org.dromara.system.listener.DeptExcelConverter; +import org.dromara.system.listener.DeptExcelOptions; import java.io.Serial; import java.io.Serializable; @@ -32,7 +35,8 @@ public class SysUserImportVo implements Serializable { /** * 部门ID */ - @ExcelProperty(value = "部门编号") + @ExcelProperty(value = "部门名称", converter = DeptExcelConverter.class) + @ExcelDynamicOptions(providerClass = DeptExcelOptions.class) private Long deptId; /** diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/listener/DeptExcelConverter.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/listener/DeptExcelConverter.java new file mode 100644 index 000000000..bbba2ec9a --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/listener/DeptExcelConverter.java @@ -0,0 +1,121 @@ +package org.dromara.system.listener; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.tree.Tree; +import lombok.RequiredArgsConstructor; +import org.apache.fesod.sheet.converters.Converter; +import org.apache.fesod.sheet.enums.CellDataTypeEnum; +import org.apache.fesod.sheet.metadata.GlobalConfiguration; +import org.apache.fesod.sheet.metadata.data.ReadCellData; +import org.apache.fesod.sheet.metadata.data.WriteCellData; +import org.apache.fesod.sheet.metadata.property.ExcelContentProperty; +import org.dromara.common.core.utils.SpringUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.core.utils.TreeBuildUtils; +import org.dromara.system.domain.bo.SysDeptBo; +import org.dromara.system.service.ISysDeptService; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +/** + * Excel 部门转换处理 + * + * @author AprilWind + */ +@RequiredArgsConstructor +@Component +public class DeptExcelConverter implements Converter { + + /** + * 线程内缓存:ID → 名称 + */ + private static final ThreadLocal> TL_ID_TO_NAME = new ThreadLocal<>(); + + /** + * 线程内缓存:名称 → ID + */ + private static final ThreadLocal> TL_NAME_TO_ID = new ThreadLocal<>(); + + /** + * 初始化当前线程的部门缓存(一次导出/导入只执行1次) + */ + private void initThreadCache() { + Map idMap = TL_ID_TO_NAME.get(); + if (CollUtil.isNotEmpty(idMap)) { + // 当前线程已加载,直接返回 + return; + } + + Map> deptPathToTreeMap = TreeBuildUtils.buildTreeNodeMap( + SpringUtils.getBean(ISysDeptService.class).selectDeptTreeList(new SysDeptBo()), + "/", + Tree::getName + ); + + // 构建互转映射 + Map idToName = new HashMap<>(); + Map nameToId = new HashMap<>(); + deptPathToTreeMap.forEach((name, treeNode) -> { + Long deptId = treeNode.getId(); + idToName.put(deptId, name); + nameToId.put(name, deptId); + }); + + // 设置到当前线程 + TL_ID_TO_NAME.set(idToName); + TL_NAME_TO_ID.set(nameToId); + } + + /** + * 指定 Java 类型:Long(部门ID) + */ + @Override + public Class supportJavaTypeKey() { + return Long.class; + } + + /** + * 指定 Excel 类型:字符串 + */ + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + /** + * 【导入】Excel 填写的部门完整路径 → 转为 ID + */ + @Override + public Long convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + String deptName = cellData.getStringValue(); + if (StringUtils.isBlank(deptName)) { + return null; + } + + // 初始化线程缓存(只执行一次) + initThreadCache(); + + // 从线程缓存中直接获取,不查库 + return TL_NAME_TO_ID.get().get(deptName); + } + + /** + * 【导出】部门 ID → 转为 Excel 显示的完整路径名称 + */ + @Override + public WriteCellData convertToExcelData(Long value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + if (value == null) { + return new WriteCellData<>(""); + } + + // 初始化线程缓存(只执行一次) + initThreadCache(); + + // 从线程缓存中直接获取,不查库 + String deptName = TL_ID_TO_NAME.get().getOrDefault(value, ""); + return new WriteCellData<>(deptName); + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/listener/DeptExcelOptions.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/listener/DeptExcelOptions.java new file mode 100644 index 000000000..8abcd0456 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/listener/DeptExcelOptions.java @@ -0,0 +1,38 @@ +package org.dromara.system.listener; + +import cn.hutool.core.lang.tree.Tree; +import lombok.RequiredArgsConstructor; +import org.dromara.common.core.utils.TreeBuildUtils; +import org.dromara.common.excel.core.ExcelOptionsProvider; +import org.dromara.system.domain.bo.SysDeptBo; +import org.dromara.system.service.ISysDeptService; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Excel 部门下拉选项数据源 + * + * @author AprilWind + */ +@RequiredArgsConstructor +@Component +public class DeptExcelOptions implements ExcelOptionsProvider { + + private final ISysDeptService deptService; + + /** + * 获取下拉选项数据 + * + * @return 下拉选项列表 + */ + @Override + public Set getOptions() { + List> trees = deptService.selectDeptTreeList(new SysDeptBo()); + Map> treeMap = TreeBuildUtils.buildTreeNodeMap(trees, "/", Tree::getName); + return treeMap.keySet(); + } + +}