必须在 uploadFromStream 或 openUploadStream 时通过 metadata 参数一次性写入自定义字段,否则无法落地;metadata 必须为 plain object,非 BSON 类型需手动序列化,contentType 应与 metadata 同级而非嵌套。

必须在 uploadFromStream 或 openUploadStream 调用时,通过 metadata 参数一次性写入自定义字段,否则字段根本不会落地到 fs.files 文档中。
为什么不能上传后再用 updateOne 补元数据
常见错误是先调用 uploadFromStream("a.pdf", stream),再执行 db.fs.files.updateOne({filename: "a.pdf"}, {$set: {author: "alice"}})。这会导致:
- GridFSBucket.find() 查不到该文件——驱动只认
fs.files中符合 GridFS 规范结构的文档,手动加的根级字段可能被忽略或触发校验失败 - MongoDB 7.0+ 启用 schema validation 时直接报错:Document failed validation
- 后续
openDownloadStream()可能找不到匹配项,因为内部查询逻辑依赖标准字段(如_id、filename)和完整结构
metadata 参数必须是 plain object,不能是字符串或数组
Node.js 驱动要求 metadata 是 plain JavaScript object;PyMongo 要求是 dict;Go 官方驱动需用 options.GridFSUpload().SetMetadata() 显式设置。以下写法均无效:
-
metadata: '{"author": "alice"}'(字符串,不解析) -
metadata: ["draft"](数组,不是对象) -
metadata: null或省略该参数(字段不会创建)
正确示例:{metadata: {author: "alice", tags: ["draft", "v2"], version: 2}}
避免踩坑:时间、枚举等非 BSON 原生类型必须手动序列化
Spring Data MongoDB 的 @WritingConverter 对 GridFS 无效——metadata 字段绕过所有 Spring 类型转换链,由 BSON 编码器直写。传 LocalDateTime 或枚举进去,结果通常是 null 或乱码。
-
LocalDateTime→ String:用DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(time),如"2026-10-21T12:13:00" -
LocalDateTime→ Long:用time.atZone(ZoneOffset.UTC).toInstant().toEpochMilli() - 枚举 → String:显式调用
.name()或.toString()
别指望框架帮你转;一漏就存空,查的时候永远对不上。
别把 contentType 塞进 metadata,它和 metadata 是同级参数
contentType 是独立字段,和 metadata 并列,不是它的子键。错误写法:{metadata: {contentType: "image/jpeg"}}。这会让 MIME 类型丢失,导致浏览器无法正确渲染或下载行为异常。
正确写法:bucket.uploadFromStream("x.jpg", stream, {contentType: "image/jpeg", metadata: {author: "bob"}})。
真正安全、跨驱动兼容的位置只有 fs.files.metadata 这个嵌套对象字段;所有其他“往根层级硬塞”的做法,在现代驱动和严格校验模式下都会失败或引发隐性故障。时间戳格式、字段命名一致性、类型稳定性,这些细节上线后极难排查。











