Skip to content

前端联调 API 手册(v1) ​

归属:API Docs → v1 → 前端联调手册 覆盖:30 个端点 — 前端 src/api/*.ts(6 文件)+ src/utils/request.ts 拦截器 + 3 条页面直调 字段事实源:tests/reports/contract-matrix.json(2026-09-20 前后端逐字段比对) 错误码权威:错误码注册表 配套文档:API 联通矩阵 · 联调问题台账

本手册按前端源码文件分组 —— 你打开哪个 .ts,就看哪一页。每个端点四块:摘要 / curl / 请求字段表 / 响应字段表 + 错误码表。

30 端点总表 ​

页对应前端文件端点数
login.mdsrc/api/login.ts + src/utils/request.ts7
user.mdsrc/api/user.ts7
device.mdsrc/api/device.ts4
training.mdsrc/api/training.ts5
splash.mdsrc/api/splash.ts2
agreement.mdsrc/api/agreement.ts2
page-direct.md页面直调(不走 src/api/*.ts)3
合计30

🔢 字段「值」速查:values.md · 取值速查 —— 本手册各页回答「字段叫什么、什么类型、必填吗」; 那个页回答「填这个值会发生什么」(每个枚举/边界值的业务后果 + 缺省行为)。 起因:2026-09-20 真实联调 BUG —— habits.exerciseFreq 只写「0–7」,前端不知 0 是有效值(写 0 就通了),卡住联调。根因不是代码,是文档只给范围不给语义。


⭐ 另有 19 个新接口(不在上面 30 个内,但你可能要用) ​

2026-09-20 新增 —— 为当时还是硬编码假数据的页面补的数据源。 接线前必读:接线必读 · 7 条兜底 —— 不做兜底会白屏(withDefaults 只补 undefined 不补 null)。

批次数量端点覆盖页面
device 健康详情10POST /api/v1/device:sleepDetail / heartRateDetail / stressDetail / oxygenDetail / stepsDetail / caloriesDetail / strengthDetail / weightDetail / activitySummary / recordDatesdetail/ 14 页 + index/ 9 组件
sport 运动会话6POST /api/v1/sport:startRun / reportRunCoordinate / finishRun / myRunRecords / runSessionDetail / addWeightRecordrun/ + mine/sports/ + 加体重表单
health_archive 健康档案3POST /api/v1/health-archive:archive / saveHealthIncident / saveHeartRateZonesmine/info/ 7 组件
simulation 仿真数据3POST /api/v1/simulation:simulateData / clearSimulatedData / statusDev 库专用(Prd 会拒)

📖 Dev / Prd 两个库有什么区别 → 双库对照(结论:契约完全一致,区别只在 Dev 有仿真数据) 📋 逐字段契约证据 → tests/reports/contract-audit-*.md(1036 行,19 个 action 每个字段的对照)


#方法路径action所在页
1POST/api/v1/auth/oauthcaptchalogin
2POST/api/v1/auth/oauthsms-sendlogin
3POST/api/v1/auth/oauthsms-loginlogin
4POST/api/v1/auth/oauthrotatelogin
5POST/api/v1/auth/oauthmySessionslogin
6POST/api/v1/auth/oauthrevokeSessionlogin
7POST/api/v1/auth/oauthlogoutlogin
8POST/api/v1/userupdateBasicProfileuser
9POST/api/v1/usersubmitOnboardinguser
10POST/api/v1/useronboardingSuggestionuser
11POST/api/v1/useradoptPlanOptionuser
12POST/api/v1/usercompleteOnboardinguser
13POST/api/v1/usermyProfileuser
14POST/api/v1/userupdateProfileuser
15GET/api/v1/device/vendors—device
16POST/api/v1/device/vendors/{id}/auth-url—device
17POST/api/v1/devicemyBindingsdevice
18POST/api/v1/deviceunbinddevice
19GET/api/v1/training/plans—training
20POST/api/v1/training/enroll—training
21GET/api/v1/training/tasks—training
22POST/api/v1/training/task/checkin—training
23GET/api/v1/training/reminders—training
24GET/api/v1/splash-screen—splash
25POST/api/v1/splash-screen/track—splash
26GET/api/v1/content/agreements—agreement
27GET/api/v1/content/agreements/{type}—agreement
28POST/api/v1/devicemyTodayHealthpage-direct
29POST/api/v1/statsgetTodayOverviewpage-direct
30POST/api/v1/upload?type=avatar—page-direct

通用约定(7 条 · 所有端点共用) ​

G1 · 统一包络 —— request.ts 不剥壳 ​

json
{ "code": "0", "msg": "ok", "data": { /* 业务字段全在这里 */ } }
  • 前端 resolve(body) 拿到的是完整 body(含 code / msg / data),业务字段一律在 body.data 之下 → 调用点写 res.data.xxx。
  • 业务码是字符串:"0" 成功、"40101012" 失败。前端用 String(body?.code) === '0' 判断,不要用 === 0。

G2 请求头 ​

header值说明
X-EnvDev | Prd(大小写不敏感)双库路由。全程必须一致 —— token 绑 X-Env,跨库即失效。主请求 + rotate 都要带
AuthorizationBearer <access>默认档位;rotate 例外(见下)
Content-Typeapplication/jsonaction 协议与 REST body 都走 JSON;upload 例外(multipart)

⚠️ 本手册所有示例统一用 X-Env: Prd —— 因为 App 产线包就是 Prd (qm-frontend-api/src/config/index.ts 的 X_ENV = "Prd",build-prod.sh 构建期硬断言)。 照着示例调 = 和 App 打同一个库,排查时不会出现「我调的结果和 App 不一样」的环境错位。 两个值都合法:Dev 用于联调准备期的独立库(数据与 Prd 隔离),Prd 是 App 实际用的。

  • GET 请求前端会自动附加 cacheTime query(防缓存击穿)—— 后端忽略,无影响。

G3 · 鉴权档位 ​

档位端点
无(免登录)captcha · sms-send · sms-login · training/plans · splash-screen 全部 · content/agreements 全部
Bearer <access>其余全部(含 3 条页面直调)
Bearer <refresh> ⚠️只有 rotate —— 传 access 会 40101011

G4 两套错误契约并存 ​

域HTTP业务码说明
oauth20040101xxx业务失败也是 HTTP 200(例外:40101013 封禁 → 403、40101011/12/14/15 → 401)
user / device / training / upload / stats400 / 401 / 404(+413)40301xxx / 40403xxx / 40406xxx / 40419xxx / 40422xxxHTTP 状态直接表达错误类别;msg 可直接 toast
agreement恒 200无业务码非法入参走 data: null,不报错

前端 401 分档(request.ts 现行策略):

业务码处理
40101015业务 401 → toast,不续期(目标会话不存在)
40101012 / 40101014强登出(重放 / 设备信任过期)
其他 401触发 rotate 单飞重放一次

G5 · rotate 由拦截器自动调用 —— 不是业务直调 ​

request.ts 的 rotateOnce 单飞拦截器在 token 将过期 / 收到 401 时自动发起;curl 调试时要自己带 refresh token。详见 login.md · 4 rotate。

G6 · 命名风格 ​

域风格
user / device / training / splashcamelCase
agreementsnake_case(updated_at —— 全项目唯一,前端已对齐,勿改)
oauthcamelCase,孤儿键 token_expiring 例外

G7 · 响应侧几乎无 schema(重要的排障前提) ​

30 个端点里只有 captcha 有 Pydantic 响应模型(CaptchaOutput)。其余 29 个的响应是手组 dict / dataclass 序列化 / repo dict 直出 —— OpenAPI 看不到响应结构、字段改动没有任何守卫挡。所以本手册的字段表就是事实防线,字段以本手册为准。


🔴 头号坑位速查(联调前必读,踩了会浪费半天) ​

#坑症状详见
1clientId 复合格式只允许一个冒号({类型}:{设备UUID})写成 android:a:b 被拒,且返回的是通用异常码 40201007("服务异常")而非参数错误 → 排障方向被误导login · 台账 #3
2X-Env 全程必须一致token 绑 X-Env,跨库即失效;rotate 漏带 → 查不到绑定行G2
3两套错误契约并存拦截器只处理 HTTP 200 或只处理 HTTP 4xx,会漏一半G4
4rotate 用 refresh token 做 Bearer传 access → 40101011login#4
5enroll 失败也返回 HTTP 200 + code 0data: {ok:false, reason} 不是幂等 upsert;后端规则「一次只允许一个进行中的计划」。必须自查 data.oktraining#2
6checkin 的 taskId 不存在/非本人 → data: null(HTTP 200 不报错)前端 await 后本地置 status='done' → 假成功training#4
7logout 有已知缺陷前端现状不传 clientId → 后端吊销 oauth:default(不存在的会话)→ 真实会话未吊销,refresh 仍可 rotate。决策:联调期再修login#7 · 台账
8upload?type=avatar 是 multipart不走 action 协议、不走 request.ts 拦截器 → token + X-Env 要手动注入,且无自动 rotatepage-direct#3
9stats/getTodayOverview 响应是嵌套的读顶层 data.budgetKcal → 恒 undefined(历史漂移:首页热量目标恒为默认值)。正解 data.calorie.budgetKcalpage-direct#2
10device/vendors 的 health.detail 后端不存在前端声明了 detail(optional)→ 展示时恒 undefined。对应物是 health.messagedevice#1

已修复(2026-09-20 · v2.0.49 生产验证) ​

项原问题现状
rotate 缺 expiresAt前端 tokenExpires 恒被覆盖为 {}(与 sms-login 路径不对称)✅ 已补 —— rotate 现返回 expiresAt.{access,refresh},与 sms-login 同口径
updateProfile.nickname只写画像表,不同步 oauth 主表 → 重登显示旧昵称✅ 已双写 —— 提交 nickname 时同步 users 主表

错误码段总览(本手册涉及) ​

段域覆盖端点
40101xxxoauthlogin 全部 7 个
40001xxxoauth 参数段同上(40001001 = HTTP 400 入参校验失败)
40301xxxuseruser 全部 7 个 + splash track(复用 UserError)
40403xxxdevicedevice 全部 4 个 + myTodayHealth
40406xxxtrainingtraining 全部 5 个
40419xxxuploadupload?type=avatar
40422xxxstats(注册表当前登记为 ranking —— 撞段待确认,见下)getTodayOverview
50001001oauth 兜底所有 oauth 端点的服务异常

全量码表见 → 错误码权威注册表。


关联阅读 ​

待确认清单(本手册中标注不确定的条目) ​

#条目待确认内容
1stats/getTodayOverview 错误码段✅ 已确认(2026-09-20 契约审计):40422xxx 是 ranking 与 stats 的事实共享段,两域 40422001/002/004 逐码重号且语义完全相同 → 前端按码分支行为一致,实际无危害。遗留风险:任一方将来新增码会真冲突(建议下次动这两个域的错误码时把一方迁走)。已据实登记进注册表
2device/myTodayHealth 响应粒度前端矩阵文档口径是简写 {steps,distance,hr,spo2,sleep…},后端实际是块状结构(steps={value,date}、todayActivity={totalCalories,totalDistanceKm});顶层 distance 后端不存在。前端 TS 核对后如有偏差需升级处理
3device/myTodayHealth 的 spo2后端恒返回 null(V2 sync 暂无血氧来源),前端按 ?? undefined 容忍;接血氧源后自动转有值
4user/myProfile 完整键集动态 dict(画像行 24 键 + account 5 键),无 schema 无法静态枚举;且双仓储字段集不一致(PG 有 updatedAt,InMemory 无)。前端类型是 Record<string, any> —— 只能按字段存在性消费,不要全等断言
5user/updateProfile 响应 user 行画像行 dict 直出全列(远超前端声明的 7 键)。前端声明的 7 键都在,其余键未定义
6logout 修复归属前端补 clientId / 后端改语义(无 clientId → 吊销全部会话)—— 两条路线联调期决策,见台账