这是 用 Python 给文件名加入 hash 防止冲撞 的后续,现在站点的图片都存在自建的图床里(实际上就是同一台机器里跑的 minio 服务,搭建过程参见 基于 minio 搭建图床服务),又因为 Typora 支持用自定义的方式将图片自动上传到图床,因此简单整理了一个上传、自动加后缀二合一的小程序,这就是 tinyImgUploader。
typora 的图片上传规则
参考 Upload Images - Typora Support,自定义上传图片程序的要求是:
- typora 调用
/path/to/command "image-path-1" "image-path-2"
; - 程序输出值的后 n 行被 typora 当作上传图片后得到的链接。
使用方法
- 编译二进制程序
go build -o tinyImgUploader.exe --ldflags=" -w -s" .\main.go
- 将编译得到的程序放在合适的位置,并在该目录下建立
config.yaml
,这个文件设置了图床的位置、用户名及密码,内容参考:
endPoint: "example.com"
accessUser: "your-user"
accessPassword: "your-password"
bucket: "your-bucket"
- 在 typora 中设置 - 图像 - 上传服务设定,上传服务选择
Custom Command
,命令一栏中填程序的位置。- 注:windows 中路径的空格和特殊字符在输入时需要特殊处理,故推荐将程序放在不含空格、中文字符和特殊字符(如括号)的路径下。
- 点击
验证图片上传选项
按钮验证设置有效性。
代码简介
- 首先从
config.yaml
加载配置; - 根据配置中的信息建立 minio 连接;
- 计算文件的 md5 值,并转为 base58 编码(长度更短,可读性更高),附加在文件名中;
- 上传到 minio 并拼接访问的字符串。
package main
import (
"context"
"crypto/md5"
"fmt"
"io"
"log"
"mime"
"os"
"path/filepath"
"github.com/btcsuite/btcutil/base58"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"gopkg.in/yaml.v2"
)
type Config struct {
EndPoint string `yaml:"endPoint"`
AccessUser string `yaml:"accessUser"`
AccessPassword string `yaml:"accessPassword"`
Bucket string `yaml:"bucket"`
}
var config Config
func main() {
// Load config
ex, err := os.Executable()
if err != nil {
log.Fatalf("cannot get executable: %v\n", err)
os.Exit(1)
}
exPath := filepath.Dir(ex)
file, err := os.ReadFile(filepath.Join(exPath, "config.yaml"))
if err != nil {
log.Fatalf("cannot read config file: %v\n", err)
os.Exit(1)
}
err = yaml.Unmarshal(file, &config)
if err != nil {
log.Fatalf("cannot unmarshal config file: %v\n", err)
os.Exit(1)
}
// Initialize minio client object.
useSSL := true
client, err := minio.New(config.EndPoint, &minio.Options{
Creds: credentials.NewStaticV4(
config.AccessUser, config.AccessPassword, ""),
Secure: useSSL,
})
if err != nil {
log.Fatalf("cannot connect to minio: %v\n", err)
os.Exit(1)
}
ctx := context.TODO()
for _, file := range os.Args[1:] {
ext := filepath.Ext(file)
fileName := filepath.Base(file)
fileNameWithoutExt := fileName[:len(fileName)-len(ext)]
contentType := mime.TypeByExtension(filepath.Ext(file))
if contentType == "" {
contentType = "text/plain"
}
newName := fmt.Sprintf("%s-%s%s",
fileNameWithoutExt, GetBase58Md5(file), ext)
_, err := client.FPutObject(
ctx, config.Bucket, newName, file,
minio.PutObjectOptions{ContentType: contentType})
if err != nil {
log.Fatalf("failed to upload: %v\n", err)
return
}
fmt.Printf("https://%s/%s/%s\n",
config.EndPoint, config.Bucket, newName)
}
}
func GetBase58Md5(filePath string) string {
file, err := os.Open(filePath)
if err != nil {
log.Fatalf("Cannot read file: %v", err)
return ""
}
hash := md5.New()
if _, err := io.Copy(hash, file); err != nil {
log.Fatalf("Cannot hash file: %v", err)
return ""
}
return base58.Encode(hash.Sum(nil))
}