完善任务库位处理模块
This commit is contained in:
parent
d8c31ebd5b
commit
7ebf10fc12
@ -14,6 +14,8 @@
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 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 | 接收库位状态广播消息 |
|
||||
|
||||
## 接口详情
|
||||
|
||||
@ -252,6 +254,335 @@ ws://your-domain/ws/task-execution-broadcast/{task_record_id}
|
||||
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. **状态统计**:收集库位使用率和状态变化统计
|
||||
|
||||
## 错误码说明
|
||||
|
||||
| 错误码 | 描述 | 解决方案 |
|
||||
@ -276,26 +607,33 @@ ws://your-domain/ws/task-execution-broadcast/{task_record_id}
|
||||
|
||||
### 3. 性能优化
|
||||
|
||||
- 使用合适的推送间隔(建议2-5秒)
|
||||
- 使用合适的推送间隔(任务执行结果建议2-5秒,库位状态建议3-10秒)
|
||||
- 客户端及时处理接收到的消息,避免消息积压
|
||||
- 对于不活跃的任务,考虑降低推送频率
|
||||
- 库位状态推送时,合理使用过滤条件,避免获取过多不必要的数据
|
||||
- 对于大规模库位监控,考虑按库区分组建立多个连接
|
||||
|
||||
### 4. 安全考虑
|
||||
|
||||
- 在生产环境中使用WSS协议(WebSocket Secure)
|
||||
- 实现适当的身份验证和授权机制
|
||||
- 限制连接数量,防止资源滥用
|
||||
- 对于库位状态推送,验证客户端是否有权限访问特定场景的库位数据
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **任务记录ID有效性**:确保传入的任务记录ID存在且有效
|
||||
1. **ID有效性**:确保传入的任务记录ID和场景ID存在且有效
|
||||
2. **网络稳定性**:WebSocket连接对网络质量要求较高,不稳定的网络可能导致频繁断连
|
||||
3. **浏览器兼容性**:确保目标浏览器支持WebSocket协议
|
||||
4. **资源清理**:页面关闭或组件销毁时,及时关闭WebSocket连接
|
||||
5. **消息处理**:合理处理接收到的消息,避免阻塞UI线程
|
||||
6. **过滤条件**:库位状态推送时,合理设置过滤条件,避免获取过多数据影响性能
|
||||
7. **数据更新频率**:库位状态数据更新频率可能较高,建议根据实际需求调整推送间隔
|
||||
8. **并发连接**:避免对同一场景建立过多并发连接,建议复用连接或使用广播接口
|
||||
|
||||
## 更新日志
|
||||
|
||||
| 版本 | 日期 | 更新内容 |
|
||||
| --- | --- | --- |
|
||||
| 1.0.0 | 2025-06-11 | 初始版本,支持任务执行结果实时推送和广播功能 |
|
||||
| 1.0.0 | 2025-06-11 | 初始版本,支持任务执行结果实时推送和广播功能 |
|
||||
| 1.1.0 | 2025-06-11 | 新增库位状态实时推送和广播功能,支持多种过滤条件和状态变化通知 |
|
Binary file not shown.
14
app.py
14
app.py
@ -8,7 +8,6 @@ from contextlib import asynccontextmanager
|
||||
from config.settings import settings
|
||||
# 导入数据库相关
|
||||
from data.session import init_database, close_database_connections, close_async_database_connections
|
||||
# from data.cache import redis_client
|
||||
# 导入路由注册函数
|
||||
from routes import register_routers
|
||||
# 导入中间件注册函数
|
||||
@ -27,11 +26,7 @@ async def lifespan(app: FastAPI):
|
||||
"""
|
||||
# 启动前的初始化操作
|
||||
# 初始化数据库
|
||||
init_database()
|
||||
# 初始化Redis连接
|
||||
# if redis_client.get_client() is None:
|
||||
# logger.warning("Redis连接失败,部分功能可能无法正常使用")
|
||||
|
||||
init_database()
|
||||
# 启动增强版任务调度器
|
||||
from services.enhanced_scheduler import scheduler
|
||||
await scheduler.start(worker_count=settings.TASK_SCHEDULER_MIN_WORKER_COUNT)
|
||||
@ -58,22 +53,21 @@ app = FastAPI(
|
||||
lifespan=lifespan,
|
||||
debug=settings.DEBUG
|
||||
)
|
||||
|
||||
# 注册中间件
|
||||
register_middlewares(app)
|
||||
|
||||
# 注册所有路由
|
||||
register_routers(app)
|
||||
|
||||
|
||||
# 主函数
|
||||
if __name__ == "__main__":
|
||||
# 从环境变量中获取端口,默认为8000
|
||||
import time
|
||||
# start_time = time.time()
|
||||
port = int(os.environ.get("PORT", settings.SERVER_PORT))
|
||||
|
||||
# 打印启动配置信息
|
||||
logger.info(f"服务器配置 - Host: 0.0.0.0, Port: {port}, Workers: {settings.SERVER_WORKERS}, Reload: {settings.SERVER_RELOAD}")
|
||||
|
||||
end_time = time.time()
|
||||
# 启动服务器
|
||||
uvicorn.run(
|
||||
"app:app",
|
||||
|
Binary file not shown.
@ -44,6 +44,7 @@ TF_API_TIMEOUT = int(os.getenv("TF_API_TIMEOUT", "60"))
|
||||
TF_API_RETRY_TIMES = int(os.getenv("TF_API_RETRY_TIMES", "3"))
|
||||
TF_API_MOCK_MODE = False
|
||||
TF_API_TOKEN_HEADER = os.getenv("TF_API_TOKEN_HEADER", "X-Access-Token") # token请求头名称
|
||||
TF_API_TOKEN = os.getenv("TF_API_TOKEN", "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NDk3NzY1MzEsInVzZXJuYW1lIjoiYWRtaW4ifQ.uRLHZuRQTrR2fHyA-dMzP46yXAa5wdjfdUcmr9PNY4g")
|
||||
|
||||
def get_tf_api_config() -> Dict[str, Any]:
|
||||
"""获取天风系统API配置"""
|
||||
|
Binary file not shown.
3565
logs/app.log
3565
logs/app.log
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -6,9 +6,9 @@
|
||||
提供呼叫器设备相关的API接口
|
||||
"""
|
||||
|
||||
from typing import Dict, List, Any, Optional
|
||||
# # from typing import Dict, List, Any, Optional
|
||||
from fastapi import APIRouter, Body, Query, Path, Request, File, UploadFile, Form, Response
|
||||
from pydantic import BaseModel
|
||||
# # from pydantic import BaseModel
|
||||
|
||||
from routes.common_api import format_response, error_response
|
||||
from utils.logger import get_logger
|
||||
|
@ -13,6 +13,9 @@ from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Path, Query
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from services.task_record_service import TaskRecordService
|
||||
from services.operate_point_service import OperatePointService
|
||||
from data.session import get_db
|
||||
from routes.model.operate_point_model import StorageLocationListRequest
|
||||
from utils.logger import get_logger
|
||||
|
||||
# 创建路由
|
||||
@ -39,8 +42,12 @@ class ConnectionManager:
|
||||
def __init__(self):
|
||||
# 存储WebSocket连接,按task_record_id分组
|
||||
self.active_connections: Dict[str, Set[WebSocket]] = {}
|
||||
# 存储库位状态连接,按scene_id分组
|
||||
self.storage_location_connections: Dict[str, Set[WebSocket]] = {}
|
||||
# 存储连接的最后推送时间
|
||||
self.last_push_time: Dict[str, datetime] = {}
|
||||
# 存储库位状态的最后推送时间
|
||||
self.storage_location_last_push_time: Dict[str, datetime] = {}
|
||||
|
||||
async def connect(self, websocket: WebSocket, task_record_id: str):
|
||||
"""连接WebSocket"""
|
||||
@ -50,6 +57,14 @@ class ConnectionManager:
|
||||
self.active_connections[task_record_id].add(websocket)
|
||||
logger.info(f"WebSocket连接已建立,任务记录ID: {task_record_id}. 当前连接数: {len(self.active_connections[task_record_id])}")
|
||||
|
||||
async def connect_storage_location(self, websocket: WebSocket, scene_id: str):
|
||||
"""连接库位状态WebSocket"""
|
||||
await websocket.accept()
|
||||
if scene_id not in self.storage_location_connections:
|
||||
self.storage_location_connections[scene_id] = set()
|
||||
self.storage_location_connections[scene_id].add(websocket)
|
||||
logger.info(f"库位状态WebSocket连接已建立,场景ID: {scene_id}. 当前连接数: {len(self.storage_location_connections[scene_id])}")
|
||||
|
||||
def disconnect(self, websocket: WebSocket, task_record_id: str):
|
||||
"""断开WebSocket连接"""
|
||||
if task_record_id in self.active_connections:
|
||||
@ -60,6 +75,16 @@ class ConnectionManager:
|
||||
self.last_push_time.pop(task_record_id, None)
|
||||
logger.info(f"WebSocket连接已断开,任务记录ID: {task_record_id}")
|
||||
|
||||
def disconnect_storage_location(self, websocket: WebSocket, scene_id: str):
|
||||
"""断开库位状态WebSocket连接"""
|
||||
if scene_id in self.storage_location_connections:
|
||||
self.storage_location_connections[scene_id].discard(websocket)
|
||||
if not self.storage_location_connections[scene_id]:
|
||||
# 如果没有连接了,清理数据
|
||||
del self.storage_location_connections[scene_id]
|
||||
self.storage_location_last_push_time.pop(scene_id, None)
|
||||
logger.info(f"库位状态WebSocket连接已断开,场景ID: {scene_id}")
|
||||
|
||||
async def send_personal_message(self, message: str, websocket: WebSocket):
|
||||
"""发送个人消息"""
|
||||
try:
|
||||
@ -83,6 +108,23 @@ class ConnectionManager:
|
||||
# 清理断开的连接
|
||||
for websocket in disconnected_websockets:
|
||||
self.disconnect(websocket, task_record_id)
|
||||
|
||||
async def broadcast_to_storage_location(self, message: str, scene_id: str):
|
||||
"""向特定场景的所有库位状态连接广播消息"""
|
||||
if scene_id not in self.storage_location_connections:
|
||||
return
|
||||
|
||||
disconnected_websockets = []
|
||||
for websocket in self.storage_location_connections[scene_id].copy():
|
||||
try:
|
||||
await websocket.send_text(message)
|
||||
except Exception as e:
|
||||
logger.error(f"广播库位状态消息失败: {str(e)}")
|
||||
disconnected_websockets.append(websocket)
|
||||
|
||||
# 清理断开的连接
|
||||
for websocket in disconnected_websockets:
|
||||
self.disconnect_storage_location(websocket, scene_id)
|
||||
|
||||
# 连接管理器实例
|
||||
manager = ConnectionManager()
|
||||
@ -146,6 +188,127 @@ async def websocket_task_execution(
|
||||
finally:
|
||||
manager.disconnect(websocket, task_record_id)
|
||||
|
||||
@router.websocket("/storage-location/{scene_id}")
|
||||
async def websocket_storage_location_status(
|
||||
websocket: WebSocket,
|
||||
scene_id: str = Path(..., description="场景ID"),
|
||||
interval: int = Query(default=3, description="推送间隔(秒)", ge=1, le=30),
|
||||
storage_area_id: Optional[str] = Query(None, description="库区ID,用于过滤特定库区"),
|
||||
station_name: Optional[str] = Query(None, description="站点名称,用于过滤特定站点"),
|
||||
layer_name: Optional[str] = Query(None, description="层名称,用于过滤特定层"),
|
||||
is_occupied: Optional[bool] = Query(None, description="是否占用过滤"),
|
||||
is_locked: Optional[bool] = Query(None, description="是否锁定过滤"),
|
||||
is_disabled: Optional[bool] = Query(None, description="是否禁用过滤")
|
||||
):
|
||||
"""
|
||||
库位状态实时推送WebSocket连接
|
||||
|
||||
Args:
|
||||
websocket: WebSocket连接对象
|
||||
scene_id: 场景ID
|
||||
interval: 推送间隔(秒),默认3秒,范围1-30秒
|
||||
storage_area_id: 库区ID,用于过滤特定库区
|
||||
station_name: 站点名称,用于过滤特定站点
|
||||
layer_name: 层名称,用于过滤特定层
|
||||
is_occupied: 是否占用过滤
|
||||
is_locked: 是否锁定过滤
|
||||
is_disabled: 是否禁用过滤
|
||||
"""
|
||||
await manager.connect_storage_location(websocket, scene_id)
|
||||
|
||||
# 构建过滤条件
|
||||
filter_params = {
|
||||
"scene_id": scene_id,
|
||||
"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
|
||||
}
|
||||
|
||||
try:
|
||||
# 立即发送一次当前状态
|
||||
await send_storage_location_status(scene_id, websocket, filter_params)
|
||||
|
||||
# 启动定时推送任务
|
||||
push_task = asyncio.create_task(
|
||||
periodic_push_storage_location_status(websocket, scene_id, interval, filter_params)
|
||||
)
|
||||
|
||||
try:
|
||||
# 监听客户端消息
|
||||
while True:
|
||||
# 接收客户端消息
|
||||
data = await websocket.receive_text()
|
||||
try:
|
||||
message = json.loads(data)
|
||||
await handle_storage_location_websocket_message(websocket, scene_id, message, filter_params)
|
||||
except json.JSONDecodeError:
|
||||
await websocket.send_text(safe_json_dumps({
|
||||
"type": "error",
|
||||
"message": "无效的JSON格式"
|
||||
}, ensure_ascii=False))
|
||||
except Exception as e:
|
||||
logger.error(f"处理库位状态WebSocket消息失败: {str(e)}")
|
||||
await websocket.send_text(safe_json_dumps({
|
||||
"type": "error",
|
||||
"message": f"处理消息失败: {str(e)}"
|
||||
}, ensure_ascii=False))
|
||||
finally:
|
||||
# 取消定时推送任务
|
||||
push_task.cancel()
|
||||
try:
|
||||
await push_task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
except WebSocketDisconnect:
|
||||
logger.info(f"库位状态WebSocket客户端断开连接,场景ID: {scene_id}")
|
||||
except Exception as e:
|
||||
logger.error(f"库位状态WebSocket连接异常: {str(e)}")
|
||||
finally:
|
||||
manager.disconnect_storage_location(websocket, scene_id)
|
||||
|
||||
@router.websocket("/storage-location-broadcast/{scene_id}")
|
||||
async def websocket_storage_location_broadcast(
|
||||
websocket: WebSocket,
|
||||
scene_id: str = Path(..., description="场景ID")
|
||||
):
|
||||
"""
|
||||
库位状态广播WebSocket连接(只接收广播,不主动推送)
|
||||
|
||||
Args:
|
||||
websocket: WebSocket连接对象
|
||||
scene_id: 场景ID
|
||||
"""
|
||||
await manager.connect_storage_location(websocket, scene_id)
|
||||
|
||||
try:
|
||||
# 发送初始状态
|
||||
await send_storage_location_status(scene_id, websocket, {"scene_id": scene_id})
|
||||
|
||||
# 等待连接断开或消息
|
||||
while True:
|
||||
try:
|
||||
data = await websocket.receive_text()
|
||||
# 可以处理客户端的心跳或其他控制消息
|
||||
try:
|
||||
message = json.loads(data)
|
||||
if message.get("type") == "ping":
|
||||
await websocket.send_text(safe_json_dumps({
|
||||
"type": "pong",
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}, ensure_ascii=False))
|
||||
except:
|
||||
pass
|
||||
except WebSocketDisconnect:
|
||||
break
|
||||
except Exception as e:
|
||||
logger.error(f"库位状态广播WebSocket连接异常: {str(e)}")
|
||||
finally:
|
||||
manager.disconnect_storage_location(websocket, scene_id)
|
||||
|
||||
async def handle_websocket_message(websocket: WebSocket, task_record_id: str, message: Dict[str, Any]):
|
||||
"""
|
||||
处理WebSocket客户端消息
|
||||
@ -268,6 +431,149 @@ async def periodic_push_task_status(websocket: WebSocket, task_record_id: str, i
|
||||
except Exception as e:
|
||||
logger.error(f"定期推送任务状态失败: {str(e)}")
|
||||
|
||||
async def send_storage_location_status(scene_id: str, websocket: WebSocket, filter_params: Dict[str, Any]):
|
||||
"""
|
||||
发送库位状态
|
||||
|
||||
Args:
|
||||
scene_id: 场景ID
|
||||
websocket: WebSocket连接对象
|
||||
filter_params: 过滤参数
|
||||
"""
|
||||
try:
|
||||
# 获取库位状态
|
||||
with get_db() as db:
|
||||
# 构建请求参数,过滤掉None值
|
||||
request_params = {k: v for k, v in filter_params.items() if v is not None}
|
||||
# 设置默认分页参数
|
||||
request_params.setdefault("page", 1)
|
||||
request_params.setdefault("page_size", 1000) # 默认获取大量数据
|
||||
|
||||
request = StorageLocationListRequest(**request_params)
|
||||
result = OperatePointService.get_storage_location_list(db, request)
|
||||
|
||||
response_data = {
|
||||
"type": "storage_location_update",
|
||||
"scene_id": scene_id,
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"data": {
|
||||
"total": result.total,
|
||||
"page": result.page,
|
||||
"page_size": result.page_size,
|
||||
"total_pages": result.total_pages,
|
||||
"storage_locations": [location.dict() for location in result.storage_locations]
|
||||
},
|
||||
"message": "成功获取库位状态"
|
||||
}
|
||||
|
||||
await websocket.send_text(safe_json_dumps(response_data, ensure_ascii=False))
|
||||
manager.storage_location_last_push_time[scene_id] = datetime.now()
|
||||
logger.debug(f"已发送库位状态,场景ID: {scene_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"发送库位状态失败: {str(e)}")
|
||||
try:
|
||||
await websocket.send_text(safe_json_dumps({
|
||||
"type": "error",
|
||||
"scene_id": scene_id,
|
||||
"message": f"获取库位状态失败: {str(e)}",
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}, ensure_ascii=False))
|
||||
except:
|
||||
# 如果连接已断开,忽略错误
|
||||
pass
|
||||
|
||||
async def periodic_push_storage_location_status(websocket: WebSocket, scene_id: str, interval: int, filter_params: Dict[str, Any]):
|
||||
"""
|
||||
定期推送库位状态
|
||||
|
||||
Args:
|
||||
websocket: WebSocket连接对象
|
||||
scene_id: 场景ID
|
||||
interval: 推送间隔(秒)
|
||||
filter_params: 过滤参数
|
||||
"""
|
||||
logger.info(f"开始定期推送库位状态,场景ID: {scene_id}, 间隔: {interval}秒")
|
||||
|
||||
last_data_hash = None # 用于检测数据是否发生变化
|
||||
|
||||
try:
|
||||
while True:
|
||||
await asyncio.sleep(interval)
|
||||
|
||||
# 获取当前数据
|
||||
try:
|
||||
with get_db() as db:
|
||||
# 构建请求参数,过滤掉None值
|
||||
request_params = {k: v for k, v in filter_params.items() if v is not None}
|
||||
request_params.setdefault("page", 1)
|
||||
request_params.setdefault("page_size", 1000)
|
||||
|
||||
request = StorageLocationListRequest(**request_params)
|
||||
result = OperatePointService.get_storage_location_list(db, request)
|
||||
|
||||
# 计算数据哈希,只有数据变化时才推送
|
||||
import hashlib
|
||||
current_data = safe_json_dumps(
|
||||
[location.dict() for location in result.storage_locations],
|
||||
sort_keys=True, ensure_ascii=False
|
||||
)
|
||||
current_hash = hashlib.md5(current_data.encode()).hexdigest()
|
||||
|
||||
if current_hash != last_data_hash:
|
||||
await send_storage_location_status(scene_id, websocket, filter_params)
|
||||
last_data_hash = current_hash
|
||||
logger.debug(f"库位状态已更新并推送,场景ID: {scene_id}")
|
||||
else:
|
||||
logger.debug(f"库位状态无变化,跳过推送,场景ID: {scene_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取库位状态失败: {str(e)}")
|
||||
# 发送错误状态
|
||||
try:
|
||||
await websocket.send_text(safe_json_dumps({
|
||||
"type": "error",
|
||||
"scene_id": scene_id,
|
||||
"message": f"获取库位状态失败: {str(e)}",
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}, ensure_ascii=False))
|
||||
except:
|
||||
# 连接可能已断开
|
||||
break
|
||||
|
||||
except asyncio.CancelledError:
|
||||
logger.info(f"定期推送库位状态已取消,场景ID: {scene_id}")
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"定期推送库位状态失败: {str(e)}")
|
||||
|
||||
async def handle_storage_location_websocket_message(websocket: WebSocket, scene_id: str, message: Dict[str, Any], filter_params: Dict[str, Any]):
|
||||
"""
|
||||
处理库位状态WebSocket客户端消息
|
||||
|
||||
Args:
|
||||
websocket: WebSocket连接对象
|
||||
scene_id: 场景ID
|
||||
message: 客户端消息
|
||||
filter_params: 过滤参数
|
||||
"""
|
||||
message_type = message.get("type", "")
|
||||
|
||||
if message_type == "get_status":
|
||||
# 获取当前状态
|
||||
await send_storage_location_status(scene_id, websocket, filter_params)
|
||||
elif message_type == "ping":
|
||||
# 心跳检测
|
||||
await websocket.send_text(safe_json_dumps({
|
||||
"type": "pong",
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}, ensure_ascii=False))
|
||||
else:
|
||||
await websocket.send_text(safe_json_dumps({
|
||||
"type": "error",
|
||||
"message": f"不支持的消息类型: {message_type}"
|
||||
}, ensure_ascii=False))
|
||||
|
||||
@router.websocket("/task-execution-broadcast/{task_record_id}")
|
||||
async def websocket_task_execution_broadcast(
|
||||
websocket: WebSocket,
|
||||
@ -327,4 +633,50 @@ async def broadcast_task_update(task_record_id: str, data: Dict[str, Any]):
|
||||
}, ensure_ascii=False)
|
||||
|
||||
await manager.broadcast_to_task(message, task_record_id)
|
||||
logger.info(f"已广播任务更新消息,任务记录ID: {task_record_id}")
|
||||
logger.info(f"已广播任务更新消息,任务记录ID: {task_record_id}")
|
||||
|
||||
async def broadcast_storage_location_update(scene_id: str, data: Dict[str, Any]):
|
||||
"""
|
||||
广播库位状态更新消息给所有相关连接
|
||||
|
||||
Args:
|
||||
scene_id: 场景ID
|
||||
data: 要广播的数据
|
||||
"""
|
||||
if scene_id not in manager.storage_location_connections:
|
||||
return
|
||||
|
||||
message = safe_json_dumps({
|
||||
"type": "storage_location_update",
|
||||
"scene_id": scene_id,
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"data": data
|
||||
}, ensure_ascii=False)
|
||||
|
||||
await manager.broadcast_to_storage_location(message, scene_id)
|
||||
logger.info(f"已广播库位状态更新消息,场景ID: {scene_id}")
|
||||
|
||||
async def broadcast_storage_location_status_change(scene_id: str, layer_name: str, action: str, new_status: Dict[str, Any]):
|
||||
"""
|
||||
广播库位状态变化消息给所有相关连接
|
||||
|
||||
Args:
|
||||
scene_id: 场景ID
|
||||
layer_name: 层名称
|
||||
action: 操作类型
|
||||
new_status: 新状态
|
||||
"""
|
||||
if scene_id not in manager.storage_location_connections:
|
||||
return
|
||||
|
||||
message = safe_json_dumps({
|
||||
"type": "storage_location_status_change",
|
||||
"scene_id": scene_id,
|
||||
"layer_name": layer_name,
|
||||
"action": action,
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"new_status": new_status
|
||||
}, ensure_ascii=False)
|
||||
|
||||
await manager.broadcast_to_storage_location(message, scene_id)
|
||||
logger.info(f"已广播库位状态变化消息,场景ID: {scene_id},层名称: {layer_name},操作: {action}")
|
Binary file not shown.
Binary file not shown.
@ -192,6 +192,64 @@ class RobotBlockHandler(BlockHandler):
|
||||
async def _call_external_api(self, api_name: str, params: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""调用外部API的通用方法"""
|
||||
return await call_robot_api(api_name, params)
|
||||
|
||||
async def _validate_and_convert_key_route(self, key_route: str, map_id: str) -> tuple[bool, str, str]:
|
||||
"""
|
||||
校验并转换keyRoute参数
|
||||
|
||||
Args:
|
||||
key_route: 传入的关键路径,可能是动作点名称或库位名称
|
||||
map_id: 地图ID,用于校验场景ID
|
||||
|
||||
Returns:
|
||||
tuple: (是否成功, 最终的station_name, 错误消息)
|
||||
"""
|
||||
try:
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from data.session import get_async_session
|
||||
from data.models.operate_point import OperatePoint
|
||||
from data.models.operate_point_layer import OperatePointLayer
|
||||
from sqlalchemy import select
|
||||
|
||||
async with get_async_session() as session:
|
||||
session: AsyncSession = session
|
||||
|
||||
# 首先检查是否是动作点(operate_point表的station_name字段)
|
||||
stmt = select(OperatePoint).where(
|
||||
OperatePoint.station_name == key_route,
|
||||
OperatePoint.is_disabled == False,
|
||||
OperatePoint.scene_id == map_id
|
||||
)
|
||||
result = await session.execute(stmt)
|
||||
operate_point = result.scalar_one_or_none()
|
||||
|
||||
if operate_point:
|
||||
logger.info(f"keyRoute '{key_route}' 识别为动作点,场景ID: {map_id}")
|
||||
return True, key_route, ""
|
||||
|
||||
# 如果不是动作点,检查是否是库位(operate_point_layer表的layer_name字段)
|
||||
stmt = select(OperatePointLayer).where(
|
||||
OperatePointLayer.layer_name == key_route,
|
||||
OperatePointLayer.is_disabled == False,
|
||||
OperatePointLayer.scene_id == map_id
|
||||
)
|
||||
result = await session.execute(stmt)
|
||||
operate_point_layer = result.scalar_one_or_none()
|
||||
|
||||
if operate_point_layer:
|
||||
station_name = operate_point_layer.station_name
|
||||
logger.info(f"keyRoute '{key_route}' 识别为库位,对应的动作点: {station_name},场景ID: {map_id}")
|
||||
return True, station_name, ""
|
||||
|
||||
# 都不匹配,返回错误
|
||||
error_msg = f"keyRoute '{key_route}' 在场景 {map_id} 中既不是有效的动作点名称也不是有效的库位名称"
|
||||
logger.error(error_msg)
|
||||
return False, "", error_msg
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"校验keyRoute时发生异常: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
return False, "", error_msg
|
||||
|
||||
def _analyze_affected_blocks(self, block: Dict[str, Any], current_block_id: str, current_block_name: str) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
@ -397,6 +455,8 @@ class AgvOperationBlockHandler(RobotBlockHandler):
|
||||
# 获取关键参数用于验证
|
||||
target_site_label = input_params.get("targetSiteLabel")
|
||||
script_name = input_params.get("task")
|
||||
map_id = context.map_id
|
||||
|
||||
# 参数检查
|
||||
if not target_site_label:
|
||||
result = {
|
||||
@ -406,6 +466,18 @@ class AgvOperationBlockHandler(RobotBlockHandler):
|
||||
await self._record_task_log(block, result, context)
|
||||
return result
|
||||
|
||||
# 校验并转换target_site_label参数
|
||||
# print(f"AgvOperation input_params >>>>>>>>>>>>>>>>>>>>>>>>>>>> target_site_label: {target_site_label}, map_id: {map_id}")
|
||||
is_valid, validated_station_name, error_msg = await self._validate_and_convert_key_route(target_site_label, map_id)
|
||||
# print(f"AgvOperation output_params >>>>>>>>>>>>>>>>>>>>>>>>>>>> validated_station_name: {validated_station_name}, error_msg: {error_msg}")
|
||||
if not is_valid:
|
||||
result = {
|
||||
"success": False,
|
||||
"message": error_msg
|
||||
}
|
||||
await self._record_task_log(block, result, context)
|
||||
return result
|
||||
|
||||
# 获取当前块信息
|
||||
current_block_id = block.get("id", "unknown")
|
||||
current_block_name = block.get("name", f"b{current_block_id}")
|
||||
@ -418,9 +490,9 @@ class AgvOperationBlockHandler(RobotBlockHandler):
|
||||
|
||||
# 记录使用的机器人ID
|
||||
if vehicle:
|
||||
logger.info(f"执行机器人通用动作,块 {current_block_name}(ID:{current_block_id}) 使用机器人: {vehicle}, 目标站点: {target_site_label}")
|
||||
logger.info(f"执行机器人通用动作,块 {current_block_name}(ID:{current_block_id}) 使用机器人: {vehicle}, 目标站点: {validated_station_name} (原始输入: {target_site_label})")
|
||||
else:
|
||||
error_msg = f"执行机器人通用动作失败:未指定机器人ID,目标站点: {target_site_label}"
|
||||
error_msg = f"执行机器人通用动作失败:未指定机器人ID,目标站点: {validated_station_name}"
|
||||
logger.error(error_msg)
|
||||
result = {
|
||||
"success": False,
|
||||
@ -432,7 +504,7 @@ class AgvOperationBlockHandler(RobotBlockHandler):
|
||||
from services.sync_service import add_action
|
||||
result = await add_action(
|
||||
task_id=agv_task_id,
|
||||
station_name=target_site_label,
|
||||
station_name=validated_station_name, # 使用校验后的站点名称
|
||||
action=script_name,
|
||||
token=context.token
|
||||
)
|
||||
@ -442,17 +514,17 @@ class AgvOperationBlockHandler(RobotBlockHandler):
|
||||
task_id = result.get("result", {}).get("id", "")
|
||||
task_block_result = await wait_for_task_block_action_completion(task_id, context.token, context)
|
||||
if task_block_result.get("is_canceled", False):
|
||||
return {"success": True, "message": f"机器人通用动作取消,目标站点: {target_site_label}", "is_canceled": True}
|
||||
return {"success": True, "message": f"机器人通用动作取消,目标站点: {validated_station_name} 执行动作: {script_name}", "is_canceled": True}
|
||||
# return result
|
||||
if task_block_result.get("success", False):
|
||||
task_block_status = task_block_result.get("result", {}).get("status", "")
|
||||
if task_block_status == 3:
|
||||
result["message"] = f"机器人通用动作成功,目标站点: {target_site_label}"
|
||||
result["message"] = f"机器人通用动作成功,目标站点: {validated_station_name} 执行动作: {script_name}"
|
||||
elif task_block_status == 4:
|
||||
result["message"] = f"机器人通用动作失败,目标站点: {target_site_label}:{task_block_result.get('message', '')}"
|
||||
result["message"] = f"机器人通用动作失败,目标站点: {validated_station_name} 执行动作: {script_name}:{task_block_result.get('message', '')}"
|
||||
result["success"] = False
|
||||
elif task_block_status == 5:
|
||||
result["message"] = f"机器人通用动作终止,目标站点: {target_site_label}"
|
||||
result["message"] = f"机器人通用动作终止,目标站点: {validated_station_name} 执行动作: {script_name}"
|
||||
else:
|
||||
result["message"] = f"机器人通用动作失败: {result.get('message', '未知错误')}"
|
||||
|
||||
@ -650,6 +722,7 @@ class SelectAgvBlockHandler(RobotBlockHandler):
|
||||
priority = input_params.get("priority", 1)
|
||||
amr_name = input_params.get("vehicle", "")
|
||||
amr_group_name = input_params.get("group", "")
|
||||
map_id = context.map_id
|
||||
|
||||
# 确保priority是整数类型,默认为1
|
||||
try:
|
||||
@ -670,12 +743,23 @@ class SelectAgvBlockHandler(RobotBlockHandler):
|
||||
await self._record_task_log(block, result, context)
|
||||
return result
|
||||
|
||||
# 校验并转换keyRoute参数
|
||||
# print(f"input_params >>>>>>>>>>>>>>>>>>>>>>>>>>>> key_route: {key_route}, map_id: {map_id}")
|
||||
is_valid, key_station_name, error_msg = await self._validate_and_convert_key_route(key_route, map_id)
|
||||
# print(f"output_params >>>>>>>>>>>>>>>>>>>>>>>>>>>> key_station_name: {key_station_name}, error_msg: {error_msg}")
|
||||
if not is_valid:
|
||||
result = {
|
||||
"success": False,
|
||||
"message": error_msg
|
||||
}
|
||||
await self._record_task_log(block, result, context)
|
||||
return result
|
||||
|
||||
# 调用外部API选择执行机器人
|
||||
# result = await self._call_external_api("select_agv", input_params)
|
||||
from services.sync_service import create_choose_amr_task, wait_for_amr_selection
|
||||
result = await create_choose_amr_task(
|
||||
task_id=context.task_record_id,
|
||||
key_station_name=key_route,
|
||||
key_station_name=key_station_name, # 使用校验后的station_name
|
||||
amr_name=amr_name,
|
||||
amr_group_name=amr_group_name,
|
||||
token=context.token,
|
||||
|
@ -20,7 +20,7 @@ from data.models.taskrecord import VWEDTaskRecord
|
||||
from data.session import get_async_session
|
||||
from utils.component_manager import component_manager
|
||||
from data.enum.task_def_enum import EnableStatus, PeriodicTaskStatus, TaskStatusEnum
|
||||
from data.enum.task_record_enum import TaskStatus
|
||||
# from data.enum.task_record_enum import TaskStatus
|
||||
from utils.logger import get_logger
|
||||
|
||||
# 获取日志记录器
|
||||
|
Binary file not shown.
Binary file not shown.
@ -17,10 +17,11 @@ from concurrent.futures import ThreadPoolExecutor
|
||||
import requests
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
from config.tf_api_config import TF_API_TOKEN
|
||||
from config.settings import settings
|
||||
|
||||
|
||||
|
||||
class AlertSyncService:
|
||||
"""
|
||||
告警同步服务类
|
||||
@ -195,7 +196,7 @@ class AlertSyncService:
|
||||
json=alert_data,
|
||||
timeout=self.timeout,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'x-access-token': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NDk3NzY1MzEsInVzZXJuYW1lIjoiYWRtaW4ifQ.uRLHZuRQTrR2fHyA-dMzP46yXAa5wdjfdUcmr9PNY4g'
|
||||
'x-access-token': TF_API_TOKEN
|
||||
}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
|
Loading…
x
Reference in New Issue
Block a user