Azure Blob 存储是 Microsoft 提供的适用于云的对象存储解决方案。 Blob 存储最适合存储巨量的非结构化数据
准备
Azure 订阅
点击创建免费帐户,选择免费开始,使用微软账户注册订阅后即可试用12个月
Azure 存储帐户
点击创建存储帐户,根据教程即可创建一个存储账户,若没有安装azure cli,推荐直接参考【门户网站】一栏
Azure门户凭据
- 登录到 Azure 门户。
- 找到自己的存储帐户。
- 在存储帐户概述的“设置”部分,选择“访问密钥”。 在这里,可以查看你的帐户访问密钥以及每个密钥的完整连接字符串。
- 找到“密钥 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】,然后从本地选择若干图片
image- 复制图片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,选择图片上传
- 上传成功
详细过程,可参考源代码:https://github.com/chetwhy/cloud-flow
参考文章:
网友评论