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. 它解决的核心问题

一个网盘系统看起来只是“上传文件、下载文件”,但真正做起来会遇到很多工程问题:

  1. 大文件不能一次性读入内存,否则会造成内存爆炸。
  2. 网络不稳定时,上传中断后不能从头重传,否则体验很差。
  3. 多用户并发上传同一个文件时,需要避免重复存储。
  4. 文件元数据和文件二进制内容需要解耦,否则数据库压力巨大。
  5. 下载大文件时需要支持流式传输,避免服务端内存占用过高。
  6. 分享链接需要有权限控制、过期时间和防盗链机制。
  7. 离线下载这类长任务不能阻塞 HTTP 请求线程。
  8. 搜索不能只靠 MySQL LIKE,否则性能和效果都不够。
  9. 热门文件和用户信息需要缓存,否则数据库容易成为瓶颈。
  10. 后续如果要接入 AI,需要把文件内容变成可检索、可问答的知识。

CloudVault 的设计本质上是在回答这些问题。

3. 总体架构

从截图描述看,CloudVault 的核心组件包括:

1
2
3
4
5
6
7
8
9
10
11
用户 / 浏览器 / 客户端
|
v
Go 后端 API 服务
|
|-- MySQL:用户、目录、文件、分享、回收站、分片状态等元数据
|-- MinIO:文件对象、分片对象、合并后的文件对象
|-- Redis:用户缓存、热点文件缓存、分布式锁、上传进度
|-- RabbitMQ:离线下载、文件处理、失败重试、DLQ
|-- Elasticsearch:文件名、路径、内容摘要、标签等搜索索引
|-- AI Agent / RAG:自然语言查询、文件语义检索、下载链接生成

这个架构有一个很重要的思想:

文件本体和业务元数据分离。

也就是说,真正的大文件不放 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
2
3
bucket: cloudvault
object key: users/{user_id}/files/{file_hash}
object key: chunks/{upload_id}/{chunk_index}

MySQL 中只保存引用:

1
2
3
4
5
6
7
8
9
10
11
file_id
user_id
filename
file_hash
size
mime_type
object_key
storage_bucket
status
created_at
updated_at

因此,MinIO 负责“存文件”,MySQL 负责“管文件”。

5. 数据模型设计

一个网盘系统至少需要以下核心表。

5.1 用户表

负责登录、权限和用户空间统计:

1
2
3
4
5
6
7
8
9
10
users
- id
- username
- email
- password_hash
- avatar
- used_quota
- total_quota
- created_at
- updated_at

5.2 文件表

记录逻辑文件信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
files
- id
- user_id
- parent_id
- filename
- file_hash
- size
- mime_type
- object_key
- ref_count
- status
- created_at
- updated_at

其中 file_hashref_count 很关键。

  • file_hash 用于判断文件内容是否相同。
  • ref_count 用于实现秒传和去重存储。

如果两个用户上传同一个文件,系统可以只保存一份对象内容,然后通过多条用户文件记录引用同一个对象。

5.3 分片上传表

用于断点续传和分片状态管理:

1
2
3
4
5
6
7
8
9
10
11
12
upload_sessions
- id
- user_id
- filename
- file_hash
- total_size
- chunk_size
- total_chunks
- uploaded_chunks
- status
- created_at
- updated_at

分片明细表:

1
2
3
4
5
6
7
8
9
upload_chunks
- id
- upload_id
- chunk_index
- chunk_hash
- size
- object_key
- status
- created_at

通过这两张表,可以知道某个大文件已经上传了哪些分片,断线后只需要补传缺失分片。

5.4 分享表

负责分享链接、访问码、过期时间和权限:

1
2
3
4
5
6
7
8
9
10
shares
- id
- user_id
- file_id
- share_code
- access_token
- expire_at
- allow_download
- visit_count
- created_at

5.5 回收站表

文件删除时可以先软删除:

1
2
3
4
5
6
trash_records
- id
- user_id
- file_id
- deleted_at
- expire_at

这样用户可以恢复文件,也方便后台定时清理真正过期的对象。

6. 文件上传链路

CloudVault 的上传链路可以拆成普通上传、秒传、分片上传、断点续传、分片合并几个部分。

6.1 普通上传

小文件可以直接上传:

1
2
3
4
5
6
客户端选择文件
-> 后端接收 multipart file
-> 计算文件 Hash
-> 上传到 MinIO
-> 写入 MySQL 文件元数据
-> 返回文件 ID

这里需要注意:后端不能把整个文件一次性读入内存,应该使用流式读取,边读边计算 Hash 或写入对象存储。

6.2 秒传机制

秒传的核心是文件 Hash。

1
2
3
4
客户端先计算文件 Hash
-> 请求后端检查 Hash 是否存在
-> 如果存在,直接创建文件引用
-> 如果不存在,走正常上传

这样可以避免重复上传同一个文件。

例如,一个热门安装包已经被用户 A 上传过,用户 B 再上传时,只要 Hash 一致,系统就可以直接复用对象存储中的文件。

6.3 分片上传

大文件通常会拆成多个 chunk:

1
file = chunk_0 + chunk_1 + chunk_2 + ... + chunk_n

上传流程:

1
2
3
4
5
6
7
8
1. 客户端计算文件 Hash 和分片信息
2. 创建上传会话 upload_session
3. 客户端并发上传多个 chunk
4. 后端保存 chunk 到 MinIO
5. 后端记录 chunk 状态到 MySQL / Redis
6. 所有 chunk 完成后触发合并
7. 合并成功后生成最终文件对象
8. 删除临时 chunk 或设置生命周期清理

这种方式的优势是:

  • 可以并发上传,提高带宽利用率。
  • 某个分片失败,只需要重传这个分片。
  • 上传中断后,可以查询已上传分片列表继续上传。
  • 服务端可以更细粒度地控制上传进度。

6.4 断点续传

断点续传依赖两个能力:

  1. 服务端记录分片状态。
  2. 客户端能查询缺失分片。

典型接口设计如下:

1
2
3
4
POST /api/uploads/init
GET /api/uploads/{upload_id}/status
PUT /api/uploads/{upload_id}/chunks/{chunk_index}
POST /api/uploads/{upload_id}/complete

当上传中断后,客户端请求 status:

1
2
3
4
5
6
{
"upload_id": "u_123",
"total_chunks": 100,
"uploaded_chunks": [0, 1, 2, 5, 6],
"missing_chunks": [3, 4, 7, 8]
}

客户端只补传 missing_chunks 即可。

7. 分片合并与一致性

分片合并是高并发上传中最容易出问题的地方。

7.1 为什么需要分布式锁

假设客户端因为网络重试,同时发了两次 complete 请求。如果没有保护,后端可能会同时执行两次合并:

1
2
请求 A:发现所有分片已上传 -> 开始合并
请求 B:发现所有分片已上传 -> 也开始合并

结果可能是:

  • 重复生成对象。
  • 重复写 MySQL 元数据。
  • ref_count 错乱。
  • 临时分片被提前删除。

因此 CloudVault 引入 Redis 分布式锁来保护合并阶段:

1
lock key: upload:merge:{upload_id}

只有拿到锁的请求才能执行合并,其它请求返回“合并中”或等待重试。

7.2 合并流程

1
2
3
4
5
6
7
8
9
10
获取 Redis 分布式锁
-> 校验 upload_session 状态
-> 校验所有分片都已上传
-> 按 chunk_index 顺序读取分片
-> 合并生成最终对象
-> 计算最终文件 Hash 并校验
-> 写入 files 元数据
-> 更新 upload_session 为 completed
-> 删除临时分片或异步清理
-> 释放锁

7.3 一致性策略

由于 MinIO、MySQL、Redis 是不同系统,无法简单依赖本地事务覆盖所有操作。比较稳妥的做法是:

  • MySQL 记录 upload_session 的状态机。
  • 对象存储操作尽量幂等。
  • 合并前后都校验状态。
  • Redis 锁防止并发合并。
  • 异步清理临时对象,即使失败也可以后续补偿。

上传会话可以设计成状态机:

1
2
3
initialized -> uploading -> merging -> completed
\-> failed
\-> expired

这样即使中途失败,也能知道恢复或清理应该从哪里开始。

8. 文件下载设计

下载看似简单,但大文件下载同样需要优化。

8.1 流式下载

后端不应该一次性把文件读进内存,而应该从 MinIO 获取对象流,再写入 HTTP Response:

1
MinIO object stream -> Go API response writer -> client

这样内存占用和文件大小无关,更适合大文件。

8.2 预签名 URL

对于大文件下载,可以生成 MinIO 预签名 URL,让客户端直接从对象存储下载:

1
2
3
4
客户端请求下载
-> 后端鉴权
-> 后端生成短期有效 presigned URL
-> 客户端直接访问 MinIO 下载

好处是:

  • 降低应用服务器带宽压力。
  • 下载链路更短。
  • URL 可以设置过期时间。
  • 适合大文件和高并发下载。

8.3 直链与防盗链

如果支持分享直链,需要考虑:

  • 链接是否过期。
  • 是否需要访问码。
  • 是否绑定用户权限。
  • 是否限制下载次数。
  • 是否限制 IP 或 Referer。
  • 是否允许批量下载。

一个安全的下载链接不应该永久有效,否则很容易被外部传播造成带宽滥用。

9. ZIP 批量打包下载

网盘常见需求是选中多个文件夹或文件后打包下载。

9.1 同步打包的问题

如果用户一次选择几百个文件,同步打包会导致:

  • HTTP 请求长时间占用。
  • 服务端 CPU 和 IO 压力大。
  • 中途失败后无法恢复。
  • 用户无法查看打包进度。

9.2 更合理的设计

可以将 ZIP 打包设计成异步任务:

1
2
3
4
5
6
7
用户提交批量下载请求
-> API 创建 zip_task
-> 投递 RabbitMQ 消息
-> Worker 拉取文件并生成 ZIP
-> ZIP 上传到 MinIO
-> 更新任务状态和下载链接
-> 用户轮询或 WebSocket 获取结果

如果文件数量较少,也可以边压缩边流式返回。但对于大型目录,异步 ZIP 更稳定。

10. RabbitMQ 异步任务系统

CloudVault 使用 RabbitMQ 构建离线下载任务系统,核心目的是把耗时任务从 HTTP 请求中拆出去。

10.1 为什么需要消息队列

离线下载可能需要几十秒甚至几分钟,如果直接在 API 请求里执行:

  • 请求容易超时。
  • API 服务线程被占用。
  • 无法控制并发。
  • 失败后不容易重试。
  • 用户无法查看任务状态。

RabbitMQ 可以将任务排队,由 Worker 后台消费。

10.2 离线下载流程

1
2
3
4
5
6
7
8
9
10
用户提交离线下载 URL
-> API 校验 URL 和权限
-> MySQL 创建 download_task
-> RabbitMQ 投递任务消息
-> Worker 消费任务
-> Worker 下载远程文件
-> 写入 MinIO
-> 更新 MySQL 任务状态
-> 同步 ES 索引
-> 通知用户完成

任务状态可以设计为:

1
2
3
4
pending -> running -> success
\-> retrying -> running
\-> failed
\-> canceled

10.3 失败重试

下载失败可能有很多原因:

  • 远程 URL 超时。
  • 网络连接断开。
  • 文件过大。
  • 目标网站限制下载。
  • 对象存储写入失败。

可以采用延迟重试:

1
2
3
4
第一次失败:10 秒后重试
第二次失败:1 分钟后重试
第三次失败:5 分钟后重试
超过最大次数:进入 DLQ

10.4 Dead Letter Queue

DLQ 是 Dead Letter Queue,死信队列。任务多次失败后不应该无限重试,否则会浪费资源。进入 DLQ 后可以:

  • 记录失败原因。
  • 后台人工排查。
  • 管理员手动重放任务。
  • 给用户展示明确失败信息。

面试中讲到 RabbitMQ,如果能主动提到重试、延迟队列和 DLQ,会比只说“用了 MQ 异步处理”更有工程深度。

11. Redis 在项目中的作用

Redis 在 CloudVault 中不是单纯做缓存,它至少承担四类职责。

11.1 用户信息缓存

用户信息、权限、空间配额这类读多写少的数据可以缓存:

1
2
user:{user_id} -> 用户基础信息
quota:{user_id} -> 空间使用量

这样可以减少 MySQL 查询。

11.2 热门文件元数据缓存

热门文件的元数据、下载次数、分享信息可以放 Redis:

1
2
3
file:meta:{file_id}
share:{share_code}
hot:files

对于高频分享链接,缓存能明显降低数据库压力。

11.3 分布式锁

分片合并、秒传引用计数更新、分享链接更新等场景都可能需要分布式锁。

1
2
lock:upload:merge:{upload_id}
lock:file:ref:{file_hash}

11.4 上传进度与临时状态

分片上传进度可以短期放 Redis:

1
2
upload:{upload_id}:chunks -> bitmap / set
upload:{upload_id}:progress -> percentage

这样查询进度更快,也减少频繁写 MySQL。

12. 缓存一致性与失效策略

缓存不是加上 Redis 就完事了,关键是怎么保证数据不过期、不脏读。

常见策略:

  1. Cache Aside:先读缓存,miss 后读 MySQL,再写缓存。
  2. 更新数据库后删除缓存,而不是直接更新缓存。
  3. 给缓存设置 TTL,防止永久脏数据。
  4. 热点 key 加随机过期时间,避免缓存雪崩。
  5. 对不存在的数据缓存空值,防止缓存穿透。
  6. 热点文件可以提前预热。

例如文件元数据更新:

1
2
3
更新 MySQL files 表
-> 删除 Redis file:meta:{file_id}
-> 下次读取时重新加载

为什么是删除缓存而不是更新缓存?因为删除更简单,也更不容易出现并发覆盖问题。

13. Elasticsearch 文件搜索

MySQL 适合精确查询和事务,但不适合复杂全文搜索。CloudVault 引入 Elasticsearch 用于文件检索。

13.1 可以索引什么

文件搜索索引可以包含:

1
2
3
4
5
6
7
8
9
10
11
{
"file_id": 123,
"user_id": 45,
"filename": "flow matching notes.pdf",
"path": "/papers/generative-models/",
"mime_type": "application/pdf",
"tags": ["AI", "Diffusion", "Flow Matching"],
"summary": "一份关于 Flow Matching 的学习笔记",
"created_at": "2026-05-10T10:00:00Z",
"updated_at": "2026-05-10T10:00:00Z"
}

如果做得更进一步,还可以对文档内容提取文本后索引。

13.2 索引同步

文件上传成功后,需要同步 ES 索引:

1
2
3
文件元数据写入 MySQL
-> 投递 file_index 任务
-> Worker 写入 Elasticsearch

为什么建议异步同步?因为 ES 暂时不可用时,不应该影响主链路上传成功。可以先保证 MySQL 和 MinIO 成功,再由后台任务补偿索引。

13.3 MySQL 回退查询

截图中提到“索引同步与 MySQL 回退查询”。这点很重要。

如果 Elasticsearch 查询失败,系统可以降级到 MySQL:

1
2
3
4
优先查 ES
-> ES 失败或超时
-> 回退 MySQL filename like / metadata query
-> 返回基础结果

这样搜索能力不会因为 ES 故障完全不可用。

14. 文件预览设计

网盘系统通常需要支持图片、PDF、文本、视频等预览。

可以按文件类型拆分:

  • 图片:直接返回预签名 URL 或缩略图。
  • PDF:浏览器内嵌预览。
  • 文本 / Markdown:后端读取前 N KB 内容并返回。
  • 视频:支持 Range 请求,实现拖动播放。
  • Office 文档:可以异步转 PDF 或使用在线预览服务。

预览时要注意权限校验,不能只要知道 object key 就能访问文件。

15. 回收站设计

用户删除文件时,不建议立刻删除对象存储中的文件。更好的方式是软删除:

1
2
3
4
5
6
用户删除文件
-> files.status = trashed
-> 写入 trash_records
-> 文件从普通列表隐藏
-> 用户可以恢复
-> 过期后后台任务真正清理

真正清理时还要考虑引用计数:

1
2
ref_count > 1:只删除当前用户引用,不删对象
ref_count = 1:删除对象存储中的真实文件

这和秒传、去重存储是配套的。

16. 分享系统设计

分享系统的核心不是生成一个 URL,而是权限控制。

一个分享链接可以包含:

  • share_code:短链接标识。
  • access_token:访问令牌。
  • password:可选访问码。
  • expire_at:过期时间。
  • allow_download:是否允许下载。
  • visit_count:访问次数。
  • max_visit_count:最大访问次数。

访问流程:

1
2
3
4
5
6
用户访问分享链接
-> 校验 share_code 是否存在
-> 校验是否过期
-> 校验访问码
-> 校验文件是否还存在
-> 返回文件列表或下载链接

如果要防止链接被滥用,可以对分享下载加限速和访问次数限制。

17. 安全性设计

文件系统项目必须重视安全。

17.1 上传安全

需要限制:

  • 单文件大小。
  • 用户总容量。
  • 文件类型。
  • 分片大小。
  • 并发上传数量。
  • 恶意文件名和路径穿越。

例如文件名不能直接拼到本地路径中,否则可能出现:

1
../../etc/passwd

对象存储 key 应该由服务端生成,而不是完全信任用户输入。

17.2 下载安全

下载前必须校验:

  • 用户是否登录。
  • 文件是否属于用户。
  • 分享链接是否有效。
  • 文件是否已经被删除。
  • 是否有下载权限。

17.3 URL 安全

预签名 URL 应该短期有效,并且只在鉴权通过后生成。

例如:

1
2
3
有效期 5 分钟
只允许 GET
只对应某一个 object key

这样即使 URL 泄露,风险也有限。

18. AI Agent 与 RAG 扩展

截图中提到:集成 AI Agent(Function Calling),实现自然语言文件查询与下载链接生成,并设计 RAG 文档问答架构。

这部分可以理解为 CloudVault 的智能化扩展。

18.1 Function Calling 能做什么

用户可以问:

1
2
3
帮我找一下上周上传的 Flow Matching PDF
把我最近的简历下载链接发给我
找出所有和 RAG 有关的文档

AI Agent 不应该直接访问数据库,而是通过工具函数调用后端能力:

1
2
3
4
search_files(query, user_id, filters)
get_file_detail(file_id, user_id)
generate_download_link(file_id, user_id)
summarize_document(file_id, user_id)

这样可以保证权限控制仍然在后端服务内完成。

18.2 RAG 文档问答流程

如果要支持“基于文件内容问答”,需要构建 RAG 管线:

1
2
3
4
5
6
7
8
文件上传成功
-> 判断是否可解析
-> 提取文本内容
-> 文本切 chunk
-> 生成 embedding
-> 写入向量数据库 / ES dense vector
-> 用户提问时检索相关 chunk
-> LLM 基于检索内容回答

例如用户问:

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
2
3
4
5
6
GET    /api/files?parent_id=xxx
POST /api/files/folders
DELETE /api/files/{file_id}
POST /api/files/{file_id}/restore
GET /api/files/{file_id}/preview
GET /api/files/{file_id}/download

20.2 上传接口

1
2
3
4
5
POST /api/uploads/check
POST /api/uploads/init
PUT /api/uploads/{upload_id}/chunks/{chunk_index}
GET /api/uploads/{upload_id}/status
POST /api/uploads/{upload_id}/complete

20.3 分享接口

1
2
3
4
5
POST   /api/shares
GET /api/shares/{share_code}
POST /api/shares/{share_code}/verify
GET /api/shares/{share_code}/download
DELETE /api/shares/{share_id}

20.4 搜索接口

1
GET /api/search?q=xxx&type=pdf&from=0&size=20

20.5 离线下载接口

1
2
3
POST /api/offline-downloads
GET /api/offline-downloads/{task_id}
POST /api/offline-downloads/{task_id}/cancel

20.6 AI 查询接口

1
2
3
POST /api/ai/chat
POST /api/ai/search-files
POST /api/ai/generate-download-link

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 工程化的理解。