向已有文档添加 PNG 附件时 Content-Type 必须为何值?如何生成对应的 _attachments 存根?
解读
面试官问的是“已有文档”,意味着不能走新建文档时一次性 multipart/form-data 的捷径,而是要在文档已存在的前提下,通过一次独立请求把 PNG 附件写进去,同时让 CouchDB 在文档元数据里生成正确的 _attachments 存根。
国内面试里,这道题常被用来区分“只会用 Fauxton 按钮上传”与“真正读过 HTTP API 文档”的候选人。答不到点上,会被追问 412、rev 树冲突、base64 体积膨胀等细节,直接掉分。
知识点
- 独立附件上传的 HTTP 端点格式
PUT /{db}/{docId}/{attachmentName}?rev={currentRev}
请求头必须带Content-Type: image/png;值必须是 RFC 2083 规定的官方 MIME 字符串,不能写image/PNG大写,也不能省掉子类型。 - 成功返回
201时,响应头里会给出新的Etag: "3-xxx",就是后续要用的新 rev。 - 附件存根(stub)是 CouchDB 在文档 JSON 里自动维护的元数据,开发者无需手动计算,只要上传成功,再次
GET /{db}/{docId}就能在_attachments字段里看到:
{ "logo.png": { "content_type": "image/png", "revpos": 3, "length": 12345, "stub": true } }
其中stub:true告诉客户端“真正的二进制在附件存储区,不在 JSON 里”。 - 如果采用“文档覆盖”方式(先把附件 base64 内嵌再
PUT /{db}/{docId}),Content-Type同样要写image/png,但数据字段名是_attachments.{name}.data,且需要先把 PNG 读成 base64 字符串,国内生产环境不推荐,因为体积膨胀 33%+ 且容易触发 413。 - 国内私有云常关掉了
multipart/related优化,因此最稳妥、面试官最认可的答案就是第一条的独立 PUT 上传。
答案
Content-Type 必须写成 image/png(全小写,不可省略)。
生成 _attachments 存根不需要手动计算,只需:
- 先
HEAD /{db}/{docId}拿到当前Etag即rev; - 执行
PUT /{db}/{docId}/avatar.png?rev=2-abc
Content-Type: image/png
body: <原始 PNG 二进制> - 成功后 CouchDB 自动在文档里插入
_attachments.avatar.png存根,下次读取文档即可看到。
拓展思考
面试官可能继续追问:
- 如果两次并发上传不同附件,rev 冲突怎么解决?(提示:先 GET 最新 rev,再重试 PUT,或使用
_bulk_docs带new_edits=false做合并) - 移动端弱网场景下,如何做到“断点续传”?(CouchDB 原生不支持 HTTP RANGE,需要在客户端拆块、自定义 doc 结构,再用 replication 合并)
- 国内项目把图片放 CouchDB 是否划算?(单附件 ≤ 64 M 官方推荐;若平均图片 5 M 以上、QPS 高,建议用对象存储,CouchDB 只保存元数据+stub,避免节点磁盘 IO 成为瓶颈)