ThinkPHP系统消息推送应优先采用“数据库+定时轮询+Redis缓存”轻量方案,而非一上来就上WebSocket或Swoole;消息表需设to_user_id、type、status等字段并建联合索引(to_user_id, status, created_at),未读数用Redis原子计数+DB事务同步,实时场景可选SSE,仅双向强交互才考虑WebSocket/Swoole独立进程。

ThinkPHP 做系统消息推送,别一上来就搞 WebSocket 或 Swoole 长连接——90% 的内部通知场景(比如订单提醒、审核结果、系统公告)根本不需要那么重。直接用「数据库 + 定时轮询 + Redis 缓存」就能稳住,开发快、部署轻、运维无压力。
怎么设计消息表才不卡死查询
消息表不能只放 content 和 user_id,否则查未读数、分页加载、按类型筛选全得扫全表。常见翻车点是漏掉联合索引或 status 字段设计太窄。
-
to_user_id(必填,接收人)、from_user_id(可为空,系统消息设为 0) -
type用tinyint:1=系统通知,2=审批待办,3=评论回复;别用字符串,避免索引失效 -
status也用tinyint:0=未读,1=已读,2=已删除(软删),留出扩展空间,别用布尔值 - 必须建联合索引:
(to_user_id, status, created_at),否则用户打开消息页时SELECT * WHERE to_user_id = ? AND status = 0 ORDER BY created_at DESC LIMIT 20就会慢到卡住
未读数为什么总不准,怎么修复
前端每 5 秒调 /message/unread,后端如果每次执行 COUNT(*) 查库,1000 用户在线就等于每秒 200 次全表 COUNT,MySQL 直接抖。Redis 缓存又容易和 DB 不一致。
- 新消息插入时,在事务里先写 DB 成功,再执行
INCR redis_key:unread:{user_id} - 用户进消息列表页时,执行原子更新:
UPDATE message SET status = 1 WHERE to_user_id = ? AND status = 0,再查缓存或 DB 获取当前未读数(此时已清零) -
/message/unread接口优先读 Redis;若返回空或超时,fallback 到 DB 查询并回填缓存 - 绝对不要在接口里写
SELECT COUNT(*) FROM message WHERE ...——这是高并发下最典型的性能雷区
想实时一点,SSE 比 WebSocket 更合适
SSE(Server-Sent Events)不是“次选”,而是多数 ThinkPHP 系统消息推送的最优解:单向下行、协议简单、原生支持、不用额外进程、兼容性比 WebSocket 好得多。
立即学习“PHP免费学习笔记(深入)”;
- ThinkPHP6 可直接用
response()->stream()返回text/event-stream响应 - 关键格式必须严格:
data: {"msg":"审核通过"}\n\n(结尾两个换行,data:后带空格) - 每个连接需绑定用户身份(如从 Cookie 或 token 解析
user_id),避免混推 - 前端
EventSource要加自动重连逻辑:es.onopen = () => es.readyState === 0 && setTimeout(() => es.close(), 1000) - 别指望浏览器一直保持连接——移动端休眠、Nginx 代理超时、WiFi 切换都会断,SSE 天然支持重连,别自己造心跳
什么时候才该上 WebSocket / Swoole
只有当你需要「用户 A 发消息 → 用户 B 实时收到并能立即回复」这种双向强交互时,才值得引入 WebSocket。但注意:ThinkPHP 的 HTTP 生命周期根本不支持长连接,硬在控制器里 new Swoole\WebSocket\Server 必然失败。
- 必须用独立常驻进程:通过
php think swoole:server启动,或写单独脚本php start_ws.php - 业务逻辑不能写在
onMessage里直接查库再广播——会阻塞 Worker;要用协程 MySQL 客户端,或走 Redis pub/sub 中转 - 多 Worker 下
$server->connections是隔离的,跨进程广播必须用Redis或Swoole\Table维护在线用户映射 - 上线前务必检查:
php -m | grep swoole、端口是否被占、宝塔/云服务器安全组是否放行
真正难的从来不是写推送代码,而是想清楚「这条消息到底要不要实时」「用户离线了要不要补推」「已读状态怎么防重复标记」——这些逻辑一旦漏掉,前端再炫酷也没用。



















