Spring Boot 集成 MinIO 实现分布式文件存储与管理

在这里插入图片描述

一、MinIO 简介

MinIO 是一个高性能的分布式对象存储服务器,兼容 Amazon S3 API。它具有以下特点:

  • 轻量级且易于部署
  • 高性能(读写速度可达每秒数GB)
  • 支持数据加密和访问控制
  • 提供多种语言的SDK
  • 开源且社区活跃

二、Spring Boot 集成 MinIO

1. 添加依赖

pom.xml 中添加 MinIO Java SDK 依赖:

<dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.5.7</version>
</dependency>

2. 配置 MinIO 连接

application.yml 中配置:

minio:endpoint: http://localhost:9000accessKey: minioadminsecretKey: minioadminbucketName: default-bucketsecure: false

3. 创建配置类

@Configuration
public class MinioConfig {@Value("${minio.endpoint}")private String endpoint;@Value("${minio.accessKey}")private String accessKey;@Value("${minio.secretKey}")private String secretKey;@Beanpublic MinioClient minioClient() {return MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey).build();}
}

三、实现文件服务

@Service
@RequiredArgsConstructor
public class MinioService {private final MinioClient minioClient;@Value("${minio.bucketName}")private String bucketName;/*** 检查存储桶是否存在** @param bucketName 存储桶名称* @return 存储桶是否存在 状态码 true:存在 false:不存在*/public boolean bucketExists(String bucketName) throws Exception {return !minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());}/*** 创建存储桶*/public void makeBucket(String bucketName) throws Exception {if (bucketExists(bucketName)) {minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());}}/*** 列出所有存储桶*/public List<Bucket> listBuckets() throws Exception {return minioClient.listBuckets();}/*** 上传文件** @param file       文件* @param bucketName 存储桶名称* @param rename     是否重命名*/public String uploadFile(MultipartFile file, String bucketName, boolean rename) throws Exception {// 如果未指定bucketName,使用默认的bucketName = getBucketName(bucketName);// 检查存储桶是否存在,不存在则创建ensureBucketExists(bucketName);// 生成唯一文件名String objectName = generateObjectName(file, rename);// 上传文件minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(file.getInputStream(), file.getSize(), -1).contentType(file.getContentType()).build());return objectName;}/*** 下载文件*/public InputStream downloadFile(String objectName, String bucketName) throws Exception {return minioClient.getObject(GetObjectArgs.builder().bucket(getBucketName(bucketName)).object(objectName).build());}/*** 删除文件*/public void removeFile(String objectName, String bucketName) throws Exception {minioClient.removeObject(RemoveObjectArgs.builder().bucket(getBucketName(bucketName)).object(objectName).build());}/*** 获取文件URL(先检查文件是否存在)*/public String getFileUrl(String objectName, String bucketName) throws Exception {try {minioClient.statObject(StatObjectArgs.builder().bucket(getBucketName(bucketName)).object(objectName).build());} catch (ErrorResponseException e) {// 文件不存在时抛出异常throw new FileNotFoundException("File not found: " + objectName);}// 文件存在,生成URLreturn minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().method(Method.GET).bucket(getBucketName(bucketName)).object(objectName).build());}/*** 生成唯一文件名*/private static @Nullable String generateObjectName(MultipartFile file, boolean rename) {String fileName = file.getOriginalFilename();String objectName = fileName;if (rename && fileName != null) {objectName = UUID.randomUUID().toString().replaceAll("-", "")+ fileName.substring(fileName.lastIndexOf("."));}return objectName;}/*** 检查存储桶是否存在,不存在则创建*/private void ensureBucketExists(String bucketName) throws Exception {if (bucketExists(bucketName)) {makeBucket(bucketName);}}/*** 获取存储桶名称*/private String getBucketName(String bucketName) {if (bucketName == null || bucketName.isEmpty()) {bucketName = this.bucketName;}return bucketName;}
}

四、REST API 实现

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/file")
public class MinioController {private final MinioService minioService;@PostMapping("/upload")public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file,@RequestParam(value = "bucketName", required = false) String bucketName) {try {String objectName = minioService.uploadFile(file, bucketName, false);return ResponseEntity.ok(objectName);} catch (Exception e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());}}@GetMapping("/download")public ResponseEntity<byte[]> downloadFile(@RequestParam String objectName,@RequestParam(value = "bucketName", required = false) String bucketName) {try {InputStream stream = minioService.downloadFile(objectName, bucketName);byte[] bytes = stream.readAllBytes();HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);headers.setContentDispositionFormData("attachment", objectName);return new ResponseEntity<>(bytes, headers, HttpStatus.OK);} catch (Exception e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);}}@DeleteMapping("/delete")public ResponseEntity<String> deleteFile(@RequestParam String objectName,@RequestParam(value = "bucketName", required = false) String bucketName) {try {minioService.removeFile(objectName, bucketName);return ResponseEntity.ok("File deleted successfully");} catch (Exception e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());}}@GetMapping("/url")public ResponseEntity<String> getFileUrl(@RequestParam String objectName,@RequestParam(value = "bucketName", required = false) String bucketName) {try {String url = minioService.getFileUrl(objectName, bucketName);return ResponseEntity.ok(url);} catch (FileNotFoundException e) {return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage());} catch (Exception e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());}}
}

五、高级功能实现

1. 分片上传

public String multipartUpload(MultipartFile file, String bucketName) {// 1. 初始化分片上传String uploadId = minioClient.initiateMultipartUpload(...);// 2. 分片上传Map<Integer, String> etags = new HashMap<>();for (int partNumber = 1; partNumber <= totalParts; partNumber++) {PartSource partSource = getPartSource(file, partNumber);String etag = minioClient.uploadPart(...);etags.put(partNumber, etag);}// 3. 完成分片上传minioClient.completeMultipartUpload(...);return objectName;
}

2. 文件预览

    @GetMapping("/preview/{objectName}")public ResponseEntity<Resource> previewFile(@PathVariable String objectName,@RequestParam(value = "bucketName", required = false) String bucketName) throws Exception {String contentType = minioService.getFileContentType(objectName, bucketName);InputStream inputStream = minioService.downloadFile(objectName, bucketName);return ResponseEntity.ok().contentType(MediaType.parseMediaType(contentType)).body(new InputStreamResource(inputStream));}

六、最佳实践

  1. 安全性考虑

    • 为预签名URL设置合理的过期时间
    • 实现细粒度的访问控制
    • 对上传文件进行病毒扫描
  2. 性能优化

    • 使用CDN加速文件访问
    • 对大文件使用分片上传
    • 实现客户端直传(Presigned URL)
  3. 监控与日志

    • 记录所有文件操作
    • 监控存储空间使用情况
    • 设置自动清理策略

七、常见问题解决

  1. 连接超时问题

    @Bean
    public MinioClient minioClient() {return MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey).httpClient(HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(30)).build()).build();
    }
    
  2. 文件存在性检查优化

    public boolean fileExists(String objectName, String bucketName) {try {minioClient.statObject(StatObjectArgs.builder().bucket(getBucketName(bucketName)).object(objectName).build());return true;} catch (ErrorResponseException e) {if (e.errorResponse().code().equals("NoSuchKey")) {return false;}throw new FileStorageException("检查文件存在性失败", e);} catch (Exception e) {throw new FileStorageException("检查文件存在性失败", e);}
    }
    

八、总结

通过本文的介绍,我们实现了:

  1. Spring Boot 与 MinIO 的基本集成
  2. 文件上传、下载、删除等基础功能
  3. 文件预览、分片上传等高级功能
  4. 安全性、性能等方面的最佳实践

MinIO 作为轻量级的对象存储解决方案,非常适合中小型项目使用。结合 Spring Boot 可以快速构建强大的文件存储服务。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/87423.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/87423.shtml
英文地址,请注明出处:http://en.pswp.cn/web/87423.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从小白入门,基于Cursor开发一个前端小程序之Cursor 编程实践与案例分析

Cursor 编程实践与案例分析 Cursor 编程实践与案例分析 1. 什么是 Cursor&#xff1f; Cursor 是一款面向开发者的 AI 编程助手&#xff0c;集成于本地 IDE&#xff0c;支持自然语言与代码的无缝协作。它不仅能自动补全、重构、查找代码&#xff0c;还能理解业务上下文&#…

一、如何用MATLAB画一个三角形 代码

一、如何用MATLAB画一个三角形 代码在MATLAB中绘制三角形可以通过指定三个顶点的坐标并使用 fill 或 patch 函数实现。以下是详细代码示例&#xff1a;方法1&#xff1a;使用 fill 函数&#xff08;简单填充&#xff09;% 定义三角形的三个顶点坐标 (x, y) x [0, 1, 0.5]; % …

Postman自动化测试提取相应body体中的参数

文章目录Postman自动化测试提取相应body体中的参数1. 示例响应 Body 参数2. 提取响应 Body 参数Postman自动化测试提取相应body体中的参数 上一篇的文中介绍了使用postman自动化测试时从响应的header中提取token参数&#xff0c;很多同学私信问如何从响应体body中提取参数。 有…

vue-39(为复杂 Vue 组件编写单元测试)

实际练习:为复杂 Vue 组件编写单元测试 单元测试对于确保复杂 Vue 组件的可靠性和可维护性至关重要。通过隔离和测试代码的各个单元,您可以在开发过程的早期发现并修复错误,从而构建更健壮和可预测的应用程序。本课程重点介绍为复杂 Vue 组件编写单元测试的实用方面,建立在…

c语言中的函数IV

函数的先后关系 直接把函数放在程序上方是可以的 在实际开发中&#xff0c;我们更希望把main函数放在前面 这样子直接把自己定义的函数放在main函数下方&#xff0c;编译会出现warning和error正确的解决方案是&#xff1a;把函数的头放到main函数上方&#xff0c;这样就能正常…

大模型Decoder-Only深入解析

Decoder-Only整体结构 我们以模型Llama-3.1-8B-Instruct为例&#xff0c;打印其结构如下&#xff08;后面会慢慢解析每一部分&#xff0c;莫慌&#xff09;&#xff1a; LlamaForCausalLM((model): LlamaModel((embed_tokens): VocabParallelEmbedding(num_embeddings128256,…

web网页,在线%电商,茶叶,商城,网上商城系统%分析系统demo,于vscode,vue,java,jdk,springboot,mysql数据库

经验心得 这也是帮之前一客户加了几个功能&#xff0c;需要掌握crud&#xff0c;前后端开发&#xff0c;前后端怎么对接&#xff0c;前后端通讯是以那种格式&#xff0c;把这些掌握后咱们就可以进行网站开发了。后端记好一定要分层开发&#xff0c;不要像老早一起所有代码写到一…

MybatisPlus-05.核心功能-条件构造器

一.条件构造器 我们前面使用的MP功能主要是根据id进行操作的&#xff0c;并未涉及到复杂查询。而根据id所进行的增删改查操作在MP中都有直接的封装。但是遇到复杂的查询条件时&#xff0c;如何使用MP进行操作是我们要考虑的问题。因此MP为我们提供了条件构造器。 在BaseMapper…

ES6从入门到精通:常用知识点

变量声明ES6引入了let和const替代var。let用于声明可变的块级作用域变量&#xff0c;const用于声明不可变的常量。块级作用域有效避免了变量提升和污染全局的问题。let name Alice; const PI 3.1415;箭头函数箭头函数简化了函数写法&#xff0c;且自动绑定当前上下文的this值…

51单片机教程(十一)- 单片机定时器

11、单片机定时器 项目目标 通过定时器/计数器实现流水灯控制。知识要点 定时器的结构。TMOD和TCON;定时/计数器工作方式;定时/计数器编程步骤;1、项目分析 前面的流水灯的时间控制通过空循环语句来实现,定时不是很精确。本章通过用定时器来控制流水灯任务可以实现精确的时…

基于opencv的疲劳驾驶监测系统

博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业多年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了多年的毕业设计程序开发&#xff0c;开发过上千套毕业设计程序&#xff0c;没有什么华丽的语言&#xff0…

Vue 2 和 Vue 3 区别

1. 响应式系统原理 Vue 2&#xff1a;利用Object.defineProperty()实现属性拦截。存在局限性&#xff0c;无法自动监测对象属性增减&#xff0c;需用Vue.set/delete&#xff1b;数组变异方法要重写&#xff1b;深层对象递归转换性能差。Vue 3&#xff1a;采用 ES6 Proxy代理对…

mv重命名报错:-bash:syntax error near unexpected token ‘(‘

文章目录 一、报错背景二、解决方法2.1、方法一&#xff1a;文件名加引号2.2、方法二&#xff1a;特殊字符前加\进行转义 一、报错背景 在linux上对一文件执行重命名时报错。原因是该文件名包含空格与括号。 文件名如下&#xff1a; aa &#xff08;1).txt执行命令及报错如下…

AWS 开源 Strands Agents SDK,简化 AI 代理开发流程

最近&#xff0c;亚马逊网络服务&#xff08;AWS&#xff09;宣布推出 Strands Agents(https://github.com/strands-agents/sdk-python)&#xff0c;这一开源软件开发工具包&#xff08;SDK&#xff09;采用模型驱动的方法&#xff0c;助力开发者仅用数行代码即可构建并运行人工…

利用 AI 打造的开发者工具集合

如图. 我利用 AI 开发了这个网站花了半个小时. 目前就上了 四个 我想到的工具。 大家可以自行体验下&#xff1a;https://xiaojinzi123.github.io 本文并不是宣传什么产品. 只是感概 Ai 真的改变我的工作方式啊. 虽然现在 AI 对于一些已有的项目进行更改代码. 由于不了解业务,…

[自然语言处理]计算语言的熵

一、要求利用给定的中英文语料&#xff0c;分别计算英语字母、英语单词、汉字、汉语词的熵&#xff0c;并和已公开结果比较&#xff0c;思考汉语的熵对汉语编码和处理的影响。二、实验内容2.1 统计英文语料的熵1.代码(1)计算英文字母的熵import math #计算每个英文字母的熵 def…

如何处理“协议异常”错误

在Java中&#xff0c;“协议异常”通常是指在网络通信或者处理特定协议相关操作时出现的异常。以下是一些处理“协议异常”错误的方法&#xff1a;一、理解协议异常的类型和原因HTTP协议异常原因&#xff1a;在进行HTTP通信时&#xff0c;可能会因为请求格式错误、响应状态码异…

Spark 4.0的VariantType 类型以及内部存储

背景 本文基于Spark 4.0 总结 Spark中的 VariantType 类型,用尽量少的字节来存储Json的格式化数据 分析 这里主要介绍 Variant 的存储,我们从VariantBuilder.buildJson方法(把对应的json数据存储为VariantType类型)开始: public static Variant parseJson(JsonParser …

跨越十年的C++演进:C++20新特性全解析

跨越十年的C演进系列&#xff0c;分为5篇&#xff0c;本文为第四篇&#xff0c;后续会持续更新C23~ 前3篇如下&#xff1a; 跨越十年的C演进&#xff1a;C11新特性全解析 跨越十年的C演进&#xff1a;C14新特性全解析 跨越十年的C演进&#xff1a;C17新特性全解析 C20标准…

LeetCode--40.组合总和II

前言&#xff1a;如果你做出来了39题&#xff0c;但是遇到40题就不会做了&#xff0c;那我建议你去再好好缕清39题的思路&#xff0c;再来看这道题&#xff0c;会有种豁然开朗的感觉解题思路&#xff1a;这道题其实与39题基本一致&#xff0c;所以本次题解是借着39题为基础来讲…