# 站内信「打开会话」合并为 1 个接口（元信息+订单卡+首屏消息+合并未读+标记已读）+ 订单消息跳转用 conversationKey

> 模块：管理后台 · 站内信聊天（打开对话 + 订单消息行跳转）
> 类型：后端合并优化（已合并 dev-v3 + 部署测试服 + API 实测闭环）+ 前端对接
> 日期：2026-06-30
> 关联：PR #4666（打开会话合并）、#4664（会话浮现 last_message_at）；**取代 58_ 的"单独调 conversations 取订单卡"** —— 订单卡现已直接在打开会话接口返回

## 一、打开会话：5 接口 → 1 接口（前端必改）

前端反馈"打开一个对话要调 5 个接口太多、已读不该有独立接口"。已合并优化：**打开会话只调 1 个接口**，且**查询即已读**。

### 唯一需要调的接口（按场景三选一，都返回同一个 `ChatOpenFullRespVO`）
| 场景 | 接口 |
|------|------|
| 定制师↔房务 订单会话 | `POST /admin/message/chat/open-house`  body `{orderId, peerAdminId?}` |
| 组长↔房务 订单会话 | `POST /admin/message/chat/open-house-lead`  body `{orderId, peerAdminId}` |
| 普通 1:1（非订单） | `POST /admin/message/chat/open`  body `{peerAdminId, bizModule, bizId}` |

**这一个接口一次性返回打开会话所需全部数据，并已把本会话标记为已读**（不必再调 `/read`）。

### `ChatOpenFullRespVO` 返回结构
```json
{
  "conversationKey": "HOUSE:2071784830965620737",
  "peerAdminId": 2021059720172838914,
  "peerName": "王骁",
  "peerRole": "CUSTOMIZER",
  "peerRoleLabel": "定制师",
  "peerOnline": true,
  "unreadCount": 0,                 // 本会话未读（打开即已读，恒 0）
  "isNew": false,
  "unreadTotal": 3,                 // 我的合并未读总数(NOTIFY+CHAT，标记本会话已读后的最新值)，刷顶部角标用
  "order": {                        // 订单卡：仅 HOUSE/HOUSE_LEAD 会话有；非订单会话/ order-v3 不可达为 null
    "orderNo": "HL20260630103610215",
    "customerName": "吕思远",
    "destination": "海拉尔",
    "tripDays": 3,
    "bizStatusLabel": "配房中",
    "progressDesc": "已配 2/4 间",
    "productName": "游牧的森林-短途版",
    "adultCount": 2,
    "childCount": 1,
    "requirementId": 70456
  },
  "thread": {                       // 首屏消息(最新一页 20 条；上滑更早历史走 GET messages)
    "conversationKey": "HOUSE:2071784830965620737",
    "hasMore": false,
    "nextCursor": null,
    "list": [
      { "messageId":"2071792080350236674","senderAdminId":"2021059720172838914","senderName":"王骁",
        "senderRole":"CUSTOMIZER","msgType":"TEXT","priority":"NORMAL","content":"77721",
        "isMine":false,"readByPeer":false,"sentAt":"2026-06-30 11:04:59" }
    ]
  }
}
```

### 优化后的完整交互（满足「≤2 普通接口 + 1 SSE」）
- **打开会话** → 1 个普通接口：`POST …/open-house`（拿到 元信息 + 订单卡 + 首屏消息 + 合并未读，且已标记已读）。
- **上滑加载更早历史** → `GET /admin/message/chat/{conversationKey}/messages?beforeId={上次最小id}&pageSize=20`（纯分页读，无副作用）。
- **发消息** → `POST /admin/message/chat/{conversationKey}/messages`。
- **实时收消息/未读/已读回执** → 1 个 SSE（既有 `admin-notify`）。

### 【前端必改】
1. 打开会话**只调 open/open-house/open-house-lead 之一**，用返回的 `order` 渲染订单信息条、`thread` 渲染消息、`unreadTotal` 刷顶部角标。
2. **删掉打开时的这些调用**：原单独的 `GET .../messages`（首屏改用 open 返回的 `thread`）、`POST .../read`（打开即已读，`unreadTotal` 已是标记后的值）、`GET .../conversations?bizId=`（订单卡改用 open 返回的 `order`，**取代 58_**）、`GET /admin/message/unread-count`（用 open 返回的 `unreadTotal`）。
3. `/read`、`/unread-count`、`conversations` 接口仍保留（会话列表视图仍用 conversations），但**打开会话流程不再需要它们**。
4. `messages`(GET) 只在上滑加载更早历史时调（用 `thread.nextCursor`/最小 messageId 作 `beforeId`）。

## 二、订单消息行的「跳转按钮」用 conversationKey（前端处理）

收件箱「订单消息」行（kind=CHAT）跳转按钮消失了。后端数据一直都在，前端按字段渲染即可：
- **普通消息（NOTIFY）**：用 `link` 跳对应页面（不变）。
- **订单消息（CHAT）**：用该行的 **`conversationKey`** 跳转 = 打开会话（即调上面的 open-house，conversationKey 形如 `HOUSE:{orderId}`，orderId 也可取行内 `bizId`）。这正是它之前的跳转行为，切到 `list?messageType=ORDER`（57_）后行组件漏带了 conversationKey，补回即可。

## 备注
- 订单卡 `order` 仅 HOUSE/HOUSE_LEAD 订单会话有，非订单会话或 order-v3 暂不可达时为 null，前端隐藏订单信息条即可，不报错。
- 打开历史老会话时后端会顺手对齐该会话"最后活跃"，使其正常出现在会话列表（修复个别老会话在列表里看不到的问题，#4664）。
