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:
@@ -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 + JWT(Bearer);Copilot 用 SSE
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ Flask 单体(gunicorn 2w/4t,NAS :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 → 后端签 JWT(7 天)→ 前端存 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_until(429 冷却 24h,DB 为准)
|
||||
→ garmin.py 分批拉取(health_data 每日指标、activities、daily_series 等)
|
||||
→ 库内 upsert → 更新 sync_status → prefetch_insights(AI 洞察预取入队)
|
||||
```
|
||||
|
||||
### 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 3000(proxy → 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 安全**:一切跨进程协调走 DB(job_locks / garmin_mfa_sessions /
|
||||
ai_jobs / sync_status),不做进程内假设。
|
||||
5. **可插拔数据层**:业务代码不分叉 SQLite/MariaDB。
|
||||
|
||||
Reference in New Issue
Block a user