# 导入逻辑优化实施计划 > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **目标:** 优化员工信息、中介库(个人/实体)、招聘信息的导入功能,从"存在则更新"改为"先删除后插入"策略。 **架构:** 三阶段流程:数据验证 → 批量删除 → 批量插入。所有操作在一个 @Transactional 事务中执行。 **技术栈:** Spring Boot 3.5.8, MyBatis Plus 3.5.10, MySQL 8.2.0 --- ## 模块 1:员工信息管理(验证方案) 此模块用于验证新逻辑的正确性,成功后应用到其他模块。 ### Task 1.1:添加批量删除方法到 Mapper 接口 **文件:** - 修改:`ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/mapper/CcdiEmployeeMapper.java` **Step 1: 在 Mapper 接口中添加方法声明** 在 `CcdiEmployeeMapper.java` 的接口中添加新方法(在现有方法后面,`insertBatch` 方法之后): ```java /** * 根据身份证号批量删除员工数据 * * @param idCards 身份证号列表 * @return 删除行数 */ int deleteBatchByIdCard(@Param("list") List idCards); ``` **Step 2: 保存文件** 无需测试,这是接口声明。 **Step 3: 提交** ```bash git add ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/mapper/CcdiEmployeeMapper.java git commit -m "feat(employee): 添加批量删除方法声明" ``` --- ### Task 1.2:在 Mapper XML 中实现批量删除 SQL **文件:** - 修改:`ruoyi-info-collection/src/main/resources/mapper/ccdi/CcdiEmployeeMapper.xml` **Step 1: 在 XML 文件中添加删除 SQL** 在 `CcdiEmployeeMapper.xml` 中,在 `insertBatch` 方法之后添加: ```xml DELETE FROM ccdi_employee WHERE id_card IN #{item} ``` **Step 2: 保存文件** 无需测试,SQL 配置。 **Step 3: 提交** ```bash git add ruoyi-info-collection/src/main/resources/mapper/ccdi/CcdiEmployeeMapper.xml git commit -m "feat(employee): 实现批量删除SQL" ``` --- ### Task 1.3:重构员工导入方法(先删后插逻辑) - [x] **已完成** (commit: ebe4fd7) **文件:** - 修改:`ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/service/impl/CcdiEmployeeServiceImpl.java` - 目标方法:`importEmployee` (第 172-311 行) **Step 1: 备份原方法** 先注释掉原有的 `importEmployee` 方法(保留参考)。 **Step 2: 实现新的导入逻辑** 将整个 `importEmployee` 方法替换为: ```java /** * 导入员工数据(先删后插模式) * * @param excelList Excel实体列表 * @param isUpdateSupport 是否更新支持(参数保留以保持兼容性,不再使用) * @return 结果 */ @Override @Transactional(rollbackFor = Exception.class) public String importEmployee(List excelList, Boolean isUpdateSupport) { if (StringUtils.isNull(excelList) || excelList.isEmpty()) { return "至少需要一条数据"; } // 第一阶段:数据验证和收集 List validEmployees = new ArrayList<>(); List errorMessages = new ArrayList<>(); Set idCards = new HashSet<>(); for (CcdiEmployeeExcel excel : excelList) { try { // 转换为AddDTO CcdiEmployeeAddDTO addDTO = new CcdiEmployeeAddDTO(); BeanUtils.copyProperties(excel, addDTO); // 验证必填字段和数据格式 validateEmployeeDataBasic(addDTO); // 检查导入数据内部是否重复 if (!idCards.add(addDTO.getIdCard())) { throw new RuntimeException("导入文件中该身份证号重复"); } // 转换为实体,设置审计字段 CcdiEmployee employee = new CcdiEmployee(); BeanUtils.copyProperties(addDTO, employee); employee.setCreateBy("导入"); employee.setUpdateBy("导入"); validEmployees.add(employee); } catch (Exception e) { errorMessages.add(String.format("%s 导入失败:%s", excel.getName(), e.getMessage())); } } // 第二阶段:批量删除已存在的记录 if (!validEmployees.isEmpty()) { employeeMapper.deleteBatchByIdCard(new ArrayList<>(idCards)); } // 第三阶段:批量插入所有数据 if (!validEmployees.isEmpty()) { employeeMapper.insertBatch(validEmployees); } // 第四阶段:返回结果 if (!errorMessages.isEmpty()) { StringBuilder failureMsg = new StringBuilder(); failureMsg.append("很抱歉,导入完成!成功 ") .append(validEmployees.size()) .append(" 条,失败 ") .append(errorMessages.size()) .append(" 条,错误如下:"); for (int i = 0; i < errorMessages.size(); i++) { failureMsg.append("
") .append(i + 1) .append("、") .append(errorMessages.get(i)); } throw new RuntimeException(failureMsg.toString()); } return "恭喜您,数据已全部导入成功!共 " + validEmployees.size() + " 条"; } ``` **Step 2: 保存文件** 无需测试,代码修改。 **Step 3: 提交** ```bash git add ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/service/impl/CcdiEmployeeServiceImpl.java git commit -m "refactor(employee): 重构导入方法为先删后插模式" ``` --- ### Task 1.4:生成员工模块测试脚本 **文件:** - 创建:`test/test_employee_import_delete.ps1` **Step 1: 创建测试脚本** 创建 PowerShell 测试脚本: ```powershell # 员工导入功能测试脚本(先删后插模式) # 目的:验证新的导入逻辑是否正常工作 # 配置 $BaseUrl = "http://localhost:8080" $LoginUrl = "$BaseUrl/login/test" $ImportUrl = "$BaseUrl/ccdi/employee/importData" # 测试账号 $Username = "admin" $Password = "admin123" # 日志文件 $LogFile = "test/employee_import_test_$(Get-Date -Format 'yyyyMMdd_HHmmss').txt" # 开始记录日志 function Write-Log { param([string]$Message) $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $logMessage = "[$timestamp] $Message" Write-Host $logMessage Add-Content -Path $LogFile -Value $logMessage } Write-Log "==========================================" Write-Log "员工导入功能测试(先删后插模式)" Write-Log "==========================================" Write-Log "" # 步骤1:登录获取Token Write-Log "步骤1:登录获取Token..." try { $loginBody = @{ username = $Username password = $Password } | ConvertTo-Json $loginResponse = Invoke-RestMethod -Uri $LoginUrl -Method Post -Body $loginBody -ContentType "application/json" if ($loginResponse.code -eq 200) { $Token = $loginResponse.token Write-Log "✓ 登录成功" } else { Write-Log "✗ 登录失败: $($loginResponse.msg)" exit 1 } } catch { Write-Log "✗ 登录请求失败: $_" exit 1 } Write-Log "" # 步骤2:准备测试数据 Write-Log "步骤2:准备测试数据..." $testData = @{ list = @( @{ employeeId = 1001 name = "测试用户A" deptId = 103 idCard = "110101199001011234" phone = "13800138001" hireDate = "2020-01-01" status = "0" }, @{ employeeId = 1002 name = "测试用户B" deptId = 103 idCard = "110101199001022345" phone = "13800138002" hireDate = "2020-01-02" status = "0" } ) } | ConvertTo-Json -Depth 10 Write-Log "测试数据准备完成(2条记录)" Write-Log "" # 步骤3:执行导入 Write-Log "步骤3:执行导入..." try { $headers = @{ "Authorization" = "Bearer $Token" } $importResponse = Invoke-RestMethod -Uri $ImportUrl -Method Post -Headers $headers -Body $testData -ContentType "application/json" Write-Log "" Write-Log "==========================================" Write-Log "导入结果:" Write-Log "==========================================" Write-Log "响应代码: $($importResponse.code)" Write-Log "响应消息: $($importResponse.msg)" if ($importResponse.code -eq 200) { Write-Log "" Write-Log "✓ 导入测试成功!" } else { Write-Log "" Write-Log "✗ 导入测试失败!" } } catch { Write-Log "" Write-Log "✗ 导入请求失败:" Write-Log "错误信息: $_" } Write-Log "" Write-Log "==========================================" Write-Log "测试完成" Write-Log "详细日志: $LogFile" Write-Log "==========================================" ``` **Step 2: 保存文件** **Step 3: 提交** ```bash git add test/test_employee_import_delete.ps1 git commit -m "test(employee): 添加导入功能测试脚本" ``` --- ### Task 1.5:测试员工模块导入功能 **Step 1: 启动后端服务** 如果后端服务未启动,先启动: ```bash mvn spring-boot:run ``` **Step 2: 在新终端运行测试脚本** ```powershell cd D:\ccdi\ccdi .\test\test_employee_import_delete.ps1 ``` **Step 3: 验证结果** 检查: - ✅ 测试脚本显示 "导入测试成功" - ✅ 日志文件显示响应代码 200 - ✅ 数据库中数据正确插入 **Step 4: 如果测试通过,提交工作** ```bash # 所有改动已提交,无需额外操作 ``` --- ## 模块 2:中介库个人管理 ### Task 2.1:添加批量删除方法到 Mapper 接口 - [x] **已完成** (commit: ba8eedc) **文件:** - 修改:`ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/mapper/CcdiBizIntermediaryMapper.java` **Step 1: 在 Mapper 接口中添加方法声明** ```java /** * 根据个人证件号批量删除中介库个人数据 * * @param personIds 个人证件号列表 * @return 删除行数 */ int deleteBatchByPersonId(@Param("list") List personIds); ``` **Step 2: 提交** ```bash git add ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/mapper/CcdiBizIntermediaryMapper.java git commit -m "feat(intermediary): 添加个人批量删除方法声明" ``` --- ### Task 2.2:在 Mapper XML 中实现批量删除 SQL **文件:** - 修改:`ruoyi-info-collection/src/main/resources/mapper/ccdi/CcdiBizIntermediaryMapper.xml` **Step 1: 在 XML 文件中添加删除 SQL** ```xml DELETE FROM ccdi_biz_intermediary WHERE person_id IN #{item} ``` **Step 2: 提交** ```bash git add ruoyi-info-collection/src/main/resources/mapper/ccdi/CcdiBizIntermediaryMapper.xml git commit -m "feat(intermediary): 实现个人批量删除SQL" ``` --- ### Task 2.3:重构中介库个人导入方法 **文件:** - 修改:`ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/service/impl/CcdiIntermediaryServiceImpl.java` - 目标方法:`importIntermediaryPerson` **Step 1: 找到 `importIntermediaryPerson` 方法** 在 `CcdiIntermediaryServiceImpl.java` 中定位方法。 **Step 2: 重构方法逻辑** 参考员工模块的模式,重构为先删后插: ```java @Override @Transactional(rollbackFor = Exception.class) public String importIntermediaryPerson(List excelList, Boolean isUpdateSupport) { if (StringUtils.isNull(excelList) || excelList.isEmpty()) { return "至少需要一条数据"; } // 第一阶段:数据验证和收集 List validList = new ArrayList<>(); List errorMessages = new ArrayList<>(); Set personIds = new HashSet<>(); for (CcdiIntermediaryPersonExcel excel : excelList) { try { // 转换并验证 CcdiIntermediaryPersonAddDTO addDTO = new CcdiIntermediaryPersonAddDTO(); BeanUtils.copyProperties(excel, addDTO); // 调用验证方法(需要根据实际情况调整) // validateIntermediaryPersonDataBasic(addDTO); // 检查导入数据内部是否重复 if (!personIds.add(addDTO.getPersonId())) { throw new RuntimeException("导入文件中该个人证件号重复"); } // 转换为实体,设置审计字段 CcdiBizIntermediary entity = new CcdiBizIntermediary(); BeanUtils.copyProperties(addDTO, entity); entity.setCreateBy("导入"); entity.setUpdateBy("导入"); validList.add(entity); } catch (Exception e) { errorMessages.add(String.format("%s 导入失败:%s", excel.getName(), e.getMessage())); } } // 第二阶段:批量删除已存在的记录 if (!validList.isEmpty()) { ccdiBizIntermediaryMapper.deleteBatchByPersonId(new ArrayList<>(personIds)); } // 第三阶段:批量插入所有数据 if (!validList.isEmpty()) { ccdiBizIntermediaryMapper.insertBatch(validList); } // 第四阶段:返回结果 if (!errorMessages.isEmpty()) { StringBuilder failureMsg = new StringBuilder(); failureMsg.append("很抱歉,导入完成!成功 ") .append(validList.size()) .append(" 条,失败 ") .append(errorMessages.size()) .append(" 条,错误如下:"); for (int i = 0; i < errorMessages.size(); i++) { failureMsg.append("
") .append(i + 1) .append("、") .append(errorMessages.get(i)); } throw new RuntimeException(failureMsg.toString()); } return "恭喜您,数据已全部导入成功!共 " + validList.size() + " 条"; } ``` **注意**:需要根据实际的 DTO 类名、验证方法名、Mapper 注入名进行调整。 **Step 3: 提交** ```bash git add ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/service/impl/CcdiIntermediaryServiceImpl.java git commit -m "refactor(intermediary): 重构个人导入方法为先删后插模式" ``` --- ## 模块 3:中介库实体管理 ### Task 3.1:添加批量删除方法到 Mapper 接口 **文件:** - 修改:`ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/mapper/CcdiEnterpriseBaseInfoMapper.java` **Step 1: 在 Mapper 接口中添加方法声明** ```java /** * 根据统一社会信用代码批量删除中介库实体数据 * * @param socialCreditCodes 统一社会信用代码列表 * @return 删除行数 */ int deleteBatchBySocialCreditCode(@Param("list") List socialCreditCodes); ``` **Step 2: 提交** ```bash git add ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/mapper/CcdiEnterpriseBaseInfoMapper.java git commit -m "feat(intermediary): 添加实体批量删除方法声明" ``` --- ### Task 3.2:在 Mapper XML 中实现批量删除 SQL **文件:** - 修改:`ruoyi-info-collection/src/main/resources/mapper/ccdi/CcdiEnterpriseBaseInfoMapper.xml` **Step 1: 在 XML 文件中添加删除 SQL** ```xml DELETE FROM ccdi_enterprise_base_info WHERE social_credit_code IN #{item} ``` **Step 2: 提交** ```bash git add ruoyi-info-collection/src/main/resources/mapper/ccdi/CcdiEnterpriseBaseInfoMapper.xml git commit -m "feat(intermediary): 实现实体批量删除SQL" ``` --- ### Task 3.3:重构中介库实体导入方法 **文件:** - 修改:`ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/service/impl/CcdiIntermediaryServiceImpl.java` - 目标方法:`importIntermediaryEntity` **Step 1: 找到 `importIntermediaryEntity` 方法** 在 `CcdiIntermediaryServiceImpl.java` 中定位方法。 **Step 2: 重构方法逻辑** 参考个人模块的模式,重构为先删后插: ```java @Override @Transactional(rollbackFor = Exception.class) public String importIntermediaryEntity(List excelList, Boolean isUpdateSupport) { if (StringUtils.isNull(excelList) || excelList.isEmpty()) { return "至少需要一条数据"; } // 第一阶段:数据验证和收集 List validList = new ArrayList<>(); List errorMessages = new ArrayList<>(); Set socialCreditCodes = new HashSet<>(); for (CcdiIntermediaryEntityExcel excel : excelList) { try { // 转换并验证 CcdiIntermediaryEntityAddDTO addDTO = new CcdiIntermediaryEntityAddDTO(); BeanUtils.copyProperties(excel, addDTO); // 调用验证方法(需要根据实际情况调整) // validateIntermediaryEntityDataBasic(addDTO); // 检查导入数据内部是否重复 if (!socialCreditCodes.add(addDTO.getSocialCreditCode())) { throw new RuntimeException("导入文件中该统一社会信用代码重复"); } // 转换为实体,设置审计字段 CcdiEnterpriseBaseInfo entity = new CcdiEnterpriseBaseInfo(); BeanUtils.copyProperties(addDTO, entity); entity.setCreateBy("导入"); entity.setUpdateBy("导入"); validList.add(entity); } catch (Exception e) { errorMessages.add(String.format("%s 导入失败:%s", excel.getEnterpriseName(), e.getMessage())); } } // 第二阶段:批量删除已存在的记录 if (!validList.isEmpty()) { ccdiEnterpriseBaseInfoMapper.deleteBatchBySocialCreditCode(new ArrayList<>(socialCreditCodes)); } // 第三阶段:批量插入所有数据 if (!validList.isEmpty()) { ccdiEnterpriseBaseInfoMapper.insertBatch(validList); } // 第四阶段:返回结果 if (!errorMessages.isEmpty()) { StringBuilder failureMsg = new StringBuilder(); failureMsg.append("很抱歉,导入完成!成功 ") .append(validList.size()) .append(" 条,失败 ") .append(errorMessages.size()) .append(" 条,错误如下:"); for (int i = 0; i < errorMessages.size(); i++) { failureMsg.append("
") .append(i + 1) .append("、") .append(errorMessages.get(i)); } throw new RuntimeException(failureMsg.toString()); } return "恭喜您,数据已全部导入成功!共 " + validList.size() + " 条"; } ``` **注意**:需要根据实际的 DTO 类名、验证方法名、Mapper 注入名进行调整。 **Step 3: 提交** ```bash git add ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/service/impl/CcdiIntermediaryServiceImpl.java git commit -m "refactor(intermediary): 重构实体导入方法为先删后插模式" ``` --- ## 模块 4:员工招聘信息管理 ### Task 4.1:添加批量删除方法到 Mapper 接口 **文件:** - 修改:`ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/mapper/CcdiStaffRecruitmentMapper.java` **Step 1: 在 Mapper 接口中添加方法声明** ```java /** * 根据招聘项目编号批量删除招聘信息数据 * * @param recruitIds 招聘项目编号列表 * @return 删除行数 */ int deleteBatchByRecruitId(@Param("list") List recruitIds); ``` **Step 2: 提交** ```bash git add ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/mapper/CcdiStaffRecruitmentMapper.java git commit -m "feat(recruitment): 添加批量删除方法声明" ``` --- ### Task 4.2:在 Mapper XML 中实现批量删除 SQL **文件:** - 修改:`ruoyi-info-collection/src/main/resources/mapper/ccdi/CcdiStaffRecruitmentMapper.xml` **Step 1: 在 XML 文件中添加删除 SQL** ```xml DELETE FROM ccdi_staff_recruitment WHERE recruit_id IN #{item} ``` **Step 2: 提交** ```bash git add ruoyi-info-collection/src/main/resources/mapper/ccdi/CcdiStaffRecruitmentMapper.xml git commit -m "feat(recruitment): 实现批量删除SQL" ``` --- ### Task 4.3:重构招聘信息导入方法 **文件:** - 修改:`ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffRecruitmentServiceImpl.java` - 目标方法:`importRecruitment` **Step 1: 找到 `importRecruitment` 方法** 在 `CcdiStaffRecruitmentServiceImpl.java` 中定位方法。 **Step 2: 重构方法逻辑** 参考员工模块的模式,重构为先删后插: ```java @Override @Transactional(rollbackFor = Exception.class) public String importRecruitment(List excelList, Boolean isUpdateSupport) { if (StringUtils.isNull(excelList) || excelList.isEmpty()) { return "至少需要一条数据"; } // 第一阶段:数据验证和收集 List validList = new ArrayList<>(); List errorMessages = new ArrayList<>(); Set recruitIds = new HashSet<>(); for (CcdiStaffRecruitmentExcel excel : excelList) { try { // 转换并验证 CcdiStaffRecruitmentAddDTO addDTO = new CcdiStaffRecruitmentAddDTO(); BeanUtils.copyProperties(excel, addDTO); // 调用验证方法(需要根据实际情况调整) // validateRecruitmentDataBasic(addDTO); // 检查导入数据内部是否重复 if (!recruitIds.add(addDTO.getRecruitId())) { throw new RuntimeException("导入文件中该招聘项目编号重复"); } // 转换为实体,设置审计字段 CcdiStaffRecruitment entity = new CcdiStaffRecruitment(); BeanUtils.copyProperties(addDTO, entity); entity.setCreateBy("导入"); entity.setUpdateBy("导入"); validList.add(entity); } catch (Exception e) { errorMessages.add(String.format("%s 导入失败:%s", excel.getRecruitName(), e.getMessage())); } } // 第二阶段:批量删除已存在的记录 if (!validList.isEmpty()) { ccdiStaffRecruitmentMapper.deleteBatchByRecruitId(new ArrayList<>(recruitIds)); } // 第三阶段:批量插入所有数据 if (!validList.isEmpty()) { ccdiStaffRecruitmentMapper.insertBatch(validList); } // 第四阶段:返回结果 if (!errorMessages.isEmpty()) { StringBuilder failureMsg = new StringBuilder(); failureMsg.append("很抱歉,导入完成!成功 ") .append(validList.size()) .append(" 条,失败 ") .append(errorMessages.size()) .append(" 条,错误如下:"); for (int i = 0; i < errorMessages.size(); i++) { failureMsg.append("
") .append(i + 1) .append("、") .append(errorMessages.get(i)); } throw new RuntimeException(failureMsg.toString()); } return "恭喜您,数据已全部导入成功!共 " + validList.size() + " 条"; } ``` **注意**:需要根据实际的 DTO 类名、验证方法名、Mapper 注入名进行调整。 **Step 3: 提交** ```bash git add ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffRecruitmentServiceImpl.java git commit -m "refactor(recruitment): 重构导入方法为先删后插模式" ``` --- ## 模块 5:清理和文档 ### Task 5.1:移除不再使用的批量更新方法(如果存在) **文件:** - 检查:各模块的 Mapper XML 和 Mapper 接口 **Step 1: 检查是否存在 updateBatch 方法** 在以下文件中搜索 `updateBatch`: - `CcdiEmployeeMapper.xml` - `CcdiBizIntermediaryMapper.xml` - `CcdiEnterpriseBaseInfoMapper.xml` - `CcdiStaffRecruitmentMapper.xml` **Step 2: 如果存在,删除 updateBatch 方法** 删除不再使用的批量更新 SQL 和接口声明。 **Step 3: 提交** ```bash git commit -am "refactor: 移除不再使用的批量更新方法" ``` --- ### Task 5.2:更新 API 文档 **文件:** - 修改:`doc/api/ccdi_staff_recruitment_api.md`(如果存在) **Step 1: 更新导入接口文档** 在 API 文档中说明新的导入逻辑: - 采用"先删除后插入"策略 - `isUpdateSupport` 参数保留以保持兼容性,但不再使用 - 所有审计字段(create_time, update_time 等)会被重置为当前时间 **Step 2: 提交** ```bash git add doc/api/ git commit -m "docs: 更新导入接口文档说明" ``` --- ## 完成检查清单 在完成所有任务后,确认以下事项: - [ ] 员工信息模块测试通过 - [ ] 中介库个人模块功能正常 - [ ] 中介库实体模块功能正常 - [ ] 招聘信息模块功能正常 - [ ] 所有代码已提交(不少于 11 个 commits) - [ ] API 文档已更新 - [ ] 设计文档已归档到 `doc/plans/` --- ## 测试指南 ### 完整功能测试 1. **启动后端服务** ```bash mvn spring-boot:run ``` 2. **测试各模块导入功能** 为每个模块运行相应的测试(参考员工模块测试脚本)。 3. **验证数据库** 检查导入的数据是否正确,旧数据是否被删除。 ### 性能测试 测试不同数据量的导入性能: - 小数据量:10 条 - 中数据量:100 条 - 大数据量:1000 条 --- **实施计划完成**