从本地 Markdown 到 CSDN:用 Cloudflare R2 + PicList + Codex 自动发布博客图片

写博客时,图片一多,最耗时的往往不是写内容,而是把本地图片一张张上传到 CSDN、掘金、知乎或微信公众号编辑器,再逐个替换图片链接。

这套工作流把这件事自动化:

本地 Markdown + 本地图片
        ↓
Codex 调用发布脚本
        ↓
PicList 将图片上传至 Cloudflare R2
        ↓
脚本把本地图片路径替换为公开 HTTPS 链接
        ↓
生成可直接导入 CSDN 等平台的 Markdown

最终,你只需要在本地正常写文章、正常插图;发布前运行一次命令或让 Codex 执行任务,就能得到一份图片链接已经替换完成的发布版 Markdown。


一、这套方案解决什么问题

以 CSDN 为例,直接导入带本地图片的 Markdown 时,常见问题包括:

  • 图片仍然是本地路径,导入后无法显示;
  • 文章里有几十张图片,需要逐张上传,操作重复且容易漏;
  • 图片文件名含中文、空格或特殊字符,上传或生成链接时容易出现乱码;
  • 图床链接不稳定,平台转存或外链加载失败;
  • 多篇文章共用一个图片目录,后期难以定位图片属于哪篇文章。

这套方案的目标不是“换一个图床”,而是把写作、图片托管、图片链接替换、平台导入串成一条稳定的发布流水线。


二、三个工具各自负责什么

1. Cloudflare R2:保存图片文件

Cloudflare R2 是对象存储服务。它负责保存文章图片,并提供可公开访问的图片地址。

在这套工作流中,R2 相当于“图片仓库”:

R2 Bucket
└── articles/
    ├── ai-product-manager-roadmap/
    │   ├── image-001.png
    │   └── image-002.png
    └── codex-workflow/
        ├── image-001.png
        └── image-002.png

建议每篇文章使用独立目录,后期查找、迁移或删除图片都会更清楚。

2. PicList:把本地图片上传到 R2

PicList 是本地图片上传客户端。它可以通过 Amazon S3 兼容协议连接 Cloudflare R2,并提供本地上传接口。

在这套工作流中,PicList 负责:

  • 接收脚本传来的本地图片;
  • 上传图片到 R2;
  • 返回图片的公开 URL;
  • 可按配置对图片进行压缩、转换 WebP、命名和归档。

3. Codex + 发布脚本:批量处理 Markdown

Codex 不需要手动逐张上传图片。它调用发布脚本后,脚本会自动:

  1. 读取本地 Markdown 文件;
  2. 找出 Markdown 图片语法和 HTML 图片标签中的本地图片;
  3. 跳过本来就是远程链接、data: 图片、代码块示例和占位符;
  4. 将真实存在的本地图片交给 PicList 上传;
  5. 把原文中的本地路径替换成 R2 的公开 HTTPS 地址;
  6. 生成一份可导入博客平台的 Markdown;
  7. 输出图片映射表和发布报告,方便核查。

三、首次配置:Cloudflare R2

这一部分只需配置一次。完成后,Cloudflare R2 会作为文章图片的长期存储位置,PicList 负责把本地图片上传到这里。

3.1 进入 R2 对象存储

登录 Cloudflare Dashboard。为了后续操作更直观,可以先在右上角将控制台切换为中文。

Cloudflare 控制台切换中文界面

在左侧菜单进入:

存储和数据库 → R2 对象存储

首次使用 R2 时,Cloudflare 可能会要求先开通 R2 订阅或补充支付方式。这一步是为了启用对象存储服务;具体可用额度、计费规则以 Cloudflare 当前控制台展示为准。

R2 对象存储入口与开通提示示例

3.2 创建图片 Bucket

点击 创建存储桶,新建一个专门用于博客图片的 Bucket。

创建 R2 存储桶入口

Bucket 名称建议使用简洁、稳定的英文,例如:

blog-assets
blog-images
my-blog-images

填写存储桶名称示例

建议:

  • 一个博客或一个个人知识库可先使用一个 Bucket;
  • 图片目录由后续的 PicList 或发布脚本统一管理,例如 articles/<文章-slug>/
  • Bucket 名称创建后尽量不要频繁修改,否则 PicList 配置也需要同步更新。

3.3 创建供 PicList 使用的 API Token

进入刚创建的 Bucket,在 R2 概览页 → 管理 中选择 Create API Token

进入 R2 管理并创建 API Token

权限选择:

Object Read & Write

作用范围建议仅限定到刚刚创建的博客图片 Bucket,而不要开放给所有 Bucket。

设置 Object Read & Write 权限

确认后创建 Token。

确认并创建 R2 API Token

创建成功后,Cloudflare 会显示三项后续配置 PicList 必须使用的信息:

Access Key ID
Secret Access Key
Endpoint URL

保存 Access Key、Secret Key 与 Endpoint URL

请立即安全保存这三项信息,尤其是 Secret Access Key:它通常只显示一次,关闭页面后无法再次查看原值。不要把密钥写入 Markdown、Git 仓库、截图或公开文章。

3.4 配置图片公开访问地址

PicList 需要用 Endpoint URL 将图片上传到 R2;但 Markdown、浏览器和 CSDN 需要的是可以直接访问图片的公开 URL。两者不是同一个地址。

你需要在 Bucket 的公开访问设置中启用 R2 的公开开发域名,或绑定自己的自定义域名。准备好一个公开图片前缀,例如:

https://pub-xxxx.r2.dev

或:

https://img.example.com

三类地址的作用如下:

地址类型 示例 用途
R2 Endpoint URL https://<account_id>.r2.cloudflarestorage.com PicList 上传到 R2 时填写
R2 公开开发域名 https://pub-xxxx.r2.dev 文章图片外链、测试访问
自定义域名 https://img.example.com 文章图片外链、长期品牌化使用

不要将 Cloudflare 控制台地址或 Endpoint URL 当成文章图片链接。发布前,务必在无登录状态的浏览器中测试一张已上传图片能否直接打开。


四、PicList 连接 Cloudflare R2

4.1 安装并进入图床设置

PicList GitHub Releases 下载并安装客户端。打开 PicList 后,进入 图床设置

Cloudflare R2 兼容 Amazon S3 协议,因此图床类型请选择:

Amazon S3

PicList 中选择 Amazon S3 图床

4.2 填写 R2 连接参数

将第 3 部分保存的 R2 信息填入 PicList。不同 PicList 版本的字段名称可能略有差异,但填写逻辑一致:

PicList 字段 应填写的内容 说明
Access Key ID R2 的 Access Key ID 创建 API Token 后得到
Secret Access Key R2 的 Secret Access Key 请勿泄露或上传到仓库
Bucket 你的 Bucket 名称 例如 blog-assets
Endpoint R2 的 Endpoint URL 格式通常为 https://<account_id>.r2.cloudflarestorage.com
Region 按 PicList 页面提示填写 R2 常见填写方式为 auto,以当前客户端校验为准
Custom URL / Public URL R2 公开开发域名或自定义域名 例如 https://pub-xxxx.r2.dev,不要填 Endpoint

PicList 填写 R2 的 Access Key、Bucket 和 Endpoint

其中最容易填错的是 Custom URL / Public URL:这里必须是浏览器、CSDN、掘金等平台能够直接访问图片的地址,而不是 R2 的上传终结点。

4.3 设置存储路径与文件命名

建议使用规则化的远程路径,避免所有图片堆在 Bucket 根目录。常见配置如下:

{year}/{month}/{md5}

这样可以按年月归档,并通过 md5 避免文件重名。

PicList 配置存储路径与命名规则

对于本文介绍的 Codex 发布工作流,也可以让脚本在每次发布时临时把上传目录设为:

articles/<文章-slug>/

例如:

articles/cloudflare-r2-piclist-codex-guide/

这样同一篇文章的所有图片会集中存放,便于以后迁移、清理或定位。

4.4 设为默认图床并验证上传

配置完成后:

  1. 点击 确认/保存
  2. 将该配置设为 默认图床
  3. 在 PicList 中随便上传一张测试图片;
  4. 复制返回链接,在浏览器无登录状态下打开;
  5. 能正常显示,才说明“R2 上传 + 公开访问”链路已经完成。

若上传成功但浏览器打不开图片,优先检查:

  • Custom URL / Public URL 是否误填成了 Endpoint URL;
  • R2 是否已开启公开访问或已正确绑定自定义域名;
  • Bucket 名称、Endpoint、密钥是否属于同一个 Cloudflare 账户和同一个 R2 配置;
  • 图片路径模板是否导致公开 URL 拼接异常。

4.5 开启 PicList 内置 Server

后续的发布脚本需要调用 PicList 的本地上传接口,因此请保持 PicList 打开,并开启它的内置 Server。

常见上传接口为:

http://127.0.0.1:36677/upload

若你为 PicList Server 配置了 API Key,接口可能是:

http://127.0.0.1:36677/upload?key=YOUR_KEY

端口以 PicList 实际配置为准。完成这一步后,Codex 或脚本才能把 Markdown 中的本地图片批量交给 PicList 上传。


五、文章文件应该如何组织

建议一篇文章使用一个独立文件夹,将 Markdown 和图片放在一起:

my-blog/
└── codex-image-publish-workflow/
    ├── article.md
    ├── images/
    │   ├── workflow.png
    │   ├── piclist-config.png
    │   └── result.png
    └── cover.png

文章中使用相对路径引用图片:

![整体工作流](images/workflow.png)

![PicList 配置示例](images/piclist-config.png)

<img src="cover.png" alt="封面图">

不建议一开始就在 Markdown 中手动填写 R2 URL。保留本地相对路径,能够让文章在本地编辑、预览和迁移时更方便;发布脚本会在生成发布版文件时自动替换它们。


六、安装并使用 Markdown Platform Publisher Skill

工作流Github链接:https://github.com/STRUGGLE1999/markdown-platform-publisher-skill/tree/main

这套工作流依赖一个用于“本地图片上传 + Markdown 链接替换”的 Skill 或脚本。它的核心能力是:

  • 支持 ![alt](image.png)
  • 支持 <img src="image.png">
  • 跳过已经是 https://data: 或协议相对地址的远程图片;
  • 跳过代码块中的示例图片语法;
  • 上传前将图片暂存为 ASCII 文件名,降低中文文件名、空格和特殊字符造成的 URL 编码问题;
  • 按文章创建独立的远程图片目录;
  • 输出发布版 Markdown、图片映射表和发布报告;
  • 可选校验图片公开链接是否真的可以访问。

假设你的项目中已经包含脚本:

scripts/publish_markdown_piclist.py

常用命令如下。

6.1 让 Codex 帮你安装这个 Skill

完成前面第三、四章节的 Cloudflare R2 配置PicList 连接 Cloudflare R2 配置 后,就可以让 Codex 帮你安装图片自动发布 Skill。

直接把下面的 GitHub 链接发给 Codex,让它帮你安装:

markdown-platform-publisher-skill

可以直接对 Codex 说:



```text
请帮我安装这个 GitHub 仓库中的 markdown-platform-publisher Skill:
https://github.com/STRUGGLE1999/markdown-platform-publisher-skill/tree/main

整个 Cloudflare R2 和 PicList 的配置过程只需要做一次,第一次会花一些时间。后续写完带有本地图片的 Markdown 博客后,直接把 Markdown 文件发给 Codex,即可自动上传图片、替换本地图片链接,并生成可导入 CSDN 等博客平台的 Markdown 文档。

在使用前,需要确认两项信息:

  1. PicList 本地上传接口端口;
  2. Cloudflare R2 最终公开图片域名。
1. 确认 PicList 本地上传接口端口

PicList 默认的本地图片上传接口通常是:

http://127.0.0.1:36677/upload

其中 36677 是 PicList 内置 Server 的默认端口。

使用前,请打开 PicList 并确认已经开启内置 Server。如果没有修改过端口,一般直接使用默认地址即可:

http://127.0.0.1:36677/upload

如果你曾修改过 PicList 的 Server 端口,则需要将上面的 36677 替换为实际端口号。

2. 获取 R2 最终公开图片域名

Codex 需要知道图片上传后的最终公开访问域名,才能将 Markdown 中的本地图片路径替换为可直接访问的 HTTPS 图片链接。

在 Cloudflare 控制台中,按下面路径查看:

  1. 进入 存储和数据库 → R2 对象存储
  2. 点击你创建的 Bucket,例如 blog-assets
  3. 打开顶部的 设置(Settings)
  4. 找到 Public Development URL(公共开发 URL)
  5. 确认已经启用;
  6. 页面中会显示一条 Public Bucket URL / 公共存储桶 URL
    在这里插入图片描述

它通常长这样:

https://pub-xxxxxxxxxxxxxxxx.r2.dev

这条地址就是 R2 的最终公开图片域名,也是 PicList 中需要填写的 Custom URL / 公网访问域名

例如,图片上传到 R2 后的路径为:

articles/codex/01-codex.png

那么最终公开访问地址就是:

https://pub-xxxxxxxxxxxxxxxx.r2.dev/articles/codex/01-codex.png

注意不要填错成下面这个地址:

https://<account_id>.r2.cloudflarestorage.com

这个是 S3 Endpoint URL,用于 PicList 连接 Cloudflare R2 并上传图片,不是给 Markdown、CSDN 或浏览器直接展示图片使用的公开域名。

简单区分如下:

地址类型 示例 用途
S3 Endpoint URL https://<account_id>.r2.cloudflarestorage.com PicList 连接 R2、上传图片
R2 公网访问域名 https://pub-xxxxxxxxxxxxxxxx.r2.dev Markdown 图片展示、浏览器访问、CSDN 导入

Cloudflare 将 r2.dev 地址称为 Public Development URL。启用后,可以在 Bucket 的 Settings 页面中查看对应的 Public Bucket URL

6.2 基础发布命令

如果你不想通过CodeX,也可以自己在命令行运行下面命令

Windows PowerShell 示例:

python scripts\publish_markdown_piclist.py `
  --input "D:\my-blog\codex-image-publish-workflow\article.md" `
  --expected-url-prefix "https://pub-xxxx.r2.dev" `
  --check-links `
  --keep-staged

macOS / Linux 示例:

python scripts/publish_markdown_piclist.py \
  --input "/Users/your-name/my-blog/codex-image-publish-workflow/article.md" \
  --expected-url-prefix "https://pub-xxxx.r2.dev" \
  --check-links \
  --keep-staged

运行完成后,通常会生成:

article(PicList发布版).md
article(PicList发布版).image-map.json
article(PicList发布版).publish-report.json
article(PicList发布版).piclist-staged/

其中:

文件或目录 用途
article(PicList发布版).md 最终可直接导入 CSDN、掘金、知乎等平台的文章
.image-map.json 本地图片路径与远程图片 URL 的映射关系
.publish-report.json 本次发布的统计、校验和异常信息
.piclist-staged/ 上传前生成的 ASCII 文件名暂存图片,便于排查文件名问题

6.3 指定文章远程目录

默认情况下,脚本可根据 Markdown 文件名自动生成英文 slug,远程目录类似:

articles/codex-image-publish-workflow/

也可以自行指定:

python scripts\publish_markdown_piclist.py `
  --input "D:\my-blog\codex-image-publish-workflow\article.md" `
  --slug "codex-image-publish-workflow" `
  --expected-url-prefix "https://pub-xxxx.r2.dev" `
  --check-links

建议 slug 使用全小写英文、数字和短横线,例如:

ai-product-manager-learning-roadmap
cloudflare-r2-piclist-guide
codex-markdown-publisher

这样生成的图片地址更干净,也更适合长期维护。


七、直接让 Codex 执行发布任务

配置完成后,日常不必记住完整命令。打开包含文章的项目后,可以直接对 Codex 说:

把当前 article.md 转成可导入 CSDN 的 Markdown。
请使用 PicList 本地上传接口上传所有本地图片到 Cloudflare R2,
将图片路径替换为公开 HTTPS 链接,
图片目录使用 articles/codex-image-publish-workflow/,
并检查生成后的图片链接是否能访问。

也可以把关键参数写得更明确:

请执行 Markdown 发布工作流:
- 输入文件:D:\my-blog\codex-image-publish-workflow\article.md
- PicList 上传接口:http://127.0.0.1:36677/upload
- R2 公开域名:https://pub-xxxx.r2.dev
- 远程目录:articles/codex-image-publish-workflow/
- 要求:上传所有本地图片、替换 Markdown 和 HTML 图片路径、校验公开链接、
  输出发布版 Markdown、图片映射 JSON 和发布报告。

Codex 执行后,重点查看以下结果:

original_local_image_refs
rewritten_remote_refs
remaining_local_image_refs

理想情况是:

{
  "original_local_image_refs": 12,
  "rewritten_remote_refs": 12,
  "remaining_local_image_refs": 0
}

这意味着文章原本有 12 个本地图片引用,已全部替换为远程链接,没有遗漏本地路径。


八、发布到 CSDN 等平台的操作

脚本成功后,使用生成的发布版文件,而不是原始 Markdown 文件。

推荐流程:

  1. 打开 article(PicList发布版).md
  2. 随机抽查几张图片链接,确认浏览器中可打开;
  3. 在 CSDN 新建文章,选择导入 Markdown;
  4. 导入发布版 .md 文件;
  5. 检查封面图、长图、表格附近图片和 HTML 图片是否显示正常;
  6. 保存草稿并预览;
  7. 确认无误后发布。

同一份发布版 Markdown 通常也适合导入:

  • 掘金;
  • 知乎;
  • 博客园;
  • 语雀;
  • Obsidian Publish;
  • 支持 Markdown 导入的微信公众号编辑器或第三方编辑器。

不同平台对 HTML、图片懒加载属性或外链策略可能略有差异。对于重要文章,建议先导入草稿并预览一次。


九、完整日常工作流

完成首次配置后,每次写文章可以按以下方式操作:

1. 在本地写 Markdown,图片继续用相对路径插入
2. 完成文章后,确认 PicList 已打开且内置 Server 已启动
3. 让 Codex 执行发布工作流,或运行发布脚本
4. 查看发布报告,确认 remaining_local_image_refs = 0
5. 打开发布版 Markdown,抽查图片链接
6. 将发布版 Markdown 导入 CSDN 等平台
7. 原始 Markdown 保留在本地,发布版用于外部平台

这样可以把“逐张上传图片、复制链接、反复替换”的重复操作,压缩成一次批处理。


十、常见问题排查

1. 脚本提示无法连接 PicList

检查:

  • PicList 是否已经打开;
  • PicList 内置 Server 是否已开启;
  • 接口端口是否为 36677
  • 你的接口是否设置了 API Key;
  • 本地防火墙是否阻止了该端口。

2. PicList 能上传,但生成的图片在浏览器打不开

优先检查:

  • R2 是否已配置公开访问;
  • PicList 的 Public URL / Custom URL 是否填写为真正可公开访问的图片域名;
  • 是否误把 R2 Endpoint URL 当成图片公开地址;
  • 上传路径和公开域名拼接后的地址是否正确。

3. 图片上传后 URL 出现乱码或平台导入失败

常见原因是原图文件名含有中文、空格或特殊字符。

建议:

  • 本地图片尽量使用英文、数字、短横线和下划线命名;
  • 保留脚本的 ASCII 暂存文件功能;
  • 不要手动修改脚本生成的远程 URL;
  • 对文件名异常的图片,先在本地重命名后重新执行发布。

4. 有些图片没有被替换

检查 Markdown 中是否使用了非常规写法,例如:

![](./images/a.png "title")

或复杂的 HTML 属性、变量占位符、模板语法。

先查看 .publish-report.json.image-map.json,确认脚本识别到哪些图片。必要时把特殊图片写法改成标准形式:

![图片说明](images/a.png)

5. 图片链接已经是远程地址,为什么没有重新上传

这是正常行为。发布脚本通常会跳过:

https://...
http://...
data:...
//example.com/...

因为这些本来就不是本地文件。若你希望把旧图床迁移到 R2,需要使用专门的“远程图片下载并重新上传”流程,而不是普通的本地图片发布流程。

6. 为什么要保留原始 Markdown

原始 Markdown 中保留相对路径,适合继续在本地编辑、Obsidian 预览或迁移项目。

发布版 Markdown 中是公开 URL,适合导入 CSDN 等外部平台。

建议两份都保留:

article.md                    # 原始写作版
article(PicList发布版).md   # 对外发布版

十一、结语

Cloudflare R2、PicList 和 Codex 组合后,真正节省时间的并不是“上传一张图片”,而是把整篇文章的图片发布过程变成可重复执行的自动化流程。

以后写博客时,只需要专注于内容和本地排版:

写文章 → 插本地图片 → 运行发布工作流 → 导入平台

当一篇文章有十几张、几十张甚至上百张图片时,这种批量上传、自动替换链接、自动校验的方式,能明显减少重复劳动,也能让多平台发布更稳定、更可维护。

更多推荐