Files
ccdi/doc/plans/2026-03-05-async-file-upload-part3-controller.md

12 KiB
Raw Blame History

项目异步文件上传功能 - 子计划3Controller和文档

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:

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<CcdiFileUploadRecord> page = new Page<>(getPageNum(), getPageSize());
        Page<CcdiFileUploadRecord> 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: 编译验证

cd ccdi-project
mvn clean compile

Expected: BUILD SUCCESS

Step 3: 提交

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:

# 文件上传 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"

返回示例

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

请求示例

curl -X GET "http://localhost:8080/ccdi/file-upload/list?projectId=1&fileStatus=parsed_success&pageNum=1&pageSize=10" \
  -H "Authorization: Bearer YOUR_TOKEN"

返回示例

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

请求示例

curl -X GET "http://localhost:8080/ccdi/file-upload/statistics/1" \
  -H "Authorization: Bearer YOUR_TOKEN"

返回示例

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

请求示例

curl -X GET "http://localhost:8080/ccdi/file-upload/detail/1" \
  -H "Authorization: Bearer YOUR_TOKEN"

返回示例

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

POST /login/test?username=admin&password=admin123

响应格式

所有接口统一返回格式:

{
  "code": 200,
  "msg": "操作成功",
  "data": {}
}

错误处理

当发生错误时,返回格式:

{
  "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: 查看所有修改

git status
git log --oneline -10

Step 2: 推送到远程仓库

git push origin dev

Expected: 推送成功

Step 3: 验证 Swagger 文档

# 启动应用后访问
# http://localhost:8080/swagger-ui/index.html
# 查找 "文件上传管理" 分组

子计划3完成检查清单

  • Controller实现完成
  • 参数校验正确
  • 异常处理完善
  • API文档创建完成
  • Swagger注解正确
  • 所有代码已提交并推送到远程仓库

功能总结

已完成的完整功能:

  • 数据库表创建和索引
  • 实体类、DTO、VO 创建
  • Mapper 接口和 XML 映射(支持批量插入和统计)
  • 线程池配置容量100AbortPolicy拒绝策略
  • Service 接口和实现(核心异步处理逻辑)
  • Controller 接口(批量上传、查询、统计、详情)
  • API 文档

核心特性:

  • 双层异步架构(调度线程 + 文件处理线程池)
  • 智能重试机制线程池满时等待30秒重试1次
  • 完整的状态追踪4种状态
  • 批量插入优化使用自定义XML
  • 完善的参数校验和异常处理
  • Swagger API 文档

后续优化方向:

  • 完善流水分析平台接口调用(当前为模拟逻辑)
  • 实现自定义日志 Appender独立批次日志文件
  • 前端页面开发
  • 更完善的轮询和重试机制
  • 性能监控和告警

部署检查清单:

  • 数据库表已创建
  • 线程池配置正确容量100
  • 文件上传大小限制配置50MB
  • 流水分析平台地址配置正确
  • 日志目录权限正确
  • 应用启动成功
  • Swagger 文档可访问

所有子计划执行完成!