Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UqNs52DGnGU29PN8Ey37Ms
20 KiB
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 | 7510 | 服务按景区口径接入供应商关系 | admin | lc(GIT) | 修改接口 | deployed | verified | pending | 2026-09-12 | 后端已部署并经 Gateway 验证;待前端展示服务列表供应商全称并接入服务供应商关系操作。 | 2026-09-11 | dev-v3 |
服务管理:按景区口径接入供应商关系
服务: hl-resource-service(8082) PR: #7554 Issue: #7510 日期: 2026-09-11 影响范围: 管理端服务列表、服务供应商查询/设置/改绑/解绑
⚠️ 关键变化
- 服务列表每条记录固定返回
supplierFullName;无当前供应商时为null。 - 服务使用通用资源供应商关系接口,路径参数固定传
resourceModule=SERVICE;供应商类型固定为SERVICE,requiredTypeCode可省略。 - 服务首次绑定、改绑和解绑都可以省略
changeReason;此前设置、改绑缺原因返回400 changeReason不能为空。 - 真正改绑或解绑前会检查订单:服务出现在未结束订单的行程中时返回
395063。
一、背景
服务管理此前列表不含供应商全称,关系操作要求填写原因,改绑、解绑不检查订单。本次按景区、餐厅、游玩项目、酒店现行口径冻结服务所需的展示字段、调用参数、原因规则和订单状态边界。
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 服务列表 | GET | /admin/service/items |
响应字段新增 | 每条记录固定返回 supplierFullName |
| 2 | 查询服务当前供应商 | GET | /admin/supplier/resource-relations/{resourceModule}/{resourceId}/view |
调用契约补充 | 服务传 resourceModule=SERVICE |
| 3 | 设置或改绑服务供应商 | PUT | /admin/supplier/resource-relations/{resourceModule}/{resourceId}/update |
请求与行为修改 | 可省略原因;真正改绑增加未结束订单门禁 |
| 4 | 解绑服务供应商 | POST | /admin/supplier/resource-relations/{resourceModule}/{resourceId}/unbind |
行为修改 | 可省略原因;增加未结束订单门禁 |
三、接口详情
1. 服务列表 GET /admin/service/items
VO: ServiceQueryRequest / PageResult<ServiceItemAdminListVO>
使用场景
服务管理列表初始化、翻页、筛选或刷新时调用;列表“供应商”列直接读取 supplierFullName。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
keyword |
Query | String | 否 | 最长 100 字 | 名称模糊搜索 |
cityKeyword |
Query | String | 否 | 最长 100 字 | 城市名、省份、地址模糊搜索 |
categoryCode |
Query | String | 否 | - | 分类编码 |
isPaid |
Query | Integer | 否 | 0 免费,1 收费 |
是否收费 |
billingType |
Query | String | 否 | - | 计费方式 |
vehicleType |
Query | String | 否 | - | 配套车型 |
instantConfirm |
Query | Integer | 否 | 0 否,1 是 |
是否即时确认 |
status |
Query | Integer | 否 | 0 下架,1 上架 |
状态筛选 |
tagId |
Query | Long | 否 | - | 单个标签 |
tagIds |
Query | String | 否 | 逗号分隔 | 多个标签 |
city |
Query | String | 否 | - | 城市 |
minPrice / maxPrice |
Query | Number | 否 | - | 价格区间 |
sortBy |
Query | String | 否 | 最长 50 字 | 排序字段 |
sortDir |
Query | String | 否 | asc 或 desc |
排序方向 |
page |
Query | Integer | 否 | 最小 1,默认 1 | 页码 |
pageSize |
Query | Integer | 否 | 1~100,默认 20 | 每页条数 |
以上入参均为既有参数,本次不变。
出参 Result<PageResult<ServiceItemAdminListVO>>
| 字段 | 类型 | 说明 |
|---|---|---|
code / message / success |
Integer / String / Boolean | 业务结果 |
data.total / page / pageSize |
Long / Integer / Integer | 分页信息 |
data.records[].serviceId |
String | 服务 ID |
data.records[].name |
String | 服务名称 |
data.records[].supplierFullName |
String 或 null | 当前有效供应商的法定全称;未关联或供应商已删除时为 null,字段始终存在 |
data.records[] 其他字段 |
Object | 原服务列表字段保持不变 |
请求示例
无请求体:
GET /admin/service/items?page=1&pageSize=20 HTTP/1.1
Authorization: Bearer <token>
响应示例
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"total": 2,
"page": 1,
"pageSize": 20,
"records": [
{"serviceId": "2029000000000000201", "name": "示例服务 A", "status": 1, "supplierFullName": "示例服务供应商有限公司"},
{"serviceId": "2029000000000000202", "name": "示例服务 B", "status": 1, "supplierFullName": null}
]
}
}
空数据 / 降级响应
{"code":200,"message":"成功","success":true,"data":{"total":0,"page":1,"pageSize":20,"records":[]}}
错误响应
{"code":401,"message":"缺少有效的 Authorization 头","success":false,"data":null}
业务边界
- 本次不新增筛选、排序或分页规则;
supplierFullName只用于展示,不是供应商名称快照。 - 无当前有效关系时必须按
null处理,不要根据字段是否存在分支。
2. 查询服务当前供应商 GET /admin/supplier/resource-relations/{resourceModule}/{resourceId}/view
VO: SupplierResourceRelationRespVO
使用场景
打开服务供应商弹窗或在写操作后刷新当前关系时调用。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
resourceModule |
Path | String | 是 | 服务固定为 SERVICE |
资源模块 |
resourceId |
Path | String | 是 | 正整数 | 服务 ID |
出参 Result<SupplierResourceRelationRespVO>
| 字段 | 类型 | 说明 |
|---|---|---|
code / message / success |
Integer / String / Boolean | 业务结果 |
data.relationId |
String | 当前关系 ID |
data.supplierId / supplierNo / supplierName |
String | 当前供应商 ID、编号和全称 |
data.resourceModule / moduleName |
String | SERVICE / 服务管理 |
data.resourceId / resourceName |
String | 服务 ID 和名称 |
data.requiredTypeCode / requiredTypeName |
String | SERVICE / 服务管理 |
data.remark |
String 或 null | 关系备注 |
data.available / unavailableReasons |
Boolean / Array | 当前关系是否仍可用及不可用原因 |
data.createTime / updateTime |
String | yyyy-MM-dd HH:mm:ss;写操作使用最新 updateTime |
请求示例
无请求体:
GET /admin/supplier/resource-relations/SERVICE/2029000000000000201/view HTTP/1.1
Authorization: Bearer <token>
响应示例
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"relationId": "2098200000000000201",
"supplierId": "2097899397319684099",
"supplierNo": "SUP260001",
"supplierName": "示例服务供应商有限公司",
"resourceModule": "SERVICE",
"moduleName": "服务管理",
"resourceId": "2029000000000000201",
"resourceName": "示例服务 A",
"requiredTypeCode": "SERVICE",
"requiredTypeName": "服务管理",
"remark": null,
"available": true,
"unavailableReasons": [],
"createTime": "2026-09-11 20:00:00",
"updateTime": "2026-09-11 20:00:00"
}
}
空数据 / 降级响应
当前没有有效关系时不是空成功,返回 395038。
错误响应
{"code":395038,"message":"供应商资源关联不存在","success":false,"data":null}
业务边界
- 使用现有登录凭证及
supplier:resource:view服务端权限;前端按钮可见性不能替代后端判权。 - 未关联时按
395038展示“未设置供应商”;不要把它当成系统异常。
3. 设置或改绑服务供应商 PUT /admin/supplier/resource-relations/{resourceModule}/{resourceId}/update
VO: SupplierResourceReassignReqVO / SupplierResourceRelationRespVO
使用场景
服务当前无关系时首次设置供应商,或选择另一供应商后改绑。候选供应商仍用 GET /admin/supplier/items/list?resourceModule=SERVICE&resourceId={id} 获取(本次不变)。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
resourceModule |
Path | String | 是 | 服务固定为 SERVICE |
资源模块 |
resourceId |
Path | String | 是 | 正整数 | 服务 ID |
supplierId |
Body | String | 是 | 正整数 | 目标供应商 ID |
requiredTypeCode |
Body | String | 否 | 可省略;传值只能为 SERVICE |
关系要求的供应商类型,由后端固定 |
remark |
Body | String | 否 | 最长 500 字 | 关系备注 |
expectedCurrentSupplierId |
Body | String | 改绑时是 | 与版本时间同时传或同时省略 | 当前关系的 supplierId |
expectedRelationUpdateTime |
Body | String | 改绑时是 | yyyy-MM-dd HH:mm:ss |
当前关系的 updateTime |
changeReason |
Body | String | 否 | 最长 500 字 | 首次设置和改绑均可省略;不要补默认原因 |
出参 Result<SupplierResourceRelationRespVO>
| 字段 | 类型 | 说明 |
|---|---|---|
code / message / success |
Integer / String / Boolean | 业务结果 |
data.relationId / supplierId / supplierNo / supplierName |
String | 生效关系及供应商信息 |
data.resourceModule / moduleName / resourceId / resourceName |
String | 服务模块和资源信息 |
data.requiredTypeCode / requiredTypeName |
String | 固定 SERVICE / 服务管理 |
data.remark |
String 或 null | 关系备注 |
data.available / unavailableReasons |
Boolean / Array | 当前可用性 |
data.createTime / updateTime |
String | 当前关系时间与后续操作版本 |
请求示例
首次绑定时不要传两个 expected* 字段,也无需传 requiredTypeCode、changeReason:
{"supplierId":"2097899397319684099"}
改绑时两个版本字段必须来自最新关系回读:
{"supplierId":"2097849575707475970","expectedCurrentSupplierId":"2097899397319684099","expectedRelationUpdateTime":"2026-09-11 20:00:00"}
响应示例
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"relationId": "2098200000000000202",
"supplierId": "2097849575707475970",
"supplierNo": "SUP260002",
"supplierName": "示例新服务供应商有限公司",
"resourceModule": "SERVICE",
"moduleName": "服务管理",
"resourceId": "2029000000000000201",
"resourceName": "示例服务 A",
"requiredTypeCode": "SERVICE",
"requiredTypeName": "服务管理",
"remark": null,
"available": true,
"unavailableReasons": [],
"createTime": "2026-09-11 20:01:00",
"updateTime": "2026-09-11 20:01:00"
}
}
空数据 / 降级响应
无空成功结果;订单服务、权限或字典等必要依赖无法明确核验时返回失败且不改变关系。
错误响应
{"code":395063,"message":"服务已关联订单,不能更换供应商","success":false,"data":null}
{"code":400,"message":"requiredTypeCode与资源模块不匹配","success":false,"data":null}
业务边界
- 当前无关系时是首次绑定:不传版本对;即使服务已有订单也允许绑定。
- 当前有关系且目标供应商不同才是真正改绑:必须传完整、最新版本对;服务出现在未结束订单行程中时返回
395063,原关系不变;只关联已完成、已取消订单(包括两者混合)时允许。 - 版本过期返回
395014;必要依赖无法明确判断返回395039;失败不改变关系或审计。
4. 解绑服务供应商 POST /admin/supplier/resource-relations/{resourceModule}/{resourceId}/unbind
VO: SupplierResourceUnbindReqVO / Result<Void>
使用场景
用户确认清除服务当前供应商关系时调用。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
resourceModule |
Path | String | 是 | 服务固定为 SERVICE |
资源模块 |
resourceId |
Path | String | 是 | 正整数 | 服务 ID |
expectedCurrentSupplierId |
Body | String | 是 | 正整数 | 当前关系的 supplierId |
expectedRelationUpdateTime |
Body | String | 是 | yyyy-MM-dd HH:mm:ss |
当前关系的 updateTime |
changeReason |
Body | String | 否 | 最长 500 字 | 可省略、null、空串或空白;不要补默认原因 |
出参 Result<Void>
| 字段 | 类型 | 说明 |
|---|---|---|
code / message / success |
Integer / String / Boolean | 业务结果 |
data |
null | 成功时固定为 null |
请求示例
{"expectedCurrentSupplierId":"2097849575707475970","expectedRelationUpdateTime":"2026-09-11 20:01:00"}
响应示例
{"code":200,"message":"成功","success":true,"data":null}
空数据 / 降级响应
当前无关系时返回 395038;必要依赖无法明确核验时返回 395039,均不执行解绑。
错误响应
{"code":395063,"message":"服务已关联订单,不能更换供应商","success":false,"data":null}
业务边界
- 解绑必须使用当前关系最新版本对;版本过期返回
395014,原关系不变。 - 服务出现在未结束订单行程中时返回
395063;只关联已完成、已取消订单(包括两者混合)时允许解绑。 - 登录、
supplier:resource:manage服务端权限、数据范围、幂等、锁和审计规则保持不变;失败零写入。
四、契约约束与正确调用方式
| 场景 | 正确 payload | 调用结果 |
|---|---|---|
| 查询当前关系 | 无请求体,resourceModule=SERVICE |
返回关系;无关系为 395038 |
| 首次绑定 | {"supplierId":"22"} |
不传版本对、类型和原因 |
| 改绑 | {"supplierId":"33","expectedCurrentSupplierId":"22","expectedRelationUpdateTime":"2026-09-11 20:00:00"} |
两个版本字段必须成对且取最新关系值 |
| 解绑 | {"expectedCurrentSupplierId":"33","expectedRelationUpdateTime":"2026-09-11 20:01:00"} |
可省略 changeReason |
| 错误:传其他类型 | {"supplierId":"33","requiredTypeCode":"SCENIC"} |
返回 400,不写入 |
| 错误:版本字段只传一个 | {"supplierId":"33","expectedCurrentSupplierId":"22"} |
返回 400,不写入 |
每次改绑或解绑前先回读当前关系,成功后重新刷新关系和服务列表。业务失败可能仍为 HTTP 200,必须同时判断响应体 code 与 success。
五、数据库行为
| 前端操作 | 外部可观察结果 |
|---|---|
| 首次绑定 | 生成一个当前有效关系,requiredTypeCode=SERVICE;未填原因时不虚构默认原因 |
| 改绑 | 旧关系转为历史,新供应商成为唯一当前关系,并保留正常变更审计 |
| 解绑 | 当前关系消失并保留正常解绑审计;服务列表返回 supplierFullName: null |
| 订单、版本、权限、类型或资格校验失败 | 当前关系和审计均不变化 |
本次不迁移或回填任何数据,不修改订单业务、订单数据或订单供应商快照。
六、边界行为
- 未登录返回
401;关系查询要求查看权限,设置、改绑、解绑要求维护权限及对应数据范围。 - 服务未绑定供应商时,列表返回
supplierFullName: null,关系查询返回395038。 changeReason最长 500 字;省略、null、空串或纯空白均按未填写处理。- 未结束订单包含除
COMPLETED、CANCELLED以外的状态以及未知状态;新旧订单任一确认存在即返回395063。 - 订单关联按景区口径:新订单看订单行程中的服务节点,旧订单看行程新增的服务节点及订单产品快照中的服务节点。
- 无法从任一订单服务得到明确结果时返回
395039,不把依赖异常当成无订单。
六.5、枚举 / 数据字典
resourceModule / requiredTypeCode(服务供应商关系)
所属字段: Path resourceModule、Body/Response requiredTypeCode | 类型: String
| 值 | 中文 | 说明 |
|---|---|---|
SERVICE |
服务管理 | 服务关系固定值;requiredTypeCode 可省略并由后端确定 |
业务错误码
| 值 | 中文 | 说明 |
|---|---|---|
395063 |
服务已关联订单,不能更换供应商 | 本次新增;真正改绑或解绑时存在未结束订单 |
六.6、修改前后对比
字段级对比
| 字段 | 改前 | 改后 |
|---|---|---|
GET /admin/service/items 的 records[].supplierFullName |
不返回 | 每条固定返回 String 或 null |
服务设置/改绑的 changeReason |
必填 | 可省略;原有非空值继续兼容 |
服务解绑的 changeReason |
已可省略 | 保持可省略 |
行为级对比
| 行为 | 改前 | 改后 |
|---|---|---|
| 服务列表展示供应商 | 列表无名称字段 | 直接读取 supplierFullName |
| 服务真正改绑、解绑 | 不检查订单 | 出现在未结束订单行程中时返回 395063;其他情形放行 |
| 尚未绑定供应商的服务 | 可首次绑定(须填原因) | 可首次绑定且可省略原因,即使已有订单 |
六.7、影响评估
- 是否破坏向后兼容: 否;新增可空字段、放宽可选入参与新增业务保护。
- 前端是否必须同步上线: 是。
- 前端 workaround 清理点: 服务列表直接展示
supplierFullName;复用供应商关系弹窗并传SERVICE;删除服务设置/改绑/解绑原因弹框和必填校验;收到395063时提示原文并保持当前关系。
七、不影响范围
- 仅影响: 管理端服务列表、服务供应商关系操作。
- 零影响: 订单创建、状态流转、结算、支付、退款、订单数据和订单供应商快照;供应商候选列表。
- 景区、餐厅、游玩项目、酒店及其他资源模块的字段、类型、原因规则和订单门禁保持原契约;数据库结构、配置、Redis、MQ 和 Gateway 路由无变化。
八、测试环境已验证
TEST 部署提交 70ef93bd249ee7f13034196d3d228c799e793512(hl-order-service-v3 任务 ee77532f、hl-order-service-v2 任务 4022d991、hl-resource-service 任务 999a16cf,均双实例健康);2026-09-12 经 Gateway 以真实 TEST 身份实测 34/34 通过:
| 场景 | 结果 |
|---|---|
GET /admin/service/items 每条记录含 supplierFullName;已绑定服务为当前供应商全称,未绑定为 null |
通过 |
未绑定服务首次绑定、改绑、解绑均不传 changeReason 成功;列表字段随之变化 |
通过 |
requiredTypeCode=SCENIC 绑定服务 |
返回 400 |
| 已绑定且有未结束订单的服务改绑、解绑 | 均返回 395063,原关系不变 |
| 尚未绑定、已被未结束订单行程节点引用的服务首次绑定 | 成功 |
| 同一服务在该订单未结束时改绑、解绑 | 均返回 395063,原关系不变 |
| 订单出行前取消后(仅关联已取消订单)改绑、解绑 | 均成功;订单取消后行程节点仍保留 |
| 验收前后真实供应商关系、V3 服务节点、V2/V3 订单摘要 | 一致 |
仅关联已完成订单、已完成与已取消混合两种情形由 V2、V3 隔离 MySQL 自动化测试证明。
当前状态:后端已部署并验证;待前端处理。
九、相关历史 PR
| PR | Issue | 说明 | 是否仍有效 |
|---|---|---|---|
| #7358 | #7357 | 景区改绑供应商免原因 | 是 |
| #7401 | #7400 | 全部资源供应商解绑免原因 | 是 |
| #7493 | #7387 | 餐厅列表、原因规则与订单门禁 | 是 |
| #7507 | #7500 | 游玩项目列表、原因规则与订单门禁 | 是 |
| #7552 | #7509 | 酒店列表、原因规则与订单门禁 | 是 |
| #7554 | #7510 | 服务列表、原因规则与订单门禁 | 是,服务最新契约 |
十、相关文档
关联 / 联系人
链接
联系人
- 后端负责人: @lc