You need to enable JavaScript to run this app.
导航

追加上传(Java SDK)

最近更新时间2024.02.04 18:30:53

首次发布时间2021.11.27 17:58:44

追加上传指的是在已存在的对象数据末尾追加写入新数据。追加上传创建的对象类型为追加类型(Appendable Object),可在对象末尾追加写入数据。普通上传和分片上传创建的对象类型为普通类型(Normal Object),无法追加写入数据。

注意事项

  • 追加上传对象前,您必须具有 tos:PutObject 权限,具体操作请参见权限配置指南
  • 上传对象时,对象名必须满足一定规范,详细信息请参见对象命名规范
  • TOS 是面向海量存储设计的分布式对象存储产品,内部分区存储了对象索引数据,为横向扩展您上传对象和下载对象时的最大吞吐量,和减小热点分区的概率,请您避免使用字典序递增的对象命名方式,详细信息,请参见性能优化
  • 如果桶中已经存在同名对象,则新对象会覆盖已有的对象。桶开启多版本的场景下,则会保留原有对象,生成一个新版本号用于标识新上传的对象。
  • 追加上传对象不支持 Chunk-Encoded 的请求方式,当您追加上传网络流时请迭代获取数据再追加上传。

限制说明

使用追加上传时, 限制条件如下:

  • 追加类型的对象大小不能大于 5 GiB。
  • 对追加类型的对象进行普通上传,对象会被覆盖,且对象类型由追加类型变为普通类型。
  • 普通类型的对象不支持对其进行追加上传。
  • 追加类型的对象不支持拷贝(CopyObject)。
  • 追加上传对象不支持 Chunk-Encoded 的请求方式,使用该接口时请保证设置上传的数据长度值。
  • 如果您的桶处于开启或者暂停多版本功能的状态下,则该桶无法使用追加上传对象接口。
  • 如果您的桶的存储类型为低频存储,则该桶无法使用追加上传对象接口。

追加上传字符串/byte 数组

以下代码展示如何将字符串多次追加上传到目标桶 bucket-example 中的 example_dir 目录下的 example_object.txt 文件。

import com.volcengine.tos.TOSV2;
import com.volcengine.tos.TOSV2ClientBuilder;
import com.volcengine.tos.TosClientException;
import com.volcengine.tos.TosServerException;
import com.volcengine.tos.model.object.AppendObjectInput;
import com.volcengine.tos.model.object.AppendObjectOutput;

import java.io.ByteArrayInputStream;
import java.util.Arrays;

public class AppendObjectWithByteArrayInputStreamExample {
    public static void main(String[] args) {
        String endpoint = "your endpoint";
        String region = "your region";
        String accessKey = System.getenv("TOS_ACCESS_KEY");
        String secretKey = System.getenv("TOS_SECRET_KEY");

        String bucketName = "bucket-example";
        // 对象名,模拟 example_dir 下的 example_object.txt 文件
        String objectKey = "example_dir/example_object.txt";

        TOSV2 tos = new TOSV2ClientBuilder().build(region, endpoint, accessKey, secretKey);

        try{
            // 第一次追加写
            byte[] data1 = new byte[128 * 1024];
            Arrays.fill(data1, (byte) 'A');
            ByteArrayInputStream stream = new ByteArrayInputStream(data1);
            // 注意:当前 TOS 使用 appendObject 接口时需要传入数据长度和偏移量
            long contentLength = data1.length;
            // 第一次追加写,偏移量为0
            long offset = 0;
            AppendObjectInput input = new AppendObjectInput().setBucket(bucketName).setKey(objectKey)
                    .setContent(stream).setContentLength(contentLength).setOffset(offset);

//            // 如果需要设置 appendable 对象的元数据,可在第一次追加写的时候设置,后续无需再添加
//            ObjectMetaRequestOptions options = new ObjectMetaRequestOptions();
//            // 以下所有设置参数均为可选,参数值仅供参考,请根据业务实际需要进行设置。
//            // 设置对象访问权限,此处为私有权限
//            options.setAclType(ACLType.ACL_PRIVATE);
//            // 设置对象存储类型
//            options.setStorageClass(StorageClassType.STORAGE_CLASS_STANDARD);
//            // SDK 默认会根据 objectKey 后缀识别 Content-Type,也可以自定义设置
//            options.setContentType("application/json");
//            // 自定义对象的元数据,对于自定义的元数据,SDK 会自动对 key 添加
//            // "X-Tos-Meta-" 的前缀,因此用户无需自行添加。
//            Map<String, String> custom = new HashMap<>();
//            custom.put("name", "volc_user");
//            // 在 TOS 服务端存储的元数据为:"X-Tos-Meta-name: volc_user"
//            options.setCustomMetadata(custom);
//            input.setOptions(options);
            
            AppendObjectOutput output = tos.appendObject(input);
            System.out.println("appendObject first time succeed, object's nextAppendOffset is " + output.getNextAppendOffset());
            System.out.println("appendObject first time succeed, object's crc64 is " + output.getHashCrc64ecma());

            // 第二次追加写
            byte[] data2 = new byte[128 * 1024 + 1024];
            Arrays.fill(data2, (byte) 'B');
            contentLength = data2.length;
            // 偏移量可以从上次 appendObject 的结果中获取。
            offset = output.getNextAppendOffset();
            // 由于 SDK 默认开启 crc64 校验,从第二次追加写开始,之后每次调用都需要传入上一次追加写请求返回的 crc64 值。
            String preHashCrc64 = output.getHashCrc64ecma();
            input = new AppendObjectInput().setBucket(bucketName).setKey(objectKey).setContent(new ByteArrayInputStream(data2))
                    .setOffset(offset).setContentLength(contentLength).setPreHashCrc64ecma(preHashCrc64);
            output = tos.appendObject(input);
            System.out.println("appendObject second time succeed, object's nextAppendOffset is " + output.getNextAppendOffset());
            System.out.println("appendObject second time succeed, object's crc64 is " + output.getHashCrc64ecma());

            // 第三次追加写
            byte[] data3 = new byte[256 * 1024 + 1024];
            Arrays.fill(data3, (byte) 'C');
            contentLength = data3.length;
            // 偏移量可以从上次 appendObject 的结果中获取。
            offset = output.getNextAppendOffset();
            // 由于 SDK 默认开启 crc64 校验,从第二次追加写开始,之后每次调用都需要传入上一次追加写请求返回的 crc64 值。
            preHashCrc64 = output.getHashCrc64ecma();
            input = new AppendObjectInput().setBucket(bucketName).setKey(objectKey).setContent(new ByteArrayInputStream(data3))
                    .setOffset(offset).setContentLength(contentLength).setPreHashCrc64ecma(preHashCrc64);
            output = tos.appendObject(input);
            System.out.println("appendObject third time succeed, object's nextAppendOffset is " + output.getNextAppendOffset());
            System.out.println("appendObject third time succeed, object's crc64 is " + output.getHashCrc64ecma());
        } catch (TosClientException e) {
            // 操作失败,捕获客户端异常,一般情况是请求参数错误,此时请求并未发送
            System.out.println("appendObject failed");
            System.out.println("Message: " + e.getMessage());
            if (e.getCause() != null) {
                e.getCause().printStackTrace();
            }
        } catch (TosServerException e) {
            // 操作失败,捕获服务端异常,可以获取到从服务端返回的详细错误信息
            System.out.println("appendObject failed");
            System.out.println("StatusCode: " + e.getStatusCode());
            System.out.println("Code: " + e.getCode());
            System.out.println("Message: " + e.getMessage());
            System.out.println("RequestID: " + e.getRequestID());
        } catch (Throwable t) {
            // 作为兜底捕获其他异常,一般不会执行到这里
            System.out.println("appendObject failed");
            System.out.println("unexpected exception, message: " + t.getMessage());
        }
    }
}

追加上传本地文件

以下代码展示如何追加上传一个本地文件 example_file.txt 上传到目标桶 bucket-example 中的 example_dir 目录下的 example_object.txt 文件。

import com.volcengine.tos.TOSV2;
import com.volcengine.tos.TOSV2ClientBuilder;
import com.volcengine.tos.TosClientException;
import com.volcengine.tos.TosServerException;
import com.volcengine.tos.model.object.AppendObjectInput;
import com.volcengine.tos.model.object.AppendObjectOutput;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class AppendObjectWithFileInputStreamExample {
    public static void main(String[] args) {
        String endpoint = "your endpoint";
        String region = "your region";
        String accessKey = System.getenv("TOS_ACCESS_KEY");
        String secretKey = System.getenv("TOS_SECRET_KEY");

        String bucketName = "bucket-example";
        // 对象名,模拟 example_dir 下的 example_object.txt 文件
        String objectKey = "example_dir/example_object.txt";
        // 本地文件路径,请保证文件存在,暂不支持文件夹功能
        String filePath1 = "example_dir/example_file_1.txt";
        // 本地文件路径,请保证文件存在,暂不支持文件夹功能
        String filePath2 = "example_dir/example_file_2.txt";
        // 本地文件路径,请保证文件存在,暂不支持文件夹功能
        String filePath3 = "example_dir/example_file_3.txt";

        TOSV2 tos = new TOSV2ClientBuilder().build(region, endpoint, accessKey, secretKey);

        try{
            AppendObjectOutput output = null;
            // 第一次追加写
            try(FileInputStream stream = new FileInputStream(filePath1)) {
                // 注意:当前 TOS 使用 appendObject 接口时需要传入数据长度和偏移量
                // 此处为文件长度
                long contentLength = new File(filePath1).length();
                // 第一次追加写,偏移量为0
                long offset = 0;
                AppendObjectInput input = new AppendObjectInput().setBucket(bucketName).setKey(objectKey)
                        .setContent(stream).setContentLength(contentLength).setOffset(offset);

//                // 如果需要设置 appendable 对象的元数据,可在第一次追加写的时候设置,后续无需再添加
//                ObjectMetaRequestOptions options = new ObjectMetaRequestOptions();
//                // 以下所有设置参数均为可选,参数值仅供参考,请根据业务实际需要进行设置。
//                // 设置对象访问权限,此处为私有权限
//                options.setAclType(ACLType.ACL_PRIVATE);
//                // 设置对象存储类型
//                options.setStorageClass(StorageClassType.STORAGE_CLASS_STANDARD);
//                // SDK 默认会根据 objectKey 后缀识别 Content-Type,也可以自定义设置
//                options.setContentType("application/json");
//                // 自定义对象的元数据,对于自定义的元数据,SDK 会自动对 key 添加
//                // "X-Tos-Meta-" 的前缀,因此用户无需自行添加。
//                Map<String, String> custom = new HashMap<>();
//                custom.put("name", "volc_user");
//                // 在 TOS 服务端存储的元数据为:"X-Tos-Meta-name: volc_user"
//                options.setCustomMetadata(custom);
//                input.setOptions(options);
                
                output = tos.appendObject(input);
                System.out.println("appendObject first time succeed, object's nextAppendOffset is " + output.getNextAppendOffset());
                System.out.println("appendObject first time succeed, object's crc64 is " + output.getHashCrc64ecma());
            } catch (IOException e) {
                System.out.println("appendObject read file failed");
                e.printStackTrace();
            }


            // 第二次追加写
            try(FileInputStream stream = new FileInputStream(filePath2)) {
                // 注意:当前 TOS 使用 appendObject 接口时需要传入数据长度和偏移量
                // 此处为文件长度
                long contentLength = new File(filePath2).length();
                // 偏移量可以从上次 appendObject 的结果中获取,也可以通过 headObject 获取 content-length。
                long offset = output.getNextAppendOffset();
                // 由于 SDK 默认开启 crc64 校验,从第二次追加写开始,之后每次调用都需要传入上一次追加写请求返回的 crc64 值。
                String preHashCrc64 = output.getHashCrc64ecma();
                AppendObjectInput input = new AppendObjectInput().setBucket(bucketName).setKey(objectKey).setContent(stream)
                        .setOffset(offset).setContentLength(contentLength).setPreHashCrc64ecma(preHashCrc64);
                output = tos.appendObject(input);
                System.out.println("appendObject second time succeed, object's nextAppendOffset is " + output.getNextAppendOffset());
                System.out.println("appendObject second time succeed, object's crc64 is " + output.getHashCrc64ecma());
            } catch (IOException e) {
                System.out.println("appendObject read file failed");
                e.printStackTrace();
            }

            // 第三次追加写
            try(FileInputStream stream = new FileInputStream(filePath3)) {
                // 注意:当前 TOS 使用 appendObject 接口时需要传入数据长度和偏移量
                // 此处为文件长度
                long contentLength = new File(filePath3).length();
                // 偏移量可以从上次 appendObject 的结果中获取。
                long offset = output.getNextAppendOffset();
                // 由于 SDK 默认开启 crc64 校验,从第二次追加写开始,之后每次调用都需要传入上一次追加写请求返回的 crc64 值。
                String preHashCrc64 = output.getHashCrc64ecma();
                AppendObjectInput input = new AppendObjectInput().setBucket(bucketName).setKey(objectKey).setContent(stream)
                        .setOffset(offset).setContentLength(contentLength).setPreHashCrc64ecma(preHashCrc64);
                output = tos.appendObject(input);
                System.out.println("appendObject second time succeed, object's nextAppendOffset is " + output.getNextAppendOffset());
                System.out.println("appendObject second time succeed, object's crc64 is " + output.getHashCrc64ecma());
            } catch (IOException e) {
                System.out.println("appendObject read file failed");
                e.printStackTrace();
            }
        } catch (TosClientException e) {
            // 操作失败,捕获客户端异常,一般情况是请求参数错误,此时请求并未发送
            System.out.println("appendObject failed");
            System.out.println("Message: " + e.getMessage());
            if (e.getCause() != null) {
                e.getCause().printStackTrace();
            }
        } catch (TosServerException e) {
            // 操作失败,捕获服务端异常,可以获取到从服务端返回的详细错误信息
            System.out.println("appendObject failed");
            System.out.println("StatusCode: " + e.getStatusCode());
            System.out.println("Code: " + e.getCode());
            System.out.println("Message: " + e.getMessage());
            System.out.println("RequestID: " + e.getRequestID());
        } catch (Throwable t) {
            // 作为兜底捕获其他异常,一般不会执行到这里
            System.out.println("appendObject failed");
            System.out.println("unexpected exception, message: " + t.getMessage());
        }
    }
}

上传时处理进度条和设置客户端限速

以下代码展示如何使用进度条功能和设置上传客户端限速,以追加上传字符串/byte 数组为例。

import com.volcengine.tos.TOSV2;
import com.volcengine.tos.TOSV2ClientBuilder;
import com.volcengine.tos.TosClientException;
import com.volcengine.tos.TosServerException;
import com.volcengine.tos.comm.event.DataTransferListener;
import com.volcengine.tos.comm.event.DataTransferStatus;
import com.volcengine.tos.comm.event.DataTransferType;
import com.volcengine.tos.comm.ratelimit.RateLimiter;
import com.volcengine.tos.internal.util.ratelimit.DefaultRateLimiter;
import com.volcengine.tos.model.object.AppendObjectInput;
import com.volcengine.tos.model.object.AppendObjectOutput;

import java.io.ByteArrayInputStream;
import java.util.Arrays;

public class AppendObjectWithProgressAndRateLimiterExample {
    public static void main(String[] args) {
        String endpoint = "your endpoint";
        String region = "your region";
        String accessKey = System.getenv("TOS_ACCESS_KEY");
        String secretKey = System.getenv("TOS_SECRET_KEY");

        String bucketName = "bucket-example";
        // 对象名,模拟 example_dir 下的 example_object.txt 文件
        String objectKey = "example_dir/example_object.txt.12123123";

        TOSV2 tos = new TOSV2ClientBuilder().build(region, endpoint, accessKey, secretKey);

        try{
            // 第一次追加写
            byte[] data1 = new byte[128 * 1024];
            Arrays.fill(data1, (byte) 'A');
            ByteArrayInputStream stream = new ByteArrayInputStream(data1);
            // 注意:当前 TOS 使用 appendObject 接口时需要传入数据长度和偏移量
            long contentLength = data1.length;
            // 第一次追加写,偏移量为0
            long offset = 0;
            AppendObjectInput input = new AppendObjectInput().setBucket(bucketName).setKey(objectKey)
                    .setContent(stream).setContentLength(contentLength).setOffset(offset);

//            // 如果需要设置 appendable 对象的元数据,可在第一次追加写的时候设置,后续无需再添加
//            ObjectMetaRequestOptions options = new ObjectMetaRequestOptions();
//            // 以下所有设置参数均为可选,参数值仅供参考,请根据业务实际需要进行设置。
//            // 设置对象访问权限,此处为私有权限
//            options.setAclType(ACLType.ACL_PRIVATE);
//            // 设置对象存储类型
//            options.setStorageClass(StorageClassType.STORAGE_CLASS_STANDARD);
//            // SDK 默认会根据 objectKey 后缀识别 Content-Type,也可以自定义设置
//            options.setContentType("application/json");
//            // 自定义对象的元数据,对于自定义的元数据,SDK 会自动对 key 添加
//            // "X-Tos-Meta-" 的前缀,因此用户无需自行添加。
//            Map<String, String> custom = new HashMap<>();
//            custom.put("name", "volc_user");
//            // 在 TOS 服务端存储的元数据为:"X-Tos-Meta-name: volc_user"
//            options.setCustomMetadata(custom);
//            input.setOptions(options);

            // 以下代码展示如何处理进度条
            // 设置文件大小,可在进度条中显示 total 总长度,否则 DataTransferStatus.getTotalBytes 值为 -1。
            input.setContentLength(contentLength);
            // 自定义实现 DataTransferListener,实现进度条功能
            DataTransferListener listener = getDataTransferListener();
            input.setDataTransferListener(listener);

            // 以下代码展示如何设置客户端限速
            // 配置上传对象最大限速为 20MiB/s,平均限速为 5MiB/s。
            RateLimiter limiter = new DefaultRateLimiter(20 * 1024 * 1024, 5 * 1024 * 1024);
            input.setRateLimiter(limiter);

            AppendObjectOutput output = tos.appendObject(input);
            System.out.println("appendObject first time succeed, object's nextAppendOffset is " + output.getNextAppendOffset());
            System.out.println("appendObject first time succeed, object's crc64 is " + output.getHashCrc64ecma());

            // 第二次追加写
            byte[] data2 = new byte[128 * 1024 + 1024];
            Arrays.fill(data2, (byte) 'B');
            contentLength = data2.length;
            // 偏移量可以从上次 appendObject 的结果中获取。
            offset = output.getNextAppendOffset();
            // 由于 SDK 默认开启 crc64 校验,从第二次追加写开始,之后每次调用都需要传入上一次追加写请求返回的 crc64 值。
            String preHashCrc64 = output.getHashCrc64ecma();
            input.setContent(new ByteArrayInputStream(data2)).setOffset(offset).setContentLength(contentLength).setPreHashCrc64ecma(preHashCrc64);
            output = tos.appendObject(input);
            System.out.println("appendObject second time succeed, object's nextAppendOffset is " + output.getNextAppendOffset());
            System.out.println("appendObject second time succeed, object's crc64 is " + output.getHashCrc64ecma());

            // 第三次追加写
            byte[] data3 = new byte[256 * 1024 + 1024];
            Arrays.fill(data3, (byte) 'C');
            contentLength = data3.length;
            // 偏移量可以从上次 appendObject 的结果中获取。
            offset = output.getNextAppendOffset();
            // 由于 SDK 默认开启 crc64 校验,从第二次追加写开始,之后每次调用都需要传入上一次追加写请求返回的 crc64 值。
            preHashCrc64 = output.getHashCrc64ecma();
            input.setContent(new ByteArrayInputStream(data3)).setOffset(offset).setContentLength(contentLength).setPreHashCrc64ecma(preHashCrc64);
            output = tos.appendObject(input);
            System.out.println("appendObject third time succeed, object's nextAppendOffset is " + output.getNextAppendOffset());
            System.out.println("appendObject third time succeed, object's crc64 is " + output.getHashCrc64ecma());
        } catch (TosClientException e) {
            // 操作失败,捕获客户端异常,一般情况是请求参数错误,此时请求并未发送
            System.out.println("appendObject failed");
            System.out.println("Message: " + e.getMessage());
            if (e.getCause() != null) {
                e.getCause().printStackTrace();
            }
        } catch (TosServerException e) {
            // 操作失败,捕获服务端异常,可以获取到从服务端返回的详细错误信息
            System.out.println("appendObject failed");
            System.out.println("StatusCode: " + e.getStatusCode());
            System.out.println("Code: " + e.getCode());
            System.out.println("Message: " + e.getMessage());
            System.out.println("RequestID: " + e.getRequestID());
        } catch (Throwable t) {
            // 作为兜底捕获其他异常,一般不会执行到这里
            System.out.println("appendObject failed");
            System.out.println("unexpected exception, message: " + t.getMessage());
        }
    }

    private static DataTransferListener getDataTransferListener() {
        return new DataTransferListener() {
            // 自定义实现 DataTransferListener 的 dataTransferStatusChange 接口
            @Override
            public void dataTransferStatusChange(DataTransferStatus dataTransferStatus) {
                if (dataTransferStatus.getType() == DataTransferType.DATA_TRANSFER_STARTED) {
                    System.out.println("appendObject started.");
                } else if (dataTransferStatus.getType() == DataTransferType.DATA_TRANSFER_RW) {
                    System.out.printf("appendObject, send %d bytes once, has sent %d bytes, total %d bytes.\n",
                            dataTransferStatus.getRwOnceBytes(), dataTransferStatus.getConsumedBytes(),
                            dataTransferStatus.getTotalBytes());
                } else if (dataTransferStatus.getType() == DataTransferType.DATA_TRANSFER_FAILED) {
                    System.out.printf("appendObject failed, has sent %d bytes, total %d bytes.\n",
                            dataTransferStatus.getConsumedBytes(), dataTransferStatus.getTotalBytes());
                } else if (dataTransferStatus.getType() == DataTransferType.DATA_TRANSFER_SUCCEED) {
                    System.out.printf("appendObject succeed, has sent %d bytes, total %d bytes.\n",
                            dataTransferStatus.getConsumedBytes(), dataTransferStatus.getTotalBytes());
                }
            }
        };
    }
}

相关文档

关于追加上传的 API 文档,请参见 AppendObject