欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring Boot 整合 Minio 實(shí)現(xiàn)高效文件存儲(chǔ)解決方案(本地和線上)

 更新時(shí)間:2025年08月23日 15:18:54   作者:FC_nian  
Minio 是一個(gè)高性能的分布式對(duì)象存儲(chǔ)系統(tǒng),專為云原生應(yīng)用而設(shè)計(jì),本文將詳細(xì)介紹如何在Spring Boot項(xiàng)目中集成Minio實(shí)現(xiàn)高效文件存儲(chǔ),實(shí)現(xiàn)文件的上傳、下載、刪除等核心功能,感興趣的朋友一起看看吧

前言

  • Minio 是一個(gè)高性能的分布式對(duì)象存儲(chǔ)系統(tǒng),專為云原生應(yīng)用而設(shè)計(jì)
  • 作為 Amazon S3 的兼容替代品,它提供了簡單易用的 API,支持海量非結(jié)構(gòu)化數(shù)據(jù)存儲(chǔ)
  • 在微服務(wù)架構(gòu)中,文件存儲(chǔ)是常見需求,而 Minio 以其輕量級(jí)、高可用和易部署的特點(diǎn)成為理想選擇

一、配置

1.配置文件:application.yml

vehicle:
  minio:
    url: http://localhost:9000 # 連接地址,如果是線上的將:localhost->ip
    username: minio # 登錄用戶名
    password: 12345678 # 登錄密碼
    bucketName: vehicle # 存儲(chǔ)文件的桶的名字
  • url:Minio 服務(wù)器地址,線上環(huán)境替換為實(shí)際 IP 或域
  • username/password:Minio 控制臺(tái)登錄憑證
  • bucketName:文件存儲(chǔ)桶名稱,類似文件夾概念
  • HTTPS 注意:若配置域名訪問,URL 需寫為 https://your.domain.name:9090

2.配置類:MinioProperties

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@Data
@ConfigurationProperties(prefix = "vehicle.minio")
public class MinioProperties {
    private String url;
    private String username;
    private String password;
    private String bucketName;
}
  • @ConfigurationProperties:將配置文件中的屬性綁定到類字段
  • @Component:使該類成為 Spring 管理的 Bean
  • 提供 Minio 連接所需的所有配置參數(shù)

3.工具類:MinioUtil

import cn.hutool.core.lang.UUID;
import com.fc.properties.MinioProperties;
import io.minio.*;
import io.minio.errors.*;
import lombok.RequiredArgsConstructor;
import org.apache.commons.compress.utils.IOUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import io.minio.http.Method;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.TimeUnit;
/**
 * 文件操作工具類
 */
@RequiredArgsConstructor
@Component
public class MinioUtil {
    private final MinioProperties minioProperties;//配置類
    private MinioClient minioClient;//連接客戶端
    private String bucketName;//桶的名字
    // 初始化 Minio 客戶端
    @PostConstruct
    public void init() {
        try {
            //創(chuàng)建客戶端
            minioClient = MinioClient.builder()
                    .endpoint(minioProperties.getUrl())
                    .credentials(minioProperties.getUsername(), minioProperties.getPassword())
                    .build();
            bucketName = minioProperties.getBucketName();
            // 檢查桶是否存在,不存在則創(chuàng)建
            boolean bucketExists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
            if (!bucketExists) {
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
            }
        } catch (Exception e) {
            throw new RuntimeException("Minio 初始化失敗", e);
        }
    }
    /*
     * 上傳文件
     */
    public String uploadFile(MultipartFile file,String extension) {
        if (file == null || file.isEmpty()) {
            throw new RuntimeException("上傳文件不能為空");
        }
        try {
            // 生成唯一文件名
            String uniqueFilename = generateUniqueFilename(extension);
            // 上傳文件
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(uniqueFilename)
                    .stream(file.getInputStream(), file.getSize(), -1)
                    .contentType(file.getContentType())
                    .build());
            return "/" + bucketName + "/" + uniqueFilename;
        } catch (Exception e) {
            throw new RuntimeException("文件上傳失敗", e);
        }
    }
    /**
     * 上傳已處理的圖片字節(jié)數(shù)組到 MinIO
     *
     * @param imageData 處理后的圖片字節(jié)數(shù)組
     * @param extension 文件擴(kuò)展名(如 ".jpg", ".png")
     * @param contentType 文件 MIME 類型(如 "image/jpeg", "image/png")
     * @return MinIO 中的文件路徑(格式:/bucketName/yyyy-MM-dd/uuid.extension)
     */
    public String uploadFileByte(byte[] imageData, String extension, String contentType) {
        if (imageData == null || imageData.length == 0) {
            throw new RuntimeException("上傳的圖片數(shù)據(jù)不能為空");
        }
        if (extension == null || extension.isEmpty()) {
            throw new IllegalArgumentException("文件擴(kuò)展名不能為空");
        }
        if (contentType == null || contentType.isEmpty()) {
            throw new IllegalArgumentException("文件 MIME 類型不能為空");
        }
        try {
            // 生成唯一文件名
            String uniqueFilename = generateUniqueFilename(extension);
            // 上傳到 MinIO
            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(bucketName)
                            .object(uniqueFilename)
                            .stream(new ByteArrayInputStream(imageData), imageData.length, -1)
                            .contentType(contentType)
                            .build()
            );
            return "/" + bucketName + "/" + uniqueFilename;
        } catch (Exception e) {
            throw new RuntimeException("處理后的圖片上傳失敗", e);
        }
    }
    /**
     * 上傳本地生成的 Excel 臨時(shí)文件到 MinIO
     * @param localFile  本地臨時(shí)文件路徑
     * @param extension 擴(kuò)展名
     * @return MinIO 存儲(chǔ)路徑,格式:/bucketName/yyyy-MM-dd/targetName
     */
    public String uploadLocalExcel(Path localFile, String extension) {
        if (localFile == null || !Files.exists(localFile)) {
            throw new RuntimeException("本地文件不存在");
        }
        try (InputStream in = Files.newInputStream(localFile)) {
            String objectKey = generateUniqueFilename(extension); // 保留日期目錄
            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectKey)
                            .stream(in, Files.size(localFile), -1)
                            .contentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
                            .build());
            return "/" + bucketName + "/" + objectKey;
        } catch (Exception e) {
            throw new RuntimeException("Excel 上傳失敗", e);
        }
    }
    /*
     * 根據(jù)URL下載文件
     */
    public void downloadFile(HttpServletResponse response, String fileUrl) {
        if (fileUrl == null || !fileUrl.contains(bucketName + "/")) {
            throw new IllegalArgumentException("無效的文件URL");
        }
        try {
            // 從URL中提取對(duì)象路徑和文件名
            String objectUrl = fileUrl.split(bucketName + "/")[1];
            String fileName = objectUrl.substring(objectUrl.lastIndexOf("/") + 1);
            // 設(shè)置響應(yīng)頭
            response.setContentType("application/octet-stream");
            String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replaceAll("\\+", "%20");
            response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"");
            // 下載文件
            try (InputStream inputStream = minioClient.getObject(GetObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectUrl)
                    .build());
                 OutputStream outputStream = response.getOutputStream()) {
                // 用IOUtils.copy高效拷貝(內(nèi)部緩沖區(qū)默認(rèn)8KB)
                IOUtils.copy(inputStream, outputStream);
            }
        } catch (Exception e) {
            throw new RuntimeException("文件下載失敗", e);
        }
    }
    /**
     * 根據(jù) MinIO 路徑生成帶簽名的直鏈
     * @param objectUrl 已存在的 MinIO 路徑(/bucketName/...)
     * @param minutes   鏈接有效期(分鐘)
     * @return 可直接訪問的 HTTPS 下載地址
     */
    public String parseGetUrl(String objectUrl, int minutes) {
        if (objectUrl == null || !objectUrl.startsWith("/" + bucketName + "/")) {
            throw new IllegalArgumentException("非法的 objectUrl");
        }
        String objectKey = objectUrl.substring(("/" + bucketName + "/").length());
        try {
            return minioClient.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder()
                            .method(Method.GET)
                            .bucket(bucketName)
                            .object(objectKey)
                            .expiry(minutes, TimeUnit.MINUTES)
                            .build());
        } catch (Exception e) {
            throw new RuntimeException("生成直鏈?zhǔn)?, e);
        }
    }
    /*
     * 根據(jù)URL刪除文件
     */
    public void deleteFile(String fileUrl) {
        try {
            // 從URL中提取對(duì)象路徑
            String objectUrl = fileUrl.split(bucketName + "/")[1];
            minioClient.removeObject(RemoveObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectUrl)
                    .build());
        } catch (Exception e) {
            throw new RuntimeException("文件刪除失敗", e);
        }
    }
    /*
     * 檢查文件是否存在
     */
    public boolean fileExists(String fileUrl) {
        if (fileUrl == null || !fileUrl.contains(bucketName + "/")) {
            return false;
        }
        try {
            String objectUrl = fileUrl.split(bucketName + "/")[1];
            minioClient.statObject(StatObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectUrl)
                    .build());
            return true;
        } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException |
                 InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException |
                 XmlParserException e) {
            if (e instanceof ErrorResponseException && ((ErrorResponseException) e).errorResponse().code().equals("NoSuchKey")) {
                return false;
            }
            throw new RuntimeException("檢查文件存在失敗", e);
        }
    }
    /**
     * 生成唯一文件名(帶日期路徑 + UUID)
     */
    private String generateUniqueFilename(String extension) {
        String dateFormat = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        String uuid = UUID.randomUUID().toString().replace("-", ""); // 去掉 UUID 中的 "-"
        return dateFormat + "/" + uuid + extension;
    }
}

3.1 初始化方法

  • 使用 @PostConstruct 在 Bean 初始化后自動(dòng)執(zhí)行
  • 創(chuàng)建 MinioClient 客戶端實(shí)例
  • 檢查并創(chuàng)建存儲(chǔ)桶(若不存在)

3.2 核心功能

方法名功能描述參數(shù)說明返回值
uploadFile()上傳MultipartFile文件文件對(duì)象,擴(kuò)展名文件路徑
uploadFileByte()上傳字節(jié)數(shù)組字節(jié)數(shù)據(jù),擴(kuò)展名,MIME類型文件路徑
uploadLocalExcel()上傳本地Excel文件文件路徑,擴(kuò)展名文件路徑
downloadFile()下載文件到響應(yīng)流HTTP響應(yīng)對(duì)象,文件URL
parseGetUrl()生成帶簽名直鏈文件路徑,有效期(分鐘)直鏈URL
deleteFile()刪除文件文件URL
fileExists()檢查文件是否存在文件URL布爾值

3.3 關(guān)鍵技術(shù)點(diǎn)

  • 唯一文件名生成:日期目錄/UUID.擴(kuò)展名 格式避免重名
  • 大文件流式傳輸:避免內(nèi)存溢出
  • 響應(yīng)頭編碼處理:解決中文文件名亂碼問題
  • 異常統(tǒng)一處理:Minio 異常轉(zhuǎn)換為運(yùn)行時(shí)異常
  • 預(yù)簽名URL:生成臨時(shí)訪問鏈接

二、使用示例

1.控制器類:FileController

import com.fc.result.Result;
import com.fc.service.FileService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Api(tags = "文件")
@RestController
@RequestMapping("/file")
@RequiredArgsConstructor
public class FileController {
    private final FileService fileService;
    @ApiOperation("圖片上傳")
    @PostMapping("/image")
    public Result<String> imageUpload(MultipartFile file) throws IOException {
        String url = fileService.imageUpload(file);
        return Result.success(url);
    }
    @ApiOperation("圖片下載")
    @GetMapping("/image")
    public void imageDownLoad(HttpServletResponse response, String url) throws IOException {
        fileService.imageDownload(response, url);
    }
    @ApiOperation("圖片刪除")
    @DeleteMapping("/image")
    public Result<Void> imageDelete(String url) {
        fileService.imageDelete(url);
        return Result.success();
    }
}

2.服務(wù)類

FileService

import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public interface FileService {
    String imageUpload(MultipartFile file) throws IOException;
    void imageDownload(HttpServletResponse response, String url) throws IOException;
    void imageDelete(String url);
}

FileServiceImpl

import com.fc.exception.FileException;
import com.fc.service.FileService;
import com.fc.utils.ImageUtil;
import com.fc.utils.MinioUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Service
@RequiredArgsConstructor
public class FileServiceImpl implements FileService {
    private final MinioUtil minioUtil;
    @Override
    public String imageUpload(MultipartFile file) throws IOException {
        byte[] bytes = ImageUtil.compressImage(file, "JPEG");
        return minioUtil.uploadFileByte(bytes, ".jpeg", "image/jpeg");
    }
    @Override
    public void imageDownload(HttpServletResponse response, String url) throws IOException {
        minioUtil.downloadFile(response, url);
    }
    @Override
    public void imageDelete(String url) {
        if (!minioUtil.fileExists(url)) {
            throw new FileException("文件不存在");
        }
        minioUtil.deleteFile(url);
    }
}

3.效果展示

利用Apifox測試下三個(gè)接口

圖片上傳

圖片下載

刪除圖片

總結(jié)

本文通過 “配置 - 工具 - 業(yè)務(wù)” 三層架構(gòu),實(shí)現(xiàn)了 Spring Boot 與 MinIO 的集成,核心優(yōu)勢如下:

  • 易用性:通過配置綁定和工具類封裝,簡化 MinIO 操作,開發(fā)者無需關(guān)注底層 API 細(xì)節(jié)。
  • 靈活性:支持多種文件類型(表單文件、字節(jié)流、本地文件),滿足不同場景需求(如圖片壓縮、Excel 生成)。
  • 可擴(kuò)展性:可基于此框架擴(kuò)展功能,如添加文件權(quán)限控制(通過 MinIO 的 Policy)、文件分片上傳(大文件處理)、定期清理過期文件等。

MinIO 作為輕量級(jí)對(duì)象存儲(chǔ)方案,非常適合中小項(xiàng)目替代本地存儲(chǔ)或云廠商 OSS(降低成本)。實(shí)際應(yīng)用中需注意:生產(chǎn)環(huán)境需配置 MinIO 集群確保高可用;敏感文件需通過預(yù)簽名 URL 控制訪問權(quán)限;定期備份桶數(shù)據(jù)以防丟失。通過本文的方案,開發(fā)者可快速搭建穩(wěn)定、可擴(kuò)展的文件存儲(chǔ)服務(wù),為應(yīng)用提供可靠的非結(jié)構(gòu)化數(shù)據(jù)管理能力。

到此這篇關(guān)于Spring Boot 整合 Minio 實(shí)現(xiàn)高效文件存儲(chǔ)解決方案(本地和線上)的文章就介紹到這了,更多相關(guān)Spring Boot Minio 文件存儲(chǔ)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論