Files
ccdi/doc/requirements/plans/2026-02-06-intermediary-async-import.md
wkc 1cd87d2695 refactor: 重命名 ruoyi-ccdi 模块为 ruoyi-info-collection
- Maven 模块从 ruoyi-ccdi 重命名为 ruoyi-info-collection
- Java 包名从 com.ruoyi.ccdi 改为 com.ruoyi.info.collection
- MyBatis XML 命名空间同步更新
- 保留数据库表名、API URL、权限标识中的 ccdi 前缀
- 更新项目文档中的模块引用
2026-02-24 17:12:11 +08:00

57 KiB

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

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类

完整代码:

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: 提交

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类

完整代码:

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: 提交

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接口

完整代码:

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: 提交

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接口

完整代码:

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: 提交

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实现类

完整代码:

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: 提交

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实现类

完整代码:

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: 提交

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注入

在类的开头添加:

@Resource
private ICcdiIntermediaryPersonImportService personImportService;

@Resource
private ICcdiIntermediaryEntityImportService entityImportService;

Step 2: 添加初始化导入状态辅助方法

在Controller类中添加私有方法:

/**
 * 初始化导入状态到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注入

在类的开头添加:

@Resource
private RedisTemplate<String, Object> redisTemplate;

Step 4: 编译验证

Run: mvn compile -pl ruoyi-info-collection Expected: BUILD SUCCESS

Step 5: 提交

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方法

将方法修改为:

/**
 * 导入个人中介数据(异步)
 */
@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: 提交

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类中添加:

/**
 * 查询个人中介导入状态
 */
@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: 提交

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类中添加:

/**
 * 查询个人中介导入失败记录
 */
@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: 提交

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方法

将方法修改为:

/**
 * 导入实体中介数据(异步)
 */
@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: 提交

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类中添加:

/**
 * 查询实体中介导入状态
 */
@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: 提交

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类中添加:

/**
 * 查询实体中介导入失败记录
 */
@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: 提交

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

在文件末尾添加:

// 查询个人中介导入状态
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: 提交

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对象中添加:

// 个人中介导入相关
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: 提交

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方法修改为:

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: 提交

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对象中添加:

// 个人中介轮询
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: 提交

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钩子

在组件选项中添加:

beforeDestroy() {
  // 清除个人中介轮询定时器
  if (this.personPollingTimer) {
    clearInterval(this.personPollingTimer);
    this.personPollingTimer = null;
  }

  // 清除实体中介轮询定时器
  if (this.entityPollingTimer) {
    clearInterval(this.entityPollingTimer);
    this.entityPollingTimer = null;
  }
}

Step 2: 提交

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对象中添加:

// 查看个人中介导入失败记录
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: 提交

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中添加失败记录按钮

在按钮区域添加(参考现有的按钮布局):

<!-- 个人中介导入失败记录按钮 -->
<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: 提交

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末尾添加:

<!-- 个人中介导入失败记录对话框 -->
<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: 提交

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中添加实体中介失败记录对话框

在个人中介对话框后面添加:

<!-- 实体中介导入失败记录对话框 -->
<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: 提交

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: 添加导入相关接口文档

在文档末尾添加:

## 导入相关接口

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

响应示例:

{
  "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

响应示例:

{
  "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 接口描述: 查询实体中介导入失败记录

响应示例:

{
  "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: 创建测试脚本

完整代码:

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: 提交

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: 最终提交

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