feat: 通知 mmg - 资源删除/下架引用校验真生效 + 产品服务不可用 fail-closed (PR #1838 + hotfix #1839, Closes wx/HL#1821)
- /internal/product/resource-in-use endpoint 闭环 - ProductItineraryNodeMapper 兼容 resource_type NULL,用 (node_type OR resource_type) 双兜底 - 资源侧 16 处 fail-closed,新增业务码 100903 "产品服务不可用" - 测试服 round-trip 实测 13 个中文产品名通过
这个提交包含在:
父节点
acb881a121
当前提交
eb91ded1ac
@ -0,0 +1,104 @@
|
||||
---
|
||||
date: 2026-05-07
|
||||
type: backend-feat
|
||||
module: hl-product-service-v2/internal + hl-resource-service
|
||||
priority: medium
|
||||
notify: ["@mmg"]
|
||||
status: tested-passed
|
||||
restart_service: hl-product-service-v2 + hl-resource-service
|
||||
gitea_pr: [1838, 1839]
|
||||
gitea_issue: [1821]
|
||||
---
|
||||
|
||||
# 资源删除/下架引用校验真实生效 + 产品服务不可用时改 fail-closed
|
||||
|
||||
## 背景
|
||||
|
||||
PR #1815 + #1807 让资源侧 11~16 处 service 在删除/下架前调 `productFeignClient.checkResourceInUse(...)` 校验,但 **`hl-product-service-v2` 侧 endpoint 一直未实现**:`ProductFeignFallbackFactory` 默认 fail-open 返空 list → **所有引用校验都被静默放行**(测试服老景区 3001000000000000016 被 13 个产品引用却能 batchDelete 直接删)。
|
||||
|
||||
PR #1838 + 紧急 hotfix #1839 一次性闭环。
|
||||
|
||||
## 后端改动
|
||||
|
||||
### product 侧 (PR #1838)
|
||||
- 新增 `/internal/product/resource-in-use` endpoint (11 类资源:SCENIC/RESTAURANT/ACTIVITY/SERVICE/HOTEL/ROOM_TYPE/SUPPLIES/SUPPLIES_COMBO/STAFF/VEHICLE/COST)
|
||||
- 返回 `Result<List<String>>`(被引用的产品名列表,空 list = 无引用)
|
||||
- 上限 100,升序稳定输出
|
||||
- Flyway V20260507_004 幂等补 `product_supplies.from_combo_id` 列(legacy alter 未入 migration)
|
||||
|
||||
### product 侧 (hotfix PR #1839)
|
||||
- `ProductItineraryNodeMapper.selectProductIdsByResource` WHERE 条件改为 `(node_type=? OR resource_type=?)` OR 双兜底
|
||||
- 修因:历史 product_itinerary_node 数据 `resource_type` 列长期 NULL,真实类型存于 `node_type` 列(MpProductSnapshotAssembler:346 注释 "快照:资源类型用 nodeType 兜底"已说明)
|
||||
- 测试服 round-trip 实测:删除 3001000000000000016 → deniedIds 含 + reasons 含 13 个中文产品名
|
||||
|
||||
### resource 侧
|
||||
- `ProductFeignFallbackFactory` 改 fail-closed:产品服务失败/熔断时返 `code=100903 "产品服务不可用,无法校验资源引用,请稍后重试"`
|
||||
- 16 处调用点统一改 `if (!result.isSuccess()) throw FEIGN_RPC_FAIL` 模式
|
||||
|
||||
### 顺带修复 PR #1815 两处 Service 层 LambdaQueryWrapper 违规
|
||||
- `ScenicSpotService.updateSpot` 下沉到 `ScenicSpotMapper.countByNameExcludeId`
|
||||
- `StaffService.validatePhoneUnique` 下沉到 `StaffMapper.countByPhoneExcludeId`
|
||||
|
||||
## 前端可见变化
|
||||
|
||||
### 资源删除/下架接口异常情况新增
|
||||
|
||||
资源(景区/餐厅/活动/酒店/房型/服务人员/备品/备品组合/用车/成本项 等)删除/下架接口在**产品服务不可用时**会返:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 100903,
|
||||
"message": "产品服务不可用,无法校验资源引用,请稍后重试"
|
||||
}
|
||||
```
|
||||
|
||||
@mmg 前端需要捕获 100903,提示用户重试。
|
||||
|
||||
### 被引用资源删除/下架的响应(原有但现在真生效)
|
||||
|
||||
batchDelete 接口返:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"successCount": 0,
|
||||
"deniedIds": ["3001000000000000016"],
|
||||
"deniedReasons": ["被产品使用: 产品A、产品B、产品C..."]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
前端原有展示 deniedReasons 即可。
|
||||
|
||||
### batchDelete 行为变化
|
||||
批量删除时若产品服务调用失败,**整体抛异常中止**(原可能 N 次重试,部分 denied 部分成功)。前端弹"产品服务不可用,请稍后重试"即可,无需逐条处理。
|
||||
|
||||
## 重启服务
|
||||
|
||||
测试服已部署:`hl-product-service-v2`(PR #1838 → #1839 hotfix 已 redeploy)+ `hl-resource-service`(PR #1838)。
|
||||
|
||||
## 验收(测试服 round-trip 实测)
|
||||
|
||||
- [x] **创建产品引用资源(景区) → batchDelete 该景区** → `deniedReasons` 含中文产品名,景区未被删除 (UC-A 实测 13 个产品名)
|
||||
- [x] **删除/下架未被引用的资源** → 正常成功 (UC-D 创建新景区→batchDelete successCount=1)
|
||||
- [x] **fail-closed 模式生效**:Mapper OR 双兜底命中 nodeType 字段
|
||||
- [x] **PR #1815 两处分层违规下沉到 Mapper** (UC-5b 编辑自身保留同名 code=200)
|
||||
- [x] **跨服务 Feign 不配 fallback 仅返 isSuccess=false** (`Result.error`)
|
||||
- [ ] hotel/activity/vehicle/cost 4 资源服务 fail-closed 同步覆盖 (代码已改,测试服 round-trip 时间所限只跑 SCENIC,留监控验证)
|
||||
|
||||
## 关联工单
|
||||
|
||||
- Gitea #1821 (本 PR 闭环, 已 auto-close via PR #1838)
|
||||
|
||||
## Follow-up(本 PR 不修)
|
||||
|
||||
- 16 处 fail-closed 模板代码可提取 `ResourceReferenceCheckHelper`
|
||||
- RoomType 禁用按 HOTEL 维度校验语义偏严(PR #1815 引入)
|
||||
- `product_itinerary_node(resource_type, resource_id)` 复合索引(数据量起来后)
|
||||
- `product_supplies(supplies_resource_id)` / `product_route_info(vehicle_model_id)` 索引
|
||||
- batchDelete TOCTOU 时序问题
|
||||
- PR #1838 单测 14 用例都用 mock,Mocked Mapper 无法暴露 SQL 与 DB schema 错位 — 后续应加 H2 集成测试或 `@MybatisPlusTest` 真跑 SQL
|
||||
|
||||
## 已知影响(测试服)
|
||||
|
||||
- QA round-trip 期间 SCENIC `3001000000000000005` + RESTAURANT `2023382103407751169` 被前轮(hotfix 前)误删,无法恢复;测试人员需重建相关测试数据
|
||||
正在加载...
x
在新工单中引用
屏蔽一个用户