比较提交

..
作者 SHA1 备注 提交日期
lc 2e18de27e1 Merge pull request 'docs(changelog): 交付行政区划主数据接口(#6350)' (#77) from docs/6350-region-api-changelog into main
changelog-filename-gate / validate (push) Successful in 2s
2026-08-26 15:53:04 +08:00
lc 6461e1ce39 docs(changelog): 交付行政区划主数据接口(#6350)
changelog-filename-gate / validate (pull_request) Successful in 2s
2026-08-26 15:52:27 +08:00
lc 48b1d069c8 Merge pull request 'docs(changelog): 交付供应商暂停与拉黑接口(#6392)' (#76) from chore/6392-api-changelog into main
changelog-filename-gate / validate (push) Successful in 2s
2026-08-26 15:45:47 +08:00
lc 36a85b8669 docs(changelog): 交付供应商暂停与拉黑接口(#6392)
changelog-filename-gate / validate (pull_request) Successful in 2s
2026-08-26 15:45:16 +08:00
lc 43b9c16a36 Merge pull request 'docs(changelog): 交付行政区划 ID 字符串契约(#6409)' (#75) from chore/6409-api-changelog into main
changelog-filename-gate / validate (push) Successful in 2s
2026-08-26 15:30:01 +08:00
共修改 2 个文件,包含 404 行新增和 0 行删除
@@ -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<T>`。业务失败可能仍是 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 或跨服务写入需要补偿。
@@ -0,0 +1,114 @@
---
schema: "hl-changelog/v2"
ticket: "6392"
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: ""
status_note: "PR #6403 已合并 dev-v3;两个状态命令已部署 TEST,ACTIVE→SUSPENDED、ACTIVE/SUSPENDED→BLACKLIST、原因审计、权限、并发、幂等和失败零写入均经真实 Gateway 验证。前端待接入原因弹窗与并发版本。"
updated_at: "2026-08-26"
base: "dev-v3"
---
# 供应商暂停合作与拉黑原因必填接口
供应商管理新增两个窄状态命令:暂停合作与列入黑名单。服务端强制要求业务原因和调用方读取到的并发版本,并沿用可信管理员身份、专用平台权限、聚合锁、短窗幂等、行锁、状态机及同事务审计。
## 变更接口
| 方法 | 路径 | 允许来源状态 | 目标状态 |
|---|---|---|---|
| POST | `/admin/supplier/items/{supplierId}/suspend` | `ACTIVE` | `SUSPENDED` |
| POST | `/admin/supplier/items/{supplierId}/blacklist` | `ACTIVE`、`SUSPENDED` | `BLACKLIST` |
两个接口都要求真实 `SUPER_ADMIN` 身份且拥有 `supplier:status:manage` 平台权限。前端按钮可按角色和状态控制展示,但不能替代服务端门禁。
## 公共请求
```json
{
"reason": "供应商连续违约,暂停合作复核",
"expectedUpdateTime": "2026-08-26 15:40:00"
}
```
| 字段 | 类型 | 必填 | 规则 |
|---|---|---|---|
| `reason` | string | 是 | 去除首尾空白后必须非空,最长 500 字符;规范化后的原因为状态审计内容 |
| `expectedUpdateTime` | string | 是 | 调用方最近一次读取到的供应商 `updateTime`,格式 `yyyy-MM-dd HH:mm:ss` |
路径参数 `supplierId` 必须为正 Long。请求体不接受角色、操作者或目标状态,身份仅来自 Gateway 验证后的可信属性。
## 成功响应
暂停合作成功:
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"supplierId": "1900000000000000001",
"status": "SUSPENDED",
"updateTime": "2026-08-26 15:40:01"
}
}
```
拉黑成功时结构相同,`status` 为 `BLACKLIST`。`supplierId` 固定为 JSON string;后续状态命令必须使用响应或详情中的新 `updateTime`,不能继续提交旧版本。
每次成功迁移与一条 `supplier_change_log` 状态审计在同一本地事务内提交。审计记录包含 `fromStatus`、`toStatus`、操作者、角色、Trace ID 和规范化后的 `reason`;审计写入失败时状态整体回滚。
## 失败语义
| 场景 | 业务码 | 结果 |
|---|---:|---|
| 未登录或 Token 无效 | `401` | Gateway/服务认证拒绝,零业务写入 |
| `reason` 缺失、空白、纯空格、超过 500 字符,或缺少版本 | `400` | 参数/服务双层拒绝,零业务写入 |
| 供应商不存在或已软删除 | `395001` | 零业务写入 |
| 非 `SUPER_ADMIN` 或缺少 `supplier:status:manage` | `395004` | 专用状态权限失败关闭,零业务写入 |
| suspend 来源不是 `ACTIVE`;blacklist 来源不是 `ACTIVE/SUSPENDED` | `395005` | 状态机拒绝,零业务写入 |
| `expectedUpdateTime` 与数据库秒级版本不一致 | `395014` | 状态和审计均不写入,调用方应刷新后重试 |
| 5 秒内同操作者、供应商和相同请求重复提交 | `100502` | 重复请求被拒绝,不产生第二条状态审计 |
统一响应可能以 HTTP 200 承载业务失败,客户端必须同时检查 `code`、`success`、`message` 和 `data`。
## 前端联调事项
- 在合作中供应商上提供“暂停合作”和“列入黑名单”;暂停合作供应商仅提供“列入黑名单”。其他状态不展示这两个动作。
- 点击动作后弹出必填原因输入框,前端限制 500 字符;提交详情或列表中最近一次读取到的 `updateTime`。
- `395014` 应提示数据已变化并刷新详情;`395005` 应刷新当前状态;`100502` 应提示不要重复提交。
- 成功后使用响应中的 `status` 和 `updateTime` 更新页面或重新拉取详情/列表。
- 前端显示控制不构成授权;不得从请求体传入操作者、角色或目标状态。
## 验证证据
- 代码:PR #6403 合并提交为 `3f3a0bf734737ca0e84ed1cfcad1801835cd46d5`;供应商目标测试 62 项通过,Resource 全量 2100 项通过、0 失败、0 错误(38 项既有条件跳过),Gateway 路由/认证 9 项通过,`hl-verify` 全部通过。
- 部署:原规范任务 `d08c52f6` 成功,目标/实际提交均为 `4b31612aeba5c0d3ac690cc6af15b66290bd8b48`;只读恢复绑定原任务期样本,5 个可验证样本均健康、3 个本地不可验证样本、0 个故障样本,没有重复部署。
- 当前制品:后续同服务规范任务 `da90f211` 将 TEST 更新为 `dev-v3@692547a5989ec1f8fa1b1495fc8dd501cddc0fe0`,该提交包含本工单合并提交;6/6 个任务期样本均为双进程、Nacos 双 healthy/enabled 且端口匹配,0 不可验证、0 故障样本。本工单最终业务验收在该当前制品上完成。
- 真实 Gateway:验证 `ACTIVE→SUSPENDED→BLACKLIST` 和 `ACTIVE→BLACKLIST`,成功响应、详情状态、并发版本与同事务审计一致;原因首尾空白按规范化值入审计。
- 失败分支:未认证、真实 `CUSTOMIZER` 越权、原因缺失/空白/超长、缺版本、旧版本、DRAFT 来源、不存在对象、重复状态与短窗重复请求全部返回预期错误,主体版本、状态和审计计数均保持不变。
- 清理:仅创建 3 个随机 TEST 草稿,并以 ID、随机全名、当前状态三重条件设置 2 个 ACTIVE 夹具;未触碰既有供应商。验收后精确恢复 2 行为 DRAFT,再通过业务删除接口软删除全部 3 个夹具;有效供应商总量恢复为 4,`HL6392-` 活跃命名空间为 0。9 条脱敏审计按系统约定保留;两枚验收会话已注销,幂等键等待 6 秒自然过期,未手工修改 Redis。
## 撤回
1. 从最新 `dev-v3` 创建独立回退分支,执行 `git revert -m 1 --no-edit 3f3a0bf734737ca0e84ed1cfcad1801835cd46d5`,经评审 PR 合入;不要回退后续无关提交。
2. 通过 Deploy Panel API 对精确回退目标执行新鲜预检和显式部署 `hl-resource-service`,复核双实例、Nacos、日志以及既有供应商查询、建档、资料更新和审批链路。
3. 本次无 DDL、配置、Redis、MQ 或跨服务写入变更,不执行 schema、缓存或消息回退。
4. 已实际发生的 `SUSPENDED`、`BLACKLIST` 状态和审计不会随代码回退自动反转;禁止直接改库,只能由后续合法恢复合作/解除黑名单业务流程处理。
5. 回退后两个新增 POST 接口恢复不可用;前端在回退部署前隐藏/停用对应动作,并继续兼容既有供应商接口。
## 关联 / 联系人
- **Issue**: [#6392](https://git.1814.love:8443/wx/HL/issues/6392)
- **PR**: [#6403](https://git.1814.love:8443/wx/HL/pulls/6403)
- **合并提交**: [3f3a0bf73](https://git.1814.love:8443/wx/HL/commit/3f3a0bf734737ca0e84ed1cfcad1801835cd46d5)
- **后端负责人**: @lc