Files
EnterpriseArchitect/docs/architecture/双色球系统架构设计文档-BIZ-74.md
T
vincent 8171373812 BIZ-74: 双色球系统架构设计文档 v1.0
- 完整文件级/函数级架构拆解
- 10个文件、40+函数的签名/输入/输出/边界条件
- 部署拓扑、数据流、数据模型
- 非功能需求(性能/安全/兼容性)
- 8项风险评估 + 改进建议
- 5项产研评审决策点
- ADR-007 架构决策记录

架构师: 梁思筑
Issue: BIZ-74

Co-authored-by: multica-agent <github@multica.ai>
2026-07-03 16:34:34 +08:00

827 lines
37 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-007
**Issue**: BIZ-74
**项目**: 双色球自动化系统(lottoData
**架构师**: 梁思筑(Serena
**日期**: 2026-07-03
**版本**: v1.0
**PRD 来源**: BIZ-73(沈路明,双色球 WebUI PRD v1.0
---
## 一、系统概述
### 1.1 系统定位
双色球自动化系统是一个局域网 Web 应用,提供双色球号码生成、历史数据查看、生成记录管理和统计分析功能。系统基于历史开奖数据分析,使用热冷号策略 + 奇偶比/大小比/和值/跨度调整算法生成推荐号码。
### 1.2 部署拓扑
```
┌──────────────────────────────────────────────────────────┐
│ Ubuntu Server (192.168.1.99) │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ lotto-web.service (systemd) │ │
│ │ web_executor.py (Flask :5000) │ │
│ │ ┌──────────────┐ ┌───────────────┐ │ │
│ │ │ / │ │ /api/status │ │ │
│ │ │ /api/execute │ │ │ │ │
│ │ └──────┬───────┘ └───────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ fetch_data.py ── HTTP ──► 55128.cn │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 双色球历史数据.xlsx (Excel 文件) │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ app.py (Flask :8085) ← 手动启动 / 尚未 systemd │ │
│ │ ┌──────────────┐ ┌───────────────┐ │ │
│ │ │ / │ │ /api/generate │ │ │
│ │ │ /api/history │ │ /api/records │ │ │
│ │ │ /api/status │ │ /api/download │ │ │
│ │ │ /api/config │ │ /api/statistics│ │ │
│ │ └──────┬───────┘ └───────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ lottery.py (DoubleColorBallGenerator) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 双色球历史数据.xlsx (Excel) │ │
│ │ .generation_records.json (JSON) │ │
│ │ lottery/ (生成结果 .xlsx) │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Cron (每日 02:30) │ │
│ │ deploy/fetch_daily.sh → fetch_data.py │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 局域网用户 (PC / 移动端) │ │
│ │ → http://192.168.1.99:5000 (数据抓取控制台) │ │
│ │ → http://192.168.1.99:8085 (号码生成 WebUI) │ │
│ └──────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
```
### 1.3 数据流
```
55128.cn ──HTTP fetch──► fetch_data.py ──写入──► 双色球历史数据.xlsx
读取 │
lottery.py (DoubleColorBallGenerator)
├─ 加载历史数据
├─ 计算热冷号统计
├─ 生成号码(高级/基础策略)
└─ 保存结果 → lottery/*.xlsx
调用 │
app.py (Flask :8085)
├─ /api/generate → 生成号码
├─ /api/history → 读历史数据
├─ /api/records → 管理生成记录
├─ /api/statistics → 统计分析
└─ /api/download → 文件下载
HTTP │
index.html (前端 UI)
├─ 号码生成页
├─ 历史数据页
├─ 生成记录页
└─ 统计分析页
```
---
## 二、技术选型
### 2.1 当前技术栈
| 层级 | 技术 | 版本 | 说明 |
|------|------|------|------|
| 后端框架 | Flask | 3.1.3 | 轻量 Web 框架 |
| 号码生成 | Python | 3.x | 核心算法 |
| 数据处理 | Pandas + NumPy | 3.0.4 / 1.x | Excel 读写 + 统计计算 |
| Excel 读写 | openpyxl | 3.1.5 | .xlsx 文件操作 |
| 数据抓取 | requests + BeautifulSoup4 | 2.34.2 / 4.15.0 | HTTP + HTML 解析 |
| 前端 | 原生 HTML/CSS/JS | ES6+ | 单文件 SPA,无构建工具 |
| 数据存储 | Excel + JSON 文件 | — | 无数据库 |
| 部署 | systemd + Cron | — | 直接运行 |
### 2.2 选型评估
| 决策点 | 当前方案 | 备选方案 | 评估 |
|--------|----------|----------|------|
| Web 框架 | Flask | FastAPI | ✅ 保持 Flask。项目规模小,同步处理足够;团队熟悉 Flask |
| 前端 | 原生 HTML/JS | Vue/React | ✅ 保持原生。单页面应用,无构建工具依赖,部署简单 |
| 数据存储 | Excel + JSON | SQLite | ⚠️ 建议中期迁移 SQLite。当前 Excel 足够,但并发写入和查询效率有瓶颈 |
| 部署 | systemd | Docker | ✅ 保持 systemd。内网环境,单服务简单可靠 |
| 进程模型 | 双 Flask 服务 | 合并为单服务 | ⚠️ 建议合并。当前 web_executor.py(:5000) 和 app.py(:8085) 分离,但功能可整合 |
---
## 三、文件级架构拆解
### 3.1 文件总览
| # | 文件 | 职责 | 行数 | 依赖 | 部署状态 |
|---|------|------|------|------|----------|
| 1 | `lottery.py` | 号码生成核心引擎 | ~1090 | pandas, numpy, openpyxl | ✅ 已部署 |
| 2 | `fetch_data.py` | 历史数据抓取脚本 | ~115 | requests, bs4, pandas, openpyxl | ✅ 已部署 |
| 3 | `web_executor.py` | 数据抓取 Web 控制台 | ~170 | flask | ✅ systemd 已部署 |
| 4 | `web_console.html` | 抓取控制台前端 | ~300 | 无 | ✅ 已部署 |
| 5 | `app.py` | 号码生成 Web 服务(主应用) | ~480 | flask, pandas, lottery.py | ⚠️ 未做 systemd |
| 6 | `index.html` | 号码生成 WebUI 前端 | ~1170 | 无 | ✅ 已部署 |
| 7 | `deploy/fetch_daily.sh` | 每日定时抓取脚本 | ~15 | bash | ✅ Cron 已配置 |
| 8 | `deploy/lotto-web.service` | web_executor systemd 单元 | ~15 | systemd | ✅ 已部署 |
| 9 | `.generation_records.json` | 生成记录持久化 | — | 无 | ✅ 运行时生成 |
| 10 | `.fetch_status.json` | 抓取状态持久化 | — | 无 | ✅ 运行时生成 |
### 3.2 模块依赖关系
```
index.html (前端)
│ HTTP (Flask routes)
app.py ──────► lottery.py ──────► 双色球历史数据.xlsx
│ │
│ ├─► .generation_records.json
│ └─► lottery/*.xlsx
└─► (独立于 web_executor.py)
web_console.html (前端)
│ HTTP (Flask routes)
web_executor.py ──► fetch_data.py ──► 双色球历史数据.xlsx
│ │
│ └─► HTTP GET → 55128.cn
└─► .fetch_status.json
```
**关键观察**`app.py``web_executor.py` 共享 `双色球历史数据.xlsx` 文件,但无进程间通信。两个 Flask 服务独立运行。
---
## 四、逐文件函数级设计
### 4.1 `lottery.py` — 号码生成核心引擎
**职责**: 加载历史数据、统计分析、生成号码、保存 Excel
**依赖**: `pandas`, `numpy`, `openpyxl`, `random`, `re`, `collections.Counter`
#### 4.1.1 类: `DoubleColorBallGenerator`
| 方法 | 签名 | 输入 | 输出 | 边界条件 |
|------|------|------|------|----------|
| `__init__` | `(history_file, config=None)` | Excel 文件路径, 配置字典 | — | config=None 时使用默认配置 |
| `load_history_data` | `()` | 无 | `bool` | 文件不存在→False; 数据为空→False; 缺"号码"列→False; 有效数据<10条→WARNING |
| `_calculate_statistics` | `()` | 无 | `None` | 数据为空时直接返回; 统计红球频次、蓝球频次、奇偶比、大小比、和值范围、跨度范围 |
| `get_hot_red_balls` | `(n=10)` | 数量 | `List[int]` | 统计为空→随机返回; 不足n个→用其他球补全 |
| `get_cold_red_balls` | `(n=10)` | 数量 | `List[int]` | 同上 |
| `get_hot_blue_balls` | `(n=5)` | 数量 | `List[int]` | 同上 |
| `parse_ratio` | `(ratio_str)` | 比率字符串 | `(int, int)` | NaN→(3,3) 默认值; 非法格式→(3,3) |
| `_adjust_balls_by_criteria` | `(red_balls, current_value, target_value, get_balls_to_remove, get_candidates, recalculate_current)` | 红球列表+3个回调函数 | `List[int]` | 最多尝试 max_adjustment_attempts=20 次 |
| `_select_hot_cold_balls` | `()` | 无 | `List[int]` | 热号2-4个+冷号补全; 不足6个→随机补全 |
| `_adjust_odd_even_ratio` | `(red_balls)` | 红球列表 | `List[int]` | 80%概率选最常见奇偶比, 20%随机; 无统计→不调整 |
| `_adjust_size_ratio` | `(red_balls)` | 红球列表 | `List[int]` | 80%概率选最常见大小比, 20%随机; 无统计→不调整 |
| `_adjust_sum_range` | `(red_balls)` | 红球列表 | `List[int]` | 90%概率调整到 mean±std 范围内, 10%保持原样 |
| `_adjust_span_range` | `(red_balls)` | 红球列表 | `List[int]` | 90%概率调整到 mean±std 范围内, 10%保持原样 |
| `_select_blue_ball` | `()` | 无 | `int` | hot_blue_probability=0.7 概率选热号, 否则随机 |
| `generate_single_ticket_advanced` | `()` | 无 | `(List[int], int)` | 返回 (红球6个排序, 蓝球1个) |
| `generate_single_ticket_basic` | `()` | 无 | `(List[int], int)` | 完全随机 |
| `generate_multiple_tickets` | `(num_tickets, strategy="advanced")` | 注数+策略 | `pd.DataFrame` | 去重检查; 每注最多尝试100次; 总失败超限→降级为basic |
| `save_to_excel` | `(tickets_df, num_tickets, strategy)` | DataFrame+注数+策略 | `str\|None` | 返回文件路径或None; 自动避免覆盖; 含统计信息sheet |
| `display_statistics` | `()` | 无 | `None` | 打印统计信息到控制台 |
| `run_tests` | `()` | 无 | `None` | 运行内置测试用例 |
| `main` | 模块级 | 无 | `int` | 交互式命令行入口 |
#### 4.1.2 异常场景
| 场景 | 处理方式 | 风险 |
|------|----------|------|
| 历史数据文件不存在 | `load_history_data` 返回 False | 低 — 调用方应检查返回值 |
| Excel 文件格式错误 | pandas 抛异常, catch 后返回 False | 低 |
| 有效数据 < 10 条 | 打印 WARNING, 继续执行 | 中 — 统计结果可能不可靠 |
| 号码解析失败 | 返回空列表 + 0, 继续处理其他行 | 低 |
| 生成号码重复 | 最多尝试 100 次/注, 超限降级为 basic | 低 |
| Excel 写入失败 | catch 异常, 删除部分文件, 返回 None | 中 — 磁盘满或权限问题 |
| 统计数据为空 | 返回随机号码作为默认值 | 中 — 历史数据不足时退化为随机 |
#### 4.1.3 配置参数
```python
DEFAULT_CONFIG = {
'hot_red_count': 15, # 热号红球取前15
'cold_red_count': 10, # 冷号红球取前10
'hot_blue_count': 8, # 热号蓝球取前8
'hot_blue_probability': 0.7, # 70%概率选热号蓝球
'max_adjustment_attempts': 20, # 号码调整最大尝试次数
'hot_red_display_count': 10, # 展示热号前10
'cold_red_display_count': 10, # 展示冷号前10
'hot_blue_display_count': 5, # 展示热号蓝球前5
'min_tickets': 1,
'max_tickets': 1000
}
```
---
### 4.2 `fetch_data.py` — 历史数据抓取脚本
**职责**: 从 55128.cn 抓取双色球历史开奖数据,保存到 Excel
**依赖**: `requests`, `bs4.BeautifulSoup`, `pandas`, `openpyxl`
#### 4.2.1 函数清单
| 函数 | 签名 | 输入 | 输出 | 边界条件 |
|------|------|------|------|----------|
| `fetch_lottery_data` | `()` | 无 | `List[List[str]]\|None` | 网络超时30s→None; 未找到表格→None; 解析0条→None |
| `save_to_excel` | `(data_rows)` | 二维数据列表 | `bool` | 空数据→False; 列数不匹配→截取; 保存失败→False |
| `main` | `()` | 无 | `int` | 返回 0(成功) / 1(失败) |
#### 4.2.2 关键常量
```python
URL = "https://www.55128.cn/kjh/fcssq-history-120.htm"
OUTPUT_FILE = os.path.join(SCRIPT_DIR, "双色球历史数据.xlsx")
HEADERS = { "User-Agent": "Mozilla/5.0 ...", ... }
```
#### 4.2.3 异常场景
| 场景 | 处理方式 | 风险 |
|------|----------|------|
| 网络请求超时 | 30s 超时, 返回 None | 中 — 网站不可达时抓取失败 |
| 网站返回非 200 | `raise_for_status` 抛异常, catch 返回 None | 低 |
| HTML 结构变更 | `soup.find("table")` 返回 None | 高 — 55128.cn 改版会导致解析失败 |
| 表格行解析失败 | `continue` 跳过该行 | 低 |
| Excel 保存失败 | catch 异常, 返回 False | 低 |
---
### 4.3 `web_executor.py` — 数据抓取 Web 控制台
**职责**: 提供 Web 界面执行数据抓取任务,异步运行 fetch_data.py
**依赖**: `flask`, `subprocess`, `threading`
#### 4.3.1 函数清单
| 函数 | 签名 | 输入 | 输出 | 边界条件 |
|------|------|------|------|----------|
| `load_status` | `()` | 无 | `None` | 从 .fetch_status.json 加载; 文件不存在→使用默认状态 |
| `save_status` | `()` | 无 | `None` | 使用 status_lock 保证线程安全 |
| `index` | Flask route `GET /` | 无 | HTML 页面 | 返回 web_console.html |
| `api_status` | Flask route `GET /api/status` | 无 | JSON | 返回当前执行状态 |
| `api_execute` | Flask route `POST /api/execute` | 无 | JSON | 已有任务运行中→409; 否则启动后台线程 |
| `run_script` (内部) | `()` | 无 | `None` | 在 daemon 线程中执行 fetch_data.py; 超时300s; 结果写入状态文件 |
| `check_dependencies` | `()` | 无 | `bool` | 检查 flask/requests/bs4/pandas/openpyxl 是否安装 |
#### 4.3.2 异常场景
| 场景 | 处理方式 | 风险 |
|------|----------|------|
| 重复执行 | 检查 is_running 标志, 返回 409 | 低 |
| 脚本超时 | `subprocess.run` 超时 300s, 设置 last_error | 中 — 长时间占用资源 |
| 脚本失败 | 解析 stderr, 记录 last_error | 低 |
| 线程异常 | catch Exception, 记录错误, 释放 is_running | 中 |
| 状态文件损坏 | `load_status` catch 异常, 使用默认状态 | 低 |
#### 4.3.3 端口与监听
- 监听: `0.0.0.0:5000`
- 线程模式: `threaded=True`
- systemd 服务: `lotto-web.service`
---
### 4.4 `app.py` — 号码生成 Web 服务(主应用)
**职责**: 提供号码生成、历史数据查看、生成记录管理、统计分析 API
**依赖**: `flask`, `pandas`, `lottery.py (DoubleColorBallGenerator)`
#### 4.4.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': False, # Token 认证默认关闭
'max_tickets': 1000,
'default_tickets': 10,
}
```
#### 4.4.2 函数清单
| 函数 | 路由 | 方法 | 输入 | 输出 | 边界条件 |
|------|------|------|------|------|----------|
| `load_records` | — | — | 无 | `List[dict]` | 文件不存在→[]; JSON 解析失败→[] |
| `save_records` | — | — | `List[dict]` | `None` | 自动创建目录 |
| `add_record` | — | — | strategy, num_tickets, filename | `dict` | 插入到列表头部, 自动生成 id |
| `require_auth` | 装饰器 | — | Flask request | — | auth_enabled=False 时跳过认证 |
| `api_generate` | `/api/generate` | POST | JSON body (num_tickets, strategy) | JSON | 参数校验 1-1000; 策略 advanced/basic; 生成失败→500 |
| `get_statistics_data` | — | — | generator(可选) | `dict` | 文件不存在→{}; 直接解析 Excel |
| `api_statistics` | `/api/statistics` | GET | 无 | JSON | 异常→500 |
| `api_records` | `/api/records` | GET | page, page_size | JSON | 分页; 默认 page=1, page_size=20 |
| `api_delete_record` | `/api/records/<id>` | DELETE | record_id | JSON | 记录不存在→404; 同时删除文件 |
| `api_download` | `/api/download/<path>` | GET | filepath | File | 路径安全检查(禁止 .. 和绝对路径); 文件不存在→404 |
| `api_history` | `/api/history` | GET | page, page_size, search | JSON | 读取 Excel; 跳过描述行; 支持搜索过滤; 分页 |
| `api_status` | `/api/status` | GET | 无 | JSON | 返回服务状态(历史数据是否存在、文件大小等) |
| `api_config` | `/api/config` | GET | 无 | JSON | 返回前端配置 |
| `index` | `/` | GET | 无 | HTML | 返回 index.html |
#### 4.4.3 异常场景
| 场景 | 处理方式 | 风险 |
|------|----------|------|
| 历史数据文件不存在 | `/api/generate` 返回 500; `/api/history` 返回 404 | 中 — 首次部署或数据被删 |
| 号码生成失败 | catch Exception, 返回 500 + 错误信息 | 低 |
| Excel 读取失败 | catch Exception, 返回 500 | 中 — 文件损坏 |
| 目录遍历攻击 | `api_download` 检查 .. 和绝对路径, 返回 403 | 低 |
| 生成记录文件损坏 | `load_records` catch 异常, 返回 [] | 低 |
| 并发写入 records | **无锁保护** | ⚠️ 高 — 多用户并发时可能导致数据丢失 |
| 并发生成号码 | **无锁保护** | ⚠️ 中 — Excel 文件并发写入可能损坏 |
#### 4.4.4 端口与监听
- 监听: `0.0.0.0:8085`
- 线程模式: `threaded=True`
- systemd 服务: **未配置**(仅手动启动 `python3 app.py`
---
### 4.5 `index.html` — 前端 UI
**职责**: 单文件 SPA,提供号码生成、历史数据、生成记录、统计分析四个 Tab
**依赖**: 无外部库(纯原生 HTML/CSS/JS
#### 4.5.1 页面结构
```
┌─────────────────────────────────┐
│ Header (标题 + 副标题) │
├─────────────────────────────────┤
│ Nav Tabs: 生成 | 历史 | 记录 | 统计│
├─────────────────────────────────┤
│ │
│ Page Content (Tab 切换) │
│ │
└─────────────────────────────────┘
```
#### 4.5.2 JS 函数清单
| 函数 | 职责 | 输入 | 输出 |
|------|------|------|------|
| `switchTab(tabName)` | 切换 Tab | tab 名称 | DOM 显示/隐藏 |
| `generateTickets()` | 调用 /api/generate | 从表单读取参数 | 渲染号码卡片 |
| `loadHistory(page)` | 调用 /api/history | 页码 | 渲染历史数据表格 |
| `loadRecords(page)` | 调用 /api/records | 页码 | 渲染记录卡片列表 |
| `loadStatistics()` | 调用 /api/statistics | 无 | 渲染统计卡片 |
| `downloadFile(path)` | 触发文件下载 | 文件路径 | 浏览器下载 |
| `deleteRecord(id)` | 调用 DELETE /api/records/:id | 记录 ID | 刷新列表 |
| `searchHistory(query)` | 搜索历史数据 | 搜索词(防抖500ms) | 过滤结果 |
#### 4.5.3 CSS 规范
| 元素 | 规范 |
|------|------|
| 主色 | `#e74c3c` (红色) |
| 辅色 | `#3498db` (蓝色), `#8e44ad` (紫色渐变) |
| 卡片圆角 | `12px` |
| 阴影 | `0 2px 12px rgba(0,0,0,0.08)` |
| 响应式 | viewport 320px-1920px 自适应 |
---
### 4.6 `web_console.html` — 抓取控制台前端
**职责**: 提供数据抓取的 Web 界面,显示抓取状态和执行按钮
**依赖**: 无外部库
---
### 4.7 `deploy/fetch_daily.sh` — 定时抓取脚本
**职责**: Cron 定时执行数据抓取
**Cron 表达式**: `30 2 * * *` (每日 02:30)
```bash
#!/bin/bash
SCRIPT_DIR="/home/vincent/Studio/lottoData"
VENV_PYTHON="${SCRIPT_DIR}/venv/bin/python3"
FETCH_SCRIPT="${SCRIPT_DIR}/fetch_data.py"
LOG_FILE="${LOG_DIR}/fetch_$(date +%Y%m%d).log"
"${VENV_PYTHON}" "${FETCH_SCRIPT}" >> "${LOG_FILE}" 2>&1
```
#### 异常场景
| 场景 | 处理方式 | 风险 |
|------|----------|------|
| venv 路径不存在 | 脚本执行失败, cron 记录错误 | 中 — Python 环境变更后需更新 |
| 网站不可达 | fetch_data.py 返回非 0, 日志记录 | 低 — 次日自动重试 |
| 磁盘满 | Excel 写入失败, 日志记录 | 中 — 需监控磁盘 |
---
## 五、数据模型
### 5.1 存储结构
本项目不使用数据库,所有数据基于文件存储:
| 数据文件 | 格式 | 读取者 | 写入者 | 大小 |
|----------|------|--------|--------|------|
| `双色球历史数据.xlsx` | Excel | app.py, lottery.py | fetch_data.py | ~12KB |
| `.generation_records.json` | JSON | app.py | app.py | <100KB |
| `.fetch_status.json` | JSON | web_executor.py | web_executor.py | <1KB |
| `lottery/*.xlsx` | Excel | app.py (下载) | lottery.py | 6-64KB/文件 |
### 5.2 Excel 数据结构
#### `双色球历史数据.xlsx` (历史开奖数据)
| 列 | 类型 | 说明 | 示例 |
|----|------|------|------|
| 开奖日期 | str | 日期 | "2026-07-02" |
| 期号 | str | 期号 | "2026076" |
| 红球 | str | 6红+1蓝拼接 | "09101316192108" |
| 开机号 | str | 开机号码 | — |
| 和值特征 | str | 和值 | "100" |
| 奇偶形态 | str | 奇偶比 | "3:3" |
| 大小比 | str | 大小比 | "4:2" |
| 奇偶形态2 | str | 奇偶形态(重复) | — |
| 跨度 | str | 跨度 | "25" |
| 其他 | str | 其他信息 | — |
**注意**: 第一行为描述行,实际数据从第二行开始。
#### `lottery/*.xlsx` (生成号码)
Sheet 1: "生成号码"
| 列 | 类型 | 说明 |
|----|------|------|
| 序号 | int | 1-N |
| 红球1-红球6 | int | 排序后的红球 |
| 蓝球 | int | 1-16 |
| 和值 | int | 红球之和 |
| 奇偶比 | str | "3:3" 格式 |
| 大小比 | str | "4:2" 格式 |
| 跨度 | int | max(红球)-min(红球) |
Sheet 2: "统计信息"
| 统计项 | 统计值 |
|--------|--------|
| 生成时间 | 2026-07-03 14:25:30 |
| 生成策略 | 高级策略/基础策略 |
| 生成注数 | 10 |
| 红球热号(前10) | 12, 15, 23, ... |
| 红球冷号(前10) | 4, 7, 31, ... |
| 蓝球热号(前5) | 8, 3, 11, ... |
| 最常见奇偶比 | 3:3 |
| 最常见大小比 | 4:2 |
| 和值范围 | 60-150 |
| 跨度范围 | 15-30 |
#### `.generation_records.json`
```json
[
{
"id": "a1b2c3d4",
"created_at": "2026-07-03 14:25:30",
"strategy": "高级策略",
"num_tickets": 10,
"filename": "lottery/双色球模拟号码-10注-20260703-001.xlsx",
"filesize": 11264
}
]
```
---
## 六、非功能需求
### 6.1 性能指标
| 指标 | 目标值 | 当前状态 | 说明 |
|------|--------|----------|------|
| 页面加载时间 | < 3s (P95) | ✅ 满足 | 单文件 HTML, 无外部依赖 |
| API 响应时间 | < 2s (P95) | ✅ 满足 | 除号码生成外 |
| 号码生成时间 | < 10s (P95) | ✅ 满足 | 100 注以内高级策略 |
| 并发用户数 | ≥ 10 | ⚠️ 未验证 | Flask threaded=True, 但无并发测试 |
| 数据文件大小 | < 1MB | ✅ 满足 | 历史 Excel ~12KB |
### 6.2 可用性 SLA
| 指标 | 目标值 | 说明 |
|------|--------|------|
| 服务可用性 | ≥ 99% (工作时段 9:00-22:00) | 内网工具, 非生产系统 |
| 数据持久化 | 生成记录永久保存 | 除非用户主动删除 |
| 数据备份 | 每日 Excel 由 Cron 自动更新 | 历史数据可从 55128.cn 重新抓取 |
### 6.3 安全策略
| 维度 | 当前措施 | 风险评估 | 建议 |
|------|----------|----------|------|
| 网络暴露 | 0.0.0.0:5000 + 0.0.0.0:8085 | ⚠️ 内网全开放 | 限制为 192.168.1.0/24 网段 |
| API 认证 | 可选 Token (auth_enabled=False) | ⚠️ 当前关闭 | 内网可接受, 外网部署需开启 |
| 目录遍历 | /api/download 路径检查 | ✅ 已防护 | — |
| 输入校验 | 参数范围检查 | ✅ 基本覆盖 | — |
| HTTPS | 未配置 | ⚠️ 内网 HTTP | 内网可接受, 外网需 SSL |
| 依赖安全 | Flask 3.1.3 最新 | ✅ 无已知漏洞 | 定期更新依赖 |
### 6.4 兼容性
| 平台 | 浏览器 | 版本要求 | 测试状态 |
|------|--------|----------|----------|
| PC | Chrome | 90+ | ✅ 主力测试 |
| PC | Safari | 14+ | ⏳ 待测 |
| PC | Edge | 90+ | ⏳ 待测 |
| 移动端 | iOS Safari | 14+ | ⏳ 待测 |
| 移动端 | Android Chrome | 90+ | ⏳ 待测 |
| 移动端 | 微信内置浏览器 | 最新版 | ⏳ 待测 |
---
## 七、风险评估
| # | 风险 | 概率 | 影响 | 应对措施 |
|---|------|------|------|----------|
| R1 | 55128.cn 网站改版导致抓取失败 | 中 | 高 | 增加 HTML 结构版本检测; 备选数据源 |
| R2 | Excel 并发写入导致数据损坏 | 低 | 高 | 加文件锁或迁移 SQLite |
| R3 | .generation_records.json 并发写入丢失 | 中 | 中 | 加 threading.Lock 或迁移 SQLite |
| R4 | app.py 未做 systemd, 进程异常不自动重启 | 高 | 中 | 创建 lotto-app.service systemd 单元 |
| R5 | 历史数据文件损坏后无法生成号码 | 低 | 高 | 定期备份 .xlsx; 增加自动重新抓取 |
| R6 | 爬虫被 55128.cn 限流或封 IP | 低 | 中 | 增加请求间隔; 备选数据源 |
| R7 | 生成大量号码时内存占用高 | 低 | 低 | 限制 max_tickets=1000; 分批生成 |
| R8 | 双 Flask 服务端口管理混乱 | 中 | 低 | 合并为单服务或统一 systemd 管理 |
---
## 八、改进建议
### 8.1 短期改进(1-2 周)
| 优先级 | 改进项 | 工作量 | 负责人 |
|--------|--------|--------|--------|
| P0 | 为 app.py 创建 systemd service | 0.5h | opengineer |
| P0 | app.py 加入 threading.Lock 保护 records 并发写入 | 1h | costcodev |
| P1 | 统一两个 Flask 服务为单服务(合并 web_executor 功能到 app.py | 3h | costcodev |
| P1 | 增加请求间隔到 fetch_data.py(避免被封) | 0.5h | costcodev |
| P2 | 增加移动端真机测试 | 2h | designer |
### 8.2 中期改进(1 月)
| 优先级 | 改进项 | 工作量 | 说明 |
|--------|--------|--------|------|
| P1 | 数据存储迁移 SQLite | 8h | 替代 Excel + JSON, 支持并发查询 |
| P2 | 数据可视化图表 | 4h | Chart.js 展示走势图/分布图 |
| P2 | 备选数据源 | 4h | 增加第二个数据源防单点故障 |
### 8.3 长期演进(本季度)
| 优先级 | 改进项 | 工作量 | 说明 |
|--------|--------|--------|------|
| P3 | 多彩种支持 | 16h | 大乐透、福彩3D 等 |
| P3 | 微信推送 | 8h | 生成结果推送到微信 |
| P3 | 用户权限系统 | 8h | 多用户管理 |
---
## 九、开发排期建议
### 9.1 当前状态评估
**系统已完成度**: ~90%
- ✅ 号码生成核心 (lottery.py) — 完成且功能丰富
- ✅ 数据抓取 (fetch_data.py + web_executor.py) — 完成且有 systemd 部署
- ✅ Web UI (app.py + index.html) — 完成且有响应式布局
- ✅ 定时任务 (Cron) — 完成且每日运行
- ⚠️ systemd 部署 — app.py 尚未配置 systemd
- ⚠️ 并发安全 — 记录文件无锁保护
### 9.2 交付计划
本系统主体代码已完成(由刘总本人编写),架构文档主要是对现有代码的规范化拆解和改进建议。后续工作量主要集中在:
| 阶段 | 工作内容 | 负责人 | 预估工时 |
|------|----------|--------|----------|
| 阶段1 | 创建 app.py systemd service | 严维序 | 0.5h |
| 阶段2 | records 并发写入加锁 | 徐聪 | 1h |
| 阶段3 | 功能测试 + 兼容性测试 | 苏绘锦 | 4h |
| 阶段4 | 刘总验收 | 刘总 | 待定 |
| **合计** | — | — | **~6h** |
---
## 十、编码规范与技术栈约束
### 10.1 Python 编码规范
| 规则 | 要求 |
|------|------|
| Python 版本 | 3.x (项目 venv 已有) |
| 类型注解 | 公开函数推荐标注参数和返回值类型 |
| 命名规范 | snake_case (函数/变量), PascalCase (类) |
| 常量 | UPPER_SNAKE_CASE |
| 错误处理 | 绝不 bare except; 明确捕获异常类型 |
| 日志 | 使用 print (当前模式) 或 logging 模块 |
| 文档字符串 | 公开函数推荐 docstring |
### 10.2 前端编码规范
| 规则 | 要求 |
|------|------|
| HTML | 语义化标签, viewport meta 必须设置 |
| CSS | CSS 变量管理主题色; 响应式断点 768px |
| JS | ES6+; 原生无框架; fetch API 调用后端 |
| 安全 | 所有用户输入需校验; innerHTML 谨慎使用 |
### 10.3 部署规范
| 规则 | 要求 |
|------|------|
| 进程管理 | systemd (Type=simple, Restart=on-failure) |
| 定时任务 | crontab (每日 02:30) |
| Python 环境 | venv 虚拟环境隔离 |
| 依赖管理 | requirements.txt (未创建, 建议补充) |
| 日志 | journalctl (systemd) + 文件日志 (Cron) |
| 监控 | systemctl status + curl /api/status |
---
## 十一、API 接口规范
### 11.1 接口清单(app.py :8085
| # | 路径 | 方法 | 认证 | 请求参数 | 响应格式 | 说明 |
|---|------|------|------|----------|----------|------|
| 1 | `/` | GET | 无 | 无 | HTML | 首页 (index.html) |
| 2 | `/api/generate` | POST | 可选 | `JSON: {num_tickets: int, strategy: str}` | `JSON: {success, data: {tickets, total, filename, download_url, record, statistics}}` | 生成号码 |
| 3 | `/api/history` | GET | 可选 | `?page=1&page_size=20&search=keyword` | `JSON: {success, data: {records, total, page, page_size}}` | 历史数据 |
| 4 | `/api/records` | GET | 可选 | `?page=1&page_size=20` | `JSON: {success, data: {records, total, page, page_size}}` | 生成记录列表 |
| 5 | `/api/records/:id` | DELETE | 可选 | URL 参数 id | `JSON: {success, message}` | 删除记录 |
| 6 | `/api/download/:path` | GET | 可选 | URL 参数 filepath | File (Excel) | 下载文件 |
| 7 | `/api/statistics` | GET | 可选 | 无 | `JSON: {success, data: {hot_reds, cold_reds, hot_blues, ...}}` | 统计数据 |
| 8 | `/api/status` | GET | 无 | 无 | `JSON: {success, data: {server_time, history_exists, ...}}` | 系统状态 |
| 9 | `/api/config` | GET | 无 | 无 | `JSON: {success, data: {max_tickets, default_tickets, auth_enabled}}` | 前端配置 |
### 11.2 接口清单(web_executor.py :5000
| # | 路径 | 方法 | 说明 |
|---|------|------|------|
| 1 | `/` | GET | 抓取控制台 (web_console.html) |
| 2 | `/api/status` | GET | 抓取执行状态 |
| 3 | `/api/execute` | POST | 触发数据抓取 |
### 11.3 错误响应格式
```json
{
"success": false,
"error": "错误描述信息"
}
```
**HTTP 状态码使用**:
- 200: 成功
- 400: 参数错误
- 401: 未授权(Token 认证开启时)
- 403: 禁止访问(目录遍历等)
- 404: 资源不存在
- 409: 冲突(重复执行)
- 500: 服务器内部错误
---
## 十二、部署架构
### 12.1 当前部署
```
┌─────────────────────────────────────────────────────┐
│ Ubuntu-OpenClaw (192.168.1.99) │
│ │
│ systemd services: │
│ ├── lotto-web.service → web_executor.py :5000 ✅ │
│ └── (lotto-app.service → app.py :8085) ⚠️ 未创建 │
│ │
│ Cron: │
│ └── 30 2 * * * fetch_daily.sh → fetch_data.py ✅ │
│ │
│ Python venv: /home/vincent/Studio/lottoData/venv/ │
│ │
│ 项目目录: /home/vincent/Studio/lottoData/ │
└─────────────────────────────────────────────────────┘
```
### 12.2 建议部署改进
```ini
# /etc/systemd/system/lotto-app.service
[Unit]
Description=双色球号码生成 Web 服务
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"
Restart=on-failure
RestartSec=5
KillMode=control-group
[Install]
WantedBy=multi-user.target
```
### 12.3 依赖清单(requirements.txt
```
flask>=3.0
pandas>=2.0
numpy>=1.24
openpyxl>=3.1
requests>=2.31
beautifulsoup4>=4.12
```
---
## 十三、产研评审项
### 需要评审确认的决策点
| # | 议题 | 当前方案 | 备选方案 | 我的建议 |
|---|------|----------|----------|----------|
| 1 | app.py 是否做 systemd | 未做 | 创建 lotto-app.service | ✅ 建议创建,确保自动重启 |
| 2 | 双 Flask 服务是否合并 | 分离(port 5000+8085) | 合并为单服务 | ✅ 建议合并,简化部署管理 |
| 3 | 数据存储是否迁移 SQLite | Excel+JSON | SQLite | ⚠️ 短期保持文件, 中期迁移 |
| 4 | 前端是否引入框架 | 原生 HTML/JS | Vue/Alpine.js | ✅ 保持原生, 功能不复杂 |
| 5 | API 认证是否启用 | 关闭 | 开启 Token 认证 | ⚠️ 内网可关闭, 外网需开启 |
---
## 十四、总结
### 架构评估结论
**✅ 系统可用,建议小幅改进后正式交付。**
**优势**:
- 代码已完整实现,功能覆盖全部 PRD 需求
- 号码生成算法丰富(热冷号策略 + 多维度调整)
- 前端响应式设计,PC + 移动端适配
- 部署简单(systemd + cron
**待改进**:
- app.py 需创建 systemd serviceP0
- records 文件并发写入需加锁(P0)
- 两个 Flask 服务建议合并(P1)
**风险评估**: 低风险。系统规模小,部署在内网,无生产级别可用性要求。
### 架构决策记录
#### ADR-007-01: 保持 Flask 框架,不迁移 FastAPI
- **Context**: 项目规模小,同步处理足够,团队熟悉 Flask
- **Decision**: 保持 Flask
- **Consequences**: 无异步性能优势,但足够满足需求
#### ADR-007-02: 保持文件存储,中期考虑 SQLite
- **Context**: 当前 Excel + JSON 满足数据量需求
- **Decision**: 短期保持文件存储;数据量 > 5000 条或并发写入问题时迁移 SQLite
- **Consequences**: 并发写入需加锁保护
#### ADR-007-03: 保持原生前端,不引入框架
- **Context**: 单页面 SPA,功能简单,无构建工具依赖
- **Decision**: 保持原生 HTML/CSS/JS
- **Consequences**: 复杂交互扩展能力有限,但满足当前需求
---
> **架构师**: 梁思筑
> **日期**: 2026-07-03
> **下一步**: 提交 Git → 发起产研评审 → 评审通过后通知胡蓉推进开发