From 6461e1ce39faa2e791963510e15af7de998aa0f1 Mon Sep 17 00:00:00 2001 From: lc Date: Wed, 26 Aug 2026 15:52:15 +0800 Subject: [PATCH] =?UTF-8?q?docs(changelog):=20=E4=BA=A4=E4=BB=98=E8=A1=8C?= =?UTF-8?q?=E6=94=BF=E5=8C=BA=E5=88=92=E4=B8=BB=E6=95=B0=E6=8D=AE=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=EF=BC=88#6350=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...政区划主数据与三级联动-新增接口-管理后台.md | 290 ++++++++++++++++++ 1 file changed, 290 insertions(+) create mode 100644 changelogs-v2/2026-08/26_6350_行政区划主数据与三级联动-新增接口-管理后台.md diff --git a/changelogs-v2/2026-08/26_6350_行政区划主数据与三级联动-新增接口-管理后台.md b/changelogs-v2/2026-08/26_6350_行政区划主数据与三级联动-新增接口-管理后台.md new file mode 100644 index 00000000..26637227 --- /dev/null +++ b/changelogs-v2/2026-08/26_6350_行政区划主数据与三级联动-新增接口-管理后台.md @@ -0,0 +1,290 @@ +--- +schema: "hl-changelog/v2" +ticket: "6350" +title: "行政区划主数据与三级联动" +consumer: "admin" +author: "lc(GIT)" +change_type: "新增接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "" +frontend_ref: "" +target_release: "" +verified_at: "2026-08-26" +status_note: "主工单 #6350 及补充工单 #6384、#6389、#6395、#6399、#6409 的六个 PR 均已合并 dev-v3。精确提交 b62054035a139e3c689179ef75e2d052d08dde52 已由 Deploy Panel 任务 52771074(hl-user-service)和 b1db24b2(hl-gateway)部署 TEST;真实管理员经 Gateway 完成未认证、读取、失败零写入、创建、CAS 更新、数据库和 Redis 一致性验收,临时数据、缓存快照及登录会话均已恢复。管理端尚待接入新增接口。" +updated_at: "2026-08-26" +base: "dev-v3" +--- + +# 行政区划主数据与三级联动 + +管理后台新增可版本化的行政区划主数据接口,统一提供省、市、县三级联动、任意节点父链回显,以及受权限、有效期、层级和并发版本保护的创建与更新能力。 + +> **服务**:`hl-user-service`,统一经 Gateway `/admin/region/**` 访问 +> **Issue**:#6350、#6384、#6389、#6395、#6399、#6409 +> **PR**:#6359、#6387、#6393、#6398、#6402、#6412 +> **影响范围**:管理后台行政区划维护、三级选择器和历史值父链回显 + +## 关键约定 + +- 所有响应中的行政区划 ID 均为 JSON 字符串,包括 `data`、`parentId`、`defaultId`、`selectedId`、`provinceId`、`prefectureId`、`countyId` 和节点 `id`;没有父级或对应层级时保持 `null`。 +- 当前写入边界为省、市、县三级,层级代码依次为 `PROVINCE`、`PREFECTURE`、`COUNTY`;服务端根据父链计算 `depth`,请求不能直接指定深度。 +- `ADMINISTRATIVE` 表示可作为业务行政区的节点;`AGGREGATION` 只用于导航分组,必须使用 `HL_GROUP`、`navigable=true`、`selectable=false`。 +- 有效期使用左闭右开区间 `[validFrom, validTo)`;`validTo=null` 表示持续有效。 +- 读接口只要求真实管理员登录;创建还要求 `system:region:create`,更新要求 `system:region:update`。 +- 统一响应为 `Result`。业务失败可能仍是 HTTP 200,调用方必须同时检查 `code`、`success`、`message` 和 `data`。 + +## 变更接口清单 + +| 方法 | 路径 | 权限 | 用途 | +|---|---|---|---| +| POST | `/admin/region` | `system:region:create` | 创建行政区或导航聚合节点 | +| PUT | `/admin/region/{id}` | `system:region:update` | 使用 `rowVersion` 更新可变字段 | +| GET | `/admin/region/children` | 管理员登录 | 查询根层或指定父节点的当前有效直接子节点 | +| GET | `/admin/region/{id}/path` | 管理员登录 | 返回任意节点从省级根开始的完整父链 | + +## 1. 单级联动列表 + +```http +GET /admin/region/children?parentId=1&selectedId=35 +Authorization: Bearer <管理员令牌> +``` + +查询省级根列表时省略 `parentId`。`selectedId` 只有在本次 `items` 中存在时才作为 `defaultId`;否则回退为排序后的首项,空列表返回 `null`。 + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { + "parentId": "1", + "items": [ + { + "id": "35", + "parentId": "1", + "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": "35" + } +} +``` + +仅返回中国业务当日有效且状态为 `ACTIVE` 的直接子节点,排序稳定为 `sortOrder`、`regionCode`、`id`。停用或历史节点不会进入联动列表,但仍可通过父链接口回显。 + +## 2. 父链回显 + +```http +GET /admin/region/376/path +Authorization: Bearer <管理员令牌> +``` + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { + "selectedId": "376", + "provinceId": "1", + "prefectureId": "35", + "countyId": "376", + "path": [ + { + "id": "1", + "parentId": null, + "levelCode": "PROVINCE", + "depth": 1, + "name": "北京市", + "nodeKind": "ADMINISTRATIVE", + "currentlyEffective": true + }, + { + "id": "35", + "parentId": "1", + "levelCode": "PREFECTURE", + "depth": 2, + "name": "北京市", + "nodeKind": "ADMINISTRATIVE", + "currentlyEffective": true + }, + { + "id": "376", + "parentId": "35", + "levelCode": "COUNTY", + "depth": 3, + "name": "东城区", + "nodeKind": "ADMINISTRATIVE", + "currentlyEffective": true + } + ] + } +} +``` + +目标为省级或地级时,尚未到达的快捷层级字段返回 `null`。历史停用节点可返回,但节点自身的 `currentlyEffective=false`,调用方不能把它重新放入当前可选列表。父链存在孤儿、环、越级或超过当前安全深度时,接口失败且不返回部分路径。 + +## 3. 创建节点 + +```http +POST /admin/region +Authorization: Bearer <具有 system:region:create 的管理员令牌> +Content-Type: application/json +``` + +创建一个县级业务行政区示例: + +```json +{ + "codeStandard": "HL_CUSTOM", + "regionCode": "DEMO-COUNTY-001", + "parentId": "35", + "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": "示例扩展信息" + } +} +``` + +成功响应的 `data` 是字符串 ID: + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": "2090000000000000001" +} +``` + +`extension` 只能是 JSON 对象;历史入参名 `extJson` 仅作为请求别名兼容,响应统一使用 `extension`。服务端会记录来源版本、来源定位、操作人和来源校验值,并在事务提交后刷新行政区划独立缓存。 + +## 4. 更新节点 + +```http +PUT /admin/region/2090000000000000001 +Authorization: Bearer <具有 system:region:update 的管理员令牌> +Content-Type: application/json +``` + +```json +{ + "parentId": "35", + "levelCode": "COUNTY", + "name": "示例业务区(更新)", + "shortName": "示例区", + "navigable": true, + "selectable": true, + "validFrom": "2026-08-26", + "validTo": null, + "status": "ACTIVE", + "sortOrder": 10001, + "extension": { + "businessNote": "名称已更新" + }, + "rowVersion": 0 +} +``` + +```json +{ + "code": 200, + "message": "行政区划更新成功", + "success": true, + "data": null +} +``` + +更新采用完整可变字段加 `rowVersion` 的 CAS 语义。成功后版本号递增;并发版本过期返回 `210807`,不会覆盖另一位管理员的提交。编码标准、地区编码、节点类型和来源证据不可通过更新接口改写。 + +已到生效日的 `GB/T 2260` 法定节点不能原位修改历史属性;只能保持其他字段不变,将旧版本关闭,再创建不重叠的新版本。有子节点的分支不能直接移动或改变层级,父节点的状态、导航能力和有效期必须持续覆盖当前或未来仍会生效的子版本。 + +## 字段说明 + +| 字段 | JSON 类型 | 说明 | +|---|---|---| +| `id`、`parentId` | string / null | 行政区划节点与父节点 ID;响应始终为字符串 | +| `codeStandard` | string | `GB/T 2260`、`HL_CUSTOM` 或聚合节点专用 `HL_GROUP` | +| `regionCode` | string | 同一编码标准内的业务标识;`GB/T 2260` 必须为六位数字 | +| `levelCode` | string | 当前三级固定为 `PROVINCE`、`PREFECTURE`、`COUNTY` | +| `nodeKind` | string | `ADMINISTRATIVE` 或 `AGGREGATION` | +| `navigable` | boolean | 是否可继续逐级导航 | +| `selectable` | boolean | 是否可作为最终业务值 | +| `hasChildren` | boolean | 是否存在当前有效、启用的直接子节点 | +| `currentlyEffective` | boolean | 节点在中国业务当日是否处于有效时间窗且为 `ACTIVE` | +| `validFrom`、`validTo` | string / null | `yyyy-MM-dd`;结束日为开区间 | +| `status` | string | `ACTIVE` 或 `INACTIVE` | +| `rowVersion` | integer | 更新 CAS 版本;创建后从 0 开始 | + +## 业务错误码 + +| code | message | 典型场景 | +|---:|---|---| +| `210801` | 行政区划节点不存在 | 查询不存在的父级或父链目标 | +| `210802` | 无行政区划维护权限 | 创建或更新缺少细粒度权限 | +| `210803` | 行政区划父节点不合法 | 父级不可导航或有效期不能覆盖子级 | +| `210804` | 行政区划层级超过当前允许深度 | 写入超过当前三级边界 | +| `210805` | 行政区划父链存在循环 | 自引用或移动后形成祖先环 | +| `210806` | 行政区划当前编码已存在 | 当前唯一键冲突 | +| `210807` | 行政区划已被其他操作更新,请刷新后重试 | `rowVersion` 过期 | +| `210808` | 行政区划有效期不合法 | `validTo` 不晚于 `validFrom` | +| `210809` | 该行政区划编码标准为系统保留值 | 新写入继续使用旧 `HL_INTERNAL` | +| `210810` | 存在子节点的行政区划不能移动或变更层级 | 分支移动或改层级 | +| `210811` | 行政区划父链数据不完整 | 孤儿、越级、环或异常终止 | +| `210812` | 行政区划扩展字段必须是合法 JSON | `extension` 不是 JSON 对象 | +| `210813` | 行政区划层级代码与父链深度不一致 | 省市县代码与派生深度不匹配 | +| `210814` | 行政区划更新与现有子节点状态或有效期冲突 | 父级提前停用、取消导航或缩短窗口 | +| `210815` | 行政区划编码的版本有效期与既有数据重叠 | 同编码版本时间窗重叠 | +| `210816` | 行政区划节点类型或编码命名空间不合法 | 聚合节点类型、命名空间或可选性组合错误 | +| `210817` | 已生效法定行政区划只能关闭旧版本后新增 | 原位改写生效中的法定节点 | +| `210818` | 行政区划数据来源信息不完整 | 缺少来源版本或定位 | +| `210819` | 法定行政区划代码必须为六位数字 | `GB/T 2260` 新版本编码格式错误 | + +参数格式错误继续使用统一参数错误响应,例如未登录返回业务码 `401`,`parentId=0` 或空创建请求返回业务码 `400`。 + +## 兼容与接入建议 + +- 三级选择器首次加载调用不带 `parentId` 的 `children`;选择省、市后分别以当前 ID 继续查询下一层。 +- 编辑历史数据时先调用 `/{id}/path` 得到完整选中链,再逐层加载 `children`;不要根据行政区编码截位推断父子关系。 +- `AGGREGATION` 节点可能可导航但不可选,页面应分别使用 `navigable` 和 `selectable`,不要只根据 `hasChildren` 判断。 +- 客户端必须把响应 ID 当字符串保存和比较;请求路径与查询参数可以继续发送十进制字符串。 +- 旧行政区划缓存中的数字 ID 与新缓存中的字符串 ID 均可被后端读取,滚动部署期间无需调用方切换缓存版本。 + +## 验证证据 + +- PR #6359、#6387、#6393、#6398、#6402、#6412 均已合并 `dev-v3`,六个合并提交都包含在 TEST 精确提交 `b62054035a139e3c689179ef75e2d052d08dde52` 中。 +- Deploy Panel API 任务 `52771074` 部署 `hl-user-service`,任务 `b1db24b2` 部署 `hl-gateway`;两项脚本均为 `/opt/hulalv/scripts/deploy-backend.sh`,终态 `success`、退出码 0,双实例、Nacos 健康注册、滚动采样与部署窗口日志检查通过。 +- 真实 TEST 管理员经 Gateway 验证未认证返回 401、根级/子级联动、父链回显、失败零写入、创建字符串 ID、数据库落行、Redis 回填、CAS 更新和更新后缓存一致性。 +- 验收创建的唯一测试行已精确删除;数据库总量与测试命名空间恢复基线,四个 Redis 快照按原值和绝对过期时间恢复,缓存锁释放,登录会话注销。操作审计按系统设计保留。 + +## 撤回 + +代码撤回需回退上述六个 PR 并按依赖逆序重新部署 `hl-gateway` 与 `hl-user-service`;调用方停止访问 `/admin/region/**`,并经 Gateway 验证新增路由已不可达、既有 User 接口正常。数据库迁移已经在 TEST 应用,回退代码时保留行政区划表和权限元数据,不执行降版 DDL;Redis 仅在确需重建时精确清理 `hl:user:region:v1:` 命名空间,禁止触碰登录及其他业务缓存。无 MQ 或跨服务写入需要补偿。