263 lines
8.3 KiB
Markdown
263 lines
8.3 KiB
Markdown
|
|
# 员工导入Excel内双字段重复检测功能实现报告
|
||
|
|
|
||
|
|
## 功能概述
|
||
|
|
为员工导入模块添加Excel内双字段(柜员号和身份证号)重复检测功能,防止同一Excel文件中出现重复数据导入到数据库。
|
||
|
|
|
||
|
|
## 实现时间
|
||
|
|
2026-02-09
|
||
|
|
|
||
|
|
## 实现位置
|
||
|
|
- 文件: `D:\ccdi\ccdi\ruoyi-ccdi\src\main\java\com\ruoyi\ccdi\service\impl\CcdiEmployeeImportServiceImpl.java`
|
||
|
|
- 方法: `importEmployeeAsync` (第43-126行)
|
||
|
|
|
||
|
|
## 核心功能
|
||
|
|
|
||
|
|
### 1. 批量查询已存在的身份证号
|
||
|
|
在数据分类前,批量查询数据库中已存在的身份证号:
|
||
|
|
```java
|
||
|
|
Set<Long> existingIds = getExistingEmployeeIds(excelList);
|
||
|
|
Set<String> existingIdCards = getExistingIdCards(excelList);
|
||
|
|
```
|
||
|
|
|
||
|
|
**优点**:
|
||
|
|
- 减少数据库查询次数,提高性能
|
||
|
|
- 避免逐条查询导致的N+1问题
|
||
|
|
|
||
|
|
### 2. 添加Excel内处理跟踪集合
|
||
|
|
```java
|
||
|
|
Set<Long> processedEmployeeIds = new HashSet<>();
|
||
|
|
Set<String> 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<Long> existingIds)
|
||
|
|
```
|
||
|
|
|
||
|
|
**修改后**:
|
||
|
|
```java
|
||
|
|
public void validateEmployeeData(CcdiEmployeeAddDTO addDTO, Boolean isUpdateSupport, Set<Long> existingIds, Set<String> 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<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);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 修改后
|
||
|
|
```java
|
||
|
|
// 批量查询已存在的柜员号和身份证号
|
||
|
|
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编译验证,无语法错误:
|
||
|
|
```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文件内部的重复数据导入到数据库,提高数据质量和导入可靠性。
|