firebase-js-sdk Cloud Storage 上传元数据 `UploadMetadata` 接口完全指南
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;
}
从签名可以看出两点关键信息:
UploadMetadata自身只声明了一个属性md5Hash,其余可设置字段全部通过继承自SettableMetadata获得;- 所有属性都是可选的(
?),意味着不传任何元数据也能正常上传,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;
}
从中可以推断出三个自动行为:
fullPath自动填充:上传目标路径由StorageReference决定,无需(也不应)手动传入;size自动填充:请求体中携带对象字节数;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 类型定义、字段映射与序列化、上传/更新请求构造、元数据序列化单元测试。