---
date: 2026-05-08
type: backend-refactor
module: hl-resource-service
priority: low
notify: ["@mmg"]
status: tested-passed
restart_service: hl-resource-service
gitea_pr: 1841
gitea_issue: []
---

# 资源服务 ResourceReferenceCheckHelper 抽取(纯重构,前端无感)

## 背景

PR #1838 引入 fail-closed 后,resource service 11 个文件 16 处调用点重复 8-10 行 `productFeignClient.checkResourceInUse + isSuccess + throw + getData + 被引用 throw` 模板。

PR #1841 抽取到 `ResourceReferenceCheckHelper`,2 个公开方法:
- `throwIfInUse(type, id, errorCode)` — 单删除/下架场景
- `getReferencingProducts(type, id)` — batchDelete 累积场景

每处调用点 8-10 行 → 1 行 helper 调用,**净减 178 行**(35 文件 +177/-355,加 helper+test +271 = 净增 87)。

## 前端可见变化

**完全无变化** — API 行为/响应/业务码全部保持一致。前端不需要任何改动。

测试服 round-trip 实测 (PR #1838 同样反例):
```
DELETE /admin/scenic/spots/batch ids=[3001000000000000016]
HTTP=200 code=200
data={successCount:0, deniedIds:["3001000000000000016"],
      deniedReasons:["被产品使用: 结婚不回家、带孩子， 看一次真正的草原。、...(13 个产品名)"]}
post-check GET 景区仍存在 ✅
```

## 重启服务

测试服已部署:`hl-resource-service`(PR #1841 合 dev → deploy panel)。

## 改造范围

11 个 service 文件 16 处调用点全部统一改用 helper:

- ScenicSpotService 4 处(updateStatus / batchUpdateStatus / batchDelete / submitApproval)
- StaffService 2 处 / SuppliesService 2 处
- HotelService / RoomTypeService / RestaurantService / ActivityService / ServiceItemService / SuppliesComboService / VehicleModelService / CostItemService 各 1 处

22 个既有 service test 同步切 mock(`ProductFeignClient` → `ResourceReferenceCheckHelper`)。

## 关联

- PR #1838 / #1839 / Issue #1821(纯重构 follow-up,无新工单)

## Follow-up(本 PR 不修)

- 余下 PR #1838 follow-up 项: RoomType 禁用按 HOTEL 维度校验语义偏严 / `product_itinerary_node(resource_type, resource_id)` 复合索引 / batchDelete TOCTOU 时序问题
- 单测加 H2 集成测试或 @MybatisPlusTest 真跑 SQL 防止 mock 单测假阳(memory `experience/qa/mock-tests-pass-cr-pass-but-prod-schema-mismatch-real-deletes-2-resources.md`)
