簡介
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/*"]
}]
}