本文介绍如何将源Bucket中的文件(Object)复制到同一地域下相同或不同目标Bucket中。

注意事项

  • 拷贝文件时,您必须拥有源文件的读权限及目标Bucket的读写权限。
  • 拷贝文件时,您需要确保源Bucket和目标Bucket均未设置合规保留策略,否则报错The object you specified is immutable.
  • 不支持跨地域拷贝。例如不能将华东1(杭州)地域存储空间中的文件拷贝到华北1(青岛)地域。

拷贝小文件

对于小于1 GB的文件,您可以通过ossClient.copyObject方法拷贝文件。此方法有两种参数指定方式:

参数指定方式 描述
CopyObjectResult copyObject(String sourceBucketName, String sourceKey, String destinationBucketName, String destinationKey) 允许指定源存储空间和源文件,以及目标存储空间和目标文件。拷贝后,目标文件的内容及元信息与源文件相同,称为简单拷贝。
CopyObjectResult copyObject(CopyObjectRequest copyObjectRequest) 允许指定目标文件的元信息和拷贝的限制条件。如果拷贝操作的源文件地址和目标文件地址相同,则直接替换源文件的元信息。

CopyObjectRequest可设置的参数请参见下表。

参数 描述 方法
sourceBucketName 源存储空间名称。 setSourceBucketName(String sourceBucketName)
sourceKey 源文件名称。 setSourceKey(String sourceKey)
destinationBucketName 目标存储空间名称。 setDestinationBucketName(String destinationBucketName)
destinationKey 目标文件名称。 setDestinationKey(String destinationKey)
newObjectMetadata 目标文件元信息。 setNewObjectMetadata(ObjectMetadata newObjectMetadata)
matchingETagConstraints 拷贝的限制条件。如果源文件的ETag值和用户提供的ETag值相等,则执行拷贝操作,否则返回错误。 setMatchingETagConstraints(List<String> matchingETagConstraints)
nonmatchingEtagConstraints 拷贝的限制条件。如果源文件的ETag值和用户提供的ETag值不相等,则执行拷贝操作,否则返回错误。 setNonmatchingETagConstraints(List<String> nonmatchingEtagConstraints)
unmodifiedSinceConstraint 拷贝的限制条件。如果传入参数中的时间等于或者晚于文件实际修改时间,则执行拷贝操作,否则返回错误。 setUnmodifiedSinceConstraint(Date unmodifiedSinceConstraint)
modifiedSinceConstraint 拷贝的限制条件。如果源文件在指定的时间之后被修改过,则执行拷贝操作,否则返回错误。 setModifiedSinceConstraint(Date modifiedSinceConstraint)

CopyObjectRequest的参数说明请参见下表。

参数 描述 方法
etag OSS文件唯一性标识。 String getETag()
lastModified 文件最后修改时间。 Date getLastModified()

拷贝小文件有以下几种拷贝形式。

  • 简单拷贝

    以下代码用于通过简单拷贝将srcexamplebucket中的srcexampleobject.txt文件拷贝到目标存储空间desexamplebucket中的desexampleobject.txt文件。

    // yourEndpoint填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
    String endpoint = "yourEndpoint";
    // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
    String accessKeyId = "yourAccessKeyId";
    String accessKeySecret = "yourAccessKeySecret";
    
    // 填写源Bucket名称。
    String sourceBucketName = "srcexamplebucket";
    // 填写源Object的完整路径。Object完整路径中不能包含Bucket名称。
    String sourceKey = "srcexampleobject.txt";
    // 填写与源Bucket处于同一地域的目标Bucket名称。
    String destinationBucketName = "desexamplebucket";
    // 填写目标Object的完整路径。Object完整路径中不能包含Bucket名称。
    String destinationKey = "desexampleobject.txt";
    
    // 创建OSSClient实例。
    OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
    
    // 拷贝文件。
    CopyObjectResult result = ossClient.copyObject(sourceBucketName, sourceKey, destinationBucketName, destinationKey);
    System.out.println("ETag: " + result.getETag() + " LastModified: " + result.getLastModified());
    
    // 关闭OSSClient。
    ossClient.shutdown();                   
  • 通过CopyObjectRequest拷贝

    以下代码用于通过CopyObjectRequest将srcexamplebucket中的srcexampleobject.txt文件拷贝到目标存储空间desexamplebucket中的desexampleobject.txt文件。

    // yourEndpoint填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
    String endpoint = "yourEndpoint";
    // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
    String accessKeyId = "yourAccessKeyId";
    String accessKeySecret = "yourAccessKeySecret";
    // 填写源Bucket名称。关于Bucket名称命名规范的更多信息,请参见Bucket。
    String sourceBucketName = "srcexamplebucket";
    // 填写源Object的完整路径。Object完整路径中不能包含Bucket名称。
    String sourceKey = "srcexampleobject.txt";
    // 填写与源Bucket处于同一地域的目标Bucket名称。
    String destinationBucketName = "desexamplebucket";
    // 填写目标Object的完整路径。Object完整路径中不能包含Bucket名称。
    String destinationKey = "desexampleobject.txt";
    
    // 创建OSSClient实例。
    OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
    
    // 创建CopyObjectRequest对象。
    CopyObjectRequest copyObjectRequest = new CopyObjectRequest(sourceBucketName, sourceKey, destinationBucketName, destinationKey);
    
    // 设置新的文件元信息。
    ObjectMetadata meta = new ObjectMetadata();
    meta.setContentType("text/txt");
    // 指定CopyObject操作时是否覆盖同名目标Object。此处设置为true,表示禁止覆盖同名Object。
    // meta.setHeader("x-oss-forbid-overwrite", "true");
    // 指定拷贝的源地址。
    // meta.setHeader(OSSHeaders.COPY_OBJECT_SOURCE, "/examplebucket/recode-test.txt");
    // 如果源Object的ETag值和您提供的ETag相等,则执行拷贝操作,并返回200 OK。
    // meta.setHeader(OSSHeaders.COPY_OBJECT_SOURCE_IF_MATCH, "5B3C1A2E053D763E1B002CC607C5****");
    // 如果源Object的ETag值和您提供的ETag不相等,则执行拷贝操作,并返回200 OK。
    // meta.setHeader(OSSHeaders.COPY_OBJECT_SOURCE_IF_NONE_MATCH, "5B3C1A2E053D763E1B002CC607C5****");
    // 如果指定的时间等于或者晚于文件实际修改时间,则正常拷贝文件,并返回200 OK。
    // meta.setHeader(OSSHeaders.COPY_OBJECT_SOURCE_IF_UNMODIFIED_SINCE, "2021-12-09T07:01:56.000Z");
    // 如果源Object在指定时间后被修改过,则执行拷贝操作。
    // meta.setHeader(OSSHeaders.COPY_OBJECT_SOURCE_IF_MODIFIED_SINCE, "2021-12-09T07:01:56.000Z");
    // 指定设置目标Object元信息的方式。此处设置为COPY,表示复制源Object的元数据到目标Object。
    // meta.setHeader(OSSHeaders.COPY_OBJECT_METADATA_DIRECTIVE, "COPY");
    // 指定OSS创建目标Object时使用的服务器端加密算法。
    // meta.setHeader(OSSHeaders.OSS_SERVER_SIDE_ENCRYPTION, ObjectMetadata.KMS_SERVER_SIDE_ENCRYPTION);
    // 表示KMS托管的用户主密钥,该参数仅在x-oss-server-side-encryption为KMS时有效。
    // meta.setHeader(OSSHeaders.OSS_SERVER_SIDE_ENCRYPTION_KEY_ID, "9468da86-3509-4f8d-a61e-6eab1eac****");
    // 指定OSS创建目标Object时的访问权限,此处设置为Private,表示只有Object的拥有者和授权用户有该Object的读写权限,其他用户没有权限操作该Object。
    // meta.setHeader(OSSHeaders.OSS_OBJECT_ACL, CannedAccessControlList.Private);
    // 指定Object的存储类型。此处设置为Standard,表示标准存储类型。
    // meta.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard);
    // 指定Object的对象标签,可同时设置多个标签。
    // meta.setHeader(OSSHeaders.OSS_TAGGING, "a:1");
    // 指定设置目标Object对象标签的方式。此处设置为COPY,表示复制源Object的对象标签到目标Object。
    // meta.setHeader(OSSHeaders.COPY_OBJECT_TAGGING_DIRECTIVE, "COPY");
    copyObjectRequest.setNewObjectMetadata(meta);
    
    // 复制文件。
    CopyObjectResult result = ossClient.copyObject(copyObjectRequest);
    System.out.println("ETag: " + result.getETag() + " LastModified: " + result.getLastModified());
    
    // 关闭OSSClient。
    ossClient.shutdown();                   

拷贝大文件

对于大于1 GB的文件,需要使用分片拷贝(UploadPartCopy)。分片拷贝分为三步:

  1. 通过ossClient.initiateMultipartUpload初始化分片拷贝任务。
  2. 通过ossClient.uploadPartCopy进行分片拷贝。除最后一个分片外,其它分片都要大于100KB。
  3. 通过ossClient.completeMultipartUpload提交分片拷贝任务。

以下代码用于通过分片拷贝将srcexamplebucket中的srcexampleobject.txt文件拷贝到目标存储空间desexamplebucket中的desexampleobject.txt文件。

// yourEndpoint填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
String endpoint = "yourEndpoint";
// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
String accessKeyId = "yourAccessKeyId";
String accessKeySecret = "yourAccessKeySecret";
// 填写源Bucket名称。
String sourceBucketName = "srcexamplebucket";
// 填写源Object的完整路径。Object完整路径中不能包含Bucket名称。
String sourceKey = "srcexampleobject.txt";
// 填写与源Bucket处于同一地域的目标Bucket名称。
String destinationBucketName = "desexamplebucket";
// 填写目标Object的完整路径。Object完整路径中不能包含Bucket名称。
String destinationKey = "desexampleobject.txt";

// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

ObjectMetadata objectMetadata = ossClient.getObjectMetadata(sourceBucketName, sourceKey);
// 获取被拷贝文件的大小。
long contentLength = objectMetadata.getContentLength();

// 设置分片大小为10 MB。单位为字节。
long partSize = 1024 * 1024 * 10;

// 计算分片总数。
int partCount = (int) (contentLength / partSize);
if (contentLength % partSize != 0) {
    partCount++;
}
System.out.println("total part count:" + partCount);

// 初始化拷贝任务。可以通过InitiateMultipartUploadRequest指定目标文件元信息。
InitiateMultipartUploadRequest initiateMultipartUploadRequest = new InitiateMultipartUploadRequest(destinationBucketName, destinationKey);
InitiateMultipartUploadResult initiateMultipartUploadResult = ossClient.initiateMultipartUpload(initiateMultipartUploadRequest);
String uploadId = initiateMultipartUploadResult.getUploadId();

// 分片拷贝。
List<PartETag> partETags = new ArrayList<PartETag>();
for (int i = 0; i < partCount; i++) {
     // 计算每个分片的大小。
    long skipBytes = partSize * i;
    long size = partSize < contentLength - skipBytes ? partSize : contentLength - skipBytes;

    // 创建UploadPartCopyRequest。可以通过UploadPartCopyRequest指定限定条件。
    UploadPartCopyRequest uploadPartCopyRequest = 
        new UploadPartCopyRequest(sourceBucketName, sourceKey, destinationBucketName, destinationKey);
    uploadPartCopyRequest.setUploadId(uploadId);
    uploadPartCopyRequest.setPartSize(size);
    uploadPartCopyRequest.setBeginIndex(skipBytes);
    uploadPartCopyRequest.setPartNumber(i + 1);
    //Map headers = new HashMap();
    // 指定拷贝的源地址。
    // headers.put(OSSHeaders.COPY_OBJECT_SOURCE, "/examplebucket/desexampleobject.txt");
    // 指定源Object的拷贝范围。例如设置bytes=0~1023,表示拷贝1~1024字节的内容。
    // headers.put(OSSHeaders.COPY_SOURCE_RANGE, "bytes=0~1023");
    // 如果源Object的ETag值和您提供的ETag相等,则执行拷贝操作,并返回200 OK。
    // headers.put(OSSHeaders.COPY_OBJECT_SOURCE_IF_MATCH, "5B3C1A2E053D763E1B002CC607C5****");
    // 如果源Object的ETag值和您提供的ETag不相等,则执行拷贝操作,并返回200 OK。
    // headers.put(OSSHeaders.COPY_OBJECT_SOURCE_IF_NONE_MATCH, "5B3C1A2E053D763E1B002CC607C5****");
    // 如果指定的时间等于或者晚于文件实际修改时间,则正常拷贝文件,并返回200 OK。
    // headers.put(OSSHeaders.COPY_OBJECT_SOURCE_IF_UNMODIFIED_SINCE, "2021-12-09T07:01:56.000Z");
    // 如果源Object在用户指定的时间以后被修改过,则执行拷贝操作。
    // headers.put(OSSHeaders.COPY_OBJECT_SOURCE_IF_MODIFIED_SINCE, "2021-12-09T07:01:56.000Z");
    // uploadPartCopyRequest.setHeaders(headers);
    UploadPartCopyResult uploadPartCopyResult = ossClient.uploadPartCopy(uploadPartCopyRequest);

    // 将返回的分片ETag保存到partETags中。
    partETags.add(uploadPartCopyResult.getPartETag());
}

// 提交分片拷贝任务。
CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest(
                    destinationBucketName, destinationKey, uploadId, partETags);
ossClient.completeMultipartUpload(completeMultipartUploadRequest);

// 关闭OSSClient。
ossClient.shutdown();           

相关文档

  • 拷贝小文件

    关于拷贝小文件的API接口说明,请参见CopyObject

  • 拷贝大文件
    • 关于拷贝大文件的完整示例代码,请参见GitHub示例
    • 关于拷贝大文件的API接口说明,请参见UploadPartCopy