Appearance
前端联调 · 用户资料(user.ts)
归属:前端联调 API 手册 →
src/api/user.ts端点:7 个 · 全部为 user 域 action 协议(POST /api/v1/user) 错误码段:40301xxx(全表见 错误码注册表) 错误契约:HTTP 400 / 401 / 404 +{code, msg, data}(与 oauth 域不同 —— 不走 HTTP 200) 取值语义:取值速查 · user ——habits.*每个取值打什么画像标签 / 怎么影响人群分型、audienceA/B/C 的判据、planId的人群相关合法集、各字段「不传 vs 传 0 vs 传 null」的区别 深入说明:User API Docs
本页 curl 变量
bash
BASE_URL=https://api.qingmulife.cn # 本地:http://localhost:3000
ACCESS=<access_token>
ENV=Prd # Dev | Prd —— 全程必须一致
USER_ID=<登录响应 data.user.id> # 仅前端本地使用,请求体不需要(身份以 token 为准)全部端点需
Bearer access+X-Env。请求体统一{"action": "...", "payload": {...}}。
端点一览
| # | action | 场景 |
|---|---|---|
| 1 | updateBasicProfile | 向导 step1:性别/年龄/身高/体重 4 维独立提交 |
| 2 | submitOnboarding | 向导 step2:生活习惯 → 返画像摘要(不标 done) |
| 3 | onboardingSuggestion | 向导 step3:方案推荐(目标 + 3 档计划 + 安全提示) |
| 4 | adoptPlanOption | 采用某档计划(服务端重算 deficit) |
| 5 | completeOnboarding | 向导收尾:标记 onboardingDone=true |
| 6 | myProfile | 我的页 / 个人资料回显 |
| 7 | updateProfile | 个人资料编辑保存 |
updateBasicProfile
一句话:向导第一步 —— 4 维基础资料独立提交(可先跑通这一段再进下一步)。
curl
bash
curl -s "$BASE_URL/api/v1/user" \
-H 'Content-Type: application/json' \
-H 'X-Env: Prd' \
-H "Authorization: Bearer $ACCESS" \
-d '{"action":"updateBasicProfile","payload":{
"gender":"male","age":30,"height":175,"weight":70
}}'请求
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
gender | 'male' | 'female' | ✔ | Literal 两值,其他值 → 40301001 |
age | number | ✔ | int,5 ≤ age ≤ 120 |
height | number | ✔ | float,50 ≤ height ≤ 250(写入时 cz_js_round 取整) |
weight | number | ✔ | float,10 ≤ weight ≤ 300 |
响应 data
| 字段 | 类型 | 可空 | 说明 |
|---|---|---|---|
gender | string | 否 | 回显 |
age | number | 否 | int |
height | number | 否 | float —— 取整后的值(如 175.0) |
weight | number | 否 | float |
错误码
| 业务码 | HTTP | 触发条件 | 前端建议处置 |
|---|---|---|---|
40301001 | 400 | 任一字段超范围 / gender 非两值 | 表单即时校验,提示具体字段 |
40301002 | 401 | 未登录 / token 无效 | 走拦截器 401 分档 |
40301013 | 400 | 年龄非法(专用码) | 提示年龄范围 |
submitOnboarding
一句话:向导第二步 —— 提交生活习惯,返画像摘要(这一步不标 done,收尾靠 completeOnboarding)。
curl
bash
curl -s "$BASE_URL/api/v1/user" \
-H 'Content-Type: application/json' \
-H 'X-Env: Prd' \
-H "Authorization: Bearer $ACCESS" \
-d '{"action":"submitOnboarding","payload":{
"gender":"male","age":30,"height":175,"weight":70,
"habits":{
"exerciseFreq":3,
"exercisePrefs":["run","walk"],
"exercisePrefRemark":"每周三次慢跑",
"takeoutFreq":5,
"sugarDrink":2,
"lateDinner":false
}
}}'请求
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
gender | 'male' | 'female' | 'unknown' | ✔ | 后端比前端更宽(含 unknown) |
age | number | ✔ | int,5–120 |
height | number | ✔ | float,50–250 |
weight | number | ✔ | float,10–300 |
habits.exerciseFreq | number | ✖ | int,0–7(每周运动次数)。0 是有效值= 不运动(久坐)→ tag「久坐少动」+ 分型走 A 类;1~3 偶尔运动;≥4 运动规律;≥3 才有「运动基础」(分型判据)。不传 → 后端按 0(2026-09-20 起改可选 — 前端不再采集,后端按打卡数据校正)。→ 取值语义 |
habits.exercisePrefs | string[] | ✔ | 值域 'run' | 'cycling' | 'walk' | 'strength' | 'other'。⚠️ 本 action 无 1–4 长度限制(CzHabitsPayload 无 min/max)—— 空数组 [] 合法;1–4 的限制在 updateProfile |
habits.exercisePrefRemark | string | ✖ | 含 other 时必填(≤200)—— 含 other 却为空 → 40301001 |
habits.takeoutFreq | number | ✖ | int,0–21 |
habits.sugarDrink | number | ✖ | int,0–20 |
habits.lateDinner | boolean | ✖ | 是否常吃宵夜 |
nickname / zodiac / bodyFat | — | ✖ | schema 支持但向导链路不收集 |
响应 data
| 字段 | 类型 | 可空 | 说明 |
|---|---|---|---|
audience | string | 否 | 人群分型 |
bmi | number | 否 | float |
bmiLabel | string | 否 | BMI 文案标签 |
bmr | number | 否 | int,基础代谢 |
tags | string[] | 否 | 画像标签 |
summary | string | 否 | 画像摘要文案 |
minorMode | boolean | 否 | 未成年模式 |
错误码
| 业务码 | HTTP | 触发条件 | 前端建议处置 |
|---|---|---|---|
40301001 | 400 | 字段超范围 / exercisePrefs 含 other 但无 exercisePrefRemark | 表单即时校验(前端注释已对齐此规则 ✅) |
40301002 | 401 | 未登录 | 走拦截器 401 分档 |
onboardingSuggestion
一句话:向导第三步 —— 拉取目标建议 + 3 档计划选项 + 安全提示。
curl
bash
curl -s "$BASE_URL/api/v1/user" \
-H 'Content-Type: application/json' \
-H 'X-Env: Prd' \
-H "Authorization: Bearer $ACCESS" \
-d '{"action":"onboardingSuggestion","payload":{}}'请求
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
payload | object | ✖ | 忽略 —— user_id 来自 token |
响应 data
| 字段 | 类型 | 可空 | 说明 |
|---|---|---|---|
audience | string | 否 | 人群分型 |
minorMode | boolean | 否 | 未成年模式 |
goal | object | null | 是 | 目标建议块;null 时前端需兜底展示 |
goal.targetWeightMin / targetWeightMax | number | 否 | float |
goal.weeks | number | 否 | int |
goal.maxWeeklyLossPct | number | 否 | float,周最大减重比例 |
goal.note | string | 否 | 说明文案 |
plans[] | object[] | 否 | 3 档计划(gentle / standard / intense) |
plans[].id | string | 否 | gentle | standard | intense —— 原样回传给 adoptPlanOption |
plans[].name / composition / reason | string | 否 | 展示文案 |
plans[].sessionsPerWeek / deficitKcal | number | 否 | int |
plans[].recommended | boolean | 否 | 是否推荐档 |
safetyNote | string | null | 是 | 安全提示;null 时不展示该区块 |
错误码
| 业务码 | HTTP | 触发条件 | 前端建议处置 |
|---|---|---|---|
40301002 | 401 | 未登录 | 走拦截器 401 分档 |
40301010 | 400 | 画像不完整(未先走 step1/2) | 引导回向导前一步 |
adoptPlanOption
一句话:采用某档计划 —— 服务端重算 deficit,不信任前端传的数值。
curl
bash
curl -s "$BASE_URL/api/v1/user" \
-H 'Content-Type: application/json' \
-H 'X-Env: Prd' \
-H "Authorization: Bearer $ACCESS" \
-d '{"action":"adoptPlanOption","payload":{"planId":"standard"}}'请求
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
planId | string | ✔ | Literal 'gentle' | 'standard' | 'intense'(TS 标注为 string,实际值域受限) |
响应 data
| 字段 | 类型 | 可空 | 说明 |
|---|---|---|---|
planOption | string | 否 | 回显采用的档位 |
dailyDeficitKcal | number | 否 | 服务端重算 —— 不是前端传的值 |
sessionsPerWeek | number | 否 | int |
错误码
| 业务码 | HTTP | 触发条件 | 前端建议处置 |
|---|---|---|---|
40301011 | 400 | planId 非法(PLAN_OPTION_INVALID) | 用 onboardingSuggestion 返回的 plans[].id,别硬编码 |
40301010 | 400 | 画像不完整 | 引导回向导 |
completeOnboarding
一句话:向导收尾 —— 标记 onboardingDone = true(新用户下次登录不再弹向导)。
curl
bash
curl -s "$BASE_URL/api/v1/user" \
-H 'Content-Type: application/json' \
-H 'X-Env: Prd' \
-H "Authorization: Bearer $ACCESS" \
-d '{"action":"completeOnboarding","payload":{}}'请求
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
payload | object | ✖ | 忽略 |
响应 data
| 字段 | 类型 | 可空 | 说明 |
|---|---|---|---|
ok | boolean | 否 | 恒 true |
错误码
| 业务码 | HTTP | 触发条件 | 前端建议处置 |
|---|---|---|---|
40301002 | 401 | 未登录 | 走拦截器 401 分档 |
myProfile
一句话:拉当前用户完整画像 —— 我的页 / 个人资料页回显。
curl
bash
curl -s "$BASE_URL/api/v1/user" \
-H 'Content-Type: application/json' \
-H 'X-Env: Prd' \
-H "Authorization: Bearer $ACCESS" \
-d '{"action":"myProfile","payload":{}}'请求
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
payload | object | ✖ | 忽略(user_id 来自 token) |
响应 data ⚠️ 动态 dict(无 schema)
响应 = 画像行 dict 合并 account 段动态组出,键集无法静态枚举。已知分组:
| 分组 | 字段 | 类型 | 可空 | 说明 |
|---|---|---|---|---|
| 画像 · 身份 | userId / nickname / avatarUrl / signature / signatureSource | string | 是 | signatureSource = 'custom' | 'agent' |
| 画像 · 偏好 | exercisePrefs / exercisePrefRemark / profileTags | array | 是 | jsonb → list,默认 [] |
| 画像 · 4 维 | gender / age / height / weight / audience / zodiac | — | 是 | agent 未分析时可能全为 null |
| 画像 · 计划 | habits / planOption / dailyDeficitKcal / sessionsPerWeek / fitness / agentAnalyzedAt | — | 是 | 同上 |
| account 段 | points / totalPointsEarned / memberExpireAt / growthLevel / onboardingDone | — | 是 | user_core 未装配 / 异常时 5 键整体消失 |
| 时间戳 | updatedAt | string ISO | 是 | ⚠️ 仅 PG 实现有;InMemory 默认建档无此键 |
⚠️ 待确认 #4:无 schema,只能按字段存在性消费,不要做全等断言。前端类型是
Record<string, any>—— 读p.exercisePrefs这类画像段字段是安全的(account 段缺席不影响)。
错误码
| 业务码 | HTTP | 触发条件 | 前端建议处置 |
|---|---|---|---|
40301002 | 401 | 未登录 | 走拦截器 401 分档 |
40301004 | 404 | 用户不存在 | 强登出 |
updateProfile
一句话:个人资料编辑保存(昵称 / 头像 / 签名 / 运动偏好)。
curl
bash
curl -s "$BASE_URL/api/v1/user" \
-H 'Content-Type: application/json' \
-H 'X-Env: Prd' \
-H "Authorization: Bearer $ACCESS" \
-d '{"action":"updateProfile","payload":{
"nickname":"前端E2E用户",
"signature":"跑起来就对了",
"exercisePrefs":["run","cycling"],
"exercisePrefRemark":"每周 4 跑 1 骑"
}}'请求
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
nickname | string | ✖ | ≤ 32。✅ 2026-09-20 起双写 oauth 主表(此前只写画像表 → 重登显示旧昵称) |
avatarUrl | string | null | ✖ | 🔴 传 null = 不修改(无法置空) —— 后端「仅非 None 字段写」 |
signature | string | null | ✖ | ≤ 60。🔴 传 null = 不修改(无法清空);写非空值时 signatureSource='custom' |
exercisePrefs | string[] | ✖ | min 1 / max 4 —— ⚠️ 空数组 → 40301001(前端已规避:页面空数组时不传) |
exercisePrefRemark | string | null | ✖ | ≤ 200;exercisePrefs 含 other 时必填 |
🔴 坑位 · 传
null无法清空:前端 TS 类型写的是string \| null(暗示可清空),但后端语义是「仅非 None 字段写」→ 传null被静默忽略。 当前调用点用undefined(不传)所以没踩;一旦实现「清空签名」传null就会静默失效。台账 · 潜在问题 #3
响应 data
| 字段 | 类型 | 可空 | 说明 |
|---|---|---|---|
user.nickname | string | null | 是 | |
user.avatarUrl | string | null | 是 | |
user.signature | string | null | 是 | |
user.signatureSource | 'custom' | 'agent' | 否 | PG server_default 保证非 null |
user.exercisePrefs | string[] | 是 | jsonb default [] |
user.exercisePrefRemark | string | null | 是 | |
user.profileTags | string[] | 是 | jsonb default [] |
⚠️ 待确认 #5:响应
user是画像行 dict 直出(含userId/gender/age/ … /updatedAt全列,远超上面声明的 7 键)。上面 7 键都在 ✅,其余键未定义。
错误码
| 业务码 | HTTP | 触发条件 | 前端建议处置 |
|---|---|---|---|
40301001 | 400 | nickname > 32 / signature > 60 / exercisePrefs 空数组 / 含 other 无 remark | 表单即时校验;空数组不传该字段 |
40301002 | 401 | 未登录 | 走拦截器 401 分档 |