Appearance
前端联调 · 训练中心(training.ts)
归属:前端联调 API 手册 →
src/api/training.ts端点:5 个 · REST 风格(非 action 协议) 错误码段:40406xxx(全表见 错误码注册表) 错误契约:HTTP 400 / 401 / 404 +{code, msg, data};⚠️ 但业务失败(如计划冲突)走 HTTP 200 + code "0" +{ok:false}—— 见下 取值语义:取值速查 · training ——kind三类计划的种子差异、data.ok=false时哪些键不存在、tasks[].status实际只产两值、completeSource两条写入路径 深入说明:training API Docs
本页 curl 变量
bash
BASE_URL=https://api.qingmulife.cn # 本地:http://localhost:3000
ACCESS=<access_token>
ENV=Prd # Dev | Prd —— 全程必须一致⚠️ 身份一律以 token 为准(2026-09-18 越权修复):
body.userId/?user_id=一律被后端忽略。 传它们是死参数 —— 前端本地 uid 与 token 用户不一致时(换号残留),返回的仍是 token 用户的数据,且前端无法感知。
端点一览
| # | 方法 | 路径 | 鉴权 | 场景 |
|---|---|---|---|---|
| 1 | GET | /api/v1/training/plans | 公开 | 计划模板列表(向导推荐用) |
| 2 | POST | /api/v1/training/enroll | access | 报名计划(生成任务 + 提醒) |
| 3 | GET | /api/v1/training/tasks | access | 我的打卡任务(日历/计划页) |
| 4 | POST | /api/v1/training/task/checkin | access | 任务打卡 |
| 5 | GET | /api/v1/training/reminders | access | 我的提醒列表 |
listTrainingPlans
一句话:计划模板列表 —— 向导推荐 / 计划选择页展示用(免登录)。
curl
bash
curl -s "$BASE_URL/api/v1/training/plans" \
-H 'X-Env: Prd'请求
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| — | — | — | 无任何入参、免登录 |
响应 data —— 顶层是数组
| 字段 | 类型 | 可空 | 说明 |
|---|---|---|---|
[].id | number | 否 | int —— 原样回传给 enroll 的 planId |
[].planKey | string | 否 | 计划键(英文机器名) |
[].name / [].description | string | 否 | 展示文案 |
[].kind | string | 否 | 种子值 running | cycling | strength(前端类型为三值联合 ✓) |
[].targetKm | number | 否 | float |
[].targetSessions | number | 否 | int |
[].durationWeeks | number | 否 | int |
错误码
无业务错误码(公开只读接口)。服务不可用 → HTTP 5xx。
enrollTrainingPlan
一句话:报名训练计划 —— 服务端生成打卡任务 + 提醒。
🔴 这是本页最大的坑:业务失败也返回 HTTP 200 +
code: "0",只是data.ok = false。不是幂等 upsert —— 后端规则是「一次只允许一个进行中的计划」。
curl
bash
curl -s "$BASE_URL/api/v1/training/enroll" \
-H 'Content-Type: application/json' \
-H 'X-Env: Prd' \
-H "Authorization: Bearer $ACCESS" \
-d '{"planId":1}'请求
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
planId | number | ✔ | 来自 listTrainingPlans 的 [].id |
userId | string | ✖ | ⚠️ 死参数 —— 后端忽略(越权修复)。传了无害,但别据此断言身份 |
Authorization / X-Env | header | ✔ |
响应 data(两条分支)
成功路径:
| 字段 | 类型 | 可空 | 说明 |
|---|---|---|---|
ok | boolean | 否 | true |
created | number | 否 | 仅成功路径有 —— 创建的任务数 |
reminders | number | 否 | 仅成功路径有 —— 创建的提醒数 |
enrollmentId | number | 否 | 后端返回第 4 键,前端未声明(不消费无影响) |
失败路径(⚠️ HTTP 200 + code "0"):
| 字段 | 类型 | 可空 | 说明 |
|---|---|---|---|
ok | boolean | 否 | false |
reason | string | 否 | 已知取值:"计划不存在或未发布" · "已有进行中的计划(一次一个)" |
createdreminders | — | — | 失败路径不返回这两个键(前端 TS 声明为必填 number → 读到 undefined) |
🔴 前端必做:await 之后先查
res.data.ok,再读created。 直接读er.created会得到undefined;直接按 HTTP/code 判成功会误报报名成功。reason可直接 toast(中文文案后端已给)。
错误码
| 业务码 | HTTP | 触发条件 | 前端建议处置 |
|---|---|---|---|
40406002 | 401 | 未登录 / token 无效 | 走拦截器 401 分档 |
40406001 | 400 | planId 缺失(body.get("planId", 0) → 0) | 检查调用点 |
40406005 | 400 | 计划已下架(PLAN_ARCHIVED) | toast「计划已下架」+ 刷新列表 |
| — | 200 | 计划冲突 / 计划不存在 | ⚠️ 不是错误码 —— 查 data.ok === false + data.reason |
listMyTasks
一句话:我的打卡任务列表(按计划日排序)—— 计划页 / 日历页。
curl
bash
curl -s "$BASE_URL/api/v1/training/tasks?cacheTime=$(date +%s)" \
-H 'X-Env: Prd' \
-H "Authorization: Bearer $ACCESS"请求
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
user_id (query) | string | ✖ | ⚠️ 死参数 —— 后端忽略(身份以 token 为准)。前端目前传本地 storage uid |
cacheTime (query) | number | ✖ | 前端自动附加(防缓存击穿),后端忽略 |
Authorization / X-Env | header | ✔ |
响应 data —— 顶层是数组
| 字段 | 类型 | 可空 | 说明 |
|---|---|---|---|
[].id | number | 否 | int —— 打卡时回传 |
[].weekIndex / [].sessionIndex | number | 否 | int,周序 / 课序 |
[].plannedDate | string | 否 | 'YYYY-MM-DD' |
[].kind | string | 否 | 来自所属 plan.kind |
[].status | string | 否 | 实际只产生 'pending' | 'done' —— 'missed' 后端无生成逻辑(前端类型更宽 ✓) |
[].completeSource | string | null | 是 | manual | auto_sport |
错误码
| 业务码 | HTTP | 触发条件 | 前端建议处置 |
|---|---|---|---|
40406002 | 401 | 未登录 / token 无效 | 走拦截器 401 分档 |
checkinTask
一句话:任务打卡(幂等:已完成直接返回 status='done')。
🔴 坑位:task 不存在 / 非本人时返回
data: null(HTTP 200,不报错不 404)。 前端现状是 await 后本地把task.status = 'done'→ null 时显示「打卡成功」= 假成功。
curl
bash
curl -s "$BASE_URL/api/v1/training/task/checkin" \
-H 'Content-Type: application/json' \
-H 'X-Env: Prd' \
-H "Authorization: Bearer $ACCESS" \
-d '{"taskId":1}'请求
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
taskId | number | ✔ | 来自 listMyTasks 的 [].id |
userId | string | ✖ | ⚠️ 死参数 —— 后端忽略 |
Authorization / X-Env | header | ✔ |
响应 data
| 字段 | 类型 | 可空 | 说明 |
|---|---|---|---|
(整个 data) | object | null | 是 | 🔴 null = task 不存在或非本人 |
taskId | number | 是 | 回显 |
status | string | 是 | 'done' |
🔴 前端必做:
if (!res.data) { /* 打卡失败,别置本地 done */ }。 正常路径taskId来自任务列表,不易触发;但任何"离线补打卡"/"从通知进打卡"的场景都会踩。
错误码
| 业务码 | HTTP | 触发条件 | 前端建议处置 |
|---|---|---|---|
| — | 200 | task 不存在 / 非本人 | ⚠️ 不是错误码 —— 判 data === null,勿本地置 done |
40406002 | 401 | 未登录 | 走拦截器 401 分档 |
40406004 | 404 | 计划/任务不存在(NOT_FOUND) | toast + 刷新任务列表 |
listMyReminders
一句话:我的提醒列表(App 内提醒)—— 计划页提醒区块。
curl
bash
curl -s "$BASE_URL/api/v1/training/reminders?cacheTime=$(date +%s)" \
-H 'X-Env: Prd' \
-H "Authorization: Bearer $ACCESS"请求
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
user_id (query) | string | ✖ | ⚠️ 死参数(同 tasks) |
Authorization / X-Env | header | ✔ |
响应 data —— 顶层是数组
| 字段 | 类型 | 可空 | 说明 |
|---|---|---|---|
[].id | number | 否 | int |
[].taskId | number | 否 | 关联任务 |
[].remindAt | string ISO | 否 | 提醒时刻 |
[].status | string | 否 | pending 等 |
错误码
| 业务码 | HTTP | 触发条件 | 前端建议处置 |
|---|---|---|---|
40406002 | 401 | 未登录 | 走拦截器 401 分档 |
本页合同要点速记
| 要点 | 值 |
|---|---|
| 身份来源 | token(body.userId / ?user_id 全是死参数) |
| 业务失败表达 | enroll → data.ok=false;checkin → data=null(都不是错误码) |
| 列表响应形态 | plans / tasks / reminders 顶层都是数组(直接 res.data.map) |
缺 missed | tasks[].status 后端只产 pending / done |