追加上传指的是在已存在的对象数据末尾追加写入新数据。追加上传创建的对象类型为追加类型(Appendable Object),可在对象末尾追加写入数据。普通上传和分片上传创建的对象类型为普通类型(Normal Object),无法追加写入数据。
tos:PutObject
权限,具体操作请参见权限配置指南。使用追加上传时, 限制条件如下:
以下代码展示如何将字符串多次追加上传到目标桶 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。