Files
john 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
Add TKMind platform extensions, H5/MindSpace stack, and deployment tooling.
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>
2026-06-14 21:30:20 +08:00

519 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 14`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` 维护。
- 回写失败不影响广场读写,下次任务重试。