v1.0 评审通过后,根据四位评审人意见迭代: 沈路明(PM) 评审意见: - P2: 前端搜索增加按期号精确查找 - P2: 生成记录增加策略类型筛选 - P3: 统计页增加历史趋势可视化 - P3: 覆盖写入先写.tmp再rename 陆怀瑾(COO) 评审意见: - 新增 R-09/R-10/R-11 风险项 - R-09 运维单点依赖 → 安排徐聪为备用运维 - R-10 数据抓取静默失败 → 接入飞书通知 - R-11 Excel格式漂移 → 增加跳过量监控 徐聪(Dev) 评审意见: - realpath+commonpath双重校验(已完成) - 修正app.py行数490→728 - 确认fetch_data已有MAX_RETRIES=3 - 确认web_executor已有timeout=300s - P2: api_history列名抽取为配置常量 - P2: .generation_records.json加fcntl.flock - P3: lottery/目录自动清理 - P3: api_token迁移至环境变量 - 新增 R-12/R-13 风险项 严维序(OPS) 评审意见: - 更新服务名为lotto-app.service(已部署) - 旧lotto-web.service建议清理 - 统一虚拟环境venv/(删除.venv/) - P1: 配置logrotate日志轮转 - P1: 服务健康监控接入心跳巡检 - 更新ExecStartPre补充numpy检查 - 确认backup.sh已部署 新增内容: - §12.1 评审意见响应矩阵(25项) - ADR-LOTTO-005 文件并发写入安全 - ADR-LOTTO-006 API Token安全管理 - BC-11a 符号链接绕过边界条件 - 部署拓扑图同步实际状态 Co-authored-by: multica-agent <github@multica.ai>
52 KiB
双色球自动化系统 — 架构设计文档
| 字段 | 值 |
|---|---|
| 文档编号 | ADR-LOTTO-001 |
| 版本 | v2.0 |
| 创建日期 | 2026-07-03 |
| 更新日期 | 2026-07-03 |
| 架构师 | 梁思筑(Serena) |
| 评审状态 | v1.0 评审通过,v2.0 迭代后待复审 |
| 关联 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,系统唯一入口
规模: ~728 行
依赖: 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 认证装饰器 |
safe_join(base, path) |
str, str | str|None | os.path | v2.0 新增:realpath + commonpath 路径安全检查 |
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
⚠️ 已知问题(v2.0 已优化):
- 每次请求都重新读取 Excel,无缓存(120 条可接受)
- ~~列名硬编码,Excel 格式变化需同步修改~~ → v2.0 抽取为配置常量 `EXCEL_COLUMNS`
- ~~搜索全列匹配可能误匹配~~ → v2.0 增加按「期号」精确查找选项
4.1.6 核心接口:api_download(filepath)
安全检查(v2.0 已加固):
1. os.path.normpath(filepath) 规范化
2. 拒绝以 ../ 开头 → 403
3. 拒绝以 / 开头 → 403
4. os.path.realpath() 解析符号链接 → 确保最终路径在 BASE_DIR 内
5. os.path.commonpath([real_path, base_real]) 校验前缀
6. 拼接 BASE_DIR + filepath,检查文件存在
✅ v1.0 评审意见(costcodev 徐聪指出):
- realpath 检查已纳入 v2.0 设计
- commonpath 二次校验确保防止符号链接绕过
- 此项为 P0 改进,costcodev 实施时已完成
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 服务,共享数据文件但端口不同。
⚠️ 架构建议:长期应合并为单一服务,通过路由区分功能。
**v2.0 部署状态更新**(opengineer 严维序反馈):
- lotto-web.service 已 inactive,建议清理:
```bash
sudo systemctl disable lotto-web.service
sudo rm /etc/systemd/system/lotto-web.service
sudo systemctl daemon-reload
- web_executor.py + web_console.html 如不再使用建议删除
- 如保留需单独 systemd 管理,避免裸进程运行
---
### 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 ├── <style> (~300行) │ ├── CSS 变量(主色 #e74c3c、蓝球 #3498db) │ ├── 响应式 @media 768px 断点 │ ├── 卡片/表格/球号样式 │ └── 移动端底部固定导航 ├── │ ├── 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):
{
"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 字符 | 跳过/返回空红球数组;v2.0 增加跳过量监控告警(R-11) |
| BC-11 | api_download | filepath 含 ../ |
返回 403 |
| BC-12 | api_download | filepath 绝对路径 | 返回 403 |
| BC-11a | api_download | 符号链接绕过(v2.0 新增) | realpath + commonpath 二次校验 → 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 返回 None,Cron 日志记录 | 中 |
| 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 再试
- 解析失败 → 记录日志,保留上次数据
- ✅ v2.0 已确认:fetch_data.py 已有 MAX_RETRIES=3 + 重试间隔机制(costcodev 评审发现)
- ⚠️ shell 层面(fetch_daily.sh)仍为单次执行,建议增加 shell 级重试(3次,间隔60s)
- ⚠️ v2.0 新增:Cron 失败需接入飞书通知,避免静默失败(R-10)
号码生成容错:
- 单注失败 → 跳过,继续生成下一注
- 全部失败 → 返回空 DataFrame → API 返回 500
- 调整算法超限 → 接受当前结果(不阻塞)
服务容错:
- systemd Restart=on-failure, RestartSec=5
- threaded=True 处理并发(但无连接数限制)
- ⚠️ 建议:增加 Flask -*- errorhandler 统一异常处理
- v2.0 新增:threaded=True 无连接数限制,建议增加 max_worker 或使用 --threads 参数(R-13)
- v2.0 新增:.generation_records.json 并发写入需加 fcntl.flock 文件锁
- v2.0 新增:覆盖写入前先写 .tmp 再 rename,避免极端场景数据损坏(沈路明建议)
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__)) - ❌ 不在代码中硬编码 API Token(v2.0:迁移至环境变量)
- ❌ 不在无文件锁的情况下并发写入 JSON 文件(v2.0:需 fcntl.flock)
10. 部署架构
10.1 部署拓扑
v2.0 更新:根据严维序(opengineer)评审反馈,部署拓扑已同步实际状态。
┌─────────────────────────────────────────────┐
│ Ubuntu Server (192.168.1.99) │
│ │
│ ┌───────────────────────────────────────┐ │
│ │ systemd: lotto-app.service ✅ active │ │
│ │ ExecStart: venv/bin/python3 app.py │ │
│ │ 监听: 0.0.0.0:8085 │ │
│ │ Restart: on-failure, RestartSec=5 │ │
│ └───────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────┐ │
│ │ systemd: lotto-web.service ⚠️ inactive│ │
│ │ 建议: disable + delete │ │
│ └───────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────┐ │
│ │ Crontab (vincent) │ │
│ │ 30 2 * * * deploy/fetch_daily.sh │ │
│ │ → python3 fetch_data.py │ │
│ └───────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────┐ │
│ │ backup.sh (每日 3:00) ✅ 已部署 │ │
│ │ → 备份 xlsx 到 backup/ 目录(保留30天) │ │
│ └───────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────┐ │
│ │ 项目目录: /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):
v2.0 更新(根据 opengineer 严维序反馈):
- 服务名已更新为
lotto-app.service(实际已部署并 active) - 旧
lotto-web.service已 inactive,建议删除 - ExecStartPre 需补充 numpy 检查
- 统一使用
venv/,删除.venv/
[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; import requests; import bs4"
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 | 主服务 |
| app.py (Web UI) | 8085 | ✅ 已部署 systemd (lotto-app.service) | 主服务 |
| web_executor.py | 5000 | ⚠️ inactive,建议清理 | 数据抓取控制台(建议删除或合并) |
| 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 超时 | 脚本退出码非0,Cron 日志记录 | 增加重试机制 |
| 解析失败 | 脚本退出 | 保留上次数据 |
| 磁盘满 | 写入失败 | 定期清理日志 |
| 虚拟环境损坏 | 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 文件损坏 | 无法生成号码 | 低 | 每日 3:00 自动备份(backup.sh 已部署) | 运维 |
| R-03 | 端口 8085 冲突 | 服务无法启动 | 中 | 修改 CONFIG['port'] | 运维 |
| R-04 | 55128.cn 反爬限制 | 抓取频率受限 | 低 | 降低频率(每周),增加 User-Agent 轮换 | 开发 |
| R-05 | 单进程性能瓶颈 | 高并发卡顿 | 低 | 内网≤10人,可接受 | 架构师 |
| R-06 | 文件锁竞争 | 抓取+生成同时写 | 低 | 时间错开(抓取2:30,生成按需) | 架构师 |
| R-07 | 数据安全(无备份) | 丢失生成记录 | 中 | 每周 Git 提交 lottery/ 目录 | 运维 |
| R-08 | 目录遍历攻击 | 任意文件读取 | 中 | ✅ v2.0 已加固:realpath + commonpath 检查 | 开发 |
| R-09 | 运维单点依赖(严维序是唯一 ops) | P0 整改无人执行 | 中 | 安排徐聪作为备用运维联系人 | COO |
| R-10 | 数据抓取静默失败 | 次日才发现数据未更新 | 中 | P3 增加 Cron 失败飞书通知 | 运维 |
| R-11 | Excel 格式漂移(55128.cn 改版) | 红球列解析规则失效 | 中 | BC-10 跳过异常行 + 监控跳过量 | 开发 |
| R-12 | 红球列解析依赖固定偏移量 | 55128.cn 改变数据格式 → 号码生成失效 | 中 | BC-10 已定义跳过,需加监控告警 | 开发 |
| R-13 | threaded=True 无连接数限制 | 局域网多人同时生成 → OOM | 低 | 建议 max_worker 或 --threads 参数 | 开发 |
12.1 v2.0 评审意见响应矩阵
| # | 评审人 | 评审意见 | 响应 | 状态 |
|---|---|---|---|---|
| 1 | 沈路明 (PM) | 前端搜索支持按「期号」精确查找 | 纳入 P2 改进项 | 📋 |
| 2 | 沈路明 (PM) | 生成记录增加「策略类型」筛选 | 纳入 P2 改进项 | 📋 |
| 3 | 沈路明 (PM) | 统计页增加历史趋势可视化 | 纳入 P3 改进项 | 📋 |
| 4 | 沈路明 (PM) | 覆盖写入先写 .tmp 再 rename | 纳入 P3 改进项 | 📋 |
| 5 | 陆怀瑾 (COO) | R-09 运维单点依赖风险 | 新增 R-09,建议徐聪作为备用运维 | 📋 |
| 6 | 陆怀瑾 (COO) | R-10 数据抓取静默失败告警 | 新增 R-10,P3 接入飞书通知 | 📋 |
| 7 | 陆怀瑾 (COO) | R-11 Excel 格式漂移监控 | 新增 R-11,BC-10 增加跳过量监控 | 📋 |
| 8 | 徐聪 (Dev) | realpath 后需 commonpath 校验 | 已纳入 v2.0 安全检查设计 | ✅ |
| 9 | 徐聪 (Dev) | api_history 列名硬编码 | 纳入 P2,抽取为配置常量 | 📋 |
| 10 | 徐聪 (Dev) | .generation_records.json 无文件锁 | 纳入 P2,增加 fcntl.flock | 📋 |
| 11 | 徐聪 (Dev) | lottery/ 目录无清理机制 | 纳入 P3,自动清理策略 | 📋 |
| 12 | 徐聪 (Dev) | CONFIG api_token 明文 | 纳入 P3,迁移至环境变量 | 📋 |
| 13 | 徐聪 (Dev) | app.py 实际 728 行非 490 行 | 已修正 | ✅ |
| 14 | 徐聪 (Dev) | fetch_data.py 已有重试机制 | 已确认,排期降级 | ✅ |
| 15 | 徐聪 (Dev) | web_executor 已有 timeout=300s | 已确认,建议降到 120s | ✅ |
| 16 | 徐聪 (Dev) | R-12 红球列解析依赖固定偏移量 | 新增 R-12 | 📋 |
| 17 | 徐聪 (Dev) | R-13 threaded=True 无连接数限制 | 新增 R-13 | 📋 |
| 18 | 严维序 (OPS) | 旧 lotto-web.service 未删除 | 纳入 P0 清理项 | 📋 |
| 19 | 严维序 (OPS) | 双虚拟环境一致性 | 纳入 P0,统一为 venv/ | 📋 |
| 20 | 严维序 (OPS) | web_executor.py 服务处理 | 文档已补充决策建议 | ✅ |
| 21 | 严维序 (OPS) | 日志轮转缺失 | 纳入 P1,配置 logrotate | 📋 |
| 22 | 严维序 (OPS) | 服务健康监控 | 纳入 P1,定时 curl + 心跳巡检 | 📋 |
| 23 | 严维序 (OPS) | 部署文档同步 | 纳入 P3,已更新文档中 | ✅ |
| 24 | 严维序 (OPS) | ExecStartPre 补充 numpy 检查 | 已更新 systemd 服务文件 | ✅ |
| 25 | 严维序 (OPS) | 数据备份已部署 backup.sh | 已确认并更新排期 | ✅ |
13. 开发排期建议
13.1 分阶段交付计划
基于 PRD 及现有代码分析,当前系统已由 costcodev 完成核心开发。架构文档定义的改进项分阶段交付:
| 阶段 | 内容 | 负责人 | 工时 | 依赖 |
|---|---|---|---|---|
| P0 | 架构文档评审 | 全员 | 1h | 本文档 |
| P0 | 路径安全加固(realpath + commonpath) | costcodev | ✅ 已完成 | 架构评审通过 |
| P0 | systemd 服务更新为 app.py (lotto-app.service) | opengineer | ✅ 已完成 | 架构评审通过 |
| P0 | 旧 lotto-web.service 清理 | opengineer | 待执行 | — |
| P0 | 统一虚拟环境(删除 .venv/) | opengineer | 待执行 | — |
| P1 | Flask errorhandler 统一异常 | costcodev | 1h | P0 |
| P1 | costcodev | ✅ 已有 | — | |
| P1 | subprocess 超时保护(建议 300s→120s) | costcodev | 0.5h | P0 |
| P1 | 日志轮转配置(logrotate) | opengineer | 0.5h | P0 |
| P1 | 服务健康监控接入 | opengineer | 1h | P0 |
| P1 | 数据文件每日备份(backup.sh) | opengineer | ✅ 已部署 | — |
| P2 | 移动端真机兼容性测试 | QA | 2h | P1 |
| P2 | 性能压测(10并发) | QA | 1h | P1 |
| P2 | api_history 列名抽取为配置常量 | costcodev | 0.5h | P1 |
| P2 | .generation_records.json 加 fcntl.flock | costcodev | 0.5h | P1 |
| P2 | 前端搜索支持按「期号」精确查找 | costcodev | 0.5h | P1 |
| P2 | 生成记录增加「策略类型」筛选 | costcodev | 0.5h | P1 |
| P3 | statistics 页增加历史趋势可视化 | costcodev | 2h | P2 |
| P3 | lottery/ 目录自动清理策略 | costcodev | 1h | P2 |
| P3 | api_token 迁移至环境变量 | costcodev | 0.5h | P2 |
| P3 | monitoring 告警接入(Cron 失败飞书通知) | opengineer | 1h | P2 |
| P3 | 覆盖写入先写 .tmp 再 rename | costcodev | 0.5h | P2 |
| P3 | 部署文档同步(文档与实际状态对齐) | architect | 0.5h | P2 |
总工时: ~13h(含新增 P2/P3 项,不含已完成项)
13.2 关键里程碑
| 里程碑 | 交付物 | 负责人 |
|---|---|---|
| M1: 架构评审通过 | 本文档 | 梁思筑 |
| M2: 安全加固完成 | 代码更新 | 徐聪 |
| M3: 部署上线 | systemd + Cron | 严维序 |
| M4: 测试验收 | 测试报告 | QA |
| M5: 项目结项 | 总结文档 | 梁思筑 |
附录 A: 文件清单
| 文件 | 大小 | 行数 | 职责 | 修改建议 |
|---|---|---|---|---|
| app.py | 17KB | ~728 | Flask Web 主服务 | ✅ 路径安全加固已完成 |
| index.html | 42KB | ~1170 | 响应式前端 UI | 无 |
| lottery.py | 51KB | ~1100 | 号码生成核心逻辑 | 无 |
| fetch_data.py | 3.8KB | ~130 | 历史数据抓取 | ✅ 已有 MAX_RETRIES=3 重试 |
| web_executor.py | 6.4KB | ~220 | 数据抓取 Web 控制台 | 已有 timeout=300s,建议降到 120s |
| web_console.html | 11KB | ~300 | 抓取控制台前端 | 无 |
| deploy/fetch_daily.sh | 641B | ~15 | Cron 抓取脚本 | 加重试逻辑 |
| deploy/lotto-app.service | 494B | ~15 | systemd 服务文件 | ✅ 已部署并 active |
附录 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: 覆盖瞬间服务数据不可用(毫秒级,可接受)
- v2.0 增强: 覆盖写入前先写 .tmp 文件再 rename,避免极端场景数据损坏(沈路明建议,P3 实施)
ADR-LOTTO-005: 文件并发写入安全
- Context: threaded=True 下 .generation_records.json 并发写入可能丢数据
- Decision: 增加 fcntl.flock 文件锁保护并发写入
- Consequences: 零额外依赖,写入串行化;极端并发下写入有微秒级等待
ADR-LOTTO-006: API Token 安全管理
- Context: CONFIG 中 api_token 明文硬编码
- Decision: 迁移至环境变量 LOTTO_API_TOKEN
- Consequences: 配置更灵活,避免代码泄露 Token
文档作者: 梁思筑(Serena)
提交至: http://192.168.1.99:12299/vincent/EnterpriseArchitect.git
路径: docs/architecture/Lotto-Architecture-v1.md
评审请求: v2.0 已根据 v1.0 评审意见迭代,请产研团队复审