Skip to content

device —— 设备共享域(v1) ​

统一前缀:/api/v1/device/ 覆盖:设备绑定读写(action 协议)+ 荣耀/Zepp/小米三厂商 OAuth + 授权中心四态 契约权威:src/app/device/interface/api/device_actions.py + src/app/honor/interface/api/honor_routes.py + src/app/zepp/interface/api/zepp_routes.py

子文档索引 ​

文档内容
vendors(授权中心)四态状态机端点 + 通用发起授权
honor(荣耀)M1 发起 / M2 回调 / 数据查询
zepp三端 OAuth / webhook 六事件 / legacy
本页共享 action 协议(myBindings / unbind / 数据查询)+ 公共约定

公共约定 ​

双路由风格 ​

风格端点说明
action 协议POST /api/v1/device + {action, payload}V1 客户端契约(URL 与 V1 平行)
独立 REST 路由POST /honor-register · GET /honor-callback · POST /zepp-auth-url · GET /zepp-callback · POST /zepp-webhook · GET /xiaomi-callback · POST /xiaomi-webhook · GET /vendors · POST /vendors/{id}/auth-url厂商 OAuth + 授权中心

统一信封 ​

  • 成功:{ "code": "0", "msg": "ok", "data": { ... } }(data camelCase — V1 契约)
  • 失败:{ "code": "4040xxxx", "msg": "..." }

错误码区段(三层隔离) ​

区段模块HTTP 映射
40401xxxhonor默认 400;USER_NOT_FOUND/ADOPT_SOURCE_MISSING → 404;UNCONFIGURED → 200(V1 兼容锚点)
40402xxxzepp默认 400;UNAUTHORIZED → 401;UNCONFIGURED → 200;WEBHOOK_BODY_MISSING → 415
40403xxxdevice 共享默认 400;NOT_FOUND/BINDING_NOT_FOUND → 404;UNAUTHORIZED → 401

device 共享错误码(40403xxx) ​

code语义HTTP
40403001请求参数非法(payload 校验 / 未知 action)400
40403002未登录 / token 无效401
40403004资源不存在404
40403005解绑目标绑定不存在404
40403006厂家审核中(预登记占位 vendor 授权尝试 →「该设备厂家正在审核中,敬请期待」)400
40403007厂商形态不支持该方法(Protocol v2 可选方法)400
40403008轨迹图生成失败(高德/COS 任一环 — 前端可重试,幂等无害)502
40403009该记录无轨迹数据(打卡类记录 — 前端显示「未记录轨迹」空态,勿重试)400
40403010运动记录删除失败(存储/COS 抖动 — 可重试幂等)502
40403011设备同步记录不可删(仅 App 记录可删 — 防同步复活;去设备侧删)400

共享 action(18 个) ​

  • 设备绑定与数据查询 5(本节详述:myBindings / unbind / listDeviceDailyActivity / myMetrics / mySleep)
  • 健康详情 10(sleepDetail / heartRateDetail / stressDetail / oxygenDetail / stepsDetail / caloriesDetail / strengthDetail / weightDetail / activitySummary / recordDates —— 契约见 home-api-reference)
  • 运动记录 3(sportRecords / sportRecordDetail / sportRecordShareImage —— 见下节)

myBindings —— 我的设备绑定 ​

{ "action": "myBindings", "payload": {} }

响应 data:{ "list": [BindingView] }

BindingView 字段(不含 token 载体 — 安全视图):

字段类型说明
idstring绑定 id
vendorstringhonor_oauth | zepp | …
statusstringactive | expired
vendorUserIdstring | null厂商侧用户标识(荣耀 openId / Zepp userId)
lastSyncAtstring | null最近一次「数据同步成功」时刻
createdAtstringISO

unbind —— 解绑 ​

{ "action": "unbind", "payload": { "vendor": "<vendor>" } }

payload 字段:vendor(1~40 字符,必填)

响应 data:{ "ok": true }

错误码:40403005(404)绑定不存在

listDeviceDailyActivity —— 每日活动 ​

{ "action": "listDeviceDailyActivity", "payload": { "vendor"?, "days"? } }

payload 字段:

字段类型必填默认约束
vendorstring | null⬜—按厂商过滤
daysint⬜301~180

响应 data:{ "list": [{id, vendor, date, step, distanceM, caloriesKcal, sleepMin, activeMin, source}], "days": <n> }(最多 200 条,date 倒序)

myMetrics —— 健康指标 ​

{ "action": "myMetrics", "payload": { "metricType", "start"?, "end"? } }

payload 字段:

字段类型必填说明
metricTypestring✅1~40 字符(如 HR / STRESS / VO2MAX / HRV / REST_HR)
start / endstring | null⬜YYYY-MM-DD 区间

响应 data:{ "list": [{id, userId, metricType, calendarDate, value, sport, level, ingestedAt}], "total": <n> }(最多 500 条,desc)

mySleep —— 睡眠记录 ​

{ "action": "mySleep", "payload": { "start"?, "end"? } }

响应 data:{ "list": [{id, userId, calendarDate, sleepStartGMT, sleepEndGMT, deepSleepSeconds, lightSleepSeconds, remSleepSeconds, awakeSleepSeconds, ingestedAt}], "total": <n> }(最多 500 条,desc)

运动记录三 action(2026-09-23 立项 / 2026-10-09 轨迹图扩展) ​

sportRecords —— 运动记录列表 ​

{ "action": "sportRecords", "payload": {} }

响应 data:{ "total", "summary": { totalDistanceKm, totalCalories, totalCount, **totalDurationSec**, **bySource** }, "groups": [{ month, summary, records: [{ id, kind, type, label, date, time, distanceKm, durationSec, pace, calories, avgHr, ..., **vendor**, **sourceLabel** }] }] }

totalDurationSec(秒)2026-10-09 补 —— 前端「累计运动用时」此前恒 -- 因该字段缺失。 vendor + sourceLabel(2026-10-10 运动数据闭环 Q23):每条记录透出数据来源 (app_run = "App 记录" / qingmu_band = "青沐手环" / ...;后端统一中文标签)。 summary.bySource(Q24):{ app: {totalCount,totalDistanceKm,totalDurationSec,totalCalories}, device: {...} } 分类小计(混合主口径不变,纯增量 — 旧前端零感知)。App 跑步记录(startRun/submitRun 提交归一)与手环记录同列表出现。

sportRecordDetail —— 单条运动详情(含轨迹 + 富曲线) ​

{ "action": "sportRecordDetail", "payload": { "externalId": "<列表项 id>" } }

字段类型必填说明
externalIdstring✅记录 id(sportRecords 列表项 id 回传)

响应 data 全字段(2026-10-10 v3 实测样例 — app_run 记录):

jsonc
{
  "record": {
    "id": "run-3af1dec50c20",
    "type": "户外跑步", "symbol": "跑",
    "time": "10/10 下午3:40",
    "durationText": "07:39", "durationSec": 459,
    "distanceKm": 0.74, "calories": null, "avgHr": null,
    "vendor": "app_run", "sourceLabel": "App 记录"    // ← 2026-10-10 来源标识(Q23)
  },
  "track": [[28.17319, 112.92432, 1791618005700], …],  // 清洗后轨迹点 [lat,lng,ts];打卡类 → null
  "trackCenter": { "latitude": 28.1732, "longitude": 112.9243 },
  "vendor": "app_run",
  "imageUrl": "https://cos-cdn.qingmulife.cn/share/sport/run-3af1dec50c20.png",  // ← 轨迹图地址(2026-10-10)
  "imageReady": true,                              // ← 就绪探测(未生成=false,地址仍给)
  "location": {                                        // ← v3:开跑位置(app_run 有;旧行 null)
    "province": "湖南省", "city": "长沙市", "area": "岳麓区",
    "address": "…西二环辅路 靠近湘江科创基地一期", "lng": "112.92…", "lat": "28.17…"
  },
  "detail": {
    "metrics": {                        // 汇总指标(app_run 清洗产出 + 设备侧聚合)
      "reportedDistanceM": 743.7,       // 上报口径(列表/卡片主口径 Q18)
      "cleanedDistanceM": 554.8,        // 去漂移后(披露)
      "removedWildPoints": 3, "simplifiedFrom": 223, "simplifiedTo": 18,   // 清洗管线自述
      "avgHr": 104, "maxHr": 125, "steps": 2807,        // 以下设备侧聚合(app_run 无源 → 缺省)
      "avgStride": 73, "maxStride": 78, "avgCadence": 88, "maxCadence": 94,
      "avgPaceSec": 426, "maxPaceSec": 369,
      "totalAscent": 78, "totalDescent": 78, "maxElevation": 70, "minElevation": 44,
      "totalCalories": 530, "hrRecoveryStart": 12, "hrRecoveryEnd": 15, "hrRecovery": -3.0
    },
    "series": {                         // 六条逐分钟曲线(与 track 点数非一一对应,分开画)
      "pace": [437, 393, …],            // 秒/公里(Y 轴反向:越小越快)
      "heart": [27, 55, …], "stride": […], "cadence": […], "recovery": […], "elevation": […]
    },
    "zones": [{ "time": "00:07:09", "label": "热身", "percent": 22 }, …],   // 心率区间
    "laps":  [{ "label": "<1", "value": "7'49\"", "percent": 79 }, …]       // 每公里分段
  }
}
顶层说明
record.vendor / record.sourceLabel数据来源(app_run="App 记录" / qingmu_band="青沐手环" / …,后端统一文案)
track清洗后轨迹(DP 抽稀;[lat, lng, epoch_ms])— 地图 polyline 直接用
location开跑位置(v3 — 详情地址条数据源;同时是轨迹图地址行兜底)
imageUrl + imageReady轨迹图字段(2026-10-10 增 — 前端详情页直显用):imageUrl = COS 确定性地址(share/sport/{externalId}.png,未生成也照给);imageReady = 就绪探测(COS HeadObject;未生成/探测异常 → false——探测失败不拖垮详情)。未配置 COS 的环境 imageUrl: null
detail.metricsapp_run:双口径距离 + 清洗自述 + (无源的设备指标缺省);设备记录:设备侧聚合全量
detail.series逐分钟六曲线(pace/heart/stride/cadence/recovery/elevation — 六键恒存在,缺源给空数组);app_run 现算 pace,无源五键诚实空数组
detail.zones / laps心率区间(Karvonen)/ 每公里分段;app_run 现算 laps

⚠️ v1 旧数据 / 空态:detail 四键恒存在,但可能为空态(metrics: {} / series: {} / zones: [] / laps: [])——前端画图前判空。打卡类记录 track: null。

错误:40403004 记录不存在(含跨用户 — 归属校验内建)。

sportRecordShareImage —— 运动轨迹图(高德地图) ​

{ "action": "sportRecordShareImage", "payload": { "externalId", "name"?, "location"? } }

字段类型必填说明
externalIdstring✅记录 id(sportRecords 列表项 id 回传)
namestring⬜记录名(如「周日夜跑 · 麓松路沿线」)→ 卡片首行显示
locationobject⬜{provice, city, area, address, lng, lat} → 卡片地址行(provice 沿前端拼写,后端兼容 province)

响应 data:{ "imageUrl": "<COS/CDN PNG URL>", "pointCount": <int> }

图片形态(1200×1600 PNG):高德底图 + 红色轨迹线 + 起终点圆点(后端 Web Mercator 投影自画)+ 底部九参数数据卡片(记录名/距离大字/时长/配速/类型/平均心率/卡路里/地址/日期+品牌)。配速后端现算(distance÷duration — 记录顶层无该字段)。幂等:COS key share/sport/{externalId}.png 覆盖写;前端直接用 <image :src> 展示(细节见 前端对接页)。

错误:40403004 记录不存在 / 40403008 生成失败(可重试)/ 40403009 无轨迹(打卡类空态)。

厂商注入 action(同 URL) ​

action归属说明
honorAdoptBindinghonor荣耀绑定切换(见 honor)
zeppAuthUrlzepp生成 Zepp 授权 URL(未配置不抛错 → {url:'', configured:false},见 zepp)

未知 action → 40403001(unknown action: <x>)。

数据落库映射(共享表) ​

表写入方关键幂等键
DeviceBindinghonor/zepp 绑定(user_id, vendor)
DeviceDailyActivityhonor vendor='honor' / zepp vendor='zepp'(user_id, vendor, date) upsert
HealthMetricRecord双厂商(user_id, metricType, calendarDate)
HealthSleepRecordhonor(user_id, calendar_date) 存在即跳过(不做同日修正)
SleepSummaryRecordzepp legacy(user_id, date) upsert
BodyCompositionRecord双厂商体重(user_id, timestamp) 查重
DeviceRawEventzepp webhook 暂存track_id / summaryId