搭建Minio对象存储及Chevereto私有图床

  1. 1. 基本介绍
    1. 1.1 Minio简介
    2. 1.2 Chevereto简介
  2. 2. 搭建前的环境准备
    1. 2.1 Docker环境搭建
    2. 2.2 使用OneinStack搭建Nginx并进行配置
      1. 2.2.1 OneinStack简介
      2. 2.2.2 使用OneinStack搭建Nginx服务
      3. 2.2.3 创建网站开启HTTPS
      4. 2.2.4 配置反向代理
    3. 2.3 Docker-MySQL环境搭建
      1. 2.3.1 拉取镜像并创建实例容器
      2. 2.3.2 创建数据库及用户
  3. 3. 搭建Minio对象存储
    1. 3.1 拉取镜像并运行容器
    2. 3.2 Minio的管理面板
    3. 3.3 添加反向代理并开启 HTTPS
  4. 4. 搭建Chevereto私有图床
    1. 4.1 拉取Chevereto的docker镜像
    2. 4.2 创建Chevereto的docker容器并运行
    3. 4.3 添加反向代理并开启 HTTPS
    4. 4.4 执行Chevereto的安装过程
    5. 4.5 使用Installer部署的注意事项
    6. 4.6 Chevereto基本设置及上传API
      1. 4.6.1 Chevereto基本设置
      2. 4.6.2 获取Chevereto的上传API
      3. 4.6.3 使用Postman测试Chevereto API
  5. 5. 配置图片自动上传及图片处理
    1. 5.1 使用Typora+Python脚本实现自动上传
      1. 5.1.1 项目结构及代码
      2. 5.1.2 配置Typora的Custom Command功能
    2. 5.2 使用ShareX工具实现截图处理上传
      1. 5.2.1 下载安装ShareX工具
      2. 5.2.2 在ShareX里配置Chevereto API
    3. 5.3 图片压缩并添加盲水印
  6. 6. 参考资料

1. 基本介绍

1.1 Minio简介

对象存储:将获取的每个数据片段指定为对象,数据保存在单独的存储库中,而不是以文件的形式保存在文件夹中,数据与关联的元数据和唯一标识符捆绑在一起,以形成存储池。

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

minio-console

1.2 Chevereto简介

图床:也就是专门提供存储图片的地方,我们上传好图片,就可以通过外链/API等方式访问了。图床分为公共图床和自建图床。

  • 公共图床,会用各种技术帮我们做图片相关的优化和服务,比如CDN 加速、图片处理、图片鉴黄、文本识别等等,我们不需要担心硬盘空间不足等问题。
  • 而下文介绍的 Chevereto 属于私有图床,将其建立在国内服务器或者套上合适的CDN,都可以为我们访问照片加速,大大加快打开网页的速度。除了储存、处理、分发照片外,Chevereto还可以作为个人分享和收集优秀图片的平台。

Chevereto图床

2. 搭建前的环境准备

以下我将采用Nginx+Docker部署的方式进行搭建,VPS系统用的是Debian 11 x86_64。VPS的购买及配置、域名解析、Docker的概念及使用…这些基本的就不再赘述了,如果不会的话见我的另一篇博客:VPS基本部署环境的搭建与配置

2.1 Docker环境搭建

1
2
3
4
$ apt-get update -y && apt-get install curl -y  # 安装curl
$ curl https://get.docker.com | sh - # 安装docker
$ sudo systemctl start docker # 启动docker服务
$ docker version # 查看docker版本(客户端要与服务端一致)

2.2 使用OneinStack搭建Nginx并进行配置

2.2.1 OneinStack简介

项目简介:OneinStack是一套部署开发环境的脚本工具,它的部署方式是纯原生部署,我主要使用里面的Nginx,用来辅助生成配置。

项目地址:https://github.com/oneinstack/oneinstack

2.2.2 使用OneinStack搭建Nginx服务

1
$ wget -c http://mirrors.linuxeye.com/oneinstack-full.tar.gz && tar xzf oneinstack-full.tar.gz && ./oneinstack/install.sh --nginx_optio

注:OneinStack采用编译安装的方式搭建Nginx服务,速度比较慢,请耐心等待,常用的默认目录有:

1
2
3
Nginx安装目录: /usr/local/nginx
Nginx配置目录:/usr/local/nginx/conf
网站数据目录:/data/wwwroot/域名

2.2.3 创建网站开启HTTPS

OneinStack 内置了 acme.sh,它会自动帮你申请 SSL 证书。

1
2
$ cd ./oneinstack
$ ./vhost.sh

配置过程的参考示例如下(示例域名为www.demo.com):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#######################################################################
# OneinStack for CentOS/RedHat 7+ Debian 8+ and Ubuntu 16+ #
# For more information please visit https://oneinstack.com #
#######################################################################

What Are You Doing?
1. Use HTTP Only
2. Use your own SSL Certificate and Key
3. Use Let's Encrypt to Create SSL Certificate and Key
q. Exit
Please input the correct option: 2 【选择2】

Please input domain(example: www.example.com): www.demo.com 【输入域名】
domain=www.demo.com

Please input the directory for the domain:www.demo.com :
(Default directory: /data/wwwroot/www.demo.com): 【回车,使用默认配置】
Virtual Host Directory=/data/wwwroot/www.demo.com

Create Virtul Host directory......
set permissions of Virtual Host directory......

Do you want to add more domain name? [y/n]: n 【输入n,不添加其他域名了】

Do you want to redirect all HTTP requests to HTTPS? [y/n]: y 【输入y,强制HTTPS】

You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.

Country Name (2 letter code) [CN]: 【回车】

State or Province Name (full name) [Shanghai]: 【回车】

Locality Name (eg, city) [Shanghai]: 【回车】

Organization Name (eg, company) [Example Inc.]: 【回车】

Organizational Unit Name (eg, section) [IT Dept.]: 【回车】

Do you want to add hotlink protection? [y/n]: n 【输入n,不需要防盗链保护】

Allow Rewrite rule? [y/n]: y 【输入y,允许重写规则】

Please input the rewrite of programme :
wordpress,opencart,magento2,drupal,joomla,codeigniter,laravel
thinkphp,pathinfo,discuz,typecho,ecshop,nextcloud,zblog,whmcs rewrite was exist.
(Default rewrite: other): 【回车】
You choose rewrite=other

Allow Nginx/Tengine/OpenResty access_log? [y/n]: y 【输入y,允许日志记录】
You access log file=/data/wwwlogs/www.demo.com_nginx.log

#######################################################################
# OneinStack for CentOS/RedHat 7+ Debian 8+ and Ubuntu 16+ #
# For more information please visit https://oneinstack.com #
#######################################################################
Your domain: www.demo.com
Virtualhost conf: /usr/local/nginx/conf/vhost/www.demo.com.conf
Directory of: /data/wwwroot/www.demo.com
Rewrite rule: /usr/local/nginx/conf/rewrite/other.conf
Self-signed SSL Certificate: /usr/local/nginx/conf/ssl/www.demo.com.crt
SSL Private Key: /usr/local/nginx/conf/ssl/www.demo.com.key
SSL CSR File: /usr/local/nginx/conf/ssl/www.demo.com.csr

配置完成后,修改一下配置文件

1
$ vim /usr/local/nginx/conf/vhost/www.demo.com.conf

server模块删除内容:

1
2
3
4
5
6
7
8
9
10
location ~ [^/]\.php(/|$) {
#fastcgi_pass remote_php_ip:9000;
fastcgi_pass unix:/dev/shm/php-cgi.sock;
fastcgi_index index.php;
include fastcgi.conf;
}

location /.well-known {
allow all;
}

server模块新增内容:

1
2
3
4
5
location ^~ /.well-known/acme-challenge/ {
default_type "text/plain";
allow all;
root /data/wwwroot/www.demo.com/; # 把路径换一下
}

检查并重载Nginx配置

1
2
$ nginx -t
$ nginx -s reload

注:使用nginx -t 命令可以检查Nginx配置,若出现如下内容则说明配置正确。

1
2
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful

之后把资源文件放置到 /data/wwwroot/www.demo.com 目录即可,打开Chrome浏览器即可访问(如果出现403则需要给这个目录添加一下权限)。

2.2.4 配置反向代理

如果需要配置反向代理的话(比如Docker部署的服务),流程同上,只是修改Nginx配置文件处有所不同。

server模块删除内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
location ~ [^/]\.php(/|$) {
#fastcgi_pass remote_php_ip:9000;
fastcgi_pass unix:/dev/shm/php-cgi.sock;
fastcgi_index index.php;
include fastcgi.conf;
}
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|flv|mp4|ico)$ {
expires 30d;
access_log off;
}
location ~ .*\.(js|css)?$ {
expires 7d;
access_log off;
}
location /.well-known {
allow all;
}

server模块新增内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
location / {
proxy_set_header HOST $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://demo; # 与下面 upstream 的名称一致即可
}
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|flv|mp4|ico)$ {
proxy_pass http://demo; # 与下面 upstream 的名称一致即可
expires 30d;
access_log off;
}
location ~ .*\.(js|css)?$ {
proxy_pass http://demo; # 与下面 upstream 的名称一致即可
expires 7d;
access_log off;
}
location ^~ /.well-known/acme-challenge/ {
default_type "text/plain";
allow all;
root /data/wwwroot/www.demo.com/; # 把路径换一下
}

与server模块同级新增内容:

1
2
3
upstream demo {
server 127.0.0.1:8888; # 8888换成自己的反向代理端口号
}

2.3 Docker-MySQL环境搭建

2.3.1 拉取镜像并创建实例容器

1
2
3
4
5
6
7
8
$ docker pull mysql:5.7
$ docker run -p 3306:3306 --name mysql \
-e TZ=Asia/Shanghai \
-v /mydocker/mysql/conf:/etc/mysql/conf.d \
-v /mydocker/mysql/logs:/var/log/mysql \
-v /mydocker/mysql/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=[password] \
-d mysql:5.7 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci

命令解释说明:

1
2
3
4
5
6
7
-p 3306:3306:将主机的3306端口映射到docker容器的3306端口。
--name mysql:运行服务名字
-e TZ=Asia/Shanghai:时区是使用了世界标准时间(UTC)。因为在中国使用,所以需要把时区改成东八区的。
-e MYSQL_ROOT_PASSWORD=[password]:初始化 root 用户的密码。
-d mysql:5.7 : 后台程序运行mysql5.7
--character-set-server=utf8mb4 :设置字符集
--collation-server=utf8mb4_unicode_ci:设置校对集

设置开机自启

1
$ docker update mysql --restart=always

2.3.2 创建数据库及用户

在本地使用Navicat工具使用root用户连接上该数据库,使用如下四条命令创建数据库及用户。

1
2
3
4
5
6
7
8
9
10
11
--创建新的数据库,并设置数据库编码
$ CREATE DATABASE 你的数据库名 DEFAULT CHARSET=utf8 DEFAULT COLLATE utf8_unicode_ci;

--创建新的用户
$ CREATE USER '你的用户名'@'你的服务器IP' IDENTIFIED BY '你的密码';

--把数据库的管理权限给予刚刚创建的MySQL用户
$ GRANT ALL PRIVILEGES ON *.* TO '你的用户名'@'%' IDENTIFIED BY '你的密码' WITH GRANT OPTION;

--刷新权限,使用设置生效
$ FLUSH PRIVILEGES;

注:如果连接数据库时出现Access denied for user '用户名'@'某IP' (using password: YES)问题,则是第三句授权出了问题,你的本地外网IP被拦截了,那个’%’代表的是访问IP不受限制。

3. 搭建Minio对象存储

3.1 拉取镜像并运行容器

1
2
3
4
5
6
7
8
9
10
$ docker pull minio/minio
$ mkdir -p /home/data/minio/data
$ mkdir -p /home/data/minio/config
$ docker run -d --restart always \
-p 9000:9000 -p 9001:9001 --name minio \
-e "MINIO_ACCESS_KEY=admin" \
-e "MINIO_SECRET_KEY=password" \
-v /home/data/minio/data:/data \
-v /home/data/minio/config:/root/.minio \
minio/minio server --console-address ":9001" /data

注:密码不可以设置的太简单了(会导致创建失败),出现此问题请查看容器日志。

3.2 Minio的管理面板

浏览器打开:http://IP:9001 查看即可。MINIO_ACCESS_KEY为账号,MINIO_SECRET_KEY为密码。进去之后创建存储桶,即可进行使用。

3.3 添加反向代理并开启 HTTPS

通用的创建网站开启HTTPS并配置反向代理见本文2.2节。

注:9000端口是用来访问图片的,9001端口是用来访问面板的,建议用两个域名都加上反向代理。

4. 搭建Chevereto私有图床

Chevereto的搭建主要介绍Docker部署的方式,而通过官方提供的Installer部署的方式仅简要介绍注意事项。

4.1 拉取Chevereto的docker镜像

先使用docker search命令搜索一下Chevereto的镜像,然后这里我们选用的是linuxserver/chevereto镜像进行搭建。

1
2
$ docker search chevereto
$ docker pull linuxserver/chevereto

Chevereto的Docker镜像

4.2 创建Chevereto的docker容器并运行

1
2
3
4
5
6
7
8
9
10
11
12
$ mkdir /appliaction
$ docker run -d \
--name=chevereto \
-e PUID=1000 \
-e PGID=1000 \
-e TZ=Asia/Shanghai \
-p 80:80 \
-p 443:443 \
-v /appliaction/chevereto/config:/config \
-v /appliaction/chevereto/data:/data \
--restart unless-stopped \
linuxserver/chevereto

命令说明:-e PUID=1000和-e PGID=1000指定用户和用户组ID,镜像官方的解释是,在用-v挂载卷的时候,主机操作系统和容器之间可能会出现权限问题,如果出现权限问题,可以指定这两个参数(不过一般情况下这俩不指定也能启动),-e TZ指定时区,两个-v挂载了配置文件和数据的目录。

4.3 添加反向代理并开启 HTTPS

通用的创建网站开启HTTPS并配置反向代理见本文2.2节。

4.4 执行Chevereto的安装过程

Chrome输入http://域名:端口号路径,打开安装界面。

Step1:首先它会让你检查这两个路径与你要安装Chevereto的位置相匹配,并且没有安装其他软件。

Step2:然后填写数据库信息(Host处填写VPS的IP,其他项根据MySQL搭建时的配置来即可)

Chevereto安装过程

Step3:然后就是填写邮箱信息,可以用来重置密码什么的,填完邮箱就开始了自动安装,如果没有报错就算是初步搭建完成了。

4.5 使用Installer部署的注意事项

官方提供的Installer:Chevereto Installer Github项目地址:https://github.com/chevereto/installer

通过Installer部署需要安装PHP和Nginx环境,本文使用的installer.php是2.2.2版本的,PHP的最低支持是7.4,如果版本再低了无法安装。

将Installer上传至网站根目录,然后需要添加Nginx规则,规则放入Nginx配置里的server{}代码块中,并reload或restart nginx。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Chevereto nginx generated rules
## Disable access to sensitive files
location ~* /(app|content|lib)/.*\.(po|php|lock|sql)$ {
deny all;
}
## CORS headers
location ~* /.*\.(ttf|ttc|otf|eot|woff|woff2|font.css|css|js) {
add_header Access-Control-Allow-Origin "*";
}
## Upload path for image content only and set 404 replacement
location ^~ /images/ {
location ~* (jpe?g|png|gif) {
log_not_found off;
error_page 404 /content/images/system/default/404.gif;
}
return 403;
}
## Pretty URLs
location / {
index index.php;
try_files $uri $uri/ /index.php?$query_string;
}
# END Chevereto nginx rules

注:如果不配置这个到installer安装的最后一步会出现“执行HTTP请求时出错”报错。上文所述的docker方式部署不用配这个,内置了。

后续与使用Docker部署的安装过程一致,不再赘述。

4.6 Chevereto基本设置及上传API

4.6.1 Chevereto基本设置

头像——仪表盘——设置——根据自己的需要进行配置,其中要注意的两点如下:

  • 如果你的图床加载速度很慢,禁用自动更新可以有效缓解此问题(系统——“自动更新检查”、“显示可用的更新通知”设置成禁用)

  • 如果你的图床只供给自己使用,请把开放注册的功能关掉(用户——“开放注册”设置成禁用)

4.6.2 获取Chevereto的上传API

头像——仪表盘——设置——API处,可以看到图片上传API的密钥,这个下面配置自动上传会用到。

Chevereto-API-v1

该API的使用说明详见: Chevereto API v1,它的调用示例如下:

1
http://mysite.com/api/1/upload/?key=12345&source=http://somewebsite/someimage.jpg&format=json

API V1没有办法上传与给定用户相关联的图像,但是你可以覆盖默认的API,以实现以用户的形式上传:

打开网站根目录,将app/routes/route.api.php文件复制到app/routes/overrides文件夹,然后在此编辑该文件

CHV\Image::uploadToWebsite($source);改为CHV\Image::uploadToWebsite($source, '用户名'); (在第105行附近)

覆盖原有API实现以用户形式上传

4.6.3 使用Postman测试Chevereto API

网络图片:上传网络图片使用GET或POST皆可。

1
GET http://mysite.com/api/1/upload/?key=12345&source=http://somewebsite/someimage.jpg&format=json

本地图片:上传本地文件时始终使用 POST。

1
POST http://mysite.com/api/1/upload/?key=12345&format=json    // 在body的form-data处上传文件(key为source)

5. 配置图片自动上传及图片处理

5.1 使用Typora+Python脚本实现自动上传

我们可以利用Typora的Custom Command功能实现自动上传,这也是我目前写博客最常用的方案,效果很好,但不支持批量上传。

注:Typora需要是新版的,版本太老可能没有这个功能。Custom Command我这里用的是Python脚本,因此需要配置Python开发环境。

代码已在Github上开源,项目地址:https://github.com/Logistic98/upload-images

5.1.1 项目结构及代码

项目结构:

1
2
3
4
5
6
7
8
.
├── requirements.txt
├── upload-chevereto
│   ├── config.ini
│   └── upload-chevereto.py
└── upload-minio
   ├── config.ini
└── upload-minio.py

项目依赖:

requirements.txt

1
2
minio==7.1.11
requests==2.27.1

项目代码:

./upload-minio/config.ini

1
2
3
4
5
6
7
8
9
[minio]
minio_url = 图片url根路径(图片url根路径,这里必须用ip和端口号,如:xxx.xxx.xxx.xxx:9000
minio_domain = 域名(图片域名根路径,如:www.demo.com)
access_key = 用户名
secret_key = 密码
minio_bucket = 存储桶名

[log]
upload_log_path = 日志记录路径(建议使用绝对路径,如:/root/logs/minioUploadLog.md)

./upload-minio/upload-minio.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# -*- coding: utf-8 -*-

import argparse
import sys

import logging
import os
import time

from minio import Minio
from minio.error import S3Error
from configparser import ConfigParser


# 读取配置文件
def read_config(config_path):
cfg = ConfigParser()
cfg.read(config_path, encoding='utf-8')
minio_url = cfg.get('minio', 'minio_url')
minio_domain = cfg.get('minio', 'minio_domain')
access_key = cfg.get('minio', 'access_key')
secret_key = cfg.get('minio', 'secret_key')
minio_bucket = cfg.get('minio', 'minio_bucket')
upload_log_path = cfg.get('log', 'upload_log_path')
config_dict = {}
config_dict['minio_url'] = minio_url
config_dict['minio_domain'] = minio_domain
config_dict['access_key'] = access_key
config_dict['secret_key'] = secret_key
config_dict['minio_bucket'] = minio_bucket
config_dict['upload_log_path'] = upload_log_path
return config_dict


# 按行读取文件的内容,保存成列表
def read_file_to_list(file_path):
result = []
with open(file_path, 'r') as f:
for line in f:
result.append(line.strip('\n'))
return result


# 初始化minio客户端
def get_minio_client(config_dict):
# 使用endpoint、access key和secret key来初始化minioClient对象。
minio_client = Minio(config_dict['minio_url'],
access_key=config_dict['access_key'],
secret_key=config_dict['secret_key'],
secure=False)
return minio_client


# 创建一个存储桶(判断桶是否已经存在,不存在则创建,存在忽略)
def minio_make_bucket_ifnotexist(minio_client, bucket_name):
bucket_name = bucket_name.replace('_', "-")
try:
if not minio_client.bucket_exists(bucket_name):
logging.info("该存储桶不存在:" + bucket_name)
minio_client.make_bucket(bucket_name)
logging.info("存储桶创建:" + bucket_name)
except S3Error as e:
if "InvalidAccessKeyId" in str(e):
logging.error("minio 的 access_key 可能有误")
elif "SignatureDoesNotMatch" in str(e):
logging.error("minio 的 secret_key 可能有误")
else:
logging.error("minio 的 endpoint、access_key、secret_key 可能有误")
raise e


# 文件上传
def minio_upload_file(minio_client, bucket_name, object_name, file_path):
logging.info(file_path)
result = minio_client.fput_object(bucket_name, object_name, file_path)
return result


# 获取object_name(上传到minio的路径)
def get_object_name(file_path):
file_dir, file_full_name = os.path.split(file_path)
return file_full_name


if __name__ == '__main__':
# 工具描述及帮助
TOOL_DESC = """
A command line tool to upload files to minio
"""
logging.info(TOOL_DESC)
if len(sys.argv) == 1:
sys.argv.append('--help')
# 获取参数
parser = argparse.ArgumentParser()
parser.add_argument('-s', '--source', type=str, nargs='+', help="上传图片路径", required=True)
parser.add_argument('-c', '--config', default="./config.ini", help="读取配置文件", required=True)
args = parser.parse_args()
img_list = args.source
config_path = args.config
# 读取配置文件
config_dict = read_config(config_path)
# 初始化minio客户端
minio_client = get_minio_client(config_dict)
# 创建一个存储桶(判断桶是否已经存在,不存在则创建,存在忽略)
minio_make_bucket_ifnotexist(minio_client, config_dict['minio_bucket'])
# 文件上传minio
for img_path in img_list:
object_name = get_object_name(img_path)
img_url = "https://" + config_dict['minio_domain'] + "/" + config_dict['minio_bucket'] + "/" + object_name
try:
minio_upload_file(minio_client, config_dict['minio_bucket'], object_name, img_path)
with open(config_dict['upload_log_path'], 'a') as file_object:
upload_log_list = read_file_to_list(config_dict['upload_log_path'])
year = time.strftime("%Y", time.localtime())
if "## " + year not in upload_log_list:
file_object.write("## " + year + "\n\n")
month = time.strftime("%Y-%m", time.localtime())
if "## " + month not in upload_log_list:
file_object.write("## " + month + "\n\n")
file_object.write(img_url + " --" + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + "\n")
file_object.write("![](" + img_url + ")\n\n")
# 上传成功的图片url必须用print(logger.info不可以)打印出来,Typora才能识别出是上传成功了,才会自动替换链接。
print(img_url)
except Exception as e:
logging.error(e)
logging.error("上传minio失败")

./upload-chevereto/config.ini

1
2
3
4
5
6
[chevereto]
chevereto_url = https://域名/api/1/upload/
api_key = API-Key值

[log]
upload_log_path = 日志记录路径(建议使用绝对路径,如:/root/logs/cheveretoUploadLog.md)

./upload-chevereto/upload-chevereto.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# -*- coding: utf-8 -*-

import json
import logging
import time
from configparser import ConfigParser

import requests
import argparse
import sys


# 读取配置文件
def read_config(config_path):
cfg = ConfigParser()
cfg.read(config_path, encoding='utf-8')
chevereto_url = cfg.get('chevereto', 'chevereto_url')
api_key = cfg.get('chevereto', 'api_key')
upload_log_path = cfg.get('log', 'upload_log_path')
config_dict = {}
config_dict['chevereto_url'] = chevereto_url
config_dict['api_key'] = api_key
config_dict['upload_log_path'] = upload_log_path
return config_dict


# 按行读取文件的内容,保存成列表
def read_file_to_list(file_path):
result = []
with open(file_path, 'r') as f:
for line in f:
result.append(line.strip('\n'))
return result


# 获得本地图片路径后,上传至图床并记录返回的json字段
def up_to_chevereto(img_list):
for img in img_list:
# 先判断传过来的是本地路径还是远程图片地址
if "http" == img[:4]:
# 非本地图片的话可以考虑下载到本地再上传
logging.info(img)
continue
else:
try:
res_json = upload(img)
parse_response_url(res_json,img)
except Exception as e:
logging.error(e)
logging.error(img+"\tUpload failed")


# 调用 Chevereto API 上传图床
def upload(img):
files = {'source': open(img, "rb")}
# 拼接url并上传图片至Chevereto图床
url = config_dict['chevereto_url'] + "?key=" + config_dict['api_key'] + "&format=json"
r = requests.post(url, files=files)
return json.loads(r.text)


# 判断是否上传成功并记录日志
def parse_response_url(json, img_path):
# 从返回的json中解析字段
if json['status_code'] != 200:
logging.info(json['error'])
logging.info("{}\tupload failure. status_code {} .".format(
img_path, json['status_code'])
)
else:
img_url = json["image"]["url"]
# 上传成功的图片url必须用print(logger.info不可以)打印出来,Typora才能识别出是上传成功了,才会自动替换链接。
print(img_url)
# 记录上传成功的日志
upload_log_path = config_dict['upload_log_path']
with open(upload_log_path, 'a') as file_object:
upload_log_list = read_file_to_list(config_dict['upload_log_path'])
year = time.strftime("%Y", time.localtime())
if "## " + year not in upload_log_list:
file_object.write("## " + year + "\n\n")
month = time.strftime("%Y-%m", time.localtime())
if "## " + month not in upload_log_list:
file_object.write("## " + month + "\n\n")
file_object.write(img_url + " --" + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + "\n")
file_object.write("![](" + img_url + ")\n\n")


if __name__ == '__main__':
# 工具描述及帮助
TOOL_DESC = """
A command line tool to upload pictures to chevereto image bed
"""
logging.info(TOOL_DESC)
if len(sys.argv) == 1:
sys.argv.append('--help')
# 获取参数
parser = argparse.ArgumentParser()
parser.add_argument('-s', '--source', type=str, nargs='+', help="上传图片路径", required=True)
parser.add_argument('-c', '--config', default="./config.ini", help="读取配置文件", required=True)
args = parser.parse_args()
img_list = args.source
config_path = args.config
# 读取配置文件
config_dict = read_config(config_path)
# 上传图片
up_to_chevereto(img_list)

5.1.2 配置Typora的Custom Command功能

打开Typora——文件——偏好设置——图像

  • “插入图片时”处:选择“复制图片到./${filename}.assets文件夹”,勾选对本地位置和网络位置的图片应用上述规则

  • ”上传服务设定“处:上传服务选择Custom Command,命令填写为python upload-xxx.py的绝对路径 -c config.ini的绝对路径 -s

    注:-s 后面是上传图片的路径,这个用的时候Typora就给我们自动补上了,不需要填写。

Typora配置Custom-Command

配置完毕可点击“验证图片上传选项”按钮进行验证,如果失败就要去检查你的INI配置文件、Python脚本文件和这里的命令是否配置正确.

配置成功后便可对md文档里的图片右键,使用上传图片功能了。上传成功后Typora会自动将我们的图片路径替换成链接,十分方便。

5.2 使用ShareX工具实现截图处理上传

5.2.1 下载安装ShareX工具

ShareX官网:https://getsharex.com/

ShareX可以实现截图——处理截图——上传图床于一体,并提供上传后的URL,将原来的四步简化为两步。这个工具的功能远不止这些,很强大的一个工具,自行探索吧。

ShareX工具

5.2.2 在ShareX里配置Chevereto API

打开ShareX——目标——上传目标设置——Chevereto——填写上传URL、API密钥,勾选直链,填写示例如下:

1
2
上传URL:http://example.com/api/1/upload
API密钥:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

注:上传URL的前半部分http://example.com就是你的图床地址;后半部分/api/1/upload是拼接成API的调用格式(upload的后面不要加/),其中1是指上传用户的ID,这个根据实际填写(如果只有一个管理员用户,那这里就是1,不是的话就去看一下用户ID据实填写)

5.3 图片压缩并添加盲水印

图片在上传到图床之前,最好是对图片进行压缩,这样别人访问时,加载的时间就会缩短,提高访问速度。可以使用OpenCV来实现图片压缩。

1
$ pip install opencv-python

如果你想保护自己的原创图片,那最好的方式就是为图片添加盲水印,盲水印就是图片有水印但人眼看不出来,需要通过程序才能提取水印,相当于隐形“盖章”,可以用在数据泄露溯源、版权保护等场景。可以使用阿里巴巴安全团队出品的 blind_watermark 库对图片添加盲水印。

1
$ pip install blind-watermark

下面我写了一个Python脚本,可以对指定文件夹下的图片批量压缩并添加盲水印。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# -*- coding: utf-8 -*-

"""
# Features: 指定文件夹下的图片压缩并添加盲水印(替换原文件)
# Description: 图片压缩使用 opencv 实现,盲水印添加使用 blind_watermark 实现
# Dependcy: pip install opencv-python 、 pip install blind-watermark
# Demo:python compressAndWaterMark.py -s 'E:\inputDir'(支持一次执行多个目录,空格分隔即可)
"""
import os
import argparse
import re
import time

import cv2
from blind_watermark import WaterMark

"""
# 使用 opencv 实现图片压缩,compress_config为图片压缩配置,说明如下:
# [cv2.IMWRITE_PNG_COMPRESSION, 9] 无损压缩(取值范围:0~9,数值越小,压缩比越低)
# [cv2.IMWRITE_JPEG_QUALITY, 80] 有损压缩(取值范围:0~100,数值越小,压缩比越高,图片质量损失越严重)
"""
class Compress_img:

def __init__(self, img_path, compress_config):
self.img_path = img_path
self.img_name = img_path.split('/')[-1]
self.compress_config = compress_config

def compress_img_CV(self, show=False):
old_fsize = os.path.getsize(self.img_path)
# 读取并压缩图片
img_resize = cv2.imread(self.img_path)
cv2.imwrite(self.img_path, img_resize, self.compress_config)
new_fsize = os.path.getsize(self.img_path)
# 计算压缩率
compress_rate = str(round(new_fsize / old_fsize * 100, 2)) + "%"
print("%s Image is compressed." % (self.img_path), "compression ratio:", compress_rate)
# 查看压缩后的图片
if show:
cv2.imshow(self.img_name, img_resize)
cv2.waitKey(0)

"""
# 使用 blind_watermark 实现图片盲水印的添加
# 项目地址:https://github.com/guofei9987/blind_watermark
"""
class WaterMark_img:

def __init__(self, img_path, password, wm_text):
self.img_path = img_path
self.password = password
self.wm_text = wm_text

def add_waterMark(self):
# 设置密码并添加水印
bwm = WaterMark(password_img=self.password, password_wm=self.password)
bwm.read_img(self.img_path)
bwm.read_wm(self.wm_text, mode='str')
bwm.embed(self.img_path)
# 打印添加的水印
len_wm = len(bwm.wm_bit)
wm_extract = bwm.extract(self.img_path, wm_shape=len_wm, mode='str')
print("%s Blind watermark added, " % (self.img_path), "Content is:", wm_extract)

# 校验是否为可读取的图片文件(路径中不可有中文)
def check_valid_img(img_path):
zhmodel = re.compile(u'[\u4e00-\u9fa5]')
match = zhmodel.search(img_path)
if match:
print("The path cannot contain Chinese: " + img_path)
return False
try:
img_content = cv2.imread(img_path)
var = img_content[0]
return True
except:
print("The image file cannot be read: " + img_path)
return False

if __name__ == '__main__':

# 获取参数配置
parser = argparse.ArgumentParser()
parser.add_argument('-s', '--source', type=str, nargs='+', help="Specify the directory path", required=True)
parser.add_argument('-c', '--config', default=[cv2.IMWRITE_PNG_COMPRESSION, 9], help="Image compression config", required=False)
parser.add_argument('-p', '--password', default=123456, help="Blind Watermark Password", required=False)
parser.add_argument('-w', '--waterMark', default='@eula.club', help="Blind watermark text", required=False)
args = parser.parse_args()
source = args.source
compress_config = args.config
password = args.password
wm_text = args.waterMark

# 定义及初始化
start_time = time.time()
start_time_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
all_img_count = 0
success_img_count = 0

# 递归读取并进行处理
print("Start compressing pictures and adding blind watermarks, please wait...")
for dir_path in source:
file_dir_list = os.listdir(dir_path)
for img_name in file_dir_list:
img_path = dir_path + "/" + img_name
if(check_valid_img(img_path)):
all_img_count += 1
# 使用 blind_watermark 添加盲水印
try:
waterMark = WaterMark_img(img_path, password, wm_text)
waterMark.add_waterMark()
print("Blind watermarking succeeded, replaced original file: " + img_path)
except Exception:
print("Blind watermarking failed, the image has been skipped: " + img_path)
success_img_count += 1
# 使用 opencv 压缩图片
try:
compress = Compress_img(img_path, compress_config)
compress.compress_img_CV()
print("Compression succeeded, replaced original file: " + img_path)
except Exception:
print("Compression failed, the image has been skipped: " + img_path)

# 输出结果
end_time = time.time()
end_time_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
time_consuming_str = str((end_time - start_time) * 1000) + 'ms'
print("=====" + end_time_str + ": ")
print("A total of " + str(all_img_count) + " pictures were found")
print("Successfully processed " + str(success_img_count) + " images, total time: " + time_consuming_str)

注:OpenCV无法读取中文路径的图片,请使用全英文路径。

6. 参考资料

[1] Chevereto-搭建私人图片外链库 from Newlearnerの小站

[2] Chevereto项目地址 from Github

[3] Chevereto-中文文档-上传到用户的解决方案 from Chevereto API

[4] 利用ShareX快速截图并自动上传分享到chevereto from 主机笔记

[5] chevereto图床打开非常慢,谷歌浏览器显示为ping值慢 from pc6a

[6] 用docker快速搭建chevereto图床 from CSDN

[7] Chevereto API v1 使用说明 from Chevereto官方文档

[8] 利用python脚本实现使用typora时图片自动上传到chevereto图床 from 知乎

[9] ValueError: check_hostname requires server_hostname from Bilibili

[10] 令人不悦的Error–requests.exceptions.ProxyError from CSDN

[11] Typora中自定义命令上传图片 from segmentfault