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-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiEmployeeImportServiceImpl.java - 招聘导入实现:
ruoyi-ccdi/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-ccdi/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-ccdi
Expected: BUILD SUCCESS
Step 3: 提交
git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/vo/IntermediaryPersonImportFailureVO.java
git commit -m "feat: 添加个人中介导入失败记录VO"
Task 2: 创建实体中介导入失败记录VO
文件:
- Create:
ruoyi-ccdi/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-ccdi
Expected: BUILD SUCCESS
Step 3: 提交
git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/vo/IntermediaryEntityImportFailureVO.java
git commit -m "feat: 添加实体中介导入失败记录VO"
Task 3: 创建个人中介导入Service接口
文件:
- Create:
ruoyi-ccdi/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-ccdi
Expected: BUILD SUCCESS
Step 3: 提交
git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/ICcdiIntermediaryPersonImportService.java
git commit -m "feat: 添加个人中介异步导入Service接口"
Task 4: 创建实体中介导入Service接口
文件:
- Create:
ruoyi-ccdi/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-ccdi
Expected: BUILD SUCCESS
Step 3: 提交
git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/ICcdiIntermediaryEntityImportService.java
git commit -m "feat: 添加实体中介异步导入Service接口"
Task 5: 实现个人中介异步导入Service
文件:
- Create:
ruoyi-ccdi/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-ccdi
Expected: BUILD SUCCESS
Step 3: 提交
git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiIntermediaryPersonImportServiceImpl.java
git commit -m "feat: 实现个人中介异步导入Service"
Task 6: 实现实体中介异步导入Service
文件:
- Create:
ruoyi-ccdi/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-ccdi
Expected: BUILD SUCCESS
Step 3: 提交
git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiIntermediaryEntityImportServiceImpl.java
git commit -m "feat: 实现实体中介异步导入Service"
Task 7: 修改Controller - 注入Service和添加辅助方法
文件:
- Modify:
ruoyi-ccdi/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-ccdi
Expected: BUILD SUCCESS
Step 5: 提交
git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/controller/CcdiIntermediaryController.java
git commit -m "feat: Controller添加导入Service注入和辅助方法"
Task 8: 修改Controller - 改造个人中介导入接口为异步
文件:
- Modify:
ruoyi-ccdi/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-ccdi
Expected: BUILD SUCCESS
Step 3: 提交
git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/controller/CcdiIntermediaryController.java
git commit -m "feat: 改造个人中介导入接口为异步"
Task 9: 修改Controller - 添加个人中介状态查询接口
文件:
- Modify:
ruoyi-ccdi/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-ccdi
Expected: BUILD SUCCESS
Step 3: 提交
git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/controller/CcdiIntermediaryController.java
git commit -m "feat: 添加个人中介导入状态查询接口"
Task 10: 修改Controller - 添加个人中介失败记录查询接口
文件:
- Modify:
ruoyi-ccdi/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-ccdi
Expected: BUILD SUCCESS
Step 3: 提交
git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/controller/CcdiIntermediaryController.java
git commit -m "feat: 添加个人中介导入失败记录查询接口"
Task 11: 修改Controller - 改造实体中介导入接口为异步
文件:
- Modify:
ruoyi-ccdi/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-ccdi
Expected: BUILD SUCCESS
Step 3: 提交
git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/controller/CcdiIntermediaryController.java
git commit -m "feat: 改造实体中介导入接口为异步"
Task 12: 修改Controller - 添加实体中介状态查询接口
文件:
- Modify:
ruoyi-ccdi/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-ccdi
Expected: BUILD SUCCESS
Step 3: 提交
git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/controller/CcdiIntermediaryController.java
git commit -m "feat: 添加实体中介导入状态查询接口"
Task 13: 修改Controller - 添加实体中介失败记录查询接口
文件:
- Modify:
ruoyi-ccdi/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-ccdi
Expected: BUILD SUCCESS
Step 3: 提交
git add ruoyi-ccdi/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-ccdi
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