背景:文档停在 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)
221 lines
8.7 KiB
Markdown
221 lines
8.7 KiB
Markdown
# 佳明健康数据分析平台 (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/<activity_id>/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
|