firebase-js-sdk Cloud Storage 上传元数据 `UploadMetadata` 接口完全指南

原创2026-10-15 18:52:511,845 阅读
文章标签:开发工具后端

firebase-js-sdk Cloud Storage 上传元数据 UploadMetadata 接口完全指南

本文档面向使用 Firebase JavaScript SDK(firebase-js-sdk)中 Cloud Storage 模块的开发者,系统讲解上传对象元数据接口 UploadMetadata 的定义、继承关系、每个可写字段的语义、在 uploadBytes / uploadString / uploadBytesResumable 等上传 API 中的用法,并结合仓库源码揭示其底层序列化与 HTTP 请求细节。阅读本文后,你可以在上传文件时精确控制对象在下载端呈现的 HTTP 头、自定义元数据以及完整性校验字段 md5Hash,同时理解这些字段在 SDK 内部如何被映射为 Google Cloud Storage JSON API 的资源字段。

接口定义与核心签名

UploadMetadata 是 firebase-js-sdk 中“上传时可设置的对象元数据”的公开类型,其官方参考文档位于 docs-devsite/storage.uploadmetadata.md,源码定义位于 packages/storage/src/public-types.ts#L165-L174:

/**
 * Object metadata that can be set at upload.
 * @public
 */
export interface UploadMetadata extends SettableMetadata {
  /**
   * A Base64-encoded MD5 hash of the object being uploaded.
   */
  md5Hash?: string | undefined;
}

从签名可以看出两点关键信息:

  1. UploadMetadata 自身只声明了一个属性 md5Hash,其余可设置字段全部通过继承自 SettableMetadata 获得;
  2. 所有属性都是可选的(?),意味着不传任何元数据也能正常上传,SDK 会自动推断必要的默认值(详见下文 metadataForUpload_ 一节)。

继承自 SettableMetadata 的可写字段

UploadMetadata 继承自 SettableMetadata,后者在 packages/storage/src/public-types.ts#L126-L164 中定义,官方参考文档见 docs-devsite/storage.settablemetadata.md。它的名称语义是“可以在任意时刻设置的对象元数据”,这些字段会在对象下载时以 HTTP 响应头的形式生效。

属性 类型 作用(下载时对应的 HTTP 头)
cacheControl string | undefined 作为 Cache-Control 响应头,控制浏览器/CDN 缓存策略
contentDisposition string | undefined 作为 Content-Disposition 响应头,例如 attachment; filename="report.pdf" 强制下载
contentEncoding string | undefined 作为 Content-Encoding 响应头,例如 gzip
contentLanguage string | undefined 作为 Content-Language 响应头,声明对象语言(如 zh-CN)
contentType string | undefined 作为 Content-Type 响应头,例如 image/png、application/json
customMetadata { [key: string]: string } | undefined 用户自定义元数据,键值对均为字符串,可用于业务标记(如 { 'owner': 'team-a' })

这些字段全部可写(writable),SDK 会将其序列化进请求体发送给服务端;同时它们也可读——当通过 getMetadata() 获取对象元数据时,服务端返回的 JSON 资源会被解析回同样的字段(见下文“底层映射机制”)。

独有属性 md5Hash 详解

UploadMetadata 唯一的自有属性是 md5Hash:

md5Hash?: string | undefined;

官方参考文档对其描述为:“A Base64-encoded MD5 hash of the object being uploaded.”,即上传对象的 Base64 编码 MD5 校验值。它的实践要点包括:

  • 格式要求:必须是 Base64 编码后的 MD5 摘要字符串,而不是裸的十六进制字符串。md5Hash 会被 SDK 原样放入请求体(见下文 toResourceString 的映射表),由服务端按 Base64 解码后与对象内容计算出的 MD5 比对;
  • 用途:为上传的对象提供完整性校验,服务端可借此验证上传数据是否在传输中损坏或被篡改;
  • 适用场景:在需要严格校验的场景(如合规要求或关键数据归档)中,可先在客户端计算文件的 MD5(Base64 形式),再随上传元数据一并提交;
  • 与下载的关系:对象上传成功后,服务端生成的资源中同样包含 md5Hash 字段,可通过 getMetadata() 读回,用于校验本地副本一致性。

仓库单元测试 packages/storage/test/unit/requests.test.ts#L105-L129 中构造的服务端资源示例也包含 md5Hash: 'deadbeef' 字段,印证了该字段在服务端与客户端之间双向流转的事实。

在三个上传 API 中的实际用法

UploadMetadata 是以下三个公开上传 API 的元数据参数类型(均定义在 packages/storage/src/api.ts):

// 非断点续传:一次性上传,返回 Promise<UploadResult>
export function uploadBytes(
  ref: StorageReference,
  data: Blob | Uint8Array | ArrayBuffer,
  metadata?: UploadMetadata
): Promise<UploadResult>;

// 上传字符串,可指定编码格式(StringFormat)
export function uploadString(
  ref: StorageReference,
  value: string,
  format?: StringFormat,
  metadata?: UploadMetadata
): Promise<UploadResult>;

// 断点续传上传:可暂停/恢复,并暴露进度回调
export function uploadBytesResumable(
  ref: StorageReference,
  data: Blob | Uint8Array | ArrayBuffer,
  metadata?: UploadMetadata
): UploadTask;

对应的参考位置为 uploadBytes、uploadString、uploadBytesResumable。三者内部都会把传入的 UploadMetadata 断言为内部类型 MetadataInternal 后交给底层请求模块处理。

完整代码示例

以最常见的图片上传并附带自定义元数据为例:

import { getStorage, ref, uploadBytes, uploadString } from 'firebase/storage';

const storage = getStorage();

// 示例 1:uploadBytes 上传二进制数据,同时设置缓存与 Content-Type
const imageRef = ref(storage, 'images/avatar.png');
const bytes = new Uint8Array([/* ...二进制数据... */]);
await uploadBytes(imageRef, bytes, {
  cacheControl: 'public, max-age=31536000',
  contentType: 'image/png',
  customMetadata: {
    'uploadedBy': 'user-1001',
    'env': 'production'
  }
});

// 示例 2:uploadString 上传文本,指定格式与语言
const noteRef = ref(storage, 'notes/hello.txt');
await uploadString(
  noteRef,
  'Hello Firebase Storage!',
  'raw',          // StringFormat:'raw' | 'base64' | 'base64url' | 'data_url'
  { contentLanguage: 'en-US' }
);

断点续传时使用 md5Hash

import { uploadBytesResumable } from 'firebase/storage';

const task = uploadBytesResumable(ref(storage, 'data/archive.bin'), bigFile, {
  contentType: 'application/octet-stream',
  // 假设 md5Base64 已由客户端计算得出(Base64 编码的 MD5)
  md5Hash: md5Base64
});

task.on('state_changed',
  snapshot => console.log('进度:', snapshot.bytesTransferred / snapshot.totalBytes),
  err => console.error('失败:', err),
  () => console.log('上传完成')
);

底层实现:元数据如何映射为 JSON 资源

理解 UploadMetadata 的字段流转,关键在于 packages/storage/src/implementation/metadata.ts 中定义的字段映射表(getMappings(),见 L65-L112):

mappings.push(new Mapping<string>('md5Hash', null, true));
mappings.push(new Mapping<string>('cacheControl', null, true));
mappings.push(new Mapping<string>('contentDisposition', null, true));
mappings.push(new Mapping<string>('contentEncoding', null, true));
mappings.push(new Mapping<string>('contentLanguage', null, true));
mappings.push(new Mapping<string>('contentType', null, true));
mappings.push(new Mapping<string>('metadata', 'customMetadata', true));

Mapping 构造函数的三个参数分别是 server 字段名、local 字段名(可选,缺省时与 server 同名) 和 writable 标志(metadata.ts#L35-L50)。从映射表可以看出:

  • md5Hash、cacheControl、contentType 等字段在客户端与服务端使用同名;
  • 只有 metadata(服务端 JSON 资源中的字段)被映射到本地名称 customMetadata;
  • 全部被标记为 writable = true,这意味着它们会进入 toResourceString() 的序列化范围。

序列化函数 toResourceString(metadata.ts#L191-L206)遍历映射表,仅收集 writable 字段拼成请求体 JSON:

export function toResourceString(
  metadata: Partial<Metadata>,
  mappings: Mappings
): string {
  const resource: { [prop: string]: unknown } = {};
  const len = mappings.length;
  for (let i = 0; i < len; i++) {
    const mapping = mappings[i];
    if (mapping.writable) {
      resource[mapping.server] = metadata[mapping.local];
    }
  }
  return JSON.stringify(resource);
}

该函数被以下请求构造器调用(见 packages/storage/src/implementation/requests.ts):

  • multipartUpload(L321 起):非断点续传上传,X-Goog-Upload-Protocol: multipart;
  • createResumableUpload(L415-L449):断点续传上传的 start 请求,同时发送 X-Goog-Upload-Header-Content-Length、X-Goog-Upload-Header-Content-Type 等头;
  • updateMetadata(L253-L275):更新元数据时以 PATCH 方法提交同样的资源 JSON。

单元测试 packages/storage/test/unit/requests.test.ts#L350-L384 对 updateMetadata 生成的 RequestInfo 断言了 method: 'PATCH'、请求体等于 toResourceString 的 JSON 输出,直接验证了这一序列化链路。

上传前的元数据补全:metadataForUpload_

无论传入哪些 UploadMetadata 字段,底层都会先经过 metadataForUpload_ 补全必要信息:

export function metadataForUpload_(
  location: Location,
  blob: FbsBlob,
  metadata?: Metadata | null
): Metadata {
  const metadataClone = Object.assign({}, metadata);
  metadataClone['fullPath'] = location.path;
  metadataClone['size'] = blob.size();
  if (!metadataClone['contentType']) {
    metadataClone['contentType'] = determineContentType_(null, blob);
  }
  return metadataClone;
}

从中可以推断出三个自动行为:

  1. fullPath 自动填充:上传目标路径由 StorageReference 决定,无需(也不应)手动传入;
  2. size 自动填充:请求体中携带对象字节数;
  3. contentType 智能推断:当你未显式指定 contentType 时,SDK 会通过 determineContentType_ 根据 Blob 类型或文件扩展名推断默认值,兜底为 application/octet-stream(见 requests.ts#L300 附近)。

也就是说,你提供的 UploadMetadata 是“增量配置”,SDK 会在其基础上补齐路径、大小和类型,再交给映射表序列化。

与 FullMetadata 的关系:上传后可读回哪些信息

UploadMetadata 并不是孤立类型,它还作为 FullMetadata 的父接口存在(packages/storage/src/public-types.ts#L176 起):

export interface FullMetadata extends UploadMetadata {
  bucket: string;       // 对象所在存储桶
  fullPath: string;     // 对象完整路径
  generation: string;   // 对象代数号(每次覆盖递增)
  metageneration: string; // 元数据版本号
  // ... 以及 size、timeCreated、updated、downloadTokens 等只读字段
}

这意味着:

  • 上传时:使用 UploadMetadata 提供可写字段;
  • 上传后/读取时:uploadBytes 的返回值 UploadResult 以及 getMetadata() 的返回值类型为 FullMetadata,其中包含 md5Hash、customMetadata 等由你设置过的字段,以及 bucket、generation、size、timeCreated 等只读字段。

内部类型 Metadata 在 packages/storage/src/metadata.ts#L27-L29 中定义,直接继承 FullMetadata 并附加索引签名,承载从服务端 JSON 资源解析出的全部信息。

实践要点与注意事项

结合以上源码分析,使用 UploadMetadata 时建议注意:

  • md5Hash 是 Base64 编码:务必使用 btoa(浏览器)或 Buffer/crypto(Node.js)把二进制 MD5 摘要转换为 Base64 字符串,格式不符将无法通过服务端校验;
  • customMetadata 值必须为字符串:类型定义强制 { [key: string]: string },如需存储数字或布尔值,请先字符串化;
  • HTTP 头字段的生效时机:cacheControl、contentDisposition 等字段影响的是下载时的响应头,上传阶段不产生副作用;
  • 断点续传与普通上传都支持元数据:uploadBytesResumable 的元数据在 createResumableUpload 的 start 请求中以 JSON body 提交(toResourceString 序列化,见 requests.ts#L434),中途 updateMetadata 可通过 PATCH 单独修改可写字段;
  • 无需手写完整元数据:fullPath、size 与默认 contentType 由 metadataForUpload_ 自动补齐,你只需关心与业务相关的字段。

如需继续深入,可阅读仓库中的相关实现与测试:UploadMetadata/SettableMetadata 类型定义、字段映射与序列化、上传/更新请求构造、元数据序列化单元测试。

登录后查看全文
firebase-js-sdk