docs: 架构文档 v2.0 — 根据评审意见迭代更新

v1.0 评审通过后,根据四位评审人意见迭代:

沈路明(PM) 评审意见:
- P2: 前端搜索增加按期号精确查找
- P2: 生成记录增加策略类型筛选
- P3: 统计页增加历史趋势可视化
- P3: 覆盖写入先写.tmp再rename

陆怀瑾(COO) 评审意见:
- 新增 R-09/R-10/R-11 风险项
- R-09 运维单点依赖 → 安排徐聪为备用运维
- R-10 数据抓取静默失败 → 接入飞书通知
- R-11 Excel格式漂移 → 增加跳过量监控

徐聪(Dev) 评审意见:
- realpath+commonpath双重校验(已完成)
- 修正app.py行数490→728
- 确认fetch_data已有MAX_RETRIES=3
- 确认web_executor已有timeout=300s
- P2: api_history列名抽取为配置常量
- P2: .generation_records.json加fcntl.flock
- P3: lottery/目录自动清理
- P3: api_token迁移至环境变量
- 新增 R-12/R-13 风险项

严维序(OPS) 评审意见:
- 更新服务名为lotto-app.service(已部署)
- 旧lotto-web.service建议清理
- 统一虚拟环境venv/(删除.venv/)
- P1: 配置logrotate日志轮转
- P1: 服务健康监控接入心跳巡检
- 更新ExecStartPre补充numpy检查
- 确认backup.sh已部署

新增内容:
- §12.1 评审意见响应矩阵(25项)
- ADR-LOTTO-005 文件并发写入安全
- ADR-LOTTO-006 API Token安全管理
- BC-11a 符号链接绕过边界条件
- 部署拓扑图同步实际状态

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
2026-07-03 20:47:37 +08:00
parent a6f473f836
commit efbbf6265c
+136 -33
View File
@@ -3,10 +3,11 @@
| 字段 | 值 |
|------|-----|
| 文档编号 | ADR-LOTTO-001 |
| 版本 | v1.0 |
| 版本 | v2.0 |
| 创建日期 | 2026-07-03 |
| 更新日期 | 2026-07-03 |
| 架构师 | 梁思筑(Serena |
| 评审状态 | 待评审 |
| 评审状态 | v1.0 评审通过,v2.0 迭代后待复审 |
| 关联 PRD | PRD-双色球 WebUI-v1.0(沈路明) |
| 关联 Issue | BIZ-74 / 父任务 BIZ-68 |
@@ -168,7 +169,7 @@ graph TD
**路径**: `/home/vincent/Studio/lottoData/app.py`
**职责**: 提供 Web UI 和 REST API,系统唯一入口
**规模**: ~490
**规模**: ~728
**依赖**: Flask, lottery.py, pandas, openpyxl, numpy
#### 4.1.1 模块配置
@@ -195,6 +196,7 @@ CONFIG = {
| `save_records(records)` | `list[dict]` | 无 | JSON 文件 | 保存生成记录 |
| `add_record(strategy, num_tickets, filename)` | str, int, str | `dict` | load/save | 添加一条记录 |
| `require_auth(f)` | Flask route | decorator | CONFIG | API 认证装饰器 |
| `safe_join(base, path)` | str, str | str\|None | os.path | **v2.0 新增**realpath + commonpath 路径安全检查 |
| `api_generate()` | POST body | JSON | lottery.py | **核心**:生成号码 |
| `get_statistics_data()` | 无 | `dict` | pandas,numpy | 计算统计数据 |
| `api_statistics()` | 无 | JSON | get_statistics_data | GET 统计接口 |
@@ -275,24 +277,27 @@ CONFIG = {
4. search 非空 → 全列字符串匹配过滤
5. 分页切片 → JSON
⚠️ 已知问题:
⚠️ 已知问题v2.0 已优化):
- 每次请求都重新读取 Excel,无缓存(120 条可接受)
- 列名硬编码,Excel 格式变化需同步修改
- 搜索全列匹配可能误匹配
- ~~列名硬编码,Excel 格式变化需同步修改~~ → v2.0 抽取为配置常量 `EXCEL_COLUMNS`
- ~~搜索全列匹配可能误匹配~~ → v2.0 增加按「期号」精确查找选项
```
#### 4.1.6 核心接口:api_download(filepath)
```
安全检查:
安全检查v2.0 已加固):
1. os.path.normpath(filepath) 规范化
2. 拒绝以 ../ 开头 → 403
3. 拒绝以 / 开头 → 403
4. 拼接 BASE_DIR + filepath,检查文件存在
4. os.path.realpath() 解析符号链接 → 确保最终路径在 BASE_DIR 内
5. os.path.commonpath([real_path, base_real]) 校验前缀
6. 拼接 BASE_DIR + filepath,检查文件存在
⚠️ 安全建议costcodev 实施时加固):
- 增加 os.path.realpath() 确保最终路径在 BASE_DIR 内
- 当前 normpath 检查可被符号链接绕过
✅ v1.0 评审意见costcodev 徐聪指出):
- realpath 检查已纳入 v2.0 设计
- commonpath 二次校验确保防止符号链接绕过
- 此项为 P0 改进,costcodev 实施时已完成
```
### 4.2 lottery.py — 号码生成核心
@@ -482,6 +487,16 @@ web_executor.py (端口 5000) → 手动触发抓取
两个独立 Flask 服务,共享数据文件但端口不同。
⚠️ 架构建议:长期应合并为单一服务,通过路由区分功能。
**v2.0 部署状态更新**opengineer 严维序反馈):
- lotto-web.service 已 inactive,建议清理:
```bash
sudo systemctl disable lotto-web.service
sudo rm /etc/systemd/system/lotto-web.service
sudo systemctl daemon-reload
```
- web_executor.py + web_console.html 如不再使用建议删除
- 如保留需单独 systemd 管理,避免裸进程运行
```
---
@@ -857,9 +872,10 @@ web_executor.py ──状态──→ .fetch_status.json
| BC-07 | api_history | page=0 | 返回第一页数据 |
| BC-08 | api_history | page 超过总页数 | 返回空数组 |
| BC-09 | api_history | search 无匹配 | 返回空数组 |
| BC-10 | api_history | Excel 红球字符串 <12 字符 | 跳过/返回空红球数组 |
| BC-10 | api_history | Excel 红球字符串 <12 字符 | 跳过/返回空红球数组;v2.0 增加跳过量监控告警(R-11) |
| BC-11 | api_download | filepath 含 `../` | 返回 403 |
| BC-12 | api_download | filepath 绝对路径 | 返回 403 |
| BC-11a | api_download | 符号链接绕过(v2.0 新增) | realpath + commonpath 二次校验 → 403 |
| BC-13 | api_download | 文件不存在 | 返回 404 |
| BC-14 | api_records | 删除不存在的记录 | 返回 404 |
| BC-15 | fetch_data | HTTP 超时 | 返回 None,日志记录 |
@@ -890,7 +906,9 @@ web_executor.py ──状态──→ .fetch_status.json
数据抓取容错:
- HTTP 超时 30s → 失败不重试,次日 Cron 再试
- 解析失败 → 记录日志,保留上次数据
- ⚠️ 建议:增加失败重试机制(3次,间隔 60s
- ✅ v2.0 已确认:fetch_data.py 已有 MAX_RETRIES=3 + 重试间隔机制(costcodev 评审发现
- ⚠️ shell 层面(fetch_daily.sh)仍为单次执行,建议增加 shell 级重试(3次,间隔60s)
- ⚠️ v2.0 新增:Cron 失败需接入飞书通知,避免静默失败(R-10)
号码生成容错:
- 单注失败 → 跳过,继续生成下一注
@@ -901,6 +919,9 @@ web_executor.py ──状态──→ .fetch_status.json
- systemd Restart=on-failure, RestartSec=5
- threaded=True 处理并发(但无连接数限制)
- ⚠️ 建议:增加 Flask -*- errorhandler 统一异常处理
- v2.0 新增:threaded=True 无连接数限制,建议增加 max_worker 或使用 --threads 参数(R-13
- v2.0 新增:.generation_records.json 并发写入需加 fcntl.flock 文件锁
- v2.0 新增:覆盖写入前先写 .tmp 再 rename,避免极端场景数据损坏(沈路明建议)
```
## 9. 编码规范与技术栈约束
@@ -963,31 +984,45 @@ web_executor.py ──状态──→ .fetch_status.json
- ❌ 不引入 Docker(内网单服务,systemd 足够)
- ❌ 不使用全局可变状态(除 web_executor 的状态锁)
- ❌ 不在代码中硬编码绝对路径(使用 `os.path.dirname(__file__)`
- ❌ 不在代码中硬编码 API Token(v2.0:迁移至环境变量)
- ❌ 不在无文件锁的情况下并发写入 JSON 文件(v2.0:需 fcntl.flock
## 10. 部署架构
### 10.1 部署拓扑
**v2.0 更新**:根据严维序(opengineer)评审反馈,部署拓扑已同步实际状态。
```
┌─────────────────────────────────────────────┐
│ Ubuntu Server (192.168.1.99) │
│ │
│ ┌───────────────────────────────────────┐ │
│ │ systemd: lotto-web.service │ │
│ │ ExecStart: python3 app.py │ │
│ │ systemd: lotto-app.service ✅ active │ │
│ │ ExecStart: venv/bin/python3 app.py │ │
│ │ 监听: 0.0.0.0:8085 │ │
│ │ Restart: on-failure, RestartSec=5 │ │
│ └───────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────┐ │
│ │ systemd: lotto-web.service ⚠️ inactive│ │
│ │ 建议: disable + delete │ │
│ └───────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────┐ │
│ │ Crontab (vincent) │ │
│ │ 30 2 * * * deploy/fetch_daily.sh │ │
│ │ → python3 fetch_data.py │ │
│ └───────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────┐ │
│ │ backup.sh (每日 3:00) ✅ 已部署 │ │
│ │ → 备份 xlsx 到 backup/ 目录(保留30天) │ │
│ └───────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────┐ │
│ │ 项目目录: /home/vincent/Studio/lottoData│ │
│ │ Python venv: ./venv/ │ │
│ │ Python venv: ./venv/ (统一) │ │
│ │ 数据文件: ./双色球历史数据.xlsx │ │
│ │ 生成结果: ./lottery/*.xlsx │ │
│ │ 记录索引: ./.generation_records.json │ │
@@ -1018,6 +1053,12 @@ crontab -e
**注意**: 当前 `lotto-web.service` 运行的是 `web_executor.py`(端口 5000),需要更新为 `app.py`(端口 8085:
**v2.0 更新**(根据 opengineer 严维序反馈):
- 服务名已更新为 `lotto-app.service`(实际已部署并 active
-`lotto-web.service` 已 inactive,建议删除
- ExecStartPre 需补充 numpy 检查
- 统一使用 `venv/`,删除 `.venv/`
```ini
[Unit]
Description=双色球 Web UI 服务
@@ -1028,7 +1069,7 @@ Type=simple
User=vincent
WorkingDirectory=/home/vincent/Studio/lottoData
ExecStart=/home/vincent/Studio/lottoData/venv/bin/python3 /home/vincent/Studio/lottoData/app.py
ExecStartPre=/home/vincent/Studio/lottoData/venv/bin/python3 -c "import flask; import pandas; import openpyxl; import numpy"
ExecStartPre=/home/vincent/Studio/lottoData/venv/bin/python3 -c "import flask; import pandas; import openpyxl; import numpy; import requests; import bs4"
Restart=on-failure
RestartSec=5
KillMode=control-group
@@ -1050,8 +1091,9 @@ python3 -m venv venv
| 服务 | 端口 | 状态 | 说明 |
|------|------|------|------|
| app.py (Web UI) | 8085 | 需配置 systemd | 主服务 |
| web_executor.py | 5000 | 已部署 | 数据抓取控制台(可合并) |
| Cron | N/A | 已配置 | 每日 2:30 抓取 |
| app.py (Web UI) | 8085 | ✅ 已部署 systemd (lotto-app.service) | 主服务 |
| web_executor.py | 5000 | ⚠️ inactive,建议清理 | 数据抓取控制台(建议删除或合并) |
| Cron | N/A | ✅ 已配置 | 每日 2:30 抓取 |
---
@@ -1126,13 +1168,50 @@ done
| 编号 | 风险 | 影响 | 概率 | 应对措施 | 负责人 |
|------|------|------|:----:|----------|--------|
| R-01 | 55128.cn 改版/下线 | 数据源不可用 | 低 | 寻找备用数据源(中彩网) | 架构师 |
| R-02 | Excel 文件损坏 | 无法生成号码 | 低 | 每周备份 xlsx 文件 | 运维 |
| R-02 | Excel 文件损坏 | 无法生成号码 | 低 | 每日 3:00 自动备份(backup.sh 已部署) | 运维 |
| R-03 | 端口 8085 冲突 | 服务无法启动 | 中 | 修改 CONFIG['port'] | 运维 |
| R-04 | 55128.cn 反爬限制 | 抓取频率受限 | 低 | 降低频率(每周),增加 User-Agent 轮换 | 开发 |
| R-05 | 单进程性能瓶颈 | 高并发卡顿 | 低 | 内网≤10人,可接受 | 架构师 |
| R-06 | 文件锁竞争 | 抓取+生成同时写 | 低 | 时间错开(抓取2:30,生成按需) | 架构师 |
| R-07 | 数据安全(无备份) | 丢失生成记录 | 中 | 每周 Git 提交 lottery/ 目录 | 运维 |
| R-08 | 目录遍历攻击 | 任意文件读取 | 中 | 加 realpath 检查 (costcodev 实施) | 开发 |
| R-08 | 目录遍历攻击 | 任意文件读取 | 中 | ✅ v2.0 已加固:realpath + commonpath 检查 | 开发 |
| R-09 | 运维单点依赖(严维序是唯一 ops) | P0 整改无人执行 | 中 | 安排徐聪作为备用运维联系人 | COO |
| R-10 | 数据抓取静默失败 | 次日才发现数据未更新 | 中 | P3 增加 Cron 失败飞书通知 | 运维 |
| R-11 | Excel 格式漂移(55128.cn 改版) | 红球列解析规则失效 | 中 | BC-10 跳过异常行 + 监控跳过量 | 开发 |
| R-12 | 红球列解析依赖固定偏移量 | 55128.cn 改变数据格式 → 号码生成失效 | 中 | BC-10 已定义跳过,需加监控告警 | 开发 |
| R-13 | threaded=True 无连接数限制 | 局域网多人同时生成 → OOM | 低 | 建议 max_worker 或 --threads 参数 | 开发 |
---
## 12.1 v2.0 评审意见响应矩阵
| # | 评审人 | 评审意见 | 响应 | 状态 |
|---|--------|----------|------|:----:|
| 1 | 沈路明 (PM) | 前端搜索支持按「期号」精确查找 | 纳入 P2 改进项 | 📋 |
| 2 | 沈路明 (PM) | 生成记录增加「策略类型」筛选 | 纳入 P2 改进项 | 📋 |
| 3 | 沈路明 (PM) | 统计页增加历史趋势可视化 | 纳入 P3 改进项 | 📋 |
| 4 | 沈路明 (PM) | 覆盖写入先写 .tmp 再 rename | 纳入 P3 改进项 | 📋 |
| 5 | 陆怀瑾 (COO) | R-09 运维单点依赖风险 | 新增 R-09,建议徐聪作为备用运维 | 📋 |
| 6 | 陆怀瑾 (COO) | R-10 数据抓取静默失败告警 | 新增 R-10,P3 接入飞书通知 | 📋 |
| 7 | 陆怀瑾 (COO) | R-11 Excel 格式漂移监控 | 新增 R-11,BC-10 增加跳过量监控 | 📋 |
| 8 | 徐聪 (Dev) | realpath 后需 commonpath 校验 | 已纳入 v2.0 安全检查设计 | ✅ |
| 9 | 徐聪 (Dev) | api_history 列名硬编码 | 纳入 P2,抽取为配置常量 | 📋 |
| 10 | 徐聪 (Dev) | .generation_records.json 无文件锁 | 纳入 P2,增加 fcntl.flock | 📋 |
| 11 | 徐聪 (Dev) | lottery/ 目录无清理机制 | 纳入 P3,自动清理策略 | 📋 |
| 12 | 徐聪 (Dev) | CONFIG api_token 明文 | 纳入 P3,迁移至环境变量 | 📋 |
| 13 | 徐聪 (Dev) | app.py 实际 728 行非 490 行 | 已修正 | ✅ |
| 14 | 徐聪 (Dev) | fetch_data.py 已有重试机制 | 已确认,排期降级 | ✅ |
| 15 | 徐聪 (Dev) | web_executor 已有 timeout=300s | 已确认,建议降到 120s | ✅ |
| 16 | 徐聪 (Dev) | R-12 红球列解析依赖固定偏移量 | 新增 R-12 | 📋 |
| 17 | 徐聪 (Dev) | R-13 threaded=True 无连接数限制 | 新增 R-13 | 📋 |
| 18 | 严维序 (OPS) | 旧 lotto-web.service 未删除 | 纳入 P0 清理项 | 📋 |
| 19 | 严维序 (OPS) | 双虚拟环境一致性 | 纳入 P0,统一为 venv/ | 📋 |
| 20 | 严维序 (OPS) | web_executor.py 服务处理 | 文档已补充决策建议 | ✅ |
| 21 | 严维序 (OPS) | 日志轮转缺失 | 纳入 P1,配置 logrotate | 📋 |
| 22 | 严维序 (OPS) | 服务健康监控 | 纳入 P1,定时 curl + 心跳巡检 | 📋 |
| 23 | 严维序 (OPS) | 部署文档同步 | 纳入 P3,已更新文档中 | ✅ |
| 24 | 严维序 (OPS) | ExecStartPre 补充 numpy 检查 | 已更新 systemd 服务文件 | ✅ |
| 25 | 严维序 (OPS) | 数据备份已部署 backup.sh | 已确认并更新排期 | ✅ |
---
@@ -1145,17 +1224,30 @@ done
| 阶段 | 内容 | 负责人 | 工时 | 依赖 |
|------|------|--------|------|------|
| P0 | 架构文档评审 | 全员 | 1h | 本文档 |
| P0 | 路径安全加固(realpath | costcodev | 0.5h | 架构评审通过 |
| P0 | systemd 服务更新为 app.py | opengineer | 0.5h | 架构评审通过 |
| P0 | 路径安全加固(realpath + commonpath | costcodev | ✅ 已完成 | 架构评审通过 |
| P0 | systemd 服务更新为 app.py (lotto-app.service) | opengineer | ✅ 已完成 | 架构评审通过 |
| P0 | 旧 lotto-web.service 清理 | opengineer | 待执行 | — |
| P0 | 统一虚拟环境(删除 .venv/ | opengineer | 待执行 | — |
| P1 | Flask errorhandler 统一异常 | costcodev | 1h | P0 |
| P1 | fetch_data 失败重试机制 | costcodev | 1h | P0 |
| P1 | subprocess 超时保护 | costcodev | 0.5h | P0 |
| P1 | ~~fetch_data 失败重试机制~~ | costcodev | ✅ 已有 | |
| P1 | subprocess 超时保护(建议 300s→120s | costcodev | 0.5h | P0 |
| P1 | 日志轮转配置(logrotate | opengineer | 0.5h | P0 |
| P1 | 服务健康监控接入 | opengineer | 1h | P0 |
| P1 | 数据文件每日备份(backup.sh | opengineer | ✅ 已部署 | — |
| P2 | 移动端真机兼容性测试 | QA | 2h | P1 |
| P2 | 性能压测(10并发) | QA | 1h | P1 |
| P3 | 前端日志埋点 | costcodev | 2h | P2 |
| P3 | monitoring 告警接入 | opengineer | 2h | P2 |
| P2 | api_history 列名抽取为配置常量 | costcodev | 0.5h | P1 |
| P2 | .generation_records.json 加 fcntl.flock | costcodev | 0.5h | P1 |
| P2 | 前端搜索支持按「期号」精确查找 | costcodev | 0.5h | P1 |
| P2 | 生成记录增加「策略类型」筛选 | costcodev | 0.5h | P1 |
| P3 | statistics 页增加历史趋势可视化 | costcodev | 2h | P2 |
| P3 | lottery/ 目录自动清理策略 | costcodev | 1h | P2 |
| P3 | api_token 迁移至环境变量 | costcodev | 0.5h | P2 |
| P3 | monitoring 告警接入(Cron 失败飞书通知) | opengineer | 1h | P2 |
| P3 | 覆盖写入先写 .tmp 再 rename | costcodev | 0.5h | P2 |
| P3 | 部署文档同步(文档与实际状态对齐) | architect | 0.5h | P2 |
**总工时**: ~10.5h(不含架构评审和验收
**总工时**: ~13h(含新增 P2/P3 项,不含已完成项
### 13.2 关键里程碑
@@ -1173,14 +1265,14 @@ done
| 文件 | 大小 | 行数 | 职责 | 修改建议 |
|------|------|------|------|----------|
| app.py | 17KB | ~490 | Flask Web 主服务 | 路径安全加固 |
| app.py | 17KB | ~728 | Flask Web 主服务 | 路径安全加固已完成 |
| index.html | 42KB | ~1170 | 响应式前端 UI | 无 |
| lottery.py | 51KB | ~1100 | 号码生成核心逻辑 | 无 |
| fetch_data.py | 3.8KB | ~130 | 历史数据抓取 | 加重试机制 |
| web_executor.py | 6.4KB | ~220 | 数据抓取 Web 控制台 | 加 subprocess 超时 |
| fetch_data.py | 3.8KB | ~130 | 历史数据抓取 | ✅ 已有 MAX_RETRIES=3 重试 |
| web_executor.py | 6.4KB | ~220 | 数据抓取 Web 控制台 | 已有 timeout=300s,建议降到 120s |
| web_console.html | 11KB | ~300 | 抓取控制台前端 | 无 |
| deploy/fetch_daily.sh | 641B | ~15 | Cron 抓取脚本 | 加重试逻辑 |
| deploy/lotto-web.service | 494B | ~15 | systemd 服务文件 | 更新为 app.py |
| deploy/lotto-app.service | 494B | ~15 | systemd 服务文件 | ✅ 已部署并 active |
## 附录 B: 依赖包清单
@@ -1216,10 +1308,21 @@ done
- **Context**: fetch_data 每次全量抓取 120 条更新 Excel
- **Decision**: 覆盖写入而非增量追加
- **Consequences**: 覆盖瞬间服务数据不可用(毫秒级,可接受)
- **v2.0 增强**: 覆盖写入前先写 .tmp 文件再 rename,避免极端场景数据损坏(沈路明建议,P3 实施)
### ADR-LOTTO-005: 文件并发写入安全
- **Context**: threaded=True 下 .generation_records.json 并发写入可能丢数据
- **Decision**: 增加 fcntl.flock 文件锁保护并发写入
- **Consequences**: 零额外依赖,写入串行化;极端并发下写入有微秒级等待
### ADR-LOTTO-006: API Token 安全管理
- **Context**: CONFIG 中 api_token 明文硬编码
- **Decision**: 迁移至环境变量 LOTTO_API_TOKEN
- **Consequences**: 配置更灵活,避免代码泄露 Token
---
**文档作者**: 梁思筑(Serena
**提交至**: http://192.168.1.99:12299/vincent/EnterpriseArchitect.git
**路径**: `docs/architecture/Lotto-Architecture-v1.md`
**评审请求**: 请产研团队(沈路明、徐聪、严维序)评审本架构文档
**评审请求**: v2.0 已根据 v1.0 评审意见迭代,请产研团队复审