Minio-對象存儲


1. 簡介

官方地址

MinIO 是一個基於Apache License v2.0開源協議的對象存儲服務。它兼容亞馬遜S3雲存儲服務接口,非常適合於存儲大容量非結構化的數據,例如圖片、視頻、日志文件、備份數據和容器/虛擬機鏡像等,而一個對象文件可以是任意大小,從幾kb到最大5T不等。

MinIO是一個非常輕量的服務,可以很簡單的和其他應用的結合,類似 NodeJS, Redis 或者 MySQL。

Minio使用糾刪碼erasure code和校驗和checksum來保護數據免受硬件故障和無聲數據損壞。 即便您丟失一半數量(N/2)的硬盤,您仍然可以恢復數據。

想要使數據丟失可恢復, 最少得掛載4塊盤或4的倍數。

1.1 什么是糾刪碼erasure code?

糾刪碼是一種恢復丟失和損壞數據的數學算法, Minio采用Reed-Solomon code將對象拆分成N/2數據和N/2 奇偶校驗塊。 這就意味着如果是12塊盤,一個對象會被分成6個數據塊、6個奇偶校驗塊,你可以丟失任意6塊盤(不管其是存放的數據塊還是奇偶校驗塊),你仍可以從剩下的盤中的數據進行恢復。

其中原數據塊和校驗塊是根據存儲類型決定的。存儲塊設置,默認是原數據塊的一半。

1.2 為什么糾刪碼有用?

糾刪碼的工作原理和RAID或者復制不同,像RAID6可以在損失兩塊盤的情況下不丟數據,而Minio糾刪碼可以在丟失一半的盤的情況下,仍可以保證數據安全。 而且Minio糾刪碼是作用在對象級別,可以一次恢復一個對象,而RAID是作用在卷級別,數據恢復時間很長。 Minio對每個對象單獨編碼,存儲服務一經部署,通常情況下是不需要更換硬盤或者修復。Minio糾刪碼的設計目標是為了性能和盡可能的使用硬件加速。

2. 安裝

2.1 docker

MinIO自定義Access和Secret密鑰

要覆蓋MinIO的自動生成的密鑰,可以將Access和Secret密鑰設為環境變量。 MinIO允許常規字符串作為Access和Secret密鑰。

docker run -p 9000:9000 --name minio1 \
  -e "MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE" \
  -e "MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
  -v /mnt/data:/data \
  -v /mnt/config:/root/.minio \
  minio/minio server /data

2.2 docker-compose

version: '3.0'
services:
  minio:
    image: minio/minio
    container_name: minio
    ports:
      - "9000:9000"
    restart: always
    command: server /data
    environment:
      MINIO_ACCESS_KEY: admin
      #大於等於8位
      MINIO_SECRET_KEY: admin123 
    volumes:
      - /Users/ludangxin/usr/local/docker/minio/data:/data # 映射文件路徑

2.3 單機多磁盤掛載

掛載多個冗余盤,防止數據丟失。

docker run -p 9000:9000 --name minio1 \
  -e "MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE" \
  -e "MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
  -v /mnt/data:/data1 \
  -v /mnt/data:/data2 \
  -v /mnt/data:/data3 \
  -v /mnt/data:/data4 \
  -v /mnt/config:/root/.minio \
  minio/minio server /data1 /data2 /data3 /data4 

2.4 分布式部署

啟動一個分布式Minio實例,你只需要把硬盤位置做為參數傳給minio server命令即可,然后,你需要在所有其它節點運行同樣的命令。

注意

  • 分布式Minio里所有的節點需要有同樣的access秘鑰和secret秘鑰,這樣這些節點才能建立聯接。為了實現這個,你需要在執行minio server命令之前,先將access秘鑰和secret秘鑰export成環境變量。
  • 分布式Minio使用的磁盤里必須是干凈的,里面沒有數據。
  • 下面示例里的IP僅供示例參考,你需要改成你真實用到的IP和文件夾路徑。
  • 分布式Minio里的節點時間差不能超過3秒,你可以使用NTP 來保證時間一致。
  • 在Windows下運行分布式Minio處於實驗階段,請悠着點使用。

2.4.1 GNU/Linux 和 macOS

export MINIO_ACCESS_KEY=<ACCESS_KEY>
export MINIO_SECRET_KEY=<SECRET_KEY>
minio server http://192.168.1.11/export1 http://192.168.1.12/export2 \
               http://192.168.1.13/export3 http://192.168.1.14/export4 \
               http://192.168.1.15/export5 http://192.168.1.16/export6 \
               http://192.168.1.17/export7 http://192.168.1.18/export8

2.4.2 docker-compose

部署4個節點,每個節點掛載一個盤。

version: '3.7'
services:
  minio:
    image: minio/minio
    volumes:
      # 值需要換成對應節點的
      - /data0:/data0
    ports:
      - 9000:9000
    environment:
      MINIO_ACCESS_KEY: admin
      MINIO_SECRET_KEY: admin123  
    command: minio server http://minio-00/data0 http://minio-01/data1 http://minio-02/data2 http://minio-03/data3
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 30s
      timeout: 20s
      retries: 3
    # 指定當前節點的hostname
    hostname: minio-00
    # ip地址要換成自己的
    extra_hosts:
      - "minio-00:192.168.1.11"
      - "minio-01:192.168.1.12"
      - "minio-02:192.168.1.13"
      - "minio-03:192.168.1.14"

部署4個節點,每個節點四個盤。

version: '3.7'
services:
  minio:
    image: minio/minio
    volumes:
      - /data0:/data0
      - /data1:/data1
      - /data2:/data2
      - /data3:/data3
    ports:
      - 9000:9000
    environment:
      MINIO_ACCESS_KEY: admin
      MINIO_SECRET_KEY: admin123  
    command: minio server http://minio-0{0...3}/data{0...3}
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 30s
      timeout: 20s
      retries: 3
    # 指定當前節點的hostname
    hostname: minio-00
    # ip地址要換成自己的
    extra_hosts:
      - "minio-00:192.168.1.11"
      - "minio-01:192.168.1.12"
      - "minio-02:192.168.1.13"
      - "minio-03:192.168.1.14"

2.5 訪問測試

如圖:訪問localhost:9000,即可訪問minio 登陸頁面。

3. quick start

3.1 項目結構

├── pom.xml
└── src
    └── main
        ├── java
        │   └── com
        │       └── ldx
        │           └── minio
        │               ├── MinioApplication.java  # 啟動類
        │               ├── config
        │               │   ├── MinioConfig.java # minio 配置類
        │               │   └── MinioProperties.java # minio 服務參數
        │               ├── controller
        │               │   └── MinioController.java # 測試控制器
        │               ├── test
        │               │   └── FileUploader.java # 測試上傳
        │               └── util
        │                   └── MinioUtils.java # 工具類
        └── resources
            └── application.yaml # 服務配置文件

3.2 引入依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>2.5.3</version>
      <relativePath/> <!-- lookup parent from repository -->
   </parent>
   <groupId>com.ldx</groupId>
   <artifactId>minio</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>minio</name>
   <description>Demo project for Spring Boot</description>
   <properties>
      <java.version>1.8</java.version>
   </properties>
   <dependencies>
      <!-- springweb 支持 -->
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <!-- minio 工具包 -->
      <dependency>
         <groupId>io.minio</groupId>
         <artifactId>minio</artifactId>
         <version>7.0.2</version>
      </dependency>
      <dependency>
         <groupId>org.projectlombok</groupId>
         <artifactId>lombok</artifactId>
         <optional>true</optional>
      </dependency>
   </dependencies>

   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
               <excludes>
                  <exclude>
                     <groupId>org.projectlombok</groupId>
                     <artifactId>lombok</artifactId>
                  </exclude>
               </excludes>
            </configuration>
         </plugin>
      </plugins>
   </build>
</project>

3.2 上傳測試

我們先利用測試代碼測試一下 minio 附件上傳功能。測試代碼如下:

import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.InvalidKeyException;
import lombok.extern.slf4j.Slf4j;
import io.minio.MinioClient;
import io.minio.errors.MinioException;

@Slf4j
public class FileUploader {
  public static void main(String[] args) throws NoSuchAlgorithmException, IOException, InvalidKeyException {
    try {
      // 使用MinIO服務的URL,端口,Access key和Secret key創建一個MinioClient對象
      MinioClient minioClient = new MinioClient("http://localhost:9000", "admin", "admin123");

      // 檢查存儲桶是否已經存在
      String bucketName = "test";
      boolean isExist = minioClient.bucketExists(bucketName);
      if(isExist) {
        log.info("Bucket already exists.");
      } else {
        // 創建一個名為test的存儲桶,用於存儲照片的zip文件。
        minioClient.makeBucket(bucketName);
      }

      // 使用putObject上傳一個文件到存儲桶中。
      minioClient.putObject(bucketName,"lion.jpg", "/Users/ludangxin/temp/舞獅.png",null);
      log.info("/Users/ludangxin/temp/舞獅.png is successfully uploaded as lion.jpg to `test` bucket.");
    } catch(MinioException e) {
      log.info("Error occurred: " + e);
    }
  }
}

直接運行mian方法,運行成功后查看minio ui界面如下。圖片上傳成功,so easy~

3.3 application.yaml

minio:
  endpoint: http://localhost
  port: 9000
  default-bucket-name: ldx
  access-key: admin
  secret-key: admin123

3.4 MinioProperties

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * minio 配置屬性類
 *
 * @author ludangxin
 * @date 2021/8/17
 */
@Data
@ConfigurationProperties(prefix = "minio")
public class MinioProperties {
   /**
    * 端點
    */
   private String endpoint;

   /**
    * 端口
    */
   private Integer port;

   /**
    * 默認的桶名稱
    */
   private String defaultBucketName;

   /**
    * 訪問key
    */
   private String accessKey;

   /**
    * 密鑰
    */
   private String secretKey;
}

3.5 MinioConfig

import io.minio.MinioClient;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Objects;

/**
 * minio 配置類
 *
 * @author ludangxin
 * @date 2021/8/17
 */
@Slf4j
@Configuration
@EnableConfigurationProperties(MinioProperties.class)
public class MinioConfig {

   @Bean
   @SneakyThrows
   public MinioClient minioClient(MinioProperties minioProperties) {
      MinioClient minioClient = new MinioClient(minioProperties.getEndpoint(), minioProperties.getPort(),
         minioProperties.getAccessKey(), minioProperties.getSecretKey());
      String defaultBucketName = minioProperties.getDefaultBucketName();

      if(Objects.nonNull(defaultBucketName)) {
         // 創建默認的bucket
         if(!minioClient.bucketExists(defaultBucketName)) {
            log.info("create default bucket \"{}\" success", defaultBucketName);
            minioClient.makeBucket(defaultBucketName);
         }
      }

      return minioClient;
   }
}

3.6 MinioController

import com.ldx.minio.util.MinioUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

/**
 * minio 控制器
 *
 * @author ludangxin
 * @date 2021/8/18
 */
@RestController
@RequestMapping("minio")
@RequiredArgsConstructor
public class MinioController {

   private final MinioUtils minioUtils;

   @GetMapping
   public String getUrl(String fileName) {
      return minioUtils.getFileUrl(fileName);
   }

   @PostMapping
   public String upload(MultipartFile file) {
      return minioUtils.upload(file);
   }
  
   @GetMapping("download/{fileName}")
   public void download(@PathVariable String fileName, HttpServletResponse response) {
      minioUtils.download(fileName, response);
   }

   @DeleteMapping
   public void del(String fileName) {
      minioUtils.delFile(fileName);
   }

}

3.7 MinioUtils

package com.ldx.minio.util;

import io.minio.MinioClient;
import io.minio.PutObjectOptions;
import io.minio.messages.Bucket;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.http.fileupload.FileUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;

/**
 * minio 工具類
 *
 * @author ludangxin
 * @date 2021/8/17
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class MinioUtils {
   @Value("${minio.default-bucket-name}")
   private String defaultBucketName;

   private final MinioClient minioClient;

   /**
    * 獲取全部bucket
    *
    * @return all bucket
    */
   @SneakyThrows
   public List<Bucket> getAllBuckets() {
      return minioClient.listBuckets();
   }

   /**
    * 判斷 bucket是否存在
    *
    * @param bucketName 桶名稱
    * @return true 存在
    */
   @SneakyThrows
   public boolean bucketExists(String bucketName){
      return minioClient.bucketExists(bucketName);
   }

   /**
    * 創建 bucket
    *
    * @param bucketName 桶名稱
    */
   @SneakyThrows
   public void createBucket(String bucketName){
      boolean isExist = minioClient.bucketExists(bucketName);
      if(!isExist) {
         minioClient.makeBucket(bucketName);
      }
   }

   /**
    * 文件上傳
    *
    * @param bucketName 桶名稱
    * @param fileName 上傳后的文件名稱
    * @param fileAbsolutePath 文件的絕對路徑
    * @return 文件url
    */
   @SneakyThrows
   public String upload(String bucketName, String fileName, String fileAbsolutePath){
      minioClient.putObject(bucketName, fileName, fileAbsolutePath,null);
      return getFileUrl(bucketName, fileName);
   }

   /**
    * 文件上傳
    *
    * @param fileName 上傳后的文件名稱
    * @param stream 文件輸入流
    * @return 文件url
    */
   @SneakyThrows
   public String upload(String fileName, InputStream stream){
      this.upload(defaultBucketName, fileName, stream);
      return getFileUrl(defaultBucketName, fileName);
   }

   /**
    * 文件上傳
    *
    * @param file 文件
    * @return 文件url
    */
   public String upload(MultipartFile file) {
      final String fileName = file.getOriginalFilename();
      this.upload(defaultBucketName, file);
      return this.getFileUrl(defaultBucketName, fileName);
   }

   /**
    * 文件上傳
    *
    * @param bucketName 桶名稱
    * @param file 文件
    * @return 文件url
    */
   public String upload(String bucketName, MultipartFile file) {
      InputStream is = null;
      try {
         is = file.getInputStream();
         final String fileName = file.getOriginalFilename();
         this.upload(bucketName, fileName, is);
         return getFileUrl(bucketName, fileName);
      }
      catch(Exception e) {
         log.error(e.getMessage());
      }
      finally {
         try {
            if(Objects.nonNull(is)) {
               is.close();
            }
         } catch(IOException e) {
            log.error(e.getMessage());
         }
      }
      return null;
   }

   /**
    * 文件上傳
    *
    * @param bucketName 桶名稱
    * @param fileName 上傳后的文件名稱
    * @param stream 文件輸入流
    * @return 文件url
    */
   @SneakyThrows
   public String upload(String bucketName, String fileName, InputStream stream){
      minioClient.putObject(bucketName, fileName, stream, new PutObjectOptions(stream.available(), -1));
      return getFileUrl(bucketName, fileName);
   }
  
   /**
    * 附件下載
    *
    * @param fileName 附件名稱
    */
   @SneakyThrows
   public void download(String fileName, HttpServletResponse response) {
      this.download(defaultBucketName, fileName, response);
   }

   /**
    * 文件下載
    * 
    * @param bucketName 桶名稱
    * @param fileName 文件名稱
    */
   public void download(String bucketName, String fileName, HttpServletResponse response) {
      InputStream in = null;
      OutputStream out = null;
      try {
         in = minioClient.getObject(bucketName, fileName);
         int len = 0;
         byte[] buffer = new byte[1024];
         out = response.getOutputStream();
         response.reset();
         response.addHeader("Content-Disposition",
               " attachment;filename=" + new String(fileName.getBytes(), StandardCharsets.ISO_8859_1));
         response.setContentType("application/octet-stream");
         while ((len = in.read(buffer)) > 0) {
            out.write(buffer, 0, len);
         }
      } catch (Exception e) {
         log.error(e.getMessage());
      } finally {
         if (in != null){
            try {
               in.close();
            } catch (Exception e) {
               log.error(e.getMessage());
            }
         }
         if (out != null) {
            try {
               out.close();
            } catch (IOException e) {
               log.error(e.getMessage());
            }
         }
      }
   }

   /**
    * 刪除文件
    *
    * @param fileName 文件名稱
    */
   @SneakyThrows
   public void delFile(String fileName){
      this.delFile(defaultBucketName, fileName);
   }

   /**
    * 刪除文件
    *
    * @param bucketName 桶名稱
    * @param fileName 文件名稱
    */
   @SneakyThrows
   public void delFile(String bucketName, String fileName){
      minioClient.removeObject(bucketName, fileName);
   }

   /**
    * 獲取minio文件的下載地址
    *
    * @param fileName 文件名
    */
   @SneakyThrows
   public String getFileUrl(String fileName) {
      return this.getFileUrl(defaultBucketName, fileName);
   }

   /**
    * 獲取minio文件的下載地址
    *
    * @param bucketName 桶名稱
    * @param fileName 文件名
    */
   @SneakyThrows
   public String getFileUrl(String bucketName, String fileName) {
      return minioClient.presignedGetObject(bucketName, fileName);
   }

   /**
    * 設置桶策略
    *
    * @param bucketName 桶名稱
    * @param policy 策略
    */
   @SneakyThrows
   public void setBucketPolicy(String bucketName, String policy) {
      minioClient.setBucketPolicy(bucketName, policy);
   }

}

4. 最新版本工具包

在當前最新的jar中(8.3.0),minio 工具類的使用方法跟7.0.2版本還是有很大出入的,在這里記錄一下。

4.1 MinioConfig

import io.minio.BucketExistsArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Objects;

/**
 * minio 配置類
 *
 * @author ludangxin
 * @date 2021/8/17
 */
@Slf4j
@Configuration
@EnableConfigurationProperties(MinioProperties.class)
public class MinioConfig {

   @Bean
   @SneakyThrows
   public MinioClient minioClient(MinioProperties minioProperties) {
      MinioClient minioClient = MinioClient.builder()
         .endpoint(minioProperties.getEndpoint(), minioProperties.getPort(), false)
         .credentials(minioProperties.getAccessKey(), minioProperties.getSecretKey())
         .build();
      String defaultBucketName = minioProperties.getDefaultBucketName();

      if(Objects.nonNull(defaultBucketName)) {
         BucketExistsArgs bucketExistsArgs = BucketExistsArgs.builder()
            .bucket(defaultBucketName)
            .build();
         // 創建默認的bucket
         if(!minioClient.bucketExists(bucketExistsArgs)) {
            MakeBucketArgs makeBucketArgs = MakeBucketArgs.builder()
               .bucket(defaultBucketName)
               .build();
            minioClient.makeBucket(makeBucketArgs);
            log.info("create default bucket \"{}\" success", defaultBucketName);
         }
      }

      return minioClient;
   }
}

4.2 MinioUtils

import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * minio 工具類
 *
 * @author ludangxin
 * @date 2021/8/17
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class MinioUtils {
   @Value("${minio.default-bucket-name}")
   private String defaultBucketName;

   private final MinioClient minioClient;

   /**
    * 獲取全部bucket
    *
    * @return all bucket
    */
   @SneakyThrows
   public List<Bucket> getAllBuckets() {
      return minioClient.listBuckets();
   }

   /**
    * 判斷 bucket是否存在
    *
    * @param bucketName 桶名稱
    * @return true 存在
    */
   @SneakyThrows
   public boolean bucketExists(String bucketName){
      BucketExistsArgs bucketExistsArgs = BucketExistsArgs.builder()
            .bucket(bucketName)
            .build();
      return minioClient.bucketExists(bucketExistsArgs);
   }

   /**
    * 創建 bucket
    *
    * @param bucketName 桶名稱
    */
   @SneakyThrows
   public void createBucket(String bucketName){
      boolean isExist = this.bucketExists(bucketName);
      if(!isExist) {
         MakeBucketArgs makeBucketArgs = MakeBucketArgs.builder()
               .bucket(bucketName)
               .build();
         minioClient.makeBucket(makeBucketArgs);
      }
   }

   /**
    * 文件上傳
    *
    * @param bucketName 桶名稱
    * @param fileName 上傳后的文件名稱
    * @param fileAbsolutePath 文件的絕對路徑
    * @return 文件url
    */
   @SneakyThrows
   public ObjectWriteResponse upload(String bucketName, String fileName, String fileAbsolutePath){
      UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder()
            .bucket(bucketName)
            .filename(fileAbsolutePath)
            .object(fileName)
            .build();
      return minioClient.uploadObject(uploadObjectArgs);
   }

   /**
    * 文件上傳
    *
    * @param fileName 上傳后的文件名稱
    * @param stream 文件輸入流
    * @return 文件url
    */
   @SneakyThrows
   public String upload(String fileName, InputStream stream){
      this.upload(defaultBucketName, fileName, stream);
      return getFileUrl(defaultBucketName, fileName);
   }

   /**
    * 文件上傳
    *
    * @param bucketName 桶名稱
    * @param fileName 上傳后的文件名稱
    * @param stream 文件輸入流
    * @return 文件url
    */
   @SneakyThrows
   public ObjectWriteResponse upload(String bucketName, String fileName, InputStream stream){
      try {
         PutObjectArgs objectArgs = PutObjectArgs.builder()
               .bucket(bucketName)
               .object(fileName)
               .stream(stream, stream.available(), -1)
               .build();
         return minioClient.putObject(objectArgs);
      }
      catch(Exception e) {
         log.error(e.getMessage());
      }
      finally {
         try {
            if(Objects.nonNull(stream)) {
               stream.close();
            }
         } catch(IOException e) {
            log.error(e.getMessage());
         }
      }
      return null;
   }

   /**
    * 文件上傳
    *
    * @param file 文件
    * @return 文件url
    */
   public ObjectWriteResponse upload(MultipartFile file) {
      return this.upload(defaultBucketName, file);
   }

   /**
    * 文件上傳
    *
    * @param bucketName 桶名稱
    * @param file 文件
    * @return 文件url
    */
   public ObjectWriteResponse upload(String bucketName, MultipartFile file) {
      InputStream is = null;
      try {
         is = file.getInputStream();
         final String fileName = file.getOriginalFilename();
         String contentType = file.getContentType();
         PutObjectArgs objectArgs = PutObjectArgs.builder()
            .bucket(bucketName)
            .object(fileName)
            .stream(is, is.available(), -1)
            .contentType(contentType)
            .build();
         return minioClient.putObject(objectArgs);
      }
      catch(Exception e) {
         log.error(e.getMessage());
      }
      finally {
         try {
            if(Objects.nonNull(is)) {
               is.close();
            }
         } catch(IOException e) {
            log.error(e.getMessage());
         }
      }
      return null;
   }

   /**
    * 附件下載
    *
    * @param fileName 附件名稱
    */
   @SneakyThrows
   public void download(String fileName, HttpServletResponse response) {
      this.download(defaultBucketName, fileName, response);
   }

   /**
    * 附件下載
    *
    * @param bucketName 桶名稱
    * @param fileName 附件名稱
    */
   @SneakyThrows
   public void download(String bucketName, String fileName, HttpServletResponse response) {
      GetObjectArgs build = GetObjectArgs.builder().bucket(bucketName).object(fileName).build();
      OutputStream out = null;
      try(GetObjectResponse object = minioClient.getObject(build)) {
         int len = 0;
         byte[] buffer = new byte[1024];
         out = response.getOutputStream();
         response.reset();
         String fileName1 = new String(fileName.getBytes(), StandardCharsets.ISO_8859_1);
         response.addHeader("Content-Disposition", " attachment;filename=" + fileName1);
         response.setContentType("application/octet-stream");

         while((len = object.read(buffer)) > 0) {
            out.write(buffer, 0, len);
         }
      } catch(Exception e) {
         log.error(e.getMessage());
      } finally {
         if(out != null) {
            try {
               out.close();
            } catch(IOException e) {
               log.error(e.getMessage());
            }
         }
      }
   }

   /**
    * 附件下載
    *
    * @param fileName 附件名稱
    */
   @SneakyThrows
   public void download(String bucketName, String fileName, String fileAbsolutePath) {
      DownloadObjectArgs downloadObjectArgs = DownloadObjectArgs.builder()
         .bucket(bucketName)
         .object(fileName)
         .filename(fileAbsolutePath)
         .build();
      minioClient.downloadObject(downloadObjectArgs);
   }

   /**
    * 刪除文件
    *
    * @param fileName 文件名稱
    */
   @SneakyThrows
   public void delFile(String fileName){
      this.delFile(defaultBucketName, fileName);
   }

   /**
    * 刪除文件
    *
    * @param bucketName 桶名稱
    * @param fileName 文件名稱
    */
   @SneakyThrows
   public void delFile(String bucketName, String fileName){
      RemoveObjectArgs removeObjectsArgs = RemoveObjectArgs.builder()
            .bucket(bucketName)
            .object(fileName)
            .build();
      minioClient.removeObject(removeObjectsArgs);
   }

   /**
    * 獲取minio文件的下載地址
    *
    * @param fileName 文件名
    */
   @SneakyThrows
   public String getFileUrl(String fileName) {
      return this.getFileUrl(defaultBucketName, fileName);
   }

   /**
    * 獲取minio文件的下載地址
    *
    * @param bucketName 桶名稱
    * @param fileName 文件名
    */
   @SneakyThrows
   public String getFileUrl(String bucketName, String fileName) {
      GetPresignedObjectUrlArgs objectUrlArgs = GetPresignedObjectUrlArgs.builder()
            .method(Method.GET)
            .bucket(bucketName)
            .object(fileName)
            .build();
      return minioClient.getPresignedObjectUrl(objectUrlArgs);
   }

   /**
    * 獲取minio文件的下載地址
    *
    * @param fileName 文件名
    */
   @SneakyThrows
   public String getFileUrl(String fileName, Integer duration, TimeUnit unit) {
      return this.getFileUrl(defaultBucketName, fileName, duration, unit);
   }

   /**
    * 獲取minio文件的下載地址
    *
    * @param bucketName 桶名稱
    * @param fileName 文件名
    */
   @SneakyThrows
   public String getFileUrl(String bucketName, String fileName, Integer duration, TimeUnit unit) {
      GetPresignedObjectUrlArgs objectUrlArgs = GetPresignedObjectUrlArgs.builder()
            .method(Method.GET)
            .bucket(bucketName)
            .object(fileName)
            .expiry(duration, unit)
            .build();
      return minioClient.getPresignedObjectUrl(objectUrlArgs);
   }

   /**
    * 設置桶策略
    *
    * @param bucketName 桶名稱
    * @param policy 策略
    */
   @SneakyThrows
   public void setBucketPolicy(String bucketName, String policy) {
      SetBucketPolicyArgs bucketPolicyArgs = SetBucketPolicyArgs.builder()
            .bucket(bucketName)
            .config(policy)
            .build();
      minioClient.setBucketPolicy(bucketPolicyArgs);
   }

}

4.3 測試類

package com.ldx.minio.controller;

import com.ldx.minio.util.MinioUtils;
import io.minio.ObjectWriteResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;


/**
 * minio 控制器
 *
 * @author ludangxin
 * @date 2021/8/18
 */
@RestController
@RequestMapping("minio")
@RequiredArgsConstructor
public class MinioController {

   private final MinioUtils minioUtils;

   @GetMapping
   public String getUrl(String fileName) {
      return minioUtils.getFileUrl(fileName);
   }

   @PostMapping
   public ObjectWriteResponse upload(MultipartFile file) {
      return minioUtils.upload(file);
   }

   @GetMapping("download/{fileName}")
   public void download(@PathVariable String fileName, HttpServletResponse response) {
      minioUtils.download(fileName, response);
   }

   @GetMapping("download/{bucketName}/{fileName}")
   public void download(@PathVariable String bucketName, @PathVariable String fileName) {
      minioUtils.download(bucketName, fileName, "/Users/ludangxin/temp/" + fileName);
   }

   @DeleteMapping
   public void del(String fileName) {
      minioUtils.delFile(fileName);
   }

}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM