是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等,相对于FastDFS部署起来要轻量很多。
中文文档: http://docs.minio.org.cn/docs/master/distributed-minio-quickstart-guide
# 下载 minio
wget https://dl.min.io/server/minio/release/linux-amd64/minio
# 添加可执行权限
chmod +x minio
# 设置登录minio的 access key
export MINIO_ACCESS_KEY=minioadmin
# 设置登录minio的 secret key
export MINIO_SECRET_KEY=minioadmin
# 启动 minio 并设置文件存储的路径为/data目录,如果需要退出命令行程序还在需要在最前面加上 nohup 命令
./minio server /data
参考:http://docs.minio.org.cn/docs/master/distributed-minio-quickstart-guide
创建minio文件存储路径
mkdir -p /data/minio
注意事项
注意:需要将新建的目录挂在到对应的磁盘下,磁盘不挂载好,集群启动会报错 Waiting for a minimum of 2 disks to come online :找不到磁盘:
mount /dev/sda1 /data
查看是否挂载成功
lsblk
vi minio-run.sh
#!/bin/bash
export MINIO_ROOT_USER=minioadmin
export MINIO_ROOT_PASSWORD=minioadmin
/usr/local/minio/minio server \
http://192.168.94.128/data/minio \
http://192.168.94.129/data/minio \
http://192.168.94.131/data/minio \
http://192.168.94.132/data/minio
./minio-run.sh
访问任意一个节点的控制台可以查看到集群信息,表示集群环境搭建成功了。
创建挂载目录
mkdir -p /docker/minio/data /docker/minio/config
配置ip4转发
vim /etc/sysctl.conf
#配置转发
net.ipv4.ip_forward=1
#重启服务,让配置生效
systemctl restart network
#查看是否成功,如果返回为“net.ipv4.ip_forward = 1”则表示成功
sysctl net.ipv4.ip_forward
构建并运行容器
docker run -p 9000:9000 -p 9001:9001 --name minio \
-it -d --restart=always \
-e "MINIO_ACCESS_KEY=minioadmin" \
-e "MINIO_SECRET_KEY=minioadmin" \
-v /docker/minio/data:/data \
-v /docker/minio/config:/root/.minio \
minio/minio server /data \
--console-address ":9001" --address ":9090"
org.springframework.boot
spring-boot-starter-web
org.projectlombok
lombok
io.minio
minio
8.3.4
com.squareup.okhttp3
okhttp
com.squareup.okhttp3
okhttp
4.9.0
com.squareup.okio
okio
2.8.0
server:
port: 8025
minio:
#minio部署的机器ip地址
endpoint: 192.168.94.128
#minio使用的端口
port: 9000
#唯一标识的账户
accessKey: minioadmin
#账户的密码
secretKey: minioadmin
#是否使用https
secure: false
#测试使用的桶名称
defaultBucketName: test
@Configuration
@ConfigurationProperties(prefix = "minio")
@Data
public class MinioConfig {
/**
* minio部署的机器ip地址
*/
private String endpoint;
/**
* minio使用的端口
*/
private Integer port;
/**
*唯一标识的账户
*/
private String accessKey;
/**
* 账户的密码
*/
private String secretKey;
/**
* 如果是true,则用的是https而不是http,默认值是true
*/
private Boolean secure;
/**
* 默认使用的桶名称
*/
private String defaultBucketName;
/**
* 对象交给spring管理
*/
@Bean
public MinioClient getMinioClient() {
MinioClient minioClient = MinioClient.builder()
.endpoint(endpoint , port , secure)
.credentials(accessKey, secretKey)
.build();
return minioClient;
}
}
@Component
@Slf4j
public class MinioUtil {
@Autowired
private MinioClient minioClient;
/**
* 获取当前日期字符串格式
* @return 2021/12/5
*/
public String getDatePath() {
LocalDateTime now = LocalDateTime.now();
return String.format("/%s/%s/%s/", now.getYear(), now.getMonthValue(), now.getDayOfMonth());
}
/**
* 判断桶是否存
* @param bucketName 桶名称
* @return
*/
public boolean bucketExists(String bucketName) throws Exception {
boolean flag = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
if (flag) {
return true;
}
return false;
}
/**
* 创建桶
*/
public boolean createBucket(String bucketName) {
try {
//判断文件存储的桶对象是否存在
boolean isExist = bucketExists(bucketName);
if (isExist) {
log.info("Bucket asiatrip already exists.");
return false;
} else {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
return true;
}
} catch (Exception e) {
log.error("errorMsg={}",e);
return false;
}
}
/**
* 列出桶里的所有对象
* @param bucketName 桶名称
*/
public Iterable<Result<Item>> listObjects(String bucketName) {
return minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).build());
}
/**
* 删除桶
* @param bucketName 桶名称
* @return 是否删除成功
*/
public boolean removeBucket(String bucketName) {
try {
boolean flag = bucketExists(bucketName);
if (flag) {
Iterable<Result<Item>> myObjects = listObjects(bucketName);
for (Result<Item> result : myObjects) {
Item item = result.get();
// 有对象文件,则删除失败
if (item.size() > 0) {
return false;
}
}
// 删除存储桶,注意,只有存储桶为空时才能删除成功。
minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
flag = bucketExists(bucketName);
if (!flag) {
return true;
}
}
} catch (Exception e) {
log.error("errorMsg={}",e);
return false;
}
return false;
}
/**
* 获取所有桶信息
*/
public List<Bucket> getAllBucket() {
try {
// 获取minio中所以的bucket
List<Bucket> buckets = minioClient.listBuckets();
for (Bucket bucket : buckets) {
log.info("bucket 名称: {} bucket 创建时间: {}", bucket.name(), bucket.creationDate());
}
return buckets;
} catch (Exception e) {
log.error("errorMsg={}",e);
return Collections.emptyList();
}
}
/**
* 上传本地文件到指定桶下
* @param bucketName 桶名称
* @param objectName 对象名称
* @param localFileName 要上传的文件路径
* @return
*/
public boolean upload(String bucketName, String objectName, String localFileName) {
try {
File file = new File(localFileName);
FileInputStream fileInputStream = new FileInputStream(file);
minioClient.putObject(PutObjectArgs.builder()
.stream(fileInputStream, file.length(), PutObjectArgs.MIN_MULTIPART_SIZE)
.bucket(bucketName)
.object(objectName)
.build());
return true;
} catch (Exception e) {
log.error("errorMsg={}",e);
return false;
}
}
/**
* 上传MultipartFile到指定桶下
* @param bucketName 桶名称
* @param objectName 对象名称
* @param file 文件流
*/
public boolean upload(String bucketName, String objectName, MultipartFile file) {
try {
minioClient.putObject(PutObjectArgs.builder()
.bucket(bucketName)
.stream(file.getInputStream(), file.getSize(), PutObjectArgs.MIN_MULTIPART_SIZE)
.object(objectName)
.build());
return true;
} catch (Exception e) {
log.error("errorMsg={}",e);
return false;
}
}
/**
* 下载文件到本地
* @param bucketName 桶名称
* @param objectName 对象名称
* @param localFileName 本地文件存储路径
*/
public boolean downLocal(String bucketName, String objectName, String localFileName) {
try {
minioClient.downloadObject(DownloadObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.filename(localFileName)
.build());
return true;
} catch (Exception e) {
log.error("errorMsg={}",e);
return false;
}
}
/**
* 下载文件写入到HttpServletResponse
* @param bucketName 桶名称
* @param objectName 对象名称
* @param response HttpServletResponse对象
*/
@SneakyThrows
public void downResponse(String bucketName, String objectName, HttpServletResponse response) {
GetObjectResponse object = minioClient.getObject(GetObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build());
response.setHeader("Content-Disposition", "attachment;filename=" + objectName.substring(objectName.lastIndexOf("/") + 1));
response.setContentType("application/force-download");
response.setCharacterEncoding("UTF-8");
IOUtils.copy(object, response.getOutputStream());
}
/**
* 删除指定桶的指定文件对象
* @param bucketName 桶名称
* @param objectName 对象名称
*/
public boolean delete(String bucketName, String objectName) {
try {
minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
return true;
}catch (Exception e){
log.error("errorMsg={}",e);
return false;
}
}
/**
* 删除指定桶的多个文件对象,返回删除错误的对象列表,全部删除成功,返回空列表
* @param bucketName 存储桶名称
* @param objectNames 含有要删除的多个object名称的迭代器对象
* @return
*/
public boolean deletes(String bucketName, List<String> objectNames) {
try {
List<String> deleteErrorNames = new ArrayList<>();
List<DeleteObject> list = new LinkedList<>();
objectNames.forEach(item -> list.add(new DeleteObject(item)));
Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(list).build());
for (Result<DeleteError> result : results) {
DeleteError error = result.get();
deleteErrorNames.add(error.objectName());
}
return deleteErrorNames.size() == 0 ? true : false;
} catch (Exception e) {
log.error("errorMsg={}",e);
return false;
}
}
/**
* 获取文件带时效的访问链接 失效时间(以秒为单位),默认是7天不得大于七天
* @param bucketName 桶名称
* @param remoteFileName 对象名称
* @param timeout 时间
* @param unit 单位
* @return 文件访问链接
*/
public String getPresignedObjectUrl(String bucketName, String remoteFileName, long timeout, TimeUnit unit) {
try {
return minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(bucketName)
.object(remoteFileName)
.expiry((int) unit.toSeconds(timeout))
.build());
} catch (Exception e) {
log.error("errorMsg={}",e);
return null;
}
}
}
重源码中能看到getPresignedObjectUrl函数可以设置的外链的时效范围为 7天>= expiry >=1,默认值为7天
@RestController
@RequestMapping("/file")
public class FileController {
@Resource
private MinioConfig minioConfig;
@Autowired
private MinioUtil minioUtil;
/**
* 上传文件
* @param file
* @param bucketName 桶名称
* @return 返回对象名称和外链地址
*/
@PostMapping(value = "/")
public ResponseEntity<HashMap<String, String>> uploadFile(MultipartFile file, @RequestParam(required = false) String bucketName) {
bucketName = StringUtils.hasLength(bucketName) ? bucketName : minioConfig.getDefaultBucketName();
String objectName = minioUtil.getDatePath() + file.getOriginalFilename();
minioUtil.upload(bucketName, objectName, file);
String viewPath = minioUtil.getPresignedObjectUrl(bucketName, objectName, 60, TimeUnit.SECONDS);
HashMap<String, String> objectInfo = new HashMap<>();
objectInfo.put("objectName", objectName);
//只能预览图片、txt等部分文件
objectInfo.put("viewPath", viewPath);
return ResponseEntity.ok(objectInfo);
}
/**
* 删除指定桶里的某个对象
* @param bucketName 桶名称
* @param objectName 对象名称
* @return
*/
@DeleteMapping(value = "/")
public ResponseEntity<String> deleteByPath(@RequestParam(required = false) String bucketName, String objectName) {
bucketName = StringUtils.hasLength(bucketName) ? bucketName : minioConfig.getDefaultBucketName();
minioUtil.delete(bucketName, objectName);
return ResponseEntity.ok("删除成功");
}
/**
* 下载文件
* @param bucketName 桶名称
* @param objectName 对象名称
* @param response 相应结果
*/
@GetMapping("/")
public void downLoad(@RequestParam(required = false) String bucketName, String objectName,HttpServletResponse response) {
// 获取文件
minioUtil.downResponse(bucketName,objectName,response);
}
}
通过postman上传文件到minio上面,会返回一个对象名称和一个带时效的外链名称
浏览器访问接口参数为对象的名称可以下载对应的文件
http://localhost:8025/file/?objectName=/test/2021/12/5/booklist.txt
通过postman设置DELETE请求类型调用删除文件接口
localhost:8025/file/?objectName=2021/12/5/booklist.txt
设置完就可以通过浏览器直接访问文件了。
访问方式为http://192.168.94.128:API端口/桶名称/对象名称
例如: http://192.168.94.128:9000/test/2021/12/5/booklist.txt
gitee代码地址
创作不易,要是觉得我写的对你有点帮助的话,麻烦在gitee上帮我点下 Star
【SpringBoot框架篇】其它文章如下,后续会继续更新。