导入历史跟踪机制实战指南一、概述在后台管理系统中Excel 批量导入是高频操作。用户提交导入后通常需要知道这次导入了多少条、成功多少、失败多少、失败文件在哪里下载。如果只在接口响应中返回结果用户关闭页面后就无法再次查看。导入历史跟踪机制解决了这个问题每次导入操作都记录到一张导入管理表中用户可以在导入历史页面随时查看所有历史导入记录和结果。二、架构设计核心思路在导入流程的关键节点开始、完成、失败通过远程服务将导入状态和结果持久化到导入管理表中。流程节点节点动作status导入开始创建导入记录1进行中导入成功更新记录成功数、失败数、失败文件2成功导入失败更新记录异常信息3失败数据流向Controller 接收文件 ↓ 解析 Excel → 生成 taskId → 创建导入记录status1 ↓ Service 逐条处理 ↓ 处理完成 → 更新导入记录status2含结果统计 ↓异常时 捕获异常 → 更新导入记录status3→ 抛出异常三、核心数据模型3.1 导入记录表CREATE TABLE import_manage ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, member_id INT NULL COMMENT 所属用户/租户ID, function_code VARCHAR(100) NOT NULL COMMENT 功能标识, function_name VARCHAR(100) NOT NULL COMMENT 功能名称, task_id VARCHAR(50) NOT NULL COMMENT 任务唯一ID, file_name VARCHAR(200) NULL COMMENT 导入文件名, file_path VARCHAR(500) NULL COMMENT 失败文件下载地址, qty INT NULL COMMENT 导入总条数, success_qty INT NULL COMMENT 成功条数, error_qty INT NULL COMMENT 失败条数, status TINYINT NOT NULL COMMENT 1-进行中 2-成功 3-失败, operator_code VARCHAR(50) NULL COMMENT 操作人工号, operator_name VARCHAR(50) NULL COMMENT 操作人姓名, create_time DATETIME NOT NULL COMMENT 创建时间, update_time DATETIME NOT NULL COMMENT 更新时间 ) COMMENT 导入管理表;3.2 导入记录 DTODatapublicclassImportManageDtoimplementsSerializable{privateIntegermemberId;// 所属用户IDprivateStringfunctionCode;// 功能标识唯一区分不同业务privateStringfunctionName;// 功能名称前端展示privateStringtaskId;// 任务唯一IDUUIDprivateStringfileName;// 导入文件名privateStringfilePath;// 失败文件OSS地址privateIntegerqty;// 导入总条数privateIntegersuccessQty;// 成功条数privateIntegererrorQty;// 失败条数privateIntegerstatus;// 1-进行中 2-成功 3-失败privateIntegeruserId;// 操作人IDprivateStringoperatorCode;// 操作人工号privateStringoperatorName;// 操作人姓名}注博客https://blog.csdn.net/badao_liumang_qizhi四、完整示例4.1 远程服务接口FeignFeignClient(namebase-service)publicinterfaceBaseFeign{/** * 创建导入记录. */PostMapping(/inner/import/import-manage/save-import-manage)RestControllerResultBooleansaveImportManage(RequestBodyImportManageDtoparamsDto);/** * 更新导入记录. */PostMapping(/inner/import/import-manage/update-import-manage)RestControllerResultBooleanupdateImportManage(RequestBodyImportManageDtoparamsDto);}4.2 Controller 实现Slf4jRestControllerRequestMapping(/api/page/product)publicclassProductImportController{ResourceprivateProductImportServiceproductImportService;ResourceprivateBaseFeignbaseFeign;PostMapping(/import-product)publicRestControllerResultImportResultDtoimportProduct(RequestParam(file)MultipartFilefile){// 1. 文件校验if(filenull||file.isEmpty()){thrownewBusinessException(导入文件不能为空);}StringfileNamefile.getOriginalFilename();if(fileNamenull||(!fileName.endsWith(.xls)!fileName.endsWith(.xlsx))){thrownewBusinessException(仅支持Excel格式文件);}// 2. 构建导入历史记录ImportManageDtoimportManagenewImportManageDto();importManage.setMemberId(UserContext.getMemberId());importManage.setFunctionCode(productImport);importManage.setFunctionName(商品信息导入);importManage.setTaskId(UUID.randomUUID().toString().replaceAll(-,));importManage.setFileName(fileName);importManage.setStatus(1);// 进行中importManage.setUserId(UserContext.getUserId());importManage.setOperatorCode(UserContext.getUserCode());importManage.setOperatorName(UserContext.getUserName());try{// 3. 解析ExcelListProductImportDtodataListExcelUtil.parse(file,ProductImportDto.class);if(dataListnull||dataList.isEmpty()){thrownewBusinessException(导入文件中无有效数据);}// 4. 记录导入总数创建导入记录importManage.setQty(dataList.size());baseFeign.saveImportManage(importManage);// 5. 执行导入ImportResultDtoimportResultproductImportService.importProduct(dataList);// 6. 导入成功更新记录importManage.setSuccessQty(importResult.getSuccessCount());importManage.setErrorQty(importResult.getFailCount());importManage.setFilePath(importResult.getFailUrl());importManage.setStatus(2);// 成功baseFeign.updateImportManage(importManage);RestControllerResultImportResultDtoresultnewRestControllerResult();result.setSuccess(true);result.setData(importResult);returnresult;}catch(BusinessExceptione){// 7. 业务异常记录失败importManage.setStatus(3);baseFeign.saveImportManage(importManage);throwe;}catch(Exceptione){// 8. 系统异常记录失败log.error(导入异常,e);importManage.setStatus(3);baseFeign.saveImportManage(importManage);thrownewBusinessException(导入失败请检查文件格式);}}}4.3 前端导入历史页面前端可以调用导入历史查询接口展示导入时间功能名称文件名总条数成功失败状态操作2026-05-29 14:30商品信息导入product_20260529.xlsx100973成功[下载失败文件]2026-05-29 10:15商品信息导入product_batch.xlsx50500成功-2026-05-28 16:00商品信息导入invalid.doc000失败-五、关键设计点5.1 taskId 的作用唯一标识每次导入生成唯一 UUID用于关联创建和更新操作幂等保证同一个 taskId 不会重复创建记录异步场景如果导入是异步的前端可通过 taskId 轮询进度5.2 functionCode 设计// 不同业务使用不同的 functionCodeproductImport// 商品导入customerImport// 客户导入stockCheckImport// 库存盘点导入warehousePriorityImport// 仓库优先级导入作用前端按 functionCode 筛选展示对应模块的导入历史后端可以按 functionCode 做权限控制5.3 status 状态流转创建时 → status1进行中 ↓ ┌─────┴─────┐ ↓ ↓ status2 status3 成功 失败注意状态只会从 1 流转到 2 或 3不会回退。5.4 filePath 的使用导入全部成功时filePath 为空导入部分失败时filePath 存放失败数据 Excel 的 OSS 下载地址前端根据 filePath 是否为空决定是否显示下载失败文件按钮六、异常处理策略6.1 何时创建 vs 何时更新场景调用方法说明文件解析成功开始处理saveImportManagestatus1此时已知总条数处理完成updateImportManagestatus2更新成功/失败数文件解析失败saveImportManagestatus3直接标记失败处理过程中异常updateImportManagestatus3更新为失败6.2 Feign 调用失败的处理// 导入历史记录是辅助功能不能影响主流程try{baseFeign.saveImportManage(importManage);}catch(Exceptione){log.warn(创建导入记录失败不影响导入流程,e);// 不抛出异常继续执行导入}6.3 异常时的记录策略try{// 正常流程...}catch(BusinessExceptione){// 业务异常如文件为空记录失败状态importManage.setStatus(3);baseFeign.saveImportManage(importManage);throwe;// 继续抛出给前端}catch(Exceptione){// 系统异常记录失败状态importManage.setStatus(3);baseFeign.saveImportManage(importManage);thrownewBusinessException(导入失败);}七、同步导入 vs 异步导入的历史跟踪差异维度同步导入异步导入MQ创建记录时机Controller 中解析文件后Controller 中发送 MQ 前更新记录时机Service 返回后立即更新MQ 消费者处理完成后更新前端交互等待接口返回结果返回 taskId轮询进度失败处理catch 中更新 status3消费者 catch 中更新适用场景数据量小 1000条数据量大 1000条异步导入示例// Controller创建记录 发MQimportManage.setStatus(1);baseFeign.saveImportManage(importManage);mqSender.send(dataList,importManage);// 发MQreturntaskId;// 返回taskId给前端// MQ Consumer处理完成后更新记录try{ImportResultDtoresultdoImport(dataList);importManage.setSuccessQty(result.getSuccessCount());importManage.setStatus(2);baseFeign.updateImportManage(importManage);}catch(Exceptione){importManage.setStatus(3);baseFeign.updateImportManage(importManage);}八、导入历史查询接口设计// 查询当前用户的导入历史GetMapping(/list-import-history)publicRestControllerResultListImportHistoryDtolistImportHistory(RequestParamStringfunctionCode,RequestParam(requiredfalse)IntegerpageNum,RequestParam(requiredfalse)IntegerpageSize){// 按 memberId functionCode 查询按时间倒序}返回示例{success:true,data:[{taskId:abc123,functionName:商品信息导入,fileName:product_20260529.xlsx,qty:100,successQty:97,errorQty:3,status:2,filePath:https://oss.example.com/fail_abc123.xlsx,createTime:2026-05-29 14:30:25,operatorName:admin 张三}]}九、最佳实践清单taskId 使用 UUID保证全局唯一便于关联创建和更新functionCode 按业务模块区分方便前端按模块展示历史记录创建时机在文件解析成功后确保有 qty总条数信息导入历史记录不影响主流程Feign 调用失败时仅记录日志不抛异常状态只前进不回退1→2 或 1→3失败文件地址存 filePath前端据此展示下载按钮catch 中必须更新状态为失败避免记录一直处于进行中设置操作人信息方便管理员排查是谁做的导入异步导入时前端轮询 taskId通过 status 判断是否完成导入历史定期清理超过 30 天的记录可以归档或删除