# VWED WebSocket接口文档 本文档描述了VWED系统WebSocket相关的API接口,主要用于实时推送任务执行结果和状态更新。 ## 基础信息 - 基础路径:`/ws` - 接口标签:`WebSocket` - 协议:WebSocket协议(ws://或wss://) ## 接口清单 | 序号 | 接口名称 | 接口路径 | 协议 | 接口描述 | | --- | --- | --- | --- | --- | | 1 | 任务执行结果实时推送 | `/task-execution/{task_record_id}` | WebSocket | 实时推送指定任务记录的执行结果更新 | | 2 | 任务执行结果广播 | `/task-execution-broadcast/{task_record_id}` | WebSocket | 接收任务执行结果广播消息 | | 3 | 库位状态实时推送 | `/storage-location/{scene_id}` | WebSocket | 实时推送指定场景的库位状态更新 | | 4 | 库位状态广播 | `/storage-location-broadcast/{scene_id}` | WebSocket | 接收库位状态广播消息 | ## 接口详情 ### 1. 任务执行结果实时推送 #### 接口说明 建立WebSocket连接,实时接收指定任务记录的执行结果更新。服务器会定期推送任务状态变化,客户端也可以主动请求获取当前状态。 #### 连接路径 ``` ws://your-domain/ws/task-execution/{task_record_id}?interval={interval} ``` #### 路径参数 | 参数名 | 类型 | 是否必须 | 描述 | | --- | --- | --- | --- | | task_record_id | string | 是 | 任务记录ID | #### 查询参数 | 参数名 | 类型 | 是否必须 | 默认值 | 描述 | | --- | --- | --- | --- | --- | | interval | integer | 否 | 2 | 推送间隔(秒),范围1-30秒 | #### 客户端消息格式 客户端可以向服务器发送以下格式的JSON消息: ##### 心跳检测 ```json { "type": "ping", "timestamp": "2025-06-11T12:00:00.000Z" } ``` ##### 获取当前状态 ```json { "type": "get_status", "timestamp": "2025-06-11T12:00:00.000Z" } ``` #### 服务器消息格式 ##### 任务执行结果更新 ```json { "type": "task_execution_update", "task_record_id": "任务记录ID", "timestamp": "2025-06-11T12:00:00.000Z", "message": "成功获取任务记录执行结果", "data": [ { "created_at": "2025-06-11T12:00:00.000Z", "context": "[块执行名称] 执行内容描述", "status": "SUCCESS/FAILED/RUNNING" } ] } ``` ##### 心跳响应 ```json { "type": "pong", "timestamp": "2025-06-11T12:00:00.000Z" } ``` ##### 错误消息 ```json { "type": "error", "task_record_id": "任务记录ID", "timestamp": "2025-06-11T12:00:00.000Z", "message": "错误描述信息" } ``` #### 响应字段说明 ##### 任务执行结果字段 | 字段名 | 类型 | 描述 | | --- | --- | --- | | type | string | 消息类型,固定为"task_execution_update" | | task_record_id | string | 任务记录ID | | timestamp | string | 消息时间戳,ISO 8601格式 | | message | string | 响应消息描述 | | data | array | 执行结果数组 | | data[].created_at | string | 结果创建时间,ISO 8601格式 | | data[].context | string | 执行内容描述 | | data[].status | string | 执行状态:SUCCESS(成功)、FAILED(失败)、RUNNING(执行中) | #### 连接示例 ##### JavaScript客户端示例 ```javascript // 建立WebSocket连接 const taskRecordId = "your-task-record-id"; const interval = 2; // 推送间隔2秒 const wsUrl = `ws://localhost:8000/ws/task-execution/${taskRecordId}?interval=${interval}`; const websocket = new WebSocket(wsUrl); // 连接建立 websocket.onopen = function(event) { console.log("WebSocket连接已建立"); // 发送心跳包 websocket.send(JSON.stringify({ type: "ping", timestamp: new Date().toISOString() })); }; // 接收消息 websocket.onmessage = function(event) { const data = JSON.parse(event.data); switch(data.type) { case "task_execution_update": console.log("任务执行结果更新:", data.data); break; case "pong": console.log("心跳响应:", data.timestamp); break; case "error": console.error("服务器错误:", data.message); break; } }; // 连接关闭 websocket.onclose = function(event) { console.log("WebSocket连接已关闭"); }; // 连接错误 websocket.onerror = function(error) { console.error("WebSocket连接错误:", error); }; ``` ##### Python客户端示例 ```python import asyncio import json import websockets async def websocket_client(): task_record_id = "your-task-record-id" interval = 2 uri = f"ws://localhost:8000/ws/task-execution/{task_record_id}?interval={interval}" async with websockets.connect(uri) as websocket: print("WebSocket连接已建立") # 发送心跳包 await websocket.send(json.dumps({ "type": "ping", "timestamp": datetime.now().isoformat() })) # 监听消息 async for message in websocket: data = json.loads(message) if data["type"] == "task_execution_update": print(f"任务执行结果更新: {data['data']}") elif data["type"] == "pong": print(f"心跳响应: {data['timestamp']}") elif data["type"] == "error": print(f"服务器错误: {data['message']}") # 运行客户端 asyncio.run(websocket_client()) ``` #### 特性说明 1. **智能推送**:服务器只在数据发生变化时才推送更新,避免不必要的网络流量 2. **心跳检测**:支持客户端主动发送心跳包,维持连接活跃状态 3. **错误处理**:完善的错误处理机制,连接异常时自动清理资源 4. **状态查询**:客户端可随时主动请求获取当前任务状态 5. **多客户端支持**:同一任务记录可支持多个客户端同时连接 ### 2. 任务执行结果广播 #### 接口说明 建立WebSocket连接,接收任务执行结果的广播消息。与实时推送接口的区别在于,此接口主要用于被动接收广播,不会主动定期推送。 #### 连接路径 ``` ws://your-domain/ws/task-execution-broadcast/{task_record_id} ``` #### 路径参数 | 参数名 | 类型 | 是否必须 | 描述 | | --- | --- | --- | --- | | task_record_id | string | 是 | 任务记录ID | #### 客户端消息格式 ##### 心跳检测 ```json { "type": "ping", "timestamp": "2025-06-11T12:00:00.000Z" } ``` #### 服务器消息格式 与任务执行结果实时推送接口相同,参见上述文档。 #### 使用场景 1. **监控面板**:多个监控客户端同时监听任务状态变化 2. **日志收集**:收集任务执行过程中的状态变化记录 3. **事件通知**:当任务状态发生变化时接收通知 ### 3. 库位状态实时推送 #### 接口说明 建立WebSocket连接,实时接收指定场景的库位状态更新。服务器会定期推送库位状态变化,客户端也可以主动请求获取当前状态。支持多种过滤条件来筛选特定的库位。 #### 连接路径 ``` ws://your-domain/ws/storage-location/{scene_id}?interval={interval}&storage_area_id={storage_area_id}&station_name={station_name}&layer_name={layer_name}&is_occupied={is_occupied}&is_locked={is_locked}&is_disabled={is_disabled} ``` #### 路径参数 | 参数名 | 类型 | 是否必须 | 描述 | | --- | --- | --- | --- | | scene_id | string | 是 | 场景ID | #### 查询参数 | 参数名 | 类型 | 是否必须 | 默认值 | 描述 | | --- | --- | --- | --- | --- | | interval | integer | 否 | 3 | 推送间隔(秒),范围1-30秒 | | storage_area_id | string | 否 | null | 库区ID,用于过滤特定库区 | | station_name | string | 否 | null | 站点名称,用于过滤特定站点 | | layer_name | string | 否 | null | 层名称,用于过滤特定层 | | is_occupied | boolean | 否 | null | 是否占用过滤 | | is_locked | boolean | 否 | null | 是否锁定过滤 | | is_disabled | boolean | 否 | null | 是否禁用过滤 | #### 客户端消息格式 客户端可以向服务器发送以下格式的JSON消息: ##### 心跳检测 ```json { "type": "ping", "timestamp": "2025-06-11T12:00:00.000Z" } ``` ##### 获取当前状态 ```json { "type": "get_status", "timestamp": "2025-06-11T12:00:00.000Z" } ``` #### 服务器消息格式 ##### 库位状态更新 ```json { "type": "storage_location_update", "scene_id": "场景ID", "timestamp": "2025-06-11T12:00:00.000Z", "message": "成功获取库位状态", "data": { "total": 100, "page": 1, "page_size": 1000, "total_pages": 1, "storage_locations": [ { "id": "层ID", "layer_index": 1, "layer_name": "层名称", "operate_point_id": "动作点ID", "station_name": "站点名称", "storage_location_name": "库位名称", "scene_id": "场景ID", "storage_area_id": "库区ID", "area_name": "库区名称", "is_occupied": false, "is_locked": false, "is_disabled": false, "is_empty_tray": false, "locked_by": null, "goods_content": "", "goods_weight": null, "goods_volume": null, "goods_stored_at": null, "goods_retrieved_at": null, "last_access_at": "2025-06-11T12:00:00.000Z", "max_weight": 5000, "max_volume": 1000, "layer_height": 100, "tags": "", "description": null, "created_at": "2025-06-11T12:00:00.000Z", "updated_at": "2025-06-11T12:00:00.000Z" } ] } } ``` ##### 库位状态变化通知 ```json { "type": "storage_location_status_change", "scene_id": "场景ID", "layer_name": "层名称", "action": "OCCUPY", "timestamp": "2025-06-11T12:00:00.000Z", "new_status": { "id": "层ID", "is_occupied": true, "is_locked": false, "is_disabled": false, "is_empty_tray": false, "locked_by": null, "goods_content": "货物内容", "last_access_at": "2025-06-11T12:00:00.000Z", "updated_at": "2025-06-11T12:00:00.000Z" } } ``` ##### 心跳响应 ```json { "type": "pong", "timestamp": "2025-06-11T12:00:00.000Z" } ``` ##### 错误消息 ```json { "type": "error", "scene_id": "场景ID", "timestamp": "2025-06-11T12:00:00.000Z", "message": "错误描述信息" } ``` #### 响应字段说明 ##### 库位状态字段 | 字段名 | 类型 | 描述 | | --- | --- | --- | | id | string | 层ID | | layer_index | integer | 层索引(从1开始) | | layer_name | string | 层名称 | | operate_point_id | string | 动作点ID | | station_name | string | 站点名称 | | storage_location_name | string | 库位名称 | | scene_id | string | 场景ID | | storage_area_id | string | 库区ID | | area_name | string | 库区名称 | | is_occupied | boolean | 是否占用 | | is_locked | boolean | 是否锁定 | | is_disabled | boolean | 是否禁用 | | is_empty_tray | boolean | 是否空托盘 | | locked_by | string | 锁定者 | | goods_content | string | 货物内容 | | goods_weight | integer | 货物重量(克) | | goods_volume | integer | 货物体积(立方厘米) | | goods_stored_at | string | 货物存放时间 | | goods_retrieved_at | string | 货物取出时间 | | last_access_at | string | 最后访问时间 | | max_weight | integer | 最大承重(克) | | max_volume | integer | 最大体积(立方厘米) | | layer_height | integer | 层高(毫米) | | tags | string | 标签 | | description | string | 层描述 | | created_at | string | 创建时间 | | updated_at | string | 更新时间 | #### 连接示例 ##### JavaScript客户端示例 ```javascript // 建立WebSocket连接 const sceneId = "your-scene-id"; const interval = 3; // 推送间隔3秒 const storageAreaId = "area-001"; // 过滤特定库区 const wsUrl = `ws://localhost:8000/ws/storage-location/${sceneId}?interval=${interval}&storage_area_id=${storageAreaId}&is_occupied=false`; const websocket = new WebSocket(wsUrl); // 连接建立 websocket.onopen = function(event) { console.log("库位状态WebSocket连接已建立"); // 发送心跳包 websocket.send(JSON.stringify({ type: "ping", timestamp: new Date().toISOString() })); }; // 接收消息 websocket.onmessage = function(event) { const data = JSON.parse(event.data); switch(data.type) { case "storage_location_update": console.log("库位状态更新:", data.data); // 处理库位状态列表 data.data.storage_locations.forEach(location => { console.log(`层${location.layer_name}: 占用=${location.is_occupied}, 锁定=${location.is_locked}`); }); break; case "storage_location_status_change": console.log("库位状态变化:", data.layer_name, data.action, data.new_status); break; case "pong": console.log("心跳响应:", data.timestamp); break; case "error": console.error("服务器错误:", data.message); break; } }; // 连接关闭 websocket.onclose = function(event) { console.log("库位状态WebSocket连接已关闭"); }; // 连接错误 websocket.onerror = function(error) { console.error("库位状态WebSocket连接错误:", error); }; ``` ##### Python客户端示例 ```python import asyncio import json import websockets from datetime import datetime async def storage_location_websocket_client(): scene_id = "your-scene-id" interval = 3 storage_area_id = "area-001" uri = f"ws://localhost:8000/ws/storage-location/{scene_id}?interval={interval}&storage_area_id={storage_area_id}&is_occupied=false" async with websockets.connect(uri) as websocket: print("库位状态WebSocket连接已建立") # 发送心跳包 await websocket.send(json.dumps({ "type": "ping", "timestamp": datetime.now().isoformat() })) # 监听消息 async for message in websocket: data = json.loads(message) if data["type"] == "storage_location_update": print(f"库位状态更新: 共{data['data']['total']}个库位") for location in data["data"]["storage_locations"]: print(f" 层{location['layer_name']}: 占用={location['is_occupied']}, 锁定={location['is_locked']}") elif data["type"] == "storage_location_status_change": print(f"库位状态变化: {data['layer_name']} {data['action']} {data['new_status']}") elif data["type"] == "pong": print(f"心跳响应: {data['timestamp']}") elif data["type"] == "error": print(f"服务器错误: {data['message']}") # 运行客户端 asyncio.run(storage_location_websocket_client()) ``` #### 特性说明 1. **智能推送**:服务器只在数据发生变化时才推送更新,避免不必要的网络流量 2. **灵活过滤**:支持多种过滤条件,可以精确筛选需要监控的库位 3. **心跳检测**:支持客户端主动发送心跳包,维持连接活跃状态 4. **错误处理**:完善的错误处理机制,连接异常时自动清理资源 5. **状态查询**:客户端可随时主动请求获取当前库位状态 6. **多客户端支持**:同一场景可支持多个客户端同时连接 ### 4. 库位状态广播 #### 接口说明 建立WebSocket连接,接收库位状态的广播消息。与实时推送接口的区别在于,此接口主要用于被动接收广播,不会主动定期推送。 #### 连接路径 ``` ws://your-domain/ws/storage-location-broadcast/{scene_id} ``` #### 路径参数 | 参数名 | 类型 | 是否必须 | 描述 | | --- | --- | --- | --- | | scene_id | string | 是 | 场景ID | #### 客户端消息格式 ##### 心跳检测 ```json { "type": "ping", "timestamp": "2025-06-11T12:00:00.000Z" } ``` #### 服务器消息格式 与库位状态实时推送接口相同,参见上述文档。 #### 使用场景 1. **监控面板**:多个监控客户端同时监听库位状态变化 2. **库位管理**:实时显示库位占用、锁定状态 3. **货物追踪**:监控货物存放和取出过程 4. **状态统计**:收集库位使用率和状态变化统计 ## 错误码说明 | 错误码 | 描述 | 解决方案 | | --- | --- | --- | | 1006 | 连接异常关闭 | 检查网络连接,重新建立连接 | | 1011 | 服务器内部错误 | 检查服务器状态和日志 | | 1013 | 临时服务不可用 | 稍后重试连接 | ## 最佳实践 ### 1. 连接管理 - 实现连接断开后的自动重连机制 - 合理设置推送间隔,避免过于频繁的请求 - 及时关闭不需要的连接,释放服务器资源 ### 2. 错误处理 - 监听`onerror`和`onclose`事件,处理连接异常 - 实现重连退避策略,避免连接风暴 - 记录错误日志,便于问题排查 ### 3. 性能优化 - 使用合适的推送间隔(任务执行结果建议2-5秒,库位状态建议3-10秒) - 客户端及时处理接收到的消息,避免消息积压 - 对于不活跃的任务,考虑降低推送频率 - 库位状态推送时,合理使用过滤条件,避免获取过多不必要的数据 - 对于大规模库位监控,考虑按库区分组建立多个连接 ### 4. 安全考虑 - 在生产环境中使用WSS协议(WebSocket Secure) - 实现适当的身份验证和授权机制 - 限制连接数量,防止资源滥用 - 对于库位状态推送,验证客户端是否有权限访问特定场景的库位数据 ## 注意事项 1. **ID有效性**:确保传入的任务记录ID和场景ID存在且有效 2. **网络稳定性**:WebSocket连接对网络质量要求较高,不稳定的网络可能导致频繁断连 3. **浏览器兼容性**:确保目标浏览器支持WebSocket协议 4. **资源清理**:页面关闭或组件销毁时,及时关闭WebSocket连接 5. **消息处理**:合理处理接收到的消息,避免阻塞UI线程 6. **过滤条件**:库位状态推送时,合理设置过滤条件,避免获取过多数据影响性能 7. **数据更新频率**:库位状态数据更新频率可能较高,建议根据实际需求调整推送间隔 8. **并发连接**:避免对同一场景建立过多并发连接,建议复用连接或使用广播接口 ## 更新日志 | 版本 | 日期 | 更新内容 | | --- | --- | --- | | 1.0.0 | 2025-06-11 | 初始版本,支持任务执行结果实时推送和广播功能 | | 1.1.0 | 2025-06-11 | 新增库位状态实时推送和广播功能,支持多种过滤条件和状态变化通知 |