Files
GarminHealthLab/docs/ARCHITECTURE.md
ericwyuan 4331a07462 docs: docs/ 目录同步甲骨文部署事实(上一提交漏加)
上一个提交 git add 漏了 docs/,架构/开发/需求/auth-hub 集成文档里的 NAS
IP、8124 端口还是旧的,补上。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 23:53:26 +08:00

11 KiB
Raw Blame History

项目架构说明

现状2026-09-12后端为 Python/Flask 单体,生产部署在甲骨文云主机 garmin.zichuan.xyz,此前是 NAS :8124已整体迁移。 早期 Node.js/Express + server/ 目录的后端已被整体重写为 backend/Python本文件描述的是当前实现。

整体架构

┌──────────────────────────────────────────────────────────┐
│                    浏览器 (React SPA)                     │
│   Framework7 9 (iOS 主题) + Recharts单端口同源部署        │
│   ┌──────┬──────┬──────┬──────┬──────────────┐           │
│   │ 今日 │ 健康 │ 趋势 │ 运动 │ 设置 / 更多…  │           │
│   └──────┴──────┴──────┴──────┴──────────────┘           │
└───────────────┬──────────────────────────────────────────┘
                │ HTTP/JSON + JWTBearerCopilot 用 SSE
                ▼
┌──────────────────────────────────────────────────────────┐
│         Flask 单体gunicorn 2w/4t甲骨文云主机 :5500    │
│  ┌────────────────────────────────────────────────────┐  │
│  │ 蓝图层 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。

后端分层

routes/ — 蓝图(只做参数校验与序列化,逻辑在 services

蓝图 前缀 主要端点
auth /api/auth SSO 登录 POST /auth-hub/startGET /callbackPOST /logout/refresh
garmin /api/garmin POST /sync/sync-latest/sync-detailsGET /auth-status/login-status/status/auto-sync/activities/<id>/detailPOST /login/mfa/disconnectDELETE /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

另有 GET /api/health/status健康检查app.py 直接定义)。

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.pyAI 生产者/消费者队列,同表互斥,含 refill_backlog)。
  • AI 教练insights.py 特征工程z 分数 / 趋势斜率 / 活动量对比)→ coach.py 三套提示词 + 规则引擎兜底 → ai.py 多模型链 + 缓存 ai_insights(数据指纹失效)。
  • 其余:analysis.py / health.py / fitness_age.py(身体年龄推算)/ settings.py(设置吸附校验)/ auth_hub_client.pyOIDC 客户端)/ 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 (生产 NASconfig.py 读取,业务代码不分叉。
  • 21 张表:users health_data activities sync_status sync_history 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 sync_history 追加式记录每次自动/手动/立即同步结果,见数据流 §1
  • init_db() 幂等(CREATE TABLE IF NOT EXISTS + 按列检测的 ALTER 多 gunicorn worker 并发调用安全。id 为 VARCHAR(64),外键指向 users

数据流

1. Garmin 同步(手动 + 自动)

用户/调度器触发trigger_kind: auto/manual/quick
  → 检查 sync_status.rate_limited_until429 冷却 24hDB 为准)
  → garmin.py 分批拉取health_data 每日指标、activities、daily_series 等)
  → 库内 upsert → 更新 sync_status → prefetch_insightsAI 洞察预取入队)
  → 每次尝试(含被限流拒绝)都向 sync_history 追加一行(只读查询
    GET /api/garmin/sync-history前端「同步记录」页展示
  • 同步频率与历史范围history_days读用户设置0 表示全部历史 = 730 天上限。
  • 全天曲线每天 5 个请求14 天内同步顺带拉取,更长的历史走「补齐详细数据」 后台任务(POST /api/garmin/sync-details 触发,队列消费)。

2. AI 晨报 / Copilot / 趋势归因

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失败非流式回退)
  • 生产 AI_MODEL_CHAIN=gateway(单上游,网关内部再 fanout NVIDIA/Gemini/Ollama
  • 队列并发 2、间隔 5s共享网关过高会 502晨报一次生成 2~5 分钟, 前端一律「后台生成 + 轮询」而非阻塞等待;meta.source 标注 AI 或规则引擎。

部署架构(生产)

  • Development 本地npm run dev = Flask 5000BACKEND_PORT 固定,避开 PORT 被 CRA 抢占)+ CRA dev server 3000proxy → 5000。SQLite。
  • Production(甲骨文云主机,129.146.26.249/opt/garmin-health-lab ubuntu 用户跑 gunicorn 127.0.0.1:55002w/4t--timeout 300 —— AI 生成 可长达分钟级systemd 单元 garmin-health-lab.serviceenabled Restart=alwaysMariaDB 10.3.39 本机 TCP、独立账号 garmin,库 garmin_health_lab。和这台机器上的 ai-gateway/auth-hub/fam-edge 同一套 部署约定。此前2026-09-01~12部署在 NAS已整体迁移并下线。
  • 公网Caddy 反代 https://garmin.zichuan.xyz127.0.0.1:5500 TLS 自动签发。
  • 一键部署deploy/push_oracle.shssh key 认证同步 backend、清空重推 client/build、pip install、systemctl restart、PID 变化 + health 200 双校验);前端先 npm run build。NAS 时代的 deploy/push.sh / start.sh / stop.sh / S99garmin.sh 已废弃,保留仅供参考。
  • auth-hub:回调只注册 https://garmin.zichuan.xyz/auth/callback 一条client 996aLPw4T5gl-rYZCORS 白名单含该地址 + localhost。

关键设计约束

  1. 阈值来自公开参考值AI 只解读不定阈值;依据页面逐条列出处。
  2. 本地日历日期优先于 UTCtoISOString() 陷阱见 DEVELOPMENT.md
  3. 单点部署、无反向代理:同端口 API + SPA减少一个故障面。
  4. 多 worker 安全:一切跨进程协调走 DBjob_locks / garmin_mfa_sessions / ai_jobs / sync_status不做进程内假设。
  5. 可插拔数据层:业务代码不分叉 SQLite/MariaDB。