GridFS支持断点续传,但需严格使用open_download_stream(下载)和open_upload_stream_with_id(上传),禁止直接操作fs.chunks;skip/limit会破坏chunk边界与数据完整性,90%文件损坏源于此误用。

GridFS 本身不提供“断点续传接口”,但支持断点续传——前提是服务端正确使用 open_download_stream 和 open_upload_stream_with_id,且前后端对齐 chunk 序号、字节边界与失败清理逻辑。绕过驱动直接操作 fs.chunks 集合是高危行为,90% 的文件损坏都源于此。
为什么不能用 skip/limit 模拟下载偏移
很多人看到 fs.chunks 是普通集合,就写 db.fs.chunks.find().skip(10).limit(1) 手动拼 chunk,这会立刻破坏文件完整性:
-
skip(10)跳的是 10 个文档,不是 10 × 255KB 字节;最后一个 chunk 可能只有 12 字节,跳过它会导致解码失败 -
n字段不严格保序:并发上传或重试时可能产生空洞(如 n=0,1,3)、重复(n=2 出现两次),skip无法识别这些异常 - 完全绕过
fs.files中的length、chunkSize、md5等元数据,后续校验、删除、按名查询全部失效 - 手动拼接漏掉一个 chunk 或顺序错乱,文件解码静默失败——没有错误提示,PDF 打不开、视频花屏才暴露问题
下载侧必须用 open_download_stream(start, end)
所有官方驱动(PyMongo、Node.js、Java)只通过 open_download_stream 支持字节级随机读取,它内部自动完成 chunk 定位、前导字节跳过、末尾截断和 CRC 校验:
-
start是起始字节位置(含),从 0 开始;end是结束字节位置(不含),例如start=1048576、end=1048576+8192表示读取第 1MB 起的 8KB - 若客户端请求
Range: bytes=1000-(无 end),end参数必须省略,否则驱动会读到文件末尾,触发底层异常 - HTTP 层必须手动解析
Range头并转换:bytes=1000-2000→start=1000,end=2001;bytes=1000-→start=1000, 不传end - 响应必须带
Content-Range: bytes 1000-2000/10485760和状态码206 Partial Content,否则浏览器拒绝后续请求 - 每次调用都要新建 stream 对象,复用同一
stream实例会导致内部 buffer 错乱
上传侧靠 max(n) 查已传块,而非 count()
客户端上传前应先查服务端:“这个 fileId 已传到第几块?” 服务端执行:
db.fs.chunks.find({ files_id: ObjectId("...") }, { n: 1 }).sort({ n: -1 }).limit(1)
返回 { n: 4 } 表示 n=0~4 已存在,前端应从 n=5 开始上传新 chunk:
- 绝不能用
countDocuments判断进度——失败 chunk 可能残留脏数据,导致误判 - 必须用
open_upload_stream_with_id(或等价 API),显式传入已有fileId;传错 ID 或漏传,旧 chunk 成为孤儿 - 并发上传同一文件时,多个请求同时查
max(n)再上传,可能撞块(n=5 被写两次),需加轻量锁(如 Redissetnx)或强制前端串行 -
chunkSize必须全局一致:上传时设为 128KB,下载时驱动却读到默认 255KB,open_download_stream定位必然错位
真正麻烦的是上传中断后的状态判断
断点续传最难的部分不是“怎么传”,而是“怎么判断哪些 chunk 没传、哪些传了一半、哪些传完了但没合并”。MongoDB 不提供跨集合事务回滚能力,fs.files 文档是否插入、fs.chunks 是否完整、length 是否匹配实际字节数——这三者必须全部校验,缺一不可。一个 n 偏移算错,文件末尾就丢数据。


















