---
date: 2026-05-07
type: backend-fix
module: hl-resource-service/scenic+staff
priority: low
notify: ["@mmg"]
status: deployed-pending
restart_service: hl-resource-service
gitea_pr: 1807
gitea_issue: [1794, 1795]
related_cases: [JQ-011, RY-002]
---

# 景区下架引用校验 + 服务人员手机号格式校验

## 现象

- **JQ-011**：景区被产品引用时 admin 直接下架，无业务校验提示
- **RY-002**：服务人员录入手机号格式校验缺失（`abc123` / `12345` 都能保存）

## 修复

PR #1807 已合并 dev，两个工单合一个 PR。

### #1794 景区下架引用校验

`ScenicSpotService.updateStatus` + `batchUpdateStatus` 加 `productFeignClient.checkResourceInUse("SCENIC", id)`：
- 仅下架（STATUS_OFF）触发，上架不触发
- 单个被引用 → 抛 `BusinessException(SCENIC_DISABLE_IN_USE, 产品名)`，message 含产品名（如"景区已被【XX 一日游】使用，请先解除关联"）
- 批量被引用 → 跳过+log.warn（与既有 approvalNo 跳过模式一致）

### #1795 服务人员手机号校验

`StaffCreateRequest.phone` + `emergencyPhone` 加 `@Pattern(regexp = "^$|^1[3-9]\\d{9}$", message = "手机号格式不正确")`，`StaffUpdateRequest` 同样。

允许空字符串（phone 是非必填字段），非空必须符合 11 位手机号格式。

## 前端配合（@mmg）

### 景区下架弹窗

下架接口现在可能返 errorCode `SCENIC_DISABLE_IN_USE`，前端可以 catch 这个错误码，弹窗提示用户解除关联：

```js
try {
  await offlineScenic(id)
} catch (e) {
  if (e.code === xxxxx /* SCENIC_DISABLE_IN_USE 错误码 */) {
    showDialog({
      title: '景区已被使用',
      content: e.message,  // 含产品名
    })
  }
}
```

### 服务人员手机号

前端可以增加同样的格式校验提供更好体验（同步反馈），但即使没加，后端会拦截并返业务码非 200。

## 重启服务

`hl-resource-service` 部署测试服后即可。

## 验收

- [ ] 关联中景区下架 → 业务码非 200 + 含产品名提示
- [ ] 无关联景区下架 → 正常下架
- [ ] 服务人员手机号 "abc" → 业务码非 200
- [ ] 手机号 "13800138000" → 正常保存
- [ ] emergencyPhone 同样校验

## 关联工单
- Gitea #1794 已 Closes 关闭
- Gitea #1795 已 Closes 关闭

## 备注：项目级遗留（建议另立工单跟踪，不在本 PR 范围）

/@bug 顺手发现两个项目级问题，**不在本 PR 修复范围**：

1. **8 个资源服务（hotel/scenic/activity/restaurant/staff/supplies/cost/vehicle）的 `updateStatus` 方法全部缺 `checkResourceInUse`**，本 PR 只修景区一个。其余 7 个建议 PM 决策是否扩大修复范围。
2. **hl-resource-service 整体无字段加密**：`Staff.phone / emergencyPhone / 身份证号` 等敏感字段未挂 `EncryptTypeHandler`（grep `EncryptTypeHandler` 整模块零匹配）。
3. **`ProductFeignFallbackFactory.checkResourceInUse` 降级返回空 list = fail-open**：product-service 真挂时景区会被偷偷下架。

以上 3 项建议 PM 评估优先级后另立 feat/refactor 工单。
