minio的上传,多文件上传
简介
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: minioadmin
secret-key: minioadmin
// 配置这个是为了传的bucker和自己设定的一样,不然容易传错,再取的时候取不到,下面代码有说明
bucket-names:
- xx.xx.xxx # 头衔图标
然后搞一下它的封装就好了:
private MinioClient client;
贴一下minio提供的方法
/**
* 创建bucket
*
* @param bucketName bucket名称
*/
@SneakyThrows
public void createBucket(String bucketName) {
if (!client.bucketExists(bucketName)) {
client.makeBucket(bucketName);
}
}
上面这块代码需要说明一下,minio要求在上传前需要在minio服务器创建bucket,说白了也就是文件夹,创建后会在服务器minio安装路径下创建bucket命名的文件夹。
/**
* 获取全部bucket
*/
@SneakyThrows
public List<Bucket> getAllBuckets() {
return client.listBuckets();
}
/**
* @param bucketName 获得单个bucket名称(做前后端上传的验证)
*/
@SneakyThrows
public Optional<Bucket> getBucket(String bucketName) {
return client.listBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();
}
/**
* @param bucketName 删除bucket名称
*/
@SneakyThrows
public void removeBucket(String bucketName) {
client.removeBucket(bucketName);
}
/**
* 根据文件前置查询文件
*
* @param bucketName bucket名称
* @param prefix 前缀
* @param recursive 是否递归查询
* @return MinioItem 列表
*/
@SneakyThrows
public 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
*/
@SneakyThrows
public String getObjectURL(String bucketName, String objectName, Integer expires) {
return client.presignedGetObject(bucketName, objectName, expires);
}
/**
* 获取文件
*
* @param bucketName bucket名称
* @param objectName 文件名称
* @return 二进制流
*/
@SneakyThrows
public 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:精神错乱猿,可以回复电影名,免费获取百度云链接电影。也会时常更新一些用到的技术。
下面是微信公众号图片:
上一篇: 初识 MinIO