前言
1.什么是Minio?
MinIO 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。(我的理解就是对象存储服务。跟fastdfs很像,有服务端和客户端)
- docker安装Minio(别问我为什么用docker。两个字简单方便)
#拉取镜像
docker pull minio
#拉取镜像
docker run
-p 9001:9000 -p 9090:9090 # 第一个端口是服务端口,第二个是客户端(9001是宿主机端口,9000是容器内部端口)
--net=host #表示容器和宿主机共享一个ip
--name minio #容器名称
-d --restart=always #自启动
-e "MINIO_ACCESS_KEY=minio" #客户端账号(有点像Mysql的用户密码)
-e "MINIO_REGION=cn-north-1"
-e "TZ=Asia/Beijing" #设置容器时区(不设置会报错)
-e "MINIO_SECRET_KEY=minio123456" #客户端密码 (密码必须不得少于8位不然启动不了)
-v /minio/data:/data #挂载目录将/minio/data挂载到容器的/data目录,防止数据丢失
-v /minio/config:/root/.minio minio/minio server /data
--console-address ":9090" -address ":9001"
没有设置时区时会报错
error occurred
ErrorResponse(code = AccessDenied, message = Access denied, bucketName = bucket, objectName = null, resource = /bucket, requestId = null, hostId = null)
request={method=HEAD, url=http://192.168.186.128:9090/bucket, headers=Host: 192.168.186.128:9090
Accept-Encoding: identity
User-Agent: MinIO (amd64; amd64) minio-java/2.0.0
Content-MD5: 1B2M2Y8AsgTpgAmY7PhCfg==
x-amz-content-sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
x-amz-date: 20220126T034536Z
Authorization: AWS4-HMAC-SHA256 Credential=REDACTED/20220126/us-east-1/s3/aws4_request, SignedHeaders=accept-encoding;content-md5;host;x-amz-content-sha256;x-amz-date, Signature=REDACTED
}
response={code=403, headers=Date: Wed, 26 Jan 2022 03:45:36 GMT
Content-Length: 209
Content-Type: text/xml; charset=utf-8
Connection: close
}
参考解决链接
2. 启动成功后访问http://ip:9090,进入控制台页面
用户密码就是上面启动命令里的账号密码
Buckets:我的理解就是一个打的文件盘,而下面的project和file是一个个文件夹,objects就是文件个数
3. SpringBoot简单使用
#pom文件
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 操作minio的java客户端-->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.2</version>
</dependency>
<!-- 操作minio的java客户端-->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.2.1</version>
</dependency>
<!-- jwt鉴权相应依赖-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
#yml文件
server:
port: 8080
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
#minio配置
minio:
access-key: minio #启动命令里的账号
secret-key: minio123456 #启动命令里的地址
url: http://192.168.32.211:9001 #访问地址
bucket-name: project
#配置文件
@Data
@Configuration
@ConfigurationProperties(prefix = "spring.minio")
public class MinioConfig {
private String accessKey;
private String secretKey;
private String url;
private String bucketName;
@Bean
public MinioClient minioClient(){
return MinioClient.builder()
.region("cn-north-1")
.endpoint(url)
.credentials(accessKey,secretKey)
.build();
}
}
#工具类
package com.example.producerserver.util;
import com.example.producerserver.config.MinioConfig;
import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import lombok.SneakyThrows;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Component
public class MinioUtils {
@Autowired
private MinioClient minioClient;
@Autowired
private MinioConfig configuration;
/**
* @param name 名字
* @return boolean
* @Description description: 判断bucket是否存在,不存在则创建
* @Author zy
* @Date 2024/08/01
*/
public boolean existBucket(String name) {
boolean exists;
try {
exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(name).build());
if (!exists) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(name).build());
exists = true;
}
} catch (Exception e) {
e.printStackTrace();
exists = false;
}
return exists;
}
/**
* @param bucketName 存储bucket名称
* @return {@link Boolean }
* @Description 创建存储bucket
* @Author zy
* @Date 2024/08/01
*/
public Boolean makeBucket(String bucketName) {
try {
minioClient.makeBucket(MakeBucketArgs.builder()
.bucket(bucketName)
.build());
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* @param bucketName 存储bucket名称
* @return {@link Boolean }
* @Description 删除存储bucket
* @Author zy
* @Date 2024/08/01
*/
public Boolean removeBucket(String bucketName) {
try {
minioClient.removeBucket(RemoveBucketArgs.builder()
.bucket(bucketName)
.build());
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* @param fileName 文件名称
* @param time 时间
* @return {@link Map }
* @Description 获取上传临时签名
* @Author zy
* @Date 2024/08/01
*/
@SneakyThrows
public Map getPolicy(String fileName, ZonedDateTime time) {
PostPolicy postPolicy = new PostPolicy(configuration.getBucketName(), time);
postPolicy.addEqualsCondition("key", fileName);
try {
Map<String, String> map = minioClient.getPresignedPostFormData(postPolicy);
HashMap<String, String> map1 = new HashMap<>();
map.forEach((k, v) -> {
map1.put(k.replaceAll("-", ""), v);
});
map1.put("host", configuration.getUrl() + "/" + configuration.getBucketName());
return map1;
} catch (ErrorResponseException e) {
e.printStackTrace();
} catch (InsufficientDataException e) {
e.printStackTrace();
} catch (InternalException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (InvalidResponseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (ServerException e) {
e.printStackTrace();
} catch (XmlParserException e) {
e.printStackTrace();
}
return null;
}
/**
* @param objectName 对象名称
* @param method 方法
* @param time 时间
* @param timeUnit 时间单位
* @return {@link String }
* @Description 获取上传文件的url
* @Author zy
* @Date 2024/08/01
*/
public String getPolicyUrl(String objectName, Method method, int time, TimeUnit timeUnit) {
try {
return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
.method(method)
.bucket(configuration.getBucketName())
.object(objectName)
.expiry(time, timeUnit).build());
} catch (ErrorResponseException e) {
e.printStackTrace();
} catch (InsufficientDataException e) {
e.printStackTrace();
} catch (InternalException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (InvalidResponseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (XmlParserException e) {
e.printStackTrace();
} catch (ServerException e) {
e.printStackTrace();
}
return null;
}
/**
* @param file 文件
* @param fileName 文件名称
* @Description 上传文件
* @Author zy
* @Date 2024/08/01
*/
public void upload(MultipartFile file, String fileName) {
// 使用putObject上传一个文件到存储桶中。
try {
InputStream inputStream = file.getInputStream();
minioClient.putObject(PutObjectArgs.builder()
.bucket(configuration.getBucketName())
.object(fileName)
.stream(inputStream, file.getSize(), -1)
.contentType(file.getContentType())
.build());
} catch (ErrorResponseException e) {
e.printStackTrace();
} catch (InsufficientDataException e) {
e.printStackTrace();
} catch (InternalException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (InvalidResponseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (ServerException e) {
e.printStackTrace();
} catch (XmlParserException e) {
e.printStackTrace();
}
}
/**
* @param objectName 对象名称
* @param time 时间
* @param timeUnit 时间单位
* @return {@link String }
* @Description 根据filename获取文件访问地址
* @Author zy
* @Date 2024/08/01
*/
public String getUrl(String objectName, int time, TimeUnit timeUnit) {
String url = null;
try {
url = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(configuration.getBucketName())
.object(objectName)
.expiry(time, timeUnit).build());
} catch (ErrorResponseException e) {
e.printStackTrace();
} catch (InsufficientDataException e) {
e.printStackTrace();
} catch (InternalException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (InvalidResponseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (XmlParserException e) {
e.printStackTrace();
} catch (ServerException e) {
e.printStackTrace();
}
return url;
}
/**
* @param fileName
* @return {@link ResponseEntity }<{@link byte[] }>
* @Description description: 下载文件
* @Author zy
* @Date 2024/08/01
*/
public ResponseEntity<byte[]> download(String fileName) {
ResponseEntity<byte[]> responseEntity = null;
InputStream in = null;
ByteArrayOutputStream out = null;
try {
in = minioClient.getObject(GetObjectArgs.builder().bucket(configuration.getBucketName()).object(fileName).build());
out = new ByteArrayOutputStream();
IOUtils.copy(in, out);
//封装返回值
byte[] bytes = out.toByteArray();
HttpHeaders headers = new HttpHeaders();
try {
headers.add("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
headers.setContentLength(bytes.length);
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setAccessControlExposeHeaders(Arrays.asList("*"));
responseEntity = new ResponseEntity<byte[]>(bytes, headers, HttpStatus.BAD_REQUEST);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return responseEntity;
}
/**
* @param objectFile 对象文件
* @return {@link String }
* @Description 根据文件名和桶获取文件路径
* @Author zy
* @Date 2024/08/01
*/
public String getFileUrl(String objectFile) {
try {
return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(configuration.getBucketName())
.object(objectFile)
.build()
);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
#写个测试接口
package com.example.producerserver.controller;
import com.example.producerserver.util.MinioUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StopWatch;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.HashMap;
@CrossOrigin
@RestController
@RequestMapping("/api")
@Log4j2
public class MinioFileUploadController {
@Autowired
private MinioUtils minioUtils;
/**
* @param file 文件
* @param fileName 文件名称
* @return {@link AjaxResult }
* @Description 上传文件
* @Author zy
* @Date 2024/08/01
*/
@GetMapping("/upload")
public Object uploadFile(@RequestParam("file") MultipartFile file, String fileName) {
StopWatch stopWatch=new StopWatch();
stopWatch.start("测试上传");
minioUtils.upload(file, fileName);
stopWatch.stop();
log.info(stopWatch.prettyPrint());
return"上传成功";
}
/**
* @param fileName 文件名称
* @return {@link ResponseEntity }
* @Description dowload文件
* @Author zy
* @Date 2024/08/01
*/
@GetMapping("/dowload")
public ResponseEntity dowloadFile(@RequestParam("fileName") String fileName) {
return minioUtils.download(fileName);
}
/**
* @param fileName 文件名称
* @return {@link AjaxResult }
* @Description 得到文件url
* @Author zy
* @Date 2024/08/01
*/
@GetMapping("/getUrl")
public Object getFileUrl(@RequestParam("fileName") String fileName){
HashMap map=new HashMap();
map.put("FileUrl",minioUtils.getFileUrl(fileName));
return map;
}
}
- 测试
当文件名相同时会覆盖掉之前的文件。
上传2G文件耗时110秒左右,总的来说还行 - 总结:工作中用到的存储文件的方式是nas盘,最近由于文件越来越多,导致磁盘空间不可用,就想找个别的东西代替nas存储文件,偶然了解到minio分布式文件存储。本次demo只是简单介绍和使用,学习之路任到重远,欢迎指点