cisd/docs/plans/2026-03-10-file-upload-fileid-design.md
2026-03-11 14:23:11 +08:00

215 lines
5.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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` 流程对齐
不在第一版加入:
- 下载
- 删除
- 清理任务
- 哈希去重
- 扩展名白名单