fix(system/dept): 修复系统用户导入提示【存在无效部门】且新增支持多级部门导入

Co-authored-by: kiki1373639299<zkai0106@163.com>



# message auto-generated for no-merge-commit merge:
merge import-uesr into dev

fix: 修复系统用户导入提示【存在无效部门】且新增支持多级部门导入

Created-by: kiki1373639299
Commit-by: kiki1373639299
Merged-by: Charles_7c
Description: <!--
  非常感谢您的 PR!在提交之前,请务必确保您 PR 的代码经过了完整测试,并且通过了代码规范检查。
-->

<!-- 在 [] 中输入 x 来勾选) -->

## PR 类型

<!-- 您的 PR 引入了哪种类型的变更? -->
<!-- 只支持选择一种类型,如果有多种类型,可以在更新日志中增加 “类型” 列。 -->

- [ ] 新 feature
- [X] Bug 修复
- [ ] 功能增强
- [ ] 文档变更
- [ ] 代码样式变更
- [ ] 重构
- [ ] 性能改进
- [ ] 单元测试
- [ ] CI/CD
- [ ] 其他

## PR 目的

<!-- 描述一下您的 PR 解决了什么问题。如果可以,请链接到相关 issues。 -->

## 解决方案

<!-- 详细描述您是如何解决的问题 -->

## PR 测试

<!-- 如果可以,请为您的 PR 添加或更新单元测试。 -->
<!-- 请描述一下您是如何测试 PR 的。例如:创建/更新单元测试或添加相关的截图。 -->

## Changelog

| 模块  | Changelog | Related issues |
|-----|-----------| -------------- |
|  系统管理 - 用户导入	   |       修复:解决用户导入时部门名称重复导致的"存在无效部门"错误,支持不同公司下同名部门区分	    |       Fixes #ICUHCT         |
|  系统管理 - 用户导入	   |       新增:支持多级部门导入功能,使用冒号(:)分隔层级,如公司A:研发部:前端组,优化错误提示和代码结构		    |                |

<!-- 如果有多种类型的变更,可以在变更日志表中增加 “类型” 列,该列的值与上方 “PR 类型” 相同。 -->
<!-- Related issues 格式为 Closes #<issue号>,或者 Fixes #<issue号>,或者 Resolves #<issue号>。 -->

## 其他信息

<!-- 请描述一下还有哪些注意事项。例如:如果引入了一个不向下兼容的变更,请描述其影响。 -->

## 提交前确认

- [X] PR 代码经过了完整测试,并且通过了代码规范检查
- [X] 已经完整填写 Changelog,并链接到了相关 issues
- [X] PR 代码将要提交到 dev 分支

See merge request: continew/continew-admin!13
This commit is contained in:
kiki1373639299
2025-09-21 19:00:26 +08:00
committed by Charles_7c
parent 3f01a5c84a
commit a39f6446d7
4 changed files with 138 additions and 12 deletions

View File

@@ -24,6 +24,7 @@ import top.continew.admin.system.model.resp.DeptResp;
import top.continew.starter.data.service.IService;
import java.util.List;
import java.util.Set;
/**
* 部门业务接口
@@ -55,5 +56,5 @@ public interface DeptService extends BaseService<DeptResp, DeptResp, DeptQuery,
* @param deptNames 名称列表
* @return 部门数量
*/
int countByNames(List<String> deptNames);
int countByNames(Set<String> deptNames);
}

View File

@@ -38,10 +38,7 @@ import top.continew.starter.data.enums.DatabaseType;
import top.continew.starter.data.util.MetaUtils;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.*;
/**
* 部门业务实现
@@ -128,7 +125,7 @@ public class DeptServiceImpl extends BaseServiceImpl<DeptMapper, DeptDO, DeptRes
}
@Override
public int countByNames(List<String> deptNames) {
public int countByNames(Set<String> deptNames) {
if (CollUtil.isEmpty(deptNames)) {
return 0;
}

View File

@@ -24,6 +24,8 @@ import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.CharsetUtil;
import java.util.HashMap;
import java.util.Map;
import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
@@ -264,10 +266,10 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes
List<String> roleNames = validRowList.stream().map(UserImportRowReq::getRoleName).distinct().toList();
int existRoleCount = roleService.countByNames(roleNames);
CheckUtils.throwIf(existRoleCount < roleNames.size(), "存在无效角色,请检查数据");
// 校验是否存在无效部门
List<String> deptNames = CollUtils.mapToList(validRowList, UserImportRowReq::getDeptName);
int existDeptCount = deptService.countByNames(deptNames);
CheckUtils.throwIf(existDeptCount < deptNames.size(), "存在无效部门,请检查数据");
// 校验是否存在无效部门(支持多级部门解析)
Set<String> deptNames = CollUtils.mapToSet(validRowList, UserImportRowReq::getDeptName);
int existDeptCount = countValidMultiLevelDepts(deptNames);
CheckUtils.throwIf(existDeptCount < deptNames.size(), "存在无效部门,请检查部门名称或部门层级是否正确");
// 查询重复用户
userImportResp
@@ -319,11 +321,11 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes
.distinct()
.toList());
Map<String, Long> roleMap = roleList.stream().collect(Collectors.toMap(RoleDO::getName, RoleDO::getId));
List<DeptDO> deptList = deptService.listByNames(importUserList.stream()
// 获取多级部门映射
Map<String, Long> deptMap = buildMultiLevelDeptMapping(importUserList.stream()
.map(UserImportRowReq::getDeptName)
.distinct()
.toList());
Map<String, Long> deptMap = deptList.stream().collect(Collectors.toMap(DeptDO::getName, DeptDO::getId));
// 批量操作数据库集合
List<UserDO> insertList = new ArrayList<>();
@@ -731,4 +733,130 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes
CheckUtils.throwIfNull(user, "用户不存在");
return user;
}
/**
* 统计有效的多级部门数量
* <p>
* 支持多级部门路径解析,使用冒号(:)作为层级分隔符
* 例如公司A:研发部:前端组 或 研发部
* </p>
*
* @param deptNames 部门名称集合
* @return 有效部门数量
*/
private int countValidMultiLevelDepts(Set<String> deptNames) {
CheckUtils.throwIfEmpty(deptNames, "部门名称集合不能为空");
int validCount = 0;
List<String> invalidDepts = new ArrayList<>();
for (String deptName : deptNames) {
try {
findDeptByHierarchicalPath(deptName);
validCount++;
} catch (Exception e) {
invalidDepts.add(deptName);
}
}
CheckUtils.throwIf(CollUtil.isNotEmpty(invalidDepts), "以下部门无效或存在歧义:{}", String.join(", ", invalidDepts));
return validCount;
}
/**
* 构建多级部门映射关系
* <p>
* 将部门名称列表转换为部门名称到ID的映射支持多级部门路径解析
* </p>
*
* @param deptNames 部门名称列表
* @return 部门名称到ID的映射
*/
private Map<String, Long> buildMultiLevelDeptMapping(List<String> deptNames) {
CheckUtils.throwIfEmpty(deptNames, "部门名称列表不能为空");
Map<String, Long> deptMap = new HashMap<>();
for (String deptName : deptNames) {
DeptDO dept = findDeptByHierarchicalPath(deptName);
CheckUtils.throwIfNull(dept, "部门 [{}] 不存在或存在歧义", deptName);
deptMap.put(deptName, dept.getId());
}
return deptMap;
}
/**
* 根据层级路径查找部门
* <p>
* 支持两种格式:
* <ul>
* <li>多级部门公司A:研发部:前端组</li>
* <li>单级部门:研发部</li>
* </ul>
* 使用冒号(:)作为层级分隔符,会逐级查找对应的部门
* </p>
*
* @param deptPath 部门路径
* @return 部门信息未找到时返回null
*/
private DeptDO findDeptByHierarchicalPath(String deptPath) {
CheckUtils.throwIfBlank(deptPath, "部门路径不能为空");
// 根据是否包含冒号选择处理方式
return deptPath.contains(":") ? findMultiLevelDept(deptPath) : findSingleLevelDept(deptPath.trim());
}
/**
* 查找多级部门
* <p>
* 从根部门开始逐级查找,确保部门层级关系正确
* </p>
*
* @param deptPath 多级部门路径
* @return 部门信息未找到时返回null
*/
private DeptDO findMultiLevelDept(String deptPath) {
String[] pathParts = deptPath.split(":");
CheckUtils.throwIf(pathParts.length == 0, "部门路径格式错误:{}", deptPath);
// 从根部门开始逐级查找
DeptDO currentDept = null;
Long parentId = 0L; // 根部门的parentId为null
for (String part : pathParts) {
String trimmedPart = part.trim();
CheckUtils.throwIfBlank(trimmedPart, "部门路径包含空名称:{}", deptPath);
// 查找当前层级下指定名称的部门
currentDept = deptService.lambdaQuery()
.eq(DeptDO::getName, trimmedPart)
.eq(DeptDO::getParentId, parentId)
.one();
CheckUtils.throwIfNull(currentDept, "找不到部门 [{}] 在路径 [{}] 中", trimmedPart, deptPath);
parentId = currentDept.getId(); // 更新父级ID为当前部门ID
}
return currentDept;
}
/**
* 查找单级部门
* <p>
* 当只提供部门名称时,检查是否存在多个同名部门
* 如果存在多个同名部门,则要求用户提供完整的层级路径
* </p>
*
* @param deptName 部门名称
* @return 部门信息未找到或存在歧义时返回null
*/
private DeptDO findSingleLevelDept(String deptName) {
// 查找所有同名部门
List<DeptDO> deptList = deptService.lambdaQuery().eq(DeptDO::getName, deptName).list();
CheckUtils.throwIfEmpty(deptList, "部门 [{}] 不存在", deptName);
CheckUtils.throwIf(deptList.size() > 1, "存在多个同名部门 [{}],请使用完整层级路径,如:公司名:{}", deptName, deptName);
return deptList.get(0);
}
}