608 行
26 KiB
Markdown
608 行
26 KiB
Markdown
---
|
||
schema: "hl-changelog/v2"
|
||
ticket: "7211"
|
||
title: "定制师 ↔ 团期管理员站内会话(GROUP:{orderId})"
|
||
consumer: "admin"
|
||
author: "wx(GIT)"
|
||
change_type: "新增接口"
|
||
backend_status: "deployed"
|
||
gateway_status: "verified"
|
||
frontend_status: "verified"
|
||
frontend_owner: "mmg"
|
||
frontend_ref: "86340b24"
|
||
target_release: ""
|
||
verified_at: "2026-09-08"
|
||
status_note: "前端已交付并验证(commit 86340b24):chat.js 新增 openGroupChat(POST /message/chat/open-group,body 仅 {orderId} 字符串不传 peerAdminId,silentError),send 静默正则扩 /^(?:FLEET|GROUP):[^:]+$/、messages/read 对 GROUP 键静默,281002 不透传原文案由 ChatDrawer 统一映射「刷新重试/订单负责人可能已变更」;住宿安排卡加「联系团期管理员」(显隐只看 itinerary.groupBatchId 非空不看 OrderMainVO.groupBatchId,无定制师显「暂无定制师」,角标 itineraryGroupUnreadCount),团期「查看需求」名单表每户加「联系定制师」(仅 group-batch:demand:confirm 持码露出,团队共享「待处理」角标,操作列宽 76→150 必要调宽);抽屉头徽章透传后端 peerRoleLabel,前端不建 GROUP_ADMIN 硬编码映射;SSE JSON 解析宽容未知字段零改动,聊天 Tab unread-total 与消息中心 unread-count 两角标按契约分开;定向 114 例+回归 87 文件 918 例全绿,对抗 review PASS,checkpoint 全绿(Vitest 全量+生产构建)。"
|
||
updated_at: "2026-09-08"
|
||
base: "dev-v3"
|
||
---
|
||
|
||
# 团期模块:定制师 ↔ 团期管理员站内会话(GROUP:{orderId})
|
||
|
||
> **服务**: `hl-order-service-v3`、`hl-user-service`
|
||
> **Issue**: #7211
|
||
> **PR**: #7225(order-v3)、#7226(user-service)
|
||
> **日期**: 2026-09-07
|
||
> **影响范围**: 管理后台子订单详情「住宿安排」卡、团期详情「查看需求」名单表
|
||
|
||
## ⚠️ 关键变化
|
||
|
||
**新增团期管理员团队会话通道**:定制师与团期管理员团队(而非指派个人)按子订单维度建立实时对话(会话键 `GROUP:{orderId}`),镜像车务团队制(团队共享未读水位行、定制师广播落池、任一管理员读即全员清零)。
|
||
|
||
**顶部角标两条规则分开**:
|
||
1. 团期管理员团队共享池(`admin_id=0`)未读**只计入**聊天 Tab 与 SSE,**不计入**消息中心角标;
|
||
2. 团期管理员回复落定制师个人行(`sender_role=GROUP_ADMIN`),照常计入定制师顶部角标、消息中心与收件箱。
|
||
|
||
**前端勾选要点**:
|
||
- `ItineraryVO.groupBatchId` 非空时才显示「联系团期管理员」按钮(**不看** `OrderMainVO.groupBatchId`);
|
||
- `peerOnline` 按账号在线判定,不承诺"几分钟内回复";
|
||
- 无定制师时 `peerName=null` 展示「暂无定制师」;
|
||
- 281002 权限错误不透传原文案,用「刷新重试 / 订单负责人可能已变更」;
|
||
- `groupChatUnreadCount` 是团队共享「待处理」,任一管理员读即全员清零。
|
||
|
||
---
|
||
|
||
## 二、变更接口清单
|
||
|
||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||
|---|------|------|------|----------|------|
|
||
| 1 | 开启团期管理员会话(新增) | POST | `/admin/message/chat/open-group` | 新增接口 | 定制师 ↔ 团期管理员团队会话,键 `GROUP:{orderId}` |
|
||
| 2 | 开启会话(通用,GROUP 分流) | POST | `/admin/message/chat/open` | 修改接口 | `bizModule` 新增 `GROUP`,分流到 open-group(`peerAdminId` 忽略,不产生 pair 键) |
|
||
| 3 | 会话列表 | GET | `/admin/message/chat/conversations` | 修改接口 | `bizModule` 过滤新增 `GROUP`;`peerRole / peerRoleLabel` 新增 `GROUP_ADMIN / 团期管理员`;持码者团队共享未读 |
|
||
| 4 | 消息列表 | GET | `/admin/message/chat/{conversationKey}/messages` | 修改接口 | 支持 `GROUP:{orderId}` 键;`senderRole` 新增 `GROUP_ADMIN` |
|
||
| 5 | 发送消息 | POST | `/admin/message/chat/{conversationKey}/messages` | 修改接口 | 定制师发 → 团队池;管理员发 → 定制师个人行 |
|
||
| 6 | 标记已读 | POST | `/admin/message/chat/{conversationKey}/read` | 修改接口 | 持码者读 → 团队水位推进、全员清零、定制师收 `im-chat-read` |
|
||
| 7 | 聊天 Tab 未读总数 | GET | `/admin/message/chat/unread-total` | 修改接口 | 当前角色持码时合入 GROUP 团队池未读 |
|
||
| 8 | 子订单行程(住宿安排卡) | GET | `/v3/admin/order/{id}/itinerary` | 修改接口 | 新增 `groupBatchId`(按钮显隐依据)、`groupUnreadMessageCount` |
|
||
| 9 | 团期子订单名单 | GET | `/v3/admin/order/group-batch/{groupBatchId}/orders` | 修改接口 | 每项新增 `groupChatUnreadCount`(团队共享「待处理」) |
|
||
|
||
**SSE 契约**(`/ws/admin-msg/stream`,事件 `im-chat` / `im-chat-read`,非 REST):payload 新增 `targetPermissionCode`(String,可空);GROUP 团队信令为 `"group-batch:demand:confirm"`,只投递给当前角色持码的连接(实测:持码 ADMIN 收到,不持码 CUSTOMIZER 收不到,同一账号切到不持码角色后的旧连接也收不到)。`im-chat-read` 示例:`{"adminId":1002,"type":"READ","conversationKey":"GROUP:2096701865117831170","readerAdminId":1001,"lastReadMessageId":"2096701909673943042"}`。前端无需改解析,但请确认 SSE JSON 解析不拒绝未知字段。
|
||
|
||
**内部 Feign**(不经网关,前端不用):`POST /internal/message/chat/biz-unread-batch` `bizModule` 新增 `GROUP`(`unreadScope` TEAM / PERSONAL);`POST /internal/message/chat/reconcile-fleet-consultant` 路径不变、同时收敛 FLEET 与 GROUP;order-v3 `POST /internal/house/order-chat-summary-batch` 新增 `groupBatchId / groupBatchNo`。
|
||
|
||
---
|
||
|
||
## 三、接口详情
|
||
|
||
### 1. 开启团期管理员会话 `POST /admin/message/chat/open-group`
|
||
|
||
**VO**: `ChatOpenGroupReqVO → ChatOpenFullRespVO`
|
||
|
||
#### 使用场景
|
||
|
||
定制师或团期管理员打开与对方团队的会话;调用方为 hl-ui 子订单详情「住宿安排」卡「联系团期管理员」、团期详情「查看需求」名单表「联系定制师」。
|
||
|
||
#### 入参字段表
|
||
|
||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||
|---|---|---|---|---|---|
|
||
| `orderId` | Body | Long(String) | ✓ | @NotNull | 子订单 ID;**不接受** `peerAdminId`(传了被忽略) |
|
||
|
||
#### 出参字段表
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `conversationKey` | String | `GROUP:{orderId}` |
|
||
| `isNew` | Boolean | 开方成员行此前是否不存在 |
|
||
| `peerAdminId` | Long | 定制师视角恒 0(团队占位);管理员视角 = 当前定制师 adminId(无定制师时 0) |
|
||
| `peerName` | String | 定制师视角「团期管理员」;管理员视角 = 定制师姓名快照,**无定制师时 null**,前端展示「暂无定制师」 |
|
||
| `peerRole` | String | 定制师视角 `GROUP_ADMIN`;管理员视角 `CUSTOMIZER` |
|
||
| `peerRoleLabel` | String | 「团期管理员」或「定制师」 |
|
||
| `peerOnline` | Boolean | 定制师视角 = 任一持码活跃账号在线;管理员视角 = 定制师在线。**按账号在线判定,不按当前角色**,不保证对方能立刻收到消息 |
|
||
| `unreadCount` | Integer | 恒 0(打开即已读) |
|
||
| `order` | ChatOrderCardVO | 订单卡:orderNo / teamNo / customerName / destination / tripDays / productName / adultCount / childCount / **groupBatchNo**(String) / **groupBatchId**(String);摘要不可达时为 null |
|
||
| `thread` | ChatThreadRespVO | 最新消息线索:conversationKey / hasMore / nextCursor / list[] 消息数组 |
|
||
| `unreadTotal` | Integer | 标记已读后顶部合并未读总数(不含 GROUP 团队池,与 unread-count REST 保持一致) |
|
||
|
||
#### 请求示例
|
||
|
||
```json
|
||
POST /admin/message/chat/open-group
|
||
|
||
{
|
||
"orderId": 60123456789001
|
||
}
|
||
```
|
||
|
||
#### 响应示例
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "成功",
|
||
"success": true,
|
||
"data": {
|
||
"conversationKey": "GROUP:60123456789001",
|
||
"isNew": false,
|
||
"peerAdminId": 0,
|
||
"peerName": "团期管理员",
|
||
"peerRole": "GROUP_ADMIN",
|
||
"peerRoleLabel": "团期管理员",
|
||
"peerOnline": true,
|
||
"unreadCount": 0,
|
||
"order": {
|
||
"orderNo": "HL2609010001",
|
||
"teamNo": "T20260901001",
|
||
"customerName": "李四",
|
||
"destination": "三亚",
|
||
"tripDays": 4,
|
||
"productName": "豪华蜜月游",
|
||
"adultCount": 2,
|
||
"childCount": 0,
|
||
"groupBatchNo": "G20260901001",
|
||
"groupBatchId": "1867000000001"
|
||
},
|
||
"thread": {
|
||
"conversationKey": "GROUP:60123456789001",
|
||
"hasMore": false,
|
||
"nextCursor": null,
|
||
"list": []
|
||
},
|
||
"unreadTotal": 0
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 空数据 / 降级响应
|
||
|
||
摘要不可达或缺失时,`order` 为 null;`thread.list` 可为空。
|
||
|
||
#### 错误响应
|
||
|
||
```json
|
||
{
|
||
"code": 281002,
|
||
"message": "无权访问该会话",
|
||
"success": false,
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
#### 业务边界条目
|
||
|
||
- 打开即标记已读,`unreadCount` 恒 0
|
||
- 团队水位行 `admin_id=0` 自动创建,定制师与管理员各有成员行
|
||
- 定制师视角 `peerRole=GROUP_ADMIN`;管理员视角 `peerRole=CUSTOMIZER`
|
||
- 无定制师的新团期:按钮不显示(`groupBatchId` 为空),`peerName` 返 null
|
||
- 转单后旧定制师再开 → 281002,新定制师开 → 200 且承接未读
|
||
|
||
---
|
||
|
||
---
|
||
|
||
---
|
||
|
||
### 2. 开启会话(通用,GROUP 分流) `POST /admin/message/chat/open`
|
||
|
||
**VO**: `ChatOpenReqVO → ChatOpenFullRespVO`
|
||
|
||
#### 使用场景
|
||
旧客户端沿用通用入口时传 `bizModule=GROUP`;新代码直接用 `open-group`。两者同一实现。
|
||
|
||
#### 入参字段表
|
||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||
|---|---|---|---|---|---|
|
||
| `bizModule` | Body | String | ✓ | — | 新增枚举值 `GROUP`(原 HOUSE / FLEET / DIRECT) |
|
||
| `bizId` | Body | Long(String) | ✓ | — | 团期子订单 ID |
|
||
| `peerAdminId` | Body | Long(String) | — | — | GROUP 下忽略:团队会话不按对端个人建键 |
|
||
|
||
#### 出参字段表
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| conversationKey | String | `GROUP:{orderId}` |
|
||
| 其余字段 | — | 与 `open-group` 完全相同(见 1) |
|
||
|
||
#### 请求示例
|
||
```json
|
||
POST /admin/message/chat/open
|
||
{"bizModule": "GROUP", "bizId": "2096701865117831170", "peerAdminId": "123"}
|
||
```
|
||
|
||
#### 响应示例
|
||
```json
|
||
{"code": 200, "message": "成功", "success": true, "data": {"conversationKey": "GROUP:2096701865117831170", "isNew": false, "peerAdminId": "0", "peerName": "团期管理员", "peerRole": "GROUP_ADMIN", "peerRoleLabel": "团期管理员", "peerOnline": true, "unreadCount": 0, "order": {"orderNo": "HL20260907044734031", "groupBatchNo": "Q202610012052935476548939777", "groupBatchId": "2096412454643802114"}, "thread": {"conversationKey": "GROUP:2096701865117831170", "hasMore": false, "list": []}, "unreadTotal": 1}}
|
||
```
|
||
|
||
#### 空数据 / 降级响应
|
||
同 `open-group`:order-v3 不可达时 `order` 为 null(会话仍可开)。
|
||
|
||
#### 错误响应
|
||
```json
|
||
{"code": 281002, "message": "无权访问该会话", "data": null, "success": false}
|
||
```
|
||
| 码 | 触发 |
|
||
|---|---|
|
||
| 281002 | 非该单当前定制师且当前角色不持 `group-batch:demand:confirm`;转单后旧定制师;order-v3 摘要不可达(失败关闭)。前端不透传原文案,用「刷新重试 / 订单负责人可能已变更」 |
|
||
| 281015 | 非团期子订单 |
|
||
| 281012 | bizId 为空 |
|
||
|
||
#### 业务边界条目
|
||
- 实测 `peerAdminId=123` 也返回 `GROUP:{orderId}`,库内不出现 `GROUP:{id}:{a}:{b}` pair 键。
|
||
|
||
---
|
||
|
||
### 3. 会话列表 `GET /admin/message/chat/conversations`
|
||
|
||
**VO**: `查询参数 → PageResult<ChatConversationRespVO>`
|
||
|
||
#### 使用场景
|
||
聊天 Tab 列表。团期管理员(持码角色)看到所有团期子订单的 GROUP 会话(团队共享未读);定制师看到自己名下团单的 GROUP 会话;不持码 ADMIN / 其他定制师看不到。
|
||
|
||
#### 入参字段表
|
||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||
|---|---|---|---|---|---|
|
||
| `bizModule` | Query | String | — | — | 新增 `GROUP`;不传时非团队模块列表**排除** FLEET / GROUP 团队行(与既有 FLEET 规则一致) |
|
||
| `pageNo / pageSize` | Query | Integer | — | — | 不变 |
|
||
|
||
#### 出参字段表
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| list[].bizModule | String | `GROUP` |
|
||
| list[].peerRole / peerRoleLabel | String | 定制师视角 `GROUP_ADMIN` / 团期管理员;管理员视角 `CUSTOMIZER` / 定制师 |
|
||
| list[].peerName | String | 定制师视角「团期管理员」;管理员视角定制师姓名 |
|
||
| list[].peerOnline | Boolean | 定制师视角 = 任一持码活跃账号在线(按账号不按当前角色,不保证对方能立刻收到) |
|
||
| list[].unreadCount | Integer | 持码者为团队共享未读(任一持码者读即全员清零) |
|
||
| list[].orderNo / teamNo | String | 订单号 / 团号 enrich |
|
||
|
||
#### 请求示例
|
||
```json
|
||
GET /admin/message/chat/conversations?pageNo=1&pageSize=50&bizModule=GROUP
|
||
```
|
||
|
||
#### 响应示例
|
||
```json
|
||
{"code": 200, "message": "成功", "success": true, "data": {"list": [{"conversationKey": "GROUP:2096701865117831170", "bizModule": "GROUP", "orderNo": "HL20260907044734031", "teamNo": "26-4983", "peerRole": "CUSTOMIZER", "peerRoleLabel": "定制师", "peerName": "test_admin", "unreadCount": 0, "peerOnline": true}], "total": 1}}
|
||
```
|
||
|
||
#### 空数据 / 降级响应
|
||
不持码的 ADMIN / 其他定制师:`list=[]`(实测 0 条);无角色 token 对团队模块失败关闭返空页。
|
||
|
||
#### 错误响应
|
||
```json
|
||
{"code": 401, "message": "Token无效或已过期", "data": null, "success": false}
|
||
```
|
||
| 码 | 触发 |
|
||
|---|---|
|
||
| 401 | 未登录 / token 失效(沿用既有) |
|
||
|
||
#### 业务边界条目
|
||
- 会话卡 `peerRole` 与消息气泡 `senderRole` 的中文映射都要补 `GROUP_ADMIN → 团期管理员`。
|
||
- 未读语义是团队共享「待处理」,别写成「你的未读」。
|
||
|
||
---
|
||
|
||
### 4. 消息列表 `GET /admin/message/chat/{conversationKey}/messages`
|
||
|
||
**VO**: `查询参数 → ChatThreadRespVO`
|
||
|
||
#### 使用场景
|
||
打开 GROUP 会话后拉取历史;`conversationKey=GROUP:{orderId}`。
|
||
|
||
#### 入参字段表
|
||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||
|---|---|---|---|---|---|
|
||
| `conversationKey` | Path | String | ✓ | — | `GROUP:{orderId}` |
|
||
| `cursor / pageSize` | Query | — | — | — | 不变 |
|
||
|
||
#### 出参字段表
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| list[].senderRole | String | 新增 `GROUP_ADMIN`(管理员回复);定制师广播为 null |
|
||
| list[].readByPeer | Boolean | 任一持码者读后为 true(`im-chat-read` 同步推送) |
|
||
|
||
#### 请求示例
|
||
```json
|
||
GET /admin/message/chat/GROUP:2096701865117831170/messages?pageSize=20
|
||
```
|
||
|
||
#### 响应示例
|
||
```json
|
||
{"code": 200, "message": "成功", "success": true, "data": {"conversationKey": "GROUP:2096701865117831170", "hasMore": false, "nextCursor": null, "list": [{"messageId": "2096701935376617473", "senderAdminId": "1001", "senderName": "admin", "senderRole": "GROUP_ADMIN", "msgType": "TEXT", "content": "管理员回复 1", "isMine": false, "readByPeer": false}]}}
|
||
```
|
||
|
||
#### 空数据 / 降级响应
|
||
无消息时 `list=[]`。
|
||
|
||
#### 错误响应
|
||
```json
|
||
{"code": 281002, "message": "无权访问该会话", "data": null, "success": false}
|
||
```
|
||
| 码 | 触发 |
|
||
|---|---|
|
||
| 281002 | 非该单当前定制师且当前角色不持 `group-batch:demand:confirm`;转单后旧定制师;order-v3 摘要不可达(失败关闭)。前端不透传原文案,用「刷新重试 / 订单负责人可能已变更」 |
|
||
|
||
#### 业务边界条目
|
||
- 定制师端消息气泡对 `senderRole=GROUP_ADMIN` 显示「团期管理员」徽章。
|
||
|
||
---
|
||
|
||
### 5. 发送消息 `POST /admin/message/chat/{conversationKey}/messages`
|
||
|
||
**VO**: `ChatMessageSendReqVO → ChatMessageSendRespVO`
|
||
|
||
#### 使用场景
|
||
定制师发 → 落团队池(全体持码者共享未读,SSE `im-chat` 定向到持码连接);管理员发 → 落定制师个人行(定制师顶部角标 / 收件箱照常 +1)。
|
||
|
||
#### 入参字段表
|
||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||
|---|---|---|---|---|---|
|
||
| `conversationKey` | Path | String | ✓ | — | `GROUP:{orderId}` |
|
||
| `content` | Body | String | ✓ | — | 不变 |
|
||
| `priority / msgType` | Body | String | — | — | 不变 |
|
||
|
||
#### 出参字段表
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| messageId | String | 不变 |
|
||
|
||
#### 请求示例
|
||
```json
|
||
POST /admin/message/chat/GROUP:2096701865117831170/messages
|
||
{"content": "请确认第二晚房型"}
|
||
```
|
||
|
||
#### 响应示例
|
||
```json
|
||
{"code": 200, "message": "成功", "success": true, "data": {"messageId": "2096701909673943042"}}
|
||
```
|
||
|
||
#### 空数据 / 降级响应
|
||
无。
|
||
|
||
#### 错误响应
|
||
```json
|
||
{"code": 281002, "message": "无权访问该会话", "data": null, "success": false}
|
||
```
|
||
| 码 | 触发 |
|
||
|---|---|
|
||
| 281002 | 非该单当前定制师且当前角色不持 `group-batch:demand:confirm`;转单后旧定制师;order-v3 摘要不可达(失败关闭)。前端不透传原文案,用「刷新重试 / 订单负责人可能已变更」 |
|
||
|
||
#### 业务边界条目
|
||
- 转单与 GROUP 发送 / 已读 / 开会话互斥(order-v3 转单入口与 user-service 共用同一把 `GROUP:{orderId}` 锁);实测与转单并发的发送被串行后按新归属拒绝(281002)。
|
||
- 新增 GROUP 日志只打标识符,不打客户姓名 / 消息内容。
|
||
|
||
---
|
||
|
||
### 6. 标记已读 `POST /admin/message/chat/{conversationKey}/read`
|
||
|
||
**VO**: `无 body → ChatReadRespVO`
|
||
|
||
#### 使用场景
|
||
持码者读 → 团队水位推进、全员未读清零、给定制师补发 `im-chat-read`;定制师读 → 只推进自己行。
|
||
|
||
#### 入参字段表
|
||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||
|---|---|---|---|---|---|
|
||
| `conversationKey` | Path | String | ✓ | — | `GROUP:{orderId}` |
|
||
|
||
#### 出参字段表
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| unreadTotal | Integer | 标记后的顶部合并未读(不含 GROUP 团队池) |
|
||
|
||
#### 请求示例
|
||
```json
|
||
POST /admin/message/chat/GROUP:2096701865117831170/read
|
||
```
|
||
|
||
#### 响应示例
|
||
```json
|
||
{"code": 200, "message": "成功", "success": true, "data": {"unreadTotal": 13}}
|
||
```
|
||
|
||
#### 空数据 / 降级响应
|
||
无。
|
||
|
||
#### 错误响应
|
||
```json
|
||
{"code": 281002, "message": "无权访问该会话", "data": null, "success": false}
|
||
```
|
||
| 码 | 触发 |
|
||
|---|---|
|
||
| 281002 | 非该单当前定制师且当前角色不持 `group-batch:demand:confirm`;转单后旧定制师;order-v3 摘要不可达(失败关闭)。前端不透传原文案,用「刷新重试 / 订单负责人可能已变更」 |
|
||
|
||
#### 业务边界条目
|
||
- 实测管理员读后名单表两持码账号 `groupChatUnreadCount` 同时 1→0,定制师收到 `im-chat-read`(`readerAdminId` 为读的管理员)。
|
||
|
||
---
|
||
|
||
### 7. 聊天 Tab 未读总数 `GET /admin/message/chat/unread-total`
|
||
|
||
**VO**: `无参 → ChatUnreadTotalRespVO`
|
||
|
||
#### 使用场景
|
||
聊天 Tab 角标。**当前角色持码时合入 GROUP 团队池未读**;消息中心 `GET /admin/message/unread-count` 与顶部合并角标**不含**团队池。
|
||
|
||
#### 入参字段表
|
||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||
|---|---|---|---|---|---|
|
||
| (无) | — | — | — | — | 无入参 |
|
||
|
||
#### 出参字段表
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| chatUnreadTotal | Integer | 持码者 = 个人未读 + GROUP 团队池未读(+ FLEET 池,车务管理员) |
|
||
|
||
#### 请求示例
|
||
```json
|
||
GET /admin/message/chat/unread-total
|
||
```
|
||
|
||
#### 响应示例
|
||
```json
|
||
{"code": 200, "message": "成功", "success": true, "data": {"chatUnreadTotal": 3}}
|
||
```
|
||
|
||
#### 空数据 / 降级响应
|
||
无。
|
||
|
||
#### 错误响应
|
||
```json
|
||
{"code": 401, "message": "Token无效或已过期", "data": null, "success": false}
|
||
```
|
||
| 码 | 触发 |
|
||
|---|---|
|
||
| 401 | 未登录 / token 失效 |
|
||
|
||
#### 业务边界条目
|
||
- 实测定制师广播后持码 ADMIN 的 `chat/unread-total` 2→3,而 `/admin/message/unread-count` 13→13。
|
||
- 定制师收到管理员回复 → 个人行 → `unread-count` 0→1,前端不得过滤 `senderRole=GROUP_ADMIN` 的行。
|
||
|
||
---
|
||
|
||
### 8. 子订单行程(住宿安排卡) `GET /v3/admin/order/{id}/itinerary`
|
||
|
||
**VO**: `ItineraryVO`
|
||
|
||
#### 使用场景
|
||
子订单详情「住宿安排」卡:「联系团期管理员」按钮显隐 + 角标。
|
||
|
||
#### 入参字段表
|
||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||
|---|---|---|---|---|---|
|
||
| `id` | Path | Long | ✓ | — | 子订单 ID |
|
||
|
||
#### 出参字段表
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| groupBatchId | String | 新增。统一归团解析结果(含历史兼容单 `product_batch_id` 反查);普通单 null。**按钮显隐只看它,不看 `OrderMainVO.groupBatchId`** |
|
||
| groupUnreadMessageCount | Integer | 新增。当前登录定制师在本单 GROUP 会话的个人未读;非团单 / 降级 / 未登录 0 |
|
||
|
||
#### 请求示例
|
||
```json
|
||
GET /v3/admin/order/2096701865117831170/itinerary
|
||
```
|
||
|
||
#### 响应示例
|
||
```json
|
||
{"code": 200, "message": "成功", "success": true, "data": {"groupBatchId": "2096412454643802114", "groupUnreadMessageCount": 1, "unreadMessageCount": 0}}
|
||
```
|
||
|
||
#### 空数据 / 降级响应
|
||
普通单:`"groupBatchId": null, "groupUnreadMessageCount": 0`;user-service 不可达 → `groupUnreadMessageCount=0`,其余字段正常。
|
||
|
||
#### 错误响应
|
||
```json
|
||
{"code": 582001, "message": "订单不存在", "data": null, "success": false}
|
||
```
|
||
| 码 | 触发 |
|
||
|---|---|
|
||
| 582001 | 订单不存在(沿用既有) |
|
||
|
||
#### 业务边界条目
|
||
- `groupBatchId` 与 `OrderMainVO.groupBatchId` 语义不同(后者是原始列,历史兼容单可能为空)。
|
||
|
||
---
|
||
|
||
### 9. 团期子订单名单 `GET /v3/admin/order/group-batch/{groupBatchId}/orders`
|
||
|
||
**VO**: `查询参数 → List<GroupBatchOrderItemRespVO>`
|
||
|
||
#### 使用场景
|
||
团期详情「查看需求」名单表每户「联系定制师」+ 角标。
|
||
|
||
#### 入参字段表
|
||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||
|---|---|---|---|---|---|
|
||
| `groupBatchId` | Path | Long | ✓ | — | 团期 ID |
|
||
| `includeTravelers / includeNeeds / includeCancelled` | Query | Boolean | — | — | 不变 |
|
||
|
||
#### 出参字段表
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| [].groupChatUnreadCount | Integer | 新增。团队共享「待处理」未读(TEAM 口径):任一管理员读即全员清零,**不是**当前登录人个人未读;降级 0 |
|
||
|
||
#### 请求示例
|
||
```json
|
||
GET /v3/admin/order/group-batch/2096412454643802114/orders
|
||
```
|
||
|
||
#### 响应示例
|
||
```json
|
||
{"code": 200, "message": "成功", "success": true, "data": [{"orderId": "2096701865117831170", "orderNo": "HL20260907044734031", "groupChatUnreadCount": 1}]}
|
||
```
|
||
|
||
#### 空数据 / 降级响应
|
||
user-service 不可达或超时 → 全部 0,主列表照常返回。
|
||
|
||
#### 错误响应
|
||
```json
|
||
{"code": 589507, "message": "无操作权限(非团期管理员 / 非本定制师名下)", "data": null, "success": false}
|
||
```
|
||
| 码 | 触发 |
|
||
|---|---|
|
||
| 589507 | 无 `group-batch:view`(沿用既有守卫) |
|
||
| 589500 | 团期不存在 |
|
||
|
||
#### 业务边界条目
|
||
- 文案用「待处理 / 待回复」而非「未读消息」;本字段只供页面级加载,不要做高频轮询。
|
||
|
||
---
|
||
|
||
## 四、契约约束与正确调用方式
|
||
|
||
### 授权规则
|
||
|
||
会话准入:①当前定制师本人(token 角色 CUSTOMIZER 且 me == consultant_id)或②团期管理员(token 当前角色持权限码 `group-batch:demand:confirm`);均不满足返 281002。
|
||
|
||
### 转单锁定
|
||
|
||
order-v3 转单入口:FLEET 锁 → 判团 → GROUP 锁 → 事务。普通单不取 GROUP 锁。并发互斥。
|
||
|
||
---
|
||
|
||
## 五、数据库行为
|
||
|
||
- **无表结构变更、无 DDL**(现有列宽已足);
|
||
- **权限种子** `V20260906_006`(INSERT 权限码 + 关联)由 #7210 落地;若本单先合则自带;
|
||
- **数据形态**:admin_message 新增 bizModule=GROUP 行;sender_role=NULL(定制师广播)或 GROUP_ADMIN(管理员回复);admin_conversation_member 新增 admin_id=0 团队行与个人行。
|
||
|
||
---
|
||
|
||
## 六、边界行为
|
||
|
||
- 打开即已读;团队水位行自动创建;
|
||
- 定制师广播到全体持码账号;无定制师时 peerName=null;
|
||
- 权限缓存 TTL 10 分钟;撤权生效靠 TTL 或运维 `DEL admin_permissions:v2:role:{roleKey}`;
|
||
- 团期软删时订单卡 `groupBatchNo=null`;前端用「团期已归档」占位。
|
||
|
||
---
|
||
|
||
## 七、不影响范围
|
||
|
||
- 车务(FLEET)聊天流程与数据;
|
||
- 房 / 车需求创建/修改;
|
||
- 抢单池流程;
|
||
- 消息中心界面(GROUP 池行暂未纳管,后续单独工单);
|
||
- 网关路由。
|
||
|
||
---
|
||
|
||
## 八、测试环境已验证
|
||
|
||
- 部署:dev-v3 `072a476be`(PR-A #7225,order-v3)+ `f4251d946`(PR-B #7226,user-service),测试服 `BRANCH=dev-v3 deploy-backend.sh` 两服务已滚;lock4j 键前缀两服务均默认值。
|
||
- 网关实测(团 A `2096412454643802114`,定制师 test_admin / 持码 ADMIN admin / SUPER_ADMIN wx / 不持码 qulili):AC-1 open-group 200(`GROUP:{orderId}`、`peerRole=GROUP_ADMIN`、订单卡 `groupBatchNo=Q202610012052935476548939777` / `groupBatchId`);AC-2 普通单 281015;AC-3 不持码 281002、短信登录临时 CUSTOMIZER(DB 角色 ADMIN)281002、撤码后 `DEL admin_permissions:v2:role:ADMIN` → 281002、恢复 → 200;AC-4 未提需求 200;AC-5 定制师广播落 `admin_id=0` 池行、名单表 TEAM 未读两账号同值 1、管理员读后全员 0 且定制师收 `im-chat-read`;AC-6 管理员回复落定制师个人行 `sender_role=GROUP_ADMIN`、SSE `im-chat` 到达、itinerary `groupUnreadMessageCount=1`、`unread-count` 0→1;AC-7 持码 ADMIN 收到含 `targetPermissionCode` 的 `im-chat`、不持码收不到、切角色旧连接收不到,`chat/unread-total` 2→3 而 `unread-count` 不变;AC-8 会话列表 GROUP 过滤命中(orderNo / teamNo / peerRoleLabel)、不持码 0 条;AC-9 通用 open 同键无 pair 键;AC-10 转单后旧行 ARCHIVED / 新行 ACTIVE / 旧定制师 281002、并发发送被串行后拒绝;AC-10b 历史兼容单 open-group 200 且 itinerary `groupBatchId` 非空;AC-12 / AC-13 字段与上述一致。
|
||
|
||
---
|
||
|
||
## 十、相关文档
|
||
|
||
- 工单正文:#7211
|
||
- 架构方案:#7211 评论「架构方案」
|
||
- 接口文档:docs/group/团期模块接口文档-v2.0.html §0C.11.3
|
||
|
||
---
|
||
|
||
## 关联 / 联系人
|
||
|
||
### 链接
|
||
|
||
- **Issue**: [#7211](https://git.1814.love:8443/wx/HL/issues/7211)
|
||
- **PR(order-v3)**: [#7225](https://git.1814.love:8443/wx/HL/pulls/7225)
|
||
- **PR(user-service)**: [#7226](https://git.1814.love:8443/wx/HL/pulls/7226)
|
||
|
||
### 联系人
|
||
|
||
- **后端负责人**: wx
|
||
- **前端负责人(hl-ui)**: mmg
|