geo/docs/plans/2026-06-04-009-chore-geo-pr...

439 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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:** nonePlan 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 后端,生产环境自动使用 RedisRedis 不可用时降级到内存
**Requirements:** R3, R4, R5
**Dependencies:** U1Plan 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 返回 nullrefreshAccessToken 标记 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 配置