这是 Beta 探索课程,内容结构、实验步骤和示例可能会继续调整。
站内信 - 消息中心设计
为什么需要站内信
入职第三周,产品经理小李找我讨论消息中心的设计。
“用户打开 App,想看最近的通知,但是…”
“但是什么?”
“但是通知已经推送过了,如果用户错过了推送,就没法再看了。”
我恍然大悟。推送是瞬间的,用户可能:
- 手机静音,没听到
- 当时没看手机
- 通知中心清空了
- App 被杀后台,推送没收到
所以,我们需要一个持久化的站内消息系统,让用户随时可以查看历史通知。
站内信 vs 推送
我画了一张对比表:
| 特性 | 推送 | 站内信 |
|---|---|---|
| 时机 | 即时 | 永久存储 |
| 可见性 | 需要用户当时看 | 随时可查看 |
| 存储 | 不存储 | 数据库存储 |
| 已读状态 | 不跟踪 | 跟踪已读未读 |
| 形式 | 弹窗/通知栏 | App 内列表 |
两者是互补的:
- 推送:提醒用户
- 站内信:记录历史
数据库设计
我设计了一个站内信表:
数据设计要点
- 核心是在
insite_messages里保存业务事实,而不是把规则散落在应用逻辑里。- 索引服务于高频查询,重点是缩小扫描范围,而不是堆更多字段。
索引设计
idx_user_created:查询用户消息列表
数据设计要点
- 查询目标是快速定位状态、任务或资源,避免在关键路径上做大范围扫描。
idx_user_unread:查询未读消息数量
数据设计要点
- 查询目标是快速定位状态、任务或资源,避免在关键路径上做大范围扫描。
附加数据设计
data 字段存储 JSON,包含关联的业务数据:
配置要点
- 配置表达的是环境差异和运行参数,不是业务规则本身。
这样,用户点击消息时,可以直接跳转到对应的评论或动态。
消息中心 API
我设计了几个核心 API:
1. 获取消息列表
2. 标记已读
3. 全部标记已读
4. 删除消息
创建消息的时机
什么时候创建站内信?我总结了几个场景:
场景 1:用户评论动态
场景 2:用户点赞动态
点赞比较特殊,因为一个热门动态可能有上千个赞。如果每个赞都发通知,用户会被淹没。
我采用了”合并通知”的策略:
这样,用户看到的点赞通知是:
张三 等 15 人点赞了你的动态
而不是 15 条独立的点赞通知。
未读数量优化
老王问:“未读数量查询会不会很慢?”
我查了一下数据量:
insite_messages 表:
- 总消息数:500 万条
- 每用户平均:100 条
- 每用户未读:10 条
未读数量查询是高频操作,每次打开 App 都要查。我考虑了几种优化方案:
方案 A:Redis 缓存
方案 B:独立计数表
数据设计要点
- 核心是在
user_unread_counts里保存业务事实,而不是把规则散落在应用逻辑里。- 索引服务于高频查询,重点是缩小扫描范围,而不是堆更多字段。
每次创建/标记已读时,更新计数:
我选择了方案 B,因为:
- 计数准确,不依赖缓存过期
- 可以按类型统计未读数(如评论未读 3 条、点赞未读 5 条)
- 更新时只需一条 SQL,性能好
消息聚合
产品经理小李提出一个需求:“同一个人连续评论 5 次,能不能合并成一条通知?”
我分析了一下:
消息列表(不合并):
- 张三 评论了你的动态(1 分钟前)
- 张三 评论了你的动态(2 分钟前)
- 张三 评论了你的动态(3 分钟前)
- 张三 评论了你的动态(4 分钟前)
- 张三 评论了你的动态(5 分钟前)
消息列表(合并):
- 张三 评论了你的动态 5 次(刚刚)
我设计了一个聚合策略:
消息清理
站内信会一直累积,需要清理机制。
我设计了一个定时清理脚本:
每天凌晨执行一次,保持表大小可控。
小结
这周我学到:
- 站内信的作用:持久化存储,让用户随时查看历史通知
- 数据库设计:索引优化,附加数据用 JSON 存储
- 未读计数:独立计数表,实时更新
- 消息聚合:合并相似通知,减少打扰
- 数据清理:定时清理已读旧消息
站内信系统已经基本完成了。接下来,我要处理多端同步的问题了。
