# 中介库异步导入功能实施计划

> **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<CcdiIntermediaryPersonExcel> excelList,
                           Boolean isUpdateSupport,
                           String taskId,
                           String userName);

    /**
     * 查询导入状态
     *
     * @param taskId 任务ID
     * @return 导入状态信息
     */
    ImportStatusVO getImportStatus(String taskId);

    /**
     * 获取导入失败记录
     *
     * @param taskId 任务ID
     * @return 失败记录列表
     */
    List<IntermediaryPersonImportFailureVO> 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<CcdiIntermediaryEntityExcel> excelList,
                           Boolean isUpdateSupport,
                           String taskId,
                           String userName);

    /**
     * 查询导入状态
     *
     * @param taskId 任务ID
     * @return 导入状态信息
     */
    ImportStatusVO getImportStatus(String taskId);

    /**
     * 获取导入失败记录
     *
     * @param taskId 任务ID
     * @return 失败记录列表
     */
    List<IntermediaryEntityImportFailureVO> 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<String, Object> redisTemplate;

    @Override
    @Async
    @Transactional
    public void importPersonAsync(List<CcdiIntermediaryPersonExcel> excelList,
                                  Boolean isUpdateSupport,
                                  String taskId,
                                  String userName) {
        List<CcdiBizIntermediary> newRecords = new ArrayList<>();
        List<CcdiBizIntermediary> updateRecords = new ArrayList<>();
        List<IntermediaryPersonImportFailureVO> failures = new ArrayList<>();

        // 1. 批量查询已存在的证件号
        Set<String> 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<Object, Object> 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<IntermediaryPersonImportFailureVO> 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<String> getExistingPersonIds(List<CcdiIntermediaryPersonExcel> excelList) {
        List<String> personIds = excelList.stream()
                .map(CcdiIntermediaryPersonExcel::getPersonId)
                .filter(StringUtils::isNotEmpty)
                .collect(Collectors.toList());

        if (personIds.isEmpty()) {
            return Collections.emptySet();
        }

        LambdaQueryWrapper<CcdiBizIntermediary> wrapper = new LambdaQueryWrapper<>();
        wrapper.select(CcdiBizIntermediary::getBizId, CcdiBizIntermediary::getPersonId);
        wrapper.in(CcdiBizIntermediary::getPersonId, personIds);
        List<CcdiBizIntermediary> 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<String, Object> 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<String, Object> redisTemplate;

    @Override
    @Async
    @Transactional
    public void importEntityAsync(List<CcdiIntermediaryEntityExcel> excelList,
                                  Boolean isUpdateSupport,
                                  String taskId,
                                  String userName) {
        List<CcdiEnterpriseBaseInfo> newRecords = new ArrayList<>();
        List<CcdiEnterpriseBaseInfo> updateRecords = new ArrayList<>();
        List<IntermediaryEntityImportFailureVO> failures = new ArrayList<>();

        // 1. 批量查询已存在的统一社会信用代码
        Set<String> 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<Object, Object> 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<IntermediaryEntityImportFailureVO> 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<String> getExistingSocialCreditCodes(List<CcdiIntermediaryEntityExcel> excelList) {
        List<String> codes = excelList.stream()
                .map(CcdiIntermediaryEntityExcel::getSocialCreditCode)
                .filter(StringUtils::isNotEmpty)
                .collect(Collectors.toList());

        if (codes.isEmpty()) {
            return Collections.emptySet();
        }

        LambdaQueryWrapper<CcdiEnterpriseBaseInfo> wrapper = new LambdaQueryWrapper<>();
        wrapper.select(CcdiEnterpriseBaseInfo::getSocialCreditCode);
        wrapper.in(CcdiEnterpriseBaseInfo::getSocialCreditCode, codes);
        List<CcdiEnterpriseBaseInfo> 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<String, Object> 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<String, Object> 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<String, Object> redisTemplate`

**Step 3: 添加RedisTemplate注入**

在类的开头添加:
```java
@Resource
private RedisTemplate<String, Object> 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<CcdiIntermediaryPersonExcel> 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<IntermediaryPersonImportFailureVO> failures =
        personImportService.getImportFailures(taskId);

    // 手动分页
    int fromIndex = (pageNum - 1) * pageSize;
    int toIndex = Math.min(fromIndex + pageSize, failures.size());

    List<IntermediaryPersonImportFailureVO> 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<CcdiIntermediaryEntityExcel> 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<IntermediaryEntityImportFailureVO> failures =
        entityImportService.getImportFailures(taskId);

    // 手动分页
    int fromIndex = (pageNum - 1) * pageSize;
    int toIndex = Math.min(fromIndex + pageSize, failures.size());

    List<IntermediaryEntityImportFailureVO> 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
<!-- 个人中介导入失败记录按钮 -->
<el-col :span="1.5" v-if="personShowFailureButton">
  <el-button
    type="warning"
    plain
    icon="el-icon-warning"
    size="mini"
    @click="viewPersonImportFailures"
  >查看个人中介导入失败记录</el-button>
</el-col>

<!-- 实体中介导入失败记录按钮 -->
<el-col :span="1.5" v-if="entityShowFailureButton">
  <el-button
    type="warning"
    plain
    icon="el-icon-warning"
    size="mini"
    @click="viewEntityImportFailures"
  >查看实体中介导入失败记录</el-button>
</el-col>
```

**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
<!-- 个人中介导入失败记录对话框 -->
<el-dialog
  title="个人中介导入失败记录"
  :visible.sync="personFailureDialogVisible"
  width="1200px"
  append-to-body
>
  <el-table :data="personFailureList" v-loading="personFailureLoading">
    <el-table-column label="姓名" prop="name" align="center" width="120" />
    <el-table-column label="证件号码" prop="personId" align="center" width="180" />
    <el-table-column label="人员类型" prop="personType" align="center" width="120" />
    <el-table-column label="性别" prop="gender" align="center" width="80" />
    <el-table-column label="手机号码" prop="mobile" align="center" width="130" />
    <el-table-column label="所在公司" prop="company" align="center" width="150" />
    <el-table-column label="失败原因" prop="errorMessage" align="center" min-width="200" :show-overflow-tooltip="true" />
  </el-table>

  <pagination
    v-show="personFailureTotal > 0"
    :total="personFailureTotal"
    :page.sync="personFailureQueryParams.pageNum"
    :limit.sync="personFailureQueryParams.pageSize"
    @pagination="getPersonFailureList"
  />

  <div slot="footer" class="dialog-footer">
    <el-button @click="personFailureDialogVisible = false">关闭</el-button>
  </div>
</el-dialog>
```

**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
<!-- 实体中介导入失败记录对话框 -->
<el-dialog
  title="实体中介导入失败记录"
  :visible.sync="entityFailureDialogVisible"
  width="1200px"
  append-to-body
>
  <el-table :data="entityFailureList" v-loading="entityFailureLoading">
    <el-table-column label="机构名称" prop="enterpriseName" align="center" width="200" />
    <el-table-column label="统一社会信用代码" prop="socialCreditCode" align="center" width="180" />
    <el-table-column label="主体类型" prop="enterpriseType" align="center" width="120" />
    <el-table-column label="企业性质" prop="enterpriseNature" align="center" width="120" />
    <el-table-column label="法定代表人" prop="legalRepresentative" align="center" width="120" />
    <el-table-column label="成立日期" prop="establishDate" align="center" width="120" />
    <el-table-column label="失败原因" prop="errorMessage" align="center" min-width="200" :show-overflow-tooltip="true" />
  </el-table>

  <pagination
    v-show="entityFailureTotal > 0"
    :total="entityFailureTotal"
    :page.sync="entityFailureQueryParams.pageNum"
    :limit.sync="entityFailureQueryParams.pageSize"
    @pagination="getEntityFailureList"
  />

  <div slot="footer" class="dialog-footer">
    <el-button @click="entityFailureDialogVisible = false">关闭</el-button>
  </div>
</el-dialog>
```

**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
