--- sidebar_position: 5 title: 后端 API 契约 sidebar_label: 后端 API description: Plaza 和运营后台所有 API 的路由、请求/响应结构、权限规则和错误码 --- # 后端 API 契约 ## 通用规范 - 所有路由前缀:`/api/plaza/v1/` - 运营路由前缀:`/api/ops/v1/` - 请求体格式:`application/json` - 响应格式:`{ "data": ... }` 或 `{ "error": { "code": "...", "message": "..." } }` - 分页:游标分页,参数 `cursor`(上一页最后一条的 `id`)和 `limit`(默认 20,最大 50) - 所有写接口需要登录,读接口游客可访问 - 所有接口携带 `X-Request-ID` 响应头 --- ## Plaza 公开 API ### GET /api/plaza/v1/feed 获取广场 Feed 列表(热门或最新)。 **权限**:无需登录 **Query 参数**: | 参数 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `sort` | `hot` \| `new` | `hot` | 排序方式 | | `category` | string | - | 分类 slug,不传则全部 | | `cursor` | string | - | 游标翻页 | | `limit` | number | 20 | 每页数量 | **响应**: ```json { "data": { "posts": [ ... ], "featured": { "homepage_banner": [ ... ], "trending": [ ... ] }, "next_cursor": "post_yyy", "has_more": true } } ``` **精选与 Feed 合并规则**(Sprint 5 起): 1. `featured.homepage_banner`:单独返回,前端渲染在 Feed 上方轮播,**不参与** `posts` 游标分页。 2. `featured.trending`:在 `sort=hot` 且 `cursor` 为空时,插入 `posts` 列表前 10 位(按 `plaza_featured.sort_order`),去重后其余位按 `hot_score` 填充。 3. Sprint 1–4:`featured` 字段为 `{ "homepage_banner": [], "trending": [] }`。 **单条 post 对象字段**(`posts` 数组元素、`GET /posts/:id` 同构): ```json { "id": "post_xxx", "title": "2025年Q2项目复盘报告", "summary": "本季度核心指标完成情况与下季度规划", "cover_url": "https://assets.go.tkmind.cn/...", "category": { "id": "cat_xxx", "name": "职场报告", "slug": "work-report" }, "tags": ["复盘", "季度报告"], "author": { "user_id": "user_xxx", "slug": "zhangsan", "display_name": "张三", "avatar_url": "https://..." }, "stats": { "view_count": 1234, "like_count": 89, "collect_count": 23, "comment_count": 15 }, "viewer_reacted": { "liked": false, "collected": true }, "published_at": "2025-06-12T10:30:00Z" } ``` `viewer_reacted` 仅登录用户返回,游客为 `null`。 --- ### GET /api/plaza/v1/posts/:id 获取单个帖子详情。 **权限**:无需登录 **响应**:同 feed 中的单条 post 对象,额外包含: ```json { "data": { "post": { ...上方字段..., "publication_url": "https://go.tkmind.cn/u/zhangsan/pages/q2-report", "allow_comment": true } } } ``` --- ### POST /api/plaza/v1/posts 将已发布的 MindSpace 页面发布到广场。 **权限**:需要登录,`publication_id` 必须归属当前用户 **请求体**: ```json { "publication_id": "pub_xxx", "category_id": "cat_xxx", "tags": ["复盘", "季度报告"], "cover_url": "https://...", // 可选,不传则用 publication 的封面 "allow_comment": true } ``` **业务校验**: 1. `publication_id` 对应的 publication `status = online`,否则返回 `PUBLICATION_NOT_ONLINE`。 2. 该 `publication_id` 在 `plaza_posts` 中已存在 → 返回 `409 ALREADY_PUBLISHED`(**创建接口不支持更新**,更新走 `PATCH`)。 3. 当前用户未被运营封禁发帖权限,否则返回 `POST_PERMISSION_DENIED`。 4. 执行内容二次安全扫描(异步),帖子先以 `status = pending_review` 入库;扫描通过且(人工或自动)审核通过后变为 `published`。 **Feed 可见性**:仅 `status = published` 的帖子出现在公开 Feed;`pending_review` 仅作者本人在 MindSpace「我的广场帖」可见(Sprint 1 可暂不实现作者视图,但不得出现在 `/plaza`)。 --- ### PATCH /api/plaza/v1/posts/:id 更新已发布到广场的帖子元数据(分类、标签、封面、是否允许评论)。 **权限**:需要登录,只能操作自己的帖子 **请求体**(字段均可选,至少传一项): ```json { "category_id": "cat_xxx", "tags": ["复盘"], "cover_url": "https://...", "allow_comment": false } ``` **约束**:不可更换 `publication_id`;若帖子 `status = rejected`,PATCH 后重新进入 `pending_review`。 **响应**: ```json { "data": { "post": { "id": "post_xxx", "status": "pending_review" } } } ``` --- ### DELETE /api/plaza/v1/posts/:id 将帖子从广场撤回(仅 status 改为 hidden,不物理删除)。 **权限**:需要登录,只能操作自己的帖子 --- ### POST /api/plaza/v1/posts/:id/reports 举报帖子。 **权限**:需要登录 **请求体**:`{ "reason": "spam", "detail": "可选说明" }`(`reason` 枚举见 [06-ops-platform](./06-ops-platform)) **响应**:`201` --- ### POST /api/plaza/v1/comments/:id/reports 举报评论。 **权限**:需要登录 **请求体**:同帖子举报 `{ "reason": "spam", "detail": "可选说明" }` **响应**:`201` --- ### GET /api/plaza/v1/categories 获取所有分类列表。 **权限**:无需登录 **响应**: ```json { "data": { "categories": [ { "id": "cat_xxx", "name": "职场报告", "slug": "work-report", "icon": "📊", "post_count": 1234 } ] } } ``` --- ### GET /api/plaza/v1/users/:slug 获取用户广场主页信息。 **权限**:无需登录 `stats.total_likes`:对该用户所有 `status = published` 帖子的 `like_count` 求和(见 [02-data-model](./02-data-model))。 **响应**: ```json { "data": { "user": { "user_id": "user_xxx", "slug": "zhangsan", "display_name": "张三", "avatar_url": "https://...", "bio": "数据分析师,喜欢用 AI 做报告", "stats": { "post_count": 12, "follower_count": 234, "following_count": 56, "total_likes": 1890 }, "viewer_following": false }, "recent_posts": [ ...最新 6 篇帖子... ] } } ``` --- ### GET /api/plaza/v1/users/:slug/posts 获取用户发布的帖子列表(分页)。 **权限**:无需登录 --- ## 互动 API ### POST /api/plaza/v1/posts/:id/reactions 点赞、收藏或标记分享。 **权限**:需要登录 **请求体**: ```json { "type": "like" } // like | collect | share ``` **幂等**:同一用户同一帖子同类型重复请求返回 200,不报错。 --- ### DELETE /api/plaza/v1/posts/:id/reactions/:type 取消点赞或收藏。 **权限**:需要登录,只能取消自己的 reaction --- ### GET /api/plaza/v1/posts/:id/comments 获取帖子评论列表。 **权限**:无需登录 **Query 参数**:`cursor`、`limit`、`parent_id`(获取某条评论的回复时传入) **响应**: ```json { "data": { "comments": [ { "id": "cmt_xxx", "content": "写得很好!", "author": { "user_id": "user_xxx", "slug": "lisi", "display_name": "李四", "avatar_url": "https://..." }, "like_count": 5, "reply_count": 2, "viewer_liked": false, "created_at": "2025-06-12T11:00:00Z", "status": "visible" } ], "next_cursor": "cmt_yyy", "has_more": false } } ``` --- ### POST /api/plaza/v1/posts/:id/comments 发表评论。 **权限**:需要登录 **请求体**: ```json { "content": "写得很好!", "parent_id": null // 回复二级时传一级评论 ID } ``` **校验**: - `content` 不能为空,最长 500 字符。 - `parent_id` 如果传入,必须是该帖子的一级评论(`parent_id IS NULL`),不允许三级嵌套。 - `plaza_posts.allow_comment = 1` 才允许评论,否则返回 `COMMENT_DISABLED`。 --- ### DELETE /api/plaza/v1/comments/:id 删除自己的评论(软删除)。 **权限**:需要登录,只能删除自己的评论 --- ### POST /api/plaza/v1/comments/:id/reactions 点赞评论。 **权限**:需要登录 --- ## 关注 API ### POST /api/plaza/v1/users/:slug/follow 关注用户。 **权限**:需要登录,不能关注自己 --- ### DELETE /api/plaza/v1/users/:slug/follow 取消关注。 **权限**:需要登录 --- ## 运营 API(/api/ops/v1/) 所有运营 API 需要 `ops_role` 为 `reviewer`、`editor` 或 `ops_admin` 的用户。平台 `role = admin` **不等于** `ops_admin`,需在 `h5_users.ops_role` 单独分配。 > **双审核入口**:Sprint 1 保留 `/admin-api/plaza/posts/:id/review`(`requireAdmin`);Sprint 5 起运营主路径为 `/api/ops/v1/review/*`。两路径均写入 `ops_audit_log`(admin 路径由服务端桥接 `reviewPostAsOps`)。 ### GET /api/ops/v1/review/queue 获取待审核帖子队列。 **Query 参数**:`status`(默认 `pending_review`)、`cursor`、`limit` --- ### POST /api/ops/v1/review/posts/:id 审核单个帖子。 **请求体**: ```json { "action": "approve", // approve | reject | hide "reason": "内容违规:含广告" // reject/hide 时必填 } ``` **副作用**: - `approve` → `plaza_posts.status = published`,写入 `ops_audit_log` - `reject` → `plaza_posts.status = rejected`,通知发布者,写入日志 - `hide` → `plaza_posts.status = hidden`,写入日志 --- ### POST /api/ops/v1/review/batch 批量审核帖子(`editor` 及以上)。 **请求体**: ```json { "post_ids": ["post_a", "post_b"], "action": "approve", "reason": null } ``` **响应**:`{ "data": { "posts": [ { "id": "...", "status": "published" } ] } }` --- ### POST /api/ops/v1/featured 设置精选内容。 **请求体**: ```json { "post_id": "post_xxx", "position": "homepage_banner", // homepage_banner | category_top | trending "expires_at": "2025-06-20T00:00:00Z" } ``` --- ### GET /api/ops/v1/analytics/overview 广场数据概览(日/周数据)。 **响应**字段:新帖数、发布转化率、日活、分类分布、TOP 创作者、违规比率。 --- ## 错误码 | 错误码 | HTTP 状态 | 含义 | | --- | --- | --- | | `PUBLICATION_NOT_ONLINE` | 422 | 发布源不是在线状态 | | `ALREADY_PUBLISHED` | 409 | 该 publication 已发布到广场 | | `POST_NOT_FOUND` | 404 | 帖子不存在或已隐藏 | | `POST_PERMISSION_DENIED` | 403 | 无发帖权限(被封禁) | | `COMMENT_DISABLED` | 422 | 该帖子已关闭评论 | | `COMMENT_TOO_LONG` | 422 | 评论超出 500 字符 | | `REPLY_DEPTH_EXCEEDED` | 422 | 不允许三级嵌套回复 | | `SELF_FOLLOW` | 422 | 不能关注自己 | | `OPS_PERMISSION_DENIED` | 403 | 非运营角色访问运营接口 | --- ## 与 MindSpace 的联动 ### publication 下线时自动隐藏广场帖子 在现有 MindSpace 下线 publication 的事务中,增加: ```sql UPDATE plaza_posts SET status = 'hidden', updated_at = :now_ms WHERE publication_id = :publication_id AND status != 'hidden'; ``` 实现位置:`ui/h5/mindspace-publications.mjs` 的 offline 流程,与现有事件写入同一连接事务。 ### 广场互动数据回写 MindSpace 通过后台定时任务(每小时),在同一 MySQL 实例内将广场计数汇总到 `h5_publish_records`: ```sql UPDATE h5_publish_records p JOIN plaza_posts pp ON pp.publication_id = p.id SET p.plaza_view_count = pp.view_count, p.plaza_like_count = pp.like_count WHERE pp.updated_at > :last_sync_time; ``` - **不**调用 MindSpace service 函数;字段定义与迁移由 MindSpace/Plaza 共用 `db.mjs` 维护。 - 回写失败不影响广场读写,下次任务重试。