3. 数据库模型设计(上):用户表、通知模板表、通知记录表的核心字段与关系设计
好,咱们今天正式进入数据库模型设计环节。说实话,这部分是整个消息通知模块的基石。你想想看,如果表结构设计得不好,后面写业务逻辑的时候,那叫一个痛苦。我在项目里见过太多因为字段少了一个索引、或者关联关系没理清,导致线上查询慢到崩溃的案例。
今天咱们先搞定三张核心表:用户表、通知模板表、通知记录表。这三张表,基本能覆盖 80% 的消息通知场景。
3.1 用户表:不只是存个用户名
用户表大家都很熟悉,但做消息通知时,有几个字段是必须额外关注的。
我个人的习惯是,用户表里除了基本的 id、用户名、密码、邮箱、手机号之外,一定要预留一个 notification_config 字段。为什么?因为每个用户对通知的偏好不一样。有的人希望邮件接收,有的人只接受站内信,还有的人想关掉所有通知。
核心字段清单:
id:主键,自增或 UUID,看团队习惯username:用户名,唯一索引email:邮箱,用于邮件通知phone:手机号,用于短信通知notification_config:JSON 字段,存储用户的通知偏好设置status:用户状态,比如启用、禁用、已删除created_at、updated_at:时间戳,必备
嗯,这里要注意:notification_config 用 JSON 字段,而不是单独建一张配置表。我在项目中试过拆成多张表,结果每次查询都要 JOIN,性能反而下降。对于这种配置信息,JSON 字段完全够用,而且 Django 和 SQLAlchemy 都原生支持 JSON 操作。
小技巧:用户表的 email 和 phone 建议加上唯一索引。我曾经遇到过一个用户注册了两个账号,邮箱一样,结果通知发重了,用户投诉说「你们系统是不是有 bug?」。加个唯一索引,从源头避免这种问题。
3.2 通知模板表:让消息内容可配置
通知模板表,说白了就是存消息的「骨架」。你想想看,如果每次发通知都在代码里硬编码消息内容,那改个文案还得重新部署,多麻烦。
我建议模板表至少包含以下字段:
| 字段名 | 类型 | 说明 |
|---|---|---|
id |
int | 主键 |
template_code |
varchar(64) | 模板唯一编码,比如 ORDER_CREATED |
title |
varchar(200) | 通知标题,支持占位符 |
content |
text | 通知正文,支持占位符 |
channel |
varchar(20) | 通知渠道:email / sms / in_app |
variables |
JSON | 占位符变量定义,比如 ["username", "order_id"] |
status |
tinyint | 启用/禁用 |
这里有个关键点:template_code 一定要加唯一索引。为什么?因为业务代码里是通过这个编码来引用模板的。如果重复了,系统就不知道该用哪个模板了。
避坑指南:我曾经在项目里犯过一个错误——模板内容里的占位符和实际传入的变量名不一致。比如模板里写的是 {user_name},但代码里传的是 {username},结果用户收到的通知里显示的是「尊敬的 {user_name},您好」。嗯,从那以后我强制要求模板的 variables 字段必须和占位符一一对应,并且在保存模板时做校验。
3.3 通知记录表:每条通知的「一生」
通知记录表,是消息通知模块里数据量最大的表。每条通知从创建到发送,再到用户是否已读,都要记录在这里。
核心字段设计如下:
CREATE TABLE notification_record (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
template_code VARCHAR(64) NOT NULL,
title VARCHAR(200),
content TEXT,
channel VARCHAR(20) NOT NULL,
status TINYINT DEFAULT 0 COMMENT '0:待发送 1:已发送 2:发送失败 3:已读',
send_time DATETIME,
read_time DATETIME,
retry_count TINYINT DEFAULT 0,
extra_data JSON COMMENT '扩展数据,比如错误信息',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user_status (user_id, status),
INDEX idx_send_time (send_time)
);
你可能会问:为什么要把 title 和 content 冗余存储,而不是直接关联模板表?
原因很简单:模板内容可能会变。假设你修改了某个模板的文案,那之前已经发送的通知记录,如果再去关联模板表,显示的就是新文案,这显然不合理。所以,发送通知时,把最终渲染好的标题和内容直接存到记录表里,这叫「快照」思想。
关键设计思路:
- 冗余存储:通知记录独立存储标题和内容,不受模板变更影响
- 状态机:status 字段记录通知的完整生命周期
- 索引策略:按用户和状态联合索引,方便查询「某个用户的未读通知」
- 重试机制:retry_count 记录发送失败后的重试次数
3.4 三张表的关系设计
这三张表的关系其实很简单:
- 用户表 和 通知记录表:一对多。一个用户可以有多条通知记录。
- 通知模板表 和 通知记录表:通过
template_code逻辑关联,但不是外键约束。为什么不用外键?因为模板可能会被删除或禁用,但历史通知记录不能受影响。
说白了,通知记录表是核心,它关联了「谁收到了什么通知」,而用户表和模板表都是它的「元数据来源」。
个人经验:我在设计时,不会在通知记录表里加外键约束。外键在写入时会有性能开销,而且对于这种日志型的数据表,我更倾向于在应用层做关联查询。你想想看,每天几百万条通知记录,每条都要检查外键约束,数据库压力会很大。
3.5 一个完整的例子
假设我们要发送一条「订单创建成功」的通知。流程是这样的:
- 从用户表查出用户的
notification_config,判断他是否允许接收这类通知 - 从模板表根据
template_code = 'ORDER_CREATED'获取模板内容 - 用实际数据(用户名、订单号)替换模板里的占位符
- 在通知记录表插入一条记录,status 设为 0(待发送)
- 异步任务消费这条记录,调用对应的渠道发送
嗯,这个流程看起来简单,但每一步都有坑。比如第二步,如果模板被禁用了怎么办?我一般会加一个兜底逻辑:如果模板不可用,就用系统默认文案。
3.6 小结
今天咱们把三张核心表的结构和关系理清楚了。用户表关注的是「谁」和「他的偏好」,模板表关注的是「说什么」,通知记录表关注的是「说了没、说了什么、结果如何」。
下一节,咱们会继续聊通知配置表和渠道配置表,以及如何用这些表支撑起一个灵活的消息通知系统。到时候我会分享一个我在项目中踩过的坑——关于渠道降级和熔断的设计,挺有意思的。