01五分钟上手
公开读端点不需要任何凭据。写端点需要登录态,凭据通过 X-WP-Nonce 头传递——nonce 由服务端在页面里下发(window.smcConfig.nonce),不要自己伪造,它是与当前登录用户绑定的。
# 公开端点,无需凭据
curl "https://example.com/wp-json/starmap-cluster/v1/posts?order=recent&page=1"
# 写端点:带 REST nonce
curl -X POST "https://example.com/wp-json/starmap-cluster/v1/posts" \
-H "X-WP-Nonce: <nonce>" \
-H "Content-Type: application/json" \
-d '{"body":"今夜视宁度 1.4 角秒,M42 尘埃带清晰。","title":"冷湖夜记"}'响应不是 JSON 而是 <!DOCTYPE html>,几乎总是因为重写规则没刷新。启用插件后必须跑一次 wp rewrite flush --hard,否则 /wp-json/ 会落回 WordPress 的 404 页面。前端脚本对这种情况有专门提示,但你自己的客户端要自己判断 content-type。
02鉴权与访客可读
读端点是否对访客开放,取决于站长在后台的「访客可读」开关。这是一个站点级策略,不是单条内容的可见性:
/posts /posts/{id} /posts/{id}/replies /groups /groups/{key}/members /media/resolve 对未登录请求返回数据。401。帖子详情也一起关——这是「私有社区」的语义,不是 bug。已分享的链接在登录后会跳回原页。/feed、/me、/me/inbox、/me/stars、/media、/reports、/bugs。nonce 的有效期与 WordPress 登录 cookie 绑定。过期后写操作会返回 403 rest_cookie_invalid_nonce,正确做法是刷新页面重新取 smcConfig.nonce,而不是重试。
03路由总表
按资源分组。「登录」一列指是否要求登录态。
| 方法 | 路径 | 说明 | 登录 |
|---|---|---|---|
| GET | /posts | 帖子流。支持 order / page / per_page / author / group / search | 否 |
| POST | /posts | 发星藏。body 必填,可选 title / star_url / group_key / media[] | 是 |
| GET | /posts/{id} | 单帖详情,附首屏楼层 | 否 |
| DELETE | /posts/{id} | 删帖(软删,保留行做审计) | 是 |
| GET | /posts/{id}/replies | 楼层分页 | 否 |
| POST | /posts/{id}/replies | 回应。支持 parent_id 形成子楼层 | 是 |
| DELETE | /posts/{id}/replies/{reply_id} | 删除单条楼层(作者本人 / 组主 / 站长) | 是 |
| POST | /posts/{id}/pin | 置顶 / 取消置顶组内帖子(仅组主,单组上限 2 条)。pinned 取 true/false | 是 |
| DELETE | /posts/{id}/group | 把帖子移出小组(不删帖)。作者本人或组主可调 | 是 |
| POST | /reactions | 切换共鸣 / 收藏。type 取 like 或 star | 是 |
| POST | /follow | 关注 / 取关某位观星者 | 是 |
| GET | /feed | 我的关注流 | 是 |
| POST | /reports | 举报帖子、楼层或私信。object_type 取 post / reply / message | 是 |
| POST | /bugs | 提交 Bug 反馈 | 是 |
| POST | /bugs/{id}/status | Bug 状态流转(修复 / 驳回 / 打回) | 是 |
| GET | /me | 我的计数:关注 / 关注者 / 星藏 / 未读 | 是 |
| GET | /me/inbox | 降级收件箱(子主题不在线时才有内容) | 是 |
| POST | /me/inbox | 标记已读,id 留空表示全部 | 是 |
| GET | /me/stars | 我收藏的星,按收藏时间倒序。返回结构与 /posts 相同,多一个 truncated | 是 |
| GET | /me/discussions | 我的讨论:我发过的 + 我回复过的帖子。每条多 myLastAt / isMine / newReplies 三个字段 | 是 |
| GET | /me/notification-preferences | 我的通知偏好(降级模式下唯一能关掉通知的地方) | 是 |
| GET | /mentions | @ 提及自动补全的候选用户,按前缀匹配 | 是 |
| POST | /messages | 给某位观星者发私信,peer + body | 是 |
| POST | /messages/block | 拒收 / 恢复接收某人的私信与通知。拒收后对方发不进私信,他的 @ 与回复也不再通知你(内容本身仍然可见) | 是 |
| GET | /announcements | 公告列表,支持 level 与 page | 否 |
| GET | /announcements/banner | 当前应展示的公告条(未过期、已到生效时间) | 否 |
| GET | /announcements/{id} | 单条公告详情 | 否 |
| GET | /groups | 小组索引,支持 order=active|new 与 search | 否 |
| POST | /groups | 建组 | 是 |
| POST | /groups/join | 加入 / 退出小组 | 是 |
| PATCH | /groups/{key} | 改组资料(仅组主) | 是 |
| DELETE | /groups/{key} | 解散小组(仅组主,需组名确认) | 是 |
| POST | /groups/{key}/owner | 转让组主 | 是 |
| GET | /groups/{key}/members | 成员列表 | 否 |
| DELETE | /groups/{key}/members/{user_id} | 把成员移出小组(仅组主) | 是 |
| POST | /groups/{key}/subscription | 订阅 / 退订「组内新帖」通知。on 取 true/false;仅成员可调 | 是 |
| POST | /media | 上传媒体(multipart),返回 attachment_id | 是 |
| POST | /media/resolve | 解析外链。只解析不入库,无副作用 | 否 |
| DELETE | /media/{id} | 删除媒体(仅作者) | 是 |
| GET | /plans | 观测计划列表。scope 取 upcoming / mine / past,可按 group 过滤。坐标只给到 0.1°(约 11km) | 否 |
| POST | /plans | 发起观测计划。title + starts_at 必填;starts_at 接受时间戳或 datetime-local 串(按站点时区解释)。每人同时最多 5 个未开始的计划 | 是 |
| GET | /plans/{id} | 单条计划详情,附报名名单。exact 字段只有发起者与已报名者能拿到,其余人为 null | 否 |
| POST | /plans/{id}/rsvp | 报名 / 改意向 / 退出。state 取 going / maybe / cancelled。名额是软上限,满了仍可进 | 是 |
| POST | /plans/{id}/status | 取消或恢复整场计划。status 取 cancelled / publish;仅发起者与站长可调 | 是 |
04一条星藏的字段
星藏的结构是星群的核心契约。媒体只存结构化引用、不存 HTML——这样渲染永远由插件自己的模板负责,客户端拿到的永远是数据。字段全部 camelCase,转换发生在 Posts::prepare_post() 一处。
{
"id": 101,
"title": "零下十八度,等到了 M42 的尘埃带",
"body": "冷湖的风把视宁度压到 1.2 角秒…",
"url": "https://example.com/cluster/p/101/",
"createdAt": 1761926400,
"starCount": 1284,
"replyCount": 96,
"views": 41209,
"liked": false,
"starred": false,
"author": { "id": 2, "name": "沈墨白", "avatar": "…", "isFollowing": true },
"group": { "key": "deep-sky", "name": "深空摄影研习社", "url": "…" },
"star": { "url": "…", "title": "M42 猎户座大星云", "icon": "…" },
"media": [
{ "kind": "image", "provider": "attachment", "url": "…", "thumb": "…" }
]
}05限流档位
限流按「用户 + 动作」计数,用 transient 实现。触发时返回 429 与错误码 smc_throttled,响应里没有 Retry-After——按下面的窗口自己退避。
| 动作 | 上限 | 窗口 | 为什么是这个数 |
|---|---|---|---|
post | 10 | 5 分钟 | 正常观星一晚发不了十条 |
reply | 20 | 5 分钟 | 讨论比发帖频繁,给两倍额度 |
reaction | 60 | 1 分钟 | 点赞是低成本的,只看是否脚本刷量 |
follow | 30 | 1 分钟 | 同上 |
report | 5 | 5 分钟 | 举报比发帖「重」,正常不会连发五条 |
bug | 3 | 10 分钟 | 要写清楚,连发三条已是异常信号 |
upload | 20 | 5 分钟 | 按文件数计,不按字节 |
group_create | 5 | 1 小时 | 建组是重操作 |
06错误码
错误响应是 WordPress 标准结构。message 是可以直接展示给用户的中文,code 才是给你做分支的。
| code | HTTP | 含义 / 处理建议 |
|---|---|---|
smc_forbidden | 403 | 不是内容作者且无审核权限。不要重试。 |
smc_forbidden_group | 403 | 不是组主。不要重试。 |
smc_no_post | 404 | 帖子已删或不存在。引导回列表。 |
smc_no_group | 404 | 小组不存在或已解散。 |
smc_no_reply | 404 | 楼层已删。 |
smc_throttled | 429 | 触发限流。按窗口退避,别立刻重试。 |
smc_empty_body | 400 | 正文为空。前端应提前拦截。 |
smc_key_taken | 409 | 组标识被占用。让用户换一个。 |
smc_media_unsupported | 415 | 媒体类型不在白名单。SVG 一律拒绝。 |
smc_too_large | 413 | 超出体积上限(按类型分别限)。 |
smc_unrecognized | 422 | 外链解析不出平台 ID。提示用户换链接。 |
smc_no_table | 500 | 数据表缺列 / 缺表,通常是升级没跑完。访问任意前台页触发升级。 |
07用过滤器扩展,而不是改插件
星群把可扩展点都做成了 apply_filters。改插件源码的代价是下次升级必然冲突,而下面这些钩子是有意暴露的稳定接口。
// 1) 注入设计令牌(颜色 / 间距),作用域限定 .smc-app
add_filter( 'starmap_cluster_theme_tokens', function ( $tokens, $view ) {
$tokens['--smc-gold'] = '#7fb6ff'; // 换强调色
return $tokens;
}, 10, 2 );
// 2) 扩展可嵌入的外部平台白名单
add_filter( 'starmap_cluster_media_providers', function ( $providers ) {
$providers['vimeo']['enabled'] = true;
return $providers;
} );
// 3) 追加通知类型(需与子主题的通知面板成对实现)
add_filter( 'starmap_cluster_notification_types', function ( $types ) {
$types['smc_mention'] = '有人在星群里 @ 了你';
return $types;
} );
// 4) 追加 body class / head 片段
add_filter( 'starmap_cluster_body_class', function ( $classes ) {
$classes[] = 'my-custom-theme';
return $classes;
} );第一,令牌名必须匹配 --[a-z0-9-]+——这些值最终拼进 <style>,一个右花括号就能闭合规则注入任意 CSS。
第二,不要返回空值。--x: ; 会让 var(--x, 16px) 的 fallback 失效(未定义才走 fallback,空值不走)。要「不设置」就把整个键删掉。
08明确没有的东西
避免你在文档里找半天:
- 没有 OAuth。第三方应用无法代用户操作,只能读公开数据或在用户自己的 cookie 会话里跑。
- 没有 webhook。想要实时推送请用子主题的 VAPID 链路,那是另一个模块。
- 没有批量端点。不要并发打出上百个请求,限流会按用户维度拦住你。
- 没有历史版本。帖子软删保留行做审计,但没有版本回滚 API。
到联系我们页走 Bug / 需求反馈,附上你的使用场景。我们优先做「现有数据结构能支撑」的端点,而不是为了接口好看新增表。