# MP 产品详情接口支持订单快照回显

- **变更日期**: 2026-04-22
- **PR**: #1182
- **Closes**: #1176
- **影响面**: 小程序端（管理端不涉及）
- **兼容性**: **完全向后兼容**，不传 `orderId` 时行为与改前一致

---

## 🎯 业务背景

用户从订单详情页点进产品详情时，应看到下单时冻结的产品快照（非实时最新），避免产品改价/改行程导致历史订单展示混乱。

---

## 📡 接口变更：`GET /mp/product/{id}`

### 1. 新增可选 Query 参数

| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `orderId` | Long | 否 | 订单 ID。传了走快照路径；不传/0/负数走原逻辑（查最新产品） |

### 2. 触发场景（建议前端）

**仅在**「订单详情页 → 产品卡片/产品详情入口」传 `orderId`。其它入口（首页搜索、列表、分享链接、退款页等）**不传 orderId**，避免快照扩散到无必要场景。

### 3. 请求示例

```http
# 实时路径（原有行为，零变化）
GET /mp/product/1001
Authorization: Bearer <token>

# 快照路径（新增）
GET /mp/product/1001?orderId=99988
Authorization: Bearer <token>
```

---

## 📦 响应 VO 变更：`MpProductDetailRespVO` 新增 3 字段

所有字段都是 **Boolean/Long 类型，可空**，旧前端代码不读时零影响。

| 字段 | 类型 | 说明 |
|---|---|---|
| `isSnapshot` | Boolean | `true`=快照数据（冻结版本）；`false`/null=实时数据 |
| `snapshotOrderId` | Long | 快照来源的订单 ID（仅 isSnapshot=true 时有值）|
| `snapshotFallback` | Boolean | `true`=原本想返回快照但降级到了实时（订单异常/快照空/Feign 失败等）；`false`=正常快照；null=原路径未涉及 |

### 响应三态对照

| 请求 | isSnapshot | snapshotOrderId | snapshotFallback | 含义 |
|---|---|---|---|---|
| 不传 orderId | null/false | null | null | 原逻辑实时数据 |
| 传 orderId（一切正常）| **true** | 99988 | **false** | 返回订单快照，前端可打「订单快照」标识 |
| 传 orderId（降级）| false | 99988 | **true** | 快照出问题降级实时了，前端可提示「当前为最新版本，下单时数据已不可查」 |

---

## 🔻 错误码 / 降级行为

| 场景 | 行为 |
|---|---|
| 不传 orderId | 原逻辑，查最新产品（5min Redis 缓存）|
| 传 orderId=0 或负数 | 同「不传 orderId」 |
| 传的 orderId 不存在 / 非本人 / 已软删 | **统一降级到实时路径** + `snapshotFallback=true`，不抛错（防 orderId 枚举攻击）|
| 快照字段为空（历史订单）/ JSON 解析失败 | 降级实时 + `snapshotFallback=true` |
| 快照正常但对应产品已下架 | 返回快照 + `productDeleted=true`（前端可提示「该产品已下架」）|
| Feign 超时 / order 服务不可达 | 降级实时 + `snapshotFallback=true` |
| `productId` 与快照里的不匹配（防串号）| HTTP 200 + 业务错误码 400 + msg「产品与订单不匹配」|

---

## 🧩 MP 独有字段（不走快照，走实时）

下列字段**永远实时查**，即使走快照路径也会实时补齐（因为这些是用户预期会更新的数据）：

- `reviewSummary`（评价摘要）
- `earlyBirdTip` / `earlyBirdLadder`（早鸟方案）
- `DayInfo.gatherPlace` / `dismissalPlace` / `quoteText`（集合/解散地、引言）
- `NodeInfo.resourceCover` / `resourceImages` / `resourceAddress` / `resourceDescription` / `resourceTags`（资源详情）
- `TierPriceInfo.depositAmount`（按比例算）
- `refundPolicy`（退款政策）

这些字段在快照路径下也正常返回，前端不用关心差异。

---

## 🔒 鉴权

- 小程序 token（登录态）必传。网关从 token 解析 `X-User-Id` Header 注入，后端据此校验订单归属
- 未登录（token 缺失）时走实时路径 + `snapshotFallback=true`

---

## ✅ 前端接入建议

```js
// 从订单详情进产品
const resp = await request({
  url: `/mp/product/${productId}?orderId=${orderId}`,
});

if (resp.data.isSnapshot === true) {
  // 显示角标：订单快照版本
  showBadge('订单快照');
} else if (resp.data.snapshotFallback === true) {
  // 提示：原订单数据已不可查，显示最新
  showTip('当前为最新版本，下单时数据已不可查');
}

if (resp.data.productDeleted === true) {
  // 产品已下架，仅展示模式
  disableActions();
  showTip('该产品已下架');
}
```

其他入口调用方式不变。

---

## 📌 非变更项（确保前端无需额外改动）

- 其他返回字段（id/name/tiers/itinerary/tierPrices/...）语义不变
- Header、认证方式、Base URL 均不变
- 其他接口（`/mp/product/list`、`/admin/product/**` 等）零影响

---

## 🔗 相关

- 工单：#1176
- PR：#1182
- 部署：合并到 dev 后由 Deploy Panel 同步部署 `hl-order-service-v2` + `hl-product-service-v2`
- 测试环境：`https://api.test.1814.love:9443`
