changelog: #7211 定制师↔团期管理员站内会话 GROUP:{orderId}——open-group 新增、聊天/列表/已读/未读改造、itinerary 与团期名单表新增字段、SSE targetPermissionCode(新增接口·管理后台)
changelog-filename-gate / validate (push) Successful in 2s
changelog-filename-gate / validate (push) Successful in 2s
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XYL5S9SsBtkg7aGyFAbrrQ
这个提交包含在:
@@ -0,0 +1,607 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "7211"
|
||||
title: "定制师 ↔ 团期管理员站内会话(GROUP:{orderId})"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "新增接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: "2026-09-07"
|
||||
status_note: "后端已合 dev-v3(PR #7225 order-v3、#7226 user-service)并部署测试服,网关实测 AC-1~AC-13 全部通过;GROUP 团队池不计入消息中心角标,定制师收到管理员回复照常计入。"
|
||||
updated_at: "2026-09-07"
|
||||
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
|
||||
在新工单中引用
屏蔽一个用户