4e21ca937a
Deploy Documentation / deploy (push) Has been cancelled
Canary / Prepare Version (push) Has been cancelled
Canary / build-cli (push) Has been cancelled
Canary / Upload Install Script (push) Has been cancelled
Canary / bundle-desktop (push) Has been cancelled
Canary / bundle-desktop-intel (push) Has been cancelled
Canary / bundle-desktop-linux (push) Has been cancelled
Canary / bundle-desktop-windows (push) Has been cancelled
Canary / bundle-desktop-windows-cuda (push) Has been cancelled
Canary / Release (push) Has been cancelled
Unused Dependencies / machete (push) Has been cancelled
CI / changes (push) Has been cancelled
CI / Check Rust Code Format (push) Has been cancelled
CI / Build and Test Rust Project (push) Has been cancelled
CI / Build Rust Project on Windows (push) Has been cancelled
CI / Check MSRV (push) Has been cancelled
CI / Lint Rust Code (push) Has been cancelled
CI / Check Generated Schemas are Up-to-Date (push) Has been cancelled
CI / Test and Lint Electron Desktop App (push) Has been cancelled
CI / H5 Plaza Tests and Build (push) Has been cancelled
Live Provider Tests / check-fork (push) Has been cancelled
Live Provider Tests / changes (push) Has been cancelled
Live Provider Tests / Build Binary (push) Has been cancelled
Live Provider Tests / Smoke Tests (push) Has been cancelled
Live Provider Tests / Smoke Tests (Code Execution) (push) Has been cancelled
Live Provider Tests / Compaction Tests (push) Has been cancelled
Live Provider Tests / goose server HTTP integration tests (push) Has been cancelled
Publish Ask AI Bot Docker Image / docker (push) Has been cancelled
Publish Docker Image / docker (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Fork goose with custom MCP widgets, platform extensions (aider, git, web, search), MindSpace H5 backend/frontend, Plaza/Ops UIs, and deploy scripts for tkmind.cn. Co-authored-by: Cursor <cursoragent@cursor.com>
519 lines
12 KiB
Markdown
519 lines
12 KiB
Markdown
---
|
||
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` 维护。
|
||
- 回写失败不影响广场读写,下次任务重试。
|