本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。
小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
前言
- 在线音乐戳我呀!
- 音乐博客源码上线啦!
- 最近在整理自己的在线音乐(因为最近换服务器了),发现上传的图片文件很杂乱,如:音乐上传到minio中(文件服务器),IT知识模块的图片上传在Node指定的文件夹中,其他模块的文件又是Node的另外一个文件夹。
- 就想着能不能全部都由一个来管理这些文件,优先想到之前实习的时候公司(dpf)搭建个文件服务器,也用了一下确实是挺方便的,也就是今天的主角 -- Minio。
- 其实自己的在线音乐已经用了很久了,只不过没有说对全部文件都归其管理,再加上现在应用部署在Docker上,不知道为什么Node一直映射失败,但Minio却映射得出来,这也是一开始初心想文件都放在minio中。
- 接下来会分享如何搭建、可能会遇到的问题,一五一十盘出。
- Are you ready ?
假期快乐。
先来张图效果。
界面直观、可视化界面、方便管理、上传下载流程简单,还能设置权限、失效时间等等。
Minio
-
是什么
-
怎么下载安装使用
-
代码层面如何开发
一、Minio是什么?
Minio是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。
MinIO是一个非常轻量的服务,可以很简单的和其他应用的结合,类似 NodeJS, Redis 或者 MySQL。
同时也支持多种语言客户端集成开发,如JavaScript 、Java、Python、Golang、.NET
。
简而言之就是:文件服务器,存放管理我们的文件。
二、Minio怎么下载安装使用?
我们拿window、docker环境进行手把手教学。目前这两种环境较多人使用吧。(😁其实我也是只用这两种环境,其他环境不敢有话语权)
2.1 Windows版
2.1.1 下载
-
百度云下载软件:链接:pan.baidu.com/s/1pu-qyW4Q… 提取码:n5le
下载好了之后,文件后缀是exe。
2.1.2 使用
进入minio.exe所在目录。
cmd走起。
minio.exe server G:\tsblog\minio
参数说明:G:\tsblog\minio
这是文件上传之后的存储目录(我们图片上传到哪)
运行成功。会看到下面的界面。
这里minio会给出ACCESS-KEY 和 SECRET-KEY,供后台管理登录使用。
2.1.3 登录使用
打开浏览器,使用命令行界面给出的地址都可以登录。
默认端口9000:http://localhost:9000/
输入ACCESS-KEY 和 SECRET-KEY即可登录minio的管理界面了。
2.1.4 创建桶和上传图片
在后台管理界面你可以上创建你的Bucket(桶),可以理解为一个文件夹用来存放图片。桶创建成功之后就可以上传图片了。如下图:
左边是桶(文件夹),右边是桶中的文件。
在文件列表的右边可以复制出图片的访问地址,在浏览器中就可以访问图片了。这个时候的图片地址是带过期时间和密钥的。
2.2 Docker版
我们会将minio部署到线上,众所周知,大部分服务器都是Linux系统,像我自己还装着Docker管理我的应用程序。
2.2.1 下载MinIO 镜像包
docker pull minio/minio
2.2.2 启动MinIO 镜像
使用docker run -p 9000:9000 minio/minio server /data
端口可以自定义修改,默认会生成ACCESS_KEY与SECRET_KEY,看下图提示生成的是minioadmin/minioadmin
当然,我们正常不会这样子启动,肯定是要自定义账号密码。还要把minio文件给映射出来(这才是重要)
使用以下脚本启动MinIO镜像,生成永久MinIO的容器。
一般复制这串代码即可适用大部分情况。
docker run -p 9000:9000 --name minio -d
-e "MINIO_ACCESS_KEY=admin"
-e "MINIO_SECRET_KEY=123456"
-v /home/minio:/data
-v /home/minio/config:/root/.minio
minio/minio server /data
参数说明
-
run:启动镜像
-
-p 9000:9000:设置端口,并映射给外面的端口
-
-d:后台启动
-
-e "MINIO_ACCESS_KEY=admin" -e "MINIO_SECRET_KEY=123456" :设置账号密码(MINIO_ACCESS_KEY:密码Key;MINIO_SECRET_KEY:密码值)
-
-v /home/minio:/data -v /home/minio/config:/root/.minio :虚拟配置目录,文件映射出来,我的minio文件夹资源放在/home/minio/下,若要改的话,要改下这里的路径
-
minio/minio server:这是我们一开始拉取下来的minio镜像名
有小伙子不知道文件映射出来是什么意思,请允许我解析一波。
我们把minio部署在docker中运行。
比如我们在minio中上传一个文件,那我们在linux服务器上怎么查看这个文件在哪?
这个时候就要文件映射,通俗的说就是将minio中上传的文件给映射到linux上,这样子我们在linux上就可以实时同步看到我们上传的文件。
2.2.3 查看MinIO 镜像
查看minio有没有启动成功:docker ps
登录客户端也可以登录成功!
三、Minio实战(代码层面如何开发)
我们知道,MinIO是一个非常轻量的服务,可以支持多种语言客户端集成开发,如JavaScript 、Java、Python、Golang、.NET
。
我们拿JavaScript 、Java进行手把手教学。目前这两种环境较多人使用吧。(😁其实我也是只用这两种环境,其他语言咱也不敢有话语权)
3.1 JavaScript引入minio并使用
在线音乐中的【IT知识】模块文件本是由node,以原生上传文件保存到服务器本地中,但后面我想把全部文件统一管理,在前几天换做上传到minio中。
本例子用koa2作为JavaScript的后台。
3.1.1 koa2安装minio
npm i minio --save-dev
3.1.2 新建minio.js
配置minio账号密码端口。
var Minio = require("minio");
let endPoint = "127.0.0.1";
var minioClient = new Minio.Client({
endPoint,
port: 9000,
useSSL: false,
accessKey: "admin",
secretKey: "123456",
});
module.exports = minioClient;
于是就可以在js接口中开始使用minio了,当然在写之前是需要引入上面定义的minio.js文件。
下面简单列出几个常用的方法操作。
3.1.3 列出全部存储桶(文件夹)
minioClient.listBuckets().then((res) => {
console.log(res);
})
3.1.4 列出一个存储桶下的所有文件
var stream = minioClient.listObjectsV2("music", "", true);
let arr = [];
stream.on("data", (obj) => {
arr.push(obj); // 每一个文件都会去执行该data方法,类似于遍历
});
stream.on("end", function () {
console.log(arr);
// 全部遍历完成后,在这里会打印出该music文件夹下的所有文件
});
stream.on("error", function (err) {
console.log(err);
});
3.1.5 判断是否有这个存储桶
minioClient.bucketExists("music", function (err, exists) {
if (err) {
console.log(err);
}
if (exists) {
console.log('存在')
} else {
console.log('不存在')
}
});
3.1.6 创建一个新的存储桶
minioClient.makeBucket("music", "us-east-1", function (err) {
if (err) {
console.log("Error creating bucket.", err);
}
console.log('创建成功')
});
3.1.7 下载对象并将其保存为本地文件系统中的文件
minioClient.fGetObject(
"music",
"周杰伦 - Mojito202006152354441192.flac",
"H:/85/周杰伦 - Mojito202006152354441192.flac",
function (err) {
if (err) {
console.log(err);
}
console.log('下载成功');
}
);
3.1.8 下载的临时url
minioClient.presignedUrl("GET", "music", "周.flac", 24 * 60 * 60, function (
err,
presignedUrl
) {
if (err) {
console.log(err);
}
console.log(presignedUrl);
});
3.1.9 往存储桶上传一个文件
上传文件的时候要注意文件名字可能会重复,所以这里加了随机数作为文件名字;
当然,规则都是人定的,可以根据自己的场景制订不同写法;
可以看到minioClient.putObject()函数中打印的那些字段,一般都会存在数据库中。
const bucketsName = "music", file = File对象;
const path = file[0][1].path;
const fileName = file[0][1].name;
const type = file[0][1].type;
const nowDate = moment(new Date()).format("YYYYMMDDHHmmss");
const random = Math.round((Math.random() * 9 + 1) * 1000);
const fileNameRandom = nowDate + random;
const fileSuffix = fileName.substring(fileName.lastIndexOf(".")); // 文件后缀名字
const filePrefix = fileName.substring(0, fileName.lastIndexOf(".")); // 文件名字前面那段
let objectName = filePrefix.length > 20
? filePrefix.substring(0, 20) + fileNameRandom + fileSuffix
: filePrefix + fileNameRandom + fileSuffix;
var fileStream = fs.createReadStream(path);
fs.stat(path, function (err, stats) {
if (err) {
console.log(err);
}
minioClient.putObject(
bucketsName,
objectName,
fileStream,
stats.size,
{ "Content-Type": type },
function (err, etag) {
if (err) {
console.log(err);
}
console.log({
fileName,
bucketsName,
fileContentType: type,
size: stats.size,
fileNameRandom,
url: objectName,
});
}
);
});
});
3.1.10 获取文件信息
minioClient.statObject("test", '9.png', function (err, stat) {
if (err) {
console.log(err);
}
console.log(stat);
});
3.1.11 移除一个对象
minioClient.removeObject("test", '9.png', function (e) {
if (e) {
console.log("Unable to remove Objects ", e);
}
console.log('移除成功');
});
3.1.12 删除objectsList 中的所有对象
minioClient.removeObjects("test", ['9.png'], function (e) {
if (e) {
console.log("Unable to remove Objects ", e);
}
console.log('移除成功');
});
我在github上写了个minio工具类,懒人可以借鉴一下(可以的话给个⭐Star):github.com/git-Dignity…
minio官方文档中也有明确的操作例子:docs.min.io/docs/javasc…
3.2 Java引入minio并使用
在线音乐中的【音乐】模块文件一直都是由minio管理的。
其实java和JavaScript的写法基本一样。
大同小异。
3.2.1 java安装minio
pom.xml文件引入依赖
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>3.0.10</version>
</dependency>
3.2.2 在application.yml
我喜欢把配置参数(账号密码)写在application.yml文件中,规范管理。
#文件服务器
minio:
endpoint: http://39.108.185.253 # 外网
accessKey: admin
secretKey: 123456
endpointIn: http://127.0.0.1:9000 # 内网
3.2.3 创建实体类Minio.java
package com.example.bkapi.common.entity;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "minio")
public class Minio {
private String endpoint;
private String accessKey;
private String secretKey;
private String endpointIn;
public String getEndpoint() {
return endpoint;
}
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
public String getAccessKey() {
return accessKey;
}
public void setAccessKey(String accessKey) {
this.accessKey = accessKey;
}
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public String getEndpointIn() {
return endpointIn;
}
public void setEndpointIn(String endpointIn) {
this.endpointIn = endpointIn;
}
}
于是就可以在接口中开始使用minio了,当然在写之前是需要引入上面定义的Minio实体类。
private static Minio minio;
下面简单列出几个常用的方法操作。
3.2.4 创建minioClient
这里的写法其实和上面的3.1.2 新建minio.js 是一样的,为了创建Minio实例。
下面的minio.getEndpoint()
获取到application.yml定义的http://39.108.185.253
下面的minio.getAccessKey()
获取到application.yml定义的admin
下面的minio.getSecretKey()
获取到application.yml定义的123456
大家的参数可以写死在这里,看个人习惯。
public static MinioClient createMinioClient() throws InvalidPortException, InvalidEndpointException {
MinioClient minioClient = new MinioClient(minio.getEndpoint(), minio.getAccessKey(), minio.getSecretKey());
return minioClient;
}
3.2.5 创建内网minioClient
下面的minio.getEndpoint()
获取到application.yml定义的http://127.0.0.1:9000
其他参数和上方createMinioClient
方法一样。
public static MinioClient createInMinioClient() throws InvalidPortException, InvalidEndpointException {
MinioClient minioClient = new MinioClient(minio.getEndpointIn(), minio.getAccessKey(), minio.getSecretKey());
return minioClient;
}
3.2.6 创建具有给定区域的新存储桶
先判断有没有这个桶,没有在进行创建新桶。
public static void makeBucket(String bucketName){
try {
MinioClient minioClient = createInMinioClient();
// Create bucket if it doesn't exist.
boolean found = minioClient.bucketExists(bucketName);
if (found) {
System.out.println(bucketName + " already exists");
} else {
// Create bucket 'my-bucketname'.
minioClient.makeBucket(bucketName);
System.out.println(bucketName + " is created successfully");
}
} catch (MinioException e) {
System.out.println("Error occurred: " + e);
} catch (Exception e) {
e.printStackTrace();
}
}
3.2.7 列出所有桶
public static List<Bucket> listBuckets(){
List<Bucket> bucketList = null;
try {
MinioClient minioClient = createInMinioClient();
// List buckets that have read access.
bucketList = minioClient.listBuckets();
for (Bucket bucket : bucketList) {
System.out.println(bucket.creationDate() + ", " + bucket.name());
}
} catch (MinioException e) {
System.out.println("Error occurred: " + e);
} catch (Exception e) {
e.printStackTrace();
}
return bucketList;
}
3.2.8 检查是否存在存储桶
public static boolean bucketExists(String bucketName){
boolean found = false;
try {
MinioClient minioClient = createInMinioClient();
// Check whether 'my-bucketname' exists or not.
found = minioClient.bucketExists(bucketName);
if (found) {
System.out.println(bucketName + " exists");
} else {
System.out.println(bucketName + " does not exist");
}
} catch (MinioException e) {
System.out.println("Error occurred: " + e);
} catch (Exception e) {
e.printStackTrace();
}
return found;
}
3.2.9 删除一个桶
先判断桶存不存在,存在则删除该桶。
注意: - removeBucket不会删除存储桶内的对象。需要使用removeObject API删除对象。
public static void removeBucket (String bucketName){
try {
MinioClient minioClient = createInMinioClient();
// Check if my-bucket exists before removing it.
boolean found = minioClient.bucketExists(bucketName);
if (found) {
// Remove bucket my-bucketname. This operation will succeed only if the bucket is empty.
minioClient.removeBucket(bucketName);
System.out.println(bucketName + " is removed successfully");
} else {
System.out.println(bucketName + " does not exist");
}
} catch(MinioException e) {
System.out.println("Error occurred: " + e);
} catch (Exception e) {
e.printStackTrace();
}
}
3.2.10 列出给定存储桶中的对象信息
先判断桶存不存在,存在则列出给定存储桶中的对象信息。
public static Iterable<Result<Item>> listObjects(String bucketName){
Iterable<Result<Item>> myObjects = null;
try {
MinioClient minioClient = createInMinioClient();
// Check whether 'mybucket' exists or not.
boolean found = minioClient.bucketExists(bucketName);
if (found) {
// List objects from 'my-bucketname'
myObjects = minioClient.listObjects(bucketName);
for (Result<Item> result : myObjects) {
Item item = result.get();
System.out.println(item.lastModified() + ", " + item.size() + ", " + item.objectName());
}
} else {
System.out.println(bucketName + " does not exist");
}
} catch (MinioException e) {
System.out.println("Error occurred: " + e);
} catch (Exception e) {
e.printStackTrace();
}
return myObjects;
}
3.2.11 列出给定存储桶和前缀中的对象信息
先判断桶存不存在,存在则列出给定存储桶和前缀中的对象信息。
public static Iterable<Result<Item>> listObjects(String bucketName,String prefix){
Iterable<Result<Item>> myObjects = null;
try {
MinioClient minioClient = createInMinioClient();
// Check whether 'mybucket' exists or not.
boolean found = minioClient.bucketExists(bucketName);
if (found) {
// List objects from 'my-bucketname'
myObjects = minioClient.listObjects(bucketName,prefix);
for (Result<Item> result : myObjects) {
Item item = result.get();
System.out.println(item.lastModified() + ", " + item.size() + ", " + item.objectName());
}
} else {
System.out.println(bucketName + " does not exist");
}
} catch (MinioException e) {
System.out.println("Error occurred: " + e);
} catch (Exception e) {
e.printStackTrace();
}
return myObjects;
}
3.2.12 将对象信息列为Iterable 在给定的桶,前缀和递归标志
public static Iterable<Result<Item>> listObjects(String bucketName, String prefix, boolean recursive){
Iterable<Result<Item>> myObjects = null;
try {
MinioClient minioClient = createInMinioClient();
// Check whether 'mybucket' exists or not.
boolean found = minioClient.bucketExists(bucketName);
if (found) {
// List objects from 'my-bucketname'
myObjects = minioClient.listObjects(bucketName,prefix,recursive);
for (Result<Item> result : myObjects) {
Item item = result.get();
System.out.println(item.lastModified() + ", " + item.size() + ", " + item.objectName());
}
} else {
System.out.println(bucketName + " does not exist");
}
} catch (MinioException e) {
System.out.println("Error occurred: " + e);
} catch (Exception e) {
e.printStackTrace();
}
return myObjects;
}
3.2.13 将对象下载为流
public static InputStream getObject(String bucketName, String objectName){
InputStream stream = null;
try {
MinioClient minioClient = createInMinioClient();
// Check whether the object exists using statObject().
// If the object is not found, statObject() throws an exception,
// else it means that the object exists.
// Execution is successful.
minioClient.statObject(bucketName, objectName);
// Get input stream to have content of 'my-objectname' from 'my-bucketname'
stream = minioClient.getObject(bucketName, objectName);
} catch (MinioException e) {
System.out.println("Error occurred: " + e);
} catch (Exception e) {
e.printStackTrace();
}
return stream;
}
3.2.14 将对象下载并保存为本地文件系统中的文件
public static void getObject(String bucketName, String objectName, String fileName){
try {
MinioClient minioClient = createInMinioClient();
// Check whether the object exists using statObject().
// If the object is not found, statObject() throws an exception,
// else it means that the object exists.
// Execution is successful.
minioClient.statObject(bucketName, objectName);
// Gets the object's data and stores it in photo.jpg
minioClient.getObject(bucketName, objectName, fileName);
} catch (MinioException e) {
System.out.println("Error occurred: " + e);
} catch (Exception e) {
e.printStackTrace();
}
}
3.2.15 通过InputStream上传对象
public static ObjectStat putObject(String bucketName, String objectName, InputStream stream, long size, String contentType){
try {
MinioClient minioClient = createInMinioClient();
// 创建对象
minioClient.putObject(bucketName, objectName, stream, size, contentType);
System.out.println(objectName + " is uploaded successfully");
} catch(MinioException e) {
System.out.println("Error occurred: " + e);
} catch (Exception e) {
e.printStackTrace();
}
return statObject(bucketName,objectName);
}
3.2.16 通过文件上传到对象中
public static int putObject(String bucketName, String objectName, String fileName){
try {
MinioClient minioClient = createInMinioClient();
minioClient.putObject(bucketName, objectName, fileName);
System.out.println(objectName + " is uploaded successfully");
} catch(MinioException e) {
System.out.println("Error occurred: " + e);
} catch (Exception e) {
e.printStackTrace();
}
return 1;
}
3.2.17 从objectName指定的对象中将数据拷贝到destObjectName指定的对象
public static void copyObject(String bucketName, String objectName, String destBucketName, String destObjectName, CopyConditions cpConds, Map<String, String> metadata){
try {
MinioClient minioClient = createInMinioClient();
CopyConditions copyConditions = new CopyConditions();
copyConditions.setMatchETagNone("TestETag");
minioClient.copyObject(bucketName, objectName, destBucketName, destObjectName, copyConditions);//copyConditions 或 cpConds
System.out.println(objectName + " is uploaded successfully");
} catch(MinioException e) {
System.out.println("Error occurred: " + e);
} catch (Exception e) {
e.printStackTrace();
}
}
3.2.18 删除一个对象
public static void removeObject(String bucketName, String objectName){
try {
MinioClient minioClient = createInMinioClient();
// 从bucketName中删除objectName。
minioClient.removeObject(bucketName, objectName);
System.out.println("successfully removed " + bucketName + "/" +objectName);
} catch (MinioException e) {
System.out.println("Error: " + e);
} catch (Exception e) {
e.printStackTrace();
}
}
3.2.19 删除多个对象
public static Iterable<Result<DeleteError>> removeObject(String bucketName, Iterable<String> objectNames){
Iterable<Result<DeleteError>> results = null;
try {
MinioClient minioClient = createInMinioClient();
// 删除my-bucketname里的多个对象
results = minioClient.removeObject(bucketName, objectNames);
for (Result<DeleteError> errorResult: results) {
DeleteError error = errorResult.get();
System.out.println("Failed to remove '" + error.objectName() + "'. Error:" + error.message());
}
} catch (MinioException e) {
System.out.println("Error: " + e);
} catch (Exception e) {
e.printStackTrace();
}
return results;
}
3.2.20 获取文件永久地址
public static String getObjectUrl(String bucketName, String objectName){
String url = null;
try {
MinioClient minioClient = createInMinioClient();
url = minioClient.getObjectUrl(bucketName, objectName);
System.out.println(url);
} catch(MinioException e) {
System.out.println("Error occurred: " + e);
} catch (Exception e) {
e.printStackTrace();
}
return url;
}
我在github上写了个minio工具类,懒人可以借鉴一下(可以的话给个⭐Star):github.com/git-Dignity…
minio官方文档中也有明确的操作例子:docs.min.io/docs/java-c…
最后
上个星期,大学学霸过来坐坐,闲聊之中,看到我在做在线音乐,说起了我打算项目中的文件都要由minio管理,于是我就问起他文件都是怎么管理的,也是上传后端指定的文件夹。
于是我就打开minio给他看,一开始看了也觉得只是可视化界面,我说还有文件过期、权限等功能,oh ~ 还有这些功能,那不错,发下给我。
如果对您有帮助,你的点赞是我前进的润滑剂。
相关文献
以往推荐
vue-typescript-admin-template后台管理系统