rusty-cat × 阿里云 OSS 直传直下实战

rusty-cat × 阿里云 OSS 直传直下实战(AccessKey 版)

本文是 rusty-cat 分片并发文档 的「云存储实战」系列之一,教你用 AccessKey 直连 的方式,把大文件分片并发上传到阿里云 OSS、并从 OSS 下载。 先读概念请看:《分片并发上传指南》《分片并发下载指南》。 需要的 feature:aliyun-oss-direct


1. 适用场景与密钥模型(先想清楚安全)

「直传(direct)」= 客户端进程直接持有 OSS 的 AccessKeyId / AccessKeySecret,自己在本地做 OSS V4 签名。

🖼️ 【配图占位 1|direct 密钥模型】 建议配图:一台「可信服务器」图标里画一把钥匙(AccessKey),箭头直接连到 OSS;旁注「密钥在客户端」。与之对照可再画一个打叉的「用户手机/浏览器」,强调这类不可信环境不要用 direct。 临时 ASCII 草图:

css 复制代码
[可信服务器 🔑AccessKey] ──直接签名请求──▶ [阿里云 OSS]
[用户手机/浏览器 ❌]     不要把 AccessKey 放这里 → 请改用「预签名」
  • 适合 :程序跑在你完全掌控的可信服务器 / 后端上。
  • 不适合 :手机 App、桌面客户端、浏览器 WASM 等不可信环境------那些场景请改用 《阿里云 OSS 预签名实战》,密钥永远留在后端。

2. 加依赖

toml 复制代码
[dependencies]
rusty-cat = { version = "0.2.4", features = ["aliyun-oss-direct"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

3. 上传:完整可运行程序

把顶部常量换成你自己的,cargo run 即可。with_max_parts_in_flight(4) 就是「单文件 4 个分片并发上传」的开关。

rust 复制代码
use std::sync::Arc;

use rusty_cat::aliyun_oss_direct::AliOssDirectUpload;
use rusty_cat::api::{FileTransferRecord, MeowClient, MeowConfig, UploadPounceBuilder};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let client = MeowClient::new(MeowConfig::default());

    // Bucket、AccessKeyId、AccessKeySecret、地域
    let protocol = AliOssDirectUpload::new("my-bucket", "AK_ID", "AK_SECRET", "cn-beijing");

    let task = UploadPounceBuilder::new(
        "big-object.bin",   // 展示用文件名
        "./big-object.bin", // 本地文件路径
        1024 * 1024,        // 分片大小 = 1 MiB
    )
    .with_url("https://my-bucket.oss-cn-beijing.aliyuncs.com/test/big-object.bin")
    .with_breakpoint_upload(Arc::new(protocol)) // 协议对象必须放进 Arc
    .with_max_parts_in_flight(4)                // 单文件最多 4 片并发
    .with_max_chunk_retries(3)                  // 每片失败最多再重试 3 次
    .build()?;

    let outcome = client
        .enqueue_and_wait(task, |r: FileTransferRecord| {
            println!("上传进度 {:.1}%  状态 {:?}", r.progress() * 100.0, r.status());
        })
        .await?;

    println!("上传完成!服务端返回={:?}", outcome.payload);
    client.close().await?; // 必须调用
    Ok(())
}

内部发生了什么

SDK 会自动帮你走完整个 OSS Multipart 流程:

🖼️ 【配图占位 2|OSS 并发分片上传数据流】 建议配图:客户端先向 OSS 发一次 InitiateMultipartUpload 打开会话;随后 4 条并行 箭头表示同时在发的 UploadPart(分片 1~4,每片的分片号 = 偏移 / 分片大小 + 1);全部到达后,客户端发一次 CompleteMultipartUpload(带 x-oss-complete-all: yes)让 OSS 按序合并。图注强调:Complete 只执行一次,且必须在所有分片落地、拼成连续前缀之后。 临时 ASCII 草图:

python 复制代码
客户端 ──InitiateMultipartUpload──▶ OSS(打开会话,拿到 UploadId)
       ═══UploadPart #1═══▶ ┐
       ═══UploadPart #2═══▶ ├─ 最多 4 片并行
       ═══UploadPart #3═══▶ │
       ═══UploadPart #4═══▶ ┘
       ──CompleteMultipartUpload(x-oss-complete-all)──▶ OSS 按序合并 ✅
  • 每片的分片号由偏移量推导,重传幂等,所以乱序 / 并发是安全的;
  • OSS 最多 10000 个分片,SDK 会提前校验;
  • 分片大小建议 1--8 MiB ,峰值内存 ≈ n × 分片大小(这里 4 × 1 MiB = 4 MiB)。

4. 下载:两种方式

方式一(串行):用 direct 下载协议

挂上 AliOssDirectDownload,SDK 用 AccessKey 签名去拉对象。注意 direct 下载协议只能串行with_max_parts_in_flight 对它无效)。

rust 复制代码
use std::sync::Arc;

use rusty_cat::aliyun_oss_direct::AliOssDirectDownload;
use rusty_cat::api::{DownloadPounceBuilder, MeowClient, MeowConfig};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let client = MeowClient::new(MeowConfig::default());

    let protocol = AliOssDirectDownload::new("my-bucket", "AK_ID", "AK_SECRET", "cn-beijing");

    let task = DownloadPounceBuilder::new(
        "obj.bin",
        "./downloads/obj.bin", // 确保目录已存在
        1024 * 1024,
        "https://my-bucket.oss-cn-beijing.aliyuncs.com/test/obj.bin",
    )
    .with_breakpoint_download(Arc::new(protocol)) // 挂 provider 协议 = 串行
    .build();

    client.enqueue_and_wait(task, |_r| {}).await?;
    client.close().await?;
    Ok(())
}

方式二(推荐,可并发):签名 GET URL + 默认下载器

想要并发 下载 OSS 对象,就不要 挂 provider 下载协议,而是让后端签一个预签名 GET URL,直接喂给内置的默认下载器:

rust 复制代码
use rusty_cat::api::{DownloadPounceBuilder, MeowClient, MeowConfig};

// presigned_get_url:后端签发的 OSS 预签名 GET URL
// total_size:文件总字节数(从你的元数据接口拿)
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let presigned_get_url = "https://my-bucket.oss-cn-beijing.aliyuncs.com/test/obj.bin?Expires=...&Signature=...";
    let total_size: u64 = 20 * 1024 * 1024;

    let client = MeowClient::new(MeowConfig::default());
    let task = DownloadPounceBuilder::new("obj.bin", "./downloads/obj.bin", 2 * 1024 * 1024, presigned_get_url)
        .with_total_size(total_size)   // 告诉 SDK 总大小,跳过 HEAD
        .with_max_parts_in_flight(4)   // 4 个 Range GET 并发
        // 不要 with_breakpoint_download(...),保持默认 StandardRangeDownload
        .build();

    client.enqueue_and_wait(task, |r| println!("{:.1}%", r.progress() * 100.0)).await?;
    client.close().await?;
    Ok(())
}

这就是「上传下载不对称」的关键点:上传要挂 provider 协议才并发,下载要不挂 provider 协议才并发 。并发下载的完整规则(稳定 URL、.rcdl 续传、InvalidRange 等)见 《分片并发下载指南》第 5~8 节。签 URL 的细节见 《阿里云 OSS 预签名实战》


5. 断点续传与取消

  • 续传 :进程重启后重建同一个上传任务再 try_enqueue。direct 上传续传时,SDK 通过 ListMultipartUploads 接管已有的 multipart 会话,并校验每个预期分片都在远端。建议持久化 provider 的 UploadIdAliOssDirectUpload::current_upload_id()),方便带外中止孤儿会话。
  • 给 Bucket 配一条 AbortMultipartUpload 生命周期规则:让废弃的 multipart 会话自动清理,避免未提交分片一直占存储、计费。
  • 取消上传 会触发 AbortMultipartUpload,让未提交分片停止占用存储。取消是尽力而为、且不可逆。

🖼️ 【配图占位 3|续传接管会话】 建议配图:时间轴上「第一次运行」传了分片 12 后进程被杀;「第二次运行」通过 ListMultipartUploads 找回同一个 UploadId,只重传分片 3N。图注:已验证的连续前缀不重传。


6. 进度回调与任务控制(暂停 / 恢复 / 取消)

前面用的 enqueue_and_wait 会一直等到任务结束,适合脚本或一次性任务。做 UI、或要在后台并发跑并随时控制任务时,改用底层的 try_enqueue:它立刻返回 TaskId,让你分别处理进度回调和完成回调。把上文构建好的 task 传进去即可:

rust 复制代码
use rusty_cat::api::{
    FileTransferRecord, MeowClient, MeowError, PounceTask, TaskId, TransferStatus,
};

// task 按上文「上传」或「下载」小节构建好后传进来
async fn enqueue_with_control(
    client: &MeowClient,
    task: PounceTask,
) -> Result<TaskId, MeowError> {
    let task_id = client
        .try_enqueue(
            task,
            // 进度回调:每次进度更新都会调用
            move |record: FileTransferRecord| match record.status() {
                TransferStatus::Failed(err) => eprintln!("失败:{err}"),
                TransferStatus::Canceled => println!("已取消"),
                _ => println!("进度 {:.1}%", record.progress() * 100.0),
            },
            // 完成回调:成功到达 Complete 时调用一次
            move |task_id, payload| println!("任务 {task_id} 完成 {payload:?}"),
        )
        .await?;
    Ok(task_id)
}

拿到 TaskId 后就能随时控制任务:

rust,ignore 复制代码
client.pause(task_id).await?;   // 暂停
client.resume(task_id).await?;  // 从断点继续(TaskId 不变)
client.cancel(task_id).await?;  // 取消(取消上传会触发 OSS 的 AbortMultipartUpload 清理未提交分片)

record.status() 返回 &TransferStatus,取值:Pending / Transmission / Paused / Complete / Failed(err) / Canceled;进度是 0.0~1.0 的比例(f32)。完整语义见《分片并发上传指南》第 8 节《分片并发下载指南》第 9 节


7. 相关阅读

本文基于 rusty-cat 0.2.4 ,代码片段均经过 cargo check --features all 编译验证。

相关推荐
临风细雨3 小时前
从 Bun 的 Rust 重写,看 C# 如何重建 AI 基础设施层
人工智能·rust·c#
kyriewen3 小时前
我用 AI 写代码快了 3 倍——然后同事的 Code Review 全变成了我的
前端·程序员·ai编程
wangruofeng3 小时前
一个 5KB 的 skill 文件,三个月砍下 9 万 star,省掉 65% 的 AI 输出 token
aigc·agent·ai编程
x-cmd3 小时前
Linus 9 年前说「模糊测试有效」——今天已成 OSS-Fuzz 时代铁律
linux·ai·rust·开源·自动化·agent·安全性测试
aqi003 小时前
15天学会AI应用开发(十三)上下文与RAG的阶段性总结
人工智能·python·大模型·ai编程·ai应用
刀法如飞4 小时前
Spring Boot 4.1 + JDK 25 DDD开源脚手架,面向 AI 编程
设计模式·架构·ai编程
CodeForWater4 小时前
RUST设置临时环境变量在PowerShell
rust
Tyler_15 小时前
iOS PlayWright mcp server
前端·ios·ai编程