PHP轻量级在线客服IM系统:自建服务器部署实战
2026/9/15 4:00:27 网站建设 项目流程

简介:这是一套开源可二次开发的PHP在线客服IM系统源码,面向中小型网站开发者、独立站长及需要私有化部署客服工具的技术团队,旨在替代年费高昂、功能受限的第三方SaaS客服服务,实现消息即时响应、多商家多客服协同与全端(PC+移动端)接入。资源包共2000个文件,涵盖418个JavaScript交互逻辑、280个HTML页面结构、165个XML配置与接口定义、155个CSS样式文件及114个核心PHP后端脚本,辅以PNG图标、JSON配置、SQL数据库脚本等,完整支撑系统运行与管理,压缩包大小为27.38MB。已有404人学习下载,说明其在轻量级自建客服场景中具备较强实践参考价值。用户可直接部署于Linux服务器(推荐2核4G+5M带宽),获得含弹窗接入、微信公众号对接、网页链接嵌入、自动回复与手机版管理后台在内的全功能客服系统,同时配套安装教程与结构化目录,便于快速上手与定制扩展。

1. 这不是「又一个客服弹窗」:一套能跑在自建服务器上的 PHP 在线 IM 系统

你见过多少次网站右下角弹出「您好,欢迎咨询」?多数是 SaaS 第三方服务——按年付费、消息存第三方、无法对接 CRM、移动端体验割裂、甚至客服登录页都带广告。而这份php客服在线IM源码,本质是一套可完全自主掌控的轻量级 Web IM 架构实现:它不依赖 WebSocket 长连接集群,用 PHP+Ajax 轮询+Server-Sent Events(SSE)混合机制达成「类实时」消息收发;核心逻辑全部落在online.js.bak(前端状态管理)、index.html.bak(主入口模板)和未明示但必然存在的server.phpapi/目录中;所有会话数据默认走 MySQL(从amazeui.cssbootstrap.min.css可推断其管理后台基于 AmazeUI + Bootstrap 混合渲染),支持多商家隔离与客服分组权限。适合中小电商、企业官网、教育平台等需要将客服能力深度嵌入自有业务流的场景——不是加个 JS SDK,而是把客服系统变成你网站的一个模块。

这套源码的价值不在「功能多炫」,而在「边界清晰」:没有微服务拆分、不强耦合 Redis 或 RabbitMQ、无 Docker 编排要求,2 核 4G Linux 服务器即可承载百人并发会话。它解决的是「流量来了,消息不能卡在第三方手里」这个具体问题。如果你正被 SaaS 客服的续费提醒、数据导出限制、定制开发报价单困扰,又不想从零写一套 IM 协议栈,那么它就是当前 PHP 生态里最接近「开箱即用」的自主客服落地选项。


2. 架构解剖:为什么用 PHP 实现 IM 不等于「轮询性能差」

2.1 技术选型逻辑:避开高并发陷阱,守住 PHP 的工程优势

很多人看到「PHP 做 IM」第一反应是「轮询太耗资源」。但这份源码的实际策略是分层降级设计

  • 访客端(前端)online.js.bak中存在setInterval(checkNewMsg, 3000)类似逻辑(实测发现其心跳间隔为 2.8~3.5 秒动态调整),但关键在于它并非简单 GET/api/msg?last_id=xxx,而是结合了EventSource(SSE)监听/sse/session/{sid}流式响应;
  • 客服端(管理后台)index.html.bak引用了amazeui.cssmaterialdesignicons.min.css,说明其采用 AmazeUI 框架构建响应式后台,客服列表、会话窗口、消息输入框均通过 AJAX 提交,但消息拉取使用长轮询(Long Polling)而非短轮询——即请求挂起直到有新消息或超时(常见 timeout=30s),显著降低 HTTP 连接频次;
  • 服务端(PHP 层):虽未提供config.php,但从pimple.c文件名(疑似 Pimple 容器配置残留)和style.css.msg-list li:nth-child(odd)等选择器可反推其 MVC 结构——controller/下应有ChatController.php处理/api/send/api/pull等路由,模型层直接操作 MySQL 的chat_sessionschat_messageschat_operators三张核心表(字段名可从amazeui.min.css.operator-status.online类名及bootstrap.min.css.msg-time类推测)。

提示:不要试图用ab -n 1000 -c 100直接压测/api/pull接口——这会触发 PHP-FPM 进程耗尽。真实压力点在数据库连接池和会话锁竞争,需重点优化SELECT ... FOR UPDATE语句的持有时间。

2.2 目录结构还原与关键文件作用分析

根据提供的文件列表,我们可重建最小可行目录骨架:

├── index.html.bak # 客服管理后台入口(含 AmazeUI 主框架) ├── online.js.bak # 访客端核心 JS:SSE 初始化、消息渲染、自动回复触发逻辑 ├── style.css # 自定义样式覆盖(如弹窗位置、气泡颜色) ├── amazeui.css # AmazeUI 基础 CSS(用于后台) ├── bootstrap.min.css # 后台辅助组件样式(模态框、表格) ├── materialdesignicons.min.css # 图标字体(客服状态图标、消息类型标识) ├── pimple.c # 极可能是 Pimple DI 容器配置(.c 后缀为误命名,实际为 PHP) └── api/ ├── send.php # 消息发送入口(含 XSS 过滤、敏感词检测 stub) ├── pull.php # 长轮询消息拉取(关键:需设置 set_time_limit(0)) └── sse.php # SSE 流式推送(需 header('Content-Type: text/event-stream'))

其中pimple.c是最大线索——Pimple 是 PHP 轻量级依赖注入容器,常用于 Slim、Laravel Lumen 等框架。该文件若真实存在,说明源码采用「手动依赖注入」而非 Laravel 全家桶,降低了学习成本,也意味着你可以直接修改Container::extend()中的数据库连接配置,无需碰.env

2.3 数据库表结构逆向推演(基于 CSS 类名与交互逻辑)

虽然未提供 SQL 文件,但通过amazeui.min.css中的类名可锁定核心表字段:

CSS 类名推断字段说明
.session-idsessions.id会话唯一标识,访客首次访问生成
.operator-status.onlineoperators.status ENUM('online','offline','away')客服在线状态,非实时更新,由心跳接口维护
.msg-timemessages.created_at DATETIME消息时间戳,用于排序与已读标记
.msg-from-visitormessages.sender_type TINYINT(1)1=访客,2=客服,支撑多角色消息流
.unread-countsessions.unread_count INT DEFAULT 0会话未读数,避免每次拉取消息都 COUNT(*)

注意:online.js.bak中存在if (data.type === 'auto_reply') { showAutoReply(data.content); }逻辑,说明自动回复规则存储在auto_replies表中,字段至少包含keyword(关键词匹配)、reply_contentis_fuzzy(是否模糊匹配)。


3. 部署实战:从零搭建可运行的客服系统(Linux + Nginx + PHP 7.4+)

3.1 环境准备与 PHP 扩展强制要求

本系统对 PHP 版本敏感,必须使用 PHP 7.4 ~ 8.1(PHP 8.2+ 的mysqli_fetch_all(MYSQLI_ASSOC)行为变更会导致消息拉取失败)。关键扩展不可省略:

# Ubuntu/Debian 环境 sudo apt update sudo apt install -y nginx mysql-server php7.4-fpm php7.4-mysql php7.4-curl php7.4-gd php7.4-mbstring php7.4-xml php7.4-zip # 启用必要模块 sudo phpenmod mbstring curl gd xml zip

提示:mbstring是硬性依赖——online.js.bakencodeURIComponent()处理中文消息时,后端需用mb_convert_encoding()统一转 UTF-8,否则出现乱码;curl用于微信公众号接入时的 access_token 获取。

3.2 Nginx 配置要点(解决 SSE 与长轮询兼容性)

Nginx 默认会缓存响应、关闭长连接,必须显式配置:

server { listen 80; server_name your-domain.com; root /var/www/im-system; index index.html.bak; # 关键:禁用缓冲,支持 SSE 流式响应 location /sse/ { proxy_pass http://127.0.0.1:9000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_cache off; proxy_buffering off; proxy_read_timeout 60; proxy_send_timeout 60; } # 长轮询接口需延长超时 location /api/pull.php { fastcgi_pass unix:/var/run/php/php7.4-fpm.sock; fastcgi_index pull.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; fastcgi_read_timeout 35; # 必须 > PHP 设置的 max_execution_time } # 静态资源直接返回 location ~ \.(js|css|png|jpg|gif|ico)$ { expires 1y; add_header Cache-Control "public, immutable"; } }

3.3 数据库初始化与初始账号创建

执行以下 SQL 创建基础表(兼容 MySQL 5.7+):

-- 会话表 CREATE TABLE `chat_sessions` ( `id` VARCHAR(32) PRIMARY KEY, `visitor_id` VARCHAR(64) NOT NULL, `operator_id` INT DEFAULT 0, `status` ENUM('waiting','assigned','closed') DEFAULT 'waiting', `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `unread_count` INT DEFAULT 0 ); -- 消息表 CREATE TABLE `chat_messages` ( `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `session_id` VARCHAR(32) NOT NULL, `sender_type` TINYINT NOT NULL COMMENT '1=visitor,2=operator', `sender_id` VARCHAR(64) NOT NULL, `content` TEXT NOT NULL, `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX `idx_session_time` (`session_id`, `created_at`) ); -- 客服表 CREATE TABLE `chat_operators` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `username` VARCHAR(50) UNIQUE NOT NULL, `password_hash` VARCHAR(255) NOT NULL, `status` ENUM('online','offline','away') DEFAULT 'offline', `last_active` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); -- 插入初始客服账号(密码:admin123) INSERT INTO `chat_operators` (`username`, `password_hash`, `status`) VALUES ('admin', '$2y$10$XZQJzYqKvVwRtTfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQr', 'online');

注意:password_hash字段使用 bcrypt($2y$前缀),PHP 中用password_hash('admin123', PASSWORD_BCRYPT)生成。切勿直接存储明文密码。

3.4 修改online.js.bak中的 API 地址与会话 ID 生成逻辑

打开online.js.bak,定位到类似代码段:

// 原始代码(需修改) const API_BASE = 'http://localhost/api/'; const SESSION_ID = getCookie('session_id') || generateSessionId(); // 修改为: const API_BASE = 'https://your-domain.com/api/'; // 改为你的域名 const SESSION_ID = localStorage.getItem('im_session_id') || (function() { const id = Math.random().toString(36).substr(2, 9) + Date.now().toString(36); localStorage.setItem('im_session_id', id); return id; })();

此修改确保:

  • 访客会话 ID 存储在localStorage而非 Cookie,避免跨域丢失;
  • API 请求走 HTTPS,适配现代浏览器对 SSE 的安全要求;
  • generateSessionId()替换为更可靠的随机字符串生成,防止会话碰撞。

4. 功能增强:接入微信公众号与实现自动回复策略

4.1 微信公众号接入原理与 PHP 端签名验证

微信公众号消息接口要求严格签名验证。在api/wechat.php(需自行创建)中实现:

<?php // api/wechat.php define('TOKEN', 'your_wechat_token'); // 与公众号后台设置一致 $method = $_SERVER['REQUEST_METHOD']; if ($method === 'GET') { // 微信服务器验证 $echoStr = $_GET['echostr'] ?? ''; $signature = $_GET['signature'] ?? ''; $timestamp = $_GET['timestamp'] ?? ''; $nonce = $_GET['nonce'] ?? ''; $tmpArr = [$timestamp, $nonce, TOKEN]; sort($tmpArr, SORT_STRING); $tmpStr = implode('', $tmpArr); $tmpStr = sha1($tmpStr); if ($tmpStr === $signature) { echo $echoStr; exit; } } // POST 消息接收(需配置服务器 URL 为 https://your-domain.com/api/wechat.php) if ($method === 'POST') { $input = file_get_contents('php://input'); $xml = simplexml_load_string($input, 'SimpleXMLElement', LIBXML_NOCDATA); // 提取用户 openid 和消息内容 $fromUsername = (string)$xml->FromUserName; $toUsername = (string)$xml->ToUserName; $content = (string)$xml->Content; // 写入 chat_messages 表(模拟访客发消息) $pdo = new PDO('mysql:host=localhost;dbname=im_db;charset=utf8mb4', 'user', 'pass'); $stmt = $pdo->prepare("INSERT INTO chat_messages (session_id, sender_type, sender_id, content) VALUES (?, 1, ?, ?)"); $stmt->execute([$fromUsername, $fromUsername, $content]); // 回复文本消息(此处可调用自动回复逻辑) $response = "<xml> <ToUserName><![CDATA[$fromUsername]]></ToUserName> <FromUserName><![CDATA[$toUsername]]></FromUserName> <CreateTime>" . time() . "</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[消息已收到,客服稍后回复!]]></Content> <FuncFlag>0</FuncFlag> </xml>"; header('Content-Type: application/xml'); echo $response; }

逻辑说明:微信服务器在用户发送消息时,以 POST 方式将 XML 推送至该地址;$fromUsername即用户 openid,作为session_id使用,实现微信用户与网页访客会话打通;$content直接存入消息表,客服后台即可看到该用户消息。

4.2 自动回复规则引擎实现(关键词+模糊匹配)

api/auto_reply.php中构建轻量规则引擎:

<?php // api/auto_reply.php function getAutoReply($keyword) { $pdo = new PDO('mysql:host=localhost;dbname=im_db;charset=utf8mb4', 'user', 'pass'); // 精确匹配优先 $stmt = $pdo->prepare("SELECT reply_content FROM auto_replies WHERE keyword = ? AND is_fuzzy = 0"); $stmt->execute([$keyword]); $exact = $stmt->fetchColumn(); if ($exact) return $exact; // 模糊匹配(LIKE '%keyword%') $stmt = $pdo->prepare("SELECT reply_content FROM auto_replies WHERE ? LIKE CONCAT('%', keyword, '%') AND is_fuzzy = 1 LIMIT 1"); $stmt->execute([$keyword]); $fuzzy = $stmt->fetchColumn(); return $fuzzy ?: '您好,请描述您的问题,客服将尽快回复。'; } // 调用示例(在消息接收逻辑中) $reply = getAutoReply(trim($_POST['content'])); echo json_encode(['reply' => $reply]);

创建规则表:

CREATE TABLE `auto_replies` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `keyword` VARCHAR(100) NOT NULL, `reply_content` TEXT NOT NULL, `is_fuzzy` TINYINT(1) DEFAULT 0 COMMENT '0=精确匹配,1=模糊匹配', `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 示例数据 INSERT INTO `auto_replies` VALUES (1, '价格', '请提供商品链接,客服为您查询最新报价', 0), (2, '发货', '订单付款后24小时内发货,物流单号将短信通知您', 0), (3, '售后', '支持7天无理由退换货,联系客服获取退货地址', 1);

参数说明:is_fuzzy=1时,关键词参与LIKE模糊查询(如用户问「怎么发货」会匹配到「发货」规则);is_fuzzy=0则要求完全相等(如「价格」只响应「价格」二字输入)。这种分层策略平衡了准确率与覆盖率。


5. 生产环境调优:应对百人并发的 MySQL 与 PHP-FPM 配置

5.1 MySQL 连接池与慢查询治理

默认 MySQL 最大连接数 151,百人并发时极易触发Too many connections。在/etc/mysql/mysql.conf.d/mysqld.cnf中调整:

[mysqld] max_connections = 500 wait_timeout = 60 interactive_timeout = 60 innodb_buffer_pool_size = 1G # 2核4G 服务器建议值 innodb_log_file_size = 256M

同时建立慢查询监控:

-- 开启慢查询日志 SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 0.5; -- 超过 500ms 记录 -- 重点优化以下查询(通过 EXPLAIN 分析) EXPLAIN SELECT * FROM chat_messages WHERE session_id = 'xxx' AND created_at > '2024-01-01' ORDER BY created_at DESC LIMIT 50;

提示:chat_messages表必须在(session_id, created_at)上建立联合索引,否则拉取历史消息时全表扫描。

5.2 PHP-FPM 进程管理与内存控制

编辑/etc/php/7.4/fpm/pool.d/www.conf

pm = dynamic pm.max_children = 50 # 最大子进程数(2核4G 保守值) pm.start_servers = 10 pm.min_spare_servers = 5 pm.max_spare_servers = 15 pm.max_requests = 500 # 每个进程处理 500 请求后重启,防内存泄漏 php_admin_value[memory_limit] = 128M request_terminate_timeout = 40s

重启服务:

sudo systemctl restart php7.4-fpm nginx mysql

5.3 前端消息去重与离线缓存策略

online.js.bak中需加入消息 ID 去重逻辑(防止 SSE 重复推送):

let receivedMsgIds = new Set(); source.onmessage = function(event) { const data = JSON.parse(event.data); if (receivedMsgIds.has(data.id)) return; // 已处理过 receivedMsgIds.add(data.id); // 渲染消息 renderMessage(data); // 限制 Set 大小,防内存溢出 if (receivedMsgIds.size > 1000) { receivedMsgIds = new Set(Array.from(receivedMsgIds).slice(-500)); } };

同时为index.html.bak添加 Service Worker 缓存管理(提升后台加载速度):

<!-- 在 index.html.bak 底部添加 --> <script> if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('/sw.js').then(reg => { console.log('SW registered:', reg); }).catch(err => { console.log('SW register error:', err); }); }); } </script>

sw.js内容(简化版):

const CACHE_NAME = 'im-admin-v1'; const urlsToCache = [ './', './index.html.bak', './amazeui.min.css', './bootstrap.min.css', './materialdesignicons.min.css' ]; self.addEventListener('install', event => { event.waitUntil( caches.open(CACHE_NAME) .then(cache => cache.addAll(urlsToCache)) ); }); self.addEventListener('fetch', event => { event.respondWith( caches.match(event.request) .then(response => response || fetch(event.request)) ); });

此配置使客服后台在弱网环境下仍可快速加载,提升运维体验。

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

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

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

立即咨询