10 个版本
0.2.4 | 2023年12月26日 |
---|---|
0.2.3 | 2023年5月23日 |
0.2.2 | 2022年12月22日 |
0.2.1 | 2022年11月1日 |
0.1.2 | 2022年6月9日 |
#77 in HTTP 客户端
每月下载量 219
在 16 个 Crates (9 个直接) 中使用
19KB
427 行
Qiniu 资源存储 SDK for Rust
概要
Qiniu Rust SDK 包含以下特性:
- 通过提供多个不同的 Crate,为不同层次的开发都提供了方便易用的编程接口。
- 同时提供阻塞 IO 接口和基于 Async/Await 的异步 IO 接口。
- 提供大量的可供二次开发的 Trait,方便灵活定制,例如 HTTP 客户端提供了
ureq
,reqwest
和isahc
三种不同的库实现,也可以基于qiniu-http
自行定制开发接入其他 HTTP 客户端实现;又例如 DNS 客户端提供了libc
,c-ares
,trust-dns
三种不同的库实现,也可以基于 Resolver 自行定制开发接入其他 DNS 客户端实现。
安装
Qiniu Rust SDK 包含以下 Crates
Crates 链接 | 文档 | 描述 |
---|---|---|
Etag 算法库,实现七牛 Etag 算法 | ||
七牛认证库,实现七牛认证接口以及签名相关算法 | ||
七牛上传凭证,实现七牛上传策略和上传凭证接口以及相关算法 | ||
七牛客户端 HTTP 接口,为不同的 HTTP 客户端实现提供相同的基础接口 | ||
基于 Ureq 库实现七牛客户端 HTTP 接口,对于使用 qiniu-http-request ,qiniu-apis ,qiniu-objects-manager 或 qiniu-upload-manager 的用户,可以直接启用 ureq 功能,将默认使用该 HTTP 客户端实现。需要注意的是,如果需要使用异步接口,则不能选择 ureq |
||
基于 Isahc 库实现七牛客户端 HTTP 接口,对于使用 qiniu-http-request ,qiniu-apis ,qiniu-objects-manager 或 qiniu-upload-manager 的用户,可以直接启用 isahc 功能,将默认使用该 HTTP 客户端实现 |
||
基于 Reqwest 库实现七牛客户端 HTTP 接口,对于使用 qiniu-http-request ,qiniu-apis ,qiniu-objects-manager 或 qiniu-upload-manager 的用户,可以直接启用 reqwest 功能,将默认使用该 HTTP 客户端实现 |
||
基于 qiniu-http 提供具有重试功能的 HTTP 客户端 | ||
实现七牛 API 调用客户端接口 | ||
实现七牛对象相关管理接口,包含对象的列举和操作 | ||
实现七牛对象上传功能 | ||
实现七牛对象下载功能 | ||
七牛 SDK 入口 |
代码示例
以下所有代码示例都以 qiniu-sdk
作为入口。
客户端上传凭证(需要依赖 qiniu-sdk
启用 upload-token
功能)
客户端(移动端或 Web 端)上传文件时,需要从客户自己的业务服务器获取上传凭证,而这些上传凭证是通过服务端的 SDK 生成的,然后通过客户自己的业务 API 分发给客户端使用。根据上传的业务需求不同,七牛云 Rust SDK 支持丰富的上传凭证生成方式。
简单上传凭证
最简单的上传凭证只需要 访问密钥
,密钥
和 存储桶
就可以。
use qiniu_sdk::upload_token::{UploadPolicy, credential::Credential, prelude::*};
use std::time::Duration;
let access_key = "access key";
let secret_key = "secret key";
let bucket_name = "bucket name";
let credential = Credential::new(access_key, secret_key);
let upload_token = UploadPolicy::new_for_bucket(bucket_name, Duration::from_secs(3600))
.build_token(credential, Default::default())?;
println!("{}", upload_token);
覆盖上传凭证
覆盖上传除了需要简单上传所需的信息之外,还需要指定要覆盖的对象名称 对象名称
,这个对象名称同时是客户端上传代码中指定的对象名称,两者必须一致。
use qiniu_sdk::upload_token::{UploadPolicy, credential::Credential, prelude::*};
use std::time::Duration;
let access_key = "access key";
let secret_key = "secret key";
let bucket_name = "bucket name";
let object_name = "object name";
let credential = Credential::new(access_key, secret_key);
let upload_token = UploadPolicy::new_for_object(bucket_name, object_name, Duration::from_secs(3600))
.build_token(credential, Default::default())?;
println!("{}", upload_token);
自定义上传回复凭证
默认情况下,文件上传到七牛后,在没有设置 return_body
或回调相关参数的情况下,七牛返回给上传端的回复格式为 hash
和 key
,例如:
{"hash":"Ftgm-CkWePC9fzMBTRNmPMhGBcSV","key":"qiniu.jpg"}
有时我们希望能自定义这个返回的 JSON 格式的内容,可以通过设置 return_body
参数来实现,在 return_body
中,我们可以使用七牛支持的魔法变量和自定义变量。
use qiniu_sdk::upload_token::{UploadPolicy, credential::Credential, prelude::*};
use std::time::Duration;
let access_key = "access key";
let secret_key = "secret key";
let bucket_name = "bucket name";
let object_name = "object name";
let credential = Credential::new(access_key, secret_key);
let upload_token = UploadPolicy::new_for_object(bucket_name, object_name, Duration::from_secs(3600))
.return_body("{\"key\":\"$(key)\",\"hash\":\"$(etag)\",\"bucket\":\"$(bucket)\",\"fsize\":$(fsize)}")
.build_token(credential, Default::default())?;
println!("{}", upload_token);
则文件上传到七牛后,收到的回复内容如下:
{"key":"qiniu.jpg","hash":"Ftgm-CkWePC9fzMBTRNmPMhGBcSV","bucket":"if-bc","fsize":39335}
带回调业务服务器的凭证
上面生成的自定义上传回复的凭证适用于上传端(无论是客户端还是服务端)和七牛服务器之间进行直接交互的情况下。在客户端上传的场景下,有时客户端需要在文件上传到七牛后,从业务服务器获取相关信息,这个时候就要用到七牛的上传回调及相关回调参数的设置。
use qiniu_sdk::upload_token::{UploadPolicy, credential::Credential, prelude::*};
use std::time::Duration;
let access_key = "access key";
let secret_key = "secret key";
let bucket_name = "bucket name";
let object_name = "object name";
let credential = Credential::new(access_key, secret_key);
let upload_token = UploadPolicy::new_for_object(bucket_name, object_name, Duration::from_secs(3600))
.callback(&["http://api.example.com/qiniu/upload/callback"], "", "{\"key\":\"$(key)\",\"hash\":\"$(etag)\",\"bucket\":\"$(bucket)\",\"fsize\":$(fsize)}", "application/json")
.build_token(credential, Default::default())?;
println!("{}", upload_token);
在使用了上传回调的情况下,客户端收到的回复就是业务服务器响应七牛的 JSON 格式内容。通常情况下,我们建议使用 application/json
格式来设置 callback_body
,保持数据格式的统一性。实际情况下,callback_body
也支持 application/x-www-form-urlencoded
格式来组织内容,这主要看业务服务器在接收到 callback_body
的内容时如何解析。例如:
use qiniu_sdk::upload_token::{UploadPolicy, credential::Credential, prelude::*};
use std::time::Duration;
let access_key = "access key";
let secret_key = "secret key";
let bucket_name = "bucket name";
let object_name = "object name";
let credential = Credential::new(access_key, secret_key);
let upload_token = UploadPolicy::new_for_object(bucket_name, object_name, Duration::from_secs(3600))
.callback(&["http://api.example.com/qiniu/upload/callback"], "", "key=$(key)&hash=$(etag)&bucket=$(bucket)&fsize=$(fsize)", "")
.build_token(credential, Default::default())?;
println!("{}", upload_token);
服务端直传(需要依赖 qiniu-sdk
启用 upload
功能)
服务端直传是指客户利用七牛服务端 SDK 从服务端直接上传文件到七牛云,交互的双方一般都在机房内,所以服务端可以自己生成上传凭证,然后利用 SDK 中的上传逻辑进行上传,最后从七牛云获取上传的结果,这个过程中由于双方都是业务服务器,所以很少利用到上传回调的功能,而是直接自定义 return_body
来获取自定义的回复内容。
文件上传
最简单的是上传本地文件,直接指定文件的完整路径即可上传。
use qiniu_sdk::upload::{
apis::credential::Credential, AutoUploader, AutoUploaderObjectParams, UploadManager,
UploadTokenSigner,
};
use std::{time::Duration};
let access_key = "access key";
let secret_key = "secret key";
let bucket_name = "bucket name";
let object_name = "object name";
let credential = Credential::new(access_key, secret_key);
let upload_manager = UploadManager::builder(UploadTokenSigner::new_credential_provider(
credential,
bucket_name,
Duration::from_secs(3600),
))
.build();
let mut uploader: AutoUploader = upload_manager.auto_uploader();
let params = AutoUploaderObjectParams::builder().object_name(object_name).file_name(object_name).build();
uploader.upload_path("/home/qiniu/test.png", params)?;
在这个场景下,AutoUploader
会自动根据文件大小判断是否启用断点续传,如果文件较大,上传了一部分时因各种原因而中断,再重新执行相同的代码时,SDK 会尝试找到先前没有完成的上传任务,从而继续进行上传。
字节数组上传/数据流上传
可以支持将内存中的字节数组或实现了 std::io::Read
的实例上传到空间中。
use qiniu_sdk::upload::{
apis::credential::Credential, AutoUploader, AutoUploaderObjectParams, UploadManager,
UploadTokenSigner,
};
use std::{io::Cursor, time::Duration};
let access_key = "access key";
let secret_key = "secret key";
let bucket_name = "bucket name";
let object_name = "object name";
let credential = Credential::new(access_key, secret_key);
let upload_manager = UploadManager::builder(UploadTokenSigner::new_credential_provider(
credential,
bucket_name,
Duration::from_secs(3600),
))
.build();
let mut uploader: AutoUploader = upload_manager.auto_uploader();
let params = AutoUploaderObjectParams::builder().object_name(object_name).file_name(object_name).build();
uploader.upload_reader(Cursor::new("hello qiniu cloud"), params)?;
自定义参数上传
use qiniu_sdk::upload::{
apis::credential::Credential, AutoUploader, AutoUploaderObjectParams, UploadManager, UploadTokenSigner,
};
use std::{io::Cursor, time::Duration};
let access_key = "access key";
let secret_key = "secret key";
let bucket_name = "bucket name";
let object_name = "object name";
let credential = Credential::new(access_key, secret_key);
let upload_manager = UploadManager::builder(
UploadTokenSigner::new_credential_provider_builder(credential, bucket_name, Duration::from_secs(3600))
.on_policy_generated(|builder| {
builder
.return_body("{\"key\":\"$(key)\",\"hash\":\"$(etag)\",\"fname\":\"$(x:fname)\",\"age\",$(x:age)}");
})
.build(),
)
.build();
let mut uploader: AutoUploader = upload_manager.auto_uploader();
let params = AutoUploaderObjectParams::builder()
.object_name(object_name)
.file_name(object_name)
.insert_custom_var("fname", "123.jpg")
.insert_custom_var("age", "20")
.build();
uploader.upload_path("/home/qiniu/test.mp4", params)?;
私有云上传
use qiniu_sdk::upload::{
apis::{
credential::Credential,
http_client::{EndpointsBuilder, HttpClient},
},
AutoUploader, AutoUploaderObjectParams, UploadManager, UploadTokenSigner,
};
use std::{time::Duration};
let access_key = "access key";
let secret_key = "secret key";
let bucket_name = "bucket name";
let object_name = "object name";
let credential = Credential::new(access_key, secret_key);
let upload_manager = UploadManager::builder(UploadTokenSigner::new_credential_provider(
credential,
bucket_name,
Duration::from_secs(3600),
))
.uc_endpoints(
EndpointsBuilder::default()
.add_preferred_endpoint("ucpub-qos.pocdemo.qiniu.io".into()) // 私有云存储空间管理服务域名,可以添加多个
.build(),
)
.use_https(false) // 私有云普遍使用 HTTP 协议,而 SDK 则默认为 HTTPS 协议
.build();
let mut uploader: AutoUploader = upload_manager.auto_uploader();
let params = AutoUploaderObjectParams::builder().object_name(object_name).file_name(object_name).build();
uploader.upload_path("/home/qiniu/test.png", params)?;
下载文件(需要依赖 qiniu-sdk
启用 download
功能)
文件下载分为公开空间的文件下载和私有空间的文件下载。
公开空间
use qiniu_sdk::download::{DownloadManager, StaticDomainsUrlsGenerator};
let object_name = "公司/存储/qiniu.jpg";
let domain = "devtools.qiniu.com";
let path = "/home/user/qiniu.jpg";
let download_manager = DownloadManager::new(
StaticDomainsUrlsGenerator::builder(domain)
.use_https(false) // 设置为 HTTP 协议
.build(),
);
download_manager
.download(object_name)?
.to_path(path)?;
私有空间
use qiniu_sdk::download::{apis::credential::Credential, DownloadManager, StaticDomainsUrlsGenerator, UrlsSigner};
let access_key = "access key";
let secret_key = "secret key";
let object_name = "公司/存储/qiniu.jpg";
let domain = "devtools.qiniu.com";
let path = "/home/user/qiniu.jpg";
let download_manager = DownloadManager::new(UrlsSigner::new(
Credential::new(access_key, secret_key),
StaticDomainsUrlsGenerator::builder(domain)
.use_https(false) // 设置为 HTTP 协议
.build(),
));
download_manager
.download(object_name)?
.to_path(path)?;
资源管理(需要依赖 qiniu-sdk
启用 objects
功能)
获取文件信息
use qiniu_sdk::objects::{apis::credential::Credential, ObjectsManager};
let access_key = "access key";
let secret_key = "secret key";
let bucket_name = "bucket name";
let object_name = "object name";
let credential = Credential::new(access_key, secret_key);
let object_manager = ObjectsManager::new(credential);
let bucket = object_manager.bucket(bucket_name);
let response = bucket.stat_object(object_name).call()?;
let entry = response.into_body();
println!("{}", entry.get_hash_as_str());
println!("{}", entry.get_size_as_u64());
println!("{}", entry.get_mime_type_as_str());
println!("{}", entry.get_put_time_as_u64());
修改文件类型
use qiniu_sdk::objects::{apis::credential::Credential, ObjectsManager};
let access_key = "access key";
let secret_key = "secret key";
let bucket_name = "bucket name";
let object_name = "object name";
let credential = Credential::new(access_key, secret_key);
let object_manager = ObjectsManager::new(credential);
let bucket = object_manager.bucket(bucket_name);
bucket
.modify_object_metadata(object_name, mime::APPLICATION_JSON)
.call()?;
移动或重命名文件
移动操作本身支持移动文件到相同,不同空间中,在移动的同时也可以支持文件重命名。唯一的限制条件是,移动的源空间和目标空间必须在同一个机房。
use qiniu_sdk::objects::{apis::credential::Credential, ObjectsManager};
let access_key = "access key";
let secret_key = "secret key";
let bucket_name = "bucket name";
let object_name = "object name";
let to_bucket_name = "to bucket name";
let to_object_name = "new object name";
let credential = Credential::new(access_key, secret_key);
let object_manager = ObjectsManager::new(credential);
let bucket = object_manager.bucket(bucket_name);
bucket
.move_object_to(object_name, to_bucket_name, to_object_name);
.call()?;
复制文件副本
文件的复制和文件移动其实操作一样,主要的区别是移动后源文件不存在了,而复制的结果是源文件还存在,只是多了一个新的文件副本。
use qiniu_sdk::objects::{apis::credential::Credential, ObjectsManager};
let access_key = "access key";
let secret_key = "secret key";
let bucket_name = "bucket name";
let object_name = "object name";
let to_bucket_name = "to bucket name";
let to_object_name = "new object name";
let credential = Credential::new(access_key, secret_key);
let object_manager = ObjectsManager::new(credential);
let bucket = object_manager.bucket(bucket_name);
bucket
.copy_object_to(object_name, to_bucket_name, to_object_name);
.call()?;
删除空间中的文件
use qiniu_sdk::objects::{apis::credential::Credential, ObjectsManager};
let access_key = "access key";
let secret_key = "secret key";
let bucket_name = "bucket name";
let object_name = "object name";
let credential = Credential::new(access_key, secret_key);
let object_manager = ObjectsManager::new(credential);
let bucket = object_manager.bucket(bucket_name);
bucket
.delete_object(object_name);
.call()?;
设置或更新文件的生存时间
可以给已经存在于空间中的文件设置文件生存时间,或者更新已设置了生存时间但尚未被删除的文件的新的生存时间。
use qiniu_sdk::objects::{apis::credential::Credential, AfterDays, ObjectsManager};
let access_key = "access key";
let secret_key = "secret key";
let bucket_name = "bucket name";
let object_name = "object name";
let credential = Credential::new(access_key, secret_key);
let object_manager = ObjectsManager::new(credential);
let bucket = object_manager.bucket(bucket_name);
bucket
.modify_object_life_cycle(object_name)
.delete_after_days(AfterDays::new(10))
.call()?;
获取空间文件列表
use qiniu_sdk::objects::{apis::credential::Credential, ObjectsManager};
let access_key = "access key";
let secret_key = "secret key";
let bucket_name = "bucket name";
let credential = Credential::new(access_key, secret_key);
let object_manager = ObjectsManager::new(credential);
let bucket = object_manager.bucket(bucket_name);
let mut iter = bucket.list().iter();
while let Some(entry) = iter.next() {
let entry = entry?;
println!(
"{}\n hash: {}\n size: {}\n mime type: {}",
entry.get_key_as_str(),
entry.get_hash_as_str(),
entry.get_size_as_u64(),
entry.get_mime_type_as_str(),
);
}
私有云中获取空间文件列表
use qiniu_sdk::objects::{
apis::{
credential::Credential,
http_client::{EndpointsBuilder, HttpClient},
},
ObjectsManager,
};
use std::net::Ipv4Addr;
let access_key = "access key";
let secret_key = "secret key";
let bucket_name = "bucket name";
let credential = Credential::new(access_key, secret_key);
let object_manager = ObjectsManager::builder(credential)
.uc_endpoints(
EndpointsBuilder::default()
.add_preferred_endpoint("ucpub-qos.pocdemo.qiniu.io".into()) // 私有云存储空间管理服务域名,可以添加多个
.build(),
)
.use_https(false) // 私有云普遍使用 HTTP 协议,而 SDK 则默认为 HTTPS 协议
.build();
let bucket = object_manager.bucket(bucket_name);
let mut iter = bucket.list().iter();
while let Some(entry) = iter.next() {
let entry = entry?;
println!(
"{}\n hash: {}\n size: {}\n mime type: {}",
entry.get_key_as_str(),
entry.get_hash_as_str(),
entry.get_size_as_u64(),
entry.get_mime_type_as_str(),
);
}
资源管理批量操作(需要依赖 qiniu-sdk
启用 objects
功能)
批量获取文件信息
use qiniu_sdk::objects::{apis::credential::Credential, ObjectsManager};
let access_key = "access key";
let secret_key = "secret key";
let bucket_name = "bucket name";
let credential = Credential::new(access_key, secret_key);
let object_manager = ObjectsManager::new(credential);
let bucket = object_manager.bucket(bucket_name);
let mut ops = bucket.batch_ops();
ops.add_operation(bucket.stat_object("qiniu.jpg"));
ops.add_operation(bucket.stat_object("qiniu.mp4"));
ops.add_operation(bucket.stat_object("qiniu.png"));
let mut iter = ops.call();
while let Some(result) = iter.next() {
match result {
Ok(entry) => {
println!(
"hash: {:?}\n size: {:?}\n mime type: {:?}",
entry.get_hash_as_str(),
entry.get_size_as_u64(),
entry.get_mime_type_as_str(),
);
}
Err(err) => {
println!("{:?}", err);
}
}
}
批量修改文件类型
use qiniu_sdk::objects::{apis::credential::Credential, ObjectsManager};
let access_key = "access key";
let secret_key = "secret key";
let bucket_name = "bucket name";
let credential = Credential::new(access_key, secret_key);
let object_manager = ObjectsManager::new(credential);
let bucket = object_manager.bucket(bucket_name);
let mut ops = bucket.batch_ops();
ops.add_operation(bucket.modify_object_metadata("qiniu.jpg", mime::IMAGE_JPEG));
ops.add_operation(bucket.modify_object_metadata("qiniu.png", mime::IMAGE_PNG));
ops.add_operation(bucket.modify_object_metadata("qiniu.mp4", "video/mp4".parse()?));
let mut iter = ops.call();
while let Some(result) = iter.next() {
match result {
Ok(_) => {
println!("Ok");
}
Err(err) => {
println!("{:?}", err);
}
}
}
批量删除文件
use qiniu_sdk::objects::{apis::credential::Credential, ObjectsManager};
let access_key = "access key";
let secret_key = "secret key";
let bucket_name = "bucket name";
let credential = Credential::new(access_key, secret_key);
let object_manager = ObjectsManager::new(credential);
let bucket = object_manager.bucket(bucket_name);
let mut ops = bucket.batch_ops();
ops.add_operation(bucket.delete_object("qiniu.jpg"));
ops.add_operation(bucket.delete_object("qiniu.png"));
ops.add_operation(bucket.delete_object("qiniu.mp4"));
let mut iter = ops.call();
while let Some(result) = iter.next() {
match result {
Ok(_) => {
println!("Ok");
}
Err(err) => {
println!("{:?}", err);
}
}
}
批量移动或重命名文件
use qiniu_sdk::objects::{apis::credential::Credential, ObjectsManager};
let access_key = "access key";
let secret_key = "secret key";
let bucket_name = "bucket name";
let credential = Credential::new(access_key, secret_key);
let object_manager = ObjectsManager::new(credential);
let bucket = object_manager.bucket(bucket_name);
let mut ops = bucket.batch_ops();
ops.add_operation(bucket.move_object_to("qiniu.jpg", bucket_name, "qiniu.jpg.move"));
ops.add_operation(bucket.move_object_to("qiniu.png", bucket_name, "qiniu.png.move"));
ops.add_operation(bucket.move_object_to("qiniu.mp4", bucket_name, "qiniu.mp4.move"));
let mut iter = ops.call();
while let Some(result) = iter.next() {
match result {
Ok(_) => {
println!("Ok");
}
Err(err) => {
println!("{:?}", err);
}
}
}
批量复制文件
use qiniu_sdk::objects::{apis::credential::Credential, ObjectsManager};
let access_key = "access key";
let secret_key = "secret key";
let bucket_name = "bucket name";
let credential = Credential::new(access_key, secret_key);
let object_manager = ObjectsManager::new(credential);
let bucket = object_manager.bucket(bucket_name);
let mut ops = bucket.batch_ops();
ops.add_operation(bucket.copy_object_to("qiniu.jpg", bucket_name, "qiniu.jpg.move"));
ops.add_operation(bucket.copy_object_to("qiniu.png", bucket_name, "qiniu.png.move"));
ops.add_operation(bucket.copy_object_to("qiniu.mp4", bucket_name, "qiniu.mp4.move"));
let mut iter = ops.call();
while let Some(result) = iter.next() {
match result {
Ok(_) => {
println!("Ok");
}
Err(err) => {
println!("{:?}", err);
}
}
}
批量解冻归档存储类型文件
use qiniu_sdk::objects::{apis::credential::Credential, ObjectsManager};
let access_key = "access key";
let secret_key = "secret key";
let bucket_name = "bucket name";
let credential = Credential::new(access_key, secret_key);
let object_manager = ObjectsManager::new(credential);
let bucket = object_manager.bucket(bucket_name);
let mut ops = bucket.batch_ops();
ops.add_operation(bucket.restore_archived_object("qiniu.jpg", 7));
ops.add_operation(bucket.restore_archived_object("qiniu.png", 7));
ops.add_operation(bucket.restore_archived_object("qiniu.mp4", 7));
let mut iter = ops.call();
while let Some(result) = iter.next() {
match result {
Ok(_) => {
println!("Ok");
}
Err(err) => {
println!("{:?}", err);
}
}
}
最低支持的 Rust 版本(MSRV)
- 1.70.0
编码规范
- 通过
cargo clippy
检查,并经过rustfmt
格式化。 - 所有阻塞操作都提供异步无阻塞版本。
- 竭力避免 unsafe 代码。
- 所有公开的
trait
都可以通过prelude
模块自动导入。 - 公开接口中如果要求传入或返回某个第三方库的类型,则该类型也必须公开导出。
联系我们
- 如果需要帮助,请提交工单(在门户右侧点击咨询和建议提交工单,或者直接向 [email protected] 发送邮件)
- 如果有任何问题,可以到问答社区提问,问答社区
- 更详细的文档,见官方文档站
- 如果发现了bug,欢迎提交 Issue
- 如果有功能需求,欢迎提交 Issue
- 如果要提交代码,欢迎提交 Pull Request
- 欢迎关注我们的微信 微博,及时获取动态信息。
代码许可
本项目采用 MIT 许可证。
lib.rs
:
qiniu-etag
七牛实用工具库
仅供七牛 SDK 内部使用,接口不保证总是兼容变更
依赖
~420–700KB
~15K SLoC