【Azure 存储服务】使用 AppendBlobClient 对象实现对Blob进行追加内容操作

简介: 【Azure 存储服务】使用 AppendBlobClient 对象实现对Blob进行追加内容操作

问题描述

在Azure Blob的官方示例中,都是对文件进行上传到Blob操作,没有实现对已创建的Blob进行追加的操作。如果想要实现对一个文件的多次追加操作,每一次写入的时候,只传入新的内容?

 

问题解答

Azure Storage Blob 有三种类型: Block Blob, Append Blob 和 Page Blob。其中,只有Append Blob类型支持追加(Append)操作。并且Blob类型在创建时就已经确定,无法后期修改。

 

在查看Java Storage SDK后,发现可以使用AppendBlobClient来实现。

/**
     * Creates a new {@link AppendBlobClient} associated with this blob.
     *
     * @return A {@link AppendBlobClient} associated with this blob.
     */
    public AppendBlobClient getAppendBlobClient() {
        return new SpecializedBlobClientBuilder()
            .blobClient(this)
            .buildAppendBlobClient();
    }

在 AppendBlobClient 类,有 appendBlock 和 appendBlockWithResponse 等多种方法来实现追加。方法定义源码如下:

View Code

代码实现

第一步: 在Java项目 pom.xml 中引入Azure Storage Blob依赖

<dependency>
      <groupId>com.azure</groupId>
      <artifactId>azure-storage-blob</artifactId>
      <version>12.13.0</version>
    </dependency>

第二步: 引入必要的 Storage 类

import java.io.ByteArrayInputStream; 
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; 
import java.time.LocalTime; 
import com.azure.core.http.rest.Response; 
import com.azure.storage.blob.BlobContainerClient;
import com.azure.storage.blob.BlobServiceClient;
import com.azure.storage.blob.BlobServiceClientBuilder;
import com.azure.storage.blob.models.AppendBlobItem;
import com.azure.storage.blob.models.AppendBlobRequestConditions; 
import com.azure.storage.blob.specialized.AppendBlobClient;

第三步:创建 AppendBlobClient 对象,使用 BlobServiceClient 及连接字符串(Connection String)

String storageConnectionString = "DefaultEndpointsProtocol=https;AccountName=*****;AccountKey=*******;EndpointSuffix=core.chinacloudapi.cn";

                String containerName = "appendblob";
                String fileName = "test.txt";
                // Create a BlobServiceClient object which will be used to create a container
                System.out.println("\nCreate a BlobServiceClient Object to Connect Storage Account");
                BlobServiceClient blobServiceClient = new BlobServiceClientBuilder()
                                .connectionString(storageConnectionString)
                                .buildClient();

                BlobContainerClient containerClient = blobServiceClient.getBlobContainerClient(containerName);
                if (!containerClient.exists())
                        containerClient.create();

                // Get a reference to a blob
                AppendBlobClient appendBlobClient = containerClient.getBlobClient(fileName).getAppendBlobClient();

 

第四步:调用 appendBlockWithResponse 方法追加内容,并根据返回状态码判断是否追加成功

boolean overwrite = true; // Default value
                if (!appendBlobClient.exists())
                        System.out.printf("Created AppendBlob at %s%n",
                                        appendBlobClient.create(overwrite).getLastModified());
                String data = "Test to append new content into exists blob! by blogs lu bian liang zhan deng @"
                                + LocalTime.now().toString() + "\n";
                InputStream inputStream = new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
                byte[] md5 = MessageDigest.getInstance("MD5").digest(data.getBytes(StandardCharsets.UTF_8));
                AppendBlobRequestConditions requestConditions = new AppendBlobRequestConditions();
                // Context context = new Context("key", "value");
                long length = data.getBytes().length;
                Response<AppendBlobItem> rsp = appendBlobClient.appendBlockWithResponse(inputStream, length, md5,
                                requestConditions, null, null);
                if (rsp.getStatusCode() == 201) {
                        System.out.println("append content successful........");
                }

运行结果展示

 

但如果操作的Blob类型不是Append Blob,就会遇见错误 Status code 409 ---- The blob type is invalid for this operation 错误

Exception in thread "main" com.azure.storage.blob.models.BlobStorageException: Status code 409, "
<?xml version="1.0" encoding="utf-8"?><Error>><Code>InvalidBlobType</Code>
<Message>The blob type is invalid for this operation.
RequestId:501ee0b9-301e-0003-4f7b-829ca6000000
Time:2023-05-09T13:37:17.7509942Z</Message></Error>"
        at java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:67)
        at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:500)
        at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:484)
        at com.azure.core.http.rest.RestProxy.instantiateUnexpectedException(RestProxy.java:343)
        at com.azure.core.http.rest.RestProxy.lambda$ensureExpectedStatus$5(RestProxy.java:382)
        at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:125)
        at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1815)
        at reactor.core.publisher.MonoCacheTime$CoordinatorSubscriber.signalCached(MonoCacheTime.java:337)
        at reactor.core.publisher.MonoCacheTime$CoordinatorSubscriber.onNext(MonoCacheTime.java:354)
        at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2397)
        at reactor.core.publisher.MonoCacheTime$CoordinatorSubscriber.onSubscribe(MonoCacheTime.java:293)
        at reactor.core.publisher.FluxFlatMap.trySubscribeScalarMap(FluxFlatMap.java:192)
        at reactor.core.publisher.MonoFlatMap.subscribeOrReturn(MonoFlatMap.java:53)
        at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:57)
        at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
        at reactor.core.publisher.MonoCacheTime.subscribeOrReturn(MonoCacheTime.java:143)
        at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:57)
        at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:157)
        at reactor.core.publisher.FluxDoFinally$DoFinallySubscriber.onNext(FluxDoFinally.java:130)
        at reactor.core.publisher.FluxHandle$HandleSubscriber.onNext(FluxHandle.java:118)
        at reactor.core.publisher.FluxMap$MapConditionalSubscriber.onNext(FluxMap.java:220)
        at reactor.core.publisher.FluxDoFinally$DoFinallySubscriber.onNext(FluxDoFinally.java:130)
        at reactor.core.publisher.FluxHandleFuseable$HandleFuseableSubscriber.onNext(FluxHandleFuseable.java:184)
        at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107)
        at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1815)
        at reactor.core.publisher.MonoCollectList$MonoCollectListSubscriber.onComplete(MonoCollectList.java:128)
        at reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:259)
        at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:142)
        at reactor.netty.channel.FluxReceive.onInboundComplete(FluxReceive.java:401)
        at reactor.netty.channel.ChannelOperations.onInboundComplete(ChannelOperations.java:416)
        at reactor.netty.channel.ChannelOperations.terminate(ChannelOperations.java:470)
        at reactor.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:685)
        at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:94)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
        at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655)
        at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581)
        at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
        at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.base/java.lang.Thread.run(Thread.java:1589)
        Suppressed: java.lang.Exception: #block terminated with an error
                at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:99)
                at reactor.core.publisher.Mono.block(Mono.java:1703)
                at com.azure.storage.common.implementation.StorageImplUtils.blockWithOptionalTimeout(StorageImplUtils.java:128)
                at com.azure.storage.blob.specialized.AppendBlobClient.appendBlockWithResponse(AppendBlobClient.java:259)
                at test.App.AppendBlobContent(App.java:68)
                at test.App.main(App.java:31)

 

参考资料

appendBlockWithResponse : https://learn.microsoft.com/en-us/java/api/com.azure.storage.blob.specialized.appendblobclient?view=azure-java-stable#com-azure-storage-blob-specialized-appendblobclient-appendblockwithresponse(java-io-inputstream-long-byte()-com-azure-storage-blob-models-appendblobrequestconditions-java-time-duration-com-azure-core-util-context)

Blob(对象)存储简介  : https://docs.azure.cn/zh-cn/storage/blobs/storage-blobs-introduction

相关文章
|
JavaScript 前端开发
javascript判断对象中是否存在某个字段
javascript判断对象中是否存在某个字段
|
网络安全 Docker 容器
内网穿透访问你家里的树莓派
你有一个自己的外网服务器,然后捏你又买了一个树莓派放家里吃灰,有一天,你觉得不能让你的树莓派吃灰,你想上班的时候用你的树莓派在家里编译arm用的镜像程序。于是,便有了本篇文章~~~如何通过内网穿透访问你家里的树莓派。(注:仅用于测试,闹着玩,千万别上生产,上生产需要自己进行改造用户权限以及审计功能,这部分就不贴出来了) 使用的工具:docker,rtty,rttys
522 0
内网穿透访问你家里的树莓派
|
10月前
|
调度 iOS开发 MacOS
python多进程一文够了!!!
本文介绍了高效编程中的多任务原理及其在Python中的实现。主要内容包括多任务的概念、单核和多核CPU的多任务实现、并发与并行的区别、多任务的实现方式(多进程、多线程、协程等)。详细讲解了进程的概念、使用方法、全局变量在多个子进程中的共享问题、启动大量子进程的方法、进程间通信(队列、字典、列表共享)、生产者消费者模型的实现,以及一个实际案例——抓取斗图网站的图片。通过这些内容,读者可以深入理解多任务编程的原理和实践技巧。
542 1
|
JavaScript 前端开发
JS之url进行编码和解码(三种方式)
JS之url进行编码和解码(三种方式)
19395 2
|
8月前
|
存储 人工智能 安全
基于区块链的数字身份认证:重塑身份安全的新范式
基于区块链的数字身份认证:重塑身份安全的新范式
922 16
|
12月前
|
开发框架 监控 前端开发
在 ASP.NET Core Web API 中使用操作筛选器统一处理通用操作
【9月更文挑战第27天】操作筛选器是ASP.NET Core MVC和Web API中的一种过滤器,可在操作方法执行前后运行代码,适用于日志记录、性能监控和验证等场景。通过实现`IActionFilter`接口的`OnActionExecuting`和`OnActionExecuted`方法,可以统一处理日志、验证及异常。创建并注册自定义筛选器类,能提升代码的可维护性和复用性。
169 3
实验深度理解Go中try...catch...的panic、defer、recover用法
文章通过实验代码演示了Go语言中如何使用panic、defer和recover函数来模拟try...catch...的异常处理机制,并详细解释了每个函数的作用和在异常处理中的使用场景。
136 0
|
Go API 开发者
Golang深入浅出之-文件与目录操作:os与path/filepath包
【4月更文挑战第26天】Go语言标准库`os`和`path/filepath`提供文件读写、目录操作等功能。本文涵盖`os.Open`, `os.Create`, `os.Mkdir`, `filepath.Join`等API的使用,强调了文件关闭、路径处理、并发写入和权限问题的处理,并给出实战代码示例,帮助开发者高效、安全地操作文件与目录。注意使用`defer`关闭文件,`filepath`处理路径分隔符,以及通过同步机制解决并发写入冲突。
801 2
|
机器学习/深度学习
大模型中的Scaling Law是什么?
【2月更文挑战第9天】大模型中的Scaling Law是什么?
16805 3
大模型中的Scaling Law是什么?
|
安全 数据挖掘 测试技术
深入研究:Go语言文件写入的性能差异
深入研究:Go语言文件写入的性能差异
289 0