向已有文档添加 PNG 附件时 Content-Type 必须为何值?如何生成对应的 _attachments 存根?

解读

面试官问的是“已有文档”,意味着不能走新建文档时一次性 multipart/form-data 的捷径,而是要在文档已存在的前提下,通过一次独立请求把 PNG 附件写进去,同时让 CouchDB 在文档元数据里生成正确的 _attachments 存根。
国内面试里,这道题常被用来区分“只会用 Fauxton 按钮上传”与“真正读过 HTTP API 文档”的候选人。答不到点上,会被追问 412、rev 树冲突、base64 体积膨胀等细节,直接掉分。

知识点

  1. 独立附件上传的 HTTP 端点格式
    PUT /{db}/{docId}/{attachmentName}?rev={currentRev}
    请求头必须带 Content-Type: image/png值必须是 RFC 2083 规定的官方 MIME 字符串,不能写 image/PNG 大写,也不能省掉子类型
  2. 成功返回 201 时,响应头里会给出新的 Etag: "3-xxx",就是后续要用的新 rev
  3. 附件存根(stub)是 CouchDB 在文档 JSON 里自动维护的元数据,开发者无需手动计算,只要上传成功,再次 GET /{db}/{docId} 就能在 _attachments 字段里看到:
    { "logo.png": { "content_type": "image/png", "revpos": 3, "length": 12345, "stub": true } }
    其中 stub:true 告诉客户端“真正的二进制在附件存储区,不在 JSON 里”。
  4. 如果采用“文档覆盖”方式(先把附件 base64 内嵌再 PUT /{db}/{docId}),Content-Type 同样要写 image/png,但数据字段名是 _attachments.{name}.data,且需要先把 PNG 读成 base64 字符串,国内生产环境不推荐,因为体积膨胀 33%+ 且容易触发 413。
  5. 国内私有云常关掉了 multipart/related 优化,因此最稳妥、面试官最认可的答案就是第一条的独立 PUT 上传

答案

Content-Type 必须写成 image/png(全小写,不可省略)。
生成 _attachments 存根不需要手动计算,只需:

  1. HEAD /{db}/{docId} 拿到当前 Etagrev
  2. 执行
    PUT /{db}/{docId}/avatar.png?rev=2-abc
    Content-Type: image/png
    body: <原始 PNG 二进制>
  3. 成功后 CouchDB 自动在文档里插入 _attachments.avatar.png 存根,下次读取文档即可看到。

拓展思考

面试官可能继续追问:

  • 如果两次并发上传不同附件,rev 冲突怎么解决?(提示:先 GET 最新 rev,再重试 PUT,或使用 _bulk_docsnew_edits=false 做合并)
  • 移动端弱网场景下,如何做到“断点续传”?(CouchDB 原生不支持 HTTP RANGE,需要在客户端拆块、自定义 doc 结构,再用 replication 合并)
  • 国内项目把图片放 CouchDB 是否划算?(单附件 ≤ 64 M 官方推荐;若平均图片 5 M 以上、QPS 高,建议用对象存储,CouchDB 只保存元数据+stub,避免节点磁盘 IO 成为瓶颈)