文件管理
上传文件
在BOS中,用户操作的基本数据单元是Object。Object包含Key、Meta和Data。其中,Key是Object的名字;Meta是用户对该Object的描述,由一系列Name-Value对组成;Data是Object的数据。
BOS C++ SDK提供了丰富的文件上传接口,可以通过以下方式上传文件:
- 简单上传
- 追加上传
- 分块上传
- 断点续传上传
简单上传
BOS在简单上传的场景中,支持以指定文件形式、以数据流方式、以文件描述符方式、以字符串方式执行Object上传,请参考如下代码:
int PutObjectDemo(Client& client,const std::string& bucketName, const std::string objectKey){
// 获取文件数据流
FileInputStream inputStream("/path/to/test.zip"); // bcesdk/util/util.h
int ret = 0;
// 以文件名作为参数上传Object
ret = client.upload_file(bucketName, objectKey, "/path/to/test.zip");
// 以数据流形式上传Object
ret = client.upload_file(bucketName, objectKey, inputStream);
// 以文件描述符形式上传Object
fd_t fd = open("/path/to/test.zip", O_RDWR, 0666);//linux下,fd_t在common.h中定义
ret = client.upload_file(bucketName, objectKey, fd);
// 以字符串形式上传Object
std::string data = "this is data";
ret = client.put_object(bucketName, objectKey, data);
return ret;
}
Object以文件的形式上传到BOS中,put_object、upload_file函数支持不超过5GB的Object上传。若要支持大文件(大于5G的文件)上传请使用upload_super_file,参考代码如下:
int PutLargeObjectDemo(Client& client,const std::string& bucketName, const std::string objectKey){
std::string fileName = "/path/to/test.zip"
return client.upload_super_file(bucketName, objectKey, fileName);//第三个参数用fd_t形式也可行
}
设置文件元信息
文件元信息(Object Meta),是对用户在向BOS上传文件时,同时对文件进行的属性描述,主要分为分为两种:设置HTTP标准属性(HTTP Headers)和用户自定义的元信息。
- 设定Object的Http Header
BOS C++ SDK本质上是调用后台的HTTP接口,因此用户可以在上传文件时自定义Object的Http Header。常用的http header说明如下:
名称 | 描述 | 默认值 |
---|---|---|
Content-MD5 | 文件数据校验,设置后BOS会启用文件内容MD5校验,把您提供的MD5与文件的MD5比较,不一致会抛出错误 | 无 |
Content-Type | 文件的MIME,定义文件的类型及网页编码,决定浏览器将以什么形式、什么编码读取文件。如没有指,BOS则根据文件的扩展名自动生成,如文件没有扩展名则填默认值 | application/octet-stream |
Content-Disposition | 指示MIME用户代理如何显示附加的文件,打开或下载,及文件名称 | 无 |
Content-Length | 上传的文件的长度,超过流/文件的长度会截断,不足为实际值 | 流/文件时间长度 |
Expires | 缓存过期时间 | 无 |
Cache-Control | 指定该Object被下载时的网页的缓存行为 | 无 |
参考代码如下:
...
// 初始化meta
ObjectMetaData meta;
// 设置ContentType
meta.set_content_type("application/json");
// 设置cache-control
meta.set_cache_control("no-cache");
// 设置x-bce-storage-class
meta.set_storage_class("STANDARD");
ret = client.upload_file(bucketName, objectKey, content, meta);
...
- 用户自定义元信息
BOS支持用户自定义元数据来对Object进行描述。如下代码所示:
// 设置自定义元数据name的值为my-data
meta.set_user_meta("name", "my-data");
// 上传Object
ret = client.upload_file(bucketName, objectKey, file_name, meta);
提示:
- 在上面代码中,用户自定义了一个名字为”name”,值为”my-data”的元数据
- 当用户下载此Object的时候,此元数据也可以一并得到
- 一个Object可以有多个类似的参数,但所有的User Meta总大小不能超过2KB
设置Object的Copy属性
BOS同时会提供CopyObject接口用于将一个已经存在的Object拷贝到另外一个Object,拷贝过程中会对源Object的Etag或修改状态进行判断,根据判断结果决定是否执行拷贝。详细的参数解释如下:
名称 | 类型 | 描述 | 是否必需 |
---|---|---|---|
x-bce-copy-source-if-match | std::string | 如果源Object的ETag值和用户提供的ETag相等,则执行拷贝操作,否则拷贝失败。 | 否 |
x-bce-copy-source-if-none-match | std::string | 如果源Object的ETag和用户提供的ETag不相等,则执行拷贝操作,否则拷贝失败。 | 否 |
x-bce-copy-source-if-unmodified-since | std::string | 如果源object在x-bce-copy-source-if-unmodified-since之后没被修改,则执行拷贝操作,否则拷贝失败。 | 否 |
x-bce-copy-source-if-modified-since | std::string | 如果源object在x-bce-copy-source-if-modified-since之后被修改了,则执行拷贝操作,否则拷贝失败。 | 否 |
对应的示例代码:
// 初始化BosClient
Client client = ...;
// 创建CopyObjectRequest对象
CopyObjectRequest copyObjectRequest(destBucketName, destKey, srcBucketName, srcKey);
CopyObjectResponse copyObjectResponse;
// 设置新的Metadata
StringMap& userMetadata = *(meta.mutable_user_meta());//StringMap == map<string, string>
userMetadata.clear();
userMetadata["<user-meta-key>"] = "<user-meta-value>";
copyObjectRequest.set_meta(&meta, false);//第二个参数(is_own)若为true则由copyObjectRequest析构时delete meta
//copy-source-if-match
copyObjectRequest.set_if_match("111111111183bf192b57a4afc76fa632");
//copy-source-if-none-match
copyObjectRequest.set_if_none_match("111111111183bf192b57a4afc76fa632");
std::string gmtDate = TimeUtil::now_gmttime();//当前GMT格式时间
//copy-source-if-modified-since
copyObjectRequest.set_if_modified_since(gmtDate);
//copy-source-if-unmodified-since
copyObjectRequest.set_if_unmodified_since(gmtDate);
// 复制Object
client.copy_object(copyObjectRequest, copyObjectResponse;);
std::cout << "ETag: " << copyObjectResponse.etag() << " LastModified: " << copyObjectResponse.last_modified() << std::endl;
上传Object时设置存储类型
BOS支持标准存储, 低频存储和冷存储,上传Object并存储为低频存储类型通过指定StorageClass实现,三种存储类型对应的参数如下:
存储类型 | 参数 |
---|---|
标准存储 | STANDRAD |
低频存储 | STANDARD_IA |
冷存储 | COLD |
归档存储 | ARCHIVE |
以低频存储为例,代码如下:
void print_common_response(BceResponse &result) {
printf("status:%d\n", result.status_code());
if (result.is_ok()) {
printf("request-id:%s\n", result.request_id().c_str());
printf("debug-id:%s\n", result.debug_id().c_str());
}
if (result.is_fail()) {
printf("error-message:%s\n", result.error().message().c_str());
}
}
int putObjectStorageClass(){
std::string filename = "file.txt";
FileInputStream file(filename);
PutObjectRequest request(bucket, object, &file);
request.mutable_meta()->set_storage_class("STANDARD_IA");
PutObjectResponse result;
client.put_object(request, &result);
print_common_response(result);
printf("etag: %s\n", result.etag().c_str());
}
使用上传进度条
// 上传进度回调函数
// 注意该回调函数中不得出现耗时较长/阻塞操作, 会影响数据上传性能.
// increment: 当次上传的数据量
// transfered: 已上传数据量
// total: 需上传的数据总量
// userData: 用户自定义数据, 例如object bucket+key等.
void progress_callback(int64_t increment, int64_t transfered, int64_t total, void* user_data) {
std::cout << "progress_callback[" << user_data << "] => " <<
increment <<" ," << transfered << "," << total << std::endl;
}
//待上传文件
std::string filename = "/tmp/put_file_test";
FileInputStream file(filename);
PutObjectRequest req(BUCKET, "transfer_progress_t1", &file);
PutObjectResponse rsp;
// 设置上传进度相关数据
// TransferProgress结构在头文件: "bcesdk/common/common.h"
TransferProgress progress;
progress.transfer_progress_cb = progress_callback;
req.set_progress(progress);
//将filename文件中数据上传
int ret = client()->put_object(req, &rsp);
if (ret) {
LOGF(WARN, "client err: %d", ret);
}
if (rsp.is_fail()) {
LOGF(WARN,
"put_object: [status_code = %d], [message = %s], [requestid = %s]",
rsp.status_code(),
rsp.error().message().c_str(),
rsp.error().request_id().c_str());
}
追加上传
上文介绍的简单上传方式,创建的Object都是Normal类型,用户不可再进行追加写,这在日志、视频监控、视频直播等数据复写较频繁的场景中使用不方便。
正因如此,百度智能云BOS特别支持了AppendObject,即以追加写的方式上传文件。通过AppendObject操作创建的Object类型为Appendable Object,可以对该Object追加数据。AppendObject大小限制为0~5G。
通过AppendObject方式上传示例代码如下:
int AppendObjectDemo(Client& client,const std::string& bucketName, const std::string& objectKey) {
// 获取数据流
FileInputStream inputStream("/path/to/test.zip");
// 以数据流形式上传Object
AppendObjectRequest appendObjectFromInputStreamRequest(bucketName, objectKey, &inputStream);
AppendObjectResponse appendObjectFromInputStreamResponse;
int ret = client.append_object(appendObjectFromInputStreamRequest, &appendObjectFromInputStreamResponse);
// 以字符串上传Object
std::string data = "this is data";
AppendObjectRequest appendObjecFromStringtRequest(bucketName, objectKey, data);
AppendObjectResponse appendObjectFromStringResponse;
ret = client.appendObject(appendObjecFromStringtRequest, &appendObjectFromStringResponse);
// 打印ETag
std::cout << appendObjectFromInputStreamResponse.etag() << std::endl;
// 打印NextAppendOffset
std::cout << appendObjectFromInputStreamResponse.next_append_offset() << std::endl;
// 追加上传的示例,需要在请求中加上下次追加写的位置
long long nextAppendOffset = appendObjectFromInputStreamResponse.next_append_offset();
AppendObjectRequest appendObjectFromStringRequest(bucketName,objectKey,data);
appendObjectFromStringRequest.set_offset(nextAppendOffset);
AppendObjectResponse appendObjectFromStringResponse;
ret = client.append_object(appendObjectFromStringRequest, &appendObjectFromStringResponse);
return ret;
}
分块上传
除了通过简单上传及追加上传方式将文上传件到BOS以外,BOS还提供了另外一种上传模式 —— Multipart Upload。用户可以在如下的应用场景内(但不仅限于此),使用Multipart Upload上传模式,如:
- 需要支持断点上传。
- 上传超过5GB大小的文件。
- 网络条件较差,和BOS的服务器之间的连接经常断开。
- 需要流式地上传文件。
- 上传文件之前,无法确定上传文件的大小。
下面将一步步介绍Multipart Upload的实现。假设有一个文件,本地路径为 /path/to/file.zip
,由于文件比较大,将其分块传输到BOS中。
初始化Multipart Upload
使用 initiateMultipartUpload
方法来初始化一个分块上传事件:
// 开始Multipart Upload
InitMultiUploadRequest initMultiUploadRequest(bucketName, objectKey);
InitMultiUploadResponse initMultiUploadResponse;
int ret = client.init_multipart_upload(initMultiUploadRequest, &initMultiUploadResponse);
//异常处理
...
// 打印UploadId
std::cout << "UploadId: " << initMultiUploadResponse.upload_id() << std::endl;
initMultiUploadResponse
的返回结果中含有 UploadId
,它是区分分块上传事件的唯一标识,在后面的操作中,我们将用到它。
- 上传低频存储类型Object的初始化
初始化低频存储的一个分块上传事件:
void putMultiUploadStorageClass(){
ObjectMetaData meta;
meta.set_storage_class("STANDARD_IA");
InitMultiUploadRequest initMultiUploadRequest(bucketName, objectKey);
InitMultiUploadResponse initMultiUploadResponse;
initMultiUploadRequest.set_meta(&meta);
client.init_multipart_upload(initMultiUploadRequest, &initMultiUploadResponse);
}
- 上传冷存储类型Object的初始化
初始化低频存储的一个分块上传事件:
void putMultiUploadStorageClass(){
ObjectMetaData meta;
meta.set_storage_class("COLD)");
InitMultiUploadRequest initMultiUploadRequest(bucketName, objectKey);
InitMultiUploadResponse initMultiUploadResponse;
initMultiUploadRequest.set_meta(&meta);
client.init_multipart_upload(initMultiUploadRequest, &initMultiUploadResponse);
}
上传分块
接着,把文件分块上传。
// 设置每块为 5MB
// [注意] 除了最后一个分块, 其余分块需满足size>=100kb
long partSize = 1024 * 1024 * 5L;
// 注意: 当分块上传的数据为string/内存数据, UploadPartRequest的构造函数如下:
// UploadPartRequest(const std::string &bucket_name, const std::string &object_name, const std::string &data, int part_number, const std::string &upload_id)
// 其中data字段为std::string, 不得传入C风格的char*串, 会导致计算数据size错误.
//待分块上传文件
std::string partFileName = "/path/to/file.zip";
FileInputStream file(partFileName);
// 计算分块数目
int partCount = static_cast<int>(file.get_size() / partSize);
if (file.get_size() % partSize != 0){
partCount++;
}
int64_t size = file.size();
int64_t off = 0;
for (int i = 0; off < file.size(); ++i) {
if (off + partSize > size) {
partSize = size - off;
}
FileInputStream partFile(file.fd(), off, partSize);
UploadPartRequest uploadPartRequest = UploadPartRequest(bucketName, objectName, partFile, i + 1, initMultiUploadResponse.upload_id());
UploadPartResponse uploadPartResponse;
int ret = client.upload_part(uploadPartRequest, &uploadPartResponse);
//校验返回值
// 将返回的PartETag保存到List中。
part_t partInfo;
partInfo.part_number = i+1;
partInfo.etag = uploadPartResponse.etag();
partEtags.push_back(partInfo);
off += partSize;
}
上面代码的核心是调用 upload_part
方法来并发的上传每一个分块,但是要注意以下几点:
upload_part
要求除最后一个Part以外,其他的Part大小都要大于等于100KB。但是Upload Part接口并不会立即校验上传Part的大小;只有当Complete Multipart Upload的时候才会校验, 若upload_part流程中的块大小不符合预期, 则complete_multipart_upload
接口会报错。- 为了保证数据在网络传输过程中不出现错误,建议您在
upload_part
后,使用每个分块BOS返回的Content-MD5值分别验证已上传分块数据的正确性。当所有分块数据合成一个Object后,不再含MD5值。 - Part号码的范围是1~10000。如果超出这个范围,BOS将返回InvalidArgument的错误码。
- 每次上传Part时都要把流定位到此次上传块开头所对应的位置。
- 每次上传Part之后,BOS的返回结果会包含一个
ETag
对象,它是上传块的ETag与块编号(PartNumber)的组合,在后续完成分块上传的步骤中会用到它,因此需要将其保存起来。一般来讲这些ETag
对象将被保存到vector中。
完成分块上传
如下代码所示,完成分块上传:
CompleteMultipartUploadRequest completeMultipartUploadRequest(bucketName, objectKey, initMultiUploadResponse.upload_id());
//添加part信息,即part合并顺序
for (part_t partInfo : partEtags) {
completeMultipartUploadRequest.add_part(partInfo.part_number, partInfo.etag);
}
// 完成分块上传
CompleteMultipartUploadResponse completeMultipartUploadResponse;
int ret = client.complete_multipart_upload(completeMultipartUploadRequest, &completeMultipartUploadResponse);
// 打印Object的ETag
std::cout << completeMultipartUploadResponse.etag() << std::endl;
上面代码中的 partETags
是第二部中保存的part_t的列表,BOS收到用户提交的Part列表后,会逐一验证每个数据Part的有效性。当所有的数据Part验证通过后,BOS将把这些数据part组合成一个完整的Object。
取消分块上传事件
用户可以使用abortMultipartUpload方法取消分块上传。
AbortMultipartUploadRequest abortMultipartUploadRequest(bucketName, objectKey, uploadId);
AbortMultipartUploadResponse abortMultipartUploadResponse;
// 取消分块上传
int ret = client.abort_multipart_upload(abortMultipartUploadRequest, &abortMultipartUploadResponse);
获取未完成的分块上传事件
用户可以使用 list_multipart_uploads
方法获取Bucket内未完成的分块上传事件。
ListMultipartUploadsRequest listMultipartUploadsRequest(bucketName);
ListMultipartUploadsResponse listMultipartUploadsResponse;
// 获取Bucket内所有上传事件
int ret = client.list_multipart_uploads(listMultipartUploadsRequest, &
listMultipartUploadsResponse);
if (ret != 0) {
return ret;
}
// 遍历所有上传事件
for (const MultipartUploadSummary& multipartUpload : listMultipartUploadsResponse.uploads()) {
std::cout << "Key: " << multipartUpload.key <<
" UploadId: " << multipartUpload.upload_id << std::endl;
}
注意:
- 默认情况下,如果Bucket中的分块上传事件的数目大于1000,则只会返回1000个Object,并且返回结果中is_truncated的值为True,同时返回next_marker作为下次读取的起点。
- 若想返回更多分块上传事件的数目,可以使用set_marker函数设置marker分次读取。
获取所有已上传的块信息
用户可以使用 listParts
方法获取某个上传事件中所有已上传的块。
ListPartsRequest listPartsRequest(bucketName, objectKey, uploadId);
// 获取上传的所有Part信息
ListPartsResponse listPartsResponse;
int ret = client.list_parts(listPartsRequest, &listPartsResponse);
if (ret != 0) {
return ret;
}
// 遍历所有Part
for (consr PartSummary& part : listPartsResponse.parts()) {
std::cout << "PartNumber: " << part.part_number << " ETag: " << part.etag;
}
如果需要查看Object的存储类型storage class使用以下代码:
public void listPartsStorageClass(){
ListPartsRequest listPartsRequest(bucketName, objectKey, uploadId);
// 获取上传的所有Part信息
ListPartsResponse listPartsResponse;
int ret = client.list_parts(listPartsRequest, &listPartsResponse);
if (ret != 0) {
return ret;
}
std::string storageClass = listPartsResponse.storage_class();
}
断点续传上传
当用户向BOS上传大文件时,如果网络不稳定或者遇到程序崩等情况,则整个上传就失败了,失败前已经上传的部分也作废,用户不得不重头再来。这样做不仅浪费资源,在网络不稳定的情况下,往往重试多次还是无法完成上传。 基于上述场景,BOS提供了断点续传上传的能力:
- 当网络情况一般的情况下,建议使用三步上传方式,将object分为5Mb的块,参考分块上传。
- 当您的网络情况非常差,推荐使用append_object的方式进行断点续传,每次append 较小数据256kb,参考追加上传。
提示
- 断点续传是分片上传的封装和加强,是用分片上传实现的;
- 文件较大或网络环境较差时,推荐使用分片上传;
下载文件
BOS C++ SDK提供了丰富的文件下载接口,用户可以通过以下方式从BOS中下载文件:
- 简单流式下载
- 下载到本地文件
- 下载到内存中的字符串
- 断点续传下载
- 范围下载
- 下载进度条
简单流式下载
用户可以通过如下代码将Object输出到一个文件流中:
void getObject(Client& client, const std::string& bucketName, const std::string& objectKey) {
//获取文件输出流
FileOutputStream outStream("test.txt");
//方法一,通过request、response
//初始化request
GetObjectRequest getObjectRequest(bucketName, objectKey);
//将outStream作为输出对象
GetObjectResponse getObjectResponse(&outStream);
int ret = client.get_object(getObjectRequest, &getObjectResponse);
//方法二,通过便利接口,将Object写到outStream文件流中
ret = client.download_file(bucketName, objectKey, outStream);
...
}
直接下载Object到文件
用户可以通过如下代码直接将Object下载到指定文件:
// 下载Object到文件
std::string localFileName = "test.txt";
ObjectMetaData ObjectMetaData;
//设置Object读取范围
int64_t start = 0;
int64_t length = 100;
//下载Object
int ret = client.download_file(bucketName, objectKey, localFileName, start, length, &ObjectMetaData); //存到test.txt中,ObjectMetaData
...
当使用上面方法将Object直接下载到文件时,方法返回ObjectMetaData对象。
使用下载进度条
// 上传进度回调函数
// 注意该回调函数中不得出现耗时较长/阻塞操作, 会影响数据下载性能.
// increment: 当次下载的数据量
// transfered: 已下载数据量
// total: 需下载的数据总量
// userData: 用户自定义数据, 例如object bucket+key等.
void progress_callback(int64_t increment, int64_t transfered, int64_t total, void* user_data) {
std::cout << "progress_callback[" << user_data << "] => " <<
increment <<" ," << transfered << "," << total << std::endl;
}
// 本地待接收数据文件
std::string filename = "/tmp/get_file_test";
FileOutputStream outStream(filename);
GetObjectRequest req(BUCKET, "transfer_progress_2");
GetObjectResponse rsp(&outStream);
// 设置下载进度条相关数据
TransferProgress progress;
progress.transfer_progress_cb = progress_callback;
rsp.set_progress(progress);
// 下载数据到本地filename文件中
int ret = client()->get_object(req, &rsp);
if (ret) {
LOGF(WARN, "client err: %d", ret);
}
if (rsp.is_fail()) {
LOGF(WARN,
"get_object: [status_code = %d], [message = %s], [requestid = %s]",
rsp.status_code(),
rsp.error().message().c_str(),
rsp.error().request_id().c_str());
}
下载到内存中的字符串
此方法直接将Object存到string当中,不会写入磁盘
std::string inMemoryData;
//方法一,通过request、response
GetObjectRequest getObjectRequest(bucketName, objectKey);
//将inMemoryData作为输出对象
GetObjectResponse getObjectResponse(&inMemoryData);
int ret = client.get_object(getObjectRequest, &getObjectResponse);
if (ret != 0) {
return ret;
}
//方法二,通过便利接口
ret = client.get_object(bucketName, objectKey, &inMemoryData);
//打印出来
std::cout << inMemoryData << std::endl;
范围下载
为了实现更多的功能,可以通过使用GetObjectRequest来指定下载范围,实现更精细化地获取Object。如果指定的下载范围是0 - 100,则返回第0到第100个字节的数据,包括第100个,共101字节的数据,即[0, 100]。
GetObjectRequest getObjectRequest(bucketName, objectKey);
//初始化文件输出流
FileOutputStream outStream("test.txt");
GetObjectResponse getObjectResponse(&outStream);
//设置下载范围[0,100]字节数据
getObjectRequest.set_range(0, 100);
int ret = client.get_object(getObjectRequest, &getObjectResponse);
if (ret != 0) {
return ret;
}
return 0;
通过getObjectRequest的set_range方法可以设置返回Object的范围。用户也可以用此功能实现文件的分段下载和断点续传。
其他使用方法
获取Object的存储类型
Object的storage class属性分为STANDARD
(标准存储), STANDARD_IA
(低频存储)和COLD
(冷存储),通过如下代码可以实现:
int getObjectStorageClass(){
HeadObjectRequest request(bucketName, objectKey);
HeadObjectResponse response;
int ret = client.head_object(request, &response);
if (ret != 0) {
return ret;
}
ObjectMetaData& meta = response.meta();
std::string storageClass = meta.storage_class();
std::cout << storageClass << std::endl;
return 0;
}
只获取ObjectMetaData
通过 head_object 方法可以只获取ObjectMetaData而不获取Object的实体。
ObjectMetaData
解析类中可供调用的参数有:
参数 | 说明 |
---|---|
content_type | Object的类型 |
content_length | Object的大小 |
content_md5 | Object的MD5 |
etag | Object的HTTP协议实体标签 |
storage_class | Object的存储类型 |
user_meta | 如果在PutObject指定了userMetadata自定义meta,则返回此项 |
expires | 下载Object时的缓存失效时间 |
content_disposition | 设置浏览器是否下载,可取值为inline、attachment; filename="download.txt" |
cache_control | 下载Object的Cache设置,常见的可取值为private、no-cache、max-age、must-revalidate |
content_range | 有range的情况下返回Object的数据范围 |
变更文件存储等级
上文中已提到,BOS支持为文件赋予STANDARD(标准存储), STANDARD_IA(低频存储)和COLD(冷存储)三种存储类型。同时,BOS C++ SDK也支持用户对特定文件执行存储类型变更的操作。 涉及到的参数如下:
参数 | 说明 |
---|---|
x-bce-storage-class | 指定Object的存储类型,STANDARD_IA代表低频存储,COLD代表冷存储,不指定时默认是标准存储类型。 |
示例如下:
// 标准存储转为低频存储
CopyObjectRequest copyObjectRequest(destBucketName, destKey, srcBucketName, srcKey);
copyObjectRequest.mutable_meta()->set_storage_class("STANDARD_IA");
CopyObjectResponse copyObjectResponse;
int ret = client.copy_object(copyObjectRequest, ©ObjectResponse);
// 低频存储转为冷存储
CopyObjectRequest copyObjectRequest(destBucketName, destKey, srcBucketName, srcKey);
copyObjectRequest.mutable_meta()->set_storage_class("COLD");
CopyObjectResponse copyObjectResponse;
ret = client.copy_object(copyObjectRequest, ©ObjectResponse);
获取文件下载URL
用户可以通过如下代码获取指定Object的URL:
std::string generatePresignedUrl(Client& client, const std::string& bucketName, const std::string& objectKey, int expirationInSeconds) {
//指定用户需要获取的Object所在的Bucket名称、该Object名称、URL的有效时长
return client.generate_url(bucketName, objectKey, expirationInSeconds);
}
说明:
- 用户在调用该函数前,需要手动设置endpoint为所属区域域名。百度智能云目前开放了多区域 支持,请参考区域选择说明。目前支持“华北-北京”、“华南-广州”和“华东-苏州”三个区域。北京区域:
http://bj.bcebos.com
,广州区域:http://gz.bcebos.com
,苏州区域:http://su.bcebos.com
。expirationInSeconds
为指定的URL有效时长,时间从当前时间算起,为可选参数,不配置时系统默认值为1800秒。如果要设置为永久不失效的时间,可以将expirationInSeconds
参数设置为 -1,不可设置为其他负数。- 如果预期获取的文件时公共可读的,则对应URL链接可通过简单规则快速拼接获取:
http://$region.bcebos.com/$bucket/$object
列举存储空间中的文件
BOS SDK支持用户通过以下两种方式列举出object:
- 简单列举
- 通过参数复杂列举
除此之外,用户还可在列出文件的同时模拟文件夹
简单列举
当用户希望简单快速列举出所需的文件时,可通过listObjects方法返回ListObjectsResponse对象,ListObjectsResponse对象包含了此次listObject请求的返回结果。用户可以通过ListObjectsResponse中的getContents方法获取所有Object的描述信息。
void listObjects(Client& client, const std::string bucketName) {
ListObjectsRequest listObjectsRequest(bucketName);
// 获取指定Bucket下的所有Object信息
ListObjectsResponse listObjectsResponse;
int ret = client.list_objects(listObjectsRequest, &listObjectsResponse);
if (ret != 0) return;
if (listObjectResponse.is_fail()) {
return;
}
// 遍历所有Object
for (const ObjectSummary& objectSummary : listObjectsResponse.contents()) {
std::cout << "ObjectKey: " << objectSummary.key << std::endl;
}
}
注意:
- 默认情况下,如果Bucket中的Object数量大于1000,则只会返回1000个Object,并且返回结果中is_truncated值为True,并返回next_marker做为下次读取的起点。
- 若想增大返回Object的数目,可以使用marker参数分次读取。
通过参数复杂列举
除上述简单列举外,用户还可通过设置ListObjectsRequest的参数实现各种灵活的查询功能。ListObjectsRequest的可设置的参数如下:
参数 | 功能 | 使用方式 |
---|---|---|
prefix | 限定返回的object key必须以prefix作为前缀 | set_prefix(const std::string& prefix) |
delimiter | 是一个用于对Object名字进行分组的字符所有名字包含指定的前缀且第一次出现。Delimiter字符之间的object作为一组元素: CommonPrefixes | set_delimiter(const std::string& delimiter) |
marker | 设定结果从marker之后按字母排序的第一个开始返回 | set_marker(const std::string& marker) |
max_keys | 限定此次返回object的最大数,最大为1000,默认值是1000。如果指定的值大于1000,按1000操作 | set_max_keys(int maxKeys) |
注意:
- 如果有Object以Prefix命名,当仅使用Prefix查询时,返回的所有Key中仍会包含以Prefix命名的Object,详见递归列出目录下所有文件。
- 如果有Object以Prefix命名,当使用Prefix和Delimiter组合查询时,返回的所有Key中会有Null,Key的名字不包含Prefix前缀,详见查看目录下的文件和子目录。
下面我们分别以几个案例说明通过参数列举的方法:
指定最大返回条数
// 指定最大返回条数为500
ListObjectsRequest listObjectsRequest("bucketName");
listObjectsRequest.set_max_keys(500);
ListObjectsResponse listObjectsResponse;
int ret = client.list_objects(listObjectsRequest, &listObjectsResponse);
if (ret != 0) return;
...//response异常处理
for(const ObjectSummary& objectSummary :listObjectsResponse.contents()) {
std::cout << "ObjectKey: " << objectSummary.key << std::endl;
}
返回指定前缀的object
// 指定返回前缀为test的object
ListObjectsRequest listObjectsRequest("bucketName");
listObjectsRequest.set_prefix("test");
ListObjectsResponse listObjectsResponse;
int ret = client.list_objects(listObjectsRequest, &listObjectsResponse);
if (ret != 0) return;
...//response异常处理
for(const ObjectSummary& objectSummary :listObjectsResponse.contents()) {
std::cout << "ObjectKey: " << objectSummary.key << std::endl;
}
从指定Object后返回
// 用户可以定义不包括某object,从其之后开始返回
ListObjectsRequest listObjectsRequest("bucketName");
listObjectsRequest.set_marker("object");
ListObjectsResponse listObjectsResponse;
int ret = client.list_objects(listObjectsRequest, &listObjectsResponse);
if (ret != 0) return;
...//response异常处理
for(const ObjectSummary& objectSummary :listObjectsResponse.contents()) {
std::cout << "ObjectKey: " << objectSummary.key << std::endl;
}
分页获取所有Object
// 用户可设置每页最多500条记录
ListObjectsRequest listObjectsRequest("bucketName");
listObjectsRequest.set_max_keys(500);
ListObjectsResponse listObjectsResponse;
int ret = client.list_objects(listObjectsRequest, &listObjectsResponse);
if (ret != 0) return;
...//response异常处理
bool isTruncated = true;
while (isTruncated) {
ListObjectsResponse listObjectsResponse;
client.list_objects(listObjectsRequest, &listObjectsResponse);
isTruncated = listObjectsResponse.is_truncated();
if (listObjectsResponse.next_marker() != "") {
listObjectsRequest.set_marker(listObjectsResponse.next_marker());
}
}
分页获取所有特定Object后的结果
// 用户可设置每页最多500条记录,并从某特定object之后开始获取
ListObjectsRequest listObjectsRequest("bucketName");
listObjectsRequest.set_max_keys(500);
listObjectsRequest.set_marker("object");
ListObjectsResponse listObjectsResponse;
int ret = client.list_objects(listObjectsRequest, &listObjectsResponse);
if (ret != 0) return;
bool isTruncated = true;
while (isTruncated) {
ListObjectsResponse listObjectsResponse;
client.list_objects(listObjectsRequest, &listObjectsResponse);
isTruncated = listObjectsResponse.is_truncated();
if (listObjectsResponse.next_marker() != "") {
listObjectsRequest.set_marker(listObjectsResponse.next_marker());
}
}
分页获取所有指定前缀的Object结果
// 用户可设置分页获取指定前缀的Object,每页最多500条记录
ListObjectsRequest listObjectsRequest("bucketName");
listObjectsRequest.set_max_keys(500);
listObjectsRequest.set_prefix("object");
ListObjectsResponse listObjectsResponse;
int ret = client.list_objects(listObjectsRequest, &listObjectsResponse);
if (ret != 0) return;
bool isTruncated = true;
while (isTruncated) {
ListObjectsResponse listObjectsResponse;
client.list_objects(listObjectsRequest, &listObjectsResponse);
isTruncated = listObjectsResponse.is_truncated();
if (listObjectsResponse.next_marker() != "") {
listObjectsRequest.set_marker(listObjectsResponse.next_marker());
}
}
list_object
方法返回的ListObjectsResponse
解析类中可供调用的参数有:
参数 | 说明 |
---|---|
name | Bucket名称 |
prefix | 匹配以prefix开始到第一次出现Delimiter字符之间的object作为一组元素返回 |
marker | 本次查询的起点 |
max_keys | 请求返回的最大数目 |
is_truncated | 指明是否所有查询都返回了;false-本次已经返回所有结果,true-本次还没有返回所有结果 |
contents | 返回的一个Object的容器 |
+key | Object名称 |
+last_modified | 此Object最后一次被修改的时间 |
+etag | Object的HTTP协议实体便签 |
+storage_class | Object的存储形态 |
+size | Object的内容的大小(字节数) |
+owner_id | Bucket Owner的用户ID |
+owner_display_name | Bucket Owner的名称 |
模拟文件夹功能
在BOS的存储结果中是没有文件夹这个概念的,所有元素都是以Object来存储,但BOS的用户在使用数据时往往需要以文件夹来管理文件。 因此,BOS提供了创建模拟文件夹的能力,其本质上来说是创建了一个size为0的Object。对于这个Object可以上传下载,只是控制台会对以”/“结尾的Object以文件夹的方式展示。
用户可以通过 Delimiter 和 Prefix 参数的配合模拟出文件夹功能。Delimiter 和 Prefix 的组合效果是这样的:
如果把 Prefix 设为某个文件夹名,就可以罗列以此 Prefix 开头的文件,即该文件夹下递归的所有的文件和子文件夹(目录)。文件名在Contents中显示。 如果再把 Delimiter 设置为 “/” 时,返回值就只罗列该文件夹下的文件和子文件夹(目录),该文件夹下的子文件名(目录)返回在 CommonPrefixes 部分,子文件夹下递归的文件和文件夹不被显示。
如下是几个应用方式:
列出Bucket内所有文件
当用户需要获取Bucket下的所有文件时,可以参考分页获取所有Object
递归列出目录下所有文件
可以通过设置 Prefix
参数来获取某个目录下所有的文件:
// 构造ListObjectsRequest请求
ListObjectsRequest listObjectsRequest("bucketName");
// 递归列出fun目录下的所有文件
listObjectsRequest.set_prefix("fun/");
ListObjectsResponse listObjectsResponse;
int ret = client.list_objects(listObjectsRequest, &listObjectsResponse);
if (ret != 0) return;
...//response异常处理
std::cout << "ObjectKeys:" << std::endl;
// 遍历所有Object
for(const ObjectSummary& objectSummary :listObjectsResponse.contents()) {
std::cout << objectSummary.key << std::endl;
}
输出:
ObjectKeys:
fun/
fun/movie/001.avi
fun/movie/007.avi
fun/test.jpg
查看目录下的文件和子目录
在 Prefix
和 Delimiter
结合的情况下,可以列出目录下的文件和子目录:
// 构造ListObjectsRequest请求
ListObjectsRequest listObjectsRequest("bucketName");
// "/" 为文件夹的分隔符
listObjectsRequest.set_delimiter("/");
// 列出fun目录下的所有文件和文件夹
listObjectsRequest.set_prefix("fun/");
ListObjectsResponse listObjectsResponse;
int ret = client.list_objects(listObjectsRequest, &listObjectsResponse);
if (ret != 0) return;
// 遍历所有Object
std::cout << "\nObjectKeys:" << std::endl;
for(const ObjectSummary& objectSummary :listObjectsResponse.contents()) {
std::cout << objectSummary.key << std::endl;
}
// 遍历所有CommonPrefix
std::cout << "CommonPrefixs:" << std::endl;
for (const std::string& commonPrefix : listObjectsResponse.common_prefixes()) {
std::cout << commonPrefix << std::endl;
}
输出:
ObjectKeys:
fun/
fun/test.jpg
CommonPrefixs:
fun/movie/
返回的结果中, ObjectSummary
的列表contents
中给出的是fun目录下的文件。而 common_prefixes
的列表中给出的是fun目录下的所有子文件夹。可以看出 fun/movie/001.avi
, fun/movie/007.avi
两个文件并没有被列出来,因为它们属于 fun
文件夹下的 movie
目录。
列举Bucket中object的存储属性
当用户完成上传后,如果需要查看指定Bucket中的全部Object的storage class属性,可以通过如下代码实现:
public void listObjectsStorageClass(){
ListObjectsRequest listObjectsRequest("bucketName");
ListObjectsResponse listObjectsResponse;
int ret = client.list_objects(listObjectsRequest, &listObjectsResponse);
std::vector<ObjectSummary> objectList = listObjectResponse.contents();
for(int i = 0; i<objectList.size(); i++) {
std::cout << objectList[i].storage_class() << std::endl;
}
}
并发接口
并发接口基于curl的多路IO实现,仍然使用单线程(调用者线程),不会额外创建线程,但是通过非阻塞IO提高请求数的吞吐。 基本接口如下:
struct BceRequestContext {
BceRequest *request;
BceResponse *response;
bool is_own;
};
int send_request(int n, BceRequestContext ctx[], int max_parallel = 0);
Context的request和response成员用来放置请求对象的指针;is_own为true时将在Context析构时主动释放request和response指针。 send_request方法中 n为需要并发执行的请求数,max_parallel为最大并发数(默认将按options设置的来); 当请求数超过最大并发数时,剩下的请求将排队等待,直到有请求完成再加入并发; 通过方法的返回值判断是否所有的请求都执行成功,同时可以遍历ctx数组挨个判断每个response的结果。
权限控制
设置对象的访问权限
目前BOS支持两种方式设置ACL. 具体可参考权限控制
第一种是使用Canned Acl,在put_object_acl的时候,通过头域的"x-bce-acl", "x-bce-grant-read", 或者 "x-bce-grant-permission"来设置对象的访问权限,当前可设置的权限包括private和public-read,三种类型的header不可以同时在一个请求中出现.
第二种方式是以自定义Acl样式, 具体可通过上传其json字符串, 设置access_control_list结构体, 或者直接上传ACL文件. 具体可参考权限控制概述
设置Canned ACL
Canned ACL是预定义的访问权限,用户可选择对某个对象进行设置,支持三种接口:
PutObjectAclRequest putObjectAclRequest(bucketName, objectKey);
PutObjectAclResponse putObjectAclResponse;
// 1. 使用x-bce-acl Header设置
// cannedAcl支持:private、public-read
std::string cannedAcl="public-read";
putObjectAclRequest.set_canned_acl(cannedAcl);
// 2. 使用x-bce-grant-read / x-bce-grant-read-permission Header设置
// idStrings为id集合, 可一次传入多个id, 用逗号隔开, 字符串固定格式为:"id=/"xxxxx/", id=/"xxxxx/"";
std::string idStrings="id=\"77f47fbbc29d41xxxxxxxxxx6\"";
putObjectAclRequest.set_xbce_grant_read(idStrings);
putObjectAclRequest.set_xbce_grant_full_control(idStrings);
int ret = client.put_object_acl(putObjectAclRequest, &putObjectAclResponse);
if (ret) {
LOGF(WARN, "client err: %d", ret);
}
if (putObjectAclResponse.is_fail()) {
LOGF(WARN,"put_object_acl: [status_code = %d], [message = %s], [requestid = %s]",
putObjectAclResponse.status_code(),
putObjectAclResponse.error().message().c_str(),
putObjectAclResponse.error().request_id().c_str());
}
注意: 三种方式set_canned_acl(),set_xbce_grant_read(),set_xbce_grant_full_control()
一次put_object_acl()调用只能设置上述三种接口其中之一.
设置自定义ACL
用户可参考如下代码设置Bucket内的对象的自定义访问权限,支持三种不同参数:
PutObjectAclRequest putObjectAclRequest(bucketName, objectKey);
PutObjectAclResponse putObjectAclResponse;
// 1. 通过上传acl json串
std::string jsonAcl =
"{\"accessControlList\":[{\"grantee\":[{\"id\":\"*\"}],\"permission\":[\"READ\"]},{"
"\"grantee\":[{\"id\":\"cb5f8xxxxxxxxxx82bbc\"}],\"permission\":["
"\"FULL_CONTROL\"]}]}";
std::string cannedAcl="public-read";
putObjectAclRequest.set_json_acl(jsonAcl);
// 2. 上传acl文件
std::string aclFilePath = "/tmp/acl.json"
int setRet = putObjectAclRequest.set_acl_file(aclFilePath);
if (ret) {
LOGF(WARN, "client set_acl_file: %d", ret);
}
// 3. 通过设置access_control_list数据
std::vector<Grant> grants;
Grant grant;
grantee.id = "77fxxxxxxxxxxx5fa406";
grant.grantee.push_back(grantee);
grant.permission.push_back("READ");
grants.push_back(grant);
putObjectAclRequest.set_access_control_list(grants);
int ret = client.put_object_acl(putObjectAclRequest, &putObjectAclResponse);
if (ret) {
LOGF(WARN, "client err: %d", ret);
}
if (putObjectAclResponse.is_fail()) {
LOGF(WARN,"put_object_acl: [status_code = %d], [message = %s], [requestid = %s]",
putObjectAclResponse.status_code(),
putObjectAclResponse.error().message().c_str(),
putObjectAclResponse.error().request_id().c_str());
}
获取对象的访问权限
如下代码可获取一个对象的访问权限:
GetObjectAclRequest getObjectAclRequest(bucketName, objectKey);
GetObjectAclResponse getObjectAclResponse;
int ret = client()->get_object_acl(getObjectAclRequest, &getObjectAclResponse);
if (ret) {
LOGF(WARN, "get_object_acl err: %d", ret);
}
if (getObjectAclResponse.is_fail()) {
LOGF(WARN,
"get_object_acl: [status_code = %d], [message = %s], [requestid = %s]",
getObjectAclResponse.status_code(),
getObjectAclResponse.error().message().c_str(),
getObjectAclResponse.error().request_id().c_str());
}
//获取具体权限(两种方式)
std::vector<Grant> objectAcl = getObjectAclResponse.access_control_list();
std::string objectAclJsonStr = getObjectAclResponse.json_access_control_list();
//acl具体结构
struct Grantee {
std::string id;
};
struct Grant {
std::vector<Grantee> grantee;
std::vector<std::string> permission;
//std::vector<std::string> resource;
//std::vector<std::string> notResource;
//Condition condition;
//std::string effect;
}
注意: acl涉及到的具体结构体Grant, 在bucket acl和object acl体系中共用
目前object acl体系中只用到其中grantee, permission两个字段.
其余注释的字段均为bucket acl体系独有.
删除对象的访问权限
对设置过访问权限的对象,可以调用此接口进行删除:
DeleteObjectAclRequest deleteObjectAclRequest(BUCKET_NAME, OBJECT_NAME);
DeleteObjectAclResponse deleteObjectAclResponse;
int ret = client.delete_object_acl(deleteObjectAclRequest, &deleteObjectAclResponse);
if (ret) {
LOGF(WARN, "client err: %d", ret);
}
if (deleteObjectAclResponse.is_fail()) {
LOGF(WARN, "put_object_acl: [status_code = %d], [message = %s], [requestid = %s]",
deleteObjectAclResponse.status_code(),
deleteObjectAclResponse.error().message().c_str(),
deleteObjectAclResponse.error().request_id().c_str());
}
删除文件
删除单个文件
可参考如下代码删除了一个Object:
int deleteObject(Client& client,const std::string bucketName, const std::string objectKey) {
// 删除Object
return client.delete_object(bucketName, objectKey); //指定要删除的Object所在Bucket名称和该Object名称
}
删除多个文件
删除多个文件deleteMultiObject
使用并发接口,提高请求数的吞吐,代码如下:
int deleteMultiObject(Client& client, const& std::string bucketName, const std::vector<std::string>& objects) {
//批量删除object
std::vector<BceRequestContext> ctx(objects.size());
int i = 0;
for (const std::string& objectKey : objects) {
//构造上下文信息
ctx[i].request = new DeleteObjectRequest(bucketName, objectKey);
ctx[i].response = new DeleteObjectResponse;
ctx[i].is_own = true;//自动析构request和response
++i;
}
//并发请求
int ret = client.send_request((int)ctx.size(), &ctx.front());
if (ret != 0) return ret;
//处理response
for (size_t i = 0; i < ctx.size(); ++i) {
DeleteObjectRequest* request = (DeleteObjectRequest*)ctx[i].request;
DeleteObjectResponse* response = (DeleteObjectResponse*)ctx[i].response;
if (response->is_fail()) {//有请求删除失败,处理这种情况
std::cout << "delete objectKey=" << request->object_name() << " failed"
<< " reason: " << response->error().message();
ret = RET_SERVICE_ERROR;
}
}
return ret;
}
查看文件是否存在
用户可通过如下操作查看某文件是否存在:
HeadObjectRequest request(bucketName, objectKey);
HeadObjectResponse response;
int ret = client.head_object(request, &response);
if (response.status_code() == 404) {
std::cout << "ObjectKey not exist!" << std::endl;
}
获取及更新文件元信息
文件元信息(Object Metadata),是对用户上传BOS的文件的属性描述,分为两种:HTTP标准属性(HTTP Headers)和User Meta(用户自定义元信息)。
获取文件元信息
修改文件元信息
BOS修改Object的Metadata通过拷贝Object实现。即拷贝Object的时候,把目的Bucket设置为源Bucket,目的Object设置为源Object,并设置新的Metadata,通过拷贝自身实现修改Metadata的目的。如果不设置新的Metadata,则报错。
void setObjectMeta(Client& client, const std::string& bucketName, const std::string& objectKey, ObjectMetaData* newObjectMetadata) {
CopyObjectRequest request(bucketName, objectKey, bucketName, objectKey);
// 设置新的ObjectMetaData
request.set_meta(newObjectMetaData);
// 拷贝Object
CopyObjectResponse copyObjectResponse;
int ret = client.copy_object(request, ©ObjectResponse);
if (ret != 0) {
return;
}
// 打印结果
std::cout << "ETag: " << copyObjectResponse.etag() << " LastModified: " << copyObjectResponse.last_modified() << std::endl;
}
拷贝文件
拷贝一个文件
用户可以通过copy_object函数拷贝一个Object,如下代码所示:
void copyObject(Client& client, std::string destBucketName, std::string destKey, const std::string& srcBucketName, const std::string& srcKey) {
// 拷贝Object
int ret = client.copy_object(srcBucketName, srcKey, destBucketName, destKey, "STANDARD");//目标Object为标准存储
}
CopyObjectResponse
对象中包含了新Object的ETag和修改时间。
用户也可以通过 CopyObjectRequest
实现Object的拷贝,如下代码所示:
// 初始化Client
Client client = ...;
// 创建CopyObjectRequest对象
CopyObjectRequest copyObjectRequest(destBucketName, destKey, srcBucketName, srcKey);
// 设置Metadata
ObjectMetaData* userMetadata = copyObjectRequest.mutable_meta();
userMetadata->set_user_meta("userMetaKey", "userMetaValue");
// 复制Object
CopyObjectResponse copyObjectResponse;
int ret = client.copy_object(copyObjectRequest, ©ObjectResponse);
std::cout << "ETag: " + copyObjectResponse.etag() <<
" LastModified: " << copyObjectResponse.last_modified());
同步Copy功能
当前BOS的CopyObject接口是通过同步方式实现的。同步方式下,BOS端会等待Copy实际完成才返回成功。同步Copy能帮助用户更准确的判断Copy状态,但用户感知的复制时间会变长,且复制时间和文件大小成正比。
同步Copy方式更符合业界常规,提升了与其它平台的兼容性。同步Copy方式还简化了BOS服务端的业务逻辑,提高了服务效率。
分块拷贝
除了通过CopyObject接⼝拷贝文件以外,BOS还提供了另外一种拷贝模式——Multipart Upload Copy。用户可以在如下的应用场景内(但不仅限于此),使用Multipart Upload Copy,如:
- 需要支持断点拷贝。
- 拷贝超过5GB大小的文件。
- 网络条件较差,和BOS的服务器之间的连接经常断开。
下面将介绍分步实现三步拷贝。
三步拷贝包含init、“拷贝分块”和complete三步,其中init和complete的操作同分块上传一致。
为了便于理解,下面提供三步拷贝完整代码:
// 第一步 init
InitMultiUploadRequest initMultiUploadRequest("targetBucketName","targetObjectName");
InitMultiUploadResponse initMultiUploadResponse;
client.init_multipart_upload(initMultiUploadRequest, &initMultiUploadResponse);
//获取Object大小
HeadObjectRequest request("targetBucketName", "targetObjectName");
HeadObjectResponse response;
client.head_object(request, &response);
if (response.is_fail()) {
return RET_SERVICE_ERROR;
}
long left_size = response.meta().content_length();
// 第二步 分块拷贝
long skipBytes = 0;
int partNumber = 1;
std::vector<part_t> partETags;
while (left_size > 0) {
long partSize = 1024 * 1024 * 5L;
if (left_size < partSize) {
partSize = left_size;
}
CopyPartRequest copyPartRequest;
copyPartRequest.set_upload_id(initMultiUploadResponse.upload_id());
copyPartRequest.set_bucket_name("targetBucketName");
copyPartRequest.set_object_name("targetObjectName");
copyPartRequest.set_source_bucket_name("sourceBucketName");
copyPartRequest.set_source_object_name("sourceObjectName");
copyPartRequest.set_range(skipBytes, skipBytes + partSize - 1);
copyPartRequest.set_part_number(partNumber);
CopyPartResponse copyPartResponse;
client.copy_part(copyPartRequest, ©PartResponse);
// 将返回的partNumber和ETag保存到vector
part_t partInfo;
partInfo.etag = copyPartResponse.etag();
partInfo.part_number = partNumber;
partETags.push_back(partInfo);
left_size -= partSize;
skipBytes += partSize;
partNumber+=1;
}
// 第三步 complete
CompleteMultipartUploadRequest completeMultipartUploadRequest("targetBucketName", "targetObjectName", initMultiUploadResponse.upload_id());
//添加part信息,即part合并顺序
for (part_t partInfo : partEtags) {
completeMultipartUploadRequest.add_part(partInfo.part_number, partInfo.etag);
}
// 完成分块上传
CompleteMultipartUploadResponse completeMultipartUploadResponse;
int ret = client.complete_multipart_upload(completeMultipartUploadRequest, &completeMultipartUploadResponse);
// 打印Object的ETag
std::cout << completeMultipartUploadResponse.etag() << std::endl;
注意:
- offset参数以字节为单位,为分块的开始偏移位置。
- size参数以字节为单位,定义每个分块的大小,除最后一个Part以外,其他的Part大小都要大于 5MB。
使用便利接口
parallel_copy
使用并发接口,提高请求吞吐,实现分块分块拷贝
client.parallel_copy(srcBucketName, sourceObjectName, destBucketName, destObjectName);
归档存储
上传
std::string filename = "test_archive";
FileInputStream file(filename);
PutObjectRequest request(bucket, "test_archive", &file);
request.mutable_meta()->set_storage_class("ARCHIVE");
PutObjectResponse result;
client.put_object(request, &result);
取回
RestoreObjectRequest request(bucket, ""test_archive"");
RestoreObjectResponse response;
client.restore_object(request, &response);
print_common_response(response);