Skip to content

前端联调 · 页面直调(3 个端点) ​

归属:前端联调 API 手册 → 页面直调(不走 src/api/*.ts,无 TS 泛型锚) 端点:3 个 —— myTodayHealth · getTodayOverview · upload?type=avatar 为什么单列:这 3 条是自动化提取器的盲区(生成器只扫 src/api/*.ts),2026-09-20 人工全审矩阵补录 ⚠️ 无 TS 泛型锚 → 本页字段表以后端源码为契约基准,前端 TS 核对后如有偏差请回填 取值语义:取值速查 · stats(profileComplete=false 意味着预算是默认画像算出来的)· 取值速查 · upload(type 非白名单静默兜底 misc、localFallback 与 source 的降级链)· 取值速查 · device(myTodayHealth 块状结构)

本页 curl 变量 ​

bash
BASE_URL=https://api.qingmulife.cn   # 本地:http://localhost:3000
ACCESS=<access_token>
ENV=Prd                              # Dev | Prd —— 全程必须一致

端点一览 ​

#方法路径action页面鉴权
1POST/api/v1/devicemyTodayHealth首页健康卡(index.vue)access
2POST/api/v1/statsgetTodayOverview首页热量 / 连续天数卡access
3POST/api/v1/upload?type=avatar—头像上传(multipart)access(手动注入)

myTodayHealth ​

一句话:首页健康卡数据 —— 步数 / 活动 / 心率 / 血氧 / 睡眠一屏聚合(块状结构)。

curl ​

bash
curl -s "$BASE_URL/api/v1/device" \
  -H 'Content-Type: application/json' \
  -H 'X-Env: Prd' \
  -H "Authorization: Bearer $ACCESS" \
  -d '{"action":"myTodayHealth","payload":{}}'

请求 ​

字段类型必填说明
actionstring✔固定 "myTodayHealth"(device 端点 6+2 action 分发)
payloadobject✖忽略(无入参)
Authorization / X-Envheader✔

响应 data ⚠️ 块状结构(不是扁平字段) ​

字段类型可空说明
datestring否CN 今日 'YYYY-MM-DD'
steps{value: number, date: string} | null是步数块
todayActivity{totalCalories: number, totalDistanceKm: number} | null是当日活动块
hr{value: number, date: string} | null是心率块
spo2null(恒 null)是血氧块 —— V2 sync 暂无血氧来源;前端按 ?? undefined 容忍
sleep{durationHours, deepHours, lightHours, remHours, score: null, date} | null是睡眠块
sources{steps?: string, hr?: string, sleep?: string}否厂商来源映射(「数据来自:XX」小字);无数据时为 {},键按有无数据出现

⚠️ 顶层就这 7 个键(date / steps / todayActivity / hr / spo2 / sleep / sources)—— 后端没有 sleepXiaomi 键。前端 const sleep = h.sleepXiaomi ?? h.sleep 里的 sleepXiaomi 是 V1 遗留分支,永远取不到(?? 右侧恒生效)。无害但误导 —— 见 KB-04 台账,接线时勿照抄。

⚠️ 待确认 #2 —— 粒度差异:前端矩阵文档口径写的是简写 {steps, distance, hr, spo2, sleep…},但后端顶层没有 distance —— 对应物是 todayActivity.totalDistanceKm。同样 steps / hr 都是 {value, date} 对象而非数字。 前端 TS 核对后如有偏差请升级为契约差异回填台账。

错误码 ​

业务码HTTP触发条件前端建议处置
40403002401未登录 / token 无效走拦截器 401 分档
40403001400未知 action检查 action 名

getTodayOverview ​

一句话:首页热量 / 连续天数卡 —— 今日热量预算 + 摄入 + 消耗 + 打卡统计。

🔴 坑位(历史漂移,已修,留档防回归):响应是嵌套的。 后端自始返回 data.calorie.budgetKcal,前端曾读顶层 data.budgetKcal → 首页热量目标恒为默认值。 前端已在 api 分支 24902e9 修正为按嵌套路径消费。别再写回顶层。

curl ​

bash
curl -s "$BASE_URL/api/v1/stats" \
  -H 'Content-Type: application/json' \
  -H 'X-Env: Prd' \
  -H "Authorization: Bearer $ACCESS" \
  -d '{"action":"getTodayOverview","payload":{}}'

请求 ​

字段类型必填说明
actionstring✔固定 "getTodayOverview"(stats 端点 15 action 分发)
payloadobject✖忽略(无入参)
Authorization / X-Envheader✔

响应 data —— 🔴 两层嵌套 ​

字段类型可空说明
datestring否CN 今日 'YYYY-MM-DD'
calorie.budgetKcalnumber✔int —— 热量预算(BMR Mifflin-St Jeor × 1.3 − 400,下限 1500 / 女性 1200)。画像缺失 = null
calorie.intakeKcalnumber否float —— 摄入(当日无记录 = 0)
calorie.burnKcalnumber否float —— 消耗 = 跑步 + 力量(无记录 = 0)
calorie.remainingKcalnumber✔int —— 剩余额度(budget + burn − intake,向零截断)。画像缺失 = null
calorie.profileCompleteboolean否false = 画像缺失
checkin.countnumber否int —— 当日打卡数
checkin.distanceKmnumber否float —— 无打卡为 0

🔴 profileComplete:false 时后端 budgetKcal / remainingKcal 一律返回 null —— 不编数(2026-09-20 用户拍板)。此前返的是「默认资料 30 岁 / 165cm / 60kg 算出来的值」, 看着像真数据,前端无法区分有无。看到 null 请渲染 --,不要再兜一个默认值 (本仓 KB-06 的教训:?? 0 把「不知道」变成可证伪的假数)。

✅ 正确读法:res.data.calorie.budgetKcal ❌ 错误读法:res.data.budgetKcal(恒 undefined → 前端回退默认值 → 需求「热量目标」静默失效)

⚠️ 本端点没有 basis 字段(2026-09-22 按源码核实并移除 —— 本表此前误列)。 basis 属于 stats.calorieBalance(另一个 action),与 getTodayOverview 无关。 首页 17 个端点的完整入参 / 出参表见 首页 API 接口参考。

错误码 ​

业务码HTTP触发条件前端建议处置
40422002401未登录 / token 无效走拦截器 401 分档
40422001400入参非法检查调用点
40422005200样本不足(关联分析不下结论)按缺省文案展示

⚠️ 待确认 #1 —— 错误码段归属:stats 源码用 40422xxx(src/app/stats/domain/exceptions.py), 但错误码注册表把 40422xxx 登记为 ranking 段 —— 疑似撞段。 本页按源码写实际值;段归属待确认后回填。


upload ​

一句话:头像上传 —— 🔴 multipart,不是 action 协议,header 要手动注入 token + X-Env。

🔴 坑位:这条不走 request.ts 拦截器 → 没有自动 rotate(token 过期时不会自动续期,直接失败)。 上传前请自查 token 有效性;失败重试需自行处理。

curl ​

bash
# --form 让 curl 自动生成 multipart boundary + filename
curl -s "$BASE_URL/api/v1/upload?type=avatar" \
  -H 'X-Env: Prd' \
  -H "Authorization: Bearer $ACCESS" \
  -F 'file=@/path/to/avatar.jpg'

# 强制本地 fallback(应急 / 调试)
curl -s "$BASE_URL/api/v1/upload?type=avatar&localFallback=1" \
  -H 'X-Env: Prd' \
  -H "Authorization: Bearer $ACCESS" \
  -F 'file=@/path/to/avatar.jpg'

请求 ​

字段类型必填说明
type (query)string✔头像传 'avatar'。净化正则 /^[a-z0-9_-]{1,32}$/(下划线合法)→ 不符静默兜底 'misc'(不报错)
file (multipart)File✔字段名必须叫 file;stdlib 解析单 file 字段
Authorizationheader✔Bearer <access> —— 页面手动注入
X-Envheader✔页面手动注入(漏了会落到默认库)
localFallback (query)string✖'1' = 强制本地存储(应急/调试);缺省 COS 优先
x-upload-passwordheader✖仅 xiaomi_zip 用(不进 URL 日志),头像不需要

约束:

项值
大小上限50MB(CZ_UPLOAD_MAX_SIZE)
MIME 白名单image/jpeg · image/png · image/webp · application/zip · application/octet-stream
存储派发COS 配置齐全 → COS(优先 CDN cos-cdn.qingmulife.cn);配置缺失 / putObject 抛错 → 静默 fallback 本地

响应 data ​

字段类型可空说明
urlstring否COS/CDN URL 或本地 /uploads/{type}/... 路径(依环境配置)—— 前端消费这个做头像回显
sizenumber否int,字节数(后端返回,前端未声明)
mimestring否回显上传 MIME(后端返回,前端未声明)
sourcestring否'cos' | 'local'(后端返回,前端未声明)
uploadRecordIdstring条件出现仅 type ∈ RECORD_TYPES(7 个数据包/截图 type)且 source='cos' —— avatar 不出现

错误码(段 40419xxx) ​

业务码HTTP触发条件前端建议处置
40419003400multipart 里没有 file 字段(NO_FILE)检查字段名是否叫 file
40419004400MIME 不在白名单(UNSUPPORTED_MIME)前置校验图片类型
40419005413文件 > 50MB(FILE_TOO_LARGE)前置压缩 / 提示超限
40419002401未登录 / token 无效无自动 rotate —— 手动刷新 token 后重试
40419001400参数非法检查 type
40419006404UploadRecord 不存在(worker 侧)用户无感

本页合同要点速记 ​

端点最容易踩的点
myTodayHealth块状结构 —— steps.value / todayActivity.totalDistanceKm;顶层无 distance;spo2 恒 null
getTodayOverview嵌套两层 —— data.calorie.budgetKcal,不是 data.budgetKcal
uploadmultipart + 手动 header + 无自动 rotate + 字段名必须 file