efbbf6265c
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>
1329 lines
52 KiB
Markdown
1329 lines
52 KiB
Markdown
# 双色球自动化系统 — 架构设计文档
|
||
|
||
| 字段 | 值 |
|
||
|------|-----|
|
||
| 文档编号 | 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-1000,strategy 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. 读取 Excel(header=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. 读取 Excel(header=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 UI,4 个功能 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 返回 None,Cron 日志记录 | 中 |
|
||
| 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 评审意见迭代,请产研团队复审
|