- Maven 模块从 ruoyi-ccdi 重命名为 ruoyi-info-collection - Java 包名从 com.ruoyi.ccdi 改为 com.ruoyi.info.collection - MyBatis XML 命名空间同步更新 - 保留数据库表名、API URL、权限标识中的 ccdi 前缀 - 更新项目文档中的模块引用
1452 lines
35 KiB
Markdown
1452 lines
35 KiB
Markdown
# 员工信息异步导入功能实施计划
|
|
|
|
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
|
|
**目标:** 实现员工信息的异步导入功能,将导入结果存储在Redis中,前端通过轮询查询导入状态,并提供失败记录查询功能。
|
|
|
|
**架构:** 使用Spring @Async实现异步导入,MyBatis Plus批量插入新数据,自定义SQL使用ON DUPLICATE KEY UPDATE批量更新已有数据,Redis存储导入状态和失败记录(TTL 7天),前端轮询查询状态并显示结果。
|
|
|
|
**技术栈:** Spring Boot 3.5.8, MyBatis Plus 3.5.10, Redis, Vue 2.6.12, Element UI
|
|
|
|
---
|
|
|
|
## Task 1: 配置异步支持
|
|
|
|
**目标:** 创建异步配置类,设置专用线程池处理导入任务
|
|
|
|
**文件:**
|
|
- 创建: `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/config/AsyncConfig.java`
|
|
|
|
**步骤 1: 创建AsyncConfig配置类**
|
|
|
|
```java
|
|
package com.ruoyi.ccdi.config;
|
|
|
|
import org.springframework.context.annotation.Bean;
|
|
import org.springframework.context.annotation.Configuration;
|
|
import org.springframework.scheduling.annotation.EnableAsync;
|
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
|
|
|
import java.util.concurrent.Executor;
|
|
import java.util.concurrent.ThreadPoolExecutor;
|
|
|
|
/**
|
|
* 异步配置类
|
|
*
|
|
* @author ruoyi
|
|
*/
|
|
@Configuration
|
|
@EnableAsync
|
|
public class AsyncConfig {
|
|
|
|
/**
|
|
* 导入任务专用线程池
|
|
*/
|
|
@Bean("importExecutor")
|
|
public Executor importExecutor() {
|
|
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
|
// 核心线程数
|
|
executor.setCorePoolSize(2);
|
|
// 最大线程数
|
|
executor.setMaxPoolSize(5);
|
|
// 队列容量
|
|
executor.setQueueCapacity(100);
|
|
// 线程名前缀
|
|
executor.setThreadNamePrefix("import-async-");
|
|
// 拒绝策略:由调用线程执行
|
|
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
|
// 等待所有任务完成后再关闭线程池
|
|
executor.setWaitForTasksToCompleteOnShutdown(true);
|
|
// 等待时间
|
|
executor.setAwaitTerminationSeconds(60);
|
|
executor.initialize();
|
|
return executor;
|
|
}
|
|
}
|
|
```
|
|
|
|
**步骤 2: 验证配置是否生效**
|
|
|
|
启动应用后,查看日志中是否有线程池创建信息。或者在后续任务中测试异步方法。
|
|
|
|
**步骤 3: 提交配置**
|
|
|
|
```bash
|
|
git add ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/config/AsyncConfig.java
|
|
git commit -m "feat: 添加异步配置类,配置导入任务专用线程池"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 2: 创建VO类
|
|
|
|
**目标:** 创建导入结果、状态和失败记录的VO类
|
|
|
|
**文件:**
|
|
- 创建: `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/domain/vo/ImportResultVO.java`
|
|
- 创建: `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/domain/vo/ImportStatusVO.java`
|
|
- 创建: `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/domain/vo/ImportFailureVO.java`
|
|
|
|
**步骤 1: 创建ImportResultVO**
|
|
|
|
```java
|
|
package com.ruoyi.ccdi.domain.vo;
|
|
|
|
import io.swagger.v3.oas.annotations.media.Schema;
|
|
import lombok.Data;
|
|
|
|
/**
|
|
* 导入结果VO
|
|
*
|
|
* @author ruoyi
|
|
*/
|
|
@Data
|
|
@Schema(description = "导入结果")
|
|
public class ImportResultVO {
|
|
|
|
@Schema(description = "任务ID")
|
|
private String taskId;
|
|
|
|
@Schema(description = "状态: PROCESSING-处理中, SUCCESS-成功, PARTIAL_SUCCESS-部分成功, FAILED-失败")
|
|
private String status;
|
|
|
|
@Schema(description = "消息")
|
|
private String message;
|
|
}
|
|
```
|
|
|
|
**步骤 2: 创建ImportStatusVO**
|
|
|
|
```java
|
|
package com.ruoyi.ccdi.domain.vo;
|
|
|
|
import io.swagger.v3.oas.annotations.media.Schema;
|
|
import lombok.Data;
|
|
|
|
/**
|
|
* 导入状态VO
|
|
*
|
|
* @author ruoyi
|
|
*/
|
|
@Data
|
|
@Schema(description = "导入状态")
|
|
public class ImportStatusVO {
|
|
|
|
@Schema(description = "任务ID")
|
|
private String taskId;
|
|
|
|
@Schema(description = "状态")
|
|
private String status;
|
|
|
|
@Schema(description = "总记录数")
|
|
private Integer totalCount;
|
|
|
|
@Schema(description = "成功数")
|
|
private Integer successCount;
|
|
|
|
@Schema(description = "失败数")
|
|
private Integer failureCount;
|
|
|
|
@Schema(description = "进度百分比")
|
|
private Integer progress;
|
|
|
|
@Schema(description = "开始时间戳")
|
|
private Long startTime;
|
|
|
|
@Schema(description = "结束时间戳")
|
|
private Long endTime;
|
|
|
|
@Schema(description = "状态消息")
|
|
private String message;
|
|
}
|
|
```
|
|
|
|
**步骤 3: 创建ImportFailureVO**
|
|
|
|
```java
|
|
package com.ruoyi.ccdi.domain.vo;
|
|
|
|
import io.swagger.v3.oas.annotations.media.Schema;
|
|
import lombok.Data;
|
|
|
|
/**
|
|
* 导入失败记录VO
|
|
*
|
|
* @author ruoyi
|
|
*/
|
|
@Data
|
|
@Schema(description = "导入失败记录")
|
|
public class ImportFailureVO {
|
|
|
|
@Schema(description = "柜员号")
|
|
private Long employeeId;
|
|
|
|
@Schema(description = "姓名")
|
|
private String name;
|
|
|
|
@Schema(description = "身份证号")
|
|
private String idCard;
|
|
|
|
@Schema(description = "部门ID")
|
|
private Long deptId;
|
|
|
|
@Schema(description = "电话")
|
|
private String phone;
|
|
|
|
@Schema(description = "状态")
|
|
private String status;
|
|
|
|
@Schema(description = "入职时间")
|
|
private String hireDate;
|
|
|
|
@Schema(description = "错误信息")
|
|
private String errorMessage;
|
|
}
|
|
```
|
|
|
|
**步骤 4: 提交VO类**
|
|
|
|
```bash
|
|
git add ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/domain/vo/
|
|
git commit -m "feat: 添加导入相关VO类(ImportResultVO, ImportStatusVO, ImportFailureVO)"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 3: 添加数据库UNIQUE约束
|
|
|
|
**目标:** 确保employee_id字段有UNIQUE约束,支持ON DUPLICATE KEY UPDATE
|
|
|
|
**文件:**
|
|
- 修改: 数据库表 `ccdi_employee`
|
|
|
|
**步骤 1: 检查现有约束**
|
|
|
|
```sql
|
|
-- 使用MCP连接数据库查询
|
|
SHOW INDEX FROM ccdi_employee WHERE Key_name = 'uk_employee_id';
|
|
```
|
|
|
|
**步骤 2: 添加UNIQUE约束(如果不存在)**
|
|
|
|
```sql
|
|
-- 如果不存在uk_employee_id索引,则添加
|
|
ALTER TABLE ccdi_employee
|
|
ADD UNIQUE KEY uk_employee_id (employee_id);
|
|
```
|
|
|
|
**步骤 3: 验证约束**
|
|
|
|
```sql
|
|
-- 验证索引已创建
|
|
SHOW INDEX FROM ccdi_employee WHERE Key_name = 'uk_employee_id';
|
|
```
|
|
|
|
**步骤 4: 记录SQL变更**
|
|
|
|
在项目 `sql` 目录下创建 `sql/update_ccdi_employee_20260206.sql`:
|
|
|
|
```sql
|
|
-- 员工信息表添加柜员号唯一索引
|
|
-- 日期: 2026-02-06
|
|
-- 目的: 支持批量更新时使用ON DUPLICATE KEY UPDATE语法
|
|
|
|
ALTER TABLE ccdi_employee
|
|
ADD UNIQUE KEY uk_employee_id (employee_id)
|
|
COMMENT '柜员号唯一索引';
|
|
```
|
|
|
|
**步骤 5: 提交SQL变更**
|
|
|
|
```bash
|
|
git add sql/update_ccdi_employee_20260206.sql
|
|
git commit -m "feat: 添加员工表柜员号唯一索引,支持批量更新"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 4: 添加Mapper方法
|
|
|
|
**目标:** 在Mapper接口和XML中添加批量查询和批量插入更新的方法
|
|
|
|
**文件:**
|
|
- 修改: `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/mapper/CcdiEmployeeMapper.java`
|
|
- 修改: `ruoyi-info-collection/src/main/resources/mapper/ccdi/CcdiEmployeeMapper.xml`
|
|
|
|
**步骤 1: 在Mapper接口中添加方法**
|
|
|
|
在 `CcdiEmployeeMapper.java` 中添加:
|
|
|
|
```java
|
|
/**
|
|
* 批量插入或更新员工信息
|
|
* 使用ON DUPLICATE KEY UPDATE语法
|
|
*
|
|
* @param list 员工信息列表
|
|
* @return 影响行数
|
|
*/
|
|
int insertOrUpdateBatch(@Param("list") List<CcdiEmployee> list);
|
|
```
|
|
|
|
**步骤 2: 在Mapper.xml中添加SQL**
|
|
|
|
在 `CcdiEmployeeMapper.xml` 中添加:
|
|
|
|
```xml
|
|
<!-- 批量插入或更新员工信息 -->
|
|
<insert id="insertOrUpdateBatch" parameterType="java.util.List">
|
|
INSERT INTO ccdi_employee
|
|
(employee_id, name, dept_id, id_card, phone, hire_date, status,
|
|
create_time, create_by, update_by, update_time, remark)
|
|
VALUES
|
|
<foreach collection="list" item="item" separator=",">
|
|
(#{item.employeeId}, #{item.name}, #{item.deptId}, #{item.idCard},
|
|
#{item.phone}, #{item.hireDate}, #{item.status}, NOW(),
|
|
#{item.createBy}, #{item.updateBy}, NOW(), #{item.remark})
|
|
</foreach>
|
|
ON DUPLICATE KEY UPDATE
|
|
name = VALUES(name),
|
|
dept_id = VALUES(dept_id),
|
|
phone = VALUES(phone),
|
|
hire_date = VALUES(hire_date),
|
|
status = VALUES(status),
|
|
update_by = VALUES(update_by),
|
|
update_time = NOW(),
|
|
remark = VALUES(remark)
|
|
</insert>
|
|
```
|
|
|
|
**步骤 3: 提交Mapper变更**
|
|
|
|
```bash
|
|
git add ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/mapper/CcdiEmployeeMapper.java
|
|
git add ruoyi-info-collection/src/main/resources/mapper/ccdi/CcdiEmployeeMapper.xml
|
|
git commit -m "feat: 添加批量插入或更新员工信息方法"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 5: 实现Service层异步导入方法
|
|
|
|
**目标:** 实现异步导入逻辑,包括数据分类、批量操作、Redis存储
|
|
|
|
**文件:**
|
|
- 修改: `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/service/ICcdiEmployeeService.java`
|
|
- 修改: `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/service/impl/CcdiEmployeeServiceImpl.java`
|
|
|
|
**步骤 1: 在Service接口中添加方法声明**
|
|
|
|
在 `ICcdiEmployeeService.java` 中添加:
|
|
|
|
```java
|
|
/**
|
|
* 异步导入员工数据
|
|
*
|
|
* @param excelList Excel数据列表
|
|
* @param isUpdateSupport 是否更新已存在的数据
|
|
* @return CompletableFuture包含导入结果
|
|
*/
|
|
CompletableFuture<ImportResultVO> importEmployeeAsync(List<CcdiEmployeeExcel> excelList, Boolean isUpdateSupport);
|
|
|
|
/**
|
|
* 查询导入状态
|
|
*
|
|
* @param taskId 任务ID
|
|
* @return 导入状态信息
|
|
*/
|
|
ImportStatusVO getImportStatus(String taskId);
|
|
|
|
/**
|
|
* 获取导入失败记录
|
|
*
|
|
* @param taskId 任务ID
|
|
* @return 失败记录列表
|
|
*/
|
|
List<ImportFailureVO> getImportFailures(String taskId);
|
|
```
|
|
|
|
**步骤 2: 在ServiceImpl中实现异步方法**
|
|
|
|
在 `CcdiEmployeeServiceImpl.java` 中添加依赖注入:
|
|
|
|
```java
|
|
@Resource
|
|
private RedisTemplate<String, Object> redisTemplate;
|
|
|
|
@Resource(name = "importExecutor")
|
|
private Executor importExecutor;
|
|
```
|
|
|
|
**步骤 3: 实现异步导入核心逻辑**
|
|
|
|
在 `CcdiEmployeeServiceImpl.java` 中添加:
|
|
|
|
```java
|
|
/**
|
|
* 异步导入员工数据
|
|
*/
|
|
@Override
|
|
@Async("importExecutor")
|
|
public CompletableFuture<ImportResultVO> importEmployeeAsync(List<CcdiEmployeeExcel> excelList, Boolean isUpdateSupport) {
|
|
// 生成任务ID
|
|
String taskId = UUID.randomUUID().toString();
|
|
long startTime = System.currentTimeMillis();
|
|
|
|
// 初始化Redis状态
|
|
String statusKey = "import:employee:" + taskId;
|
|
Map<String, Object> statusData = new HashMap<>();
|
|
statusData.put("taskId", taskId);
|
|
statusData.put("status", "PROCESSING");
|
|
statusData.put("totalCount", excelList.size());
|
|
statusData.put("successCount", 0);
|
|
statusData.put("failureCount", 0);
|
|
statusData.put("progress", 0);
|
|
statusData.put("startTime", startTime);
|
|
statusData.put("message", "正在处理...");
|
|
|
|
redisTemplate.opsForHash().putAll(statusKey, statusData);
|
|
redisTemplate.expire(statusKey, 7, TimeUnit.DAYS);
|
|
|
|
try {
|
|
// 执行导入逻辑
|
|
ImportResult result = doImport(excelList, isUpdateSupport, taskId);
|
|
|
|
// 更新最终状态
|
|
String finalStatus = result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS";
|
|
updateImportStatus(taskId, finalStatus, result, startTime);
|
|
|
|
// 返回结果
|
|
ImportResultVO resultVO = new ImportResultVO();
|
|
resultVO.setTaskId(taskId);
|
|
resultVO.setStatus(finalStatus);
|
|
resultVO.setMessage("导入任务已提交");
|
|
|
|
return CompletableFuture.completedFuture(resultVO);
|
|
|
|
} catch (Exception e) {
|
|
// 处理异常
|
|
Map<String, Object> errorData = new HashMap<>();
|
|
errorData.put("status", "FAILED");
|
|
errorData.put("message", "导入失败: " + e.getMessage());
|
|
errorData.put("endTime", System.currentTimeMillis());
|
|
redisTemplate.opsForHash().putAll(statusKey, errorData);
|
|
|
|
ImportResultVO resultVO = new ImportResultVO();
|
|
resultVO.setTaskId(taskId);
|
|
resultVO.setStatus("FAILED");
|
|
resultVO.setMessage("导入失败: " + e.getMessage());
|
|
|
|
return CompletableFuture.completedFuture(resultVO);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 执行导入逻辑
|
|
*/
|
|
private ImportResult doImport(List<CcdiEmployeeExcel> excelList, Boolean isUpdateSupport, String taskId) {
|
|
List<CcdiEmployee> newRecords = new ArrayList<>();
|
|
List<CcdiEmployee> updateRecords = new ArrayList<>();
|
|
List<ImportFailureVO> failures = new ArrayList<>();
|
|
|
|
// 批量查询已存在的柜员号
|
|
Set<Long> existingIds = getExistingEmployeeIds(excelList);
|
|
|
|
// 分类数据
|
|
for (int i = 0; i < excelList.size(); i++) {
|
|
CcdiEmployeeExcel excel = excelList.get(i);
|
|
|
|
try {
|
|
// 验证数据
|
|
validateEmployeeData(excel, existingIds);
|
|
|
|
CcdiEmployee employee = convertToEntity(excel);
|
|
|
|
if (existingIds.contains(excel.getEmployeeId())) {
|
|
if (isUpdateSupport) {
|
|
updateRecords.add(employee);
|
|
} else {
|
|
throw new RuntimeException("柜员号已存在且未启用更新支持");
|
|
}
|
|
} else {
|
|
newRecords.add(employee);
|
|
}
|
|
|
|
// 更新进度
|
|
int progress = (int) ((i + 1) * 100.0 / excelList.size());
|
|
updateImportProgress(taskId, progress);
|
|
|
|
} catch (Exception e) {
|
|
ImportFailureVO failure = new ImportFailureVO();
|
|
BeanUtils.copyProperties(excel, failure);
|
|
failure.setErrorMessage(e.getMessage());
|
|
failures.add(failure);
|
|
}
|
|
}
|
|
|
|
// 批量插入新数据
|
|
if (!newRecords.isEmpty()) {
|
|
saveBatch(newRecords, 500);
|
|
}
|
|
|
|
// 批量更新已有数据
|
|
if (!updateRecords.isEmpty() && isUpdateSupport) {
|
|
employeeMapper.insertOrUpdateBatch(updateRecords);
|
|
}
|
|
|
|
// 保存失败记录到Redis
|
|
if (!failures.isEmpty()) {
|
|
String failuresKey = "import:employee:" + taskId + ":failures";
|
|
redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS);
|
|
}
|
|
|
|
// 构建结果
|
|
ImportResult result = new ImportResult();
|
|
result.setTotalCount(excelList.size());
|
|
result.setSuccessCount(newRecords.size() + updateRecords.size());
|
|
result.setFailureCount(failures.size());
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* 获取已存在的员工ID集合
|
|
*/
|
|
private Set<Long> getExistingEmployeeIds(List<CcdiEmployeeExcel> excelList) {
|
|
List<Long> employeeIds = excelList.stream()
|
|
.map(CcdiEmployeeExcel::getEmployeeId)
|
|
.filter(Objects::nonNull)
|
|
.collect(Collectors.toList());
|
|
|
|
if (employeeIds.isEmpty()) {
|
|
return Collections.emptySet();
|
|
}
|
|
|
|
List<CcdiEmployee> existingEmployees = employeeMapper.selectBatchIds(employeeIds);
|
|
return existingEmployees.stream()
|
|
.map(CcdiEmployee::getEmployeeId)
|
|
.collect(Collectors.toSet());
|
|
}
|
|
|
|
/**
|
|
* 转换Excel对象为Entity
|
|
*/
|
|
private CcdiEmployee convertToEntity(CcdiEmployeeExcel excel) {
|
|
CcdiEmployee employee = new CcdiEmployee();
|
|
BeanUtils.copyProperties(excel, employee);
|
|
return employee;
|
|
}
|
|
|
|
/**
|
|
* 更新导入进度
|
|
*/
|
|
private void updateImportProgress(String taskId, Integer progress) {
|
|
String key = "import:employee:" + taskId;
|
|
redisTemplate.opsForHash().put(key, "progress", progress);
|
|
}
|
|
|
|
/**
|
|
* 更新导入状态
|
|
*/
|
|
private void updateImportStatus(String taskId, String status, ImportResult result, Long startTime) {
|
|
String key = "import:employee:" + 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);
|
|
}
|
|
|
|
/**
|
|
* 导入结果内部类
|
|
*/
|
|
@Data
|
|
private static class ImportResult {
|
|
private Integer totalCount;
|
|
private Integer successCount;
|
|
private Integer failureCount;
|
|
}
|
|
```
|
|
|
|
**步骤 4: 实现查询导入状态方法**
|
|
|
|
```java
|
|
@Override
|
|
public ImportStatusVO getImportStatus(String taskId) {
|
|
String key = "import:employee:" + 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;
|
|
}
|
|
```
|
|
|
|
**步骤 5: 实现查询失败记录方法**
|
|
|
|
```java
|
|
@Override
|
|
public List<ImportFailureVO> getImportFailures(String taskId) {
|
|
String key = "import:employee:" + taskId + ":failures";
|
|
Object failuresObj = redisTemplate.opsForValue().get(key);
|
|
|
|
if (failuresObj == null) {
|
|
return Collections.emptyList();
|
|
}
|
|
|
|
// 使用JSON转换
|
|
return JSON.parseArray(JSON.toJSONString(failuresObj), ImportFailureVO.class);
|
|
}
|
|
```
|
|
|
|
**步骤 6: 提交Service层代码**
|
|
|
|
```bash
|
|
git add ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/service/
|
|
git commit -m "feat: 实现员工信息异步导入服务"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 6: 修改Controller层接口
|
|
|
|
**目标:** 修改导入接口为异步,添加状态查询和失败记录查询接口
|
|
|
|
**文件:**
|
|
- 修改: `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/controller/CcdiEmployeeController.java`
|
|
|
|
**步骤 1: 添加Resource注入**
|
|
|
|
在Controller中添加:
|
|
|
|
```java
|
|
@Resource
|
|
private RedisTemplate<String, Object> redisTemplate;
|
|
```
|
|
|
|
**步骤 2: 修改导入接口**
|
|
|
|
将现有的 `importData` 方法改为:
|
|
|
|
```java
|
|
/**
|
|
* 导入员工信息(异步)
|
|
*/
|
|
@Operation(summary = "导入员工信息")
|
|
@PreAuthorize("@ss.hasPermi('ccdi:employee:import')")
|
|
@Log(title = "员工信息", businessType = BusinessType.IMPORT)
|
|
@PostMapping("/importData")
|
|
public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception {
|
|
List<CcdiEmployeeExcel> list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiEmployeeExcel.class);
|
|
|
|
if (list == null || list.isEmpty()) {
|
|
return error("至少需要一条数据");
|
|
}
|
|
|
|
// 异步导入
|
|
CompletableFuture<ImportResultVO> future = employeeService.importEmployeeAsync(list, updateSupport);
|
|
|
|
// 立即返回taskId
|
|
ImportResultVO result = future.get();
|
|
|
|
return success("导入任务已提交,正在后台处理", result);
|
|
}
|
|
```
|
|
|
|
**步骤 3: 添加状态查询接口**
|
|
|
|
```java
|
|
/**
|
|
* 查询导入状态
|
|
*/
|
|
@Operation(summary = "查询员工导入状态")
|
|
@GetMapping("/importStatus/{taskId}")
|
|
public AjaxResult getImportStatus(@PathVariable String taskId) {
|
|
try {
|
|
ImportStatusVO status = employeeService.getImportStatus(taskId);
|
|
return success(status);
|
|
} catch (Exception e) {
|
|
return error(e.getMessage());
|
|
}
|
|
}
|
|
```
|
|
|
|
**步骤 4: 添加失败记录查询接口**
|
|
|
|
```java
|
|
/**
|
|
* 查询导入失败记录
|
|
*/
|
|
@Operation(summary = "查询导入失败记录")
|
|
@GetMapping("/importFailures/{taskId}")
|
|
public TableDataInfo getImportFailures(
|
|
@PathVariable String taskId,
|
|
@RequestParam(defaultValue = "1") Integer pageNum,
|
|
@RequestParam(defaultValue = "10") Integer pageSize) {
|
|
|
|
List<ImportFailureVO> failures = employeeService.getImportFailures(taskId);
|
|
|
|
// 手动分页
|
|
int fromIndex = (pageNum - 1) * pageSize;
|
|
int toIndex = Math.min(fromIndex + pageSize, failures.size());
|
|
|
|
List<ImportFailureVO> pageData = failures.subList(fromIndex, toIndex);
|
|
|
|
return getDataTable(pageData, failures.size());
|
|
}
|
|
```
|
|
|
|
**步骤 5: 提交Controller变更**
|
|
|
|
```bash
|
|
git add ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/controller/CcdiEmployeeController.java
|
|
git commit -m "feat: 修改导入接口为异步,添加状态和失败记录查询接口"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 7: 前端API定义
|
|
|
|
**目标:** 在前端添加导入状态查询和失败记录查询的API方法
|
|
|
|
**文件:**
|
|
- 修改: `ruoyi-ui/src/api/ccdiEmployee.js`
|
|
|
|
**步骤 1: 添加API方法**
|
|
|
|
在 `ccdiEmployee.js` 中添加:
|
|
|
|
```javascript
|
|
// 查询导入状态
|
|
export function getImportStatus(taskId) {
|
|
return request({
|
|
url: '/ccdi/employee/importStatus/' + taskId,
|
|
method: 'get'
|
|
})
|
|
}
|
|
|
|
// 查询导入失败记录
|
|
export function getImportFailures(taskId, pageNum, pageSize) {
|
|
return request({
|
|
url: '/ccdi/employee/importFailures/' + taskId,
|
|
method: 'get',
|
|
params: { pageNum, pageSize }
|
|
})
|
|
}
|
|
```
|
|
|
|
**步骤 2: 提交前端API变更**
|
|
|
|
```bash
|
|
git add ruoyi-ui/src/api/ccdiEmployee.js
|
|
git commit -m "feat: 添加导入状态和失败记录查询API"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 8: 前端导入流程优化
|
|
|
|
**目标:** 修改导入成功处理逻辑,实现后台处理+完成通知
|
|
|
|
**文件:**
|
|
- 修改: `ruoyi-ui/src/views/ccdiEmployee/index.vue`
|
|
|
|
**步骤 1: 添加data属性**
|
|
|
|
在 `data()` 中添加:
|
|
|
|
```javascript
|
|
data() {
|
|
return {
|
|
// ...现有data
|
|
pollingTimer: null,
|
|
showFailureButton: false,
|
|
currentTaskId: null,
|
|
failureDialogVisible: false,
|
|
failureList: [],
|
|
failureLoading: false,
|
|
failureTotal: 0,
|
|
failureQueryParams: {
|
|
pageNum: 1,
|
|
pageSize: 10
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**步骤 2: 修改handleFileSuccess方法**
|
|
|
|
将现有的 `handleFileSuccess` 方法替换为:
|
|
|
|
```javascript
|
|
handleFileSuccess(response, file, fileList) {
|
|
this.upload.isUploading = false;
|
|
this.upload.open = false;
|
|
|
|
if (response.code === 200) {
|
|
const taskId = response.data.taskId;
|
|
|
|
// 显示后台处理提示
|
|
this.$notify({
|
|
title: '导入任务已提交',
|
|
message: '正在后台处理中,处理完成后将通知您',
|
|
type: 'info',
|
|
duration: 3000
|
|
});
|
|
|
|
// 开始轮询检查状态
|
|
this.startImportStatusPolling(taskId);
|
|
} else {
|
|
this.$modal.msgError(response.msg);
|
|
}
|
|
}
|
|
```
|
|
|
|
**步骤 3: 添加轮询方法**
|
|
|
|
```javascript
|
|
methods: {
|
|
// ...现有methods
|
|
|
|
startImportStatusPolling(taskId) {
|
|
this.pollingTimer = setInterval(async () => {
|
|
try {
|
|
const response = await getImportStatus(taskId);
|
|
|
|
if (response.data && response.data.status !== 'PROCESSING') {
|
|
clearInterval(this.pollingTimer);
|
|
this.handleImportComplete(response.data);
|
|
}
|
|
} catch (error) {
|
|
clearInterval(this.pollingTimer);
|
|
this.$modal.msgError('查询导入状态失败: ' + error.message);
|
|
}
|
|
}, 2000); // 每2秒轮询一次
|
|
},
|
|
|
|
handleImportComplete(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.showFailureButton = true;
|
|
this.currentTaskId = statusResult.taskId;
|
|
|
|
// 刷新列表
|
|
this.getList();
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**步骤 4: 添加生命周期销毁钩子**
|
|
|
|
```javascript
|
|
beforeDestroy() {
|
|
// 组件销毁时清除定时器
|
|
if (this.pollingTimer) {
|
|
clearInterval(this.pollingTimer);
|
|
this.pollingTimer = null;
|
|
}
|
|
}
|
|
```
|
|
|
|
**步骤 5: 提交前端轮询逻辑**
|
|
|
|
```bash
|
|
git add ruoyi-ui/src/views/ccdiEmployee/index.vue
|
|
git commit -m "feat: 实现导入状态轮询和完成通知"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 9: 添加失败记录查询UI
|
|
|
|
**目标:** 在页面添加查看失败记录按钮和对话框
|
|
|
|
**文件:**
|
|
- 修改: `ruoyi-ui/src/views/ccdiEmployee/index.vue`
|
|
|
|
**步骤 1: 在搜索表单区域添加按钮**
|
|
|
|
在 `<el-row :gutter="10" class="mb8">` 中添加:
|
|
|
|
```vue
|
|
<el-col :span="1.5" v-if="showFailureButton">
|
|
<el-button
|
|
type="warning"
|
|
plain
|
|
icon="el-icon-warning"
|
|
size="mini"
|
|
@click="viewImportFailures"
|
|
>查看导入失败记录</el-button>
|
|
</el-col>
|
|
```
|
|
|
|
**步骤 2: 添加失败记录对话框**
|
|
|
|
在模板最后、`</template>` 标签前添加:
|
|
|
|
```vue
|
|
<!-- 导入失败记录对话框 -->
|
|
<el-dialog
|
|
title="导入失败记录"
|
|
:visible.sync="failureDialogVisible"
|
|
width="1200px"
|
|
append-to-body
|
|
>
|
|
<el-table :data="failureList" v-loading="failureLoading">
|
|
<el-table-column label="姓名" prop="name" align="center" />
|
|
<el-table-column label="柜员号" prop="employeeId" align="center" />
|
|
<el-table-column label="身份证号" prop="idCard" align="center" />
|
|
<el-table-column label="电话" prop="phone" align="center" />
|
|
<el-table-column label="失败原因" prop="errorMessage" align="center" min-width="200" :show-overflow-tooltip="true" />
|
|
</el-table>
|
|
|
|
<pagination
|
|
v-show="failureTotal > 0"
|
|
:total="failureTotal"
|
|
:page.sync="failureQueryParams.pageNum"
|
|
:limit.sync="failureQueryParams.pageSize"
|
|
@pagination="getFailureList"
|
|
/>
|
|
|
|
<div slot="footer" class="dialog-footer">
|
|
<el-button @click="failureDialogVisible = false">关闭</el-button>
|
|
</div>
|
|
</el-dialog>
|
|
```
|
|
|
|
**步骤 3: 添加方法**
|
|
|
|
```javascript
|
|
methods: {
|
|
// ...现有methods
|
|
|
|
viewImportFailures() {
|
|
this.failureDialogVisible = true;
|
|
this.getFailureList();
|
|
},
|
|
|
|
getFailureList() {
|
|
this.failureLoading = true;
|
|
getImportFailures(
|
|
this.currentTaskId,
|
|
this.failureQueryParams.pageNum,
|
|
this.failureQueryParams.pageSize
|
|
).then(response => {
|
|
this.failureList = response.rows;
|
|
this.failureTotal = response.total;
|
|
this.failureLoading = false;
|
|
}).catch(error => {
|
|
this.failureLoading = false;
|
|
this.$modal.msgError('查询失败记录失败: ' + error.message);
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
**步骤 4: 添加样式(可选)**
|
|
|
|
```vue
|
|
<style scoped>
|
|
/* ...现有样式 */
|
|
|
|
.failure-dialog .el-table {
|
|
margin-top: 10px;
|
|
}
|
|
</style>
|
|
```
|
|
|
|
**步骤 5: 提交失败记录UI**
|
|
|
|
```bash
|
|
git add ruoyi-ui/src/views/ccdiEmployee/index.vue
|
|
git commit -m "feat: 添加导入失败记录查询对话框"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 10: 生成API文档
|
|
|
|
**目标:** 更新Swagger文档,记录新增的接口
|
|
|
|
**文件:**
|
|
- 修改: `doc/api/ccdi-employee-api.md`
|
|
|
|
**步骤 1: 添加接口文档**
|
|
|
|
```markdown
|
|
## 员工信息导入相关接口
|
|
|
|
### 1. 导入员工信息(异步)
|
|
|
|
**接口地址:** `POST /ccdi/employee/importData`
|
|
|
|
**权限标识:** `ccdi:employee:import`
|
|
|
|
**请求参数:**
|
|
|
|
| 参数名 | 类型 | 必填 | 说明 |
|
|
|--------|------|------|------|
|
|
| file | File | 是 | Excel文件 |
|
|
| updateSupport | boolean | 否 | 是否更新已存在的数据,默认false |
|
|
|
|
**响应示例:**
|
|
|
|
\`\`\`json
|
|
{
|
|
"code": 200,
|
|
"msg": "导入任务已提交,正在后台处理",
|
|
"data": {
|
|
"taskId": "uuid-string",
|
|
"status": "PROCESSING",
|
|
"message": "任务已创建"
|
|
}
|
|
}
|
|
\`\`\`
|
|
|
|
### 2. 查询导入状态
|
|
|
|
**接口地址:** `GET /ccdi/employee/importStatus/{taskId}`
|
|
|
|
**权限标识:** 无
|
|
|
|
**路径参数:**
|
|
|
|
| 参数名 | 类型 | 必填 | 说明 |
|
|
|--------|------|------|------|
|
|
| taskId | String | 是 | 任务ID |
|
|
|
|
**响应示例:**
|
|
|
|
\`\`\`json
|
|
{
|
|
"code": 200,
|
|
"data": {
|
|
"taskId": "uuid-string",
|
|
"status": "SUCCESS",
|
|
"totalCount": 100,
|
|
"successCount": 95,
|
|
"failureCount": 5,
|
|
"progress": 100,
|
|
"startTime": 1707225600000,
|
|
"endTime": 1707225900000,
|
|
"message": "导入完成"
|
|
}
|
|
}
|
|
\`\`\`
|
|
|
|
### 3. 查询导入失败记录
|
|
|
|
**接口地址:** `GET /ccdi/employee/importFailures/{taskId}`
|
|
|
|
**权限标识:** 无
|
|
|
|
**路径参数:**
|
|
|
|
| 参数名 | 类型 | 必填 | 说明 |
|
|
|--------|------|------|------|
|
|
| taskId | String | 是 | 任务ID |
|
|
|
|
**查询参数:**
|
|
|
|
| 参数名 | 类型 | 必填 | 说明 |
|
|
|--------|------|------|------|
|
|
| pageNum | Integer | 否 | 页码,默认1 |
|
|
| pageSize | Integer | 否 | 每页条数,默认10 |
|
|
|
|
**响应示例:**
|
|
|
|
\`\`\`json
|
|
{
|
|
"code": 200,
|
|
"rows": [
|
|
{
|
|
"employeeId": "1234567",
|
|
"name": "张三",
|
|
"idCard": "110101199001011234",
|
|
"deptId": 100,
|
|
"phone": "13800138000",
|
|
"status": "0",
|
|
"hireDate": "2020-01-01",
|
|
"errorMessage": "身份证号格式错误"
|
|
}
|
|
],
|
|
"total": 5
|
|
}
|
|
\`\`\`
|
|
```
|
|
|
|
**步骤 2: 提交文档**
|
|
|
|
```bash
|
|
git add doc/api/ccdi-employee-api.md
|
|
git commit -m "docs: 更新员工导入相关接口文档"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 11: 编写测试脚本
|
|
|
|
**目标:** 生成完整的测试脚本,验证导入功能
|
|
|
|
**文件:**
|
|
- 创建: `test/test_employee_import.py`
|
|
|
|
**步骤 1: 创建测试脚本**
|
|
|
|
完整的测试脚本将在任务实施过程中生成,包括:
|
|
- 登录获取token
|
|
- 测试正常导入
|
|
- 测试导入状态查询
|
|
- 测试失败记录查询
|
|
- 测试重复导入(验证批量更新)
|
|
- 生成测试报告
|
|
|
|
**步骤 2: 提交测试脚本**
|
|
|
|
```bash
|
|
git add test/test_employee_import.py
|
|
git commit -m "test: 添加员工导入功能测试脚本"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 12: 最终集成测试
|
|
|
|
**目标:** 完整功能测试,确保前后端联调正常
|
|
|
|
**步骤 1: 启动后端服务**
|
|
|
|
```bash
|
|
cd ruoyi-admin
|
|
mvn spring-boot:run
|
|
```
|
|
|
|
**步骤 2: 启动前端服务**
|
|
|
|
```bash
|
|
cd ruoyi-ui
|
|
npm run dev
|
|
```
|
|
|
|
**步骤 3: 手动测试流程**
|
|
|
|
1. 访问 `http://localhost` 登录系统
|
|
2. 进入员工信息管理页面
|
|
3. 点击"导入"按钮
|
|
4. 下载模板并填写测试数据
|
|
5. 上传文件,观察是否显示"导入任务已提交"通知
|
|
6. 等待2-5秒,观察是否显示"导入完成"通知
|
|
7. 如果有失败数据,验证"查看导入失败记录"按钮是否显示
|
|
8. 点击按钮查看失败记录对话框
|
|
9. 验证失败记录是否正确显示
|
|
|
|
**步骤 4: 性能测试**
|
|
|
|
使用包含100、1000、10000条数据的Excel文件进行测试,验证:
|
|
- 导入响应时间 < 500ms(立即返回taskId)
|
|
- 1000条数据处理时间 < 10秒
|
|
- 10000条数据处理时间 < 60秒
|
|
|
|
**步骤 5: Redis验证**
|
|
|
|
使用Redis客户端查看:
|
|
|
|
```bash
|
|
# 连接Redis
|
|
redis-cli
|
|
|
|
# 查看所有导入任务
|
|
KEYS import:employee:*
|
|
|
|
# 查看某个任务状态
|
|
HGETALL import:employee:{taskId}
|
|
|
|
# 查看失败记录
|
|
LRANGE import:employee:{taskId}:failures 0 -1
|
|
|
|
# 验证TTL
|
|
TTL import:employee:{taskId}
|
|
```
|
|
|
|
**步骤 6: 记录测试结果**
|
|
|
|
在 `doc/test/employee-import-test-report.md` 中记录测试结果:
|
|
|
|
```markdown
|
|
# 员工导入功能测试报告
|
|
|
|
**测试日期:** 2026-02-06
|
|
**测试人员:**
|
|
|
|
## 测试环境
|
|
|
|
- 后端版本:
|
|
- 前端版本:
|
|
- Redis版本:
|
|
- 浏览器版本:
|
|
|
|
## 功能测试
|
|
|
|
### 1. 正常导入测试
|
|
- [ ] 100条数据导入成功
|
|
- [ ] 1000条数据导入成功
|
|
- [ ] 状态查询正常
|
|
- [ ] 轮询通知正常
|
|
|
|
### 2. 失败数据处理测试
|
|
- [ ] 部分数据失败时正确显示失败记录
|
|
- [ ] 失误原因准确
|
|
- [ ] 失败记录可以正常查询
|
|
|
|
### 3. 批量更新测试
|
|
- [ ] 启用更新支持时,已有数据正确更新
|
|
- [ ] 未启用更新支持时,已有数据跳过
|
|
|
|
### 4. 并发测试
|
|
- [ ] 同时导入多个文件互不影响
|
|
- [ ] 任务ID唯一
|
|
- [ ] 各自状态独立
|
|
|
|
## 性能测试
|
|
|
|
| 数据量 | 响应时间 | 处理时间 | 结果 |
|
|
|--------|----------|----------|------|
|
|
| 100条 | | | |
|
|
| 1000条 | | | |
|
|
| 10000条 | | | |
|
|
|
|
## 问题记录
|
|
|
|
记录测试中发现的问题和解决方案。
|
|
|
|
## 测试结论
|
|
|
|
- [ ] 通过
|
|
- [ ] 不通过
|
|
```
|
|
|
|
**步骤 7: 提交测试报告**
|
|
|
|
```bash
|
|
git add doc/test/employee-import-test-report.md
|
|
git commit -m "test: 添加员工导入功能测试报告"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 13: 代码审查和优化
|
|
|
|
**目标:** 代码review,优化性能和代码质量
|
|
|
|
**检查项:**
|
|
|
|
- [ ] 代码符合项目编码规范
|
|
- [ ] 异常处理完善
|
|
- [ ] 日志记录充分
|
|
- [ ] Redis操作性能优化(考虑使用Pipeline)
|
|
- [ ] 批量操作批次大小合理
|
|
- [ ] 前端轮询间隔合理
|
|
- [ ] 内存使用合理,无内存泄漏风险
|
|
- [ ] 事务边界清晰
|
|
- [ ] 并发安全性
|
|
|
|
**步骤 1: 代码审查**
|
|
|
|
使用IDE的代码检查工具或人工review代码。
|
|
|
|
**步骤 2: 性能优化**
|
|
|
|
根据测试结果进行针对性优化:
|
|
- 调整批量操作批次大小
|
|
- 优化Redis操作
|
|
- 调整线程池参数
|
|
|
|
**步骤 3: 提交优化代码**
|
|
|
|
```bash
|
|
git add .
|
|
git commit -m "refactor: 代码审查和性能优化"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 14: 文档完善
|
|
|
|
**目标:** 完善用户手册和开发文档
|
|
|
|
**文件:**
|
|
- 修改: `README.md`
|
|
- 创建: `doc/user-guide/employee-import-guide.md`
|
|
|
|
**步骤 1: 更新README**
|
|
|
|
在项目README中添加导入功能说明。
|
|
|
|
**步骤 2: 编写用户指南**
|
|
|
|
```markdown
|
|
# 员工信息导入用户指南
|
|
|
|
## 导入流程
|
|
|
|
1. 准备Excel文件
|
|
2. 登录系统
|
|
3. 进入员工信息管理
|
|
4. 点击"导入"按钮
|
|
5. 选择文件并上传
|
|
6. 等待后台处理完成
|
|
7. 查看导入结果
|
|
8. 如有失败,查看并修正失败记录
|
|
|
|
## 常见问题
|
|
|
|
### Q1: 导入后多久能看到结果?
|
|
A: 通常2-10秒,具体取决于数据量。
|
|
|
|
### Q2: 失败记录会保留多久?
|
|
A: 7天,过期自动删除。
|
|
|
|
### Q3: 如何修正失败的数据?
|
|
A: 点击"查看导入失败记录",修正后重新导入。
|
|
```
|
|
|
|
**步骤 3: 提交文档**
|
|
|
|
```bash
|
|
git add README.md doc/user-guide/employee-import-guide.md
|
|
git commit -m "docs: 完善员工导入功能文档"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 15: 最终提交和发布
|
|
|
|
**目标:** 合并代码,发布新版本
|
|
|
|
**步骤 1: 查看所有变更**
|
|
|
|
```bash
|
|
git status
|
|
git log --oneline -10
|
|
```
|
|
|
|
**步骤 2: 创建合并请求**
|
|
|
|
如果使用Git Flow,创建feature分支合并请求。
|
|
|
|
**步骤 3: 代码最终review**
|
|
|
|
进行最后一次代码review。
|
|
|
|
**步骤 4: 合并到主分支**
|
|
|
|
```bash
|
|
git checkout dev
|
|
git merge feature/employee-async-import
|
|
git push origin dev
|
|
```
|
|
|
|
**步骤 5: 打Tag(可选)**
|
|
|
|
```bash
|
|
git tag -a v1.x.x -m "员工信息异步导入功能"
|
|
git push origin v1.x.x
|
|
```
|
|
|
|
**步骤 6: 部署到测试环境**
|
|
|
|
按照部署流程部署到测试环境。
|
|
|
|
**步骤 7: 用户验收测试**
|
|
|
|
邀请用户进行验收测试。
|
|
|
|
**步骤 8: 部署到生产环境**
|
|
|
|
验收通过后,部署到生产环境。
|
|
|
|
---
|
|
|
|
## 附录
|
|
|
|
### A. 相关文件清单
|
|
|
|
**后端:**
|
|
- `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/config/AsyncConfig.java`
|
|
- `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/domain/vo/ImportResultVO.java`
|
|
- `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/domain/vo/ImportStatusVO.java`
|
|
- `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/domain/vo/ImportFailureVO.java`
|
|
- `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/service/ICcdiEmployeeService.java`
|
|
- `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/service/impl/CcdiEmployeeServiceImpl.java`
|
|
- `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/mapper/CcdiEmployeeMapper.java`
|
|
- `ruoyi-info-collection/src/main/resources/mapper/ccdi/CcdiEmployeeMapper.xml`
|
|
- `ruoyi-info-collection/src/main/java/com/ruoyi/ccdi/controller/CcdiEmployeeController.java`
|
|
|
|
**前端:**
|
|
- `ruoyi-ui/src/api/ccdiEmployee.js`
|
|
- `ruoyi-ui/src/views/ccdiEmployee/index.vue`
|
|
|
|
**数据库:**
|
|
- `sql/update_ccdi_employee_20260206.sql`
|
|
|
|
### B. Redis Key命名规范
|
|
|
|
```
|
|
import:employee:{taskId} # 导入状态
|
|
import:employee:{taskId}:failures # 失败记录列表
|
|
```
|
|
|
|
### C. 状态枚举
|
|
|
|
| 状态值 | 说明 | 前端行为 |
|
|
|--------|------|----------|
|
|
| PROCESSING | 处理中 | 继续轮询 |
|
|
| SUCCESS | 全部成功 | 显示成功通知,刷新列表 |
|
|
| PARTIAL_SUCCESS | 部分成功 | 显示警告通知,显示失败按钮 |
|
|
| FAILED | 全部失败 | 显示错误通知,显示失败按钮 |
|
|
|
|
---
|
|
|
|
**计划版本:** 1.0
|
|
**创建日期:** 2026-02-06
|
|
**预计总工时:** 8-12小时
|