topfans/docs/specs/2026-05-15-notification-system-design.md
zheng020 7d119d34ba docs: 修复通知系统设计文档问题
修复内容:
- 修正章节编号 8.2 -> 8.3
- 查询逻辑增加 star_id 条件
- HTTP 接口说明增加 star_id 传递方式

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 11:29:04 +08:00

11 KiB
Raw Blame History

通知系统设计方案

日期: 2026-05-15 状态: 已确认 版本: v1.0


1. 概述

1.1 目标

构建统一的通知系统,支持以下通知类型:

  • 点赞通知 - 用户点赞藏品时触发
  • 系统通知 - 后台运营人员/系统触发
  • 活动通知 - 系统活动事件触发

1.2 设计原则

  • 统一存储 - 所有通知类型使用同一张表,通过 type 字段区分
  • 轻量服务 - Notification Service 只负责存储和查询,不包含业务逻辑
  • 扩展性 - JSON 字段存储类型特定的扩展数据,便于后续扩展新通知类型

2. 数据库设计

2.1 通知主表

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 角标显示。

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 字段示例

// 点赞通知 (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 事务边界

创建通知和更新统计必须在同一个数据库事务中完成:

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 点赞通知防重复

由于藏品可下架再上架,同一用户对同一藏品当天可能产生多条点赞通知。为避免重复:

-- 为点赞通知添加唯一约束(可选,取决于业务需求)
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. 确认点

  • 统一通知表存储所有类型
  • 独立 Notification Service
  • 支持 star_id 数据隔离
  • 支持今日/历史 Tab 查询
  • 不合并点赞记录
  • 支持未读数统计(事务保证一致性)
  • 支持通知直达
  • 支持批量操作
  • INSERT ... ON CONFLICT DO UPDATE 处理首次创建统计