Files
EnterpriseArchitect/docs/architecture/Lotto-Architecture-v1.md
T
vincent a6f473f836 feat: 双色球自动化系统架构设计文档 v1.0 (BIZ-74)
- 系统架构图(拓扑/数据流/模块依赖)
- 技术选型对比(后端/前端/存储/部署)
- 模块详细设计(单文件/单函数级)
- 接口定义(8个API完整规范)
- 数据模型 + ER关系
- 非功能需求(性能/可用性/安全/兼容性)
- 边界条件20项 + 异常场景10项
- 编码规范与技术栈约束
- 部署架构 + 定时任务设计
- 风险评估 + 开发排期
- ADR决策记录4条

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

44 KiB
Raw Blame History

双色球自动化系统 — 架构设计文档

字段
文档编号 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 模块依赖图

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,系统唯一入口 规模: ~490 行 依赖: Flask, lottery.py, pandas, openpyxl, numpy

4.1.1 模块配置

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-1000strategy advanced/basic
  2. 实例化 DoubleColorBallGenerator(history_file)
  3. generator.load_history_data() 加载历史数据
  4. generator.generate_multiple_tickets(num_tickets, strategy) 生成号码
  5. generator.save_to_excel() 保存结果文件
  6. add_record() 记录生成历史
  7. get_statistics_data() 获取统计信息
  8. 构建 JSON 响应(前端最多展示 50 注)

输出: {
  success: true,
  data: {
    tickets: [{index, reds[6], blue, sum_value, odd_even, size_ratio, span}],
    total: int, filename: str, download_url: str, record: dict, statistics: dict
  }
}

边界条件:
  - num_tickets < 1 或 > 1000 → 400
  - strategy 非 advanced/basic → 400
  - 历史数据文件不存在 → 500
  - load_history_data() 返回 False → 500
  - tickets_df.empty → 500
  - save_to_excel 返回 None → 500

4.1.4 核心接口:get_statistics_data()

处理流程:
  1. 读取 Excelheader=None, iloc[1:] 跳过描述行)
  2. 解析红球(6个)+蓝球(1个)从拼接字符串(每2字符=1球)
  3. Counter 统计频次 → 排序取热冷号
  4. 统计奇偶比/大小比频次
  5. numpy 计算和值/跨度的 min/max/mean/std

输出: {
  hot_reds: [int...15], cold_reds: [int...15],
  hot_blues: [int...8],
  common_odd_even: "3:3", common_size_ratio: "4:2",
  sum_range: {min, max, mean, std},
  span_range: {min, max, mean, std},
  history_count: int
}

边界条件:
  - Excel 不存在 → 返回 {}
  - 数据行为空 → 返回 {}
  - 号码字符串长度 <14 → 跳过该行
  - 号码超出范围 → 跳过

4.1.5 核心接口:api_history()

处理流程:
  1. 读取 Excelheader=None, iloc[1:]
  2. 硬编码列名: ['开奖日期','期号','红球','开机号','和值特征','奇偶形态','大小比','奇偶形态2','跨度','其他']
  3. 解析红球列表(前12字符每2位) + 蓝球(第13-14位)
  4. search 非空 → 全列字符串匹配过滤
  5. 分页切片 → JSON

⚠️ 已知问题:
  - 每次请求都重新读取 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

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 UI4 个功能 Tab 规模: ~1170 行(HTML + CSS + JS 单文件) 依赖: 无外部 CDN

4.5.1 页面结构

index.html
├── <style> (~300行)
│   ├── CSS 变量(主色 #e74c3c、蓝球 #3498db
│   ├── 响应式 @media 768px 断点
│   ├── 卡片/表格/球号样式
│   └── 移动端底部固定导航
├── <body>
│   ├── Header(标题+副标题,sticky
│   ├── Nav Tabs(生成|历史|记录|统计,sticky)
│   ├── Tab 1: 号码生成(统计概览+参数配置+生成按钮+结果区)
│   ├── Tab 2: 历史数据(搜索框+表格+分页)
│   ├── Tab 3: 生成记录(卡片列表+下载/删除+分页)
│   └── Tab 4: 统计(热冷号+奇偶/大小/和值/跨度)
└── <script> (~400行)
    ├── fetch() 调用 API
    ├── Tab 切换逻辑
    ├── 分页加载
    ├── 搜索防抖(500ms)
    ├── 结果渲染(DOM 操作)
    └── 下载触发

4.5.2 前端 API 调用清单

调用时机 方法 端点 用途
页面加载 GET /api/config 获取配置(max_tickets等)
页面加载 GET /api/statistics 统计概览
点击生成 POST /api/generate 生成号码
切换历史 Tab GET /api/history?page=1 加载历史数据
搜索输入 GET /api/history?search=xxx 搜索(500ms防抖)
切换记录 Tab GET /api/records?page=1 加载生成记录
点击下载 GET /api/download/filepath 下载 Excel
点击删除 DELETE /api/records/:id 删除记录
切换统计 Tab GET /api/statistics 加载统计数据

5. 接口定义

5.1 API 接口清单

# 接口 方法 路径 认证 说明
1 生成号码 POST /api/generate 可选 核心接口
2 历史数据 GET /api/history 可选 分页+搜索
3 生成记录 GET /api/records 可选 分页
4 删除记录 DELETE /api/records/:id 可选 删记录+文件
5 统计数据 GET /api/statistics 可选 热冷号+指标
6 下载文件 GET /api/download/:path 可选 Excel 下载
7 系统状态 GET /api/status 健康检查
8 前端配置 GET /api/config 前端初始化
9 首页 GET / 返回 index.html

5.2 接口详细定义

5.2.1 POST /api/generate

请求:

{
  "num_tickets": 10,
  "strategy": "advanced"
}

参数说明:

参数 类型 必填 范围 默认值
num_tickets int 1-1000 10
strategy str "advanced"|"basic" "advanced"

成功响应 (200):

{
  "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):

{
  "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)

成功响应:

{
  "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

成功响应:

{
  "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

成功响应:

{
  "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 数据结构: 数组,每条记录结构如下:

{
  "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 数据结构:

{
  "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 字符 跳过/返回空红球数组
BC-11 api_download filepath 含 ../ 返回 403
BC-12 api_download filepath 绝对路径 返回 403
BC-13 api_download 文件不存在 返回 404
BC-14 api_records 删除不存在的记录 返回 404
BC-15 fetch_data HTTP 超时 返回 None,日志记录
BC-16 fetch_data 表格不存在 返回 None
BC-17 fetch_data 数据行不完整 跳过该行
BC-18 lottery 调整算法超过 max_attempts 接受当前结果
BC-19 lottery 红球出现重复 重新选择
BC-20 lottery 蓝球不在 1-16 范围 重新选择

8.2 异常场景与处理

编号 场景 触发条件 处理方式 严重度
EX-01 历史数据文件丢失 误删或磁盘故障 api_generate/status 返回错误
EX-02 .generation_records.json 损坏 JSON 格式错误 load_records 返回空列表
EX-03 lottery/ 目录不存在 误删 save_to_excel 自动创建
EX-04 55128.cn 不可达 网络故障/网站维护 fetch 返回 NoneCron 日志记录
EX-05 Excel 文件被占用 其他进程打开 写入失败,返回 None
EX-06 磁盘空间不足 长期运行未清理 save_to_excel 返回 None
EX-07 Flask 进程崩溃 未捕获异常 systemd 自动重启(RestartSec=5)
EX-08 端口 8085 被占用 端口冲突 Flask 启动失败 → systemd 重启循环
EX-09 生成记录文件无写权限 权限变更 save_records 抛异常 → 500
EX-10 红球号码全部相同(极端) 随机策略失败 调整算法重试,超限接受

8.3 容错策略

数据抓取容错:
  - HTTP 超时 30s → 失败不重试,次日 Cron 再试
  - 解析失败 → 记录日志,保留上次数据
  - ⚠️ 建议:增加失败重试机制(3次,间隔 60s)

号码生成容错:
  - 单注失败 → 跳过,继续生成下一注
  - 全部失败 → 返回空 DataFrame → API 返回 500
  - 调整算法超限 → 接受当前结果(不阻塞)

服务容错:
  - systemd Restart=on-failure, RestartSec=5
  - threaded=True 处理并发(但无连接数限制)
  - ⚠️ 建议:增加 Flask -*- errorhandler 统一异常处理

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__)

10. 部署架构

10.1 部署拓扑

┌─────────────────────────────────────────────┐
│  Ubuntu Server (192.168.1.99)               │
│                                             │
│  ┌───────────────────────────────────────┐  │
│  │ systemd: lotto-web.service            │  │
│  │ ExecStart: python3 app.py             │  │
│  │ 监听: 0.0.0.0:8085                     │  │
│  │ Restart: on-failure, RestartSec=5     │  │
│  └───────────────────────────────────────┘  │
│                                             │
│  ┌───────────────────────────────────────┐  │
│  │ Crontab (vincent)                      │  │
│  │ 30 2 * * * deploy/fetch_daily.sh      │  │
│  │ → python3 fetch_data.py               │  │
│  └───────────────────────────────────────┘  │
│                                             │
│  ┌───────────────────────────────────────┐  │
│  │ 项目目录: /home/vincent/Studio/lottoData│  │
│  │ Python venv: ./venv/                   │  │
│  │ 数据文件: ./双色球历史数据.xlsx          │  │
│  │ 生成结果: ./lottery/*.xlsx             │  │
│  │ 记录索引: ./.generation_records.json   │  │
│  └───────────────────────────────────────┘  │
└─────────────────────────────────────────────┘

10.2 服务管理

# 安装服务
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:

[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"
Restart=on-failure
RestartSec=5
KillMode=control-group

[Install]
WantedBy=multi-user.target

10.4 依赖安装

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 主服务
web_executor.py 5000 已部署 数据抓取控制台(可合并)
Cron N/A 已配置 每日 2:30 抓取

11. 定时任务设计

11.1 当前定时任务

# 每日 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 脚本

#!/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 超时 脚本退出码非0Cron 日志记录 增加重试机制
解析失败 脚本退出 保留上次数据
磁盘满 写入失败 定期清理日志
虚拟环境损坏 python3 找不到 systemd ExecStartPre 检查

11.5 建议增强

# 建议增加失败重试(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 文件损坏 无法生成号码 每周备份 xlsx 文件 运维
R-03 端口 8085 冲突 服务无法启动 修改 CONFIG['port'] 运维
R-04 55128.cn 反爬限制 抓取频率受限 降低频率(每周),增加 User-Agent 轮换 开发
R-05 单进程性能瓶颈 高并发卡顿 内网≤10人,可接受 架构师
R-06 文件锁竞争 抓取+生成同时写 时间错开(抓取2:30,生成按需) 架构师
R-07 数据安全(无备份) 丢失生成记录 每周 Git 提交 lottery/ 目录 运维
R-08 目录遍历攻击 任意文件读取 加 realpath 检查 (costcodev 实施) 开发

13. 开发排期建议

13.1 分阶段交付计划

基于 PRD 及现有代码分析,当前系统已由 costcodev 完成核心开发。架构文档定义的改进项分阶段交付:

阶段 内容 负责人 工时 依赖
P0 架构文档评审 全员 1h 本文档
P0 路径安全加固(realpath costcodev 0.5h 架构评审通过
P0 systemd 服务更新为 app.py opengineer 0.5h 架构评审通过
P1 Flask errorhandler 统一异常 costcodev 1h P0
P1 fetch_data 失败重试机制 costcodev 1h P0
P1 subprocess 超时保护 costcodev 0.5h P0
P2 移动端真机兼容性测试 QA 2h P1
P2 性能压测(10并发) QA 1h P1
P3 前端日志埋点 costcodev 2h P2
P3 monitoring 告警接入 opengineer 2h P2

总工时: ~10.5h(不含架构评审和验收)

13.2 关键里程碑

里程碑 交付物 负责人
M1: 架构评审通过 本文档 梁思筑
M2: 安全加固完成 代码更新 徐聪
M3: 部署上线 systemd + Cron 严维序
M4: 测试验收 测试报告 QA
M5: 项目结项 总结文档 梁思筑

附录 A: 文件清单

文件 大小 行数 职责 修改建议
app.py 17KB ~490 Flask Web 主服务 路径安全加固
index.html 42KB ~1170 响应式前端 UI
lottery.py 51KB ~1100 号码生成核心逻辑
fetch_data.py 3.8KB ~130 历史数据抓取 加重试机制
web_executor.py 6.4KB ~220 数据抓取 Web 控制台 加 subprocess 超时
web_console.html 11KB ~300 抓取控制台前端
deploy/fetch_daily.sh 641B ~15 Cron 抓取脚本 加重试逻辑
deploy/lotto-web.service 494B ~15 systemd 服务文件 更新为 app.py

附录 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: 覆盖瞬间服务数据不可用(毫秒级,可接受)

文档作者: 梁思筑(Serena
提交至: http://192.168.1.99:12299/vincent/EnterpriseArchitect.git
路径: docs/architecture/Lotto-Architecture-v1.md
评审请求: 请产研团队(沈路明、徐聪、严维序)评审本架构文档