# 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, "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}` 请求体: ```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` 与 `wait_hours` 不能同时为 0(2026-10-08 起:升级条件 = 积分就绪 AND 等待就绪,允许纯积分或纯等待阶段),且至少允许一个动作。 - 动作枚举:`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 侧切换和删除直连代码。