Files
EnterpriseArchitect/docs/architecture/Lotto-Architecture-v1.md
T
vincent efbbf6265c 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>
2026-07-03 20:47:37 +08:00

1329 lines
52 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.
# 双色球自动化系统 — 架构设计文档
| 字段 | 值 |
|------|-----|
| 文档编号 | ADR-LOTTO-001 |
| 版本 | v2.0 |
| 创建日期 | 2026-07-03 |
| 更新日期 | 2026-07-03 |
| 架构师 | 梁思筑(Serena |
| 评审状态 | v1.0 评审通过,v2.0 迭代后待复审 |
| 关联 PRD | PRD-双色球 WebUI-v1.0(沈路明) |
| 关联 Issue | BIZ-74 / 父任务 BIZ-68 |
---
## 1. 系统概述
### 1.1 业务定位
双色球自动化系统是一套面向内部用户的福利彩票双色球辅助工具,核心功能包括:
- **数据采集**:每日自动从 55128.cn 抓取双色球历史开奖数据
- **号码生成**:基于历史数据分析(热冷号、奇偶比、大小比、和值、跨度)+ 随机策略生成推荐号码
- **Web 展示**:提供响应式 Web UI,支持 PC + 移动端访问,局域网内可多人同时使用
### 1.2 用户场景
| 场景 | 角色 | 触发方式 | 预期结果 |
|------|------|----------|----------|
| 生成号码 | 刘总/团队成员 | Web UI 点击「生成」 | 3s 内返回号码列表 + 统计指标 |
| 查看历史 | 刘总/团队成员 | Web UI 切换 Tab | 分页查看历史开奖数据 |
| 下载结果 | 刘总/团队成员 | Web UI 点击「下载」 | 获取 Excel 文件 |
| 自动抓取 | 系统 | Cron 定时触发 | 凌晨 2:30 自动更新历史数据 |
### 1.3 系统边界
- 数据来源:55128.cn(外部网站,仅读取)
- 用户终端:PC/移动端浏览器(内网访问)
- 数据存储:本地 Excel + JSON 文件
- 服务进程:Flask 单进程 + threaded=True
- 部署环境:Ubuntu Server (192.168.1.99)
## 2. 系统架构图
### 2.1 系统拓扑
```
┌────────────────────────────────────────────────────┐
│ 用户浏览器 │
│ (PC / 移动端 / 微信内置) │
│ http://192.168.1.99:8085 │
└───────────────────────┬────────────────────────────┘
│ HTTP (内网)
┌────────────────────────────────────────────────────┐
│ Flask Web 服务 (app.py) │
│ 监听 0.0.0.0:8085 │
│ ┌──────────┬──────────┬──────────┬──────────────┐ │
│ │ /api/ │ /api/ │ /api/ │ /api/ │ │
│ │ generate │ history │ records │ statistics │ │
│ ├──────────┼──────────┼──────────┼──────────────┤ │
│ │ /api/ │ /api/ │ /api/ │ / │ │
│ │ download │ status │ config │ (index.html) │ │
│ └──────────┴──────────┴──────────┴──────────────┘ │
└───────┬────────────────────┬───────────────────────┘
│ │
▼ ▼
┌───────────────┐ ┌──────────────────┐
│ lottery.py │ │ fetch_data.py │
│ 号码生成核心 │ │ 数据抓取脚本 │
│ (NumPy+Pandas)│ │ (requests+BS4) │
└───────┬───────┘ └────────┬─────────┘
│ │
▼ ▼
┌──────────────────────────────────────┐
│ 本地文件系统 │
│ ┌─────────────┬──────────────────┐ │
│ │双色球历史数据 │.generation_ │ │
│ │ .xlsx │records.json │ │
│ └─────────────┴──────────────────┘ │
│ ┌──────────────────────────────────┐│
│ │ lottery/ (生成结果 Excel 目录) ││
│ └──────────────────────────────────┘│
└──────────────────────────────────────┘
```
### 2.2 数据流
```
数据抓取流:
55128.cn → HTTP GET → fetch_data.py → BeautifulSoup 解析 → Excel 写入
号码生成流:
双色球历史数据.xlsx → lottery.py → 统计分析 → 策略调整 → Excel 输出 → app.py JSON 返回
前端数据流:
index.html → fetch() → app.py API → JSON → DOM 渲染
记录管理流:
生成 → add_record() → .generation_records.json
删除 → remove file + update JSON
```
### 2.3 模块依赖图
```mermaid
graph TD
A[index.html<br/>前端 UI] -->|HTTP API| B[app.py<br/>Flask 后端]
B -->|import| C[lottery.py<br/>号码生成器]
B -->|read/write| D[.generation_records.json]
B -->|read| E[双色球历史数据.xlsx]
C -->|read| E
C -->|write| F[lottery/*.xlsx]
B -->|send_file| F
G[fetch_data.py<br/>数据抓取] -->|HTTP GET| H[55128.cn]
G -->|write| E
I[web_executor.py<br/>抓取控制台] -->|subprocess| G
K[Cron<br/>fetch_daily.sh] -->|exec| G
L[web_console.html] -->|HTTP API| I
```
## 3. 技术选型对比
### 3.1 后端框架
| 维度 | Flask (当前选择) | FastAPI | Django |
|------|:-:|:-:|:-:|
| 学习成本 | ★★★★★ 团队熟悉 | ★★★★ 需学异步 | ★★★ 概念重 |
| 异步支持 | ★★ 需 threaded | ★★★★★ 原生 async | ★★ 有限 |
| 依赖体积 | ★★★★★ 轻量 | ★★★★ 较轻 | ★★ 笨重 |
| 适合度 | ✅ 项目规模小、路由少 | 未来升级 | 过度设计 |
**决策**:维持 Flask。现有代码已基于 Flask 开发且运行稳定;项目路由仅 8 个,无异步需求。
### 3.2 前端方案
| 维度 | 原生 HTML/CSS/JS (当前) | Vue 3 | React |
|------|:-:|:-:|:-:|
| 构建工具 | 无需 | 需 Vite | 需 Vite |
| 交互复杂度 | ★★ 满足当前 | ★★★★★ | ★★★★★ |
| 包体积 | 0 KB | ~30KB(gz) | ~40KB(gz) |
| 适合度 | ✅ 4 Tab + 表单 | 未来增强 | 过度设计 |
**决策**:维持原生 HTML/CSS/JS。当前 UI 仅 4 个 Tab + 少量表单交互,引入框架不合理。
### 3.3 数据存储方案
| 维度 | Excel + JSON (当前) | SQLite | MySQL |
|------|:-:|:-:|:-:|
| 运维成本 | ★★★★★ 零运维 | ★★★★ 嵌入式 | ★★★ 独立部署 |
| 并发读写 | ★ 文件锁 | ★★★★ | ★★★★★ |
| 数据量需求 | <1MB | <1GB | 任意 |
| 适合度 | ✅ 120条/12KB | 可选升级 | 过度设计 |
**决策**:维持 Excel + JSON。数据量极小(120 条记录、12KB),无并发写入场景。若未来 >10,000 条迁移 SQLite。
### 3.4 部署方式
| 维度 | systemd + Cron (当前) | Docker Compose | 裸进程 |
|------|:-:|:-:|:-:|
| 隔离性 | ★★ 系统级 | ★★★★★ 容器 | ★ 无 |
| 开机自启 | ★★★★★ systemd | ★★★★ restart:always | ★★ 手动 |
| 适合度 | ✅ 内网单服务 | 未来扩展 | 不推荐 |
**决策**:维持 systemd + Cron。内网单服务,引入 Docker 增加不必要复杂度。
## 4. 模块详细设计(单文件/单函数级)
### 4.1 app.py — Flask Web 主服务
**路径**: `/home/vincent/Studio/lottoData/app.py`
**职责**: 提供 Web UI 和 REST API,系统唯一入口
**规模**: ~728 行
**依赖**: Flask, lottery.py, pandas, openpyxl, numpy
#### 4.1.1 模块配置
```python
CONFIG = {
'host': '0.0.0.0', # 监听地址(局域网可访问)
'port': 8085, # 端口
'history_file': '双色球历史数据.xlsx',
'lottery_output_dir': 'lottery/',
'records_file': '.generation_records.json',
'api_token': 'lotto2026', # auth_enabled=True 时生效
'auth_enabled': False, # 认证开关
'max_tickets': 1000, # 单次生成上限
'default_tickets': 10, # 默认注数
}
```
#### 4.1.2 函数清单
| 函数 | 输入 | 输出 | 依赖 | 说明 |
|------|------|------|------|------|
| `load_records()` | 无 | `list[dict]` | JSON 文件 | 加载生成记录 |
| `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 统计接口 |
| `api_records()` | query: page,page_size | JSON | load_records | 分页获取记录 |
| `api_delete_record(record_id)` | URL param | JSON | load/save_records | 删除记录+文件 |
| `api_download(filepath)` | URL param | File | send_file | 下载 Excel |
| `api_history()` | query: page,search | JSON | pandas | 分页历史数据 |
| `api_status()` | 无 | JSON | os | 系统状态 |
| `api_config()` | 无 | JSON | CONFIG | 前端配置 |
| `index()` | 无 | HTML | send_from_directory | 返回 index.html |
#### 4.1.3 核心接口:api_generate()
```
输入: POST /api/generate
Body: {"num_tickets": int (1-1000), "strategy": "advanced"|"basic"}
处理流程:
1. 解析并校验请求参数(num_tickets 1-1000strategy advanced/basic
2. 实例化 DoubleColorBallGenerator(history_file)
3. generator.load_history_data() 加载历史数据
4. generator.generate_multiple_tickets(num_tickets, strategy) 生成号码
5. generator.save_to_excel() 保存结果文件
6. add_record() 记录生成历史
7. get_statistics_data() 获取统计信息
8. 构建 JSON 响应(前端最多展示 50 注)
输出: {
success: true,
data: {
tickets: [{index, reds[6], blue, sum_value, odd_even, size_ratio, span}],
total: int, filename: str, download_url: str, record: dict, statistics: dict
}
}
边界条件:
- num_tickets < 1 或 > 1000 → 400
- strategy 非 advanced/basic → 400
- 历史数据文件不存在 → 500
- load_history_data() 返回 False → 500
- tickets_df.empty → 500
- save_to_excel 返回 None → 500
```
#### 4.1.4 核心接口:get_statistics_data()
```
处理流程:
1. 读取 Excelheader=None, iloc[1:] 跳过描述行)
2. 解析红球(6个)+蓝球(1个)从拼接字符串(每2字符=1球)
3. Counter 统计频次 → 排序取热冷号
4. 统计奇偶比/大小比频次
5. numpy 计算和值/跨度的 min/max/mean/std
输出: {
hot_reds: [int...15], cold_reds: [int...15],
hot_blues: [int...8],
common_odd_even: "3:3", common_size_ratio: "4:2",
sum_range: {min, max, mean, std},
span_range: {min, max, mean, std},
history_count: int
}
边界条件:
- Excel 不存在 → 返回 {}
- 数据行为空 → 返回 {}
- 号码字符串长度 <14 → 跳过该行
- 号码超出范围 → 跳过
```
#### 4.1.5 核心接口:api_history()
```
处理流程:
1. 读取 Excelheader=None, iloc[1:]
2. 硬编码列名: ['开奖日期','期号','红球','开机号','和值特征','奇偶形态','大小比','奇偶形态2','跨度','其他']
3. 解析红球列表(前12字符每2位) + 蓝球(第13-14位)
4. search 非空 → 全列字符串匹配过滤
5. 分页切片 → JSON
⚠️ 已知问题(v2.0 已优化):
- 每次请求都重新读取 Excel,无缓存(120 条可接受)
- ~~列名硬编码,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. os.path.realpath() 解析符号链接 → 确保最终路径在 BASE_DIR 内
5. os.path.commonpath([real_path, base_real]) 校验前缀
6. 拼接 BASE_DIR + filepath,检查文件存在
✅ v1.0 评审意见(costcodev 徐聪指出):
- realpath 检查已纳入 v2.0 设计
- commonpath 二次校验确保防止符号链接绕过
- 此项为 P0 改进,costcodev 实施时已完成
```
### 4.2 lottery.py — 号码生成核心
**路径**: `/home/vincent/Studio/lottoData/lottery.py`
**职责**: 基于历史数据分析 + 随机策略生成双色球号码
**规模**: ~1100 行
**依赖**: pandas, numpy, openpyxl, datetime, collections.Counter, re
#### 4.2.1 类定义:DoubleColorBallGenerator
```python
class DoubleColorBallGenerator:
def __init__(self, history_file="双色球历史数据.xlsx", config=None):
# config 默认值:
# hot_red_count=15, cold_red_count=10, hot_blue_count=8
# hot_blue_probability=0.7, max_adjustment_attempts=20
```
#### 4.2.2 方法清单
| 方法 | 输入 | 输出 | 说明 |
|------|------|------|------|
| `__init__(history_file, config)` | str, dict | None | 初始化配置 |
| `load_history_data()` | 无 | bool | 加载 Excel + 统计计算 |
| `_calculate_statistics()` | 无 | None | 计算热冷号/奇偶/大小/和值/跨度 |
| `get_hot_red_balls(n=10)` | int | `list[int]` | 红球热号 TOP n |
| `get_cold_red_balls(n=10)` | int | `list[int]` | 红球冷号 TOP n |
| `get_hot_blue_balls(n=5)` | int | `list[int]` | 蓝球热号 TOP n |
| `parse_ratio(ratio_str)` | str | tuple | 解析比例字符串 |
| `_select_hot_cold_balls()` | 无 | tuple | 选择热冷号组合 |
| `_adjust_odd_even_ratio(red_balls)` | list | list | 调整奇偶比至 2:4~4:2 |
| `_adjust_size_ratio(red_balls)` | list | list | 调整大小比至 2:4~4:2 |
| `_adjust_sum_range(red_balls)` | list | list | 调整和值至 [90,120] |
| `_adjust_span_range(red_balls)` | list | list | 调整跨度至 [22,30] |
| `_select_blue_ball()` | 无 | int | 选蓝球(热号概率0.7 |
| `generate_single_ticket_advanced()` | 无 | list[6]+int | 高级策略生成单注 |
| `generate_single_ticket_basic()` | 无 | list[6]+int | 基础策略生成单注 |
| `generate_multiple_tickets(n, strategy)` | int, str | DataFrame | 批量生成号码 |
| `save_to_excel(df, n, strategy)` | df, int, str | str\|None | 保存为 Excel |
| `display_statistics()` | 无 | None | 控制台输出统计 |
| `run_tests()` | 无 | None | 内置测试 |
| `main()` | 无 | None | CLI 入口 |
#### 4.2.3 关键算法:generate_single_ticket_advanced()
```
高级策略生成流程:
1. _select_hot_cold_balls() → 从热号选3-4个 + 冷号选2-3个 = 6个
2. 排序红球
3. _adjust_odd_even_ratio() → 目标奇偶比 2:4~4:2,尝试上限20次
4. _adjust_size_ratio() → 目标大小比 2:4~4:2
5. _adjust_sum_range() → 目标和值 [90, 120]
6. _adjust_span_range() → 目标跨度 [22, 30]
7. 红球去重 + 范围校验 (1-33)
8. _select_blue_ball() → 70%热号 + 30%随机,范围1-16
9. 返回 reds[6] + blue
边界条件:
- 历史数据不足10条 → 警告但可生成
- 调整超 max_adjustment_attempts → 接受当前结果
- 红球不允许重复
```
#### 4.2.4 关键算法:generate_multiple_tickets()
```
输入: num_tickets (1-1000), strategy ("advanced"|"basic")
处理:
1. 循环 num_tickets 次
2. 每次调用 generate_single_ticket_advanced/basic()
3. 计算每注统计指标(和值、奇偶比、大小比、跨度)
4. 构建 pandas DataFrame
输出 DataFrame 列: 序号 | 红球1-6 | 蓝球 | 和值 | 奇偶比 | 大小比 | 跨度
边界条件:
- 单注生成失败 → 跳过并记日志
- 全部失败 → 返回空 DataFrame
- 1000注耗时预估 5-8s
```
#### 4.2.5 关键算法:save_to_excel()
```
输入: tickets_df, num_tickets, strategy
处理:
1. 文件名: 双色球_YYYYMMDD_HHMMSS_策略_N注.xlsx
2. 保存到 lottery/ 目录(不存在自动创建)
3. openpyxl 写入
输出: 文件完整路径 or None(失败)
```
---
### 4.3 fetch_data.py — 数据抓取脚本
**路径**: `/home/vincent/Studio/lottoData/fetch_data.py`
**职责**: 从 55128.cn 抓取双色球历史开奖数据,更新本地 Excel
**规模**: ~130 行
**依赖**: requests, beautifulsoup4, pandas, openpyxl
#### 4.3.1 函数清单
| 函数 | 输入 | 输出 | 说明 |
|------|------|------|------|
| `fetch_lottery_data()` | 无 | `list[list]`\|None | HTTP GET + BS4 解析表格 |
| `save_to_excel(data_rows)` | `list[list]` | bool | DataFrame → Excel 覆盖写入 |
| `main()` | 无 | None | CLI 入口 |
#### 4.3.2 fetch_lottery_data() 详细设计
```
处理流程:
1. requests.get(URL, headers=HEADERS, timeout=30)
2. BeautifulSoup(response.text, "html.parser")
3. soup.find("table") → 定位数据表格
4. 遍历 table.find_all("tr") → 解析每行
5. 提取:期号、日期、红球6个、蓝球、和值、奇偶比、大小比、跨度
6. 返回 data_rows
边界条件:
- HTTP 超时(30s) → 返回 None
- 非 200 → raise_for_status()
- 表格不存在 → 返回 None
- 行数据不完整 → 跳过
数据源说明:
- URL: https://www.55128.cn/kjh/fcssq-history-120.htm
- 内容:最近 120 期开奖数据
- 频率:每日 1 次(Cron 2:30
- 无需登录、无 API 限流
```
#### 4.3.3 save_to_excel() 详细设计
```
处理:
1. 构建 pandas DataFrame
2. 覆盖写入 双色球历史数据.xlsx
⚠️ 覆盖写入决策:
- data_rows 最多 120 行,数据量极小
- 覆盖过程中服务短暂数据不可用(毫秒级,可接受)
- 替代方案"增量追加"复杂度高、收益低
```
---
### 4.4 web_executor.py — 数据抓取 Web 控制台
**路径**: `/home/vincent/Studio/lottoData/web_executor.py`
**职责**: 提供 Web 界面手动触发数据抓取
**规模**: ~220 行
**依赖**: Flask, subprocess, threading
#### 4.4.1 函数清单
| 函数 | 输入 | 输出 | 说明 |
|------|------|------|------|
| `load_status()` | 无 | dict | 从 .fetch_status.json 加载 |
| `save_status()` | 无 | None | 保存状态到文件 |
| `index()` | 无 | HTML | 返回 web_console.html |
| `api_status()` | 无 | JSON | 获取抓取状态 |
| `api_execute()` | 无 | JSON | 异步触发 fetch_data.py |
| `check_dependencies()` | 无 | JSON | 检查依赖 |
#### 4.4.2 api_execute() 设计
```
处理流程:
1. 检查 is_running 状态锁
2. 已在运行 → 返回 409
3. 启动 threading.Thread → subprocess.Popen 执行 fetch_data.py
4. 更新全局 execution_status + .fetch_status.json
边界条件:
- 并发触发 → status_lock 保护,返回 409
- subprocess 退出码非 0 → 记录 last_error
- ⚠️ 缺少 subprocess 超时(建议加 timeout=120s
```
#### 4.4.3 与 app.py 的关系
```
app.py (端口 8085) → 号码生成/历史/记录/统计
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 管理,避免裸进程运行
```
---
### 4.5 index.html — 前端 UI
**路径**: `/home/vincent/Studio/lottoData/index.html`
**职责**: 响应式 Web UI4 个功能 Tab
**规模**: ~1170 行(HTML + CSS + JS 单文件)
**依赖**: 无外部 CDN
#### 4.5.1 页面结构
```
index.html
├── <style> (~300行)
│ ├── CSS 变量(主色 #e74c3c、蓝球 #3498db
│ ├── 响应式 @media 768px 断点
│ ├── 卡片/表格/球号样式
│ └── 移动端底部固定导航
├── <body>
│ ├── Header(标题+副标题,sticky
│ ├── Nav Tabs(生成|历史|记录|统计,sticky)
│ ├── Tab 1: 号码生成(统计概览+参数配置+生成按钮+结果区)
│ ├── Tab 2: 历史数据(搜索框+表格+分页)
│ ├── Tab 3: 生成记录(卡片列表+下载/删除+分页)
│ └── Tab 4: 统计(热冷号+奇偶/大小/和值/跨度)
└── <script> (~400行)
├── fetch() 调用 API
├── Tab 切换逻辑
├── 分页加载
├── 搜索防抖(500ms)
├── 结果渲染(DOM 操作)
└── 下载触发
```
#### 4.5.2 前端 API 调用清单
| 调用时机 | 方法 | 端点 | 用途 |
|----------|------|------|------|
| 页面加载 | GET | /api/config | 获取配置(max_tickets等) |
| 页面加载 | GET | /api/statistics | 统计概览 |
| 点击生成 | POST | /api/generate | 生成号码 |
| 切换历史 Tab | GET | /api/history?page=1 | 加载历史数据 |
| 搜索输入 | GET | /api/history?search=xxx | 搜索(500ms防抖) |
| 切换记录 Tab | GET | /api/records?page=1 | 加载生成记录 |
| 点击下载 | GET | /api/download/filepath | 下载 Excel |
| 点击删除 | DELETE | /api/records/:id | 删除记录 |
| 切换统计 Tab | GET | /api/statistics | 加载统计数据 |
## 5. 接口定义
### 5.1 API 接口清单
| # | 接口 | 方法 | 路径 | 认证 | 说明 |
|---|------|------|------|:----:|------|
| 1 | 生成号码 | POST | `/api/generate` | 可选 | 核心接口 |
| 2 | 历史数据 | GET | `/api/history` | 可选 | 分页+搜索 |
| 3 | 生成记录 | GET | `/api/records` | 可选 | 分页 |
| 4 | 删除记录 | DELETE | `/api/records/:id` | 可选 | 删记录+文件 |
| 5 | 统计数据 | GET | `/api/statistics` | 可选 | 热冷号+指标 |
| 6 | 下载文件 | GET | `/api/download/:path` | 可选 | Excel 下载 |
| 7 | 系统状态 | GET | `/api/status` | 无 | 健康检查 |
| 8 | 前端配置 | GET | `/api/config` | 无 | 前端初始化 |
| 9 | 首页 | GET | `/` | 无 | 返回 index.html |
### 5.2 接口详细定义
#### 5.2.1 POST /api/generate
**请求**:
```json
{
"num_tickets": 10,
"strategy": "advanced"
}
```
**参数说明**:
| 参数 | 类型 | 必填 | 范围 | 默认值 |
|------|------|:----:|------|--------|
| num_tickets | int | 否 | 1-1000 | 10 |
| strategy | str | 否 | "advanced"\|"basic" | "advanced" |
**成功响应** (200):
```json
{
"success": true,
"data": {
"tickets": [
{
"index": 1,
"reds": [3, 12, 18, 23, 27, 31],
"blue": 9,
"sum_value": 114,
"odd_even": "3:3",
"size_ratio": "4:2",
"span": 28
}
],
"total": 10,
"filename": "lottery/双色球_20260703_142530_高级策略_10注.xlsx",
"download_url": "/api/download/lottery/双色球_20260703_142530_高级策略_10注.xlsx",
"record": {
"id": "a1b2c3d4",
"created_at": "2026-07-03 14:25:30",
"strategy": "高级策略",
"num_tickets": 10,
"filename": "lottery/...",
"filesize": 12345
},
"statistics": {
"hot_reds": [1, 3, 7, ...],
"cold_reds": [2, 5, 8, ...],
"hot_blues": [9, 12, ...],
"common_odd_even": "3:3",
"common_size_ratio": "4:2",
"sum_range": {"min": 60, "max": 180, "mean": 105.5, "std": 25.3},
"span_range": {"min": 15, "max": 32, "mean": 25.8, "std": 3.2},
"history_count": 120
}
}
}
```
**错误响应**:
| 状态码 | 场景 | 响应 |
|--------|------|------|
| 400 | 参数校验失败 | `{"success": false, "error": "注数必须在 1-1000 之间"}` |
| 401 | 未授权 | `{"success": false, "error": "未授权访问"}` |
| 500 | 生成失败 | `{"success": false, "error": "生成失败: ..."}` |
#### 5.2.2 GET /api/history
**请求参数**:
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|:----:|--------|------|
| page | int | 否 | 1 | 页码 |
| page_size | int | 否 | 20 | 每页条数 |
| search | str | 否 | "" | 搜索关键词 |
**成功响应** (200):
```json
{
"success": true,
"data": {
"records": [
{
"开奖日期": "2026-07-02",
"期号": "2026072",
"红球": [3, 12, 18, 23, 27, 31],
"蓝球": 9,
"和值": "114",
"奇偶形态": "3:3",
"大小比": "4:2",
"跨度": "28"
}
],
"total": 120,
"page": 1,
"page_size": 20
}
}
```
#### 5.2.3 GET /api/records
**请求参数**: `page` (默认1), `page_size` (默认20)
**成功响应**:
```json
{
"success": true,
"data": {
"records": [
{
"id": "a1b2c3d4",
"created_at": "2026-07-03 14:25:30",
"strategy": "高级策略",
"num_tickets": 10,
"filename": "lottery/xxx.xlsx",
"filesize": 12345
}
],
"total": 25,
"page": 1,
"page_size": 20
}
}
```
#### 5.2.4 DELETE /api/records/:id
**成功响应**: `{"success": true, "message": "记录已删除"}`
**错误**: 404 记录不存在
#### 5.2.5 GET /api/download/:path
**成功**: Excel 文件附件下载 (Content-Disposition: attachment)
**错误**: 403 路径非法, 404 文件不存在
#### 5.2.6 GET /api/statistics
**成功响应**: 同 generate 中的 statistics 对象
#### 5.2.7 GET /api/status
**成功响应**:
```json
{
"success": true,
"data": {
"server_time": "2026-07-03 14:25:30",
"history_exists": true,
"history_size": 12808,
"total_generations": 25,
"total_lottery_files": 13,
"config": {"port": 8085, "auth_enabled": false, "max_tickets": 1000}
}
}
```
#### 5.2.8 GET /api/config
**成功响应**:
```json
{
"success": true,
"data": {"max_tickets": 1000, "default_tickets": 10, "auth_enabled": false}
}
```
## 6. 数据模型
### 6.1 文件存储模型
本系统不使用数据库,采用文件系统存储。数据模型通过文件结构描述。
### 6.2 双色球历史数据.xlsx
**文件格式**: Excel (.xlsx)
**数据行数**: ~120 条
**文件大小**: ~12KB
**更新方式**: 覆盖写入(fetch_data.py 每日全量更新)
| 列序号 | 列名 | 数据类型 | 示例 | 说明 |
|--------|------|----------|------|------|
| 0 | 开奖日期 | string | "2026-07-02" | 开奖日期 |
| 1 | 期号 | string | "2026072" | 期号 |
| 2 | 红球 | string | "03121823273109" | 6红+1蓝拼接(每2位=1球) |
| 3 | 开机号 | string | "..." | 开机号 |
| 4 | 和值特征 | string | "114" | 红球和值 |
| 5 | 奇偶形态 | string | "3:3" | 奇偶比 |
| 6 | 大小比 | string | "4:2" | 大小比 |
| 7 | 奇偶形态2 | string | "..." | 备用 |
| 8 | 跨度 | string | "28" | 红球跨度 |
| 9 | 其他 | string | "..." | 备用 |
**⚠️ 解析规则**(红球列):
- 前 12 个字符 = 6 个红球,每 2 字符一个号码
- 第 13-14 字符 = 蓝球号码
- 示例: `"03121823273109"` → 红球 [03,12,18,23,27,31] + 蓝球 09
### 6.3 .generation_records.json
**文件格式**: JSON
**数据结构**: 数组,每条记录结构如下:
```json
{
"id": "a1b2c3d4", // UUID前8位
"created_at": "2026-07-03 14:25:30", // 生成时间
"strategy": "高级策略", // 策略名称
"num_tickets": 10, // 生成注数
"filename": "lottery/xxx.xlsx", // 文件相对路径
"filesize": 12345 // 文件大小(bytes)
}
```
**管理方式**: 最新记录插入数组头部(insert(0, ...)
### 6.4 .fetch_status.json
**文件格式**: JSON
**数据结构**:
```json
{
"is_running": false,
"last_update": "2026-07-03 02:30:00",
"last_record_count": 120,
"last_error": null
}
```
### 6.5 lottery/ 目录
**用途**: 存放号码生成结果的 Excel 文件
**文件命名规则**: `双色球_YYYYMMDD_HHMMSS_策略_N注.xlsx`
**保留策略**: 全部保留,由用户通过 Web UI 手动删除
### 6.6 ER 关系图(文件间关系)
```
双色球历史数据.xlsx ──┬──→ lottery.py (读取,生成号码)
└──→ app.py (读取,历史数据展示)
lottery.py ──写入──→ lottery/*.xlsx
lottery/*.xlsx ──┬──→ app.py (send_file 下载)
└──→ .generation_records.json (记录索引)
fetch_data.py ──覆盖写入──→ 双色球历史数据.xlsx
web_executor.py ──状态──→ .fetch_status.json
```
## 7. 非功能需求
### 7.1 性能指标
| 指标 | 目标值 | 测量方法 | 约束说明 |
|------|--------|----------|----------|
| 页面首屏加载 | <3s (P95) | 浏览器 Performance API | 含静态资源 |
| API 生成响应 | <10s (P95) | API 调用计时 | 1000注以内,高级策略 |
| API 查询响应 | <2s (P95) | API 调用计时 | 历史数据、统计查询 |
| 并发用户数 | ≥10 | 并发测试 | 局域网内同时访问 |
| 号码生成耗时 | <5s/100注 | 计时 | 高级策略 |
### 7.2 可用性 SLA
| 维度 | 目标 | 说明 |
|------|------|------|
| 服务可用性 | ≥99% 工作时段 (9:00-22:00) | 内部工具 |
| 数据持久化 | 100% | 生成记录永久保存 |
| 故障恢复 | <5min | systemd RestartSec=5 |
### 7.3 安全策略
| 维度 | 当前状态 | 建议 |
|------|----------|------|
| API 认证 | 可选 Token (auth_enabled) | 生产环境建议开启 |
| 目录遍历 | normpath 基本检查 | 加 realpath 完善 |
| HTTPS | 内网不强制 | 外网部署需配置 Nginx + SSL |
| 文件权限 | 用户 vincent | 确保 600 对敏感文件 |
| XSS 防护 | Flask Jinja2 自动转义 | 前端使用 textContent |
| CSRF 防护 | 无(纯 JSON API | 如加 Cookie 认证需补 CSRF Token |
### 7.4 兼容性矩阵
| 平台 | 浏览器 | 版本 | 测试优先级 |
|------|--------|------|------------|
| PC | Chrome | 90+ | P0 |
| PC | Safari | 14+ | P1 |
| PC | Edge | 90+ | P1 |
| 移动 | iOS Safari | 14+ | P0 |
| 移动 | Android Chrome | 90+ | P1 |
| 移动 | 微信内置浏览器 | 最新版 | P2 |
---
## 8. 边界条件与异常场景
### 8.1 边界条件清单
| 编号 | 模块 | 边界条件 | 处理方式 |
|------|------|----------|----------|
| BC-01 | api_generate | num_tickets=0 | 返回 400 |
| BC-02 | api_generate | num_tickets=1001 | 返回 400 |
| BC-03 | api_generate | num_tickets=1000(上限) | 允许,耗时5-8s |
| BC-04 | api_generate | strategy="invalid" | 返回 400 |
| BC-05 | api_generate | 历史数据文件不存在 | 返回 500 |
| BC-06 | api_generate | 历史数据 <10 条 | 警告但可生成 |
| BC-07 | api_history | page=0 | 返回第一页数据 |
| BC-08 | api_history | page 超过总页数 | 返回空数组 |
| BC-09 | api_history | search 无匹配 | 返回空数组 |
| 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,日志记录 |
| BC-16 | fetch_data | 表格不存在 | 返回 None |
| BC-17 | fetch_data | 数据行不完整 | 跳过该行 |
| BC-18 | lottery | 调整算法超过 max_attempts | 接受当前结果 |
| BC-19 | lottery | 红球出现重复 | 重新选择 |
| BC-20 | lottery | 蓝球不在 1-16 范围 | 重新选择 |
### 8.2 异常场景与处理
| 编号 | 场景 | 触发条件 | 处理方式 | 严重度 |
|------|------|----------|----------|--------|
| EX-01 | 历史数据文件丢失 | 误删或磁盘故障 | api_generate/status 返回错误 | 高 |
| EX-02 | .generation_records.json 损坏 | JSON 格式错误 | load_records 返回空列表 | 中 |
| EX-03 | lottery/ 目录不存在 | 误删 | save_to_excel 自动创建 | 低 |
| EX-04 | 55128.cn 不可达 | 网络故障/网站维护 | fetch 返回 NoneCron 日志记录 | 中 |
| EX-05 | Excel 文件被占用 | 其他进程打开 | 写入失败,返回 None | 中 |
| EX-06 | 磁盘空间不足 | 长期运行未清理 | save_to_excel 返回 None | 高 |
| EX-07 | Flask 进程崩溃 | 未捕获异常 | systemd 自动重启(RestartSec=5) | 高 |
| EX-08 | 端口 8085 被占用 | 端口冲突 | Flask 启动失败 → systemd 重启循环 | 高 |
| EX-09 | 生成记录文件无写权限 | 权限变更 | save_records 抛异常 → 500 | 中 |
| EX-10 | 红球号码全部相同(极端) | 随机策略失败 | 调整算法重试,超限接受 | 低 |
### 8.3 容错策略
```
数据抓取容错:
- HTTP 超时 30s → 失败不重试,次日 Cron 再试
- 解析失败 → 记录日志,保留上次数据
- ✅ v2.0 已确认:fetch_data.py 已有 MAX_RETRIES=3 + 重试间隔机制(costcodev 评审发现)
- ⚠️ shell 层面(fetch_daily.sh)仍为单次执行,建议增加 shell 级重试(3次,间隔60s)
- ⚠️ v2.0 新增:Cron 失败需接入飞书通知,避免静默失败(R-10)
号码生成容错:
- 单注失败 → 跳过,继续生成下一注
- 全部失败 → 返回空 DataFrame → API 返回 500
- 调整算法超限 → 接受当前结果(不阻塞)
服务容错:
- 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. 编码规范与技术栈约束
### 9.1 Python 编码规范
| 规则 | 标准 | 说明 |
|------|------|------|
| 代码风格 | PEP 8 | 所有 Python 文件 |
| 命名规范 | snake_case | 函数和变量 |
| 类命名 | PascalCase | 如 DoubleColorBallGenerator |
| 常量命名 | UPPER_CASE | 如 CONFIG, URL, HEADERS |
| 文件编码 | UTF-8 | 文件头声明 `# -*- coding: utf-8 -*-` |
| 类型标注 | 可选 | 建议核心函数添加 |
| 文档字符串 | 必须 | 所有公共函数/类 |
| 导入顺序 | stdlib → third-party → local | PEP 8 |
| 异常处理 | 不裸 except | 使用具体异常类型 |
| 日志输出 | print 或 logging | 当前项目使用 print,未来建议迁移至 logging |
### 9.2 JavaScript 编码规范
| 规则 | 标准 |
|------|------|
| 代码风格 | ES6+ (const/let, arrow functions, template literals) |
| 命名 | camelCase |
| DOM 操作 | 使用 querySelector / textContent(非 innerHTML |
| 事件绑定 | addEventListener |
| 错误处理 | try/catch + 用户友好提示 |
| 网络请求 | fetch API |
### 9.3 CSS 编码规范
| 规则 | 标准 |
|------|------|
| 预处理器 | 无(纯 CSS) |
| 命名规范 | BEM 或 kebab-case class |
| 响应式断点 | @media (max-width: 768px) |
| CSS 变量 | :root 中统一定义 |
| 单位 | 优先 rem/em,固定布局可用 px |
| 浏览器前缀 | 不需要(目标浏览器均为现代版) |
### 9.4 技术栈约束(锁定清单)
| 层级 | 技术 | 版本 | 锁定理由 |
|------|------|------|----------|
| Python | 3.x | 3.10+ | 团队熟悉 |
| Flask | 3.x | 3.1+ | 现有代码基于此 |
| pandas | 2.x/3.x | 兼容 | 数据处理核心 |
| numpy | 1.x | 兼容 | 统计计算 |
| openpyxl | 3.x | 3.1+ | Excel 读写 |
| requests | 2.x | 2.34+ | HTTP 客户端 |
| beautifulsoup4 | 4.x | 4.15+ | HTML 解析 |
| 前端 | 原生 JS | ES6+ | 无框架依赖 |
### 9.5 禁止事项
- ❌ 不引入前端框架(Vue/React)
- ❌ 不引入 ORM/数据库(当前数据量不需要)
- ❌ 不引入 Celery/Redis(不需要异步任务队列)
- ❌ 不引入 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-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/ (统一) │ │
│ │ 数据文件: ./双色球历史数据.xlsx │ │
│ │ 生成结果: ./lottery/*.xlsx │ │
│ │ 记录索引: ./.generation_records.json │ │
│ └───────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
```
### 10.2 服务管理
```bash
# 安装服务
sudo cp deploy/lotto-web.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable lotto-web
sudo systemctl start lotto-web
# 日常管理
sudo systemctl status lotto-web # 查看状态
sudo systemctl restart lotto-web # 重启
sudo journalctl -u lotto-web -f # 实时日志
# Crontab 配置
crontab -e
# 添加: 30 2 * * * /home/vincent/Studio/lottoData/deploy/fetch_daily.sh >> /home/vincent/Studio/lottoData/deploy/cron.log 2>&1
```
### 10.3 systemd 服务文件
**注意**: 当前 `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 服务
After=network.target
[Service]
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; import requests; import bs4"
Restart=on-failure
RestartSec=5
KillMode=control-group
[Install]
WantedBy=multi-user.target
```
### 10.4 依赖安装
```bash
cd /home/vincent/Studio/lottoData
python3 -m venv venv
./venv/bin/pip install flask pandas openpyxl numpy requests beautifulsoup4
```
### 10.5 端口规划
| 服务 | 端口 | 状态 | 说明 |
|------|------|------|------|
| app.py (Web UI) | 8085 | 需配置 systemd | 主服务 |
| app.py (Web UI) | 8085 | ✅ 已部署 systemd (lotto-app.service) | 主服务 |
| web_executor.py | 5000 | ⚠️ inactive,建议清理 | 数据抓取控制台(建议删除或合并) |
| Cron | N/A | ✅ 已配置 | 每日 2:30 抓取 |
---
## 11. 定时任务设计
### 11.1 当前定时任务
```cron
# 每日 2:30 自动抓取双色球历史数据
30 2 * * * /home/vincent/Studio/lottoData/deploy/fetch_daily.sh >> /home/vincent/Studio/lottoData/deploy/cron.log 2>&1
```
### 11.2 fetch_daily.sh 脚本
```bash
#!/bin/bash
SCRIPT_DIR="/home/vincent/Studio/lottoData"
VENV_PYTHON="${SCRIPT_DIR}/venv/bin/python3"
FETCH_SCRIPT="${SCRIPT_DIR}/fetch_data.py"
LOG_DIR="${SCRIPT_DIR}/deploy"
LOG_FILE="${LOG_DIR}/fetch_$(date +%Y%m%d).log"
echo "=== $(date) 开始执行双色球数据抓取 ==="
"${VENV_PYTHON}" "${FETCH_SCRIPT}" >> "${LOG_FILE}" 2>&1
RC=$?
echo "=== $(date) 执行完成, exit code=${RC} ==="
```
### 11.3 定时任务链路
```
Cron (2:30) → fetch_daily.sh → python3 fetch_data.py
HTTP GET 55128.cn
BeautifulSoup 解析
pandas DataFrame
覆盖写入 双色球历史数据.xlsx
日志: deploy/fetch_YYYYMMDD.log
```
### 11.4 异常处理
| 异常 | 处理 | 建议 |
|------|------|------|
| HTTP 超时 | 脚本退出码非0,Cron 日志记录 | 增加重试机制 |
| 解析失败 | 脚本退出 | 保留上次数据 |
| 磁盘满 | 写入失败 | 定期清理日志 |
| 虚拟环境损坏 | python3 找不到 | systemd ExecStartPre 检查 |
### 11.5 建议增强
```bash
# 建议增加失败重试(3次,间隔60s)
MAX_RETRIES=3
RETRY_INTERVAL=60
for i in $(seq 1 $MAX_RETRIES); do
"${VENV_PYTHON}" "${FETCH_SCRIPT}" >> "${LOG_FILE}" 2>&1
RC=$?
if [ $RC -eq 0 ]; then break; fi
if [ $i -lt $MAX_RETRIES ]; then sleep $RETRY_INTERVAL; fi
done
```
---
## 12. 风险与应对
| 编号 | 风险 | 影响 | 概率 | 应对措施 | 负责人 |
|------|------|------|:----:|----------|--------|
| R-01 | 55128.cn 改版/下线 | 数据源不可用 | 低 | 寻找备用数据源(中彩网) | 架构师 |
| 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 | 目录遍历攻击 | 任意文件读取 | 中 | ✅ 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 | 已确认并更新排期 | ✅ |
---
## 13. 开发排期建议
### 13.1 分阶段交付计划
基于 PRD 及现有代码分析,当前系统已由 costcodev 完成核心开发。架构文档定义的改进项分阶段交付:
| 阶段 | 内容 | 负责人 | 工时 | 依赖 |
|------|------|--------|------|------|
| P0 | 架构文档评审 | 全员 | 1h | 本文档 |
| 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 | ✅ 已有 | — |
| 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 |
| 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 |
**总工时**: ~13h(含新增 P2/P3 项,不含已完成项)
### 13.2 关键里程碑
| 里程碑 | 交付物 | 负责人 |
|--------|--------|--------|
| M1: 架构评审通过 | 本文档 | 梁思筑 |
| M2: 安全加固完成 | 代码更新 | 徐聪 |
| M3: 部署上线 | systemd + Cron | 严维序 |
| M4: 测试验收 | 测试报告 | QA |
| M5: 项目结项 | 总结文档 | 梁思筑 |
---
## 附录 A: 文件清单
| 文件 | 大小 | 行数 | 职责 | 修改建议 |
|------|------|------|------|----------|
| app.py | 17KB | ~728 | Flask Web 主服务 | ✅ 路径安全加固已完成 |
| index.html | 42KB | ~1170 | 响应式前端 UI | 无 |
| lottery.py | 51KB | ~1100 | 号码生成核心逻辑 | 无 |
| 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-app.service | 494B | ~15 | systemd 服务文件 | ✅ 已部署并 active |
## 附录 B: 依赖包清单
| 包名 | 用途 | 安装命令 |
|------|------|----------|
| flask | Web 框架 | `pip install flask` |
| pandas | 数据处理 | `pip install pandas` |
| numpy | 数值计算 | `pip install numpy` |
| openpyxl | Excel 读写 | `pip install openpyxl` |
| requests | HTTP 客户端 | `pip install requests` |
| beautifulsoup4 | HTML 解析 | `pip install beautifulsoup4` |
---
## 附录 C: ADR 决策记录
### ADR-LOTTO-001: 维持 Flask 框架
- **Context**: 项目已有 Flask 代码且运行稳定,路由仅 8 个
- **Decision**: 维持 Flask,不迁移至 FastAPI
- **Consequences**: 无异步支持,但内网单进程足以应对
### ADR-LOTTO-002: 维持文件存储(Excel + JSON
- **Context**: 数据量极小(120 条/12KB),纯读场景
- **Decision**: 不引入数据库
- **Consequences**: 并发写入受限,但当前无此场景
### ADR-LOTTO-003: 双服务独立运行
- **Context**: app.py(8085) 和 web_executor.py(5000) 独立运行
- **Decision**: 暂时维持双服务,长期合并为单服务
- **Consequences**: 两个端口需分别管理,运维复杂度略增
### ADR-LOTTO-004: 覆盖写入历史数据
- **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 评审意见迭代,请产研团队复审