de30497f59
- Implemented tenant and user management features including: - Tenant creation and management with associated migrations. - User creation and management with associated migrations. - Tenant membership management with associated migrations. - Platform user roles management with associated migrations. - Quota management with associated migrations. - Article and template management with associated migrations. - Added HTTP handlers for templates and workspaces. - Created tests for protected and public routes. - Introduced a script to check tenant scope in SQL queries. - Documented task plan for backend completion and frontend foundation.
319 lines
16 KiB
Markdown
319 lines
16 KiB
Markdown
# 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 |
|