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

docker-compose搭建minio对象存储服务器

docker-compose搭建minio对象存储服务器

最近想使用oss对象存储进行用户图片上传的管理,了解了一下例如aliyun或者腾讯云的oss对象存储服务,但是呢涉及到对象存储以及经费有限的缘故,决定自己手动搭建一个oss对象存储服务器;

首先大致了解一下对象存储:

对象存储OSS(Object Storage Service)是一种云存储服务,它提供了海量、安全、低成本、高可靠的存储解决方案

然后在经过大致了解后,选择了MiNiO,进行oss对象服务器的搭建工作,MinIO是一个开源的对象存储服务器,它兼容Amazon S3 API,并提供高性能、高可用性的存储解决方案。在本文中,我们将介绍如何使用Docker Compose快速部署MinIO。

一、docker-compose中的minio对象服务部署

准备工作:

1、服务器必须安装docker

2、服务器必须安装docker对应版本的docker-compose

1.1获取镜像:

首先,如果你的docker还能连上网,能够通过docker pull相关的镜像(咳咳,最近docker不对劲,拉取不到镜像),如果可以拉取镜像,可以执行下述命令:

docker pull minio/minio:latest

如果不可以,建议在往上下载一个minio的tar包,上传至服务器后,可以执行以下命令:

docker load -i minio.tar  ## minio 是你自己tar包的名字。

通过上述操作后,可以使用 docker images 进行查看获取到的镜像
请添加图片描述

1.2 docker-compose.yml文件制作

vim docker-compose.yml

先贴一个代码叭,一会儿挨个儿解释:

version: '3'
services:minio:image: minio/miniocontainer_name: minioports:- 9010:9000- 9011:9011environment:TZ: Asia/ShanghaiMINIO_ACCESS_KEY: minioMINIO_SECRET_KEY: minio123volumes:- ./data:/datacommand: server /data --console-address ":9011"

大概配置如上所示,有几点注意

注:

1、minio容器默认使用两个端口,9000和9001 9000端口主要适用于数据传输,9001端口主要是用于管理界面,上述文件中我为了好记且避免端口冲突,将9000端口映射到了服务器的9010端口,将9001端口改成了9011并映射到了服务器的9011端口

2、数据卷映射: 默认将数据卷映射到了docker-compose.yml同文件目录下的data文件夹

3、command: server --console-address ‘:9011’ /data 这行一定要加,否则端口号是随机的,你压根映射不出去

4、新版本中用户名和密码改用成了 “MINIO_ROOT_USER” 和 “MINIO_ROOT_PASSWORD” 旧版本是 “MINIO_ACCESS_KEY” 和 “MINIO_SECRET_KEY” 可以自己按照版本进行设置。

5、4中分别对应的是管理界面的用户名和密码

在编辑docker-compose.yml并保存后,通过下述命令创建并启动minio容器

#如果你的docker-compose.yml文件中有好几个容器,你并不想启动其他容器,只想启动minio
docker-compose up -d minio
#如果你的docker-compose.yml文件中只有目前的minio
docker-compose up -d 

启动成功后会看到服务器显示

请添加图片描述

此时可以在浏览器输入 上面docker-compose.yml文件中的【你自己的IP+9011】 访问minio的控制面板,记得开启防火墙9010,9011端口哟~

请添加图片描述

输入用户名和密码,登录minio控制面板

请添加图片描述

至此呢 单机版本 通过docker-compose 部署minio对象存储结束

二、spring-boot 集成 minio 对象存储

1、自己创建spring-boot 工程,在这里不多赘述

2、引入pom依赖

在自己的boot项目中引入minio依赖

<!---minio cos对象存储--><dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.4.0</version></dependency>

3、集成代码

在集成代码之前呢,首先了解一下minio的几个知识点

  • Object:存储到Minio的基本对象,如文件、字节流、Anything…
  • Bucket:用来存储Object的逻辑空间。每个Bucket之间的数据量是互相隔离的。对于客户端而言,就相当于一个存放文件的顶层文件夹。
  • Drive:即存储数据的磁盘,在Minio启动时,以参数的方式传入。Minio中所有的对象数据都会存储在Drive里。
  • Set:即一组Drive的集合,分布式部署根据集群规模自动划分一个或多个Set,每个Set中的Drive分布在不同位置。一个对象存储在一个Set上.(for example:{1…64} is divided into 4 sets each of size 16)
    • 一个对象存储在一个Set上
    • 一个集群划分为多个Set
    • 一个Set包含的Drive数量是固定的,默认由系统根据集群规模自动计算得出
    • 一个Set中我的Drive尽可能分布在不同的节点上
3.1 创建用户,创建桶

可以在minio控制面板进行用户的创建以及存储桶(bucket)的创建。我们创建一个test的桶以及创建一个用户并赋予读写的权限

请添加图片描述

请添加图片描述

3.2 添加application.yml配置文件
minio:url: http://xxxxxxx #Minio服务所在地址bucketName: xxxxxx #存储桶名称accessKey: testUser #创建用户访问的key secretKey: 000000000 #创建用户 访问的秘钥
3.3 引入配置

创建MinioConfig 配置文件,将MinioClient 注入容器

@Data
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinioConfig {/*** 服务地址*/private String url;/*** 用户名*/private String accessKey;/*** 密码*/private String secretKey;/*** 存储桶名称*/private String bucketName;/*** 预览到期时间(小时)*/private Integer previewExpiry;@Beanpublic MinioClient getMinIOClient() {return MinioClient.builder().endpoint(url).credentials(accessKey, secretKey).build();}
}
3.4 引入相关操作

创建MinioCosManger文件 封装了minio客户端的一些操作

@Component
@Slf4j
public class MinioCosManger {@Autowiredprivate MinioConfig prop;@Resourceprivate MinioClient minioClient;/*** 查看存储bucket是否存在** @return boolean*/public Boolean bucketExists(String bucketName) {Boolean found;try {found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());} catch (Exception e) {e.printStackTrace();return false;}return found;}/*** 创建存储bucket** @return Boolean*/public Boolean makeBucket(String bucketName) {try {minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());} catch (Exception e) {e.printStackTrace();return false;}return true;}/*** 删除存储bucket** @return Boolean*/public Boolean removeBucket(String bucketName) {try {minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());} catch (Exception e) {e.printStackTrace();return false;}return true;}/*** 获取全部bucket*/public List<Bucket> getAllBuckets() {try {List<Bucket> buckets = minioClient.listBuckets();return buckets;} catch (Exception e) {e.printStackTrace();}return null;}/*** 文件上传** @param file 文件* @return Boolean*/public String upload(MultipartFile file) {String originalFilename = file.getOriginalFilename();if (StringUtils.isBlank(originalFilename)) {throw new RuntimeException();}String fileName = UUID.randomUUID() + originalFilename.substring(originalFilename.lastIndexOf("."));String dateFormat = "yyyy-MM/dd";DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat);LocalDate nowDate = LocalDate.now();String format = nowDate.format(formatter);String objectName = format + "/" + fileName;try {PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(prop.getBucketName()).object(objectName).stream(file.getInputStream(), file.getSize(), -1).contentType(file.getContentType()).build();//文件名称相同会覆盖minioClient.putObject(objectArgs);} catch (Exception e) {e.printStackTrace();return null;}return objectName;}/*** 预览图片** @param fileName* @return*/public String preview(String fileName) {// 查看文件地址GetPresignedObjectUrlArgs build = new GetPresignedObjectUrlArgs().builder().bucket(prop.getBucketName()).object(fileName).method(Method.GET).build();try {String url = minioClient.getPresignedObjectUrl(build);return url;} catch (Exception e) {e.printStackTrace();}return null;}/*** 文件下载** @param fileName 文件名称* @param res      response* @return Boolean*/public void download(String fileName, HttpServletResponse res) {GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(prop.getBucketName()).object(fileName).build();try (GetObjectResponse response = minioClient.getObject(objectArgs)) {byte[] buf = new byte[1024];int len;try (FastByteArrayOutputStream os = new FastByteArrayOutputStream()) {while ((len = response.read(buf)) != -1) {os.write(buf, 0, len);}os.flush();byte[] bytes = os.toByteArray();res.setCharacterEncoding("utf-8");// 设置强制下载不打开// res.setContentType("application/force-download");res.addHeader("Content-Disposition", "attachment;fileName=" + fileName);try (ServletOutputStream stream = res.getOutputStream()) {stream.write(bytes);stream.flush();}}} catch (Exception e) {e.printStackTrace();}}/*** 查看文件对象** @return 存储bucket内文件对象信息*/public List<Item> listObjects() {Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder().bucket(prop.getBucketName()).build());List<Item> items = new ArrayList<>();try {for (Result<Item> result : results) {items.add(result.get());}} catch (Exception e) {e.printStackTrace();return null;}return items;}/*** 删除** @param fileName* @return* @throws Exception*/public boolean remove(String fileName) {try {minioClient.removeObject(RemoveObjectArgs.builder().bucket(prop.getBucketName()).object(fileName).build());} catch (Exception e) {return false;}return true;}}
3.5 创建controller进行测试
/*** @version 1.0* @Author jerryLau* @Date 2024/7/1 11:23* @注释*/
@Api(tags = "文件相关接口")
@Slf4j
@RestController
@RequestMapping(value = "product/file")
public class FileController2 {@Autowiredprivate MinioCosManger minioUtil;@Autowiredprivate MinioConfig prop;@ApiOperation(value = "查看存储bucket是否存在")@GetMapping("/bucketExists")public BaseResponse<String> bucketExists(@RequestParam("bucketName") String bucketName) {if (minioUtil.bucketExists(bucketName)) {return ResultUtils.success("bucketName is exit!");} elsereturn ResultUtils.error(ErrorCode.NOT_FOUND_ERROR, "bucketName is not exit!");}@ApiOperation(value = "创建存储bucket")@GetMapping("/makeBucket")public BaseResponse<String> makeBucket(String bucketName) {if (minioUtil.makeBucket(bucketName)) {return ResultUtils.success("create bucket success!");} elsereturn ResultUtils.error(ErrorCode.OPERATION_ERROR, "create bucket error!");}@ApiOperation(value = "删除存储bucket")@GetMapping("/removeBucket")public BaseResponse<String> removeBucket(String bucketName) {if (minioUtil.removeBucket(bucketName)) {return ResultUtils.success("removeBucket  success!");} elsereturn ResultUtils.error(ErrorCode.OPERATION_ERROR, "removeBucket error!");}@ApiOperation(value = "获取全部bucket")@GetMapping("/getAllBuckets")public BaseResponse<List<Bucket>> getAllBuckets() {List<Bucket> allBuckets = minioUtil.getAllBuckets();return ResultUtils.success(allBuckets);}@ApiOperation(value = "文件上传返回url")@PostMapping("/upload")public BaseResponse<String> upload(@RequestParam("file") MultipartFile file) {String objectName = minioUtil.upload(file);if (null != objectName) {String url = (prop.getUrl() + "/" + prop.getBucketName() + "/" + objectName);return ResultUtils.success(url);}return ResultUtils.error(ErrorCode.OPERATION_ERROR, "upload error!");}@ApiOperation(value = "图片/视频预览")@GetMapping("/preview")public BaseResponse<String> preview(@RequestParam("fileName") String fileName) {String preview = minioUtil.preview(fileName);return ResultUtils.success(preview);}@ApiOperation(value = "文件下载")@GetMapping("/download")public void download(@RequestParam("fileName") String fileName, HttpServletResponse res) {minioUtil.download(fileName, res);}@ApiOperation(value = "删除文件", notes = "根据url地址删除文件")@PostMapping("/delete")public BaseResponse<String> remove(String url) {String objName = url.substring(url.lastIndexOf(prop.getBucketName() + "/") + prop.getBucketName().length() + 1);boolean remove = minioUtil.remove(objName);if (remove) {return ResultUtils.success(objName + "delete success!");} else return ResultUtils.error(ErrorCode.OPERATION_ERROR, objName + "delete error!");}}
3.6 接口测试以及存储验证

通过knife4j或者其他请求测试工具(postman、apifox等),测试接口

请添加图片描述

注意:

1、按照理论来说在上传结束后返回的这个文件的url应该没有办法直接访问,应该在访问该存储对象的时候,去调用preview方法,但是对本人而言,调用preview返回的地址太长了,并且存在一定的时效性,在超过一段时间后将不会在被访问到,所以本人通过给bucket设置access prefix为 readandwrite,这样一来,上传接口的返回url便可直接被访问到了。

2、如果想去调用preview 或者download 方法时,所传入的文件名一定是bucket后面的全部文件名称,比如上面测试图片中,如果调用,传入文件名应为2024-07/01/0011c366-f2a4-4b26-adbc-931d444d7205.png 而不是简单的0011c366-f2a4-4b26-adbc-931d444d7205.png ,否则即使返回了preview的url ,这个url也无法被访问到。

请添加图片描述


至此,通过docker-compose手动搭建minio 对象存储服务器已全部完结,喜欢的观众老爷,请一键三连 🎉🎉🎉,感谢大家~

相关文章:

  • c++ primer plus 第15章友,异常和其他:15.1.2 友元成员函数
  • HTML+CSS笔记
  • ECOLOGY9重置系统管理员密码
  • 【堆 优先队列】23. 合并 K 个升序链表
  • 有哪些在本地运行大模型的方法
  • 交换机需要多大 buffer(续:更一般的原理)
  • 百日筑基第十一天-看看SpringBoot
  • Windows 下用MSYS2 环境为RP2040 编译MicroPython 固件
  • 深度学习基准模型Transformer
  • 开灯问题(数学思路)
  • 第二十条:与抽象类相比,优先选择接口
  • 程序员需要具备的核心竞争力
  • 【等保2.0是什么意思?等保2.0的基本要求有哪些? 】
  • 游戏中的坐标转换函数*2(laya2D)
  • JVM的五大内存区域
  • 【翻译】Mashape是如何管理15000个API和微服务的(三)
  • 4月23日世界读书日 网络营销论坛推荐《正在爆发的营销革命》
  • Apache Zeppelin在Apache Trafodion上的可视化
  • Fabric架构演变之路
  • PHP 程序员也能做的 Java 开发 30分钟使用 netty 轻松打造一个高性能 websocket 服务...
  • text-decoration与color属性
  • 聊一聊前端的监控
  • 前端临床手札——文件上传
  • 前端性能优化——回流与重绘
  • 区块链技术特点之去中心化特性
  • 入门级的git使用指北
  • 使用权重正则化较少模型过拟合
  • 思考 CSS 架构
  • 腾讯优测优分享 | 你是否体验过Android手机插入耳机后仍外放的尴尬?
  • 体验javascript之美-第五课 匿名函数自执行和闭包是一回事儿吗?
  • 无服务器化是企业 IT 架构的未来吗?
  • Java数据解析之JSON
  • ​【原创】基于SSM的酒店预约管理系统(酒店管理系统毕业设计)
  • ​软考-高级-系统架构设计师教程(清华第2版)【第15章 面向服务架构设计理论与实践(P527~554)-思维导图】​
  • # 数论-逆元
  • (2024.6.23)最新版MAVEN的安装和配置教程(超详细)
  • (6)STL算法之转换
  • (二)pulsar安装在独立的docker中,python测试
  • (附源码)小程序儿童艺术培训机构教育管理小程序 毕业设计 201740
  • (算法设计与分析)第一章算法概述-习题
  • (图)IntelliTrace Tools 跟踪云端程序
  • (一)pytest自动化测试框架之生成测试报告(mac系统)
  • (转)LINQ之路
  • (转)MVC3 类型“System.Web.Mvc.ModelClientValidationRule”同时存在
  • (转)Sql Server 保留几位小数的两种做法
  • .NET/C# 反射的的性能数据,以及高性能开发建议(反射获取 Attribute 和反射调用方法)
  • .NET上SQLite的连接
  • .net中应用SQL缓存(实例使用)
  • @manytomany 保存后数据被删除_[Windows] 数据恢复软件RStudio v8.14.179675 便携特别版...
  • []新浪博客如何插入代码(其他博客应该也可以)
  • [20170728]oracle保留字.txt
  • [AR Foundation] 人脸检测的流程
  • [BZOJ] 2427: [HAOI2010]软件安装
  • [BZOJ5125]小Q的书架(决策单调性+分治DP+树状数组)
  • [C#]OpenCvSharp使用帧差法或者三帧差法检测移动物体