Install
openclaw skills install @yangaiwu/blog-publishopenclaw skills install @yangaiwu/blog-publish博客系统内容发布与管理 Skill。封装博客系统 API(FastAPI 实现的无认证公开接口)的文章发布、标签管理、文件上传全流程,提供 curl 和 Python 双语言调用示例。
references/api-reference.md 为准:严禁根据经验/记忆/猜测自行拼写 API 路径。[blog-publish] 开头> {用户评论}\n\n[blog-publish] {回复}目标系统 API 地址必须在使用前确定。所有命令中的 {base_url} 替换为实际地址(格式 http://<host>:<port>)。
{base_url} 解析优先级:
.project-info/ 目录下 JSON 配置文件(config.BLOG_PUBLISH_BASE_URL)BLOG_PUBLISH_BASE_URLexport BLOG_PUBLISH_BASE_URL="http://<host>:<port>"
在项目根目录下创建 .project-info/ 目录,放入任意名称的 .json 文件:
{
"config": {
"BLOG_PUBLISH_BASE_URL": "http://<host>:<port>"
}
}
⚠️
.project-info/含敏感配置,不提交到 git 仓库(加入 .gitignore)。
Don't use for: 非本博客系统 API 的操作;用户/评论/留言/说说管理(本 skill 仅覆盖文章/标签/文件上传,按 Issue #3 范围)。
{base_url}
All endpoints below are relative to this base. 无认证(公开 API),不需要 header。
| Method | Path | Body / Params | Description |
|---|---|---|---|
| GET | /api/articles | page, size, lid, keyword | 分页查询文章列表 |
| GET | /api/articles/{id} | path: id | 查询文章详情(热度+1) |
| POST | /api/articles | ArticleCreate | 发布新文章 |
| PUT | /api/articles/{id} | ArticleUpdate | 更新文章(全可选) |
| DELETE | /api/articles/{id} | query: soft=true|false | 删除文章 |
| POST | /api/articles/{id}/restore | path: id | 恢复软删除文章 |
| GET | /api/articles/heat/top | query: limit | 热门文章 Top N |
| Method | Path | Body | Description |
|---|---|---|---|
| GET | /api/lables | — | 获取所有标签 |
| POST | /api/lables | LableCreate | 创建标签 |
⚠️ API 路径为
/api/lables(非标准拼写),调用须用实际路径。
| Method | Path | Body | Description |
|---|---|---|---|
| POST | /api/upload | multipart, field=file | 上传单个文件 |
| POST | /api/upload/multiple | multipart, field=files | 批量上传 |
| GET | /api/uploads/list | — | 列出已上传文件 |
无认证(公开 API)。仅需配置 {base_url}。
BLOG_PUBLISH_BASE_URL凭据前缀:由 skill name 推导(blog-publish → BLOG_PUBLISH)。
curl --max-time 30 {base_url}/health 确认可达,再调其他端点。/api/lables(非 labels),脚本内部已正确处理,curl 手动调用须用 lables。DELETE 默认软删除(soft=true);硬删除需显式传 soft=false,数据永久丢失,操作前务必确认。GET /api/articles/{id} 都会使 heat+1,批量查询热度时注意副作用。PUT 请求体所有字段为空时返回 400「没有需要更新的字段」,至少传一个字段。| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| title | string | 是(创建) | — | 文章标题 |
| content | string | 是(创建) | — | 文章内容(支持 HTML) |
| uid | int | 否 | 1 | 作者用户 ID |
| lid | int | 否 | 1 | 标签 ID |
| img | string | 否 | null | 封面图 URL |
| heat | int | 否 | 0 | 热度 |
| lname | string | 是(标签) | — | 标签名称 |
# curl
curl -s --max-time 30 {base_url}/health
# Expected: {"status":"ok","service":"blog-api","version":"1.0.0"}
# Python
python3 scripts/blog-publish.py health-check
# curl
curl -s --max-time 30 {base_url}/api/lables
# Expected: {"code":200,"data":[{"id":1,"lname":"技术"},...]}
# Python
python3 scripts/blog-publish.py list-labels
# curl
curl -s --max-time 30 -X POST {base_url}/api/articles \
-H "Content-Type: application/json" \
-d '{"title":"我的第一篇博客","content":"<p>Hello World</p>","uid":1,"lid":1}'
# Expected: {"code":200,"message":"文章发布成功","data":{"id":3}}
# Python
python3 scripts/blog-publish.py create-article \
--title "我的第一篇博客" --content "<p>Hello World</p>" --uid 1 --lid 1
# curl(用返回的 ID)
curl -s --max-time 30 {base_url}/api/articles/3
# Expected: {"code":200,"data":{"article":{...},"comments":[...]}}
# Python
python3 scripts/blog-publish.py get-article --id 3
# curl
curl -s --max-time 30 -X PUT {base_url}/api/articles/3 \
-H "Content-Type: application/json" \
-d '{"title":"更新后的标题","heat":10}'
# Expected: {"code":200,"message":"文章更新成功"}
# Python
python3 scripts/blog-publish.py update-article --id 3 --title "更新后的标题" --heat 10
# curl — 软删除(默认,可恢复)
curl -s --max-time 30 -X DELETE "{base_url}/api/articles/3?soft=true"
# Expected: {"code":200,"message":"文章已删除"}
# curl — ⚠️ 硬删除(不可恢复,需显式 opt-in)
curl -s --max-time 30 -X DELETE "{base_url}/api/articles/3?soft=false"
# Expected: {"code":200,"message":"文章已删除"}(数据永久丢失!)
# Python — 软删除
python3 scripts/blog-publish.py delete-article --id 3
# Python — 硬删除(不可恢复)
python3 scripts/blog-publish.py delete-article --id 3 --hard
# curl
curl -s --max-time 30 -X POST {base_url}/api/articles/3/restore
# Expected: {"code":200,"message":"文章已恢复"}
# Python
python3 scripts/blog-publish.py restore-article --id 3
# curl — 分页 + 按标签 + 关键词
curl -s --max-time 30 "{base_url}/api/articles?page=1&size=10&lid=1&keyword=博客"
# Expected: {"code":200,"data":[...],"total":N,"page":1,"size":10}
# Python
python3 scripts/blog-publish.py list-articles --page 1 --size 10 --lid 1 --keyword 博客
curl -s --max-time 30 "{base_url}/api/articles/heat/top?limit=5"
python3 scripts/blog-publish.py top-articles --limit 5
# 创建标签
curl -s --max-time 30 -X POST {base_url}/api/lables \
-H "Content-Type: application/json" -d '{"lname":"新标签"}'
# Expected: {"code":200,"data":{"id":7,"lname":"新标签"}}
python3 scripts/blog-publish.py create-label --lname "新标签"
# 标签列表
curl -s --max-time 30 {base_url}/api/lables
python3 scripts/blog-publish.py list-labels
# 单文件上传(curl -F)
curl -s --max-time 30 -X POST {base_url}/api/upload \
-F "file=@/path/to/image.jpg"
# Expected: {"code":200,"data":{"url":"/uploads/xxx.jpg","filename":"image.jpg","type":"image","size":12345}}
# Python(单文件)
python3 scripts/blog-publish.py upload-file --filepath /path/to/image.jpg
# 批量上传(curl -F 多个 file 字段)
curl -s --max-time 30 -X POST {base_url}/api/upload/multiple \
-F "files=@/path/to/a.jpg" -F "files=@/path/to/b.png"
# Expected: {"code":200,"data":[{"url":"...","filename":"a.jpg",...},...]}}
# Python(批量)
python3 scripts/blog-publish.py upload-files --filepaths /path/to/a.jpg /path/to/b.png
# 文件列表
curl -s --max-time 30 {base_url}/api/uploads/list
python3 scripts/blog-publish.py list-uploads
| HTTP 状态码 | 含义 | 处理建议 |
|---|---|---|
| 200 | 成功 | 正常处理返回数据 |
| 400 | 请求错误 | 检查文件类型 / 更新字段是否为空 |
| 404 | 资源不存在 | 确认 ID 正确,文章可能已被硬删除 |
| 422 | 验证错误 | 检查必填字段(title/content/lname)是否缺失或类型错误 |
| 500 | 服务异常 | API 后端异常,稍后重试或联系运维 |
{"status":"ok"})--help 无语法错误--format json)--format md)curl -s --max-time 30 {base_url}/api/...[blog-publish] ✅ 操作完成
| 操作 | 结果 |
|------|------|
| 创建文章 | ID=3 |
| 验证查询 | ✅ 可查 |
| 标签 | 技术类 |
[blog-publish] ❌ 操作失败
| 步骤 | 错误 |
|------|------|
| 创建文章 | HTTP 422: title field required |
references/api-reference.mdtemplates/test-vars.jsonscripts/blog-publish.py成功时:
分析结论:
- 场景:skill 创建(A2A 触发,Issue Agent 交棒 Coding Agent)
- Skill:blog-publish v1.0.0
- 产物:SKILL.md + scripts/blog-publish.py + references/api-reference.md + templates/test-vars.json
- 验证:validate-skill.sh ✅ / API 调用 ✅(health-check + list-articles + create-article)
- 必须执行:创建 PR → A2A 交棒 QA
失败时:
分析结论:
- 场景:skill 创建
- Skill:blog-publish
- 结果:🔴 失败(validate-skill.sh 失败 / API 不可达)
- 必须执行:Issue 评论报告错误 → A2A 回 Issue Agent
完成所有业务动作后,在最终答复末尾输出 JSON 块,然后结束任务:
{
"actions": ["做了什么"],
"conclusion": "处理结论",
"artifacts": ["产生的产物"],
"next_step": "下一步建议",
"issue_summary": "Issue 最新聚合结论(紧凑文本,用 | 分隔)"
}