# 中介库异步导入功能实施计划 > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **目标:** 将中介库管理的文件导入功能改造为异步实现,支持个人中介和实体中介两种类型的异步导入 **架构:** 采用拆分式设计,为个人中介和实体中介分别创建独立的异步导入服务,使用Spring @Async注解实现异步处理,Redis存储导入状态和失败记录 **技术栈:** Spring Boot 3.5.8, MyBatis Plus 3.5.10, Redis, Vue 2.6.12, Element UI --- ## 前置条件 **参考资料:** - 设计文档: `doc/plans/2026-02-06-intermediary-async-import-design.md` - 员工导入实现: `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/service/impl/CcdiEmployeeImportServiceImpl.java` - 招聘导入实现: `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffRecruitmentImportServiceImpl.java` **关键依赖:** - `ImportResultVO` - 导入结果VO(已存在,复用) - `ImportStatusVO` - 导入状态VO(已存在,复用) - `AsyncConfig` - 异步配置(已存在,复用) - `importExecutor` - 导入任务线程池(已存在,复用) **Mapper:** - `CcdiBizIntermediaryMapper` - 个人中介Mapper(已存在) - `CcdiEnterpriseBaseInfoMapper` - 实体中介Mapper(已存在) --- ## Task 1: 创建个人中介导入失败记录VO **文件:** - Create: `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/domain/vo/IntermediaryPersonImportFailureVO.java` **Step 1: 创建VO类** 完整代码: ```java package com.ruoyi.ccdi.domain.vo; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.io.Serial; import java.io.Serializable; /** * 个人中介导入失败记录VO * * @author ruoyi * @date 2026-02-06 */ @Data @Schema(description = "个人中介导入失败记录") public class IntermediaryPersonImportFailureVO implements Serializable { @Serial private static final long serialVersionUID = 1L; @Schema(description = "姓名") private String name; @Schema(description = "证件号码") private String personId; @Schema(description = "人员类型") private String personType; @Schema(description = "性别") private String gender; @Schema(description = "手机号码") private String mobile; @Schema(description = "所在公司") private String company; @Schema(description = "错误信息") private String errorMessage; } ``` **Step 2: 编译验证** Run: `mvn compile -pl ruoyi-info-collection` Expected: BUILD SUCCESS **Step 3: 提交** ```bash git add ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/domain/vo/IntermediaryPersonImportFailureVO.java git commit -m "feat: 添加个人中介导入失败记录VO" ``` --- ## Task 2: 创建实体中介导入失败记录VO **文件:** - Create: `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/domain/vo/IntermediaryEntityImportFailureVO.java` **Step 1: 创建VO类** 完整代码: ```java package com.ruoyi.ccdi.domain.vo; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.io.Serial; import java.io.Serializable; import java.util.Date; /** * 实体中介导入失败记录VO * * @author ruoyi * @date 2026-02-06 */ @Data @Schema(description = "实体中介导入失败记录") public class IntermediaryEntityImportFailureVO implements Serializable { @Serial private static final long serialVersionUID = 1L; @Schema(description = "机构名称") private String enterpriseName; @Schema(description = "统一社会信用代码") private String socialCreditCode; @Schema(description = "主体类型") private String enterpriseType; @Schema(description = "企业性质") private String enterpriseNature; @Schema(description = "法定代表人") private String legalRepresentative; @Schema(description = "成立日期") private Date establishDate; @Schema(description = "错误信息") private String errorMessage; } ``` **Step 2: 编译验证** Run: `mvn compile -pl ruoyi-info-collection` Expected: BUILD SUCCESS **Step 3: 提交** ```bash git add ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/domain/vo/IntermediaryEntityImportFailureVO.java git commit -m "feat: 添加实体中介导入失败记录VO" ``` --- ## Task 3: 创建个人中介导入Service接口 **文件:** - Create: `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/service/ICcdiIntermediaryPersonImportService.java` **Step 1: 创建Service接口** 完整代码: ```java package com.ruoyi.ccdi.service; import com.ruoyi.ccdi.domain.excel.CcdiIntermediaryPersonExcel; import com.ruoyi.ccdi.domain.vo.ImportStatusVO; import com.ruoyi.ccdi.domain.vo.IntermediaryPersonImportFailureVO; import java.util.List; /** * 个人中介异步导入Service接口 * * @author ruoyi * @date 2026-02-06 */ public interface ICcdiIntermediaryPersonImportService { /** * 异步导入个人中介数据 * * @param excelList Excel数据列表 * @param isUpdateSupport 是否更新已存在的数据 * @param taskId 任务ID * @param userName 当前用户名(用于审计字段) */ void importPersonAsync(List excelList, Boolean isUpdateSupport, String taskId, String userName); /** * 查询导入状态 * * @param taskId 任务ID * @return 导入状态信息 */ ImportStatusVO getImportStatus(String taskId); /** * 获取导入失败记录 * * @param taskId 任务ID * @return 失败记录列表 */ List getImportFailures(String taskId); } ``` **Step 2: 编译验证** Run: `mvn compile -pl ruoyi-info-collection` Expected: BUILD SUCCESS **Step 3: 提交** ```bash git add ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/service/ICcdiIntermediaryPersonImportService.java git commit -m "feat: 添加个人中介异步导入Service接口" ``` --- ## Task 4: 创建实体中介导入Service接口 **文件:** - Create: `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/service/ICcdiIntermediaryEntityImportService.java` **Step 1: 创建Service接口** 完整代码: ```java package com.ruoyi.ccdi.service; import com.ruoyi.ccdi.domain.excel.CcdiIntermediaryEntityExcel; import com.ruoyi.ccdi.domain.vo.ImportStatusVO; import com.ruoyi.ccdi.domain.vo.IntermediaryEntityImportFailureVO; import java.util.List; /** * 实体中介异步导入Service接口 * * @author ruoyi * @date 2026-02-06 */ public interface ICcdiIntermediaryEntityImportService { /** * 异步导入实体中介数据 * * @param excelList Excel数据列表 * @param isUpdateSupport 是否更新已存在的数据 * @param taskId 任务ID * @param userName 当前用户名(用于审计字段) */ void importEntityAsync(List excelList, Boolean isUpdateSupport, String taskId, String userName); /** * 查询导入状态 * * @param taskId 任务ID * @return 导入状态信息 */ ImportStatusVO getImportStatus(String taskId); /** * 获取导入失败记录 * * @param taskId 任务ID * @return 失败记录列表 */ List getImportFailures(String taskId); } ``` **Step 2: 编译验证** Run: `mvn compile -pl ruoyi-info-collection` Expected: BUILD SUCCESS **Step 3: 提交** ```bash git add ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/service/ICcdiIntermediaryEntityImportService.java git commit -m "feat: 添加实体中介异步导入Service接口" ``` --- ## Task 5: 实现个人中介异步导入Service **文件:** - Create: `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/service/impl/CcdiIntermediaryPersonImportServiceImpl.java` **Step 1: 创建Service实现类** 完整代码: ```java package com.ruoyi.ccdi.service.impl; import com.alibaba.fastjson2.JSON; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.ruoyi.ccdi.domain.CcdiBizIntermediary; import com.ruoyi.ccdi.domain.excel.CcdiIntermediaryPersonExcel; import com.ruoyi.ccdi.domain.vo.ImportResult; import com.ruoyi.ccdi.domain.vo.ImportStatusVO; import com.ruoyi.ccdi.domain.vo.IntermediaryPersonImportFailureVO; import com.ruoyi.ccdi.mapper.CcdiBizIntermediaryMapper; import com.ruoyi.ccdi.service.ICcdiIntermediaryPersonImportService; import com.ruoyi.common.utils.StringUtils; import jakarta.annotation.Resource; import org.springframework.beans.BeanUtils; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** * 个人中介异步导入Service实现 * * @author ruoyi * @date 2026-02-06 */ @Service @EnableAsync public class CcdiIntermediaryPersonImportServiceImpl implements ICcdiIntermediaryPersonImportService { @Resource private CcdiBizIntermediaryMapper intermediaryMapper; @Resource private RedisTemplate redisTemplate; @Override @Async @Transactional public void importPersonAsync(List excelList, Boolean isUpdateSupport, String taskId, String userName) { List newRecords = new ArrayList<>(); List updateRecords = new ArrayList<>(); List failures = new ArrayList<>(); // 1. 批量查询已存在的证件号 Set existingPersonIds = getExistingPersonIds(excelList); // 2. 分类数据 for (CcdiIntermediaryPersonExcel excel : excelList) { try { // 验证必填字段 if (StringUtils.isEmpty(excel.getName())) { throw new RuntimeException("姓名不能为空"); } if (StringUtils.isEmpty(excel.getPersonId())) { throw new RuntimeException("证件号码不能为空"); } CcdiBizIntermediary person = new CcdiBizIntermediary(); BeanUtils.copyProperties(excel, person); person.setPersonType("中介"); person.setDataSource("IMPORT"); if (existingPersonIds.contains(excel.getPersonId())) { if (isUpdateSupport) { person.setUpdateBy(userName); updateRecords.add(person); } else { throw new RuntimeException("该证件号已存在"); } } else { person.setCreateBy(userName); person.setUpdateBy(userName); newRecords.add(person); } } catch (Exception e) { IntermediaryPersonImportFailureVO failure = new IntermediaryPersonImportFailureVO(); BeanUtils.copyProperties(excel, failure); failure.setErrorMessage(e.getMessage()); failures.add(failure); } } // 3. 批量插入 if (!newRecords.isEmpty()) { intermediaryMapper.insertBatch(newRecords); } // 4. 批量更新 if (!updateRecords.isEmpty()) { intermediaryMapper.updateBatch(updateRecords); } // 5. 保存失败记录到Redis if (!failures.isEmpty()) { String failuresKey = "import:intermediary-person:" + taskId + ":failures"; redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS); } // 6. 更新最终状态 ImportResult result = new ImportResult(); result.setTotalCount(excelList.size()); result.setSuccessCount(newRecords.size() + updateRecords.size()); result.setFailureCount(failures.size()); String finalStatus = result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS"; updateImportStatus(taskId, finalStatus, result); } @Override public ImportStatusVO getImportStatus(String taskId) { String key = "import:intermediary-person:" + taskId; Boolean hasKey = redisTemplate.hasKey(key); if (Boolean.FALSE.equals(hasKey)) { throw new RuntimeException("任务不存在或已过期"); } Map statusMap = redisTemplate.opsForHash().entries(key); ImportStatusVO statusVO = new ImportStatusVO(); statusVO.setTaskId((String) statusMap.get("taskId")); statusVO.setStatus((String) statusMap.get("status")); statusVO.setTotalCount((Integer) statusMap.get("totalCount")); statusVO.setSuccessCount((Integer) statusMap.get("successCount")); statusVO.setFailureCount((Integer) statusMap.get("failureCount")); statusVO.setProgress((Integer) statusMap.get("progress")); statusVO.setStartTime((Long) statusMap.get("startTime")); statusVO.setEndTime((Long) statusMap.get("endTime")); statusVO.setMessage((String) statusMap.get("message")); return statusVO; } @Override public List getImportFailures(String taskId) { String key = "import:intermediary-person:" + taskId + ":failures"; Object failuresObj = redisTemplate.opsForValue().get(key); if (failuresObj == null) { return Collections.emptyList(); } return JSON.parseArray(JSON.toJSONString(failuresObj), IntermediaryPersonImportFailureVO.class); } /** * 批量查询已存在的证件号 */ private Set getExistingPersonIds(List excelList) { List personIds = excelList.stream() .map(CcdiIntermediaryPersonExcel::getPersonId) .filter(StringUtils::isNotEmpty) .collect(Collectors.toList()); if (personIds.isEmpty()) { return Collections.emptySet(); } LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.select(CcdiBizIntermediary::getBizId, CcdiBizIntermediary::getPersonId); wrapper.in(CcdiBizIntermediary::getPersonId, personIds); List existingList = intermediaryMapper.selectList(wrapper); return existingList.stream() .map(CcdiBizIntermediary::getPersonId) .collect(Collectors.toSet()); } /** * 更新导入状态 */ private void updateImportStatus(String taskId, String status, ImportResult result) { String key = "import:intermediary-person:" + taskId; Map statusData = new HashMap<>(); statusData.put("status", status); statusData.put("successCount", result.getSuccessCount()); statusData.put("failureCount", result.getFailureCount()); statusData.put("progress", 100); statusData.put("endTime", System.currentTimeMillis()); if ("SUCCESS".equals(status)) { statusData.put("message", "全部成功!共导入" + result.getTotalCount() + "条数据"); } else { statusData.put("message", "成功" + result.getSuccessCount() + "条,失败" + result.getFailureCount() + "条"); } redisTemplate.opsForHash().putAll(key, statusData); } } ``` **Step 2: 编译验证** Run: `mvn compile -pl ruoyi-info-collection` Expected: BUILD SUCCESS **Step 3: 提交** ```bash git add ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/service/impl/CcdiIntermediaryPersonImportServiceImpl.java git commit -m "feat: 实现个人中介异步导入Service" ``` --- ## Task 6: 实现实体中介异步导入Service **文件:** - Create: `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/service/impl/CcdiIntermediaryEntityImportServiceImpl.java` **Step 1: 创建Service实现类** 完整代码: ```java package com.ruoyi.ccdi.service.impl; import com.alibaba.fastjson2.JSON; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.ruoyi.ccdi.domain.CcdiEnterpriseBaseInfo; import com.ruoyi.ccdi.domain.excel.CcdiIntermediaryEntityExcel; import com.ruoyi.ccdi.domain.vo.ImportResult; import com.ruoyi.ccdi.domain.vo.ImportStatusVO; import com.ruoyi.ccdi.domain.vo.IntermediaryEntityImportFailureVO; import com.ruoyi.ccdi.mapper.CcdiEnterpriseBaseInfoMapper; import com.ruoyi.ccdi.service.ICcdiIntermediaryEntityImportService; import com.ruoyi.common.utils.StringUtils; import jakarta.annotation.Resource; import org.springframework.beans.BeanUtils; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** * 实体中介异步导入Service实现 * * @author ruoyi * @date 2026-02-06 */ @Service @EnableAsync public class CcdiIntermediaryEntityImportServiceImpl implements ICcdiIntermediaryEntityImportService { @Resource private CcdiEnterpriseBaseInfoMapper enterpriseMapper; @Resource private RedisTemplate redisTemplate; @Override @Async @Transactional public void importEntityAsync(List excelList, Boolean isUpdateSupport, String taskId, String userName) { List newRecords = new ArrayList<>(); List updateRecords = new ArrayList<>(); List failures = new ArrayList<>(); // 1. 批量查询已存在的统一社会信用代码 Set existingCodes = getExistingSocialCreditCodes(excelList); // 2. 分类数据 for (CcdiIntermediaryEntityExcel excel : excelList) { try { // 验证必填字段 if (StringUtils.isEmpty(excel.getEnterpriseName())) { throw new RuntimeException("机构名称不能为空"); } CcdiEnterpriseBaseInfo entity = new CcdiEnterpriseBaseInfo(); BeanUtils.copyProperties(excel, entity); entity.setRiskLevel("1"); entity.setEntSource("INTERMEDIARY"); entity.setDataSource("IMPORT"); if (StringUtils.isNotEmpty(excel.getSocialCreditCode()) && existingCodes.contains(excel.getSocialCreditCode())) { if (isUpdateSupport) { entity.setUpdateBy(userName); updateRecords.add(entity); } else { throw new RuntimeException("该统一社会信用代码已存在"); } } else { entity.setCreateBy(userName); entity.setUpdateBy(userName); newRecords.add(entity); } } catch (Exception e) { IntermediaryEntityImportFailureVO failure = new IntermediaryEntityImportFailureVO(); BeanUtils.copyProperties(excel, failure); failure.setErrorMessage(e.getMessage()); failures.add(failure); } } // 3. 批量插入 if (!newRecords.isEmpty()) { enterpriseMapper.insertBatch(newRecords); } // 4. 批量更新 if (!updateRecords.isEmpty()) { enterpriseMapper.updateBatch(updateRecords); } // 5. 保存失败记录到Redis if (!failures.isEmpty()) { String failuresKey = "import:intermediary-entity:" + taskId + ":failures"; redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS); } // 6. 更新最终状态 ImportResult result = new ImportResult(); result.setTotalCount(excelList.size()); result.setSuccessCount(newRecords.size() + updateRecords.size()); result.setFailureCount(failures.size()); String finalStatus = result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS"; updateImportStatus(taskId, finalStatus, result); } @Override public ImportStatusVO getImportStatus(String taskId) { String key = "import:intermediary-entity:" + taskId; Boolean hasKey = redisTemplate.hasKey(key); if (Boolean.FALSE.equals(hasKey)) { throw new RuntimeException("任务不存在或已过期"); } Map statusMap = redisTemplate.opsForHash().entries(key); ImportStatusVO statusVO = new ImportStatusVO(); statusVO.setTaskId((String) statusMap.get("taskId")); statusVO.setStatus((String) statusMap.get("status")); statusVO.setTotalCount((Integer) statusMap.get("totalCount")); statusVO.setSuccessCount((Integer) statusMap.get("successCount")); statusVO.setFailureCount((Integer) statusMap.get("failureCount")); statusVO.setProgress((Integer) statusMap.get("progress")); statusVO.setStartTime((Long) statusMap.get("startTime")); statusVO.setEndTime((Long) statusMap.get("endTime")); statusVO.setMessage((String) statusMap.get("message")); return statusVO; } @Override public List getImportFailures(String taskId) { String key = "import:intermediary-entity:" + taskId + ":failures"; Object failuresObj = redisTemplate.opsForValue().get(key); if (failuresObj == null) { return Collections.emptyList(); } return JSON.parseArray(JSON.toJSONString(failuresObj), IntermediaryEntityImportFailureVO.class); } /** * 批量查询已存在的统一社会信用代码 */ private Set getExistingSocialCreditCodes(List excelList) { List codes = excelList.stream() .map(CcdiIntermediaryEntityExcel::getSocialCreditCode) .filter(StringUtils::isNotEmpty) .collect(Collectors.toList()); if (codes.isEmpty()) { return Collections.emptySet(); } LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.select(CcdiEnterpriseBaseInfo::getSocialCreditCode); wrapper.in(CcdiEnterpriseBaseInfo::getSocialCreditCode, codes); List existingList = enterpriseMapper.selectList(wrapper); return existingList.stream() .map(CcdiEnterpriseBaseInfo::getSocialCreditCode) .collect(Collectors.toSet()); } /** * 更新导入状态 */ private void updateImportStatus(String taskId, String status, ImportResult result) { String key = "import:intermediary-entity:" + taskId; Map statusData = new HashMap<>(); statusData.put("status", status); statusData.put("successCount", result.getSuccessCount()); statusData.put("failureCount", result.getFailureCount()); statusData.put("progress", 100); statusData.put("endTime", System.currentTimeMillis()); if ("SUCCESS".equals(status)) { statusData.put("message", "全部成功!共导入" + result.getTotalCount() + "条数据"); } else { statusData.put("message", "成功" + result.getSuccessCount() + "条,失败" + result.getFailureCount() + "条"); } redisTemplate.opsForHash().putAll(key, statusData); } } ``` **Step 2: 编译验证** Run: `mvn compile -pl ruoyi-info-collection` Expected: BUILD SUCCESS **Step 3: 提交** ```bash git add ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/service/impl/CcdiIntermediaryEntityImportServiceImpl.java git commit -m "feat: 实现实体中介异步导入Service" ``` --- ## Task 7: 修改Controller - 注入Service和添加辅助方法 **文件:** - Modify: `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/controller/CcdiIntermediaryController.java` **Step 1: 添加导入Service注入** 在类的开头添加: ```java @Resource private ICcdiIntermediaryPersonImportService personImportService; @Resource private ICcdiIntermediaryEntityImportService entityImportService; ``` **Step 2: 添加初始化导入状态辅助方法** 在Controller类中添加私有方法: ```java /** * 初始化导入状态到Redis */ private void initImportStatus(String taskType, String taskId, int totalCount) { String key = "import:" + taskType + ":" + taskId; Map statusData = new HashMap<>(); statusData.put("taskId", taskId); statusData.put("status", "PROCESSING"); statusData.put("totalCount", totalCount); statusData.put("successCount", 0); statusData.put("failureCount", 0); statusData.put("progress", 0); statusData.put("startTime", System.currentTimeMillis()); statusData.put("message", "正在处理中..."); redisTemplate.opsForHash().putAll(key, statusData); redisTemplate.expire(key, 7, TimeUnit.DAYS); } ``` 注意: 需要在Controller中注入`RedisTemplate redisTemplate` **Step 3: 添加RedisTemplate注入** 在类的开头添加: ```java @Resource private RedisTemplate redisTemplate; ``` **Step 4: 编译验证** Run: `mvn compile -pl ruoyi-info-collection` Expected: BUILD SUCCESS **Step 5: 提交** ```bash git add ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/controller/CcdiIntermediaryController.java git commit -m "feat: Controller添加导入Service注入和辅助方法" ``` --- ## Task 8: 修改Controller - 改造个人中介导入接口为异步 **文件:** - Modify: `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/controller/CcdiIntermediaryController.java` **Step 1: 修改importPersonData方法** 将方法修改为: ```java /** * 导入个人中介数据(异步) */ @Operation(summary = "导入个人中介数据") @PreAuthorize("@ss.hasPermi('ccdi:intermediary:import')") @Log(title = "个人中介", businessType = BusinessType.IMPORT) @PostMapping("/importPersonData") public AjaxResult importPersonData(MultipartFile file, @RequestParam(defaultValue = "false") boolean updateSupport) throws Exception { List list = EasyExcelUtil.importExcel( file.getInputStream(), CcdiIntermediaryPersonExcel.class ); if (list == null || list.isEmpty()) { return error("至少需要一条数据"); } // 生成任务ID String taskId = UUID.randomUUID().toString(); // 获取当前用户名 String userName = getUsername(); // 初始化导入状态到Redis initImportStatus("intermediary-person", taskId, list.size()); // 提交异步任务 personImportService.importPersonAsync(list, updateSupport, taskId, userName); // 立即返回,不等待后台任务完成 ImportResultVO result = new ImportResultVO(); result.setTaskId(taskId); result.setStatus("PROCESSING"); result.setMessage("导入任务已提交,正在后台处理"); return AjaxResult.success("导入任务已提交,正在后台处理", result); } ``` **Step 2: 编译验证** Run: `mvn compile -pl ruoyi-info-collection` Expected: BUILD SUCCESS **Step 3: 提交** ```bash git add ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/controller/CcdiIntermediaryController.java git commit -m "feat: 改造个人中介导入接口为异步" ``` --- ## Task 9: 修改Controller - 添加个人中介状态查询接口 **文件:** - Modify: `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/controller/CcdiIntermediaryController.java` **Step 1: 添加getPersonImportStatus方法** 在Controller类中添加: ```java /** * 查询个人中介导入状态 */ @Operation(summary = "查询个人中介导入状态") @GetMapping("/importPersonStatus/{taskId}") public AjaxResult getPersonImportStatus(@PathVariable String taskId) { try { ImportStatusVO status = personImportService.getImportStatus(taskId); return success(status); } catch (Exception e) { return error(e.getMessage()); } } ``` **Step 2: 编译验证** Run: `mvn compile -pl ruoyi-info-collection` Expected: BUILD SUCCESS **Step 3: 提交** ```bash git add ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/controller/CcdiIntermediaryController.java git commit -m "feat: 添加个人中介导入状态查询接口" ``` --- ## Task 10: 修改Controller - 添加个人中介失败记录查询接口 **文件:** - Modify: `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/controller/CcdiIntermediaryController.java` **Step 1: 添加getPersonImportFailures方法** 在Controller类中添加: ```java /** * 查询个人中介导入失败记录 */ @Operation(summary = "查询个人中介导入失败记录") @GetMapping("/importPersonFailures/{taskId}") public TableDataInfo getPersonImportFailures( @PathVariable String taskId, @RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize) { List failures = personImportService.getImportFailures(taskId); // 手动分页 int fromIndex = (pageNum - 1) * pageSize; int toIndex = Math.min(fromIndex + pageSize, failures.size()); List pageData = failures.subList(fromIndex, toIndex); return getDataTable(pageData, failures.size()); } ``` **Step 2: 编译验证** Run: `mvn compile -pl ruoyi-info-collection` Expected: BUILD SUCCESS **Step 3: 提交** ```bash git add ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/controller/CcdiIntermediaryController.java git commit -m "feat: 添加个人中介导入失败记录查询接口" ``` --- ## Task 11: 修改Controller - 改造实体中介导入接口为异步 **文件:** - Modify: `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/controller/CcdiIntermediaryController.java` **Step 1: 修改importEntityData方法** 将方法修改为: ```java /** * 导入实体中介数据(异步) */ @Operation(summary = "导入实体中介数据") @PreAuthorize("@ss.hasPermi('ccdi:intermediary:import')") @Log(title = "实体中介", businessType = BusinessType.IMPORT) @PostMapping("/importEntityData") public AjaxResult importEntityData(MultipartFile file, @RequestParam(defaultValue = "false") boolean updateSupport) throws Exception { List list = EasyExcelUtil.importExcel( file.getInputStream(), CcdiIntermediaryEntityExcel.class ); if (list == null || list.isEmpty()) { return error("至少需要一条数据"); } // 生成任务ID String taskId = UUID.randomUUID().toString(); // 获取当前用户名 String userName = getUsername(); // 初始化导入状态到Redis initImportStatus("intermediary-entity", taskId, list.size()); // 提交异步任务 entityImportService.importEntityAsync(list, updateSupport, taskId, userName); // 立即返回,不等待后台任务完成 ImportResultVO result = new ImportResultVO(); result.setTaskId(taskId); result.setStatus("PROCESSING"); result.setMessage("导入任务已提交,正在后台处理"); return AjaxResult.success("导入任务已提交,正在后台处理", result); } ``` **Step 2: 编译验证** Run: `mvn compile -pl ruoyi-info-collection` Expected: BUILD SUCCESS **Step 3: 提交** ```bash git add ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/controller/CcdiIntermediaryController.java git commit -m "feat: 改造实体中介导入接口为异步" ``` --- ## Task 12: 修改Controller - 添加实体中介状态查询接口 **文件:** - Modify: `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/controller/CcdiIntermediaryController.java` **Step 1: 添加getEntityImportStatus方法** 在Controller类中添加: ```java /** * 查询实体中介导入状态 */ @Operation(summary = "查询实体中介导入状态") @GetMapping("/importEntityStatus/{taskId}") public AjaxResult getEntityImportStatus(@PathVariable String taskId) { try { ImportStatusVO status = entityImportService.getImportStatus(taskId); return success(status); } catch (Exception e) { return error(e.getMessage()); } } ``` **Step 2: 编译验证** Run: `mvn compile -pl ruoyi-info-collection` Expected: BUILD SUCCESS **Step 3: 提交** ```bash git add ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/controller/CcdiIntermediaryController.java git commit -m "feat: 添加实体中介导入状态查询接口" ``` --- ## Task 13: 修改Controller - 添加实体中介失败记录查询接口 **文件:** - Modify: `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/controller/CcdiIntermediaryController.java` **Step 1: 添加getEntityImportFailures方法** 在Controller类中添加: ```java /** * 查询实体中介导入失败记录 */ @Operation(summary = "查询实体中介导入失败记录") @GetMapping("/importEntityFailures/{taskId}") public TableDataInfo getEntityImportFailures( @PathVariable String taskId, @RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize) { List failures = entityImportService.getImportFailures(taskId); // 手动分页 int fromIndex = (pageNum - 1) * pageSize; int toIndex = Math.min(fromIndex + pageSize, failures.size()); List pageData = failures.subList(fromIndex, toIndex); return getDataTable(pageData, failures.size()); } ``` **Step 2: 编译验证** Run: `mvn compile -pl ruoyi-info-collection` Expected: BUILD SUCCESS **Step 3: 提交** ```bash git add ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/controller/CcdiIntermediaryController.java git commit -m "feat: 添加实体中介导入失败记录查询接口" ``` --- ## Task 14: 前端 - 添加API定义 **文件:** - Modify: `ruoyi-ui/src/api/ccdiIntermediary.js` **Step 1: 添加个人中介导入API** 在文件末尾添加: ```javascript // 查询个人中介导入状态 export function getPersonImportStatus(taskId) { return request({ url: '/ccdi/intermediary/importPersonStatus/' + taskId, method: 'get' }) } // 查询个人中介导入失败记录 export function getPersonImportFailures(taskId, pageNum, pageSize) { return request({ url: '/ccdi/intermediary/importPersonFailures/' + taskId, method: 'get', params: { pageNum, pageSize } }) } // 查询实体中介导入状态 export function getEntityImportStatus(taskId) { return request({ url: '/ccdi/intermediary/importEntityStatus/' + taskId, method: 'get' }) } // 查询实体中介导入失败记录 export function getEntityImportFailures(taskId, pageNum, pageSize) { return request({ url: '/ccdi/intermediary/importEntityFailures/' + taskId, method: 'get', params: { pageNum, pageSize } }) } ``` **Step 2: 提交** ```bash git add ruoyi-ui/src/api/ccdiIntermediary.js git commit -m "feat: 添加中介导入状态和失败记录查询API" ``` --- ## Task 15: 前端 - 修改Vue组件 - 添加data属性 **文件:** - Modify: `ruoyi-ui/src/views/ccdiIntermediary/index.vue` **Step 1: 在data()中添加导入相关属性** 在`data()`函数的`return`对象中添加: ```javascript // 个人中介导入相关 personPollingTimer: null, personShowFailureButton: false, personCurrentTaskId: null, personFailureDialogVisible: false, personFailureList: [], personFailureLoading: false, personFailureTotal: 0, personFailureQueryParams: { pageNum: 1, pageSize: 10 }, // 实体中介导入相关 entityPollingTimer: null, entityShowFailureButton: false, entityCurrentTaskId: null, entityFailureDialogVisible: false, entityFailureList: [], entityFailureLoading: false, entityFailureTotal: 0, entityFailureQueryParams: { pageNum: 1, pageSize: 10 } ``` **Step 2: 提交** ```bash git add ruoyi-ui/src/views/ccdiIntermediary/index.vue git commit -m "feat: 添加中介导入相关data属性" ``` --- ## Task 16: 前端 - 修改handleFileSuccess方法 **文件:** - Modify: `ruoyi-ui/src/views/ccdiIntermediary/index.vue` **Step 1: 修改handleFileSuccess方法** 将`handleFileSuccess`方法修改为: ```javascript handleFileSuccess(response, file, fileList) { this.upload.isUploading = false; this.upload.open = false; if (response.code === 200) { const taskId = response.data.taskId; const importType = this.upload.importType; // 'person' 或 'entity' // 显示后台处理提示 const typeName = importType === 'person' ? '个人中介' : '实体中介'; this.$notify({ title: '导入任务已提交', message: `${typeName}数据正在后台处理中,处理完成后将通知您`, type: 'info', duration: 3000 }); // 根据类型开始轮询 if (importType === 'person') { this.startPersonImportPolling(taskId); } else { this.startEntityImportPolling(taskId); } } else { this.$modal.msgError(response.msg); } } ``` **Step 2: 提交** ```bash git add ruoyi-ui/src/views/ccdiIntermediary/index.vue git commit -m "feat: 修改handleFileSuccess支持异步导入轮询" ``` --- ## Task 17: 前端 - 添加轮询和完成处理方法 **文件:** - Modify: `ruoyi-ui/src/views/ccdiIntermediary/index.vue` **Step 1: 在methods中添加轮询方法** 在`methods`对象中添加: ```javascript // 个人中介轮询 startPersonImportPolling(taskId) { this.personPollingTimer = setInterval(async () => { try { const response = await getPersonImportStatus(taskId); if (response.data && response.data.status !== 'PROCESSING') { clearInterval(this.personPollingTimer); this.handlePersonImportComplete(response.data); } } catch (error) { clearInterval(this.personPollingTimer); this.$modal.msgError('查询导入状态失败: ' + error.message); } }, 2000); }, // 实体中介轮询 startEntityImportPolling(taskId) { this.entityPollingTimer = setInterval(async () => { try { const response = await getEntityImportStatus(taskId); if (response.data && response.data.status !== 'PROCESSING') { clearInterval(this.entityPollingTimer); this.handleEntityImportComplete(response.data); } } catch (error) { clearInterval(this.entityPollingTimer); this.$modal.msgError('查询导入状态失败: ' + error.message); } }, 2000); }, // 个人中介导入完成处理 handlePersonImportComplete(statusResult) { if (statusResult.status === 'SUCCESS') { this.$notify({ title: '导入完成', message: `个人中介数据全部成功!共导入${statusResult.totalCount}条`, type: 'success', duration: 5000 }); this.getList(); } else if (statusResult.failureCount > 0) { this.$notify({ title: '导入完成', message: `成功${statusResult.successCount}条,失败${statusResult.failureCount}条`, type: 'warning', duration: 5000 }); this.personShowFailureButton = true; this.personCurrentTaskId = statusResult.taskId; this.getList(); } }, // 实体中介导入完成处理 handleEntityImportComplete(statusResult) { if (statusResult.status === 'SUCCESS') { this.$notify({ title: '导入完成', message: `实体中介数据全部成功!共导入${statusResult.totalCount}条`, type: 'success', duration: 5000 }); this.getList(); } else if (statusResult.failureCount > 0) { this.$notify({ title: '导入完成', message: `成功${statusResult.successCount}条,失败${statusResult.failureCount}条`, type: 'warning', duration: 5000 }); this.entityShowFailureButton = true; this.entityCurrentTaskId = statusResult.taskId; this.getList(); } } ``` **Step 2: 提交** ```bash git add ruoyi-ui/src/views/ccdiIntermediary/index.vue git commit -m "feat: 添加中介导入轮询和完成处理方法" ``` --- ## Task 18: 前端 - 添加beforeDestroy生命周期钩子 **文件:** - Modify: `ruoyi-ui/src/views/ccdiIntermediary/index.vue` **Step 1: 添加beforeDestroy钩子** 在组件选项中添加: ```javascript beforeDestroy() { // 清除个人中介轮询定时器 if (this.personPollingTimer) { clearInterval(this.personPollingTimer); this.personPollingTimer = null; } // 清除实体中介轮询定时器 if (this.entityPollingTimer) { clearInterval(this.entityPollingTimer); this.entityPollingTimer = null; } } ``` **Step 2: 提交** ```bash git add ruoyi-ui/src/views/ccdiIntermediary/index.vue git commit -m "feat: 添加beforeDestroy生命周期钩子清除定时器" ``` --- ## Task 19: 前端 - 添加失败记录查询方法 **文件:** - Modify: `ruoyi-ui/src/views/ccdiIntermediary/index.vue` **Step 1: 在methods中添加查询失败记录方法** 在`methods`对象中添加: ```javascript // 查看个人中介导入失败记录 viewPersonImportFailures() { this.personFailureDialogVisible = true; this.getPersonFailureList(); }, // 获取个人中介失败记录列表 getPersonFailureList() { this.personFailureLoading = true; getPersonImportFailures( this.personCurrentTaskId, this.personFailureQueryParams.pageNum, this.personFailureQueryParams.pageSize ).then(response => { this.personFailureList = response.rows; this.personFailureTotal = response.total; this.personFailureLoading = false; }).catch(error => { this.personFailureLoading = false; this.$modal.msgError('查询失败记录失败: ' + error.message); }); }, // 查看实体中介导入失败记录 viewEntityImportFailures() { this.entityFailureDialogVisible = true; this.getEntityFailureList(); }, // 获取实体中介失败记录列表 getEntityFailureList() { this.entityFailureLoading = true; getEntityImportFailures( this.entityCurrentTaskId, this.entityFailureQueryParams.pageNum, this.entityFailureQueryParams.pageSize ).then(response => { this.entityFailureList = response.rows; this.entityFailureTotal = response.total; this.entityFailureLoading = false; }).catch(error => { this.entityFailureLoading = false; this.$modal.msgError('查询失败记录失败: ' + error.message); }); } ``` **Step 2: 提交** ```bash git add ruoyi-ui/src/views/ccdiIntermediary/index.vue git commit -m "feat: 添加中介导入失败记录查询方法" ``` --- ## Task 20: 前端 - 添加失败记录按钮 **文件:** - Modify: `ruoyi-ui/src/views/ccdiIntermediary/index.vue` **Step 1: 在template中添加失败记录按钮** 在按钮区域添加(参考现有的按钮布局): ```vue 查看个人中介导入失败记录 查看实体中介导入失败记录 ``` **Step 2: 提交** ```bash git add ruoyi-ui/src/views/ccdiIntermediary/index.vue git commit -m "feat: 添加中介导入失败记录查看按钮" ``` --- ## Task 21: 前端 - 添加个人中介失败记录对话框 **文件:** - Modify: `ruoyi-ui/src/views/ccdiIntermediary/index.vue` **Step 1: 在template中添加个人中介失败记录对话框** 在template末尾添加: ```vue ``` **Step 2: 提交** ```bash git add ruoyi-ui/src/views/ccdiIntermediary/index.vue git commit -m "feat: 添加个人中介导入失败记录对话框" ``` --- ## Task 22: 前端 - 添加实体中介失败记录对话框 **文件:** - Modify: `ruoyi-ui/src/views/ccdiIntermediary/index.vue` **Step 1: 在template中添加实体中介失败记录对话框** 在个人中介对话框后面添加: ```vue ``` **Step 2: 提交** ```bash git add ruoyi-ui/src/views/ccdiIntermediary/index.vue git commit -m "feat: 添加实体中介导入失败记录对话框" ``` --- ## Task 23: 更新API文档 **文件:** - Modify: `doc/api/ccdi_intermediary_api.md` **Step 1: 添加导入相关接口文档** 在文档末尾添加: ```markdown ## 导入相关接口 ### 1. 导入个人中介数据 **接口地址:** `/ccdi/intermediary/importPersonData` **请求方式:** POST **接口描述:** 异步导入个人中介数据 **请求参数:** | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | file | MultipartFile | 是 | Excel文件 | | updateSupport | boolean | 否 | 是否更新已存在的数据,默认false | **响应示例:** ```json { "code": 200, "msg": "导入任务已提交,正在后台处理", "data": { "taskId": "550e8400-e29b-41d4-a716-446655440000", "status": "PROCESSING", "message": "导入任务已提交,正在后台处理" } } ``` ### 2. 查询个人中介导入状态 **接口地址:** `/ccdi/intermediary/importPersonStatus/{taskId}` **请求方式:** GET **接口描述:** 查询个人中介导入状态 **路径参数:** | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | taskId | String | 是 | 任务ID | **响应示例:** ```json { "code": 200, "data": { "taskId": "550e8400-e29b-41d4-a716-446655440000", "status": "SUCCESS", "totalCount": 100, "successCount": 98, "failureCount": 2, "progress": 100, "message": "成功98条,失败2条" } } ``` ### 3. 查询个人中介导入失败记录 **接口地址:** `/ccdi/intermediary/importPersonFailures/{taskId}` **请求方式:** GET **接口描述:** 查询个人中介导入失败记录 **路径参数:** | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | taskId | String | 是 | 任务ID | **Query参数:** | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | pageNum | Integer | 否 | 页码,默认1 | | pageSize | Integer | 否 | 每页大小,默认10 | **响应示例:** ```json { "code": 200, "rows": [ { "name": "张三", "personId": "110101199001011234", "personType": "中介", "gender": "男", "mobile": "13800138000", "company": "某公司", "errorMessage": "该证件号已存在" } ], "total": 2 } ``` ### 4. 导入实体中介数据 **接口地址:** `/ccdi/intermediary/importEntityData` **请求方式:** POST **接口描述:** 异步导入实体中介数据 **请求参数:** | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | file | MultipartFile | 是 | Excel文件 | | updateSupport | boolean | 否 | 是否更新已存在的数据,默认false | **响应示例:** 同个人中介导入 ### 5. 查询实体中介导入状态 **接口地址:** `/ccdi/intermediary/importEntityStatus/{taskId}` **请求方式:** GET **接口描述:** 查询实体中介导入状态 **响应示例:** 同个人中介状态查询 ### 6. 查询实体中介导入失败记录 **接口地址:** `/ccdi/intermediary/importEntityFailures/{taskId}` **请求方式:** GET **接口描述:** 查询实体中介导入失败记录 **响应示例:** ```json { "code": 200, "rows": [ { "enterpriseName": "某机构", "socialCreditCode": "91110000123456789X", "enterpriseType": "企业", "enterpriseNature": "民营企业", "legalRepresentative": "张三", "establishDate": "2020-01-01", "errorMessage": "该统一社会信用代码已存在" } ], "total": 1 } ``` ## 状态枚举 | 状态值 | 说明 | |--------|------| | PROCESSING | 处理中 | | SUCCESS | 全部成功 | | PARTIAL_SUCCESS | 部分成功 | | FAILED | 全部失败 | ``` **Step 2: 提交** ```bash git add doc/api/ccdi_intermediary_api.md git commit -m "docs: 添加中介导入相关接口文档" ``` --- ## Task 24: 创建测试脚本 **文件:** - Create: `test/test_intermediary_import.py` **Step 1: 创建测试脚本** 完整代码: ```python import requests import json import time import os # 配置 BASE_URL = "http://localhost:8080" USERNAME = "admin" PASSWORD = "admin123" def login(): """登录获取token""" url = f"{BASE_URL}/login/test" data = { "username": USERNAME, "password": PASSWORD } response = requests.post(url, data=data) result = response.json() if result.get("code") == 200: print("✓ 登录成功") return result.get("token") else: print(f"✗ 登录失败: {result.get('msg')}") return None def test_person_import(file_path, update_support=False): """测试个人中介导入""" print("\n=== 测试个人中介导入 ===") url = f"{BASE_URL}/ccdi/intermediary/importPersonData" headers = {"Authorization": f"Bearer {TOKEN}"} with open(file_path, 'rb') as f: files = {'file': f} data = {'updateSupport': update_support} response = requests.post(url, headers=headers, files=files, data=data) result = response.json() if result.get("code") == 200: task_id = result.get("data").get("taskId") print(f"✓ 个人中介导入任务已提交, taskId: {task_id}") return task_id else: print(f"✗ 个人中介导入失败: {result.get('msg')}") return None def test_entity_import(file_path, update_support=False): """测试实体中介导入""" print("\n=== 测试实体中介导入 ===") url = f"{BASE_URL}/ccdi/intermediary/importEntityData" headers = {"Authorization": f"Bearer {TOKEN}"} with open(file_path, 'rb') as f: files = {'file': f} data = {'updateSupport': update_support} response = requests.post(url, headers=headers, files=files, data=data) result = response.json() if result.get("code") == 200: task_id = result.get("data").get("taskId") print(f"✓ 实体中介导入任务已提交, taskId: {task_id}") return task_id else: print(f"✗ 实体中介导入失败: {result.get('msg')}") return None def wait_for_completion(task_id, import_type="person"): """等待导入完成""" print(f"\n=== 等待{import_type}中介导入完成 ===") url = f"{BASE_URL}/ccdi/intermediary/import{import_type.capitalize()}Status/{task_id}" headers = {"Authorization": f"Bearer {TOKEN}"} max_retries = 30 for i in range(max_retries): response = requests.get(url, headers=headers) result = response.json() if result.get("code") == 200: status_data = result.get("data") status = status_data.get("status") print(f"状态: {status}, 进度: {status_data.get('progress')}%, " f"成功: {status_data.get('successCount')}, 失败: {status_data.get('failureCount')}") if status != "PROCESSING": print(f"✓ 导入完成: {status_data.get('message')}") return status_data time.sleep(2) print("✗ 导入超时") return None def get_failures(task_id, import_type="person"): """获取失败记录""" print(f"\n=== 获取{import_type}中介导入失败记录 ===") url = f"{BASE_URL}/ccdi/intermediary/import{import_type.capitalize()}Failures/{task_id}" headers = {"Authorization": f"Bearer {TOKEN}"} params = {"pageNum": 1, "pageSize": 10} response = requests.get(url, headers=headers, params=params) result = response.json() if result.get("code") == 200: rows = result.get("rows", []) total = result.get("total", 0) print(f"✓ 共有 {total} 条失败记录") for i, row in enumerate(rows[:5], 1): print(f" {i}. {row.get('errorMessage')}") if total > 5: print(f" ... 还有 {total - 5} 条") else: print(f"✗ 获取失败记录失败: {result.get('msg')}") if __name__ == "__main__": # 登录 TOKEN = login() if not TOKEN: exit(1) # 测试个人中介导入 person_file = "doc/test-data/intermediary/intermediary_person_test.xlsx" if os.path.exists(person_file): task_id = test_person_import(person_file) if task_id: status = wait_for_completion(task_id, "person") if status and status.get("failureCount") > 0: get_failures(task_id, "person") # 测试实体中介导入 entity_file = "doc/test-data/intermediary/intermediary_entity_test.xlsx" if os.path.exists(entity_file): task_id = test_entity_import(entity_file) if task_id: status = wait_for_completion(task_id, "entity") if status and status.get("failureCount") > 0: get_failures(task_id, "entity") print("\n=== 测试完成 ===") ``` **Step 2: 提交** ```bash git add test/test_intermediary_import.py git commit -m "test: 添加中介导入测试脚本" ``` --- ## Task 25: 最终编译和测试 **Step 1: 编译后端** Run: `mvn clean compile -pl ruoyi-info-collection` Expected: BUILD SUCCESS **Step 2: 检查前端语法** Run: `cd ruoyi-ui && npm run lint -- --no-fix` Expected: No ESLint errors **Step 3: 最终提交** ```bash git add . git commit -m "feat: 完成中介库异步导入功能实现" ``` --- ## 测试检查清单 ### 功能测试 - [ ] 个人中介导入 - 全部成功 - [ ] 个人中介导入 - 部分失败 - [ ] 个人中介导入 - 重复数据不更新 - [ ] 个人中介导入 - 重复数据更新 - [ ] 实体中介导入 - 全部成功 - [ ] 实体中介导入 - 部分失败 - [ ] 实体中介导入 - 重复数据不更新 - [ ] 实体中介导入 - 重复数据更新 - [ ] 状态查询 - 返回正确状态 - [ ] 失败记录查询 - 正确显示 - [ ] 前端轮询 - 每2秒查询一次 - [ ] 前端通知 - 显示正确的成功/警告通知 - [ ] 失败记录UI - 个人和实体按钮正确显示 - [ ] 失败记录对话框 - 正确展示失败数据 ### 性能测试 - [ ] 导入接口响应时间 < 500ms - [ ] 500条数据处理时间 < 5秒 - [ ] Redis数据正确存储,TTL为7天 ### 异常测试 - [ ] 空文件上传 - [ ] 不存在的taskId查询 - [ ] 并发导入多个文件 --- ## 预期完成时间 - 后端开发: 2-3小时 - 前端开发: 1-2小时 - 测试和调试: 1-2小时 **总计: 4-7小时** --- **实施计划版本:** 1.0 **创建日期:** 2026-02-06 **创建人员:** Claude