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
This commit is contained in:
bujinyuan
2026-04-21 10:21:01 +08:00
parent ccfceefdaf
commit f1853f125d
42 changed files with 4493 additions and 97 deletions

View File

@@ -0,0 +1,249 @@
# Phase 2e-1 实施计划:统一通知中心
> **状态:** ✅ 已完成
> **设计文档:** [Phase 2e 设计文档](./2026-04-20-phase2e-design.md)
> **验证报告:** [Phase 2e-1 测试验证报告](../../test-report-phase2e-1-notification.md)
> **分支:** `feature/phase2e-meeting-notification`
> **原规划文件Cursor 临时):** `.cursor/plans/phase_2e-1_通知系统实施_b2d43c9d.plan.md`(已被 `.gitignore`,本文档为其正式归档版本)
> **最后更新:** 2026-04-20Task 11 落盘同步)
---
## 一、范围锁定
### 包含
- **10 种业务通知类型**(本期落地触发):
- `friend_request` / `friend_accepted` / `friend_rejected`
- `group_invite` / `group_join_request` / `group_join_approved` / `group_join_rejected` / `group_kicked` / `group_role_changed`
- `system_broadcast`
- **2 种仅预留枚举**(业务由 2e-2/2e-3 对接):`meeting_invite``meeting_reminder`
- 单端 WS 连接架构(沿用现状):**不做多端已读同步**
- 双通道推送:持久化入库 + WS `notify.new` 实时推送 + 铃铛徽标 + mini-toast
- 通知中心 UI顶部 5 分类 Tab全部/好友/群聊/会议/系统)+ 下拉刷新 + 上拉加载
- 管理员广播:`POST /api/v1/admin/notifications/broadcast`(仅后端,前端 UI 推迟)
### 显式推迟(详见设计文档 §九)
- **WS Hub 多端连接支持改造** → Phase 2f / 二期
- `notify.read.ack` 跨设备广播 → 依赖上项,同步推迟
- 管理端广播发布 UI → Phase 2f
- 通知分类开关push 偏好设置)→ Phase 2f
- Playwright 自动化 CI → 待 CI 基础设施建设统一接入
---
## 二、关键架构决策
### 2.1 跨模块通信模式(沿用 Phase 2a 接口注入标准)
```mermaid
flowchart LR
contactSvc[contact Service]
groupSvc[group Service]
adminCtrl[admin/notify Controller]
pusher[notify.Pusher interface]
notifySvc[notify Service]
notifyDAO[notify DAO]
db[(PostgreSQL)]
hub[ws.Hub]
contactSvc -->|Wire 注入| pusher
groupSvc -->|Wire 注入| pusher
adminCtrl -->|Wire 注入| pusher
pusher --> notifySvc
notifySvc --> notifyDAO
notifyDAO --> db
notifySvc -->|SendToUser| hub
```
- `notify.Pusher` 接口定义在 `app/notify/service/pusher.go`,实现同包
- contact/group 通过 Wire 绑定 `NotifyPusher` 字段interface 类型)
- **单向依赖**contact/group → notify**反向禁止**Phase 2e-2 meeting 模块同样遵守)
- **降级策略**WS 推送失败不回滚入库,下一次 `notify.unread.total` 补偿兜底
### 2.2 单端 WS 推送2 个事件)
| 事件 | 方向 | 触发时机 | Payload |
|------|------|----------|---------|
| `notify.new` | S→C | Pusher 入库成功后立即推送 | 完整通知对象 |
| `notify.unread.total` | S→C | 连接建立/重连后钩子触发 | `{ total, by_category }` 权威值 |
**不引入** `notify.read.ack`WS Hub 单连接架构已天然规避多端同步风暴)。
### 2.3 数据库(新增 1 张表)
`notify_notifications` 表:`id / user_id / type / title / content / extra(JSONB) / actor_id / target_type / target_id / is_read / read_at / created_at`
索引:`(user_id, is_read, created_at DESC)``(user_id, type, created_at DESC)``created_at`(配合 30 天清理)。
---
## 三、文件与变更清单
### 3.1 后端新增(`backend/go-service/`
| 文件 | 作用 |
|------|------|
| `app/notify/constants/notify_types.go` | 11 种 type 常量 + 5 种 category 映射 + WS 事件名 |
| `app/notify/model/notification.go` | GORM 模型 |
| `app/notify/dao/notification_dao.go` | CRUD + 批量已读 + 未读统计 + 清理 + 全量用户列表 |
| `app/notify/service/notify_service.go` | 业务逻辑(创建/列表/标已读/广播) |
| `app/notify/service/pusher.go` | `Pusher` 接口 + Impl持久化 + WS 推送) |
| `app/notify/controller/notification_controller.go` | 4 用户接口 + 1 管理员广播接口 |
| `app/notify/router.go` | 路由注册 |
| `app/notify/provider.go` | Wire `NotifySet` |
| `app/notify/task/cleanup_task.go` | 30 天清理 cron默认每日 |
| `app/constants/notify.go` | 跨模块共享常量type / category / WS event |
| `app/dto/notify_dto.go` | 请求 / 响应 / 广播 DTO |
### 3.2 后端改造
| 文件 | 改动 |
|------|------|
| `app/provider/provider.go` | `App` 结构体新增 `NotificationController` / `NotifyCleanupTask` / `NotifyPusher``NewApp` 签名扩展 |
| `app/provider/wire.go` | 注册 `NotifySet`;绑定 `contactService.NotifyPusher` / `groupService.NotifyPusher` / `notifyService.UserInfoResolver` / `ws.NotifyConnectHook` |
| `app/provider/wire_gen.go` | 同步 Wire 生成产物 |
| `app/contact/service/contact_service.go` | **3 处** Pusher.PushSendFriendRequest / AcceptFriendRequest / RejectFriendRequest |
| `app/group/service/group_service.go` | **6 处** Pusher.PushInviteMembers / RequestJoin / ApproveJoin / RejectJoin / KickMember / ChangeRole |
| `app/ws/handler.go` | 连接建立/重连时触发 `NotifyConnectHook.OnUserConnected` → 推送 `notify.unread.total` |
| `cmd/server/main.go` | 启动 `app.NotifyCleanupTask.Start()` + `defer Stop()` |
| `router/router.go` | `notifyApp.RegisterRoutes(engine, app.NotifyController, jwtAuth)` |
| `deploy/docker/postgres/init.sql` | 追加 `notify_notifications` DDL + 3 个索引 |
### 3.3 前端新增(`frontend/src/`
| 文件 | 作用 |
|------|------|
| `api/notify.js` | REST API 封装getNotifications / getUnreadCount / markRead / markAllRead |
| `constants/notify.js` | 前端常量(与后端 type/category 对齐 + 图标/颜色/支持内联操作判定) |
| `store/notify.js` | Pinia Store分类缓存 + cursor 分页 + WS 监听 + reset |
| `components/notify/NotifyItem.vue` | 通用卡片(按 type 渲染 + 内联"接受/拒绝"按钮) |
| `pages/notify/index.vue` | 通知中心页5 分类 Tab + 骨架/空态/列表) |
### 3.4 前端改造
| 文件 | 改动 |
|------|------|
| `pages.json` | 注册 `pages/notify/index`custom navigationStyle |
| `App.vue` | 全局 WS 初始化时调用 `useNotifyStore().initWsListeners() + fetchUnreadCount()` |
| `pages/auth/login.vue` | 登录成功后同上初始化 |
| `pages/profile/index.vue` | 顶部铃铛图标 + 徽标 + 「通知中心」菜单项 + `onShow` 刷新未读 |
| `store/user.js` | `logout()` 内调用 `notifyStore.reset()`,防止跨用户数据泄漏 |
| `store/contact.js` | 删除原 `notify.friend.request` 散落监听(由 notify store 接管) |
| `store/group.js` | 清理 `_onJoinRequest` / `_onJoinApproved` 中的 `uni.showToast`(统一到 notify store |
### 3.5 文档
| 文件 | 操作 |
|------|------|
| `docs/plans/2026-04-20-phase2e-design.md` | §3.1/§3.5/§八/§九 修订「单端 WS 架构」约束 |
| `docs/plans/2026-04-20-phase2e-1-implementation.plan.md` | **本文档**(正式版归档) |
| `docs/api/frontend/notify.md` | 新增 notify API 文档5 REST + 2 WS 事件) |
| `docs/api/README.md` | 导航更新 |
| `docs/progress/CURRENT_STATUS.md` | 进度同步 |
| `.cursor/rules/project-context.mdc` | 跨模块通信模式 + 推迟清单更新 |
| `test-report-phase2e-1-notification.md` | E2E 验证报告 + Task 11 审查修复记录 |
---
## 四、Task 拆分11 个 Task按依赖分层
| # | Task | 依赖 | 交付产物 |
|---|------|------|----------|
| **Task 0** | 基础设施DDL + constants + DTO + model | — | 数据库表、常量、DTO、GORM 模型 |
| **Task 1** | Notify 模块骨架DAO + Service + Pusher 接口 + Wire | T0 | 完整模块雏形,可独立编译 |
| **Task 2** | REST API4 用户接口 + 1 管理员广播 | T1 | `/api/v1/notifications/*` + `/api/v1/admin/notifications/broadcast` |
| **Task 3** | WS 事件:`notify.new` + `notify.unread.total` 断线补偿 | T1 | Pusher 内部推送 + `ws.Handler` 钩子 |
| **Task 4** | contact 模块 Pusher 集成3 类好友事件) | T1 | 申请/接受/拒绝 → 3 处 Push |
| **Task 5** | group 模块 Pusher 集成6 类群聊事件) | T1 | 邀请/申请/批准/拒绝/踢人/角色变更 → 6 处 Push |
| **Task 6** | 30 天清理定时任务 | T1 | `CleanupTask` + 启动/停止集成 |
| **Task 7** | 前端 Pinia Store + API 封装 + WS 监听 | T2+T3 | `store/notify.js` + `api/notify.js` + 2 事件监听 |
| **Task 8** | 通知中心页 + NotifyItem 组件 + 5 分类 Tab | T7 | `pages/notify/index.vue` + 组件 |
| **Task 9** | profile 入口集成 + 冗余代码清理 | T8 | 铃铛徽标 + `contact.js:155` / `group.js:292` 清理 |
| **Task 10** | 端到端验证4 类场景 + 视觉回归) | T4+T5+T9 | `test-report-phase2e-1-notification.md` |
| **Task 11** | 设计文档修订 + `code-reviewer` 审查 + 所有文档同步 | T10 | 设计文档 §3.1/§3.5/§八/§九 修订 + 审查报告 |
### 依赖关系图
```mermaid
graph TD
T0[Task 0 基础设施] --> T1[Task 1 Notify 骨架]
T1 --> T2[Task 2 REST API]
T1 --> T3[Task 3 WS 事件]
T1 --> T4[Task 4 contact 集成]
T1 --> T5[Task 5 group 集成]
T1 --> T6[Task 6 清理任务]
T2 --> T7[Task 7 前端 Store]
T3 --> T7
T7 --> T8[Task 8 通知中心页]
T8 --> T9[Task 9 入口集成]
T4 --> T10[Task 10 E2E 验证]
T5 --> T10
T9 --> T10
T10 --> T11[Task 11 文档同步 + 审查]
```
---
## 五、端到端验收标准
- [x] 用户 A 给 B 发好友申请 → B「我的」铃铛徽标 +1通知中心出现记录WS 实时收到 `notify.new`
- [x] B 点击通知 → 跳转好友申请页;处理后通知自动标已读 → 徽标 -1
- [x] 张三邀请李四入群 → 李四收到卡片内联「接受/拒绝」,操作后群状态与通知状态一致
- [x] 管理员 `POST /api/v1/admin/notifications/broadcast` → 所有在线用户实时收到 `system_broadcast`
- [x] 断线重连:前端收到 `notify.unread.total`,徽标与后端 API 查询一致
- [x] 30 天前已读通知被清理任务删除;未读通知无论多久都不被清理
- [x] `frontend/src/store/contact.js:155` 旧处理已删除,无重复提示
- [x] Logout → `notifyStore.reset()` 清空,防止跨用户数据泄漏
- [x] ~~多端已读同步~~**架构限制不支持**,设计文档已修订
---
## 六、风险与应对
| 风险 | 等级 | 应对 | 实际结果 |
|------|------|------|----------|
| Pusher 同步调用阻塞业务 | 中 | WS 推送失败仅 warn 日志,不阻塞业务主流程 | ✅ 已落实 |
| contact/group 现有业务逻辑受影响 | 中 | 每个调用点保留原路径Pusher 仅旁路调用 | ✅ 无回归 |
| Wire 生成器循环依赖 | 高 | 严格单向 contact/group → notifyinterface 注入 | ✅ 无循环 |
| 前端 toast 与现有 `uni.showToast` 冲突 | 中 | 统一由 notify store 触发,其他模块不再直接调用 | ✅ 已清理 `contact.js:155` / `group.js:292` |
| 管理端广播权限 | 高 | `middleware.RequireRole(admin/super_admin)` | ✅ 已校验 |
| 代码审查漏网 | 中 | Task 11 调用 `code-reviewer` 子代理 | ✅ 发现 1 Blocker 已修复 |
---
## 七、实施后的连带动作
1. ✅ 修订 `docs/plans/2026-04-20-phase2e-design.md`
- §3.1 决策表「多端已读同步」改为「暂不支持WS Hub 单端架构)」
- §3.5 WS 事件表移除 `notify.read.ack`
- §八验收标准全部标记完成并注明限制
- §九推迟清单新增「WS Hub 多端连接支持改造」+「Playwright E2E CI」
- §十新增 2026-04-20 实施完成变更记录
2.`code-reviewer` 子代理审查(结论:有条件通过)
- 🔴 Blocker`markAllRead` 前端 body / 后端 query 契约错位 → 已修复(`frontend/src/api/notify.js` 改走 Query
- 🟡 Major × 5 / 🟢 Minor × 11不阻塞合入纳入 Phase 2f 清理清单
3. ✅ 所有规划 + 实施 + 验证文档在同一分支(`feature/phase2e-meeting-notification`)提交
4.`feature/phase2e-meeting-notification` 分支准备就绪,下一步可 PR 合并至主干或继续 Phase 2e-2
---
## 八、工期回顾
| 子任务 | 预估 | 实际 | 备注 |
|--------|------|------|------|
| 后端Task 0-6 | 2 人日 | ~2 人日 | Wire 绑定与 ws.Handler 钩子略超预期 |
| 前端Task 7-9 | 1.5 人日 | ~1.5 人日 | 分类缓存 + WS 去重实现顺利 |
| E2E + 审查 + 文档Task 10-11 | 0.5 人日 | ~1 人日 | 代码审查发现 Blocker 增加修复耗时 |
| **合计** | **4 人日** | **~4.5 人日** | 与设计文档预估基本一致 |
---
## 九、关联文档
- [Phase 2e 总体设计](./2026-04-20-phase2e-design.md)
- [Phase 2e-1 测试验证报告](../../test-report-phase2e-1-notification.md)
- [Notify API 文档](../api/frontend/notify.md)
- [项目开发进度 · CURRENT_STATUS](../progress/CURRENT_STATUS.md)
- [项目上下文 · project-context.mdc](../../.cursor/rules/project-context.mdc)