rusty-cat × 阿里云 OSS 预签名实战(后端签 URL 版)
本文是 rusty-cat 分片并发文档 的「云存储实战」系列之一,教你用 预签名(presigned) 的方式做 OSS 分片并发上传与下载:密钥永远留在后端,客户端只拿短期有效的临时 URL。 先读概念请看:《分片并发上传指南》、《分片并发下载指南》。 需要的 feature:
aliyun-oss-presigned。
1. 适用场景与密钥模型
「预签名」的安全模型是:你的应用服务器持有 AccessKey,签发短期有效的 UploadPart / GET URL,客户端用 rusty-cat 只负责对着这些 URL 执行上传 / 下载,从不接触密钥。
🖼️ 【配图占位 1|预签名密钥模型(三方时序)】 建议配图:三列时序图——客户端、你的后端(🔑 持 AccessKey)、OSS。①客户端向后端「要 N 个分片的上传 URL」;②后端用密钥签好 N 个短期
UploadPartURL 返回;③客户端拿着这些 URL 直接并发 PUT 到 OSS。图注:密钥只在后端,客户端只有临时 URL。 临时 ASCII 草图:客户端 ──①请求上传URL──▶ 后端(🔑AccessKey) 客户端 ◀──②返回 N 个短期 UploadPart URL── 后端 客户端 ═══③并发 PUT 分片═══▶ OSS
- ✅ 适合:手机 App、桌面客户端、浏览器等不可信环境(这是推荐的默认方案)。
- 客户端不需要地域 / Bucket / 密钥,只需要后端给的 URL。
2. 加依赖
[dependencies]
rusty-cat = { version = "0.2.4", features = ["aliyun-oss-presigned"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
3. 上传:完整可运行程序
后端为每个分片偏移签好一个预签名 PUT URL(一片一个),客户端用 aliyun::upload_part 构建分片列表:
use std::sync::Arc;
use rusty_cat::aliyun_oss_presigned as aliyun;
use rusty_cat::api::{
MeowClient, MeowConfig, PresignedMultipartUpload, PresignedMultipartUploadPlan,
UploadPounceBuilder,
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let one_mb: u64 = 1024 * 1024;
let total: u64 = 5 * one_mb; // 文件总大小
// 这些预签名 PUT URL 由你的后端签发,每个分片一个
let part_urls: [&str; 5] = [
"https://my-bucket.oss-cn-beijing.aliyuncs.com/obj?partNumber=1&uploadId=...&Signature=...",
"https://my-bucket.oss-cn-beijing.aliyuncs.com/obj?partNumber=2&uploadId=...&Signature=...",
"https://my-bucket.oss-cn-beijing.aliyuncs.com/obj?partNumber=3&uploadId=...&Signature=...",
"https://my-bucket.oss-cn-beijing.aliyuncs.com/obj?partNumber=4&uploadId=...&Signature=...",
"https://my-bucket.oss-cn-beijing.aliyuncs.com/obj?partNumber=5&uploadId=...&Signature=...",
];
// 为每个分片偏移构建一个 PresignedUploadPart:
// upload_part(分片号从1开始, 偏移量, 该片大小, 预签名URL)
let parts = part_urls
.iter()
.enumerate()
.map(|(i, url)| aliyun::upload_part((i + 1) as u64, i as u64 * one_mb, one_mb, *url))
.collect::<Vec<_>>();
// Plan 的 total / chunk 必须和任务一致
let plan = PresignedMultipartUploadPlan::new(total, one_mb, parts);
let protocol = PresignedMultipartUpload::new(plan);
let client = MeowClient::new(MeowConfig::default());
let task = UploadPounceBuilder::new("presigned.bin", "./presigned.bin", one_mb)
.with_url(part_urls[0]) // 用任意一个分片 URL 占位,plan 里已带全部
.with_breakpoint_upload(Arc::new(protocol))
.with_max_parts_in_flight(4) // 4 片并发
.build()?;
client
.enqueue_and_wait(task, |r| println!("{:.1}%", r.progress() * 100.0))
.await?;
client.close().await?;
Ok(())
}
几个关键约束
plan的total/chunk必须等于任务的总大小 / 分片大小,协议会在prepare()(发送任何字节前)检查;- 最后一片允许比
chunk短(文件不是分片整数倍时); PresignedMultipartUploadPlan::validate()会拒绝零长度分片、重复偏移、超出总大小的分片;- 合并通常由你的后端在带外完成(拿到
protocol.uploaded_parts()后调CompleteMultipartUpload)。
🖼️ 【配图占位 2|预签名并发上传数据流】 建议配图:客户端拿着后端签好的 N 个分片 URL,4 条并行箭头直接 PUT 到 OSS;末尾用虚线画「后端带外发起 Complete 合并」。图注:客户端只负责传字节,合并由后端完成。
4. 下载:串行 vs 并发
方式一(串行):预签名 range 下载协议
用 PresignedRangeDownload + PresignedRangeDownloadPlan,走 provider 串行路径。注意这里 with_total_size 是 Plan 上的方法(不是任务构建器的那个):
use std::sync::Arc;
use rusty_cat::api::{
DownloadPounceBuilder, MeowClient, MeowConfig, PresignedRangeDownload,
PresignedRangeDownloadPlan,
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let download_url = "https://my-bucket.oss-cn-beijing.aliyuncs.com/obj?Expires=...&Signature=...";
let total_size: u64 = 5 * 1024 * 1024;
let client = MeowClient::new(MeowConfig::default());
let plan = PresignedRangeDownloadPlan::new(download_url).with_total_size(total_size);
let protocol = PresignedRangeDownload::new(plan);
let task = DownloadPounceBuilder::new("obj.bin", "./downloads/obj.bin", 1024 * 1024, download_url)
.with_breakpoint_download(Arc::new(protocol))
.build();
client.enqueue_and_wait(task, |_r| {}).await?;
client.close().await?;
Ok(())
}
方式二(推荐,可并发):预签名 GET URL + 默认下载器
同一个预签名 GET URL,不挂 provider 协议、直接喂默认下载器,就能并发。这是并发下载 OSS 对象的推荐姿势:
use rusty_cat::api::{DownloadPounceBuilder, MeowClient, MeowConfig};
#[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/obj?Expires=...&Signature=...";
let total_size: u64 = 5 * 1024 * 1024;
let client = MeowClient::new(MeowConfig::default());
let task = DownloadPounceBuilder::new("obj.bin", "./downloads/obj.bin", 1024 * 1024, presigned_get_url)
.with_total_size(total_size) // 任务构建器上的 with_total_size:开启并发的关键
.with_max_parts_in_flight(4) // 4 个 Range GET 并发
.build();
client.enqueue_and_wait(task, |r| println!("{:.1}%", r.progress() * 100.0)).await?;
client.close().await?;
Ok(())
}
⚠️ 两个
with_total_size别搞混:任务构建器DownloadPounceBuilder::with_total_size会开启并发;而PresignedRangeDownloadPlan::with_total_size是给串行协议用的、不开并发。⚠️ 稳定 URL 规则:并发下载对每个分片复用同一个 URL、且没有刷新机制,所以预签名 GET URL 的有效期要覆盖整个下载(含续传)。完整规则见 《分片并发下载指南》。
5. 断点续传
进程重启后重建任务再 try_enqueue。预签名上传续传用 PresignedMultipartUpload::with_resumed_parts(...) 把上次持久化的分片重新注入,prepare 会跳过已验证的最长连续前缀、只重传其余部分。
🖼️ 【配图占位 3|预签名续传注入已传分片】 建议配图:重启后把已持久化的分片记录(offset+size)注入协议,协议校验连续前缀后只重传空洞之后的分片。
6. 进度回调与任务控制(暂停 / 恢复 / 取消)
前面用的 enqueue_and_wait 会一直等到任务结束,适合脚本或一次性任务。做 UI、或要在后台并发跑并随时控制任务时,改用底层的 try_enqueue:它立刻返回 TaskId,让你分别处理进度回调和完成回调。把上文构建好的 task 传进去即可:
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 后就能随时控制任务:
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. 相关阅读
- 《分片并发上传指南》、《分片并发下载指南》 — 通用机制与避坑。
- 《阿里云 OSS 直传直下实战》 — 可信服务器上的 AccessKey 直连方案。
- 文档总索引:doc/README.md。
本文基于
rusty-cat0.2.4,代码片段均经过cargo check --features all编译验证。