销售 PC 端在「启动外呼任务」时,若任务为 APP 外呼模式(callModel = 3 / 4 / 5)且已绑定设备,服务端会通过 同一条 WebSocket 长连接 向对应工作机推送 taskRun 消息。
cmd 区分类型。taskRun 后,根据 callModel 跳转或刷新对应外呼页面。| 服务 | 端口 | 说明 |
|---|---|---|
| fs-worker-app | 8008 | WebSocket + APP HTTP 接口 |
| fs-saas-company | 8006 | PC 端启动任务(taskRun) |
ws://{host}:8008/worker/webSocket/{meId}?tenantId={tenantId}
| 参数 | 说明 |
|---|---|
meId |
设备 IMEI,路径参数;须与登录 meId、主库 company_sms_device.imei 一致 |
tenantId |
租户 ID,查询参数,建议必传 |
示例:
ws://192.168.11.62:8008/worker/webSocket/8601234561212129012?tenantId=338
taskRun(无需 APP 回包)。所有消息均为 JSON 文本,通过字段 cmd 区分类型。
| 方向 | cmd |
说明 |
|---|---|---|
| APP → 服务端 | heartbeat |
心跳 |
| APP → 服务端 | deviceStatus |
主动查询设备状态(可选) |
| 服务端 → APP | heartbeat |
心跳回复 |
| 服务端 → APP | deviceStatus |
禁用/离线等状态变更 |
| 服务端 → APP | taskRun |
PC 启动任务推送 |
| 服务端 → APP | error |
处理异常 |
{
"cmd": "heartbeat",
"tenantId": 338,
"appVersion": "1.0.0",
"deviceName": "工作机A",
"phone1": "13800138000",
"phone2": "13900139000",
"imei": "卡1IMEI,卡2IMEI"
}
{
"cmd": "heartbeat",
"meId": "8601234561212129012",
"status": 1,
"msg": "ok",
"appVersion": "1.0.0",
"deviceName": "工作机A",
"timestamp": 1718534400000
}
status:0 离线 / 1 在线 / 2 禁用。
status === 2 时提示禁用并限制外呼。taskRun 单独建连接。taskRun(本次对接重点)PC 调用 GET /company/companyVoiceRobotic/taskRun?id={任务ID} 且满足:
callModel 为 3、4 或 5;deviceIds 非空(已绑定设备);不满足上述条件时 不会推送 taskRun。
PC taskRun (8006)
→ Redis PUBLISH channel: worker:ws:task:run
→ fs-worker-app Redisson 订阅
→ WorkerWebSocketServer.pushTaskRunToDevices
→ 向在线设备的 WebSocket Session 发送消息
前置条件:8006 与 8008 使用同一 Redis 实例;APP 已连接 WS 且 meId 在推送列表中。
{
"cmd": "taskRun",
"robotic": 294,
"callModel": 3,
"timestamp": 1718534400000
}
| 字段 | 类型 | 说明 |
|---|---|---|
cmd |
String | 固定 taskRun |
robotic |
Long | 外呼任务 ID(company_voice_robotic.id) |
callModel |
Integer | 外呼模式:3 / 4 / 5 |
timestamp |
Long | 服务端时间戳(毫秒) |
说明:taskRun 为服务端单向推送,APP 不需要回包。
| callModel | 名称 | 业务含义 | APP 页面/行为建议 |
|---|---|---|---|
| 3 | APP-网页点呼 | 销售在 APP 上手动点选客户逐个拨打 | 跳转或刷新 网页点呼页;展示该 robotic 任务下的待拨客户列表,用户手动点击拨打 |
| 4 | APP-自动呼叫 | APP 按任务自动连续外呼 | 跳转或刷新 自动呼叫页;进入自动拨号流程,按队列自动拨打 |
| 5 | APP-AI呼叫 | APP 侧 AI 辅助 / AI 外呼流程 | 跳转或刷新 AI 呼叫页;进入 AI 外呼交互流程 |
后端枚举定义见 AiCallModeEnum:
3 = APP_WEB(APP-网页点呼)4 = APP_AUTO(APP-自动呼叫)5 = APP_AI(APP-AI呼叫)注意:callModel 为 1(人工外呼)、2(AI 自动外呼 / EasyCall)时 不会 收到 taskRun 推送。
taskRun 后的推荐处理流程onWebSocketMessage(json)
→ 解析 cmd
→ if cmd == "taskRun":
roboticId = json.robotic
callModel = json.callModel
switch (callModel):
3 → 打开/刷新「网页点呼」页,带 roboticId,拉取任务与客户列表
4 → 打开/刷新「自动呼叫」页,带 roboticId,启动自动拨号
5 → 打开/刷新「AI 呼叫」页,带 roboticId,进入 AI 外呼流程
若当前已在对应模式页面,仅需 刷新任务列表/客户列表(调用下方 HTTP 接口),不必重复跳转。
收到 taskRun 后,按 callModel 调用对应接口拉取数据(均需登录态 WorkerToken)。
GET /company/companyVoiceRobotic/myTaskStatList?callModel={3|4|5}&meId={meId}
callModel:与推送消息一致(3 / 4 / 5)meId:当前设备 IMEI用于各外呼模式首页的任务列表刷新。
GET /company/companyVoiceRoboticCallLogCallphone/...?roboticId={robotic}&callModel={3|4|5}&meId={meId}
(具体路径见 CompanyVoiceRoboticCallLogCallphoneController,参数需与 callModel、meId 一致。)
POST /app/aiSipCall/validateAppCall
Body 示例:
{
"roboticId": 294,
"calleeId": 12345,
"meId": "8601234561212129012",
"callModel": 3
}
POST /app/aiSipCall/appCardCallBack
通话结束后回调,Body 中需带 callModel(默认 3)。
POST /company/companyVoiceRobotic/pauseRoboticActive
与 PC 端共用,APP / PC 任意一端操作会联动。
// 同一 WebSocket 连接
const ws = new WebSocket(
`ws://${host}:8008/worker/webSocket/${meId}?tenantId=${tenantId}`
);
ws.onopen = () => {
startHeartbeat(); // 定时发 cmd=heartbeat
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
switch (msg.cmd) {
case 'heartbeat':
handleHeartbeatReply(msg); // 更新在线/禁用状态
break;
case 'deviceStatus':
handleDeviceStatus(msg); // 禁用、离线提示
break;
case 'taskRun':
handleTaskRunPush(msg); // 任务推送(见下)
break;
case 'error':
showError(msg.msg);
break;
}
};
function handleTaskRunPush(msg) {
const { robotic, callModel } = msg;
if (callModel === 3) {
navigateOrRefresh('WebClickCallPage', { roboticId: robotic, callModel: 3 });
} else if (callModel === 4) {
navigateOrRefresh('AutoCallPage', { roboticId: robotic, callModel: 4 });
} else if (callModel === 5) {
navigateOrRefresh('AiCallPage', { roboticId: robotic, callModel: 5 });
}
fetchMyTaskStatList(callModel, meId);
}
function startHeartbeat() {
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({
cmd: 'heartbeat',
tenantId,
appVersion,
deviceName,
phone1,
phone2,
imei: cardImeiJoined
}));
}
}, 30000);
}
| 步骤 | 期望现象 |
|---|---|
| APP 连接 WS | 服务端日志:[WorkerWS] 设备连接: meId=... |
| 8008 启动 | 日志:[WorkerTaskRunWS] Redisson 订阅成功 channel=worker:ws:task:run |
| PC 点「启动任务」 | 8006:[WorkerTaskRunWS] 已发布 roboticId=... callModel=... meIds=[...] |
| 8008 收 Redis | [WorkerTaskRunWS] 收到消息 roboticId=... |
| 设备在线 | [WorkerWS] 任务启动推送成功 meId=... |
| APP 收消息 | cmd=taskRun,按 callModel 进入对应页面 |
常见问题:
taskRun:检查 meId 是否与 deviceIds 解析出的 IMEI 一致;APP 是否已连 WS。onMessage 是否处理 cmd === 'taskRun'。callModel、meId 是否与推送一致。| 字段 | 类型 | 说明 |
|---|---|---|
cmd |
String | heartbeat / deviceStatus / taskRun / error |
meId |
String | 设备 IMEI |
tenantId |
Long | 租户 ID |
appVersion |
String | APP 版本 |
deviceName |
String | 设备名称 |
phone1 / phone2 |
String | 双卡手机号 |
imei |
String | SIM 卡 IMEI(逗号分隔) |
status |
Integer | 0 离线 / 1 在线 / 2 禁用 |
msg |
String | 提示信息 |
timestamp |
Long | 时间戳 |
robotic |
Long | 任务 ID(仅 taskRun) |
callModel |
Integer | 外呼模式(仅 taskRun) |
文档版本:与 fs-worker-app WebSocket + Redis 任务推送实现同步。
Redis 通道:worker:ws:task:run(WorkerWsConstants.REDIS_CHANNEL_TASK_RUN)。