Skip to content

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