https://zhuanlan.zhihu.com/p/463415830
搭建Minio
分为下载、安装、启动、访问、自定义启动脚本及设置永久访问链接等几步操作。
1. 下载minio
远程拉取
在工作目录下
创建自己的minio目录
远程拉取:
中文网的404了,找官网 是可以下载的最新的 第二个图片是错的
wget https://dl.min.io/server/minio/release/linux-amd64/minio


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

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

2)、在后台进程启动 指定端口
记得把端口防火墙通开
./minio server --console-address 0.0.0.0:7777 /data
4. 访问minio
设置固定的静态端口后,日志提示的访问地址是 http://127.0.0.1:9999 ,这里我们就替换成自己服务器的ip地址即可,我这里用的是腾讯云服务器。
访问地址:http://42.192.89.31
效果如下,和老版的界面也不一样了:
默认账号密码: minioadmin minioadmin
5. 使用minio
进入后台后便可以简单使用minio上传文件、预览、分享URL等来尝试minio带来的美好。 许多配置使用默认的就好,不明白的就多点点很快就会了,唯一要明白的是Bucket概念,因为调用minio的API时经常会用到它,简单点就可以理解为存放鸡蛋的篮子(存放文件的目录)。

PS: 刚开始使用的同学可能会习惯点击文件右侧几个按钮中的share按钮copy后台生成的文件链接,然后粘贴到浏览器打开,基本上都会遭遇打不开的情况,因为你仔细看链接就发现,这个链接地址的ip端口是错误的,这是一个误区,我们一般使用minio会通过mc客户端来执行命令进行一些配置,达到永久访问文件及直接下载文件的效果。

6. 设置永久访问链接
1)、安装mc客户端
可以参考官网,写的很详细:https://docs.min.io/docs/minio-client-complete-guide.html
也可以参考中文网: http://docs.minio.org.cn/docs/master/minio-client-complete-guide
当你打开文档读一会儿后,你会发现写的很棒,但是看不懂。没关系,有许多踩过坑的人已经把障碍扫清了。
安装MC客户端: wget https://dl.min.io/client/mc/release/linux-amd64/mc

同样的,要给mc执行文件赋予权限,否则会提示权限不足的错误。
chmod +x mc
设置永久访问链接,这里官网和中文网都讲的不清楚,个人认为这里就是设置了一个可访问的前缀地址,方便之后开放桶权限后能直接访问到图片,方便理解你可以想象为nginx做代理。

设置配置名称为minio,设置访问前缀为http://42.192.89.31 ,这是前面说的我的腾讯云服务器地址,端口设为9000,当然也可以设为别的,我这里设为9000是因为腾讯云安全组的规则已经存在9000端口,我不需要重新添加规则了。这里的root和123456就是前面自定义启动脚本设置的账号密码,你改成自己的就好。其他都不需要改。
./mc config host add minio http://42.192.89.31:9000 minioadmin minioadmin --api S3v4
特别说明:切记,这里设置端口,如果用的是本地虚拟机,要么关闭防火墙,要么就打开你设定的这个端口;如果用的是和我一样的云服务器,不管有没有打开防火墙,都要在云服务器后台管理中添加规则开放这个端口,否则你依然打不开文件。
劳资开7778端口就你妈不行 我真是吐了 换个端口就行了 也不知道为啥 奶奶的
设置某个桶(即文件目录)中的文件可直接下载的权限:
./mc policy set download minio/hospitalimages
这里的hospitalimages就是我自己建的存放互联网医院文件的桶了,记得一定要加上前面的minio,是上一步命令设定的配置名。
执行命令后,这个桶下面的文件就可以直接访问到了。
设置永久访问链接和下载权限的命令执行完后,最终效果如下:
可通过 http://服务器ip:端口/桶名称/文件名称 直接访问到了!
SpringBoot整合Minio
特别说明:minio引入不同版本依赖使用过程是有较大区别的,比如7和8就区别很大,本人也踩过不少坑,搜过不少资料,虽然7版本算是用上了,但目前版本较新,就使用8版本,8的坑也很多,后来在某网站的风间影月老师那里终于找到了能使用的方案,也已经用在了公司的项目中,在这里直接分享给大家。
1. 引入依赖
<!-- MinIO --><dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.2.1</version></dependency>
2. MinioUtils工具类
import io.minio.*;import io.minio.http.Method;import 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.net.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<Bucket> getAllBuckets() throws Exception {return minioClient.listBuckets();}/*** 根据bucketName获取其相关信息* @param bucketName* @return* @throws Exception*/public static Optional<Bucket> 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<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).prefix(objectName).recursive(false).build());for (Result<Item> 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<Item> getAllObjectsByPrefix(String bucketName,String prefix,boolean recursive) throws Exception {List<Item> list = new ArrayList<>();Iterable<Result<Item>> objectsIterator = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(recursive).build());if (objectsIterator != null) {for (Result<Item> 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<Result<Item>> 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<String> keys) {List<DeleteObject> 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 ******************************/}
vue操作minio
总结
这样其实就完成了整合,是不是So Easy?咔咔,在需要用到的地方通过MinioUtils.xxx()方法调用即可,比如我在公司项目中用到的就是MinioUtils.getPresignedObjectUrl()这个获取文件外链的方法,因为大多数时候不需要你对文件本身进行修改删除操作,正常来讲只会用到上传和查询文件的操作,在设计上许多产品老师也会规避这种风险问题。另外,工具类中传递的endpoint、bucketName、accessKey、ecretKey等参数,都是在minio后台可以拿到的,没有的话也可以自己设置。
