12 KiB
12 KiB
项目异步文件上传功能 - 子计划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:
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 映射(支持批量插入和统计)
- ✅ 线程池配置(容量100,AbortPolicy拒绝策略)
- ✅ Service 接口和实现(核心异步处理逻辑)
- ✅ Controller 接口(批量上传、查询、统计、详情)
- ✅ API 文档
核心特性:
- ✅ 双层异步架构(调度线程 + 文件处理线程池)
- ✅ 智能重试机制(线程池满时等待30秒重试1次)
- ✅ 完整的状态追踪(4种状态)
- ✅ 批量插入优化(使用自定义XML)
- ✅ 完善的参数校验和异常处理
- ✅ Swagger API 文档
后续优化方向:
- ⏳ 完善流水分析平台接口调用(当前为模拟逻辑)
- ⏳ 实现自定义日志 Appender(独立批次日志文件)
- ⏳ 前端页面开发
- ⏳ 更完善的轮询和重试机制
- ⏳ 性能监控和告警
部署检查清单:
- 数据库表已创建
- 线程池配置正确(容量100)
- 文件上传大小限制配置(50MB)
- 流水分析平台地址配置正确
- 日志目录权限正确
- 应用启动成功
- Swagger 文档可访问
所有子计划执行完成!