尧图精选

多商户在线客服系统PHP源码架构与WebSocket实时通信实现解析

🕒 发布时间:2026/8/31 22:07:08 📁 来源:尧图网络
简介这是一套面向中小企业与开发者的新一代PHP多商户在线客服系统源码专为解决电商平台、SaaS服务或多租户场景下的统一客服接入与分权管理需求而设计。资源包含2000个文件涵盖140个核心PHP后端逻辑文件、549个JS交互脚本、316个HTML页面模板、204个CSS样式资源及4个SQL数据库结构文件前端采用AmazeUI、Bootstrap与Layer等主流框架构建响应式界面后端依托Composer依赖管理与.htaccess路由控制实现商户子域名隔离。压缩包体积36.89MB结构清晰public目录承载静态资源application与config模块分离业务与配置version.json、domain.json等JSON文件支持版本追踪与多域名适配配套的安装说明.docx文档提供从环境部署到数据库初始化的全流程指引。目前已有184人学习下载适合具备基础PHP与MySQL能力的中初级开发者快速二次开发或私有化部署。1. 项目概述与整体思路拆解1.1 多商户客服到底解决什么问题先别急着看代码我们把多商户在线客服系统这几个字拆开理解一下。普通的单商户客服系统只要解决一件事访客和客服之间能聊起来消息不丢、不乱序、不掉线就万事大吉。但一旦加上多商户三个字复杂度立刻上了一个台阶。你想一想一个平台方要同时服务几十上百家企业每家企业都有自己的客服团队、自己的访客群体、自己的对话记录甚至可能要求客服账号只能看到自己商户的会话访客打开聊天窗口时也只能连接到归属商户的客服队列——这就是多商户客服系统的核心难点。从实际使用场景来看这类系统的需求方通常是客服SaaS平台、电商代运营公司、多品牌商城后台甚至是一些做聚合服务的企业。他们需要一个能统一管理所有商户客服能力的后台同时让每个商户在使用时感觉自己是独立的。源码级别的实现意味着你可以根据业务需求深度定制比如修改分配逻辑、调整消息推送方式、对接自己的CRM系统而不是被困在商业软件的固定功能里。我最早接触这类项目时犯过一个认知错误以为把单商户代码复制几套、加个商户ID字段就算多商户了。实际上真正的多商户要考虑的是数据隔离级别、权限边界、资源分配策略和会话路由规则。这些问题如果在架构设计阶段没想清楚后期改起来会非常痛苦。这篇文章就围绕这套PHP源码从设计思路、核心机制、代码实现到部署上线把整个链路完整拆一遍。1.2 这套源码的适用人群和技术选型考量先说适合谁来学习参考。如果你是PHP开发者想自己搭建一个内部的客服支撑系统或者正在做一个SaaS化的客服产品雏形这份源码的参考价值很大。如果你是非技术背景的站长或小企业主想直接部署一套客服系统给网站用这篇文章里的部署步骤也能帮你完成上线。但坦白讲我建议你至少知道PHP和MySQL的基本概念否则后面遇到问题排查时会比较吃力。再说技术选型。PHP做在线客服系统很多人的第一反应是PHP不是同步阻塞模型吗怎么做实时聊天。这个质疑有一定的道理但并不是无解。PHP生态里有Swoole扩展和Workerman框架它们让PHP也能跑常驻内存的异步网络服务WebSocket服务端完全可以用PHP写。这套源码的架构思路本质上就是传统PHP业务层提供API、管理后台 Swoole/Workerman长连接层处理实时消息推送 MySQL持久化会话和消息记录三方协作。选PHP而不是Go或Node.js来写服务端原因很实际团队技术栈好维护、市场上PHP开发资源多、与现有的网站后台大部分也是PHP无缝集成。如果你的业务量没到百万级并发PHP这套方案完全撑得住。我在测试环境里用Workerman起WebSocket服务单进程扛几千个连接没有压力真实部署时再按CPU核心数开多进程一般企业场景绰绰有余。当然如果你的目标是做成千万用户量级的客服平台那还是要考虑Go或Java的架构但这个就超出这篇源码的范围了。2. 核心功能拆解与数据模型设计2.1 客服系统的核心业务闭环一个标准的在线客服系统业务闭环其实可以拆成六步访客发起会话、系统分配客服、双方收发消息、客服转接/结束会话、访客提交评价、数据统计分析。多商户版在此基础上增加了一条主线所有这些操作都要被限定在某个商户的上下文里。从访客视角看他打开的是商户A网站上的聊天窗口那么他提交的姓名、联系方式、IP、浏览页面都归属到商户A下。从客服视角看他登录的是客服工作台工作台只会拉取属于本商户的会话列表和消息。从平台管理员视角看他可以查看全部商户的数据但通常情况下也不能直接代替商户的客服回复消息——这是权限边界问题。身份认证和token体系是这里的关键。访客在商户网站上初始化时系统需要生成一个会话凭证我习惯把它叫做visitor_token这个凭证要包含商户标识、访客唯一标识、会话入口来源比如是从哪个商品页发起的咨询。客服登录后拿到的是另一个凭证agent_token包含客服账号、所属商户ID、角色权限。两个凭证体系通过会话ID关联起来但彼此的权限范围是严格分离的。这样设计的好处是即使访客token泄露也只能模拟访客身份发起会话无法读取历史会话列表或冒充客服客服token泄露也只能操作自己权限范围内的会话无法越权看到其他商户的数据。我见过不少早期客服系统把两个身份混在一个凭证里结果出现商户A的客服能搜到商户B的客户记录的严重事故这个坑在架构设计时就要避开。2.2 数据表设计与隔离策略多商户系统最重要的就是数据隔离而这个隔离是从数据库表结构设计开始的。我会把核心表分成三类平台级表、商户级表、会话级表。平台级表包括管理员账号表、系统配置表、套餐/权限表商户级表包括商户信息表、商户客服账号表、商户客服组表会话级表包括会话表、消息表、访客信息表、评价表。重点说一下会话表和消息表的字段思路。会话表至少要包含这些字段会话唯一ID、商户ID、客服组ID、当前接待客服ID、访客标识、访客名称、来源页面、会话状态待接入/进行中/已结束、创建时间、结束时间、满意度评分。消息表则包含消息ID、会话ID、发送者类型访客/客服/系统、发送者ID、内容类型文本/图片/商品卡片、消息内容、创建时间。这里的商户ID字段在会话表里必须建索引因为所有关联查询都要通过商户ID加状态来过滤数据没有索引的查询在数据量上来之后会非常慢。关于消息内容的存储我有一些经验要分享。纯文本消息直接用TEXT字段存没问题但如果是图片、语音、视频这类消息数据库里只存URL和缩略图地址文件本身要上传到独立的存储服务本地目录、阿里云OSS或者MinIO都可以。此外WebSocket推送的是实时消息但消息落地到MySQL必须有异步机制。我见过有的系统直接在消息处理回调里写数据库结果高并发时MySQL连接池被打满连锁反应是WebSocket服务也卡死。合理的做法是消息先推到内存队列Swoole的channel或Redis的list由单独的消费者进程批量写入数据库这样读性能和写性能都能兼顾。2.3 访客接入与商户路由的细节实现访客在商户网站上点击在线咨询按钮看起来是一个很简单的动作背后其实发生了这么一串事情。首先前端脚本会去请求后端的一个初始化接口传参包括当前商户的唯一标识我习惯叫merchant_key是一个对外公开但不可猜测的字符串和来源页面URL。后端收到请求后需要做几件事校验商户key是否有效、商户的客服服务是否启用、生成或复用该访客的标识、创建或复用当前会话、返回WebSocket连接地址和会话凭证。这里有个容易踩的坑一个访客在同一家商户网站的多个页面来回跳转时不应该每次都创建新会话否则会造成会话碎片化客服后台会看到一堆只有一句你好的僵尸会话。通常的做法是用Cookie存访客的唯一标识比如叫visitor_id有效期可以设30天。访客再次进入时先通过visitor_id查最近是否有未结束的会话有就直接复用没有才创建新会话。会话恢复机制也要做好。访客的浏览器从页面A跳到页面B再返回聊天窗口重新初始化后要能把之前的聊天记录拉回来。这个场景下前端需要保存会话ID并对会话中的消息做一次增量同步。还有一个细节是跨设备的场景如果访客在手机上聊了一半又去电脑上打开同一家商户网站要能继续同一段会话。做到这一点需要在登录态如果商户网站有自己的会员体系里绑定visitor_id或者通过手机号验证等辅助手段关联访客身份。3. 长连接通信机制与消息推送实现3.1 轮询、长轮询与WebSocket的取舍在线客服系统绕不开的核心技术点就是消息推送。我梳理一下常见的三种方案以及这套源码为什么选取了WebSocket作为主要通信方式同时保留了HTTP轮询作为降级方案。第一种是前端定时轮询短轮询。前端每隔2秒或3秒向后端发起一次HTTP请求查询有没有新消息。这个方案实现最简单但问题是浪费严重假设一个访客开着聊天窗口但5分钟没说一句话这5分钟内的几十次请求全部是无效请求数据库被白白查了几十次。如果在线访客有500人服务器的QPS直接多出几千纯粹是无意义开销。第二种是长轮询。前端发起请求后服务端如果有新消息就立即返回没有就挂起连接等有新消息时再返回前端收到后立刻发起下一次请求。这个方案比短轮询好一些但服务端长连接的数量依然受限于Web服务器进程数Nginx默认的worker连接数配置不好很快就会被占满。第三种就是WebSocket。它是真正的全双工通信客户端和服务端建立一次TCP连接之后双方可以随时互发消息没有HTTP请求头的重复开销。对于聊天这种高频交互场景WebSocket在实时性和资源占用上都是最优解。PHP生态里用Swoole或Workerman可以很轻松地实现一个WebSocket服务端。这套源码的后端消息层我推荐用Workerman的web-msg-sender组件在网上可以找到对应的文档和示例它专门用来做WebSocket消息推送接入成本很低。需要特别提醒的是浏览器端连接WebSocket使用的是ws协议如果站点本身是用HTTPS的生产环境不是HTTPS也建议加上那么WebSocket必须用wss协议否则浏览器会拦截这个不安全的连接。这个通常需要在Nginx层配置SSL终止再把wss请求转发到PHP的WebSocket端口证书就用站点已有的SSL证书。3.2 WebSocket服务端的连接管理与会话绑定接下来是这一章最核心的部分当访客或客服通过WebSocket连上来之后服务端怎么管理这些连接以及消息怎么准确推送给目标用户。我先给出一个关键的代码思路模拟的是在Workerman环境中实现连接与用户绑定的过程。每次客户端建立WebSocket连接时可能会带着一个token参数服务端在onConnect事件里解析token得到用户ID访客ID或客服ID和用户类型然后把这个连接映射关系存到一个全局数组或Redis哈希里。映射关系大概是这样的访客ID到connection对象、或者客服ID到connection对象的对应关系。聊天业务里需要做的是当收到访客发来的消息时从消息里提取出目标客服ID从映射表里找到这个客服对应的connection直接$connection-send()把消息推过去。如果客服不在线就把这条消息存到离线消息表等客服上线后拉取未读消息。这个映射表的维护有几个细节。连接异常断开时要在onClose事件里及时清理映射关系否则会出现明明客服已经关掉工作台消息还往旧连接上推的问题表现为客服这边收不到消息而服务端日志显示消息已推送成功。同时要处理掉线检测WebSocket没有自带的心跳机制如果客户端网络断开但TCP连接没有及时释放比如手机切换Wi-Fi导致网络中断服务端可能一直以为连接是正常的结果消息推过去石沉大海。解决方案是客户端每隔30秒发一个ping帧服务端在60秒内没收到这个连接的任何数据就主动断开它并清理映射。这套心跳检测逻辑是所有WebSocket系统必备的保命机制。推送给客服的消息格式也需要设计。客服工作台可能同时挂多个会话窗口所以服务端推送消息时不能只推一个裸字符串需要带上会话ID、发送者信息、消息类型等字段。我常用来处理的MQTT协议里的topic概念同理可以把客服ID当成一个频道把会话ID当成一个子主题前端拿到消息后通过会话ID定位到具体的聊天窗口并渲染消息气泡。前端收到一条消息时的处理流程通常是判断所属会话是否存在本地缓存不存在就创建一个新的会话卡片存在就往对应会话的消息列表里追加显示并更新会话列表的排序和未读数。3.3 客服分配路由与离线消息补偿访客发来第一句话时系统需要决定这个会话由哪个客服来接待。传统做法是轮流分配轮询也就是不管客服忙闲按顺序把访客轮流分配给在线客服。稍微好一点的做法是按当前接待量分配负荷最低的客服优先接收新会话。再好一点的做法是结合客服的技能组和访客的来源页面做路由比如来自售前页面的咨询分配给销售组客服来自售后页面的分配给售后组客服。这套源码里我更推荐实现一个简单的按空闲量优先级的分配算法。具体思路是查询当前商户里所有在线客服过滤掉已达到最大接待数上限的客服剩下的按当前接待会话数从低到高排序如果接待数相同则按客服等级高级客服优先排序把会话分配给排名最靠前的客服。这里要注意并发问题两个访客同时发起会话可能同时查到同一个空闲客服导致一个客服被分配了两个新会话。解决方法是使用Redis的原子操作或者数据库的悲观锁来标记这个客服正在被分配的状态分配完成后释放。我在实际项目中还遇到过客服队列里只有一个人在线但该客服的最大接待数已经满了这时候应该让访客进入排队等待状态前端显示当前排队人数较多请耐心等待而不是报错。然后是离线消息补偿。现实中客服不可能7x24小时挂在工作台上访客凌晨发起咨询时如果没有客服在线消息不能丢。常规方案是访客发来的消息在无客服在线时直接写入会话表和消息表会话状态标记为待接入。客服第二天登录工作台后系统自动拉取所有状态为待接入的会话和对应消息列表以会话卡片的形式展示在界面上客服点击后即可回复。这看起来不难但有个细节要注意会话状态要从待接入切换到进行中时要确认当前没有其他客服已经接管了这个会话。这里可以用一个简单的CASCompare And Set操作比如UPDATE session SET agent_id?, statusongoing WHERE session_id? AND statuspending受影响行数为1才表示接管成功这样能避免多个客服同时抢同一会话的问题。4. 服务端接口设计与前端工作台实现4.1 面向访客和客服的两套API设计在线客服系统的前后端交互不是只有一个聊天接口而是要拆成访客端API和客服端API两套体系分开独立设计、独立鉴权。访客端API的核心接口大概有六个初始化连接获取访客标识和会话凭证、发送消息、拉取历史消息、标记会话结束、上传文件图片、提交满意度评价。这些接口全部使用访客会话token鉴权token在初始化时由后端签发有效期可以和会话绑定的时间一致。客服端API要复杂一些包含登录认证、获取当前客服的会话列表、获取某个会话的详细消息记录、发送消息回复访客、接管待接入会话、转接会话给其他客服、结束会话、修改个人在线状态、获取个人统计数据。这些接口使用客服token鉴权而且Token里要包含商户ID每个接口在服务端都要校验当前登录客服的商户ID和请求路径中的商户ID是否一致。例如客服A所属商户ID是10他请求获取会话ID是3012的数据时服务端必须校验该会话的merchant_id也是10否则直接返回403。这个权限校验逻辑可以通过中间件统一实现避免每个接口重复写。4.2 客服工作台的消息实时收发体验客服工作台是客服人员每天要面对的核心界面做好这个界面的关键不只是能收发消息还要考虑到客服同时处理多会话的工作场景。我的设计思路参考了市面上成熟客服产品的布局左侧是会话列表按最新消息时间排序未读会话置顶并显示未读数中间是当前会话的聊天消息区域采用气泡式布局访客消息在左客服回复在右右侧是访客信息面板显示访客姓名、来源页面、当前浏览页面、会话开始时间等。顶栏要有当前登录客服的状态切换在线/忙碌/离线底部是消息输入框和快捷回复面板。消息实时收发这一块前端主要处理三个事件连接成功上线、收到新消息、发送消息回执。用户输入文字后点击发送前端的流程是先把消息以临时状态渲染到聊天区域然后通过WebSocket发送消息到服务端服务端返回一个ack回执包含服务端生成的消息ID和入库时间前端拿到回执后把临时消息状态更新为已发送。如果发送失败或超时前端把消息状态变为发送失败提供点击重发按钮。没有这个回执机制在弱网环境下用户会看到消息发出去后自己消失了体验非常差。还有一个小细节多页面协同。客服可能同时开着浏览器工作台和手机工作台他在手机端回复了一条消息电脑端的聊天窗口要能实时同步。这个场景不需要每个页面都建立一条独立的WebSocket连接只需要服务端在收到一条新消息时做一次广播到该客服的所有在线设备的操作前端每个设备各自监听并更新消息列表。同一客服多设备登录时每个设备都存一份连接映射就可以实现这个效果。4.3 访客侧聊天窗的接入脚本与定制化访客侧聊天窗要嵌入到商户自己的网站上为了方便商户安装最合理的做法是提供一段JavaScript接入代码。商户只需要把这段代码粘贴到自己网站的HTML底部就能显示一个可拖动的聊天按钮和聊天窗口浮层。我建议把接入脚本设计成支持配置的比如配置商户的唯一标识merchant_key、聊天窗口标题、主题色、默认欢迎语、自动弹出延迟时间等。另外建议支持两种触发方式点击按钮弹出窗口、滑动一定距离自动弹出窗口。这里我给出一个常见的接入脚本初始化示例框架方便你理解整个流程(function() { var config { merchant_key: YOUR_MERCHANT_KEY, title: 在线客服, theme_color: #1890ff, welcome_text: 您好请问有什么可以帮您 }; // 加载样式和DOM结构 // 初始化访客身份并连接WebSocket // 渲染聊天窗口浮层 // 监听消息事件和发送操作 })();前端聊天窗在发消息之前要先确认WebSocket已经连接成功如果没连上就先把消息暂存在本地队列里等连接建立后自动补发。这样即使用户在弱网环境下快速输入了好几句话也不会丢失。聊天窗口还要支持显示消息发送状态、图片预览、客服正在输入的状态提示。这个正在输入提示要在前端做防抖比如用户停止输入1.5秒后才发送输入状态事件否则几毫秒发一个事件服务端和消息队列都得被刷爆。5. 系统部署与运行环境配置5.1 服务器环境准备与软件安装接下来我们进入实战部署环节。在开始之前我需要明确一下这套源码的运行环境大概需要这几个组件PHP 7.4或以上版本建议PHP 8.0或8.1、MySQL 5.7或以上、Redis 5.0或以上、Nginx、Composer、以及Swoole扩展或Workerman。PHP版本这里我特别强调一下不要用PHP 5.x了很多依赖库已经不支持旧版本而且PHP 8.0对性能和类型系统都有明显提升跑客服系统这种长连接服务会更稳。服务器建议最低配置是2核4G内存。这个配置能支撑什么水平呢我实测下来在2核4G的云服务器上单机部署PHP-FPM加Workerman的WebSocket服务同时保持在线连接数在3000到5000时CPU和内存都还有余量。如果你的业务量更小比如同时在线客服不到20人、访客不到500人1核2G的机器也能跑但建议还是留点余量避免高峰期内存打满导致OOM。PHP环境我用一个典型的LNMP一键脚本或Docker Compose来部署。如果你手头有已经装好的PHP环境和MySQL直接跳过这一步。如果没有我建议用宝塔面板或者腾讯云/阿里的镜像市场自带的LNMP环境省去手动编译Nginx和PHP的时间。装好环境后还需要给PHP装上必要的扩展其中pcntl和posix扩展是Workerman跑多进程时必需的这两个默认就是启用的。需要另外确认的是Redis扩展用于Session存储和消息队列。如果用的是Swoole方案还需要安装Swoole扩展并开启openssl和sockets选项。我自己在做这个项目时用的是Docker Compose来管理环境几个服务分别跑在容器里方便以后迁移和扩容。下面是一份极简的docker-compose.yml配置示例供你参考version: 3 services: nginx: image: nginx:1.24-alpine ports: - 80:80 - 443:443 volumes: - ./www:/var/www/html - ./nginx/conf.d:/etc/nginx/conf.d depends_on: - php php: image: php:8.1-fpm volumes: - ./www:/var/www/html depends_on: - mysql - redis mysql: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: rootpass MYSQL_DATABASE: kefu volumes: - dbdata:/var/lib/mysql redis: image: redis:6.2-alpine workerman: build: ./workerman volumes: - ./www:/var/www/html depends_on: - redis volumes: dbdata:5.2 Nginx和WebSocket的联调配置部署中最容易出问题的一个环节就是Nginx和后端WebSocket服务之间的转发关系。这里我展开说一下。假设PHP的WebSocket服务Workerman监听在服务器的8282端口Nginx负责对外提供HTTP和HTTPS服务。访客的浏览器发起wss://yourdomain.com/wss连接请求时Nginx需要识别出这是一个WebSocket升级请求然后把它反向代理到后端的127.0.0.1:8282端口。Nginx的关键配置如下map $http_upgrade $connection_upgrade { default upgrade; close; } server { listen 443 ssl http2; server_name yourdomain.com; ssl_certificate /path/to/your/cert.pem; ssl_certificate_key /path/to/your/privkey.pem; # HTTP API 入口 location ~ ^/api/ { proxy_pass http://127.0.0.1:9000; # PHP-FPM include proxy_params; } # WebSocket 入口 location /wss { proxy_pass http://127.0.0.1:8282; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_set_header Host $host; proxy_read_timeout 300s; } }需要注意几个参数proxy_read_timeout不要设太短我建议设置300秒以上否则长时间没有消息往来时Nginx可能会主动断开这个连接。前端的心跳机制保证连接一直在活跃但Nginx的读写超时仍然要设置得大一些防止中间网络设备把空闲连接杀掉。另外如果用了负载均衡器比如阿里云SLB也要检查负载均衡层是否支持WebSocket的upgrade头不支持的话就要用TCP四层转发的方式把8282端口直接暴露出去。5.3 WebSocket服务进程的启动和守护用Workerman实现的服务端需要以CLI模式启动并且常驻后台运行。单纯在终端运行php start.php start的话关掉SSH终端服务就停了生产环境必须用守护进程的方式运行。我的建议是使用systemd来管理Workerman进程。写一个service文件比如[Unit] DescriptionKefu Workerman Service Afternetwork.target [Service] Userwww-data Groupwww-data WorkingDirectory/var/www/html/kefu/server ExecStart/usr/bin/php start.php start ExecStop/usr/bin/php start.php stop Restartalways RestartSec5 [Install] WantedBymulti-user.target执行systemctl enable kefu和systemctl start kefu之后服务就会开机自启崩了自动拉起。这里有一个血泪教训Workerman运行时会临时生成pid文件如果多个进程用同一个用户运行权限错乱会导致启动失败。我把服务设置为用www-data用户跑与PHP-FPM保持一致Web目录权限也统一用www-data整体会省去很多麻烦。进程数方面Workerman的count配置建议设置为CPU核心数不过也要看实际情况。比如2核CPU可以设置count4每个进程处理500个连接的话总共能扛2000个连接对大多数业务足够了。内存方面每个WebSocket进程默认要预留一些内存给PHP运行环境我建议进程数不要超过8个不然光Worker进程本身就能占用一两GB内存对小机器不友好。6. 常见问题排查与技术避坑6.1 消息推送到访客端总是延迟这是在线客服系统最常被反馈的问题。如果你发现访客发消息后客服工作台要好几秒才能看到或者反过来客服回复的消息访客半天才收到大概率要按下面的顺序排查。第一步检查是不是WebSocket连接断开了前端自动降级到了HTTP轮询模式。如果是这种情况浏览器控制台会直接输出相关日志网络请求里也会出现频繁的HTTP接口轮询。第二步检查服务端日志里有没有Redis操作超时的报错。消息推送过程中如果用到Redis作为订阅发布通道Redis阻塞会导致消息转发延迟。还有一个很容易被忽略的点如果服务器开启了swap分区且内存紧张PHP进程会发生大量内存交换性能会急剧下降典型表现就是连接正常但消息流转慢。这时候加内存比优化代码更有效。6.2 客服接收到的消息顺序错乱WebSocket本身是TCP协议消息在单条连接上是有序的但多进程场景下消息可能会经由不同进程写入MySQL时间戳相同或网络到达顺序不同就会导致前端拿到的消息列表顺序不对。我在实现中处理这个问题的方法是优先按消息ID排序而消息ID用数据库自增主键生成这样能保证按插入顺序排列。消息内容本身的时间字段只作为展示用途不作为排序依据。另外前端收到消息后要做本地排序合并不能只append到末尾否则并发到达时会出现新消息排在旧消息前面的情况。6.3 数据库连接数被打满的应急预案客服系统是典型的写多读多场景尤其是WebSocket推送和HTTP API共用一个数据库时高并发下MySQL连接数很容易被打满。MyISAM时代还曾经因为表锁导致整个会话表被锁死生产事故级别的问题。我给出的解决方案分三步。第一步PHP-FPM和Workerman各自独立使用不同的MySQL账号分别设置连接数上限避免一个服务异常把另一个拖死。第二步消息写库不要走同步操作要写进Redis队列后异步落库。第三步加一个MySQL慢查询监控把超过200毫秒的SQL日志单独记录定期排查。很多慢查询问题其实出在SQL没有走索引比如会话状态和商户ID的联合索引缺失。这里要特别提醒的是每次部署时把会话表的索引检查一遍尤其是商户ID状态、客服ID状态这两组联合索引漏掉哪个都会在实际使用中拖垮性能。6.4 常见问答速查表下面我把这套源码上线后最常见的几类问题汇总成一个速查表方便你有问题的时候对照排查症状可能原因解决思路访客无法建立WebSocket连接Nginx未配置Upgrade头转发检查Nginx配置中的$http_upgrade映射和proxy_set_header客服工作台登录后没有会话列表商户ID权限校验失败检查登录客服的商户ID与会话数据的merchant_id是否一致访客发了消息但客服未收到WebSocket连接掉线或客服上下线状态未同步检查客服连接映射关系是否维护正常确认Redis在线状态连接正常但消息延迟数秒Redis操作超时或PHP-FPM进程卡顿查看Redis慢日志检查服务器内存和swap占用情况客服A能看到商户B的会话数据权限处理不当紧急下线该客服账号检查后端所有查询是否都带商户ID条件图片或文件发送失败上传目录权限不对或存储路径配置错误检查上传目录写权限、后端返回的URL是否可访问、nginx上传大小限制欢迎语或自动回复未触发事件监听未设置或事件顺序错误检查访客初始化流程中的事件调用顺序和参数传递6.5 上线前必须做好的三件安全加固最后讲一下上线前要处理的几个安全性问题。第一个是SQL注入虽然现在框架和ORM已经内置了参数绑定功能但如果团队里有同事习惯写原生SQL要特别注意使用预处理。第二个是接口频率限制访客接口和客服接口都要做限流比如同一个IP在10秒内最多调用发送消息接口20次防止恶意刷消息把数据库打爆。Redis实现一个简单的滑动窗口计数器就能搞定。第三个是对上传文件严格限制格式和白名单检查只允许图片和少数可预览的文件类型文件名用随机字符串重命名不要用用户上传的原始文件名防止脚本文件被恶意上传后获得执行机会。这套源码我从初次接触、二次开发到实际部署前后折腾了差不多一个月时间。最大的感受是PHP做客服系统完全可行关键是把架构层的关系理清连接层归连接层业务层归业务层存储层归存储层别把它们揉在一起。最后再分享一个个人经验任何在线客服系统都一定要有完整的日志记录WebSocket的连接事件、消息收发事件、错误事件全部记录到日志文件或ELK平台。没有日志的话线上出了问题你连排查的抓手都没有只能看着前端界面干着急。建议从部署第一天就养成查日志的习惯。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →