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

1226 lines
44 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-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<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 模块配置
```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-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
```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 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
**请求**:
```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 字符 | 跳过/返回空红球数组 |
| 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 服务管理
```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:
```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"
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 | 主服务 |
| web_executor.py | 5000 | 已部署 | 数据抓取控制台(可合并) |
| 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 文件损坏 | 无法生成号码 | 低 | 每周备份 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`
**评审请求**: 请产研团队(沈路明、徐聪、严维序)评审本架构文档