# 通知系统设计方案 **日期**: 2026-05-15 **状态**: 已确认 **版本**: v1.0 --- ## 1. 概述 ### 1.1 目标 构建统一的通知系统,支持以下通知类型: - **点赞通知** - 用户点赞藏品时触发 - **系统通知** - 后台运营人员/系统触发 - **活动通知** - 系统活动事件触发 ### 1.2 设计原则 - **统一存储** - 所有通知类型使用同一张表,通过 `type` 字段区分 - **轻量服务** - Notification Service 只负责存储和查询,不包含业务逻辑 - **扩展性** - JSON 字段存储类型特定的扩展数据,便于后续扩展新通知类型 --- ## 2. 数据库设计 ### 2.1 通知主表 ```sql CREATE TABLE notifications ( id BIGSERIAL PRIMARY KEY, user_id BIGINT NOT NULL, -- 接收通知的用户ID star_id BIGINT NOT NULL, -- 数据隔离(star ID) type VARCHAR(20) NOT NULL, -- 通知类型: like / system / activity title VARCHAR(200) NOT NULL, -- 通知标题 content VARCHAR(500), -- 通知内容 data JSONB, -- 扩展数据(类型特定) is_read BOOLEAN DEFAULT FALSE, -- 是否已读 is_deleted BOOLEAN DEFAULT FALSE, -- 是否删除(软删除) created_at BIGINT NOT NULL, -- 创建时间(毫秒时间戳) read_at BIGINT, -- 阅读时间(毫秒时间戳) -- 索引 INDEX idx_notifications_user_type_created (user_id, star_id, type, created_at DESC), INDEX idx_notifications_user_unread (user_id, star_id, is_read, created_at DESC) ); ``` ### 2.2 通知统计表 用于快速查询未读数,支持 TabBar 角标显示。 ```sql CREATE TABLE notification_stats ( user_id BIGINT NOT NULL, -- 用户ID star_id BIGINT NOT NULL, -- 数据隔离(star ID) like_unread_count INT DEFAULT 0, -- 点赞通知未读数 system_unread_count INT DEFAULT 0, -- 系统通知未读数 activity_unread_count INT DEFAULT 0, -- 活动通知未读数 total_unread_count INT DEFAULT 0, -- 总未读数 updated_at BIGINT NOT NULL, -- 更新时间 PRIMARY KEY (user_id, star_id) ); ``` > **注意**:`notification_stats` 使用 `(user_id, star_id)` 作为联合主键,支持多 star 场景下各 star 独立统计未读数。 ### 2.3 JSON data 字段示例 ```json // 点赞通知 (type: "like") { "target_type": "asset", "target_id": 123, "actor_id": 456, "actor_name": "张三", "actor_avatar": "https://example.com/avatar/456.png", "asset_title": "我的藏品", "asset_cover": "https://example.com/asset/123/cover.png", "star_id": 1 } // 系统通知 (type: "system") { "action_type": "url", "action_url": "/pages/settings/detail", "action_text": "查看详情", "attachments": ["https://example.com/img1.jpg"] } // 活动通知 (type: "activity") { "activity_id": 789, "activity_title": "端午节活动", "activity_cover": "https://example.com/activity/789/cover.png", "reward_type": "badge", "reward_name": "端午限定徽章", "star_id": 1 } ``` --- ## 3. 服务架构 ### 3.1 Notification Service 职责 | 模块 | 职责 | |-----|-----| | Repository | 通知记录的 CRUD,查询列表 | | Service | 业务逻辑:写入通知、更新统计、查询未读 | | Provider | RPC 接口,供其他服务调用 | ### 3.2 服务边界 - **Notification Service** 只负责通知的存储和查询 - **业务逻辑** 由各自的服务处理(如点赞由 Social Service 处理) - 其他服务在业务发生时调用 Notification Service 写入通知 ### 3.3 调用关系 ``` ┌─────────────────┐ ┌─────────────────────┐ │ Social Service │────▶│ Notification Service │ │ (点赞业务) │ │ (存储 + 查询) │ └─────────────────┘ └─────────────────────┘ │ │ ▼ ▼ ┌─────────────────┐ ┌─────────────────────┐ │ Asset Service │ │ PostgreSQL │ │ (更新点赞数) │ │ notifications + │ └─────────────────┘ │ notification_stats │ └─────────────────────┘ ``` > **事务边界**:Notification Service 在数据库事务中同时写入 `notifications` 表和更新 `notification_stats` 表。 --- ## 4. API 接口设计 ### 4.1 RPC 接口(供内部服务调用) | 方法 | 描述 | 参数 | |-----|------|------| | CreateNotification | 创建通知 | userID, starID, type, title, content, data | | GetNotifications | 查询通知列表 | userID, starID, type, page, pageSize | | GetUnreadCount | 获取未读数 | userID, starID | | MarkAsRead | 标记已读 | notificationID, userID, starID | | MarkAllAsRead | 全部标已读 | userID, starID, type | | DeleteNotification | 删除通知 | notificationID, userID, starID | > **说明**:所有接口都需要传入 `starID`,确保数据隔离。 ### 4.2 HTTP 接口(供前端调用) | 方法 | 路径 | 描述 | |-----|------|------| | GET | /api/v1/notifications | 查询通知列表 | | GET | /api/v1/notifications/unread-count | 获取未读数 | | POST | /api/v1/notifications/:id/read | 标记单条已读 | | POST | /api/v1/notifications/read-all | 全部标已读 | | DELETE | /api/v1/notifications/:id | 删除通知 | > **说明**:所有 HTTP 接口都需要通过 Header 或 Cookie 传递 `star_id` 进行数据隔离。 ### 4.3 查询参数 ``` GET /api/v1/notifications?type=like&tab=today&page=1&pageSize=20 参数说明: - type: 通知类型 (like / system / activity) - tab: 查询tab (today / history) - page: 页码 - pageSize: 每页数量 - star_id: 数据隔离 ID(从 Header 或上下文获取) ``` --- ## 5. 功能详细设计 ### 5.1 点赞通知生成流程 ``` 1. 用户点赞 → Social Service 处理 2. Social Service 调用 Asset Service RPC 更新点赞数 3. Social Service 调用 Notification Service CreateNotification 4. Notification Service(在事务中完成): - 写入 notifications 表 - 更新 notification_stats 表 (+1 未读数,INSERT ... ON CONFLICT DO UPDATE 处理首次创建) 5. 返回成功响应 ``` > **事务保证**:创建通知和更新统计必须在同一个事务中,确保数据一致性。 ### 5.2 查询逻辑 ``` 今日 Tab: WHERE type = 'like' AND user_id = ? AND star_id = ? AND created_at >= 今日零点 ORDER BY created_at DESC 历史 Tab: WHERE type = 'like' AND user_id = ? AND star_id = ? AND created_at < 今日零点 ORDER BY created_at DESC ``` > **说明**:所有查询都需要 `star_id` 确保数据隔离。 ### 5.3 未读数统计 - 每次创建通知时,在同一事务中更新 `notification_stats` 表对应类型的未读数 - 使用 `INSERT ... ON CONFLICT DO UPDATE` 确保首次创建时自动插入记录 - 每次标记已读时,减少对应类型的未读数 - 批量标已读时,重置对应类型的未读数为 0 - 删除通知时,同步减少未读数 ### 5.4 通知直达 | 通知类型 | 跳转逻辑 | |---------|---------| | like | 跳转藏品详情页: `/pages/asset/detail?id={target_id}` | | system | 跳转 `data.action_url` 指定页面 | | activity | 跳转活动详情页: `/pages/activity/detail?id={activity_id}` | --- ## 6. 支持的功能 ### 6.1 TabBar 角标 - 查询 `notification_stats` 表获取各类型未读数 - 前端展示: 点赞未读数、系统未读数、活动未读数 ### 6.2 今日/历史 Tab - 今日: 当天 00:00:00 至今的点赞通知 - 历史: 更早的点赞通知 ### 6.3 通知直达 - 点击通知跳转到对应详情页面 ### 6.4 批量操作 - 全部标为已读(支持按类型) - 删除历史通知(软删除) --- ## 7. 记录合并规则 - **不合并记录** - 同一用户对同一藏品当天多次点赞,每条点赞都产生独立通知 - 例如: 用户A当天点赞藏品B 3次,产生3条独立记录 --- ## 8. 数据一致性保证 ### 8.1 事务边界 创建通知和更新统计必须在同一个数据库事务中完成: ```go func (s *NotificationService) CreateNotification(ctx context.Context, ...) error { return s.db.Transaction(func(tx *sql.Tx) error { // 1. 写入 notifications 表 _, err := tx.Exec("INSERT INTO notifications (...) VALUES (...)", ...) if err != nil { return err } // 2. 更新 notification_stats 表(使用 INSERT ... ON CONFLICT DO UPDATE) _, err = tx.Exec(` INSERT INTO notification_stats (user_id, star_id, like_unread_count, updated_at) VALUES (?, ?, 1, ?) ON CONFLICT (user_id, star_id) DO UPDATE SET like_unread_count = notification_stats.like_unread_count + 1, total_unread_count = notification_stats.total_unread_count + 1, updated_at = ? `, userID, starID, now, now) return err }) } ``` ### 8.2 点赞通知防重复 由于藏品可下架再上架,同一用户对同一藏品当天可能产生多条点赞通知。为避免重复: ```sql -- 为点赞通知添加唯一约束(可选,取决于业务需求) CREATE UNIQUE INDEX idx_like_notification_daily ON notifications (user_id, star_id, type, data->>'target_id', data->>'actor_id', date_trunc('day', to_timestamp(created_at/1000))); ``` > **说明**:如果业务上允许同一天多条点赞通知(每条都展示),则不需要此唯一约束。 ### 8.3 补偿机制 如果事务提交后 RPC 调用方未收到响应,调用方会重试。此时: - 使用唯一约束 `UNIQUE (user_id, star_id, type, target_type, target_id, actor_id, date)` 防止重复创建点赞通知 - 或者通过幂等性设计(如每次点赞生成唯一 notification_id)处理重复 --- ## 9. 项目结构 ``` services/notificationService/ ├── main.go ├── repository/ │ ├── notification_repository.go -- 通知 CRUD │ └── notification_stats_repository.go -- 统计更新 ├── service/ │ └── notification_service.go -- 业务逻辑 + 事务处理 ├── provider/ │ └── notification_provider.go -- RPC 接口 ├── model/ │ └── notification.go -- 数据模型 └── client/ └── (供其他服务调用的客户端,如需要) ``` --- ## 10. 后续扩展 - 支持评论通知 (type: "comment") - 支持 @提及通知 (type: "mention") - 支持推送功能(实时通知) --- ## 11. 确认点 - [x] 统一通知表存储所有类型 - [x] 独立 Notification Service - [x] 支持 star_id 数据隔离 - [x] 支持今日/历史 Tab 查询 - [x] 不合并点赞记录 - [x] 支持未读数统计(事务保证一致性) - [x] 支持通知直达 - [x] 支持批量操作 - [x] INSERT ... ON CONFLICT DO UPDATE 处理首次创建统计