鸿蒙端云一体:手写 OBS Header 签名,做一个个人云相册 App

举报
yd_288537773 发表于 2026/11/08 08:58:42 2026/11/08
【摘要】 用 ArkTS 手写华为云 OBS 的 Header 签名,从零做一个个人云相册:HMAC-SHA1 两轮签名、逐文件串行上传、内容寻址对象键实现幂等,附踩坑清单。

鸿蒙端云一体:手写 OBS Header 签名,做一个个人云相册 App

手机相册越攒越满,云备份又贵又不放心。这个案例用 HarmonyOS 的 ArkTS + 华为云 OBS,从零做一个自己的云相册:本地选图 → 逐文件上传 → 云端列表预览,全程只用对象存储,不写一行后端服务。

关键难点只有一个:OBS 的 Header 签名。官方 SDK 在鸿蒙侧不一定顺手,手写一遍反而更清楚。这篇把签名算法、逐文件上传、幂等与断点三件事拆开讲。

一、为什么选 OBS 直传,而不是自建后端

传统做法是端侧传后端、后端再转 OBS。多一层服务意味着多一台 ECS、多一份鉴权代码、多一个故障点。

OBS 支持客户端直接签名访问:端侧持有 AK/SK,对每个请求现算一个 Authorization 头,直接打对象存储的 REST 接口。省掉后端,代价是 AK/SK 落在端侧,所以这个方案只适合个人自用 App,不适合分发给他人——真要分发,必须换成 STS 临时凭证或后端代理。

这点先说清楚,因为它决定了整个架构。

二、OBS Header 签名到底怎么算

OBS 的签名走的是 HMAC-SHA1,构造一条规范化字符串(CanonicalizedResource),再用 SK 做两次 HMAC,最后 Base64。四步:

1. 拼 CanonicalizedResource

格式是 /{bucket}/{objectKey},如果请求带子资源(如 ?acl、?uploads)还要按字典序追加。上传单个对象时最简单:

/photo-album-bucket/photo_backup/v2/2026/11/xxx.jpg

2. 拼 StringToSign

Verb + 换行 + Content-MD5 + 换行 + Content-Type + 换行 + Date + 换行 + CanonicalizedResource

Date 必须是 RFC 1123 格式的 GMT 时间,和服务器时间偏差超过 15 分钟会直接 403——这是新手最常踩的坑,鸿蒙设备时间不准就全军覆没。

3. HMAC-SHA1 两轮

Signature = Base64( HMAC-SHA1( SK, HMAC-SHA1( SK, StringToSign ) ) )

注意是两轮,且两轮都用 SK 做密钥,不是先 SK 后 AK。

4. 组 Authorization 头

Authorization: OBS {AK}:{Signature}

三、ArkTS 里怎么算 HMAC-SHA1

鸿蒙的 CryptoArchitectureKit 提供 cryptoFramework.createMac(‘SHA1’)。核心片段:

import { cryptoFramework } from '@kit.CryptoArchitectureKit';
import { util } from '@kit.ArkTS';

async function hmacSha1(key: string, data: string): Promise<Uint8Array> {
  const mac = cryptoFramework.createMac('SHA1');
  const enc = new util.TextEncoder();
  await mac.init({ algName: 'HMAC', key: { data: enc.encodeInto(key) } as cryptoFramework.SymKey });
  const out = await mac.doFinal({ data: enc.encodeInto(data) });
  return out.data;
}

async function obsSignature(sk: string, stringToSign: string): Promise<string> {
  const first = await hmacSha1(sk, stringToSign);
  const mac2 = cryptoFramework.createMac('SHA1');
  const enc = new util.TextEncoder();
  await mac2.init({ algName: 'HMAC', key: { data: enc.encodeInto(sk) } as cryptoFramework.SymKey });
  const second = await mac2.doFinal({ data: first });
  return new util.Base64Helper().encodeToStringSync(second.data);
}

Base64 用 util.Base64Helper,不要自己手写查表。

四、逐文件上传:为什么不做批量

相册备份动辄几百个文件,一口气 Promise.all 会把内存和网络同时打满,且任何一个失败都难定位。改成串行队列加单文件进度:

for (const file of pendingFiles) {
  if (cancelled) break;
  const md5 = await calcFileMd5(file.uri);
  const key = buildObjectKey(file, md5);
  await putObject(bucket, key, file, md5);
  await recordDone(key, md5);
}

好处很直接:进度条是真实的,取消能立刻停,失败能精确知道是哪个文件。

五、对象键设计:让重复备份自动变幂等

如果键直接用文件名,同名照片会互相覆盖;如果用时间戳,同一张图每次备份都会重传。这里的做法是内容寻址:把本地 URI、文件大小、修改时间、MD5 一起参与生成键,前缀固定 photo_backup/v2/:

function buildObjectKey(f: MediaFile, md5: string): string {
  const seed = f.uri + '|' + f.size + '|' + f.mtime + '|' + md5;
  return 'photo_backup/v2/' + sha256Hex(seed).slice(0, 32) + extOf(f.uri);
}

同一张照片内容不变,键就不变,重复备份时先查本地记录,命中就直接跳过。MD5 同时塞进 Content-MD5 头,OBS 服务端会校验,传坏了立刻报错而不是留个坏对象。

需要说明的是:取消的当前文件要整文件重传,OBS 单次 PUT 不支持字节级断点续传;想要断点得走分段上传(Multipart Upload),那是另一个量级的复杂度,个人相册场景不必上。

六、云端预览:签名 GET 拿临时可访问链接

私有桶的对象不能直接给 Image 组件用,每个 GET 也要签名。做法是给预览单独算一个带 Expires 的链接:

GET /{bucket}/{key}?Expires={unix秒}

这时 StringToSign 里把 Expires 放在 Date 的位置。这样生成的链接在一段时间内有效,可以直接丢给图片组件加载,不用每次渲染都重新签。

七、踩过的坑

  1. 时间偏差:设备时间不准导致签名 403。上传前先拿一次服务端时间校准。
  2. Content-Type 不一致:签名时用了 image/jpeg,实际发请求却是 application/octet-stream,直接 403。签名用的头必须和真实请求完全一致。
  3. 对象键里的中文:URI 编码要在签名前统一做一次,签名后不要再编一次,否则重复编码。
  4. 并发太高:鸿蒙端并发 4 到 6 路比较稳,再多容易触发限流。

八、这个案例还能往哪扩

  • 分段上传:超过 64 MiB 的视频走 Multipart,支持真正断点续传
  • 时间轴视图:按对象最后修改时间分组,注意时区统一用 UTC 存、本地展示
  • 跨设备恢复:云端 GET 拉回本地相册,重名不覆盖
  • 文档归档:用系统文件选择器把 PDF、TXT 也纳入备份

小结

这个案例的技术含量不在 UI,而在把云厂商的签名协议手写对。一旦签名跑通,端侧直连云存储就是一条极短的通路:没有后端、没有中转、没有额外费用。

对鸿蒙开发者来说,这也是个不错的练手项目——ArkTS 的异步、文件 API、加密框架、网络请求全都会用到,而且成果是能天天用的东西。

案例仓库在鸿蒙开发实践中心可以找到,配套的建桶、取访问密钥、客户端配置步骤都在仓库 README 里。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。