客服通话
客服通话插件(Customer Service)为 Monibuca V6 提供 1v1 实时音视频客服 能力。适用于在线客服、远程咨询、售后支持等场景。
- 1v1 通话:每个会话固定 2 人(客服 + 用户),基于 Room 服务的
max_users: 2限制 - 免登录用户端:用户无需注册账号,通过链接直接进入通话
- 客服坐席管理:五态状态机(Offline/Online/Ready/Ringing/Busy),支持签入签出、就绪示忙
- 排队分配系统:访客排队 → FIFO 自动分配 → 坐席接听的完整工作台闭环
- 来电振铃:坐席收到来电通知,30 秒倒计时,支持音频/视频接听或拒接
- 通话转接:坐席可将通话转接给其他就绪坐席,用户不断线
- 满意度评价:通话结束后用户可进行 1-5 星评价,后台可查看评分统计
- 音频混音:客服端可将本地音频文件混入推流(如背景音乐、提示音)
- Admin 管理后台:完整的会话管理界面,支持创建会话、生成用户链接/二维码
features = ["customer-service"]
# 客服通话依赖 Room 服务# room feature 会自动启用graph TB subgraph CS["CustomerService Plugin"] SM["SessionManager<br/>会话管理(DashMap + SQLite)"] AP["AgentPool<br/>坐席池(Round-Robin)"] VQ["VisitorQueue<br/>排队队列(FIFO)"] RM["RingManager<br/>振铃管理"] HTTP["HTTP API<br/>/customer-service/*"] Signal["WebSocket 信令<br/>AcceptCall / EndCall / Transfer"] end
subgraph RS["Room Service (引擎内置)"] Room["房间管理<br/>max_users: 2"] WS["WebSocket<br/>信令通信"] end
subgraph Client["客户端"] Agent["客服端<br/>账号登录"] Customer["用户端<br/>免登录"] end
CS -->|"register_callbacks<br/>RoomType::CustomerService"| RS HTTP --> SM HTTP --> AP HTTP --> VQ Agent -->|"WebRTC (WHIP)"| Room Customer -->|"WebRTC (WHIP)"| Room Agent <-->|"WebSocket"| WS Customer <-->|"WebSocket"| WS会话生命周期
Section titled “会话生命周期”Waiting → Active → Ended| 状态 | 说明 |
|---|---|
Waiting | 会话已创建,等待客服/用户加入 |
Active | 双方均已加入,通话进行中 |
Ended | 通话已结束 |
Offline → Online → Ready → Ringing → Busy ↑ │ └──────────────────┘| 状态 | 说明 |
|---|---|
Offline | 未登录/已签出 |
Online | 已签入但未就绪(休息/小结中) |
Ready | 就绪,等待分配来电(调度目标) |
Ringing | 来电已推送,等待接听/拒接(默认 30 秒超时) |
Busy | 通话中 |
状态转换:
- 坐席注册 →
Ready - 分配来电 →
Ringing(30s 超时自动返回Ready,访客重新入队) - 接听 →
Busy - 通话结束 →
Ready - 拒接 →
Ready(访客重新入队) - 下线 →
Offline
- FIFO 队列:访客按进入顺序排队,最大队列长度可配(默认 200)
- 轮询分配:后台每 500ms 检查队列,使用 Round-Robin 算法选取就绪坐席
- 两阶段分配:出队 → 坐席进入 Ringing → 接听后正式建立通话
- 即时分配:访客加入时若有空闲坐席可跳过排队直接分配
- 超时清理:排队超时(默认 10 分钟)自动移除
HTTP API
Section titled “HTTP API”所有接口前缀为 /customer-service/。
| 方法 | 路径 | 说明 |
|---|---|---|
GET | /sessions | 获取会话列表 |
GET | /sessions/{id} | 获取会话详情 |
POST | /sessions | 创建新会话 |
DELETE | /sessions/{id} | 关闭/删除会话 |
POST | /sessions/{id}/assign | 手动分配客服坐席 |
POST | /sessions/{id}/transfer | 转接通话到其他坐席 |
POST | /sessions/{id}/rate | 提交满意度评价 |
GET | /sessions/{id}/rating | 查询会话评价 |
| 方法 | 路径 | 说明 |
|---|---|---|
POST | /agents/register | 注册坐席(上线就绪) |
POST | /agents/{id}/offline | 坐席下线 |
POST | /agents/{id}/available | 坐席设为就绪 |
POST | /agents/{id}/accept | 接听来电 |
POST | /agents/{id}/reject | 拒接来电 |
GET | /agents | 获取所有坐席列表 |
GET | /agents/available | 获取就绪坐席列表 |
GET | /agents/stats | 坐席池统计 |
| 方法 | 路径 | 说明 |
|---|---|---|
POST | /queue/join | 访客加入排队(有空闲坐席时直接分配) |
POST | /queue/leave | 访客离开排队 |
GET | /queue/status | 队列状态快照 |
GET | /queue/position/{visitor_id} | 查询排队位置 |
| 方法 | 路径 | 说明 |
|---|---|---|
GET | /stats | 综合统计(会话 + 坐席 + 队列 + 评分) |
GET | /stats/ratings | 评分统计(总数、均值、分布) |
GET | /stats/trend?days=7 | 每日会话趋势 |
逐接口详细说明
Section titled “逐接口详细说明”默认基地址:http://localhost:8180
1) 创建会话
Section titled “1) 创建会话”POST /customer-service/sessionsContent-Type: application/json请求体:
{ "title": "售后咨询", "customer_id": "cust_001", "customer_name": "张三"}| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
title | string | 是 | 会话标题 |
customer_id | string | 是 | 用户 ID |
customer_name | string | 否 | 用户展示名 |
1.1) 会话列表
Section titled “1.1) 会话列表”GET /customer-service/sessions响应字段:
| 字段 | 类型 | 说明 |
|---|---|---|
sessions | array | 会话列表 |
count | int | 会话总数 |
1.2) 会话详情
Section titled “1.2) 会话详情”GET /customer-service/sessions/{id}| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | string | 是 | 会话 ID |
响应(示例):
{ "session_id": "cs_abc123", "status": "waiting", "created_at": "2026-05-07T19:05:00+08:00"}2) 坐席注册
Section titled “2) 坐席注册”POST /customer-service/agents/registerContent-Type: application/json请求体:
{ "agent_id": "agent_001", "agent_name": "客服小王"}2.1) 坐席列表与就绪列表
Section titled “2.1) 坐席列表与就绪列表”GET /customer-service/agentsGET /customer-service/agents/availableGET /customer-service/agents/stats3) 访客排队
Section titled “3) 访客排队”POST /customer-service/queue/joinContent-Type: application/json| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
visitor_id | string | 是 | 访客 ID |
visitor_name | string | 否 | 访客名称 |
preferred_skill | string | 否 | 指定技能组(可选) |
排队响应可能是 assigned(直接分配)或 queued(进入队列)。
3.1) 查询排队状态/位置
Section titled “3.1) 查询排队状态/位置”GET /customer-service/queue/statusGET /customer-service/queue/position/{visitor_id}| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
visitor_id | string | 条件必填 | 访客 ID(位置查询时必填) |
4) 转接会话
Section titled “4) 转接会话”POST /customer-service/sessions/{id}/transferContent-Type: application/json| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
target_agent_id | string | 是 | 目标坐席 ID |
5) 提交评分
Section titled “5) 提交评分”POST /customer-service/sessions/{id}/rateContent-Type: application/json请求体:
{ "rating": 5, "comment": "服务很好"}| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
rating | int | 是 | 1-5 分 |
comment | string | 否 | 文本反馈 |
6) 统计接口
Section titled “6) 统计接口”GET /customer-service/statsGET /customer-service/stats/ratingsGET /customer-service/stats/trend?days=7| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
days | int | 否 | 趋势天数,默认 7 |
| HTTP 状态码 | 场景 |
|---|---|
400 | 参数缺失或格式错误 |
404 | 会话/坐席不存在 |
409 | 状态冲突(如坐席非 Ready) |
429 | 队列超限或请求过频 |
快速调用示例
Section titled “快速调用示例”详细字段和状态码以“详细入参与返回格式”章节为准。
最小示例:
curl -X POST http://localhost:8180/customer-service/sessions \ -H "Content-Type: application/json" \ -d '{"title":"售后咨询","customer_id":"cust_001"}'WebSocket 信令
Section titled “WebSocket 信令”客服通话使用专用的 WebSocket 信令,通过 Room 服务的 on_message 回调透传到插件处理。
| Action | 方向 | 说明 |
|---|---|---|
accept_call | C→S | 客服接受通话 |
reject_call | C→S | 客服拒接来电 |
end_call | C→S | 任一方挂断通话 |
transfer_call | C→S | 客服转接至其他坐席 |
mute_user | C→S | 静音指定参与者 |
unmute_user | C→S | 取消静音 |
incoming_call | S→C | 来电通知(含 visitor_name, timeout_secs) |
ring_timeout | S→C | 来电超时通知 |
call_accepted | S→C | 通知对方已接听 |
call_ended | S→C | 通知通话已结束 |
call_transferred | S→C | 通知通话已转接 |
transfer_failed | S→C | 转接失败通知 |
agent_assignment | S→C | 坐席分配通知 |
{ "type": "control", "data": { "type": "AcceptCall" }}{ "type": "control", "data": { "type": "TransferCall", "data": { "target_agent_id": "agent_002" } }}customer_service: max_sessions: 1000 # 最大并发会话数 session_timeout_secs: 3600 # 会话超时时间(秒),默认 1 小时 max_queue_size: 200 # 最大排队人数 dispatch_interval_ms: 500 # 自动分配轮询间隔(毫秒) queue_timeout_secs: 600 # 排队超时时间(秒),默认 10 分钟 accept_timeout_secs: 30 # 来电接听超时时间(秒),默认 30 秒 auto_record: false # 是否启用通话录制 recording_dir: "/tmp/cs-recordings" # 录制文件目录WebSocket 连接
Section titled “WebSocket 连接”客户端通过标准的 Room WebSocket 连接加入客服通话:
ws://host:port/room?type=customer_service房间 ID 格式为 customer-service/{session_id},通过前缀区分不同的房间类型。
Web-SDK 集成
Section titled “Web-SDK 集成”Web Components
Section titled “Web Components”客服通话 Demo 基于 Monibuca Web-SDK 的 Web Components 构建:
<!-- 房间容器 --><mb-room id="room"></mb-room>
<!-- 本地推流 --><mb-publisher id="publisher"></mb-publisher>
<!-- 远端订阅 --><mb-subscriber id="subscriber"></mb-subscriber>const room = document.getElementById('room') as MbRoom;const publisher = document.getElementById('publisher') as MbPublisher;
// 设置 WebSocket 连接room.setAttribute('ws-url', 'ws://localhost:8180/room?type=customer_service');room.setAttribute('room-id', 'customer-service/session_001');
// 开始推流await publisher.startCapture({ audio: true, video: true });await publisher.startPublish();客服端支持将本地音频文件混入推流,例如背景音乐、等待提示音:
const publisher = document.getElementById('publisher') as MbPublisher;
// 混入本地音频文件publisher.mixAudio('bgm', '/audio/background-music.mp3', 0.3);
// 调整混音音量(0.0 ~ 1.0)publisher.setMixVolume('bgm', 0.5);
// 移除混音源publisher.unmixAudio('bgm');混音原理:
graph LR Mic["麦克风<br/>MediaStream"] --> GainA["GainNode<br/>麦克风音量"] BGM["音频文件<br/>MediaElement"] --> GainB["GainNode<br/>混音音量"] GainA --> Dest["Destination<br/>合成输出"] GainB --> Dest Dest --> WHIP["WHIPClient<br/>WebRTC 推流"]SDK 内部使用 Web Audio API 的 AudioContext 将多个音源(麦克风 + 混音文件)通过 GainNode 混合到同一个 MediaStreamAudioDestinationNode,最终输出到 WebRTC 推流。
通话状态管理
Section titled “通话状态管理”推荐使用状态机管理通话流程:
type CallStage = 'waiting' | 'connecting' | 'connected' | 'ended';
// waiting → 等待对方加入(显示等待界面、脉冲动画)// connecting → WebRTC 连接建立中// connected → 通话进行中(显示视频画面、计时器、控制栏)// ended → 通话结束(显示通话时长、满意度评价)Admin 管理后台
Section titled “Admin 管理后台”客服通话在 Admin 后台提供完整的管理功能。
- 自动就绪:坐席进入工作台后自动注册为就绪状态
- 实时仪表盘:排队人数、就绪坐席数、通话中坐席数实时展示
- 自动分配:系统自动分配访客,坐席无需手动操作即可进入通话
- 手动入口:保留手动输入会话 ID 的备用入口
- 通话控制:麦克风/摄像头开关、屏幕共享
- 通话转接:通话中点击”转接”按钮,选择就绪坐席执行转接
- 文字聊天:通话中的实时文字聊天面板
- 通话统计:码率、丢包率、RTT 实时展示
- 统计卡片:总会话数、通话中、等待接入
- 会话列表:支持状态筛选、搜索、批量操作、评价星级展示
- 创建会话:填写标题和描述,自动分配坐席
- 分配坐席:手动将会话分配给指定客服
- 生成链接:为用户/客服生成专属入口链接和二维码
- 平均评分:Dashboard 展示所有会话的平均满意度评分
- 评分分布:1-5 星的分布柱状图
- 会话列表评价列:每个已结束会话显示用户评价星级
房间上报统计
Section titled “房间上报统计”客服通话的房间数据通过 Room 服务的上报系统采集,在 Admin 中按 customer-service/ 前缀自动筛选展示。
Demo 项目
Section titled “Demo 项目”完整的客服通话 Demo 位于 web-sdk/packages/demo/customer-service/:
customer-service/├── src/│ ├── pages/│ │ ├── Lobby/index.tsx # 入口:角色选择│ │ ├── CallRoom/index.tsx # 通话:1v1 视频、控制栏、聊天、评价│ │ ├── AgentConsole/index.tsx # 坐席工作台:排队监控、来电弹窗、倒计时│ │ └── QueueWaiting/index.tsx # 用户排队等待页│ ├── services/api.ts # API 封装│ └── types/index.ts # 类型定义├── package.json # port: 5476└── vite.config.ts启动 Demo
Section titled “启动 Demo”cd web-sdk/packages/demo/customer-servicepnpm installpnpm dev# 访问 http://localhost:5476用户通过带参数的链接直接进入通话,无需登录:
http://localhost:5476?session=cs_abc123&role=customer客服需要账号密码登录后进入坐席工作台:
http://localhost:5476?session=cs_abc123&role=agent坐席登录后进入 AgentConsole 页面:
- 点击”上线”注册为就绪状态
- 实时显示排队人数和在线坐席数
- 收到来电时弹出全屏振铃面板(30 秒倒计时)
- 支持”视频接听”、“语音接听”、“拒接”三个操作
- 接听后自动跳转到 CallRoom 通话页面
Demo 自动检测设备类型,移动端采用:
- 上下分屏视频布局
- 底部固定控制栏(适配安全区域)
- 触摸优化的交互
联系我们