# 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/` 或 `upload-base-dir//` 解析文件。 ## 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//` - `/home/tms/uploads//` 这样做的原因: - 与当前执行器解析逻辑兼容 - 保留原始文件名,便于排查 - 每个 `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` 流程对齐 不在第一版加入: - 下载 - 删除 - 清理任务 - 哈希去重 - 扩展名白名单