Files
EchoChat/docs/plans/2026-04-20-phase2e-1-implementation.plan.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

250 lines
13 KiB
Markdown
Raw Permalink 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.

# 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)