如果您看着CSDN格式觉得不舒服,可以移步我的语雀该篇文章:
目录
1、安装
- MinIO英文官网地址:MinIO High Performance Object Storage — MinIO Object Storage for Kubernetes
- MinIO中文官网地址:MinIO Quickstart Guide| Minio中文文档
安装请按照英文官网,中文官网更新不及时。或者看我也可以!
1.1、Linux安装
1、先新建个你喜欢的目录,例如:mkdir -p /usr/local/minio
2、执行:wget https://dl.min.io/server/minio/release/darwin-amd64/minio
3、给文件夹赋权:chmod +x minio
PS:正常的目录应该是白色,赋权后应该是绿色,反正不是白色就对了
4、可以运行了:./minio /xxxxxxxxx/data
PS:启动minio server服务,指定数据存储目录/xxxxxxxxx/data
5、启动成功后,会出现红色Warning,不要慌
它的大概意思是告诉你:
一、这种方式启动,端口是随机的,下次再启动minIO端口就不一定是啥了
二、建议你用别的用户名,密码
解决:
一、添加用户名密码到环境变量:
export MINIO_ROOT_USER=admin
export MINIO_ROOT_PASSWORD=12345678
PS:长度要在8位以上,至于为啥,往后看
二、指定端口号启动:
./minio server --console-address ":50000" /xxxxxxxxx/data
PS:相较于第四步多了--console-address
指定端口号启动
6、访问:http://xxxxx:50000/dashboard
1.2、Docker安装
1、新建挂载数据卷目录,例如:/mydata/minio/data
和/mydata/minio/config
2、拉取镜像:docker pull minio/minio
3、运行镜像
docker run -d -p 9000:9000 -p 50000:50000 --name minio \
-v /mydata/minio/data:/data \
-v /mydata/minio/config:/root/.minio \
-e "MINIO_ROOT_USER=admin" \
-e "MINIO_ROOT_PASSWORD=123456789" \
minio/minio server --console-address ":50000" /data
解释为什么密码长度要最短8位,这里分享下我遇见的故障:
第一次运行的时候,也是使用-d
后台运行,镜像启动之后,执行docker ps -a
命令,看不到minio的运行端口号,执行docker ps
命令查看镜像启动失败。
所以我使用控制台输出信息方式运行镜像(就是去掉-d
命令)
它是告诉我,密码最少是8位。。。。
总结!!!以小窥大,告诉我们要养成看日志的习惯
4、访问:IP+端口号
1.3、Docker纠删码模式安装
docker run -d -p 9000:9000 -p 50000:50000 --name minio \
-v /mydata/minio/data1:/data1 \
-v /mydata/minio/data2:/data2 \
-v /mydata/minio/data3:/data3 \
-v /mydata/minio/data:/data4 \
-v /mydata/minio/data5:/data5 \
-v /mydata/minio/data:/data6 \
-v /mydata/minio/data7:/data7 \
-v /mydata/minio/data8:/data8 \
minio/minio server /data{1...8} --console-address ":50000"
1.4、分布式集群安装
自行百度吧,我买的腾讯云现成的。。。
2、代码实现
建议您呢,新建一个Spring Boot工程,复制下面我的代码,代码很简单,吸收了之后,再运用到你的项目
config:
package com.greedy.minio.config;
import io.minio.MinioClient;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Author: Greedy
* @Date: Created in:2022-09-15
* Description:
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinioConfig {
/**
* 对象存储服务的 URL
*/
private String endpoint;
/**
* 用户名
*/
private String accessKey;
/**
* 密码
*/
private String secretKey;
/**
* 存储桶名称
*/
private String bucketName;
/**
* 预览到期时间(小时)
*/
private Integer previewExpiry;
@Bean
public MinioClient minioClient() {
return MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey).build();
}
}
controller:
package com.greedy.minio.controller;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.greedy.minio.pojo.FileUploadBody;
import com.greedy.minio.service.MinioService;
import com.greedy.minio.utils.Msg;
import com.greedy.minio.vo.FileVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* @Author: Greedy
* @Date: Created in:2022-09-15
* Description:
*/
@RestController
@Slf4j
public class MinioController {
private final MinioService minioService;
public MinioController(MinioService minioService) {
this.minioService = minioService;
}
/**
* 上传文件
*
* @param body
* @return
*/
@PostMapping("/upload")
public Msg upload(FileUploadBody body) {
List<FileVo> list = null;
if (ObjUtil.isNull(body.getFiles())) {
return Msg.ERROR.setNewMsg("上传文件不能为空");
}
try {
list = minioService.uploadFile(body);
log.info("文件上传成功,文件名称:{},bucket名称:{}", body.getFileName(), body.getBucket());
} catch (Exception e) {
log.error("文件上传失败,文件名称:{},bucket名称:{},错误信息:", body.getFileName(), body.getBucket(), e);
return Msg.UPLOAD_ERROR;
}
return Msg.UPLOAD_SUCCESS.setNewData(list);
}
/**
* 获取预览地址
*
* @param fileName 文件名
* @param bucketName 桶名
* @return
*/
@GetMapping("/getPreviewUrl")
public Msg getPreviewUrl(@RequestParam("fileName") String fileName, @RequestParam("bucketName") String bucketName) {
String url = minioService.getPreviewUrl(fileName, bucketName);
String previewUrl = null;
try {
if (url == null || StrUtil.isBlank(url)) {
return Msg.SUCCESS.setNewMsg("不好意思,没查着该文件");
}
// 前端返回格式:ip:端口/桶名/文件名
previewUrl = url.substring(0, url.indexOf("?"));
} catch (Exception e) {
log.error("预览文件地址错误,文件名为:{},bucket名为:{},错误信息为:", fileName, bucketName, e);
}
log.info("文件查找成功,文件预览地址为:{},文件名:{},bucket:{}", previewUrl, fileName, bucketName);
return Msg.SUCCESS.setNewData(previewUrl);
}
/**
* 下载文件
*
* @param response
* @param fileName 文件名
* @param bucketName 桶名
* @return
*/
@PostMapping("/download")
public Msg download(HttpServletResponse response, @RequestParam("fileName") String fileName, @RequestParam("bucketName") String bucketName) {
try {
minioService.downloadFile(response, fileName, bucketName);
log.info("文件下载成功,文件名称:{},bucket名为:{}", fileName, bucketName);
return Msg.DOWNLOAD_SUCCESS;
} catch (Exception e) {
log.error("文件下载失败,文件名称:{},错误信息:", fileName, e);
return Msg.DOWNLOAD_ERROR;
}
}
/**
* 删除文件
*
* @param fileName 文件名
* @param bucketName 桶名
* @return
*/
@DeleteMapping("/delFile")
public Msg delFile(@RequestParam("fileName") String fileName, @RequestParam("bucketName") String bucketName) {
try {
minioService.delFile(fileName, bucketName);
log.info("文件删除成功,文件名称为:{},bucket为:{}", fileName, bucketName);
return Msg.DELETE_SUCCESS.setNewData("删除文件:" + fileName + "成功!");
} catch (Exception e) {
log.error("删除文件失败,文件名称为:{},bucket为:{},错误信息:", bucketName, fileName, e);
return Msg.DELETE_ERROR.setNewData("删除文件:" + fileName + "失败!");
}
}
}
pojo:
package com.greedy.minio.pojo;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
import java.io.Serializable;
/**
* @Author: Greedy
* @Date: Created in:2022-09-15
* Description: 文件上传入参实体类
*/
@Data
public class FileUploadBody implements Serializable {
/**
* 自定义文件名
*/
private String fileName;
/**
* 多文件
*/
private MultipartFile[] files;
/**
* 所属模块
*/
private String bucket;
}
serviceImpl:
package com.greedy.minio.service.impl;
import cn.hutool.core.util.StrUtil;
import com.greedy.minio.config.MinioConfig;
import com.greedy.minio.pojo.FileUploadBody;
import com.greedy.minio.service.MinioService;
import com.greedy.minio.vo.FileVo;
import io.minio.*;
import io.minio.http.Method;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* @Author: Greedy
* @Date: Created in:2022-09-15
* Description:
*/
@Service
@Slf4j
public class MinioServiceImpl implements MinioService {
@Autowired
private MinioConfig minioConfig;
@Autowired
private MinioClient minioClient;
/**
* 上传文件
* @param body
* @return
*/
@Override
public List<FileVo> uploadFile(FileUploadBody body) {
MultipartFile[] files=body.getFiles();
if (Objects.nonNull(files) && files.length > 0) {
String bucketName = StrUtil.isNotBlank(body.getBucket()) ? body.getBucket().replaceAll(",", "") : minioConfig.getBucketName();
List<FileVo> resultList = new ArrayList<>();
String fileName = null;
for (MultipartFile file : files) {
try {
if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
fileName = file.getOriginalFilename();
if (StrUtil.isNotBlank(body.getFileName())){
fileName = body.getFileName();
}
StringBuffer fileNameSB = new StringBuffer();
fileNameSB.append(System.currentTimeMillis()).append("_").append(bucketName).append("_");
if (StrUtil.isNotBlank(fileName)){
fileNameSB.append(fileName.replaceAll(",", ""));
}
fileName = fileNameSB.toString();
PutObjectArgs args = PutObjectArgs.builder()
.bucket(bucketName)
.object(fileName)
.stream(file.getInputStream(), file.getSize(), -1)
.contentType(file.getContentType())
.build();
minioClient.putObject(args);
FileVo fileVo=new FileVo();
fileVo.setFileName(fileName);
fileVo.setBucket(bucketName);
fileVo.setPreviewUrl(getPreviewUrl(fileName,bucketName));
resultList.add(fileVo);
log.info("文件上传成功,文件名称:{},bucket名称:{}", fileName, bucketName);
}catch (Exception e){
log.error("文件上传失败,文件名称:{},bucket名称:{},错误信息:", fileName, bucketName, e);
}
}
return resultList;
}
return Collections.emptyList();
}
/**
* 获取预览地址
* @param fileName
* @param bucketName
* @return
*/
@Override
public String getPreviewUrl(String fileName, String bucketName) {
if (StrUtil.isNotBlank(fileName)) {
bucketName = StrUtil.isNotBlank(bucketName) ? bucketName : minioConfig.getBucketName();
try {
minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(fileName).build());
if (null != minioConfig.getPreviewExpiry()){
String presignedObjectUrl = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
.method(Method.GET).bucket(bucketName).object(fileName)
.expiry(minioConfig.getPreviewExpiry(), TimeUnit.HOURS).build());
log.info("有时限文件预览地址为:{},文件名:{},bucket名:{},有效时间为:{}", presignedObjectUrl, fileName, bucketName, minioConfig.getPreviewExpiry());
return presignedObjectUrl;
}else {
String presignedObjectUrl = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
.method(Method.GET).bucket(bucketName).object(fileName).build());
log.info("文件预览地址为:{},文件名:{},bucket名:{},有效时间为:永久", presignedObjectUrl, fileName, bucketName);
return presignedObjectUrl;
}
} catch (Exception e) {
log.error("预览文件地址错误,文件名为:{},bucket名为:{},错误信息为:", fileName, bucketName, e);
}
}
return null;
}
/**
* 下载文件
* @param response
* @param fileName
* @param bucketName
*/
@Override
public void downloadFile(HttpServletResponse response, String fileName, String bucketName) {
InputStream inputStream = null;
if (StrUtil.isNotBlank(fileName)) {
bucketName = StrUtil.isNotBlank(bucketName) ? bucketName : minioConfig.getBucketName();
try {
StatObjectResponse objectStat = minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(fileName).build());
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
response.setContentType(objectStat.contentType());
response.setCharacterEncoding("UTF-8");
inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(fileName).build());
IOUtils.copy(inputStream, response.getOutputStream());
log.info("文件下载成功,文件名称:{},bucket名为:{}", fileName, bucketName);
} catch (Exception e) {
log.error("文件下载失败,文件名称:{},错误信息:", fileName, e);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
log.error("输入流关闭失败,文件名:{},错误信息:", fileName, e);
}
}
}
}
}
/**
* 删除文件
* @param fileName
* @param bucketName
* @return
*/
@Override
public String delFile(String fileName, String bucketName) {
if (StrUtil.isNotBlank(fileName)) {
bucketName = StrUtil.isNotBlank(bucketName) ? bucketName : minioConfig.getBucketName();
try {
minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(fileName).build());
log.info("文件删除成功,文件名称为:{},bucket为:{}", fileName, bucketName);
return "success";
}catch (Exception e) {
log.error("删除文件失败,文件名称为:{},bucket为:{},错误信息:", bucketName, fileName, e);
return "删除文件失败,请刷新后重试";
}
}
return "文件不能为空";
}
}
service:
package com.greedy.minio.service;
import com.greedy.minio.pojo.FileUploadBody;
import com.greedy.minio.vo.FileVo;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* @Author: Greedy
* @Date: Created in:2022/09/15
* Description:
*/
public interface MinioService {
/**
* 上传文件
* @param body
* @return
*/
List<FileVo> uploadFile(FileUploadBody body);
/**
* 获取预览地址
* @param fileName
* @param bucketName
* @return
*/
String getPreviewUrl(String fileName, String bucketName);
/**
* 下载文件
* @param response
* @param fileName
* @param bucketName
*/
void downloadFile(HttpServletResponse response, String fileName, String bucketName);
/**
* 删除文件
* @param fileName
* @param bucketName
* @return
*/
String delFile(String fileName, String bucketName);
}
utils:这个统一结果返回实体类,在代码里换成你寄几的就行( •̀ ω •́ )✧
package com.greedy.minio.utils;
import lombok.Data;
/**
* 统一返回结果集实体类
* @param <T> 返回数据对象
*/
@Data
public class Msg<T> {
/**
* 错误码
*/
private Integer Code;
/**
* 错误信息,一般为前端提示信息
*/
private String Msg;
/**
* 返回值,一般为成功后返回的数据
*/
private T data;
/**
* 错误详情,一般为失败后的详细原因,如空指针之类的
*/
private String Detail;
public Msg() {}
public Msg(Integer Code, String Msg) {
this.Code = Code;
this.Msg = Msg;
}
public Msg(Integer Code, String Msg, T data) {
this.Code = Code;
this.Msg = Msg;
this.data = data;
}
/**
* 配合静态对象直接设置 data 参数
* @param data
* @return
*/
public Msg setNewData(T data) {
Msg error = new Msg();
error.setCode(this.Code);
error.setMsg(this.Msg);
error.setDetail(this.Detail);
error.setData(data);
return error;
}
/**
* 配合静态对象直接设置 errorMsg 参数
* @param
* @return
*/
public Msg setNewMsg(String Msg) {
Msg error = new Msg();
error.setCode(this.Code);
error.setMsg(Msg);
error.setDetail(this.Detail);
error.setData(this.data);
return error;
}
public static final Msg SUCCESS = new Msg(200, "成功");
public static final Msg ERROR = new Msg(405, "失败");
public static final Msg INSERT_SUCCESS = new Msg(200, "新增成功");
public static final Msg UPDATE_SUCCESS = new Msg(200, "更新成功");
public static final Msg DELETE_SUCCESS = new Msg(200, "删除成功");
public static final Msg UPLOAD_SUCCESS = new Msg(200, "上传成功");
public static final Msg DOWNLOAD_SUCCESS = new Msg(200, "下载成功");
public static final Msg LOGIN_SUCCESS = new Msg(200, "登陆成功");
public static final Msg LOGOUT_SUCCESS = new Msg(200, "登出成功");
public static final Msg LOGIN_ERROR = new Msg(201, "登陆错误");
public static final Msg LOGIN_EXPIRE = new Msg(202, "登陆过期");
public static final Msg ACCESS_LIMITED = new Msg(301, "访问受限");
public static final Msg ARGS_ERROR = new Msg(501, "参数错误");
public static final Msg UNKOWN_ERROR = new Msg(502, "系统异常");
public static final Msg INSERT_ERROR = new Msg(503, "新增错误");
public static final Msg UPDATE_ERROR = new Msg(504, "更新错误");
public static final Msg DELETE_ERROR = new Msg(506, "删除错误");
public static final Msg UPLOAD_ERROR = new Msg(507, "上传错误");
public static final Msg DOWNLOAD_ERROR = new Msg(508, "下载错误");
public static final Msg OTHER_SYSTEM_ERROR = new Msg(509, "调用系统异常");
// return ErrorMsg.SUCCESS; 成功返回
// return ErrorMsg.SUCCESS.setNewData(list); 成功带参返回
// return ErrorMsg.LOGIN_ERROR.setNewErrorMsg("用户名或密码不正确"); 错误返回并设定自定义错误信息
}
vo:
package com.greedy.minio.vo;
import lombok.Data;
import java.io.Serializable;
/**
* @Author: Greedy
* @Date: Created in:2022-09-15
* Description: 展示给前端的具体信息
*/
@Data
public class FileVo implements Serializable {
/**
* 文件名称
*/
private String fileName;
/**
* 预览地址
*/
private String previewUrl;
/**
* 所属模块
*/
private String bucket;
}
MinioApp:主启动类省略。。。
application.yaml:
pom:
<dependencies>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>去mvn中央仓库自己找</version>
</dependency>
<!-- 工具类依赖 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.6</version>
</dependency>
<!-- 可以自行扩展,例如显示进度条等依赖 -->
</dependencies>
3、使用MinIO
代码Controller里面几个方法注释写的都很详细了,就不用说了吧。。。
如果您坚持到了这里了都,不给个赞可说不过去熬