https://zhuanlan.zhihu.com/p/463415830

搭建Minio

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

1. 下载minio

远程拉取
在工作目录下
创建自己的minio目录
远程拉取:
中文网的404了,找官网 是可以下载的最新的 第二个图片是错的

  1. wget https://dl.min.io/server/minio/release/linux-amd64/minio

image.png
image.png

2. 安装minio

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

image.png

3. 启动minio

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

image.png

2)、在后台进程启动 指定端口
记得把端口防火墙通开

  1. ./minio server --console-address 0.0.0.0:7777 /data

image.png

4. 访问minio

设置固定的静态端口后,日志提示的访问地址是 http://127.0.0.1:9999 ,这里我们就替换成自己服务器的ip地址即可,我这里用的是腾讯云服务器。
访问地址:http://42.192.89.31
效果如下,和老版的界面也不一样了:
默认账号密码: minioadmin minioadmin
image.png

5. 使用minio

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

image.png

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

image.png

6. 设置永久访问链接

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

安装MC客户端: wget https://dl.min.io/client/mc/release/linux-amd64/mc

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

image.png

设置配置名称为minio,设置访问前缀为http://42.192.89.31 ,这是前面说的我的腾讯云服务器地址,端口设为9000,当然也可以设为别的,我这里设为9000是因为腾讯云安全组的规则已经存在9000端口,我不需要重新添加规则了。这里的root和123456就是前面自定义启动脚本设置的账号密码,你改成自己的就好。其他都不需要改。

  1. ./mc config host add minio http://42.192.89.31:9000 minioadmin minioadmin --api S3v4

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

设置某个桶(即文件目录)中的文件可直接下载的权限:

  1. ./mc policy set download minio/hospitalimages

这里的hospitalimages就是我自己建的存放互联网医院文件的桶了,记得一定要加上前面的minio,是上一步命令设定的配置名。
image.png

执行命令后,这个桶下面的文件就可以直接访问到了。
设置永久访问链接和下载权限的命令执行完后,最终效果如下:
可通过 http://服务器ip:端口/桶名称/文件名称 直接访问到了!
image.png

SpringBoot整合Minio

特别说明:minio引入不同版本依赖使用过程是有较大区别的,比如7和8就区别很大,本人也踩过不少坑,搜过不少资料,虽然7版本算是用上了,但目前版本较新,就使用8版本,8的坑也很多,后来在某网站的风间影月老师那里终于找到了能使用的方案,也已经用在了公司的项目中,在这里直接分享给大家。

1. 引入依赖

  1. <!-- MinIO -->
  2. <dependency>
  3. <groupId>io.minio</groupId>
  4. <artifactId>minio</artifactId>
  5. <version>8.2.1</version>
  6. </dependency>

2. MinioUtils工具类

  1. import io.minio.*;
  2. import io.minio.http.Method;
  3. import io.minio.messages.Bucket;
  4. import io.minio.messages.DeleteObject;
  5. import io.minio.messages.Item;
  6. import lombok.extern.slf4j.Slf4j;
  7. import org.springframework.web.multipart.MultipartFile;
  8. import java.io.ByteArrayInputStream;
  9. import java.io.InputStream;
  10. import java.io.UnsupportedEncodingException;
  11. import java.net.URLDecoder;
  12. import java.util.ArrayList;
  13. import java.util.LinkedList;
  14. import java.util.List;
  15. import java.util.Optional;
  16. /**
  17. * MinIO工具类
  18. *
  19. * @author guoj
  20. * @date 2021/12/14 19:30
  21. */
  22. @Slf4j
  23. public class MinIOUtils {
  24. private static MinioClient minioClient;
  25. private static String endpoint;
  26. private static String bucketName;
  27. private static String accessKey;
  28. private static String secretKey;
  29. private static Integer imgSize;
  30. private static Integer fileSize;
  31. private static final String SEPARATOR = "/";
  32. public MinIOUtils() {
  33. }
  34. public MinIOUtils(String endpoint, String bucketName, String accessKey, String secretKey, Integer imgSize, Integer fileSize) {
  35. MinIOUtils.endpoint = endpoint;
  36. MinIOUtils.bucketName = bucketName;
  37. MinIOUtils.accessKey = accessKey;
  38. MinIOUtils.secretKey = secretKey;
  39. MinIOUtils.imgSize = imgSize;
  40. MinIOUtils.fileSize = fileSize;
  41. createMinioClient();
  42. }
  43. /**
  44. * 创建基于Java端的MinioClient
  45. */
  46. public void createMinioClient() {
  47. try {
  48. if (null == minioClient) {
  49. log.info("开始创建 MinioClient...");
  50. minioClient = MinioClient
  51. .builder()
  52. .endpoint(endpoint)
  53. .credentials(accessKey, secretKey)
  54. .build();
  55. createBucket(bucketName);
  56. log.info("创建完毕 MinioClient...");
  57. }
  58. } catch (Exception e) {
  59. log.error("[Minio工具类]>>>> MinIO服务器异常:", e);
  60. }
  61. }
  62. /**
  63. * 获取上传文件前缀路径
  64. * @return
  65. */
  66. public static String getBasisUrl() {
  67. return endpoint + SEPARATOR + bucketName + SEPARATOR;
  68. }
  69. /****************************** Operate Bucket Start ******************************/
  70. /**
  71. * 启动SpringBoot容器的时候初始化Bucket
  72. * 如果没有Bucket则创建
  73. * @throws Exception
  74. */
  75. private static void createBucket(String bucketName) throws Exception {
  76. if (!bucketExists(bucketName)) {
  77. minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
  78. }
  79. }
  80. /**
  81. * 判断Bucket是否存在,true:存在,false:不存在
  82. * @return
  83. * @throws Exception
  84. */
  85. public static boolean bucketExists(String bucketName) throws Exception {
  86. return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
  87. }
  88. /**
  89. * 获得Bucket的策略
  90. * @param bucketName
  91. * @return
  92. * @throws Exception
  93. */
  94. public static String getBucketPolicy(String bucketName) throws Exception {
  95. return minioClient
  96. .getBucketPolicy(
  97. GetBucketPolicyArgs
  98. .builder()
  99. .bucket(bucketName)
  100. .build()
  101. );
  102. }
  103. /**
  104. * 获得所有Bucket列表
  105. * @return
  106. * @throws Exception
  107. */
  108. public static List<Bucket> getAllBuckets() throws Exception {
  109. return minioClient.listBuckets();
  110. }
  111. /**
  112. * 根据bucketName获取其相关信息
  113. * @param bucketName
  114. * @return
  115. * @throws Exception
  116. */
  117. public static Optional<Bucket> getBucket(String bucketName) throws Exception {
  118. return getAllBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();
  119. }
  120. /**
  121. * 根据bucketName删除Bucket,true:删除成功; false:删除失败,文件或已不存在
  122. * @param bucketName
  123. * @throws Exception
  124. */
  125. public static void removeBucket(String bucketName) throws Exception {
  126. minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
  127. }
  128. /****************************** Operate Bucket End ******************************/
  129. /****************************** Operate Files Start ******************************/
  130. /**
  131. * 判断文件是否存在
  132. * @param bucketName 存储桶
  133. * @param objectName 文件名
  134. * @return
  135. */
  136. public static boolean isObjectExist(String bucketName, String objectName) {
  137. boolean exist = true;
  138. try {
  139. minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
  140. } catch (Exception e) {
  141. log.error("[Minio工具类]>>>> 判断文件是否存在, 异常:", e);
  142. exist = false;
  143. }
  144. return exist;
  145. }
  146. /**
  147. * 判断文件夹是否存在
  148. * @param bucketName 存储桶
  149. * @param objectName 文件夹名称
  150. * @return
  151. */
  152. public static boolean isFolderExist(String bucketName, String objectName) {
  153. boolean exist = false;
  154. try {
  155. Iterable<Result<Item>> results = minioClient.listObjects(
  156. ListObjectsArgs.builder().bucket(bucketName).prefix(objectName).recursive(false).build());
  157. for (Result<Item> result : results) {
  158. Item item = result.get();
  159. if (item.isDir() && objectName.equals(item.objectName())) {
  160. exist = true;
  161. }
  162. }
  163. } catch (Exception e) {
  164. log.error("[Minio工具类]>>>> 判断文件夹是否存在,异常:", e);
  165. exist = false;
  166. }
  167. return exist;
  168. }
  169. /**
  170. * 根据文件前置查询文件
  171. * @param bucketName 存储桶
  172. * @param prefix 前缀
  173. * @param recursive 是否使用递归查询
  174. * @return MinioItem 列表
  175. * @throws Exception
  176. */
  177. public static List<Item> getAllObjectsByPrefix(String bucketName,
  178. String prefix,
  179. boolean recursive) throws Exception {
  180. List<Item> list = new ArrayList<>();
  181. Iterable<Result<Item>> objectsIterator = minioClient.listObjects(
  182. ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(recursive).build());
  183. if (objectsIterator != null) {
  184. for (Result<Item> o : objectsIterator) {
  185. Item item = o.get();
  186. list.add(item);
  187. }
  188. }
  189. return list;
  190. }
  191. /**
  192. * 获取文件流
  193. * @param bucketName 存储桶
  194. * @param objectName 文件名
  195. * @return 二进制流
  196. */
  197. public static InputStream getObject(String bucketName, String objectName) throws Exception {
  198. return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
  199. }
  200. /**
  201. * 断点下载
  202. * @param bucketName 存储桶
  203. * @param objectName 文件名称
  204. * @param offset 起始字节的位置
  205. * @param length 要读取的长度
  206. * @return 二进制流
  207. */
  208. public InputStream getObject(String bucketName, String objectName, long offset, long length)throws Exception {
  209. return minioClient.getObject(
  210. GetObjectArgs.builder()
  211. .bucket(bucketName)
  212. .object(objectName)
  213. .offset(offset)
  214. .length(length)
  215. .build());
  216. }
  217. /**
  218. * 获取路径下文件列表
  219. * @param bucketName 存储桶
  220. * @param prefix 文件名称
  221. * @param recursive 是否递归查找,false:模拟文件夹结构查找
  222. * @return 二进制流
  223. */
  224. public static Iterable<Result<Item>> listObjects(String bucketName, String prefix,
  225. boolean recursive) {
  226. return minioClient.listObjects(
  227. ListObjectsArgs.builder()
  228. .bucket(bucketName)
  229. .prefix(prefix)
  230. .recursive(recursive)
  231. .build());
  232. }
  233. /**
  234. * 使用MultipartFile进行文件上传
  235. * @param bucketName 存储桶
  236. * @param file 文件名
  237. * @param objectName 对象名
  238. * @param contentType 类型
  239. * @return
  240. * @throws Exception
  241. */
  242. public static ObjectWriteResponse uploadFile(String bucketName, MultipartFile file,
  243. String objectName, String contentType) throws Exception {
  244. InputStream inputStream = file.getInputStream();
  245. return minioClient.putObject(
  246. PutObjectArgs.builder()
  247. .bucket(bucketName)
  248. .object(objectName)
  249. .contentType(contentType)
  250. .stream(inputStream, inputStream.available(), -1)
  251. .build());
  252. }
  253. /**
  254. * 上传本地文件
  255. * @param bucketName 存储桶
  256. * @param objectName 对象名称
  257. * @param fileName 本地文件路径
  258. */
  259. public static ObjectWriteResponse uploadFile(String bucketName, String objectName,
  260. String fileName) throws Exception {
  261. return minioClient.uploadObject(
  262. UploadObjectArgs.builder()
  263. .bucket(bucketName)
  264. .object(objectName)
  265. .filename(fileName)
  266. .build());
  267. }
  268. /**
  269. * 通过流上传文件
  270. *
  271. * @param bucketName 存储桶
  272. * @param objectName 文件对象
  273. * @param inputStream 文件流
  274. */
  275. public static ObjectWriteResponse uploadFile(String bucketName, String objectName, InputStream inputStream) throws Exception {
  276. return minioClient.putObject(
  277. PutObjectArgs.builder()
  278. .bucket(bucketName)
  279. .object(objectName)
  280. .stream(inputStream, inputStream.available(), -1)
  281. .build());
  282. }
  283. /**
  284. * 创建文件夹或目录
  285. * @param bucketName 存储桶
  286. * @param objectName 目录路径
  287. */
  288. public static ObjectWriteResponse createDir(String bucketName, String objectName) throws Exception {
  289. return minioClient.putObject(
  290. PutObjectArgs.builder()
  291. .bucket(bucketName)
  292. .object(objectName)
  293. .stream(new ByteArrayInputStream(new byte[]{}), 0, -1)
  294. .build());
  295. }
  296. /**
  297. * 获取文件信息, 如果抛出异常则说明文件不存在
  298. *
  299. * @param bucketName 存储桶
  300. * @param objectName 文件名称
  301. */
  302. public static String getFileStatusInfo(String bucketName, String objectName) throws Exception {
  303. return minioClient.statObject(
  304. StatObjectArgs.builder()
  305. .bucket(bucketName)
  306. .object(objectName)
  307. .build()).toString();
  308. }
  309. /**
  310. * 拷贝文件
  311. *
  312. * @param bucketName 存储桶
  313. * @param objectName 文件名
  314. * @param srcBucketName 目标存储桶
  315. * @param srcObjectName 目标文件名
  316. */
  317. public static ObjectWriteResponse copyFile(String bucketName, String objectName,
  318. String srcBucketName, String srcObjectName) throws Exception {
  319. return minioClient.copyObject(
  320. CopyObjectArgs.builder()
  321. .source(CopySource.builder().bucket(bucketName).object(objectName).build())
  322. .bucket(srcBucketName)
  323. .object(srcObjectName)
  324. .build());
  325. }
  326. /**
  327. * 删除文件
  328. * @param bucketName 存储桶
  329. * @param objectName 文件名称
  330. */
  331. public static void removeFile(String bucketName, String objectName) throws Exception {
  332. minioClient.removeObject(
  333. RemoveObjectArgs.builder()
  334. .bucket(bucketName)
  335. .object(objectName)
  336. .build());
  337. }
  338. /**
  339. * 批量删除文件
  340. * @param bucketName 存储桶
  341. * @param keys 需要删除的文件列表
  342. * @return
  343. */
  344. public static void removeFiles(String bucketName, List<String> keys) {
  345. List<DeleteObject> objects = new LinkedList<>();
  346. keys.forEach(s -> {
  347. objects.add(new DeleteObject(s));
  348. try {
  349. removeFile(bucketName, s);
  350. } catch (Exception e) {
  351. log.error("[Minio工具类]>>>> 批量删除文件,异常:", e);
  352. }
  353. });
  354. }
  355. /**
  356. * 获取文件外链
  357. * @param bucketName 存储桶
  358. * @param objectName 文件名
  359. * @param expires 过期时间 <=7 秒 (外链有效时间(单位:秒))
  360. * @return url
  361. * @throws Exception
  362. */
  363. public static String getPresignedObjectUrl(String bucketName, String objectName, Integer expires) throws Exception {
  364. GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder().expiry(expires).bucket(bucketName).object(objectName).build();
  365. return minioClient.getPresignedObjectUrl(args);
  366. }
  367. /**
  368. * 获得文件外链
  369. * @param bucketName
  370. * @param objectName
  371. * @return url
  372. * @throws Exception
  373. */
  374. public static String getPresignedObjectUrl(String bucketName, String objectName) throws Exception {
  375. GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder()
  376. .bucket(bucketName)
  377. .object(objectName)
  378. .method(Method.GET).build();
  379. return minioClient.getPresignedObjectUrl(args);
  380. }
  381. /**
  382. * 将URLDecoder编码转成UTF8
  383. * @param str
  384. * @return
  385. * @throws UnsupportedEncodingException
  386. */
  387. public static String getUtf8ByURLDecoder(String str) throws UnsupportedEncodingException {
  388. String url = str.replaceAll("%(?![0-9a-fA-F]{2})", "%25");
  389. return URLDecoder.decode(url, "UTF-8");
  390. }
  391. /****************************** Operate Files End ******************************/
  392. }


vue操作minio

总结

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