# 项目异步文件上传功能 - 子计划3:Controller和文档 > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** 实现文件上传的 REST API 接口,提供批量上传、查询、统计等功能 **Architecture:** RESTful API 设计,参数校验,异常处理,Swagger 文档 **Tech Stack:** Spring MVC, Swagger/OpenAPI 3.0, Jackson --- ## Task 1: Controller 实现 **Files:** - Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadController.java` **Step 1: 创建 Controller** 创建文件 `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadController.java`: ```java package com.ruoyi.ccdi.project.controller; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.ccdi.project.domain.dto.CcdiFileUploadQueryDTO; import com.ruoyi.ccdi.project.domain.entity.CcdiFileUploadRecord; import com.ruoyi.ccdi.project.domain.vo.CcdiFileUploadStatisticsVO; import com.ruoyi.ccdi.project.service.ICcdiFileUploadService; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.utils.SecurityUtils; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.util.concurrent.RejectedExecutionException; /** * 文件上传 Controller * * @author ruoyi * @date 2026-03-05 */ @Slf4j @RestController @RequestMapping("/ccdi/file-upload") @Tag(name = "文件上传管理", description = "项目文件上传相关接口") public class CcdiFileUploadController extends BaseController { @Resource private ICcdiFileUploadService fileUploadService; /** * 批量上传文件(异步) */ @PostMapping("/batch") @Operation(summary = "批量上传文件", description = "异步批量上传流水文件") public AjaxResult batchUpload(@RequestParam Long projectId, @RequestParam MultipartFile[] files) { // 参数校验 if (projectId == null) { return AjaxResult.error("项目ID不能为空"); } if (files == null || files.length == 0) { return AjaxResult.error("请选择要上传的文件"); } if (files.length > 100) { return AjaxResult.error("单次最多上传100个文件"); } // 校验文件大小和格式 for (MultipartFile file : files) { if (file.isEmpty()) { return AjaxResult.error("文件不能为空"); } if (file.getSize() > 50 * 1024 * 1024) { return AjaxResult.error("文件 " + file.getOriginalFilename() + " 超过50MB限制"); } String fileName = file.getOriginalFilename(); if (!fileName.endsWith(".xlsx") && !fileName.endsWith(".xls")) { return AjaxResult.error("文件 " + fileName + " 格式不支持,仅支持Excel文件"); } } try { String username = SecurityUtils.getUsername(); String batchId = fileUploadService.batchUploadFiles(projectId, files, username); return AjaxResult.success("上传任务已提交", batchId); } catch (RejectedExecutionException e) { log.warn("线程池已满,拒绝上传请求: projectId={}, fileCount={}", projectId, files.length); return AjaxResult.error("系统繁忙,请稍后再试"); } catch (Exception e) { log.error("批量上传失败: projectId={}", projectId, e); return AjaxResult.error("上传失败:" + e.getMessage()); } } /** * 查询上传记录列表 */ @GetMapping("/list") @Operation(summary = "查询上传记录列表", description = "分页查询文件上传记录") public TableDataInfo list(CcdiFileUploadQueryDTO queryDTO) { Page page = new Page<>(getPageNum(), getPageSize()); Page result = fileUploadService.selectPage(page, queryDTO); return getDataTable(result.getRecords(), result.getTotal()); } /** * 查询上传统计 */ @GetMapping("/statistics/{projectId}") @Operation(summary = "查询上传统计", description = "统计各状态的文件数量") public AjaxResult getStatistics(@PathVariable Long projectId) { CcdiFileUploadStatisticsVO statistics = fileUploadService.countByStatus(projectId); return AjaxResult.success(statistics); } /** * 查询记录详情 */ @GetMapping("/detail/{id}") @Operation(summary = "查询记录详情", description = "根据ID查询文件上传记录详情") public AjaxResult getDetail(@PathVariable Long id) { CcdiFileUploadRecord record = fileUploadService.getById(id); return AjaxResult.success(record); } } ``` **Step 2: 编译验证** ```bash cd ccdi-project mvn clean compile ``` Expected: BUILD SUCCESS **Step 3: 提交** ```bash git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadController.java git commit -m "feat: 添加文件上传Controller" ``` --- ## Task 2: API 文档 **Files:** - Create: `doc/api-docs/ccdi-file-upload-api.md` **Step 1: 创建 API 文档** 创建文件 `doc/api-docs/ccdi-file-upload-api.md`: ```markdown # 文件上传 API 文档 ## 1. 批量上传文件 ### 接口地址 POST /ccdi/file-upload/batch ### 请求参数 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | projectId | Long | 是 | 项目ID | | files | File[] | 是 | 文件数组(最多100个,单个最大50MB) | ### 请求示例 ```bash curl -X POST "http://localhost:8080/ccdi/file-upload/batch" \ -H "Authorization: Bearer YOUR_TOKEN" \ -F "projectId=1" \ -F "files=@/path/to/file1.xlsx" \ -F "files=@/path/to/file2.xlsx" ``` ### 返回示例 ```json { "code": 200, "msg": "上传任务已提交", "data": "a1b2c3d4e5f6g7h8" } ``` ### 返回字段说明 | 字段 | 类型 | 说明 | |------|------|------| | code | Integer | 状态码,200表示成功 | | msg | String | 提示信息 | | data | String | 批次ID,用于追踪上传任务 | ### 错误码说明 | code | msg | 说明 | |------|-----|------| | 500 | 项目ID不能为空 | 缺少必填参数 | | 500 | 请选择要上传的文件 | 文件数组为空 | | 500 | 单次最多上传100个文件 | 文件数量超限 | | 500 | 文件 xxx 超过50MB限制 | 文件大小超限 | | 500 | 文件 xxx 格式不支持,仅支持Excel文件 | 文件格式错误 | | 500 | 系统繁忙,请稍后再试 | 线程池已满 | --- ## 2. 查询上传记录列表 ### 接口地址 GET /ccdi/file-upload/list ### 请求参数 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | projectId | Long | 否 | 项目ID | | fileStatus | String | 否 | 文件状态:uploading/parsing/parsed_success/parsed_failed | | fileName | String | 否 | 文件名称(模糊查询) | | uploadUser | String | 否 | 上传人 | | pageNum | Integer | 否 | 页码,默认1 | | pageSize | Integer | 否 | 每页数量,默认10 | ### 请求示例 ```bash curl -X GET "http://localhost:8080/ccdi/file-upload/list?projectId=1&fileStatus=parsed_success&pageNum=1&pageSize=10" \ -H "Authorization: Bearer YOUR_TOKEN" ``` ### 返回示例 ```json { "code": 200, "msg": "查询成功", "rows": [ { "id": 1, "projectId": 1, "lsfxProjectId": 100, "logId": 123456, "fileName": "流水1.xlsx", "fileSize": 2621440, "fileStatus": "parsed_success", "enterpriseNames": "张三,李四", "accountNos": "622xxx,623xxx", "uploadTime": "2026-03-05 10:30:00", "uploadUser": "admin" } ], "total": 100 } ``` ### 返回字段说明 | 字段 | 类型 | 说明 | |------|------|------| | rows | Array | 记录列表 | | total | Long | 总记录数 | --- ## 3. 查询上传统计 ### 接口地址 GET /ccdi/file-upload/statistics/{projectId} ### 路径参数 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | projectId | Long | 是 | 项目ID | ### 请求示例 ```bash curl -X GET "http://localhost:8080/ccdi/file-upload/statistics/1" \ -H "Authorization: Bearer YOUR_TOKEN" ``` ### 返回示例 ```json { "code": 200, "msg": "查询成功", "data": { "uploading": 2, "parsing": 3, "parsedSuccess": 15, "parsedFailed": 1, "total": 21 } } ``` ### 返回字段说明 | 字段 | 类型 | 说明 | |------|------|------| | uploading | Long | 上传中数量 | | parsing | Long | 解析中数量 | | parsedSuccess | Long | 解析成功数量 | | parsedFailed | Long | 解析失败数量 | | total | Long | 总数量 | --- ## 4. 查询记录详情 ### 接口地址 GET /ccdi/file-upload/detail/{id} ### 路径参数 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | id | Long | 是 | 记录ID | ### 请求示例 ```bash curl -X GET "http://localhost:8080/ccdi/file-upload/detail/1" \ -H "Authorization: Bearer YOUR_TOKEN" ``` ### 返回示例 ```json { "code": 200, "msg": "查询成功", "data": { "id": 1, "projectId": 1, "lsfxProjectId": 100, "logId": 123456, "fileName": "流水1.xlsx", "fileSize": 2621440, "fileStatus": "parsed_success", "enterpriseNames": "张三,李四", "accountNos": "622xxx,623xxx", "errorMessage": null, "uploadTime": "2026-03-05 10:30:00", "uploadUser": "admin" } } ``` --- ## 5. 文件状态说明 | 状态 | 说明 | |------|------| | uploading | 文件上传中 | | parsing | 文件解析中 | | parsed_success | 文件解析成功 | | parsed_failed | 文件解析失败 | --- ## 6. 通用说明 ### 认证方式 所有接口需要在请求头中携带 Token: ``` Authorization: Bearer YOUR_TOKEN ``` ### 获取 Token ```bash POST /login/test?username=admin&password=admin123 ``` ### 响应格式 所有接口统一返回格式: ```json { "code": 200, "msg": "操作成功", "data": {} } ``` ### 错误处理 当发生错误时,返回格式: ```json { "code": 500, "msg": "错误信息" } ``` ``` **Step 2: 提交文档** ```bash git add doc/api-docs/ccdi-file-upload-api.md git commit -m "docs: 添加文件上传API文档" ``` --- ## Task 3: 最终提交和推送 **Step 1: 查看所有修改** ```bash git status git log --oneline -10 ``` **Step 2: 推送到远程仓库** ```bash git push origin dev ``` Expected: 推送成功 **Step 3: 验证 Swagger 文档** ```bash # 启动应用后访问 # http://localhost:8080/swagger-ui/index.html # 查找 "文件上传管理" 分组 ``` --- ## 子计划3完成检查清单 - [ ] Controller实现完成 - [ ] 参数校验正确 - [ ] 异常处理完善 - [ ] API文档创建完成 - [ ] Swagger注解正确 - [ ] 所有代码已提交并推送到远程仓库 --- ## 功能总结 **已完成的完整功能:** - ✅ 数据库表创建和索引 - ✅ 实体类、DTO、VO 创建 - ✅ Mapper 接口和 XML 映射(支持批量插入和统计) - ✅ 线程池配置(容量100,AbortPolicy拒绝策略) - ✅ Service 接口和实现(核心异步处理逻辑) - ✅ Controller 接口(批量上传、查询、统计、详情) - ✅ API 文档 **核心特性:** - ✅ 双层异步架构(调度线程 + 文件处理线程池) - ✅ 智能重试机制(线程池满时等待30秒重试1次) - ✅ 完整的状态追踪(4种状态) - ✅ 批量插入优化(使用自定义XML) - ✅ 完善的参数校验和异常处理 - ✅ Swagger API 文档 **后续优化方向:** - ⏳ 完善流水分析平台接口调用(当前为模拟逻辑) - ⏳ 实现自定义日志 Appender(独立批次日志文件) - ⏳ 前端页面开发 - ⏳ 更完善的轮询和重试机制 - ⏳ 性能监控和告警 **部署检查清单:** - [ ] 数据库表已创建 - [ ] 线程池配置正确(容量100) - [ ] 文件上传大小限制配置(50MB) - [ ] 流水分析平台地址配置正确 - [ ] 日志目录权限正确 - [ ] 应用启动成功 - [ ] Swagger 文档可访问 --- **所有子计划执行完成!**