# 双色球自动化系统 — 架构设计文档 **文档编号**: 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/` | DELETE | record_id | JSON | 记录不存在→404; 同时删除文件 | | `api_download` | `/api/download/` | 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 service(P0) - 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 → 发起产研评审 → 评审通过后通知胡蓉推进开发