# GEO 平台技术架构深度审查报告(第三轮) > 在前两轮审查(功能覆盖度 + 5 万并发初审)基础上,交叉审查全部 6 份文档(共 ~4700 行),聚焦**跨文档一致性、数据流完整性、实施风险**三个维度。 --- ## 一、跨文档一致性审查 现有文档体系: | 文档 | 行数 | 定位 | | --- | --- | --- | | [geo-platform-ops-admin-tech-architecture-v1.md](file:///d:/project/geo/docs/geo-platform-ops-admin-tech-architecture-v1.md) | 1257 | 🏗️ 主架构 | | [geo-platform-prd-v1.md](file:///d:/project/geo/docs/geo-platform-prd-v1.md) | 1079 | 📋 租户端 PRD | | [geo-platform-ops-admin-prd-v1.md](file:///d:/project/geo/docs/geo-platform-ops-admin-prd-v1.md) | 652 | 📋 平台管理端 PRD | | [media-binding-and-publish-v1.md](file:///d:/project/geo/docs/media-binding-and-publish-v1.md) | 562 | 🔌 插件方案 | | [qdrant-rag-architecture-v1.md](file:///d:/project/geo/docs/qdrant-rag-architecture-v1.md) | 651 | 🧠 RAG 方案 | | [question-driven-monitoring-design-v1.md](file:///d:/project/geo/docs/question-driven-monitoring-design-v1.md) | 495 | 📊 数据监控方案 | ### 问题 1:RAG 组件未纳入主架构图和交互图 [qdrant-rag-architecture-v1.md](file:///d:/project/geo/docs/qdrant-rag-architecture-v1.md) 定义了完整的检索链路:`query rewrite → filter → Qdrant 检索 → rerank → 上下文拼装 → 提交模型`。但主架构中: - §8 交互图中 `worker-generate → Qdrant` 的箭头缺失(只有 `worker-knowledge → QD`) - 文章生成链路(§10.1)只提到"调用模型",**没有 RAG 检索步骤** - 后端模块职责中无 `shared/rag` 或 `shared/retrieval` 模块 > [!WARNING] > **影响**:`worker-generate` 生成文章时需要从 Qdrant 检索品牌知识,这条数据流在主架构中完全不可见。开发时容易遗漏 RAG 集成。 > > **建议**: > 1. 交互图中为 `worker-generate → Qdrant` 增加箭头 > 2. §10.1 生成链路在步骤 3 和 4 之间插入 "RAG 检索品牌知识上下文" > 3. 后端目录中增加 `internal/shared/retrieval` 模块 ### 问题 2:media-binding 插件协议与主架构定义存在分歧 | 对比项 | media-binding-v1.md | 主架构 §13.8 | | --- | --- | --- | | 绑定落库 | 前端调用后端 API 保存 | 插件**双通道**:回传前端 + 回调 API 落库 | | 会话管理 | 无 `plugin_session` 概念 | 有 `plugin_sessions` 表 + `session_token` | | 回调路径 | `POST /api/platform-accounts/bind` | `POST /api/callback/plugin/bind` | | 发布记录 | `POST /api/publish-records` | `POST /api/callback/plugin/publish` | > **结论**:主架构 §13.8 的设计更完善(双通道 + 会话令牌 + 审计),但 media-binding-v1.md 还是旧方案。**两份文档需要统一**,否则前端、插件、后端开发人员会看不同的接口文档。 ### 问题 3:数据追踪的数据模型字段不一致 | 对比项 | question-driven-monitoring-v1.md | 主架构 §13.11.6 | | --- | --- | --- | | 汇总表名 | `question_model_daily_metrics` | 未出现此表(但有 `keyword_model_daily_metrics`) | | `question_monitor_runs` 字段 | 有 `run_at`、`task_batch_id` | 缺少 `run_at`、`task_batch_id` | | `question_monitor_parse_results` 字段 | 有 `citation_platform_count`、`parser_version` | 缺少这两个字段 | | 保留策略 | 明确 30/90/180 天分级 | 未提及保留策略 | > **建议**:主架构 §13.11.6 的 Schema 应与 [question-driven-monitoring-design-v1.md](file:///d:/project/geo/docs/question-driven-monitoring-design-v1.md) 完全对齐,特别是补充 `question_model_daily_metrics` 表和缺失字段。 ### 问题 4:RAG 中的 `knowledge_chunks_meta` 表未出现在主架构 Schema [qdrant-rag-architecture-v1.md](file:///d:/project/geo/docs/qdrant-rag-architecture-v1.md) §6.1 提到 PostgreSQL 需保存 `knowledge_chunks_meta`(chunk 元数据和状态),但主架构 §13.11.4 知识库 Schema 中**没有这张表**。这意味着: - 每个文档被切成多少 chunk、每个 chunk 的 token 数、status 在 PostgreSQL 中无处存储 - chunk 重建/版本化时没有元数据支持 > **建议**:在 §13.11.4 增加 `knowledge_chunks_meta` 表: > ``` > | knowledge_chunks_meta | id, knowledge_item_id, chunk_index, token_count, qdrant_point_id, item_version, status | > ``` ### 问题 5:RAG 中的 `retrieval_logs` 和 `rag_eval_cases` 未纳入主架构 RAG 文档要求记录每次检索的 query/filter/rerank 结果用于 badcase 分析,但主架构没有对应表和模块。V1 如果不记录,后续无法评估 RAG 精准度。 > **建议**:如果目标是"足够简单",V1 可以先将检索日志写入**文件日志**而非数据库表,后续再升级。在架构中明确这一策略。 ### 问题 6:PRD 中定义了 `kol` 角色但无实际权限描述 主架构 §6.2.2 新增 `kol`(用于某个专家的 prompt 从底层共享),但: - 租户端 PRD 只定义了 管理员/编辑/只读 三种角色 - `kol` 角色的具体权限范围(能看什么、能做什么)未在任何文档中定义 - PRD 中的 `viewer`(只读成员)角色被删除 > **建议**:保留 `viewer`、额外增加 `kol`,并在权限矩阵中补充 `kol` 的能力边界。 ### 问题 7:`article_templates` 表的 `tenant_id` 字段暗示模板是租户级的 主架构 §13.11.2 中 `article_templates` 有 `tenant_id`。但: - 平台端 PRD §8.5 明确有"系统模板管理"功能 - 平台级模板应 `tenant_id = NULL` 或专门的 `scope` 字段 - 参考截图中工作台的模板卡片(Top X / 产品评测 / 研究报告)看起来是**平台级**预设 > **建议**:`article_templates` 表增加 `scope` 字段(`platform` / `tenant`),平台级模板 `tenant_id = NULL`,租户可在此基础上自定义。 ### 问题 8:ops-admin-web PRD 中的"平台接入管理"路由在主架构 §13.4.2 中已有但 §5.3.3 中缺失 §13.4.2 ops-admin-web 路由有 `/platform/integrations/*`,§13.5.4 模块也有 `platform/integration`,但 §5.3.3 原始 API 路由列表中没有 `/api/platform/integrations/*`。 > **影响较小**,因为 §13.6.2 覆盖了。但前后不一致容易造成混乱。 --- ## 二、关键数据流深度审查(面向 5 万并发 + 简单) ### 数据流 1:文章生成全链路 ``` 用户 → admin-web → gateway → tenant-api(额度校验 + 任务入队) → RabbitMQ → worker-generate(RAG 检索 + 模型调用 + 结果存储) → PostgreSQL + Redis → SSE/轮询 → 前端更新 ``` **关键问题:LLM 调用超时与成本** - 一篇文章生成可能调用 LLM 3-20 秒(取决于文章长度和模型) - 5 万用户中假设 1% 同时在生成 = **500 并发生成任务** - 每个任务占用 1 个 Worker goroutine + 1 个 LLM API 连接 - 如果 LLM 上游限频(如 DeepSeek 300 RPM),Worker 会排队堆积 > [!CAUTION] > **架构中缺少**: > 1. LLM 调用超时策略(多久算超时?超时后任务状态如何?) > 2. LLM 上游限流适配(令牌桶/重试退避) > 3. 生成任务的优先级控制(VIP 租户优先?) > 4. Worker 对 LLM 的并发连接池设计 ### 数据流 2:知识库入库全链路 ``` 用户上传 → tenant-api → 对象存储 → RabbitMQ → worker-knowledge(提取 → 清洗 → 切片 → Embedding → Qdrant 写入 + PG 元数据) ``` **关键问题:Embedding 调用成本** - 每个文档可能切出 50-200 个 chunk - 每个 chunk 需要一次 Embedding API 调用 - 50 万租户 × 平均 10 个知识文档 = 500 万文档 → 潜在 2.5-10 亿 chunk > 但 V1 阶段用户量有限,这主要是演进方向的考量。当前建议在架构中明确 **Embedding 批量调用**策略(一次请求多个 chunk),减少 API round-trip。 ### 数据流 3:定时任务调度全链路 ``` scheduler 每分钟扫描 → schedule_tasks(到期的) → 生成 generation_tasks → 入队 RabbitMQ → 生成 analytics 任务 → 入队 RabbitMQ ``` **关键问题:调度雷群效应** - 很多租户的定时任务可能设在同一时刻(如每天 08:00) - 某分钟可能扫描出数千个到期任务 - 全部同时入队可能瞬间打爆 `q.generate` 队列 > **建议**: > - Scheduler 支持 **平滑派发**(将同时到期的任务分散到 5-10 分钟窗口内入队) > - 或租户创建定时任务时自动 **加随机偏移量**(±15 分钟) ### 数据流 4:数据追踪采集全链路 ``` scheduler → 为每个启用的 question_version × model 生成采集任务 → RabbitMQ → worker-analytics(调用模型 → 解析回答 → 存解析结果) → 日级聚合 → 刷新缓存 ``` **关键问题:采集任务与生成任务共享模型调用** - 数据追踪的采集本质上也是"调用 LLM 获取回答" - 与文章生成共用 LLM 额度/限频 - 但采集任务优先级应**低于**用户直接触发的生成任务 > **建议**: > - 采集任务使用独立的 LLM 通道/频次限制 > - 或在共享通道中标记优先级,用户直接任务优先 ### 数据流 5:插件发布全链路 ``` admin-web → 创建 plugin_session → browser-extension 执行发布 → 同时:回传前端(即时反馈)+ 回调 /api/callback/plugin/publish(可靠落库) → tenant-api 存储 publish_records → SSE 广播 ``` **问题:插件离线时的降级处理** - 如果用户没装插件、或插件版本过低,点击"发布"会发生什么? - 主架构中未定义**插件检测**机制(前端如何知道插件是否可用?) - media-binding-v1.md §12 提到只注入本系统后台域名,但没定义版本检测协议 > **建议**:前端在页面加载时检测插件安装状态和版本,未安装或版本过低时灰掉发布按钮并引导安装。在 `packages/shared-types` 中定义最低兼容插件版本常量。 --- ## 三、实施风险与工程注意事项 ### 风险 1:`worker-generate` 中 LLM 超时导致 goroutine 泄漏 Go 中如果 HTTP 请求到 LLM API 设置了很长的超时(如 60s),大量排队任务会占用大量 goroutine。如果 LLM 侧故障,可能导致 goroutine 数量爆炸。 > **建议**: > - 对 LLM 调用设置 `context.WithTimeout`(建议 30s) > - Worker 设置最大并发 goroutine 数(如基于 `semaphore` 限制并发 50) > - 超时任务标记为 `failed`,进入重试队列 ### 风险 2:PostgreSQL 事务边界不清晰 生成任务涉及多表写入:`articles` + `article_versions` + `generation_tasks` + `tenant_quota_ledgers`。如果不在同一事务中: - 额度扣了但文章没写入 → 额度泄漏 - 文章写了但额度没扣 → 超额使用 > **建议**:文档中明确关键事务边界——至少"额度扣减 + 任务创建"必须在同一 PostgreSQL 事务中。Worker 侧的"结果写入 + 额度确认"也应事务保护。 ### 风险 3:migration 策略缺失 30+ 张表的 Schema 定义好了,但没有 migration 策略: - 如何管理 Schema 版本?(golang-migrate / Atlas / sponge 内置?) - 线上 DDL 变更如何无停机执行? - 是否有 down migration? > **建议**:明确使用 `golang-migrate` 或 `Atlas`,并在 `server/migrations/` 目录约定命名规则。 ### 风险 4:`tenant_id` 数据隔离的执行保障 5 万用户意味着大量租户。所有租户数据共库共表,`tenant_id` 过滤是唯一隔离手段。如果某个查询漏加 `tenant_id`,就是数据泄露。 > **建议**: > - 在 repository 层封装 `TenantScope(ctx)` 自动注入 `WHERE tenant_id = ?` > - 在 CI 中运行静态分析检查所有查询是否带 `tenant_id` > - 文档中将此列为**强制编码规范** ### 风险 5:对象存储的清理策略 知识库文档、原始模型回答、发布请求/响应 payload 持续写入对象存储。无清理策略意味着存储成本线性增长。 > **建议**: > - 已删除知识文档的对象存储文件应在 T+7 天后清理 > - 原始模型回答按 `question-driven-monitoring` 建议保留 30-90 天 > - 定期运行 GC 任务扫描"孤儿文件" ### 风险 6:Sponge 框架的 code-gen 与手写代码冲突 Sponge 的核心卖点是代码生成(从 protobuf/sql 生成 handler/service/dao)。但主架构的分层是自定义的 `transport/app/domain/repository`。如果使用 sponge 的代码生成: - 生成的代码放在哪个层? - 手写的 domain 逻辑如何与生成的 dao 共存? - 重新生成时是否覆盖手写代码? > **建议**:明确 sponge 仅用于**脚手架初始化**(项目初始化、数据库连接、中间件配置),**不使用**其默认的 handler/service/dao 代码生成,由团队按自定义分层手写业务代码。 ### 风险 7:SSE 与 Kubernetes HPA 的兼容性 Kubernetes 默认负载均衡(基于 Service → Pod 的 round-robin)会将新请求分配到不同 Pod。但 SSE 是长连接,如果用户的 SSE 连接在 Pod A,但任务结果写入后通知只发到 Pod B,用户收不到。 > **建议**: > - SSE 推送使用 **Redis Pub/Sub** 作为跨 Pod 广播层 > - 或使用 **Sticky Session**(Ingress `nginx.ingress.kubernetes.io/affinity: cookie`) > - 文档中需要明确选择哪种方案 ### 风险 8:`brand_question_versions` 的碎片化 每次用户修改问题文案就新增一个版本。如果用户频繁微调(改错字、加时间等),版本表会快速碎片化。监控计划如果绑定 `question_version_id`,可能产生大量只使用 1-2 天的版本。 > **建议**: > - 对问题修改增加 **debounce**(如 10 分钟内多次修改只产生一个版本) > - 或者只在"发布/启用"时才生成新版本,编辑中间态不算版本 ### 风险 9:批量生成的额度预扣与回滚 用户提交"批量生成 100 篇"时: - 是预扣 100 次额度? - 还是每篇生成成功时扣 1 次? - 如果预扣后有 30 篇生成失败,如何退回额度? > **建议**:文档应明确**额度策略**:推荐"提交时预扣 + 失败后按条退还 + 定期对账"模式。 ### 风险 10:`viewer` 角色缺失导致 PRD 不满足 PRD 明确定义了"只读成员"角色,但主架构删除了 `viewer` 替换为 `kol`。如果按当前架构开发: - "只读成员"无法创建——PRD 交付不通过 - 前端权限配置缺少对应的角色枚举 --- ## 四、"足够简单"维度的优化建议 | 可简化选项 | 当前方案 | 简化建议 | 风险 | | --- | --- | --- | --- | | SSE 推送 | 全场景 SSE | **V1 全部用轮询**(10s),V1.5 引入 SSE | 延迟略高但架构极度简化 | | Gateway 独立进程 | `cmd/gateway` | **V1 把网关逻辑合并到 tenant-api 的中间件**,省一个进程 | 后续拆分需重构 | | Rerank 模块 | cross-encoder reranker | **V1 跳过 rerank**,只用 dense + filter + top5 | RAG 精度降低 | | 独立 Scheduler | `cmd/scheduler` | **用 cron job 或 pg_cron** 代替独立进程 | 灵活性降低 | | `question_model_daily_metrics` | 问题级汇总表 | **V1 只保留 `keyword_model_daily_metrics`** | 丢失问题级穿透能力 | > [!NOTE] > 以上是**可选简化**,不是必须。如果团队人力充足,保持当前设计无问题。但如果"足够简单"是硬约束,可以选择部分简化。 --- ## 五、修改建议汇总 ### P0(必须修复) | # | 问题 | 预计工作量 | | --- | --- | --- | | 1 | 主架构中补充 RAG 检索步骤(交互图 + 生成链路 + 模块定义) | 30min | | 2 | 统一 media-binding-v1.md 与主架构 §13.8 的接口定义 | 1h | | 3 | 补充 LLM 调用超时/限流/并发池设计 | 30min | | 4 | 明确关键事务边界(额度扣减 + 任务创建 + 结果写入) | 15min | | 5 | 恢复 `viewer` 角色 + 定义 `kol` 角色权限 | 15min | ### P1(重要补充) | # | 问题 | 预计工作量 | | --- | --- | --- | | 6 | §13.11.4 补充 `knowledge_chunks_meta` 表 | 10min | | 7 | §13.11.6 与 question-driven-monitoring 对齐字段 | 20min | | 8 | `article_templates` 增加 `scope` 字段区分平台/租户模板 | 5min | | 9 | 补充 SSE 跨 Pod 广播方案(Redis Pub/Sub 或 Sticky Session) | 20min | | 10 | 补充额度预扣/退还/对账策略 | 15min |