Files
im_manager/docs/im/Cultivate-Server管理后台内部接口开发对接文档.md
T

697 lines
19 KiB
Markdown
Raw 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.
# Cultivate Server 管理后台内部接口开发对接文档
## 1. 文档目的
本文用于指导 `cultivate-server` 开发管理后台内部接口,使 IM Manager 可以通过服务间 HTTP API 管理养成业务,不再直接连接 Cultivate 数据库。
改造完成后必须满足:
1. IM Manager 不配置 `CULTIVATE_MYSQL_DSN`,也不持有 Cultivate 数据库账号。
2. Cultivate 数据只能由 `cultivate-server` 读写。
3. Manager 只负责管理员登录态、RBAC、页面协议转换和调用内部接口。
4. Cultivate 服务不可用时,只影响养成管理菜单,不得影响 Manager 启动及其他菜单。
5. 所有查询和写入必须按商户隔离,禁止信任前端传入的 `tenant_id`、`operator_id`。
## 2. 当前问题
当前 IM Manager 在配置中注册了独立数据库组:
```yaml
database:
cultivate:
link: "$CULTIVATE_MYSQL_DSN"
```
养成管理逻辑通过 `g.DB("cultivate")` 及 group 为 `cultivate` 的 DAO 直接访问以下表:
- `cv_flower_catalog`
- `cv_flower_stage_rule`
- `cv_user_flower`
- `cv_garden_active_day`
- `cv_feed_post`
- `cv_feed_comment`
- `cv_feed_media`
- `cv_feed_report`
- `cv_content_audit_log`
- `cv_admin_operation_log`
该实现造成:
- Manager 启动依赖 Cultivate 数据库配置。
- Cultivate 数据库账号扩散到 Manager。
- Cultivate 表结构变化会直接影响 Manager。
- 内容审核、通知、文件校验等事务边界跨服务泄漏。
本次应将上述数据库逻辑迁移到 `cultivate-server`,不能以给 Manager 增加数据库 Secret 的方式规避。
## 3. 服务职责
### 3.1 Cultivate Server
负责:
- 查询养成概览。
- 花种及成长阶段配置的增删改查和状态变更。
- 动态、评论审核列表、详情和人工审核。
- 举报列表、详情和处理。
- 管理操作日志查询和写入。
- 调用 IM 内部接口查询用户摘要、校验文件、发送系统通知及执行用户治理。
- 数据事务、幂等、租户隔离和审计。
### 3.2 IM Manager
负责:
- 校验后台管理员登录态和菜单权限。
- 从登录态解析商户、操作人和角色。
- 对 Cultivate 内部请求签名。
- 将 Cultivate 返回结果转换为现有后台页面响应。
- 将下游不可用错误转换成清晰的中文业务错误。
Manager 不再保存 Cultivate DAO、实体模型或数据库连接。
## 4. 接口基础约定
### 4.1 路径
内部管理接口统一使用:
```text
/internal/admin/cultivate/tenants/{tenant_code}/...
```
示例:
```text
GET /internal/admin/cultivate/tenants/default/dashboard
```
这些接口只能在集群内访问,不挂到公网 Ingress。
### 4.2 数据格式
- 请求和响应均使用 UTF-8 JSON。
- JSON 字段统一使用 `snake_case`。
- ID 统一使用十进制字符串返回,避免 JavaScript 超出安全整数范围。
- 时间统一使用 UTC RFC3339,例如 `2026-09-23T08:30:00Z`。
- 分页参数为 `page`、`per_page`,默认分别为 `1`、`20`。
- `per_page` 最大为 `100`。
统一成功响应:
```json
{
"code": 0,
"data": {},
"server_time": "2026-09-23T08:30:00Z",
"request_id": "req_xxx"
}
```
统一失败响应:
```json
{
"code": 40001,
"reason": "INVALID_ARGUMENT",
"message": "内容类型不正确",
"details": null,
"server_time": "2026-09-23T08:30:00Z",
"request_id": "req_xxx"
}
```
分页数据统一放在 `data` 中:
```json
{
"items": [],
"total": 0,
"page": 1,
"per_page": 20
}
```
## 5. 内部鉴权
### 5.1 请求头
| 请求头 | 必填 | 说明 |
| --- | --- | --- |
| `X-Service-Name` | 是 | 调用方服务名,Manager 固定为 `im-manager` |
| `X-Timestamp` | 是 | UTC Unix 秒 |
| `X-Nonce` | 是 | 每次请求随机值,服务端防重放 |
| `X-Signature` | 是 | HMAC-SHA256 十六进制小写字符串 |
| `X-Tenant-ID` | 是 | Manager 登录态解析出的商户 ID |
| `X-Operator-ID` | 是 | Manager 登录态解析出的管理员 ID |
| `X-Role-Key` | 否 | 管理员角色标识,用于审计,不作为唯一授权依据 |
| `X-Request-ID` | 是 | 全链路请求 ID |
### 5.2 签名串
双方复用现有内部服务密钥,不新增数据库相关 Secret。签名原文为:
```text
METHOD\n
REQUEST_URI\n
SHA256_HEX(BODY)\n
TIMESTAMP\n
NONCE\n
TENANT_ID\n
OPERATOR_ID\n
ROLE_KEY\n
REQUEST_ID
```
说明:
- `REQUEST_URI` 包含路径和排序后的 Query String。
- GET 请求的 Body 为空字节数组。
- 使用共享密钥计算 `HMAC-SHA256`。
- 时间窗口建议为正负 300 秒。
- `X-Service-Name + X-Nonce` 在时间窗口内只能使用一次。
- 必须使用常量时间比较签名。
- 商户、操作人及角色头已经纳入签名,业务层不得接受 Body 中同名字段覆盖。
### 5.3 商户校验
1. `{tenant_code}` 必须与签名后的 `X-Tenant-ID` 对应。
2. 所有 SQL 必须带 `tenant_id = X-Tenant-ID`。
3. 查不到商户或编码与 ID 不匹配时返回 `TENANT_NOT_FOUND` 或 `TENANT_MISMATCH`。
4. 禁止使用默认商户兜底处理错误的商户参数。
## 6. 接口清单
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | `/dashboard` | 养成管理概览 |
| GET | `/flowers` | 花种列表 |
| GET | `/flowers/{id}` | 花种详情 |
| POST | `/flowers` | 新建花种 |
| PUT | `/flowers/{id}` | 编辑花种 |
| POST | `/flowers/{id}/status` | 花种状态变更 |
| GET | `/content-reviews` | 内容审核列表 |
| GET | `/content-reviews/{object_type}/{id}` | 内容审核详情 |
| POST | `/content-reviews/{object_type}/{id}/actions` | 单条审核操作 |
| POST | `/content-reviews/batch-actions` | 批量审核操作 |
| GET | `/reports` | 举报列表 |
| GET | `/reports/{id}` | 举报详情 |
| POST | `/reports/{id}/actions` | 处理举报 |
| GET | `/operation-logs` | 操作日志列表 |
以下章节中的路径均省略前缀:
```text
/internal/admin/cultivate/tenants/{tenant_code}
```
## 7. 数据概览
### 7.1 `GET /dashboard`
响应 `data`:
```json
{
"plant_count": 0,
"growing_count": 0,
"bloom_count": 0,
"harvested_count": 0,
"removed_count": 0,
"active_7d": 0,
"active_30d": 0,
"post_count": 0,
"public_post_count": 0,
"friends_post_count": 0,
"private_post_count": 0,
"comment_count": 0,
"like_count": 0,
"taken_down_count": 0,
"pending_audit_count": 0,
"approved_audit_count": 0,
"rejected_audit_count": 0,
"pending_report_count": 0,
"processing_report_count": 0,
"processed_report_count": 0
}
```
统计口径必须沿用当前业务状态,且全部按商户过滤。没有数据时返回 `0`,不能返回 `null`。
## 8. 花种管理
### 8.1 `GET /flowers`
Query 参数:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `flower_code` | string | 编码模糊查询 |
| `flower_name` | string | 名称模糊查询 |
| `status` | string | 状态精确查询 |
| `config_version` | string | 配置版本 |
| `created_from` | RFC3339 | 创建开始时间 |
| `created_to` | RFC3339 | 创建结束时间 |
| `page` | int | 页码 |
| `per_page` | int | 每页数量 |
列表项:
```json
{
"id": "10001",
"flower_code": "rose",
"flower_name": "玫瑰",
"flower_desc": "",
"icon_file_id": "file_xxx",
"status": "online",
"config_version": "cat_20260923_163000",
"daily_water_limit": 3,
"daily_fertilize_limit": 1,
"sort_no": 10,
"stage_count": 4,
"created_at": "2026-09-23T08:30:00Z",
"updated_at": "2026-09-23T08:30:00Z"
}
```
排序规则:`sort_no ASC, id DESC`。
### 8.2 `GET /flowers/{id}`
在列表字段基础上增加:
```json
{
"share_config": {},
"stage_rules": [
{
"id": "10002",
"stage": "seed",
"next_stage": "sprout",
"required_points": 10,
"water_points": 2,
"fertilize_points": 3,
"allowed_actions": ["water", "fertilize"],
"image_file_id": "file_xxx",
"animation_file_id": "file_xxx",
"progress_text_template": "成长中",
"completion_text": "已进入发芽阶段",
"sort_no": 10
}
]
}
```
### 8.3 `POST /flowers` 与 `PUT /flowers/{id}`
请求体:
```json
{
"flower_code": "rose",
"flower_name": "玫瑰",
"flower_desc": "",
"icon_file_id": "file_xxx",
"status": "offline",
"daily_water_limit": 3,
"daily_fertilize_limit": 1,
"share_config": {},
"sort_no": 10,
"stage_rules": []
}
```
业务规则:
- `flower_code`、`flower_name` 必填。
- `flower_code` 创建后不可修改。
- 状态枚举:`online`、`offline`、`activity_only`、`maintenance`。
- 阶段枚举:`seed`、`sprout`、`bud`、`bloom`。
- 阶段链必须从 `seed` 连续配置并以 `bloom` 结束,不能重复或成环。
- 非 `bloom` 阶段 `required_points > 0`,且至少允许一个动作。
- 动作枚举:`water`、`fertilize`。
- 文件 ID 必须通过 IM 内部文件校验,风险或待审核素材不能保存。
- 花种和阶段规则必须在一个数据库事务内保存。
- 每次新增或编辑生成新的 `config_version` 并写管理操作日志。
- 新建返回 `data.id`,ID 使用字符串。
### 8.4 `POST /flowers/{id}/status`
```json
{
"status": "online",
"reason": "审核通过后上架"
}
```
更新上下架时间、配置版本并写操作日志。
## 9. 内容审核
### 9.1 `GET /content-reviews`
Query 参数:
- `object_type`:必填,`feed_post` 或 `feed_comment`。
- `author_id`:作者 ID。
- `status`:业务状态。
- `keyword`:正文关键字。
- `created_from`、`created_to`。
- `page`、`per_page`。
列表项:
```json
{
"id": "20001",
"object_type": "feed_post",
"post_id": "",
"author_id": "30001",
"content_text": "正文",
"flower_code": "rose",
"visibility": "public",
"status": "reviewing",
"audit_status": "pending",
"reject_reason": "",
"like_count": 0,
"comment_count": 0,
"created_at": "2026-09-23T08:30:00Z",
"updated_at": "2026-09-23T08:30:00Z"
}
```
按 `id DESC` 排序。
### 9.2 `GET /content-reviews/{object_type}/{id}`
详情在列表项基础上增加:
- `author_nickname`
- `author_avatar_file_id`
- `media[]`:`id`、`file_id`、`thumbnail_file_id`、`media_type`、`audit_status`、`sort_no`
- `audits[]`:`id`、`audit_source`、`status_before`、`status_after`、`result_code`、`risk_labels`、`operator_id`、`occurred_at`
- `related_reports[]`:关联举报摘要
用户摘要通过 IM 内部 API 查询。用户服务临时不可用时,允许昵称和头像为空,但内容主体仍应返回。
### 9.3 `POST /content-reviews/{object_type}/{id}/actions`
```json
{
"action": "approve",
"reason": ""
}
```
动作枚举:
- `approve`:审核通过。
- `reject`:审核拒绝,必须填写原因。
- `take_down`:下架,必须填写原因。
- `restore`:恢复内容。
要求:
- 使用行锁或等价并发控制。
- 校验合法的前置状态。
- 更新内容状态和审核字段。
- 评论首次通过时只能增加一次动态评论数。
- 下架动态时应按现有业务规则同步处理其可见性或关联评论。
- 每次操作写 `cv_content_audit_log` 和 `cv_admin_operation_log`。
- 事务提交后向作者发送系统通知。
- 重复请求不能重复计数或重复产生治理副作用。
### 9.4 `POST /content-reviews/batch-actions`
```json
{
"object_type": "feed_post",
"ids": ["20001", "20002"],
"action": "approve",
"reason": ""
}
```
- 单次 1 至 100 条。
- 每条独立处理,单条失败不回滚其他成功项。
- 返回:
```json
{
"success_count": 1,
"failed_count": 1,
"failures": {
"20002": "当前状态不允许审核通过"
}
}
```
## 10. 举报管理
### 10.1 `GET /reports`
Query 参数:
- `object_type`
- `status`
- `reporter_id`
- `created_from`、`created_to`
- `page`、`per_page`
列表项:
```json
{
"id": "40001",
"reporter_id": "30001",
"reported_user_id": "30002",
"object_type": "feed_post",
"object_id": "20001",
"reason_code": "spam",
"description": "疑似广告",
"evidence_file_ids": ["file_xxx"],
"source_page": "feed_detail",
"status": "submitted",
"handler_id": "",
"handle_result": "",
"handled_at": null,
"created_at": "2026-09-23T08:30:00Z"
}
```
### 10.2 `GET /reports/{id}`
详情增加:
- `reporter_nickname`
- `reported_user_nickname`
- `target_content`
- `history[]`:同一对象的历史举报
不得向被举报方的通知或普通业务接口泄露举报人身份和证据。
### 10.3 `POST /reports/{id}/actions`
```json
{
"action": "resolve",
"handle_result": "举报成立,内容已下架",
"content_action": "take_down",
"user_action": "none",
"reason": "违规广告"
}
```
枚举:
- `action`:`processing`、`resolve`、`reject`。
- `content_action`:`none`、`take_down`。
- `user_action`:`none`、`freeze`、`ban`。
校验规则:
- `processing` 不要求处理结果。
- `resolve`、`reject` 必须填写 `handle_result`。
- 只有 `resolve` 可以联动内容和用户处置。
- 发生联动处置时必须填写 `reason`。
- 报告状态更新、内容处置和操作日志应处于同一事务。
- 用户冻结或封禁通过 IM 内部治理 API 执行;失败时不得将举报错误标记为已完成。
- 完成后分别通知举报方和被举报方,被举报方通知不得携带举报人信息。
## 11. 操作日志
### 11.1 `GET /operation-logs`
Query 参数:
- `operator_id`
- `operation_type`
- `target_type`
- `target_id`
- `request_id`
- `created_from`、`created_to`
- `page`、`per_page`
列表项:
```json
{
"id": "50001",
"operator_id": "14",
"operation_type": "content.approve",
"target_type": "feed_post",
"target_id": "20001",
"request_id": "req_xxx",
"before_snapshot": {},
"after_snapshot": {},
"reason": "",
"created_at": "2026-09-23T08:30:00Z"
}
```
日志只读,不提供删除接口。快照不得保存 Token、手机号、数据库连接、完整举报证据或私有文件 URL。
## 12. 状态和枚举
| 类型 | 枚举 |
| --- | --- |
| 内容类型 | `feed_post`、`feed_comment` |
| 花种状态 | `online`、`offline`、`activity_only`、`maintenance` |
| 成长阶段 | `seed`、`sprout`、`bud`、`bloom` |
| 成长动作 | `water`、`fertilize` |
| 可见范围 | `public`、`friends`、`private` |
| 内容状态 | `reviewing`、`published`、`rejected`、`taken_down` |
| 审核动作 | `approve`、`reject`、`take_down`、`restore` |
| 举报状态 | `submitted`、`processing`、`resolved`、`rejected` |
| 举报动作 | `processing`、`resolve`、`reject` |
| 用户处置 | `none`、`freeze`、`ban` |
如果现有数据库已经使用其他稳定枚举,开发前应与 Manager 对齐,不允许接口层静默转换出第三套状态。
## 13. 错误码建议
| HTTP | reason | 场景 |
| --- | --- | --- |
| 400 | `INVALID_ARGUMENT` | 参数或枚举错误 |
| 401 | `INTERNAL_AUTH_REQUIRED` | 缺少内部签名 |
| 403 | `INTERNAL_AUTH_FORBIDDEN` | 签名错误、服务未授权 |
| 404 | `TENANT_NOT_FOUND` | 商户不存在 |
| 404 | `RESOURCE_NOT_FOUND` | 花种、内容或举报不存在 |
| 409 | `STATE_CONFLICT` | 当前状态不允许执行动作 |
| 409 | `DUPLICATE_FLOWER_CODE` | 商户内花种编码重复 |
| 422 | `ASSET_REVIEW_PENDING` | 素材仍在审核中 |
| 422 | `ASSET_REVIEW_REJECTED` | 素材审核未通过 |
| 429 | `RATE_LIMITED` | 调用频率过高 |
| 503 | `DEPENDENCY_UNAVAILABLE` | IM 用户、文件、通知或治理服务不可用 |
| 500 | `INTERNAL_ERROR` | 未知内部错误 |
响应不得返回 SQL、数据库地址、堆栈或 Secret。
## 14. 数据一致性与安全要求
1. 每条 SQL 都必须显式过滤 `tenant_id`。
2. 更新语句同时按主键和 `tenant_id` 定位。
3. 写接口记录签名后的 `X-Operator-ID` 和 `X-Request-ID`。
4. 审核、举报处置和计数变更必须考虑重试幂等。
5. 批量操作最多 100 条,避免长事务。
6. 私有媒体只返回 `file_id`,访问地址由受控文件接口临时签发。
7. 关键操作日志永久保留;普通查询对已注销用户做匿名化。
8. 内部接口不可复用 C 端 Token 鉴权,必须单独使用服务间 HMAC。
9. 内部 API 路由应在普通 C 端认证中间件之前单独注册,不能误走 Redis 用户会话认证。
## 15. Cultivate Server 建议目录
按现有分层新增,禁止在 controller 中写数据库逻辑:
```text
api/internal/admin/
cultivate.go
internal/controller/admin/
cultivate.go
internal/service/
admin_cultivate.go
internal/logic/admin/
dashboard.go
flower.go
content_review.go
report.go
operation_log.go
internal/middleware/
internal_admin_auth.go
internal/model/
admin_cultivate.go
```
路由建议独立注册:
```text
/internal/admin/cultivate/*
```
不要将管理接口混入 `/v1/cultivate/*` C 端路由。
## 16. Manager 后续改造
Cultivate Server 接口完成后,Manager 侧将执行:
1. 新增 Cultivate 内部 API Client。
2. 将现有页面 API 转发到本文定义的内部接口。
3. 删除 Manager 内的 Cultivate 直接数据库逻辑。
4. 删除 Cultivate DAO、DO、Entity 等仅为直连数据库生成的代码。
5. 删除配置模板中的 `database.cultivate` 和 `CULTIVATE_MYSQL_DSN`。
6. 保持现有后台页面路径和前端交互不变。
服务地址建议支持配置,但 Kubernetes 环境可提供集群内默认值:
```text
http://cultivate-server.cultivate-system.svc.cluster.local:8080
```
该地址不是数据库配置。密钥复用双方已有内部 HMAC Secret,不增加 Cultivate 数据库 Secret。
## 17. 测试要求
### 17.1 单元测试
- HMAC 正确、错误、过期和 Nonce 重放。
- 商户编码与 ID 不匹配。
- 所有列表过滤和分页。
- 花种阶段链缺失、重复、成环和合法场景。
- 内容每种状态动作的合法与非法转换。
- 重复审核不重复增加评论数。
- 举报各种状态和联动处置。
- 跨商户 ID 查询和更新均返回不存在。
### 17.2 集成测试
- 使用真实 Cultivate 测试库验证全部接口。
- 模拟 IM 用户、文件、通知和治理内部接口。
- 验证事务失败不会留下半完成数据。
- 验证操作日志中的商户、管理员和请求 ID 正确。
- 验证接口响应不包含数据库信息和堆栈。
### 17.3 联调验收
1. Manager 不设置 `CULTIVATE_MYSQL_DSN` 可以正常启动。
2. Manager 登录、IM 管理和工作台管理均正常。
3. 养成概览、花种、内容审核、举报和操作日志页面可正常查询。
4. 花种新增、编辑、上下架生效。
5. 内容审核和举报处置生效,通知与审计记录正确。
6. 停止 Cultivate Server 后,Manager 仍保持健康,仅养成页面提示“养成服务暂不可用”。
7. Manager Pod 环境变量和 Secret 中不存在 Cultivate 数据库 DSN。
## 18. 交付清单
Cultivate Server 开发完成后需提供:
- 接口代码及单元测试。
- OpenAPI 或等价接口定义。
- 内部 HMAC 鉴权说明。
- 测试环境服务地址。
- 联调使用的服务名和 Secret 键名,不提供明文 Secret。
- 数据迁移说明,如无迁移需明确说明。
- 一份 curl 签名测试脚本或 Go 测试客户端。
上述内容齐备后,再开始 Manager 侧切换和删除直连代码。