当前位置: 首页 > article >正文

MinIO的预签名直传机制

我们传统使用MinIo做OSS对象存储的应用方式往往都是在后端配置与MinIO的连接和文件上传下载的相关接口,然后我们在前端调用这些接口完成文件的上传下载机制,但是,当并发量过大,频繁访问会对后端的并发往往会对服务器造成极大的压力,大文件传输场景下,服务器被迫承担数据中转的角色,既消耗大量带宽资源,又形成单点性能瓶颈。这时,我们引入了MinIO的一种预签名机制。

预签名机制:在后端对文件的上传和下载操作生成一个URL,前端针对不同的文件操作形式请求会获取到对应的URL,这个URL可以理解为一个临时的通行证,有了这个URL后,前端可以直接向MinIO的服务端发上传和下载的相应请求,与MinIO直连操作,大大减缓了对后端服务器的压力

1.后端配置

1.1 引入Maven依赖并配置MinIO

<!--minio-->
<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
</dependency>
/*
 * MinIO配置类
 * @Author GuihaoLv
 */
@Configuration
@EnableConfigurationProperties(MinIoProperties.class)
public class MinIoConfiguration {
    @Autowired
    private MinIoProperties properties;

    @Bean
    public MinioClient minioClient() {
        return MinioClient.builder()
                .endpoint(properties.getEndpoint())
                .credentials(properties.getAccessKey(), properties.getSecretKey())
                .build();
    }

}

1.2 生成预签名接口封装:

/**要改成使用预签名URL,让前端直接与MinIO交互,减轻服务器负担。
 * 生成上传预签名URL(PUT)
 * @param fileName
 * @return
 */
@GetMapping("/presigned-upload-url")
@ApiOperation("获取上传预签名URL")
public Result<String> generateUploadUrl(@RequestParam("fileName") String fileName) {
    System.out.println("测试"+fileName);
    String url = commonFileService.generatePresignedUploadUrl(fileName);
    System.out.println("结构"+url);
    return Result.success(url);
}

/**要改成使用预签名URL,让前端直接与MinIO交互,减轻服务器负担。
 * 生成下载预签名URL(GET)
 * @param fileName
 * @return
 */
@GetMapping("/presigned-download-url")
@ApiOperation("获取下载预签名URL")
public Result<String> generateDownloadUrl(@RequestParam("fileName") String fileName) {
    String url = commonFileService.generatePresignedDownloadUrl(fileName);
    return Result.success(url);
}
/**
 生成上传预签名URL(PUT)
 * @param fileName
 * @return
 */
public String generatePresignedUploadUrl(String fileName) {
    try {
        // 安全处理文件名(防止路径遍历)
        String safeFileName = sanitizeFileName(fileName);
        // 生成预签名URL(PUT方法)
        return client.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                        .method(Method.PUT)
                        .bucket(properties.getBucketName())
                        .object(safeFileName)
                        .expiry(15, TimeUnit.MINUTES) // 15分钟有效
                        .build()
        );
    } catch (Exception e) {
        throw new RuntimeException("生成预签名URL失败", e);
    }
}

 /**
 * 生成下载预签名URL(GET)
 * @param fileName
 * @return
 */
public String generatePresignedDownloadUrl(String fileName) {
    try {
        String safeFileName = sanitizeFileName(fileName);
        return client.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                        .method(Method.GET)
                        .bucket(properties.getBucketName())
                        .object(safeFileName)
                        .expiry(1, TimeUnit.HOURS) // 1小时有效
                        .build()
        );
    } catch (Exception e) {
        throw new RuntimeException("生成预签名URL失败", e);
    }
}

// 文件名安全处理
private String sanitizeFileName(String fileName) {
    // 过滤非法字符,防止路径遍历
    return fileName.replaceAll("[^a-zA-Z0-9-_.]", "");
}

1.3 前端封装获取预签名和直连MinIO做上传下载的请求

// 获取上传预签名URL
export const getPresignedUploadUrl = (fileName) => {
  return httpInstance({
    url: '/web/commonFile/presigned-upload-url',
    method: 'GET',
    params: { fileName },
  });
};

// 获取下载预签名URL
export const getPresignedDownloadUrl = (fileName) => {
  return httpInstance({
    url: '/web/commonFile/presigned-download-url',
    method: 'GET',
    params: { fileName },
  });
};


// 单个文件直传MinIO,上传文件
export const uploadViaPresignedUrl = async (file: File) => {
  try {
    // 步骤1: 获取未编码的原始文件名(需与后端生成的签名匹配)
    const rawFileName = file.name;

    // 步骤2: 调用后端接口获取预签名URL(必须传递原始文件名)
    const res=await getPresignedUploadUrl(rawFileName);
    const presignedUrl=res.data;

    // 调试输出:验证URL格式
    console.log('[DEBUG] 预签名URL:', presignedUrl); // 应输出类似 http://47.99.49.193:9000/...

    // 步骤3: 直接向MinIO发送PUT请求(绕过代理)
    const response = await axios.put(presignedUrl, file, {
      // 关键配置:禁用代理和默认请求头
      baseURL: '', // [!code ++] 清除默认baseURL
      headers: {
        'Content-Type': 'application/octet-stream' // MinIO通用类型
      }
    });

    return response.data;
  } catch (error) {
    throw new Error(`上传失败: ${(error).response?.data || error.message}`);
  }
};



// 使用预签名URL直连MinIO下载文件
export const downloadViaPresignedUrl = async (fileName) => {
  try {
    // 1. 获取预签名URL:调用后端接口生成临时有效的下载URL
    const { data: { data: presignedUrl } } = await getPresignedDownloadUrl(fileName);

    // 2. 创建隐藏链接触发下载
    const link = document.createElement('a');
    link.href = presignedUrl;       // 设置URL
    link.download = fileName;       // 设置下载文件名,需与 MinIO 存储的文件名一致。
    document.body.appendChild(link); // 将链接添加到DOM
    link.click();                    // 模拟点击触发下载
    document.body.removeChild(link); // 移除临时链接
    return true;                     // 表示下载已触发
  } catch (error) {
    throw new Error('下载失败: ' + error.message); // 统一错误处理
  }
};

1.4:写一个前端页面测试前端直连MinIO的功能实现

<script setup lang="ts">
import { ref } from 'vue';
import {
  uploadViaPresignedUrl,
  downloadViaPresignedUrl
} from '@/api/file';

// 定义响应式变量
const selectedFile = ref<File | null>(null); // 存储用户选择的文件
const downloadFileName = ref<string>('');    // 下载时输入的文件名
const uploadStatus = ref<string>('');        // 上传状态提示
const downloadStatus = ref<string>('');      // 下载状态提示

// 处理文件选择事件
const handleFileChange = (event: Event) => {
  const target = event.target as HTMLInputElement;
  if (target.files && target.files.length > 0) {
    selectedFile.value = target.files[0];
    uploadStatus.value = ''; // 重置上传状态
  }
};

// 上传文件到MinIO
const uploadFile = async () => {
    uploadStatus.value = '上传中...';
    await uploadViaPresignedUrl(selectedFile.value);
    uploadStatus.value = '上传成功!';
    selectedFile.value = null; // 清空文件选择
}

// 下载文件从MinIO
const downloadFile = async () => {
    downloadStatus.value = '正在触发下载...';
    const success = await downloadViaPresignedUrl(downloadFileName.value);
    if (success) {
      downloadStatus.value = '下载已触发!';
    }

};
</script>

<template>
  <div class="container">
    <!-- 上传文件部分 -->
    <h2>测试MinIO文件上传</h2>
    <input type="file" @change="handleFileChange" />
    <button @click="uploadFile" :disabled="!selectedFile">上传</button>
    <p>{{ uploadStatus }}</p>

    <!-- 下载文件部分 -->
    <h2>测试MinIO文件下载</h2>
    <input
      v-model="downloadFileName"
      type="text"
      placeholder="请输入文件名(如 test.jpg)"
    />
    <button @click="downloadFile">下载</button>
    <p>{{ downloadStatus }}</p>
  </div>
</template>

上传测试结果:
 

下载测试:


http://www.kler.cn/a/583364.html

相关文章:

  • c++比较与对比动态内存分配和回收运算符new,new[],delete,delete[]。
  • 爬虫案例十四js逆向中国五矿
  • 【工具使用】IDEA 社区版如何创建 Spring Boot 项目(详细教程)
  • 深度学习视觉2D检测算法综述
  • 游戏辅助技术培训班课程学习【B002-中级班】
  • DeepSeek-R1 论文阅读总结
  • ubuntu 解决 DNS 代理设置错误,导致不能上网的 DoH、DoT问题
  • 鸿基智启:东土科技为具身智能时代构建确定性底座
  • 水雷探测用水下航行器侧扫声纳成像数据之论文阅读
  • Deepin通过二进制方式升级部署高版本 Docker
  • c语言 结构体对齐
  • 2020年蓝桥杯第十一届CC++大学B组(第一次)真题及代码
  • WPF 与 GMap.NET 结合实现雷达目标动态显示与地图绘制
  • JVM常用概念之常量
  • 【MySQL基础-3.1】MySQL DDL 语句详解:数据库操作篇
  • sql语句分页的关键字是?
  • 什么是 React 的合成事件?
  • paimon---同步mysql数据到paimon表中
  • uv python包管理工具
  • [极客大挑战 2019]FinalSQL【SQL布尔盲注】