19 KiB
Cultivate Server 管理后台内部接口开发对接文档
1. 文档目的
本文用于指导 cultivate-server 开发管理后台内部接口,使 IM Manager 可以通过服务间 HTTP API 管理养成业务,不再直接连接 Cultivate 数据库。
改造完成后必须满足:
- IM Manager 不配置
CULTIVATE_MYSQL_DSN,也不持有 Cultivate 数据库账号。 - Cultivate 数据只能由
cultivate-server读写。 - Manager 只负责管理员登录态、RBAC、页面协议转换和调用内部接口。
- Cultivate 服务不可用时,只影响养成管理菜单,不得影响 Manager 启动及其他菜单。
- 所有查询和写入必须按商户隔离,禁止信任前端传入的
tenant_id、operator_id。
2. 当前问题
当前 IM Manager 在配置中注册了独立数据库组:
database:
cultivate:
link: "$CULTIVATE_MYSQL_DSN"
养成管理逻辑通过 g.DB("cultivate") 及 group 为 cultivate 的 DAO 直接访问以下表:
cv_flower_catalogcv_flower_stage_rulecv_user_flowercv_garden_active_daycv_feed_postcv_feed_commentcv_feed_mediacv_feed_reportcv_content_audit_logcv_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 路径
内部管理接口统一使用:
/internal/admin/cultivate/tenants/{tenant_code}/...
示例:
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。
统一成功响应:
{
"code": 0,
"data": {},
"server_time": "2026-09-23T08:30:00Z",
"request_id": "req_xxx"
}
统一失败响应:
{
"code": 40001,
"reason": "INVALID_ARGUMENT",
"message": "内容类型不正确",
"details": null,
"server_time": "2026-09-23T08:30:00Z",
"request_id": "req_xxx"
}
分页数据统一放在 data 中:
{
"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。签名原文为:
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 商户校验
{tenant_code}必须与签名后的X-Tenant-ID对应。- 所有 SQL 必须带
tenant_id = X-Tenant-ID。 - 查不到商户或编码与 ID 不匹配时返回
TENANT_NOT_FOUND或TENANT_MISMATCH。 - 禁止使用默认商户兜底处理错误的商户参数。
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 |
操作日志列表 |
以下章节中的路径均省略前缀:
/internal/admin/cultivate/tenants/{tenant_code}
7. 数据概览
7.1 GET /dashboard
响应 data:
{
"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 | 每页数量 |
列表项:
{
"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}
在列表字段基础上增加:
{
"share_config": {},
"stage_rules": [
{
"id": "10002",
"stage": "seed",
"next_stage": "sprout",
"required_points": 10,
"water_points": 2,
"fertilize_points": 3,
"wait_hours": 36,
"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}
请求体:
{
"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与wait_hours不能同时为 0(2026-10-08 起:升级条件 = 积分就绪 AND 等待就绪,允许纯积分或纯等待阶段),且至少允许一个动作。 - 动作枚举:
water、fertilize。 - 文件 ID 必须通过 IM 内部文件校验,风险或待审核素材不能保存。
- 花种和阶段规则必须在一个数据库事务内保存。
- 每次新增或编辑生成新的
config_version并写管理操作日志。 - 新建返回
data.id,ID 使用字符串。
8.4 POST /flowers/{id}/status
{
"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。
列表项:
{
"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_nicknameauthor_avatar_file_idmedia[]:id、file_id、thumbnail_file_id、media_type、audit_status、sort_noaudits[]:id、audit_source、status_before、status_after、result_code、risk_labels、operator_id、occurred_atrelated_reports[]:关联举报摘要
用户摘要通过 IM 内部 API 查询。用户服务临时不可用时,允许昵称和头像为空,但内容主体仍应返回。
9.3 POST /content-reviews/{object_type}/{id}/actions
{
"action": "approve",
"reason": ""
}
动作枚举:
approve:审核通过。reject:审核拒绝,必须填写原因。take_down:下架,必须填写原因。restore:恢复内容。
要求:
- 使用行锁或等价并发控制。
- 校验合法的前置状态。
- 更新内容状态和审核字段。
- 评论首次通过时只能增加一次动态评论数。
- 下架动态时应按现有业务规则同步处理其可见性或关联评论。
- 每次操作写
cv_content_audit_log和cv_admin_operation_log。 - 事务提交后向作者发送系统通知。
- 重复请求不能重复计数或重复产生治理副作用。
9.4 POST /content-reviews/batch-actions
{
"object_type": "feed_post",
"ids": ["20001", "20002"],
"action": "approve",
"reason": ""
}
- 单次 1 至 100 条。
- 每条独立处理,单条失败不回滚其他成功项。
- 返回:
{
"success_count": 1,
"failed_count": 1,
"failures": {
"20002": "当前状态不允许审核通过"
}
}
10. 举报管理
10.1 GET /reports
Query 参数:
object_typestatusreporter_idcreated_from、created_topage、per_page
列表项:
{
"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_nicknamereported_user_nicknametarget_contenthistory[]:同一对象的历史举报
不得向被举报方的通知或普通业务接口泄露举报人身份和证据。
10.3 POST /reports/{id}/actions
{
"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_idoperation_typetarget_typetarget_idrequest_idcreated_from、created_topage、per_page
列表项:
{
"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. 数据一致性与安全要求
- 每条 SQL 都必须显式过滤
tenant_id。 - 更新语句同时按主键和
tenant_id定位。 - 写接口记录签名后的
X-Operator-ID和X-Request-ID。 - 审核、举报处置和计数变更必须考虑重试幂等。
- 批量操作最多 100 条,避免长事务。
- 私有媒体只返回
file_id,访问地址由受控文件接口临时签发。 - 关键操作日志永久保留;普通查询对已注销用户做匿名化。
- 内部接口不可复用 C 端 Token 鉴权,必须单独使用服务间 HMAC。
- 内部 API 路由应在普通 C 端认证中间件之前单独注册,不能误走 Redis 用户会话认证。
15. Cultivate Server 建议目录
按现有分层新增,禁止在 controller 中写数据库逻辑:
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
路由建议独立注册:
/internal/admin/cultivate/*
不要将管理接口混入 /v1/cultivate/* C 端路由。
16. Manager 后续改造
Cultivate Server 接口完成后,Manager 侧将执行:
- 新增 Cultivate 内部 API Client。
- 将现有页面 API 转发到本文定义的内部接口。
- 删除 Manager 内的 Cultivate 直接数据库逻辑。
- 删除 Cultivate DAO、DO、Entity 等仅为直连数据库生成的代码。
- 删除配置模板中的
database.cultivate和CULTIVATE_MYSQL_DSN。 - 保持现有后台页面路径和前端交互不变。
服务地址建议支持配置,但 Kubernetes 环境可提供集群内默认值:
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 联调验收
- Manager 不设置
CULTIVATE_MYSQL_DSN可以正常启动。 - Manager 登录、IM 管理和工作台管理均正常。
- 养成概览、花种、内容审核、举报和操作日志页面可正常查询。
- 花种新增、编辑、上下架生效。
- 内容审核和举报处置生效,通知与审计记录正确。
- 停止 Cultivate Server 后,Manager 仍保持健康,仅养成页面提示“养成服务暂不可用”。
- Manager Pod 环境变量和 Secret 中不存在 Cultivate 数据库 DSN。
18. 交付清单
Cultivate Server 开发完成后需提供:
- 接口代码及单元测试。
- OpenAPI 或等价接口定义。
- 内部 HMAC 鉴权说明。
- 测试环境服务地址。
- 联调使用的服务名和 Secret 键名,不提供明文 Secret。
- 数据迁移说明,如无迁移需明确说明。
- 一份 curl 签名测试脚本或 Go 测试客户端。
上述内容齐备后,再开始 Manager 侧切换和删除直连代码。