适合读者:已经知道
rusty-cat是做断点上传下载的,准备真正接入项目,需要把参数、回调、错误处理和生命周期搞明白的同学。
1. 接入时先记住一条主线
使用 rusty-cat 的主线非常固定:
text
创建 MeowConfig
-> 创建 MeowClient
-> 用 Builder 创建上传/下载任务
-> try_enqueue 或 enqueue_and_wait 提交任务
-> 通过 FileTransferRecord 观察进度
-> 需要时 pause / resume / cancel
-> 应用退出前 close
这条主线比背 API 更重要。只要理解了它,后面的参数就很好记。
推荐统一使用门面导入:
rust
use rusty_cat::api::*;
版本提示:本文示例以 rusty-cat = "0.2.2" API 为基础。正式接入时,请先到 https://crates.io/crates/rusty-cat 确认最新版本,再到 https://docs.rs/rusty-cat 对照对应版本文档核对 API。
这样常用类型都从一个入口拿到,包括:
MeowClientMeowConfigUploadPounceBuilderDownloadPounceBuilderFileTransferRecordTransferStatusTaskIdBreakpointUploadBreakpointDownload
2. MeowConfig:先控制"全局怎么跑"
MeowConfig 是创建 MeowClient 时传入的全局配置。它创建后是不可变的,也就是说:客户端创建完以后,不会一边跑任务一边被业务代码偷偷改配置。
最简单写法:
rust
let config = MeowConfig::default();
let client = MeowClient::new(config);
自定义写法:
rust
use std::time::Duration;
use rusty_cat::api::MeowConfig;
let config = MeowConfig::builder()
.max_upload_concurrency(2)
.max_download_concurrency(2)
.http_timeout(Duration::from_secs(30))
.tcp_keepalive(Duration::from_secs(60))
.command_queue_capacity(256)
.worker_event_queue_capacity(1024)
.build()?;
2.1 MeowConfig 参数总表
| 参数 | 默认值 | 约束 | 小白解释 |
|---|---|---|---|
max_upload_concurrency |
2 |
>= 1 |
同时运行多少个上传任务组。不是单个分片数量,而是上传任务组并发限制。 |
max_download_concurrency |
2 |
>= 1 |
同时运行多少个下载任务组。 |
breakpoint_download_http.range_accept |
application/octet-stream |
合法 HTTP header 值 | Range 下载时默认使用的 Accept 请求头。普通二进制下载用默认值即可。 |
http_client |
None |
可复用 reqwest::Client |
如果你需要代理、统一 TLS、默认 header、监控埋点,可以注入自定义 HTTP Client。 |
http_timeout |
5s |
必须大于 0 | SDK 内部 HTTP 请求超时时间。公网或大文件建议调大。 |
tcp_keepalive |
30s |
必须大于 0 | TCP keepalive 时间。网络不稳定时可以结合业务情况调整。 |
command_queue_capacity |
128 |
>= 1 |
控制命令队列容量,承载入队、暂停、恢复、取消、快照、关闭等命令。 |
worker_event_queue_capacity |
256 |
>= 1 |
Worker 事件队列容量,承载进度和状态事件。 |
2.2 每个 Builder 方法怎么选?
| 方法 | 什么时候用 |
|---|---|
MeowConfig::builder() |
想自定义配置时使用。 |
max_upload_concurrency(n) |
上传任务多时调大。新手建议从 1 或 2 开始。 |
max_download_concurrency(n) |
下载任务多时调大。注意别把本地磁盘和远程服务打满。 |
http_client(client) |
公司内网代理、统一证书、默认 header、链路追踪等场景。 |
http_timeout(duration) |
单个 HTTP 请求最大等待时间。弱网、跨境链路、大对象存储建议调大。 |
tcp_keepalive(duration) |
保持连接活性,减少长连接异常难以及时发现的问题。 |
command_queue_capacity(n) |
批量提交任务多时调大,避免 try_enqueue 因队列满而快速失败。 |
worker_event_queue_capacity(n) |
进度事件很多、全局监听器多时可以调大。 |
breakpoint_download_http(config) |
下载时需要自定义 Accept 等 Range HTTP 行为时使用。 |
build() |
校验配置并生成 MeowConfig。如果传 0 并发、0 队列、0s 超时会报错。 |
2.3 新手推荐配置
普通桌面工具:
rust
let config = MeowConfig::builder()
.max_upload_concurrency(1)
.max_download_concurrency(2)
.build()?;
服务端批量任务:
rust
use std::time::Duration;
let config = MeowConfig::builder()
.max_upload_concurrency(4)
.max_download_concurrency(4)
.http_timeout(Duration::from_secs(60))
.command_queue_capacity(1024)
.worker_event_queue_capacity(4096)
.build()?;
需要代理或自定义 TLS:
rust
let http = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(60))
.build()?;
let config = MeowConfig::builder()
.http_client(http)
.build()?;
3. MeowClient:SDK 的主入口
MeowClient 负责管理 SDK 的后台调度器。它不是 Clone,如果多线程或多个 async task 都要用同一个客户端,应该包一层 Arc。
rust
use std::sync::Arc;
use rusty_cat::api::{MeowClient, MeowConfig};
let client = Arc::new(MeowClient::new(MeowConfig::default()));
let client2 = Arc::clone(&client);
为什么不直接让 MeowClient 实现 Clone?
因为它内部懒加载一个后台 executor。如果随便 clone,可能导致多个 clone 各自启动独立 executor,任务表、并发限制、关闭逻辑都会乱。用 Arc<MeowClient> 共享同一个 client 是更清晰的所有权设计。
3.1 MeowClient 常用方法
| 方法 | 作用 | 注意点 |
|---|---|---|
MeowClient::new(config) |
创建客户端。 | executor 会在第一次任务操作时懒启动。 |
http_client() |
获取和 SDK 配置一致的 reqwest::Client。 |
如果配置注入过自定义 client,会返回它的 clone。 |
register_global_progress_listener(listener) |
注册全局进度监听器。 | 能看到所有任务进度,适合 UI 汇总、数据库异步持久化。 |
unregister_global_progress_listener(id) |
移除一个全局监听器。 | 找不到时返回 Ok(false),方便清理代码安全调用。 |
clear_global_listener() |
清空所有全局监听器。 | 测试、退出登录、应用关闭前常用。 |
set_debug_log_listener(Some(listener)) |
设置 SDK 调试日志监听器。 | 这是进程级全局监听器,不只是当前 client。 |
set_debug_log_listener(None) |
清理调试日志监听器。 | 测试或关闭前建议清理。 |
try_enqueue(task, progress_cb, complete_cb).await |
提交任务但不等待完成。 | 返回 TaskId,队列满时会快速失败。 |
enqueue_and_wait(task, progress_cb).await |
提交任务并等待终态。 | 小白更容易用,适合 CLI 或简单流程。 |
pause(task_id).await |
暂停任务。 | 需要保存 try_enqueue 返回的 TaskId。 |
resume(task_id).await |
恢复暂停任务。 | 使用同一个 TaskId。 |
cancel(task_id).await |
取消任务。 | 取消是尽力而为,上传协议可能会执行清理逻辑。 |
snapshot().await |
获取队列和运行中任务快照。 | 适合监控面板和排查调度问题。 |
close().await |
关闭客户端和后台 executor。 | 生产代码必须显式调用。 |
is_closed() |
判断是否已经关闭。 | 成功关闭后不能重新打开,只能创建新 client。 |
4. UploadPounceBuilder:上传任务参数详解
上传有两种来源:
- 本地文件路径:
UploadPounceBuilder::new(...) - 内存字节:
UploadPounceBuilder::from_bytes(...)
4.1 文件上传
rust
let task = UploadPounceBuilder::new("demo.bin", "./demo.bin", 1024 * 1024)
.with_url("https://upload.example.com/files")
.with_max_chunk_retries(3)
.build()?;
注意:https://upload.example.com/files 是占位地址,不是可以直接上传的公共接口。复制运行前,你需要替换成自己的上传接口,并确认服务端能够理解 rusty-cat 默认 multipart 分片上传字段,或者通过 with_breakpoint_upload(...) 接入自定义上传协议。
参数解释:
| 参数或方法 | 是否必需 | 说明 |
|---|---|---|
new(file_name, file_path, chunk_size) |
是 | 创建基于本地文件的上传任务。 |
file_name |
是 | 展示名,会出现在回调、日志和默认 multipart 字段里。 |
file_path |
是 | 本地源文件路径。build() 会读取 metadata,文件不存在会报错。 |
chunk_size |
是 | 分片大小,单位字节。0 会自动归一化为 1 MiB。 |
with_url(url) |
通常必需 | 上传地址。普通 HTTP 是接口地址;OSS/Azure 直传通常是对象最终 URL。 |
with_file_path(path) |
可选 | 替换本地文件路径。 |
with_bytes(bytes) |
可选 | 改用内存字节作为上传来源。 |
with_method(method) |
可选 | 设置 HTTP 方法,默认 POST。有些服务端可能要求 PUT。 |
with_headers(headers) |
可选 | 设置请求头,例如 Authorization、业务 header。 |
with_breakpoint_upload(upload) |
可选 | 注入自定义上传协议,例如阿里云 OSS、Azure Blob、预签名上传。 |
with_max_chunk_retries(retries) |
可选 | 每个分片失败后的额外重试次数,默认 3,0 表示不重试。 |
with_max_upload_prepare_retries(retries) |
可选 | 上传 prepare 阶段失败后的额外重试次数,默认 3。 |
build() |
是 | 生成 PounceTask。文件模式下可能返回 std::io::Error。 |
4.2 内存上传
如果文件内容已经在内存里:
rust
let data = b"hello rusty-cat".to_vec();
let task = UploadPounceBuilder::from_bytes("hello.txt", data, 1024)
.with_url("https://upload.example.com/files")
.build()?;
源码里内存上传会把 Vec<u8> 转成 bytes::Bytes。它的好处是:后续分片和重试时 clone 是引用计数增加,不会复制整块大数据。对新手来说,只需要记住:内存上传适合小到中等大小的数据,不建议把超大文件先全部读进内存。
4.3 上传分片大小怎么选?
| 文件大小 | 建议起点 |
|---|---|
| 小于 100 MiB | 1 MiB |
| 100 MiB 到 2 GiB | 4 MiB 或 8 MiB |
| 更大文件 | 结合对象存储限制、网络质量和服务端能力压测 |
分片越小,失败重试越精细,但请求数量越多。分片越大,请求数量少,但失败后重传成本更高。
5. DownloadPounceBuilder:下载任务参数详解
下载任务的基础写法:
rust
let task = DownloadPounceBuilder::new(
"demo.bin",
"./downloads/demo.bin",
1024 * 1024,
"https://example.com/demo.bin",
)
.with_client_file_sign("business-file-id-001")
.with_max_chunk_retries(3)
.build();
注意:https://example.com/demo.bin 是占位下载地址。真实运行时,远程服务最好支持 HEAD 获取文件大小,并支持 GET 请求中的 Range 头;如果不支持,需要使用 provider 插件或自定义 BreakpointDownload。
参数解释:
| 参数或方法 | 是否必需 | 说明 |
|---|---|---|
new(file_name, file_path, chunk_size, url) |
是 | 创建 Range 下载任务。 |
file_name |
是 | 展示名,用在回调和日志中。 |
file_path |
是 | 下载到本地的目标路径。 |
chunk_size |
是 | 每次 Range 请求的分片大小。0 会使用默认 1 MiB。 |
url |
是 | 下载地址。默认要求支持 HEAD 和 Range GET。 |
with_url(url) |
可选 | 替换下载 URL。 |
with_file_path(path) |
可选 | 替换本地保存路径。 |
with_headers(headers) |
可选 | 设置 HEAD 和 Range GET 都会用到的基础请求头。 |
with_client_file_sign(sign) |
可选 | 设置业务侧文件标识,会出现在进度记录里,适合和数据库主键关联。 |
with_breakpoint_download(download) |
可选 | 注入自定义下载协议,例如阿里云、Azure、预签名 Range 下载。 |
with_breakpoint_download_http(config) |
可选 | 覆盖该任务的 Range 下载 HTTP 行为,例如 Accept。 |
with_max_chunk_retries(retries) |
可选 | 每个 Range 分片失败后的额外重试次数,默认 3,0 表示不重试。 |
build() |
是 | 生成 PounceTask。下载构建本身不读远程资源,校验主要发生在入队和运行时。 |
下载的 HTTP 方法不能通过 Builder 改。这是设计选择:断点下载依赖标准 HEAD 获取大小、GET + Range 获取分片。如果你的网关必须用特殊方法,应该实现 BreakpointDownload,而不是强行改 Builder。
6. 进度回调与全局监听器
每个任务都有自己的进度回调:
rust
let task_id = client
.try_enqueue(
task,
|record| {
println!(
"task={} file={} progress={:.2}% status={:?}",
record.task_id(),
record.file_name(),
record.progress() * 100.0,
record.status(),
);
},
|task_id, payload| {
println!("task {task_id} complete, payload={payload:?}");
},
)
.await?;
全局监听器可以观察所有任务:
rust
let listener_id = client.register_global_progress_listener(|record| {
println!(
"[global] task={} progress={:.2}%",
record.task_id(),
record.progress() * 100.0
);
})?;
client.unregister_global_progress_listener(listener_id)?;
什么时候用任务回调?
- 更新单个任务 UI。
- 判断某个任务失败或完成。
- 和任务提交代码放在一起,更好理解。
什么时候用全局监听器?
- 整体传输面板。
- 统一日志。
- 统一持久化队列。
- 服务端监控。
重要建议:回调要快。不要在回调里进行慢 SQL、慢网络请求或大量日志格式化。可以把 FileTransferRecord 发送到业务自己的 channel 里异步处理。
7. 暂停、恢复、取消怎么写?
try_enqueue 会返回 TaskId:
rust
let task_id = client.try_enqueue(task, |_| {}, |_, _| {}).await?;
暂停:
rust
client.pause(task_id).await?;
恢复:
rust
client.resume(task_id).await?;
取消:
rust
client.cancel(task_id).await?;
这里要注意:
- 暂停后,任务仍然是同一个
TaskId。 - 取消通常被视为终态。如果用户想重新传,建议创建新的任务。
- 上传取消时,自定义上传协议可能会调用
abort_upload做服务端清理。 - 应用关闭时,应该调用
close().await,不要只依赖 Rust 的Drop。
8. snapshot() 用来排查什么?
rust
let snapshot = client.snapshot().await?;
println!(
"queued={}, active={}",
snapshot.queued_groups,
snapshot.active_groups
);
它适合:
- 看队列里还有多少任务。
- 看当前活跃任务数。
- 调试并发配置是否生效。
- 服务端健康检查或管理后台展示。
如果你发现任务"像是没动",可以先看 snapshot,再看 debug log listener。
9. Debug Log Listener
调试日志监听器是进程级的:
rust
use std::sync::Arc;
use rusty_cat::api::{Log, MeowClient, MeowConfig};
let client = MeowClient::new(MeowConfig::default());
client.set_debug_log_listener(Some(Arc::new(|log: Log| {
println!("[rusty-cat] {log}");
})))?;
// 不需要时清理
client.set_debug_log_listener(None)?;
它适合开发、测试和排障。生产环境要注意日志里不要打印云存储密钥、预签名 URL、SAS token 等敏感信息。
10. 云存储 feature 怎么选?
rusty-cat 默认不启用云厂商 feature。你需要按需打开。
| Feature | 用途 | 适合场景 |
|---|---|---|
aliyun-oss-direct |
阿里云 OSS AccessKey 直连上传下载。 | 后端服务、内部 CLI、可信 Worker。 |
aliyun-oss-presigned |
阿里云 OSS 预签名 URL 上传下载。 | 桌面端、移动端等不应持有 AccessKey 的客户端。 |
azure-blob-direct |
Azure Blob Shared Key 直连上传下载。 | 后端服务或可信环境。 |
azure-blob-sas |
Azure Blob SAS URL 上传下载。 | 用户侧客户端或需要短期授权的场景。 |
presigned |
通用预签名上传/Range 下载基础能力。 | 自定义预签名方案。 |
all |
启用全部 provider。 | 示例、集成测试,不建议生产最小依赖使用。 |
阿里云直连:
toml
rusty-cat = { version = "0.2.2", features = ["aliyun-oss-direct"] }
Azure SAS:
toml
rusty-cat = { version = "0.2.2", features = ["azure-blob-sas"] }
上面两个依赖片段中的版本号用于匹配本文示例。实际项目建议把 version 改成 crates.io 当前最新稳定版本;如果升级后 API 有变化,以对应版本 docs.rs 为准。
安全原则非常简单:
- 服务端、内部工具、可信进程:可以考虑 direct。
- 桌面端、移动端、用户机器:优先考虑 presigned/SAS。
- SDK 不会替你存密钥,你要自己管理密钥加载、轮换、最小权限和日志脱敏。
11. 自己接数据库:推荐做法
rusty-cat 没有内置数据库,这是设计选择。接入项目时,你可以建一张自己的传输表:
| 字段 | 示例 |
|---|---|
id |
业务传输 ID |
file_name |
demo.bin |
local_path |
./downloads/demo.bin |
remote_url_or_object_key |
URL 或对象 key |
direction |
upload / download |
chunk_size |
1048576 |
provider |
http / aliyun / azure |
status |
pending / running / complete |
progress |
0.73 |
credential_ref |
指向密钥管理系统的引用,不建议存原始密钥 |
进度回调里不要直接慢写库,推荐:
text
progress callback
-> 发送 FileTransferRecord 到业务 channel
-> 后台 worker 批量写数据库
进程重启后:
- 从数据库查未完成任务。
- 重新创建
MeowClient。 - 根据记录重建
UploadPounceBuilder或DownloadPounceBuilder。 - 重新注入对应 provider 协议。
- 调用
try_enqueue(...)。
12. 一份更完整的接入模板
rust
use std::time::Duration;
use rusty_cat::api::{
DownloadPounceBuilder, FileTransferRecord, MeowClient, MeowConfig, TransferStatus,
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let config = MeowConfig::builder()
.max_download_concurrency(2)
.http_timeout(Duration::from_secs(30))
.command_queue_capacity(256)
.worker_event_queue_capacity(1024)
.build()?;
let client = MeowClient::new(config);
let global_id = client.register_global_progress_listener(|record| {
println!(
"[global] task={} status={:?} progress={:.2}%",
record.task_id(),
record.status(),
record.progress() * 100.0,
);
})?;
let task = DownloadPounceBuilder::new(
"demo.bin",
"./downloads/demo.bin",
1024 * 1024,
"https://example.com/demo.bin",
)
.with_client_file_sign("biz-file-001")
.with_max_chunk_retries(3)
.build();
let task_id = client
.try_enqueue(
task,
|record: FileTransferRecord| {
match record.status() {
TransferStatus::Failed(err) => {
eprintln!("任务失败:{err}");
}
TransferStatus::Canceled => {
eprintln!("任务被取消");
}
_ => {
println!("进度:{:.2}%", record.progress() * 100.0);
}
}
},
|task_id, payload| {
println!("任务 {task_id} 完成,payload={payload:?}");
},
)
.await?;
println!("已提交任务:{task_id}");
let snap = client.snapshot().await?;
println!("queued={}, active={}", snap.queued_groups, snap.active_groups);
client.unregister_global_progress_listener(global_id)?;
client.close().await?;
Ok(())
}
这份模板里的下载地址仍然是示例占位符。要让代码真实运行,请替换成你自己的可 Range 下载文件地址,并确保 ./downloads 目录存在或由业务代码提前创建。
13. 实战避坑清单
- 文章中的
example.com、upload.example.com都是占位域名,复制运行前必须替换为真实服务。 - 依赖版本要以 crates.io 和 docs.rs 的最新稳定版本为准,本文中的
0.2.2用于匹配当前示例。 close().await必须显式调用,尤其是服务端、桌面应用、测试用例。try_enqueue不等任务完成,只表示任务已提交成功。enqueue_and_wait更适合 CLI 或简单流程,但取消和超时要由业务配合处理。- 回调里不要做慢操作。
chunk_size不要太小,除非你明确知道服务端承受得住大量请求。- 对象存储直连模式不要把 AccessKey Secret 或 Account Key 放进公开客户端。
- 预签名 URL 和 SAS URL 过期后,需要后端重新签发或由业务重新入队。
- 本库不内置数据库,恢复能力依赖你自己的业务记录。
本篇把接入层面的参数基本讲完了。下一篇可以继续看内部设计:为什么 rusty-cat 要用 Builder、后台调度器、协议插件、回调记录和无内置数据库设计,以及如何评估它是否值得长期依赖。