ThinkPHP站内信与消息推送应分场景选路径:TP6用队列+DB+轮询,TP8宜选SSE或Workerman;结构需轻量,核心字段含接收者、发送者、类型、标题、内容、跳转链接及已读状态;扩展推荐think-notification统一多渠道。

ThinkPHP 实现站内信与消息推送,关键不在“怎么写代码”,而在于分清场景、选对路径、避开框架限制。TP6 和 TP8 本质不同:TP6 有成熟队列支持,适合异步写库+轮询;TP8 虽无 Channel 抽象,但可结合 SSE 或 Workerman 做轻量实时;强行套用 Laravel 风格的广播模型,大概率失败。
站内信基础结构要轻量且可扩展
消息不是日志,字段必须克制。核心字段建议如下:
- to_user_id:接收者 ID(单人即可,批量可后续改数组)
- from_user_id:发送者 ID(0 表示系统)
- type:类型标识(如 'system'、'comment'、'like'),前端靠它做分类图标和跳转逻辑
- title 和 content:标题≤50字,正文支持纯文本或简单 Markdown(渲染由前端控制)
- link:跳转路径(如 /order/123),空字符串表示不跳转
- is_read:默认 0,查收后调接口更新为 1(避免页面刷新时重复标记)
TP6 推荐方案:队列 + 数据库 + 前端轮询
这是最稳、最易落地的组合,适用于 90% 的通知类场景(如审核通过、评论提醒、订单状态变更)。
- 用 think-queue 投递 SendNoticeJob,数据结构即上文定义的数组
- Job 的 handle() 方法只做一件事:写入 notice 表,并用 Redis INCR 更新未读数 unread_count:{uid}
- 前端每 30 秒调一次 /api/message/unread,优先查 Redis,超时再 fallback 到 DB(绝不用 COUNT(*))
- 用户打开消息页前,先发请求清空缓存并批量更新 DB 中 status=0 的记录,保证已读状态同步
TP8 想做实时?别碰“Channel”,盯紧 SSE 和 Workerman
TP8 官方没有广播层,所谓 “Channel” 多是开发者自己用 Redis key 模拟的命名规则,不可靠。真要实时,两个务实选择:
立即学习“PHP免费学习笔记(深入)”;
- SSE(Server-Sent Events):适合单向提醒(如审批完成、支付成功)。用 response()->stream(),必须设响应头:Content-Type: text/event-stream、Cache-Control: no-cache、X-Accel-Buffering: no(Nginx 关键)
- Workerman 独立进程:适合双向交互(如客服聊天、多端状态同步)。ThinkPHP 只管业务逻辑和数据库,WebSocket 连接、心跳、广播全部交给 Workerman 处理;两者通过 Redis 共享用户在线状态和消息队列
扩展性增强:用 think-notification 统一通知入口
如果项目还需发短信、邮件、APP 推送,推荐引入 yzh52521/think-notification 扩展。它把站内信只是其中一个 channel:
- 在通知类中定义 toDatabase() 返回站内信结构,toMail() 返回邮件内容
- 模型使用 Notifiable trait,调用 $user->notify(new OrderShipped($order)) 即可自动分发到所有启用的渠道
- 需要立即发送(跳过队列)时,用 sendNow();需延迟,直接实现 ShouldQueue 接口



















