hl-api-changelog/changelogs-v2/2026-06/04_3414_内部员工站内信收件箱-SSE实时推送-管理后台.md

13 KiB

【新增接口·管理后台】内部员工站内信收件箱 + SSE 实时推送(房务/车务可用)

PR: #3424 #3425工单 #3414 服务: hl-user-service + hl-gateway | 更新时间: 2026-06-04

存放目录: changelogs-v2/2026-06/ 影响范围: 管理后台「通知中心 / 顶部消息铃铛 / 自定义推送」 状态: 已合并 dev-v3 + 测试服双实例部署 + API 全链路实测通过(含 SSE 实时推送过网关 9443


⚠️ 关键说明

原站内信只能发给 C 端微信客人内部员工(管理后台账号 admin_user房务管家、车务调度、运营等收不到站内信、只能收企微。本次补齐:

  1. 内部员工有了自己的站内信收件箱/admin/message/*,与 C 端 /mp/message 完全独立)。
  2. SSE 实时推送:前端用 EventSource 连一条长连接,后端产生新站内信时实时下发,刷新顶部铃铛角标(无需轮询)。
  3. 自定义推送可选内部员工:原「自定义推送」选人只能搜 C 端客人,现可按角色 / 按人选内部员工发站内信。

前端需要做三块:① 顶部消息铃铛 + 未读角标 ② 站内信收件箱页(列表/分类/已读/删除)③ EventSource 客户端(连接 + 收到事件刷新角标)。下面给全套接口契约 + SSE 对接细节。

🔔 入口位置(重点,别漏)

当前管理后台「通知中心」只有 通知配置 / 消息分类管理 / 通知日志 / 自定义推送,缺一个让登录员工看「发给自己」站内信的入口。请加两个入口(指向同一套 /admin/message/* 接口):

  1. 全局顶部铃铛(所有页面常驻):红点未读数 = unread-count,由 SSE 实时刷新;点击下拉显示最近几条 + 「查看全部」。
  2. 「我的消息」页平铺收件箱——分页列表(list+ 单条/全部已读 + 删除(内部员工站内信统一 SYSTEM 分类,不需要分类 tab)。

⚠️ 菜单是 DB 驱动(sys_menu 表):本系统后台菜单存在 sys_menu,每行 component 指向前端组件(如「自定义推送」→ notification/CustomPush)。所以加「我的消息」要两步一起做:① 在 sys_menu「通知中心」(menu_id=950) 下加一行(component 指向新页面如 notification/MyMessages,可经 系统管理→菜单管理 加或随前端发版)② 实现该前端组件。只加菜单行不建组件 = 点开空白页,务必同步。

注意区分:现有「自定义推送」是消息(运营发给员工/客人),本次新增的是消息(员工看自己的站内信),是两个不同入口。


1. 接口清单

# 用途 方法 / 路径
1 SSE 实时长连接 GET /ws/admin-msg/streamtext/event-stream,见 §3
2 收件箱列表(分页,可按分类) GET /admin/message/list
3 未读总数(铃铛角标) GET /admin/message/unread-count
4 分类未读汇总 GET /admin/message/categories
5 单条标已读 PUT /admin/message/{id}/read
6 全部标已读 PUT /admin/message/read-all
7 某分类标已读 PUT /admin/message/category/{categoryCode}/read
8 删除单条 DELETE /admin/message/{id}
9 自定义推送选内部员工(搜人) GET /admin/notification/custom-push/admins
10 自定义推送(发内部员工站内信) POST /admin/notification/custom-pushchannel=ADMIN_INAPP,见 §5

全部走管理后台 admin token顶部铃铛/收件箱用当前登录员工身份,后端从网关注入的 X-Admin-Id 取人,前端无需传 adminId


2. 背景

房务/车务等业务需要给「内部员工」实时通知(到岗/到人),而站内信此前是 C 端专属。本次给内部员工建独立收件箱 admin_message(按 adminId 维度),并用 SSE 做实时角标刷新。SSE 仅作「有新消息了」的轻量信令,正文仍走收件箱列表接口拉取,DB 为唯一真相源(即便 SSE 偶尔断线也不丢消息,重连后拉未读对账即可)。


3. SSE 实时推送对接(重点)

3.1 建立连接

前端用浏览器原生 EventSource 连接。EventSource 不能自定义请求头,所以 token 走 query 参数 ?token=

const token = /* 当前登录 admin token */;
const es = new EventSource(`https://api.test.1814.love:9443/ws/admin-msg/stream?token=${token}`);
  • 鉴权:网关校验 token 通过后注入身份,未带 token / 无效 token 返 401。
  • 缓冲:后端已下发 X-Accel-Buffering: no 关闭 nginx 反代缓冲,前端无需处理。
  • 保活:服务端每 25 秒发 :ping 心跳,浏览器自动忽略。
  • 重连:EventSource 断线自动重连;重连后建议主动调一次 /admin/message/unread-count 对账(防断线期间漏推)。

3.2 事件类型

event data 说明
connected ok 连接建立确认(可忽略,仅表示连上了)
message JSON见下 有新站内信,按此刷新角标
:ping 注释行) 心跳,浏览器自动忽略

message 事件的 data 是轻量信令:

{ "adminId": 1001, "unreadCount": 2, "title": "realtime through nginx" }
  • unreadCount:该员工当前未读总数 → 直接用它刷新铃铛角标
  • title:新消息标题 → 可选弹个 toast 提示。
  • 正文/列表:收到 message/admin/message/list 拉最新一页展示,不在 SSE 里传全文。

3.3 前端处理示例

es.addEventListener('message', (e) => {
  const sig = JSON.parse(e.data);
  setBadge(sig.unreadCount);        // 刷新铃铛角标
  toast(`新消息:${sig.title}`);    // 可选
  if (inboxOpen) reloadInboxList(); // 收件箱打开着就刷新列表
});
es.onerror = () => { /* EventSource 会自动重连;重连后 fetchUnreadCount() 对账 */ };

4. 收件箱接口详情

4.1 列表 GET /admin/message/list

QuerypageNo(默认1) / pageSize(默认20,≤100) / categoryCode(可选,按分类过滤)

curl -H "Authorization: Bearer <adminToken>" \
  "https://api.test.1814.love:9443/admin/message/list?pageNo=1&pageSize=20"

响应 data{ records: AdminMessageRespVO[], total, ... }(标准分页)。

4.2 未读数 / 分类汇总

# 铃铛角标
GET /admin/message/unread-count            -> data: 2 (整数)
# 分类未读汇总
GET /admin/message/categories              -> data: [{categoryCode, categoryName, unreadCount}, ...]

⚠️ 内部员工站内信分类统一为 SYSTEM(不用 C 端 订单/行程/促销 分类)。所以 categories 只会返回一条 SYSTEM「我的消息」做平铺列表即可,不需要分类 tabcategory/{code}/read 也基本用不到,标已读直接用 read-all 或单条)。

4.3 标已读 / 删除

PUT    /admin/message/{id}/read                      # 单条已读
PUT    /admin/message/read-all                       # 全部已读
PUT    /admin/message/category/{categoryCode}/read   # 某分类已读
DELETE /admin/message/{id}                            # 删除(软删)

进入某条消息 / 某分类时调对应「已读」,未读数随即下降;越权防护:只能操作自己的消息(后端按 adminId 限定)。


5. 自定义推送选内部员工

5.1 搜人 GET /admin/notification/custom-push/admins

Querykeyword(姓名模糊,可空) / roleKey(按角色过滤,可空)

curl -H "Authorization: Bearer <adminToken>" \
  "https://api.test.1814.love:9443/admin/notification/custom-push/admins?keyword=张"

响应 dataAdminPushTargetVO[]adminId/name/username/roleKey/roleName),供选人下拉。

🔴 2026-06-05 前端接线 bugmmg 必看):测试环境「自定义推送」页选「站内信(内部员工)」+「指定员工」后,「选择员工」下拉显示「无数据」。查测试服 nginx 访问日志实证:该页面浏览器只调了 /admin/role/all(给「按角色」用),对本接口 /admin/notification/custom-push/admins 零调用。即下拉 UI 建了但没接搜索接口。后端实测 200、返 20 个员工,后端没问题

  • 修法:把「指定员工」的选择员工下拉接成远程搜索——输入时调 GET /admin/notification/custom-push/admins?keyword={输入值},用返回的 adminId 作 option value、name(无则 username) 作 label,最终提交 §5.2 的 adminIds
  • ⚠️ 字段名和 C 端不一样别照搬C 端选人接口 /custom-push/usersid/realName/phone,本员工接口返的是 adminId/name/username。若克隆 C 端组件,必须改字段映射(id→adminIdrealName→name),否则即便接上了也会因取不到字段而「无数据」。

5.2 发内部员工站内信 POST /admin/notification/custom-push

在原「自定义推送」请求上新增channel="ADMIN_INAPP" + 两种目标类型:

# 按指定员工
curl -X POST -H "Authorization: Bearer <adminToken>" -H 'Content-Type: application/json' \
  -d '{"channel":"ADMIN_INAPP","targetType":"ADMIN_SPECIFIC","adminIds":[1001,1002],
       "title":"系统维护通知","content":"今晚 22:00 维护","categoryCode":"SYSTEM"}' \
  "https://api.test.1814.love:9443/admin/notification/custom-push"

# 按角色roleKeys 如 house_lead/house_keeper
#  body: {"channel":"ADMIN_INAPP","targetType":"ADMIN_BY_ROLE","roleKeys":["house_lead"], "title":..,"content":..}

响应:{ totalCount, successCount, failCount }。被推的员工立即在 SSE 收到 message 事件、收件箱多一条。

说明 1消息分类对内部员工固定 SYSTEM,后端强制写 SYSTEM,前端**「消息分类」下拉对内部员工可隐藏/不传**(传了也会被忽略为 SYSTEM。 说明 2按角色ADMIN_BY_ROLE当前覆盖已绑企微的同角色活跃员工;要覆盖未绑企微的,用 ADMIN_SPECIFIC 指定人。


6. 数据结构

6.1 AdminMessageRespVO收件箱条目

字段 类型 说明
messageId string 消息 ID雪花,String 透传防精度丢失)
categoryCode string 分类编码SYSTEM/ORDER/TRIP/PROMO …)
title string 标题
content string 正文
link string 跳转链接(可空)
bizId / bizType string 业务关联(可空)
isRead int 0=未读 1=已读(数字,非布尔)
createTime long 毫秒时间戳

6.2 AdminPushTargetVO选人

adminId(string) / name(企微真名优先,回退用户名) / username / roleKey / roleName

6.3 SSE message 信令

adminId(number) / unreadCount(number) / title(string)


9. 业务边界

  • 与 C 端站内信完全隔离:内部员工走 admin_message,C 端客人走 user_message,互不影响。
  • DB 为真相源,SSE 仅信令SSE 丢一条不丢数据(重连拉未读对账)。
  • 多实例安全:经 Redis Pub/Sub 跨实例广播,员工连任意实例都能实时收到。
  • 越权防护:收件箱所有读写按当前 adminId 限定,只能看/操作自己的消息。
  • 房务/车务事件:内部员工类事件(房务组长/房务组/认领人等)开站内信后也会落员工收件箱(与企微并行)。

11. 影响评估

  • 破坏性:无(全为新增接口 + 新增 channel/targetType,C 端站内信与原自定义推送 C 端路径零改动)。
  • 前端是否必须同步:是(铃铛 + 收件箱页 + EventSource 客户端 + 自定义推送选人加「内部员工」选项)。
  • 存量数据:无影响(新表新建)。

12. 注意事项

  1. SSE token 走 query?token=,EventSource 不能带 Authorization 头;后端已处理 nginx 缓冲,前端正常用 EventSource 即可。
  2. SSE 是信令不是正文:收到 messageunreadCount 刷角标,正文调 /admin/message/list 拉。
  3. 重连对账EventSource 自动重连,重连后调一次 unread-count 防漏推。
  4. isRead 是数字 0/1(不是布尔),messageId/adminId 出参注意是 String 透传防精度。
  5. 未读角标双来源:登录/刷新页用 unread-count 拉一次初值,之后由 SSE message 事件增量刷新。

13. 关联

  • 工单 #3414;PR #3424主功能+ #3425SSE 过 nginx 缓冲修复)
  • 后端负责人:@wx
  • 前端对接管理后台mmg