OSS JAVA SDK
安装OSS JAVA SDK
直接在Eclipse中使用JAR包
步骤如下:
- 在官方网站下载 Open Service Java SDK 。
- 解压文件。
- 将解压后文件夹中的文件: aliyun-openservice-<versionId>.jar 以及lib文件夹下的所有文件拷贝到你的工程文件夹中。
- 在Eclipse右键工程 -> Properties -> Java Build Path -> Add JARs 。
- 选择你拷贝的所有JAR文件。
经过上面几步之后,你就可以在工程中使用OSS JAVA SDK了。
在Maven工程中使用SDK¶
在Maven工程中使用JAVA SDK十分简单,只要在在pom.xml文件中加入依赖就可以了。
在 dependencies 标签内加入如下内容:
<dependency>
<groupId>com.aliyun.openservices</groupId>
<artifactId>aliyun-openservices</artifactId>
<version>1.0.10</version>
</dependency>
version为版本号,随着版本更新可能有变动。
快速入门
在这一章里,您将学到如何用OSS Java SDK完成一些基本的操作。
Step 1. 初始化一个OSSClient
OSSClient是与OSS服务交互的客户端,SDK的OSS操作都是通过OSSClient完成的。
下面代码新建了一个OSSClient:
import com.aliyun.openservices.oss.OSSClient;
public class Sample {
public static void main(String[] args) {
String accessKeyId = "<key>";
String accessKeySecret = "<secret>";
// 初始化一个OSSClient
OSSClient client = new OSSClient(accessKeyId, accessKeySecret);
// 下面是一些调用代码...
...
}
}
在上面代码中,变量 accessKeyId 与 accessKeySecret 是由系统分配给用户的,称为ID对,用于标识用户,为访问OSS做签名验证。
关于OSSClient的详细介绍,参见 OSSClient 。
Step 2. 新建Bucket
Bucket是OSS上的命名空间,相当于数据的容器,可以存储若干数据实体(Object)。
你可以按照下面的代码新建一个Bucket:
public void createBucket(String bucketName) {
// 初始化OSSClient
OSSClient client = ...;
// 新建一个Bucket
client.createBucket(bucketName);
}
由于Bucket的名字是全局唯一的,所以尽量保证你的 bucketName 不与别人重复。
关于Bucket的命名规范,参见 Bucket命名规范。
Step 3. 上传Object
Object是OSS中最基本的数据单元,你可以把它简单地理解为文件,用下面代码可以实现一个Object的上传:
public void putObject(String bucketName, String key, String filePath) throws FileNotFoundException {
// 初始化OSSClient
OSSClient client = ...;
// 获取指定文件的输入流
File file = new File(filePath);
InputStream content = new FileInputStream(file);
// 创建上传Object的Metadata
ObjectMetadata meta = new ObjectMetadata();
// 必须设置ContentLength
meta.setContentLength(file.length());
// 上传Object.
PutObjectResult result = client.putObject(bucketName, key, content, meta);
// 打印ETag
System.out.println(result.getETag());
}
Object通过InputStream的形式上传到OSS中。在上面的例子里我们可以看出,每上传一个Object,都需要指定和Object关联的ObjectMetadata。ObjectMetaData是用户对该object的描述,由一系列name-value对组成;其中ContentLength是必须设置的,以便SDK可以正确识别上传Object的大小。
Put Object请求处理成功后,OSS会将收到文件的MD5值放在返回结果的ETag中。用户可以根据ETag检验上传的文件与本地的是否一致。
关于Object的命名规范,参见 Object命名规范 。
关于上传Object更详细的信息,参见 上传Object 。
Step 4. 列出所有Object
当你完成一系列上传后,可能会需要查看在某个Bucket中有哪些Object,可以通过下面的程序实现:
public void listObjects(String bucketName) {
// 初始化OSSClient
OSSClient client = ...;
// 获取指定bucket下的所有Object信息
ObjectListing listing = client.listObjects(bucketName);
// 遍历所有Object
for (OSSObjectSummary objectSummary : listing.getObjectSummaries()) {
System.out.println(objectSummary.getKey());
}
}
listObjects方法会返回ObjectListing对象,ObjectListing对象包含了此次listObject请求的返回结果。其中我们可以通过ObjetListing中的getObjectSummaries方法获取所有Object的描述信息(List<OSSObjectSummary>)。
Step 5. 获取指定Object
你可以参考下面的代码简单地实现一个Object的获取:
public void getObject(String bucketName, String key) throws IOException {
// 初始化OSSClient
OSSClient client = ...;
// 获取Object,返回结果为OSSObject对象
OSSObject object = client.getObject(bucketName, key);
// 获取Object的输入流
InputStream objectContent = object.getObjectContent();
// 处理Object
...
// 关闭流
objectContent.close();
}
当调用OSSClient的getObject方法时,会返回一个OSSObject的对象,此对象包含了Object的各种信息。通过OSSObject的getObjectContent方法,还可以获取返回的Object的输入流,你可以读取这个输入流来对Object的内容进行操作;记得在用完之后关闭这个流。
OSSClient
OSSClient是OSS服务的Java客户端,它为调用者提供了一系列的方法,用于和OSS服务进行交互。
新建OSSClient
新建一个OSSClient很简单,如下面代码所示:
String key = "<key>";
String secret = "<secret>";
OSSClient client = new OSSClient(key, secret);
上面的方式使用默认域名作为OSS的服务地址,如果你想自己指定域名,可以传入endpoint参数来指定。
String key = "<key>";
String secret = "<secret>";
String endpoint = "http://oss.aliyuncs.com";
OSSClient client = new OSSClient(endpoint, accessKeyId, accessKeySecret);
配置OSSClient
如果你想配置OSSClient的一些细节的参数,可以在构造OSSClient的时候传入ClientConfiguration对象。 ClientConfiguration是OSS服务的配置类,可以为客户端配置代理,最大连接数等参数。
使用代理
下面一段代码可以使客户端使用代理访问OSS服务:
// 创建ClientConfiguration实例
ClientConfiguration conf = new ClientConfiguration();
// 配置代理为本地8080端口
conf.setProxyHost("127.0.0.1");
conf.setProxyPort(8080);
// 创建OSS客户端
client = new OSSClient(endpoint, accessKeySecret, accessKeySecret, conf);
上面代码使得客户端的所有操作都会使用127.0.0.1地址的8080端口做代理执行。
对于有用户验证的代理,可以配置用户名和密码:
// 创建ClientConfiguration实例
ClientConfiguration conf = new ClientConfiguration();
// 配置代理为本地8080端口
conf.setProxyHost("127.0.0.1");
conf.setProxyPort(8080);
//设置用户名和密码
conf.setProxyUsername("username");
conf.setProxyPassword("password");
设置网络参数
我们可以用ClientConfiguration设置一些网络参数:
ClientConfiguration conf = new ClientConfiguration();
// 设置HTTP最大连接数为10
conf.setMaxConnections(10);
// 设置TCP连接超时为5000毫秒
conf.setConnectionTimeout(5000);
// 设置最大的重试次数为3
conf.setMaxErrorRetry(3);
// 设置Socket传输数据超时的时间为2000毫秒
conf.setSocketTimeout(2000);
ClientConfiguration所有参数
通过ClientConfiguration能指定的所有参数如下表所示:
| 参数 | 说明 |
|---|---|
| UserAgent | 用户代理,指HTTP的User-Agent头。默认为”aliyun-sdk-java” |
| ProxyHost | 代理服务器主机地址 |
| ProxyPort | 代理服务器端口 |
| ProxyUsername | 代理服务器验证的用户名 |
| ProxyPassword | 代理服务器验证的密码 |
| ProxyDomain | 访问NTLM验证的代理服务器的Windows域名 |
| ProxyWorkstation | NTLM代理服务器的Windows工作站名称 |
| MaxConnections | 允许打开的最大HTTP连接数。默认为50 |
| SocketTimeout | 通过打开的连接传输数据的超时时间(单位:毫秒)。默认为50000毫秒 |
| ConnectionTimeout | 建立连接的超时时间(单位:毫秒)。默认为50000毫秒 |
| MaxErrorRetry | 可重试的请求失败后最大的重试次数。默认为3次 |
Bucket
Bucket是OSS上的命名空间,也是计费、权限控制、日志记录等高级功能的管理实体;Bucket名称在整个OSS服务中具有全局唯一性,且不能修改;存储在OSS上的每个Object必须都包含在某个Bucket中。一个应用,例如图片分享网站,可以对应一个或多个Bucket。一个用户最多可创建10个Bucket,但每个Bucket中存放的Object的数量和大小总和没有限制,用户不需要考虑数据的可扩展性。
命名规范
Bucket的命名有以下规范:
- 只能包括小写字母,数字,短横线(-)
- 必须以小写字母或者数字开头
- 长度必须在3-63字节之间
新建Bucket
如下代码可以新建一个Bucket:
String bucketName = "my-bucket-name";
// 初始化OSSClient
OSSClient client = ...;
// 新建一个Bucket
client.createBucket(bucketName);
由于Bucket的名字是全局唯一的,所以尽量保证你的 bucketName 不与别人重复。
列出用户所有的Bucket
下面代码可以列出用户所有的Bucket:
// 获取用户的Bucket列表
List<Bucket> buckets = client.listBuckets();
// 遍历Bucket
for (Bucket bucket : buckets) {
System.out.println(bucket.getName());
}
判断Bucket是否存在
有时候,我们的需求只是判断Bucket是否存在。则下面代码可以做到:
String bucketName = "your-bucket-name";
// 获取Bucket的存在信息
boolean exists = client.doesBucketExist(bucketName);
// 输出结果
if (exists) {
System.out.println("Bucket exists");
} else {
System.out.println("Bucket not exists");
}
删除Bucket
下面代码删除了一个Bucket:
String bucketName = "your-bucket-name";
// 删除Bucket
client.deleteBucket(bucketName)
需要注意的是,如果Bucket不为空(Bucket中有Object),则Bucket无法删除,必须清空Bucket后才能成功删除。
Bucket权限控制
Bucket的访问权限
OSS提供Bucket级别的权限访问控制,Bucket目前有三种访问权限:public-read-write,public-read和private。它们的含义如下:
- public-read-write: 任何人(包括匿名访问)都可以对该bucket中的object进行上传、下载和删除操作;所有这些操作产生的费用由该bucket的创建者承担,请慎用该权限。
- public-read: 只有该bucket的创建者可以对该bucket内的Object进行写操作(包括上传和删除);任何人(包括匿名访问)可以对该bucket中的object进行读操作。
- private: 只有该bucket的创建者才可以访问此Bukcet。其他人禁止对此Bucket做任何操作。
用户新创建一个新Bucket时,如果不指定Bucket权限,OSS会自动为该Bucket设置private权限。对于一个已经存在的Bucket,只有它的创建者可以通过OSS的所提供的接口修改其访问权限。
修改Bucket的访问权限
下面代码将Bucket的权限设置为了private。
String bucketName = "your-bucket-name";
client.setBucketAcl(bucketName, CannedAccessControlList.Private);
CannedAccessControlList是枚举类型,包含三个值: Private 、 PublicRead 、 PublicReadWrite ,它们分别对应相关权限。
Object
在OSS中,用户操作的基本数据单元是Object。单个Object最大允许存储5TB的数据。Object包含key、meta和data。其中,key是Object的名字;meta是用户对该object的描述,由一系列name-value对组成;data是Object的数据。
命名规范
Object的命名规范如下:
- 使用UTF-8编码
- 长度必须在1-1023字节之间
- 不能以“/”或者“\”字符开头
- 不能含有“\r”或者“\n”的换行符
上传Object
最简单的上传
如下代码:
public void putObject(String bucketName, String key, String filePath) throws FileNotFoundException {
// 初始化OSSClient
OSSClient client = ...;
// 获取指定文件的输入流
File file = new File(filePath);
InputStream content = new FileInputStream(file);
// 创建上传Object的Metadata
ObjectMetadata meta = new ObjectMetadata();
// 必须设置ContentLength
meta.setContentLength(file.length());
// 上传Object.
PutObjectResult result = client.putObject(bucketName, key, content, meta);
// 打印ETag
System.out.println(result.getETag());
}
Object通过InputStream的形式上传到OSS中。在上面的例子里我们可以看出,每上传一个Object,都需要指定和Object关联的ObjectMetadata。ObjectMetaData是用户对该object的描述,由一系列name-value对组成;其中ContentLength是必须设置的,以便SDK可以正确识别上传Object的大小。
Put Object请求处理成功后,OSS会将收到文件的MD5值放在返回结果的ETag中。用户可以根据ETag检验上传的文件与本地的是否一致。
设定Object的Http Header
OSS Java SDK本质上是调用后台的HTTP接口,因此OSS服务允许用户自定义Object的Http Header。下面代码为Object设置了过期时间:
// 初始化OSSClient
OSSClient client = ...;
// 初始化上传输入流
InputStream content = ...;
// 创建上传Object的Metadata
ObjectMetadata meta = new ObjectMetadata();
// 设置ContentLength为1000
meta.setContentLength(1000);
// 设置1小时后过期
Date expire = new Date(new Date().getTime() + 3600 * 1000);
meta.setExpirationTime(expire);
client.putObject(bucketName, key, content, meta);
Java SDK支持的Http Header有四种,分别为:Cache-Control 、 Content-Disposition 、Content-Encoding 、 Expires 。它们的相关介绍见 RFC2616 。
用户自定义元数据
OSS支持用户自定义元数据来对Object进行描述。比如:
// 设置自定义元数据name的值为my-data
meta.addUserMetadata("name", "my-data");
// 上传object
client.putObject(bucketName, key, content, meta);
在上面代码中,用户自定义了一个名字为”name”,值为”my-data”的元数据。当用户下载此Object的时候,此元数据也可以一并得到。一个Object可以有多个类似的参数,但所有的user meta总大小不能超过2k。
分块上传
OSS允许用户将一个Object分成多个请求上传到后台服务器中,关于分块上传的内容,我们将在 Object的分块上传 这一章中做介绍。
列出Bucket中的Object
列出Object
public void listObjects(String bucketName) {
// 初始化OSSClient
OSSClient client = ...;
// 获取指定bucket下的所有Object信息
ObjectListing listing = client.listObjects(bucketName);
// 遍历所有Object
for (OSSObjectSummary objectSummary : listing.getObjectSummaries()) {
System.out.println(objectSummary.getKey());
}
}
listObjects方法会返回 ObjectListing 对象,ObjectListing 对象包含了此次listObject请求的返回结果。其中我们可以通过 ObjetListing 中的 getObjectSummaries 方法获取所有Object的描述信息(List<OSSObjectSummary>)。
Note
默认情况下,如果Bucket中的Object数量大于100,则只会返回100个Object, 且返回结果中 IsTruncated 为 false,并返回 NextMarker 作为下此读取的起点。若想增大返回Object数目,可以修改 MaxKeys 参数,或者使用 Marker 参数分次读取。
扩展参数
通常,我们可以通过设置ListObjectsRequest的参数来完成更强大的功能。比如:
// 构造ListObjectsRequest请求
ListObjectsRequest listObjectsRequest = new ListObjectsRequest(bucketName);
// 设置参数
listObjectsRequest.setDelimiter("/");
listObjectsRequest.setMarker("123");
...
ObjectListing listing = client.listObjects(listObjectsRequest);
上面代码中我们调用了 listObjects 的一个重载方法,通过传入 ListObjectsRequest 来完成请求。通过 ListObjectsRequest 中的参数设置我们可以完成很多扩展的功能。下表列出了 ListObjectsRequest 中可以设置的参数名称和作用:
| 名称 | 作用 |
|---|---|
| Delimiter | 是一个用于对Object名字进行分组的字符。所有名字包含指定的前缀且第一次出现Delimiter字符之间的object作为一组元素: CommonPrefixes。 |
| Marker | 设定结果从Marker之后按字母排序的第一个开始返回。 |
| MaxKeys | 限定此次返回object的最大数,如果不设定,默认为100,MaxKeys取值不能大于1000。 |
| Prefix | 限定返回的object key必须以Prefix作为前缀。注意使用prefix查询时,返回的key中仍会包含Prefix。 |
文件夹功能模拟
我们可以通过 Delimiter 和 Prefix 参数的配合模拟出文件夹功能。
Delimiter 和 Prefix 的组合效果是这样的:如果把 Prefix 设为某个文件夹名,就可以罗列以此 Prefix 开头的文件,即该文件夹下递归的所有的文件和子文件夹。如果再把 Delimiter 设置为 “/” 时,返回值就只罗列该文件夹下的文件,该文件夹下的子文件名返回在 CommonPrefixes 部分,子文件夹下递归的文件和文件夹不被显示.
假设Bucket中有4个文件: oss.jpg , fun/test.jpg , fun/movie/001.avi , fun/movie/007.avi ,我们把 “/” 符号作为文件夹的分隔符。
列出Bucket内所有文件
当我们需要获取Bucket下的所有文件时,可以这样写:
// 构造ListObjectsRequest请求
ListObjectsRequest listObjectsRequest = new ListObjectsRequest(bucketName);
// List Objects
ObjectListing listing = client.listObjects(listObjectsRequest);
// 遍历所有Object
System.out.println("Objects:");
for (OSSObjectSummary objectSummary : listing.getObjectSummaries()) {
System.out.println(objectSummary.getKey());
}
// 遍历所有CommonPrefix
System.out.println("CommonPrefixs:");
for (String commonPrefix : listing.getCommonPrefixes()) {
System.out.println(commonPrefix);
}
输出:
Objects:
fun/movie/001.avi
fun/movie/007.avi
fun/test.jpg
oss.jpg
CommonPrefixs:
递归列出目录下所有文件
我们可以通过设置 Prefix 参数来获取某个目录下所有的文件:
// 构造ListObjectsRequest请求
ListObjectsRequest listObjectsRequest = new ListObjectsRequest(bucketName);
// 递归列出fun目录下的所有文件
listObjectsRequest.setPrefix("fun/");
ObjectListing listing = client.listObjects(listObjectsRequest);
// 遍历所有Object
System.out.println("Objects:");
for (OSSObjectSummary objectSummary : listing.getObjectSummaries()) {
System.out.println(objectSummary.getKey());
}
// 遍历所有CommonPrefix
System.out.println("\nCommonPrefixs:");
for (String commonPrefix : listing.getCommonPrefixes()) {
System.out.println(commonPrefix);
}
输出:
Objects:
fun/movie/001.avi
fun/movie/007.avi
fun/test.jpg
CommonPrefixs:
列出目录下的文件和子目录
在 Prefix 和 Delimiter 结合的情况下,可以列出目录下的文件和子目录:
// 构造ListObjectsRequest请求
ListObjectsRequest listObjectsRequest = new ListObjectsRequest(bucketName);
// "/" 为文件夹的分隔符
listObjectsRequest.setDelimiter("/");
// 列出fun目录下的所有文件和文件夹
listObjectsRequest.setPrefix("fun/");
ObjectListing listing = client.listObjects(listObjectsRequest);
// 遍历所有Object
System.out.println("Objects:");
for (OSSObjectSummary objectSummary : listing.getObjectSummaries()) {
System.out.println(objectSummary.getKey());
}
// 遍历所有CommonPrefix
System.out.println("\nCommonPrefixs:");
for (String commonPrefix : listing.getCommonPrefixes()) {
System.out.println(commonPrefix);
}
输出:
Objects:
fun/test.jpg
CommonPrefixs:
fun/movie/
返回的结果中, ObjectSummaries 的列表中给出的是fun目录下的文件。而 CommonPrefixs 的列表中给出的是fun目录下的所有子文件夹。可以看出 fun/movie/001.avi , fun/movie/007.avi 两个文件并没有被列出来,因为它们属于 fun 文件夹下的 movie 目录。
获取Object
简单的读取Object
我们可以通过以下代码将Object读取到一个流中:
public void getObject(String bucketName, String key) throws IOException {
// 初始化OSSClient
OSSClient client = ...;
// 获取Object,返回结果为OSSObject对象
OSSObject object = client.getObject(bucketName, key);
// 获取ObjectMeta
ObjectMetadata meta = object.getObjectMetadata();
// 获取Object的输入流
InputStream objectContent = object.getObjectContent();
// 处理Object
...
// 关闭流
objectContent.close();
}
`OSSObject 包含了Object的各种信息,包含Object所在的Bucket、Object的名称、Metadata以及一个输入流。我们可以通过操作输入流将Object的内容读取到文件或者内存中。而ObjectMetadata包含了Object上传时定义的,ETag,Http Header以及自定义的元数据。
通过GetObjectRequest获取Object
为了实现更多的功能,我们可以通过使用 GetObjectRequest 来获取Object。
// 初始化OSSClient
OSSClient client = ...;
// 新建GetObjectRequest
GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, key);
// 获取0~100字节范围内的数据
getObjectRequest.setRange(0, 100);
// 获取Object,返回结果为OSSObject对象
OSSObject object = client.getObject(getObjectRequest);
我们通过 getObjectRequest 的 setRange 方法设置了返回的Object的范围。我们可以用此功能实现文件的分段下载和断点续传。
GetObjectRequest可以设置以下参数:
| 参数 | 说明 |
|---|---|
| Range | 指定文件传输的范围。 |
| ModifiedSinceConstraint | 如果指定的时间早于实际修改时间,则正常传送文件。否则抛出304 Not Modified异常。 |
| UnmodifiedSinceConstraint | 如果传入参数中的时间等于或者晚于文件实际修改时间,则正常传输文件。否则抛出412 precondition failed异常 |
| MatchingETagConstraints | 传入一组ETag,如果传入期望的ETag和object的 ETag匹配,则正常传输文件。否则抛出412 precondition failed异常 |
| NonmatchingEtagConstraints | 传入一组ETag,如果传入的ETag值和Object的ETag不匹配,则正常传输文件。否则抛出304 Not Modified异常。 |
| ResponseHeaderOverrides | 自定义OSS返回请求中的一些Header。 |
修改 ResponseHeaderOverrides , 它提供了一系列的可修改参数,可以自定义OSS的返回Header,如下表所示:
| 参数 | 说明 |
|---|---|
| ContentType | OSS返回请求的content-type头 |
| ContentLanguage | OSS返回请求的content-language头 |
| Expires | OSS返回请求的expires头 |
| CacheControl | OSS返回请求的cache-control头 |
| ContentDisposition | OSS返回请求的content-disposition头 |
| ContentEncoding | OSS返回请求的content-encoding头 |
直接下载Object到文件
我们可以通过下面的代码直接将Object下载到指定文件:
// 新建GetObjectRequest
GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, key);
// 下载Object到文件
ObjectMetadata objectMetadata = client.getObject(getObjectRequest, new File("/path/to/file"));
当使用上面方法将Object直接下载到文件时,方法返回ObjectMetadata对象。
只获取ObjectMetadata
通过 getObjectMetadata 方法可以只获取ObjectMetadata而不获取Object的实体。如下代码所示:
ObjectMetadata objectMetadata = client.getObjectMetadata(bucketName, key);
删除Object
下面代码删除了一个Object:
public void deleteObject(String bucketName, String key) {
// 初始化OSSClient
OSSClient client = ...;
// 删除Object
client.deleteObject(bucketName, key);
}
拷贝Object
拷贝一个Object
通过 copyObject 方法我们可以拷贝一个Object,如下面代码:
public void copyObject(String srcBucketName, String srcKey, String destBucketName, String destKey) {
// 初始化OSSClient
OSSClient client = ...;
// 拷贝Object
CopyObjectResult result = client.copyObject(srcBucketName, srcKey, destBucketName, destKey);
// 打印结果
System.out.println("ETag: " + result.getETag() + " LastModified: " + result.getLastModified());
}
copyObject 方法返回一个 CopyObjectResult 对象,对象中包含了新Object的ETag和修改时间。
通过CopyObjectRequest拷贝Object
也可以通过 CopyObjectRequest 实现Object的拷贝:
// 初始化OSSClient
OSSClient client = ...;
// 创建CopyObjectRequest对象
CopyObjectRequest copyObjectRequest = new CopyObjectRequest(srcBucketName, srcKey, destBucketName, destKey);
// 设置新的Metadata
ObjectMetadata meta = new ObjectMetadata();
meta.setContentType("text/html");
copyObjectRequest.setNewObjectMetadata(meta);
// 复制Object
CopyObjectResult result = client.copyObject(copyObjectRequest);
System.out.println("ETag: " + result.getETag() + " LastModified: " + result.getLastModified());
CopyObjectRequest 允许用户修改目的Object的ObjectMeta,同时也提供 ModifiedSinceConstraint , UnmodifiedSinceConstraint , MatchingETagConstraints , NonmatchingEtagConstraints 四个参数的设定, 用法与 GetObjectRequest 的参数相似,参见 GetObjectRequest的可设置参数。
Object的分块上传
除了通过putObject接口上传文件到OSS以外,OSS还提供了另外一种上传模式 —— Multipart Upload。用户可以在如下的应用场景内(但不仅限于此),使用Multipart Upload上传模式,如:
- 需要支持断点上传。
- 上传超过100MB大小的文件。
- 网络条件较差,和OSS的服务器之间的链接经常断开。
- 需要流式地上传文件。
- 上传文件之前,无法确定上传文件的大小。
下面我们将一步步介绍怎样实现Multipart Upload。
分步完成Multipart Upload
假设我们有一个文件,本地路径为 /path/to/file.zip 由于文件比较大,我们将其分块传输到OSS中。
1. 初始化Multipart Upload
我们使用 initiateMultipartUpload 方法来初始化一个分块上传事件:
String bucketName = "your-bucket-name";
String key = "your-key";
// 初始化OSSClient
OSSClient client = ...;
// 开始Multipart Upload
InitiateMultipartUploadRequest initiateMultipartUploadRequest =
new InitiateMultipartUploadRequest(bucketName, key);
InitiateMultipartUploadResult initiateMultipartUploadResult =
client.initiateMultipartUpload(initiateMultipartUploadRequest);
// 打印UploadId
System.out.println("UploadId: " + initiateMultipartUploadResult.getUploadId());
我们用 InitiateMultipartUploadRequest 来指定上传Object的名字和所属Bucket。在 InitiateMultipartUploadRequest 中,你也可以设置 ObjectMetadata ,但是不必指定其中的 ContentLength (指定了也无效)。
initiateMultipartUpload 的返回结果中含有 UploadId ,它是区分分块上传事件的唯一标识,在后面的操作中,我们将用到它。
2. 上传分块
接着,我们把文件分块上传。
// 设置每块为 5M
final int partSize = 1024 * 1024 * 5;
File partFile = new File("/path/to/file.zip");
// 计算分块数目
int partCount = (int) (partFile.length() / partSize);
if (partFile.length() % partSize != 0){
partCount++;
}
// 新建一个List保存每个分块上传后的ETag和PartNumber
List<PartETag> partETags = new ArrayList<PartETag>();
for(int i = 0; i < partCount; i++){
// 获取文件流
FileInputStream fis = new FileInputStream(partFile);
// 跳到每个分块的开头
long skipBytes = partSize * i;
fis.skip(skipBytes);
// 计算每个分块的大小
long size = partSize < partFile.length() - skipBytes ?
partSize : partFile.length() - skipBytes;
// 创建UploadPartRequest,上传分块
UploadPartRequest uploadPartRequest = new UploadPartRequest();
uploadPartRequest.setBucketName(bucketName);
uploadPartRequest.setKey(key);
uploadPartRequest.setUploadId(initiateMultipartUploadResult.getUploadId());
uploadPartRequest.setInputStream(fis);
uploadPartRequest.setPartSize(size);
uploadPartRequest.setPartNumber(i + 1);
UploadPartResult uploadPartResult = client.uploadPart(uploadPartRequest);
// 将返回的PartETag保存到List中。
partETags.add(uploadPartResult.getPartETag());
// 关闭文件
fis.close();
}
上面程序的核心是调用 uploadPart 方法来上传每一个分块,但是要注意以下几点:
- uploadPart 方法要求除最后一个Part以外,其他的Part大小都要大于5MB。但是Upload Part接口并不会立即校验上传Part的大小(因为不知道是否为最后一块);只有当Complete Multipart Upload的时候才会校验。
- OSS会将服务器端收到Part数据的MD5值放在ETag头内返回给用户。为了保证数据在网络传输过程中不出现错误,强烈推荐用户在收到OSS的返回请求后,用该MD5值验证上传数据的正确性。
- Part号码的范围是1~10000。如果超出这个范围,OSS将返回InvalidArgument的错误码。
- 每次上传part时都要把流定位到此次上传块开头所对应的位置。
- 每次上传part之后,OSS的返回结果会包含一个 PartETag 对象,他是上传块的ETag与块编号(PartNumber)的组合,在后续完成分块上传的步骤中会用到它,因此我们需要将其保存起来。一般来讲我们将这些 PartETag 对象保存到List中。
3. 完成分块上传
完成分块上传很简单,如下:
CompleteMultipartUploadRequest completeMultipartUploadRequest =
new CompleteMultipartUploadRequest(bucketName, key, initiateMultipartUploadResult.getUploadId(), partETags);
// 完成分块上传
CompleteMultipartUploadResult completeMultipartUploadResult =
client.completeMultipartUpload(completeMultipartUploadRequest);
// 打印Object的ETag
System.out.println(completeMultipartUploadResult.getETag());
上面代码中的 partETags 就是第二部中保存的partETag的列表,OSS收到用户提交的Part列表后,会逐一验证每个数据Part的有效性。当所有的数据Part验证通过后,OSS将把这些数据part组合成一个完整的Object。
completeMultipartUpload 方法的返回结果中会包含拼装后Object的ETag,用户可以和本地文件的MD5值进行校验以保证数据的有效性。
取消分块上传事件
我们可以用 abortMultipartUpload 方法取消分块上传。
AbortMultipartUploadRequest abortMultipartUploadRequest =
new AbortMultipartUploadRequest(bucketName, key, uploadId);
// 取消分块上传
client.abortMultipartUpload(abortMultipartUploadRequest);
获取Bucket内所有分块上传事件
我们可以用 listMultipartUploads 方法获取Bucket内所有上传事件。
// 获取Bucket内所有上传事件
MultipartUploadListing listing = client.listMultipartUploads(listMultipartUploadsRequest);
// 遍历所有上传事件
for (MultipartUpload multipartUpload : listing.getMultipartUploads()) {
System.out.println("Key: " + multipartUpload.getKey() + " UploadId: " + multipartUpload.getUploadId());
}
Note
默认情况下,如果Bucket中的分块上传事件的数量大于1000,则只会返回1000个Object, 且返回结果中 IsTruncated 为 false,并返回 NextKeyMarker 和 NextUploadMarker 作为下此读取的起点。若想增大返回分块上传事件数目,可以修改 MaxUploads 参数,或者使用 KeyMarker 以及 UploadIdMarker 参数分次读取。
获取所有已上传的块信息
我们可以用 listParts 方法获取某个上传事件所有已上传的块。
ListPartsRequest listPartsRequest = new ListPartsRequest(bucketName, key, uploadId);
// 获取上传的所有Part信息
PartListing partListing = client.listParts(listPartsRequest);
// 遍历所有Part
for (PartSummary part : partListing.getParts()) {
System.out.println("PartNumber: " + part.getPartNumber() + " ETag: " + part.getETag());
}
Note
默认情况下,如果Bucket中的分块上传事件的数量大于1000,则只会返回1000个Object, 且返回结果中 IsTruncated 为 false,并返回 NextPartNumberMarker 作为下此读取的起点。若想增大返回分块上传事件数目,可以修改 MaxParts 参数,或者使用 PartNumberMarker 参数分次读取。
生成预签名URL
如果你想把自己的资源发放给第三方用户访问,但是又不想开放Bucket的读权限,可以通过生成预签名URL的形式提供给用户一个临时的访问URL。在生成URL时,你可以指定URL过期的时间,从而限制用户长时间访问。
生成一个预签名的URL
如下代码:
String bucketName = "your-bucket-name";
String key = "your-object-key";
// 设置URL过期时间为1小时
Date expiration = new Date(new Date().getTime() + 3600 * 1000);
// 生成URL
URL url = client.generatePresignedUrl(bucketName, key, expiration);
生成的URL默认以GET方式访问,这样,用户可以直接通过浏览器访问相关内容。
生成其他Http方法的URL
如果你想允许用户临时进行其他操作(比如上传,删除Object),可能需要签名其他方法的URL,如下:
// 生成PUT方法的URL
URL url = client.generatePresignedUrl(bucketName, key, expiration, HttpMethod.PUT);
通过传入 HttpMethod.PUT 参数,用户可以使用生成的URL上传Object。
添加用户自定义参数(UserMetadata)
如果你想使用签名的URL上传Object,并指定UserMetadata等参数,可以这样做:
// 创建请求
GeneratePresignedUrlRequest generatePresignedUrlRequest =
new GeneratePresignedUrlRequest(bucketName, key);
// HttpMethod为PUT
generatePresignedUrlRequest.setMethod(HttpMethod.PUT);
// 添加UserMetadata
generatePresignedUrlRequest.addUserMetadata("key", "value");
// 生成预签名的URL
URL url = client.generatePresignedUrl(bucketName, key, expiration);
需要注意的是,上述过程只是生成了签名的URL,你仍需要在request header中添加UserMetadata的信息。
关于如何在Http请求中设置UserMetadata等参数,可以参考 OSS REST API 文档 中的相关内容。
异常
OSS Java SDK 中有两种异常 ClientException 以及 OSSException , 他们都继承自或者间接继承自 RuntimeException 。
ClientException
ClientException指SDK内部出现的异常,比如未设置BucketName,网络无法到达等等。
OSSException
OSSException指服务器端错误,它来自于对服务器错误信息的解析。OSSException一般有以下几个成员:
- Code: OSS返回给用户的错误码。
- Message: OSS给出的详细错误信息。
- RequestId: 用于唯一标识该次请求的UUID;当你无法解决问题时,可以凭这个RequestId来请求OSS开发工程师的帮助。
- HostId: 用于标识访问的OSS集群(目前统一为oss.aliyuncs.com)
下面是OSS中常见的异常:
| 错误码 | 描述 |
|---|---|
| AccessDenied | 拒绝访问 |
| BucketAlreadyExists | Bucket已经存在 |
| BucketNotEmpty | Bucket不为空 |
| EntityTooLarge | 实体过大 |
| EntityTooSmall | 实体过小 |
| FileGroupTooLarge | 文件组过大 |
| FilePartNotExist | 文件Part不存在 |
| FilePartStale | 文件Part过时 |
| InvalidArgument | 参数格式错误 |
| InvalidAccessKeyId | Access Key ID不存在 |
| InvalidBucketName | 无效的Bucket名字 |
| InvalidDigest | 无效的摘要 |
| InvalidObjectName | 无效的Object名字 |
| InvalidPart | 无效的Part |
| InvalidPartOrder | 无效的part顺序 |
| InvalidTargetBucketForLogging | Logging操作中有无效的目标bucket |
| InternalError | OSS内部发生错误 |
| MalformedXML | XML格式非法 |
| MethodNotAllowed | 不支持的方法 |
| MissingArgument | 缺少参数 |
| MissingContentLength | 缺少内容长度 |
| NoSuchBucket | Bucket不存在 |
| NoSuchKey | 文件不存在 |
| NoSuchUpload | Multipart Upload ID不存在 |
| NotImplemented | 无法处理的方法 |
| PreconditionFailed | 预处理错误 |
| RequestTimeTooSkewed | 发起请求的时间和服务器时间超出15分钟 |
| RequestTimeout | 请求超时 |
| SignatureDoesNotMatch | 签名错误 |
| TooManyBuckets | 用户的Bucket数目超过限制 |
作者:王超 原文:http://aliyun_portal_storage.oss.aliyuncs.com/oss_api/oss_javahtml/index.html

浙公网安备 33010602011771号