
简介最新PHP客服系统源码是一套面向多商户环境的在线客服系统完整代码包适用于需要快速部署客服平台的企业、独立开发者以及学习PHP项目结构的进阶用户。资源共2000个文件包含549个JavaScript交互脚本、316个HTML页面、204个CSS样式、140个PHP后端逻辑文件以及SQL数据库结构、JSON配置、图片与GIF素材等压缩包整体约36.89MB目录划分清楚便于按需检索。已有188人学习/下载。包内提供安装说明文档详细覆盖环境准备、PHP安装、数据库导入与部署运行流程同时包含Composer依赖管理、.htaccess多商户域名重写规则、version/domain等配置可准确区分不同商户请求。前端资源基于bootstrap、amazeui等框架构建配合后端实现用户、聊天记录、商户信息等核心数据管理可作为企业级客服系统开发参考与二次扩展基础。1. 新拿到的多商户 PHP 在线客服系统源码先分清“跑通”和“能用”做客服系统相关开发的朋友手头多半会存几套 PHP 客服系统源码。客服系统这几年的需求变化很明显网页弹窗已经不够用小程序、App、公众号里的在线会话都要收到同一个后台处理而多商户客服系统又把问题推进一步——一个平台要同时服务几十上百个商户每个商户只能看到自己名下的对话不能越权翻别人家。标题里的“多商户客服 附教程”外行人以为难点在聊天窗口怎么弹出来实际拆过源码才知道真正的门槛在租户隔离、客服分配、长连接稳定性这三件事。这篇把我拆这类源码的完整步骤写一遍适合给客户交付客服产品、要在源码基础上做二次开发的场景也适合刚开始接触多商户 PHP 项目想有个整体认知的朋友。2. 在线客服系统的核心架构选型与请求链路先把架构立住后面改代码才不会翻车。多数人拿到源码第一反应是解压丢进 nginx 然后看登录页长什么样我一般会先花半小时确认这套源码的连接方案和表结构方向错了后面全是白干。2.1 长连接网关 vs 传统轮询选型直接决定成本和二次开发量在线客服系统和普通 CMS 最大的差别是“实时”。用户发一句话客服要在 1 秒内看到传统做法是前端每 2 到 3 秒调一次“拉取新消息”接口消息多了请求量爆炸客服端还会频繁看到 loading。另一个做法是让客户端和服务器保持一条长连接由服务器主动把消息推给客服端。常见做法是PHP 业务代码跑在 nginx/php-fpm 上另起一个常驻内存的 gateway 进程负责维护长连接。这个 gateway 用 Workerman 或 Swoole 实现不依赖 php-fpm 的进程生命周期。判断一套客服源码是不是“正经在线客服系统”就看它有没有这个独立 gateway只有 PHP-FPM 轮询接口的只能叫留言板硬拿来做多商户客服客服数量一上来数据库和带宽先撑不住。对比项PHP-FPM 轮询方案Workerman/Swoole 长连接方案消息实时性延迟取决于轮询间隔通常 2-5 秒毫秒级推送服务器压力连接数 x 轮询频率几千在线就可能打满数据库连接连接挂在内存里CPU 占用与消息量相关开发部署难度低普通虚拟主机能跑需要 CLI 常驻进程要求 shell 权限二次开发切入点接口轮询、消息表事件回调、队列消费适合场景内部工具、日会话量小于几百的平台真正的多商户 SaaS、电商平台、多渠道聚合我一般拿到源码先看 vendor 目录里有没有 workerman 或 swoole没有就去根目录找有没有类似start.php、bin/server.php这种常驻入口文件。标题里既然写“在线客服系统源码”交付包里基本都带了 gateway但版本新旧差异很大老项目还在用 PHP 5.6 的也多这个在第 3 章会系统说。2.2 多商户数据模型先看表结构再动手省得后面返工多商户和单商户在代码层面最大的区别是核心表里有没有一个贯穿始终的商户标识。我拆过的客服源码里设计得规整的表结构基本长这样表名关键字段多商户要点merchantid, name, status商户主表一套源码支持 N 个商户独立运营agentid, merchant_id, account, name, max_sessions客服账号归属某个商户跨商户看不到别人客服visitorid, merchant_id, openid, nickname访客按商户维度冗余存储同一访客在不同商户下是两条记录conversationid, merchant_id, visitor_id, agent_id, status会话主表多商户租户隔离的核心表messageid, conversation_id, merchant_id, from_type, from_id, content消息明细顺着会话归档也要带 merchant_id 便于排查为什么 visitor 要按商户冗余而不做一张全局访客表因为同一访客在 A 商户聊过再去 B 商户咨询A 商户的客服没有权限看到 B 商户的会话记录。如果访客表全局唯一就得在会话层再设一次权限判断多一层关联多一个出漏洞的口子。按商户冗余看似重复存数据实际是把权限问题提前消化了。索引设计也很重要会话多了之后最容易慢的查询就是“查某个商户下待处理的会话”。我一般会确认源码包里有没有下面这两个索引没有就自己补上ALTER TABLE conversation ADD INDEX idx_tenant_status (merchant_id, status, updated_at); ALTER TABLE message ADD INDEX idx_conversation_time (conversation_id, created_at);第一个索引支撑客服工作台“待接会话列表”第二个索引支撑聊天记录滚动加载。加了之后查询从全表扫描变成索引范围扫描几百条数据感觉不明显上到十万级会话差异就是 50 毫秒和 1 秒的区别。2.3 一条消息从访客到客服的完整路径搞清链路才知道出了问题该看哪块日志。我这里按常见做法把整条路径捋一遍访客端浏览器打开网页嵌入的 JS SDK 调后端接口拿一个带 token 的连接地址然后和 gateway 建立 WebSocket 长连接访客输入一句话JS 通过连接把消息发给 gatewaygateway 收到后不直接落库而是把消息事件转给业务进程业务进程写 MySQL、推 Redis 队列最后客服端通过自己的长连接收到新消息提醒。这个链路里有两类进程在跑一类是 nginx php-fpm 处理 HTTP 接口一类是 gateway 长连接进程另外还可能有消费 Redis 队列的任务进程。部署时最容易犯的错是只启动了 php-fpm没启动 gateway结果前台聊天窗口能打开一发消息就失败或者一直转圈。带教程的源码包一般会在教程文档第 2 到第 3 页写明启动 gateway 的命令通常长这样# 常见做法是 php 直接加载一个常驻入口 # 具体文件名看源码包workerman 项目常见 start.phpswoole 常见 bin/server.php php think chat:gateway start # 如果已经装了 supervisor可以用 stop/restart 参数管理进程这条命令说明一个关键概念gateway 进程和 Web 进程是独立生命周期的即使 nginx 挂了已经建立的长连接理论上还能保持但新连接建立不了反过来 gateway 挂了前端能打开页面消息也发不出去。所以排查问题时先确认两端进程都在再看中间的消息队列。这也是我反复跟做二次开发的人强调的别一上来就改聊天 UI先把这条链路用最小环境跑通后面改任何东西都有个对照基准。3. 把多商户客服系统在本地完整跑起来从环境检查到登录第一个商户后台到这一步开始动真格。这里说的“本地”泛指你能拿到 shell 权限的 Linux 开发机或者便宜的云主机纯 Windows 桌面环境跑常驻进程会比较折腾建议直接用虚拟机或容器。标题里写着“附教程”但教程文档写得糊弄的源码包我见过很多所以下面的步骤我尽量按不依赖具体文档的方式来写。3.1 环境清单先对照这张表检查缺什么补什么多商户在线客服系统对运行环境的要求比普通 PHP 网站高主要是多了扩展、进程管理和队列这三块。我拿到源码包会先把composer.json或requirements.txt翻出来确认项目要求的 PHP 版本再按下面的清单逐项检查检查项推荐配置缺了会怎样PHP 版本7.4 或 8.0 以上具体看源码声明老代码在 PHP 8 下常有兼容性报错新代码在 PHP 7 下跑不起来PHP 扩展pdo_mysql, redis, pcntl, posix, swoole/event缺 redis 扩展消息队列直接没法用缺 pcntlworkerman 起不来MySQL5.7 以上utf8mb4 字符集表情消息写入报错会话内容乱码Redis5.0 以上不需要集群离线消息、在线状态、消息推送队列都依赖它服务器2 核 4G 以上Linux 系统连接数一上来内存不够常驻进程被 oom killer 杀掉如果你还没装 MySQL 和 Redis可以先去搜一份安装配置教程把这两个基础服务拉起来版本尽量新一点。这里特别提醒 PHP 扩展别漏掉 redis 和 pcntl。很多人用php -v看到版本没问题就以为万事大吉结果php -m一查才发现扩展没装到后面还得回头补浪费时间。3.2 初始化项目解压、装依赖、配环境、建库假设源码包已经传到服务器并解压到/data/www/chat。我习惯先看一眼目录里有没有.env.example这是 Laravel 系项目的标配如果是 ThinkPHP 系配置一般在.env或config/database.php里。下面以最常见的 Laravel 结构为例命令基本通用cd /data/www/chat # 1. 复制环境配置文件正式环境不要直接改 .env.example cp .env.example .env # 2. 安装依赖包这一步会读取 composer.json # 如果服务器没连上网需要先配好 composer 国内镜像 composer install --no-dev --optimize-autoloader # 3. 生成应用密钥Laravel 系必须执行否则登录和 session 都会异常 php artisan key:generate每一步都有关键意义。composer install --no-dev跳过开发环境依赖减少体积也避免把调试工具带上生产--optimize-autoloader生成优化后的自动加载映射类文件多了之后提升明显的。我见过有人跳过了key:generate结果后台登录后 session 一直失效折腾了半天才发现是 APP_KEY 为空。然后改.env里的数据库连接和 Redis 配置DB_HOST127.0.0.1 DB_PORT3306 DB_DATABASEchat_multi_merchant DB_USERNAMEchat_admin DB_PASSWORD你的密码 REDIS_HOST127.0.0.1 REDIS_PORT6379 REDIS_PASSWORD数据库要先建好字符集选 utf8mb4排序规则选 utf8mb4_unicode_ci。建库语句和执行迁移命令是连着的两个步骤# 进入 mysql 客户端执行PHP 代码里拼接 SQL 也建议统一 utf8mb4 CREATE DATABASE chat_multi_merchant DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; # 回到终端执行数据迁移和初始数据填充 php artisan migrate --seedmigrate会把设计好的表结构建出来--seed填充初始数据里面通常包含一个总后台管理员账号和一个演示商户账密在源码教程里会写明。执行完看到 “Database seed completed successfully” 之类的提示基本可以进入下一步。3.3 启动长连接 gateway 和队列消费进程这是和普通 PHP 项目差异最大的一步。先看看有没有进程管理脚本或者 supervisor 配置。常见做法是项目自带的命令# 启动消息推送队列消费进程这个进程负责“把落库消息推给客服端”不能省 php artisan queue:work --queuechat_push --daemon --tries3 # 启动长连接网关常见 workerman 风格的入口 php think chat:gateway start # 用 ps 确认两个进程都在 ps aux | grep -E queue:work|chat:gateway逻辑说明queue:work消费 Redis 队列里的消息事件--queuechat_push指定消费的队列名这个名字要和代码里入队时写的队列名一致否则消息会堆积在 Redis 里没人管典型表现是“客服端收不到新消息提醒但刷新页面能看到新消息”。--tries3表示消费失败最多重试三次避免偶发网络抖动把错误消息叼住不放。gateway 进程如果命令行带start参数通常会进入前台阻塞模式终端关了就断所以生产环境要么用 nohup 挂着要么配 supervisor 托管。本地测试阶段直接在终端前台跑就好CtrlC 能停方便看日志。3.4 Nginx 转发配置HTTP 业务与 WebSocket 握手分开处理Nginx 配置有两个关键点一是 PHP 请求照常转发给 php-fpm二是/ws这个路径要转发给 gateway 端口并且必须带上Upgrade和Connection两个头否则浏览器建立 WebSocket 握手时会被 Nginx 挡掉客服工作台一直显示“连接中”。server { listen 80; server_name your-domain.com; root /data/www/chat/public; index index.php; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { include fastcgi_params; fastcgi_pass 127.0.0.1:9000; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } # 长连接网关单独转发注意这里不是 PHP-FPM location /ws { proxy_pass http://127.0.0.1:8282; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_read_timeout 3600s; proxy_send_timeout 3600s; } }proxy_read_timeout 3600s是必须的长连接场景下 Nginx 默认 60 秒没数据就掐断连接客服端就会周期性掉线。有些源码包用的连接路径不是/ws具体看前端 JS 里new WebSocket(ws:// host /ws)这段代码和这里配的 location 对上就行。3.5 登录商户后台验证多商户场景第一步打开http://your-domain.com/admin用 seed 出来的商户管理员账号登录。正常会进到商户专属后台左侧菜单有会话列表、客服管理、访客管理。这个时候去 MySQL 里看一眼agent表的数据能确认你当前账号的merchant_id是多少。然后——重要——开一个浏览器无痕窗口用另一个商户的账号登录两个后台的会话列表、客服列表应该是完全隔离的。这一步验证的是最基本的多商户隔离逻辑如果发现 A 商户后台能看到 B 商户的会话数据那这套源码的权限设计基本可以放弃省得后面填不完的坑。提示第一次部署别急着改端口和加功能先把原样跑通到“两个商户能各自登录、互不可见”。源码教程文档我一般只看三处环境要求、初始化命令、gateway 启动方式。三处对不上说明文档和源码版本可能不一致后面每做一步都得多留个心眼。4. 多商户改造实操数据隔离、客服分配与消息推送的最小实现很多人找这套源码不只是要部署而是要二开把默认的单商户界面改成自己的多商户平台或者把原有的业务系统接进来。这章我按最常见的落地路径讲怎么在代码层确保商户数据隔离怎么做客服自动分配怎么把消息可靠地推给在线客服。所有代码都是可直接套用的风格请结合你自己的源码目录调整命名空间。4.1 用中间件把 merchant_id 挂到每个请求上杜绝漏过滤多商户系统最常见的越权漏洞是列表查询忘了按商户过滤。好比一个列表接口写成了SELECT * FROM conversation WHERE status 0忘记加merchant_id那 A 商户的客服只要知道接口地址就能看到全部商户的待处理会话。我一般会在所有商户后台接口前挂一个中间件把登录账号所属的merchant_id解析出来绑定到当前请求上// app/Http/Middleware/MerchantScope.php ?php namespace App\Http\Middleware; use Closure; class MerchantScope { public function handle($request, Closure $next) { $user $request-user(); // 多商户系统里每个后台账号必须挂 merchant_id // 拿不到就主动拒绝而不是当作超级管理员放行 $merchantId $user-merchant_id ?? null; if ($merchantId null) { return response()-json([code 401, msg 登录态异常或非商户账号], 401); } $request-attributes-set(merchant_id, $merchantId); return $next($request); } }这段代码解决了两个问题。第一是规范来源所有需要商户身份的接口都经过这个中间件拿统一的merchant_id不需要每个控制器里重复$user-merchant_id这种写法第二是兜底商户账号数据异常时直接返回 401而不是继续执行后续代码。路由注册时把这个中间件包在商户后台路由组外面新增接口时就不会因为忘了过滤而留个洞。4.2 客服自动分配空闲优先的查询策略与排队兜底多商户场景下每个商户有多个客服新会话进来要自动派给一个客服。常见的分配策略有三种轮流分配、按客服当前会话数分配、按客服最后回复时间分配。我实际用得最多的是“空闲优先 最后回复时间兜底”先排除离线和满负载的客服再把当前会话数最少的客服排在前面会话数一样就看谁更久没被派单// app/Services/AgentDispatcher.php 核心片段 use Illuminate\Support\Facades\DB; public function assignToAgent(int $merchantId) { $agent DB::table(agent) -where(merchant_id, $merchantId) -where(status, 1) // 账号启用 -where(online, 1) // 客服在线 -where(current_sessions, , max_sessions) -orderBy(current_sessions, asc) // 手头会话最少的优先接 -orderBy(last_reply_at, asc) // 更久没回复过的优先接 -first(); if ($agent null) { // 没有可用客服走排队逻辑给访客一个排队提示 return [code 1, msg 当前没有空闲客服请稍等]; } return [code 0, data $agent]; }这个查询是典型的“按条件过滤后取最空闲”。current_sessions max_sessions用到了这两个字段的对比如果客服一天都挂着不动这个条件也能撑住。要注意online字段的维护逻辑客服退出工作台时前端要通知后端把这个字段置 0否则客服人已经走了系统还在给他派单访客就会对着一个不会回复的会话干等。这块在切换标签页时容易忽略后面第 5 章会说踩坑细节。4.3 消息落库与推送写 MySQL 和推 Redis 两步走客服系统不能只把消息推到前端就完事必须落库作为历史记录和“客服下班后回来还能看”的保障。常见做法是收到消息先写 MySQL拿到消息 ID 后再把推送事件塞进 Redis 队列由队列消费进程推给客服端。这个顺序有讲究先落库后推送如果推送环节挂了消息还在库里客服刷新页面能找回来反过来先推送后落库推送成功了但写库失败消息就丢了。// app/Http/Controllers/ChatController.php 发消息片段 use Illuminate\Support\Facades\Redis; public function send(Request $request, $conversationId) { $merchantId $request-attributes-get(merchant_id); $content trim($request-input(content)); // 1. 落库拿到自增的消息 ID $messageId DB::table(message)-insertGetId([ merchant_id $merchantId, conversation_id $conversationId, from_type visitor, from_id $request-input(visitor_id), content $content, message_type text, created_at date(Y-m-d H:i:s), ]); // 2. 把推送事件写入 Redis 队列消费进程会取出来推给客服端 Redis::rpush(chat:push:queue, json_encode([ message_id $messageId, merchant_id $merchantId, conversation_id $conversationId, content $content, ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); return response()-json([code 0, message_id $messageId]); }这里的JSON_UNESCAPED_UNICODE很关键不加的话中文会被转成\uXXXX形式虽然前端能解码但进了 Redis 队列后想用redis-cli LINDEX排查问题时就只能看到一堆转义字符没法人肉读。rpush是右侧写入消费端blpop是左侧取出正好是先进先出的队列语义。队列里放了三个核心标识——消息 ID、商户 ID、会话 ID消费进程拿到后就能定位到具体会话再通过 gateway 连接表找到对应客服的长连接把消息推过去。4.4 商户后台与总后台权限边界两个维度别混在一个表里多商户源码经常被人改坏的地方是总后台和商户后台的关系。我见过有人图省事给用户表加一个is_super字段总后台和商户后台用同一套登录接口、同一个菜单文件结果总后台账号登录后左侧菜单既有商户功能又有平台功能点着点着就乱套了。正确的边界设计是两套入口、两套鉴权。总后台管理商户、管理整个平台的会话监控、查看系统运行状态商户后台只管自己名下的会话、客服、访客。同一个用户表可以但中间件要区分防抖总后台路由挂在admin前缀下校验的是user.is_super商户后台路由挂在merchant前缀下校验的是user.merchant_id。如果源码包默认只提供了一套后台二次开发时第一件事就是把这两个入口拆出来而不是猜。实践中我会用两个独立的配置文件存放菜单权限总后台菜单按模块聚合商户后台菜单按商户维度聚合。前端再根据登录接口返回的role_type切换到对应路由表这样新增功能模块时就不会出现“功能做出来了但不知道该放哪个后台”的尴尬。5. 部署多商户客服源码的五个翻车点从连接掉线到数据越权这章是血泪经验汇总。在线客服系统比普通 CRUD 项目容易翻车核心原因是“实时”二字。下面五条是我在拆不同客服源码时实际踩过、或者看着别人踩过的坑按现象到原因到解决写清楚。5.1 客服工作台频繁“连接中”,几分钟掉一次现象是客服端页面能打开但右上角状态一直在“连接中”和“已连接”之间反复横跳访客发消息经常要重连之后才刷出来。原因大概率是两个一是 Nginx 的proxy_read_timeout默认 60 秒长连接超过 60 秒没有客户端和服务器之间的数据往来Nginx 主动断掉第二个是 gateway 进程空闲连接回收机制太激进把超过 30 秒没消息的连接标记为僵尸连接。解决方法是把 Nginx 超时调大然后在 gateway 的配置里把心跳检测间隔调小。常见做法是客户端每 30 秒发一个心跳包服务器收到心跳就刷新连接最后活跃时间。具体参数在 gateway 的配置类或常量文件里搜ping_interval和timeout就能找到。调完之后观察 10 分钟不断线基本就稳了。5.2 访客消息发出去一直转圈但数据库里能查到现象是访客端点击发送后前端一直 loading客服端什么都没有去 MySQL 查message表数据其实已经写进去了。原因是消息走的路径分两条访客发送时先走 HTTP 接口落库再走 WebSocket 推给客服端。数据库有数据说明 HTTP 接口没问题客服端收不到说明 WebSocket 链路断了最常见的断点是 gateway 进程没启动或者队列消费进程没起来。解决方法是先看进程列表queue:work和 gateway 都在吗不在就启动都在就看 Redis 队列LLEN chat:push:queue是不是大于 0数据堆积说明消费进程有问题打开日志看具体报错。这一条排查链路基本覆盖九成“数据在但推不动”的场景。5.3 两个商户的会话串了现象是 A 商户的客服在会话列表里看到了 B 商户的访客点进去还能回复。原因很直接查询语句漏了merchant_id条件。这种情况在源码包给的原生 SQL 里最多因为作者写 demo 时没有考虑多商户隔离只在页面层做了按钮隐藏接口层没有做数据过滤。解决方法是全局搜代码里所有conversation表的查询看where条件里有没有带merchant_id。不要一个个文件点开看直接在项目根目录搜from conversation和-where(status这类特征串然后逐个补条件。补完用第 3 章 3.5 说的双商户登录方式把所有主要列表页过一遍确认串数据问题清零。5.4 新会话进来没有自动派给客服现象是访客发起了会话conversation表里agent_id是空的前台列表一直显示“待分配”客服手动刷新后才有。原因通常是分配逻辑没有在会话创建时触发而是依赖定时任务或多商户事件绑定漏配。很多源码把“分配客服”写在 controller 里人工调用没有在conversation创建事件里挂上监听。解决方法是找到会话创建的地方比如ConversationService::create()在创建完成后立即调用AgentDispatcher::assignToAgent($merchantId)把返回的客服 ID 更新到会话记录上。如果需要轮询兜底再配一个每 5 分钟跑一次的定时任务把超过 5 分钟还无人处理的会话重新分配一次。5.5 客服下班后消息没人管,离线消息第二天全没了现象是客服晚上退出登录访客发的消息当时没有提醒第二天客服登录后会话列表里翻不到昨晚的消息。原因是离线消息只存在于 Redis 或者内存里进程一重启就清空了没有把无人会话转成“留言”状态并持久化。解决方法是确认源码里有没有离线消息的落库机制。正确的做法是当 gateway 检测到某个会话对应的客服不在线时把这条消息标记为离线状态同时把会话置为“未读留言”客服登录后拉取的是status 未读的消息列表。如果源码没有这个机制二次开发时要在消息落库那一步加一个判断查agent表的online字段如果客服不在线就把消息的is_offline字段置 1。这个改造不复杂但直接影响买家满意度值得优先做。6. 拿这套客服源码做二次开发前先花一晚上做这三项验收源码能不能用不是看它演示页多漂亮而是用三个手段验收。我拿任何一套多商户客服源码都会先单独抽一晚上做这些测试过了才往业务里接。第一项租户隔离验收。创建两个商户 A 和 B各配一个客服账号。分别登录两个后台把 A 商户的会话列表、访客列表、统计报表截图再登 B 商户后台逐一对比。重点不是“看起来不一样”而是用开发者工具看接口返回的数据里A 的返回有没有包含 B 的merchant_id。这一步能直接揪出最危险的越权漏洞。第二项长连接压力小测。准备一个脚本模拟 50 个访客连接 gateway,每个连接发 20 条消息,然后观察 gateway 进程的内存和 CPU 状态。靠谱的源码在这轮测试中内存是平稳上升或小幅波动的如果内存一路翻倍且不回收说明有连接泄漏跑不了几天进程就得重启。测试完记得用redis-cli DBSIZE看队列里有没有堆积消息堆积超过一百条就要回头查消费进程。第三项故障恢复验证。把 Redis 或 gateway 进程手动 kill 掉观察客户端是什么表现是自动重连并无感恢复还是用户要刷新页面才能继续恢复后历史消息能不能正常拉取在线客服系统最怕的是“服务重启后客服端悄无声息断开用户以为自己还在连线”。我踩过最深的一次就是 gateway 重启后前端没有重连机制客服看着工作台发呆以为没消息其实客户已经等急了。这三项过完你才对这套源码的底细有了数。我自己的习惯是任何时候拿到源码都不急着看聊天窗口的样式先查conversation表有没有merchant_id、再查 gateway 入口文件存不存在这两个点决定了整套方案的底线在哪里。希望这套方法能让你少走点弯路也祝你顺利把它改造成能上线的多商户客服平台。本文还有配套的精品资源点击获取