SpringBoot+Minio搭建不再爆肝秃头的分布式文件服务器

网友投稿 1410 2022-10-20

SpringBoot+Minio搭建不再爆肝秃头的分布式文件服务器

SpringBoot+Minio搭建不再爆肝秃头的分布式文件服务器

前言

1)、有人一定会问,为什么不用FastDFS?众所周知,FastDFS的原生安装非常复杂,有过安装经验的人大体都明白,虽然可以利用别人做好的docker直接安装,但真正使用过程中也可能出现许多莫名其妙的问题;之前也有写了一篇《一文搞定FastDFS的搭建和使用》,许多朋友也都强烈推荐使用Minio,有朋友感兴趣可以自行翻阅。2)、还有人会问,为什么不用oss或其他现有云产品?道理很简单,你不能保证自己所在的公司拥有的项目一定会上云,据我个人了解,大部分公司要么依托于甲方使用内网服务器,要么是公司自己内部搭建的,比如我公司就是依托于医院自己的服务器,所有部署以安全为首,只能自己搭建内部文件服务器;3)、Minio是GO语言开发的,性能很好,安装简单,可分布式存储海量图片、音频、视频等文件,且拥有自己的管理后台界面,十分友好。4)、我有个习惯,每年会观察流量大的培训机构会新增什么技术去除什么技术,虽然Minio出来也有些时日了,但这两年陆续有知名机构的讲师开始引入Minio了,意味着这个产品的接受度正在升高,随着培训机构培养的人员扩散到各个IT公司,接受度只会越来越高。所以,此时不了解更待何时~minio官网地址: ​​docs.min.io/docs/minio-…​​minio中文网地址: docs.minio.org-/docs/特别说明:大部分内容直接看中文网即可,但-minio时,最好看官网,因为minio版本更新非常快,经常会出现资源文件更换目录的情况,同时中文网的地址可能会失效,导致-失败。

搭建Minio

分为-、安装、启动、访问、自定义启动脚本及设置永久访问链接等几步操作。

1. -minio

1)、手工-:​​docs.min.io/docs/minio-…​​

找到这个位置,-自己需要的版本,我这里使用Linux,所以-第一个就行。

2)、远程拉取  创建自己的minio目录  远程拉取: ​​wget 安装minio

1)、给minio二进制文件赋权限,否则无法执行:​​chmod +x minio​​  2)、在二进制文件所在目录执行 ./minio ,成功后可看到最下面的版本号,我这里安装的是当前最新版。

3. 启动minio

1)、在minio安装目录新建data目录,用来存放minio的数据:​​mkdir data​​ ;

2)、在后台进程启动minio: ​​./minio server /data/minio/data > /data/minio/minio.log 2>&1 &​​    查看后台运行日志: tail -f minio.log

特别说明: 这里日志可以看出来,新版的minio和老版是有区别的,这里API后面的地址是9000端口,console也就是控制台地址的端口是33587,而且最后一句WARNING有提示,控制台端口是动态生成的,请使用命令选择一个静态端口,意思就是如果重启了,那么这个控制台的端口又会发生改变,需要自己设置一个固定不变的静态端口,具体的设置方法可以按照提示的命令设置。命令如下:(注意重启时要执行 ​​kill -9 [进程号]​​ 把之前后台进程启动的minio杀掉)

# 指定后台端口为9999./minio server --console-address 0.0.0.0:9999 /data/minio/data > /data/minio/minio.log 2>&1 &

4. 访问minio

设置固定的静态端口后,日志提示的访问地址是 ​​minioadmin

5. 自定义脚本启动minio

1)、新建一个shell脚本,把启动时需要设置的命令放进来即可。这里新增了设置账号密码的命令,不再用之前的默认账号密码minioadmin。  新建shell脚本:​​vim minio-start.sh​​

# 设置账号密码export MINIO_ACCESS_KEY=rootexport MINIO_SECRET_KEY=123456# 后台进程启动minio./minio server --console-address 0.0.0.0:9999 /data/minio/data > /data/minio/minio.log 2>&1 &

2)、给这个脚本赋予执行权限:​​chmod +x minio-start.sh​​  3)、执行脚本启动minio:./minio-start.sh

最终效果和上面一样!

6. 使用minio

进入后台后便可以简单使用minio上传文件、预览、分享URL等来尝试minio带来的美好。 许多配置使用默认的就好,不明白的就多点点很快就会了,唯一要明白的是Bucket概念,因为调用minio的API时经常会用到它,简单点就可以理解为存放鸡蛋的篮子(存放文件的目录)。

7. 设置永久访问链接

1)、安装mc客户端  可以参考官网,写的很详细:​​docs.min.io/docs/minio-…​​  也可以参考中文网: docs.minio.org-/docs/master…  当你打开文档读一会儿后,你会发现写的很棒,但是看不懂。没关系,有许多踩过坑的人已经把障碍扫清了。

安装MC客户端: ​​wget +x mc​​

设置永久访问链接,这里官网和中文网都讲的不清楚,个人认为这里就是设置了一个可访问的前缀地址,方便之后开放桶权限后能直接访问到图片,方便理解你可以想象为nginx做代理。

设置配置名称为minio,设置访问前缀为​​config host add minio root 123456 --api S3v4

特别说明:切记,这里设置端口,如果用的是本地虚拟机,要么关闭防火墙,要么就打开你设定的这个端口;如果用的是和我一样的云服务器,不管有没有打开防火墙,都要在云服务器后台管理中添加规则开放这个端口,否则你依然打不开文件。

设置某个桶(即文件目录)中的文件可直接-的权限:​​./mc policy set download minio/hospitalimages​​

执行命令后,这个桶下面的文件就可以直接访问到了。  设置永久访问链接和-权限的命令执行完后,最终效果如下:可通过 ​​引入依赖

io.minio minio 8.2.1

2. MinioUtils工具类

import io.minio.*;import io.minio.io.minio.messages.Bucket;import io.minio.messages.DeleteObject;import io.minio.messages.Item;import lombok.extern.slf4j.Slf4j;import org.springframework.web.multipart.MultipartFile;import java.io.ByteArrayInputStream;import java.io.InputStream;import java.io.UnsupportedEncodingException;import java-.URLDecoder;import java.util.ArrayList;import java.util.LinkedList;import java.util.List;import java.util.Optional;/** * MinIO工具类 * * @author guoj * @date 2021/12/14 19:30 */@Slf4jpublic class MinIOUtils { private static MinioClient minioClient; private static String endpoint; private static String bucketName; private static String accessKey; private static String secretKey; private static Integer imgSize; private static Integer fileSize; private static final String SEPARATOR = "/"; public MinIOUtils(){ } public MinIOUtils(String endpoint, String bucketName, String accessKey, String secretKey, Integer imgSize, Integer fileSize){ MinIOUtils.endpoint = endpoint; MinIOUtils.bucketName = bucketName; MinIOUtils.accessKey = accessKey; MinIOUtils.secretKey = secretKey; MinIOUtils.imgSize = imgSize; MinIOUtils.fileSize = fileSize; createMinioClient(); } /** * 创建基于Java端的MinioClient */ public void createMinioClient(){ try { if (null == minioClient) { log.info("开始创建 MinioClient..."); minioClient = MinioClient .builder() .endpoint(endpoint) .credentials(accessKey, secretKey) .build(); createBucket(bucketName); log.info("创建完毕 MinioClient..."); } } catch (Exception e) { log.error("[Minio工具类]>>>> MinIO服务器异常:", e); } } /** * 获取上传文件前缀路径 * @return */ public static String getBasisUrl(){ return endpoint + SEPARATOR + bucketName + SEPARATOR; } /****************************** Operate Bucket Start ******************************/ /** * 启动SpringBoot容器的时候初始化Bucket * 如果没有Bucket则创建 * @throws Exception */ private static void createBucket(String bucketName) throws Exception { if (!bucketExists(bucketName)) { minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build()); } } /** * 判断Bucket是否存在,true:存在,false:不存在 * @return * @throws Exception */ public static boolean bucketExists(String bucketName) throws Exception { return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()); } /** * 获得Bucket的策略 * @param bucketName * @return * @throws Exception */ public static String getBucketPolicy(String bucketName) throws Exception { return minioClient .getBucketPolicy( GetBucketPolicyArgs .builder() .bucket(bucketName) .build() ); } /** * 获得所有Bucket列表 * @return * @throws Exception */ public static List getAllBuckets() throws Exception { return minioClient.listBuckets(); } /** * 根据bucketName获取其相关信息 * @param bucketName * @return * @throws Exception */ public static Optional getBucket(String bucketName) throws Exception { return getAllBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst(); } /** * 根据bucketName删除Bucket,true:删除成功; false:删除失败,文件或已不存在 * @param bucketName * @throws Exception */ public static void removeBucket(String bucketName) throws Exception { minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build()); } /****************************** Operate Bucket End ******************************/ /****************************** Operate Files Start ******************************/ /** * 判断文件是否存在 * @param bucketName 存储桶 * @param objectName 文件名 * @return */ public static boolean isObjectExist(String bucketName, String objectName){ boolean exist = true; try { minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build()); } catch (Exception e) { log.error("[Minio工具类]>>>> 判断文件是否存在, 异常:", e); exist = false; } return exist; } /** * 判断文件夹是否存在 * @param bucketName 存储桶 * @param objectName 文件夹名称 * @return */ public static boolean isFolderExist(String bucketName, String objectName){ boolean exist = false; try { Iterable> results = minioClient.listObjects( ListObjectsArgs.builder().bucket(bucketName).prefix(objectName).recursive(false).build()); for (Result result : results) { Item item = result.get(); if (item.isDir() && objectName.equals(item.objectName())) { exist = true; } } } catch (Exception e) { log.error("[Minio工具类]>>>> 判断文件夹是否存在,异常:", e); exist = false; } return exist; } /** * 根据文件前置查询文件 * @param bucketName 存储桶 * @param prefix 前缀 * @param recursive 是否使用递归查询 * @return MinioItem 列表 * @throws Exception */ public static List getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive) throws Exception { List list = new ArrayList<>(); Iterable> objectsIterator = minioClient.listObjects( ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(recursive).build()); if (objectsIterator != null) { for (Result o : objectsIterator) { Item item = o.get(); list.add(item); } } return list; } /** * 获取文件流 * @param bucketName 存储桶 * @param objectName 文件名 * @return 二进制流 */ public static InputStream getObject(String bucketName, String objectName) throws Exception { return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build()); } /** * 断点- * @param bucketName 存储桶 * @param objectName 文件名称 * @param offset 起始字节的位置 * @param length 要读取的长度 * @return 二进制流 */ public InputStream getObject(String bucketName, String objectName, long offset, long length)throws Exception { return minioClient.getObject( GetObjectArgs.builder() .bucket(bucketName) .object(objectName) .offset(offset) .length(length) .build()); } /** * 获取路径下文件列表 * @param bucketName 存储桶 * @param prefix 文件名称 * @param recursive 是否递归查找,false:模拟文件夹结构查找 * @return 二进制流 */ public static Iterable> listObjects(String bucketName, String prefix, boolean recursive) { return minioClient.listObjects( ListObjectsArgs.builder() .bucket(bucketName) .prefix(prefix) .recursive(recursive) .build()); } /** * 使用MultipartFile进行文件上传 * @param bucketName 存储桶 * @param file 文件名 * @param objectName 对象名 * @param contentType 类型 * @return * @throws Exception */ public static ObjectWriteResponse uploadFile(String bucketName, MultipartFile file, String objectName, String contentType) throws Exception { InputStream inputStream = file.getInputStream(); return minioClient.putObject( PutObjectArgs.builder() .bucket(bucketName) .object(objectName) .contentType(contentType) .stream(inputStream, inputStream.available(), -1) .build()); } /** * 上传本地文件 * @param bucketName 存储桶 * @param objectName 对象名称 * @param fileName 本地文件路径 */ public static ObjectWriteResponse uploadFile(String bucketName, String objectName, String fileName) throws Exception { return minioClient.uploadObject( UploadObjectArgs.builder() .bucket(bucketName) .object(objectName) .filename(fileName) .build()); } /** * 通过流上传文件 * * @param bucketName 存储桶 * @param objectName 文件对象 * @param inputStream 文件流 */ public static ObjectWriteResponse uploadFile(String bucketName, String objectName, InputStream inputStream) throws Exception { return minioClient.putObject( PutObjectArgs.builder() .bucket(bucketName) .object(objectName) .stream(inputStream, inputStream.available(), -1) .build()); } /** * 创建文件夹或目录 * @param bucketName 存储桶 * @param objectName 目录路径 */ public static ObjectWriteResponse createDir(String bucketName, String objectName) throws Exception { return minioClient.putObject( PutObjectArgs.builder() .bucket(bucketName) .object(objectName) .stream(new ByteArrayInputStream(new byte[]{}), 0, -1) .build()); } /** * 获取文件信息, 如果抛出异常则说明文件不存在 * * @param bucketName 存储桶 * @param objectName 文件名称 */ public static String getFileStatusInfo(String bucketName, String objectName) throws Exception { return minioClient.statObject( StatObjectArgs.builder() .bucket(bucketName) .object(objectName) .build()).toString(); } /** * 拷贝文件 * * @param bucketName 存储桶 * @param objectName 文件名 * @param srcBucketName 目标存储桶 * @param srcObjectName 目标文件名 */ public static ObjectWriteResponse copyFile(String bucketName, String objectName, String srcBucketName, String srcObjectName) throws Exception { return minioClient.copyObject( CopyObjectArgs.builder() .source(CopySource.builder().bucket(bucketName).object(objectName).build()) .bucket(srcBucketName) .object(srcObjectName) .build()); } /** * 删除文件 * @param bucketName 存储桶 * @param objectName 文件名称 */ public static void removeFile(String bucketName, String objectName) throws Exception { minioClient.removeObject( RemoveObjectArgs.builder() .bucket(bucketName) .object(objectName) .build()); } /** * 批量删除文件 * @param bucketName 存储桶 * @param keys 需要删除的文件列表 * @return */ public static void removeFiles(String bucketName, List keys){ List objects = new LinkedList<>(); keys.forEach(s -> { objects.add(new DeleteObject(s)); try { removeFile(bucketName, s); } catch (Exception e) { log.error("[Minio工具类]>>>> 批量删除文件,异常:", e); } }); } /** * 获取文件外链 * @param bucketName 存储桶 * @param objectName 文件名 * @param expires 过期时间 <=7 秒 (外链有效时间(单位:秒)) * @return url * @throws Exception */ public static String getPresignedObjectUrl(String bucketName, String objectName, Integer expires) throws Exception { GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder().expiry(expires).bucket(bucketName).object(objectName).build(); return minioClient.getPresignedObjectUrl(args); } /** * 获得文件外链 * @param bucketName * @param objectName * @return url * @throws Exception */ public static String getPresignedObjectUrl(String bucketName, String objectName) throws Exception { GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder() .bucket(bucketName) .object(objectName) .method(Method.GET).build(); return minioClient.getPresignedObjectUrl(args); } /** * 将URLDecoder编码转成UTF8 * @param str * @return * @throws UnsupportedEncodingException */ public static String getUtf8ByURLDecoder(String str) throws UnsupportedEncodingException { String url = str.replaceAll("%(?![0-9a-fA-F]{2})", "%25"); return URLDecoder.decode(url, "UTF-8"); } /****************************** Operate Files End ******************************/}

总结

这样其实就完成了整合,是不是So Easy?咔咔,在需要用到的地方通过MinioUtils.xxx()方法调用即可,比如我在公司项目中用到的就是MinioUtils.getPresignedObjectUrl()这个获取文件外链的方法,因为大多数时候不需要你对文件本身进行修改删除操作,正常来讲只会用到上传和查询文件的操作,在设计上许多产品老师也会规避这种风险问题。另外,工具类中传递的endpoint、bucketName、accessKey、ecretKey等参数,都是在minio后台可以拿到的,没有的话也可以自己设置。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:基于C的gRPC库和框架
下一篇:Kahlan - 全栈 Unit/BDD测试框架
相关文章

 发表评论

暂时没有评论,来抢沙发吧~