# 佳明健康数据分析平台 (Garmin Health Lab) 一个完整的健康数据分析平台,用于获取、分析和可视化你的佳明(Garmin)设备数据。 ## 📊 主要功能 - **数据同步**: 通过 Garmin API 自动同步你的健康数据 - **综合分析**: 步数、心率、睡眠、运动、压力等多维度分析 - **数据可视化**: 交互式图表和仪表板展示健康数据趋势 - **智能建议**: 基于数据分析的个性化健康建议 - **数据导出**: 支持数据导出为 CSV/JSON 格式 ## 🛠 技术栈 ### 前端 - React 18 + TypeScript(CRA / react-scripts 构建) - Framework7 9(`framework7-react`,iOS 风格 UI) - Recharts (数据可视化) - Axios (API 请求,JWT 拦截器) ### 后端 - Python 3.10+ + Flask(应用工厂) - Gunicorn(生产运行,NAS :8124) - 可插拔数据层:SQLite(本地开发)/ MariaDB(生产,NAS 10.11 库 `garmin_health_lab`,经 PyMySQL/socket) - JWT 鉴权;登录走 auth-hub 统一 SSO(OAuth2/OIDC,本地密码登录已移除) - garminconnect(Garmin API 集成) > 早期 Node.js/TypeScript 后端(`server/`)已整体重写为 `backend/` 下的 > Python/Flask 单体,目录已删除。 ## 📁 项目结构 ``` GarminHealthLab/ ├── client/ # 前端应用 (React 18 + TypeScript) │ └── src/services/api.ts # API 客户端(含 JWT 拦截器) ├── backend/ # 后端应用 (Python + Flask) —— 当前可运行实现 │ ├── app.py # 应用工厂 / 路由装配 │ ├── wsgi.py # Gunicorn 入口 │ ├── config.py # 配置(读 .env) │ ├── db.py # 可插拔数据层 (SQLite / MariaDB) │ ├── auth.py # JWT 签发/校验 + require_auth │ ├── services/ # 业务逻辑 (health / analysis / garmin) │ ├── routes/ # 蓝图 (auth / garmin / health / analysis) │ ├── tests/smoke.py # 冒烟测试 │ ├── requirements.txt │ └── .env.example ├── server/ # (已删除)原 Node/TS 后端,已重写为 backend/ ├── docs/ # 文档 ├── package.json # 工作空间根配置 └── README.md ``` ## 🚀 快速开始 ### 前置要求 - Node.js 18+ - npm 或 yarn - Garmin Connect 账户 ### 安装依赖 前端(React): ```bash npm install ``` 后端(Python/Flask,建议在虚拟环境中安装): ```bash cd backend python -m venv venv && source venv/bin/activate pip install -r requirements.txt ``` ### 配置环境变量 复制 `backend/.env.example` 为 `backend/.env` 并填入真实值(`.env` 已被 `.gitignore` 忽略,不会进入版本库): ```env PORT=5000 DB_TYPE=sqlite # 本地开发用 sqlite;生产切 mariadb DATABASE_PATH=./data/health.db JWT_SECRET=your_jwt_secret_here # 生产务必更换 CORS_ORIGIN=http://localhost:3000,http://localhost:5173 ``` > 早期 Node 后端的配置方式已弃用,一律使用 `backend/.env`。 ### 数据库:SQLite / MariaDB 可插拔 数据层通过 `DB_TYPE` 环境变量切换后端,**业务代码无需改动**: - **SQLite(默认,本地开发)**:零配置,由 `DATABASE_PATH` 指定文件位置。 - **MariaDB(生产)**:运行于 NAS(192.168.50.64)本地 MariaDB 10.11,root 经 socket `/run/mysqld/mysqld10.sock`(或 TCP `127.0.0.1:3306`)连接(PyMySQL), 独立库 `garmin_health_lab`。完整配置见 `backend/.env.example`: ```env DB_TYPE=mariadb MARIADB_SOCKET=/run/mysqld/mysqld10.sock MARIADB_HOST=127.0.0.1 MARIADB_PORT=3306 MARIADB_USER=root MARIADB_PASSWORD=your_production_mariadb_password MARIADB_DATABASE=garmin_health_lab ``` > 复制 `backend/.env.example` 为 `backend/.env` 并填入真实值;`.env` 已被 `.gitignore` 忽略,不会进入版本库。 ### 启动开发服务器 后端(Flask,端口 5000): ```bash cd backend source venv/bin/activate python app.py # 或生产方式:gunicorn wsgi:app -b 0.0.0.0:5000 ``` 前端(React,CRA 开发服务器,proxy 到后端 5000): ```bash npm install # 在仓库根目录执行(workspaces 装 client) npm run dev # 同时起后端 5000 + 前端 3000(或分别 dev:backend / dev:client) ``` - 前端: http://localhost:3000 - 后端 API: http://localhost:5000/api > 登录走 auth-hub SSO:点登录会跳到统一认证页,回跳后后端签 JWT。 > 数据库首次启动自动建表(`db.init_db()`,幂等)。 ### 冒烟测试 ```bash cd backend python tests/smoke.py ``` ## 📚 API 文档 ### 认证(auth-hub 统一 SSO) - `POST /api/auth/auth-hub/start` - 发起 SSO 登录(返回 auth-hub 授权 URL) - `GET /api/auth/callback?code=…` - auth-hub 回调(浏览器跳转,签发 JWT) - `POST /api/auth/refresh` - 刷新 Token - `POST /api/auth/logout` - 登出 > 本地邮箱/密码注册登录已移除;账号体系由 auth-hub 统一提供 > (见 [AUTH_HUB_INTEGRATION.md](docs/AUTH_HUB_INTEGRATION.md))。 > Garmin 账号的绑定/解绑走 `/api/garmin/*`(见下),与 Web 登录相互独立。 ### Garmin 数据同步 - `POST /api/garmin/login` - 发起 Garmin OAuth 授权(需验证码时 `POST /api/garmin/mfa` 提交) - `GET /api/garmin/auth-status` / `GET /api/garmin/login-status` - 授权状态 - `DELETE /api/garmin/login` / `POST /api/garmin/disconnect` - 退出/解绑 Garmin 账号 - `POST /api/garmin/sync` - 同步 Garmin 数据 - `POST /api/garmin/sync-latest` - 仅同步最新数据 - `POST /api/garmin/sync-details` - 补齐运动详情/全天曲线(后台队列) - `GET /api/garmin/status` / `GET /api/garmin/auto-sync` - 同步状态 - `GET /api/garmin/activities//detail` - 单次运动详情 ### 健康数据 - `GET /api/health/summary` - 获取健康摘要 - `GET /api/health/steps` - 获取步数数据 - `GET /api/health/heart-rate` - 获取心率数据 - `GET /api/health/sleep` - 获取睡眠数据 - `GET /api/health/activities` - 获取运动数据 - `GET /api/health/fitness-age` - 身体年龄 - `GET /api/health/series` - 全天曲线(心率/压力/身体电量/呼吸/血氧) - `GET /api/health/body-composition` / `blood-pressure` / `race-predictions` - `GET /api/health/badges` / `personal-records` / `challenges` / `devices` ### 分析与建议 - `GET /api/analysis/trends` - 获取数据趋势 - `GET /api/analysis/recommendations` - 规则引擎健康建议 - `GET /api/analysis/models` - 可用大模型及其配置状态 - `GET /api/analysis/ai-recommendations` - 大模型健康建议(带缓存) ### AI 教练 - `GET /api/analysis/briefing` - 晨间简报 + 今日运动处方,附计算出的特征上下文 - 立即返回。若没有匹配当前数据的模型答案,先返回规则版并带上 `meta.pending`,模型版本在后台生成,再次请求即可取到 - `?date=` 指定日期(默认最新有数据的一天)、`?refresh=1` 忽略缓存、 `?wait=1` 阻塞等待模型(一次生成 2~5 分钟) - `GET /api/analysis/trend-insight?metric=&startDate=&endDate=` - 对选定区间内 单个指标的变化做归因分析(阻塞,未知指标返回 400 并附 `supported` 列表) - `POST /api/analysis/copilot` - 健康 Copilot 问答,SSE 流式返回 - 请求体 `{question, history?, date?, model?}` - 事件序列 `start` → `delta`* → `done`,失败时为 `error` > AI 相关接口全部经由自建 **ai-gateway**(OpenAI 兼容,`https://ai.zichuan.xyz/v1`, > 见 `AI_GATEWAY_BASE_URL`)。 > 该网关的主上游是大型推理模型,一次生成实测需 2~5 分钟,因此简报走后台生成 + > 轮询,趋势归因与 Copilot 走显式触发;任一模型失败时降级为规则引擎, > `meta.source` 会说明本次由谁作答。 ## 🚀 部署(生产 = NAS) - **位置**:NAS `192.168.50.64` `/volume1/web/garmin-health-lab`,root 跑 gunicorn `0.0.0.0:8124`(2 workers / 4 threads / --timeout 300),开机自启 DSM 任务调度器执行 `deploy/S99garmin.sh`。 - **公网**:NAS frpc → `http://129.146.26.249:8124`。 - **一键部署**:先 `npm run build`,再 `./deploy/push.sh`(同步 backend + deploy + 清空重推 `client/build` + sudo 重启 + pid/health 双校验)。 - **部署排障前**:先读 `PROGRESS.md` 与 `deploy/` 脚本确认事实。 ## 🔐 安全说明 - Garmin 账户密码不入库(令牌授权,约一年有效) - 所有 API 请求需要 JWT 认证(auth-hub SSO 签发) - 敏感数据不在前端存储;`backend/.env` 含密钥,不提交 git ## 📝 开发指南 详见 [DEVELOPMENT.md](./docs/DEVELOPMENT.md) 与 [ARCHITECTURE.md](./docs/ARCHITECTURE.md) ## 📄 许可证 MIT