feat(server/s3): support multipart upload - #2813
Conversation
9a51b73 to
48f5af8
Compare
Replace itsHenry35/gofakes3 with OpenListTeam/gofakes3 Signed-off-by: MadDogOwner <xiaoran@xrgzs.top>
- Implement gofakes3 MultipartBackend (Create/UploadPart/Complete/Abort) on s3Backend so multipart parts stream to local temp files instead of being buffered in memory - Track in-progress uploads via a new uploads sync.Map on s3Backend - Refactor the PutObject body into a reusable putStream helper shared with the multipart Complete path - Validate part ordering, etags and existence, reject short reads, and return S3-style multipart etags - Add end-to-end multipart tests against a real Local driver Co-authored-by: Codex <267193182+codex@users.noreply.github.com>
- Track lastActivity on each multipart upload, updated on create and every part upload, so idle uploads can be detected - Add a background reaper per backend instance that removes uploads inactive for longer than the TTL (default 24h) and cleans their temp directories - Add a startup sweep that removes leftover s3-multipart-* directories older than the TTL, recovering part files from a previous crash - Add a configurable s3.multipart_ttl (env S3_MULTIPART_TTL) duration, parsed via time.ParseDuration with a 24h default - Add tests for TTL-based reaping and stale-directory cleanup Co-authored-by: Codex <267193182+codex@users.noreply.github.com>
48f5af8 to
59b1dfd
Compare
Signed-off-by: MadDogOwner <xiaoran@xrgzs.top>
Signed-off-by: MadDogOwner <xiaoran@xrgzs.top>
e66f47b to
6ef934f
Compare
Signed-off-by: MadDogOwner <xiaoran@xrgzs.top>
pikachuren
left a comment
There was a problem hiding this comment.
🙏 感谢 @xrgzs 提交!
🤖 AI 自动审核声明:本评审报告由 AI 自动生成,当前使用 Claude Opus 5 模型进行分析。
🎯 结论
✅ 建议 Approve(由维护者人工确认)— 依赖迁移来源可信,分片落盘方案解决了真实的内存问题
📖 概要
feat(server/s3): support multipart upload · 为内置 S3 服务端增加分片上传支持。
核心改动:依赖从 itsHenry35/gofakes3 迁移到 OpenListTeam/gofakes3 v0.8.1,实现 gofakes3.MultipartBackend,分片先落本地临时文件、完成时再组装写入存储;新增 MultipartTTL 配置与后台清理协程。
🧭 整体方案
技术路线是「分片落盘 + 完成时组装」而非全内存缓冲——注释里也明确写了这一动机。对于内置 S3 服务端来说这是正确取舍:客户端上传大文件时若把所有分片堆在内存,很容易 OOM。配合 startReaper() 后台清理过期的未完成上传,避免临时文件无限堆积,设计比较完整。putStream 把 PutObject 与 multipart complete 两条路径的目录创建、元数据、忽略规则统一起来,也避免了两套逻辑漂移。
📊 变更统计
12 个文件(+749 / -32 行) | 功能 ⭐⭐⭐⭐⭐ | 最小改动 ⭐⭐⭐ | 前向兼容 ⭐⭐⭐⭐ | 方案设计 ⭐⭐⭐⭐
🚨 关键问题
P0(阻塞合并):无
P1(建议修复):无
P2(可选):
- 💡
MultipartTTL是 string 类型(默认"24h")。若用户填入无法被time.ParseDuration解析的值,请问会 fallback 到默认值还是导致启动失败?建议做一次显式校验并在配置错误时给出清晰提示~ - 💡 分片临时文件落在本地磁盘,24h TTL 期间若有大量未完成上传,磁盘占用可能相当可观。是否考虑增加一个总容量上限,或在文档中提示运维预留空间?
- 💡
uploads *sync.Map保存上传状态,进程重启后这些状态会丢失,但磁盘上的临时文件仍在。请问startReaper会清理这些「孤儿」临时文件吗?如果只清理 map 中记录的项,重启后残留文件可能永远不会被回收~ - 💡 建议补充集成测试:大文件分片上传、中途 abort、分片乱序到达、TTL 过期清理这几个场景~
🔐 依赖安全审查
- 审查范围:
github.com/itsHenry35/gofakes3 v0.0.8→github.com/OpenListTeam/gofakes3 v0.8.1 - 审查方式:核查两个仓库的 fork 来源、组织归属、tag 真实性与近期提交记录
- 安全评估:✅ 无恶意代码,来源可信
- 详细结论:
- 新依赖
OpenListTeam/gofakes3归属 OpenListTeam 组织(owner type = Organization),是项目自有仓库而非第三方个人账号 - 两者同源:新旧依赖均 fork 自上游
rclone/gofakes3,不存在换用来路不明的替代包 v0.8.1tag 真实存在(f3fb8106)- 近期提交记录正常:包含上游 rclone 维护者
ncw的安全修复(修复多实例间 auth key 共享、更新 aws-sdk-go-v2 修补 GO-2026-5764 漏洞),以及原作者itsHenry35的 AWS Signature V2 实现 - 本次迁移实际上是安全性提升:从个人 fork 转为组织维护,并同步了上游的漏洞修复
- 未发现供应链投毒迹象
- 新依赖
📂 逐文件分析
server/s3/backend.go
改动意图:实现分片上传后端。
代码逻辑:s3Backend 新增 uploads *sync.Map 跟踪上传状态;newBackend 启动 reaper;putStream 抽出共用写入逻辑供 PutObject 与 complete 复用。
问题分析:分片落盘避免内存膨胀的取舍正确,共用 putStream 保证了两条路径行为一致,未发现阻塞问题。
internal/conf/config.go
问题分析:新增 MultipartTTL 且有默认值,向后兼容良好;建议补充解析失败的处理(P2)。
go.mod / go.sum
问题分析:见上方依赖安全审查,来源可信。
✅ 待处理清单
- [P2] 为
MultipartTTL增加解析校验与错误提示 - [P2] 评估临时文件总容量上限,或在文档中提示磁盘预留
- [P2] 确认进程重启后遗留临时文件能否被 reaper 回收
- [P2] 补充分片上传的集成测试(abort / 乱序 / TTL 过期)
🎯 结论:✅ 建议 Approve — 依赖迁移到组织自有 fork 且同步了上游安全修复,分片落盘方案合理,无阻塞项。
Summary / 摘要
为 fake S3 服务器实现分片上传(multipart upload)支持,并自动回收被遗弃的分片上传。本 PR 含三个提交:
refactor(server/s3): use OpenListTeam/gofakes3-- 将依赖从github.com/itsHenry35/gofakes3 v0.0.8切换到github.com/OpenListTeam/gofakes3 v0.8.1。旧版只有把所有分片缓存在内存的默认上传器;OpenListTeam fork 新增了可选的MultipartBackend接口,允许后端自行流式处理分片,这是实现分片上传的前提。同步更新server/s3下 8 个文件的 import 路径。feat(server/s3): support multipart upload-- 在s3Backend上实现gofakes3.MultipartBackend,把分片上传四个操作(Initiate / UploadPart / Complete / Abort)路由到 OpenList 自有后端:每个分片流式写入本地临时文件,完成时按分片号顺序拼合并写入底层存储。feat(server/s3): reap abandoned multipart uploads-- 回收被遗弃的分片上传,避免临时文件无限累积。为什么需要:此前分片上传回退到 gofakes3 默认的内存上传器,会把每个分片都缓存在内存中,大文件分片上传可能耗尽内存;且客户端不 complete/abort 的孤儿上传会一直占用磁盘。现在改为按分片落盘(内存占用恒定),并由后台 reaper 自动清理超时未活动的上传。
用户可感知的行为变化:S3 客户端(awscli / s3cmd / rclone 等)的分片上传现在可用,不再因内存限制而失败;被遗弃的分片上传会在 TTL 后被自动回收。
重要实现变化:
OpenListTeam/gofakes3 v0.8.1(baseBackend接口兼容,既有单分片PutObject等行为不变)server/s3/multipart.go,实现gofakes3.MultipartBackend(CreateMultipartUpload / UploadPart / CompleteMultipartUpload / AbortMultipartUpload)s3Backend新增uploads sync.Map跟踪进行中的上传;每个上传记录lastActivity,在创建与每次分片上传时更新PutObject主体抽取为可复用的putStream,与分片 Complete 路径共享(目录创建、元数据、忽略规则一致);单分片PutObject行为不变conf.Conf.TempDir下的s3-multipart-*目录;返回 S3 规范的分片 etag("<md5ofmd5s>-N")ErrIncompleteBody;abort 幂等;Complete 失败时保留上传以便客户端重试TTL/4(钳制[10s, 1h])间隔回收lastActivity超过 TTL 的上传及其临时目录;启动时额外清理上次进程崩溃残留的s3-multipart-*目录(仅清理超过 TTL 的,避免误伤并发启动的兄弟实例)s3.multipart_ttl(envS3_MULTIPART_TTL),用time.ParseDuration解析,空/非法/<=0 回退默认24h配置/存储/API/兼容性:新增一个可选配置项
s3.multipart_ttl(默认 24h,不配置即用默认值);不修改已有 API、存储格式或迁移行为。This PR has breaking changes.
/ 此 PR 包含破坏性变更。
This PR changes public API, config, storage format, or migration behavior.
/ 此 PR 修改了公开 API、配置、存储格式或迁移行为。
This PR requires corresponding changes in related repositories.
/ 此 PR 需要关联仓库同步修改。
Related repository PRs / 关联仓库 PR:
Related Issues / 关联 Issue
Related to #460
Testing / 测试
go test ./...执行过的命令与结果(平台:macOS arm64,Go 1.26.5):
go build ./server/s3/、go build ./server/、go build ./internal/conf/-- 通过go vet ./server/...、go vet ./internal/conf/-- 干净gofmt -l server/s3/*.go internal/conf/config.go-- 干净go test ./server/s3/ -count=1-- 通过,包含新增测试:TestMultipartUploadEndToEnd:在真实 Local 驱动上 create->3 分片->complete,校验落盘文件内容为拼接结果、etag 为"<hex>-3";同号重传覆盖;短读->ErrIncompleteBody;未知 upload->ErrNoSuchUpload;越界分片号->ErrInvalidPart;乱序->ErrInvalidPartOrder;错 etag->ErrInvalidPart;缺分片->ErrInvalidPart;失败的 complete 保留上传可重试TestMultipartAbort:abort 删除临时目录并移除记录,且对未知 upload 幂等TestMultipartReapExpired:lastActivity超过 TTL 的上传被回收且临时目录删除,活跃上传保留TestMultipartCleanupStaleDirs:超过 TTL 的s3-multipart-*残留目录被清理,新目录保留go test ./...:server/s3通过。其余失败均与本次改动无关且为环境/既有问题--pkg/aria2/rpc:需连接localhost:6800的 aria2 守护进程(本机禁网)drivers/teldrive、drivers/webdav:需外部服务器/凭据internal/netTestNewOSSClientUsesEnvironmentHTTPSProxy:仓库既有失败(expected *http.Transport, got *net.safeTransport)drivers/*、internal/offline_download/*的 build failed:并行go test ./...时模块缓存写入竞争所致,单独go build正常server/s3(仅server/s3.go导入),不受本次改动影响未执行:对运行中的 OpenList 实例用 awscli / rclone 做手动联调(如维护者需要可补)。
Checklist / 检查清单
/ 我已阅读 CONTRIBUTING。
/ 我确认此贡献符合仓库许可证、贡献规范和行为准则。
gofmt,go fmt, orprettierwhere applicable./ 我已按适用情况使用
gofmt、go fmt或prettier格式化变更代码。/ 我已在适用情况下请求相关维护者或代码所有者审查。
AI Disclosure / AI 使用声明
/ 此 PR 包含 AI 辅助内容。
Tools used / 使用工具:
Usage scope / 使用范围:
Code generation / 代码生成
Refactoring / 重构
Documentation / 文档
Tests / 测试
Translation / 翻译
Review assistance / 审查辅助
I have reviewed and validated all AI-assisted content included in this PR.
/ 我已审核并验证此 PR 中的所有 AI 辅助内容。
I have ensured that all AI-assisted commits include
Co-Authored-Byattribution./ 我已确保所有 AI 辅助提交都包含
Co-Authored-By归属信息。I can reproduce all AI-assisted content included in this PR without any AI tools.
/ 我可以在没有任何 AI 工具的情况下重现此 PR 中包含的所有 AI 辅助内容。
Known Limitations / 已知限制
conf.Conf.TempDir,大文件上传会占用等量本地磁盘(相对原内存缓冲已是改进,为流式后端的固有取舍)。