Minio+Nginx搭建图床一键上传博客图片

本方案要求:有一台有公网 IP 的服务器,可以是云服务器或者有公网 IP 的家庭网络。

如果事先了解 Docker、Nginx 等可能会更方便。

前因

写博客的时候一大痛点就是图片的处理,MarkDown 格式好用是好用,但是没办法直接附带图片信息,只能放一个链接,图片存在本地的话发给别人的文档里图就没了,存在网上就涉及图床的选择问题,又是另一个麻烦事。大家分享出来的解决方案也是五花八门,有用公有云块存储的,有用收费图床的,前些年还有用微博当图床的,现在微博禁止外链了该方案也不行了,还有其他奇技淫巧比如放到 GitHub 上。

作为不折腾会死星人+对广大乱七八糟免费服务抱着怀疑心态,我决定还是自己搭建,牺牲一点点稳定性和易用性(其实故障率也没多高)。自己的宽带是北京联通,恰好有公网 IP,只不过不是固定的,之前找了个 ddns 的脚本自己更新 IP 倒也没多大问题。

开始我使用的方案是用的一个叫 Lychee 的图床,功能简单,用起来感觉还行,就是跟写博客的流程不能很好的配合,往往插入图片的时候步骤是这样的:

  • 准备好图片,剪切板上的话先保存到本地,网络图片粘贴一下url
  • 打开 Lychee,选择相应的方式上传
  • 上传完毕之后点开图片,右键选择图片地址
  • 把地址粘回 Typora 想要插入图片的位置

可以看出来这个流程太蛋疼了,插入一张图片就得十几秒,这一点都不 geek。

针对这个问题,Typora 最近更新了个功能,就是检测到插入图片之后会自动上传到指定的位置,可以是几种内置的服务,也可以自己写脚本。这种方式大大减少了插入图片的工作量,无论是剪切板上的图,还是网图,你只要粘贴到 Typora 上,就可以自动上传到图床,然后把图床中的链接替换掉开始的链接。这个功能的详细介绍可以去官网查看,或者看其他博主的介绍。下面分享下我用 Minio+Python 脚本实现的整个流程。

Minio

Minio 是亚马逊开源的一个文件存储系统,官方介绍这么说:

MinIO 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。

他的 API 非常简洁,而且如果你要求高的话,很方便做高可用,又提供了官方 Docker 镜像和一个简单的管理界面,所以我选择了他作为图床。

废话少说,直接贴我的 Docker 命令:

docker run  -itd -p 9000:9000 --name minio \
-v ~/docker/minio/data:/data \
-v ~/docker/minio/config:/root/.minio \
-e "MINIO_ACCESS_KEY=你的账号" \
-e "MINIO_SECRET_KEY=你的密码" \ 
minio/minio server /data

把里面 data 和 config 文件夹改成你想要存储数据和配置文件的文件夹,然后把账号密码填进去就行了。

如果想用 docker-compose 或者 k8s 启动,请参考官方文档,挺详细的而且竟然还有中文版。

启动之后,打开 9000 端口之后就可以看到自带的管理界面了。

Minio 的存储逻辑很简单,分成多个 bucket(桶),没个桶内部就是以文件夹为层级,而且所有文件是直接存储在文件系统上的,没有分成小文件块之类的,因此你可以直接复制到你的 data 文件夹内。

但是,你没办法直接用链接访问放在 Minio 中的文件,虽然它提供了分享等功能,但是提供的是一个下载链接,我们需要的是类似于图片预览,因此需要一个 Nginx 作为文件服务器。

Nginx

可以看到,我把 Minio 的数据文件夹挂载进去了,挂载到了 Nginx 容器的/data目录。然后修改 Nginx 配置文件如下:

server
{
    listen 80;
        listen 443 ssl http2;
    server_name minio.zccie.com;

    #SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则
    #error_page 404/404.html;
    #HTTP_TO_HTTPS_START
    if ($server_port !~ 443){
        rewrite ^(/.*)$ https://$host$1 permanent;
    }
    #HTTP_TO_HTTPS_END
    ssl_certificate    /www/server/panel/vhost/cert/minio.zccie.com/fullchain.pem;
    ssl_certificate_key    /www/server/panel/vhost/cert/minio.zccie.com/privkey.pem;
    ssl_protocols TLSv1.1 TLSv1.2;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    error_page 497  https://$host$request_uri;

    #SSL-END

    #ERROR-PAGE-START  错误页配置,可以注释、删除或修改
    #error_page 404 /404.html;
    #error_page 502 /502.html;
    #ERROR-PAGE-END

    #反向代理minIO配置
    location / {
        proxy_pass http://127.0.0.1:9000;
        add_header Content-Security-Policy "upgrade-insecure-requests";
        proxy_set_header Host $host;
    }

    #一键申请SSL证书验证目录相关设置
    location ~ \.well-known{
        allow all;
    }

    access_log  /www/wwwlogs/minio.zccie.com.log;
    error_log  /www/wwwlogs/minio.zccie.com.error.log;
}

于是只要我向 blog 这个桶中上传文件,文件就可以直接通过 Nginx 预览了。

Typora 和 Python 脚本

现在只需要准备一个脚本,让 Typora 能够向 Minio 上传文件就行了。选择 Python 是因为 Minio 官方提供了 API,而且我正好熟悉。

Typora 上传的规则是这样,它会让你填写你的命令,比如你填写python upload.py,它就会执行python upload.py "image1-path" "image2-path"等等把图片位置作为参数穿进去。然后约定好需要你传回来上传完毕后的每张图片位置,每张图片占一行。比如上面的例子,传了两张,它就会自动从后往前查两行,作为 URL 替换掉原本的图片链接。

于是我写了个脚本如下,写的比较糙,可以作为参考:

import os

import time

import uuid

import sys

import requests

from minio import Minio

from minio.error import ResponseError

import warnings

warnings.filterwarnings('ignore')

images = sys.argv[1:]

minioClient = Minio("这里写你的Minio地址,格式为域名:端口,不带http://",

                    access_key='你的Minio账号', secret_key='你的Minio密码', secure=False)//secure为True的话第一项会填充为https://

result = "Upload Success:\n"

date = time.strftime("%Y%m", time.localtime())

def download(image_url):

    local_path = os.getcwd() + "/temp"

    r = requests.get(image_url, verify=False)

    with open(local_path, "wb") as code:

        code.write(r.content)

    return local_path

for image in images:

    if os.path.isfile(image):

        file_type = os.path.splitext(image)[-1]

        new_file_name = str(uuid.uuid1()).replace('-', '') + file_type

    elif image.startswith("https://") or image.startswith("http://")://处理网络图片

        if image.endswith(".png") or image.endswith(".jpg") or image.endswith(".jpeg") or image.endswith(".gif"):

            url = image.split("/")

            if len(url) > 1:

                image = download(image)//先把网图下载到本地了,然后在传到Minio

                new_file_name = url[-1]

            else:

                result = result + "error:parsing image error!"

                continue

        else:

            result = result + "error:parsing image error!"

            continue

    else:

        result = result + "error:parsing image error!"

        continue

    try:

        minioClient.fput_object(bucket_name='blog', object_name=date + "/" + new_file_name, file_path=image)

        if image.endswith("temp"):

            os.remove(image)

        result = result +"你的Nginx地址,比如http://yourdomain:8081" + "/blog/" + date + "/" + new_file_name + "\n"

    except ResponseError as err:

        result = result + "error:" + err.message + "\n"

print(result)

我是把博客图片都放到了 blog 这个桶,然后按照月份创建文件夹,每个月的都放在同一个文件夹,文件名是用的 uuid。具体的逻辑想要修改的话自己改上面的脚本吧。

搞定之后在 Typora 里面填上脚本,然后点击测试,就可以看看结果了,测试通过就万事大吉了,写个博客插入图片看看吧!

发表评论