Skip to content

city —— 选择城市(C 端 / 4 action) ​

统一入口:POST /api/v1/city body = { "action": "<4种之一>", "payload": { ... } } 鉴权:3 action 需登录(selectCity / getCurrentCity / getRecentCities),1 action 公开(listCities 选城市页用) 契约权威:src/app/city/interface/api/city_actions.py(DDD interface 层,路由必须在此 — 详踩坑见 docs/internal-wiki/decisions/0013-city-domain.md)

4 个 Action 一览 ​

Action鉴权触发场景详细
listCities无选城市页进入 / 搜索→ §1
selectCityBearer用户点选 / 提交定位→ §2
getCurrentCityBearer首页 / 我的页查当前城市→ §3
getRecentCitiesBearer选城市页 onShow 拉最近 + 热门→ §4

公共约定 ​

统一信封(V2 全局) ​

  • 成功:{ "code": "0", "msg": "ok", "data": { ... } }
  • 业务失败:HTTP 200 + { "code": "40431xxx", "msg": "<原因>" }
  • 入参校验失败:HTTP 400 + { "msg": "<字段路径>: <原因>" }
  • 响应 data 全部 camelCase(V2 风格:adcode / parentCode / isHot / updatedAt / boundAt)

city 对象结构 ​

json
{
  "adcode": "440100",
  "name": "广州市",
  "parentCode": "440000",
  "level": 2,
  "pinyin": "guangzhou",
  "isHot": true
}
字段类型说明
adcodestring6 位行政区划码(110000=北京 / 440100=广州 / 500000=重庆等)
namestring中文名(含「市」)
parentCodestring | null父级 adcode(直辖市 = 自身 adcode)
levelint1 省 / 2 市(含直辖市场次归 2 — 见 D-6)
pinyinstring | null拼音首字母(去「市」)
isHotbool10 热门城市标记(启动屏首推)

HTTP 状态映射 ​

层HTTP说明
参数校验失败400payload 格式错(双模入参并存 / level 越界)
业务失败(默认)40040431001 / 40431005
未登录40140431004
城市不存在40440431002
用户未绑城市(新用户常态)40440431003(前端按 code 跳引导页,不打错误 toast)

错误码矩阵(40431xxx) ​

code语义HTTP前端处置
40431001参数错(含双模入参并存 / unknown action / adcode 非 6 位 / level 越界)400toast
40431002adcode 不存在404toast
40431003用户未绑城市(新用户常态)404跳引导页 / 选城市页,不当错误
40431004未登录 / token 无效401走登录
40431005GPS 转码失败(v1 GPS 不可用 — seed 无 geo → SeedNearestCityLocator 恒返 None)400前端 v1 不做「自动定位」按钮,只做手动选择

双模入参契约(D-7 · selectCity) ​

selectCity 支持两种入参,二选一,双传 → 40431001:

模式payload适用
手动{ "adcode": "440100" }用户点选 / 搜索结果
GPS{ "gps": { "lng": 113.2644, "lat": 23.1291 } }自动定位(v1 不可用 — 必返 40431005)

⚠️ GPS 路径隐私红线:后端一次性转码,坐标不持久化(不会落 PG,不会写日志)。

seed 数据规模 ​

  • 368 条:27 省 + 341 地级市
  • 不含区县(D-6 v1 决策 —— 用户选城市粒度是「我在哪个市」)
  • 数据源:src/app/city/seeds/adcode_seed.json(scripts/gen_city_seed.py 生成;modood/administrative-divisions-of-China 派生)
  • 10 热门城市(isHot=true):北京/上海/广州/深圳/佛山/东莞/南京/无锡/杭州/成都/武汉/重庆

1. listCities ​

公开 —— 选城市页拉列表 / 搜索过滤。

请求 payload:

字段类型必填默认约束说明
levelint⬜—1 | 2 | 31=省 / 2=市 / 3=区县(v1 seed 无 3)
keywordstring⬜—≤32 字符搜 name 或 pinyin("广州" / "guangzhou" 都命中)
hotbool⬜false—true 返 10 热门城市(不受 level 影响)
pageint⬜1≥1页码
sizeint⬜50≥1每页条数(v1 全部 368 条内存,单页能拉满)

响应 data:

字段类型说明
cities[City]当前页结果(结构见公共约定)
totalint过滤后总数(非分页大小)

错误码:40431001(level 越界)

示例:

json
// 请求
{ "action": "listCities", "payload": { "keyword": "广州", "page": 1, "size": 20 } }

// 响应
{
  "code": "0", "msg": "ok", "data": {
    "cities": [{"adcode":"440100","name":"广州市","parentCode":"440000","level":2,"pinyin":"guangzhou","isHot":true}],
    "total": 1
  }
}

2. selectCity ​

登录 —— 绑定当前城市(D-7 双模入参)。

请求 payload(二选一,双传 40431001):

模式字段类型必填约束
手动adcodestring✅^\d{6}$(6 位数字)
GPSgps.lngfloat✅
gps.latfloat✅

响应 data:

字段类型说明
adcodestring6 位 adcode
namestring城市中文名
sourcestring"manual" / "auto_gps"(GPS 路径 → auto_gps,手动 → manual)
updatedAtstringISO 8601(2026-09-29T11:06:48)

错误码:

code场景
40431001双模入参并存 / 都缺 / adcode 格式非 6 位
40431002adcode 不在 seed(unauthorized)
40431004未登录(401)
40431005GPS 转码失败(v1 不可用 —— 实际必走这条)

示例:

json
// 手动请求
{ "action": "selectCity", "payload": { "adcode": "440100" } }

// 手动响应
{
  "code": "0", "msg": "ok", "data": {
    "adcode": "440100", "name": "广州市", "source": "manual", "updatedAt": "2026-09-29T11:06:48"
  }
}
json
// GPS 请求(v1 必返 40431005)
{ "action": "selectCity", "payload": { "gps": { "lng": 113.2644, "lat": 23.1291 } } }

// GPS 响应(v1)
{ "code": "40431005", "msg": "GPS 坐标无法解析到城市" }

3. getCurrentCity ​

登录 —— 首页 / 我的页读当前绑定城市。

请求:{ "action": "getCurrentCity", "payload": {} }

响应 data:

字段类型说明
cityCity当前绑定城市(结构见公共约定)
boundAtstringISO 8601(首次绑定或最后一次 selectCity 时间)
sourcestring"manual" / "auto_gps"

错误码:

code场景前端处置
40431003用户尚未绑定城市(新用户 / 清 cookie 重装)跳引导页 / 选城市页,不当错误展示
40431002绑定 adcode 在 seed 找不到(数据不一致 — 走 admin 排查)toast
40431004未登录走登录

示例:

json
// 已绑定用户响应
{
  "code": "0", "msg": "ok", "data": {
    "city": {"adcode":"440100","name":"广州市","parentCode":"440000","level":2,"pinyin":"guangzhou","isHot":true},
    "boundAt": "2026-09-29T11:06:48",
    "source": "manual"
  }
}

// 新用户响应(**预期**返回)
{ "code": "40431003", "msg": "尚未绑定城市" }

4. getRecentCities ​

登录 —— 选城市页 onShow 拉最近选择 + 热门垫底。

v1 简化:每用户绑定表只有 1 条 → 「最近」= 当前绑定单条 + seed 10 热门按此去重数垫底。

请求 payload:

字段类型必填默认约束说明
limitint⬜51~50返回上限

响应 data:

字段类型说明
cities[City]已有绑定前置 + 热门按 adcode 去重垫底

错误码:40431004(401 未登录)

示例:

json
// 请求
{ "action": "getRecentCities", "payload": { "limit": 5 } }

// 响应(已绑广州市 + 热门垫底)
{
  "code": "0", "msg": "ok", "data": {
    "cities": [
      {"adcode":"440100","name":"广州市","parentCode":"440000","level":2,"pinyin":"guangzhou","isHot":true},
      {"adcode":"110100","name":"北京市","parentCode":"110000","level":2,"pinyin":"beijing","isHot":true},
      {"adcode":"310100","name":"上海市","parentCode":"310000","level":2,"pinyin":"shanghai","isHot":true},
      {"adcode":"440300","name":"深圳市","parentCode":"440000","level":2,"pinyin":"shenzhen","isHot":true},
      {"adcode":"440600","name":"佛山市","parentCode":"440000","level":2,"pinyin":"foshan","isHot":true}
    ]
  }
}

页面 × API 矩阵 ​

uniapp 页面 / 组件调用的 action调用时机
首页getCurrentCity启动时查当前城市(40431003 跳引导页)
选城市页getRecentCitiesonShow 拉最近 + 热门垫底
选城市页 · 搜索框listCities输入 keyword 时实时过滤
选城市页 · 点选selectCity {adcode}点列表项 → 返回 + 刷新
设置页getCurrentCity显示当前绑定

实现参考 ​

后端实施规范见 🟢 Internal Wiki → ADR-0013 city 域。 前端对接指南(推荐页面流图)见 🟢 Internal Wiki → 2026-09-29 选择城市 + 生吃排名 前端对接指引。