article:用 Claude Code 打造博客文章发布 Skill 的实战记录

2026年7月25日 兴趣使然 19 分钟阅读 1 次阅读
📖 文章摘要

从需求到落地,记录如何用 Claude Code 为个人博客构建一个 Agent 可调用的文章发布 Skill,涵盖 API 分析、智能元数据生成、缺失信息兜底等设计思路

用 Claude Code 打造博客文章发布 Skill 的实战记录

背景

我的博客是 Nuxt 3 + FastAPI 的全栈项目,数据库用 SQLite,部署在一台韩国首尔的阿里云 ECS 上。平时发文章都是打开后台管理界面手动填写标题、选分类、贴标签、粘贴 Markdown 内容,流程比较繁琐。

自从开始用 Claude Code 作为日常开发工具后,一直想实现一个场景:我丢一段正文给 Agent,它自己拟标题、选分类标签、调 API 发布,我确认一下就行。正好博客项目里已经有完整的 RESTful API,于是决定动手做一个 Skill。

这篇文章完整记录了从需求分析到最终可用的全过程,包括踩过的坑和设计上的迭代。

第一步:摸清 API

做 Skill 之前,先得搞清楚博客有哪些接口可以用。直接让 Claude Code 去读后端源码,它几分钟就梳理出来了。

认证流程

博客后台接口用 JWT 认证,流程是:

POST /api/auth/login
Body: {"username": "leap", "password": "xxx"}
Response: {"access_token": "eyJhbGciOiJIUzI1NiIs..."}

拿到 token 后,后续所有请求带上 Authorization: Bearer <token> 头。token 有效期 7 天(在 config.py 里配置的 ACCESS_TOKEN_EXPIRE_DAYS = 7)。

文章相关接口

关键接口只有一个:

POST /api/admin/articles
Headers: Authorization: Bearer <token>, Content-Type: application/json

请求体对应 ArticleCreate schema,字段如下:

class ArticleCreate(ArticleBase):
    title: str              # 必填
    slug: str               # 必填,URL 标识
    content_md: str         # 必填,Markdown 正文
    summary: str = ""       # 可选,摘要
    cover_image: str = ""   # 可选,封面图
    category_id: int = None # 可选,分类 ID
    is_published: bool = False  # 是否发布
    is_top: bool = False    # 是否置顶
    top_order: int = 0      # 置顶排序
    allow_comment: bool = True  # 是否允许评论
    meta: dict = {}         # 扩展元数据
    tag_ids: list[int] = [] # 标签 ID 列表
    created_at: str = None  # 自定义创建时间
    updated_at: str = None  # 自定义更新时间
    published_at: str = None # 自定义发布时间

辅助接口

还需要两个查询接口来获取分类和标签:

GET /api/categories     → 返回分类列表
GET /api/tags           → 返回标签列表

这两个是公开接口,不需要认证。

参考已有的 pan Skill

项目里已经有一个 pan Skill(网盘上传),它的认证模式是:把账号密码写死在脚本里,每次调用时自动登录获取 token,内存缓存 7 天。直接复用这个思路,但把硬编码的账号密码改成从 env 文件读取。

第二步:设计 Skill 结构

参考 pan Skill 的目录结构,article Skill 也是三个文件:

article/
├── env                      # 配置文件(地址 + 账号密码)
├── SKILL.md                 # Skill 说明文档(Agent 读这个来知道怎么用)
└── scripts/
    └── publish_article.py   # 主脚本

env 文件

BLOG_URL=https://vue2.xyz
BLOG_USERNAME=leap
BLOG_PASSWORD=xxx

为什么用 env 文件而不是环境变量?因为 Skill 可能在不同机器上用,env 文件跟着 Skill 目录走,不需要额外配置系统环境变量。同时支持环境变量覆盖,方便 CI 场景。

脚本核心逻辑

脚本的主要流程:

# 1. 加载配置
cfg = load_env()  # 从 env 文件读取,环境变量可覆盖

# 2. 登录获取 token(带缓存)
def login():
    # 检查内存缓存是否有效(提前 5 分钟刷新)
    if _token_cache["token"] and _token_cache["expire"] > now + 300:
        return _token_cache["token"]
    # 调用登录接口
    resp = urllib.request.urlopen(login_url, data)
    _token_cache["token"] = resp["access_token"]
    return _token_cache["token"]

# 3. 解析 Markdown frontmatter
def parse_frontmatter(content):
    # 支持 --- 包裹的 YAML 头部
    # 提取 title, slug, summary, category, tags 等字段
    # 返回 (metadata_dict, body)

# 4. 调用 API 创建文章
def create_article(payload):
    token = login()
    req = urllib.request.Request(url, data=json.dumps(payload), ...)
    return json.loads(urllib.request.urlopen(req).read())

slug 自动生成

用户不一定提供 slug,需要从标题自动生成:

def slugify(text):
    text = text.lower().strip()
    text = re.sub(r'[^\w一-鿿\s-]', '', text)  # 保留中文和字母数字
    text = re.sub(r'\s+', '-', text)              # 空格转连字符
    text = re.sub(r'-+', '-', text).strip('-')    # 去重连字符
    return text or "untitled"

中文标题也能处理,比如「运维工程师面试备战全攻略」会生成 运维工程师面试备战全攻略(中文保留在 slug 中,浏览器能正常访问)。

第三步:设计交互模式

这是整个 Skill 设计里最关键的决策:Agent 拿到文章后该怎么处理?

初版方案:直接发布

一开始想的是 Agent 直接调 API 发布就行。但实际测试后发现几个问题:

  1. 用户可能只丢一段正文,连标题都没有
  2. 分类和标签是博客里预设的,Agent 不知道有哪些选项
  3. 如果信息不完整,脚本报错退出,Agent 拿到错误信息不知道该怎么办

迭代后的方案:四步流程

最终设计了一个四步流程:

1. --list        → 获取博客现有分类和标签
2. 读文章内容    → Agent 自动生成标题、slug、摘要、分类、标签
3. 展示给用户    → "标题xxx,分类xxx,标签xxx,你看看有没有要改的?"
4. --dry-run     → 预检信息完整性 → 正式发布

这个流程的核心思想是:Agent 负责生成,用户负责确认。不是让用户从零填写,而是让 Agent 基于已有数据推荐,用户只需要说「可以」或「改一下」。

SKILL.md 的作用

SKILL.md 是 Agent 的使用手册,定义了工作流和规则。写这个文档的关键是:不要假设 Agent 会「聪明地」处理各种情况,要把每一步都写清楚

比如「生成元数据」这一步,明确写出了:

  • slug 要英文小写+短横线,如 fastapi-async-guide
  • 摘要 1-2 句话,100 字以内
  • 标题 10-30 字,体现核心主题
  • 分类选最核心的一个,都不匹配则建议创建新分类
  • 标签选多个相关的,覆盖文章涉及的主题

这些规则不是一次写好的,是测试了几轮后逐步补充的。

第四步:处理缺失信息

实际测试中发现一个问题:如果用户没给分类和标签,脚本直接报错退出,Agent 拿到错误信息不知道该怎么办。

方案:--dry-run 预检模式

加了一个 --dry-run 参数,不创建文章,只验证信息完整性,输出结构化 JSON:

python scripts/publish_article.py article.md --dry-run
{
  "ok": false,
  "payload": {...},
  "warnings": [],
  "missing": {
    "category": {
      "provided": null,
      "available": [
        {"name": "技术", "id": 1},
        {"name": "server", "id": 8}
      ]
    },
    "tags": {
      "provided": null,
      "available": ["Python", "FastAPI", "运维", "Linux"]
    }
  }
}

Agent 看到 ok: false 就知道还缺信息,从 missing 里拿到可用选项,展示给用户选择。这样即使用户什么都不提供,Agent 也能一步步引导完成。

退出码设计

脚本用不同的退出码区分状态:

  • 0:成功(发布完成或 dry-run 信息完整)
  • 1:错误(网络、认证等)
  • 2:信息不完整(需要补齐后重试)

Agent 可以根据退出码决定下一步操作,比解析错误信息更可靠。

第五步:--list 查询分类标签

一开始的设计是让 Agent 直接读取 frontmatter 中的分类和标签名称,脚本内部去 API 查询对应的 ID。但后来发现一个问题:用户可能不知道博客里有哪些分类和标签

于是加了 --list 命令,直接输出博客所有分类和标签:

python scripts/publish_article.py --list
{
  "categories": [
    {"name": "技术", "id": 1, "article_count": 55},
    {"name": "blogTech", "id": 2, "article_count": 20},
    {"name": "兴趣使然", "id": 3, "article_count": 32},
    {"name": "server", "id": 8, "article_count": 9}
  ],
  "tags": [
    {"name": "Python", "id": 7, "article_count": 14},
    {"name": "运维", "id": 12, "article_count": 62},
    {"name": "Linux", "id": 32, "article_count": 7}
  ]
}

Agent 先调 --list,再根据文章内容从已有选项中推荐。这样既不会编造不存在的分类标签,也能给用户一个清晰的选项列表。

第六步:处理极端场景

场景:用户只给了正文,什么都没填

这是最极端的情况,也是最考验 Skill 设计的场景。完整流程如下:

Agent: 收到一段 Markdown 正文
  ↓
Agent: 调用 --list 获取分类标签
  ↓
Agent: 读取正文,自动生成:
  - 标题:根据内容提炼
  - slug:英文 SEO 友好
  - 摘要:1-2 句概括
  - 分类:从 --list 结果中选
  - 标签:从 --list 结果中选
  - 状态:默认草稿
  ↓
Agent: 展示给用户确认
  "我帮你拟好了:
   标题:FastAPI 异步编程实践指南
   slug:fastapi-async-guide
   摘要:...
   分类:技术
   标签:Python、FastAPI
   状态:草稿
   你看看有没有要改的?"
  ↓
用户: "ok"
  ↓
Agent: 调用 --dry-run 预检
  ↓
dry-run 返回 ok: true
  ↓
Agent: 调用发布命令(不加 --published,保持草稿)
  ↓
发布成功,返回文章链接

场景:slug 已存在

如果用户提供的 slug 在博客中已存在,API 会返回 400 错误。脚本会捕获这个错误并提示用户更换 slug。

场景:分类或标签不存在

如果用户指定了一个不存在的分类或标签名,--dry-run 会在 warnings 中输出提示,并在 missing 中列出所有可用选项。Agent 看到后会告知用户并建议更换。

第七步:部署脚本的教训

在做 Skill 的过程中,还顺手排查了一个部署问题。测试 Skill 时发现 API 返回 502 Bad Gateway,上服务器一查发现 blog-api.service 被停掉了。

原因是跑 deploy.py 的数据同步功能时,脚本会先 stop API 服务 → 下载数据 → start API 服务。如果中间被中断(比如 Ctrl+C),就卡在 stop 状态,API 服务不会自动恢复。

修复方案是在下载分支加 try/finally:

run('systemctl stop blog-api', "停止后端")
try:
    pull_db()
    rsync_down(REMOTE_UPLOADS, LOCAL_UPLOADS, "下载 uploads")
    rsync_down(REMOTE_PAN, LOCAL_PAN, "下载 pan")
finally:
    run('systemctl start blog-api', "启动后端")

这个经验也应用到了 Skill 的设计里:预检不通过不要直接报错,而是输出结构化的缺失信息,让调用方能优雅地处理。

最终成品

Skill 目录结构

article/
├── env                      # 博客地址 + 账号密码
├── SKILL.md                 # Skill 说明文档
└── scripts/
    └── publish_article.py   # 主脚本(约 350 行)

SKILL.md 内容

---
name: article
description: 发布文章到博客,智能生成元数据,支持分类标签查询。
---

# 博客文章发布

## 工作流

### 1. 获取博客现有分类和标签
python scripts/publish_article.py --list

### 2. 读取文章,生成全部元数据
根据文章正文自动生成:标题、slug、摘要、分类、标签、发布状态。
分类和标签必须从 --list 结果中选择,不可编造。
展示给用户确认。

### 3. 用户确认后,预检再发布
python scripts/publish_article.py article.md --title "..." --slug "..." \
  --summary "..." --category "..." --tags "..." --dry-run
python scripts/publish_article.py article.md --title "..." --slug "..." \
  --summary "..." --category "..." --tags "..."

核心命令

# 查询分类标签
python scripts/publish_article.py --list

# 预检(不创建文章)
python scripts/publish_article.py article.md \
  --title "运维工程师面试备战全攻略" \
  --slug "ops-engineer-interview-guide" \
  --category "server" \
  --tags "运维,Linux,Nginx" \
  --dry-run

# 发布(默认草稿)
python scripts/publish_article.py article.md \
  --title "运维工程师面试备战全攻略" \
  --slug "ops-engineer-interview-guide" \
  --category "server" \
  --tags "运维,Linux,Nginx"

# 直接发布
python scripts/publish_article.py article.md \
  --title "..." --category "..." --tags "..." --published

实际测试结果

丢了一篇运维面试计划的 Markdown 文件给 Agent,它自动生成了:

  • 标题:运维工程师面试备战全攻略
  • slug:ops-engineer-interview-guide
  • 摘要:涵盖 Linux 基础、Nginx、阿里云、CI/CD 及常见面试问题的回答思路
  • 分类:server
  • 标签:运维、Linux、Nginx、Docker、Shell、CI/CD
  • 状态:草稿

dry-run 预检 ok: true,正式发布成功,文章 ID 215,链接 https://vue2.xyz/p/xxxxx。
整个过程不需要手动填任何字段。
图片
图片

总结

做这个 Skill 的几个关键经验:

  1. 先搞清 API 再动手:让 Claude Code 读源码分析接口,比自己翻文档快得多。几分钟就梳理出了认证流程、请求体结构、所有字段的含义
  2. Agent 负责生成,用户负责确认:不要让用户从零填写,Agent 基于已有数据推荐,用户只管确认或修改。这个模式比传统的表单填写体验好得多
  3. 预检比直接报错更好--dry-run 输出结构化 JSON,让 Agent 能理解缺什么、有什么选项,而不是对着一条错误信息束手无策
  4. 配置外部化:账号密码放 env 文件,不写死在脚本里,改密码时只改一处。顺便也给已有的 pan Skill 加上了 env 支持
  5. 容错要考虑中断场景:关键操作用 try/finally 确保恢复,不管是 Skill 还是部署脚本都一样
  6. SKILL.md 要写得足够明确:不要假设 Agent 会「聪明地」处理各种情况,每一步该做什么、不该做什么都要写清楚

整个 Skill 从想法到可用,大概花了10分钟。Claude Code 在这个过程中的角色不只是写代码,更像是一个搭档 — 我提需求,它分析、实现、测试,遇到问题一起排查。比如 API 返回 502 时它主动去服务器查日志,发现是部署脚本中断导致服务没重启,顺便还帮我修了部署脚本的容错逻辑。这种协作模式比我之前自己从零写要高效得多。

文章创建于:2026年7月25日CC BY-NC-SA 4.0
📡

评论

暂无评论,来写第一条吧

© 2026 My Blog. Built with Nuxt.js + FastAPI.