215 lines
5.0 KiB
Markdown
215 lines
5.0 KiB
Markdown
# File Upload FileId Design
|
||
|
||
**Date:** 2026-03-10
|
||
|
||
## Goal
|
||
|
||
为 TMS 提供一个通用文件上传能力,前端在初始化页面选择文件后先上传,后端返回 `fileId`,初始化任务创建和执行阶段继续只引用 `fileId`,不直接传输文件流。
|
||
|
||
## Scope
|
||
|
||
本设计只覆盖:
|
||
- 通用文件上传接口
|
||
- 文件元信息记录
|
||
- 与 CISD 初始化的 `fileId` 约定
|
||
- 前端调用时序
|
||
|
||
本设计不覆盖:
|
||
- 文件下载
|
||
- 文件删除回收
|
||
- 秒传/去重
|
||
- 文件内容级业务校验
|
||
- 初始化主流程改造
|
||
|
||
## Current State
|
||
|
||
当前初始化接口:
|
||
- `/api/v1/init/preview`
|
||
- `/api/v1/init/tasks`
|
||
|
||
都只接收 JSON `InitPreviewRequest`,不接收 `multipart/form-data`。
|
||
|
||
当前初始化模块已经完整依赖以下 `fileId` 字段:
|
||
- `licenses.receiverLicenseFileId`
|
||
- `licenses.cfgZipFileId`
|
||
- `mq.tlqLicenseFileId`
|
||
- `mq.cfmqConfigFileId`
|
||
|
||
当前执行器通过 `upload-base-dir/<fileId>` 或 `upload-base-dir/<fileId>/<preferredFileName>` 解析文件。
|
||
|
||
## Design Choice
|
||
|
||
采用:`通用上传接口 + 初始化继续引用 fileId`
|
||
|
||
不采用:`初始化任务接口直接接收 MultipartFile`
|
||
|
||
原因:
|
||
- 初始化主流程已经基于 `fileId`,继续沿用改动最小
|
||
- 上传与初始化创建失败语义不同,拆分更清晰
|
||
- 通用上传能力后续可被证书、升级、备份等模块复用
|
||
|
||
## API Design
|
||
|
||
### 1. Upload API
|
||
|
||
`POST /api/v1/files/upload`
|
||
|
||
Content-Type:`multipart/form-data`
|
||
|
||
表单字段:
|
||
- `file`: 必填,文件本体
|
||
- `bizType`: 可选,默认 `GENERIC`
|
||
|
||
返回示例:
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"code": 200,
|
||
"msg": "success",
|
||
"data": {
|
||
"fileId": "f_20260310_ab12cd34",
|
||
"bizType": "INIT",
|
||
"originalFilename": "cmep.license",
|
||
"contentType": "application/octet-stream",
|
||
"size": 2048,
|
||
"storagePath": "/home/tms/uploads/f_20260310_ab12cd34/cmep.license"
|
||
},
|
||
"timestamp": 1741600000000,
|
||
"traceId": "xxx"
|
||
}
|
||
```
|
||
|
||
### 2. File Detail API
|
||
|
||
`GET /api/v1/files/{fileId}`
|
||
|
||
用于页面回显和排障,不参与初始化主链路。
|
||
|
||
## Storage Design
|
||
|
||
### Storage root
|
||
|
||
- `/home/tms/uploads`
|
||
|
||
### Directory layout
|
||
|
||
- `/home/tms/uploads/<fileId>/`
|
||
- `/home/tms/uploads/<fileId>/<originalFilename>`
|
||
|
||
这样做的原因:
|
||
- 与当前执行器解析逻辑兼容
|
||
- 保留原始文件名,便于排查
|
||
- 每个 `fileId` 单独目录,避免同名覆盖
|
||
|
||
## Database Design
|
||
|
||
新增表:`sys_file_record`
|
||
|
||
字段建议:
|
||
- `id` bigint pk
|
||
- `file_id` varchar(128) unique not null
|
||
- `biz_type` varchar(32) not null
|
||
- `original_filename` varchar(256) not null
|
||
- `stored_filename` varchar(256) not null
|
||
- `storage_path` varchar(512) not null
|
||
- `content_type` varchar(128) null
|
||
- `file_size` bigint not null
|
||
- `sha256` varchar(64) null
|
||
- `status` varchar(16) not null
|
||
- `create_time` datetime not null
|
||
- `update_time` datetime not null
|
||
|
||
状态值当前只需要:
|
||
- `ACTIVE`
|
||
- `DELETED`
|
||
|
||
## Backend Module Layout
|
||
|
||
新增模块:
|
||
|
||
```text
|
||
src/main/java/com/cisd/tms/modules/file
|
||
├── controller/internal
|
||
│ └── FileInternalController.java
|
||
├── dto
|
||
│ ├── FileUploadResponse.java
|
||
│ └── FileDetailResponse.java
|
||
├── entity
|
||
│ └── FileRecordEntity.java
|
||
├── repository
|
||
│ └── FileRecordRepository.java
|
||
└── service
|
||
└── FileService.java
|
||
```
|
||
|
||
资源文件:
|
||
|
||
```text
|
||
src/main/resources/mapper/file/FileRecordMapper.xml
|
||
src/main/resources/db/migration/V3__create_sys_file_record.sql
|
||
```
|
||
|
||
## Validation Boundary
|
||
|
||
### Upload API validates
|
||
|
||
- 文件非空
|
||
- 原始文件名非空
|
||
- 文件大小不超过配置上限
|
||
- `bizType` 枚举合法
|
||
|
||
### Init API continues to validate
|
||
|
||
- `fileId` 格式
|
||
- 按版本和 `mqType` 的必填规则
|
||
- 执行阶段按 `fileId` 是否能找到对应文件
|
||
|
||
## Frontend Flow
|
||
|
||
1. 用户在初始化页面选择文件
|
||
2. 前端立即调用上传接口
|
||
3. 上传成功后拿到 `fileId`
|
||
4. 前端将 `fileId` 保存到表单状态
|
||
5. 用户点击“预检”或“创建任务”时,仅提交 JSON
|
||
|
||
字段映射:
|
||
- 收发器 license -> `licenses.receiverLicenseFileId`
|
||
- TLQ 配置/许可证 -> `mq.tlqLicenseFileId`
|
||
- CFMQ 配置 -> `mq.cfmqConfigFileId`
|
||
- 直参 cfg 包 -> `licenses.cfgZipFileId`
|
||
|
||
## Configuration
|
||
|
||
建议新增:
|
||
|
||
```yaml
|
||
tms:
|
||
file:
|
||
upload-base-dir: /home/tms/uploads
|
||
max-file-size-mb: 50
|
||
```
|
||
|
||
后续可考虑让初始化执行器复用同一配置根,而不是再单独维护一份路径配置。
|
||
|
||
## Risks
|
||
|
||
1. 当前初始化执行器已有 `tms.init.executor.upload-base-dir`,如果新增 `tms.file.upload-base-dir` 而两者不一致,会导致上传成功但执行阶段找不到文件。
|
||
2. 如果上传接口只保留原始文件名而不做额外清洗,需要限制危险文件名字符。
|
||
3. 目前不做业务文件类型校验,上传成功不代表该文件一定适合初始化使用。
|
||
|
||
## Recommendation
|
||
|
||
先实现最小闭环:
|
||
- 上传接口
|
||
- 查询接口
|
||
- 文件元信息表
|
||
- 与初始化 `fileId` 流程对齐
|
||
|
||
不在第一版加入:
|
||
- 下载
|
||
- 删除
|
||
- 清理任务
|
||
- 哈希去重
|
||
- 扩展名白名单
|