# op-asset-resource-server **Repository Path**: liyuncc/op-asset-resource-server ## Basic Information - **Project Name**: op-asset-resource-server - **Description**: op-asset-resource-server - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-12-22 - **Last Updated**: 2026-06-01 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 分片上传与异步合并系统(Multipart Upload & Async Merge System) 本系统提供一套 **高性能、可扩展、可断点续传、支持异步合并与访问控制** 的文件上传能力,适用于大文件上传、文档管理、媒体资源平台等场景。 支持: - 分片上传(Multipart Upload) - 断点续传(Resumable Upload) - 秒传(Instant Upload) - 多线程并发上传(Multi-threaded Concurrent Upload) - 异步合并(Async Merge) - 上传状态查询(Upload Status) - 最终一致性补偿(Eventual Consistency) - 访问控制(Token-based Access) - 高可扩展性与可维护性 设计理念参考了 **AWS S3、阿里云 OSS、腾讯云 COS、MinIO** 的分片上传模型,并结合实际业务需求进行了工程化落地。 --- ## 1.系统架构概览 ```mermaid flowchart TD FE[前端\n分片切割\n多线程上传\n断点续传\n秒传\n实时速度 ETA\n状态轮询] API[后端 API\nchunk-upload\nupload-status\nresource-id] CHUNKDIR[分片存储 chunkDir\nchunk_0..n\nprogress BitSet\nmetadata.json] MERGE[MergeCoordinator\n幂等控制\n线程池合并\n校验\n写入 finalFile\n写库\ncleanupAsync] FINALFILE[最终文件存储\nfinalFile\nrelativePath] DB[(ResourceEntity 数据库\n元数据\nURL\nchecksum\nowner\nuploadId)] FE -->|chunk - upload| API API -->|写入分片/进度| CHUNKDIR API -->|提交合并任务| MERGE MERGE -->|读取分片| CHUNKDIR MERGE -->|写入最终文件| FINALFILE MERGE -->|写入数据库| DB MERGE -->|清理 chunkDir| CHUNKDIR API -->|状态轮询\n检查 chunkDir| CHUNKDIR API -->|状态轮询\n检查 finalFile| FINALFILE API -->|状态轮询\n检查 DB| DB API -->|返回资源 URL| FE ``` --- ## 2.目录结构 ```text /data/uploads/ chunks/ {uploadId}/ metadata.json progress chunk_0 chunk_1 ... files/ 2026/01/23/ abc.png ``` 说明: - `chunks/{uploadId}`:上传过程的临时目录 - `files/{yyyy/MM/dd}`:最终文件存储目录(由 metadata.relativePath 决定) ## 3.模块职责说明 系统采用“分层 + 分模块”的方式组织代码,每个模块职责清晰、边界明确。 ### 3.1 存储层(StorageService) 负责: - 分片文件写入 - 最终文件路径管理 - chunkDir 目录结构管理 原则: - 分片文件是局部状态,可并发写入;最终文件是全局状态,只能由合并流程生成。 --- ### 3.2 进度管理(ProgressService) 负责: - 维护 `progress` 文件(BitSet) - 计算缺失分片 - 判断是否全部上传完毕 - 断点续传支持 原则: - BitSet 更新必须串行(避免并发覆盖) - 进度文件是“权威进度来源” --- ### 3.3 校验管理(ChecksumService) 负责: - 分片校验和计算 - checksum 文件读写 - 合并后校验一致性 原则: - 校验是上传体系的底线,必须独立于进度管理。 --- ### 3.4 元数据管理(MetadataService) 负责: - 读写 `metadata.json` - 记录 totalChunks、chunkSize、checksum、owner、时间戳等 设计理念: - 元数据是整个上传体系的核心,必须独立存储、可扩展、可追溯。 --- ### 3.5 合并协调器(MergeCoordinator) 负责: - 合并任务的提交 - 幂等控制 - 校验 - 合并 - 写入数据库 - 清理临时文件 设计理念: - 上传是前台行为,合并是后台行为;两者必须解耦。 --- ### 3.6 状态查询(UploadStatusService) 负责: - 查询上传状态 - 自动补偿 - 返回状态码(UPLOADING / WAITING_MERGE / MERGING / COMPLETED) - 返回最终资源信息(resourceId、resourceUrl) 设计理念: - 状态查询是前端体验的核心,必须简单、稳定、可轮询、具备自愈能力。 --- ### 3.7 访问控制(TokenService) 负责: - 构建公开/私有资源 URL - 生成 token - 验证 token 设计理念: - 访问控制与上传解耦,URL 构建可扩展。 --- ## 4.最终一致性状态机 ### 4.1 状态定义 系统共有 6 个状态: | 状态 | 含义 | 是否终态 | 是否需要 chunkDir | 是否需要 DB | |---------------|--------------|------|---------------|---------| | NOT_EXISTS | 上传任务不存在 | 是 | 否 | 否 | | UPLOADING | 分片上传中 | 否 | 是 | 否 | | WAITING_MERGE | 分片已全部上传,等待合并 | 否 | 是 | 否 | | MERGING | 合并线程执行中 | 否 | 是 | 否 | | COMPLETED | 合并成功 + 写库成功 | 是 | 否(可清理) | 是 | | MERGE_FAILED | 合并失败或补偿失败 | 是 | 可选 | 否 | --- ### 4.2 状态迁移图 ``` ┌──────────────┐ │ NOT_EXISTS │ └──────┬───────┘ │ 初始化 uploadId ▼ ┌──────────────┐ │ UPLOADING │ ← 分片上传中 └──────┬───────┘ │ 所有分片上传完成 ▼ ┌──────────────┐ │ WAITING_MERGE│ ← 等待合并任务 └──────┬───────┘ │ 合并任务开始 ▼ ┌──────────────┐ │ MERGING │ ← 合并线程执行 └──────┬───────┘ 合并失败 │ │ 合并成功 + 写库成功 ┌─────────────┘ ▼ ▼ ┌──────────────┐ ┌──────────────┐ │ COMPLETED │ ← 终态 │ MERGE_FAILED │ └──────────────┘ └──────────────┘ 【补偿路径】 MERGING 阶段如果:finalFile 存在 + DB 无记录 → getUploadStatus() 触发 attemptToLoadAndRestore() → 成功 → COMPLETED → 失败 → MERGE_FAILED ``` ```mermaid stateDiagram-v2 [*] --> NOT_EXISTS NOT_EXISTS --> UPLOADING: 初始化 uploadId\n创建 chunkDir + metadata UPLOADING --> WAITING_MERGE: 所有分片上传完成 UPLOADING --> MERGE_FAILED: metadata 丢失 / chunkDir 损坏 WAITING_MERGE --> MERGING: 合并任务开始 WAITING_MERGE --> MERGE_FAILED: metadata 丢失 / chunkDir 损坏 MERGING --> COMPLETED: 合并成功 + 写库成功 MERGING --> MERGE_FAILED: 合并失败(IO 错误等) %% 最终一致性补偿路径 MERGING --> COMPLETED: finalFile 存在 + DB 无记录\n→ 自动补建记录成功 MERGING --> MERGE_FAILED: finalFile 存在 + DB 无记录\n→ 自动补建失败 COMPLETED --> [*] MERGE_FAILED --> [*] ``` --- ## 5.合并流程与补偿机制 ### 5.1 时序图 ```mermaid sequenceDiagram participant FE as 前端 participant API as 上传接口 participant MERGE as 合并线程 participant FS as 文件系统 participant DB as 数据库 FE ->> API: 分片上传 API ->> FS: 写入 chunkDir/chunk_x FE ->> API: 通知分片全部上传完成 API ->> MERGE: 触发异步合并任务 MERGE ->> FS: 读取 chunkDir MERGE ->> FS: 写入 finalFile MERGE ->> DB: 写入资源记录 DB -->> MERGE: 写库失败(网络/事务/超时) Note over MERGE: chunkDir 不清理 metadata 保留 FE ->> API: 轮询 getUploadStatus API ->> FS: 检查 finalFile 是否存在 API ->> DB: 检查 DB 是否有记录 DB -->> API: 无记录 API ->> FS: 读取 metadata.json API ->> DB: 自动补建记录(attemptToLoadAndRestore) DB -->> API: ✔ 写入成功 API ->> FS: cleanupAsync(chunkDir) API -->> FE: 返回 COMPLETED ``` --- ### 5.2 最终一致性补偿逻辑 ```mermaid flowchart TD A[状态轮询 getUploadStatus] --> B{DB 是否有记录?} B -->|是| C[返回 COMPLETED] C --> Z[结束] B -->|否| D{chunkDir 是否存在?} D -->|否| E[返回 NOT_EXISTS] E --> Z D -->|是| F[读取 metadata.json] F -->|失败| G[返回 MERGE_FAILED] G --> Z F -->|成功| H{finalFile 是否存在?} H -->|否| I{是否在合并中?} I -->|是| J[返回 MERGING] I -->|否| K{分片是否全部上传?} K -->|是| L[返回 WAITING_MERGE] K -->|否| M[返回 UPLOADING] H -->|是| N[自动补建记录 attemptToLoadAndRestore] N -->|成功| C N -->|失败| G ``` --- ## 6.清理策略 ### 6.1 chunkDir 清理规则 - 仅在 写库成功后 才允许清理 - 写库失败时必须保留 chunkDir(用于补偿) ### 6.2 cleanupAsync 安全检查 ```text if (DB 有记录) → 允许清理 if (DB 无记录) → 禁止清理 ``` ## 7.系统不变量(必须遵守) 1. 写库成功前不得清理 chunkDir / metadata.json 2. DB 有记录 → 状态必为 COMPLETED 3. DB 无记录且 chunkDir 不存在 → 状态必为 NOT_EXISTS 4. finalFile 存在且 DB 无记录 → 必须触发补偿 5. relativePath 必须在上传初始化时固定 6. chunkDir 路径必须只依赖 uploadId ## 8.常见问题(FAQ) ### Q:为什么 chunkDir 与 finalFile 必须分离? A:chunkDir 是临时状态,finalFile 是最终状态,两者生命周期不同,必须解耦。 ### Q:为什么 metadata.json 如此重要? A:它是整个上传体系的真相来源,包含 relativePath、checksum、owner 等关键字段。 ### Q:为什么需要最终一致性补偿? A:因为合并成功但写库失败是常见场景,补偿机制保证系统可恢复。