CloudVault 是一个基于 Go 的云端存储与网盘系统,面向大文件传输、高并发访问和文件管理场景设计。它的核心目标不是简单做一个“文件上传下载 Demo”,而是围绕真实网盘系统中的关键问题展开:大文件如何稳定上传、分片如何管理、断点续传如何恢复、离线下载任务如何异步执行、热门文件如何缓存、文件如何搜索、下载链接如何安全分享,以及后续如何接入 AI Agent 做自然语言文件查询。
项目地址:https://github.com/bestVictor2/CloudVault
这篇文章会从系统定位、核心业务、存储架构、上传下载链路、异步任务、缓存设计、搜索系统、AI 扩展和面试表达几个角度,完整拆解 CloudVault 这个项目。
1. 项目一句话介绍
CloudVault 可以概括为:
一个基于 Go 的云端存储与网盘系统,使用 MinIO 承载对象文件,MySQL 管理业务元数据,Redis 处理缓存、锁和高并发状态,RabbitMQ 承载异步任务,Elasticsearch 提供文件检索能力,并预留 AI Agent / RAG 能力用于自然语言文件查询。
如果放到简历或者面试里,可以这样讲:
我做了一个面向大文件传输和高并发场景的云端网盘系统,支持文件上传、下载、分享、回收站、离线下载、搜索和预览等功能。系统采用对象存储与业务元数据解耦架构,文件本体放在 MinIO,用户、目录、文件索引、分片状态等元数据放在 MySQL;通过 Redis 做热点缓存、分布式锁和上传状态管理;通过 RabbitMQ 将离线下载、文件处理等耗时任务异步化;通过 Elasticsearch 建立文件搜索索引;后续还设计了 AI Agent 与 RAG 文档问答扩展。
2. 它解决的核心问题
一个网盘系统看起来只是“上传文件、下载文件”,但真正做起来会遇到很多工程问题:
- 大文件不能一次性读入内存,否则会造成内存爆炸。
- 网络不稳定时,上传中断后不能从头重传,否则体验很差。
- 多用户并发上传同一个文件时,需要避免重复存储。
- 文件元数据和文件二进制内容需要解耦,否则数据库压力巨大。
- 下载大文件时需要支持流式传输,避免服务端内存占用过高。
- 分享链接需要有权限控制、过期时间和防盗链机制。
- 离线下载这类长任务不能阻塞 HTTP 请求线程。
- 搜索不能只靠 MySQL
LIKE,否则性能和效果都不够。 - 热门文件和用户信息需要缓存,否则数据库容易成为瓶颈。
- 后续如果要接入 AI,需要把文件内容变成可检索、可问答的知识。
CloudVault 的设计本质上是在回答这些问题。
3. 总体架构
从截图描述看,CloudVault 的核心组件包括:
1 | 用户 / 浏览器 / 客户端 |
这个架构有一个很重要的思想:
文件本体和业务元数据分离。
也就是说,真正的大文件不放 MySQL,而是放 MinIO 这类对象存储;MySQL 只记录文件的业务信息,例如文件名、大小、Hash、对象存储 key、所属用户、目录关系、分享状态、删除状态等。
这样做的好处是:
- MySQL 不需要承载大对象,读写压力更可控。
- MinIO 更适合存储大文件和对象数据。
- 文件迁移、扩容、备份和生命周期管理更灵活。
- 后续可以把 MinIO 替换成 S3、OSS、COS 等云厂商对象存储。
4. 为什么用 MinIO + MySQL 解耦
很多初学者做文件系统会把文件直接存到本地磁盘,或者把文件内容塞到数据库 BLOB 字段里。这两种方式在小 Demo 中可以工作,但在真实场景中问题很大。
4.1 本地磁盘的问题
如果文件只放在单机本地磁盘:
- 服务扩容后,不同机器看到的文件不一致。
- 机器宕机后,文件可能不可用。
- 文件迁移和备份麻烦。
- 很难做统一的权限、生命周期和对象管理。
4.2 MySQL BLOB 的问题
如果文件直接存 MySQL:
- 大文件会让数据库体积迅速膨胀。
- 备份恢复非常慢。
- 数据库 buffer pool 被大对象污染。
- 文件下载会占用数据库连接和网络资源。
- 高并发下载时数据库压力极大。
4.3 MinIO 的作用
MinIO 是兼容 S3 API 的对象存储服务,适合存储文件对象。CloudVault 可以把每个文件或分片当作对象写入 MinIO,例如:
1 | bucket: cloudvault |
MySQL 中只保存引用:
1 | file_id |
因此,MinIO 负责“存文件”,MySQL 负责“管文件”。
5. 数据模型设计
一个网盘系统至少需要以下核心表。
5.1 用户表
负责登录、权限和用户空间统计:
1 | users |
5.2 文件表
记录逻辑文件信息:
1 | files |
其中 file_hash 和 ref_count 很关键。
file_hash用于判断文件内容是否相同。ref_count用于实现秒传和去重存储。
如果两个用户上传同一个文件,系统可以只保存一份对象内容,然后通过多条用户文件记录引用同一个对象。
5.3 分片上传表
用于断点续传和分片状态管理:
1 | upload_sessions |
分片明细表:
1 | upload_chunks |
通过这两张表,可以知道某个大文件已经上传了哪些分片,断线后只需要补传缺失分片。
5.4 分享表
负责分享链接、访问码、过期时间和权限:
1 | shares |
5.5 回收站表
文件删除时可以先软删除:
1 | trash_records |
这样用户可以恢复文件,也方便后台定时清理真正过期的对象。
6. 文件上传链路
CloudVault 的上传链路可以拆成普通上传、秒传、分片上传、断点续传、分片合并几个部分。
6.1 普通上传
小文件可以直接上传:
1 | 客户端选择文件 |
这里需要注意:后端不能把整个文件一次性读入内存,应该使用流式读取,边读边计算 Hash 或写入对象存储。
6.2 秒传机制
秒传的核心是文件 Hash。
1 | 客户端先计算文件 Hash |
这样可以避免重复上传同一个文件。
例如,一个热门安装包已经被用户 A 上传过,用户 B 再上传时,只要 Hash 一致,系统就可以直接复用对象存储中的文件。
6.3 分片上传
大文件通常会拆成多个 chunk:
1 | file = chunk_0 + chunk_1 + chunk_2 + ... + chunk_n |
上传流程:
1 | 1. 客户端计算文件 Hash 和分片信息 |
这种方式的优势是:
- 可以并发上传,提高带宽利用率。
- 某个分片失败,只需要重传这个分片。
- 上传中断后,可以查询已上传分片列表继续上传。
- 服务端可以更细粒度地控制上传进度。
6.4 断点续传
断点续传依赖两个能力:
- 服务端记录分片状态。
- 客户端能查询缺失分片。
典型接口设计如下:
1 | POST /api/uploads/init |
当上传中断后,客户端请求 status:
1 | { |
客户端只补传 missing_chunks 即可。
7. 分片合并与一致性
分片合并是高并发上传中最容易出问题的地方。
7.1 为什么需要分布式锁
假设客户端因为网络重试,同时发了两次 complete 请求。如果没有保护,后端可能会同时执行两次合并:
1 | 请求 A:发现所有分片已上传 -> 开始合并 |
结果可能是:
- 重复生成对象。
- 重复写 MySQL 元数据。
- ref_count 错乱。
- 临时分片被提前删除。
因此 CloudVault 引入 Redis 分布式锁来保护合并阶段:
1 | lock key: upload:merge:{upload_id} |
只有拿到锁的请求才能执行合并,其它请求返回“合并中”或等待重试。
7.2 合并流程
1 | 获取 Redis 分布式锁 |
7.3 一致性策略
由于 MinIO、MySQL、Redis 是不同系统,无法简单依赖本地事务覆盖所有操作。比较稳妥的做法是:
- MySQL 记录 upload_session 的状态机。
- 对象存储操作尽量幂等。
- 合并前后都校验状态。
- Redis 锁防止并发合并。
- 异步清理临时对象,即使失败也可以后续补偿。
上传会话可以设计成状态机:
1 | initialized -> uploading -> merging -> completed |
这样即使中途失败,也能知道恢复或清理应该从哪里开始。
8. 文件下载设计
下载看似简单,但大文件下载同样需要优化。
8.1 流式下载
后端不应该一次性把文件读进内存,而应该从 MinIO 获取对象流,再写入 HTTP Response:
1 | MinIO object stream -> Go API response writer -> client |
这样内存占用和文件大小无关,更适合大文件。
8.2 预签名 URL
对于大文件下载,可以生成 MinIO 预签名 URL,让客户端直接从对象存储下载:
1 | 客户端请求下载 |
好处是:
- 降低应用服务器带宽压力。
- 下载链路更短。
- URL 可以设置过期时间。
- 适合大文件和高并发下载。
8.3 直链与防盗链
如果支持分享直链,需要考虑:
- 链接是否过期。
- 是否需要访问码。
- 是否绑定用户权限。
- 是否限制下载次数。
- 是否限制 IP 或 Referer。
- 是否允许批量下载。
一个安全的下载链接不应该永久有效,否则很容易被外部传播造成带宽滥用。
9. ZIP 批量打包下载
网盘常见需求是选中多个文件夹或文件后打包下载。
9.1 同步打包的问题
如果用户一次选择几百个文件,同步打包会导致:
- HTTP 请求长时间占用。
- 服务端 CPU 和 IO 压力大。
- 中途失败后无法恢复。
- 用户无法查看打包进度。
9.2 更合理的设计
可以将 ZIP 打包设计成异步任务:
1 | 用户提交批量下载请求 |
如果文件数量较少,也可以边压缩边流式返回。但对于大型目录,异步 ZIP 更稳定。
10. RabbitMQ 异步任务系统
CloudVault 使用 RabbitMQ 构建离线下载任务系统,核心目的是把耗时任务从 HTTP 请求中拆出去。
10.1 为什么需要消息队列
离线下载可能需要几十秒甚至几分钟,如果直接在 API 请求里执行:
- 请求容易超时。
- API 服务线程被占用。
- 无法控制并发。
- 失败后不容易重试。
- 用户无法查看任务状态。
RabbitMQ 可以将任务排队,由 Worker 后台消费。
10.2 离线下载流程
1 | 用户提交离线下载 URL |
任务状态可以设计为:
1 | pending -> running -> success |
10.3 失败重试
下载失败可能有很多原因:
- 远程 URL 超时。
- 网络连接断开。
- 文件过大。
- 目标网站限制下载。
- 对象存储写入失败。
可以采用延迟重试:
1 | 第一次失败:10 秒后重试 |
10.4 Dead Letter Queue
DLQ 是 Dead Letter Queue,死信队列。任务多次失败后不应该无限重试,否则会浪费资源。进入 DLQ 后可以:
- 记录失败原因。
- 后台人工排查。
- 管理员手动重放任务。
- 给用户展示明确失败信息。
面试中讲到 RabbitMQ,如果能主动提到重试、延迟队列和 DLQ,会比只说“用了 MQ 异步处理”更有工程深度。
11. Redis 在项目中的作用
Redis 在 CloudVault 中不是单纯做缓存,它至少承担四类职责。
11.1 用户信息缓存
用户信息、权限、空间配额这类读多写少的数据可以缓存:
1 | user:{user_id} -> 用户基础信息 |
这样可以减少 MySQL 查询。
11.2 热门文件元数据缓存
热门文件的元数据、下载次数、分享信息可以放 Redis:
1 | file:meta:{file_id} |
对于高频分享链接,缓存能明显降低数据库压力。
11.3 分布式锁
分片合并、秒传引用计数更新、分享链接更新等场景都可能需要分布式锁。
1 | lock:upload:merge:{upload_id} |
11.4 上传进度与临时状态
分片上传进度可以短期放 Redis:
1 | upload:{upload_id}:chunks -> bitmap / set |
这样查询进度更快,也减少频繁写 MySQL。
12. 缓存一致性与失效策略
缓存不是加上 Redis 就完事了,关键是怎么保证数据不过期、不脏读。
常见策略:
- Cache Aside:先读缓存,miss 后读 MySQL,再写缓存。
- 更新数据库后删除缓存,而不是直接更新缓存。
- 给缓存设置 TTL,防止永久脏数据。
- 热点 key 加随机过期时间,避免缓存雪崩。
- 对不存在的数据缓存空值,防止缓存穿透。
- 热点文件可以提前预热。
例如文件元数据更新:
1 | 更新 MySQL files 表 |
为什么是删除缓存而不是更新缓存?因为删除更简单,也更不容易出现并发覆盖问题。
13. Elasticsearch 文件搜索
MySQL 适合精确查询和事务,但不适合复杂全文搜索。CloudVault 引入 Elasticsearch 用于文件检索。
13.1 可以索引什么
文件搜索索引可以包含:
1 | { |
如果做得更进一步,还可以对文档内容提取文本后索引。
13.2 索引同步
文件上传成功后,需要同步 ES 索引:
1 | 文件元数据写入 MySQL |
为什么建议异步同步?因为 ES 暂时不可用时,不应该影响主链路上传成功。可以先保证 MySQL 和 MinIO 成功,再由后台任务补偿索引。
13.3 MySQL 回退查询
截图中提到“索引同步与 MySQL 回退查询”。这点很重要。
如果 Elasticsearch 查询失败,系统可以降级到 MySQL:
1 | 优先查 ES |
这样搜索能力不会因为 ES 故障完全不可用。
14. 文件预览设计
网盘系统通常需要支持图片、PDF、文本、视频等预览。
可以按文件类型拆分:
- 图片:直接返回预签名 URL 或缩略图。
- PDF:浏览器内嵌预览。
- 文本 / Markdown:后端读取前 N KB 内容并返回。
- 视频:支持 Range 请求,实现拖动播放。
- Office 文档:可以异步转 PDF 或使用在线预览服务。
预览时要注意权限校验,不能只要知道 object key 就能访问文件。
15. 回收站设计
用户删除文件时,不建议立刻删除对象存储中的文件。更好的方式是软删除:
1 | 用户删除文件 |
真正清理时还要考虑引用计数:
1 | ref_count > 1:只删除当前用户引用,不删对象 |
这和秒传、去重存储是配套的。
16. 分享系统设计
分享系统的核心不是生成一个 URL,而是权限控制。
一个分享链接可以包含:
- share_code:短链接标识。
- access_token:访问令牌。
- password:可选访问码。
- expire_at:过期时间。
- allow_download:是否允许下载。
- visit_count:访问次数。
- max_visit_count:最大访问次数。
访问流程:
1 | 用户访问分享链接 |
如果要防止链接被滥用,可以对分享下载加限速和访问次数限制。
17. 安全性设计
文件系统项目必须重视安全。
17.1 上传安全
需要限制:
- 单文件大小。
- 用户总容量。
- 文件类型。
- 分片大小。
- 并发上传数量。
- 恶意文件名和路径穿越。
例如文件名不能直接拼到本地路径中,否则可能出现:
1 | ../../etc/passwd |
对象存储 key 应该由服务端生成,而不是完全信任用户输入。
17.2 下载安全
下载前必须校验:
- 用户是否登录。
- 文件是否属于用户。
- 分享链接是否有效。
- 文件是否已经被删除。
- 是否有下载权限。
17.3 URL 安全
预签名 URL 应该短期有效,并且只在鉴权通过后生成。
例如:
1 | 有效期 5 分钟 |
这样即使 URL 泄露,风险也有限。
18. AI Agent 与 RAG 扩展
截图中提到:集成 AI Agent(Function Calling),实现自然语言文件查询与下载链接生成,并设计 RAG 文档问答架构。
这部分可以理解为 CloudVault 的智能化扩展。
18.1 Function Calling 能做什么
用户可以问:
1 | 帮我找一下上周上传的 Flow Matching PDF |
AI Agent 不应该直接访问数据库,而是通过工具函数调用后端能力:
1 | search_files(query, user_id, filters) |
这样可以保证权限控制仍然在后端服务内完成。
18.2 RAG 文档问答流程
如果要支持“基于文件内容问答”,需要构建 RAG 管线:
1 | 文件上传成功 |
例如用户问:
1 | 我那篇 Flow Matching 文章里 Rectified Flow 是怎么解释的? |
系统应该先检索该用户有权限访问的文件内容,再让模型基于检索片段回答,而不是让模型凭空编。
18.3 AI 权限边界
AI Agent 接入文件系统时必须注意:
- Agent 只能访问当前用户有权限的文件。
- 下载链接必须由后端鉴权后生成。
- RAG 检索必须带 user_id / permission filter。
- 不允许模型自己拼 object key 访问对象存储。
- 工具调用需要审计日志。
这也是 AI Infra 项目里很重要的工程点。
19. 高并发优化总结
CloudVault 的高并发优化可以总结为几类。
19.1 存储层
- 文件本体放对象存储,避免压垮 MySQL。
- 大文件分片上传,失败只重传分片。
- Hash 去重,减少重复存储。
- 预签名 URL,降低应用服务器带宽压力。
19.2 数据库层
- MySQL 只存元数据。
- 高频查询字段建立索引。
- 文件列表按目录和用户分页查询。
- 删除使用软删除状态。
19.3 缓存层
- 用户信息缓存。
- 热门文件元数据缓存。
- 分享链接缓存。
- 上传进度缓存。
- 分布式锁保护关键阶段。
19.4 异步层
- 离线下载异步化。
- ZIP 打包异步化。
- ES 索引同步异步化。
- 失败重试和 DLQ 兜底。
19.5 搜索层
- Elasticsearch 承担全文搜索。
- MySQL 作为回退查询。
- 文件内容可进一步接入 RAG。
20. 典型接口设计
下面是一组比较合理的 API 草图。
20.1 文件接口
1 | GET /api/files?parent_id=xxx |
20.2 上传接口
1 | POST /api/uploads/check |
20.3 分享接口
1 | POST /api/shares |
20.4 搜索接口
1 | GET /api/search?q=xxx&type=pdf&from=0&size=20 |
20.5 离线下载接口
1 | POST /api/offline-downloads |
20.6 AI 查询接口
1 | POST /api/ai/chat |
21. 项目难点
如果面试官追问这个项目难在哪里,可以重点讲下面几个。
21.1 分片上传状态一致性
难点在于分片并发上传、complete 重复请求、服务重启、对象存储成功但数据库失败等边界情况。
解决思路:
- 上传会话状态机。
- 分片状态持久化。
- Redis 分布式锁保护合并。
- 合并操作幂等。
- 定时任务清理过期 session 和孤儿 chunk。
21.2 秒传和去重的一致性
如果多个用户同时上传同一个 Hash,需要保证 ref_count 正确。
解决思路:
- file_hash 建唯一索引或单独 object 表。
- 更新引用计数时使用事务或分布式锁。
- 对象写入和元数据写入要有补偿机制。
21.3 离线下载的可靠性
离线下载可能失败、超时、重试,也可能 Worker 崩溃。
解决思路:
- RabbitMQ ack 机制。
- 任务状态持久化。
- 最大重试次数。
- 延迟重试。
- DLQ 保存失败任务。
- Worker 幂等处理。
21.4 搜索索引一致性
ES 和 MySQL 之间可能短暂不一致。
解决思路:
- 主数据以 MySQL 为准。
- ES 异步同步。
- 失败任务重试。
- 定时全量校验或重建索引。
- ES 故障时回退 MySQL。
21.5 AI Agent 的权限控制
AI 查询文件时很容易出现越权风险。
解决思路:
- 所有工具函数都必须带 user_id。
- 检索时强制加权限过滤。
- 下载链接必须后端生成。
- 工具调用写审计日志。
- 模型只负责决策,不直接访问底层存储。
22. 简历写法建议
可以把这个项目写成下面这种格式:
CloudVault(基于 Go 的云端存储与网盘系统):面向大文件传输与高并发场景,完成文件上传、下载、分享、回收站、离线下载、搜索与预览等核心功能。采用 MinIO 对象存储 + MySQL 元数据解耦架构,支持分片上传、断点续传、Hash 秒传和引用计数去重;使用 Redis 实现热点缓存、上传进度缓存和分布式锁,保障分片合并一致性;基于 RabbitMQ 构建离线下载任务系统,支持任务入队、Worker 并发消费、失败重试、延迟重试和 DLQ;引入 Elasticsearch 构建文件检索能力,并设计 AI Agent / RAG 扩展,实现自然语言文件查询与下载链接生成。
23. 面试 1 分钟讲法
如果面试官让你介绍这个项目,可以这样说:
CloudVault 是我做的一个 Go 云端网盘系统,核心是解决大文件上传下载和高并发文件管理问题。架构上我把文件本体和业务元数据解耦,文件放 MinIO,对象 key、Hash、目录、权限、分享等元数据放 MySQL。上传侧支持分片上传、断点续传和 Hash 秒传,分片合并时用 Redis 分布式锁保证并发一致性。下载侧支持流式下载、预签名 URL 和 ZIP 批量下载,降低应用服务器带宽压力。异步任务方面用 RabbitMQ 做离线下载,支持 Worker 消费、失败重试、延迟重试和 DLQ。搜索方面用 Elasticsearch 建文件索引,ES 不可用时回退 MySQL。后续还设计了 AI Agent 和 RAG,让用户可以用自然语言查文件、生成下载链接和基于文档内容问答。
24. 面试高频追问
Q1:为什么不用本地磁盘存文件?
本地磁盘不利于多实例部署和扩容,服务迁移、容灾、备份都麻烦。对象存储更适合大文件,可以统一管理对象、生命周期和访问权限。
Q2:为什么文件本体不放 MySQL?
MySQL 更适合存结构化元数据,不适合承载大文件。大文件会导致数据库体积、备份恢复、连接占用和 IO 压力都变大。因此文件放 MinIO,MySQL 只保存 object key 和业务元数据。
Q3:断点续传怎么实现?
客户端把文件切成多个 chunk,服务端记录 upload_session 和每个 chunk 的上传状态。中断后客户端查询已上传分片列表,只补传缺失分片。所有分片完成后再合并。
Q4:分片合并怎么保证不会重复执行?
使用 Redis 分布式锁保护 upload_id 对应的合并流程,同时在 MySQL 中维护 session 状态机。complete 请求重复到达时,只有一个请求能拿到锁,其它请求返回合并中或读取最终状态。
Q5:秒传怎么实现?
上传前先计算文件 Hash,服务端检查 Hash 是否已经存在。如果存在,只创建新的用户文件记录并增加引用计数;如果不存在,再真正上传对象内容。
Q6:离线下载为什么要用 RabbitMQ?
离线下载是长耗时任务,直接在 HTTP 请求中执行容易超时,也不利于控制并发。RabbitMQ 可以让任务入队,由 Worker 异步消费,并支持失败重试、延迟重试和 DLQ。
Q7:Redis 缓存如何保持一致?
使用 Cache Aside 模式。读时先查缓存,miss 后查 MySQL 并回填;写时先更新 MySQL,再删除缓存,让下次读取重新加载。同时设置 TTL 防止永久脏数据。
Q8:Elasticsearch 和 MySQL 数据不一致怎么办?
MySQL 作为主数据源,ES 作为搜索索引。索引更新通过异步任务同步,失败可以重试或定时重建。查询时如果 ES 不可用,可以降级到 MySQL 模糊查询。
Q9:预签名 URL 有什么好处?
预签名 URL 可以让客户端下载直接走对象存储,减少应用服务器带宽压力。同时 URL 可以设置短期有效,兼顾性能和安全。
Q10:AI Agent 接入文件系统怎么防越权?
Agent 只能调用后端暴露的工具函数,所有工具函数都必须带 user_id 和权限校验。RAG 检索也必须加用户权限过滤,下载链接只能由后端鉴权后生成。
25. 总结
CloudVault 这个项目的价值在于它覆盖了网盘系统的核心工程问题:对象存储与元数据解耦、大文件分片上传、断点续传、Hash 秒传、分布式锁、异步任务、失败重试、DLQ、缓存一致性、全文搜索、预签名下载链接以及 AI 文件查询扩展。
如果只是从功能看,它是一个云盘;但如果从架构看,它是一个很适合展示后端工程能力的综合项目。它能体现你对 Go 后端、对象存储、MySQL、Redis、RabbitMQ、Elasticsearch、高并发一致性和 AI Agent 工程化的理解。