通常伴随着web项目开发, 很大可能会涉及到文件处理服务,再涉及文件转存方案的时候,需要涉及的方面有很多,涉及IO,接口开发,文件转存,文件权限,租户隔离,集群同步等实施方案,这个轮子建造还是需要很大力气的,因此有一套现成的解决方案,通过容器化部署即可实现整套的文件传输系统。
它的应用场景分为server端、Client端(内部服务)以及Console控制台端。Server端就是文件转存的服务端,除了默认配置,通常部署的Server会提供Client端和控制台,控制台是其内部可视化页面,Client端又细分为很多语言的驱动,主要包含Python、Java以及JS等。
为什么用Nginx,这里有个小坑,可能我技术能力不够吧,后面会详细应用到。
# 1、拉去Minio镜像
docker pull minio/minio
# 2、运行容器 采用运行容器的方式创建access_key和secret_key
# 这里的两个key可以自己设置
docker run -p 9000:9000 -p 9100:9100 \
--name MinioServer \
-d --restart=always \
-v /docker/minio/data:/data \
-v /docker/minio/config:/root/.minio \
-e "MINIO_ROOT_USER=admin" \
-e "MINIO_ROOT_PASSWORD=Changeme_123" \
minio/minio server /data --console-address ":9100"
# 3、检查容器是否启动成功,这里容器ID需要记录一下
docker ps -a
参数解释:
MINIO_ROOT_USER 管理员用户的登录账号
MINIO_ROOT_PASSWORD 管理员用户的登录密码
9000端口是API 接口端口
9101端口是控制台访问端口(--console-address ":9100" 这个必须加上,不然启动是不能访问的)
基于Centos7环境,适配Linux环境,同时需要具备基本的docker容器化部署技术基础
docker启动后,我们根据容器ID查看启动日志
# 获取MinIO日志,使用 docker logs 命令。
docker logs <container_id>
如上图所示,这是MinioServer启动日志,但是我发现一个问题,无论是API还是Console的链接地址都是内网或者容器内地址。查看资料有人说可以配置环境变量,然并卵。网上大部门的资料都是部署在本地,如果本地的话,大家都是内网地址,因此不影响后面的功能使用。
上面已经说过Nginx简易版本部署,差不多五分钟就搞好了,我们需要修改一下default.conf 文件配置
server {
# Minio API
listen 9002;
server_name 公网IP;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_connect_timeout 300;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass http://172.17.0.3:9000;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
server {
# Minio Console
listen 9001;
server_name 公网IP;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_connect_timeout 300;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass http://172.17.0.3:9101;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
在第一个server里面,我加了一个
更改配置后,重启Nginx容器,并且要开发端口和安全组。
同时这里需要主要的时候这里的9001和9002是可以随便设置的,但是不能被占用
# 使用端口前需要查看一下是否被占用了,否则会导致启动失败的
ps -l | grep "端口号"
输入IP端口可以直接访问了,输入你上面登录和密码就可以进去了
这里对Minio 我简单研究了一下该控制台功能点,我们主要用到的功能
Buckets:桶的含义就是类似具体的C、D等盘符,因此我们需要先创建一个具体的桶才能上传文件,或者继续在桶里面创建新的文件夹
Manage:配置桶规则,这里我们主要关注点就是这个Access Rules ,我们新增一个规则,* 表示该桶里面的文件可以直接通过API固定链接访问,也就是匿名访问。
Identity:用户模块,这里可以创建具体用户组,用户以及用户下面的API生成的签名access密钥
具体细致的功能可以参考 Minio Doc 研究
重点看一下预览功能-share
你会发现这里IP地址是他妈的内网地址,这就很离谱,这个不是分享了一个寂寞。于是我开始百度,看资料~~~~发现网上给的方案几乎全部试了一遍,然并卵。
肯定是有办法的解决的,不然这个系统按理说是不完善的,只是 我还没有找到解决办法。目前只能开启匿名访问了。但是一旦开启了匿名访问,对文件预览下载直接通过固定IP地址去访问,可能就没办法做到安全问题。【就这样吧】
我会继续研究的,如果能解决的话,就会更新一下 // TODO
<dependency>
<groupId>io.miniogroupId>
<artifactId>minioartifactId>
<version>7.1.0version>
dependency>
代码部分
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.List;
import java.util.UUID;
public class MinioUtils {
/**
* 配置自己服务的IP与端口号 这里设置就是上面Nginx中 Minio API 里面的端口别搞混了
*/
private static final String server = "http://1公网IP:9002";
/**
* access_key
*/
private static final String accessKey = "申请的key";
/**
* secret_key
*/
private static final String secretKey = "申请的key";
/**
* MinioClient客户端
*/
private static MinioClient client;
/**
* 初始化连接
*/
public static void initClient() {
try {
client = MinioClient
.builder()
.endpoint(server)
.credentials(accessKey, secretKey)
.build();
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
/**
* 获取桶列表
*
* @return 桶列表
*/
public static List<Bucket> getAllBucketList() {
try {
return client.listBuckets();
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
/**
* 创建桶或者文件夹
*
* @param bucketName 桶名
* @param prefix 文件夹名称
*/
public static void createBucketOrFolder(String bucketName, String prefix) {
try {
// 首先查看当前桶是否存在
if (!client.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
client.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
if (!isFolderExits(bucketName, prefix)) {
client.putObject(PutObjectArgs.builder().bucket(bucketName).object(prefix)
.stream(new ByteArrayInputStream(new byte[]{}), 0, -1).build());
}
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
/**
* 文件上传
*
* - 桶的概念就类似我们PC的“此电脑”,在“此电脑”下面才会存在我们不同的盘符,不同的盘符中有不同文件夹或者文件
* - 在桶里面的文件的objectName与文件夹的objectName显示存在差异,具体可以调试桶列表查看
* - 桶里面的文件夹实际就是前缀,它后面跟随的文件夹分割符号往往与我们部署的系统环境有关
*
*
* @param bucketName 桶名称
* @param prefix 桶内文件路径 例如: Demo桶下存在文件夹 它的objectName则为 folder/
* @param in 文件流
* @param fileFormatType 文件后缀 例如: .png .svg .xlsx 等
* @return 范围服务器端的文件随机名称 例如:35ecae61-d6a6-4ce0-8175-e506dcdbc07a.png
*/
public static String putObject(String bucketName, String prefix, InputStream in, String fileFormatType) {
try {
// 设置UUID主要原因
String key = UUID.randomUUID().toString() + fileFormatType;
client.putObject(
PutObjectArgs
.builder()
.bucket(bucketName)
.object(prefix + key)
.stream(in, in.available(), -1)
.contentType("application/octet-stream")
.build());
return key;
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
/**
* 获取文件信息 这里就用Minio原生模板类展示
* 如果是存在文件路径下的文件,例如objectName = "demo/avatar.png"
*
* @param bucketName 桶名
* @param objectName 文件名称
* @return StatObjectResponse
*/
public static StatObjectResponse getObjectInfo(String bucketName, String objectName) {
try {
return client.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
/**
* 获取文件下载链接,此链接是Minio提供的链接
*
* @param bucketName 桶名
* @param objectName 文件名称,如果存在文件路径下的文件,例如objectName = "demo/avatar.png"
* @return String 下载链接
*/
public static String getObjectUrl(String bucketName, String objectName) {
try {
return client.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(bucketName)
.object(objectName)
.build());
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
/**
* 读取文件流的方式获取文件
* @param bucketName 桶名
* @param objectName 文件名称,如果存在文件路径下的文件,例如objectName = "demo/avatar.png"
* @return InputStream 字节输入流
*/
public static InputStream getObjectStream(String bucketName, String objectName) {
try {
return client.getObject(
GetObjectArgs
.builder()
.bucket(bucketName)
.object(objectName)
.build());
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
/**
* 判断桶和文件夹是否存在
*
* @param bucketName 桶名称
* @param prefix 文件夹 格式: folder/
* @return boolean 是否存在
*/
public static boolean isFolderExits(String bucketName, String prefix) {
try {
if (null != prefix && !"".equals(prefix)) {
Iterable<Result<Item>> results = client.listObjects(ListObjectsArgs.builder().bucket(bucketName)
.prefix(prefix).recursive(false).build());
for (Result<Item> result : results) {
Item item = result.get();
if (item.isDir()) {
return true;
}
}
}
} catch (Throwable e) {
throw new RuntimeException(e);
}
return false;
}
}
注意事项