☰
PHP+Workerman开源聚合客服系统源码部署与二次开发
2026/10/26 17:45:22 网站建设 项目流程

简介:聚合客服(万能客服)cy163_customerservice 22.0.0 全开源版安装更新一体包面向企业客服团队、站点运营者与二次开发者,提供集多渠道接入、工单流转、知识库、智能机器人、数据报表、API 对接和权限控制于一体的客服系统方案。压缩包共 269 个文件,约 2.93MB,以 html 页面模板、php 后端逻辑、js 交互脚本、css 样式表为主,并配有 png、gif 图标与字体资源,能够同时支撑前端界面展示和后台业务处理。全开源特性允许查看、修改和分发源码,便于在现有功能上做界面调整和流程扩展;安装更新一体包也降低了部署与版本维护门槛,适合缺少专职运维的小型团队,已有 1061 人学习。对需要搭建客服工作台、研究客服系统模块划分或做业务定制的读者来说,可从中了解多渠道会话组织、工单与知识库的配合方式,以及权限和接口层的设计思路,并复用其页面样式与图标资源进行二次开发。

1. 五个后台、五个账号、一天在浏览器标签页之间切上百次——这是我第一次接手那个电商代运营团队客服现场时看到的样子,网页、公众号、小程序、抖音、企业微信的咨询全散在各自后台,漏回和重复回轮着来。

这套聚合客服(万能客服)22.0.0 全开源版就是冲这个场景做的:把多个入口的会话收进同一张工作台,访客在哪个渠道发消息,坐席都在同一个界面里回。它把渠道接入、会话分配、坐席工作台、WebSocket 长连接、数据统计拆成了各自独立的模块,装完能接着改成自己公司的客服中台,不用从零搭一套。适合两类人:一类是要自建客服、不想按坐席数按年付费的团队;另一类是接私活时需要一套能跑通、能二次开发的完整底子。下面按我实际部署和改代码的顺序,把这套源码从技术栈、目录结构、安装、核心链路一路拆到二次开发。

2. 技术栈与目录结构:PHP + MySQL + Workerman 是怎么拼起来的

这套源码是典型的 PHP 单体应用加一个独立的长连接进程,别指望用 PHP-FPM 硬扛 WebSocket——FPM 的生命周期是一个请求一个进程,连不住。所以后端接口走 Nginx + PHP-FPM,实时消息走 Workerman 的 GatewayWorker 独立进程,两边靠一个注册中心进程通信。这个拆分决定了你后面所有的部署动作:Web 站点要配伪静态,长连接要单独起进程、单独放行端口、单独做进程守护。

2.1 运行环境与依赖清单

先说环境,这套代码对版本比较挑,版本选错会卡在各种莫名其妙的地方。我在两台机器上装过,PHP 8.1 能跑起来但日志里会刷满动态属性的弃用告警,PHP 7.4 相对干净。

组件建议版本在这套系统里的角色备注
PHP7.4 / 8.0后台接口、坐席工作台接口、渠道回调8.1 有兼容告警,老代码会刷日志
MySQL5.7 / 8.0会话、消息、坐席、渠道配置字符集必须 utf8mb4
Redis6.x在线状态、接待计数、未读序号、排队队列要开持久化,否则重启丢在线状态
Workerman / GatewayWorker4.xWebSocket 长连接、消息推送依赖 pcntl / posix / sockets
Nginx1.20+站点入口、静态资源、ws 转发转发要带 Upgrade 头
Supervisor任意守护 gateway 进程用 nohup 迟早翻车

装之前先把扩展点一遍,这一步省不得。Web 能打开、长连接起不来,十有八九就是扩展没齐。

# 逐项确认,缺一个都可能卡在启动或收消息 php -v php -m | grep -Ei 'pdo_mysql|redis|fileinfo|openssl|pcntl|posix|sockets|mbstring'

pcntl、posix、sockets是 Workerman 的硬依赖,PHP-FPM 完全不需要它们,所以很多人装完之后站点一切正常,php start.php start却直接报错退出。fileinfo是上传图片、文件消息用的,缺了会出现「发图没反应但接口返回 200」这种玄学现象,日志里什么都看不到,只能靠php -m挨个对。redis扩展注意版本,php-redis 5.x 和 4.x 在超时参数上的行为不一样,我一般直接把版本钉死在安装脚本里,别让服务器自己升级。

2.2 目录结构与各目录职责

解压之后先别急着开浏览器,花五分钟把目录看一遍,后面改代码能省掉大量搜索时间。这套工程的分层比较清爽,业务代码集中在app/,长连接单独在gateway/,安装和更新各占一个目录。

cs_customerservice/ ├── app/ │ ├── controller/ # 后台接口 + 坐席工作台接口 │ ├── service/ # 业务服务层:会话分配、消息分发、统计 │ ├── model/ # 数据模型,对应 cs_ 前缀的表 │ ├── channel/ # 渠道接入,一个渠道一个适配器类 │ └── common/ # 常量、工具类、异常处理 ├── config/ # 数据库、Redis、渠道密钥配置 ├── extend/ # 第三方 SDK(对象存储、短信等) ├── gateway/ # Workerman 长连接服务,四个启动文件 ├── public/ # 站点入口 index.php + 工作台静态资源 ├── runtime/ # 日志、缓存、编译产物,必须可写 ├── install/ # 安装向导,装完必须处理掉 └── update/ # 更新包目录,放补丁 zip

真正需要动手改的地方就三个。app/channel/是接新渠道的入口,一个渠道一个类,实现同一套接口,加渠道不用动别的地方;app/service/里放着会话分配和消息分发,是整套系统的中枢,改业务逻辑基本都在这;gateway/下面四个启动文件分别对应 Gateway、Register、BusinessWorker 和启动脚本,端口和进程数都在这里配。

runtime/目录是血泪经验的重灾区:权限给不够,页面白屏;权限给太宽,日志文件谁都能读。我一般设成www用户可写、其他用户只读,并且用 Nginx 把整个目录 deny 掉。

2.3 三张核心表:会话、消息、坐席

整套系统的数据模型不复杂,但三张表的设计思路值得看一眼,因为它决定了你后面查询会不会卡。会话表一行代表一次咨询,消息表按会话聚合,坐席表存并发上限。

-- 会话表:同一访客在同一渠道的一次咨询 = 一行 CREATE TABLE `cs_session` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `visitor_id` varchar(64) NOT NULL COMMENT '访客标识,前端生成的 uuid', `channel` varchar(20) NOT NULL COMMENT 'web/mp/wxapp/douyin/workwx', `open_id` varchar(128) DEFAULT NULL COMMENT '渠道侧用户唯一标识', `group_id` int unsigned NOT NULL DEFAULT 0 COMMENT '技能组', `agent_id` int unsigned NOT NULL DEFAULT 0 COMMENT '接待坐席,0 表示排队中', `status` tinyint NOT NULL DEFAULT 0 COMMENT '0 排队 1 接待中 2 已结束', `unread` int NOT NULL DEFAULT 0 COMMENT '坐席侧未读数', `last_msg_time` int unsigned NOT NULL DEFAULT 0, PRIMARY KEY (`id`), KEY `idx_visitor` (`visitor_id`,`channel`), KEY `idx_agent_status` (`agent_id`,`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 消息表:历史消息按 id 游标翻页,不用 offset CREATE TABLE `cs_message` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `session_id` bigint unsigned NOT NULL, `direction` tinyint NOT NULL COMMENT '1 访客发 2 坐席发 3 系统消息', `msg_type` varchar(20) NOT NULL DEFAULT 'text' COMMENT 'text/image/file/voice', `content` text NOT NULL, `ext` json DEFAULT NULL COMMENT '渠道特有字段,统一塞这里', `create_time` int unsigned NOT NULL, PRIMARY KEY (`id`), KEY `idx_session` (`session_id`,`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

unread这个字段是冗余的,故意冗余。工作台左侧会话列表要显示红点,如果每次都去count(*)子查询,几十个坐席同时刷新就能把数据库拖住。消息表的分页走id游标而不是offset,历史消息上到十万级之后limit 90000,20会明显变慢,游标翻页是这种场景的标准做法。ext用 JSON 字段存渠道私有数据,是我最喜欢的部分——最早期的版本把不同渠道的字段直接加列,接到第三个渠道时表已经四十多列,后面全部改成 JSON 才收住。

3. 安装更新一体包怎么跑:从解压到第一个坐席上线

这个包把安装向导和更新目录放在一起,装完可以直接在后台打补丁,不用再满世界找升级包,这一点确实省事。但向导给了便利也留了隐患:装完不处理install/,等于给任何人留了一个重装入口,能直接把你的库覆盖掉。下面按我实际部署的顺序走一遍。

3.1 建库、建账号、写配置文件

先把数据库和账号准备好,别用 root 跑站点,这是基本盘。

# 建库,字符集必须 utf8mb4,否则访客发个表情就乱码 mysql -uroot -p -e "CREATE DATABASE cs_center DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" # 单独建业务账号,只给这个库的增删改查权限 mysql -uroot -p -e "CREATE USER 'cs_user'@'127.0.0.1' IDENTIFIED BY '换成你的强密码';" mysql -uroot -p -e "GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,ALTER,INDEX ON cs_center.* TO 'cs_user'@'127.0.0.1';" mysql -uroot -p -e "FLUSH PRIVILEGES;"

拿到账号之后改配置文件。这里有两个参数最容易填错,一旦填错报错信息还很不直观。

// config/database.php 关键几项,改完记得清 runtime 缓存 return [ 'type' => 'mysql', 'hostname' => '127.0.0.1', // 别写 localhost,部分机器会走 socket 导致 2002 错误 'database' => 'cs_center', 'username' => 'cs_user', 'password' => '换成你的强密码', 'hostport' => '3306', 'charset' => 'utf8mb4', 'prefix' => 'cs_', // 必须和安装向导里填的表前缀一致 ];

prefix对不上是最隐蔽的坑:安装向导会正常走完、提示成功,但登录后台一片空白,因为模型去查cs_开头的表,而向导实际建的是别的名字。hostname写localhost在部分发行版上会走 Unix socket,容器里根本没这个 socket,报出来的就是「连接失败」而不是「host 不存在」,很难一眼看出原因。

3.2 走一遍 install 向导

浏览器打开http://你的域名/install/,五步走完:

  1. 环境检测页,重点看目录权限和扩展两项,红叉必须先解决;
  2. 数据库页,填地址、库名、账号、密码、表前缀;
  3. 管理员页,设账号密码,同时可以改后台入口路径;
  4. 执行安装,等待建表语句跑完;
  5. 完成页,提示删除install目录。

安装完成后立刻做两件事,别拖到「上线前再说」。

# 装完就把这两个目录挡在门外 location ^~ /install/ { deny all; } location ^~ /runtime/ { deny all; }

install/不只是配置文件的问题,它带着完整的建表和执行入口,没删就等于留了个后门。runtime/里面有日志和缓存,日志里可能带手机号、会话内容,被扫到就是数据泄露。

更新包的使用方式有两种。后台有「系统更新」入口,可以直接上传update/目录里的补丁包;也可以手动解压覆盖,再执行update/update.php跑 SQL 变更。不管走哪条,先备份数据库——我见过补丁跑到一半断掉,表结构只改了一半,回滚比重装还麻烦。

3.3 起长连接服务

这是整套系统里最需要动手的一步,也是新手最容易卡住的地方。先改gateway/start_gateway.php里几行配置。

// gateway/start_gateway.php 里需要改的就这么几行 $gateway = new Gateway("websocket://0.0.0.0:8282"); // 对外监听端口 $gateway->count = 4; // 按 CPU 核数给,别一上来就 16 $gateway->name = 'csGateway'; $gateway->lanIp = '127.0.0.1'; // 多机部署改成内网 IP $gateway->startPort = 2900; // 内部通信端口段起点 $gateway->registerAddress = '127.0.0.1:1236'; // 必须和 start_register.php 一致

启动和自查:

cd /www/wwwroot/cs/gateway php start.php start # 调试阶段前台跑,报错能直接看到 php start.php start -d # 确认没问题再后台常驻 php start.php status # 看四个进程是否都在

count是 Gateway 进程数,每个进程占一个内部端口,count = 4就会占 2900 到 2903,端口段被别的服务占了会起不来。对外只需要放行 8282,内部端口段不要暴露到公网。生产环境用 Supervisor 守护,别用nohup挂着,机器重启后进程不会自己起来,用户第二天上班发现聊天框一直转圈,排查半天才发现是进程没了。

3.4 登录后台,把坐席和渠道配起来

后台用安装时设的管理员账号登录,按「角色权限 → 技能组 → 坐席 → 渠道绑定」的顺序配。技能组是分配逻辑的基础,访客进来先落到组,组里再挑人,所以组没建好,后面全乱。

-- 配完自查:坐席是否落在正确的技能组、并发上限有没有设 SELECT a.id, a.nickname, a.group_id, a.max_service, a.status FROM cs_agent a LEFT JOIN cs_group g ON g.id = a.group_id WHERE a.status = 1;

max_service是单个坐席的并发接待上限,默认值一般是 5。这个数字不按实际情况调,分配逻辑就会一直往同一个人身上堆,其他坐席空着,客户还在排队。我一般让运营先估一个「一个人同时能聊几个不串」,再往上留一格余量。

4. 核心链路拆解:一条访客消息从进入到坐席回复

把这套系统拆开看,其实就是一条流水线:渠道消息进来 → 适配器统一格式 → 落库并找到对应会话 → 分配或查找接待坐席 → 通过长连接推给坐席 → 坐席回复走反方向。这条链路上有四个地方值得逐段看代码,改业务基本都在这里动刀。

4.1 接入层:把五个渠道的消息压成同一个结构

接新渠道的成本,取决于这一层的抽象做得好不好。这套代码用的是接口加驱动的写法,一个渠道一个类,注册进配置就能用。

<?php namespace app\channel\contract; /** * 渠道适配器:接一个新渠道 = 实现这个接口 + 配置里注册一行 */ interface ChannelAdapter { public function verify(array $params): bool; // 回调校验,防止伪造消息 public function parse(array $payload): array; // 渠道原始报文 -> 内部统一消息体 public function reply(array $msg): bool; // 内部消息体 -> 回推渠道 }

再看一个具体实现,网页渠道,逻辑最简单,适合当模板抄。

namespace app\channel\driver; use app\channel\contract\ChannelAdapter; class WebChannel implements ChannelAdapter { private string $secret = '和前端约定的签名密钥'; public function verify(array $params): bool { // token + 时间戳签名,时间戳超过 300 秒直接拒,防重放 if (empty($params['token']) || empty($params['ts'])) return false; if (abs(time() - (int)$params['ts']) > 300) return false; return hash_equals($this->secret, md5($params['token'] . $params['ts'])); } public function parse(array $payload): array { // 六个字段是硬约定,其余全部丢进 ext return [ 'channel' => 'web', 'open_id' => (string)($payload['visitor_id'] ?? ''), 'msg_type' => $payload['type'] ?? 'text', 'content' => (string)($payload['content'] ?? ''), 'ext' => json_encode($payload['ext'] ?? [], JSON_UNESCAPED_UNICODE), 'ts' => time(), ]; } public function reply(array $msg): bool { // 网页渠道不主动推,交给 Gateway 推给浏览器即可 return true; } }

parse返回的结构就是整套系统的内部消息契约:channel定来源,open_id定是谁,msg_type定怎么渲染,content是正文,ext兜底,ts排序用。把这个结构定死的好处是,上层分配、落库、推送完全不关心消息来自抖音还是公众号;坏处是当初定契约时要克制,别把渠道特有字段往主结构里塞——我踩过这个坑,第三个渠道接进来的时候主表已经四十多列了。

verify里的时间戳校验别省。没有它,任何人抓到一次回调报文就能重复往里灌消息,会话列表瞬间被刷爆,而且是那种「坐席以为是客户发疯」的场面。

4.2 路由分配:技能组过滤加忙闲比排序

分配逻辑在中枢服务里,思路是先从技能组里拿在线坐席,再按忙闲比挑一个最闲的。

public function assign(int $sessionId, int $groupId): int { $cacheKey = "cs:agents:group:{$groupId}"; $agentIds = $this->redis->sMembers($cacheKey); // 组内在线的坐席 id if (!$agentIds) return 0; // 没人接待,留在排队队列 $best = 0; $bestLoad = PHP_FLOAT_MAX; foreach ($agentIds as $id) { $load = (int)$this->redis->get("cs:agent:{$id}:load"); // 当前接待数 $max = (int)$this->redis->get("cs:agent:{$id}:max"); // 并发上限 if ($max > 0 && $load >= $max) continue; // 满了直接跳过 $ratio = $max > 0 ? $load / $max : $load; // 忙闲比 if ($ratio < $bestLoad) { $bestLoad = $ratio; $best = (int)$id; } } return $best; }

这里用忙闲比而不是绝对接待数,原因很实际:一个上限 20 的坐席手上挂着 8 个,另一个上限 5 的挂着 4 个,按绝对数量会派给后者,但它其实已经快满了。按比例算,前者 0.4、后者 0.8,派给前者才对。

load这个计数是难点。它必须在三个地方减:会话正常结束、坐席主动离线、会话超时被定时任务关闭。漏掉任何一个,计数只增不减,跑几天之后全组人都显示「已满」,新会话全部堆在排队里,而工作台上明明没人忙。这是这套系统上线后最常见的线上问题,没有之一。

4.3 长连接:Workerman 网关怎么把消息推到浏览器

gateway/下的Events.php是长连接的事件入口。坐席和访客连上来之后都要先绑定身份,之后推送靠 uid 找连接。

class Events { public static function onMessage($clientId, $message) { $data = json_decode($message, true); if (!$data || empty($data['type'])) return; if ($data['type'] === 'bind') { // 访客和坐席 id 可能重号,必须加命名空间前缀 $uid = ($data['role'] === 'agent' ? 'agent_' : 'visitor_') . (int)$data['id']; Gateway::bindUid($clientId, $uid); Gateway::sendToClient($clientId, json_encode(['type' => 'bind_ok'])); } if ($data['type'] === 'ping') { // 心跳,30 秒一次,中间层掐空闲连接是常态 Gateway::sendToClient($clientId, json_encode(['type' => 'pong'])); } } public static function onClose($clientId) { // 断开不等于下线,但要减掉这个坐席的 ws 连接计数 } }

业务侧推消息走的是 GatewayClient,注意它连的是 Register 的地址,不是 Gateway 的对外端口。

// 业务代码里推消息给某个访客 GatewayClient::$registerAddress = '127.0.0.1:1236'; GatewayClient::sendToUid('visitor_' . $visitorId, json_encode([ 'type' => 'message', 'data' => ['content' => $content, 'ts' => time()], ], JSON_UNESCAPED_UNICODE));

bindUid的前缀是关键设计。访客 id 和坐席 id 都是自增整数,不加前缀必然串消息,会出现客户咨询被推到另一个客服浏览器上的事故。sendToUid只在同一个 Register 集群内有效,多机房部署得自己加中转层,这一点源码里没有现成实现,别指望改个配置就能跨机房。

4.4 落库与已读回执

消息落库和会话状态更新必须在同一个事务里,否则会出现「列表显示有新消息,点进去是空的」。

START TRANSACTION; INSERT INTO cs_message (session_id, direction, msg_type, content, create_time) VALUES (1024, 1, 'text', '你好,想问下发货时间', UNIX_TIMESTAMP()); UPDATE cs_session SET last_msg_time = UNIX_TIMESTAMP(), unread = unread + 1 WHERE id = 1024; COMMIT;

direction是方向标记,1 表示访客发、2 表示坐席发、3 是系统消息(比如「坐席已接入」这类提示),工作台按这个字段决定气泡靠左还是靠右。unread只在访客发消息时加一,坐席打开会话时清零。

已读回执别用 UPDATE 全表的方式做。常见做法是用 Redis 的 hash 存每个会话的已读序号,工作台通过长连接拉增量,隔一段时间再异步刷一次数据库。每次已读都去 UPDATE 一遍消息表,几百个坐席同时在线时数据库会很难受,而且这些更新其实没人会去查。

5. 踩坑排查:部署和二次开发里最常见的五个问题

这套系统从装到改,坑基本集中在权限、端口、进程和计数这四类。下面五条都是我或同事实际遇到过的,现象、原因、解决按顺序写,照着对号入座就行。

5.1 装完首页 500 或整个白屏

现象:向导走完了,访问首页返回 500,或者干脆一片空白,Nginx 错误日志里只有一行PHP Fatal error。

原因:大概率是runtime/不可写,或者 PHP 版本偏高导致语法兼容问题。白屏且没有任何输出,说明错误被display_errors关掉了,报错信息全进了日志。

解决:先tail -f runtime/log/下的日志文件确认;把runtime/递归设成 Web 用户可写;打开config/app.php里的调试开关复现一次;确认 PHP 版本在 7.4 或 8.0。伪静态没配也会 404,但那是 404 不是 500,别混。

5.2 长连接连不上,控制台 ws 一直超时

现象:页面能打开、接口正常,聊天窗口一直显示「连接中」,浏览器控制台里 WebSocket 请求 pending 后失败。

原因:四种可能——8282 端口没放行、Nginx 转发没带 Upgrade 头、gateway 进程根本没起、https 站点连了 ws 而不是 wss。

解决:先telnet 域名 8282确认端口通;再php start.php status确认进程;Nginx 里 ws 的 location 必须带proxy_set_header Upgrade $http_upgrade;和proxy_set_header Connection "upgrade";;https 站点必须用 wss,且证书要覆盖到 ws 的子域名。

5.3 消息发出去了,对方收不到

现象:坐席这边点了发送,界面上有气泡,数据库里也查得到,访客那边就是没反应。

原因:推送链路断了。可能是GatewayClient::$registerAddress指向的地址和实际 Register 端口不一致,也可能是 uid 前缀写错导致推给了不存在的连接,还有一种是自己改了bindUid的逻辑,只绑了坐席没绑访客。

解决:在sendToUid前后打日志,把目标 uid 打出来;用Gateway::isUidOnline($uid)判断连接在不在;检查 StartRegister 的监听地址,多机部署时lanIp不能写 127.0.0.1。

5.4 坐席接待数只增不减,全员显示已满

现象:跑了几天之后,工作台上所有人都显示「接待中 5/5」,新会话全部排队,但实际每个坐席手上只有一两个会话。

原因:Redis 里的load计数漏减。会话结束后没有触发减一,或者坐席直接关浏览器、没走正常下线流程,超时清理的定时任务又没跑起来。

解决:先手动GET cs:agent:{id}:load核对,确认是计数问题;把会话结束、坐席离线、超时关闭三个入口都梳理一遍,确保都调了减一;定时任务用 Supervisor 或 crontab 挂上,别靠人记得跑。这个坑的后悔药就是加一个兜底任务,每小时用数据库里的真实接待数校正一次 Redis 计数。

5.5 打完更新包,后台页面错乱或按钮点不动

现象:更新提示成功,刷新之后样式全乱,或者某些按钮点了没反应。

原因:浏览器缓存着旧版静态资源,前端资源包换了但文件名没变,缓存直接命中;或者补丁包里的 SQL 只跑了一半,接口返回的字段和前端对不上。

解决:先在 URL 后面加随机参数强制刷新验证;确认public/下的静态资源版本号有没有更新;update/update.php手动再执行一次,看有没有报错中断;数据库结构用SHOW COLUMNS对比一下补丁里的变更。

6. 二次开发:三个真正值得动手的改造点

改造方向不用摊得太开,这套代码里性价比最高的是三处:

改造点动哪里改动量适合场景
接新渠道新增app/channel/driver/下一个类,配置里注册小,一天内有自建小程序、内部 App、邮件入口
扩展消息类型parse加msg_type,工作台加渲染分支中,两三天要发订单卡片、商品链接、表单
改造工作台public/下的前端资源 + 对应接口大,按需要嵌进已有后台、要加业务侧边栏

接新渠道是最推荐先做的,因为它能验证你对整条链路的理解。具体做法是照WebChannel抄一个类,实现verify、parse、reply三个方法,然后在渠道配置里加一行,后台下拉框就会出现新选项。reply方法要特别注意,有些渠道要求被动回复必须在 5 秒内返回,超时就只能走主动推送接口,这个延迟差异要在适配器里自己处理掉。

验证方式我一般走三步:先灌一批假会话压分配逻辑,看 Redis 计数会不会偏;再手动断开坐席连接,确认会话能不能被回收再分配;最后用两个浏览器分别扮演访客和坐席,把发文本、发图片、关窗口重进三种情况各走一遍。这三步走完,基本能覆盖 80% 的线上问题。

有个具体的技巧:改分配逻辑之前,先把assign方法里的$best和$bestLoad打进日志,跑一天真实流量,你会看到很多想当然的假设是错的。我当初按绝对接待数派单,日志一看,忙的永远是那两三个人,改完比例计算才均衡。

从那以后我每次接这类客服系统,都先把install/删掉、把runtime/权限收紧、再灌一批假会话压一遍分配逻辑,才敢交付给运营用。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询