简介
MiniO 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。
正题
最近公司在做一个新项目,需要传输大量的图片或附件(下面都以图片叙述),如果按照传统的文件上传,有些繁琐了,作为一个合格的上进的程序猿,不应该技术停滞不前,要向前看嘛,所以研究了下较前沿的MiniO,再结合业务发现不错,所以在这里总结了下从安装到开发的过程。
一 地址
自行去minio官网 https://min.io/ 这个不多说
二 安装-启动minio服务
windows
安装完后会生成一下两个文件夹
点进去“server”文件夹,shift+右键,点击‘在此处打开命令窗口’
在终端输入:
minio.exe server D:\minio\file --address=0.0.0.0:9006这里注意,minio默认启动语句为minio.exe server D:\minio\file,此时它会生成默认端口号:9000,我不想用9000端口,我要用9006,所以加后缀:--address=0.0.0.0:9006
执行完毕,如下图:
用户名和密码默认同为:minioadmin
如果想修改用户名或密码,可参考博文:
https://blog.csdn.net/weixin_44981485/article/details/106809902
ok,此刻你的minio已经启动完毕,浏览器输入:127.0.0.1:+端口号,可登入可视化界面,这个不做过多介绍了,登入进去就是你传输进来的图片,储存的文件位置就是上图的file文件夹。
linux
安装自行百度吧,过程差不多都一个样,本来也是不想介绍安装的。
三 开发
重头戏来了。
首先思路,前面说到不想每个业务表都搞个存储图片路径的字段,所以得建一个中间表,所以需要查询上传图片的业务,都根据上传时候的唯一标示到这个表里取一下,根据现在系统的业务,我的存储路径的表结构关键字段大体如下:
uuid_name:取服务器上的图片名称bucket_name:这个需要在开发时进行配置,说白了就是minio服务器存储图片文件的文件夹,这个后面开发会具体说到business_id:唯一标示id,我们的新产品数据库id使用的是uuid,比如说我想上传一个医生的头像,上传完成后,图片的url地址会存储在我的中间表url字段中,我就能通过医生id,来找到这个医生的头像。url:存储图片的可访问地址
在我项目的业务里,上传分为两种业务模式:
一:点击直接上传。也就是说要把图片传到我的新建的业务表中,同时也要上传到minio服务器。用户当场没有修改的机会,选中即上传。
二:间接上传。先把图片上传到minio服务器,我会返回图片的存储url给前端,这时候用户可以对图片做修改,修改后再返回给前端修改后的url,最后用户统一点提交按钮,完成业务表图片url的存储。
下面是开发实现时间:
我把minio给提供的api方法,做了一个封装,做成了公共调用模块,下面我会拆出来展示具体的minio的api的方法,如果需要看我封装的公共模块代码,业务模块就不给了,下面都有说,直接关注微信公众号:精神错乱猿,最下面也有公众号图片,公众号首页面回复:minio 就可以获取到了。
首先,先引入minio依赖,这没什么好说(我项目是springboot+maven,所以引入依赖就可以了,如果不是maven项目,那就去找下minio的jar吧)
依赖:
<dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>3.0.12</version></dependency>
minio服务器yml配置:
minio:// minio服务器地址+多口url: http://127.0.0.0:9006// 用户名密码access-key: minioadminsecret-key: minioadmin// 配置这个是为了传的bucker和自己设定的一样,不然容易传错,再取的时候取不到,下面代码有说明bucket-names:- xx.xx.xxx # 头衔图标
然后搞一下它的封装就好了:
private MinioClient client;
贴一下minio提供的方法
/*** 创建bucket** @param bucketName bucket名称*/@SneakyThrowspublic void createBucket(String bucketName) {if (!client.bucketExists(bucketName)) {client.makeBucket(bucketName);}}
上面这块代码需要说明一下,minio要求在上传前需要在minio服务器创建bucket,说白了也就是文件夹,创建后会在服务器minio安装路径下创建bucket命名的文件夹。
/*** 获取全部bucket*/@SneakyThrowspublic List<Bucket> getAllBuckets() {return client.listBuckets();}/*** @param bucketName 获得单个bucket名称(做前后端上传的验证)*/@SneakyThrowspublic Optional<Bucket> getBucket(String bucketName) {return client.listBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();}/*** @param bucketName 删除bucket名称*/@SneakyThrowspublic void removeBucket(String bucketName) {client.removeBucket(bucketName);}/*** 根据文件前置查询文件** @param bucketName bucket名称* @param prefix 前缀* @param recursive 是否递归查询* @return MinioItem 列表*/@SneakyThrowspublic List<MinioItem> getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive) {List<MinioItem> objectList = new ArrayList<>();Iterable<Result<Item>> objectsIterator = client.listObjects(bucketName, prefix, recursive);while (objectsIterator.iterator().hasNext()) {objectList.add(new MinioItem(objectsIterator.iterator().next().get()));}return objectList;}/*** 获取文件外链** @param bucketName bucket名称* @param objectName 文件名称* @param expires 过期时间 <=7* @return url*/@SneakyThrowspublic String getObjectURL(String bucketName, String objectName, Integer expires) {return client.presignedGetObject(bucketName, objectName, expires);}/*** 获取文件** @param bucketName bucket名称* @param objectName 文件名称* @return 二进制流*/@SneakyThrowspublic InputStream getObject(String bucketName, String objectName) {return client.getObject(bucketName, objectName);}/*** 上传文件** @param bucketName bucket名称* @param objectName 文件名称* @param stream 文件流* @throws Exception https://docs.minio.io/cn/java-client-api-reference.html#putObject*/public void putObject(String bucketName, String objectName, InputStream stream) throws Exception {this.createBucket(bucketName);client.putObject(bucketName, objectName, stream, stream.available(), "application/octet-stream");}/*** 上传文件** @param bucketName bucket名称* @param objectName 文件名称* @param stream 文件流* @param size 大小* @param contextType 类型* @throws Exception https://docs.minio.io/cn/java-client-api-reference.html#putObject*/public void putObject(String bucketName, String objectName, InputStream stream, long size, String contextType) throws Exception {client.putObject(bucketName, objectName, stream, size, contextType);}/*** 获取文件信息** @param bucketName bucket名称* @param objectName 文件名称* @throws Exception https://docs.minio.io/cn/java-client-api-reference.html#statObject*/public ObjectStat getObjectInfo(String bucketName, String objectName) throws Exception {return client.statObject(bucketName, objectName);}/*** 删除文件** @param bucketName bucket名称* @param objectName 文件名称* @throws Exception https://docs.minio.io/cn/java-client-api-reference.html#removeObject*/public void removeObject(String bucketName, String objectName) throws Exception {client.removeObject(bucketName, objectName);}
正式代码实现,只列举上面提到的业务逻辑一的代码,业务逻辑二无非就是把下面代码做分离:
/** 这里需要前端传bucketName和businessId*///bucketName 文件夹名称// businessId 唯一业务id,区分是谁传的附件,上面逻辑有提到过//判断yml中是否有这个已写的BucketName,防止有业务场景需要前端传bucketName,导致名称写错,取得时候取不到if (!minioTemplate.getBucketNames().contains(bucketName)) {return error("非法的bucketName!");}// 生成图片名String fileName = IdWorker.getIdStr() + StrUtil.DOT + FileUtil.extName(file.getOriginalFilename());// 我的业务表Attachment attachment = new Attachment();try {//创建一个MinIO的Java客户端MinioClient minioClient = new MinioClient(MinioDeployConstant.ENDPOINT_TYPE, MinioDeployConstant.ACCESS_KEY_TYPE, MinioDeployConstant.SECRET_KEY_TYPE);boolean isExist = minioClient.bucketExists(bucketName);if (!isExist) {/**第一次搞没写这块代码,导致bucker创建了,图片也上传上去了,但是只能下,却不能预览需要登陆minio服务器后,把对应的bucker设置权限,这样太麻烦了,直接创建即设置权限 */minioClient.makeBucket(bucketName);minioClient.setBucketPolicy(bucketName, "*.*", PolicyType.READ_ONLY);}// 使用putObject上传一个文件到存储桶中minioClient.putObject(bucketName, fileName, file.getInputStream(), file.getContentType());// 获取图片存贮在服务器上的地址String url = MinioDeployConstant.ENDPOINT_TYPE + "/" + bucketName + "/" + fileName;// 上传minioTemplate.putObject(bucketName, fileName, file.getInputStream());// 业务数据保存attachment.setUrl(url);attachment.setBucketName(bucketName);attachment.setUuidName(fileName);attachment.setBusinessId(businessId);infAttachmentService.save(attachment);
删除:
minioTemplate.removeObject(bucketName, fileName);
多文件上传,在网上也没找到相关的demo,我自己采用流的方式:
List<MultipartFile> fileList = ((MultipartHttpServletRequest) request).getFiles("file");if (CollectionUtil.isEmpty(fileList)) {return new R().error("上传的文件列表不能为空!");}if (fileList.size() > 9) {return new R().error("上传失败,一次最多上传9个文件!");}String bucketName = request.getParameter("bucketName");if (!minioTemplate.getBucketNames().contains(bucketName)) {return new R().error("非法的bucketName");}Long businessId = Long.valueOf(request.getParameter("businessId"));if (numberIsNullOrZero(businessId)) {return new R().error("businessId is not find");}List<InfAttachment> attachmentList = new ArrayList<>();for (MultipartFile file : fileList) {String fileName = IdWorker.getIdStr() + StrUtil.DOT + FileUtil.extName(file.getOriginalFilename());Attachment attachment = new Attachment();minioTemplate.putObject(bucketName, fileName, file.getInputStream());attachment.setBucketName(bucketName);attachment.setUuidName(fileName);attachment.setBusinessId(businessId);attachmentList.add(attachment);}return success("成功");
对于minio的更新操作,好像minio没有提供,还是我看api不够仔细。不过这都可以克服,无非就是用现有的方法先删除在上传就是了,业务数据也是如此,灵活变化。
对于minio的介绍就到这里了。
可以关注微信gongzhonghao:精神错乱猿,可以回复电影名,免费获取百度云链接电影。也会时常更新一些用到的技术。
下面是微信公众号图片:
