文件
hl-api-changelog/changelogs-v2/2026-08/26_6350_行政区划主数据与三级联动-新增接口-管理后台.md
T
lc 3d6f6245a0
changelog-filename-gate / validate (pull_request) Successful in 2s
补齐行政区划三级联动接口文档与模板门禁(#6422)
2026-08-26 16:23:19 +08:00

34 KiB
原始文件 Blame 文件历史

schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
schema ticket title consumer author change_type backend_status gateway_status frontend_status frontend_owner frontend_ref target_release verified_at status_note updated_at base
hl-changelog/v2 6350 行政区划主数据与三级联动 admin lc(GIT) 新增接口 deployed verified pending 主工单 #6350 及补充工单 #6384、#6389、#6395、#6399、#6409 的六个 PR 均已合并 dev-v3。精确提交 b62054035a139e3c689179ef75e2d052d08dde52 已部署 TEST;真实管理员经 Gateway 完成未认证、读取、失败零写入、创建、CAS 更新、数据库和 Redis 一致性验收。#6422 已按 CHANGELOG_TEMPLATE.md 补齐四个接口的前端联调契约。 2026-08-26 dev-v3

User: 行政区划主数据与三级联动

服务: hl-user-service(统一经 Gateway 访问)

PR: #6359、#6387、#6393、#6398、#6402、#6412

Issue: #6350、#6384、#6389、#6395、#6399、#6409

日期: 2026-08-26

影响范围: 管理后台行政区划维护、省/市/县三级选择器和历史值父链回显


⚠️ 关键变化

  • 本次补文档不改变 TEST 上已经部署的接口行为;它把原 Changelog 中分散的说明整理为可直接联调的完整契约。
  • 前端不得再把行政区划 ID 当 JavaScript number:响应中的所有行政区划 ID 均为 JSON string,缺失层级保持真正的 null。
  • 三级联动不允许通过行政区编码截位推断父子关系;必须逐级调用 children,编辑回显先调用 path。
  • 业务失败可能仍使用 HTTP 200,调用方必须同时判断响应体 code、success、message 和 data。

一、背景

管理后台需要一套统一的行政区划主数据能力,同时支持:

  • 省、市、县三级选择器逐级加载;
  • 编辑历史业务数据时,从任意节点恢复完整父链;
  • 具有专用权限的管理员创建自定义行政区或导航聚合节点;
  • 使用 rowVersion 防止两位管理员并发更新时互相覆盖。

接口当前只开放省、市、县三级写入,层级代码依次为 PROVINCE、PREFECTURE、COUNTY。接口返回的示例 ID 仅用于展示字符串类型,前端不得硬编码。


二、变更接口清单

# 接口 方法 路径 变更类型 说明
1 单级联动列表 GET /admin/region/children 新增接口 加载根层省级或指定父节点的当前有效直接子节点
2 父链回显 GET /admin/region/{id}/path 新增接口 从任意省/市/县节点恢复省级根到目标节点的完整路径
3 创建行政区划节点 POST /admin/region 新增接口 创建自定义行政区或不可选择的导航聚合节点
4 更新行政区划节点 PUT /admin/region/{id} 新增接口 使用完整可变字段和 rowVersion 执行 CAS 更新

三、接口详情

1. 单级联动列表 GET /admin/region/children

VO: AdministrativeRegionChildrenVO / AdministrativeRegionItemVO

使用场景

  • 新建页面首次加载省级列表:省略 parentId。
  • 选择省后加载市、选择市后加载县:把当前选中节点 ID 作为下一次请求的 parentId。
  • 编辑页面逐层恢复默认值:把本层历史 ID 作为 selectedId;它仍在当前 items 中时会成为 defaultId。

入参

字段 位置 类型 必填 约束 说明
Authorization Header String ✅ Bearer <token> 真实管理端登录令牌
parentId Query String ❌ 十进制正整数;根层省级列表必须省略 指定要查询直接子节点的父节点 ID
selectedId Query String ❌ 十进制正整数 编辑回显时希望优先选中的本级节点 ID

无请求体。

出参 Result<AdministrativeRegionChildrenVO>

字段 类型 说明
code Integer 200 表示成功;业务失败读取具体业务码
message String 响应说明
success Boolean 仅当 code=200 时为 true
traceId String / null 链路追踪 ID;报错排查时提供给后端
data.parentId String / null 本次查询父节点;根层查询为 null
data.defaultId String / null 合法 selectedId,否则为排序后首项 ID;空列表为 null
data.items Array 当前有效且状态为 ACTIVE 的直接子节点,稳定排序
data.items[].id String 节点 ID,始终为字符串
data.items[].parentId String / null 父节点 ID;省级根节点为 null
data.items[].codeStandard String 编码标准,例如 GB/T 2260、HL_CUSTOM、HL_GROUP
data.items[].regionCode String 编码标准内的地区代码
data.items[].levelCode String PROVINCE、PREFECTURE 或 COUNTY
data.items[].depth Integer 树深度,省/市/县分别为 1/2/3
data.items[].name String 行政区划完整名称
data.items[].shortName String / null 简称
data.items[].nodeKind String ADMINISTRATIVE 或 AGGREGATION
data.items[].navigable Boolean 是否允许继续查询下一层
data.items[].selectable Boolean 是否允许作为最终业务行政区值
data.items[].hasChildren Boolean 是否存在当前有效、启用的直接子节点
data.items[].currentlyEffective Boolean 中国业务当日是否位于有效期且状态启用
data.items[].status String ACTIVE 或 INACTIVE
data.items[].validFrom String 生效日期,格式 yyyy-MM-dd
data.items[].validTo String / null 失效日期;null 表示持续有效
data.items[].sortOrder Integer 同级排序号
data.items[].rowVersion Integer 当前 CAS 版本,更新时原样提交

请求示例

GET /admin/region/children?parentId=2090000000000000001&selectedId=2090000000000000035 HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <管理员令牌>
Accept: application/json

根层省级列表:

GET /admin/region/children HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <管理员令牌>
Accept: application/json

响应示例

{
  "code": 200,
  "message": "成功",
  "data": {
    "parentId": "2090000000000000001",
    "items": [
      {
        "id": "2090000000000000035",
        "parentId": "2090000000000000001",
        "codeStandard": "GB/T 2260",
        "regionCode": "110100",
        "levelCode": "PREFECTURE",
        "depth": 2,
        "name": "北京市",
        "shortName": null,
        "nodeKind": "ADMINISTRATIVE",
        "navigable": true,
        "selectable": true,
        "hasChildren": true,
        "currentlyEffective": true,
        "status": "ACTIVE",
        "validFrom": "2025-12-31",
        "validTo": null,
        "sortOrder": 100,
        "rowVersion": 0
      }
    ],
    "defaultId": "2090000000000000035"
  },
  "traceId": "a1b2c3d4-e5f6-7890",
  "success": true
}

空数据 / 降级响应

父节点存在但没有当前有效直接子节点时,返回成功空数组:

{
  "code": 200,
  "message": "成功",
  "data": {
    "parentId": "2090000000000000035",
    "items": [],
    "defaultId": null
  },
  "success": true
}

错误响应

父节点不存在或已删除:

{
  "code": 210801,
  "message": "行政区划节点不存在",
  "data": null,
  "success": false
}

业务边界

  • 只返回 status=ACTIVE 且当前有效的直接子节点,停用或历史节点不会混入当前选择项。
  • selectedId 不在本次 items 中时不会报错,而是稳定回退到排序后的第一项;空列表回退为 null。
  • 排序固定为 sortOrder、regionCode、id,前端不得另用行政区编码推断顺序或父子关系。
  • navigable 控制能否继续向下加载,selectable 控制能否作为最终值;两者不能相互替代。
  • 未登录返回业务码 401;parentId=0 等非正整数参数返回业务码 400。

2. 父链回显 GET /admin/region/{id}/path

VO: AdministrativeRegionPathVO / AdministrativeRegionItemVO

使用场景

编辑已有业务数据时,后端只保存了一个最终行政区 ID。前端先调用本接口得到完整省/市/县父链,再按父链逐层调用 children 加载每一级可选项。

入参

字段 位置 类型 必填 约束 说明
Authorization Header String ✅ Bearer <token> 真实管理端登录令牌
id Path String ✅ 十进制正整数 要回显的省、市或县节点 ID

无请求体。

出参 Result<AdministrativeRegionPathVO>

字段 类型 说明
code Integer 200 表示成功;业务失败读取具体业务码
message String 响应说明
success Boolean 仅当 code=200 时为 true
data.selectedId String 调用方传入并成功解析的目标节点 ID
data.provinceId String 父链中的省级 ID
data.prefectureId String / null 父链中的地级 ID;目标为省级时为 null
data.countyId String / null 父链中的县级 ID;目标高于县级时为 null
data.path Array 从省级根到目标节点的完整有序父链
data.path[].id String 节点 ID,始终为字符串
data.path[].parentId String / null 父节点 ID;首个省级节点为 null
data.path[].codeStandard String 编码标准
data.path[].regionCode String 地区代码
data.path[].levelCode String 省/市/县层级代码
data.path[].depth Integer 从 1 开始的连续深度
data.path[].name String 完整名称
data.path[].shortName String / null 简称
data.path[].nodeKind String 节点类型
data.path[].navigable Boolean 是否允许逐级导航
data.path[].selectable Boolean 是否允许作为最终值
data.path[].hasChildren Boolean 是否存在当前有效直接子节点
data.path[].currentlyEffective Boolean 当前是否有效;历史节点可能为 false
data.path[].status String ACTIVE 或 INACTIVE
data.path[].validFrom String 生效日期
data.path[].validTo String / null 失效日期
data.path[].sortOrder Integer 同级排序号
data.path[].rowVersion Integer 当前 CAS 版本

请求示例

GET /admin/region/2090000000000000376/path HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <管理员令牌>
Accept: application/json

响应示例

{
  "code": 200,
  "message": "成功",
  "data": {
    "selectedId": "2090000000000000376",
    "provinceId": "2090000000000000001",
    "prefectureId": "2090000000000000035",
    "countyId": "2090000000000000376",
    "path": [
      {
        "id": "2090000000000000001",
        "parentId": null,
        "codeStandard": "GB/T 2260",
        "regionCode": "110000",
        "levelCode": "PROVINCE",
        "depth": 1,
        "name": "北京市",
        "shortName": null,
        "nodeKind": "ADMINISTRATIVE",
        "navigable": true,
        "selectable": true,
        "hasChildren": true,
        "currentlyEffective": true,
        "status": "ACTIVE",
        "validFrom": "2025-12-31",
        "validTo": null,
        "sortOrder": 100,
        "rowVersion": 0
      },
      {
        "id": "2090000000000000035",
        "parentId": "2090000000000000001",
        "codeStandard": "GB/T 2260",
        "regionCode": "110100",
        "levelCode": "PREFECTURE",
        "depth": 2,
        "name": "北京市",
        "shortName": null,
        "nodeKind": "ADMINISTRATIVE",
        "navigable": true,
        "selectable": true,
        "hasChildren": true,
        "currentlyEffective": true,
        "status": "ACTIVE",
        "validFrom": "2025-12-31",
        "validTo": null,
        "sortOrder": 100,
        "rowVersion": 0
      },
      {
        "id": "2090000000000000376",
        "parentId": "2090000000000000035",
        "codeStandard": "GB/T 2260",
        "regionCode": "110101",
        "levelCode": "COUNTY",
        "depth": 3,
        "name": "东城区",
        "shortName": null,
        "nodeKind": "ADMINISTRATIVE",
        "navigable": true,
        "selectable": true,
        "hasChildren": false,
        "currentlyEffective": true,
        "status": "ACTIVE",
        "validFrom": "2025-12-31",
        "validTo": null,
        "sortOrder": 100,
        "rowVersion": 0
      }
    ]
  },
  "success": true
}

空数据 / 降级响应

本接口不返回“成功但空父链”。目标不存在、父链不完整或超过安全深度时使用错误响应,不返回部分路径。

{
  "code": 210801,
  "message": "行政区划节点不存在",
  "data": null,
  "success": false
}

错误响应

父链存在孤儿、越级、循环或异常终止:

{
  "code": 210811,
  "message": "行政区划父链数据不完整",
  "data": null,
  "success": false
}

业务边界

  • path 必须从省级根开始,parentId、depth 和 levelCode 逐级连续,最后一个节点必须等于 selectedId。
  • 目标为省级时 prefectureId、countyId 均为 null;目标为地级时只有 countyId=null,绝不会返回字符串 "null"。
  • 历史停用节点允许回显,节点自身 currentlyEffective=false;前端只能展示历史值,不能把它重新加入当前可选列表。
  • 父链异常时整体失败,不返回可被误用的部分路径。
  • 未登录返回业务码 401;不存在节点返回 210801。

3. 创建行政区划节点 POST /admin/region

VO: AdministrativeRegionCreateReqVO / String

使用场景

具有 system:region:create 权限的管理员创建自定义业务行政区,或创建只用于层级导航、不可作为最终业务值的聚合节点。

入参

字段 位置 类型 必填 约束 说明
Authorization Header String ✅ Bearer <token> 且具有 system:region:create 管理端登录令牌
codeStandard Body String ✅ 1~32 字符;聚合节点必须为 HL_GROUP 编码命名空间,自定义行政区推荐 HL_CUSTOM
regionCode Body String ✅ 1~64 字符;首字符为字母或数字;仅含字母、数字、.、_、:、- 编码标准内的唯一地区代码;服务端转大写
parentId Body String / null ❌ 十进制正整数;创建省级根节点时为 null 父节点 ID
levelCode Body String ✅ 1~32 字符;省/市/县必须匹配派生深度 PROVINCE、PREFECTURE、COUNTY
nodeKind Body String ✅ ADMINISTRATIVE 或 AGGREGATION 节点业务类型
name Body String ✅ 非空,最多 128 字符 完整名称
shortName Body String / null ❌ 最多 64 字符;空白归一为 null 简称
navigable Body Boolean ✅ 聚合节点固定为 true 是否允许继续向下导航
selectable Body Boolean ✅ 聚合节点固定为 false 是否允许作为最终业务值
validFrom Body String ✅ yyyy-MM-dd 生效日期
validTo Body String / null ❌ yyyy-MM-dd,必须严格晚于 validFrom 失效日期;null 表示持续有效
status Body String ✅ ACTIVE 或 INACTIVE 节点状态
sortOrder Body Integer ✅ 0~2,000,000,000 同级排序号
sourceVersion Body String ✅ 非空,最多 64 字符 可审计的数据来源版本
sourceRef Body String ✅ 非空,最多 512 字符 来源 URL、文件定位或管理批次标识
extension Body Object / null ❌ 必须是 JSON object,序列化后最多 4000 字符 结构化扩展;旧入参别名 extJson 仍兼容

depth、操作人和来源校验值由服务端派生,禁止由前端提交。

出参 Result<String>

字段 类型 说明
code Integer 200 表示创建成功
message String 响应说明
success Boolean 创建成功为 true
data String 新节点 ID,始终为 JSON string

请求示例

POST /admin/region HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <具有 system:region:create 的管理员令牌>
Content-Type: application/json

{
  "codeStandard": "HL_CUSTOM",
  "regionCode": "DEMO-COUNTY-001",
  "parentId": "2090000000000000035",
  "levelCode": "COUNTY",
  "nodeKind": "ADMINISTRATIVE",
  "name": "示例业务区",
  "shortName": "示例区",
  "navigable": true,
  "selectable": true,
  "validFrom": "2026-08-26",
  "validTo": null,
  "status": "ACTIVE",
  "sortOrder": 10000,
  "sourceVersion": "ADMIN-20260826",
  "sourceRef": "工单或数据批次定位",
  "extension": {
    "businessNote": "示例扩展信息"
  }
}

响应示例

{
  "code": 200,
  "message": "成功",
  "data": "2090000000000001001",
  "success": true
}

空数据 / 降级响应

本接口成功时一定返回字符串 ID,不存在 data=null 的成功语义。失败时不创建节点。

错误响应

层级代码与父链派生深度不一致:

{
  "code": 210813,
  "message": "行政区划层级代码与父链深度不一致",
  "data": null,
  "success": false
}

业务边界

  • 当前最大写入深度为 3;省、市、县层级必须分别使用 PROVINCE、PREFECTURE、COUNTY。
  • 父节点必须存在、可导航、有效期完整覆盖子节点,并且不会让写入深度超过三级。
  • AGGREGATION 必须同时满足 codeStandard=HL_GROUP、navigable=true、selectable=false;它只能导航,不能作为最终业务值。
  • ADMINISTRATIVE 不得占用 HL_GROUP;旧命名空间 HL_INTERNAL 不允许产生新写入。
  • 同一 codeStandard + regionCode 的版本有效期不得重叠;有效期采用左闭右开 [validFrom, validTo)。
  • 缺少权限返回 210802;空请求或格式错误返回 400;全部失败路径零业务写入。

4. 更新行政区划节点 PUT /admin/region/{id}

VO: AdministrativeRegionUpdateReqVO / Void

使用场景

具有 system:region:update 权限的管理员更新节点名称、父级、层级、可导航/可选状态、有效期、排序和扩展信息。请求必须携带最近读取到的完整可变字段和 rowVersion。

入参

字段 位置 类型 必填 约束 说明
Authorization Header String ✅ Bearer <token> 且具有 system:region:update 管理端登录令牌
id Path String ✅ 十进制正整数 要更新的节点 ID
parentId Body String / null ❌ 十进制正整数;省级根节点为 null 更新后的父节点 ID
levelCode Body String ✅ 1~32 字符;必须匹配更新后父链深度 省/市/县层级代码
name Body String ✅ 非空,最多 128 字符 完整名称
shortName Body String / null ❌ 最多 64 字符;空白归一为 null 简称
navigable Body Boolean ✅ 聚合节点必须保持 true 是否允许继续向下导航
selectable Body Boolean ✅ 聚合节点必须保持 false 是否允许作为最终业务值
validFrom Body String ✅ yyyy-MM-dd 生效日期
validTo Body String / null ❌ 必须严格晚于 validFrom 失效日期
status Body String ✅ ACTIVE 或 INACTIVE 节点状态
sortOrder Body Integer ✅ 0~2,000,000,000 同级排序号
extension Body Object / null ❌ 必须是 JSON object,最多 4000 字符 结构化扩展;旧别名 extJson 仍兼容
rowVersion Body Integer ✅ 大于等于 0,必须等于当前版本 CAS 并发版本,成功后自动加 1

codeStandard、regionCode、nodeKind、sourceVersion 和 sourceRef 不属于更新接口,不能通过请求改写。

出参 Result<Void>

字段 类型 说明
code Integer 200 表示更新成功
message String 成功时为“行政区划更新成功”
success Boolean 更新成功为 true
data null 更新接口不返回业务数据

请求示例

PUT /admin/region/2090000000000001001 HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <具有 system:region:update 的管理员令牌>
Content-Type: application/json

{
  "parentId": "2090000000000000035",
  "levelCode": "COUNTY",
  "name": "示例业务区(更新)",
  "shortName": "示例区",
  "navigable": true,
  "selectable": true,
  "validFrom": "2026-08-26",
  "validTo": null,
  "status": "ACTIVE",
  "sortOrder": 10001,
  "extension": {
    "businessNote": "名称已更新"
  },
  "rowVersion": 0
}

响应示例

{
  "code": 200,
  "message": "行政区划更新成功",
  "data": null,
  "success": true
}

空数据 / 降级响应

本接口成功时 data=null 是固定契约,调用方以 code=200 && success=true 判断成功。

错误响应

提交了已经过期的 rowVersion:

{
  "code": 210807,
  "message": "行政区划已被其他操作更新,请刷新后重试",
  "data": null,
  "success": false
}

业务边界

  • 更新采用完整可变字段快照,不是局部 PATCH;前端必须先读取最新节点信息再提交。
  • rowVersion 冲突返回 210807 且零写入;前端应重新加载,而不是自动覆盖重试。
  • 有子节点的分支禁止移动父级或改变层级,返回 210810。
  • 父节点提前停用、取消导航或缩短有效期,导致现有子节点失去合法父级时返回 210814。
  • 已到生效日的 GB/T 2260 法定节点不能原位改写历史属性;应先关闭旧版本,再创建有效期不重叠的新版本。
  • 成功后 rowVersion 增加 1;再次编辑必须使用新版本号。

四、契约约束与正确调用方式

三级联动新建流程

GET /admin/region/children
  → 用户选择 provinceId
GET /admin/region/children?parentId={provinceId}
  → 用户选择 prefectureId
GET /admin/region/children?parentId={prefectureId}
  → 用户选择 countyId
  → 业务表单最终保存 countyId(或业务允许的上级 selectable 节点)

编辑历史值回显流程

GET /admin/region/{savedRegionId}/path
  → 读取 provinceId / prefectureId / countyId
  → 按 path 顺序逐层调用 children
  → 每层把对应历史 ID 作为 selectedId
  → 历史节点 currentlyEffective=false 时只展示历史值,不重新作为当前选项提交

✅ 正确 / ❌ 错误调用对照

场景 调用或 payload
✅ 加载省级 GET /admin/region/children,省略 parentId
✅ 加载市级 GET /admin/region/children?parentId={provinceId}
✅ 编辑回显 先 GET /admin/region/{savedId}/path,再逐层加载 children
✅ 比较 ID 全程以 string 比较,例如 item.id === selectedId
❌ 根层传 0 GET /admin/region/children?parentId=0 → 400
❌ 截行政区编码推父级 根据 110101 截取 110100;父子关系只能以接口返回的 ID 为准
❌ 把聚合节点当最终值 nodeKind=AGGREGATION && selectable=false 时仍提交为业务行政区
❌ 强转 number Number("2090000000000000376") 会丢失精度

统一响应判断

if (response.code !== 200 || response.success !== true) {
  throw new Error(response.message || '行政区划接口调用失败');
}

禁止只判断 HTTP status;业务错误可能使用 HTTP 200 返回。


五、数据库行为

前端操作 外部可观察的持久化行为 失败行为
查询 children 只读,不改变节点、版本或审计 不写入
查询 path 只读,不改变节点、版本或审计 不写入,不返回部分父链
创建节点 成功新增一个节点,初始 rowVersion=0;事务提交后联动和父链读取可见 参数、权限、父链、有效期或唯一性失败时不新增节点
更新节点 成功更新一个节点并令 rowVersion+1;事务提交后联动和父链返回新值 版本冲突或业务 guard 失败时原节点保持不变

前端无需理解或操作物理表、缓存键和审计表,也不得通过其他接口绕过创建/更新权限和版本检查。


六、边界行为

  • 未登录:业务码 401。
  • 已登录但缺少创建/更新专用权限:210802。
  • Query/Path ID 为 0、负数或非十进制正整数:400。
  • 父节点或目标节点不存在:210801。
  • 父级不可导航、有效期不能覆盖子级:210803。
  • 写入超过当前三级边界:210804。
  • 自引用或父链成环:210805。
  • 当前编码或版本冲突:210806 / 210815。
  • rowVersion 过期:210807,零写入。
  • validTo 不晚于 validFrom:210808。
  • 父链异常:210811,不返回部分路径。
  • extension 不是 JSON object 或过长:210812。
  • 层级代码与父链深度不一致:210813。
  • 老数据兼容:停用历史节点可由 path 回显;旧缓存中的数字 ID 可由后端读取,但当前 API 响应始终输出 string ID。

业务错误码

code message 典型接口与场景
400 参数校验失败文案 任一接口的 ID 非正整数、Body 缺少必填字段或格式不合法
401 未认证文案 任一接口未携带有效管理端身份
210801 行政区划节点不存在 children 父节点或 path/update 目标不存在
210802 无行政区划维护权限 create / update 缺少专用写权限
210803 行政区划父节点不合法 父级不可导航、已失效或有效期不能覆盖子级
210804 行政区划层级超过当前允许深度 create / update 试图写入第四级
210805 行政区划父链存在循环 update 自引用或移动后形成祖先环
210806 行政区划当前编码已存在 create 唯一编码冲突
210807 行政区划已被其他操作更新,请刷新后重试 update.rowVersion 已过期
210808 行政区划有效期不合法 validTo 不晚于 validFrom
210809 该行政区划编码标准为系统保留值 新写入继续使用旧 HL_INTERNAL
210810 存在子节点的行政区划不能移动或变更层级 update 移动分支或改变分支层级
210811 行政区划父链数据不完整 path 遇到孤儿、越级、循环或异常终止
210812 行政区划扩展字段必须是合法 JSON extension 不是 JSON object 或内容过长
210813 行政区划层级代码与父链深度不一致 省/市/县代码与派生深度不匹配
210814 行政区划更新与现有子节点状态或有效期冲突 父级提前停用、取消导航或缩短有效期
210815 行政区划编码的版本有效期与既有数据重叠 同编码的两个版本时间窗重叠
210816 行政区划节点类型或编码命名空间不合法 nodeKind、codeStandard、可导航/可选组合错误
210817 已生效法定行政区划只能关闭旧版本后新增 原位改写生效中的 GB/T 2260 节点
210818 行政区划数据来源信息不完整 sourceVersion 或 sourceRef 缺失/过长
210819 法定行政区划代码必须为六位数字 GB/T 2260 的 regionCode 格式错误

六.5、枚举 / 数据字典

levelCode(行政区划层级)

所属字段: AdministrativeRegionCreateReqVO.levelCode、AdministrativeRegionUpdateReqVO.levelCode、AdministrativeRegionItemVO.levelCode | 类型: String

值 中文 说明
PROVINCE 省级 深度 1,父节点必须为 null
PREFECTURE 地级 深度 2,父节点必须是省级
COUNTY 县级 深度 3,父节点必须是地级

nodeKind(节点类型)

所属字段: AdministrativeRegionCreateReqVO.nodeKind、AdministrativeRegionItemVO.nodeKind | 类型: String

值 中文 说明
ADMINISTRATIVE 行政区节点 可按 selectable 决定是否作为最终业务值;不得使用 HL_GROUP
AGGREGATION 导航聚合节点 必须使用 HL_GROUP、navigable=true、selectable=false

status(节点状态)

所属字段: 创建/更新请求及节点响应的 status | 类型: String

值 中文 说明
ACTIVE 启用 位于有效期时可进入当前联动列表
INACTIVE 停用 不进入当前联动列表,但历史父链仍可回显

codeStandard(编码命名空间)

所属字段: AdministrativeRegionCreateReqVO.codeStandard、AdministrativeRegionItemVO.codeStandard | 类型: String

值 中文 说明
GB/T 2260 法定行政区编码 regionCode 必须为六位数字;生效历史受不可变保护
HL_CUSTOM 自定义行政区编码 管理端创建普通自定义行政区的推荐命名空间
HL_GROUP 导航聚合编码 只能与 AGGREGATION 搭配

六.6、修改前后对比

本条为新增接口,没有需要兼容的旧 /admin/region/** 公开接口。补充工单 #6409 在正式前端接入前固定了 ID 输出类型:

字段级对比

字段 初始实现风险 当前已部署契约
所有行政区划 Long ID 小数值可能被全局序列化规则输出为 JSON number 一律输出 JSON string
缺失父级/层级 可能被调用方误当字符串处理 保持真正的 JSON null
extension 历史入参 旧调用方可能使用 extJson 请求继续兼容 extJson,当前文档统一使用 extension

行为级对比

行为 接入前 当前已部署行为
三级联动 无统一公开接口,容易截行政区编码推断 逐级调用 children,以后端父子关系为准
编辑回显 调用方自行拼装父链 调用 path 返回完整有序父链和三级快捷 ID
并发更新 无本接口旧语义 必须提交 rowVersion,冲突返回 210807 且零写入

六.7、影响评估

  • 是否破坏向后兼容: 否;这是新增接口,且正式前端接入前已经固定字符串 ID 契约。
  • 前端是否必须同步上线: 是;frontend_status=pending,管理端需接入三级选择器、历史回显和主数据维护。
  • 前端 workaround 清理点: 不得保留行政区编码截位、Long ID 转 number、只判断 HTTP status 或把 hasChildren 等同于 selectable 的临时逻辑。

七、不影响范围

  • 仅影响: 管理后台行政区划维护、省/市/县三级选择器及历史行政区值回显。
  • 零影响:
    • 小程序端现有行政区接口;
    • Product、Order、Resource 和 Fleet 现有业务接口;
    • 已保存业务数据中的行政区 ID;
    • 登录、权限菜单和非行政区划缓存;
    • 生产环境(本 Changelog 只证明 TEST 已部署和验收)。

八、测试环境已验证

GET  /admin/region/children                  → 根级/子级列表、defaultId、string/null ID ✓
GET  /admin/region/{id}/path                 → 省市县完整父链、缺失层级 null、string ID ✓
POST /admin/region                           → 创建成功返回 string ID,数据库与缓存读取一致 ✓
PUT  /admin/region/{id}                      → CAS 更新成功,rowVersion 0→1,联动与父链同步 ✓
GET  /admin/region/children(未认证)         → 401 ✓
POST /admin/region(缺参/非法节点类型)       → 400 且零业务写入 ✓
GET  /admin/region/{missingId}/path           → 210801 ✓
  • TEST 精确提交:b62054035a139e3c689179ef75e2d052d08dde52,包含六个相关 PR 的合并提交。
  • Deploy Panel API 任务:52771074(hl-user-service)和 b1db24b2(hl-gateway),终态均为 success,双实例、Nacos、滚动采样及部署窗口日志检查通过。
  • 真实 TEST 管理员经 Gateway 完成未认证、读取、失败零写入、创建、CAS 更新、持久化结果和缓存一致性验收。
  • 验收创建的临时节点已精确清理;数据库数量与测试命名空间恢复基线,四个缓存快照按原值和绝对过期时间恢复,登录会话已注销;操作审计按系统设计保留。

九、相关历史 PR

PR Issue 说明 是否仍有效
#6359 #6350 行政区划主数据、三级写入与读接口基础实现 ✅ 有效
#6387 #6384 权限、来源和节点类型契约补强 ✅ 有效
#6393 #6389 有效期、父链和历史版本保护补强 ✅ 有效
#6398 #6395 Gateway 路由与认证策略补齐 ✅ 有效
#6402 #6399 缓存一致性与滚动兼容补强 ✅ 有效
#6412 #6409 所有行政区划响应 ID 固定为 JSON string ✅ 有效

十、相关文档


撤回

本次 #6422 仅补充 Changelog 和模板校验,不改变后端制品。若文档补充需要撤回,从最新 hl-api-changelog/main 创建独立分支,revert #6422 对应合并提交并重新运行仓库全部校验;TEST 行政区划 API、数据、配置、Redis 和 MQ 均不需要回退。撤回后前端暂以当前 Controller/VO 契约为准,并重新提交一份通过模板门禁的自包含文档。


关联 / 联系人

链接

联系人

  • 后端负责人: @lc