SpringBoot整合minio前後端聯調

风希落發表於2024-04-27

基本配置

初始化專案

新建一個 SpringBoot 專案,整合 lombok mybatis-plus minio hutool-core(可有可無)。
新建一個資料表 attachement,用於儲存檔案上傳後在 minio 中的位置。

drop table if exists attachment;
create table attachment
(
    id               int auto_increment,
    bucket           varchar(32)  not null comment '桶名',
    object           varchar(64)  not null comment 'minio中的檔名',
    origin_file_name varchar(225) not null comment '原始檔名全稱',
    create_time      timestamp(6) default current_timestamp(6) comment '建立時間',
    update_time      timestamp(6) on update current_timestamp(6) comment '更新時間',
    primary key (id)
) comment '附件表';

配置專案

  1. 新增 attachment 相關的 controller、mapper、entity、service

  2. application.yml 中宣告 minio 的配置項,連線資料庫,設定檔案上傳大小

server:
  port: 8080

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/minio?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  # 允許上傳的檔案大小限制
  servlet:
    multipart:
      max-file-size: 1000MB
      max-request-size: 1000MB

# minio 自定義配置
minio:
  endpoint:  http://127.0.0.1:9000
  accessKey: minioadmin
  secretKey: minioadmin
  bucket: public-readonly-file

注意: endpoint 設定的必須是 API 地址,而非 WebUI 地址,也不能是 localhost

  1. 新增 MinIOConfigInfo 讀取 application 中的相關配置
@Data
@Component
@ConfigurationProperties(prefix = "minio")
public class MinIOConfigInfo {

        private String endpoint;
        private String accessKey;
        private String secretKey;
        private String bucket;
}
  1. 新增 minIOConfig 配置類
public class MinIOConfig {
    @Resource
    private MinIOConfigInfo minIOConfigInfo;

    @Bean
    public MinioClient minioClient() {
        //鏈式程式設計,構建MinioClient物件
        return MinioClient.builder()
                .endpoint(minIOConfigInfo.getEndpoint())
                .credentials(minIOConfigInfo.getAccessKey(), minIOConfigInfo.getSecretKey())
                .build();
    }
}

圖片/檔案通用上傳

建議把圖片上傳和檔案上傳分開,因為圖片只關係 url 地址,而檔案需要額外知道檔名,且兩者的業務場景並不完全相同

後端程式碼

Controller

    @Resource
    private AttachmentService attachmentService;

    @PostMapping("file")
    public R<String> uploadFile(MultipartFile file) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        return attachmentService.uploadFile(file);
    }

Service

  1. 獲取檔名和字尾,並生成一個隨機 uuid
  2. uuid+檔案字尾 用於上傳至 minio,防止檔案重複。原檔名用於前端檔案類的展示
  3. 將重新命名的圖片/檔案上傳至 minio,預設訪問是直接下載的,上傳時對該檔案的 Content-Type 進行設定,生成的 url 地址才可以預覽而不是下載
  4. 上傳到 minio 成功後,向資料庫插入該檔案在 minio 的資訊
  5. 生成一個可訪問的 url 地址返回給前端(具體返回值要根據實際場景結合應用)
    public R<String> uploadFile(MultipartFile file) throws IOException, ServerException, InsufficientDataException, ErrorResponseException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        //處理檔案,對檔案進行重新命名並上傳到 minio
        String originalFilename = file.getOriginalFilename(); // 原始檔案全稱
        String suffix = FileUtil.extName(originalFilename); // 檔案字尾
        String uuid = IdUtil.simpleUUID(); // 重新命名檔名儲存到 minio,防止上傳的檔名重複
        String renameFile = uuid + "." + suffix;
        // 上傳至 minio
        LocalDateTime now = LocalDateTime.now();
        // 檔案以年月日的格式分資料夾儲存
        String object = now.getYear() + "/" + now.getMonthValue() + "/" + now.getDayOfMonth() + "/" + renameFile;
        minioClient.putObject(PutObjectArgs.builder()
                .bucket(minIOConfigInfo.getBucket())
                .object(object)
                .contentType(file.getContentType())
                .stream(file.getInputStream(), file.getSize(), -1)
                .build()
        );
        // 插入檔案資料到表中
        Attachment attachment = new Attachment();
        attachment.setOriginFileName(originalFilename)
                .setBucket(minIOConfigInfo.getBucket())
                .setObject(object)
                .setCreateTime(now);
        attachmentMapper.insert(attachment);
        // 生成圖片 url 地址
        // String url = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
        //         .bucket(minIOConfigInfo.getBucket())
        //         .object(path)
        //         .expiry(1, TimeUnit.MILLISECONDS)
        //         .method(Method.GET)
        //         .build());
        // return R.ok(url);

        // 如果設定了公開只讀訪問策略,則可以不經過getPresignedObjectUrl生成簽名,直接訪問即可
        return R.ok(minIOConfigInfo.getEndpoint() + "/" + minIOConfigInfo.getBucket() + "/" + object);
    }

程式碼可最佳化項,封裝/校驗檔案型別,如果 minio 設定了公開訪問,可以將url帶的簽名去掉,如何建立只讀策略的桶,可參考上一篇文章。
什麼是公共只讀策略?檔案可以不經過使用者名稱密碼直接訪問,但沒有其它訪問許可權。如果設定 public,可以不使用使用者名稱密碼,直接使用 SDK 對桶檔案進行增刪改,十分不安全

前端程式碼

antd 元件,前端設定了跨域代理,以 /api 開頭的請求都會代理轉發到 http://localhost:8080 即後端程式碼執行地址,如果後端設定 @CrossOrigin 則無跨域問題

  <Upload name="file" action="/api/upload/image" list-type="picture-card">
    Click to Upload
  </Upload>

前端展示效果

image

minio 後臺檔案結構

image

url 地址預覽(需上傳時設定 Content-Type

image

檔案下載

在有預覽功能的前提下,下載是非必須的,但不同的業務場景可能會需要直接下載,不走預覽

  1. 前端傳給後端檔案 id
  2. 後端根據 id 查詢資料庫中對應的檔案資料
  3. 後端根據 bucket 和 object 去 minio 找對應的檔案
  4. 使用 SDK 下載檔案,返回給前端
    @Override
    public void downloadFile(Integer id, HttpServletResponse response) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        Attachment attachment = attachmentMapper.selectById(id);

        if (attachment == null) {
            // 這裡可以丟擲異常,根據實際業務處理
            System.out.println("檔案不存在");
        } else {
            //瀏覽器直接下載,要設定一下響應頭資訊
            response.setContentType("application/octet-stream");
            response.setCharacterEncoding("utf-8");
            response.setHeader("Content-disposition",
                    "attachment;filename=" + URLEncoder.encode(attachment.getObject(), StandardCharsets.UTF_8));
            // 下載
            GetObjectResponse getObjectResponse = minioClient.getObject(
                    GetObjectArgs.builder()
                            .bucket(attachment.getBucket())
                            .object(attachment.getObject())
                            .build());
            getObjectResponse.transferTo(response.getOutputStream());
        }
    }

相關文章