- Maven 模块从 ruoyi-ccdi 重命名为 ruoyi-info-collection - Java 包名从 com.ruoyi.ccdi 改为 com.ruoyi.info.collection - MyBatis XML 命名空间同步更新 - 保留数据库表名、API URL、权限标识中的 ccdi 前缀 - 更新项目文档中的模块引用
8.3 KiB
8.3 KiB
员工导入Excel内双字段重复检测功能实现报告
功能概述
为员工导入模块添加Excel内双字段(柜员号和身份证号)重复检测功能,防止同一Excel文件中出现重复数据导入到数据库。
实现时间
2026-02-09
实现位置
- 文件:
D:\ccdi\ccdi\ruoyi-info-collection\src\main\java\com\ruoyi\ccdi\service\impl\CcdiEmployeeImportServiceImpl.java - 方法:
importEmployeeAsync(第43-126行)
核心功能
1. 批量查询已存在的身份证号
在数据分类前,批量查询数据库中已存在的身份证号:
Set<Long> existingIds = getExistingEmployeeIds(excelList);
Set<String> existingIdCards = getExistingIdCards(excelList);
优点:
- 减少数据库查询次数,提高性能
- 避免逐条查询导致的N+1问题
2. 添加Excel内处理跟踪集合
Set<Long> processedEmployeeIds = new HashSet<>();
Set<String> processedIdCards = new HashSet<>();
作用:
- 跟踪Excel文件中已处理的柜员号
- 跟踪Excel文件中已处理的身份证号
- 用于检测Excel内部的重复数据
3. 双字段重复检测逻辑
在逐条处理时,按以下顺序检查:
if (existingIds.contains(excel.getEmployeeId())) {
// 柜员号在数据库中已存在
if (isUpdateSupport) {
updateRecords.add(employee);
} else {
throw new RuntimeException("柜员号已存在且未启用更新支持");
}
} else if (processedEmployeeIds.contains(excel.getEmployeeId())) {
// 柜员号在Excel文件中重复
throw new RuntimeException(String.format("柜员号[%d]在导入文件中重复,已跳过此条记录", excel.getEmployeeId()));
} else if (StringUtils.isNotEmpty(excel.getIdCard()) &&
processedIdCards.contains(excel.getIdCard())) {
// 身份证号在Excel文件中重复
throw new RuntimeException(String.format("身份证号[%s]在导入文件中重复,已跳过此条记录", excel.getIdCard()));
} else {
// 无重复,添加到新记录
newRecords.add(employee);
// 只在成功时标记
if (excel.getEmployeeId() != null) {
processedEmployeeIds.add(excel.getEmployeeId());
}
if (StringUtils.isNotEmpty(excel.getIdCard())) {
processedIdCards.add(excel.getIdCard());
}
}
检查顺序:
- 先检查柜员号是否在数据库中存在
- 再检查柜员号是否在Excel文件内重复
- 最后检查身份证号是否在Excel文件内重复
- 只在记录成功添加到newRecords后才标记为已处理
4. 更新validateEmployeeData方法
修改前:
public void validateEmployeeData(CcdiEmployeeAddDTO addDTO, Boolean isUpdateSupport, Set<Long> existingIds)
修改后:
public void validateEmployeeData(CcdiEmployeeAddDTO addDTO, Boolean isUpdateSupport, Set<Long> existingIds, Set<String> existingIdCards)
身份证号唯一性检查优化:
// 导入场景:如果柜员号不存在,才检查身份证号唯一性
if (!existingIds.contains(addDTO.getEmployeeId())) {
// 使用批量查询的结果检查身份证号唯一性
if (existingIdCards != null && existingIdCards.contains(addDTO.getIdCard())) {
throw new RuntimeException("该身份证号已存在");
}
}
优点:
- 使用批量查询结果,避免逐条查询
- 提高导入性能
技术特点
1. 双字段同时检测
同时检测柜员号(Long类型)和身份证号(String类型)的Excel内重复
2. 检查顺序合理
- 先检查数据库重复(避免无效数据处理)
- 再检查Excel内重复(防止重复导入)
- 最后标记已处理(只在成功后标记)
3. 空值处理
使用StringUtils.isNotEmpty和Objects::nonNull进行空值检查,避免空指针异常
4. 错误消息明确
- 柜员号重复: "柜员号[XXX]在导入文件中重复,已跳过此条记录"
- 身份证号重复: "身份证号[XXX]在导入文件中重复,已跳过此条记录"
5. 性能优化
- 批量查询数据库中已存在的柜员号和身份证号
- 使用HashSet进行O(1)复杂度的重复检测
- 减少数据库查询次数
测试场景
场景1: 柜员号在Excel内重复
输入:
柜员号 姓名 身份证号
1001 张三 110101199001011234
1001 李四 110101199001011235
期望结果:
- 第一条记录成功导入
- 第二条记录失败,错误信息: "柜员号[1001]在导入文件中重复,已跳过此条记录"
场景2: 身份证号在Excel内重复
输入:
柜员号 姓名 身份证号
1001 张三 110101199001011234
1002 李四 110101199001011234
期望结果:
- 第一条记录成功导入
- 第二条记录失败,错误信息: "身份证号[110101199001011234]在导入文件中重复,已跳过此条记录"
场景3: 柜员号和身份证号同时重复
输入:
柜员号 姓名 身份证号
1001 张三 110101199001011234
1001 张三 110101199001011234
期望结果:
- 第一条记录成功导入
- 第二条记录失败,错误信息: "柜员号[1001]在导入文件中重复,已跳过此条记录"
场景4: 正常导入(无重复)
输入:
柜员号 姓名 身份证号
1001 张三 110101199001011234
1002 李四 110101199001011235
1003 王五 110101199001011236
期望结果:
- 所有记录都成功导入
代码对比
修改前
// 批量查询已存在的柜员号
Set<Long> existingIds = getExistingEmployeeIds(excelList);
// 分类数据
for (int i = 0; i < excelList.size(); i++) {
// ...
validateEmployeeData(addDTO, isUpdateSupport, existingIds);
if (existingIds.contains(excel.getEmployeeId())) {
if (isUpdateSupport) {
updateRecords.add(employee);
} else {
throw new RuntimeException("柜员号已存在且未启用更新支持");
}
} else {
newRecords.add(employee);
}
}
修改后
// 批量查询已存在的柜员号和身份证号
Set<Long> existingIds = getExistingEmployeeIds(excelList);
Set<String> existingIdCards = getExistingIdCards(excelList);
// 用于跟踪Excel文件内已处理的主键
Set<Long> processedEmployeeIds = new HashSet<>();
Set<String> processedIdCards = new HashSet<>();
// 分类数据
for (int i = 0; i < excelList.size(); i++) {
// ...
validateEmployeeData(addDTO, isUpdateSupport, existingIds, existingIdCards);
if (existingIds.contains(excel.getEmployeeId())) {
if (isUpdateSupport) {
updateRecords.add(employee);
} else {
throw new RuntimeException("柜员号已存在且未启用更新支持");
}
} else if (processedEmployeeIds.contains(excel.getEmployeeId())) {
throw new RuntimeException(String.format("柜员号[%d]在导入文件中重复,已跳过此条记录", excel.getEmployeeId()));
} else if (StringUtils.isNotEmpty(excel.getIdCard()) &&
processedIdCards.contains(excel.getIdCard())) {
throw new RuntimeException(String.format("身份证号[%s]在导入文件中重复,已跳过此条记录", excel.getIdCard()));
} else {
newRecords.add(employee);
// 只在成功时标记
if (excel.getEmployeeId() != null) {
processedEmployeeIds.add(excel.getEmployeeId());
}
if (StringUtils.isNotEmpty(excel.getIdCard())) {
processedIdCards.add(excel.getIdCard());
}
}
}
参考实现
本功能参考了中介人员导入模块的双字段重复检测实现:
- 文件:
CcdiIntermediaryEntityImportServiceImpl.java - 关键方法:
importEntityAsync
编译验证
已通过Maven编译验证,无语法错误:
mvn clean compile -DskipTests
编译结果: BUILD SUCCESS
测试脚本
测试脚本位置: D:\ccdi\ccdi\doc\test-scripts\test_employee_duplicate_detection.py
总结
本次实现成功为员工导入模块添加了Excel内双字段重复检测功能,主要改进包括:
- 批量查询优化: 添加
getExistingIdCards方法批量查询已存在的身份证号 - 双字段检测: 同时检测柜员号和身份证号的Excel内重复
- 性能优化: 使用批量查询减少数据库访问次数
- 错误处理: 提供明确的错误提示信息
- 代码规范: 遵循若依框架编码规范,使用MyBatis Plus进行数据操作
该功能可以有效防止Excel文件内部的重复数据导入到数据库,提高数据质量和导入可靠性。