SpringBoot移动文件至Azure存储容器后无法读取文件问题
问题根因
当前代码存在4个核心问题,直接导致读取失败、认证报错:
- SAS令牌作用域错误:生成的SAS仅对临时容器内的源文件生效,文件移动到永久容器、源文件被删除后,该SAS既不匹配目标文件的位置,也没有永久容器的访问权限,用它拼接目标文件URL访问必然报认证失败。
- 异步复制未等待完成:Azure Blob的
beginCopy是异步操作,调用后仅会发起复制请求,不会等待文件复制完成。当前代码发起复制后立刻删除源文件、读取目标文件,此时目标文件可能还未生成、或复制未完成,自然无法读取。 - 文件读取API误用:
java.io.File类仅支持访问本地文件系统的文件,传入HTTP/HTTPS格式的Blob URL根本无法读取远程资源,这是抛出Can't read input file异常的直接原因。 - 缩略图存储逻辑错误:生成缩略图后直接写入本地File对象,文件仅存在于应用服务器本地磁盘,并未上传回Azure Blob永久容器,业务侧根本无法访问到生成的缩略图。
修复步骤
1. 修正文件复制逻辑,等待复制完成后再操作
替换moveFileToNewPlace方法中的复制逻辑,阻塞等待复制完成后再删除源文件,避免复制未完成就操作目标文件:
// 原代码 destBlobClient.beginCopy(String.format("%s?%s", srcBlobClient.getBlobUrl(), sasToken), null); // 替换为以下逻辑 SyncPoller<BlobCopyInfo, Void> copyPoller = destBlobClient.beginCopy( String.format("%s?%s", srcBlobClient.getBlobUrl(), sasToken), null ); // 等待复制完成,超时时间可根据业务最大文件大小调整 copyPoller.waitForCompletion(Duration.ofMinutes(3)); // 确认复制成功后再删除源文件 srcBlobClient.delete();
需要额外导入依赖:java.time.Duration、com.azure.core.util.polling.SyncPoller、com.azure.storage.blob.models.BlobCopyInfo
2. 重写缩略图生成逻辑,直接通过Azure SDK读取Blob流
不要用new File(URL)的方式读远程文件,直接通过Blob客户端打开输入流读取图片,跳过SAS拼接、远程URL读取的问题,同时优化缩放质量:
public BufferedImage generateImageThumbnail(BlobClient blobClient) throws IOException { // 直接通过SDK鉴权读取Blob流,不需要依赖SAS try (InputStream blobInputSteam = blobClient.openInputStream()) { BufferedImage originalImg = ImageIO.read(blobInputSteam); if (originalImg == null) { throw new IOException("图片格式不支持,无法解析"); } BufferedImage thumbnail = new BufferedImage(300, 300, BufferedImage.TYPE_INT_RGB); Graphics2D graphics = thumbnail.createGraphics(); // 配置缩放参数,避免缩略图模糊 graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); graphics.drawImage(originalImg, 0, 0, 300, 300, null); graphics.dispose(); return thumbnail; } }
需要额外导入依赖:java.awt.image.BufferedImage、java.awt.Graphics2D、java.awt.RenderingHints、java.io.InputStream、java.io.IOException
3. 修正缩略图上传逻辑,不要存储在本地
替换moveFilesToNewContainer方法中图片处理块的逻辑,生成缩略图后直接上传回永久Blob容器,不要写入本地磁盘:
// 原if (CommonUtils.isImageType(extension)) 块内的逻辑全部替换为以下代码 if (CommonUtils.isImageType(extension)) { // 构造缩略图的Blob路径 String thumbBlobName = String.format("%s/%s_preview%s", savedOrder.getOrderNo(), CommonUtils.removeFileExtension(fileName), extension); // 获取原文件、缩略图对应的Blob客户端 BlobClient originalBlobClient = destContainer.getBlobClient( String.format("%s/%s", savedOrder.getOrderNo(), fileName) ); BlobClient thumbBlobClient = destContainer.getBlobClient(thumbBlobName); // 生成缩略图 BufferedImage thumbnail = generateImageThumbnail(originalBlobClient); // 缩略图转字节流直接上传到Blob容器 try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { ImageIO.write(thumbnail, extension.replace(".", ""), outputStream); byte[] thumbBytes = outputStream.toByteArray(); // 第三个参数为true表示覆盖已存在的同名文件 thumbBlobClient.upload(new ByteArrayInputStream(thumbBytes), thumbBytes.length, true); } }
需要额外导入依赖:java.io.ByteArrayInputStream、java.io.ByteArrayOutputStream、com.azure.storage.blob.BlobClient
注意事项
如果后续业务需要通过URL直接访问Blob文件,不要复用源文件的SAS,需要针对目标容器内的具体Blob单独生成对应权限、有效期的SAS,跨容器、跨文件的SAS完全不通用。
内容的提问来源于stack exchange,提问作者Ashar
相关产品推荐
相关产品推荐

