docs: 添加 FastAPI + Docker 迁移设计文档
This commit is contained in:
@@ -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=<secret>
|
||||
WECHAT_TOKEN=<token>
|
||||
WECHAT_ENCODING_AES_KEY=<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` |
|
||||
Reference in New Issue
Block a user