Skip to content

xiaomi —— 小米运动健康(v1) ​

前缀:/api/v1/device/(URL 与 V1 平行) 代码:src/app/xiaomi/interface/api/xiaomi_routes.py + application/usecase/xiaomi_*.py 鉴权:callback / webhook 均公开(OAuth 重定向落点 + 官方推送无用户态;webhook 以验签鉴权) 官方文档:dev.mi.com/xiaomihyperos/documentation/detail?pId=2333(服务端接入指南)/ 2328(云服务接口)/ 2329(数据类型) 注册地址(申请清单逐字一致):回调 …/api/v1/device/xiaomi-callback · 数据变更回调 …/api/v1/device/xiaomi-webhook

发起授权 · POST /api/v1/device/vendors/xiaomi/auth-url ​

通用授权中心端点(registry 分发 → XiaomiVendor.cz_start_auth)——与 /vendors 页「去连接」按钮同源。

请求 body:{}(无需参数;userId 取自登录态 Bearer)

响应 data:

字段类型说明
authUrlstringhttps://account.xiaomi.com/oauth2/authorize?client_id=…&response_type=code&scope_provider=healthkit&scope=…&state=…
sessionKeystring会话标识(服务端 10min TTL 一次性 — callback 经 state 反查本地用户)
requireProfileCompletionbool恒 false(小米 token 响应自带 openId — 无需补资料)

错误码:

codeHTTP场景
40403006400XIAOMI_CLIENT_ID/SECRET 未配置(msg「该设备厂家正在审核中,敬请期待」+ data.vendor='xiaomi')
40403002401未登录
40403004404vendor 不存在

四态:未配置 → reviewing(授权页灰色「正在审核中」);配置就绪 → available。审批通过 = 配 env(XIAOMI_CLIENT_ID / XIAOMI_CLIENT_SECRET),零代码变更。

M2 · GET /api/v1/device/xiaomi-callback —— OAuth 回调 ​

Query:code(授权码 — 10 分钟单次)+ state(sessionKey);可选 error

流程:会话校验(一次性消费)→ POST account.xiaomi.com/oauth2/token(form-urlencoded;&&&START&&& 前缀剥离)→ openId → DeviceBinding upsert(vendor='xiaomi', vendor_user_id=<openId>, status=active, token JSON {accessToken, refreshToken, expiresAt, openId, unionId})→ 消费会话 → HTML 结果页(✅/❌ + 2s 自动关窗)。

绑定成功后 fire-and-forget(失败静默不阻塞绑定):订阅登记(POST /healthkit/subscribe/change 五事件)+ 首拉(采样 30 天 + dailies 7 天 + 睡眠记录 30 天 + 运动概要 5 天)。

HTML 文案分支(回调路径不抛 JSON 错误码):缺 code/state → 「缺少 code/state 参数」;会话过期 → 「会话已过期,请重新发起授权」;未配置 → 「该设备厂家正在审核中,敬请期待」;换 token 失败 → 「换取 token 失败:…」。

订阅推送 · POST /api/v1/device/xiaomi-webhook ​

Header:x-notification-signature = hex(HMAC-SHA256(openId_eventType_subType_eventTime, AppSecret))(官方逐字)

Body(单事件对象):{openId, eventTime, eventType, subType, metaData: [{metaKey, metaValue}]}

事件分发(官方 7.3 表):

eventType / subType处理
USER_EVENT / CANCEL_ALLDeviceBinding → expired(用户在小米侧解绑 — 引导重绑)
USER_EVENT / CHANGE_SCOPE记录日志(无表操作)
FITNESS_DATA_EVENT / FITNESS_AGGREGATED_DATA_EVENT / FITNESS_RECORD_DATA_EVENT / SPORT_RECORD_DATA_EVENT(UPDATE)反查用户 → 增量拉取(metaData startTime/endTime 窗口,clamp [1,30] 天)
未知类别skipped(不炸整批)

响应(成功恒 HTTP 200 — 官方 FAQ Q4):

json
{"code": "0", "msg": "ok", "data": {"handled": true, "eventCount": 1, "skipped": 0}}

错误码:

codeHTTP场景
40404001401验签失败(fail-loud — 公开端点不静默采信)
40404002400通知体非法(非 JSON / 缺事件字段)

数据同步语义(job / vendor — 对外可观测) ​

端点族窗口落库
dailies 聚合≤7 天(硬约束)DeviceDailyActivity(step/calories_kcal/active_min)+ SleepSummaryRecord(全天睡眠报告)
采样 dataPoints≤30 天HealthMetricRecord(HR/SPO2/STRESS/VO2MAX/REST_HR — 东八区日均)+ BodyCompositionRecord(体重)
healthRecords(睡眠)≤30 天HealthSleepRecord(三维幂等:user+calendarDate+sleepStartGMT)
sportRecords/all5 天必保DeviceRawEvent 暂存(sportId 幂等 — 消费管线后续棒)

每日 job:Celery beat daily(cz_xiaomi_sync_daily);token 临期(<30min)先 refresh 回写。推送为主通道,job 兜底。

解绑 ​

POST /api/v1/device action unbind(vendor='xiaomi')→ 远端 open.account.xiaomi.com/user/unbindOAuth2Grained 尽力撤销(code=0 唯一成功标志)→ 本地绑定删除(远端失败不阻塞 — fail-safe)。