Files
EchoChat/docs/api/frontend/notify.md
bujinyuan f1853f125d feat: Phase 2e-1 统一通知中心 + 我的 TabBar 聚合未读红点
后端(notify 模块)
- 新增 notify 模块:DAO/Service/Pusher 接口/Controller/Router/CleanupTask
- 数据库 DDL:notify_notifications 表 + 3 索引(user+created/user+is_read/user+category)
- 11 种 type 枚举(好友/群聊 9 种 + meeting_* 2 种预留)+ 4 种 category
- 跨模块集成:contact 3 处 Pusher(friend_request/accepted/rejected)
- 跨模块集成:group 6 处 Pusher(invite/join_request/approved/rejected/kicked/role_changed)
- WS handler 断线补偿:连接建立即推送 notify.unread.total
- 5 REST API(4 用户 + 1 管理员广播)+ 2 WS 事件(notify.new / notify.unread.total)
- 30 天已读通知定时清理(未读永久保留)
- Provider/Wire 依赖注入(NotifyPusher、NotifyConnectHook、UserInfoResolver 接口)

前端
- 新增 notify 模块:API/Pinia Store(5 分类分页缓存 + 未读数 + WS 事件)/NotifyItem/通知中心主页
- profile 入口:铃铛 badge + 菜单项 badge + 数字显示
- App.vue/login 初始化 notifyStore WS 监听;logout 调用 notifyStore.reset() 清缓存
- 清理 contact.js/group.js 中散落 toast 与冗余 notify.friend.request/group.join.request 处理
- CustomTabBar 新增 hasDot() 聚合指示器:我的 Tab 显示纯红点(无数字),
  当前聚合 notifyStore.unreadTotal,未来可扩展「资料待完善/安全提醒/新版本」等

文档
- 新增 Phase 2e 整体路线图 docs/plans/2026-04-20-phase2e-design.md
- 新增 Phase 2e-1 专用设计 docs/plans/2026-04-20-phase2e-1-design.md(§6.4 TabBar 聚合红点)
- 新增 Phase 2e-1 实施计划 docs/plans/2026-04-20-phase2e-1-implementation.plan.md
- 新增 E2E 验证报告 test-report-phase2e-1-notification.md(含 Playwright MCP 2 个现场 Bug 修复记录)
- 更新 docs/progress/CURRENT_STATUS.md、docs/api/README.md、docs/api/frontend/notify.md
- 更新 .cursor/rules/project-context.mdc、docs/plans/2026-02-27-echochat-system-design.md

其他
- .gitignore 排除 .playwright-mcp/ MCP 临时快照

架构决策
- 单端 WS 连接:沿用现有 ws.Hub,多端已读同步推迟到 Phase 2f/二期
- 跨模块依赖:contact/group → notify 严格单向(接口注入模式)
- 降级策略:Pusher 先入库后推送;WS 失败不回滚入库;入库失败仅 Warn 不影响业务

Playwright MCP 回归(4 类场景全通)
- 实时推送(admin 广播 → 1s 内前端自动插入 + 角标 +1)
- Deep-link 跳转(好友申请通知 → contact/request 页)
- 批量清零(全部已读按钮)
- TabBar 聚合红点(有未读亮/全部已读灭)与 notifyStore.unreadTotal 三层同步

Made-with: Cursor
2026-04-21 10:21:01 +08:00

238 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 通知模块 API (Notify)
> 通用规范(认证方式、响应格式、错误码)见 [README.md](../README.md)
> 新通知的实时推送通过 WebSocket 完成,见 [websocket.md](../websocket.md)
> 所属阶段Phase 2e-1已完成
---
## 概览
统一通知中心,覆盖四大业务类型:
| 分类 | 类型常量 | 触发模块 |
|---|---|---|
| 好友 | `friend_request` / `friend_accepted` / `friend_rejected` | contact |
| 群聊 | `group_invite` / `group_join_request` / `group_join_approved` / `group_join_rejected` / `group_kicked` / `group_role_changed` | group |
| 系统 | `system_broadcast` | admin 广播 |
| 会议 | `meeting_invite` / `meeting_reminder` | meetingPhase 2e-2/2e-3 接入) |
> 通知持久化在 `notify_notifications` 表每条通知对应单个接收者30 天前的已读通知由后台定时任务自动清理,未读通知永久保留。
---
## 接口列表
| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| GET | /api/v1/notifications | 需认证 | 获取通知列表(游标分页) |
| GET | /api/v1/notifications/unread-count | 需认证 | 获取未读数统计 |
| PUT | /api/v1/notifications/:id/read | 需认证 | 标记单条已读 |
| PUT | /api/v1/notifications/read-all | 需认证 | 全部/按分类标记已读 |
| POST | /api/v1/admin/notifications/broadcast | 管理员 | 全员广播系统通知 |
---
## 1. 获取通知列表
`GET /api/v1/notifications`
**权限:** 需认证
**查询参数:**
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| category | string | `all` | 分类过滤:`all` / `friend` / `group` / `meeting` / `system` |
| is_read | bool | 无 | 是否已读:`true`/`false`,不传代表全部 |
| before_id | int64 | 无 | 游标:返回 id 小于此值的记录(按 id 降序) |
| limit | int | 20 | 页大小(最大 100 |
**成功响应:**
```json
{
"code": 0,
"message": "success",
"data": {
"list": [
{
"id": 128,
"type": "group_invite",
"category": "group",
"title": "",
"content": "张三 邀请你加入「产品交流群」",
"extra": "{\"group_id\":12,\"group_name\":\"产品交流群\",\"conversation_id\":45,\"inviter_id\":3,\"inviter_name\":\"张三\"}",
"actor_id": 3,
"actor_name": "张三",
"actor_avatar": "",
"target_type": "group",
"target_id": 12,
"is_read": false,
"created_at": "2026-04-20 10:15:00"
}
],
"has_more": true
}
}
```
> `extra` 是 JSON 字符串,前端按需 `JSON.parse` 解析。游标翻页使用最后一条 `id` 作为下一次请求的 `before_id`。
---
## 2. 获取未读数统计
`GET /api/v1/notifications/unread-count`
**权限:** 需认证
**成功响应:**
```json
{
"code": 0,
"message": "success",
"data": {
"total": 5,
"by_category": {
"friend": 1,
"group": 3,
"meeting": 0,
"system": 1
}
}
}
```
---
## 3. 标记单条已读
`PUT /api/v1/notifications/:id/read`
**权限:** 需认证(只能标记属于自己的通知)
**响应:**
```json
{ "code": 0, "message": "success", "data": { "affected": 1 } }
```
> 已读幂等:重复调用返回 `affected=0`。
---
## 4. 批量标记已读
`PUT /api/v1/notifications/read-all?category={category}`
**权限:** 需认证
**Query 参数:**
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| category | string | 否 | 分类:`friend`/`group`/`meeting`/`system`,不传代表全部 |
> ⚠️ 注意:`category` 通过 **Query String** 传递(非 Request Body。`PUT` 方法无需请求体。
**示例:**
- 全部标已读:`PUT /api/v1/notifications/read-all`
- 仅好友类标已读:`PUT /api/v1/notifications/read-all?category=friend`
**响应:**
```json
{ "code": 0, "message": "success", "data": { "affected": 3 } }
```
---
## 5. 管理员广播系统通知
`POST /api/v1/admin/notifications/broadcast`
**权限:** `RoleAdmin``RoleSuperAdmin`JWT + 角色校验)
**请求体:**
```json
{
"title": "系统维护通知",
"content": "今晚 02:00 - 04:00 将进行系统升级,请提前保存工作",
"target_type": "system",
"target_id": null,
"extra": null
}
```
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| title | string | 是 | 标题,最长 100 |
| content | string | 是 | 正文,最长 500 |
| target_type | string | 否 | 业务对象类型,默认 `system` |
| target_id | int64 | 否 | 业务对象 ID |
| extra | string | 否 | 扩展 JSON 字符串 |
**响应:**
```json
{ "code": 0, "message": "success", "data": { "affected": 128 } }
```
> 后端将遍历所有活跃用户批量写入通知,在线用户实时收到 `notify.new` WS 推送;离线用户重连后通过 `notify.unread.total` 触发未读数补偿。
---
## 6. WebSocket 事件
### `notify.new` — 新通知到达
| 方向 | 触发 |
|---|---|
| Server → Client | 每次通知写入成功后 |
**载荷**:与 REST 返回的单条通知对象字段一致。
```json
{
"event": "notify.new",
"data": {
"id": 129,
"type": "friend_request",
"category": "friend",
"title": "好友申请",
"content": "Bob 请求添加你为好友",
"actor_id": 5,
"actor_name": "Bob",
"target_type": "user",
"target_id": 5,
"is_read": false,
"created_at": "2026-04-20 10:17:21"
}
}
```
### `notify.unread.total` — 未读数补偿
| 方向 | 触发 |
|---|---|
| Server → Client | WebSocket 连接建立成功时 |
**载荷**
```json
{
"event": "notify.unread.total",
"data": {
"total": 5,
"by_category": { "friend": 1, "group": 3, "meeting": 0, "system": 1 }
}
}
```
> 前端以该值覆盖本地缓存的未读数,保证断线重连后铃铛徽标准确。
---
## 已知限制(单端连接架构)
- 当前 `ws.Hub` 仅支持同一用户单连接(新连接关闭旧连接),**不提供多端已读同步**;此限制已在 `docs/plans/2026-04-20-phase2e-design.md` §3.1/§3.5 修订。
- 管理端广播发布 UI 推迟到 Phase 2f后端接口已落地管理端页面未提供
- `group_invite` 的"接受/拒绝"内联操作当前语义:接受仅跳转会话;拒绝调用 `leaveGroup`(由于当前 InviteMembers 为直接加入成员)。