diff --git a/docs/superpowers/specs/2026-05-09-fastapi-docker-migration-design.md b/docs/superpowers/specs/2026-05-09-fastapi-docker-migration-design.md new file mode 100644 index 0000000..8ec327f --- /dev/null +++ b/docs/superpowers/specs/2026-05-09-fastapi-docker-migration-design.md @@ -0,0 +1,390 @@ +# FastAPI + Docker 迁移设计文档 + +**日期**: 2026-05-09 +**版本**: 1.0 +**状态**: 设计阶段 + +--- + +## 1. 背景与目标 + +将现有的广西政府采购网公告监控系统从 Flask CLI 架构迁移到 FastAPI + Docker 部署方案,同时提升代码质量、安全性和可维护性。 + +### 核心目标 + +- FastAPI 替代 Flask 回调服务器 + argparse CLI,统一为一个 Web 应用 +- Docker 单容器部署,连接现有 PostgreSQL +- SQLAlchemy ORM 替代原始 psycopg2 操作 +- APScheduler 内置定时任务 +- 环境变量管理配置,敏感信息不提交 git +- 异步改造(httpx + asyncpg) + +--- + +## 2. 新项目结构 + +``` +gx-gp-notify/ +├── app/ +│ ├── __init__.py +│ ├── main.py # FastAPI 应用入口 + lifespan +│ ├── config.py # Pydantic Settings (环境变量驱动) +│ ├── api/ +│ │ ├── __init__.py +│ │ ├── router.py # 统一路由注册 +│ │ ├── deps.py # 依赖注入 (get_db, get_scheduler) +│ │ ├── announcements.py # 公告 CRUD 端点 +│ │ ├── crawl.py # 爬取触发端点 +│ │ └── wechat.py # 企业微信回调端点 +│ ├── models/ +│ │ ├── __init__.py +│ │ ├── announcement.py # SQLAlchemy 模型 +│ │ └── schemas.py # Pydantic 请求/响应模型 +│ ├── crawler/ +│ │ ├── __init__.py +│ │ ├── spider.py # 爬虫核心 +│ │ ├── dahuagov_spider.py # 大化县专用爬虫 +│ │ └── parsers.py # 数据解析器 +│ ├── services/ +│ │ ├── __init__.py +│ │ ├── crawl_service.py # 爬取业务流程 +│ │ ├── filter_service.py # 筛选逻辑 +│ │ └── notification_service.py # 通知服务 +│ ├── wechat/ +│ │ ├── __init__.py +│ │ ├── handler.py # 消息处理器 +│ │ ├── menu.py # 菜单管理 +│ │ ├── crypto.py # WXBizMsgCrypt 加解密 +│ │ └── client.py # 企业微信 API 客户端 +│ └── scheduler/ +│ ├── __init__.py +│ └── jobs.py # APScheduler 定时任务 +├── tests/ +│ ├── __init__.py +│ ├── conftest.py # pytest fixtures +│ ├── test_crawler/ # 爬虫解析器测试 +│ ├── test_api/ # API 端点测试 +│ └── test_services/ # 业务逻辑测试 +├── alembic/ # 数据库迁移 +│ └── versions/ +├── docker/ +│ ├── Dockerfile +│ └── docker-compose.yml +├── pyproject.toml # 项目元数据 + 依赖 + 工具配置 +├── .env.example # 环境变量模板 +├── .gitignore +└── logs/ # 日志目录 (gitignore) +``` + +--- + +## 3. API 端点设计 + +### 3.1 公告查询 + +| 方法 | 路径 | 说明 | +|------|------|------| +| `GET` | `/api/v1/announcements` | 查询公告列表(分页、来源筛选、日期筛选、关键词搜索) | +| `GET` | `/api/v1/announcements/{id}` | 获取单条公告详情 | +| `GET` | `/api/v1/announcements/today` | 获取今日公告 | +| `GET` | `/api/v1/announcements/stats` | 获取公告统计信息 | + +### 3.2 爬取控制 + +| 方法 | 路径 | 说明 | +|------|------|------| +| `POST` | `/api/v1/crawl/trigger` | 手动触发一次爬取(支持关键词/来源参数) | +| `GET` | `/api/v1/crawl/status` | 查看最近爬取状态 | +| `GET` | `/api/v1/crawl/sources` | 获取已配置的公告来源列表 | + +### 3.3 定时任务 + +| 方法 | 路径 | 说明 | +|------|------|------| +| `GET` | `/api/v1/scheduler/jobs` | 查看活跃的定时任务 | +| `POST` | `/api/v1/scheduler/pause/{job_id}` | 暂停某个定时任务 | +| `POST` | `/api/v1/scheduler/resume/{job_id}` | 恢复某个定时任务 | + +### 3.4 企业微信 + +| 方法 | 路径 | 说明 | +|------|------|------| +| `GET` `POST` | `/api/v1/wechat/callback` | 企业微信回调入口(URL验证 + 消息接收) | + +### 3.5 系统 + +| 方法 | 路径 | 说明 | +|------|------|------| +| `GET` | `/health` | 健康检查(包含数据库连通性) | + +### 3.6 通用特性 + +- FastAPI 自动生成 Swagger UI (`/docs`) 和 ReDoc (`/redoc`) +- 统一分页格式:`{ total, page, page_size, items }` +- 统一错误响应:`{ detail: "error message" }` +- 依赖注入:数据库 session、配置、调度器均通过 FastAPI `Depends()` 注入 +- CORS 默认关闭,通过 `CORS_ORIGINS` 环境变量可选开启 + +--- + +## 4. 数据模型 + +### 4.1 公告表 (announcements) + +合并现有的 `announcements`、`auto_announcements`、`manual_announcements` 三张表为一张,通过 `crawl_mode` 字段区分自动/手动。 + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Integer | 主键 | +| title | String(500) | 公告标题 | +| publish_date | DateTime | 发布时间 | +| purchase_name | String(200) | 发布单位 | +| content_url | Text | 内容链接 | +| source_code | String(50) | 来源代码 | +| source_name | String(100) | 来源名称 | +| announcement_type | String(50) | 公告类型枚举值 | +| content_hash | String(64) | SHA256 内容哈希,UNIQUE | +| crawl_mode | String(20) | "auto" / "manual" | +| is_new | Boolean | 是否新公告 | +| is_today | Boolean | 是否今日公告 | +| created_at | DateTime | 创建时间 | +| updated_at | DateTime | 更新时间 | + +### 4.2 大化县公告表 (dahuagov_announcements) + +保留独立表,因为推送逻辑不同(全部推送,不筛选关键词)。 + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Integer | 主键 | +| title | String(500) | 公告标题 | +| publish_date | DateTime | 发布时间 | +| purchase_name | String(200) | 发布单位 | +| content_url | Text | 内容链接 | +| source_code | String(50) | 默认 "dahuagov" | +| source_name | String(100) | 默认 "大化县政府网采购公告" | +| content_hash | String(64) | SHA256 内容哈希,UNIQUE | +| is_new | Boolean | 是否新公告(未推送) | +| created_at | DateTime | 创建时间 | +| updated_at | DateTime | 更新时间 | + +### 4.3 变更说明 + +- `content_hash` 从 MD5 (32位) 改为 SHA256 (64位) +- 去掉 `announcement_sources` 表,来源信息从环境变量配置动态读取 +- 去掉 `crawl_results` 表,爬取结果通过日志记录 +- 去掉多余的索引表(keyword_matched / date_filtered 字段),筛选在应用层处理 +- 使用 Alembic 管理数据库迁移 + +--- + +## 5. 配置管理 + +### 5.1 pydantic-settings + +所有配置通过 `pydantic-settings` 的 `BaseSettings` 加载,来源优先级:环境变量 > `.env` 文件 > 默认值。 + +### 5.2 环境变量 + +```bash +# 应用 +DEBUG=false +LOG_LEVEL=INFO + +# 数据库(连接现有 PostgreSQL) +DATABASE_URL=postgresql+asyncpg://gx-gp-notify:password@10.10.10.14:5432/gx-gp-notify + +# 爬虫 +CRAWLER_BASE_URL=https://zfcg.gxzf.gov.cn +CRAWLER_KEYWORDS=["大化"] +CRAWLER_MAX_PAGES=10 +CRAWLER_TIMEOUT=30 + +# 企业微信 +WECHAT_ENABLED=true +WECHAT_CORP_ID=ww69e8e44636f47780 +WECHAT_AGENT_ID=1000007 +WECHAT_SECRET= +WECHAT_TOKEN= +WECHAT_ENCODING_AES_KEY= + +# 定时任务 +SCHEDULER_ENABLED=true +SCHEDULER_CRON=0 8,14,18 * * * + +# Markdown 输出 +MARKDOWN_OUTPUT_FILE=onu.md +``` + +### 5.3 安全措施 + +- `.env` 文件加入 `.gitignore` +- `config.yaml` 中的敏感信息不再提交到仓库 +- 使用 SHA256 替代 MD5 做内容哈希 +- 建议清理 git 历史中的敏感信息 + +--- + +## 6. Docker 部署 + +### 6.1 架构 + +``` +docker-compose.yml +┌─────────────────────────────┐ +│ app (FastAPI) │ +│ - uvicorn 服务器 │ +│ - APScheduler (内置) │ +│ - 端口 8000 │ +│ - 挂载: ./logs │ +└──────────┬──────────────────┘ + │ TCP 连接 +┌──────────▼──────────────────┐ +│ 外部 PostgreSQL (现有) │ +│ 10.10.10.14:5432 │ +└─────────────────────────────┘ +``` + +### 6.2 Dockerfile + +```dockerfile +FROM python:3.12-slim +WORKDIR /app +COPY pyproject.toml . +RUN pip install --no-cache-dir . +COPY . . +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] +``` + +### 6.3 docker-compose.yml + +```yaml +services: + app: + build: . + ports: + - "8000:8000" + env_file: + - .env + volumes: + - ./logs:/app/logs + restart: unless-stopped +``` + +### 6.4 启动步骤 + +```bash +cp .env.example .env # 编辑 .env 填入真实配置 +docker compose up -d # 启动服务 +docker compose logs -f app # 查看日志 +``` + +--- + +## 7. 技术栈 + +| 组件 | 当前 | 迁移后 | +|------|------|--------| +| Web 框架 | Flask (仅回调用) | FastAPI | +| CLI | argparse | 不再需要,API 替代 | +| 数据库驱动 | psycopg2 (同步) | asyncpg (异步) + SQLAlchemy 2.0 | +| HTTP 客户端 | requests (同步) | httpx (异步) | +| 定时任务 | schedule 库 | APScheduler | +| 配置 | PyYAML + config.yaml | pydantic-settings + .env | +| 加密 | pycryptodome + cryptography | 保留 | +| 部署 | 裸机 + uWSGI | Docker + uvicorn | +| 代码检查 | 无 | ruff | +| 测试 | 无 | pytest + httpx | +| 迁移 | 手动 CREATE TABLE | Alembic | + +--- + +## 8. 额外改进 + +### 8.1 异步改造 + +- 爬虫网络请求:`httpx.AsyncClient` 替代 `requests` +- 数据库:`asyncpg` + SQLAlchemy async engine +- 通知发送:`httpx.AsyncClient` +- API 端点使用 `async def` + +### 8.2 自动化测试 + +- 爬虫解析器单元测试(Mock 网页响应,验证解析逻辑) +- 筛选逻辑单元测试 +- API 端点集成测试(用 `httpx.AsyncClient` + 测试数据库) +- pytest + pytest-asyncio + pytest-cov + +### 8.3 日志改进 + +- 结构化日志(JSON 格式) +- Docker 环境下输出到 stdout +- 保留文件日志供宿主机查看(挂载 `./logs` 目录) + +### 8.4 错误处理 + +- 统一异常处理中间件 +- 健康检查 `/health` 含数据库连通性验证 +- 关键操作结构化日志记录 + +### 8.5 代码质量 + +- `ruff` 替代 flake8 + black +- `pyproject.toml` 统一管理依赖和工具配置 +- 全面 Type Hints + +### 8.6 .gitignore + +```gitignore +.env +logs/ +*.log +__pycache__/ +*.pyc +.venv/ +.ruff_cache/ +.pytest_cache/ +*.egg-info/ +``` + +--- + +## 9. 迁移策略 + +### 分步执行 + +1. 创建新项目结构骨架(FastAPI app + config) +2. 数据库模型 + Alembic 迁移 +3. 迁移爬虫模块(异步改造) +4. 迁移服务层(筛选、通知、Markdown) +5. 迁移 WeChat 回调(Flask → FastAPI route) +6. 添加 APScheduler 定时任务 +7. 编写测试 +8. Docker 化 +9. 清理旧文件 + +### 向后兼容 + +- 保留 `dahuagov_announcements` 表结构不变 +- 企业微信回调 URL 路径保持不变 (`/api/v1/wechat/callback`) +- 数据库迁移使用 Alembic,不丢失现有数据 + +--- + +## 10. 未包含的内容 + +- 不做多用户认证/授权(个人使用) +- 不做 Celery 分布式任务(个人使用,APScheduler 足够) +- 不做 Redis 缓存 +- 不做前端 UI + +--- + +## 11. 风险与缓解 + +| 风险 | 缓解 | +|------|------| +| 数据库迁移丢失数据 | Alembic 自动生成迁移脚本,先在测试环境验证 | +| 异步爬虫被目标站限流 | 保留现有延迟/重试机制,httpx 支持同样的超时配置 | +| 企业微信回调兼容性 | 回调路径不变,加解密逻辑完全复用现有代码 | +| Docker 网络访问 10.10.10.14 | 确认 Docker 宿主机可访问该 IP,必要时用 `host.docker.internal` |