上一篇把对象的存储类型显示了出来——你能看到某个对象在标准、低频还是归档层。但只看得见不够,真正省钱要能动手:把冷数据转到归档层,以及在需要时把归档对象取回解冻。这次补上这两个写操作。

  • 转换存储类型:右键对象 →「转换存储类型」→ 按账号所属厂商列出可选层级(标准 / 低频 / 归档 / 深度归档…)→ 一键转换。
  • 取回归档:归档 / 冷归档的对象不能直接下载,得先发一个取回请求、等它解冻。右键 →「取回归档」→ 填保持天数。

两个操作都是写请求、都得跨 7 家云,而且——没法在本地做端到端测试(要连真实云)。所以关键是尽量复用已经验证过的代码路径,而不是新写一堆签名逻辑。

转换:一次"复制到自己"

对象存储没有"改一下这个对象的存储类型"这种直接操作。但它们都支持服务端复制时指定目标存储类型。所以转换的实现是:把对象复制到它自己,带上新的存储类型头和"元数据保持不变"指令:

// s3-core:PUT 到同一个 key,带三个头
amz_headers: &[
    ("x-amz-copy-source", format!("/{bucket}/{key}")),
    ("x-amz-storage-class", class),          // 新存储类型
    ("x-amz-metadata-directive", "COPY"),    // 元数据原样保留
],

这条路复用的正是各 SDK 已经在用的 copy_object 签名逻辑——我只是在被签名的头里多加了两个。阿里云用 x-oss-storage-class、腾讯云 x-cos-storage-class、华为云 x-obs-storage-class,头名不同,但套路一模一样。

取回:POST ?restore,复用分片上传的子资源签名

取回是一个带子资源的 POST:POST /{bucket}/{key}?restore,请求体写明保持天数:

<RestoreRequest><Days>2</Days></RestoreRequest>

难点在签名。?restore 是一个子资源,必须计入签名的规范化资源里,否则签名不过。自己手写子资源签名容易出错,而且没法连真云验证。

但这里有个现成的宝贝:分片上传早就在正确地签子资源了。初始化分片上传是 POST ?uploads、上传分片带 ?partNumber&uploadId——这些子资源 POST 一直跑得好好的。所以我没有另起炉灶,而是直接复用各 SDK 里那个已经验证过的"子资源请求"构造器:

// 阿里云 / 华为:复用 build_part_request,只是把子资源换成 restore
self.build_part_request(bucket, PartRequest {
    method: Method::POST,
    key,
    subresources: &[("restore", None)],   // ← 和 ("uploads", None) 同一条路
    content_type: Some("application/xml"),
    body: Some(restore_body),
    ..
})

restoreuploads 走的是完全相同的签名代码。这样取回的签名正确性,搭的是分片上传那条久经考验的便车,而不是新写的、没法测的逻辑。

对每家云:trait 默认兜底

老规矩,这两个操作是 trait 上的新方法,默认返回"不支持",支持的适配层覆盖并置上 storage_class_ops 能力位。7 家全实现了;以后新接入的云,不实现也只是这两项不可用(安全回退),实现了就自动拥有。

诚实说一句:测试的边界

这次我要坦白测试的局限。转换和取回是写操作,真正的行为(存储类型有没有变、取回有没有发起)只能连真实云验证,没法在 cargo test 里断言。我能测的、也测了的是:trait 默认在未实现时安全报错而非 panic;签名逻辑本身则通过复用各 SDK 已有的、已测的 copy / 分片子资源路径来获得信心——没有新写一行签名代码。

所以这一版:请求构造是复用可信路径搭出来的,端到端行为待真实环境确认。把这条边界讲清楚,比假装"全绿=一定对"更诚实。

小结

存储类型从"看得见"走到了"管得了":转换 = 带存储类型头的自我复制,取回 = 复用分片子资源签名的 POST ?restore,两者都建在各 SDK 已验证的代码路径上,靠 trait 默认覆盖每家云。冷数据转归档省钱,要用时再取回——闭环补上了。