# 双色球自动化系统 — 架构设计文档 | 字段 | 值 | |------|-----| | 文档编号 | ADR-LOTTO-001 | | 版本 | v1.0 | | 创建日期 | 2026-07-03 | | 架构师 | 梁思筑(Serena) | | 评审状态 | 待评审 | | 关联 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
前端 UI] -->|HTTP API| B[app.py
Flask 后端] B -->|import| C[lottery.py
号码生成器] 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
数据抓取] -->|HTTP GET| H[55128.cn] G -->|write| E I[web_executor.py
抓取控制台] -->|subprocess| G K[Cron
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,系统唯一入口 **规模**: ~490 行 **依赖**: 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 认证装饰器 | | `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 ⚠️ 已知问题: - 每次请求都重新读取 Excel,无缓存(120 条可接受) - 列名硬编码,Excel 格式变化需同步修改 - 搜索全列匹配可能误匹配 ``` #### 4.1.6 核心接口:api_download(filepath) ``` 安全检查: 1. os.path.normpath(filepath) 规范化 2. 拒绝以 ../ 开头 → 403 3. 拒绝以 / 开头 → 403 4. 拼接 BASE_DIR + filepath,检查文件存在 ⚠️ 安全建议(costcodev 实施时加固): - 增加 os.path.realpath() 确保最终路径在 BASE_DIR 内 - 当前 normpath 检查可被符号链接绕过 ``` ### 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 服务,共享数据文件但端口不同。 ⚠️ 架构建议:长期应合并为单一服务,通过路由区分功能。 ``` --- ### 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 ├──