网页多商户客服系统whisper-v2.1.11:坐席隔离与多租户架构方案
发布时间:2026/9/28 5:35:50 作者:尧图编辑部 阅读量:1,286

简介网页多商户客服系统whisper-v2.1.11是一套面向多商户场景的在线客服解决方案适合需要为多个商家提供独立客服入口的网站运营者、开发者及二次开发团队使用可解决多租户环境下会话分配、坐席管理与消息互通等问题。压缩包共1890个文件整体约27.85MB以801个php源码文件为核心配合215个js脚本、59个css样式与97个html页面构成前后端主体另有269个gif、123个png、51个jpg等图片资源及14个ttf、7个woff字体文件支撑界面展示并包含81个asciidoc与30个md文档、26个json配置、2个sql与2个db数据库文件便于理解系统结构与部署。目前已有1138人学习下载。资源内含完整的客服系统源码与文档说明目录按模块划分清晰读者可据此研究多商户客服的架构设计、接口调用与前端交互实现也可作为搭建或二次开发同类客服平台的参考基础。1. 网页多商户客服系统 whisper-v2.1.11一套代码支撑 N 个商户的坐席隔离方案如果你手头有一套面向多个商户的网页客服系统大概率遇到过这种局面A 商户的坐席能看到 B 商户的会话列表或者某个商户的访客消息串进了别人的工作台。whisper-v2.1.11 这个版本号背后核心要解决的就是多租户场景下的数据隔离与坐席调度问题。它不是单纯的聊天窗口而是一套把「商户—坐席—访客」三层关系管起来的服务端加前端方案。适合谁看正在做 SaaS 客服、需要给不同商户分配独立坐席组、又不想为每个商户单独部署一套服务的后端和全栈工程师。读完你能拿到一套可复现的隔离模型、关键参数配置和几个容易翻车的边界处理。2. 多商户隔离模型从数据表到 WebSocket 房间的三层映射2.1 商户、坐席、访客的实体关系怎么定多商户客服系统最容易踩的坑是把「商户」当成一个简单的字段塞进会话表就完事。实际落地时至少需要四张核心表商户表tenant、坐席表agent、访客会话表session、消息表message。关键在于 session 表必须同时持有 tenant_id 和 agent_id而 message 表只挂 session_id不直接挂 tenant_id——这样消息的归属通过会话间接确定避免消息表膨胀出冗余的租户字段。我一般会这样建表CREATE TABLE tenant ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64) NOT NULL, status TINYINT DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE agent ( id BIGINT PRIMARY KEY AUTO_INCREMENT, tenant_id BIGINT NOT NULL, name VARCHAR(32) NOT NULL, online_status TINYINT DEFAULT 0, max_concurrent INT DEFAULT 5, INDEX idx_tenant (tenant_id) ); CREATE TABLE session ( id BIGINT PRIMARY KEY AUTO_INCREMENT, tenant_id BIGINT NOT NULL, agent_id BIGINT DEFAULT NULL, visitor_key VARCHAR(64) NOT NULL, status TINYINT DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_tenant_status (tenant_id, status), INDEX idx_agent (agent_id) ); CREATE TABLE message ( id BIGINT PRIMARY KEY AUTO_INCREMENT, session_id BIGINT NOT NULL, sender_type TINYINT NOT NULL, content TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_session (session_id) );逻辑说明tenant_id 在 session 层做第一道隔离agent 表通过 tenant_id 限定坐席归属。message 不冗余 tenant_id 是为了写入性能查询时通过 session_id 关联即可。参数上max_concurrent 控制单个坐席同时接待的访客数默认 5 是经验值超过 8 个并发会话人工坐席的响应质量会明显下降。2.2 WebSocket 房间命名与消息路由网页客服的实时性靠 WebSocket但多商户场景下不能所有连接都进同一个广播房间。常见做法是用「tenant_{tenantId}agent{agentId}」作为坐席侧房间名「tenant_{tenantId}visitor{visitorKey}」作为访客侧房间名。消息路由时服务端先根据 session_id 查出 tenant_id 和 agent_id再定向推送到对应房间。// 服务端消息路由伪代码 function routeMessage(sessionId, payload) { const session getSessionById(sessionId); if (!session) return; const tenantRoom tenant_${session.tenant_id}; const targetRoom session.agent_id ? ${tenantRoom}_agent_${session.agent_id} : ${tenantRoom}_visitor_${session.visitor_key}; // 只向目标房间推送禁止全局广播 wsServer.to(targetRoom).emit(message, payload); }参数说明session.agent_id 为空表示访客还未被分配坐席此时消息只回给访客自己同时触发分配逻辑。注意不要用 wsServer.emit 全局广播否则 A 商户的消息会漏给 B 商户的所有连接。这个错误在开发环境不容易发现因为本地测试往往只有一个商户。2.3 坐席分配策略与租户级排队分配策略直接决定多商户系统好不好用。我一般会实现三种模式轮询、最少会话优先、指定坐席。轮询适合坐席能力平均的团队最少会话优先适合老带新的混合组指定坐席用于 VIP 商户的专属客服。分配时必须在 tenant_id 范围内筛选坐席不能跨商户。def assign_agent(tenant_id, session_id): agents get_online_agents(tenant_id) if not agents: return None # 过滤掉已达并发上限的坐席 available [a for a in agents if a.current_count a.max_concurrent] if not available: enqueue_waiting(tenant_id, session_id) return None # 最少会话优先 chosen min(available, keylambda a: a.current_count) bind_session_to_agent(session_id, chosen.id) return chosen.id逻辑说明get_online_agents 必须带 tenant_id 参数这是隔离的关键。enqueue_waiting 把会话放入该商户的等待队列不同商户的队列互相独立。参数上max_concurrent 建议按商户配置而不是全局统一因为不同商户的访客消息频率差异很大。3. whisper-v2.1.11 的部署与核心配置项3.1 服务端启动与多租户配置加载whisper-v2.1.11 这类系统通常由 Node.js 服务端加前端 SDK 组成。部署时第一步是把租户配置从硬编码里拿出来放到数据库或配置中心。启动时加载所有 active 状态的商户建立各自的 WebSocket 命名空间。# 安装依赖 npm install # 配置环境变量 export DB_HOST127.0.0.1 export DB_PORT3306 export DB_NAMEwhisper export WS_PORT9101 export TENANT_CACHE_TTL300 # 启动服务 node server.js --config ./config/tenant.json参数说明TENANT_CACHE_TTL 控制商户配置的缓存时间默认 300 秒。如果商户信息变更频繁可以降到 60 秒但会增加数据库查询压力。WS_PORT 是 WebSocket 监听端口注意和 HTTP 端口区分开避免反向代理配置冲突。3.2 前端 SDK 接入与商户标识传递前端接入时访客侧需要携带 tenant_key 或 tenant_id 来标识自己属于哪个商户。常见做法是在页面初始化时从 URL 参数或后端渲染的全局变量中读取。// 访客侧初始化 const whisper new WhisperClient({ wsUrl: wss://your-domain/ws, tenantKey: window.__TENANT_KEY__, visitorKey: getCookie(visitor_key) || generateVisitorKey(), onMessage: (msg) renderMessage(msg), onAgentJoin: (agent) showAgentInfo(agent) }); whisper.connect();逻辑说明tenantKey 必须由服务端注入不能从用户输入里直接取否则会出现越权连接。visitorKey 首次生成后写入 Cookie保证刷新页面后会话不丢失。onAgentJoin 回调用于在界面上显示坐席昵称和头像提升访客体验。3.3 坐席工作台的会话列表隔离坐席工作台是最容易暴露隔离问题的地方。会话列表接口必须强制带 tenant_id 过滤且这个 tenant_id 从坐席的登录态里取不能从前端参数取。// 坐席侧获取会话列表 app.get(/api/agent/sessions, authMiddleware, async (req, res) { const tenantId req.agent.tenant_id; // 从 token 解析不信任前端传参 const sessions await db.query( SELECT * FROM session WHERE tenant_id ? AND status IN (0,1) ORDER BY created_at DESC, [tenantId] ); res.json({ code: 0, data: sessions }); });参数说明authMiddleware 负责解析坐席 token 并挂载 agent 对象。status IN (0,1) 表示只拉取等待中和进行中的会话已结束的不展示。注意这里如果写成 req.query.tenant_id就等于把隔离交给了前端属于典型翻车点。4. 避坑与排查多商户客服系统最常见的 5 个问题4.1 现象A 商户坐席收到 B 商户的访客消息原因WebSocket 房间命名没有带 tenant_id或者消息路由时用了全局广播。解决检查房间名生成函数确保 tenant_id 是房间名的前缀检查推送逻辑禁止使用全局 emit。4.2 现象访客刷新页面后会话丢失重新分配了新坐席原因visitorKey 没有持久化或者服务端没有根据 visitorKey 复用已有 session。解决访客侧用 Cookie 或 localStorage 存 visitorKey服务端在创建 session 前先按 tenant_id visitor_key 查询是否已有未结束会话。4.3 现象坐席并发数超限系统仍然继续分配新会话原因max_concurrent 的判断逻辑放在了分配之后或者 current_count 没有实时更新。解决把并发检查前置到分配函数入口每次会话绑定和解绑时同步更新坐席的 current_count建议用数据库行锁或 Redis 原子操作。4.4 现象商户配置更新后部分坐席仍然看到旧配置原因TENANT_CACHE_TTL 设置过长或者缓存没有主动失效机制。解决商户配置变更时主动清除对应 tenant_id 的缓存将 TTL 降到 60 秒作为兜底。4.5 现象消息时间戳在不同商户间显示不一致原因服务端用本地时间或者前端直接用了客户端时间。解决消息的 created_at 统一由服务端生成并存储 UTC 时间前端根据浏览器时区做展示转换。不要信任客户端传来的时间戳。5. 进阶技巧用租户级限流和消息回执把系统做稳多商户客服系统上线后最先扛不住的不是数据库而是某个商户的访客消息突发流量把整个 WebSocket 服务打满。我一般会在消息入口加一层租户级限流每个 tenant_id 每秒最多处理 N 条消息超过的进入该商户的缓冲队列而不是直接拒绝。const rateLimiter new Map(); // tenant_id - { count, windowStart } function checkTenantRate(tenantId, limit 200) { const now Date.now(); const record rateLimiter.get(tenantId) || { count: 0, windowStart: now }; if (now - record.windowStart 1000) { record.count 0; record.windowStart now; } record.count; rateLimiter.set(tenantId, record); return record.count limit; }参数说明limit 默认 200 条/秒适合中小型商户。如果某个商户的访客量特别大可以单独配置更高的阈值但要在网关层做总入口限流防止单个商户耗尽全局资源。另一个值得做的细节是消息回执。访客发出消息后服务端在写入数据库成功后返回一个 ack前端收到 ack 才把消息标记为「已发送」。如果 3 秒内没收到 ack前端显示重试按钮。这个机制在多商户场景下尤其重要因为不同商户的网络环境和消息量差异很大没有回执的话访客会反复点击发送导致消息重复。配置项建议值说明租户级限流200 条/秒按商户单独配置消息回执超时3000ms超时显示重试坐席最大并发5按商户可调会话空闲回收30 分钟超时自动结束配置缓存 TTL60-300 秒变更频繁取低值最后说一个我踩过的坑早期版本里我把租户配置直接写在了前端代码里结果每次新增商户都要重新构建前端。后来改成服务端注入 window.TENANT_KEY前端只认这个变量新增商户只需要在数据库加一条记录。这个改动不大但让整个系统的可运维性上了一个台阶。希望帮到你。本文还有配套的精品资源点击获取