Appearance
sport API(运动打卡 + 跑群 + 跑步会话)
路由:
POST /api/v1/sport(action 协议:{"action": "...", "payload": {...}}) 错误码段:40418xxx(全表见错误码权威注册表) 鉴权:全部需登录 字段真相:src/app/sport/application/usecase/(Request dataclass / pydantic payload)+tests/integration/test_sport_actions.py(可执行契约) 前端对接页:run-share(跑步链) · run-submit-debug(排障) · 方案设计 v4
Action 一览(19)
| action | 鉴权 | 说明 |
|---|---|---|
today | 登录 | 今日运动大卡 |
checkin | 登录 | 打卡(goal 达成检测钩子触发点) |
myStats | 登录 | 我的统计 |
myGroups | 登录 | 我的跑群 |
createGroup | 登录 | 建群(上限约束) |
joinGroup | 登录 | 入群 |
quitGroup | 登录 | 退群(群主限制) |
groupRanking | 登录 | 群排名 |
groupDetail | 登录 | 群详情 |
groupMembers | 登录 | 成员列表 |
announceGroup | 登录 | 群公告 |
startRun | 登录 | 开跑(v3 登记簿 — 传 activityType+location,后端分配 sessionId) |
submitRun | 登录 | 结束上报(v3 — 一次全量交轨迹,清洗+归一入库) |
myActiveRun | 登录 | 查活跃会话(App 被杀恢复现场) |
finishRun | 登录 | 收尾(旧契约 — 手工补汇总;重复幂等) |
myRunRecords | 登录 | 运动记录列表(会话 + 打卡两源合并) |
runSessionDetail | 登录 | 单次运动详情(轨迹 + 配速派生) |
addWeightRecord | 登录 | 体重记录写入 |
runShareImage | 登录 | 轨迹分享图生成(→ COS URL)→ 前端对接页 |
⚠️
reportRunCoordinate(逐点上报)已下线(2026-10-09 任务制改造)——调用返回 unknown action(40418001)。
跑步会话 v3 契约(2026-10-10 运动数据闭环)
startRun —— 开跑登记(Redis 登记簿,PG 零写入)
jsonc
// 请求
{ "action": "startRun",
"payload": { "activityType": "running", // 必填 running|walking|cycling
"location": { "province": "湖南省", "city": "长沙市", "area": "岳麓区",
"address": "…", "lng": "112.93", "lat": "28.17" }, // 可选
"deviceId": "…" } } // 可选(多设备提示)
// 响应 data
{ "sessionId": "run-4a95716dcd8f", // 后端分配(12 hex);Redis key = sport:run:active:{env}:{uid}
"startedAt": 1791609225395, "resumed": false, "resumedFromDevice": "…" } // 后两个可选- 单活跃幂等:同用户 8h 内重复 go → 同一 sessionId +
resumed:true(不重开) - 8h TTL:过期自动蒸发;过期后提交仍收(冷补路径 +
no_registration内部标记) - 旧包宽容兼容:旧参数(前端自造 sessionId 等)静默忽略
provice/province双拼写兼容(前端历史 typo)
submitRun —— 结束上报(一次全量;同步清洗 + 单事务归一)
jsonc
// 请求
{ "action": "submitRun",
"payload": { "sessionId": "run-4a95716dcd8f",
"points": [{"lat": 28.2, "lng": 112.9, "ts": 1791609225395}, …], // ≤20000,≥2
"duration": 150000, // 毫秒(含暂停累计)
"distance": 2000.0, // 可选 米(前端 SDK 实时里程——给了以它为主口径)
"activityType": "running", // 可选(登记里有;离线补交必带)
"calories": 120, "avgHr": 118, "steps": 2100,
"startedAt": 1791609225395 } } // 可选(离线补交必带)
// 响应 data
{ "summary": { "distanceKm": 2.0, … }, "cleanedDistanceKm": 1.93, // 双口径(上报主/清洗披露)
"routePointCount": 42, "newPointsInserted": 42, "flagged": "…" } // flagged 仅异常时- 五验硬拒(→ 400 · 40418001):点 ≥2 · 距离 ≥50m · 点 ≤20000 · 跨度 ≤24h · 段速 ≤物理上限(对清洗后点集判定 — 先清洗后五验,个例 GPS 尖刺被野点剔除器滤除)
- 乱序容错:时间戳乱序由服务端自动排序(2026-10-10 联调拍板 — 打包问题非物理错误);原始顺序留档 raw_event
- 同步单事务四写:run_session 定稿 + 清洗轨迹点(DP 抽稀) + raw_event 留档 + sport_record 归一(vendor='app_run' → 出现在 device 域运动记录列表)
- 幂等:重复提交零重复(点级 + 记录级 upsert)
myActiveRun —— 查活跃会话
jsonc
{ "action": "myActiveRun", "payload": {} }
// data: { "sessionId": "…", "startedAt": 1791609225395, "activityType": "running" } 或 null通用约定
- 信封:HTTP 200 +
{"code","msg","data"};code="0"成功;40418002未登录(HTTP 401);40418001参数错(HTTP 400) - 写操作均落 audit/测试可断言(集成测试 = 可执行契约)
- payload 校验:pydantic +
extra="ignore"(多余字段静默忽略不报错 —— 多传即丢,注意)
变更记录
| 日期 | 内容 |
|---|---|
| 2026-09-16 | 首版(四域审查棒 3 — action 清单代码直出) |
| 2026-10-08 | 跑步会话 6 action(startRun/reportRunCoordinate/finishRun/myRunRecords/runSessionDetail/addWeightRecord)入档 + runShareImage 新增(轨迹分享图 — 前端对接页) |
| 2026-10-10 | 运动数据闭环 v3(v2.0.157–159) — startRun 改登记簿制(Redis 8h TTL + 后端分配 ID + 单活跃幂等 + location 随登记落库);submitRun 重写(清洗管线 + 同步单事务四写 + sport_record 归一 vendor='app_run' + 双口径距离 + 上限 20000);新增 myActiveRun;reportRunCoordinate 正式标注下线;配套 device 域 sportRecordDelete/sourceLabel/bySource。联调排障实录:run-submit-debug |