Skip to content

City Ranking 城市里程榜(前端调用视角) ​

路由:POST /api/v1/ranking(action 协议:{"action":"...", "payload":{...}}) action:cityRanking — 城市圈定用户的打卡里程排行 鉴权:需登录(Authorization: Bearer <access_token>) 错误码段:40422xxx 后端契约权威:api-docs/v1/ranking/index.md §3(2026-09-30 上线 v2.0.120 + v2.0.122 修 SQL)

场景(榜单页) ​

用户在「城市榜页」查看所在城市的跑步里程排行:

  • 默认城市长沙 —— 不传 cityAdcode 即看长沙榜(无需先做城市选择)
  • 可切城市:从城市选择页拿到的 adcode 传入 cityAdcode
  • 两档周期:近 30 天(month30)/ 全部(all)(2026-09-30 修正 —— 原当日/每周档已下线)

调用链(与 city 域联动) ​

进入城市榜页
  ├─(可选)getCurrentCity → 拿到用户已绑定城市 adcode
  │     ├─ 已绑定 → cityRanking {period, cityAdcode: 绑定城市}
  │     └─ 未绑定 → cityRanking {period}          # 默认长沙榜
  └─ 用户切周期 / 切城市 / 下拉刷新 → 重调

绑定的用户才会上榜(进榜单需要先 selectCity)—— 未绑定用户能看榜,但自己的 myRank 恒为 null。

请求 ​

json
{ "action": "cityRanking", "payload": { ... } }
字段类型必填默认说明
periodstring⬜"month30""month30"(近 30 天)/ "all"(全部历史,不限下界)
cityAdcodestring⬜"430100"6 位行政区划码;缺省 = 长沙
limitint⬜501~100(榜单返回条数)

响应字段 ​

字段类型说明
periodstring入参回显
cityAdcodestring实际生效的城市码(缺省时后端回 "430100",前端可据此确认)
listarray榜单(最长 limit 条)
list[].rankint名次(1 起)
list[].userIdstring用户 ID
list[].nicknamestring | null昵称 —— 可能为 null(见必读 ③)
list[].avatarUrlstring | null头像 URL —— 可能为 null
list[].distancefloat周期内累计里程 km(保留 2 位小数)
list[].checkinsint周期内打卡次数
myRankint | null当前用户在该城榜单的排名;未绑定该城 → null(见必读 ①)
totalint上榜总人数
generatedAtstring聚合时刻 ISO 8601(服务端 5min 缓存 —— 见必读 ④)

⚠️ 必读六条 ​

  1. myRank = null 是两种含义 —— ① 未绑定该城(想上榜先走城市选择页 selectCity);② 排名在聚合上限(500 名)之外。前端建议:myRank === null 且用户未绑定 → 展示「选择城市,参与排名」引导入口。
  2. 空榜是正常冷启动 —— list: [] + total: 0 表示该城暂无绑定用户打卡,不是错误。画空态(例如「还没有人上榜,来打第一个卡」),不要走错误分支。
  3. nickname / avatarUrl 可为 null —— 用户未填资料时后端不编造。展示兜底:昵称 null → 「匿名」,头像 null → 默认头像。
  4. 5 分钟缓存 —— 后端对 (城市, 周期) 组合缓存 5 分钟,generatedAt 是聚合时刻。5 分钟内重复进入页面数据不变是预期行为,不要做「下拉刷新无变化 = bug」的误判;需要强实时可等窗口过期后再拉。
  5. 接口不回城市名 —— 榜头「长沙 · 近 7 天」这类城市名由前端映射:用城市选择页 listCities 结果或本地字典做 adcode → name(后端只回 cityAdcode)。
  6. 排序已稳定 —— distance 降序、同分按 userId 升序。前端不要二次排序,否则与 myRank 序号可能不一致。

错误码 ​

codeHTTP场景前端处理
40422001400period 越界 / cityAdcode 非 6 位数字检查入参(周期只允许 month30/all)
40422002401未登录 / token 无效跳登录
40422004404服务端未装配该 action(罕见)提示稍后重试

业务失败恒为 HTTP 200 + code 非 "0"(V2 信封约定),前端按 code 分支而非 HTTP 状态。

调用示例 ​

请求:默认长沙 · 近 30 天榜(全缺省) ​

json
{ "action": "cityRanking", "payload": {} }

请求:长沙 · 全部历史 ​

json
{ "action": "cityRanking", "payload": { "period": "all" } }

请求:切城市 · 近 30 天 ​

json
{ "action": "cityRanking", "payload": { "period": "month30", "cityAdcode": "440100", "limit": 20 } }

成功响应 ​

json
{
  "code": "0",
  "msg": "ok",
  "data": {
    "period": "month30",
    "cityAdcode": "430100",
    "list": [
      {"rank": 1, "userId": "u000074", "nickname": "荣耀用户", "avatarUrl": null,
       "distance": 42.3, "checkins": 7},
      {"rank": 2, "userId": "u000100", "nickname": "跑者甲", "avatarUrl": "https://cdn.example/a.png",
       "distance": 36.2, "checkins": 7}
    ],
    "myRank": 1,
    "total": 2,
    "generatedAt": "2026-09-30T12:34:56+00:00"
  }
}

未绑定用户的响应(可看榜 · 不参与排名) ​

json
{
  "code": "0", "msg": "ok",
  "data": {
    "period": "month30", "cityAdcode": "430100",
    "list": [ {"rank": 1, "userId": "u000100", "nickname": "跑者甲", "avatarUrl": null,
               "distance": 6.2, "checkins": 1} ],
    "myRank": null,
    "total": 1,
    "generatedAt": "2026-09-30T12:34:56+00:00"
  }
}

错误响应 ​

json
{ "code": "40422001", "msg": "period 越界: year(month30|all)", "data": null }

页面 × API 矩阵 ​

页面 / 交互调用时机
城市榜页 · 进入cityRankingonShow / onLoad
城市榜页 · 切周期(30 天 / 全部)cityRankingTab 切换
城市榜页 · 切城市cityRanking(新 cityAdcode)城市选择页返回后
城市榜页 · 下拉刷新cityRanking手动刷新(注意 5min 缓存)
城市选择页city 域 listCities / selectCity见 api-docs/v1/city

相关 ​