| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349 |
- # -*- coding: utf-8 -*-
- import pathlib
- ROOT = pathlib.Path(r"D:\hdProject\newsaas\ylrz_saas\java\fs-worker-app\docs\worker-ws-task-push.md")
- CONTENT = r"""# 工作机 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`)。
- """
- ROOT.write_text(CONTENT, encoding="utf-8", newline="\n")
- print("written:", ROOT, "bytes:", len(CONTENT.encode("utf-8")))
|