Appearance
Run Share 跑步分享图 + finishRun 接线(前端调用视角)
路由:
POST /api/v1/sport(action 协议:{"action":"...", "payload":{...}}) 涉及 action:finishRun(前置接线)+runShareImage(分享图生成) 鉴权:需登录(Authorization: Bearer <access_token>) 错误码段:40418xxx 后端契约权威:api-docs/v1/sport ·src/app/sport/interface/api/sport_actions.py
一句话
跑步结束必须调 finishRun(当前前端未接 — 见必读①);用户点「分享」时调 runShareImage, 后端用已上报的轨迹生成地图分享图存 COS,直接返回图片 URL —— 前端不传轨迹数组。
跑步数据上报的完整链路(现状 + 缺口)
(2026-10-10 运动数据闭环 v3 — Redis 登记簿 + 后端分配 sessionId + 清洗归一)
startRun {activityType, location?{provice,city,area,address,lng,lat}, deviceId?}
→ {sessionId(后端分配), resumed, resumedFromDevice?}
⭐ 单活跃幂等:同用户 8h 内重复 go → 同 ID
↓ 跑步全程 —— 高德 SDK 本地记录,零请求(App 被杀 → myActiveRun 查活跃恢复现场)
submitRun {sessionId, points[{lat,lng,ts}]≤20000, duration(毫秒), calories?, avgHr?, steps?,
activityType?(离线补交必带), distance?(米—上报口径), startedAt?, deviceId?}
→ {summary(上报口径主), cleanedDistanceKm,
routePointCount, newPointsInserted}
⭐ 单事务四写:run_session + 清洗点 +
raw_event 留档 + sport_record 归一
(vendor='app_run' — 运动记录列表可见)
↓ 用户点分享
runShareImage {sessionId} → {imageUrl, pointCount}⭐ v3 变更要点(2026-10-10):① startRun 不再收前端 sessionId(旧参数静默忽略 — 宽容兼容);②
location随开始上报,落库到运动记录(详情地址条/轨迹图地址自动填充); ③ 点数上限 5000→20000(全马可收;需 nginxclient_max_body_size ≥ 2m); ④ 提交后立即可查 sportRecords(同事务归一,强一致);⑤distance(米)可选 — 前端 SDK 实时里程,给了以它为主口径(cleanedDistanceKm披露去漂移值)。
⚠️ 旧链 reportRunCoordinate(逐点上报)已下线(返 unknown action)—— 前端 reporter 若还在调需移除(此前实为 mock 未发真请求)。finishRun 保留但不再是主路径(submitRun 已含收尾定稿)。
submitRun 契约速览(v3 — 结束上报)
jsonc
{ "action": "submitRun",
"payload": {
"sessionId": "run-8f3a2b", // ✅ startRun 返回的那个
"points": [{"lat": 28.2, "lng": 112.9, "ts": 1791374400000}, …], // ✅ ≤20000 · ≥2
"duration": 1962000, // ✅ 毫秒(含暂停累计)
"distance": 5000.0, // ⬜ 米(SDK 实时里程 — 给了以它为主口径)
"activityType": "running", // ⬜ 登记里有;离线补交必带
"calories": 379, "avgHr": 123, "steps": 2807,
"startedAt": 1791374400000 // ⬜ 离线补交必带
}}
// data → { summary(上报口径), cleanedDistanceKm(清洗披露), routePointCount, newPointsInserted }五验硬拒(400 · 40418001):≥2 点 · ≥50 米 · ≤20000 点 · ≤24h · 段速物理上限(清洗后判定——GPS 偶发尖刺先被滤除,仅全程超限才拒)。 时间戳乱序由服务端自动排序(客户端无需预处理;原始顺序留档可溯源)。 全字段表 + 完整语义见 API Docs → sport;排障见 run-submit-debug。
runShareImage 契约
请求
json
{ "action": "runShareImage", "payload": { "sessionId": "running-1790000000000" } }| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
sessionId | string | ✅ | startRun 返回的客户端会话 id(与 finishRun 同一个值) |
成功响应
json
{
"code": "0", "msg": "ok",
"data": {
"imageUrl": "https://cos-cdn.qingmulife.cn/share/run/running-1790000000000.png",
"pointCount": 342
}
}| 字段 | 类型 | 说明 |
|---|---|---|
imageUrl | string | 分享图 CDN 地址(可直接展示 / 下载 / 分享) |
pointCount | int | 后端实际使用的轨迹点数(服务端库内全量,非前端上报截断) |
后端做了什么(前端无需关心,仅供理解)
读库轨迹(reportRunCoordinate 已落库的全量点)→ 等步长抽稀 ≤200 点 → 高德静态地图 (轨迹线自动居中)→ Pillow 叠数据卡片(距离/时长/配速/日期 + 品牌)→ 传 COS。
⚠️ 必读四条
finishRun当前前端未接线 — 跑步收尾只有本地 stopRun,服务端 session 永远收不了尾 (无终值、分享图取到的汇总不完整)。接分享图前必须先补 finishRun:{sessionId, distance(米), duration(毫秒), calories?, avgHr?, steps?}。 ⚠️ 不要把轨迹 points 塞进 finishRun —— 后端extra="ignore"会静默丢弃(轨迹 属于 reportRunCoordinate 逐点上报,已在线上跑)。- 轨迹点不足会报错(诚实空态) —
40418001「该记录轨迹点不足」= GPS 未记录 (室内/权限拒绝)。前端按业务错处理并提示,不要重试。 - 生成失败可重试(502 段) —
40418012「分享图生成失败」= 高德/COS 任一环抖动, 幂等(同 sessionId 重试安全),建议前端给「重试」按钮而非死报错。 - imageUrl 同 session 恒定 — COS key 固定
share/run/{sessionId}.png,重复生成覆盖 同一地址(可放心缓存 URL;但重新生成会覆盖旧图,无多版本)。
错误码
| code | HTTP | 场景 | 前端处理 |
|---|---|---|---|
40418001 | 400 | sessionId 空 / 轨迹点 <2 | 提示「本次运动未记录到轨迹」 |
40418002 | 401 | 未登录 | 跳登录 |
40418004 | 404 | session 不存在(或非本人 — 跨用户查不到) | 提示记录不存在 |
40418012 | 502 | 高德/COS 生成失败(含 AMAP_WEB_KEY 未配置) | 可重试按钮 |
调用时序(推荐)
点「分享」
→ 若本次跑步未 finishRun:先 finishRun(拿 routePointCount 校验轨迹完整)
→ runShareImage {sessionId}
├─ code=0 → 展示 imageUrl(uni.previewImage / 下载 / 分享)
├─ 40418001 → 「未记录到轨迹」空态
└─ 40418012 → 「生成失败」+ 重试相关
- 后端完整契约:api-docs/v1/sport
- 逐点上报契约(已接线):
src/api/sport.ts↔run-coordinate-reporter.ts - 城市里程榜(另一条榜线):api/city-ranking