文件
hl-api-changelog/changelogs-v2/2026-06/19_4020_站内信升级聊天P1-1对1会话6个管理端接口-新增接口-管理后台.md
T

13 KiB
原始文件 Blame 文件历史

站内信升级聊天(1:1 会话)P1 — 6 个管理端接口

  • 变更类型:新增接口
  • 端类型:管理后台
  • 日期:2026-06-19
  • Issue:#4020
  • PR:#4028(+ hotfix #4030 / #4031)
  • 后端负责人:王骁

🛑 前端暂不需要接入(2026-06-19 更新,最高优先级)

本次仅「后端 P1」就绪,前端聊天 UI 集成暂缓——收到本文先不用动工。 本文档是后端接口的提前留档/参考,不是开工通知;待产品排期、正式通知前端接入时再据此对接。下方接口说明仅供后端联调与未来参考。


⚠️ 关键说明(前端正式接入时再读)

  1. 本期 P1 没有实时推送(SSE)。聊天靠「发消息 + 拉线程 + 轮询未读」的请求/响应模式工作;实时角标冒泡(顶部铃铛实时 +1、打开的会话 live append 新气泡)是 P2 再上。前端正式接入时可用轮询(如进入会话时拉线程、定时拉 unread-total)先把界面跑起来。
  2. 能力归属 user-service,复用现有站内信。聊天行和系统通知行同表(admin_message),顶部合并未读角标(聊天+通知合计)继续走现有 GET /admin/message/unread-count,本次不变;本期新增的 GET /admin/message/chat/unread-total 只给「聊天 Tab」单独展示聊天未读数。
  3. 一个订单 × 一对人 = 一个对话框(不是整单一个群)。会话靠 conversationKey 唯一定位,订单 ID 已编入键;订单详情页「围绕这个订单的全部对话」= 调会话列表接口按 bizModule=HOUSE&bizId=订单ID 过滤。
  4. 前端不传 adminId / 姓名。当前操作人由网关注入的 token 解析;发件人姓名后端快照写入,直接展示。

1. 背景

房务(定制师↔房务·配房沟通)、车务(车管↔定制师·派车沟通)都需要内部员工之间的 1:1 实时聊天。后端把现有「单向系统通知站内信」升级为「通知 + 1:1 聊天」统一消息中心:同一套收件箱 / 未读 / (未来)实时通道,新增「谁发的 + 成线程 + 双向」三要素。

本期 P1 交付 6 个管理端接口(admin 后台用),是聊天的核心读写能力。SSE 实时(P2)、跨服务接入与业务列表红点透传(P3 房务 / P4 车务)后续迭代。


2. 变更清单

序号 接口 说明 变更类型
1 POST /admin/message/chat/open 打开 / 找回 1:1 会话(幂等) ✨ 新增接口
2 GET /admin/message/chat/conversations 我的会话列表(支持按订单聚合) ✨ 新增接口
3 GET /admin/message/chat/{conversationKey}/messages 拉线程消息(游标分页) ✨ 新增接口
4 POST /admin/message/chat/{conversationKey}/messages 发一条消息 ✨ 新增接口
5 POST /admin/message/chat/{conversationKey}/read 标记已读到最新 ✨ 新增接口
6 GET /admin/message/chat/unread-total 我的聊天未读总数 ✨ 新增接口
  • 认证:全部 Bearer JWT(管理后台 Token),经网关 https://api.test.1814.love:9443。
  • 网关路由:复用现有 /admin/message/** 路由,无新增。
  • DDL:admin_message 扩 8 列 + 新表 admin_conversation_member(前端无感)。
  • 顶部合并角标:仍走 GET /admin/message/unread-count,本次不变。

3. 核心概念:conversationKey(会话键)

conversationKey = {bizModule}:{bizId}:{minAdminId}:{maxAdminId}
例: HOUSE:70123:1001:2046065465288949761
  • bizModule:HOUSE(房务)/ FLEET(车务)/ DIRECT(纯私聊,无业务对象)。
  • bizId:业务对象 id(房务=订单ID,车务=派车单/车辆ID);DIRECT 时后端按 0 处理。
  • 两人 adminId 小的在前(后端规范化),保证 A↔B 双向命中同一会话,不分叉。
  • 前端拿到 conversationKey 后,作为路径参数透传(含冒号,直接放进 URL path,无需手动编码也可;若用 encodeURIComponent 编码同样可用)。

4. 接口详情

4.1 POST /admin/message/chat/open — 打开 / 找回会话

幂等:同两人同业务对象重复调用返回同一个 conversationKey(isNew=false),不会重复建会话。

入参(RequestBody)

字段 类型 必填 说明
bizModule String 是 HOUSE / FLEET / DIRECT
bizId String 否 业务对象 id(HOUSE=订单ID;DIRECT 留空)
peerAdminId String 是 对端员工 adminId(雪花,字符串透传)

出参 data

字段 类型 说明
conversationKey String 规范化会话键
peerAdminId String 对方 adminId
peerName String 对方姓名快照
peerRole String 对方角色(P1 admin 端开会话暂为 null,P3 业务侧接入后带值)
unreadCount Integer 我在该会话的未读数
isNew Boolean true=新建,false=找回已存在

4.2 GET /admin/message/chat/conversations — 我的会话列表

查询参数 类型 必填 说明
bizModule String 否 过滤业务分类,空=全部
bizId Long 否 过滤业务对象。传 bizModule=HOUSE&bizId=订单ID 即「围绕这个订单的全部对话」
pageNo Integer 否 页码,默认 1
pageSize Integer 否 每页,默认 20

返回分页 data.records[],每项:conversationKey / bizModule / bizId / peerAdminId / peerName / peerRole / unreadCount / lastMessagePreview / lastMessageAt / status,按 lastMessageAt 倒序。

4.3 GET /admin/message/chat/{conversationKey}/messages — 拉线程(游标分页)

参数 位置 类型 必填 说明
conversationKey Path String 是 会话键
beforeId Query String 否 游标:拉该 messageId 之前的历史(首屏不传=拉最新一页)
pageSize Query Integer 否 每页,默认 20

返回 data:conversationKey / hasMore / nextCursor(下次 beforeId)/ list[]。每条消息:messageId / senderAdminId / senderName / senderRole / msgType / priority / content / isMine(是否我发的,用于左右气泡)/ sentAt。列表按时间正序(从旧到新)。

4.4 POST /admin/message/chat/{conversationKey}/messages — 发一条

字段 类型 必填 说明
content String 是 文本内容(≤ 1000 字;命中本地敏感词后端替换为 *,不报错)
priority String 否 NORMAL(默认)/ URGENT(加急;P1 不触发实时强提示,P2 起生效)
msgType String 否 TEXT(默认)/ IMAGE(content 存图 URL)

返回 data:messageId / conversationKey / sentAt。前端不传发件人/收件人,后端按会话键解析。

4.5 POST /admin/message/chat/{conversationKey}/read — 标记已读到最新

无请求体。把该会话标记为已读到最新:该会话未读清零、已读水位推进、对应站内信行置已读(顶部合并角标相应回落)。返回 data:conversationKey / unreadCount(=0)/ lastReadMessageId。

4.6 GET /admin/message/chat/unread-total — 我的聊天未读总数

无参数。返回 data.chatUnreadTotal(仅 kind=CHAT 的未读数,供「聊天 Tab」单独展示)。

顶部合并角标(聊天+通知)请继续用 GET /admin/message/unread-count,本端点不替代它。


5. 错误码

错误码 说明 触发
281002 无权访问该会话 操作人不是该会话成员(越权)
281005 不能与自己创建会话 open 时 peerAdminId == 当前登录人
281010 目标员工不存在或已离职 open 的 peerAdminId 无效
281011 消息内容不合法 content 为空或超 1000 字
281004 发送过于频繁,请稍后再试 发消息超限流(每人 20 条/分钟)

HTTP 恒 200,业务结果看 code(成功 code=200、success=true)。


6. 典型调用流程(示例)

# 1) 打开会话(房务给订单 70123 的定制师留言)
POST /admin/message/chat/open
Authorization: Bearer <admin-token>
{ "bizModule": "HOUSE", "bizId": "70123", "peerAdminId": "2046065465288949761" }
--> data: { "conversationKey": "HOUSE:70123:1001:2046065465288949761", "peerName": "cz", "isNew": true, "unreadCount": 0 }

# 2) 发消息
POST /admin/message/chat/HOUSE:70123:1001:2046065465288949761/messages
{ "content": "标间超预算了,客人要不要升一档?", "priority": "NORMAL" }
--> data: { "messageId": "2067795811529314306", "sentAt": "2026-06-19 10:25:13" }

# 3) 拉线程
GET /admin/message/chat/HOUSE:70123:1001:2046065465288949761/messages?pageSize=20
--> data.list[]: 含双方消息,每条带 senderName / isMine

# 4) 标记已读
POST /admin/message/chat/HOUSE:70123:1001:2046065465288949761/read
--> data: { "unreadCount": 0, "lastReadMessageId": "2067795811529314306" }

7. 本期范围与后续

阶段 内容 状态
P1 6 个 admin 端点(open / 列表 / 线程 / 发消息 / 已读 / 聊天未读) ✅ 已上测试服
P2 SSE 实时推送(CHAT 走新增 im-chat 事件,含 URGENT priority)+ 敏感词 DFA(Hutool) ✅ 已上测试服(后端就绪,前端接入时用,见下「SSE 实时契约」)
P3 房务(order-v3)接入:房务「我的接单」列表 + 订单详情的 unreadMessageCount 接真聊天未读(经 internal Feign 透传,原占位恒 0) ✅ 已上测试服(后端透传就绪,见下「P3 房务红点」)
P4 车务(fleet)接入 ⏳ 待开发

P3 房务红点(后端已就绪)

房务「我的接单」列表项 HouseMyOrderItemRespVO.unreadMessageCount 与订单详情 tabCounts.unreadMessageCount 从此返真聊天未读(此前恒 0 占位):order-v3 服务端组装列表/详情时经 Feign 调 user-service POST /internal/message/chat/biz-unread-batch 批量取「当前登录房务在该订单(bizModule=HOUSE)上的会话未读」。前端无需改调用——字段同名,值从 0 变真;可据 unreadMessageCount > 0 显红点、按它排序(列表 sortBy=unreadMessageCount,desc 已支持)。user 不可达时降级回 0,不阻断列表。仍待前端排期接入展示。

SSE 实时契约(P2 后端已就绪,前端接入时用)

复用现有 SSE 长连接 GET /ws/admin-msg/stream(EventSource,网关注入鉴权),不新增连接。一条连接上按 event 名分发:

  • message 事件(现有系统通知,行为不变):前端原有监听不用动。
  • im-chat 事件(P2 新增,聊天专用):对端在线时实时收到,payload:
{
  "type": "CHAT",
  "adminId": "收件方adminId",
  "unreadCount": 9,                  // 合并未读(聊天+通知),直接刷顶部角标
  "conversationKey": "HOUSE:70123:101:205",
  "senderName": "房务·小呼",
  "preview": "标间超预算了…",
  "priority": "URGENT"               // URGENT 时前端做强提示(弹窗/声音)
}

前端用法:监听 im-chat → unreadCount 刷顶部角标;conversationKey 命中当前打开的会话则 live append 新气泡;priority=URGENT 强提示。P2 是后端能力,前端接入仍待排期通知。


8. 注意事项

  1. P1 无实时:前端先用轮询(拉线程 + 定时拉 unread-total)做交互;不要等 SSE,SSE 在 P2。
  2. 会话键透传:conversationKey 含冒号,作为 path 参数直接用即可(encodeURIComponent 也兼容)。
  3. 雪花 id 用字符串:peerAdminId / messageId / conversationKey 内嵌的 adminId 均以字符串处理,禁做数值运算(防精度丢失)。
  4. 顶部角标不变:继续用 GET /admin/message/unread-count(合并聊天+通知);本期 unread-total 仅细分聊天数。
  5. 重复 open 安全:同两人同对象重复 open 返回同一会话(isNew=false),可放心在进入会话时每次调用。

9. 关联 / 联系人

项目 链接
Issue https://git.1814.love:8443/wx/HL/issues/4020
PR(P1) https://git.1814.love:8443/wx/HL/pulls/4028
PR(hotfix bean 名冲突) https://git.1814.love:8443/wx/HL/pulls/4030
PR(hotfix open 幂等语义) https://git.1814.love:8443/wx/HL/pulls/4031
后端负责人 王骁