# /mp/product/{id} itinerary[].hotels 补 hotelId 字段

**日期**: 2026-04-24
**PR**: #1365 (dev)
**Issue**: #1363
**类型**: fix
**服务**: hl-product-service-v2

---

## 摘要

小程序产品详情接口 `GET /mp/product/{productId}` 返回的 `itinerary[].hotels[]` 数组**新增 `hotelId` 字段**（String 序列化 Long，与 `roomTypeId` 风格一致），前端酒店卡片终于能拿到 ID 跳转酒店详情页。

此前响应只有 `hotelName/roomTypeId/coverUrl/hotelType/city` 等 9 个字段，**没有 hotelId**，点击卡片无法跳详情（根因是后端 VO 未声明字段，`BeanUtil.toBean` 按属性名匹配时静默丢弃）。

## 影响接口

| 接口 | 字段路径 |
|------|----------|
| `GET /mp/product/{productId}`（orderId 不传，实时路径） | `data.itinerary[].hotels[].hotelId` |
| `GET /mp/product/{productId}?orderId=xxx`（快照路径） | `data.itinerary[].hotels[].hotelId` |
| `GET /mp/product/{productId}/day/{dayNumber}` 复用同 VO | `data.hotels[].hotelId` |

GROUP / CORE / CUSTOM 三类产品 + 快照路径一并修复。

## 新字段

```json
{
  "data": {
    "itinerary": [
      {
        "dayNumber": 1,
        "hotels": [
          {
            "hotelId": "2023714929877450753",   // ⬅️ 新增
            "hotelName": "呼伦贝尔香格里拉大酒店",
            "tierSeq": 1,
            "tierName": "轻奢",
            "roomTypeId": "2023727403196502017",
            "roomTypeName": "普通标间",
            "roomCount": 8,
            "coverUrl": "...",
            "hotelType": "HOTEL",
            "city": "呼伦贝尔市"
          }
        ]
      }
    ]
  }
}
```

**注意**：`hotelId` 是 `Long` 类型但后端走全局 `NumberSerializer` **输出为 String**（规避前端大整数精度问题），前端务必按 String 接收。

## 前端适配建议

- 酒店卡片点击事件可以读 `hotels[i].hotelId` 直接跳酒店详情页（此前只能跳房型详情或降级处理）
- 老前端不读 `hotelId` 也不受影响（纯增字段，向后兼容）

## 验证

- 测试服 `GET /mp/product/2044306857534636034`：`itinerary[0].hotels[0].hotelId = "2023714929877450753"` ✓
- 同响应第 2 天 `hotels[0].hotelId = "3001000000000000002"` ✓

## 风险与回滚

- 纯增字段，JSON 向后兼容，无签名变化
- 缓存：`mp:product:detail` 5min TTL / `mp:product:snapshot` 24h TTL，测试环境已过期/覆盖
- 单测：hl-product-service-v2 模块 966 全绿
- 回滚：revert PR #1365 即可
