跳转到主要内容

什么是 Provider?

Provider 是将 Mirobody Health 与外部健康和健身设备 API 集成的插件模块。每个 Provider 负责处理 OAuth 身份验证、数据获取以及将数据转换为标准化格式。

Provider 架构

Mirobody Health 使用可扩展的 Provider 架构,使添加新的健康设备集成变得非常容易:
BaseThetaProvider (抽象基类)

ThetaGarminProvider (OAuth1 实现)
ThetaWhoopProvider (OAuth2 实现)
YourCustomProvider (您的自定义实现)

核心组件

身份验证处理程序

管理 OAuth 1.0 或 OAuth 2.0 流程,实现安全的用户授权

数据拉取器

从厂商 API 获取健康数据,支持分页和速率限制

数据格式化程序

将厂商特定的数据转换为标准化的健康指标

数据库服务

持久化存储原始数据和格式化数据,以便进行审计追踪

可用 Provider

Garmin Connect

身份验证: OAuth 1.0
状态: 生产就绪 (Production Ready)
数据类型: 活动、睡眠、心率、HRV、呼吸、压力、身体成分
Garmin Connect 是最全面的健康数据源之一,提供来自 Garmin 可穿戴设备和健身设备的详细指标。

Whoop

身份验证: OAuth 2.0
状态: 生产就绪 (Production Ready)
数据类型: 睡眠、恢复、周期、锻炼、身体测量
Whoop 提供全面的恢复和负荷指标,深受运动员和健身爱好者的欢迎。

Provider 生命周期

理解 Provider 的生命周期有助于您更有效地开发和使用它们:
1

初始化

应用程序启动时,会自动从 THETA_PROVIDER_DIRS 指定的目录中发现并初始化 Provider。
config.yaml
THETA_PROVIDER_DIRS:
  - mirobody/pulse/theta
2

注册

系统会调用每个 Provider 的 create_provider() 类方法来验证配置并创建实例。
@classmethod
def create_provider(cls, config: Dict[str, Any]) -> Optional['ThetaYourProvider']:
    if not cls._validate_config(config):
        return None
    return cls()
3

用户绑定

当用户发起绑定时,Provider 的 link() 方法会生成一个 OAuth 授权 URL。
async def link(self, request: LinkRequest) -> Dict[str, Any]:
    # 生成 OAuth 授权 URL
    return {"link_web_url": authorization_url}
4

身份验证

用户授权后,Provider 的 callback() 方法会交换 Token 并保存凭据。
async def callback(self, code: str, state: str) -> Dict[str, Any]:
    # 使用 code 交换 token
    # 将凭据保存到数据库
    # 触发初始数据拉取
    return {"provider_slug": self.info.slug, "stage": "completed"}
5

数据同步

Provider 会定期或按需从厂商 API 拉取数据,保存原始数据,进行格式化,并推送到平台。
async def _pull_and_push_for_user(self, credentials: Dict) -> bool:
    # 从厂商 API 拉取数据
    # 保存原始数据
    # 转换为标准格式
    # 推送到平台
    pass
6

取消绑定

当用户取消绑定时,Provider 的 unlink() 方法会撤销访问权限并删除存储的凭据。
async def unlink(self, user_id: str) -> Dict[str, Any]:
    # 在厂商处撤销访问权限 (可选)
    # 从数据库中删除凭据
    return {"success": True}

标准化健康指标

所有 Provider 收集的数据都会转换为标准化的健康指标,以确保不同设备之间的一致性:
  • DAILY_STEPS: 步数
  • DAILY_DISTANCE: 行进距离(米)
  • DAILY_CALORIES_ACTIVE: 活动消耗卡路里(kcal)
  • DAILY_CALORIES_BASAL: 基础代谢卡路里(kcal)
  • DAILY_FLOORS_CLIMBED: 爬楼层数
  • ACTIVE_TIME: 活动时长(分钟)
  • HEART_RATE: 瞬时心率 (bpm)
  • DAILY_HEART_RATE_MIN: 每日最低心率 (bpm)
  • DAILY_HEART_RATE_MAX: 每日最高心率 (bpm)
  • DAILY_AVG_HEART_RATE: 每日平均心率 (bpm)
  • DAILY_HEART_RATE_RESTING: 静息心率 (bpm)
  • HRV: 心率变异性 RMSSD (ms)
  • DAILY_SLEEP_DURATION: 总睡眠时长 (ms)
  • SLEEP_IN_BED: 在床时间 (ms)
  • DAILY_AWAKE_TIME: 睡眠期间清醒时间 (ms)
  • DAILY_LIGHT_SLEEP: 浅睡时长 (ms)
  • DAILY_DEEP_SLEEP: 深睡时长 (ms)
  • DAILY_REM_SLEEP: REM 睡眠时长 (ms)
  • SLEEP_EFFICIENCY: 睡眠效率百分比
  • WEIGHT: 体重 (kg)
  • HEIGHT: 身高 (m)
  • BMI: 身体质量指数
  • BODY_FAT_PERCENTAGE: 体脂率
  • SKELETAL_MUSCLE_MASS: 骨骼肌重量 (kg)
  • RESPIRATORY_RATE: 呼吸频率 (次/分钟)
  • BLOOD_OXYGEN: 血氧饱和度 (SpO2) 百分比
  • WORKOUT_DURATION_LOW: 低强度锻炼时长 (分钟)
  • WORKOUT_DURATION_MEDIUM: 中强度锻炼时长 (分钟)
  • WORKOUT_DURATION_HIGH: 高强度锻炼时长 (分钟)
  • ALTITUDE_GAIN: 爬升高度 (米)
  • SPEED: 平均速度 (m/s)
有关标准化指标的完整列表,请参阅 Mirobody 框架文档中的 StandardIndicator 枚举。

Provider 要求

为确保质量和一致性,所有 Provider 必须实现以下内容:
class ThetaYourProvider(BaseThetaProvider):
    @classmethod
    def create_provider(cls, config: Dict) -> Optional['ThetaYourProvider']:
        """用于实例化 Provider 的工厂方法"""
        
    @property
    def info(self) -> ProviderInfo:
        """Provider 元数据"""
        
    async def link(self, request: Any) -> Dict[str, Any]:
        """发起 OAuth 流程"""
        
    async def callback(self, *args, **kwargs) -> Dict[str, Any]:
        """处理 OAuth 回调"""
        
    async def unlink(self, user_id: str) -> Dict[str, Any]:
        """取消用户关联"""
        
    async def format_data(self, raw_data: Dict) -> StandardPulseData:
        """将原始数据转换为标准格式"""
        
    async def pull_from_vendor_api(self, *args, **kwargs) -> List[Dict]:
        """从厂商 API 拉取数据"""
        
    async def save_raw_data_to_db(self, raw_data: Dict) -> List[Dict]:
        """将原始数据保存到数据库"""
        
    async def is_data_already_processed(self, raw_data: Dict) -> bool:
        """检查数据是否已处理"""
        
    async def _pull_and_push_for_user(self, credentials: Dict) -> bool:
        """为用户拉取并推送数据"""

Provider 发现机制

Mirobody Health 会从配置的目录中自动发现并加载 Provider:
# 在 config.localdb.yaml 中
THETA_PROVIDER_DIRS:
  - mirobody/pulse/theta     # 系统内置 Provider
  - mirobody/pulse/custom    # 您的自定义 Provider
目录结构:
mirobody/pulse/theta/
├── mirobody_garmin_connect/
│   ├── __init__.py
│   └── provider_garmin.py
├── mirobody_whoop/
│   ├── __init__.py
│   └── provider_whoop.py
└── mirobody_your_provider/
    ├── __init__.py
    └── provider_your_provider.py
为保持一致性,Provider 目录名称应遵循 mirobody_<provider_name> 模式。

最佳实践

  • 外部 API 调用始终使用 try-except 代码块
  • 记录带有上下文的错误日志(用户 ID、时间戳、错误详情)
  • 失败时返回空结果,而不是导致程序崩溃
  • 对瞬时错误实现指数退避 (Exponential backoff)
  • 优雅地处理速率限制
  • 使用前验证 OAuth Token 的有效性
  • 在转换前检查数据类型
  • 优雅地处理缺失值或 null 值
  • 验证时间戳格式
  • 验证单位是否符合预期值
  • 所有 I/O 操作使用 async/await
  • 为大型数据集实现分页
  • 限制对厂商 API 的并发请求数
  • 缓存频繁访问的配置项
  • 使用数据库连接池
  • 切勿记录敏感数据(Token、密码)
  • 在数据库中加密存储凭据
  • 所有的外部通信都使用 HTTPS
  • 验证 OAuth state 参数
  • 为 OAuth 2.0 实现 Token 刷新机制

下一步