站内信表结构需设sender_id、receiver_id、subject、body、is_read、deleted_by_sender、deleted_by_receiver字段,禁用软删除,采用双向逻辑删除与复合索引优化查询。

怎么设计站内信的数据表结构
站内信不是简单存个“标题+内容”就行,核心在于区分发送方、接收方、状态和类型。用 notifications 表会踩坑——它默认只服务系统通知,不支持用户间私信;硬塞进去会导致查询慢、逻辑混乱。
-
messages表必须包含:sender_id、receiver_id、subject、body、is_read、deleted_by_sender、deleted_by_receiver - 别用软删除(
SoftDeletes)直接套在消息表上——收件人删了但发件人还想查记录?得双向逻辑删除 - 加复合索引:
INDEX(receiver_id, is_read, deleted_by_receiver),否则收件箱列表一查就卡 - 如果要支持群聊或一对多广播,先别急着加
message_recipients中间表——90% 的站内信场景是 1 对 1,过早抽象反而让unreadcount()变慢
laravel 怎么发站内信(不用第三方包)
别碰 Notification 类发私信——它没内置 receiver_id 字段,强行改会破坏 Laravel 的通知通道契约。直接用 Eloquent 插入更稳、更可控。
- 封装一个
Message::send($from, $to, $subject, $body)静态方法,内部做参数校验(比如$from->id !== $to->id) - 别在控制器里直接 new Model——把创建逻辑收进模型的
create()或自定义静态方法里,方便后续加事务、事件或限流 - 如果要异步发(比如批量通知),用
dispatch(new SendprivateMessage($message)),但注意:队列任务里不能依赖 request 或 session - 示例插入语句:
Message::create([ 'sender_id' => $user->id, 'receiver_id' => $target->id, 'subject' => '你好', 'body' => '这是一条测试消息', 'is_read' => false ]);
查未读数和分页列表为什么总出错
最常见的是用 count() + paginate() 连用导致 sql 报错,或者漏掉软删除/双向删除判断,结果用户看到“3 条未读”,点进去只有 1 条。
- 查未读数必须加双重过滤:
Message::where('receiver_id', $user->id) ->where('is_read', false) ->where('deleted_by_receiver', false) ->count(); - 分页查收件箱时,
orderBy('created_at', 'desc')必须放在paginate()前,否则排序失效 - 别用
withTrashed()查站内信——已逻辑删除的消息不该出现在列表里,哪怕只是“隐藏”状态 - 如果用户自己删了某条,但对方还没删,这条消息对 sender 仍应可查(只要没被 sender 自己删),所以查询条件永远以当前用户角色为基准
websockets 实时提醒怎么做才不崩
用 Laravel echo + Pusher 是最快路子,但本地开发用 redis + laravel-websockets 更轻量。关键不是连上 WebSocket,而是消息到达后前端怎么精准更新未读数,而不是整个刷新页面。
- 后端发消息后,立刻用
Event(new MessageSent($message))触发事件,事件里用broadcastOn()指定频道:private-user.{$message->receiver_id} - 前端监听频道时,别用全局
listenToAllChannels()——性能差,且容易收到无关消息 - 广播数据里至少带
message.id和message.is_read,前端收到后用document.getElementById('unread-count').textContent = +count + 1局部更新 - 别在 WebSocket 连接里重复拉未读数——连接建立后,服务端应主动推送当前未读总数,避免竞态
实际最难的不是发和收,是“已读回执”和“撤回”这种边缘操作——它们会让数据库查询条件指数级变复杂,上线前务必用真实数据压测 500+ 条未读时的列表加载速度。