# 工作机 APP WebSocket 任务推送对接说明 ## 1. 概述 销售 PC 端在「启动外呼任务」时,若任务为 **APP 外呼模式**(`callModel = 3 / 4 / 5`)且已绑定设备,服务端会通过 **同一条 WebSocket 长连接** 向对应工作机推送 `taskRun` 消息。 - **心跳**与**任务推送**共用连接地址,通过消息体字段 `cmd` 区分类型。 - APP 收到 `taskRun` 后,根据 `callModel` 跳转或刷新对应外呼页面。 | 服务 | 端口 | 说明 | |------|------|------| | fs-worker-app | 8008 | WebSocket + APP HTTP 接口 | | fs-saas-company | 8006 | PC 端启动任务(`taskRun`) | --- ## 2. WebSocket 连接 ### 2.1 连接地址(唯一) ``` 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 ``` ### 2.2 连接生命周期 1. APP 登录成功后建立长连接(与心跳共用)。 2. 连接成功时,服务端主动推送一次当前设备状态。 3. APP 定时发送心跳(建议间隔 30s)。 4. PC 启动任务时,服务端可能额外推送 `taskRun`(无需 APP 回包)。 --- ## 3. 消息协议(统一 JSON) 所有消息均为 JSON 文本,通过字段 `cmd` 区分类型。 ### 3.1 消息类型一览 | 方向 | `cmd` | 说明 | |------|-------|------| | APP → 服务端 | `heartbeat` | 心跳 | | APP → 服务端 | `deviceStatus` | 主动查询设备状态(可选) | | 服务端 → APP | `heartbeat` | 心跳回复 | | 服务端 → APP | `deviceStatus` | 禁用/离线等状态变更 | | 服务端 → APP | `taskRun` | **PC 启动任务推送** | | 服务端 → APP | `error` | 处理异常 | --- ## 4. 心跳(已有机制,与任务推送无关) ### 4.1 APP 发送 ```json { "cmd": "heartbeat", "tenantId": 338, "appVersion": "1.0.0", "deviceName": "工作机A", "phone1": "13800138000", "phone2": "13900139000", "imei": "卡1IMEI,卡2IMEI" } ``` ### 4.2 服务端回复 ```json { "cmd": "heartbeat", "meId": "8601234561212129012", "status": 1, "msg": "ok", "appVersion": "1.0.0", "deviceName": "工作机A", "timestamp": 1718534400000 } ``` `status`:`0` 离线 / `1` 在线 / `2` 禁用。 ### 4.3 APP 处理建议 - 更新本地设备状态展示。 - `status === 2` 时提示禁用并限制外呼。 - 心跳逻辑保持现有实现,**无需为 `taskRun` 单独建连接**。 --- ## 5. 任务推送 `taskRun`(本次对接重点) ### 5.1 触发条件(PC 端) PC 调用 `GET /company/companyVoiceRobotic/taskRun?id={任务ID}` 且满足: - 任务 `callModel` 为 `3`、`4` 或 `5`; - 任务 `deviceIds` 非空(已绑定设备); - 设备 IMEI 能解析成功。 不满足上述条件时 **不会推送** `taskRun`。 ### 5.2 推送链路 ``` PC taskRun (8006) → Redis PUBLISH channel: worker:ws:task:run → fs-worker-app Redisson 订阅 → WorkerWebSocketServer.pushTaskRunToDevices → 向在线设备的 WebSocket Session 发送消息 ``` **前置条件**:8006 与 8008 使用同一 Redis 实例;APP 已连接 WS 且 `meId` 在推送列表中。 ### 5.3 服务端 → APP 消息体 ```json { "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 **不需要回包**。 --- ## 6. callModel 与页面对应关系 | 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` 推送。 ### 6.1 收到 `taskRun` 后的推荐处理流程 ``` onWebSocketMessage(json) → 解析 cmd → if cmd == "taskRun": roboticId = json.robotic callModel = json.callModel switch (callModel): 3 → 打开/刷新「网页点呼」页,带 roboticId,拉取任务与客户列表 4 → 打开/刷新「自动呼叫」页,带 roboticId,启动自动拨号 5 → 打开/刷新「AI 呼叫」页,带 roboticId,进入 AI 外呼流程 ``` 若当前已在对应模式页面,仅需 **刷新任务列表/客户列表**(调用下方 HTTP 接口),不必重复跳转。 --- ## 7. 配套 HTTP 接口(刷新页面数据) 收到 `taskRun` 后,按 `callModel` 调用对应接口拉取数据(均需登录态 `WorkerToken`)。 ### 7.1 执行中任务统计列表 ``` GET /company/companyVoiceRobotic/myTaskStatList?callModel={3|4|5}&meId={meId} ``` - `callModel`:与推送消息一致(3 / 4 / 5) - `meId`:当前设备 IMEI 用于各外呼模式首页的任务列表刷新。 ### 7.2 任务拨打统计 / 客户列表 ``` GET /company/companyVoiceRoboticCallLogCallphone/...?roboticId={robotic}&callModel={3|4|5}&meId={meId} ``` (具体路径见 `CompanyVoiceRoboticCallLogCallphoneController`,参数需与 `callModel`、`meId` 一致。) ### 7.3 拨打前校验 ``` POST /app/aiSipCall/validateAppCall ``` Body 示例: ```json { "roboticId": 294, "calleeId": 12345, "meId": "8601234561212129012", "callModel": 3 } ``` ### 7.4 外呼结果回调 ``` POST /app/aiSipCall/appCardCallBack ``` 通话结束后回调,Body 中需带 `callModel`(默认 3)。 ### 7.5 暂停/继续任务 ``` POST /company/companyVoiceRobotic/pauseRoboticActive ``` 与 PC 端共用,APP / PC 任意一端操作会联动。 --- ## 8. APP 端集成示例(伪代码) ```javascript // 同一 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); } ``` --- ## 9. 联调检查清单 | 步骤 | 期望现象 | |------|----------| | 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` 进入对应页面 | **常见问题**: 1. **收不到 `taskRun`**:检查 `meId` 是否与 `deviceIds` 解析出的 IMEI 一致;APP 是否已连 WS。 2. **8008 收到消息但 APP 没反应**:检查 APP `onMessage` 是否处理 `cmd === 'taskRun'`。 3. **推送成功但页面无数据**:检查刷新接口的 `callModel`、`meId` 是否与推送一致。 --- ## 10. 附录:完整消息字段(WorkerWsMsg) | 字段 | 类型 | 说明 | |------|------|------| | `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`)。