Spring Boot版本:2.3.4.RELEASE
minio是一个高性能对象存储服务,我们可以用它来实现文件的上传下载。
minio的官方教程绝对会更加详细,本文只是作为个人笔记备忘。
自从学习了docker,我几乎所有的环境都是容器,minio当然也不例外。
minio容器安装
docker run --name myminio -p 8789:9000 -itd --restart=always -v /etc/localtime:/etc/localtime -v /home/mycontainers/myminio/data:/data -v /home/mycontainers/myminio/config:/root/.minio --net mynetwork -e "MINIO_ACCESS_KEY=minio" -e "MINIO_SECRET_KEY=minio123" minio/minio:RELEASE.2021-06-17T00-10-46Z server /data
指令说明:
- --restart=always,容器随着docker的启动而启动
- -p,端口映射,8789是我宿主机的限制端口,这个随意,9000是minio的默认端口
- -v 路径A:路径B,映射宿主机的路径A到容器的路径B下,在上面这条指令中映射了3个路径,按顺序分别是1(保证宿主机的时间和容器内的时间一致);2(从宿主机可以快速访问到容器的文件存储路径,以及数据持久化);3(minio的配置持久化)
- --net,网段
- -e,minio的账密
- minio/minio:RELEASE.2021-06-17T00-10-46Z,镜像版本
- server,以server服务启动
- /data,工作路径
docker的介绍不是本文重点,网上资料众多,相信有很多优秀的文章可以学习。
启动成功后,访问localhost:9000/minio可以看到可视化界面,操作易上手,可以多试试。
Maven依赖
<!--minio-->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>7.1.0</version>
</dependency>
配置文件
server:
port: 8888
# minio配置
minio:
endpoint: http://myserverhost:8789 # minio链接
accessKey: minio # minio的用户名
secretKey: minio123 # minio的密码
bucket: default # 默认的文件存储桶名
配置类
MinioConfig:
package com.cc.config;
import io.minio.MinioClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
/**
* minio配置类
* @author cc
* @date 2021-07-09 14:37
*/
@Configuration
public class MinioConfig {
@Bean
public MinioClient minioClient(MinioProperties properties){
try {
MinioClient.Builder builder = MinioClient.builder();
builder.endpoint(properties.getEndpoint());
if (StringUtils.hasLength(properties.getAccessKey()) && StringUtils.hasLength(properties.getSecretKey())) {
builder.credentials(properties.getAccessKey(),properties.getSecretKey());
}
return builder.build();
} catch (Exception e) {
return null;
}
}
}
MinioProperties:
package com.cc.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* minio属性类,从配置文件中获取默认配置
* @author cc
* @date 2021-07-09 14:37
*/
@Component
@ConfigurationProperties(prefix = "minio")
public class MinioProperties {
/**
* 对象存储服务的URL
*/
private String endpoint;
/**
* Access key就像用户ID,可以唯一标识你的账户
*/
private String accessKey;
/**
* Secret key是你账户的密码
*/
private String secretKey;
/**
* 默认文件桶
*/
private String bucket;
...
}
Service
编写MinioService:
package com.cc.service;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* minio的api调用
* @author cc
* @date 2021-07-09 10:53
*/
public interface MinioService {
/**
* 上传,返回文件名,然后通过文件名下载
* @param file: 文件
* @param bucket: 文件桶名
* @param filename: 文件名
* @return java.lang.String
* @author cc
* @date 2021-07-09 14:16
*/
String upload(MultipartFile file, String bucket, String filename);
/**
* 下载
* @param response:
* @param bucket: 文件桶名
* @param filename: 文件名
* @return void
* @author cc
* @date 2021-07-09 14:16
*/
void download(HttpServletResponse response, String bucket, String filename);
/**
* 删除
* @param bucket: 文件桶名
* @param filename: 文件名
* @return java.lang.String
* @author cc
* @date 2021-07-09 14:16
*/
String remove(String bucket, String filename);
/**
* 查询文件是否存在
* @param bucket: 文件桶名
* @param filename: 文件名
* @return boolean
* @author cc
* @date 2021-07-09 14:16
*/
boolean doesObjectExist(String bucket, String filename);
}
MinioServiceImpl:
package com.cc.service;
import com.cc.config.MinioProperties;
import io.minio.*;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
/**
* minio api官网链接:https://docs.min.io/docs/java-client-api-reference.html
* @author cc
* @date 2021-07-09 10:47
*/
@Service
public class MinioServiceImpl implements MinioService {
private final MinioClient minioClient;
private final MinioProperties properties;
public MinioServiceImpl(MinioClient minioClient, MinioProperties properties) {
this.minioClient = minioClient;
this.properties = properties;
}
@Override
public String upload(MultipartFile file, String bucket, String filename) {
InputStream inputStream;
if (StringUtils.isEmpty(bucket)) {
bucket = properties.getBucket();
}
if (StringUtils.isEmpty(filename)) {
filename = UUID.randomUUID().toString().replace("-", "") + "-" + file.getOriginalFilename();
}
try {
// 是否有bucket,没有则创建
boolean bucketExist = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucket).build());
if (!bucketExist) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucket).build());
}
inputStream = file.getInputStream();
minioClient.putObject(PutObjectArgs.builder()
.bucket(bucket)
.object(filename).contentType(file.getContentType())
.stream(inputStream, inputStream.available(), -1)
.build());
} catch (Exception e) {
throw new RuntimeException("上传文件失败:" + e.getMessage());
}
try {
inputStream.close();
} catch (IOException e) {
throw new RuntimeException("上传文件-inputStream关闭失败:" + e.getMessage());
}
return filename;
}
@Override
public void download(HttpServletResponse response, String bucket, String filename) {
InputStream inputStream;
if (StringUtils.isEmpty(bucket)) {
bucket = properties.getBucket();
}
try {
inputStream = minioClient.getObject(GetObjectArgs.builder()
.bucket(bucket)
.object(filename)
.build());
IOUtils.copy(inputStream, response.getOutputStream());
} catch (Exception e) {
throw new RuntimeException("下载文件失败:" + e.getMessage());
}
try {
inputStream.close();
} catch (IOException e) {
throw new RuntimeException("下载文件-inputStream关闭失败:" + e.getMessage());
}
}
@Override
public String remove(String bucket, String filename) {
if (bucket == null || bucket.length() == 0) {
bucket = properties.getBucket();
}
if (!doesObjectExist(bucket, filename)) {
throw new RuntimeException("文件不存在,无法删除,请检查文件名和存储桶");
}
try {
minioClient.removeObject(
RemoveObjectArgs.builder().bucket(properties.getBucket()).object(filename).build());
} catch (Exception e) {
throw new RuntimeException("删除文件失败:" + e.getMessage());
}
return String.format("删除文件[%s]成功", filename);
}
@Override
public boolean doesObjectExist(String bucket, String filename) {
if (bucket == null || bucket.length() == 0) {
bucket = properties.getBucket();
}
try {
minioClient.statObject(StatObjectArgs.builder()
.bucket(bucket)
.object(filename).build());
} catch (Exception e) {
return false;
}
return true;
}
}
编写接口Controller来测试
MinioController:
package com.cc.controller;
import com.cc.service.MinioServiceImpl;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
@RestController
public class MinioController {
private final MinioServiceImpl minioService;
public MinioController(MinioServiceImpl minioService) {
this.minioService = minioService;
}
// 文件上传
@PostMapping("/upload")
public void upload(@RequestParam("file") MultipartFile file) {
minioService.upload(file, null, null);
}
// 文件下载
@GetMapping("/download")
public void download(HttpServletResponse response, @RequestParam("bucket") String bucket, @RequestParam("filename") String filename) {
minioService.download(response, bucket, filename);
}
// 文件删除
@GetMapping("/remove")
public void remove(@RequestParam("bucket") String bucket, @RequestParam("filename") String filename) {
minioService.remove(bucket, filename);
}
// 查询文件是否存在
@GetMapping("/find")
public Boolean find(@RequestParam("bucket") String bucket, @RequestParam("filename") String filename) {
return minioService.doesObjectExist(bucket, filename);
}
}
代码编写完成,启动程序即可进行测试。