跳转到正文

给博客图床造了个轮子:从手动上传到自动水印的 blogtool

PicGo 配不好的痛,化成了自己造轮子的动力。用 AI 写了一个 Markdown 图片自动上传 + 水印工具,顺手开源了。

麦极客
给博客图床造了个轮子:从手动上传到自动水印的 blogtool

写博客的人大概都有一个共同的痛点:图片管理太麻烦了。

本地写 Markdown 的时候引用本地图片,发布时再一张张上传到图床、手动替换链接——这流程重复、无聊、容易出错。在写上一篇文章的过程中,我终于下定决心把这个事情彻底解决掉。


为什么需要一个图床?

静态博客(比如我用的 Astro)本身不会帮你托管图片。虽然可以把图片放在 public/ 目录里一起构建,但随着文章增多,仓库体积会快速膨胀。把图片放到独立的对象存储服务(图床),用 CDN 加速访问,才是更合理的架构。

图床 = 图片的”云硬盘”,通过 URL 直接访问,博客文章里只需要放链接即可。


Cloudflare R2:薅个免费羊毛

对比了一圈主流对象存储之后,我选择了 Cloudflare R2。原因很简单:

  • 免费额度够用:每月 10GB 存储 + 1000 万次读请求,个人博客完全够
  • S3 兼容协议:直接用 AWS SDK 操作,生态成熟
  • 自带 CDN:Cloudflare 全球节点加速,图片加载飞快
  • 可绑定自定义域名:比如我的图床域名是 img.maigic.top

创建存储桶

R2 的创建流程很直观:注册 Cloudflare 账号 → 左侧菜单找到 R2 → 创建存储桶 → 记下 Endpoint 和密钥。如果你是第一次用,可以参考 Cloudflare R2 官方文档


图床上传工具的折腾之路

有了 R2 存储桶之后,接下来就是怎么把图片传上去的问题。

手动上传?算了吧

R2 控制台自带文件上传功能,但对于博客场景来说,每次写完文章都要手动上传、复制链接、替换 Markdown 里的路径……三步操作重复 N 次,写一篇有 10 张图的文章就得操作 30 次。

PicGo:过于庞大,依赖插件

PicGo 是国内最知名的图床上传工具,支持多种图床、自动复制链接、甚至有 VS Code 插件。看起来完美。

但有两个问题:

  • 本身程序较大,如果是GUI版本的,安装后就几百M大小了
  • 功能需要开发者维护第三方插件,我在实际配置的时候卡住了——R2 的 S3 插件配置项和 R2 的实际参数对不上,试了几种组合都连接失败

可能是我配置方式不对,也可能是插件版本兼容问题,总之 PicGo 和 R2 这对组合,在我这里没能愉快地工作起来。

既然现成的不好用,那就自己写

作为一个 AI 时代的开发者,我的第一反应不再是”去哪找个更好的工具”,而是——让 AI 帮我写一个。

于是我花了一个下午,从零开始搭建了一个基于 AWS S3 SDK 的上传工具。核心功能很简单:

  1. 扫描 Markdown 文件,找到所有本地图片引用(![alt](./img.png) 这种)
  2. 上传到 R2,按内容 hash 自动命名,避免重复上传
  3. 替换原始链接,把本地路径换成 R2 的公网 URL

第一次跑通的时候,看着 Markdown 里的本地路径一个个变成 https://img.maigic.top/blog/... 的链接,那一刻的满足感,比写十篇博客还强。

dryrun


新需求:给图片加上品牌水印

工具用了一段时间之后,我有了新的想法——给上传的图片自动加上品牌水印。

毕竟写技术博客免不了截图,截图里可能有产品界面、代码编辑器、终端输出。如果这些图片被别人转载,没有水印的话完全看不出处。

但手动加水印又回到了老问题:重复劳动。既然上传已经自动化了,水印为什么不一起加?

于是我继续迭代这个工具,增加了水印功能:

  • 使用 Sharp 库处理图片,性能优秀
  • 支持 SVG / PNG 水印图片源
  • 水印大小按图片短边动态缩放(不会在小图上贴一个巨大的 logo)
  • 图片太小时自动跳过(短边 < 400px 不加水印,避免喧宾夺主)
  • 可配置透明度、位置、最小图片尺寸

水印位置

水印放在左下角,透明度很低(15%),正常浏览几乎看不出来,但放大或者仔细看可以辨认。存在感恰到好处。


整合开源:@maigic/blogtool

工具在本地跑稳之后,我觉得这个东西也许能帮到其他有类似需求的人。于是我把它整理成了一个标准的 npm 包,开源到了 GitHub。

@maigic/blogtool — 一个 Markdown 图片自动上传 + 水印工具。

安装

npm install @maigic/blogtool

快速开始

首先在项目根目录创建配置文件 blogtool.config.mjs

export default {
  storage: {
    endpoint: "https://<ACCOUNT_ID>.r2.cloudflarestorage.com",
    credentials: {
      accessKeyId: "你的 Access Key ID",
      secretAccessKey: "你的 Secret Access Key",
    },
    bucket: "你的存储桶名称",
    publicDomain: "你的图床域名",
  },
  watermark: {
    imagePath: "./logo.svg",  // 水印图片路径
  },
  markdown: {
    dirs: ["./src/content/blog"],  // 要扫描的 Markdown 目录
  },
};

如果懒得手写配置,可以直接运行 npx blogtool init,会自动生成一份模板。

然后运行:

npx blogtool

工具会自动扫描配置目录下所有 Markdown 文件,找到本地图片引用,上传到 R2 并替换链接。

blogtool

加到 Git Hook 自动化

如果你想在每次 git commit 之前自动处理图片,可以添加 pre-commit hook:

echo '#!/bin/sh\nnpx blogtool' > .git/hooks/pre-commit

这样每次提交代码时,所有新的本地图片都会自动上传并替换链接。写博客的时候只管用本地路径引用图片,提交时一切自动完成。

也支持编程调用

如果你需要在构建流程中集成,也可以作为 Node.js 模块使用:

import { processImages } from "@maigic/blogtool";

const result = await processImages({
  storage: { /* ... */ },
  markdown: { dirs: ["./posts"] },
});

console.log(`上传了 ${result.totalUploaded} 张图片`);

技术细节

在开发过程中遇到了一些有意思的技术问题,记录一下。

图片去重

同一张图片如果出现在多篇文章里,不应该重复上传。工具使用文件的 MD5 hash 作为 R2 的 key 的一部分,上传前先 HEAD 请求检查文件是否存在,已存在则跳过。这样相同的图片无论引用多少次,在 R2 里只有一份。

SVG 水印处理

SVG 水印不能直接贴上去——原始 SVG 可能有大面积背景色,直接叠加会遮挡图片。工具会自动解析 SVG 结构,移除背景矩形,只保留图形元素,再用 Sharp 以指定透明度合成到目标图片上。

跨平台兼容

工具使用 tsup 构建,同时输出 ESM 和 CJS 格式,支持 Node.js 18+。CLI 和编程接口都有完整的 TypeScript 类型定义。


写在最后

这个工具的诞生路径很典型:遇到问题 → 现有方案不满意 → 用 AI 快速实现 → 迭代完善 → 开源分享。

整个过程里 AI 写了大部分代码,我做的是:定义需求、做技术决策、测试验证、处理 AI 搞不定的边界情况(比如 SVG 水印渲染的细节调优)。这种协作模式在可预见的未来会越来越普遍。

如果你也在维护一个博客,并且被图片管理折磨过,不妨试试 Maigic 的 blogtool。有问题或者建议,欢迎在 GitHub 提 Issue。