Azure Blob 存儲(chǔ)是 Microsoft 提供的適用于云的對(duì)象存儲(chǔ)解決方案。 Blob 存儲(chǔ)最適合存儲(chǔ)巨量的非結(jié)構(gòu)化數(shù)據(jù)
準(zhǔn)備
Azure 訂閱
點(diǎn)擊創(chuàng)建免費(fèi)帳戶,選擇免費(fèi)開始薯演,使用微軟賬戶注冊(cè)訂閱后即可試用12個(gè)月
Azure 存儲(chǔ)帳戶
點(diǎn)擊創(chuàng)建存儲(chǔ)帳戶毯侦,根據(jù)教程即可創(chuàng)建一個(gè)存儲(chǔ)賬戶,若沒有安裝azure cli砖茸,推薦直接參考【門戶網(wǎng)站】一欄
Azure門戶憑據(jù)
- 登錄到 Azure 門戶酵镜。
- 找到自己的存儲(chǔ)帳戶碉碉。
- 在存儲(chǔ)帳戶概述的“設(shè)置”部分,選擇“訪問密鑰”淮韭。 在這里垢粮,可以查看你的帳戶訪問密鑰以及每個(gè)密鑰的完整連接字符串。
- 找到“密鑰 1”下面的“連接字符串”值靠粪,選擇“復(fù)制”按鈕復(fù)制該連接字符串蜡吧。 下一步需將此連接字符串值添加到某個(gè)環(huán)境變量。
開發(fā)步驟
配置
- 引入依賴
<!--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儲(chǔ)存配置
default-endpoints-protocol: https
account-name: [account-name]
account-key: [account-key]
endpoint-suffix: [endpoint-suffix]
container-reference: [container-reference] # 容器名稱
generate-thumbnail: false # 生成縮略圖
代碼編寫
- 根據(jù)屬性編寫對(duì)應(yīng)參數(shù)類
@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;
}
- 編寫文件上傳的業(yè)務(wù)層接口
public interface IAzureStorageService {
/**
* 上傳文件(圖片)
* @param type 文件類型
* @param multipartFiles 文件
* @return
*/
BaseResult<Object> uploadFile(String type, MultipartFile[] multipartFiles);
}
- 實(shí)現(xiàn)類
@Service
public class AzureStorageServiceImpl implements IAzureStorageService {
// 設(shè)置縮略圖的寬高
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) {
// 校驗(yàn)圖片
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");
}
// 時(shí)間+隨機(jī)數(shù)+文件擴(kuò)展名
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());
// 關(guān)閉流
baos.close();
bais.close();
}
blobUploadEntities.add(blobUploadEntity);
} catch (Exception e) {
logger.error("上傳[{}]時(shí)出現(xiàn)異常:[{}]", tempMultipartFile.getOriginalFilename(), e.getMessage());
return BaseResult.error("上傳出現(xiàn)異常占键,請(qǐng)稍后再試");
}
}
return BaseResult.success(blobUploadEntities);
} catch (Exception e) {
logger.error("上傳文件出現(xiàn)異常: [{}]", e.getMessage());
}
return BaseResult.error("上傳出現(xiàn)異常昔善,請(qǐng)稍后再試");
}
/**
* 判斷批量文件中是否都為圖片
*/
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("上傳失敗,請(qǐng)選擇文件");
}
return azureStorageService.uploadFile("PICTURE", multipartFiles);
}
}
實(shí)列測(cè)試
使用postman測(cè)試
【請(qǐng)求體】-【form-data】-【key=file】畔乙,然后從本地選擇若干圖片
- 復(fù)制圖片url即可查看圖片
- 查看blob容器,即可以看到最新上傳的文件
使用表單提交測(cè)試
為方便測(cè)試君仆,直接使用thymeleaf模板進(jìn)行頁(yè)面上傳
- 引入依賴
<!-- thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
- 簡(jiǎn)易頁(yè)面
<!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>
- 控制器添加跳轉(zhuǎn)請(qǐng)求
@Controller
public class UploadController {
private static final Logger logger = LoggerFactory.getLogger(UploadController.class);
@Autowired
AzureStorageServiceImpl azureStorageService;
/**
* 文件上傳,跳轉(zhuǎn)使用
*/
@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("上傳失敗,請(qǐng)選擇文件");
}
return azureStorageService.uploadFile("PICTURE", multipartFiles);
}
}
- 瀏覽器訪問http://localhost:8082/upload牲距,選擇圖片上傳
- 上傳成功
詳細(xì)過程返咱,可參考源代碼:https://github.com/chetwhy/cloud-flow
參考文章: