MinIO的预签名直传机制

news2025/3/13 21:09:00

我们传统使用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.coloradmin.cn/o/2314493.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Qt开源控件库(qt-material-widgets)的编译及使用

项目简介 qt-material-widgets是一个基于 Qt 小部件的 Material Design 规范实现。 项目地址 项目地址&#xff1a;qt-material-widgets 本地构建环境 Win11 家庭中文版 VS2019 Qt5.15.2 (MSVC2019) 本地构建流程 克隆后的目录结构如图&#xff1a; 直接使用Qt Crea…

用python批量生成文件夹

问题描述 当批量生成文件夹时&#xff0c;手动右键创建文件夹是一个繁琐的过程&#xff0c;尤其是文件夹的命名过程。假设从3月10日到3月19日&#xff0c;每天要为某个日常工作创建一个名为2025031x的文件夹&#xff0c;手动创建文件夹并命名费时费力。 百度给出了以下四种方法…

【MySQL】基本操作 —— DDL

目录 DDLDDL 常用操作对数据库的常用操作查看所有数据库创建数据库切换、显示当前数据库删除数据库修改数据库编码 对表的常用操作创建表数据类型数值类型日期和时间类型字符串类型 查看当前数据库所有表查看指定表的创建语句查看指定表结构删除表 对表结构的常用操作给表添加字…

游戏引擎学习第152天

仓库:https://gitee.com/mrxiao_com/2d_game_3 回顾昨天的内容 这个节目展示了我们如何从零开始制作一款完整的游戏。我们不使用任何游戏引擎或库&#xff0c;而是从头开始创建一款游戏&#xff0c;整个开发过程都会呈现给大家。你将能够看到每一行代码的编写&#xff0c;了解…

考研数学非数竞赛复习之Stolz定理求解数列极限

在非数类大学生数学竞赛中&#xff0c;Stolz定理作为一种强大的工具&#xff0c;经常被用来解决和式数列极限的问题&#xff0c;也被誉为离散版的’洛必达’方法&#xff0c;它提供了一种简洁而有效的方法&#xff0c;使得原本复杂繁琐的极限计算过程变得直观明了。本文&#x…

故障诊断——neo4j入门

文章目录 neo4jQuickStartDemo neo4j QuickStart 详情可见博客&#xff1a;https://www.cnblogs.com/nhdlb/p/18703804&#xff0c;使用docker拉取最近的一个版本进行创建 docker run -it -d -p 7474:7474 -p 7687:7687 \ -v /disk5/neo4j_docker/data:/data \ -v /disk5/ne…

【JavaWeb】快速入门——HTMLCSS

文章目录 一、 HTML简介1、HTML概念2、HTML文件结构3、可视化网页结构 二、 HTML标签语法1、标题标签2、段落标签3、超链接4、换行5、无序列表6、路径7、图片8、块1 盒子模型2 布局标签 三、 使用HTML表格展示数据1、定义表格2、合并单元格横向合并纵向合并 四、 使用HTML表单收…

若依框架-给sys_user表添加新字段并获取当前登录用户的该字段值

目录 添加字段 修改SysUser类 修改SysUserMapper.xml 修改user.js 前端获取字段值 添加字段 若依框架的sys_user表是没有age字段的&#xff0c;但由于业务需求&#xff0c;我需要新添加一个age字段&#xff1a; 修改SysUser类 添加age字段后&#xff0c;要在SysUser类 …

前端监测窗口尺寸和元素尺寸变化的方法

前端监测窗口尺寸变化和元素尺寸变化的方法 window.resize 简介 window.resize事件是浏览器提供的一种事件&#xff0c;用于监听窗口大小的改变。这意味着当用户调整浏览器窗口大小时&#xff0c;相关的JavaScript代码将被触发执行。这为开发者提供了一种机制&#xff0c;可…

ubuntu 部署deepseek

更新 apt update 升级 apt upgrade 格式化硬盘 mkfs.ext4 /dev/sdb 安装nginx 查看端口 一、安装Ollama Ollama是一个开源的大型语言模型&#xff08;LLM&#xff09;推理服务器&#xff0c;为用户提供了灵活、安全和高性能的语言模型推理解决方案。 ollama/docs/linux.m…

MySQL库和表的操作详解:从创建库到表的管理全面指南

目录 一、MySQL库的操作详解 〇、登录MySQL 一、数据库的创建与字符集设置 1. 创建数据库的语法 2. 创建数据库示例 查看创建出来的文件: bash下查看MySQL创建的文件 二、字符集与校验规则 1. 查看系统默认设置 2. 查看支持的字符集与校验规则 3. 校验规则对查询的影响…

PyTorch 系列教程:使用CNN实现图像分类

图像分类是计算机视觉领域的一项基本任务&#xff0c;也是深度学习技术的一个常见应用。近年来&#xff0c;卷积神经网络&#xff08;cnn&#xff09;和PyTorch库的结合由于其易用性和鲁棒性已经成为执行图像分类的流行选择。 理解卷积神经网络&#xff08;cnn&#xff09; 卷…

Java 大视界 -- Java 大数据中的数据可视化大屏设计与开发实战(127)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

「Unity3D」UGUI将元素固定在,距离屏幕边缘的某个比例,以及保持元素自身比例

在不同分辨率的屏幕下&#xff0c;UI元素按照自身像素大小&#xff0c;会发生位置与比例的变化&#xff0c;本文仅利用锚点&#xff08;Anchors&#xff09;使用&#xff0c;来实现UI元素&#xff0c;固定在某个比例距离的屏幕边缘。 首先&#xff0c;将元素的锚点设置为中心&…

Deep research深度研究:ChatGPT/ Gemini/ Perplexity/ Grok哪家最强?(实测对比分析)

目前推出深度研究和深度检索的AI大模型有四家&#xff1a; OpenAI和Gemini 的deep research&#xff0c;以及Perplexity 和Grok的deep search&#xff0c;都能生成带参考文献引用的主题报告。 致力于“几分钟之内生成一份完整的主题调研报告&#xff0c;解决人力几小时甚至几天…

关于sqlalchemy的ORM的使用

关于sqlalchemy的ORM的使用 二、创建表三、使用数据表、查询记录三、批量插入数据四、关于with...as...:的使用 二、创建表 使用Mapped来映射字段 from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker,Mapped,mapped_columnBa…

【leetcode hot 100 148】排序序列

解法一&#xff1a;&#xff08;双重循环&#xff09;第一个循环head&#xff0c;逐步将head的node加入有序列表&#xff1b;第二个循环在有序列表中找到合适的位置&#xff0c;插入node。 /*** Definition for singly-linked list.* public class ListNode {* int val;* …

【Linux】在VMWare中安装Ubuntu操作系统(2025最新_Ubuntu 24.04.2)#VMware安装Ubuntu实战分享#

今天田辛老师为大家带来一篇关于在VMWare虚拟机上安装Ubuntu系统的详细教程。无论是学习、开发还是测试&#xff0c;虚拟机都是一个非常实用的工具&#xff0c;它允许我们在同一台物理机上运行多个操作系统。Ubuntu作为一款开源、免费且用户友好的Linux发行版&#xff0c;深受广…

AutoGen学习笔记系列(十三)Advanced - Logging

这篇文章瞄的是AutoGen官方教学文档 Advanced 章节中的 Logging 篇章&#xff0c;介绍了怎样在使用过程中添加日志信息&#xff0c;其实就是使用了python自带的日志库 logging。 官网链接&#xff1a;https://microsoft.github.io/autogen/stable/user-guide/agentchat-user-g…

scrcpy pc机远程 无线 控制android app 查看调试log

背景&#xff1a; 公司的安卓机&#xff0c;是那种大屏幕的连接usb外设的。不好挪动&#xff0c;占地方&#xff0c;不能直接连接pc机上的android stduio来调试。 所以从网上找了一个python adb.exe控制器&#xff0c;可以局域网内远程控制开发的app,并在android stduio上看…