Azure Blob 存储是 Microsoft 提供的适用于云的对象存储解决方案。 Blob 存储最适合存储巨量的非结构化数据
点击 创建免费帐户 ,选择免费开始,使用微软账户注册订阅后即可试用12个月
点击 创建存储帐户 ,根据教程即可创建一个存储账户,若没有安装azure cli,推荐直接参考【门户网站】一栏
<!--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())); } } 复制代码
@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); } } 复制代码
【请求体】-【form-data】-【key=file】,然后从本地选择若干图片
为方便测试,直接使用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); } } 复制代码