docs: 文档/配置/测试同步 NAS :8124 生产现状,清除 Node.js 与甲骨文残留

背景:文档停在 Node.js 时代或甲骨文 8123 部署,与生产(NAS :8124 + Flask +
auth-hub + ai-gateway)严重脱节,曾导致凭旧记忆误判'无线上环境'。

- CLAUDE.md 重写:技术栈/结构/命令/部署事实/关键坑(F7 button、UTC 日期、
  429 退避以 DB 为准、迁移幂等、AI 生成耗时)
- docs/ARCHITECTURE.md 重写为 Flask 蓝图+services+可插拔数据层 + NAS 部署
- docs/DEVELOPMENT.md 重写为 Flask/CRA 开发指南 + push.sh 部署流程
- docs/REQUIREMENTS.md:部署条目改 NAS 8124;补 auth-hub/AI 教练/新修复
- docs/AUTH_HUB_INTEGRATION.md 新增(补 .env.example 悬空引用)
- README.md:技术栈/DB/auth-hub/API 清单/部署节修正
- backend/config.py 与 .env.example:AUTH_HUB_REDIRECT_URI 默认 8123→8124,
  MariaDB 注释 Oracle→NAS
- tests:GatewayCourtesy 并发测试对齐 MAX_CONCURRENT(AI_JOB_CONCURRENCY=2);
  conftest 禁用 create_app 后台队列线程,修整库测试 flaky(585 passed)
This commit is contained in:
ericwyuan
2026-09-02 19:34:32 +08:00
parent 68363957ec
commit 2d9a2be185
12 changed files with 609 additions and 496 deletions

View File

@@ -1,167 +1,160 @@
# 项目架构说明
> 现状2026-09-02后端为 **Python/Flask 单体**,生产部署在 **NAS :8124**。
> 早期 Node.js/Express + `server/` 目录的后端已被整体重写为
> `backend/`Python本文件描述的是当前实现。
## 整体架构
```
┌─────────────────────────────────────────────────────┐
│ 浏览器 (React 前端)
┌────────────┬──────────────┬──────────┬──────────┐
│ 仪表板 │ 数据同步 │ 数据分析 │ 设置
└────────────┴──────────────┴──────────┴──────────┘
────────────────────────────────────────────────────
│ HTTP/REST
┌──────────────────────────────────────────────────────┐
│ Node.js/Express 后端服务器 │
┌──────────────────────────────────────────────┐
│ API 路由层 │
│ │ ├─ /auth - 用户认证
│ │ ├─ /garmin - Garmin 数据同步 │
│ ├─ /health - 健康数据查询 │
│ │ └─ /analysis - 数据分析
└──────────────────────────────────────────────┘
┌──────────────────────────────────────────────┐
│ │ 服务层
│ ├─ GarminService - Garmin API 集成 │
│ │ ├─ HealthService - 健康数据业务逻辑
│ │ ├─ AuthService - 认证授权
│ └─ AnalysisService - 数据分析 │
└──────────────────────────────────────────────┘
└──────────────────────┬───────────────────────────────┘
┌──────────────────────────────┐
│ SQLite 数据库
│ ├─ users
│ ├─ health_data │
│ ├─ activities
└─ sync_status
└──────────────────────────────┘
┌──────────────────────────────┐
│ Garmin Cloud API
│ ├─ 用户认证
│ ├─ 数据获取 │
│ └─ 数据同步 │
└──────────────────────────────┘
┌──────────────────────────────────────────────────────────
浏览器 (React SPA)
Framework7 9 (iOS 主题) + Recharts单端口同源部署
┌──────┬──────┬──────┬──────┬──────────────┐
│ 今日 │ 健康 │ 趋势 │ 运动 │ 设置 / 更多… │
└──────────────────────────────────────┘
└───────────────┬──────────────────────────────────────────┘
│ HTTP/JSON + JWTBearerCopilot 用 SSE
┌──────────────────────────────────────────────────────────┐
Flask 单体gunicorn 2w/4tNAS :8124
┌────────────────────────────────────────────────────┐
│ │ 蓝图层 routes/ /api/auth /api/garmin /api/health │ │
│ │ /api/analysis /api/settings │
├────────────────────────────────────────────────────┤
│ │ 服务层 services/ 业务逻辑(无 Flask 依赖) │ │
│ auth_hub_client garmin garmin_auth garmin_extras │
│ scheduler(定时同步) ai/coach/insights/jobs(AI队列) │
│ │ health analysis fitness_age settings scopes │ │
├────────────────────────────────────────────────────┤
│ │ 横切auth.py(JWT+require_auth) config.py(配置) │ │
│ │ db.py(可插拔数据层+幂等迁移) │ │
└────────────────────────────────────────────────────┘
───────┬──────────────────────────────┬──────────────────┘
│ │
┌──────────────────┐ ┌─────────────────────────────┐
│ SQLite (开发) │ │ MariaDB (生产, NAS 10.11) │
│ data/health.db │ │ garmin_health_lab 库
│ DB_TYPE=sqlite │ root@socket mysqld10.sock
└──────────────────┘ └─────────────────────────────┘
│ 外部系统(经 services/ 调用)│
▼ ▼
┌──────────────────────────────────────────────────────────┐
auth-hub SSO Garmin Cloud API ai-gateway │
129.146.26.249: (OAuth, 账号级限流 (https://ai.zichuan
│ 5300 OIDC 429 → 退避24h) .xyz/v1, NVIDIA推理)
└──────────────────────────────────────────────────────────┘
```
## 核心模块
- 单端口部署Flask 既出 API 又托管 React 构建产物(`STATIC_DIR`),前端 API
请求为**同源 `/api/*`**`client/.env.production` 写死 `REACT_APP_API_URL=/api`)。
- SPA 路由兜底404 handler 对非 `/api/` 路径回退 `index.html`,页面刷新不 404。
### 1. 前端 (Client)
- **框架**: React 18 + TypeScript
- **路由**: React Router
- **UI 组件**: 自定义 + CSS
- **数据可视化**: Recharts
- **API 通信**: Axios
## 后端分层
**主要页面**:
- Dashboard (仪表板)
- DataSync (数据同步)
- Analysis (数据分析)
- Recommendations (健康建议)
- Settings (设置)
### routes/ — 蓝图(只做参数校验与序列化,逻辑在 services
### 2. 后端 (Server)
- **框架**: Express.js
- **语言**: TypeScript
- **数据库**: SQLite3
- **认证**: JWT
| 蓝图 | 前缀 | 主要端点 |
|---|---|---|
| `auth` | `/api/auth` | SSO 登录 `POST /auth-hub/start``GET /callback``POST /logout``/refresh` |
| `garmin` | `/api/garmin` | `POST /sync``/sync-latest``/sync-details``GET /auth-status``/login-status``/status``/auto-sync``/activities/<id>/detail``POST /login``/mfa``/disconnect``DELETE /login` |
| `health` | `/api/health` | `summary` `steps` `heart-rate` `sleep` `activities` `fitness-age` `badges` `personal-records` `body-composition` `blood-pressure` `race-predictions` `series` `challenges` `devices` |
| `analysis` | `/api/analysis` | `trends` `recommendations` `models` `ai-recommendations` `briefing` `trend-insight` `copilot`(SSE) `insight` `insight/queue` |
| `settings` | `/api/settings` | `GET/PUT ""` `GET /options` `GET /rating-basis` |
**主要服务**:
- **AuthService**: 用户认证和授权
- **GarminService**: Garmin API 集成和数据获取
- **HealthService**: 健康数据管理
- **AnalysisService**: 数据分析和建议生成
另有 `GET /api/health/status`健康检查app.py 直接定义)。
### 3. 数据库 (SQLite)
**表结构**:
- `users`: 用户信息和 Garmin 凭证
- `health_data`: 每日健康数据汇总
- `activities`: 运动活动记录
- `sync_status`: 数据同步状态追踪
### services/ — 领域逻辑
- **garmin 同步族**`garmin.py`(核心同步 + 429 限流退避,`rate_limited_until`
持久化到 `sync_status`,以 DB 为准防多 worker 内存不一致)、`garmin_auth.py`
OAuth 登录 + MFA 验证码会话,跨 worker 经 `garmin_mfa_sessions` 表交接)、
`garmin_extras.py`(身体成分/血压/血氧/全天曲线/设备/徽章/挑战等扩展数据)。
- **调度与队列**`scheduler.py`自动同步DB `job_locks` 抢占,多 worker 只
跑一次)、`jobs.py`AI 生产者/消费者队列,同表互斥,含 `refill_backlog`)。
- **AI 教练**`insights.py` 特征工程z 分数 / 趋势斜率 / 活动量对比)→
`coach.py` 三套提示词 + 规则引擎兜底 → `ai.py` 多模型链 + 缓存
`ai_insights`(数据指纹失效)。
- 其余:`analysis.py` / `health.py` / `fitness_age.py`(身体年龄推算)/
`settings.py`(设置吸附校验)/ `auth_hub_client.py`OIDC 客户端)/
`scopes.py`
### 认证链路auth-hub SSO
```
用户点「登录」→ POST /api/auth/auth-hub/start → 302 auth-hub 登录页
→ auth-hub 回调 GET /api/auth/callback?code=… → exchange_code_for_token
→ get_userinfo → find_or_create_user → 后端签 JWT7 天)→ 前端存 localStorage
```
- 本地邮箱/密码注册登录**已移除**;每个账号来自 auth-hub。
- **Garmin 账号授权是独立流程**(与 Web 登录分开):`/api/garmin/login`
起 OAuth 流程,需要验证码时 `/api/garmin/mfa` 提交,令牌存
`garmin_tokens`(约一年有效,密码不入库)。
- 所有数据路由经 `require_auth` 校验 JWT。
### 数据层db.py 可插拔)
- `DB_TYPE=sqlite`(开发默认,`backend/data/health.db`)↔ `DB_TYPE=mariadb`
(生产 NAS`config.py` 读取,业务代码不分叉。
- 20 张表:`users health_data activities sync_status garmin_tokens badges
personal_records job_locks garmin_mfa_sessions user_settings
activity_details body_composition blood_pressure race_predictions
daily_series challenges devices ai_recommendations ai_jobs ai_insights`。
- `init_db()` 幂等(`CREATE TABLE IF NOT EXISTS` + 按列检测的 `ALTER`
多 gunicorn worker 并发调用安全。`id` 为 VARCHAR(64),外键指向 `users`。
## 数据流
### 1. Garmin 数据同步流程
### 1. Garmin 同步(手动 + 自动)
```
用户点击"同步"
POST /api/garmin/sync
GarminService.syncData()
获取 Garmin 授权 Token
调用 Garmin API 获取数据
转换数据格式
存储到本地 SQLite
更新 sync_status
返回同步结果
用户/调度器触发
→ 检查 sync_status.rate_limited_until429 冷却 24hDB 为准)
→ garmin.py 分批拉取health_data 每日指标、activities、daily_series 等)
→ 库内 upsert → 更新 sync_status → prefetch_insightsAI 洞察预取入队)
```
### 2. 数据分析流程
- 同步频率与历史范围history_days读用户设置`0` 表示全部历史 = 730 天上限。
- 全天曲线每天 5 个请求14 天内同步顺带拉取,更长的历史走「补齐详细数据」
后台任务(`POST /api/garmin/sync-details` 触发,队列消费)。
### 2. AI 晨报 / Copilot / 趋势归因
```
GET /api/health/summary (日期范围)
从数据库查询健康数据
计算统计指标
生成趋势分析
返回分析结果
前端绘制图表
AI 作业入队(ai_jobs优先级队列) → jobs.py worker 单飞消费(DB 互斥)
→ services/ai.py 调 ai-gateway(https://ai.zichuan.xyz/v1)
→ 结果写 ai_insights(指纹缓存) → 前端轮询 GET /analysis/insight 取回
Copilot: POST /analysis/copilot 走 SSE(stream_chat失败非流式回退)
```
### 3. 健康建议生成流程
```
GET /api/analysis/recommendations
AnalysisService.generateRecommendations()
分析历史数据
识别异常和趋势
根据规则引擎生成建议
按优先级排序
返回建议列表
```
- 生产 `AI_MODEL_CHAIN=gateway`(单上游,网关内部再 fanout NVIDIA/Gemini/Ollama
- 队列并发 2、间隔 5s共享网关过高会 502晨报一次生成 2~5 分钟,
前端一律「后台生成 + 轮询」而非阻塞等待;`meta.source` 标注 AI 或规则引擎。
## 关键特性
## 部署架构(生产)
### 安全性
- Garmin 密码使用加密存储
- JWT 令牌验证所有请求
- CORS 配置限制来源
- 环境变量管理敏感配置
- **Development 本地**`npm run dev` = Flask 5000`BACKEND_PORT` 固定,避开
`PORT` 被 CRA 抢占)+ CRA dev server 3000proxy → 5000。SQLite。
- **Production NAS**`192.168.50.64` `/volume1/web/garmin-health-lab`root
跑 gunicorn `0.0.0.0:8124`2w/4t--timeout 300 —— AI 生成可长达分钟级),
开机自启 DSM 任务调度器 → `deploy/S99garmin.sh`MariaDB 10.11
root@`/run/mysqld/mysqld10.sock`,库 `garmin_health_lab`。
- **公网**NAS frpc → 甲骨文 `129.146.26.249:8124`frp 隧道,重连需数秒)。
- **一键部署**`deploy/push.sh`tar-over-ssh 同步 backend+deploy、清空重推
`client/build`、sudo 重启、**pid 变化 + health 200 双校验**);前端先
`npm run build`。`deploy/start.sh` / `stop.sh` 供手工与开机脚本调用。
- **auth-hub**:回调注册 LAN `192.168.50.64:8124` 与公网 `129.146.26.249:8124`
两个地址client `996aLPw4T5gl-rYZ`CORS 白名单含两地址 + localhost。
### 扩展性
- 模块化的服务设计
- 易于添加新的分析算法
- 支持数据导出功能
- 可扩展的 API 端点
## 关键设计约束
### 性能
- 数据库索引优化查询
- API 缓存策略
- 异步处理长运行任务
- 增量数据同步支持
## 部署架构
- **Development 本地开发**`npm run dev` 同时启动前后端 —— Vite/React (3000/5173) + Flask (5000, SQLite)。
- **Production 生产环境**Oracle 云服务器 129.146.26.249
- Flask + gunicorn 直绑 `0.0.0.0:8123`systemd `garmin-health-lab.service`,开机自启)
- React SPA 静态包由后端 `STATIC_DIR` 提供
- MariaDB 10.3`127.0.0.1:3306`,专用账号 `garmin`,库 `garmin_health_lab`
- AI 网关 `129.146.26.249:5100/v1`
1. **阈值来自公开参考值AI 只解读不定阈值**;依据页面逐条列出处。
2. **本地日历日期**优先于 UTC`toISOString()` 陷阱见 DEVELOPMENT.md
3. **单点部署、无反向代理**:同端口 API + SPA减少一个故障面。
4. **多 worker 安全**:一切跨进程协调走 DBjob_locks / garmin_mfa_sessions /
ai_jobs / sync_status不做进程内假设。
5. **可插拔数据层**:业务代码不分叉 SQLite/MariaDB。