安装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