Files
ccdi/doc/implementation-reports/employee-duplicate-detection-feature.md
2026-02-09 14:34:27 +08:00

8.3 KiB

员工导入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. 批量查询已存在的身份证号

在数据分类前,批量查询数据库中已存在的身份证号:

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());
    }
}

检查顺序:

  1. 先检查柜员号是否在数据库中存在
  2. 再检查柜员号是否在Excel文件内重复
  3. 最后检查身份证号是否在Excel文件内重复
  4. 只在记录成功添加到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.isNotEmptyObjects::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内双字段重复检测功能,主要改进包括:

  1. 批量查询优化: 添加getExistingIdCards方法批量查询已存在的身份证号
  2. 双字段检测: 同时检测柜员号和身份证号的Excel内重复
  3. 性能优化: 使用批量查询减少数据库访问次数
  4. 错误处理: 提供明确的错误提示信息
  5. 代码规范: 遵循若依框架编码规范,使用MyBatis Plus进行数据操作

该功能可以有效防止Excel文件内部的重复数据导入到数据库,提高数据质量和导入可靠性。