简介
Minio是Apache License v2.0下发布的对象存储服务器。它与Amazon S3云存储服务兼容。它最适合存储非结构化数据,如照片,视频,日志文件,备份和容器/ VM映像。对象的大小可以从几KB到最大5TBMinio服务器足够轻,可以与应用程序堆栈捆绑在一起,类似于NodeJS,Redis和MySQL。https://docs.minio.io/
使用分布式文件服务FASTDFS和阿里云的OSS对象存储来存文件。奈何OSS太贵,FASTDFS搭建配置又太繁琐,推荐高性能对象存储服务MinIO。
Docker安装
docker pull minio/minio
docker run -p 9000:9000 --name minio
-e "MINIO_ACCESS_KEY=minio"
-e "MINIO_SECRET_KEY=gulimall_minio"
-v /mydata/minio/data:/data
-v /mydata/minio/config:/root/.minio
-d minio/minio server /data
存储桶命名规则
以下规则适用于命名 S3 存储桶:
- 存储桶名称必须长在 3 到 63 个字符之间。
- 存储桶名称只能由小写字母、数字、点 (.) 和连字符 (-) 组成。
- 存储桶名称必须以字母或数字开头和结尾。
- 存储桶名称不得格式化为 IP 地址(例如,192.168.5.4)。
- 存储桶名称在分区中必须是唯一的。分区是区域的分组。AWS 目前有三个分区:(标准区域)、(中国区域)和(AWS GovCloud [美国]区域)。awsaws-cnaws-us-gov
- 与 Amazon S3 传输加速一起使用的存储桶的名称中不能有点 (.)。有关传输加速的详细信息,请参阅亚马逊S3 传输加速。
java客户端使用
依赖:
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>7.0.2</version>
</dependency>
application.yml配置
# 图片服务器 minio配置
minio:
ip: xxxxxxx:9000
# minio登录账号密码
accessKey: xxxxxxx
secretKey: xxxxxxxx
## 桶名(文件夹)命名规则要符合 亚马逊S3标准 详情可看http://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html
bucketName:
## 照片文件夹
facility: facility-photos
操作API 类
/**
* @author zhan
* @since 2020-05-18 11:23
*/
@Slf4j
public class Minio {
/**
* 服务器地址
*/
@Value("${minio.ip}")
private String ip;
/**
* 登录账号
*/
@Value("${minio.accessKey}")
private String accessKey;
/**
* 登录密码
*/
@Value("${minio.secretKey}")
private String secretKey;
/**
* 缩略图大小
*/
@Value("${minio.thumbor.width}")
private String thumborWidth;
/**
* Minio文件上传
*
* @param file 文件实体
* @param fileName 修饰过的文件名 非源文件名
* @param bucketName 所存文件夹(桶名)
* @return
*/
public R minioUpload(MultipartFile file, String fileName, String bucketName) {
try {
MinioClient minioClient = new MinioClient("http://" + ip, accessKey, secretKey);
boolean bucketExists = minioClient.bucketExists(bucketName);
if (bucketExists) {
log.info("仓库" + bucketName + "已经存在,可直接上传文件。");
} else {
minioClient.makeBucket(bucketName);
}
if (file.getSize() <= 20971520) {
// fileName为空,说明要使用源文件名上传
if (fileName == null) {
fileName = file.getOriginalFilename();
fileName = fileName.replaceAll(" ", "_");
}
// minio仓库名
minioClient.putObject(bucketName, fileName, file.getInputStream(), file.getContentType());
log.info("成功上传文件 " + fileName + " 至 " + bucketName);
String fileUrl = bucketName + "/" + fileName;
Map<String, Object> map = new HashMap<String, Object>();
map.put("fileUrl", fileUrl);
map.put("bucketName", bucketName);
map.put("originFileName", fileName);
return R.ok(map);
} else {
throw new Exception("请上传小于20mb的文件");
}
} catch (Exception e) {
e.printStackTrace();
if (e.getMessage().contains("ORA")) {
return R.error("上传失败:【查询参数错误】");
}
return R.error("上传失败:【" + e.getMessage() + "】");
}
}
/**
* 判断文件是否存在
*
* @param fileName 文件名
* @param bucketName 桶名(文件夹)
* @return
*/
public boolean isFileExisted(String fileName, String bucketName) {
InputStream inputStream = null;
try {
MinioClient minioClient = new MinioClient("http://" + ip, accessKey, secretKey);
inputStream = minioClient.getObject(bucketName, fileName);
if (inputStream != null) {
return true;
}
} catch (Exception e) {
return false;
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return false;
}
/**
* 删除文件
*
* @param bucketName 桶名(文件夹)
* @param fileName 文件名
* @return
*/
public boolean delete(String bucketName, String fileName) {
try {
MinioClient minioClient = new MinioClient("http://" + ip, accessKey, secretKey);
minioClient.removeObject(bucketName, fileName);
return true;
} catch (Exception e) {
log.error(e.getMessage());
return false;
}
}
/**
* 下载文件
*
* @param objectName 文件名
* @param bucketName 桶名(文件夹)
* @param response
* @return
*/
public R downloadFile(String objectName, String bucketName, HttpServletResponse response) {
try {
MinioClient minioClient = new MinioClient("http://" + ip, accessKey, secretKey);
InputStream file = minioClient.getObject(bucketName, objectName);
String filename = new String(objectName.getBytes("ISO8859-1"), StandardCharsets.UTF_8);
response.setHeader("Content-Disposition", "attachment;filename=" + filename);
ServletOutputStream servletOutputStream = response.getOutputStream();
int len;
byte[] buffer = new byte[1024];
while ((len = file.read(buffer)) > 0) {
servletOutputStream.write(buffer, 0, len);
}
servletOutputStream.flush();
file.close();
servletOutputStream.close();
return R.ok(objectName + "下载成功");
} catch (Exception e) {
e.printStackTrace();
if (e.getMessage().contains("ORA")) {
return R.error("下载失败:【查询参数错误】");
}
return R.error("下载失败:【" + e.getMessage() + "】");
}
}
/**
* 获取文件流
*
* @param objectName 文件名
* @param bucketName 桶名(文件夹)
* @return
*/
public InputStream getFileInputStream(String objectName, String bucketName) {
try {
MinioClient minioClient = new MinioClient("http://" + ip, accessKey, secretKey);
return minioClient.getObject(bucketName, objectName);
} catch (Exception e) {
e.printStackTrace();
log.error(e.getMessage());
}
return null;
}
}
springboot整合Minio
- 导入依赖
<!-- https://mvnrepository.com/artifact/com.jlefebure/spring-boot-starter-minio -->
<dependency>
<groupId>com.jlefebure</groupId>
<artifactId>spring-boot-starter-minio</artifactId>
<version>1.4</version>
</dependency>
- 配置yml
spring:
minio:
url: http://192.168.1.119:9000/
access-key: minio
secret-key: gulimall_minio
bucket: gulimall
create-bucket: true
- 直接在controller中使用
package com.atguigu.gulimall.product.controller;
import com.jlefebure.spring.boot.minio.MinioConfigurationProperties;
import com.jlefebure.spring.boot.minio.MinioService;
import io.minio.messages.Item;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamResource;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLConnection;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
/**
* @author zhan
* @since 2020-05-18 14:50
*/
@RestController
public class TestController {
@Autowired
MinioService minioService;
@Autowired
private MinioConfigurationProperties configurationProperties;
@GetMapping("/files")
public List<Item> testMinio(){
return minioService.list();
}
/**
* 根据文件名称下载文件
* @param object
* @param response
* @throws com.jlefebure.spring.boot.minio.MinioException
* @throws IOException
* @throws IOException
*/
@GetMapping("files/{object}")
public void getObject(@PathVariable("object") String object, HttpServletResponse response) throws com.jlefebure.spring.boot.minio.MinioException, IOException, IOException {
InputStream inputStream = minioService.get(Paths.get(object));
InputStreamResource inputStreamResource = new InputStreamResource(inputStream);
// Set the content type and attachment header.
response.addHeader("Content-disposition", "attachment;filename=" + object);
response.setContentType(URLConnection.guessContentTypeFromName(object));
// Copy the stream to the response's output stream.
IOUtils.copy(inputStream, response.getOutputStream());
response.flushBuffer();
}
@PostMapping("/upload")
public void addAttachement(@RequestParam("file") MultipartFile file) throws IOException {
System.out.println(file);
String filename = file.getOriginalFilename();
// System.out.println(filename);
// Path path = Paths.get(file.getResource().getURI());
Path path = Paths.get(filename);
String url = configurationProperties.getUrl() + "/" + configurationProperties.getBucket() + path.toAbsolutePath();
// System.out.println(path.toAbsolutePath());
// url += path.toAbsolutePath();
System.out.println(url);
// System.out.println(path);
try {
minioService.upload(path, file.getInputStream(), file.getContentType());
System.out.println("上传完成!!!");
} catch (com.jlefebure.spring.boot.minio.MinioException e) {
throw new IllegalStateException("The file cannot be upload on the internal storage. Please retry later", e);
} catch (IOException e) {
throw new IllegalStateException("The file cannot be read", e);
}
}
}
# 附上单元方法测试结论。
@Test
public void t1() throws IOException, InvalidKeyException, NoSuchAlgorithmException, InsufficientDataException, InvalidExpiresRangeException, InvalidResponseException, InternalException, NoResponseException, InvalidBucketNameException, XmlPullParserException, ErrorResponseException, BucketPolicyTooLargeException, InvalidObjectPrefixException {
System.out.println(mmclient);
Iterable<Result<Item>> results = mmclient.listObjects("gulimall");
for (Result<Item> result : results) {
Item item = result.get();
System.out.println(item.lastModified() + ", " + item.size() + ", " + item.objectName());
}
//根据文件名称获取浏览地址,此种方式在不设置策略的情况下【使用默认策略】是不能直接下载文件的
String gulimall = mmclient.presignedGetObject("gulimall", "11111.jpg");
System.out.println("下载地址:"+gulimall);
String gulimall1 = mmclient.getBucketLifeCycle("gulimall");
System.out.println(gulimall1);// 空
String policy = mmclient.getBucketPolicy("gulimall");
System.out.println("policy:"+policy);
//通过修改桶策略即可使用返回的url直接访问minio中的文件【推荐这种方式!!!】
String objectUrl = mmclient.getObjectUrl("gulimall", "刘德华+-+练习.ape");
System.out.println("objectUrl:" + objectUrl);
//不能下载
String putObject = mmclient.presignedPutObject("gulimall", "刘德华+-+练习.ape");
System.out.println("putObject:"+putObject);
}
读写策略json如下【简单粗暴点也可在搭建完成后直接在控制台操作】:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"AWS": ["*"]
},
"Action": ["s3:ListBucketMultipartUploads", "s3:GetBucketLocation", "s3:ListBucket"],
"Resource": ["arn:aws:s3:::gulimall"]
}, {
"Effect": "Allow",
"Principal": {
"AWS": ["*"]
},
"Action": ["s3:AbortMultipartUpload", "s3:DeleteObject", "s3:GetObject", "s3:ListMultipartUploadParts", "s3:PutObject"],
"Resource": ["arn:aws:s3:::gulimall/*"]
}]
}