【MInio】Docker+Nginx+Minio Servrer部署单点文件存储系统

一 Minio简介

通常伴随着web项目开发, 很大可能会涉及到文件处理服务,再涉及文件转存方案的时候,需要涉及的方面有很多,涉及IO,接口开发,文件转存,文件权限,租户隔离,集群同步等实施方案,这个轮子建造还是需要很大力气的,因此有一套现成的解决方案,通过容器化部署即可实现整套的文件传输系统。
它的应用场景分为server端、Client端(内部服务)以及Console控制台端。Server端就是文件转存的服务端,除了默认配置,通常部署的Server会提供Client端和控制台,控制台是其内部可视化页面,Client端又细分为很多语言的驱动,主要包含Python、Java以及JS等。

前期准备

  1. Centos7.9系统(有条件的就使用云产品或者内网穿透也可以,因为很多场景都是外网服务的坑比较多)
  2. docker部署
  3. Nginx安装与部署 快速部署Nginx

为什么用Nginx,这里有个小坑,可能我技术能力不够吧,后面会详细应用到。

Docker快速部署 Server(单点模式)

安装

# 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进行转发代理

上面已经说过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里面,我加了一个<proxy_set_header Host $http_host;>参数,这里出现的问题,是因为我调用Java Minio Client 服务的时候,Client是可以链接的,但是无法获取文件,提示我请求头中的签名不能匹配,可能就是Nginx在这里转发的时候把请求头给丢了。
更改配置后,重启Nginx容器,并且要开发端口和安全组。
同时这里需要主要的时候这里的9001和9002是可以随便设置的,但是不能被占用

# 使用端口前需要查看一下是否被占用了,否则会导致启动失败的
ps -l | grep "端口号"

在这里插入图片描述
输入IP端口可以直接访问了,输入你上面登录和密码就可以进去了
在这里插入图片描述
这里对Minio 我简单研究了一下该控制台功能点,我们主要用到的功能

  • Buckets:桶的含义就是类似具体的C、D等盘符,因此我们需要先创建一个具体的桶才能上传文件,或者继续在桶里面创建新的文件夹
    在这里插入图片描述

  • Manage:配置桶规则,这里我们主要关注点就是这个Access Rules ,我们新增一个规则,* 表示该桶里面的文件可以直接通过API固定链接访问,也就是匿名访问。
    在这里插入图片描述

  • Identity:用户模块,这里可以创建具体用户组,用户以及用户下面的API生成的签名access密钥
    在这里插入图片描述
    具体细致的功能可以参考 Minio Doc 研究

上传、下载,分享、删除、预览

在这里插入图片描述
重点看一下预览功能-share
在这里插入图片描述
你会发现这里IP地址是他妈的内网地址,这就很离谱,这个不是分享了一个寂寞。于是我开始百度,看资料~~~~发现网上给的方案几乎全部试了一遍,然并卵。
肯定是有办法的解决的,不然这个系统按理说是不完善的,只是 我还没有找到解决办法。目前只能开启匿名访问了。但是一旦开启了匿名访问,对文件预览下载直接通过固定IP地址去访问,可能就没办法做到安全问题。【就这样吧】
我会继续研究的,如果能解决的话,就会更新一下 // TODO

Minio Client 使用以Java为例

Maven依赖引入

		<dependency>
			<groupId>io.minio</groupId>
			<artifactId>minio</artifactId>
			<version>7.1.0</version>
		</dependency>

Minio Client 配置以及使用

代码部分

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);
        }
    }


    /**
     * <p>文件上传</p>
     * <ul>
     *     <li>桶的概念就类似我们PC的“此电脑”,在“此电脑”下面才会存在我们不同的盘符,不同的盘符中有不同文件夹或者文件</li>
     *     <li>在桶里面的文件的objectName与文件夹的objectName显示存在差异,具体可以调试桶列表查看</li>
     *     <li>桶里面的文件夹实际就是前缀,它后面跟随的文件夹分割符号往往与我们部署的系统环境有关</li>
     * </ul>
     *
     * @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原生模板类展示
     * <p>如果是存在文件路径下的文件,例如objectName = "demo/avatar.png" </p>
     *
     * @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;
    }
}

注意事项

  • 由于桶下面包含文件和文件夹,它们的区别就是存在文件夹分割符号,Linux / window \,具体区别可以通过上面getAllBucketList() 方法查看差异
  • 上传文件最好追加文件后缀,不然获取下载链接的时候,就需要前端工作人员对流文件进行明明,由于前端不一定知道文件的类型,所以尽量还是跟上后缀比较方便
  • 在业务层面要注意参数的判空处理,提升代码的质量性
  • 因为我使用的是Nginx代理转发,关于前面说到的$http_host配置问题,如果不加上的话,可能转发的时候直接把URL后面的签名给丢了,导致上面的utils中的方法不可用了。
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我的钱包空指针了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值