439 lines
18 KiB
Markdown
439 lines
18 KiB
Markdown
---
|
||
title: "chore: GEO Platform Production Hardening & Test Infrastructure"
|
||
type: chore
|
||
status: completed
|
||
date: 2026-06-04
|
||
---
|
||
|
||
# GEO Platform Production Hardening & Test Infrastructure
|
||
|
||
## Summary
|
||
|
||
在 Plan 008(生产就绪)基础上,完成收尾工作并进一步加固生产环境:实现限流中间件 Redis 双后端、补全标签验证逻辑、加固认证错误处理、清理过时注释。同时全面深化 E2E 测试体系:提取共享 Playwright fixture、补充交互测试和错误状态测试、建立 API mock 层。完成后平台具备多实例部署能力和高质量测试保障。
|
||
|
||
## Problem Frame
|
||
|
||
Plan 008 的 U1-U6 已完成代码变更,但 U7 验证和 U8 尚未收尾,所有变更未提交 Git。深度调研发现多个生产阻塞问题:限流中间件基于内存存储,多实例部署时限流失效;`platform_rules.py` 中标签验证逻辑(min_tags/max_tags)未实现;认证模块 catch 块吞掉错误无日志;Sentry 集成已完成但 TODO 注释未清理。E2E 测试方面,19 个测试文件中 loginAndWait 函数重复定义,测试以页面渲染验证为主,缺少交互测试、错误状态测试和 API mock 层,测试深度不足以支撑生产信心。
|
||
|
||
---
|
||
|
||
## Requirements
|
||
|
||
**Plan 008 收尾**
|
||
|
||
R1. Plan 008 U7 测试文件迁移后 pytest 验证通过
|
||
R2. Plan 008 U8 pytest-cov 覆盖率配置完成,`pytest` 自动生成覆盖率报告
|
||
|
||
**限流中间件加固**
|
||
|
||
R3. 限流中间件支持 Redis 后端,生产环境自动使用 Redis
|
||
R4. Redis 不可用时自动降级到内存后端,不影响服务启动
|
||
R5. 内存后端添加定期清理机制,防止长期运行内存增长
|
||
|
||
**标签验证逻辑**
|
||
|
||
R6. `platform_rules.py` 中 min_tags/max_tags 规则实际执行校验
|
||
|
||
**认证错误处理**
|
||
|
||
R7. 认证模块 authorize 和 refreshAccessToken 中 catch 块记录错误日志
|
||
|
||
**代码卫生**
|
||
|
||
R8. 已完成的 Sentry 集成点 TODO 注释清理
|
||
|
||
**E2E 测试基础设施**
|
||
|
||
R9. 19 个 E2E 测试文件中 loginAndWait 提取为共享 Playwright fixture
|
||
R10. 关键业务流程补充交互测试(数据输入、提交、验证)
|
||
R11. 补充错误状态测试(网络错误、API 500、空数据)
|
||
R12. 建立 API mock 层,E2E 测试不依赖真实后端数据
|
||
|
||
---
|
||
|
||
## Key Technical Decisions
|
||
|
||
KTD1. **限流中间件采用 Redis 优先 + 内存 fallback 双后端。** 生产环境通过 `RATE_LIMIT_BACKEND=redis` 使用 Redis,开发/测试环境默认使用内存后端。Redis 连接失败时自动降级到内存后端并记录警告,不阻塞服务启动。内存后端添加 `asyncio.create_task` 后台清理任务。
|
||
|
||
KTD2. **E2E API mock 层使用 Playwright route interception。** 不引入额外的 mock server 依赖,利用 Playwright 原生的 `page.route()` 拦截 API 请求并返回预设数据。mock 数据按场景组织为 JSON fixture 文件,测试可按需加载。
|
||
|
||
KTD3. **共享 Playwright fixture 放在 `e2e/fixtures/` 目录。** 包含 `authenticatedPage`(自动登录的 page)、`mockApi`(API mock 辅助函数)、`testData`(测试数据工厂)。各测试文件通过 `import` 引用,消除 loginAndWait 重复。
|
||
|
||
KTD4. **标签验证在现有 `validate_content` 方法中扩展。** 不新建验证器类,在 `platform_rules.py` 的 `validate_content` 方法中增加标签数量校验分支,读取 content 的 tags 字段与 min_tags/max_tags 规则比对。
|
||
|
||
---
|
||
|
||
## High-Level Technical Design
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
subgraph Phase1["Phase 1 — Plan 008 收尾"]
|
||
U1[U1. U7 验证 + U8 覆盖率配置]
|
||
end
|
||
|
||
subgraph Phase2["Phase 2 — 生产加固"]
|
||
U2[U2. 限流中间件双后端]
|
||
U3[U3. 标签验证逻辑]
|
||
U4[U4. 认证错误处理]
|
||
U5[U5. 代码卫生清理]
|
||
end
|
||
|
||
subgraph Phase3["Phase 3 — E2E 测试基础设施"]
|
||
U6[U6. 共享 Playwright fixture]
|
||
U7[U7. API mock 层]
|
||
U8[U8. 交互测试 + 错误状态测试]
|
||
end
|
||
|
||
U1 --> U2
|
||
U2 --> U3
|
||
U2 --> U4
|
||
U4 --> U5
|
||
U5 --> U6
|
||
U6 --> U7
|
||
U7 --> U8
|
||
```
|
||
|
||
---
|
||
|
||
## Implementation Units
|
||
|
||
### U1. Plan 008 Completion — U7 Verification + U8 Coverage Config
|
||
|
||
**Goal:** 完成 Plan 008 剩余工作:验证测试文件迁移无回归,配置 pytest-cov 自动覆盖率
|
||
|
||
**Requirements:** R1, R2
|
||
|
||
**Dependencies:** none(Plan 008 U1-U7 代码变更已存在)
|
||
|
||
**Files:**
|
||
- `backend/pyproject.toml` — 添加 pytest-cov addopts 和 coverage 配置
|
||
- `backend/requirements.txt` — 确认 pytest-cov 已存在
|
||
- `backend/tests/test_infrastructure/` — 验证迁移后测试通过
|
||
- `backend/tests/test_services/` — 验证迁移后测试通过
|
||
|
||
**Approach:**
|
||
1. 运行 `pytest tests/` 验证 U7 测试文件迁移后无回归
|
||
2. 在 `pyproject.toml` 的 `[tool.pytest.ini_options]` 添加 `addopts = "--cov=app --cov-report=term-missing --cov-report=html"`
|
||
3. 添加 `[tool.coverage.run]` 配置 omit 列表(排除 `tests/*`、`migrations/*`、`__pycache__/*`)
|
||
4. 添加 `[tool.coverage.report]` 配置 `exclude_lines`(排除 `pragma: no cover`、`if __name__` 等)
|
||
5. 验证 `pytest` 自动生成覆盖率报告
|
||
|
||
**Patterns to follow:** pytest-cov 标准配置模式
|
||
|
||
**Test scenarios:**
|
||
- `pytest tests/` 全部通过,通过率与迁移前一致
|
||
- `pytest` 自动生成覆盖率报告,显示各模块覆盖率百分比
|
||
- 覆盖率报告排除 migrations 和测试文件
|
||
- HTML 覆盖率报告可正常打开查看
|
||
|
||
**Verification:** `pytest` 成功生成覆盖率报告,`find tests/ -maxdepth 1 -name "test_*.py"` 返回空
|
||
|
||
---
|
||
|
||
### U2. Rate Limiter Dual Backend — Redis + Memory Fallback
|
||
|
||
**Goal:** 限流中间件支持 Redis 后端,生产环境自动使用 Redis,Redis 不可用时降级到内存
|
||
|
||
**Requirements:** R3, R4, R5
|
||
|
||
**Dependencies:** U1(Plan 008 收尾后再改限流)
|
||
|
||
**Files:**
|
||
- `backend/app/middleware/rate_limit.py` — 重构为双后端架构
|
||
- `backend/app/config.py` — 添加 `RATE_LIMIT_BACKEND` 配置
|
||
- `backend/tests/test_infrastructure/test_rate_limit.py` — 新建限流测试
|
||
- `.env.production.example` — 添加 `RATE_LIMIT_BACKEND=redis`
|
||
|
||
**Approach:**
|
||
1. 定义 `RateLimitBackend` 抽象接口(`is_rate_limited`、`reset` 方法)
|
||
2. 实现 `MemoryRateLimitBackend`(当前逻辑提取,添加后台清理 task)
|
||
3. 实现 `RedisRateLimitBackend`(使用 Redis sorted set + ZRANGEBYSCORE 实现滑动窗口)
|
||
4. `RateLimitMiddleware.__init__` 根据 `RATE_LIMIT_BACKEND` 配置选择后端
|
||
5. Redis 后端初始化时连接失败则降级到内存后端并记录警告
|
||
6. 内存后端添加 `asyncio.create_task` 后台清理任务,每 60 秒清理过期记录
|
||
7. 保留 `RATE_LIMIT_DISABLED` 环境变量用于 E2E 测试
|
||
|
||
**Patterns to follow:** 策略模式(Strategy Pattern),现有 Redis 连接管理(`app/core/redis.py`)
|
||
|
||
**Test scenarios:**
|
||
- `RATE_LIMIT_BACKEND=memory` 时使用内存后端,限流正常工作
|
||
- `RATE_LIMIT_BACKEND=redis` 且 Redis 可用时使用 Redis 后端
|
||
- `RATE_LIMIT_BACKEND=redis` 且 Redis 不可用时降级到内存后端,记录警告
|
||
- Redis 后端多实例共享限流状态(模拟两个客户端连接同一 Redis)
|
||
- 内存后端后台清理任务运行,过期记录被清理
|
||
- `RATE_LIMIT_DISABLED=1` 时所有后端跳过限流
|
||
- 现有 4 层限流规则(auth_strict、auth、query_run、global)在新后端下均正常
|
||
|
||
**Verification:** `pytest tests/test_infrastructure/test_rate_limit.py` 通过,生产配置 Redis 后端时限流生效
|
||
|
||
---
|
||
|
||
### U3. Tag Validation Logic Implementation
|
||
|
||
**Goal:** 实现 `platform_rules.py` 中 min_tags/max_tags 标签数量校验
|
||
|
||
**Requirements:** R6
|
||
|
||
**Dependencies:** U2
|
||
|
||
**Files:**
|
||
- `backend/app/services/distribution/platform_rules.py` — 补全标签验证逻辑
|
||
- `backend/tests/test_services/test_platform_rules.py` — 新建或扩展标签验证测试
|
||
|
||
**Approach:**
|
||
1. 定位 `validate_content` 方法中 TODO 注释(约第 1015 行)
|
||
2. 从 content 数据中提取 tags 字段(支持 list 和 comma-separated string 两种格式)
|
||
3. 与规则中的 min_tags/max_tags 比对
|
||
4. 返回验证结果:tags 数量不足时返回错误信息,包含当前 tags 数和要求的范围
|
||
5. 无 tags 字段时视为 0 个标签
|
||
|
||
**Patterns to follow:** 现有 `validate_content` 中其他规则的校验模式
|
||
|
||
**Test scenarios:**
|
||
- content tags 数量在 min_tags 和 max_tags 范围内 → 验证通过
|
||
- content tags 数量 < min_tags → 验证失败,错误信息包含当前数量和最小要求
|
||
- content tags 数量 > max_tags → 验证失败,错误信息包含当前数量和最大限制
|
||
- content 无 tags 字段 → 视为 0 个标签,按 min_tags 规则校验
|
||
- content tags 为 comma-separated string → 正确解析并计数
|
||
- 规则无 min_tags/max_tags → 跳过标签校验
|
||
|
||
**Verification:** `pytest tests/test_services/test_platform_rules.py` 通过,标签规则校验逻辑覆盖全部场景
|
||
|
||
---
|
||
|
||
### U4. Auth Error Handling Hardening
|
||
|
||
**Goal:** 认证模块 catch 块添加错误日志记录,不再静默吞掉异常
|
||
|
||
**Requirements:** R7
|
||
|
||
**Dependencies:** U2
|
||
|
||
**Files:**
|
||
- `frontend/lib/auth.ts` — authorize 和 refreshAccessToken 添加错误日志
|
||
- `frontend/lib/logger.ts` — 新建前端日志工具(或使用 console.error + Sentry)
|
||
|
||
**Approach:**
|
||
1. 在 `authorize` 函数的 catch 块中添加 `console.error("[Auth] Login failed:", error)`
|
||
2. 在 `refreshAccessToken` 的 catch 块中添加 `console.error("[Auth] Token refresh failed:", error)`
|
||
3. 如果 Sentry 已初始化,同时调用 `Sentry.captureException(error)` 上报
|
||
4. 保持现有返回值逻辑不变(authorize 返回 null,refreshAccessToken 标记 error)
|
||
5. 可选:创建简单的 `logger.ts` 工具封装 console.error + Sentry 上报
|
||
|
||
**Patterns to follow:** 后端 `app/core/logging.py` 的日志模式
|
||
|
||
**Test scenarios:**
|
||
- 登录失败时控制台输出错误日志
|
||
- Token 刷新失败时控制台输出错误日志
|
||
- Sentry 已配置时错误自动上报
|
||
- 错误日志不影响现有认证流程(返回值不变)
|
||
|
||
**Verification:** 模拟登录失败和 token 刷新失败,确认日志输出和 Sentry 上报
|
||
|
||
---
|
||
|
||
### U5. Code Hygiene — Sentry Comment Cleanup
|
||
|
||
**Goal:** 清理已完成的 Sentry 集成点 TODO 注释,更新为准确描述
|
||
|
||
**Requirements:** R8
|
||
|
||
**Dependencies:** U4
|
||
|
||
**Files:**
|
||
- `backend/app/middleware/metrics.py` — 更新 Sentry 集成点注释
|
||
- `frontend/components/ErrorBoundary.tsx` — 更新 Sentry 集成点注释
|
||
|
||
**Approach:**
|
||
1. `metrics.py`:将"预留 Sentry 集成点(TODO 注释标注)"更新为"已集成 Sentry 性能监控"
|
||
2. `ErrorBoundary.tsx`:将"预留 Sentry 集成点(搜索 TODO:SENTRY)"更新为"已集成 Sentry 错误上报"
|
||
3. 确认两处实际 Sentry 代码正常工作
|
||
|
||
**Test expectation:** none — 纯注释更新,无行为变更
|
||
|
||
**Verification:** grep 搜索确认无残留的 Sentry TODO 注释
|
||
|
||
---
|
||
|
||
### U6. Shared Playwright Fixtures
|
||
|
||
**Goal:** 提取 19 个 E2E 测试文件中重复的 loginAndWait 为共享 Playwright fixture
|
||
|
||
**Requirements:** R9
|
||
|
||
**Dependencies:** U5
|
||
|
||
**Files:**
|
||
- `frontend/e2e/fixtures/auth.ts` — 新建,authenticatedPage fixture
|
||
- `frontend/e2e/fixtures/index.ts` — 新建,统一导出
|
||
- `frontend/e2e/tests/*.spec.ts` — 19 个文件重构为使用共享 fixture
|
||
|
||
**Approach:**
|
||
1. 创建 `e2e/fixtures/auth.ts`,定义 `authenticatedPage` fixture:
|
||
- 基于 Playwright `test.extend()` 扩展
|
||
- 自动执行登录流程(复用当前 loginAndWait 逻辑)
|
||
- 测试结束后自动清理
|
||
2. 创建 `e2e/fixtures/index.ts`,统一导出所有 fixture
|
||
3. 逐个重构 19 个测试文件:
|
||
- 移除文件内的 `loginAndWait` 函数定义
|
||
- 将 `loginAndWait(page)` 调用替换为 `authenticatedPage` fixture 使用
|
||
- 保持测试逻辑不变
|
||
4. 验证重构后所有 E2E 测试通过
|
||
|
||
**Patterns to follow:** Playwright 官方 fixture 模式(`test.extend()`)
|
||
|
||
**Test scenarios:**
|
||
- `authenticatedPage` fixture 自动完成登录,测试可直接使用已认证的 page
|
||
- 所有 19 个 E2E 测试重构后行为与重构前一致
|
||
- 无测试文件内重复定义 loginAndWait
|
||
|
||
**Verification:** `npx playwright test` 全部通过,grep 确认无残留的 loginAndWait 函数定义
|
||
|
||
---
|
||
|
||
### U7. E2E API Mock Layer
|
||
|
||
**Goal:** 建立 Playwright route interception API mock 层,E2E 测试不依赖真实后端数据
|
||
|
||
**Requirements:** R12
|
||
|
||
**Dependencies:** U6
|
||
|
||
**Files:**
|
||
- `frontend/e2e/fixtures/api-mock.ts` — 新建,API mock 辅助函数
|
||
- `frontend/e2e/fixtures/mock-data/` — 新建,mock 数据 JSON 文件目录
|
||
- `frontend/e2e/fixtures/index.ts` — 更新导出
|
||
|
||
**Approach:**
|
||
1. 创建 `api-mock.ts`,提供以下功能:
|
||
- `mockApi(page, endpoint, response)` — 拦截指定 API 请求返回预设数据
|
||
- `mockApiError(page, endpoint, statusCode)` — 模拟 API 错误响应
|
||
- `mockApiDelay(page, endpoint, ms)` — 模拟 API 延迟
|
||
- `clearApiMocks(page)` — 清除所有 mock
|
||
2. 创建 `mock-data/` 目录,按业务场景组织 JSON fixture:
|
||
- `health-score.json` — 健康评分数据
|
||
- `citations.json` — 引用记录数据
|
||
- `competitors.json` — 竞品分析数据
|
||
- `analytics.json` — 数据监测数据
|
||
- `knowledge.json` — 知识库数据
|
||
- `empty-state.json` — 空状态数据
|
||
3. 所有 mock 使用 Playwright `page.route()` 实现,不引入额外依赖
|
||
4. mock 辅助函数作为 fixture 导出,测试可按需使用
|
||
|
||
**Patterns to follow:** Playwright route interception 官方文档模式
|
||
|
||
**Test scenarios:**
|
||
- `mockApi` 成功拦截 API 请求并返回预设数据
|
||
- `mockApiError` 成功模拟 500 错误响应
|
||
- `mockApiDelay` 成功模拟 API 延迟
|
||
- 多个 mock 同时生效不冲突
|
||
- `clearApiMocks` 清除后请求正常发送到真实后端
|
||
- mock 数据 JSON 格式正确,可被前端正确解析
|
||
|
||
**Verification:** 使用 mock 的 E2E 测试在无后端环境下通过
|
||
|
||
---
|
||
|
||
### U8. E2E Interaction and Error State Tests
|
||
|
||
**Goal:** 补充关键业务流程的交互测试和错误状态测试
|
||
|
||
**Requirements:** R10, R11
|
||
|
||
**Dependencies:** U7
|
||
|
||
**Files:**
|
||
- `frontend/e2e/tests/health-score-interaction.spec.ts` — 新建,健康评分交互测试
|
||
- `frontend/e2e/tests/citation-flow.spec.ts` — 新建,引用记录完整流程测试
|
||
- `frontend/e2e/tests/competitor-interaction.spec.ts` — 新建,竞品分析交互测试
|
||
- `frontend/e2e/tests/error-states.spec.ts` — 新建,错误状态测试
|
||
- `frontend/e2e/tests/knowledge-interaction.spec.ts` — 新建,知识库交互测试
|
||
|
||
**Approach:**
|
||
1. **交互测试**(3 个文件):
|
||
- 健康评分:选择时间范围 → 查看评分变化 → 查看维度详情
|
||
- 引用记录:搜索引用 → 筛选来源 → 查看引用详情
|
||
- 竞品分析:添加竞品 → 查看对比 → 删除竞品
|
||
- 知识库:上传文档 → 查看文档列表 → 删除文档
|
||
2. **错误状态测试**(1 个文件):
|
||
- API 500 错误:页面显示错误提示,提供重试按钮
|
||
- 网络断开:页面显示离线提示
|
||
- 空数据状态:页面显示空状态引导
|
||
- 认证过期:自动跳转登录页
|
||
3. 所有测试使用 U7 的 API mock 层,不依赖真实后端
|
||
4. 使用 U6 的 `authenticatedPage` fixture,无需手动登录
|
||
|
||
**Patterns to follow:** 现有 E2E 测试结构,Playwright 最佳实践
|
||
|
||
**Test scenarios:**
|
||
- 健康评分交互:切换时间范围后数据更新
|
||
- 引用记录搜索:输入关键词后结果过滤
|
||
- 竞品添加:填写表单提交后列表更新
|
||
- API 500:页面显示错误提示,点击重试后重新请求
|
||
- 空数据:页面显示引导文案和操作按钮
|
||
- 认证过期:跳转登录页,重新登录后恢复
|
||
|
||
**Verification:** `npx playwright test` 全部通过,新增测试覆盖交互和错误状态
|
||
|
||
---
|
||
|
||
## Scope Boundaries
|
||
|
||
**In scope:**
|
||
- Plan 008 收尾(U7 验证 + U8 覆盖率配置)
|
||
- 限流中间件 Redis 双后端实现
|
||
- 标签验证逻辑补全
|
||
- 认证错误处理加固
|
||
- Sentry 注释清理
|
||
- E2E 共享 fixture 提取
|
||
- E2E API mock 层建立
|
||
- E2E 交互测试和错误状态测试
|
||
|
||
**Deferred for later:**
|
||
- 真实微信/支付宝 SDK 商户密钥配置(需外部资质审批)
|
||
- Redis 连接池统一管理
|
||
- 前端组件级单元测试扩展(当前仅 13 个)
|
||
- 依赖大版本升级(Next.js 14→15、Tailwind 3→4、ESLint 8→9)
|
||
- Playwright 移动端设备测试
|
||
- 性能/负载测试框架(Locust/k6)
|
||
- 视觉回归测试
|
||
- 生产环境域名和 HTTPS 配置
|
||
- Docker 日志驱动和日志轮转配置
|
||
- Plan 001/002 状态更新(行政性工作,随 Git 提交一起处理)
|
||
|
||
**Outside this plan:**
|
||
- 新功能开发
|
||
- 代码重构(除限流中间件架构调整外)
|
||
- UI 打磨
|
||
|
||
---
|
||
|
||
## Risks & Dependencies
|
||
|
||
- **限流中间件重构可能影响现有请求处理。** 内存后端逻辑提取为独立类时需保持行为一致。缓解:先写测试覆盖现有行为,再重构。
|
||
- **Redis 后端实现需确保原子性。** 滑动窗口的 ZADD + ZRANGEBYSCORE 需要使用 Redis pipeline 或 Lua script 保证原子性。缓解:使用 pipeline 执行多命令。
|
||
- **E2E fixture 重构涉及 19 个文件。** 逐个重构时可能遗漏或引入不一致。缓解:重构后全量运行 E2E 测试验证。
|
||
- **API mock 数据可能与真实 API 响应格式不同步。** 后端 API 变更时 mock 数据可能过时。缓解:mock 数据基于 TypeScript 类型定义生成,类型变更时编译报错。
|
||
- **交互测试依赖前端组件稳定性。** 前端 UI 变更可能导致选择器失效。缓解:使用 data-testid 属性而非 CSS 选择器。
|
||
|
||
---
|
||
|
||
## Open Questions
|
||
|
||
**Deferred to implementation:**
|
||
- Redis 连接使用现有 `app/core/redis.py` 还是新建连接?需确认现有 Redis 客户端配置
|
||
- E2E mock 数据的初始数据集从哪里获取?可从开发环境 API 响应录制
|
||
- 前端 logger 是否需要统一的日志级别控制?还是简单的 console.error + Sentry 即可
|
||
|
||
---
|
||
|
||
## Sources & Research
|
||
|
||
- `docs/plans/2026-06-04-008-chore-geo-production-readiness-plan.md` — 当前计划(status: active)
|
||
- `docs/plans/2026-05-31-002-test-quality-assurance-system-plan.md` — 测试质量体系(status: active)
|
||
- `backend/app/middleware/rate_limit.py` — 当前内存限流实现
|
||
- `backend/app/services/distribution/platform_rules.py` — 标签验证 TODO(第 1015 行)
|
||
- `frontend/lib/auth.ts` — 认证模块
|
||
- `frontend/e2e/tests/` — 19 个 E2E 测试文件
|
||
- `backend/pyproject.toml` — 缺少 pytest-cov 配置
|