---
url: /docs/guide/uniapp/api/run-share.md
---
# Run Share 跑步分享图 + finishRun 接线（前端调用视角）

> 路由：`POST /api/v1/sport`（action 协议：`{"action":"...", "payload":{...}}`）
> 涉及 action：**`finishRun`**（前置接线）+ **`runShareImage`**（分享图生成）
> 鉴权：需登录（`Authorization: Bearer <access_token>`）
> 错误码段：40418xxx
> 后端契约权威：[api-docs/v1/sport](/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**（全马可收；需 nginx `client_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](/api-docs/v1/sport/)；排障见 [run-submit-debug](./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。

## ⚠️ 必读四条

1. **`finishRun` 当前前端未接线** — 跑步收尾只有本地 stopRun，服务端 session 永远收不了尾
   （无终值、分享图取到的汇总不完整）。**接分享图前必须先补 finishRun**：
   `{sessionId, distance(米), duration(毫秒), calories?, avgHr?, steps?}`。
   ⚠️ **不要把轨迹 points 塞进 finishRun** —— 后端 `extra="ignore"` 会**静默丢弃**（轨迹
   属于 reportRunCoordinate 逐点上报，已在线上跑）。
2. **轨迹点不足会报错（诚实空态）** — `40418001`「该记录轨迹点不足」= GPS 未记录
   （室内/权限拒绝）。前端按业务错处理并提示，不要重试。
3. **生成失败可重试（502 段）** — `40418012`「分享图生成失败」= 高德/COS 任一环抖动，
   幂等（同 sessionId 重试安全），建议前端给「重试」按钮而非死报错。
4. **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](/api-docs/v1/sport/)
* 逐点上报契约（已接线）：`src/api/sport.ts` ↔ `run-coordinate-reporter.ts`
* 城市里程榜（另一条榜线）：[api/city-ranking](/guide/uniapp/api/city-ranking)
