如何在上传时计算 SHA-256 并写入 _attachments?

解读

国内企业在面试 CouchDB 岗位时,常把“附件上传 + 摘要校验”作为数据完整性离线同步能力的综合考点。
面试官真正想确认的是:

  1. 你是否理解 CouchDB 的 multipart/related 上传协议;
  2. 能否在客户端(Node、浏览器、Flutter 等)实时流式计算 SHA-256,避免二次读取;
  3. 能否把摘要写进 attachments 元数据的正确字段,并保证 后续同步不触发哈希冲突
  4. 是否知道 国内等保 2.0 对存储完整性 的合规要求,SHA-256 是最低推荐算法。

知识点

  • _attachments 是文档顶层对象,键为文件名,值必须含 content_typedata(base64)或 follows(multipart 占位)、length 以及可选digest 字段。
  • CouchDB 3.x 之后,如果 digest 存在,服务器会强制校验,格式为 algorithm-hex,例如 sha256-a7c…
  • 浏览器端使用 SubtleCrypto.digest('SHA-256', ArrayBuffer),Node 端使用 crypto.createHash('sha256');都必须把文件读成 ArrayBuffer,避免编码错误。
  • 上传路径有两条:
    1. 普通 JSON PUT:先把文件 base64 编码,计算 SHA-256,把 digest 写进 _attachments.filename.digest
    2. 高性能 multipart:先在 attachments 元数据里写 follows=truedigest,再按顺序输出 boundary,摘要必须在第一个 boundary 之前算完,否则顺序错乱会导致 400 bad_request。
  • 国内移动端场景(如微信小程序)受 包体积限制,通常采用 分片 256 KB + WebWorker 计算 SHA-256,最后把摘要拼到主线程的 _attachments 对象里一次性上传,避免 UI 阻塞。
  • 如果摘要算错,CouchDB 返回 {"error":"attachment_digest_mismatch","reason":"expected sha256-xxx but got sha256-yyy"}国内 CDN 边缘节点缓存此响应 60 s,重试前必须刷新 URL。

答案

以 Node 16+ 为例,演示“单文档 + 单附件”的合规写法(满足等保 2.0 完整性要求):

import { readFile } from 'fs/promises';
import crypto from 'crypto';
import got from 'got'; // 国内镜像源已配置

const filePath = './invoice.pdf';
const docId = 'invoice:202406:001';
const couchUrl = 'http://couch.internal:5984/erp';

// 1. 流式计算 SHA-256
const buffer = await readFile(filePath);
const hash = crypto.createHash('sha256').update(buffer).digest('hex');
const digest = `sha256-${hash}`;

// 2. 构造 _attachments
const attachment = {
  content_type: 'application/pdf',
  data: buffer.toString('base64'),
  length: buffer.length,
  digest          // **关键字段**
};

// 3. 写文档
const doc = {
  _id: docId,
  type: 'invoice',
  date: '2024-06-01',
  _attachments: {
    'invoice.pdf': attachment
  }
};

const resp = await got.put(`${couchUrl}/${docId}`, {
  json: doc,
  username: 'admin',
  password: '***',
  responseType: 'json'
});

console.log('rev:', resp.body.rev);

要点强调

  • digest 必须小写 hex,前缀 sha256- 不能省略;
  • 如果附件 > 4 MB,建议改用 multipart/related 并把 data 换成 follows=true,但摘要仍需预计算
  • 国内私有云往往关闭 _security 默认写权限,需提前给 admininvoice_writer 角色授权,否则返回 401 Unauthorized

拓展思考

  1. 离线优先场景:PouchDB 浏览器端同样支持 digest 字段,但计算 SHA-256 会阻塞主线程;可使用 WebAssembly 版 sha256,再搭配 revpos 字段,确保后续与 CouchDB 双向同步时不会重复上传相同摘要的块。
  2. 国内金融项目:监管要求原文件 + 摘要 + 时间戳一起存证,可把 SHA-256 再做一次 SM3 杂凑,形成双摘要,写入同一附件元数据的 extra_digest 自定义字段,CouchDB 不会校验,但业务层可审计。
  3. 多主复制冲突:若两个数据中心同时上传同名附件但内容不同,CouchDB 以 digest胜出依据;如果摘要相同则自动去重,节省跨机房流量 30% 以上,这对国内带宽成本敏感的企业尤为关键。