概述
MinIO 是一款高性能、分布式的对象存储系统。它是一款软件产品, 可以100%的运行在标准硬件。即X86等低成本机器也能够很好的运行MinIO。MinIO与传统的存储和其他的对象存储不同的是:它一开始就针对性能要求更高的私有云标准进行软件架构设计。因为MinIO一开始就只为对象存储而设计。所以他采用了更易用的方式进行设计,它能实现对象存储所需要的全部功能,在性能上也更加强劲,它不会为了更多的业务功能而妥协,失去MinIO的易用性、高效性。这样的结果所带来的好处是:它能够更简单的实现局有弹性伸缩能力的原生对象存储服务。MinIO在传统对象存储用例(例如辅助存储,灾难恢复和归档)方面表现出色。同时,它在机器学习、大数据、私有云、混合云等方面的存储技术上也独树一帜。当然,也不排除数据分析、高性能应用负载、原生云的支持。
Docker 安装MinIO
- 创建目录和赋予权限
mkdir-p/app/cloud/minio/datamkdir-p/app/cloud/minio/configchmod-R777/app/cloud/minio/datachmod-R777/app/cloud/minio/config
- 拉取镜像
docker pull minio:minio
- 创建容器
dockerrun-d-p9000:9000--nameminio\-e"MINIO_ACCESS_KEY=minio"\-e"MINIO_SECRET_KEY=Aa123456"\-v/app/cloud/minio/data:/data\-v/app/cloud/minio/config:/root/.minio\minio/minioserver/data
- 浏览器访问http://192.168.1.6:9000账号 : minio 密码:Aa123456 登录右下角加号创建mybucket桶
image.png
- 开放 mybucket 读写权限
image.png
创建项目 操作 MinIO
- pom.xml 相关依赖
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>LATEST</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>7.0.1</version></dependency><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.6</version></dependency>
- 编辑配置文件
application.properties
修改MinIO相关配置
server.port=80spring.application.name=book-miniospring.thymeleaf.cache=falsespring.servlet.multipart.max-file-size=10MBspring.servlet.multipart.max-request-size=100MBminio.endpoint=http://192.168.1.6:9000minio.accesskey=miniominio.secretKey=Aa123456
- 连接 MinIO 配置
importlombok.Data; importorg.springframework.boot.context.properties.ConfigurationProperties; importorg.springframework.stereotype.Component; prefix="minio") (publicclassMinioProp { privateStringendpoint; privateStringaccesskey; privateStringsecretKey; }
- 创建
MinioClient
importio.minio.MinioClient; importio.minio.errors.InvalidEndpointException; importio.minio.errors.InvalidPortException; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.context.annotation.Bean; importorg.springframework.context.annotation.Configuration; publicclassMinioConfiguration { privateMinioPropminioProp; publicMinioClientminioClient() throwsInvalidPortException, InvalidEndpointException { MinioClientclient=newMinioClient(minioProp.getEndpoint(), minioProp.getAccesskey(), minioProp.getSecretKey()); returnclient; } }
- MinIO 查看桶列表,存入,删除 操作
MinioController
importcom.alibaba.fastjson.JSON; importcom.lab.book.minio.common.Res; importio.minio.MinioClient; importio.minio.ObjectStat; importio.minio.PutObjectOptions; importio.minio.Result; importio.minio.messages.Item; importlombok.extern.slf4j.Slf4j; importorg.apache.commons.io.IOUtils; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.ui.ModelMap; importorg.springframework.web.bind.annotation.*; importorg.springframework.web.multipart.MultipartFile; importjavax.servlet.http.HttpServletResponse; importjava.io.IOException; importjava.io.InputStream; importjava.net.URLEncoder; importjava.text.DecimalFormat; importjava.util.*; publicclassMinioController { privateMinioClientminioClient; privatestaticfinalStringMINIO_BUCKET="mybucket"; "/list") (publicList<Object>list(ModelMapmap) throwsException { Iterable<Result<Item>>myObjects=minioClient.listObjects(MINIO_BUCKET); Iterator<Result<Item>>iterator=myObjects.iterator(); List<Object>items=newArrayList<>(); Stringformat="{'fileName':'%s','fileSize':'%s'}"; while (iterator.hasNext()) { Itemitem=iterator.next().get(); items.add(JSON.parse(String.format(format, item.objectName(), formatFileSize(item.size())))); } returnitems; } "/upload") (publicResupload( (name="file", required=false) MultipartFile[] file) { Resres=newRes(); res.setCode(500); if (file==null||file.length==0) { res.setMessage("上传文件不能为空"); returnres; } List<String>orgfileNameList=newArrayList<>(file.length); for (MultipartFilemultipartFile : file) { StringorgfileName=multipartFile.getOriginalFilename(); orgfileNameList.add(orgfileName); try { InputStreamin=multipartFile.getInputStream(); minioClient.putObject(MINIO_BUCKET, orgfileName, in, newPutObjectOptions(in.available(), -1)); in.close(); } catch (Exceptione) { log.error(e.getMessage()); res.setMessage("上传失败"); returnres; } } Map<String, Object>data=newHashMap<String, Object>(); data.put("bucketName", MINIO_BUCKET); data.put("fileName", orgfileNameList); res.setCode(200); res.setMessage("上传成功"); res.setData(data); returnres; } "/download/{fileName}") (publicvoiddownload(HttpServletResponseresponse, ("fileName") StringfileName) { InputStreamin=null; try { ObjectStatstat=minioClient.statObject(MINIO_BUCKET, fileName); response.setContentType(stat.contentType()); response.setHeader("Content-Disposition", "attachment;filename="+URLEncoder.encode(fileName, "UTF-8")); in=minioClient.getObject(MINIO_BUCKET, fileName); IOUtils.copy(in, response.getOutputStream()); } catch (Exceptione) { log.error(e.getMessage()); } finally { if (in!=null) { try { in.close(); } catch (IOExceptione) { log.error(e.getMessage()); } } } } "/delete/{fileName}") (publicResdelete( ("fileName") StringfileName) { Resres=newRes(); res.setCode(200); try { minioClient.removeObject(MINIO_BUCKET, fileName); } catch (Exceptione) { res.setCode(500); log.error(e.getMessage()); } returnres; } privatestaticStringformatFileSize(longfileS) { DecimalFormatdf=newDecimalFormat("#.00"); StringfileSizeString=""; StringwrongSize="0B"; if (fileS==0) { returnwrongSize; } if (fileS<1024) { fileSizeString=df.format((double) fileS) +" B"; } elseif (fileS<1048576) { fileSizeString=df.format((double) fileS/1024) +" KB"; } elseif (fileS<1073741824) { fileSizeString=df.format((double) fileS/1048576) +" MB"; } else { fileSizeString=df.format((double) fileS/1073741824) +" GB"; } returnfileSizeString; } }
- Res 文件
importlombok.AllArgsConstructor; importlombok.NoArgsConstructor; importjava.io.Serializable; publicclassResimplementsSerializable { privatestaticfinallongserialVersionUID=1L; privateIntegercode; privateObjectdata=""; privateStringmessage=""; }
- 路由文件
RouterController
importorg.springframework.stereotype.Controller; importorg.springframework.web.bind.annotation.GetMapping; publicclassRouterController { "/", "/index.html"}) ({publicStringindex() { return"index"; } "/upload.html"}) ({publicStringupload() { return"upload"; } }
- 前端 列表页面 src\main\resources\templates\index.html
<!DOCTYPEhtml><htmllang="zh-cn"><head><metacharset="utf-8"/><title>图片列表</title><linkrel="stylesheet"href="http://cdn.staticfile.org/element-ui/2.13.1/theme-chalk/index.css"></head><body><divid="app"><el-linkicon="el-icon-upload"href="/upload.html">上传图片</el-link><br/><el-table :data="results"stripestyle="width: 60%"-click="preview"><el-table-columntype="index"width="50"></el-table-column><el-table-columnprop="fileName"label="文件名"width="180"></el-table-column><el-table-columnprop="fileSize"label="文件大小"></el-table-column><el-table-columnlabel="操作"><templateslot-scope="scope"><a :href="'/download/' + scope.row.fileName + ''"class="el-icon-download">下载</a><a :href="'/delete/' + scope.row.fileName + ''"="deleteFile($event,scope.$index,results)"class="el-icon-delete">删除</a></template></el-table-column></el-table><br/><el-linkicon="el-icon-picture">预览图片</el-link><br/><divclass="demo-image__preview"v-if="previewImg"><el-imagestyle="width: 100px; height: 100px" :src="imgSrc" :preview-src-list="imgList"></el-image></div></div><scriptsrc="http://cdn.staticfile.org/vue/2.6.11/vue.min.js"></script><scriptsrc="http://cdn.staticfile.org/axios/0.19.2/axios.min.js"></script><scriptsrc="http://cdn.staticfile.org/element-ui/2.13.1/index.js"></script><script>newVue({ el: '#app', data: { bucketURL: 'http://192.168.1.6:9000/mybucket/', previewImg: true, results: [], imgSrc: '', imgList: [] }, methods: { init() { axios.get('/list').then(response=> { this.results=response.data; if (this.results.length==0) { this.imgSrc=''; this.previewImg=false; } else { for (vari=0; i<this.results.length; i++) { this.imgList.push(this.bucketURL+this.results[i].fileName); if (i==0) { this.imgSrc=this.bucketURL+this.results[0].fileName; } } } }); }, preview(row, event, column) { this.imgSrc=this.bucketURL+row.fileName; this.previewImg=true; }, deleteFile(e,index,list) { axios.delete(e.target.href, {}).then(res=> { if (res.data.code==200) { this.$message('删除成功!'); list.splice(index, 1); this.previewImg=false; } else { this.$message('删除失败!'); } }); } }, mounted() { this.init(); } }); </script></body></html>
- 前端上传页面 src\main\resources\templates\upload.html
<!DOCTYPEhtml><htmllang="zh-CN"><head><metacharset="UTF-8"><title>图片上传</title><linkrel="stylesheet"type="text/css"href="http://cdn.staticfile.org/webuploader/0.1.5/webuploader.css"><scripttype="text/javascript"src="https://cdn.staticfile.org/jquery/3.5.0/jquery.min.js"></script><scripttype="text/javascript"src="http://cdn.staticfile.org/webuploader/0.1.5/webuploader.min.js"></script></head><body><divid="uploader-demo"><divid="fileList"class="uploader-list"></div><divid="filePicker">选择图片</div></div><br/><ahref="/index.html">返回图片列表页面</a><scripttype="text/javascript">varuploader=WebUploader.create({ auto: true, swf: 'http://cdn.staticfile.org/webuploader/0.1.5/Uploader.swf', server: '/upload', pick: '#filePicker', accept: { title: 'Images', extensions: 'gif,jpg,jpeg,bmp,png', mimeTypes: 'image/*' } }); uploader.on('fileQueued', function (file) { var$li=$( '<div id="'+file.id+'" class="file-item thumbnail">'+'<img>'+'<div class="info">'+file.name+'</div>'+'</div>' ), $img=$li.find('img'); var$list=$("#fileList"); $list.append($li); uploader.makeThumb(file, function (error, src) { if (error) { $img.replaceWith('<span>不能预览</span>'); return; } $img.attr('src', src); }, 100, 100); }); uploader.on('uploadProgress', function (file, percentage) { var$li=$('#'+file.id), $percent=$li.find('.progress span'); if (!$percent.length) { $percent=$('<p class="progress"><span></span></p>') .appendTo($li) .find('span'); } $percent.css('width', percentage*100+'%'); }); uploader.on('uploadSuccess', function (file) { $('#'+file.id).addClass('upload-state-done'); }); uploader.on('uploadError', function (file) { var$li=$('#'+file.id), $error=$li.find('div.error'); if (!$error.length) { $error=$('<div class="error"></div>').appendTo($li); } $error.text('上传失败'); }); uploader.on('uploadComplete', function (file) { $('#'+file.id).find('.progress').remove(); }); </script></body></html>
运行项目
image.png
- 上传页面,批量上传图片
image.png
- 上传效果
image.png
- 查看 MinIO Browser
image.png
- 列表页面,下载,删除,预览操作
image.png
- 预览图片
image.png
- 删除图片
image.png
- 下载图片
image.png