02 rusty-cat 实战:MeowClient 配置、任务参数、进度回调与暂停恢复

适合读者:已经知道 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。

这样常用类型都从一个入口拿到,包括:

  • MeowClient
  • MeowConfig
  • UploadPounceBuilder
  • DownloadPounceBuilder
  • FileTransferRecord
  • TransferStatus
  • TaskId
  • BreakpointUpload
  • BreakpointDownload

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) 上传任务多时调大。新手建议从 12 开始。
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) 可选 每个分片失败后的额外重试次数,默认 30 表示不重试。
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 MiB8 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 下载地址。默认要求支持 HEADRange 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 分片失败后的额外重试次数,默认 30 表示不重试。
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 批量写数据库

进程重启后:

  1. 从数据库查未完成任务。
  2. 重新创建 MeowClient
  3. 根据记录重建 UploadPounceBuilderDownloadPounceBuilder
  4. 重新注入对应 provider 协议。
  5. 调用 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.comupload.example.com 都是占位域名,复制运行前必须替换为真实服务。
  • 依赖版本要以 crates.iodocs.rs 的最新稳定版本为准,本文中的 0.2.2 用于匹配当前示例。
  • close().await 必须显式调用,尤其是服务端、桌面应用、测试用例。
  • try_enqueue 不等任务完成,只表示任务已提交成功。
  • enqueue_and_wait 更适合 CLI 或简单流程,但取消和超时要由业务配合处理。
  • 回调里不要做慢操作。
  • chunk_size 不要太小,除非你明确知道服务端承受得住大量请求。
  • 对象存储直连模式不要把 AccessKey Secret 或 Account Key 放进公开客户端。
  • 预签名 URL 和 SAS URL 过期后,需要后端重新签发或由业务重新入队。
  • 本库不内置数据库,恢复能力依赖你自己的业务记录。

本篇把接入层面的参数基本讲完了。下一篇可以继续看内部设计:为什么 rusty-cat 要用 Builder、后台调度器、协议插件、回调记录和无内置数据库设计,以及如何评估它是否值得长期依赖。

相关推荐
wzhao1012 小时前
Relink 0.15.1:一个 no_std 的 ELF 加载器/链接器
linux·rust·gnu
yzwlord4 小时前
【无标题】
linux·运维·rust·ssh
Arman_4 小时前
Rust 客户端安全上传下载阿里云 OSS:rusty-cat 预签名 URL 实战
安全·阿里云·rust·oss断点续传
灵机一物4 小时前
灵机一物AI原生电商小程序、PC端(已上线)-【技术深度解析】Bun 6 天 AI 重写 96 万行代码:从 Zig 迁移 Rust 全流程与行业影响
开发语言·人工智能·rust
Arman_4 小时前
03 rusty-cat 进阶解析:架构设计、云存储接入、安全模型与长期维护评估
css·安全·rust·文件分片上传·文件分片下载
techdashen5 小时前
半小时读懂 Rust:从语法符号到所有权思维
开发语言·rust
星栈5 小时前
Rust 全栈 SSR 用了一年,我踩过的 5 个坑和 3 个真香瞬间
rust·开源·全栈
Rust研习社5 小时前
手把手带你使用 Bacon 高效开发应用
后端·rust·编程语言
wangjialelele6 小时前
HTTP Cookie 和 Session
http·cookie·session