SpringBoot实现azure blob的文件上传

2,547 阅读4分钟

Azure Blob 存储是 Microsoft 提供的适用于云的对象存储解决方案。 Blob 存储最适合存储巨量的非结构化数据

准备

Azure 订阅

点击创建免费帐户,选择免费开始,使用微软账户注册订阅后即可试用12个月

Azure 存储帐户

点击创建存储帐户,根据教程即可创建一个存储账户,若没有安装azure cli,推荐直接参考【门户网站】一栏

Azure门户凭据

  1. 登录到 Azure 门户
  2. 找到自己的存储帐户。
  3. 在存储帐户概述的“设置”部分,选择“访问密钥”。 在这里,可以查看你的帐户访问密钥以及每个密钥的完整连接字符串。
  4. 找到“密钥 1”下面的“连接字符串”值,选择“复制”按钮复制该连接字符串。 下一步需将此连接字符串值添加到某个环境变量。

开发步骤

配置

  • 引入依赖
<!--lombok -->
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <optional>true</optional>
</dependency>

<!--azure storage -->
<dependency>
    <groupId>com.microsoft.azure</groupId>
    <artifactId>azure-storage-spring-boot-starter</artifactId>
    <version>0.2.0</version>
</dependency>
  • 属性配置
spring:
  servlet:  	
    multipart:	# spring mvc文件上传
      max-request-size: 10MB
      max-file-size: 1MB

azure:
  storage: # azure储存配置
    default-endpoints-protocol: https
    account-name: [account-name]
    account-key: [account-key]
    endpoint-suffix: [endpoint-suffix]
    container-reference: [container-reference]	# 容器名称
    generate-thumbnail: false  # 生成缩略图

代码编写

  • 根据属性编写对应参数类
@Data
@Component
public class AzureStorageParam {

    @Value("${azure.storage.default-endpoints-protocol}")
    private String defaultEndpointsProtocol;

    @Value("${azure.storage.account-name}")
    private String accountName;

    @Value("${azure.storage.account-key}")
    private String accountKey;

    @Value("${azure.storage.endpoint-suffix}")
    private String endpointSuffix;

    @Value("${azure.storage.container-reference}")
    private String containerReference;

    /**
     * 拼接连接字符串
     */
    public String getStorageConnectionString() {
        String storageConnectionString =
            String.format("DefaultEndpointsProtocol=%s;AccountName=%s;AccountKey=%s;EndpointSuffix=%s",
                defaultEndpointsProtocol, accountName, accountKey, endpointSuffix);
        return storageConnectionString;
    }
}
  • 编写文件上传的返回模型
@Data
@Accessors(chain = true)
public class BlobUpload {
    // 文件名
    private String fileName;

    // 原文件
    private String fileUrl;

    // 缩略图
    private String thumbnailUrl;
}
  • 工具类
/**
 * 获取blob container
 * 
 * @param storageConnectionString
 * @param containerReference
 * @return
 */
public static CloudBlobContainer getAzureContainer(String storageConnectionString, String containerReference) {
    CloudStorageAccount storageAccount;
    CloudBlobClient blobClient = null;
    CloudBlobContainer container = null;
    try {
        storageAccount = CloudStorageAccount.parse(storageConnectionString);
        blobClient = storageAccount.createCloudBlobClient();
        container = blobClient.getContainerReference(containerReference);

        container.createIfNotExists(BlobContainerPublicAccessType.CONTAINER, new BlobRequestOptions(),
            new OperationContext());
        return container;
    } catch (Exception e) {
        logger.error("获取azure container异常: [{}]" , e.getMessage());
    }
    return null;
}
  • 编写文件上传的业务层接口
public interface IAzureStorageService {

    /**
     * 上传文件(图片)
     * @param type 文件类型
     * @param multipartFiles 文件
     * @return
     */
    BaseResult<Object> uploadFile(String type, MultipartFile[] multipartFiles);
}
  • 实现类
@Service
public class AzureStorageServiceImpl implements IAzureStorageService {
    // 设置缩略图的宽高
    private static int thumbnailWidth = 150;
    private static int thumbnailHeight = 100;
    private static String thumbnailPrefix = "mini_";
    private static String originPrefix = "FAQ_";
    private final Logger logger = LoggerFactory.getLogger(AzureStorageServiceImpl.class);

    @Value("{azure.storage.generate-thumbnail}")
    private String generateThumbnail;

    @Autowired
    private AzureStorageParam azureStorageParam;

    @Override
    public BaseResult<Object> uploadFile(String type, MultipartFile[] multipartFiles) {
        // 校验图片
        if (hasInvalidPic(multipartFiles)) {
            return BaseResult.error("包含非法图片格式");
        }

        List<BlobUpload> blobUploadEntities = new ArrayList<>();

        // 获取blob容器
        CloudBlobContainer container = AzureStorageUtil.getAzureContainer(
            azureStorageParam.getStorageConnectionString(), azureStorageParam.getContainerReference());
        if (container == null) {
            logger.error("获取azure container异常");
            return BaseResult.error("获取容器失败");
        }
        try {
            for (MultipartFile tempMultipartFile : multipartFiles) {
                try {
                    // 将 blob 上传到容器
                    String contentType = tempMultipartFile.getContentType().toLowerCase();
                    if (!contentType.equals("image/jpg") && !contentType.equals("image/jpeg")
                        && !contentType.equals("image/png")) {
                        return BaseResult.error("not pic");
                    }
                    // 时间+随机数+文件扩展名
                    String picType = contentType.split("/")[1];
                    String timeStamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
                    int number = (int)((Math.random() * 9) * 1000);
                    String referenceName = originPrefix + timeStamp + number + "." + picType;
                    CloudBlockBlob blob = container.getBlockBlobReference(referenceName);
                    blob.getProperties().setContentType(tempMultipartFile.getContentType());
                    blob.upload(tempMultipartFile.getInputStream(), tempMultipartFile.getSize());

                    // 返回图片URL
                    BlobUpload blobUploadEntity = new BlobUpload();
                    blobUploadEntity.setFileName(tempMultipartFile.getOriginalFilename())
                        .setFileUrl(blob.getUri().toString());

                    // 生成缩略图
                    if ("true".equalsIgnoreCase(generateThumbnail)) {
                        BufferedImage img =
                            new BufferedImage(thumbnailWidth, thumbnailHeight, BufferedImage.TYPE_INT_RGB);
                        BufferedImage read = ImageIO.read(tempMultipartFile.getInputStream());
                        img.createGraphics().drawImage(
                            read.getScaledInstance(thumbnailWidth, thumbnailHeight, Image.SCALE_SMOOTH), 0, 0, null);
                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
                        ImageIO.write(img, "jpg", baos);
                        InputStream bais = new ByteArrayInputStream(baos.toByteArray());

                        String blobThumbnail = originPrefix + thumbnailPrefix + timeStamp + number + ".jpg";
                        CloudBlockBlob thumbnailBlob = container.getBlockBlobReference(blobThumbnail);
                        thumbnailBlob.getProperties().setContentType("image/jpeg");
                        thumbnailBlob.upload(bais, baos.toByteArray().length);

                        blobUploadEntity.setFileUrl(blob.getUri().toString())
                            .setThumbnailUrl(thumbnailBlob.getUri().toString());

                        // 关闭流
                        baos.close();
                        bais.close();
                    }
                    blobUploadEntities.add(blobUploadEntity);
                } catch (Exception e) {
                    logger.error("上传[{}]时出现异常:[{}]", tempMultipartFile.getOriginalFilename(), e.getMessage());
                    return BaseResult.error("上传出现异常,请稍后再试");
                }
            }
            return BaseResult.success(blobUploadEntities);
        } catch (Exception e) {
            logger.error("上传文件出现异常: [{}]", e.getMessage());
        }
        return BaseResult.error("上传出现异常,请稍后再试");
    }

    /**
     * 判断批量文件中是否都为图片
     */
    private boolean hasInvalidPic(MultipartFile[] multipartFiles) {
        List<String> picTypeList = Arrays.asList("image/jpg", "image/jpeg", "image/png");
        return Arrays.stream(multipartFiles).anyMatch(i -> !picTypeList.contains(i.getContentType().toLowerCase()));
    }
}
  • mvc控制器
@RestController
public class UploadController {
    private static final Logger logger = LoggerFactory.getLogger(UploadController.class);

    @Autowired
    AzureStorageServiceImpl azureStorageService;

    /**
     * 文件上传(图片)
     * 
     * @param multipartFiles
     * @return
     */
    @PostMapping("/upload")
    public BaseResult<Object> upload(@RequestPart("file") MultipartFile[] multipartFiles) {
        logger.info("开始文件上传...");
        if (multipartFiles == null || multipartFiles.length == 0) {
            return BaseResult.error("上传失败,请选择文件");
        }

        return azureStorageService.uploadFile("PICTURE", multipartFiles);
    }
}

实列测试

使用postman测试

【请求体】-【form-data】-【key=file】,然后从本地选择若干图片

  • 复制图片url即可查看图片

  • 查看blob容器,即可以看到最新上传的文件

使用表单提交测试

为方便测试,直接使用thymeleaf模板进行页面上传

  • 引入依赖
<!-- thymeleaf -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
  • 简易页面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>多文件上传</title>
</head>
<body>
<form method="post" action="/upload" enctype="multipart/form-data">
    <input type="file" name="file"><br>
    <input type="file" name="file"><br>
    <input type="file" name="file"><br>
    <input type="submit" value="提交">
</form>
</body>
</html>
  • 控制器添加跳转请求
@Controller
public class UploadController {
    private static final Logger logger = LoggerFactory.getLogger(UploadController.class);

    @Autowired
    AzureStorageServiceImpl azureStorageService;

    /**
     * 文件上传,跳转使用
     */
    @GetMapping("/upload")
    public String upload() {
        return "upload";
    }

    /**
     * 文件上传(图片)
     */
    @PostMapping("/upload")
    @ResponseBody
    public BaseResult<Object> upload(@RequestPart("file") MultipartFile[] multipartFiles) {
        logger.info("开始文件上传...");
        if (multipartFiles == null || multipartFiles.length == 0) {
            return BaseResult.error("上传失败,请选择文件");
        }

        return azureStorageService.uploadFile("PICTURE", multipartFiles);
    }
}
  • 浏览器访问http://localhost:8082/upload,选择图片上传

  • 上传成功


详细过程,可参考源代码:github.com/chetwhy/clo…


参考文章:

Azure Blob 存储文档

Spring Boot实战之文件上传存入Azure Storage