# 员工导入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. 批量查询已存在的身份证号 在数据分类前,批量查询数据库中已存在的身份证号: ```java Set existingIds = getExistingEmployeeIds(excelList); Set existingIdCards = getExistingIdCards(excelList); ``` **优点**: - 减少数据库查询次数,提高性能 - 避免逐条查询导致的N+1问题 ### 2. 添加Excel内处理跟踪集合 ```java Set processedEmployeeIds = new HashSet<>(); Set processedIdCards = new HashSet<>(); ``` **作用**: - 跟踪Excel文件中已处理的柜员号 - 跟踪Excel文件中已处理的身份证号 - 用于检测Excel内部的重复数据 ### 3. 双字段重复检测逻辑 在逐条处理时,按以下顺序检查: ```java 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()); } } ``` **检查顺序**: 1. 先检查柜员号是否在数据库中存在 2. 再检查柜员号是否在Excel文件内重复 3. 最后检查身份证号是否在Excel文件内重复 4. 只在记录成功添加到newRecords后才标记为已处理 ### 4. 更新validateEmployeeData方法 **修改前**: ```java public void validateEmployeeData(CcdiEmployeeAddDTO addDTO, Boolean isUpdateSupport, Set existingIds) ``` **修改后**: ```java public void validateEmployeeData(CcdiEmployeeAddDTO addDTO, Boolean isUpdateSupport, Set existingIds, Set existingIdCards) ``` **身份证号唯一性检查优化**: ```java // 导入场景:如果柜员号不存在,才检查身份证号唯一性 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 ``` **期望结果**: - 所有记录都成功导入 ## 代码对比 ### 修改前 ```java // 批量查询已存在的柜员号 Set 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); } } ``` ### 修改后 ```java // 批量查询已存在的柜员号和身份证号 Set existingIds = getExistingEmployeeIds(excelList); Set existingIdCards = getExistingIdCards(excelList); // 用于跟踪Excel文件内已处理的主键 Set processedEmployeeIds = new HashSet<>(); Set 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编译验证,无语法错误: ```bash mvn clean compile -DskipTests ``` 编译结果: BUILD SUCCESS ## 测试脚本 测试脚本位置: `D:\ccdi\ccdi\doc\test-scripts\test_employee_duplicate_detection.py` ## 总结 本次实现成功为员工导入模块添加了Excel内双字段重复检测功能,主要改进包括: 1. **批量查询优化**: 添加`getExistingIdCards`方法批量查询已存在的身份证号 2. **双字段检测**: 同时检测柜员号和身份证号的Excel内重复 3. **性能优化**: 使用批量查询减少数据库访问次数 4. **错误处理**: 提供明确的错误提示信息 5. **代码规范**: 遵循若依框架编码规范,使用MyBatis Plus进行数据操作 该功能可以有效防止Excel文件内部的重复数据导入到数据库,提高数据质量和导入可靠性。