追加上传指的是在已上传的Appendable类型Object后面直接追加内容。
前提条件
已创建云盒Bucket。具体操作,请参见创建云盒Bucket。
背景信息
通过简单上传生成的Object类型为Normal,通过分片上传生成的Object类型为Multipart。这两种类型Object在上传结束之后内容是固定的,只能读取,不能修改。如果Object内容发生了改变,只能重新上传同名的Object来覆盖之前的内容。
由于这一特性,如果通过上述方式上传视频监控、视频直播等领域生成的实时视频流,只能将视频流按照一定规律切分成小块然后不断地上传新的Object,在实际使用中存在以下缺点:
软件架构比较复杂,需要考虑文件分块等细节问题。
需要有位置保存元数据,比如已经生成的Object列表等,然后每次请求都重复读取元数据来判断是否有新的Object生成。这样对服务器的压力很大,而且客户端每次都需要发送两次网络请求,延时上也会有一定的影响。
如果Object切分较小能有效降低数据延时,但是切分过多的Object会引发管理复杂的问题。如果Object切分较大,则数据延时又会明显提升。
如果需要实时更新已上传Object内容的视频流,您需要先在本地进行视频拼接,然后通过OSS提供的追加上传(AppendObject)的方式上传视频,上传后将生成Appendable类型的Object。Appendable类型Object后面允许直接追加内容,且每次追加上传的数据都能够即时可读。
功能优势
通过追加上传,可在视频数据产生之后即时将数据上传至同一个Object,而客户端只需要定时获取该Object的长度,并与上次读取的长度进行对比。如果发现有新的可读数据,则触发一次读操作来获取新上传的数据部分即可。通过这种方式可以简化架构,增强扩展性。
使用限制
大小限制
Object大小不能超过5 GB。
命名限制
使用UTF-8编码。
长度必须在1~1023字符之间。
不能以正斜线(/)或者反斜线(\)字符开头。
操作限制
不支持通过追加上传的方式上传深度冷归档类型的Object。
不支持拷贝通过追加上传方式上传的Object,但允许修改Object本身的meta信息。
追加上传不支持上传回调操作。
操作步骤
使用阿里云SDK
仅支持通过Java SDK在已上传的Appendable类型Object后面直接追加内容,Java SDK要求3.15.0及以上版本。
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.AppendObjectRequest;
import com.aliyun.oss.model.AppendObjectResult;
import com.aliyun.oss.model.ObjectMetadata;
import java.io.ByteArrayInputStream;
import com.aliyun.oss.common.auth.DefaultCredentialProvider;
import com.aliyun.oss.common.comm.SignVersion;
import com.aliyun.oss.ClientBuilderConfiguration;
import com.aliyun.oss.common.auth.CredentialsProviderFactory;
import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
public class Demo {
public static void main(String[] args) throws Exception {
// 填写云盒Bucket的数据域名。
String endpoint = "https://cb-f8z7yvzgwfkl9q0h****.cn-hangzhou.oss-cloudbox.aliyuncs.com";
// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
// 填写云盒Bucket名称,例如examplebucket。
String bucketName = "examplebucket";
// 填写云盒Bucket所在地域。
String region = "cn-hangzhou";
// 填写云盒ID。
String cloudBoxId = "cb-f8z7yvzgwfkl9q0h****";
// 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
String objectName = "exampledir/exampleobject.txt";
String content1 = "Hello OSS A \n";
String content2 = "Hello OSS B \n";
String content3 = "Hello OSS C \n";
// 创建OSSClient实例。
ClientBuilderConfiguration conf = new ClientBuilderConfiguration();
conf.setSignatureVersion(SignVersion.V4);
OSS ossClient = OSSClientBuilder.create()
.endpoint(endpoint)
.credentialsProvider(new DefaultCredentialProvider(credentialsProvider.getCredentials()))
.clientConfiguration(conf)
.region(region)
.cloudBoxId(cloudBoxId)
.build();
try {
ObjectMetadata meta = new ObjectMetadata();
// 指定上传的内容类型。
meta.setContentType("text/plain");
// 指定该Object的网页缓存行为。
//meta.setCacheControl("no-cache");
// 指定该Object被下载时的名称。
//meta.setContentDisposition("attachment;filename=oss_download.txt");
// 指定该Object的内容编码格式。
//meta.setContentEncoding(OSSConstants.DEFAULT_CHARSET_NAME);
// 该请求头用于检查消息内容是否与发送时一致。
//meta.setContentMD5("ohhnqLBJFiKkPSBO1eNaUA==");
// 指定过期时间。
//try {
// meta.setExpirationTime(DateUtil.parseRfc822Date("Wed, 08 Jul 2022 16:57:01 GMT"));
//} catch (ParseException e) {
// e.printStackTrace();
//}
// 指定服务器端加密方式。此处指定为OSS完全托管密钥进行加密(SSE-OSS)。
//meta.setServerSideEncryption(ObjectMetadata.AES_256_SERVER_SIDE_ENCRYPTION);
// 指定Object的访问权限。此处指定为私有访问权限。
//meta.setObjectAcl(CannedAccessControlList.Private);
// 指定Object的存储类型。
//meta.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard);
// 创建AppendObject时可以添加x-oss-meta-*,继续追加时不可以携带此参数。如果配置以x-oss-meta-*为前缀的参数,则该参数视为元数据。
//meta.setHeader("x-oss-meta-author", "Alice");
// 通过AppendObjectRequest设置多个参数。
AppendObjectRequest appendObjectRequest = new AppendObjectRequest(bucketName, objectName, new ByteArrayInputStream(content1.getBytes()),meta);
// 通过AppendObjectRequest设置单个参数。
// 设置Bucket名称。
//appendObjectRequest.setBucketName(bucketName);
// 设置Object名称。
//appendObjectRequest.setKey(objectName);
// 设置待追加的内容。可选类型包括InputStream类型和File类型。此处为InputStream类型。
//appendObjectRequest.setInputStream(new ByteArrayInputStream(content1.getBytes()));
// 设置待追加的内容。可选类型包括InputStream类型和File类型。此处为File类型。
//appendObjectRequest.setFile(new File("D:\\localpath\\examplefile.txt"));
// 指定文件的元数据,第一次追加时有效。
//appendObjectRequest.setMetadata(meta);
// 第一次追加。
// 设置文件的追加位置。
appendObjectRequest.setPosition(0L);
AppendObjectResult appendObjectResult = ossClient.appendObject(appendObjectRequest);
// 文件的64位CRC值。此值根据ECMA-182标准计算得出。
System.out.println(appendObjectResult.getObjectCRC());
// 第二次追加。
// nextPosition表示下一次请求中应当提供的Position,即文件当前的长度。
appendObjectRequest.setPosition(appendObjectResult.getNextPosition());
appendObjectRequest.setInputStream(new ByteArrayInputStream(content2.getBytes()));
appendObjectResult = ossClient.appendObject(appendObjectRequest);
// 第三次追加。
appendObjectRequest.setPosition(appendObjectResult.getNextPosition());
appendObjectRequest.setInputStream(new ByteArrayInputStream(content3.getBytes()));
appendObjectResult = ossClient.appendObject(appendObjectRequest);
} catch (OSSException oe) {
System.out.println("Caught an OSSException, which means your request made it to OSS, "
+ "but was rejected with an error response for some reason.");
System.out.println("Error Message:" + oe.getErrorMessage());
System.out.println("Error Code:" + oe.getErrorCode());
System.out.println("Request ID:" + oe.getRequestId());
System.out.println("Host ID:" + oe.getHostId());
} catch (ClientException ce) {
System.out.println("Caught an ClientException, which means the client encountered "
+ "a serious internal problem while trying to communicate with OSS, "
+ "such as not being able to access the network.");
System.out.println("Error Message:" + ce.getMessage());
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
}
}
使用命令行工具ossutil
关于使用ossutil追加上传的具体操作,请参见appendfromfile(追加上传)。
使用REST API
如果您的程序自定义要求较高,您可以直接发起REST API请求。直接发起REST API请求需要手动编写代码计算签名。更多信息,请参见AppendObject。