上传文件
在BOS中,用户操作的基本数据单元是Object。Object包含Key、Meta和Data。其中,Key是Object的名字;Meta是用户对该Object的描述,由一系列Name-Value对组成;Data是Object的数据。
BOS Java SDK提供了丰富的文件上传接口,可以通过以下方式上传文件:
- 简单上传
- 追加上传
- 分片上传
- 断点续传上传
简单上传
BOS在简单上传的场景中,支持以指定文件形式、以数据流方式、以二进制串方式、以字符串方式执行Object上传,请参考如下代码,或简单上传Demo。
1public void PutObject(BosClient client, String bucketName, String objectKey, byte[] byte1, String string1){
2 // 获取指定文件
3 File file = new File("/path/to/file.zip");
4 // 获取数据流
5 InputStream inputStream = new FileInputStream("/path/to/test.zip");
6
7 // 以文件形式上传Object
8 PutObjectResponse putObjectFromFileResponse = client.putObject(bucketName, objectKey, file);
9 // 以数据流形式上传Object
10 PutObjectResponse putObjectResponseFromInputStream = client.putObject(bucketName, objectKey, inputStream);
11 // 以二进制串上传Object
12 PutObjectResponse putObjectResponseFromByte = client.putObject(bucketName, objectKey, byte1);
13 // 以字符串上传Object
14 PutObjectResponse putObjectResponseFromString = client.putObject(bucketName, objectKey, string1);
15 // 创建空目录,objectKey需要以正斜线结尾,例如test/
16 // BOS控制台默认会将以正斜线(/)结尾的对象,作为文件目录的形式展示
17 PutObjectResponse putObjectResponseFromString = client.putObject(bucketName, objectKey, "");
18
19 // 打印ETag
20 System.out.println(putObjectFromFileResponse.getETag());
21}
Object以文件的形式上传到BOS中,PutObject函数支持不超过5GB的Object上传。在PutObject请求处理成功后,BOS会在Header中返回Object的ETag作为文件标识。
设置文件元信息
文件元信息(Object Meta),是对用户在向BOS上传文件时,同时对文件进行的属性描述,主要分为分为两种:设置HTTP标准属性(HTTP Headers)和用户自定义的元信息。
- 设定Object的Http Header
BOS Java 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被下载时的网页的缓存行为 | 无 |
x-bce-content-crc32 | 上传object的CRC值(循环冗余校验码) | 无 |
参考代码如下:
1// 初始化上传输入流
2ObjectMetadata meta = new ObjectMetadata();
3
4// 设置ContentLength大小
5meta.setContentLength(1000);
6
7// 设置ContentType
8meta.setContentType("application/json");
9
10// 设置cache-control
11meta.setCacheControl("no-cache");
12
13// 设置x-bce-content-crc32,CRC32校验码需自行计算,示例格式如下
14meta.setxBceCrc("0x74E947D0");
15
16client.putObject(bucketName, objectKey, content, meta);
- 用户自定义元信息
BOS支持用户自定义元数据来对Object进行描述。如下代码所示:
1// 设置自定义元数据name的值为my-data
2meta.addUserMetadata("name", "my-data");
3
4// 上传Object
5client.putObject(bucketName, objectKey, content, meta);
提示:
- 在上面代码中,用户自定义了一个名字为”name”,值为”my-data”的元数据
- 当用户下载此Object的时候,此元数据也可以一并得到
- 一个Object可以有多个类似的参数,但所有的User Meta总大小不能超过2KB
上传Object时设置存储类型
BOS支持标准存储, 低频存储和冷存储,上传Object并存储为低频存储类型通过指定StorageClass实现,三种存储类型对应的参数如下:
存储类型 | 参数 |
---|---|
标准存储 | STANDARD |
低频存储 | STANDARD_IA |
冷存储 | COLD |
以低频存储为例,代码如下:
1public void putObjectStorageClass(){
2 PutObjectRequest request = new PutObjectRequest(bucketName, key, file);
3 request.withStorageClass(BosClient.STORAGE_CLASS_STANDARD_IA);
4 client.putObject(request);
5}
追加上传
上文介绍的简单上传方式,创建的Object都是Normal类型,用户不可再进行追加写,这在日志、视频监控、视频直播等数据复写较频繁的场景中使用不方便。
正因如此,百度智能云BOS特别支持了AppendObject,即以追加写的方式上传文件。通过AppendObject操作创建的Object类型为Appendable Object,可以对该Object追加数据。AppendObject大小限制为0~5G。
通过AppendObject方式上传示例代码如下,或参考追加上传Demo。
1public void AppendObject(BosClient client, String bucketName, String objectKey, byte[] byte1, String string1) {
2 // 获取指定文件
3 File file = new File("/path/to/file.zip");
4 // 获取数据流
5 InputStream inputStream = new FileInputStream("/path/to/test.zip");
6
7 // 以文件形式上传Object
8 AppendObjectResponse appendObjectFromFileResponse = client.appendObject(bucketName, objectKey, file);
9 // 以数据流形式上传Object
10 AppendObjectResponse appendObjectResponseFromInputStream = client.appendObject(bucketName, objectKey, inputStream);
11 // 以二进制串上传Object
12 AppendObjectResponse appendObjectResponseFromByte = client.appendObject(bucketName, objectKey, byte1);
13 // 以字符串上传Object
14 AppendObjectResponse appendObjectResponseFromString = client.appendObject(bucketName, objectKey, string1);
15
16 // 打印ETag
17 System.out.println(appendObjectFromFileResponse.getETag());
18 // 打印NextAppendOffset
19 System.out.println(appendObjectFromFileResponse.getNextAppendOffset());
20 // 打印ContentMd5
21 System.out.println(appendObjectFromFileResponse.getContentMd5());
22
23 // 追加上传的示例,需要在请求中加上下次追加写的位置
24 Long nextAppendOffset = appendObjectFromFileResponse.getNextAppendOffset();
25 AppendObjectRequest appendObjectFromFileRequest = new AppendObjectRequest(bucketName,objectKey,file);
26 appendObjectFromFileRequest.setOffset(nextAppendOffset);
27 AppendObjectResponse appendObjectFromFileResponse = client.appendObject(appendObjectFromFileRequest);
28 }
注意:
- 追加上传需要指定追加写的位置,否则已写数据会被覆盖
- 不支持并发,如果确实有多客户端写入的需求,需要自行维护追加写位置,避免冲突
分块上传
除了通过简单上传及追加上传方式将文上传到BOS以外,BOS还提供了另外一种上传模式 —— Multipart Upload。用户可以在如下的应用场景内(但不仅限于此),使用Multipart Upload上传模式,如:
- 需要支持断点上传。
- 上传超过5GB大小的文件。
- 网络条件较差,和BOS的服务器之间的连接经常断开。
- 需要流式地上传文件。
- 上传文件之前,无法确定上传文件的大小。
下面将一步步介绍Multipart Upload的实现。假设有一个文件,本地路径为 /path/to/file.zip
,由于文件比较大,将其分块传输到BOS中。或参考分块上传Demo
初始化Multipart Upload
使用 initiateMultipartUpload
方法来初始化一个分块上传事件:
1// 开始Multipart Upload
2InitiateMultipartUploadRequest initiateMultipartUploadRequest =
3 new InitiateMultipartUploadRequest(bucketName, objectKey);
4InitiateMultipartUploadResponse initiateMultipartUploadResponse =
5 client.initiateMultipartUpload(initiateMultipartUploadRequest);
6
7// 打印UploadId
8System.out.println("UploadId: " + initiateMultipartUploadResponse.getUploadId());
initiateMultipartUpload
的返回结果中含有 UploadId
,它是区分分块上传事件的唯一标识,在后面的操作中,我们将用到它。
- 上传低频存储类型Object的初始化
初始化低频存储的一个分块上传事件:
1public void putMultiUploadStorageClass(){
2 InitiateMultipartUploadRequest iniReq = new InitiateMultipartUploadRequest(bucketName, key);
3 iniReq.withStorageClass(BosClient.STORAGE_CLASS_STANDARD_IA);
4 client.initiateMultipartUpload(iniReq);
5}
- 上传冷存储类型Object的初始化
初始化低频存储的一个分块上传事件:
1public void putMultiUploadStorageClass(){
2 InitiateMultipartUploadRequest iniReq = new InitiateMultipartUploadRequest(bucketName, key);
3 iniReq.withStorageClass(BosClient.STORAGE_CLASS_COLD);
4 client.initiateMultipartUpload(iniReq);
5}
上传分块
接着,把文件分块上传。
1// 设置每块为 5MB
2final long partSize = 1024 * 1024 * 5L;
3
4File partFile = new File("/path/to/file.zip");
5
6// 计算分块数目
7int partCount = (int) (partFile.length() / partSize);
8if (partFile.length() % partSize != 0){
9 partCount++;
10}
11
12// 新建一个List保存每个分块上传后的ETag和PartNumber
13List<PartETag> partETags = new ArrayList<PartETag>();
14
15for(int i = 0; i < partCount; i++){
16 // 获取文件流
17 FileInputStream fis = new FileInputStream(partFile);
18
19 // 跳到每个分块的开头
20 long skipBytes = partSize * i;
21 fis.skip(skipBytes);
22
23 // 计算每个分块的大小
24 long size = partSize < partFile.length() - skipBytes ?
25 partSize : partFile.length() - skipBytes;
26
27 // 创建UploadPartRequest,上传分块
28 UploadPartRequest uploadPartRequest = new UploadPartRequest();
29 uploadPartRequest.setBucketName(bucketName);
30 uploadPartRequest.setKey(objectKey);
31 uploadPartRequest.setUploadId(initiateMultipartUploadResponse.getUploadId());
32 uploadPartRequest.setInputStream(fis);
33 uploadPartRequest.setPartSize(size);
34 uploadPartRequest.setPartNumber(i + 1);
35 UploadPartResponse uploadPartResponse = client.uploadPart(uploadPartRequest);
36
37 // 将返回的PartETag保存到List中。
38 partETags.add(uploadPartResponse.getPartETag());
39
40 // 关闭文件
41 fis.close();
42}
上面代码的核心是调用 UploadPart
方法来上传每一个分块,但是要注意以下几点:
- UploadPart 方法要求除最后一个Part以外,其他的Part大小都要大于等于100KB。但是Upload Part接口并不会立即校验上传Part的大小;只有当Complete Multipart Upload的时候才会校验。
- 为了保证数据在网络传输过程中不出现错误,建议您在
UploadPart
后,使用每个分块BOS返回的Content-MD5值分别验证已上传分块数据的正确性。当所有分块数据合成一个Object后,不再含MD5值。 - Part号码的范围是1~10000。如果超出这个范围,BOS将返回InvalidArgument的错误码。
- 每次上传Part时都要把流定位到此次上传块开头所对应的位置。
- 每次上传Part之后,BOS的返回结果会包含一个
PartETag
对象,它是上传块的ETag与块编号(PartNumber)的组合,在后续完成分块上传的步骤中会用到它,因此需要将其保存起来。一般来讲这些PartETag
对象将被保存到List中。
完成分块上传
如下代码所示,完成分块上传:
1CompleteMultipartUploadRequest completeMultipartUploadRequest =
2 new CompleteMultipartUploadRequest(bucketName, objectKey, initiateMultipartUploadResponse.getUploadId(), partETags);
3
4// 完成分块上传
5CompleteMultipartUploadResponse completeMultipartUploadResponse =
6 client.completeMultipartUpload(completeMultipartUploadRequest);
7
8// 打印Object的ETag
9System.out.println(completeMultipartUploadResponse.getETag());
上面代码中的 partETags
是第二部中保存的partETag的列表,BOS收到用户提交的Part列表后,会逐一验证每个数据Part的有效性。当所有的数据Part验证通过后,BOS将把这些数据part组合成一个完整的Object。
取消分块上传事件
用户可以使用abortMultipartUpload方法取消分块上传。
1AbortMultipartUploadRequest abortMultipartUploadRequest =
2 new AbortMultipartUploadRequest(bucketName, objectKey, uploadId);
3
4// 取消分块上传
5client.abortMultipartUpload(abortMultipartUploadRequest);
获取未完成的分块上传事件
用户可以使用 listMultipartUploads
方法获取Bucket内未完成的分块上传事件。
1ListMultipartUploadsRequest listMultipartUploadsRequest =
2 new ListMultipartUploadsRequest(bucketName);
3
4// 获取Bucket内所有上传事件
5ListMultipartUploadsResponse listing = client.listMultipartUploads(listMultipartUploadsRequest);
6
7// 遍历所有上传事件
8for (MultipartUploadSummary multipartUpload : listing.getMultipartUploads()) {
9 System.out.println("Key: " + multipartUpload.getKey() + " UploadId: " + multipartUpload.getUploadId());
10}
注意:
- 默认情况下,如果Bucket中的分块上传事件的数目大于1000,则只会返回1000个Object,并且返回结果中IsTruncated的值为True,同时返回NextKeyMarker作为下次读取的起点。
- 若想返回更多分块上传事件的数目,可以使用KeyMarker参数分次读取。
获取所有已上传的块信息
用户可以使用 listParts
方法获取某个上传事件中所有已上传的块。
1ListPartsRequest listPartsRequest = new ListPartsRequest(bucketName, objectKey, uploadId);
2
3// 获取上传的所有Part信息
4ListPartsResponse partListing = client.listParts(listPartsRequest);
5
6// 遍历所有Part
7for (PartSummary part : partListing.getParts()) {
8 System.out.println("PartNumber: " + part.getPartNumber() + " ETag: " + part.getETag());
9}
如果需要查看Object的存储类型storage class使用以下代码:
1public void listPartsStorageClass(){
2 ListResponse listPartsResponse = client.listParts(bucketName, key, uploadId);
3 String storageClass = listPartsResponse.getStorageClass();
4}
注意:
- 默认情况下,如果Bucket中的分块上传事件的数目大于1000,则只会返回1000个Object,并且返回结果中IsTruncated的值为True,同时返回NextPartNumberMarker作为下次读取的起点。
- 若想返回更多分块上传事件的数目,可以使用PartNumberMarker参数分次读取。
封装分块上传
在Java SDK中,Bos为用户提供了putSuperObjectFromFile接口,它对分块上传涉及到的initiateMultipartUpload、UploadPart、completeMultipartUpload三个方法进行封装,用户只需调用该接口即可完成分块上传,
1File file = new File("/path/to/file.zip");
2PutSuperObjectRequest request = new PutSuperObjectRequest(bucketName, objectKey, file);
3bosClient.putSuperObjectFromFile(request);
其中PutSuperObjectRequest的参数有:
参数 | 说明 |
---|---|
chunkSize | 分块大小,默认为5MB,不能小于100KB |
nThreads | 分块上传中线程池中线程的数量,默认等于CPU的核数 |
isSuperObjectUploadCanced | 是否取消分块上传 |
File | 上传文件 |
若一个大文件耗时很长,用户想结束分块上传,可调用PutSuperObjectRequest中的cancel()方法设置isSuperObjectUploadCanced为true实现取消分块上传操作。
断点续传上传
当用户向BOS上传大文件时,如果网络不稳定或者遇到程序崩等情况,则整个上传就失败了,失败前已经上传的部分也作废,用户不得不重头再来。这样做不仅浪费资源,在网络不稳定的情况下,往往重试多次还是无法完成上传。 基于上述场景,BOS提供了断点续传上传的能力:
- 当网络情况一般的情况下,建议使用三步上传方式,将object分为5Mb的块,参考分块上传。
- 当您的网络情况非常差,推荐使用appendObject的方式进行断点续传,每次append 较小数据256kb,参考追加上传。
提示
- 断点续传是分片上传的封装和加强,是用分片上传实现的;
- 文件较大或网络环境较差时,推荐使用分片上传;
抓取上传
如下代码用于从指定URL抓取资源,并将资源存储到指定的Bucket中。此操作需要请求者对该Bucket有写权限,每次只能抓取一个Object,且用户可以自定义Object的名称,详情可参考FetchObject接口。
1String fetchurl = "bj.bcebos.com/sourbucket/sourceobject";
2FetchObjectRequest req = new FetchObjectRequest(bucketname, objectname, fetchurl);
3req.setReferer("www.referer.com");
4bosClient.fetchObject(req);
其中FetchObjectRequest的参数有:
参数 | 说明 |
---|---|
source | 抓取文件的源地址 |
mode | 抓取模式,支持异步抓取async和同步抓取sync两种模式,可选MODE_SYNC、MODE_ASYNC |
storageClass | 存储类型,支持标准存储STANDARD、低频存储STANDARD_IA、冷存储COLD和归档存储ARCHIVE,默认存储为STANDARD |
callbackAddress | fetch结果回调地址,若选择异步抓取,抓取完成后会将结果推送到该地址 |
referer | 源站fetch时需要透传的Referer头域 |
userAgent | 源站fetch时需要透传的User-Agent头域 |
获取上传进度
支持在上传过程中实时提供上传进度信息。目前支持PutObject, AppendObject, UploadPart以及PutSuperObjectFromFile四个接口。进度上传接口可以传入自定义数据,非必需。
Java SDK定义的上传进度回调接口如下,您可以在其中定义上传过程中您所需要的操作,如更新界面等等。
1public interface ProgressCallback<T> {
2 void onProgress(long currentSize, long totalSize, T data);
3}
- PutObject示例代码:
1BosProgressCallback<Object> callback = new BosProgressCallback<Object>() {
2 @Override
3 public void onProgress(long currentSize, long totalSize, Object data) {
4 // 打印上传进度
5 System.out.println("put " + currentSize + "/" + totalSize);
6 }
7};
8// 如果需要在onProgress使用自定义数据
9callback.setData(obj);
10PutObjectRequest request = new PutObjectRequest(bucketName, objectKey, file);
11request.setProgressCallback(callback);
12client.putObject(request);
- AppendObject示例代码:
1BosProgressCallback<Object> callback = new BosProgressCallback<Object>() {
2 @Override
3 public void onProgress(long currentSize, long totalSize, Object data) {
4 System.out.println("put " + currentSize + "/" + totalSize);
5 }
6};
7// 如果需要在onProgress使用自定义数据
8callback.setData(obj);
9AppendObjectRequest request = new AppendObjectRequest(bucketName, objectKey, file);
10request.setProgressCallback(callback);
11AppendObjectResponse response = client.appendObject(request);
12Long nextAppendOffset = response.getNextAppendOffset();
- UploadPart示例代码:
1BosProgressCallback<Object> callback = new BosProgressCallback<Object>() {
2 @Override
3 public void onProgress(long currentSize, long totalSize, Object data) {
4 System.out.println("put " + currentSize + "/" + totalSize);
5 }
6};
7// 如果需要在onProgress使用自定义数据
8callback.setData(obj);
9UploadPartRequest request = new UploadPartRequest();
10request.setBucketName(bucketName);
11request.setKey(objectKey);
12request.setUploadId(id);
13request.setInputStream(inputStream);
14request.setPartSize(size);
15request.setPartNumber(i);
16request.setProgressCallback(callback);
17client.uploadPart(request);
- PutSuperObjectFromFile示例代码:
1BosProgressCallback<Object> callback = new BosProgressCallback<Object>() {
2 @Override
3 public void onProgress(long currentSize, long totalSize, Object data) {
4 System.out.println("put " + currentSize + "/" + totalSize);
5 }
6};
7// 如果需要在onProgress使用自定义数据
8callback.setData(obj);
9PutSuperObjectRequest request = new PutSuperObjectRequest(bucketName, objectKey, file);
10request.setProgressCallback(callback);
11client.putSuperObjectFromFile(request);
单链接限速
对象存储BOS的单Bucket带宽是有限制的,当用户的上传或下载占用带宽达到带宽限制阈值时,会返回RequestRateLimitExceeded的错误码。
为保证用户能够正常使用服务,BOS支持在进行上传、下载等行为时进行流量控制,保证大流量服务占用带宽不会对其他应用服务造成影响。
上传接口支持设置指定限速值,限速值的取值范围为819200~838860800,单位为bit/s,即100KB/s~100MB/s。限速值取值必须为数字,BOS将按照指定的限速值对此次请求进行限速,当限速值不在此范围或不合法时将返回400错误码。
- PutObject示例代码:
1PutObjectRequest request = new PutObjectRequest(bucketName, objectKey, file);
2// 指定上传的限速值为819200bit/s,即100KB/s
3request.setTrafficLimitBitPS(819200);
4client.putObject(request);
- AppendObject示例代码:
1AppendObjectRequest request = new AppendObjectRequest(bucketName, objectKey, file);
2// 指定上传的限速值为819200bit/s,即100KB/s
3request.setTrafficLimitBitPS(819200);
4AppendObjectResponse response = client.appendObject(request);
5Long nextAppendOffset = response.getNextAppendOffset();
- PutSuperObjectFromFile示例代码:
1PutSuperObjectRequest request = new PutSuperObjectRequest(bucketName, objectKey, file);
2// 指定上传的限速值为819200bit/s,即100KB/s
3request.setTrafficLimitBitPS(819200);
4client.putSuperObjectFromFile(request);
上传回调
此接口用于BOS在上传文件(Object)时提供同步回调(Callback)给应用服务器。应用服务器指的是用户提供的回调地址对应的服务器。具体描述参考上传回调。Java SDK服务器上传回调使用方式如下:
- PutObject示例代码:
1PutObjectRequest putObjectRequest = new PutObjectRequest("bucketName","objectKey", file);
2String callBackUrl = "[\""+"https://localhost:8080/callback" +"\"]";
3String enCodeUrl = "";
4try {
5 enCodeUrl = Base64.getEncoder().encodeToString(callBackUrl.getBytes(DEFAULT_ENCODING));
6}catch (Exception e){
7 e.printStackTrace();
8}
9putObjectRequest.setxBceProcess("callback/callback,u_" + enCodeUrl);
10PutObjectResponse putObjectResponse = client.putObject(putObjectRequest);
11System.out.println(putObjectResponse.getCallback().getResult());