资讯动态

PHP客服系统源码实战:朵米3.5架构解析、部署与二次开发避坑指南

发布时间:2026/10/22 21:22:10 来源:尧图企业网站定制
简介朵米3.5客服系统源码2023正式版是基于PHPMySQL开发的全功能客户服务解决方案面向需要搭建在线客服、统一沟通渠道与客户关系管理场景的中小企业及个人开发者。系统支持在线聊天、邮件、电话等多渠道接入通过智能分流快速将客户请求分派至合适的客服人员配合丰富的数据分析与报告功能可帮助企业了解客户需求并持续优化服务。整个资源包共2000个文件以js、html、css等前端资源构建交互界面配合php后端逻辑、sql数据库脚本以及json/xml配置项另含md说明文档、sh部署脚本等辅助内容压缩包整体大小43.85MB。压缩包内附完整站点源码、数据库入库文件以及《朵米3.5客服系统安装搭建配置文档v3.docx》详细说明运行目录、伪静态规则、数据库参数修改等关键步骤可帮助使用者在宝塔面板NginxPHP7.1-7.3MySQL5.6-5.7环境中快速完成部署。当前已有384人学习下载适合作为客服系统二次开发、功能扩展或生产环境搭建的参考范例。1. 看到“朵米3.5客服系统源码2023正式版”先别急着装这套源码真正值钱在哪看到“朵米3.5客服系统源码2023正式版”这串标题我先说个反直觉的判断这套东西最值钱的不是那个能聊天的客服面板而是它把即时会话、工单、消息存储三条链路揉进了一个 PHP 项目里。不少团队拿它当在线聊天窗口用对话结束就关页面数据全躺在 MySQL 里睡大觉换个思路把它当成接收业务表单、派发工单、沉淀对话记录的中枢能省下自建客服系统两三个月的开发量前提是知道它在哪儿使劲、在哪儿会翻车。这篇笔记适合三类人想本地跑通演示的、准备接进现有业务做二次开发的以及线上已经跑着、正被各种小毛病折磨的。不列功能清单直接讲架构、部署、状态机和避坑。2. 先看懂朵米3.5的架构再动手PHP 后端、消息通道与数据模型动手改代码之前先把链路图在脑子里画出来。朵米 3.5 这类客服源码不管界面做得多花哨所有功能最后都能归一到三个对象上访客发起的「会话」、会话里流动的「消息」以及需要跨人跨天跟踪的「工单」。很多开发者上手就翻消息表想着把聊天记录改成自己想要的格式其实会话的状态机才是客服系统的灵魂状态没理清消息和工单都会跟着乱。2.1 一套源码里三层职责会话、工单与消息谁管谁会话是一次完整的服务过程有生命周期访客点开对话窗口是「排队中」客服点接入变成「服务中」对话结束进入「已结束」。消息是会话里的流水账每条消息必须挂在某个会话 ID 下面。工单则是从会话里抽出来的任务比如“退款审核”“技术排查”它可以跨多个会话持续跟进不随聊天窗口关闭而消失。这套源码里常见的设计是三个维度分开管理代码里通常用一组常量来标识会话状态类似下面这种结构?php // 会话状态常量定义二次开发时统一引用不要直接写数字 define(SESSION_STATUS_QUEUE, 0); // 排队中等待客服接入 define(SESSION_STATUS_WORK, 1); // 服务中客服已接入 define(SESSION_STATUS_HOLD, 2); // 挂起等待访客回复超时 define(SESSION_STATUS_CLOSED, 3); // 已结束正常关闭 define(SESSION_STATUS_TRANSFER, 4);// 转接中会话在客服间转移 // 判断当前会话是否允许接收新消息 function session_can_message(int $status): bool { // 只有服务中和挂起状态允许继续收发消息 return in_array($status, [ SESSION_STATUS_WORK, SESSION_STATUS_HOLD, ], true); }逻辑说明状态常量是整个客服流转的地基所有接口在读写消息前都会先过session_can_message这道闸门。排队中或已结束的会话如果收到消息要么进留言队列要么直接拒绝不能写进正常消息表。参数说明状态值用 0-4 的整数而不是字符串是为了节省索引空间、方便比对in_array第三个参数传true开启严格模式避免字符串1被误判成整数 1。2.2 为什么是 PHP MySQL而不是常驻内存服务这套源码的取舍边界常见做法是分两条腿走路普通 HTTP 请求走 PHP-FPM处理登录、历史记录、工单管理等非实时操作另起一个独立的 CLI 常驻进程监听消息端口负责 WebSocket 长连接和消息推送。朵米 3.5 这种体量的项目一般就是这种 PHP-FPM CLI 常驻进程的组合而不是全部跑在 Swoole 常驻服务里。为什么不推荐把整套系统改成 Swoole 常驻核心原因有三个。一是兼容性PHP 常驻服务对全局变量、静态变量的处理方式和 FPM 完全不同线上改造成本高。二是维护门槛客服系统的代码通常由业务团队维护让所有人习惯常驻内存的编程模型培训成本不小。三是收益不明显客服场景的并发远低于通用 IM100 个客服同时在线的团队已经是中大型规模这个量级下瓶颈在 MySQL 写入不在 PHP 进程模型。方案适合规模主要成本稳定性PHP-FPM CLI 消息进程10-100 客服消息进程需单独守护高进程挂了可自动拉起Swoole 常驻服务1000 连接团队学习成本、内存泄漏排查中需要更精细的运维参数边界如果你预估同时在线客服超过 200 人或者单日消息量超过 20 万条这套 PHP 方案的 MySQL 写入会成为瓶颈那时候再考虑迁移消息通道不迟。规模没到这个量级之前稳定优先。2.3 核心数据表关系从访客到一条消息要穿过几张表打开数据库常见的数据结构大致是五张核心表访客表、客服表、会话表、消息表、工单表。访客表记录匿名访客的唯一标识客服表记录坐席账号会话表把访客和客服串起来消息表存聊天流水工单表存需要跟进的任务。它们的关系是一对多嵌套一个访客可以发起多个会话一个会话下面挂多条消息多个会话可以归并到一个工单。-- 会话表核心关联表几乎所有查询都从这里出发 CREATE TABLE chat_session ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, visitor_id INT UNSIGNED NOT NULL COMMENT 访客ID关联访客表, worker_id INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 客服ID0表示未接入, status TINYINT NOT NULL DEFAULT 0 COMMENT 状态见PHP常量定义, channel TINYINT NOT NULL DEFAULT 1 COMMENT 渠道来源1网页 2小程序 3APP, created_at DATETIME NOT NULL, accepted_at DATETIME DEFAULT NULL COMMENT 客服接入时间用于计算响应时长, closed_at DATETIME DEFAULT NULL COMMENT 结束时间, source_url VARCHAR(500) DEFAULT COMMENT 访客发起对话的页面地址, PRIMARY KEY (id), KEY idx_visitor (visitor_id, created_at), KEY idx_worker_status (worker_id, status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT会话表;字段说明里最容易被忽略的是accepted_at。很多团队做“首次响应时长”统计时发现数据不对就是因为接入时间没有在客服点“接入”按钮那一瞬间写库而是用消息表里第一条客服消息的时间替代遇到客服先打字后点接入就全算错了。source_url看起来不起眼但它是事后排查“哪个页面转化率低、哪个页面投诉多”的关键线索二次开发时不要随手丢掉。3. 本地跑通朵米 3.5 的最小部署路径LNMP 与三处必改配置拿到源码第一步不是急着看代码而是先把环境对齐。朵米 3.5 这类 PHP 客服系统最常见的部署环境是 LNMP也就是 Linux Nginx MySQL PHP。用 Docker 跑也行但生产环境我一般建议直接装在裸机上少一层网络转发就少一个排查变量。下面这套路径我反复走过很多遍照着做基本半小时内能跑起来。3.1 环境准备与源码放置PHP 版本和扩展先对齐先确认 PHP 版本和扩展。这套源码按 2023 正式版的常见依赖PHP 7.4 是最稳妥的选择PHP 8.0 以上部分老代码会有兼容问题后面避坑章节会专门讲。必要扩展包括pdo_mysql、redis如果做队列和mbstring。# 查看当前PHP版本和已安装扩展 php -v php -m | grep -E pdo_mysql|mbstring|redis # 如果扩展缺失以Debian/Ubuntu为例安装 sudo apt-get install -y php7.4-cli php7.4-fpm php7.4-mysql php7.4-mbstring # 装完后确认扩展已加载 php -m | grep pdo_mysql逻辑说明这套源码靠 PDO 连 MySQLmbstring处理中文消息的截断和编码转换少了这两个扩展会在访客发中文消息时出现乱码或直接白屏。参数说明php7.4这个版本号要和你系统里的实际版本对应在 CentOS 上包名通常是php74-php-mysqlnd这种带前缀的写法装之前先yum list installed | grep php看清楚再动手。3.2 建库导表与 Nginx 站点配置伪静态和消息端口一起解决源码上传到站点目录后第一件事是建库导表。很多预打包源码会带一个install.sql或db.sql直接用命令行导入比用 phpMyAdmin 上传大文件可靠得多。# 创建数据库并导入初始表结构 mysql -uroot -p -e CREATE DATABASE IF NOT EXISTS duomi_chat DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; mysql -uroot -p duomi_chat /var/www/duomi/db/install.sql # 确认核心表已生成 mysql -uroot -p -e USE duomi_chat; SHOW TABLES;导入完成后配置 Nginx。客服系统和普通博客最大的区别是页面请求走 PHP-FPM消息推送走独立的 WebSocket 端口两者在 Nginx 里要分开处理。# /etc/nginx/conf.d/duomi.conf server { listen 80; server_name chat.example.com; root /var/www/duomi/public; # 入口目录按实际源码结构调整 index index.php; # 业务接口全部走PHP-FPM location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass unix:/run/php/php7.4-fpm.sock; } # 消息推送的WebSocket路径反代到CLI常驻进程端口 location /ws { proxy_pass http://127.0.0.1:8280; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_read_timeout 3600s; } }逻辑说明根路径先按静态文件找找不到再交给index.php做路由分发。/ws专门转发长连接流量proxy_read_timeout必须设大否则 Nginx 默认 60 秒就会切断 WebSocket 连接导致客服那边“明明在线却收不到消息”。参数说明8280端口要和源码里消息进程的实际监听端口一致如果改端口Nginx 和源码配置必须同步修改。3.3 三处必改参数内存、超时、连接数每次部署新环境我拿到手的第一件事就是改这三处不改后面必出问题。第一处是php.ini里的资源限制第二处是 MySQL 的连接数第三处是消息进程的守护方式。; /etc/php/7.4/fpm/php.ini memory_limit 256M ; 客服系统页面加载历史消息列表时比较吃内存128M容易超限 max_execution_time 60 ; 导出聊天记录、批量操作工单时防止脚本被掐断 post_max_size 64M ; 上传图片、文件作为附件时会用到太小会静默失败 upload_max_filesize 50MMySQL 端默认的max_connections在客服这种长连接场景下经常不够。每个客服工作台会同时占用一个 HTTP 连接和一个 WebSocket 连接加上后台定时任务100 个客服在线就能吃掉两三百个连接。建议至少改成 512。# /etc/mysql/mysql.conf.d/mysqld.cnf [mysqld] max_connections 512 wait_timeout 300 interactive_timeout 600参数说明wait_timeout调小的目的是让死连接快速释放避免 MySQL 被“僵尸连接”占满。最后一步消息进程必须用守护方式启动不能直接nohup扔后台完事否则进程一崩消息通道就静默罢工了。# 使用systemd守护消息进程假设入口脚本是 start_chat_server.php cat /etc/systemd/system/duomi-chat.service EOF [Unit] DescriptionDuomi Chat Message Server Afternetwork.target mysql.service [Service] WorkingDirectory/var/www/duomi ExecStart/usr/bin/php /var/www/duomi/bin/chat_server.php Restartalways RestartSec3 [Install] WantedBymulti-user.target EOF systemctl daemon-reload systemctl enable --now duomi-chat逻辑说明Restartalways保证进程挂了 3 秒内自动拉起这是客服系统最不能省的一道防线。我见过很多团队把消息进程挂在前台终端里一关 SSH 窗口客服就集体掉线改成 systemd 托管之后这类问题彻底消失。4. 二次开发绕不开的三块硬骨头状态机、消息顺序与工单流转部署跑通只是开始真正常被拉去改需求的是这三块会话状态机、消息入库顺序、工单流转逻辑。这三块改不好线上就会出现“客服明明接了单却收不到消息”“用户发最后一句没存上”“工单莫名其妙消失了”等灵异现象。多数时候不是源码 bug而是改代码的人没搞懂内部约定。4.1 会话状态机排队、接入、转接、结束的状态迁移会话状态机的核心约束是非法迁移不能发生。已结束的会话不能被重新接入排队中的会话不能被直接关闭。常见的实现是写一个专门的状态迁移函数所有入口统一调它。?php // 统一状态迁移入口禁止在业务代码里直接UPDATE状态字段 function session_transition(int $sessionId, int $newStatus, int $operatorId): bool { // 读取当前会话信息这里应走缓存减少DB压力 $session get_session_by_id($sessionId); $old (int)$session[status]; // 状态迁移合法性映射只允许以下流转 $allowed [ SESSION_STATUS_QUEUE [SESSION_STATUS_WORK, SESSION_STATUS_CLOSED], SESSION_STATUS_WORK [SESSION_STATUS_HOLD, SESSION_STATUS_TRANSFER, SESSION_STATUS_CLOSED], SESSION_STATUS_HOLD [SESSION_STATUS_WORK, SESSION_STATUS_CLOSED], SESSION_STATUS_TRANSFER [SESSION_STATUS_WORK, SESSION_STATUS_CLOSED], ]; if (!isset($allowed[$old]) || !in_array($newStatus, $allowed[$old], true)) { // 记录非法迁移日志便于追查是谁在绕过状态机 log_states(illegal_transition, $sessionId, $old, $newStatus, $operatorId); return false; } return update_session_status($sessionId, $newStatus); }逻辑说明$allowed数组画出了状态迁移的“白名单”比如排队中可以直接关闭访客取消咨询但不能直接转接因为还没接入的会话没有可转接的客服。所有外部入口包括客服点击“接入”“转接”“结束”最终都调session_transition而不是各自 UPDATE 数据库。参数说明$operatorId必须传出问题时要能追溯到“谁在什么时间把会话从什么状态改成了什么状态”这是客服系统审计的基本要求。4.2 消息入库与推送的顺序先落库再广播顺序反了会丢消息这是客服系统最典型的翻车点。新手喜欢先推送 WebSocket 再写数据库觉得这样消息送达快。但推送是有可能失败的客户端断网、服务端进程重启、中间链路超时任何一步出错这条消息就人间蒸发了。正确的顺序是先落库、再推送。?php function send_message(int $sessionId, string $content, int $senderType): array { // 第一步写入消息表拿到自增ID作为消息唯一标识 $msgId insert_message($sessionId, $content, $senderType, time()); // 第二步推送WebSocket带上刚生成的msgId $pushResult ws_push_to_session($sessionId, [ msg_id $msgId, content $content, sender_type $senderType, ts time(), ]); // 第三步推送失败时写入待补偿队列由定时任务重推 if (!$pushResult) { push_fail_queue_add($msgId, $sessionId); } return [msg_id $msgId, pushed $pushResult]; }逻辑说明先 insert 后 push推送失败还有数据库这条底账定时任务可以捞出来重推如果先 push 后 insert推送成功但 insert 失败客户端能看到消息刷新页面后消息却消失了用户会坚定地认为你们系统吞消息。参数说明$senderType区分访客和客服推送时客户端要根据这个字段决定消息气泡显示在左边还是右边传反了会出现客服自己发的消息显示在访客侧。4.3 工单流转把聊天记录变成可跟踪的任务工单的价值在于把“聊过的事情”变成“要做的事情”。常见逻辑是客服在会话界面点“创建工单”系统把当前会话的关键信息快照到工单表并关联会话 ID。这里有坑很多新手工单只存了工单标题和内容没存访客 ID 和会话 ID过几天想回头看上下文发现根本连不回去。?php function create_ticket_from_session(int $sessionId, int $workerId, string $title, string $content): int { // 获取会话关联的访客信息和原始诉求摘要 $session get_session_by_id($sessionId); $visitorId $session[visitor_id]; // 同一访客同一会话不重复建单避免客服手抖连点 $exists find_ticket_by_session_id($sessionId); if ($exists) { return (int)$exists[id]; } $ticketId insert_ticket([ session_id $sessionId, visitor_id $visitorId, worker_id $workerId, title $title, content $content, status 0, // 0待处理 1处理中 2已完成 3已关闭 created_at date(Y-m-d H:i:s), ]); // 创建后自动把会话状态置为挂起聊天窗口提示“已创建工单” session_transition($sessionId, SESSION_STATUS_HOLD, $workerId); return $ticketId; }逻辑说明session_id是工单回溯上下文的关键外键visitor_id是为了跨会话聚合同一个人的所有工单。创建成功后把会话置为挂起而不是结束是因为用户可能还要补充信息挂起状态下访客可以继续回复。参数说明工单状态建议用整数状态机管理不要直接 UPDATE和会话状态机同理否则工单流转记录一查一个乱。5. 避坑清单部署朵米 3.5 最常见的五个翻车现场这套源码我前后经手过不少部署和二次开发踩过的坑和帮别人排过的坑都集中在这五个点上。每一条都是“现象 - 原因 - 解决”的结构排查顺序也是我验证过的按这个顺序走能少走两小时弯路。5.1 排查顺序日志、端口、会话表三件套遇到“客服收不到消息”“访客发了消息没反应”之类的问题别急着翻源码先按下面三步定位通常十分钟内能找到方向。# 第一步看消息进程有没有活着端口有没有在监听 netstat -tlnp | grep 8280 systemctl status duomi-chat # 第二步看PHP日志里有没有报错 tail -f /var/log/php7.4-fpm.log # 第三步看数据库里最新几条消息是否落库了 mysql -uroot -p duomi_chat -e SELECT id, session_id, content, created_at FROM chat_message ORDER BY id DESC LIMIT 5;逻辑说明第一步排除进程层问题第二步排除代码层报错第三步确认数据是否进了库。如果消息落库了但客户端收不到问题在推送链路如果没落库问题在入站处理。参数说明8280换成你实际配置的端口日志路径也随发行版不同而不同CentOS 上通常是/var/log/php-fpm/www-error.log。5.2 现象一客服工作台一直“连接中”或一直转圈这是最高频的问题。现象是客服登录后台能看到会话列表但点进去一直转圈或者右上角状态永远是“连接中”。原因通常是 WebSocket 握手失败大部分情况是 Nginx 没配置好Upgrade头少部分是消息进程监听端口被防火墙挡住。解决办法先确认服务端端口可达再确认 Nginx 的/ws反代配置。我在 3.2 节里写的proxy_set_header Connection upgrade是重点少一行握手就失败。很多系统默认带了 Nginx但默认配置里没有这段 WebSocket 专有设置直接套用就会踩坑。5.3 现象二最后一条消息偶发丢失用户记录里查不到现象是用户说“我发了消息客服没回”后台查消息记录里确实没有那条。原因大概率是消息推送到客户端了但没入库。很多团队为了省时间会临时改代码“先 push 后 insert”或者消息处理进程异常退出导致后续流程中断。解决办法按 4.2 节的顺序重构先落库再推送并且给消息表加一个“推送补偿”的扫描任务每五分钟扫一次消息表把推送状态为失败的消息重推一遍。这个补偿机制能兜住绝大多数偶发丢失。5.4 现象三高版本 PHP 下页面白屏或函数报错现象是安装文档明明写着“支持 PHP 7.0-8.0”但用 PHP 8.1 跑起来直接白屏。查日志看到类似call_user_func传参方式变化、each()函数被移除之类的问题。这是因为这套源码按 2023 年之前的老写法写的某些内置函数和传参规则在 PHP 8 里被废弃或严格化了。解决办法如果只是演示装 PHP 7.4 最省事如果生产环境已有 PHP 8.2 且不能降级需要全局排查each()、create_function()这类老函数把它们替换成等价的现代写法。这是一个体力活但不用改业务逻辑纯兼容层改动。5.5 现象四数据库连接被打满MySQL 报 too many connections现象是早晨一上班客服系统集体报错MySQL 提示连接数超限。原因有两层一是工作台每个标签页都会建一个长连接客服开了多个标签页就把连接数翻倍二是 MySQL 的默认连接数只有 151按 100 个客服算完全不不够分。解决办法先把max_connections调到 512 以上再把客服端的 WebSocket 连接做单例复用一个浏览器只保持一个长连接不要每个会话标签页各连一次。最后监控show processlist里的Sleep线程如果大量堆积调低wait_timeout到 120 秒左右。5.6 现象五定时任务不触发工单超时无人管现象是工单设置了“24 小时未响应自动升级”但从来没见升级过。原因是源码里的定时任务依赖系统的 cron而部署时根本没有配 crontab。这类源码一般会在文档里提一句“将以下命令加入 crontab”但很多人只看安装步骤就跳过去了。# 每5分钟执行一次消息补偿 */5 * * * * php /var/www/duomi/bin/compensate_message.php # 每10分钟执行一次工单超时扫描 */10 * * * * php /var/www/duomi/bin/ticket_timeout_check.php systemctl reload crond参数说明定时任务的执行频率要和业务需求匹配工单超时扫描不建议超过 15 分钟一次否则用户等很久没反馈消息补偿也不要短于 30 秒太频繁会把数据库拖垮。6. 进阶把朵米 3.5 的会话记录变成运营指标部署稳定、二次开发告一段落之后这套源码还能再挖一层价值把聊天记录转成可量化指标。客服系统每天产生大量会话数据很多团队只用来“查记录”浪费了。这里说两个最实用、也最容易算错的操作。先算首次响应时长。这个指标的算法是访客发出第一条消息的时间到客服接入会话的时间取差值。注意用的是chat_session.accepted_at减去chat_message里该会话第一条访客消息的created_at不是客服第一条回复的时间。SELECT s.id AS session_id, TIMESTAMPDIFF(SECOND, first_msg.created_at, s.accepted_at) AS first_response_seconds FROM chat_session s LEFT JOIN ( SELECT session_id, MIN(created_at) AS created_at FROM chat_message WHERE sender_type 0 -- 0访客 1客服 GROUP BY session_id ) first_msg ON first_msg.session_id s.id WHERE s.accepted_at IS NOT NULL AND s.created_at DATE_SUB(NOW(), INTERVAL 7 DAY) ORDER BY first_response_seconds DESC LIMIT 50;逻辑说明sender_type 0滤掉客服消息取每个会话里最早的访客消息时间做基准TIMESTAMPDIFF直接算秒数方便后续按分钟聚合。如果发现大量会话的响应时间是负数说明接入时间记录有问题回到 2.3 节检查accepted_at是否正确写入。第二个习惯是历史消息归档。客服系统跑半年后消息表轻松破百万行统计查询会越来越慢影响线上写入。我一般会在数据库里给chat_message建分区按月分区或者每季度把三个月前的数据迁移到独立的归档库。业务代码不需要改查询历史记录时路由到归档库即可。很多团队拖到慢查询告警才处理那时表已经 500 万行了凌晨全表扫描把 CPU 跑满客服体验瞬间崩塌。最后分享一个习惯每次发布版本前我会在测试环境跑一遍“消息补偿模拟”——手动断开一个客服的 WebSocket然后发一条消息等 5 分钟看重推任务能不能把消息补上。这套系统最怕的不是功能缺失而是数据悄悄丢了用户还不知道。把数据完整性的验证脚本固化到发布流程里比什么都重要。这个习惯救过我很多次希望也能帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑