ThinkPHP做在线客服必须用Workerman独立部署WebSocket服务,不可挂载HTTP路由;身份校验在onConnect阶段用Redis存会话状态;消息广播需分单聊、群聊、客服队列三类处理;AI与人工回复混合调度由后端统一控制。

ThinkPHP 做在线客服功能,不是套个模板就能跑通的——**WebSocket 连接必须独立于 HTTP 生命周期存在,否则消息会断、状态会丢、多轮对话直接失效**。用 thinkphp 的 controller 直接处理长连接请求,必踩坑。
WebSocket 服务不能挂载在 HTTP 路由里
常见错误是把聊天逻辑写在 ChatController@index 里,用 file_get_contents 或 cURL 模拟推送,结果用户一刷新页面就掉线、消息延迟几秒甚至丢失。
-
Workerman必须作为常驻进程单独启动,监听独立端口(如ws://yourdomain.com:2346),和 Nginx/Apache 的 HTTP 服务完全解耦 -
thinkphp的Route和MiddleWare对 WebSocket 请求无效——它们只处理 HTTP 请求,别试图用middleware('auth')拦 WebSocket 握手 - 真实身份校验得在 WebSocket
onConnect阶段做:解析前端传来的token或session_id,查think-model验证有效性,失败直接$connection->close()
用户会话状态不能依赖 PHP session
Workerman 不共享 Apache/Nginx 的 PHP-FPM 进程,$_SESSION 在 WebSocket 进程里是空的,且默认不支持 session_start()。
- 必须改用持久化存储:把用户 ID、当前会话 ID、最后活跃时间存到
redis,键名建议用ws:session:{user_id} - 不要在
onMessage里反复查数据库——每次消息都查user表,QPS 上不去;连接建立时查一次,缓存在$connection->user属性里 - 注意
redis连接是阻塞的,高并发下建议用predis+ 连接池,或直接上workerman-redis异步客户端
消息广播要区分“单聊”“群聊”“客服队列”三类场景
客服系统不是聊天室,不能所有消息都 foreach($connections) 推一遍,否则用户发一句,所有客服都弹窗,体验灾难。
- 单聊:消息带
to_user_id字段,只推给目标连接(从redis查该用户当前有效的$connection) - 客服队列:新访客接入时,按负载均衡策略选一个在线客服(比如取
redis中online:staff集合里zrangescore 最低的),再把会话 ID 写入该客服专属频道chat:staff_{id} - 离线消息:客服掉线期间的消息,不能丢,得落库(
chat_message表加is_delivered字段),上线后主动拉取
AI 回复与人工回复的混合调度必须在服务层控制
别让前端决定“该不该转人工”,那是业务规则,得收口到后端。
立即学习“PHP免费学习笔记(深入)”;
- AI 回复接口(如调
nlpengine::getanswer())应设超时(≤800ms),超时或返回空/置信度低,立刻触发人工兜底流程 - 连续 3 条用户消息未被 AI 明确回答(检测响应中是否含有效答案片段,而非只看 HTTP 状态码),自动标记为
need_human = 1并通知最近空闲客服 - 客服端收到转接请求后,需主动向用户连接发送
{"type":"switch","from":"ai","to":"staff_102"},前端据此切换 UI 状态,避免用户重复提问
thinkphp 的模型和路由,只要把连接态交给了 Workerman,你就已经脱离了框架的“舒适区”——这时候,redis 的原子操作、connection 对象的生命周期、断线重连时的上下文恢复,才是实际卡点。



















