近期在构建公司技术服务平台,在搭建完一些基础技术服务之后,考虑到项目过程中使用到很多文件存储功能,所以在平台上面提供统一的文件存储服务。
一开始计划直接使用阿里云OSS服务(想法简单了),后来与CTO讨论后,决定使用MinIO来完成该服务。然后开始学习MinIO相关知识,各种查看中英文版文档,现在最新的中文文档版本还是MinIO7的,所以查看了官网的英文版文档:
MinIO文档
梳理下过程,将基础功能整理如下:
1. MinIO安装(docker单机)
前提需要了解docker及docker-compose相关知识。
windows本地使用MinIO,安装完docker及docker-compose后,
打开PowerShell,执行命令:
docker run -p 9000:9000 --name minio1 -v D:\data:/data -e “MINIO_ROOT_USER=phli” -e “MINIO_ROOT_PASSWORD=123456” minio/minio server /data
docker相关指令自行学习,本篇不做展开,将容器端口与宿主机端口做映射,文件存储目录挂载到本地D:\data目录下(容器内在/data下),设置MinIO root的user及password,minio/minio镜像。
本地MinIO启动成功,端口为9000。
2. 国际惯例,添加依赖
io.minio
minio
8.0.3
3. application配置
endpoint为本地MinIO服务url,accessKey及secretKey为MinIO设置的类似于userName/password。
4. MinIO配置类
@Data
@Component
public class MinIoClientConfig {
@Value("${minio.endpoint}")
private String endpoint;
@Value("${minio.accessKey}")
private String accessKey;
@Value("${minio.secretKey}")
private String secretKey;
/**
* 注入minio 客户端
* @return
*/
@Bean
public MinioClient minioClient(){
return MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.build();
}
}
通过@Bean注入minIO客户端。
5. MinIOUtil类,完成与MinIO的操作
@Component
public class MinIoUtil {
@Autowired
private MinioClient minioClient;
/**
* 查看存储bucket是否存在
* @param bucketName 存储bucket
* @return boolean
*/
public Boolean bucketExists(String bucketName) {
Boolean found;
try {
found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
} catch (Exception e) {
e.printStackTrace();
return false;
}
return found;
}
/**
* 创建存储bucket
* @param bucketName 存储bucket名称
* @return Boolean
*/
public Boolean makeBucket(String bucketName) {
try {
minioClient.makeBucket(MakeBucketArgs.builder()
.bucket(bucketName)
.build());
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 删除存储bucket
* @param bucketName 存储bucket名称
* @return Boolean
*/
public Boolean removeBucket(String bucketName) {
try {
minioClient.removeBucket(RemoveBucketArgs.builder()
.bucket(bucketName)
.build());
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 文件上传
* @param file 文件
* @param bucketName 存储bucket
* @return Boolean
*/
public Boolean upload(MultipartFile file, String bucketName) {
try {
PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(bucketName).object(file.getOriginalFilename())
.stream(file.getInputStream(),file.getSize(),-1).contentType(file.getContentType()).build();
//文件名称相同会覆盖
minioClient.putObject(objectArgs);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 文件下载
* @param bucketName 存储bucket名称
* @param fileName 文件名称
* @param res response
* @return Boolean
*/
public void download(String bucketName, String fileName, HttpServletResponse res) {
GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(bucketName)
.object(fileName).build();
try (GetObjectResponse response = minioClient.getObject(objectArgs)){
byte[] buf = new byte[1024];
int len;
try (FastByteArrayOutputStream os = new FastByteArrayOutputStream()){
while ((len=response.read(buf))!=-1){
os.write(buf,0,len);
}
os.flush();
byte[] bytes = os.toByteArray();
res.setCharacterEncoding("utf-8");
//设置强制下载不打开
res.setContentType("application/force-download");
res.addHeader("Content-Disposition", "attachment;fileName=" + fileName);
try (ServletOutputStream stream = res.getOutputStream()){
stream.write(bytes);
stream.flush();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 查看文件对象
* @param bucketName 存储bucket名称
* @return 存储bucket内文件对象信息
*/
public List listObjects(String bucketName) {
Iterable> results = minioClient.listObjects(
ListObjectsArgs.builder().bucket(bucketName).build());
List objectItems = new ArrayList<>();
try {
for (Result- result : results) {
Item item = result.get();
ObjectItem objectItem = new ObjectItem();
objectItem.setObjectName(item.objectName());
objectItem.setSize(item.size());
objectItems.add(objectItem);
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return objectItems;
}
/**
* 批量删除文件对象
* @param bucketName 存储bucket名称
* @param objects 对象名称集合
*/
public Iterable
> removeObjects(String bucketName, List objects) {
List dos = objects.stream().map(e -> new DeleteObject(e)).collect(Collectors.toList());
Iterable> results = minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(dos).build());
return results;
}
}
MinIoUtil提供了创建存储bucket、删除存储bucket、判断存储bucket是否存在、文件的上传下载、查看bucket内文件对象、批量删除文件对象等功能(存储bucket及文件对象等的概念不做赘述,可查看minIO官方文档)。
6. Controller类
这儿不做赘述,自行编写即可。仅以文件上传为demo:
@ApiOperation(value = "文件上传")
@PostMapping("/upload")
public Result upload(MultipartFile file, @RequestParam(value = "bucketName") String bucketName) {
if (StringUtils.isBlank(bucketName)) {
log.error("存储bucket名称为空,无法上传");
return Result.fail("存储bucket名称为空,无法上传");
}
if (!minIoUtil.upload(file, bucketName)) {
log.error("文件上传异常");
return Result.fail("文件上传异常");
}
return Result.success("文件上传成功");
}
然后就可以调用接口完成文件上传了,可以发现在本地及容器内均有所上传的文件。
补充说明:
MinIO在正式环境往往是分布式,分布式Minio可以让你将多块硬盘(甚至在不同的机器上)组成一个对象存储服务。由于硬盘分布在不同的节点上,分布式Minio避免了单点故障。
Docker Compose允许定义和运行单主机,多容器Docker应用程序。使用Compose,您可以使用Compose文件来配置MinIO服务。 然后,使用单个命令,您可以通过你的配置创建并启动所有分布式MinIO实例。 分布式MinIO实例将部署在同一主机上的多个容器中。 这是建立基于分布式MinIO的开发,测试和分期环境的好方法。
Docker Compose定义运行单主机,复杂场景可以考虑Kubernetes或者Docker Swarm,具体配置可查看MinIO官网文档。