docs(changelog): 内部员工站内信收件箱 + SSE 实时推送(房务/车务可用) (工单#3414 PR#3424#3425)
这个提交包含在:
父节点
614c9a10e6
当前提交
80ae44d8f2
@ -0,0 +1,223 @@
|
|||||||
|
# 【新增接口·管理后台】内部员工站内信收件箱 + 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 对接细节。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 接口清单
|
||||||
|
|
||||||
|
| # | 用途 | 方法 / 路径 |
|
||||||
|
|---|------|------------|
|
||||||
|
| 1 | **SSE 实时长连接** | GET `/ws/admin-msg/stream`(text/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-push`(channel=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=`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
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 是轻量信令:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "adminId": 1001, "unreadCount": 2, "title": "realtime through nginx" }
|
||||||
|
```
|
||||||
|
|
||||||
|
- `unreadCount`:该员工当前未读总数 → **直接用它刷新铃铛角标**。
|
||||||
|
- `title`:新消息标题 → 可选弹个 toast 提示。
|
||||||
|
- 正文/列表:收到 `message` 后**调 `/admin/message/list` 拉最新一页**展示,不在 SSE 里传全文。
|
||||||
|
|
||||||
|
### 3.3 前端处理示例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
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`
|
||||||
|
|
||||||
|
Query:`pageNo`(默认1) / `pageSize`(默认20,≤100) / `categoryCode`(可选,按分类过滤)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -H "Authorization: Bearer <adminToken>" \
|
||||||
|
"https://api.test.1814.love:9443/admin/message/list?pageNo=1&pageSize=20"
|
||||||
|
```
|
||||||
|
|
||||||
|
响应 `data`:`{ records: AdminMessageRespVO[], total, ... }`(标准分页)。
|
||||||
|
|
||||||
|
### 4.2 未读数 / 分类汇总
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 铃铛角标
|
||||||
|
GET /admin/message/unread-count -> data: 2 (整数)
|
||||||
|
# 分类未读汇总(进收件箱分 tab 用)
|
||||||
|
GET /admin/message/categories -> data: [{categoryCode, categoryName, unreadCount}, ...]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 标已读 / 删除
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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`
|
||||||
|
|
||||||
|
Query:`keyword`(姓名模糊,可空) / `roleKey`(按角色过滤,可空)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -H "Authorization: Bearer <adminToken>" \
|
||||||
|
"https://api.test.1814.love:9443/admin/notification/custom-push/admins?keyword=张"
|
||||||
|
```
|
||||||
|
|
||||||
|
响应 `data`:`AdminPushTargetVO[]`(`adminId`/`name`/`username`/`roleKey`/`roleName`),供选人下拉。
|
||||||
|
|
||||||
|
### 5.2 发内部员工站内信 POST `/admin/notification/custom-push`
|
||||||
|
|
||||||
|
在原「自定义推送」请求上**新增**:`channel="ADMIN_INAPP"` + 两种目标类型:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 按指定员工
|
||||||
|
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` 事件、收件箱多一条。
|
||||||
|
|
||||||
|
> 说明:按角色(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 是信令不是正文**:收到 `message` 用 `unreadCount` 刷角标,正文调 `/admin/message/list` 拉。
|
||||||
|
3. **重连对账**:EventSource 自动重连,重连后调一次 `unread-count` 防漏推。
|
||||||
|
4. **isRead 是数字 0/1**(不是布尔),`messageId`/`adminId` 出参注意是 String 透传防精度。
|
||||||
|
5. **未读角标双来源**:登录/刷新页用 `unread-count` 拉一次初值,之后由 SSE `message` 事件增量刷新。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. 关联
|
||||||
|
|
||||||
|
- 工单 #3414;PR #3424(主功能)+ #3425(SSE 过 nginx 缓冲修复)
|
||||||
|
- 后端负责人:@wx
|
||||||
|
- 前端对接(管理后台):mmg
|
||||||
正在加载...
x
在新工单中引用
屏蔽一个用户