增加库位库区模块
This commit is contained in:
parent
1185f25fd3
commit
7ef023842b
@ -40,7 +40,7 @@ RUN mkdir -p logs && chmod -R 755 logs
|
||||
COPY . .
|
||||
|
||||
# 暴露端口
|
||||
EXPOSE 8000
|
||||
EXPOSE 8001
|
||||
|
||||
# 启动命令
|
||||
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8001"]
|
Binary file not shown.
4
app.py
4
app.py
@ -63,8 +63,8 @@ if __name__ == "__main__":
|
||||
# 从环境变量中获取端口,默认为8000
|
||||
import time
|
||||
# start_time = time.time()
|
||||
port = int(os.environ.get("PORT", settings.SERVER_PORT))
|
||||
|
||||
# port = int(os.environ.get("PORT", settings.SERVER_PORT))
|
||||
port = 8001
|
||||
# 打印启动配置信息
|
||||
logger.info(f"服务器配置 - Host: 0.0.0.0, Port: {port}, Workers: {settings.SERVER_WORKERS}, Reload: {settings.SERVER_RELOAD}")
|
||||
end_time = time.time()
|
||||
|
BIN
config/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
config/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
config/__pycache__/settings.cpython-313.pyc
Normal file
BIN
config/__pycache__/settings.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
@ -99,7 +99,26 @@
|
||||
"options": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Wait",
|
||||
"type": "String",
|
||||
"label": "等待 Wait",
|
||||
"description": "",
|
||||
"required": false,
|
||||
"defaultValue": null,
|
||||
"options": [
|
||||
{
|
||||
"name": "wait_time",
|
||||
"type": "Integer",
|
||||
"label": "等待时间(ms)",
|
||||
"description": "",
|
||||
"required": true,
|
||||
"defaultValue": null,
|
||||
"options": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
@ -239,7 +239,7 @@
|
||||
"label": "是否为降序",
|
||||
"description": "",
|
||||
"required": false,
|
||||
"defaultValue": false,
|
||||
"defaultValue": null,
|
||||
"options": []
|
||||
}
|
||||
],
|
||||
@ -399,7 +399,7 @@
|
||||
"extraInputParamsFunc": "",
|
||||
"outputParams": {},
|
||||
"contextVariables": {
|
||||
"site": {
|
||||
"siteId": {
|
||||
"type": "Object",
|
||||
"label": "选出的库位",
|
||||
"description": null,
|
||||
@ -564,7 +564,7 @@
|
||||
"label": "加锁者",
|
||||
"description": "",
|
||||
"required": false,
|
||||
"defaultValue": null,
|
||||
"defaultValue": "",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
@ -645,7 +645,7 @@
|
||||
"label": "解锁者",
|
||||
"description": "",
|
||||
"required": false,
|
||||
"defaultValue": null,
|
||||
"defaultValue": "",
|
||||
"options": []
|
||||
}
|
||||
],
|
||||
|
@ -27,8 +27,9 @@ class DBConfig:
|
||||
"pool_size": settings.DB_POOL_SIZE,
|
||||
"max_overflow": settings.DB_MAX_OVERFLOW,
|
||||
"pool_recycle": settings.DB_POOL_RECYCLE,
|
||||
"pool_timeout": settings.DB_POOL_TIMEOUT,
|
||||
"echo": False, # 强制关闭SQL日志输出
|
||||
"pool_pre_ping": True
|
||||
"pool_pre_ping": settings.DB_POOL_PRE_PING
|
||||
}
|
||||
|
||||
@classmethod
|
||||
|
@ -245,9 +245,11 @@ class BaseConfig(BaseSettings):
|
||||
DB_NAME: str = Field(default=_db_config['database'], env="DB_NAME")
|
||||
DB_CHARSET: str = Field(default=_db_config['charset'], env="DB_CHARSET")
|
||||
DB_ECHO: bool = False # 是否输出SQL语句
|
||||
DB_POOL_SIZE: int = 10
|
||||
DB_MAX_OVERFLOW: int = 20
|
||||
DB_POOL_RECYCLE: int = 3600 # 连接池回收时间,防止连接过期
|
||||
DB_POOL_SIZE: int = 50 # 增加连接池基础大小
|
||||
DB_MAX_OVERFLOW: int = 100 # 增加溢出连接数
|
||||
DB_POOL_RECYCLE: int = 1800 # 减少连接回收时间,防止连接过期
|
||||
DB_POOL_TIMEOUT: int = 60 # 获取连接的超时时间
|
||||
DB_POOL_PRE_PING: bool = True # 连接前检测连接可用性
|
||||
|
||||
# Redis配置
|
||||
_redis_config = get_config_for_env(_env, 'redis')
|
||||
|
@ -20,7 +20,8 @@ tf_api_endpoints = {
|
||||
"set_task_in_progress": "/task/vwedtask/{id}/inprogress",
|
||||
"set_task_completed": "/task/vwedtask/{id}/completed",
|
||||
"set_task_terminated": "/task/vwedtask/{id}/terminated",
|
||||
"set_task_failed": "/task/vwedtask/{id}/failed"
|
||||
"set_task_failed": "/task/vwedtask/{id}/failed",
|
||||
"set_task_description":"/task/vwedtask/{id}/description"
|
||||
}
|
||||
|
||||
# 系统内部服务API HTTP方法配置
|
||||
@ -34,18 +35,42 @@ tf_api_methods = {
|
||||
"set_task_in_progress": "PUT",
|
||||
"set_task_completed": "PUT",
|
||||
"set_task_terminated": "PUT",
|
||||
"set_task_failed": "PUT"
|
||||
"set_task_failed": "PUT",
|
||||
"set_task_description": "PUT"
|
||||
}
|
||||
# 外部任务类型对应的优先级
|
||||
TASK_TYPE_PRIORITY={
|
||||
"GT": "6", # 缸体
|
||||
"GG": "5", # 缸盖
|
||||
"QZ": "4", # 曲轴
|
||||
"ZG": "3",#
|
||||
"PHZ": "2",
|
||||
"LG": "1",
|
||||
"OR": "1"
|
||||
}
|
||||
# 外部任务对应所属库区
|
||||
TASK_TYPE_AREA={
|
||||
"GT": "ZK/ZKG",
|
||||
"GG": "ZK/ZKG",
|
||||
"QZ": "KW",
|
||||
"ZG": "ZK/ZKG",
|
||||
"PHZ": "AGW/PL",
|
||||
"LG": "AGW/PL"
|
||||
}
|
||||
|
||||
# 从环境变量读取配置,或使用默认值
|
||||
# TF_API_BASE_URL = os.getenv("TF_API_BASE_URL", "http://192.168.189.80:8080/jeecg-boot")
|
||||
TF_API_BASE_URL = os.getenv("TF_API_BASE_URL", "http://111.231.146.230:4080/jeecg-boot")
|
||||
TF_API_TIMEOUT = int(os.getenv("TF_API_TIMEOUT", "60"))
|
||||
TF_API_TIMEOUT = int(os.getenv("TF_API_TIMEOUT", "10")) # 减少超时时间从60秒到10秒
|
||||
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")
|
||||
|
||||
TF_WEB_POST = False # 是否过账
|
||||
sync_disabled_label = "sync_disabled" # 是否限制接口调用的变量名
|
||||
# 外部接口叫料模板id
|
||||
CM_ID = "571985c1-cfa5-4186-8acd-6e3868a5e08c"
|
||||
# 送货模板id
|
||||
DG_ID = "e22cacb4-a580-45ba-949e-356f57fa1a43"
|
||||
def get_tf_api_config() -> Dict[str, Any]:
|
||||
"""获取天风系统API配置"""
|
||||
return {
|
||||
|
BIN
data/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
data/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
data/__pycache__/session.cpython-313.pyc
Normal file
BIN
data/__pycache__/session.cpython-313.pyc
Normal file
Binary file not shown.
@ -23,6 +23,7 @@ from data.models.operate_point import OperatePoint
|
||||
from data.models.operate_point_layer import OperatePointLayer
|
||||
from data.models.extended_property import ExtendedProperty, ExtendedPropertyTypeEnum
|
||||
from data.models.storage_location_log import StorageLocationLog
|
||||
from data.models.external_task_record import VWEDExternalTaskRecord, ExternalTaskTypeEnum, ExternalTaskStatusEnum
|
||||
|
||||
# 导出所有模型供应用程序使用
|
||||
__all__ = [
|
||||
@ -31,5 +32,6 @@ __all__ = [
|
||||
'VWEDScript', 'VWEDScriptVersion', 'VWEDScriptLog', 'VWEDModbusConfig',
|
||||
'VWEDCallDevice', 'VWEDCallDeviceButton', 'InterfaceDefHistory',
|
||||
'StorageArea', 'StorageAreaType', 'OperatePoint', 'OperatePointLayer',
|
||||
'ExtendedProperty', 'ExtendedPropertyTypeEnum', 'StorageLocationLog'
|
||||
'ExtendedProperty', 'ExtendedPropertyTypeEnum', 'StorageLocationLog',
|
||||
'VWEDExternalTaskRecord', 'ExternalTaskTypeEnum', 'ExternalTaskStatusEnum'
|
||||
]
|
||||
|
Binary file not shown.
BIN
data/models/__pycache__/external_task_record.cpython-312.pyc
Normal file
BIN
data/models/__pycache__/external_task_record.cpython-312.pyc
Normal file
Binary file not shown.
137
data/models/external_task_record.py
Normal file
137
data/models/external_task_record.py
Normal file
@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
外部任务执行记录模型
|
||||
用于记录外部接口调用的任务执行情况
|
||||
"""
|
||||
|
||||
import datetime
|
||||
from sqlalchemy import Column, String, Integer, Text, Boolean, Index, Enum as SQLEnum
|
||||
from sqlalchemy.dialects.mysql import DATETIME, LONGTEXT
|
||||
from data.models.base import BaseModel
|
||||
import enum
|
||||
|
||||
class ExternalTaskTypeEnum(enum.Enum):
|
||||
"""外部任务类型枚举"""
|
||||
NEW_TASK = "newTask" # /newTask 接口
|
||||
GEN_AGV_SCHEDULING_TASK = "GenAgvSchedulingTask" # /GenAgvSchedulingTask 接口
|
||||
|
||||
class ExternalTaskStatusEnum(enum.Enum):
|
||||
"""外部任务状态枚举"""
|
||||
PENDING = "pending" # 待执行
|
||||
RUNNING = "running" # 执行中
|
||||
SUCCESS = "success" # 执行成功
|
||||
FAILED = "failed" # 执行失败
|
||||
CANCELLED = "cancelled" # 已取消
|
||||
|
||||
class VWEDExternalTaskRecord(BaseModel):
|
||||
"""
|
||||
外部任务执行记录模型
|
||||
对应vwed_external_task_record表
|
||||
功能:记录外部接口调用的任务执行情况和关联关系
|
||||
"""
|
||||
__tablename__ = 'vwed_external_task_record'
|
||||
|
||||
__table_args__ = (
|
||||
Index('idx_external_task_req_code', 'req_code'),
|
||||
Index('idx_external_task_task_code', 'task_code'),
|
||||
Index('idx_external_task_type', 'task_type'),
|
||||
Index('idx_external_task_status', 'task_status'),
|
||||
Index('idx_external_task_record_id', 'task_record_id'),
|
||||
Index('idx_external_task_related_req_code', 'related_req_code'),
|
||||
Index('idx_external_task_created_at', 'created_at'),
|
||||
{
|
||||
'mysql_engine': 'InnoDB',
|
||||
'mysql_charset': 'utf8mb4',
|
||||
'mysql_collate': 'utf8mb4_general_ci',
|
||||
'info': {'order_by': 'created_at DESC'}
|
||||
}
|
||||
)
|
||||
|
||||
# 主键和基本信息
|
||||
id = Column(String(255), primary_key=True, nullable=False, comment='主键ID')
|
||||
req_code = Column(String(255), nullable=False, comment='请求标识码(ReqCode)')
|
||||
task_type = Column(SQLEnum(ExternalTaskTypeEnum), nullable=False, comment='外部任务类型(newTask或GenAgvSchedulingTask)')
|
||||
task_status = Column(SQLEnum(ExternalTaskStatusEnum), nullable=False, default=ExternalTaskStatusEnum.PENDING, comment='任务状态')
|
||||
|
||||
# 关联字段 - 用于两个任务之间的关联
|
||||
task_code = Column(String(255), comment='任务代码(TaskCode,GenAgvSchedulingTask专用)')
|
||||
related_req_code = Column(String(255), comment='关联的ReqCode(用于关联newTask和GenAgvSchedulingTask)')
|
||||
|
||||
# TaskEditService.run_task 返回的任务记录ID
|
||||
task_record_id = Column(String(255), comment='内部任务记录ID(用于检测任务是否结束)')
|
||||
|
||||
# newTask 接口专用字段
|
||||
source_id = Column(String(255), comment='来源ID(SourceID)')
|
||||
target_id = Column(String(255), comment='目标ID(TargetID)')
|
||||
business_task_type = Column(String(50), comment='业务任务类型(TaskType,如GT2MP)')
|
||||
|
||||
# GenAgvSchedulingTask 接口专用字段
|
||||
security_key = Column(String(255), comment='安全密钥(SecurityKey)')
|
||||
type_field = Column(String(50), comment='类型字段(Type)')
|
||||
sub_type = Column(String(50), comment='子类型(SubType)')
|
||||
area_position_code = Column(String(255), comment='区域位置代码(AreaPositonCode)')
|
||||
area_position_name = Column(String(255), comment='区域位置名称(AreaPositonName)')
|
||||
position_code_path = Column(LONGTEXT, comment='位置代码路径JSON(PositionCodePath)')
|
||||
client_code = Column(String(255), comment='客户端代码(ClientCode)')
|
||||
token_code = Column(String(255), comment='令牌代码(TokenCode)')
|
||||
|
||||
# 请求和响应信息
|
||||
request_params = Column(LONGTEXT, comment='完整请求参数JSON')
|
||||
response_data = Column(LONGTEXT, comment='完整响应数据JSON')
|
||||
response_code = Column(Integer, comment='响应状态码')
|
||||
response_message = Column(Text, comment='响应消息')
|
||||
response_row_count = Column(Integer, comment='响应行数(rowCount)')
|
||||
|
||||
# 执行时间信息
|
||||
start_time = Column(DATETIME(fsp=6), comment='任务开始时间')
|
||||
end_time = Column(DATETIME(fsp=6), comment='任务结束时间')
|
||||
duration = Column(Integer, comment='执行时长(毫秒)')
|
||||
|
||||
# 客户端信息
|
||||
client_ip = Column(String(50), comment='客户端IP地址')
|
||||
client_info = Column(Text, comment='客户端信息JSON')
|
||||
|
||||
# 任务模板信息
|
||||
template_id = Column(String(255), comment='使用的任务模板ID')
|
||||
|
||||
# 错误信息
|
||||
error_message = Column(Text, comment='错误信息')
|
||||
error_stack = Column(LONGTEXT, comment='错误堆栈信息')
|
||||
|
||||
# 备注信息
|
||||
remarks = Column(Text, comment='备注信息')
|
||||
|
||||
def __repr__(self):
|
||||
return f"<VWEDExternalTaskRecord(id='{self.id}', req_code='{self.req_code}', task_type='{self.task_type}', status='{self.task_status}')>"
|
||||
|
||||
def is_completed(self):
|
||||
"""判断任务是否已完成(成功或失败)"""
|
||||
return self.task_status in [ExternalTaskStatusEnum.SUCCESS, ExternalTaskStatusEnum.FAILED, ExternalTaskStatusEnum.CANCELLED]
|
||||
|
||||
def update_status(self, status, error_message=None, response_data=None):
|
||||
"""更新任务状态"""
|
||||
self.task_status = status
|
||||
if error_message:
|
||||
self.error_message = error_message
|
||||
if response_data:
|
||||
self.response_data = response_data
|
||||
|
||||
# 更新时间信息
|
||||
now = datetime.datetime.now()
|
||||
if status == ExternalTaskStatusEnum.RUNNING and not self.start_time:
|
||||
self.start_time = now
|
||||
elif status in [ExternalTaskStatusEnum.SUCCESS, ExternalTaskStatusEnum.FAILED, ExternalTaskStatusEnum.CANCELLED]:
|
||||
if not self.end_time:
|
||||
self.end_time = now
|
||||
if self.start_time and not self.duration:
|
||||
self.duration = int((self.end_time - self.start_time).total_seconds() * 1000) # 毫秒
|
||||
|
||||
def get_related_task(self, session):
|
||||
"""获取关联的任务记录"""
|
||||
if not self.related_req_code:
|
||||
return None
|
||||
return session.query(VWEDExternalTaskRecord).filter(
|
||||
VWEDExternalTaskRecord.req_code == self.related_req_code
|
||||
).first()
|
@ -100,6 +100,28 @@ async def get_async_session():
|
||||
await session.close()
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def get_async_session_read_committed():
|
||||
"""
|
||||
获取使用READ COMMITTED隔离级别的异步数据库会话
|
||||
用于需要读取最新已提交数据的场景,解决并发数据同步问题
|
||||
|
||||
Yields:
|
||||
AsyncSession: 异步数据库会话对象
|
||||
"""
|
||||
session = AsyncSessionLocal()
|
||||
try:
|
||||
# 设置事务隔离级别为READ COMMITTED
|
||||
await session.execute(text("SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED"))
|
||||
yield session
|
||||
await session.commit()
|
||||
except Exception as e:
|
||||
await session.rollback()
|
||||
raise e
|
||||
finally:
|
||||
await session.close()
|
||||
|
||||
|
||||
async def get_async_db():
|
||||
"""
|
||||
获取异步数据库会话生成器,用于FastAPI依赖注入
|
||||
|
59572
logs/app.log
59572
logs/app.log
File diff suppressed because it is too large
Load Diff
167873
logs/app.log.1
167873
logs/app.log.1
File diff suppressed because it is too large
Load Diff
96521
logs/app.log.2
Normal file
96521
logs/app.log.2
Normal file
File diff suppressed because it is too large
Load Diff
56329
logs/app.log.2025-08-03
Normal file
56329
logs/app.log.2025-08-03
Normal file
File diff suppressed because it is too large
Load Diff
1797
logs/app.log.2025-08-05
Normal file
1797
logs/app.log.2025-08-05
Normal file
File diff suppressed because it is too large
Load Diff
14734
logs/app.log.2025-08-06
Normal file
14734
logs/app.log.2025-08-06
Normal file
File diff suppressed because it is too large
Load Diff
106059
logs/app.log.2025-08-07
Normal file
106059
logs/app.log.2025-08-07
Normal file
File diff suppressed because it is too large
Load Diff
194808
logs/app.log.2025-08-08
Normal file
194808
logs/app.log.2025-08-08
Normal file
File diff suppressed because it is too large
Load Diff
21791
logs/app.log.2025-08-10
Normal file
21791
logs/app.log.2025-08-10
Normal file
File diff suppressed because it is too large
Load Diff
55620
logs/app.log.2025-08-11
Normal file
55620
logs/app.log.2025-08-11
Normal file
File diff suppressed because it is too large
Load Diff
2396
logs/app.log.2025-08-12
Normal file
2396
logs/app.log.2025-08-12
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
@ -65,4 +65,9 @@ class GenAgvSchedulingTaskRequest(BaseModel):
|
||||
AreaPositonName: str = Field("", description="区域位置名称")
|
||||
PositionCodePath: List[PositionCodePath1] = Field(..., description="位置代码路径")
|
||||
ClientCode: str = Field("", description="客户端代码")
|
||||
TokenCode: str = Field("", description="令牌代码")
|
||||
TokenCode: str = Field("", description="令牌代码")
|
||||
|
||||
# 取消任务请求模型
|
||||
class CancelTaskRequest(BaseModel):
|
||||
"""取消任务请求模型"""
|
||||
ReqCode: str = Field(..., description="请求唯一标识码")
|
@ -205,6 +205,7 @@ class TaskEditRunRequest(BaseModel):
|
||||
modbus_configs: Optional[List[ModbusConfigParam]] = Field(None, description="任务关联的Modbus配置列表,用于任务执行时的Modbus操作")
|
||||
use_modbus: Optional[bool] = Field(False, description="是否使用Modbus通信")
|
||||
modbus_timeout: Optional[int] = Field(5000, description="Modbus通信超时时间(毫秒)")
|
||||
priority: Optional[int] = Field(1, description="任务优先级 大于等于 1")
|
||||
|
||||
|
||||
# 重新定义更准确的任务参数模型,确保包含所有必要字段
|
||||
|
662
scripts/automated_task_test.py
Normal file
662
scripts/automated_task_test.py
Normal file
@ -0,0 +1,662 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
自动化任务测试脚本
|
||||
用于测试create_new_task和gen_agv_scheduling_task接口
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import random
|
||||
import time
|
||||
import uuid
|
||||
import sys
|
||||
import os
|
||||
from typing import Dict, List, Any, Optional
|
||||
import aiohttp
|
||||
import json
|
||||
from datetime import datetime
|
||||
from asyncio import Semaphore, Queue
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from data.session import get_async_session
|
||||
from data.models.operate_point_layer import OperatePointLayer
|
||||
from data.models.external_task_record import VWEDExternalTaskRecord
|
||||
from services.task_record_service import TaskRecordService
|
||||
from data.enum.task_record_enum import TaskStatus
|
||||
from sqlalchemy import select, and_
|
||||
|
||||
# API配置
|
||||
BASE_URL = "http://localhost:8001"
|
||||
CREATE_TASK_URL = f"{BASE_URL}/newTask"
|
||||
GEN_AGV_TASK_URL = f"{BASE_URL}/GenAgvSchedulingTask"
|
||||
|
||||
# 任务类型配置
|
||||
CREATE_TASK_TYPES = ["GG2MP", "GGFK2MP", "GT2MP", "GTFK2MP", "ZG2MP", "QZ2MP", "LG2MP", "PHZ2MP"]
|
||||
|
||||
# 任务类型分组配置 - 确保同时运行的4个任务尽可能不同类型
|
||||
TASK_TYPE_GROUPS = {
|
||||
"GG": ["GG2MP", "GGFK2MP"], # 钢构类型
|
||||
"GT": ["GT2MP", "GTFK2MP"], # 钢台类型
|
||||
"ZG": ["ZG2MP"], # 铸钢类型
|
||||
"QZ": ["QZ2MP"], # 其他类型1
|
||||
"LG": ["LG2MP"], # 其他类型2
|
||||
"PHZ": ["PHZ2MP"] # 其他类型3
|
||||
}
|
||||
# GEN_TASK_TYPES = ["MP2GG", "MP2GGFK", "MP2GT", "MP2GTFK", "MP2ZG", "MP2QZ", "MP2LG", "MP2PHZ"]
|
||||
CREATE_TASK_TYPES_JSON = {
|
||||
"GG2MP": "MP2GG",
|
||||
"GGFK2MP": "MP2GGFK",
|
||||
"GT2MP": "MP2GT",
|
||||
"GTFK2MP": "MP2GTFK",
|
||||
"ZG2MP": "MP2ZG",
|
||||
"QZ2MP": "MP2QZ",
|
||||
"LG2MP": "MP2LG",
|
||||
"PHZ2MP": "MP2PHZ",
|
||||
}
|
||||
|
||||
# 任务类型对应的动作点映射
|
||||
CREATE_TASK_TARGET_MAPPING = {
|
||||
"QZ2MP": ["AP135", "AP136"],
|
||||
"PHZ2MP": ["AP208", "AP209", "AP210", "AP211", "AP212", "AP213", "AP214", "AP215"],
|
||||
"LG2MP": ["AP216", "AP217"],
|
||||
"GT2MP": ["AP255", "AP256", "AP257", "AP258"],
|
||||
"GTFK2MP": ["AP255", "AP256", "AP257", "AP258"],
|
||||
"GG2MP": ["AP310", "AP311", "AP312", "AP313"],
|
||||
"GGFK2MP": ["AP310", "AP311", "AP312", "AP313"],
|
||||
"ZG2MP": ["AP297", "AP298"]
|
||||
}
|
||||
|
||||
GEN_TASK_ENDPOINT_MAPPING = {
|
||||
"MP2GG": ["AP310", "AP311", "AP312", "AP313"],
|
||||
"MP2GGFK": ["AP310", "AP311", "AP312", "AP313"],
|
||||
"MP2GT": ["AP255", "AP256", "AP257", "AP258"],
|
||||
"MP2GTFK": ["AP255", "AP256", "AP257", "AP258"],
|
||||
"MP2ZG": ["AP297", "AP298"],
|
||||
"MP2QZ": ["AP135", "AP136"],
|
||||
"MP2LG": ["AP216", "AP217"],
|
||||
"MP2PHZ": ["AP208", "AP209", "AP210", "AP211", "AP212", "AP213", "AP214", "AP215"]
|
||||
}
|
||||
|
||||
# 任务类型对应库区映射
|
||||
TASK_TYPE_AREA_MAPPING = {
|
||||
"MP2GG": ["ZK/ZKG"],
|
||||
"MP2GGFK": ["ZK/ZKG"],
|
||||
"MP2GT": ["ZK/ZKG"],
|
||||
"MP2GTFK": ["ZK/ZKG"],
|
||||
"MP2ZG": ["ZK/ZKG"],
|
||||
"MP2QZ": ["KW"],
|
||||
"MP2LG": ["AGW/PL"],
|
||||
"MP2PHZ": ["AGW/PL"]
|
||||
}
|
||||
|
||||
|
||||
class AutomatedTaskTester:
|
||||
"""自动化任务测试器"""
|
||||
|
||||
def __init__(self, max_concurrent_tasks: int = 4, max_total_tasks: int = 400):
|
||||
self.used_req_codes = set()
|
||||
self.session = None
|
||||
self.create_task_records = [] # 存储create_new_task的记录
|
||||
self.max_concurrent_tasks = max_concurrent_tasks # 最大并发任务数
|
||||
self.max_total_tasks = max_total_tasks # 最大总任务数
|
||||
self.semaphore = Semaphore(max_concurrent_tasks) # 并发控制信号量
|
||||
self.task_queue = Queue(maxsize=max_total_tasks) # 任务队列
|
||||
self.completed_tasks = 0 # 已完成任务数
|
||||
self.success_count = 0 # 成功任务数
|
||||
self.failed_count = 0 # 失败任务数
|
||||
self.task_lock = asyncio.Lock() # 任务统计锁
|
||||
self.running_task_types = set() # 当前运行的任务类型组
|
||||
self.task_type_lock = asyncio.Lock() # 任务类型锁
|
||||
|
||||
async def __aenter__(self):
|
||||
"""异步上下文管理器入口"""
|
||||
self.session = aiohttp.ClientSession()
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||
"""异步上下文管理器出口"""
|
||||
if self.session:
|
||||
await self.session.close()
|
||||
|
||||
def generate_unique_req_code(self) -> str:
|
||||
"""生成唯一的请求码"""
|
||||
while True:
|
||||
req_code = f"TEST_{int(time.time() * 1000)}_{random.randint(1000, 9999)}"
|
||||
if req_code not in self.used_req_codes:
|
||||
self.used_req_codes.add(req_code)
|
||||
return req_code
|
||||
|
||||
async def get_available_storage_locations(self, area_names: List[str]) -> List[str]:
|
||||
"""
|
||||
查询指定库区中已占用且未锁定的库位,如果找不到则持续查询直到找到
|
||||
|
||||
Args:
|
||||
area_names: 库区名称列表
|
||||
|
||||
Returns:
|
||||
List[str]: 可用库位的station_name列表
|
||||
"""
|
||||
print(f"开始在库区 {area_names} 中查询可用库位...")
|
||||
attempt = 0
|
||||
|
||||
while True:
|
||||
attempt += 1
|
||||
try:
|
||||
async with get_async_session() as db_session:
|
||||
stmt = select(OperatePointLayer).where(
|
||||
and_(
|
||||
OperatePointLayer.area_name.in_(area_names),
|
||||
OperatePointLayer.is_occupied == True,
|
||||
OperatePointLayer.is_locked == False,
|
||||
OperatePointLayer.is_deleted == False
|
||||
)
|
||||
)
|
||||
result = await db_session.execute(stmt)
|
||||
layers = result.scalars().all()
|
||||
|
||||
station_names = [layer.station_name for layer in layers if layer.station_name]
|
||||
|
||||
if station_names:
|
||||
print(f"✓ 第{attempt}次尝试:在库区 {area_names} 中找到 {len(station_names)} 个可用库位")
|
||||
return station_names
|
||||
else:
|
||||
print(f"✗ 第{attempt}次尝试:在库区 {area_names} 中未找到可用库位,等待2秒后重试...")
|
||||
await asyncio.sleep(2)
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 第{attempt}次查询库位时出错: {str(e)},等待2秒后重试...")
|
||||
await asyncio.sleep(2)
|
||||
|
||||
def get_task_type_group(self, task_type: str) -> str:
|
||||
"""
|
||||
获取任务类型对应的组名
|
||||
|
||||
Args:
|
||||
task_type: 任务类型
|
||||
|
||||
Returns:
|
||||
str: 组名
|
||||
"""
|
||||
for group_name, types in TASK_TYPE_GROUPS.items():
|
||||
if task_type in types:
|
||||
return group_name
|
||||
return "UNKNOWN"
|
||||
|
||||
async def select_task_type_with_diversity(self) -> Optional[str]:
|
||||
"""
|
||||
选择任务类型,确保尽可能不重复组类型
|
||||
|
||||
Returns:
|
||||
Optional[str]: 选择的任务类型,如果没有可用类型返回None
|
||||
"""
|
||||
async with self.task_type_lock:
|
||||
available_groups = []
|
||||
|
||||
# 找出当前未使用的组
|
||||
for group_name in TASK_TYPE_GROUPS.keys():
|
||||
if group_name not in self.running_task_types:
|
||||
available_groups.append(group_name)
|
||||
|
||||
# 如果没有未使用的组,且当前运行任务少于最大并发数,可以重复使用组
|
||||
if not available_groups and len(self.running_task_types) < self.max_concurrent_tasks:
|
||||
available_groups = list(TASK_TYPE_GROUPS.keys())
|
||||
|
||||
if not available_groups:
|
||||
return None # 所有组都在使用中
|
||||
|
||||
# 随机选择一个组
|
||||
selected_group = random.choice(available_groups)
|
||||
|
||||
# 从该组中随机选择一个任务类型
|
||||
task_types = TASK_TYPE_GROUPS[selected_group]
|
||||
selected_task_type = random.choice(task_types)
|
||||
|
||||
# 标记该组为使用中
|
||||
self.running_task_types.add(selected_group)
|
||||
|
||||
return selected_task_type
|
||||
|
||||
async def release_task_type_group(self, task_type: str):
|
||||
"""
|
||||
释放任务类型组
|
||||
|
||||
Args:
|
||||
task_type: 任务类型
|
||||
"""
|
||||
async with self.task_type_lock:
|
||||
group_name = self.get_task_type_group(task_type)
|
||||
self.running_task_types.discard(group_name)
|
||||
|
||||
async def monitor_task_status(self, req_code: str, timeout: int = 5) -> bool:
|
||||
"""
|
||||
监控任务状态,超时或失败时返回False
|
||||
|
||||
Args:
|
||||
req_code: 请求码
|
||||
timeout: 超时时间(秒)
|
||||
|
||||
Returns:
|
||||
bool: True表示任务成功或继续运行,False表示失败或超时
|
||||
"""
|
||||
start_time = time.time()
|
||||
|
||||
while time.time() - start_time < timeout:
|
||||
try:
|
||||
# 通过req_code查询external_task_record表获取task_record_id
|
||||
async with get_async_session() as db_session:
|
||||
stmt = select(VWEDExternalTaskRecord).where(
|
||||
VWEDExternalTaskRecord.req_code == req_code
|
||||
)
|
||||
result = await db_session.execute(stmt)
|
||||
external_record = result.scalar_one_or_none()
|
||||
|
||||
if external_record and external_record.task_record_id:
|
||||
# 调用TaskRecordService.get_task_record_detail获取任务状态
|
||||
task_detail_result = await TaskRecordService.get_task_record_detail(
|
||||
external_record.task_record_id
|
||||
)
|
||||
|
||||
if task_detail_result.get("success", False):
|
||||
task_detail = task_detail_result.get("data", {})
|
||||
task_status = task_detail.get("status", "")
|
||||
|
||||
# 如果任务失败,返回False
|
||||
if task_status == TaskStatus.FAILED:
|
||||
print(f"任务 {req_code} 失败,状态: {task_status}")
|
||||
return False
|
||||
|
||||
# 如果任务成功完成,返回True(可以继续后续步骤)
|
||||
if task_status == TaskStatus.COMPLETED:
|
||||
print(f"任务 {req_code} 完成,状态: {task_status}")
|
||||
return True
|
||||
|
||||
# 任务还在运行中,继续监控
|
||||
print(f"任务 {req_code} 运行中,状态: {task_status}")
|
||||
|
||||
await asyncio.sleep(1) # 每秒检查一次
|
||||
|
||||
except Exception as e:
|
||||
print(f"监控任务状态异常: {str(e)}")
|
||||
await asyncio.sleep(1)
|
||||
|
||||
# 超时,返回True继续执行(不阻塞后续流程)
|
||||
print(f"任务 {req_code} 监控超时,继续执行后续流程")
|
||||
return True
|
||||
|
||||
async def monitor_agv_task_completion(self, req_code: str) -> bool:
|
||||
"""
|
||||
监控GenAgvSchedulingTask任务完成状态,必须等待任务完成(成功或失败)
|
||||
|
||||
Args:
|
||||
req_code: GenAgvSchedulingTask的请求码
|
||||
|
||||
Returns:
|
||||
bool: True表示任务完成(成功或失败),用于统计
|
||||
"""
|
||||
print(f"开始监控AGV调度任务完成状态: {req_code}")
|
||||
|
||||
while True:
|
||||
try:
|
||||
# 通过req_code查询external_task_record表获取task_record_id
|
||||
async with get_async_session() as db_session:
|
||||
stmt = select(VWEDExternalTaskRecord).where(
|
||||
VWEDExternalTaskRecord.req_code == req_code
|
||||
)
|
||||
result = await db_session.execute(stmt)
|
||||
external_record = result.scalar_one_or_none()
|
||||
|
||||
if external_record and external_record.task_record_id:
|
||||
# 调用TaskRecordService.get_task_record_detail获取任务状态
|
||||
task_detail_result = await TaskRecordService.get_task_record_detail(
|
||||
external_record.task_record_id
|
||||
)
|
||||
|
||||
if task_detail_result.get("success", False):
|
||||
task_detail = task_detail_result.get("data", {})
|
||||
task_status = task_detail.get("status", "")
|
||||
|
||||
print(f"AGV调度任务 {req_code} 当前状态: {task_status}")
|
||||
|
||||
# 如果任务完成(成功或失败),返回对应结果
|
||||
if task_status == TaskStatus.COMPLETED:
|
||||
print(f"✓ AGV调度任务 {req_code} 执行成功完成")
|
||||
return True
|
||||
|
||||
elif task_status == TaskStatus.FAILED:
|
||||
print(f"✗ AGV调度任务 {req_code} 执行失败")
|
||||
return False
|
||||
|
||||
elif task_status == TaskStatus.CANCELED:
|
||||
print(f"⚠ AGV调度任务 {req_code} 被取消")
|
||||
return False
|
||||
|
||||
# 任务还在运行中,继续监控
|
||||
else:
|
||||
print(f"AGV调度任务 {req_code} 仍在运行中,状态: {task_status}")
|
||||
else:
|
||||
print(f"无法获取AGV调度任务 {req_code} 的详情,继续等待...")
|
||||
else:
|
||||
print(f"未找到AGV调度任务 {req_code} 的记录或task_record_id,继续等待...")
|
||||
|
||||
await asyncio.sleep(2) # 每2秒检查一次
|
||||
|
||||
except Exception as e:
|
||||
print(f"监控AGV调度任务状态异常: {str(e)}")
|
||||
await asyncio.sleep(2) # 异常时等待2秒后重试
|
||||
|
||||
async def create_new_task(self, task_type: str, target_id: str, req_code: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
调用create_new_task接口创建新任务
|
||||
|
||||
Args:
|
||||
task_type: 任务类型
|
||||
target_id: 目标ID
|
||||
|
||||
Returns:
|
||||
Dict: 接口响应结果
|
||||
"""
|
||||
|
||||
payload = {
|
||||
"ReqCode": req_code,
|
||||
"SourceID": "",
|
||||
"TargetID": target_id,
|
||||
"TaskType": task_type
|
||||
}
|
||||
|
||||
try:
|
||||
async with self.session.post(CREATE_TASK_URL, json=payload) as response:
|
||||
result = await response.json()
|
||||
print(f"创建任务 - 类型: {task_type}, 目标: {target_id}, 请求码: {req_code}")
|
||||
print(f"响应: {result}")
|
||||
|
||||
if result.get("code") == 0:
|
||||
self.create_task_records.append({
|
||||
"req_code": req_code,
|
||||
"task_type": task_type,
|
||||
"target_id": target_id,
|
||||
"response": result
|
||||
})
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
print(f"创建任务失败: {str(e)}")
|
||||
return None
|
||||
|
||||
async def gen_agv_scheduling_task(self, task_type: str, task_code: str, start_point: str, end_point: str) -> \
|
||||
Optional[Dict[str, Any]]:
|
||||
"""
|
||||
调用gen_agv_scheduling_task接口生成AGV调度任务
|
||||
|
||||
Args:
|
||||
task_type: 任务类型
|
||||
task_code: 任务代码(create_new_task的ReqCode)
|
||||
start_point: 起点
|
||||
end_point: 终点
|
||||
|
||||
Returns:
|
||||
Dict: 接口响应结果
|
||||
"""
|
||||
req_code = self.generate_unique_req_code()
|
||||
|
||||
payload = {
|
||||
"ReqCode": req_code,
|
||||
"TaskTyp": task_type,
|
||||
"TaskCode": task_code,
|
||||
"SecurityKey": "",
|
||||
"Type": "",
|
||||
"SubType": "",
|
||||
"AreaPositonCode": "",
|
||||
"AreaPositonName": "",
|
||||
"PositionCodePath": [
|
||||
{"PositionCode": start_point, "Type": "start"},
|
||||
{"PositionCode": end_point, "Type": "end"}
|
||||
],
|
||||
"ClientCode": "",
|
||||
"TokenCode": ""
|
||||
}
|
||||
|
||||
try:
|
||||
async with self.session.post(GEN_AGV_TASK_URL, json=payload) as response:
|
||||
result = await response.json()
|
||||
print(f"生成AGV调度 - 类型: {task_type}, 任务码: {task_code}, 路径: {start_point} -> {end_point}")
|
||||
print(f"响应: {result}")
|
||||
|
||||
# 确保返回的结果包含我们生成的req_code,用于后续监控
|
||||
if result and isinstance(result, dict):
|
||||
result["generated_req_code"] = req_code # 添加生成的req_code
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
print(f"生成AGV调度任务失败: {str(e)}")
|
||||
return None
|
||||
|
||||
async def execute_single_task(self, task_id: int):
|
||||
"""
|
||||
执行单个完整任务流程(在并发控制下)
|
||||
|
||||
Args:
|
||||
task_id: 任务ID
|
||||
"""
|
||||
create_task_type = None
|
||||
async with self.semaphore: # 获取信号量,控制并发数
|
||||
try:
|
||||
print(f"\n========== 任务 {task_id} 开始执行 ==========")
|
||||
|
||||
# Step 1: 智能选择create_new_task的任务类型,确保不同类型分布
|
||||
create_task_type = await self.select_task_type_with_diversity()
|
||||
if not create_task_type:
|
||||
print(f"✗ 任务 {task_id} - 无法选择任务类型(所有类型都在使用中),跳过")
|
||||
async with self.task_lock:
|
||||
self.failed_count += 1
|
||||
self.completed_tasks += 1
|
||||
return
|
||||
|
||||
print(f"任务 {task_id} - 选择任务类型: {create_task_type} (组: {self.get_task_type_group(create_task_type)})")
|
||||
target_points = CREATE_TASK_TARGET_MAPPING.get(create_task_type, [])
|
||||
if not target_points:
|
||||
print(f"✗ 任务 {task_id} - 任务类型 {create_task_type} 没有配置对应的动作点,跳过")
|
||||
async with self.task_lock:
|
||||
self.failed_count += 1
|
||||
self.completed_tasks += 1
|
||||
return
|
||||
|
||||
target_id = random.choice(target_points)
|
||||
|
||||
print(f"任务 {task_id} - 1. 调用 newTask - 类型: {create_task_type}, 目标: {target_id}")
|
||||
req_code = self.generate_unique_req_code()
|
||||
create_result = await self.create_new_task(create_task_type, target_id, req_code)
|
||||
if not create_result or create_result.get("code") != 0:
|
||||
print(f"✗ 任务 {task_id} - newTask 调用失败,跳过后续步骤")
|
||||
async with self.task_lock:
|
||||
self.failed_count += 1
|
||||
self.completed_tasks += 1
|
||||
return
|
||||
|
||||
print(f"✓ 任务 {task_id} - newTask 调用成功")
|
||||
|
||||
# Step 1.5: 监控create_new_task创建的任务状态(5秒超时)
|
||||
print(f"任务 {task_id} - 开始监控任务状态...")
|
||||
task_continue = await self.monitor_task_status(req_code, timeout=5)
|
||||
if not task_continue:
|
||||
print(f"✗ 任务 {task_id} - 任务监控失败,终止执行")
|
||||
async with self.task_lock:
|
||||
self.failed_count += 1
|
||||
self.completed_tasks += 1
|
||||
return
|
||||
|
||||
print(f"✓ 任务 {task_id} - 任务监控通过,继续执行")
|
||||
|
||||
# Step 2: 随机选择gen_agv_scheduling_task的任务类型
|
||||
gen_task_type = CREATE_TASK_TYPES_JSON.get(create_task_type)
|
||||
task_code = req_code
|
||||
|
||||
# 获取起点(从库区中持续查询直到找到)
|
||||
area_names = TASK_TYPE_AREA_MAPPING.get(gen_task_type, [])
|
||||
if not area_names:
|
||||
print(f"✗ 任务 {task_id} - 任务类型 {gen_task_type} 没有配置对应的库区,跳过")
|
||||
async with self.task_lock:
|
||||
self.failed_count += 1
|
||||
self.completed_tasks += 1
|
||||
return
|
||||
|
||||
print(f"任务 {task_id} - 2. 查询库区 {area_names} 中的可用库位...")
|
||||
available_locations = await self.get_available_storage_locations(area_names)
|
||||
start_point = random.choice(available_locations)
|
||||
|
||||
# 获取终点
|
||||
end_points = GEN_TASK_ENDPOINT_MAPPING.get(gen_task_type, [])
|
||||
if not end_points:
|
||||
print(f"✗ 任务 {task_id} - 任务类型 {gen_task_type} 没有配置对应的终点,跳过")
|
||||
async with self.task_lock:
|
||||
self.failed_count += 1
|
||||
self.completed_tasks += 1
|
||||
return
|
||||
|
||||
print(f"任务 {task_id} - 3. 调用 GenAgvSchedulingTask - 类型: {gen_task_type}, 任务码: {task_code}")
|
||||
|
||||
gen_result = await self.gen_agv_scheduling_task(gen_task_type, task_code, start_point, target_id)
|
||||
|
||||
if gen_result:
|
||||
print(f"✓ 任务 {task_id} - GenAgvSchedulingTask 调用成功")
|
||||
|
||||
# Step 3.5: 监控GenAgvSchedulingTask任务直到完成
|
||||
# 获取GenAgvSchedulingTask的req_code (gen_agv_scheduling_task方法中生成的req_code)
|
||||
# 优先使用接口返回的reqCode,如果没有则使用我们生成的generated_req_code
|
||||
agv_req_code = gen_result.get("reqCode") or gen_result.get("generated_req_code")
|
||||
if agv_req_code:
|
||||
print(f"任务 {task_id} - 4. 开始监控AGV调度任务完成状态...")
|
||||
agv_task_success = await self.monitor_agv_task_completion(agv_req_code)
|
||||
|
||||
async with self.task_lock:
|
||||
if agv_task_success:
|
||||
print(f"✓ 任务 {task_id} - 完整流程执行成功(AGV任务已完成)")
|
||||
self.success_count += 1
|
||||
else:
|
||||
print(f"✗ 任务 {task_id} - AGV调度任务执行失败")
|
||||
self.failed_count += 1
|
||||
self.completed_tasks += 1
|
||||
else:
|
||||
# 如果无法获取agv_req_code,仍然记录为成功(接口调用成功)
|
||||
print(f"⚠ 任务 {task_id} - 无法获取AGV调度任务的请求码,标记为成功")
|
||||
async with self.task_lock:
|
||||
self.success_count += 1
|
||||
self.completed_tasks += 1
|
||||
else:
|
||||
print(f"✗ 任务 {task_id} - GenAgvSchedulingTask 调用失败")
|
||||
async with self.task_lock:
|
||||
self.failed_count += 1
|
||||
self.completed_tasks += 1
|
||||
|
||||
# 任务间隔
|
||||
await asyncio.sleep(1)
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 任务 {task_id} 执行异常: {str(e)}")
|
||||
async with self.task_lock:
|
||||
self.failed_count += 1
|
||||
self.completed_tasks += 1
|
||||
finally:
|
||||
# 确保任务类型组被释放
|
||||
if create_task_type:
|
||||
await self.release_task_type_group(create_task_type)
|
||||
|
||||
async def run_complete_task_workflow(self, count: int = 500):
|
||||
"""
|
||||
运行完整的任务工作流测试(多线程并发执行,最多4个并发任务)
|
||||
|
||||
Args:
|
||||
count: 测试次数(最大400个)
|
||||
"""
|
||||
# 限制总任务数不超过最大值
|
||||
count = min(count, self.max_total_tasks)
|
||||
|
||||
print(f"\n开始运行完整任务工作流测试,共 {count} 个任务")
|
||||
print(f"最大并发数: {self.max_concurrent_tasks},最大总任务数: {self.max_total_tasks}")
|
||||
print("=" * 80)
|
||||
|
||||
# 重置统计计数器
|
||||
async with self.task_lock:
|
||||
self.completed_tasks = 0
|
||||
self.success_count = 0
|
||||
self.failed_count = 0
|
||||
|
||||
# 创建所有任务
|
||||
tasks = []
|
||||
for i in range(count):
|
||||
task = asyncio.create_task(self.execute_single_task(i + 1))
|
||||
tasks.append(task)
|
||||
|
||||
# 启动进度监控任务
|
||||
monitor_task = asyncio.create_task(self.monitor_progress(count))
|
||||
|
||||
# 等待所有任务完成
|
||||
await asyncio.gather(*tasks, return_exceptions=True)
|
||||
|
||||
# 停止进度监控
|
||||
monitor_task.cancel()
|
||||
|
||||
# 最终统计
|
||||
print(f"\n========== 最终统计 ===========")
|
||||
print(f"总任务数: {count}")
|
||||
print(f"成功: {self.success_count} 个")
|
||||
print(f"失败: {self.failed_count} 个")
|
||||
print(f"成功率: {self.success_count / count * 100:.1f}%")
|
||||
print("=" * 40)
|
||||
|
||||
async def monitor_progress(self, total_count: int):
|
||||
"""
|
||||
监控任务执行进度
|
||||
|
||||
Args:
|
||||
total_count: 总任务数
|
||||
"""
|
||||
try:
|
||||
while True:
|
||||
await asyncio.sleep(10) # 每10秒输出一次进度
|
||||
async with self.task_lock:
|
||||
if self.completed_tasks >= total_count:
|
||||
break
|
||||
|
||||
print(f"\n========== 进度统计 ===========")
|
||||
print(f"已完成: {self.completed_tasks}/{total_count} 个任务")
|
||||
print(f"成功: {self.success_count} 个")
|
||||
print(f"失败: {self.failed_count} 个")
|
||||
if self.completed_tasks > 0:
|
||||
print(f"成功率: {self.success_count / self.completed_tasks * 100:.1f}%")
|
||||
print(f"当前并发数: {self.max_concurrent_tasks - self.semaphore._value}")
|
||||
print(f"运行中的任务类型组: {list(self.running_task_types)}")
|
||||
print("=" * 40)
|
||||
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
|
||||
async def main():
|
||||
"""主函数"""
|
||||
print("自动化任务压测脚本启动")
|
||||
print(f"启动时间: {datetime.now()}")
|
||||
print(f"API基础URL: {BASE_URL}")
|
||||
print(f"压测任务量: 400个(最大并发4个)")
|
||||
print(f"测试流程: newTask -> 监控任务状态 -> GenAgvSchedulingTask -> 监控AGV任务完成")
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
async with AutomatedTaskTester(max_concurrent_tasks=4, max_total_tasks=400) as tester:
|
||||
# 运行完整工作流测试 - 最大并发4个任务
|
||||
await tester.run_complete_task_workflow(count=400)
|
||||
|
||||
end_time = time.time()
|
||||
duration = end_time - start_time
|
||||
|
||||
print(f"\n========== 压测完成 ===========")
|
||||
print(f"结束时间: {datetime.now()}")
|
||||
print(f"总耗时: {duration:.2f} 秒")
|
||||
print(f"平均每个任务耗时: {duration / 400:.2f} 秒")
|
||||
print("=" * 40)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
@ -1,168 +1,37 @@
|
||||
2025-06-29 14:11:21,895 - services.task_service - WARNING - [logger.py:190] - warning() - 任务执行超时警告
|
||||
2025-06-29 14:11:22,105 - services.task_service - ERROR - [logger.py:204] - error() - 任务执行失败
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 29, in test_alert_sync
|
||||
task_logger.error("任务执行失败", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 204, in error
|
||||
self.logger.error(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 14:11:22,105 - services.task_service - CRITICAL - [logger.py:217] - critical() - 任务调度器严重错误
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 30, in test_alert_sync
|
||||
task_logger.critical("任务调度器严重错误", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 217, in critical
|
||||
self.logger.critical(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 14:11:22,120 - system.database - WARNING - [logger.py:190] - warning() - 数据库连接不稳定
|
||||
2025-06-29 14:11:22,120 - system.database - ERROR - [logger.py:204] - error() - 系统配置错误
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 34, in test_alert_sync
|
||||
system_logger.error("系统配置错误", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 204, in error
|
||||
self.logger.error(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 14:11:22,133 - services.robot_scheduler - WARNING - [logger.py:190] - warning() - 机器人电池电量低
|
||||
2025-06-29 14:11:22,133 - services.robot_scheduler - ERROR - [logger.py:204] - error() - 机器人失去控制权
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 38, in test_alert_sync
|
||||
robot_logger.error("机器人失去控制权", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 204, in error
|
||||
self.logger.error(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 14:11:22,133 - services.task_service - ERROR - [logger.py:228] - exception() - 发生了一个除零异常
|
||||
Traceback (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 42, in test_alert_sync
|
||||
1 / 0
|
||||
~~^~~
|
||||
ZeroDivisionError: division by zero
|
||||
2025-06-29 14:11:22,133 - services.task_service - INFO - 这是一条普通信息,不应该同步到主系统
|
||||
2025-06-29 14:47:38,730 - services.task_service - WARNING - [logger.py:190] - warning() - 任务执行超时警告
|
||||
2025-06-29 14:47:38,927 - services.task_service - ERROR - [logger.py:204] - error() - 任务执行失败
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 29, in test_alert_sync
|
||||
task_logger.error("任务执行失败", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 204, in error
|
||||
self.logger.error(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 14:47:38,927 - services.task_service - CRITICAL - [logger.py:217] - critical() - 任务调度器严重错误
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 30, in test_alert_sync
|
||||
task_logger.critical("任务调度器严重错误", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 217, in critical
|
||||
self.logger.critical(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 14:47:38,929 - system.database - WARNING - [logger.py:190] - warning() - 数据库连接不稳定
|
||||
2025-06-29 14:47:38,930 - system.database - ERROR - [logger.py:204] - error() - 系统配置错误
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 34, in test_alert_sync
|
||||
system_logger.error("系统配置错误", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 204, in error
|
||||
self.logger.error(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 14:47:38,931 - services.robot_scheduler - WARNING - [logger.py:190] - warning() - 机器人电池电量低
|
||||
2025-06-29 14:47:38,931 - services.robot_scheduler - ERROR - [logger.py:204] - error() - 机器人失去控制权
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 38, in test_alert_sync
|
||||
robot_logger.error("机器人失去控制权", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 204, in error
|
||||
self.logger.error(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 14:47:38,933 - services.task_service - ERROR - [logger.py:228] - exception() - 发生了一个除零异常
|
||||
Traceback (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 42, in test_alert_sync
|
||||
1 / 0
|
||||
~~^~~
|
||||
ZeroDivisionError: division by zero
|
||||
2025-06-29 14:47:38,933 - services.task_service - INFO - 这是一条普通信息,不应该同步到主系统
|
||||
2025-06-29 16:00:35,123 - services.task_service - WARNING - [logger.py:190] - warning() - 任务执行超时警告
|
||||
2025-06-29 16:00:35,316 - services.task_service - ERROR - [logger.py:204] - error() - 任务执行失败
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 29, in test_alert_sync
|
||||
task_logger.error("任务执行失败", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 204, in error
|
||||
self.logger.error(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 16:00:35,317 - services.task_service - CRITICAL - [logger.py:217] - critical() - 任务调度器严重错误
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 30, in test_alert_sync
|
||||
task_logger.critical("任务调度器严重错误", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 217, in critical
|
||||
self.logger.critical(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 16:00:35,318 - system.database - WARNING - [logger.py:190] - warning() - 数据库连接不稳定
|
||||
2025-06-29 16:00:35,319 - system.database - ERROR - [logger.py:204] - error() - 系统配置错误
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 34, in test_alert_sync
|
||||
system_logger.error("系统配置错误", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 204, in error
|
||||
self.logger.error(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 16:00:35,319 - services.robot_scheduler - WARNING - [logger.py:190] - warning() - 机器人电池电量低
|
||||
2025-06-29 16:00:35,320 - services.robot_scheduler - ERROR - [logger.py:204] - error() - 机器人失去控制权
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 38, in test_alert_sync
|
||||
robot_logger.error("机器人失去控制权", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 204, in error
|
||||
self.logger.error(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 16:00:35,320 - services.task_service - ERROR - [logger.py:228] - exception() - 发生了一个除零异常
|
||||
Traceback (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 42, in test_alert_sync
|
||||
1 / 0
|
||||
~~^~~
|
||||
ZeroDivisionError: division by zero
|
||||
2025-06-29 16:00:35,321 - services.task_service - INFO - 这是一条普通信息,不应该同步到主系统
|
||||
2025-06-29 16:01:38,561 - services.task_service - WARNING - [logger.py:190] - warning() - 任务执行超时警告
|
||||
2025-06-29 16:01:38,760 - services.task_service - ERROR - [logger.py:204] - error() - 任务执行失败
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 29, in test_alert_sync
|
||||
task_logger.error("任务执行失败", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 204, in error
|
||||
self.logger.error(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 16:01:38,765 - services.task_service - CRITICAL - [logger.py:217] - critical() - 任务调度器严重错误
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 30, in test_alert_sync
|
||||
task_logger.critical("任务调度器严重错误", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 217, in critical
|
||||
self.logger.critical(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 16:01:38,776 - system.database - WARNING - [logger.py:190] - warning() - 数据库连接不稳定
|
||||
2025-06-29 16:01:38,777 - system.database - ERROR - [logger.py:204] - error() - 系统配置错误
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 34, in test_alert_sync
|
||||
system_logger.error("系统配置错误", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 204, in error
|
||||
self.logger.error(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 16:01:38,785 - services.robot_scheduler - WARNING - [logger.py:190] - warning() - 机器人电池电量低
|
||||
2025-06-29 16:01:38,787 - services.robot_scheduler - ERROR - [logger.py:204] - error() - 机器人失去控制权
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 38, in test_alert_sync
|
||||
robot_logger.error("机器人失去控制权", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 204, in error
|
||||
self.logger.error(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 16:01:38,788 - services.task_service - ERROR - [logger.py:228] - exception() - 发生了一个除零异常
|
||||
Traceback (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 42, in test_alert_sync
|
||||
1 / 0
|
||||
~~^~~
|
||||
ZeroDivisionError: division by zero
|
||||
2025-06-29 16:01:38,792 - services.task_service - INFO - 这是一条普通信息,不应该同步到主系统
|
||||
2025-08-08 00:12:14,486 - asyncio - DEBUG - Using proactor: IocpProactor
|
||||
2025-08-08 00:12:14,905 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-08 00:12:14,925 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-08 00:12:15,944 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-08 00:12:15,944 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-08 00:16:42,645 - asyncio - DEBUG - Using proactor: IocpProactor
|
||||
2025-08-08 00:16:43,109 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-08 00:16:43,132 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-08 00:16:43,137 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-08 00:16:44,135 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-08 00:19:48,423 - asyncio - DEBUG - Using proactor: IocpProactor
|
||||
2025-08-08 00:19:48,849 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-08 00:19:48,869 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-08 00:19:48,869 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-08 00:19:48,874 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-08 00:23:32,741 - asyncio - DEBUG - Using proactor: IocpProactor
|
||||
2025-08-08 00:23:33,301 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-08 00:23:33,322 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-08 00:23:33,322 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-08 00:23:33,326 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-08 00:25:02,171 - asyncio - DEBUG - Using proactor: IocpProactor
|
||||
2025-08-08 00:25:02,618 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-08 00:25:02,621 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-08 00:25:02,639 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-08 00:25:02,639 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-08 00:27:38,254 - asyncio - DEBUG - Using proactor: IocpProactor
|
||||
2025-08-08 00:27:38,655 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-08 00:27:38,676 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-08 00:27:38,676 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-08 00:27:38,680 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-08 06:39:18,307 - asyncio - DEBUG - Using proactor: IocpProactor
|
||||
2025-08-08 06:39:18,795 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-08 06:39:18,797 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-08 06:39:18,830 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-08 06:39:18,834 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-08 06:39:38,941 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-08 07:04:14,981 - asyncio - DEBUG - Using proactor: IocpProactor
|
||||
|
168
scripts/logs/app.log.2025-06-29
Normal file
168
scripts/logs/app.log.2025-06-29
Normal file
@ -0,0 +1,168 @@
|
||||
2025-06-29 14:11:21,895 - services.task_service - WARNING - [logger.py:190] - warning() - 任务执行超时警告
|
||||
2025-06-29 14:11:22,105 - services.task_service - ERROR - [logger.py:204] - error() - 任务执行失败
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 29, in test_alert_sync
|
||||
task_logger.error("任务执行失败", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 204, in error
|
||||
self.logger.error(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 14:11:22,105 - services.task_service - CRITICAL - [logger.py:217] - critical() - 任务调度器严重错误
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 30, in test_alert_sync
|
||||
task_logger.critical("任务调度器严重错误", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 217, in critical
|
||||
self.logger.critical(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 14:11:22,120 - system.database - WARNING - [logger.py:190] - warning() - 数据库连接不稳定
|
||||
2025-06-29 14:11:22,120 - system.database - ERROR - [logger.py:204] - error() - 系统配置错误
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 34, in test_alert_sync
|
||||
system_logger.error("系统配置错误", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 204, in error
|
||||
self.logger.error(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 14:11:22,133 - services.robot_scheduler - WARNING - [logger.py:190] - warning() - 机器人电池电量低
|
||||
2025-06-29 14:11:22,133 - services.robot_scheduler - ERROR - [logger.py:204] - error() - 机器人失去控制权
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 38, in test_alert_sync
|
||||
robot_logger.error("机器人失去控制权", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 204, in error
|
||||
self.logger.error(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 14:11:22,133 - services.task_service - ERROR - [logger.py:228] - exception() - 发生了一个除零异常
|
||||
Traceback (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 42, in test_alert_sync
|
||||
1 / 0
|
||||
~~^~~
|
||||
ZeroDivisionError: division by zero
|
||||
2025-06-29 14:11:22,133 - services.task_service - INFO - 这是一条普通信息,不应该同步到主系统
|
||||
2025-06-29 14:47:38,730 - services.task_service - WARNING - [logger.py:190] - warning() - 任务执行超时警告
|
||||
2025-06-29 14:47:38,927 - services.task_service - ERROR - [logger.py:204] - error() - 任务执行失败
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 29, in test_alert_sync
|
||||
task_logger.error("任务执行失败", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 204, in error
|
||||
self.logger.error(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 14:47:38,927 - services.task_service - CRITICAL - [logger.py:217] - critical() - 任务调度器严重错误
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 30, in test_alert_sync
|
||||
task_logger.critical("任务调度器严重错误", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 217, in critical
|
||||
self.logger.critical(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 14:47:38,929 - system.database - WARNING - [logger.py:190] - warning() - 数据库连接不稳定
|
||||
2025-06-29 14:47:38,930 - system.database - ERROR - [logger.py:204] - error() - 系统配置错误
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 34, in test_alert_sync
|
||||
system_logger.error("系统配置错误", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 204, in error
|
||||
self.logger.error(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 14:47:38,931 - services.robot_scheduler - WARNING - [logger.py:190] - warning() - 机器人电池电量低
|
||||
2025-06-29 14:47:38,931 - services.robot_scheduler - ERROR - [logger.py:204] - error() - 机器人失去控制权
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 38, in test_alert_sync
|
||||
robot_logger.error("机器人失去控制权", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 204, in error
|
||||
self.logger.error(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 14:47:38,933 - services.task_service - ERROR - [logger.py:228] - exception() - 发生了一个除零异常
|
||||
Traceback (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 42, in test_alert_sync
|
||||
1 / 0
|
||||
~~^~~
|
||||
ZeroDivisionError: division by zero
|
||||
2025-06-29 14:47:38,933 - services.task_service - INFO - 这是一条普通信息,不应该同步到主系统
|
||||
2025-06-29 16:00:35,123 - services.task_service - WARNING - [logger.py:190] - warning() - 任务执行超时警告
|
||||
2025-06-29 16:00:35,316 - services.task_service - ERROR - [logger.py:204] - error() - 任务执行失败
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 29, in test_alert_sync
|
||||
task_logger.error("任务执行失败", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 204, in error
|
||||
self.logger.error(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 16:00:35,317 - services.task_service - CRITICAL - [logger.py:217] - critical() - 任务调度器严重错误
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 30, in test_alert_sync
|
||||
task_logger.critical("任务调度器严重错误", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 217, in critical
|
||||
self.logger.critical(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 16:00:35,318 - system.database - WARNING - [logger.py:190] - warning() - 数据库连接不稳定
|
||||
2025-06-29 16:00:35,319 - system.database - ERROR - [logger.py:204] - error() - 系统配置错误
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 34, in test_alert_sync
|
||||
system_logger.error("系统配置错误", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 204, in error
|
||||
self.logger.error(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 16:00:35,319 - services.robot_scheduler - WARNING - [logger.py:190] - warning() - 机器人电池电量低
|
||||
2025-06-29 16:00:35,320 - services.robot_scheduler - ERROR - [logger.py:204] - error() - 机器人失去控制权
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 38, in test_alert_sync
|
||||
robot_logger.error("机器人失去控制权", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 204, in error
|
||||
self.logger.error(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 16:00:35,320 - services.task_service - ERROR - [logger.py:228] - exception() - 发生了一个除零异常
|
||||
Traceback (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 42, in test_alert_sync
|
||||
1 / 0
|
||||
~~^~~
|
||||
ZeroDivisionError: division by zero
|
||||
2025-06-29 16:00:35,321 - services.task_service - INFO - 这是一条普通信息,不应该同步到主系统
|
||||
2025-06-29 16:01:38,561 - services.task_service - WARNING - [logger.py:190] - warning() - 任务执行超时警告
|
||||
2025-06-29 16:01:38,760 - services.task_service - ERROR - [logger.py:204] - error() - 任务执行失败
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 29, in test_alert_sync
|
||||
task_logger.error("任务执行失败", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 204, in error
|
||||
self.logger.error(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 16:01:38,765 - services.task_service - CRITICAL - [logger.py:217] - critical() - 任务调度器严重错误
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 30, in test_alert_sync
|
||||
task_logger.critical("任务调度器严重错误", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 217, in critical
|
||||
self.logger.critical(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 16:01:38,776 - system.database - WARNING - [logger.py:190] - warning() - 数据库连接不稳定
|
||||
2025-06-29 16:01:38,777 - system.database - ERROR - [logger.py:204] - error() - 系统配置错误
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 34, in test_alert_sync
|
||||
system_logger.error("系统配置错误", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 204, in error
|
||||
self.logger.error(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 16:01:38,785 - services.robot_scheduler - WARNING - [logger.py:190] - warning() - 机器人电池电量低
|
||||
2025-06-29 16:01:38,787 - services.robot_scheduler - ERROR - [logger.py:204] - error() - 机器人失去控制权
|
||||
Stack (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 64, in <module>
|
||||
test_alert_sync()
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 38, in test_alert_sync
|
||||
robot_logger.error("机器人失去控制权", exc_info=False)
|
||||
File "D:\jsw_code\project\VWED_task\utils\logger.py", line 204, in error
|
||||
self.logger.error(message, *args, exc_info=exc_info, stack_info=stack_info, **kwargs)
|
||||
2025-06-29 16:01:38,788 - services.task_service - ERROR - [logger.py:228] - exception() - 发生了一个除零异常
|
||||
Traceback (most recent call last):
|
||||
File "D:\jsw_code\project\VWED_task\scripts\test_alert_sync.py", line 42, in test_alert_sync
|
||||
1 / 0
|
||||
~~^~~
|
||||
ZeroDivisionError: division by zero
|
||||
2025-06-29 16:01:38,792 - services.task_service - INFO - 这是一条普通信息,不应该同步到主系统
|
236
scripts/logs/app.log.2025-08-07
Normal file
236
scripts/logs/app.log.2025-08-07
Normal file
@ -0,0 +1,236 @@
|
||||
2025-08-07 10:52:07,304 - asyncio - DEBUG - Using proactor: IocpProactor
|
||||
2025-08-07 10:53:14,604 - asyncio - DEBUG - Using proactor: IocpProactor
|
||||
2025-08-07 10:55:06,442 - asyncio - DEBUG - Using proactor: IocpProactor
|
||||
2025-08-07 10:59:26,848 - asyncio - DEBUG - Using proactor: IocpProactor
|
||||
2025-08-07 10:59:46,529 - asyncio - DEBUG - Using proactor: IocpProactor
|
||||
2025-08-07 11:00:10,957 - asyncio - DEBUG - Using proactor: IocpProactor
|
||||
2025-08-07 11:00:32,854 - asyncio - DEBUG - Using proactor: IocpProactor
|
||||
2025-08-07 11:01:18,127 - asyncio - DEBUG - Using proactor: IocpProactor
|
||||
2025-08-07 11:01:19,390 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-07 11:03:55,901 - asyncio - DEBUG - Using proactor: IocpProactor
|
||||
2025-08-07 11:03:56,670 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-07 11:06:36,215 - asyncio - DEBUG - Using proactor: IocpProactor
|
||||
2025-08-07 11:07:02,363 - asyncio - DEBUG - Using proactor: IocpProactor
|
||||
2025-08-07 11:07:03,119 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-07 11:08:10,478 - asyncio - DEBUG - Using proactor: IocpProactor
|
||||
2025-08-07 11:08:11,295 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-07 11:10:08,784 - asyncio - DEBUG - Using proactor: IocpProactor
|
||||
2025-08-07 11:10:09,528 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-07 11:14:21,335 - asyncio - DEBUG - Using proactor: IocpProactor
|
||||
2025-08-07 11:14:22,067 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-07 23:03:14,525 - asyncio - DEBUG - Using proactor: IocpProactor
|
||||
2025-08-07 23:03:14,526 - TaskTester - INFO - 开始运行并发任务工作流测试,目标任务数: 500,最大并发数: 1
|
||||
2025-08-07 23:03:14,526 - AGV-1 - INFO - AGV-1 工作线程启动
|
||||
2025-08-07 23:03:14,526 - AGV-1 - INFO - 开始执行任务 - 类型: LG2MP, 目标: AP217, 请求码: TEST_1754578994526_4381
|
||||
2025-08-07 23:03:15,300 - AGV-1 - INFO - newTask 调用成功
|
||||
2025-08-07 23:03:15,304 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-07 23:03:44,538 - TaskTester - INFO - ========== 进度统计 ===========
|
||||
2025-08-07 23:03:44,539 - TaskTester - INFO - 总处理: 0/500
|
||||
2025-08-07 23:03:44,539 - TaskTester - INFO - 成功: 0
|
||||
2025-08-07 23:03:44,539 - TaskTester - INFO - 失败: 0
|
||||
2025-08-07 23:03:44,539 - TaskTester - INFO - 当前运行: 0
|
||||
2025-08-07 23:03:44,539 - TaskTester - INFO - ========================================
|
||||
2025-08-07 23:03:44,539 - TaskTester - INFO - AGV-1: 空闲, 已完成: 0
|
||||
2025-08-07 23:04:14,552 - TaskTester - INFO - ========== 进度统计 ===========
|
||||
2025-08-07 23:04:14,552 - TaskTester - INFO - 总处理: 0/500
|
||||
2025-08-07 23:04:14,552 - TaskTester - INFO - 成功: 0
|
||||
2025-08-07 23:04:14,553 - TaskTester - INFO - 失败: 0
|
||||
2025-08-07 23:04:14,553 - TaskTester - INFO - 当前运行: 0
|
||||
2025-08-07 23:04:14,553 - TaskTester - INFO - ========================================
|
||||
2025-08-07 23:04:14,553 - TaskTester - INFO - AGV-1: 空闲, 已完成: 0
|
||||
2025-08-07 23:04:44,566 - TaskTester - INFO - ========== 进度统计 ===========
|
||||
2025-08-07 23:04:44,567 - TaskTester - INFO - 总处理: 0/500
|
||||
2025-08-07 23:04:44,567 - TaskTester - INFO - 成功: 0
|
||||
2025-08-07 23:04:44,567 - TaskTester - INFO - 失败: 0
|
||||
2025-08-07 23:04:44,567 - TaskTester - INFO - 当前运行: 0
|
||||
2025-08-07 23:04:44,567 - TaskTester - INFO - ========================================
|
||||
2025-08-07 23:04:44,567 - TaskTester - INFO - AGV-1: 空闲, 已完成: 0
|
||||
2025-08-07 23:05:14,575 - TaskTester - INFO - ========== 进度统计 ===========
|
||||
2025-08-07 23:05:14,575 - TaskTester - INFO - 总处理: 0/500
|
||||
2025-08-07 23:05:14,575 - TaskTester - INFO - 成功: 0
|
||||
2025-08-07 23:05:14,575 - TaskTester - INFO - 失败: 0
|
||||
2025-08-07 23:05:14,575 - TaskTester - INFO - 当前运行: 0
|
||||
2025-08-07 23:05:14,576 - TaskTester - INFO - ========================================
|
||||
2025-08-07 23:05:14,576 - TaskTester - INFO - AGV-1: 空闲, 已完成: 0
|
||||
2025-08-07 23:05:44,581 - TaskTester - INFO - ========== 进度统计 ===========
|
||||
2025-08-07 23:05:44,581 - TaskTester - INFO - 总处理: 0/500
|
||||
2025-08-07 23:05:44,581 - TaskTester - INFO - 成功: 0
|
||||
2025-08-07 23:05:44,581 - TaskTester - INFO - 失败: 0
|
||||
2025-08-07 23:05:44,581 - TaskTester - INFO - 当前运行: 0
|
||||
2025-08-07 23:05:44,582 - TaskTester - INFO - ========================================
|
||||
2025-08-07 23:05:44,582 - TaskTester - INFO - AGV-1: 空闲, 已完成: 0
|
||||
2025-08-07 23:06:14,586 - TaskTester - INFO - ========== 进度统计 ===========
|
||||
2025-08-07 23:06:14,586 - TaskTester - INFO - 总处理: 0/500
|
||||
2025-08-07 23:06:14,587 - TaskTester - INFO - 成功: 0
|
||||
2025-08-07 23:06:14,587 - TaskTester - INFO - 失败: 0
|
||||
2025-08-07 23:06:14,587 - TaskTester - INFO - 当前运行: 0
|
||||
2025-08-07 23:06:14,587 - TaskTester - INFO - ========================================
|
||||
2025-08-07 23:06:14,587 - TaskTester - INFO - AGV-1: 空闲, 已完成: 0
|
||||
2025-08-07 23:06:44,599 - TaskTester - INFO - ========== 进度统计 ===========
|
||||
2025-08-07 23:06:44,600 - TaskTester - INFO - 总处理: 0/500
|
||||
2025-08-07 23:06:44,600 - TaskTester - INFO - 成功: 0
|
||||
2025-08-07 23:06:44,600 - TaskTester - INFO - 失败: 0
|
||||
2025-08-07 23:06:44,600 - TaskTester - INFO - 当前运行: 0
|
||||
2025-08-07 23:06:44,600 - TaskTester - INFO - ========================================
|
||||
2025-08-07 23:06:44,600 - TaskTester - INFO - AGV-1: 空闲, 已完成: 0
|
||||
2025-08-07 23:07:14,614 - TaskTester - INFO - ========== 进度统计 ===========
|
||||
2025-08-07 23:07:14,614 - TaskTester - INFO - 总处理: 0/500
|
||||
2025-08-07 23:07:14,614 - TaskTester - INFO - 成功: 0
|
||||
2025-08-07 23:07:14,614 - TaskTester - INFO - 失败: 0
|
||||
2025-08-07 23:07:14,614 - TaskTester - INFO - 当前运行: 0
|
||||
2025-08-07 23:07:14,614 - TaskTester - INFO - ========================================
|
||||
2025-08-07 23:07:14,614 - TaskTester - INFO - AGV-1: 空闲, 已完成: 0
|
||||
2025-08-07 23:07:44,627 - TaskTester - INFO - ========== 进度统计 ===========
|
||||
2025-08-07 23:07:44,627 - TaskTester - INFO - 总处理: 0/500
|
||||
2025-08-07 23:07:44,627 - TaskTester - INFO - 成功: 0
|
||||
2025-08-07 23:07:44,627 - TaskTester - INFO - 失败: 0
|
||||
2025-08-07 23:07:44,627 - TaskTester - INFO - 当前运行: 0
|
||||
2025-08-07 23:07:44,627 - TaskTester - INFO - ========================================
|
||||
2025-08-07 23:07:44,627 - TaskTester - INFO - AGV-1: 空闲, 已完成: 0
|
||||
2025-08-07 23:08:14,641 - TaskTester - INFO - ========== 进度统计 ===========
|
||||
2025-08-07 23:08:14,641 - TaskTester - INFO - 总处理: 0/500
|
||||
2025-08-07 23:08:14,641 - TaskTester - INFO - 成功: 0
|
||||
2025-08-07 23:08:14,641 - TaskTester - INFO - 失败: 0
|
||||
2025-08-07 23:08:14,641 - TaskTester - INFO - 当前运行: 0
|
||||
2025-08-07 23:08:14,641 - TaskTester - INFO - ========================================
|
||||
2025-08-07 23:08:14,641 - TaskTester - INFO - AGV-1: 空闲, 已完成: 0
|
||||
2025-08-07 23:08:44,650 - TaskTester - INFO - ========== 进度统计 ===========
|
||||
2025-08-07 23:08:44,650 - TaskTester - INFO - 总处理: 0/500
|
||||
2025-08-07 23:08:44,650 - TaskTester - INFO - 成功: 0
|
||||
2025-08-07 23:08:44,650 - TaskTester - INFO - 失败: 0
|
||||
2025-08-07 23:08:44,650 - TaskTester - INFO - 当前运行: 0
|
||||
2025-08-07 23:08:44,650 - TaskTester - INFO - ========================================
|
||||
2025-08-07 23:08:44,650 - TaskTester - INFO - AGV-1: 空闲, 已完成: 0
|
||||
2025-08-07 23:09:14,665 - TaskTester - INFO - ========== 进度统计 ===========
|
||||
2025-08-07 23:09:14,665 - TaskTester - INFO - 总处理: 0/500
|
||||
2025-08-07 23:09:14,665 - TaskTester - INFO - 成功: 0
|
||||
2025-08-07 23:09:14,665 - TaskTester - INFO - 失败: 0
|
||||
2025-08-07 23:09:14,665 - TaskTester - INFO - 当前运行: 0
|
||||
2025-08-07 23:09:14,665 - TaskTester - INFO - ========================================
|
||||
2025-08-07 23:09:14,666 - TaskTester - INFO - AGV-1: 空闲, 已完成: 0
|
||||
2025-08-07 23:09:44,669 - TaskTester - INFO - ========== 进度统计 ===========
|
||||
2025-08-07 23:09:44,669 - TaskTester - INFO - 总处理: 0/500
|
||||
2025-08-07 23:09:44,671 - TaskTester - INFO - 成功: 0
|
||||
2025-08-07 23:09:44,671 - TaskTester - INFO - 失败: 0
|
||||
2025-08-07 23:09:44,671 - TaskTester - INFO - 当前运行: 0
|
||||
2025-08-07 23:09:44,671 - TaskTester - INFO - ========================================
|
||||
2025-08-07 23:09:44,671 - TaskTester - INFO - AGV-1: 空闲, 已完成: 0
|
||||
2025-08-07 23:10:14,672 - TaskTester - INFO - ========== 进度统计 ===========
|
||||
2025-08-07 23:10:14,672 - TaskTester - INFO - 总处理: 0/500
|
||||
2025-08-07 23:10:14,673 - TaskTester - INFO - 成功: 0
|
||||
2025-08-07 23:10:14,673 - TaskTester - INFO - 失败: 0
|
||||
2025-08-07 23:10:14,673 - TaskTester - INFO - 当前运行: 0
|
||||
2025-08-07 23:10:14,673 - TaskTester - INFO - ========================================
|
||||
2025-08-07 23:10:14,673 - TaskTester - INFO - AGV-1: 空闲, 已完成: 0
|
||||
2025-08-07 23:10:30,682 - AGV-1 - ERROR - [automated_task_test.py:349] - execute_single_task() - GenAgvSchedulingTask 调用失败: None
|
||||
2025-08-07 23:10:31,866 - AGV-1 - INFO - 开始执行任务 - 类型: QZ2MP, 目标: AP135, 请求码: TEST_1754579431866_3639
|
||||
2025-08-07 23:10:34,175 - AGV-1 - ERROR - [automated_task_test.py:332] - execute_single_task() - newTask 调用失败: None
|
||||
2025-08-07 23:10:34,176 - AGV-1 - INFO - 开始执行任务 - 类型: GT2MP, 目标: AP257, 请求码: TEST_1754579434176_1944
|
||||
2025-08-07 23:10:36,494 - AGV-1 - ERROR - [automated_task_test.py:332] - execute_single_task() - newTask 调用失败: None
|
||||
2025-08-07 23:10:36,495 - AGV-1 - INFO - 开始执行任务 - 类型: GT2MP, 目标: AP258, 请求码: TEST_1754579436495_1959
|
||||
2025-08-07 23:10:38,805 - AGV-1 - ERROR - [automated_task_test.py:332] - execute_single_task() - newTask 调用失败: None
|
||||
2025-08-07 23:10:38,806 - AGV-1 - INFO - 开始执行任务 - 类型: GGFK2MP, 目标: AP312, 请求码: TEST_1754579438806_7127
|
||||
2025-08-07 23:10:41,117 - AGV-1 - ERROR - [automated_task_test.py:332] - execute_single_task() - newTask 调用失败: None
|
||||
2025-08-07 23:10:41,118 - AGV-1 - INFO - 开始执行任务 - 类型: GGFK2MP, 目标: AP310, 请求码: TEST_1754579441118_4272
|
||||
2025-08-07 23:10:43,400 - AGV-1 - ERROR - [automated_task_test.py:332] - execute_single_task() - newTask 调用失败: None
|
||||
2025-08-07 23:10:43,401 - AGV-1 - INFO - 开始执行任务 - 类型: LG2MP, 目标: AP216, 请求码: TEST_1754579443401_4100
|
||||
2025-08-07 23:10:44,700 - TaskTester - INFO - ========== 进度统计 ===========
|
||||
2025-08-07 23:10:44,700 - TaskTester - INFO - 总处理: 6/500
|
||||
2025-08-07 23:10:44,700 - TaskTester - INFO - 成功: 0
|
||||
2025-08-07 23:10:44,700 - TaskTester - INFO - 失败: 6
|
||||
2025-08-07 23:10:44,700 - TaskTester - INFO - 当前运行: 0
|
||||
2025-08-07 23:10:44,700 - TaskTester - INFO - 成功率: 0.0%
|
||||
2025-08-07 23:10:44,700 - TaskTester - INFO - ========================================
|
||||
2025-08-07 23:10:44,700 - TaskTester - INFO - AGV-1: 空闲, 已完成: 0
|
||||
2025-08-07 23:10:45,729 - AGV-1 - ERROR - [automated_task_test.py:332] - execute_single_task() - newTask 调用失败: None
|
||||
2025-08-07 23:10:45,729 - AGV-1 - INFO - 开始执行任务 - 类型: GT2MP, 目标: AP255, 请求码: TEST_1754579445729_4561
|
||||
2025-08-07 23:10:48,052 - AGV-1 - ERROR - [automated_task_test.py:332] - execute_single_task() - newTask 调用失败: None
|
||||
2025-08-07 23:10:48,053 - AGV-1 - INFO - 开始执行任务 - 类型: QZ2MP, 目标: AP136, 请求码: TEST_1754579448053_8373
|
||||
2025-08-07 23:10:50,348 - AGV-1 - ERROR - [automated_task_test.py:332] - execute_single_task() - newTask 调用失败: None
|
||||
2025-08-07 23:10:50,349 - AGV-1 - INFO - 开始执行任务 - 类型: LG2MP, 目标: AP216, 请求码: TEST_1754579450349_3721
|
||||
2025-08-07 23:10:52,665 - AGV-1 - ERROR - [automated_task_test.py:332] - execute_single_task() - newTask 调用失败: None
|
||||
2025-08-07 23:10:52,665 - AGV-1 - INFO - 开始执行任务 - 类型: QZ2MP, 目标: AP135, 请求码: TEST_1754579452665_1009
|
||||
2025-08-07 23:10:54,981 - AGV-1 - ERROR - [automated_task_test.py:332] - execute_single_task() - newTask 调用失败: None
|
||||
2025-08-07 23:10:54,982 - AGV-1 - INFO - 开始执行任务 - 类型: QZ2MP, 目标: AP136, 请求码: TEST_1754579454982_6749
|
||||
2025-08-07 23:10:57,270 - AGV-1 - ERROR - [automated_task_test.py:332] - execute_single_task() - newTask 调用失败: None
|
||||
2025-08-07 23:10:57,270 - AGV-1 - INFO - 开始执行任务 - 类型: GTFK2MP, 目标: AP258, 请求码: TEST_1754579457270_1302
|
||||
2025-08-07 23:10:59,579 - AGV-1 - ERROR - [automated_task_test.py:332] - execute_single_task() - newTask 调用失败: None
|
||||
2025-08-07 23:10:59,580 - AGV-1 - INFO - 开始执行任务 - 类型: ZG2MP, 目标: AP298, 请求码: TEST_1754579459580_9170
|
||||
2025-08-07 23:11:01,867 - AGV-1 - ERROR - [automated_task_test.py:332] - execute_single_task() - newTask 调用失败: None
|
||||
2025-08-07 23:11:01,868 - AGV-1 - INFO - 开始执行任务 - 类型: PHZ2MP, 目标: AP211, 请求码: TEST_1754579461868_1685
|
||||
2025-08-07 23:11:04,172 - AGV-1 - ERROR - [automated_task_test.py:332] - execute_single_task() - newTask 调用失败: None
|
||||
2025-08-07 23:11:04,172 - AGV-1 - INFO - 开始执行任务 - 类型: GG2MP, 目标: AP312, 请求码: TEST_1754579464172_4280
|
||||
2025-08-07 23:11:06,519 - AGV-1 - INFO - newTask 调用成功
|
||||
2025-08-07 23:11:14,699 - TaskTester - INFO - ========== 进度统计 ===========
|
||||
2025-08-07 23:11:14,699 - TaskTester - INFO - 总处理: 15/500
|
||||
2025-08-07 23:11:14,699 - TaskTester - INFO - 成功: 0
|
||||
2025-08-07 23:11:14,699 - TaskTester - INFO - 失败: 15
|
||||
2025-08-07 23:11:14,700 - TaskTester - INFO - 当前运行: 0
|
||||
2025-08-07 23:11:14,701 - TaskTester - INFO - 成功率: 0.0%
|
||||
2025-08-07 23:11:14,702 - TaskTester - INFO - ========================================
|
||||
2025-08-07 23:11:14,703 - TaskTester - INFO - AGV-1: 空闲, 已完成: 0
|
||||
2025-08-07 23:11:44,727 - TaskTester - INFO - ========== 进度统计 ===========
|
||||
2025-08-07 23:11:44,728 - TaskTester - INFO - 总处理: 15/500
|
||||
2025-08-07 23:11:44,728 - TaskTester - INFO - 成功: 0
|
||||
2025-08-07 23:11:44,728 - TaskTester - INFO - 失败: 15
|
||||
2025-08-07 23:11:44,728 - TaskTester - INFO - 当前运行: 0
|
||||
2025-08-07 23:11:44,728 - TaskTester - INFO - 成功率: 0.0%
|
||||
2025-08-07 23:11:44,728 - TaskTester - INFO - ========================================
|
||||
2025-08-07 23:11:44,728 - TaskTester - INFO - AGV-1: 空闲, 已完成: 0
|
||||
2025-08-07 23:12:12,445 - asyncio - DEBUG - Using proactor: IocpProactor
|
||||
2025-08-07 23:12:12,447 - TaskTester - INFO - 开始运行并发任务工作流测试,目标任务数: 500,最大并发数: 1
|
||||
2025-08-07 23:12:12,447 - AGV-1 - INFO - AGV-1 工作线程启动
|
||||
2025-08-07 23:12:12,447 - AGV-1 - INFO - 开始执行任务 - 类型: GG2MP, 目标: AP313, 请求码: TEST_1754579532447_3957
|
||||
2025-08-07 23:12:14,775 - AGV-1 - ERROR - [automated_task_test.py:332] - execute_single_task() - newTask 调用失败: None
|
||||
2025-08-07 23:12:15,539 - AGV-1 - INFO - 开始执行任务 - 类型: GT2MP, 目标: AP258, 请求码: TEST_1754579535539_6017
|
||||
2025-08-07 23:12:17,831 - AGV-1 - ERROR - [automated_task_test.py:332] - execute_single_task() - newTask 调用失败: None
|
||||
2025-08-07 23:12:17,832 - AGV-1 - INFO - 开始执行任务 - 类型: LG2MP, 目标: AP216, 请求码: TEST_1754579537832_1783
|
||||
2025-08-07 23:12:20,139 - AGV-1 - ERROR - [automated_task_test.py:332] - execute_single_task() - newTask 调用失败: None
|
||||
2025-08-07 23:12:20,139 - AGV-1 - INFO - 开始执行任务 - 类型: GTFK2MP, 目标: AP258, 请求码: TEST_1754579540139_1250
|
||||
2025-08-07 23:12:29,778 - asyncio - DEBUG - Using proactor: IocpProactor
|
||||
2025-08-07 23:12:29,780 - TaskTester - INFO - 开始运行并发任务工作流测试,目标任务数: 500,最大并发数: 1
|
||||
2025-08-07 23:12:29,781 - AGV-1 - INFO - AGV-1 工作线程启动
|
||||
2025-08-07 23:12:29,781 - AGV-1 - INFO - 开始执行任务 - 类型: GGFK2MP, 目标: AP312, 请求码: TEST_1754579549781_8453
|
||||
2025-08-07 23:12:32,066 - AGV-1 - ERROR - [automated_task_test.py:332] - execute_single_task() - newTask 调用失败: None
|
||||
2025-08-07 23:12:32,786 - AGV-1 - INFO - 开始执行任务 - 类型: QZ2MP, 目标: AP136, 请求码: TEST_1754579552786_7372
|
||||
2025-08-07 23:12:35,094 - AGV-1 - ERROR - [automated_task_test.py:332] - execute_single_task() - newTask 调用失败: None
|
||||
2025-08-07 23:12:35,094 - AGV-1 - INFO - 开始执行任务 - 类型: GTFK2MP, 目标: AP255, 请求码: TEST_1754579555094_6719
|
||||
2025-08-07 23:12:57,753 - asyncio - DEBUG - Using proactor: IocpProactor
|
||||
2025-08-07 23:12:57,754 - TaskTester - INFO - 开始运行并发任务工作流测试,目标任务数: 500,最大并发数: 1
|
||||
2025-08-07 23:12:57,755 - AGV-1 - INFO - AGV-1 工作线程启动
|
||||
2025-08-07 23:12:57,755 - AGV-1 - INFO - 开始执行任务 - 类型: GG2MP, 目标: AP313, 请求码: TEST_1754579577755_9774
|
||||
2025-08-07 23:12:59,566 - AGV-1 - INFO - newTask 调用成功
|
||||
2025-08-07 23:12:59,574 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-07 23:14:13,082 - asyncio - DEBUG - Using proactor: IocpProactor
|
||||
2025-08-07 23:14:13,083 - TaskTester - INFO - 开始运行并发任务工作流测试,目标任务数: 500,最大并发数: 1
|
||||
2025-08-07 23:14:13,084 - AGV-1 - INFO - AGV-1 工作线程启动
|
||||
2025-08-07 23:14:13,084 - AGV-1 - INFO - 开始执行任务 - 类型: LG2MP, 目标: AP216, 请求码: TEST_1754579653084_8024
|
||||
2025-08-07 23:14:13,957 - AGV-1 - INFO - newTask 调用成功
|
||||
2025-08-07 23:14:13,965 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-07 23:14:43,102 - TaskTester - INFO - ========== 进度统计 ===========
|
||||
2025-08-07 23:14:43,102 - TaskTester - INFO - 总处理: 0/500
|
||||
2025-08-07 23:14:43,103 - TaskTester - INFO - 成功: 0
|
||||
2025-08-07 23:14:43,103 - TaskTester - INFO - 失败: 0
|
||||
2025-08-07 23:14:43,103 - TaskTester - INFO - 当前运行: 0
|
||||
2025-08-07 23:14:43,103 - TaskTester - INFO - ========================================
|
||||
2025-08-07 23:14:43,103 - TaskTester - INFO - AGV-1: 空闲, 已完成: 0
|
||||
2025-08-07 23:15:13,110 - TaskTester - INFO - ========== 进度统计 ===========
|
||||
2025-08-07 23:15:13,110 - TaskTester - INFO - 总处理: 0/500
|
||||
2025-08-07 23:15:13,110 - TaskTester - INFO - 成功: 0
|
||||
2025-08-07 23:15:13,111 - TaskTester - INFO - 失败: 0
|
||||
2025-08-07 23:15:13,111 - TaskTester - INFO - 当前运行: 0
|
||||
2025-08-07 23:15:13,111 - TaskTester - INFO - ========================================
|
||||
2025-08-07 23:15:13,111 - TaskTester - INFO - AGV-1: 空闲, 已完成: 0
|
||||
2025-08-07 23:15:43,111 - TaskTester - INFO - ========== 进度统计 ===========
|
||||
2025-08-07 23:15:43,111 - TaskTester - INFO - 总处理: 0/500
|
||||
2025-08-07 23:15:43,111 - TaskTester - INFO - 成功: 0
|
||||
2025-08-07 23:15:43,111 - TaskTester - INFO - 失败: 0
|
||||
2025-08-07 23:15:43,111 - TaskTester - INFO - 当前运行: 0
|
||||
2025-08-07 23:15:43,112 - TaskTester - INFO - ========================================
|
||||
2025-08-07 23:15:43,112 - TaskTester - INFO - AGV-1: 空闲, 已完成: 0
|
||||
2025-08-07 23:58:02,815 - asyncio - DEBUG - Using proactor: IocpProactor
|
||||
2025-08-07 23:58:54,657 - asyncio - DEBUG - Using proactor: IocpProactor
|
||||
2025-08-07 23:58:55,227 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-07 23:58:55,247 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-07 23:58:55,249 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
||||
2025-08-07 23:58:55,252 - aiomysql - DEBUG - caching sha2: succeeded by fast path.
|
722
scripts/new_task_test.py
Normal file
722
scripts/new_task_test.py
Normal file
@ -0,0 +1,722 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
自动化任务测试脚本
|
||||
用于测试create_new_task和gen_agv_scheduling_task接口
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import random
|
||||
import time
|
||||
import uuid
|
||||
import sys
|
||||
import os
|
||||
import signal
|
||||
from typing import Dict, List, Any, Optional
|
||||
import aiohttp
|
||||
import json
|
||||
from datetime import datetime
|
||||
from asyncio import Semaphore, Queue
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from data.session import get_async_session
|
||||
from data.models.operate_point_layer import OperatePointLayer
|
||||
from data.models.external_task_record import VWEDExternalTaskRecord
|
||||
from services.task_record_service import TaskRecordService
|
||||
from data.enum.task_record_enum import TaskStatus
|
||||
from sqlalchemy import select, and_
|
||||
|
||||
# API配置
|
||||
BASE_URL = "http://localhost:8001"
|
||||
CREATE_TASK_URL = f"{BASE_URL}/newTask"
|
||||
GEN_AGV_TASK_URL = f"{BASE_URL}/GenAgvSchedulingTask"
|
||||
|
||||
# 任务类型配置
|
||||
CREATE_TASK_TYPES = ["GG2MP", "GGFK2MP", "GT2MP", "GTFK2MP", "ZG2MP", "QZ2MP", "LG2MP", "PHZ2MP"]
|
||||
|
||||
# 任务类型分组配置 - 确保同时运行的4个任务尽可能不同类型
|
||||
TASK_TYPE_GROUPS = {
|
||||
"GG": ["GG2MP", "GGFK2MP"], # 钢构类型
|
||||
"GT": ["GT2MP", "GTFK2MP"], # 钢台类型
|
||||
"ZG": ["ZG2MP"], # 铸钢类型
|
||||
"QZ": ["QZ2MP"], # 其他类型1
|
||||
"LG": ["LG2MP"], # 其他类型2
|
||||
"PHZ": ["PHZ2MP"] # 其他类型3
|
||||
}
|
||||
# GEN_TASK_TYPES = ["MP2GG", "MP2GGFK", "MP2GT", "MP2GTFK", "MP2ZG", "MP2QZ", "MP2LG", "MP2PHZ"]
|
||||
CREATE_TASK_TYPES_JSON = {
|
||||
"GG2MP": "MP2GG",
|
||||
"GGFK2MP": "MP2GGFK",
|
||||
"GT2MP": "MP2GT",
|
||||
"GTFK2MP": "MP2GTFK",
|
||||
"ZG2MP": "MP2ZG",
|
||||
"QZ2MP": "MP2QZ",
|
||||
"LG2MP": "MP2LG",
|
||||
"PHZ2MP": "MP2PHZ",
|
||||
}
|
||||
|
||||
# 任务类型对应的动作点映射
|
||||
CREATE_TASK_TARGET_MAPPING = {
|
||||
"QZ2MP": ["AP135", "AP136"],
|
||||
"PHZ2MP": ["AP208", "AP209", "AP210", "AP211", "AP212", "AP213", "AP214", "AP215"],
|
||||
"LG2MP": ["AP216", "AP217"],
|
||||
"GT2MP": ["AP255", "AP256", "AP257", "AP258"],
|
||||
"GTFK2MP": ["AP255", "AP256", "AP257", "AP258"],
|
||||
"GG2MP": ["AP310", "AP311", "AP312", "AP313"],
|
||||
"GGFK2MP": ["AP310", "AP311", "AP312", "AP313"],
|
||||
"ZG2MP": ["AP297", "AP298"]
|
||||
}
|
||||
|
||||
GEN_TASK_ENDPOINT_MAPPING = {
|
||||
"MP2GG": ["AP310", "AP311", "AP312", "AP313"],
|
||||
"MP2GGFK": ["AP310", "AP311", "AP312", "AP313"],
|
||||
"MP2GT": ["AP255", "AP256", "AP257", "AP258"],
|
||||
"MP2GTFK": ["AP255", "AP256", "AP257", "AP258"],
|
||||
"MP2ZG": ["AP297", "AP298"],
|
||||
"MP2QZ": ["AP135", "AP136"],
|
||||
"MP2LG": ["AP216", "AP217"],
|
||||
"MP2PHZ": ["AP208", "AP209", "AP210", "AP211", "AP212", "AP213", "AP214", "AP215"]
|
||||
}
|
||||
|
||||
# 任务类型对应库区映射
|
||||
TASK_TYPE_AREA_MAPPING = {
|
||||
"MP2GG": ["ZK/ZKG"],
|
||||
"MP2GGFK": ["ZK/ZKG"],
|
||||
"MP2GT": ["ZK/ZKG"],
|
||||
"MP2GTFK": ["ZK/ZKG"],
|
||||
"MP2ZG": ["ZK/ZKG"],
|
||||
"MP2QZ": ["KW"],
|
||||
"MP2LG": ["AGW/PL"],
|
||||
"MP2PHZ": ["AGW/PL"]
|
||||
}
|
||||
|
||||
|
||||
class AutomatedTaskTester:
|
||||
"""自动化任务测试器"""
|
||||
|
||||
def __init__(self, max_concurrent_tasks: int = 4, max_total_tasks: int = 400):
|
||||
self.used_req_codes = set()
|
||||
self.session = None
|
||||
self.create_task_records = [] # 存储create_new_task的记录
|
||||
self.max_concurrent_tasks = max_concurrent_tasks # 最大并发任务数
|
||||
self.max_total_tasks = max_total_tasks # 最大总任务数
|
||||
self.semaphore = Semaphore(max_concurrent_tasks) # 并发控制信号量
|
||||
self.task_queue = Queue(maxsize=max_total_tasks) # 任务队列
|
||||
self.completed_tasks = 0 # 已完成任务数
|
||||
self.success_count = 0 # 成功任务数
|
||||
self.failed_count = 0 # 失败任务数
|
||||
self.task_lock = asyncio.Lock() # 任务统计锁
|
||||
self.running_task_types = set() # 当前运行的任务类型组
|
||||
self.task_type_lock = asyncio.Lock() # 任务类型锁
|
||||
self.shutdown_event = asyncio.Event() # 优雅关闭事件
|
||||
self.running_tasks = [] # 跟踪运行中的任务
|
||||
|
||||
async def __aenter__(self):
|
||||
"""异步上下文管理器入口"""
|
||||
self.session = aiohttp.ClientSession()
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||
"""异步上下文管理器出口"""
|
||||
if self.session:
|
||||
await self.session.close()
|
||||
|
||||
def signal_handler(self, signum, frame):
|
||||
"""信号处理器,用于处理Ctrl+C"""
|
||||
print(f"\n接收到信号 {signum},开始优雅关闭...")
|
||||
self.shutdown_event.set()
|
||||
|
||||
async def cancel_all_tasks(self):
|
||||
"""取消所有运行中的任务"""
|
||||
print("正在取消所有运行中的任务...")
|
||||
for task in self.running_tasks:
|
||||
if not task.done():
|
||||
task.cancel()
|
||||
|
||||
if self.running_tasks:
|
||||
await asyncio.gather(*self.running_tasks, return_exceptions=True)
|
||||
|
||||
print("所有任务已取消")
|
||||
|
||||
def generate_unique_req_code(self) -> str:
|
||||
"""生成唯一的请求码"""
|
||||
while True:
|
||||
req_code = f"TEST_{int(time.time() * 1000)}_{random.randint(1000, 9999)}"
|
||||
if req_code not in self.used_req_codes:
|
||||
self.used_req_codes.add(req_code)
|
||||
return req_code
|
||||
|
||||
async def get_available_storage_locations(self, area_names: List[str]) -> List[str]:
|
||||
"""
|
||||
查询指定库区中已占用且未锁定的库位,如果找不到则持续查询直到找到
|
||||
|
||||
Args:
|
||||
area_names: 库区名称列表
|
||||
|
||||
Returns:
|
||||
List[str]: 可用库位的station_name列表
|
||||
"""
|
||||
print(f"开始在库区 {area_names} 中查询可用库位...")
|
||||
attempt = 0
|
||||
|
||||
while True:
|
||||
attempt += 1
|
||||
try:
|
||||
async with get_async_session() as db_session:
|
||||
stmt = select(OperatePointLayer).where(
|
||||
and_(
|
||||
OperatePointLayer.area_name.in_(area_names),
|
||||
OperatePointLayer.is_occupied == True,
|
||||
OperatePointLayer.is_locked == False,
|
||||
OperatePointLayer.is_deleted == False
|
||||
)
|
||||
)
|
||||
result = await db_session.execute(stmt)
|
||||
layers = result.scalars().all()
|
||||
|
||||
station_names = [layer.station_name for layer in layers if layer.station_name]
|
||||
|
||||
if station_names:
|
||||
print(f"✓ 第{attempt}次尝试:在库区 {area_names} 中找到 {len(station_names)} 个可用库位")
|
||||
return station_names
|
||||
else:
|
||||
print(f"✗ 第{attempt}次尝试:在库区 {area_names} 中未找到可用库位,等待2秒后重试...")
|
||||
await asyncio.sleep(2)
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 第{attempt}次查询库位时出错: {str(e)},等待2秒后重试...")
|
||||
await asyncio.sleep(2)
|
||||
|
||||
def get_task_type_group(self, task_type: str) -> str:
|
||||
"""
|
||||
获取任务类型对应的组名
|
||||
|
||||
Args:
|
||||
task_type: 任务类型
|
||||
|
||||
Returns:
|
||||
str: 组名
|
||||
"""
|
||||
for group_name, types in TASK_TYPE_GROUPS.items():
|
||||
if task_type in types:
|
||||
return group_name
|
||||
return "UNKNOWN"
|
||||
|
||||
async def select_task_type_with_diversity(self) -> Optional[str]:
|
||||
"""
|
||||
选择任务类型,确保尽可能不重复组类型
|
||||
|
||||
Returns:
|
||||
Optional[str]: 选择的任务类型,如果没有可用类型返回None
|
||||
"""
|
||||
async with self.task_type_lock:
|
||||
available_groups = []
|
||||
|
||||
# 找出当前未使用的组
|
||||
for group_name in TASK_TYPE_GROUPS.keys():
|
||||
if group_name not in self.running_task_types:
|
||||
available_groups.append(group_name)
|
||||
|
||||
# 如果没有未使用的组,且当前运行任务少于最大并发数,可以重复使用组
|
||||
if not available_groups and len(self.running_task_types) < self.max_concurrent_tasks:
|
||||
available_groups = list(TASK_TYPE_GROUPS.keys())
|
||||
|
||||
if not available_groups:
|
||||
return None # 所有组都在使用中
|
||||
|
||||
# 随机选择一个组
|
||||
selected_group = random.choice(available_groups)
|
||||
|
||||
# 从该组中随机选择一个任务类型
|
||||
task_types = TASK_TYPE_GROUPS[selected_group]
|
||||
selected_task_type = random.choice(task_types)
|
||||
|
||||
# 标记该组为使用中
|
||||
self.running_task_types.add(selected_group)
|
||||
|
||||
return selected_task_type
|
||||
|
||||
async def release_task_type_group(self, task_type: str):
|
||||
"""
|
||||
释放任务类型组
|
||||
|
||||
Args:
|
||||
task_type: 任务类型
|
||||
"""
|
||||
async with self.task_type_lock:
|
||||
group_name = self.get_task_type_group(task_type)
|
||||
self.running_task_types.discard(group_name)
|
||||
|
||||
async def monitor_task_status(self, req_code: str, timeout: int = 5) -> bool:
|
||||
"""
|
||||
监控任务状态,超时或失败时返回False
|
||||
|
||||
Args:
|
||||
req_code: 请求码
|
||||
timeout: 超时时间(秒)
|
||||
|
||||
Returns:
|
||||
bool: True表示任务成功或继续运行,False表示失败或超时
|
||||
"""
|
||||
start_time = time.time()
|
||||
|
||||
while time.time() - start_time < timeout:
|
||||
try:
|
||||
# 通过req_code查询external_task_record表获取task_record_id
|
||||
async with get_async_session() as db_session:
|
||||
stmt = select(VWEDExternalTaskRecord).where(
|
||||
VWEDExternalTaskRecord.req_code == req_code
|
||||
)
|
||||
result = await db_session.execute(stmt)
|
||||
external_record = result.scalar_one_or_none()
|
||||
|
||||
if external_record and external_record.task_record_id:
|
||||
# 调用TaskRecordService.get_task_record_detail获取任务状态
|
||||
task_detail_result = await TaskRecordService.get_task_record_detail(
|
||||
external_record.task_record_id
|
||||
)
|
||||
|
||||
if task_detail_result.get("success", False):
|
||||
task_detail = task_detail_result.get("data", {})
|
||||
task_status = task_detail.get("status", "")
|
||||
|
||||
# 如果任务失败,返回False
|
||||
if task_status == TaskStatus.FAILED:
|
||||
print(f"任务 {req_code} 失败,状态: {task_status}")
|
||||
return False
|
||||
|
||||
# 如果任务成功完成,返回True(可以继续后续步骤)
|
||||
if task_status == TaskStatus.COMPLETED:
|
||||
print(f"任务 {req_code} 完成,状态: {task_status}")
|
||||
return True
|
||||
|
||||
# 任务还在运行中,继续监控
|
||||
print(f"任务 {req_code} 运行中,状态: {task_status}")
|
||||
|
||||
await asyncio.sleep(1) # 每秒检查一次
|
||||
|
||||
except Exception as e:
|
||||
print(f"监控任务状态异常: {str(e)}")
|
||||
await asyncio.sleep(1)
|
||||
|
||||
# 超时,返回True继续执行(不阻塞后续流程)
|
||||
print(f"任务 {req_code} 监控超时,继续执行后续流程")
|
||||
return True
|
||||
|
||||
async def monitor_agv_task_completion(self, req_code: str) -> bool:
|
||||
"""
|
||||
监控GenAgvSchedulingTask任务完成状态,必须等待任务完成(成功或失败)
|
||||
|
||||
Args:
|
||||
req_code: GenAgvSchedulingTask的请求码
|
||||
|
||||
Returns:
|
||||
bool: True表示任务完成(成功或失败),用于统计
|
||||
"""
|
||||
print(f"开始监控AGV调度任务完成状态: {req_code}")
|
||||
|
||||
while True:
|
||||
try:
|
||||
# 通过req_code查询external_task_record表获取task_record_id
|
||||
async with get_async_session() as db_session:
|
||||
stmt = select(VWEDExternalTaskRecord).where(
|
||||
VWEDExternalTaskRecord.req_code == req_code
|
||||
)
|
||||
result = await db_session.execute(stmt)
|
||||
external_record = result.scalar_one_or_none()
|
||||
|
||||
if external_record and external_record.task_record_id:
|
||||
# 调用TaskRecordService.get_task_record_detail获取任务状态
|
||||
task_detail_result = await TaskRecordService.get_task_record_detail(
|
||||
external_record.task_record_id
|
||||
)
|
||||
|
||||
if task_detail_result.get("success", False):
|
||||
task_detail = task_detail_result.get("data", {})
|
||||
task_status = task_detail.get("status", "")
|
||||
|
||||
print(f"AGV调度任务 {req_code} 当前状态: {task_status}")
|
||||
|
||||
# 如果任务完成(成功或失败),返回对应结果
|
||||
if task_status == TaskStatus.COMPLETED:
|
||||
print(f"✓ AGV调度任务 {req_code} 执行成功完成")
|
||||
return True
|
||||
|
||||
elif task_status == TaskStatus.FAILED:
|
||||
print(f"✗ AGV调度任务 {req_code} 执行失败")
|
||||
return False
|
||||
|
||||
elif task_status == TaskStatus.CANCELED:
|
||||
print(f"⚠ AGV调度任务 {req_code} 被取消")
|
||||
return False
|
||||
|
||||
# 任务还在运行中,继续监控
|
||||
else:
|
||||
print(f"AGV调度任务 {req_code} 仍在运行中,状态: {task_status}")
|
||||
else:
|
||||
print(f"无法获取AGV调度任务 {req_code} 的详情,继续等待...")
|
||||
else:
|
||||
print(f"未找到AGV调度任务 {req_code} 的记录或task_record_id,继续等待...")
|
||||
|
||||
await asyncio.sleep(2) # 每2秒检查一次
|
||||
|
||||
except Exception as e:
|
||||
print(f"监控AGV调度任务状态异常: {str(e)}")
|
||||
await asyncio.sleep(2) # 异常时等待2秒后重试
|
||||
|
||||
async def create_new_task(self, task_type: str, target_id: str, req_code: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
调用create_new_task接口创建新任务
|
||||
|
||||
Args:
|
||||
task_type: 任务类型
|
||||
target_id: 目标ID
|
||||
|
||||
Returns:
|
||||
Dict: 接口响应结果
|
||||
"""
|
||||
|
||||
payload = {
|
||||
"ReqCode": req_code,
|
||||
"SourceID": "",
|
||||
"TargetID": target_id,
|
||||
"TaskType": task_type
|
||||
}
|
||||
|
||||
try:
|
||||
async with self.session.post(CREATE_TASK_URL, json=payload) as response:
|
||||
result = await response.json()
|
||||
print(f"创建任务 - 类型: {task_type}, 目标: {target_id}, 请求码: {req_code}")
|
||||
print(f"响应: {result}")
|
||||
|
||||
if result.get("code") == 0:
|
||||
self.create_task_records.append({
|
||||
"req_code": req_code,
|
||||
"task_type": task_type,
|
||||
"target_id": target_id,
|
||||
"response": result
|
||||
})
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
print(f"创建任务失败: {str(e)}")
|
||||
return None
|
||||
|
||||
async def gen_agv_scheduling_task(self, task_type: str, task_code: str, start_point: str, end_point: str) -> \
|
||||
Optional[Dict[str, Any]]:
|
||||
"""
|
||||
调用gen_agv_scheduling_task接口生成AGV调度任务
|
||||
|
||||
Args:
|
||||
task_type: 任务类型
|
||||
task_code: 任务代码(create_new_task的ReqCode)
|
||||
start_point: 起点
|
||||
end_point: 终点
|
||||
|
||||
Returns:
|
||||
Dict: 接口响应结果
|
||||
"""
|
||||
req_code = self.generate_unique_req_code()
|
||||
|
||||
payload = {
|
||||
"ReqCode": req_code,
|
||||
"TaskTyp": task_type,
|
||||
"TaskCode": task_code,
|
||||
"SecurityKey": "",
|
||||
"Type": "",
|
||||
"SubType": "",
|
||||
"AreaPositonCode": "",
|
||||
"AreaPositonName": "",
|
||||
"PositionCodePath": [
|
||||
{"PositionCode": start_point, "Type": "start"},
|
||||
{"PositionCode": end_point, "Type": "end"}
|
||||
],
|
||||
"ClientCode": "",
|
||||
"TokenCode": ""
|
||||
}
|
||||
|
||||
try:
|
||||
async with self.session.post(GEN_AGV_TASK_URL, json=payload) as response:
|
||||
result = await response.json()
|
||||
print(f"生成AGV调度 - 类型: {task_type}, 任务码: {task_code}, 路径: {start_point} -> {end_point}")
|
||||
print(f"响应: {result}")
|
||||
|
||||
# 确保返回的结果包含我们生成的req_code,用于后续监控
|
||||
if result and isinstance(result, dict):
|
||||
result["generated_req_code"] = req_code # 添加生成的req_code
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
print(f"生成AGV调度任务失败: {str(e)}")
|
||||
return None
|
||||
|
||||
async def execute_single_task(self, task_id: int):
|
||||
"""
|
||||
执行单个完整任务流程(在并发控制下)
|
||||
|
||||
Args:
|
||||
task_id: 任务ID
|
||||
"""
|
||||
create_task_type = None
|
||||
async with self.semaphore: # 获取信号量,控制并发数
|
||||
try:
|
||||
# 检查是否需要关闭
|
||||
if self.shutdown_event.is_set():
|
||||
print(f"任务 {task_id} - 接收到关闭信号,跳过执行")
|
||||
return
|
||||
|
||||
print(f"\n========== 任务 {task_id} 开始执行 ==========")
|
||||
|
||||
# Step 1: 智能选择create_new_task的任务类型,确保不同类型分布
|
||||
create_task_type = await self.select_task_type_with_diversity()
|
||||
if not create_task_type:
|
||||
print(f"✗ 任务 {task_id} - 无法选择任务类型(所有类型都在使用中),跳过")
|
||||
async with self.task_lock:
|
||||
self.failed_count += 1
|
||||
self.completed_tasks += 1
|
||||
return
|
||||
|
||||
print(f"任务 {task_id} - 选择任务类型: {create_task_type} (组: {self.get_task_type_group(create_task_type)})")
|
||||
target_points = CREATE_TASK_TARGET_MAPPING.get(create_task_type, [])
|
||||
if not target_points:
|
||||
print(f"✗ 任务 {task_id} - 任务类型 {create_task_type} 没有配置对应的动作点,跳过")
|
||||
async with self.task_lock:
|
||||
self.failed_count += 1
|
||||
self.completed_tasks += 1
|
||||
return
|
||||
|
||||
target_id = random.choice(target_points)
|
||||
|
||||
print(f"任务 {task_id} - 1. 调用 newTask - 类型: {create_task_type}, 目标: {target_id}")
|
||||
req_code = self.generate_unique_req_code()
|
||||
create_result = await self.create_new_task(create_task_type, target_id, req_code)
|
||||
if not create_result or create_result.get("code") != 0:
|
||||
print(f"✗ 任务 {task_id} - newTask 调用失败,跳过后续步骤")
|
||||
async with self.task_lock:
|
||||
self.failed_count += 1
|
||||
self.completed_tasks += 1
|
||||
return
|
||||
|
||||
print(f"✓ 任务 {task_id} - newTask 调用成功")
|
||||
|
||||
# Step 1.5: 监控create_new_task创建的任务状态(5秒超时)
|
||||
print(f"任务 {task_id} - 开始监控任务状态...")
|
||||
task_continue = await self.monitor_task_status(req_code, timeout=5)
|
||||
if not task_continue:
|
||||
print(f"✗ 任务 {task_id} - 任务监控失败,终止执行")
|
||||
async with self.task_lock:
|
||||
self.failed_count += 1
|
||||
self.completed_tasks += 1
|
||||
return
|
||||
|
||||
print(f"✓ 任务 {task_id} - 任务监控通过,继续执行")
|
||||
|
||||
# Step 2: 随机选择gen_agv_scheduling_task的任务类型
|
||||
gen_task_type = CREATE_TASK_TYPES_JSON.get(create_task_type)
|
||||
task_code = req_code
|
||||
|
||||
# 获取起点(从库区中持续查询直到找到)
|
||||
area_names = TASK_TYPE_AREA_MAPPING.get(gen_task_type, [])
|
||||
if not area_names:
|
||||
print(f"✗ 任务 {task_id} - 任务类型 {gen_task_type} 没有配置对应的库区,跳过")
|
||||
async with self.task_lock:
|
||||
self.failed_count += 1
|
||||
self.completed_tasks += 1
|
||||
return
|
||||
|
||||
print(f"任务 {task_id} - 2. 查询库区 {area_names} 中的可用库位...")
|
||||
available_locations = await self.get_available_storage_locations(area_names)
|
||||
start_point = random.choice(available_locations)
|
||||
|
||||
# 获取终点
|
||||
end_points = GEN_TASK_ENDPOINT_MAPPING.get(gen_task_type, [])
|
||||
if not end_points:
|
||||
print(f"✗ 任务 {task_id} - 任务类型 {gen_task_type} 没有配置对应的终点,跳过")
|
||||
async with self.task_lock:
|
||||
self.failed_count += 1
|
||||
self.completed_tasks += 1
|
||||
return
|
||||
|
||||
print(f"任务 {task_id} - 3. 调用 GenAgvSchedulingTask - 类型: {gen_task_type}, 任务码: {task_code}")
|
||||
|
||||
gen_result = await self.gen_agv_scheduling_task(gen_task_type, task_code, start_point, target_id)
|
||||
|
||||
if gen_result:
|
||||
print(f"✓ 任务 {task_id} - GenAgvSchedulingTask 调用成功")
|
||||
|
||||
# Step 3.5: 监控GenAgvSchedulingTask任务直到完成
|
||||
# 获取GenAgvSchedulingTask的req_code (gen_agv_scheduling_task方法中生成的req_code)
|
||||
# 优先使用接口返回的reqCode,如果没有则使用我们生成的generated_req_code
|
||||
agv_req_code = gen_result.get("reqCode") or gen_result.get("generated_req_code")
|
||||
if agv_req_code:
|
||||
print(f"任务 {task_id} - 4. 开始监控AGV调度任务完成状态...")
|
||||
agv_task_success = await self.monitor_agv_task_completion(agv_req_code)
|
||||
|
||||
async with self.task_lock:
|
||||
if agv_task_success:
|
||||
print(f"✓ 任务 {task_id} - 完整流程执行成功(AGV任务已完成)")
|
||||
self.success_count += 1
|
||||
else:
|
||||
print(f"✗ 任务 {task_id} - AGV调度任务执行失败")
|
||||
self.failed_count += 1
|
||||
self.completed_tasks += 1
|
||||
else:
|
||||
# 如果无法获取agv_req_code,仍然记录为成功(接口调用成功)
|
||||
print(f"⚠ 任务 {task_id} - 无法获取AGV调度任务的请求码,标记为成功")
|
||||
async with self.task_lock:
|
||||
self.success_count += 1
|
||||
self.completed_tasks += 1
|
||||
else:
|
||||
print(f"✗ 任务 {task_id} - GenAgvSchedulingTask 调用失败")
|
||||
async with self.task_lock:
|
||||
self.failed_count += 1
|
||||
self.completed_tasks += 1
|
||||
|
||||
# 任务间隔
|
||||
await asyncio.sleep(1)
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 任务 {task_id} 执行异常: {str(e)}")
|
||||
async with self.task_lock:
|
||||
self.failed_count += 1
|
||||
self.completed_tasks += 1
|
||||
finally:
|
||||
# 确保任务类型组被释放
|
||||
if create_task_type:
|
||||
await self.release_task_type_group(create_task_type)
|
||||
|
||||
async def run_complete_task_workflow(self, count: int = 500):
|
||||
"""
|
||||
运行完整的任务工作流测试(多线程并发执行,最多4个并发任务)
|
||||
|
||||
Args:
|
||||
count: 测试次数(最大400个)
|
||||
"""
|
||||
# 限制总任务数不超过最大值
|
||||
count = min(count, self.max_total_tasks)
|
||||
|
||||
print(f"\n开始运行完整任务工作流测试,共 {count} 个任务")
|
||||
print(f"最大并发数: {self.max_concurrent_tasks},最大总任务数: {self.max_total_tasks}")
|
||||
print("=" * 80)
|
||||
|
||||
# 重置统计计数器
|
||||
async with self.task_lock:
|
||||
self.completed_tasks = 0
|
||||
self.success_count = 0
|
||||
self.failed_count = 0
|
||||
|
||||
# 创建所有任务
|
||||
tasks = []
|
||||
for i in range(count):
|
||||
task = asyncio.create_task(self.execute_single_task(i + 1))
|
||||
tasks.append(task)
|
||||
self.running_tasks.append(task)
|
||||
|
||||
# 启动进度监控任务
|
||||
monitor_task = asyncio.create_task(self.monitor_progress(count))
|
||||
self.running_tasks.append(monitor_task)
|
||||
|
||||
try:
|
||||
# 等待所有任务完成或关闭事件
|
||||
done, pending = await asyncio.wait(
|
||||
tasks,
|
||||
return_when=asyncio.ALL_COMPLETED
|
||||
)
|
||||
|
||||
# 如果接收到关闭信号,取消所有待完成的任务
|
||||
if self.shutdown_event.is_set() and pending:
|
||||
print(f"\n取消 {len(pending)} 个未完成的任务...")
|
||||
for task in pending:
|
||||
task.cancel()
|
||||
await asyncio.gather(*pending, return_exceptions=True)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n检测到键盘中断,开始优雅关闭...")
|
||||
self.shutdown_event.set()
|
||||
await self.cancel_all_tasks()
|
||||
finally:
|
||||
# 停止进度监控
|
||||
if not monitor_task.done():
|
||||
monitor_task.cancel()
|
||||
|
||||
# 最终统计
|
||||
print(f"\n========== 最终统计 ===========")
|
||||
print(f"总任务数: {count}")
|
||||
print(f"成功: {self.success_count} 个")
|
||||
print(f"失败: {self.failed_count} 个")
|
||||
if count > 0:
|
||||
print(f"成功率: {self.success_count / count * 100:.1f}%")
|
||||
print("=" * 40)
|
||||
|
||||
async def monitor_progress(self, total_count: int):
|
||||
"""
|
||||
监控任务执行进度
|
||||
|
||||
Args:
|
||||
total_count: 总任务数
|
||||
"""
|
||||
try:
|
||||
while True:
|
||||
await asyncio.sleep(10) # 每10秒输出一次进度
|
||||
async with self.task_lock:
|
||||
if self.completed_tasks >= total_count:
|
||||
break
|
||||
|
||||
print(f"\n========== 进度统计 ===========")
|
||||
print(f"已完成: {self.completed_tasks}/{total_count} 个任务")
|
||||
print(f"成功: {self.success_count} 个")
|
||||
print(f"失败: {self.failed_count} 个")
|
||||
if self.completed_tasks > 0:
|
||||
print(f"成功率: {self.success_count / self.completed_tasks * 100:.1f}%")
|
||||
print(f"当前并发数: {self.max_concurrent_tasks - self.semaphore._value}")
|
||||
print(f"运行中的任务类型组: {list(self.running_task_types)}")
|
||||
print("=" * 40)
|
||||
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
|
||||
async def main():
|
||||
"""主函数"""
|
||||
print("自动化任务压测脚本启动")
|
||||
print(f"启动时间: {datetime.now()}")
|
||||
print(f"API基础URL: {BASE_URL}")
|
||||
print(f"压测任务量: 400个(最大并发4个)")
|
||||
print(f"测试流程: newTask -> 监控任务状态 -> GenAgvSchedulingTask -> 监控AGV任务完成")
|
||||
print("按Ctrl+C可随时终止脚本并取消所有任务")
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
async with AutomatedTaskTester(max_concurrent_tasks=4, max_total_tasks=400) as tester:
|
||||
# 设置信号处理器
|
||||
signal.signal(signal.SIGINT, tester.signal_handler)
|
||||
signal.signal(signal.SIGTERM, tester.signal_handler)
|
||||
|
||||
# 运行完整工作流测试 - 最大并发4个任务
|
||||
await tester.run_complete_task_workflow(count=400)
|
||||
|
||||
end_time = time.time()
|
||||
duration = end_time - start_time
|
||||
|
||||
print(f"\n========== 压测完成 ===========")
|
||||
print(f"结束时间: {datetime.now()}")
|
||||
print(f"总耗时: {duration:.2f} 秒")
|
||||
print(f"平均每个任务耗时: {duration / 400:.2f} 秒")
|
||||
print("=" * 40)
|
||||
|
||||
|
||||
def main_sync():
|
||||
"""同步入口函数,用于正确处理异步信号"""
|
||||
try:
|
||||
asyncio.run(main())
|
||||
except KeyboardInterrupt:
|
||||
print("\n程序被用户中断")
|
||||
except Exception as e:
|
||||
print(f"\n程序异常退出: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main_sync()
|
Binary file not shown.
@ -1,5 +0,0 @@
|
||||
def name(a: int, b: int) -> int:
|
||||
return a + b
|
||||
|
||||
def name1():
|
||||
print('=====')
|
@ -1,5 +1,197 @@
|
||||
def test1(a: int, b: int) -> int:
|
||||
return {"name":a + b}
|
||||
import asyncio
|
||||
from typing import Dict, Any
|
||||
|
||||
async def test1(a: int, b: int) -> int:
|
||||
return {"name": a + b}
|
||||
|
||||
def name1():
|
||||
print('=====')
|
||||
print('=====')
|
||||
|
||||
async def validate_task_condition(function_args: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
任务状态验证功能,用于验证关联任务的状态
|
||||
|
||||
Args:
|
||||
function_args: 包含以下参数的字典
|
||||
- task_code: 任务代码,用于查询外部任务记录
|
||||
- task_type: 任务类型,用于判断验证逻辑
|
||||
- end_node: 终点节点(仅对GT类型任务需要)
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: 验证结果
|
||||
"""
|
||||
from services.external_task_record_service import ExternalTaskRecordService
|
||||
from services.task_record_service import TaskRecordService
|
||||
from data.session import get_async_session
|
||||
from data.models.operate_point_layer import OperatePointLayer
|
||||
from data.enum.task_record_enum import TaskStatus
|
||||
from sqlalchemy import select
|
||||
from utils.logger import get_logger
|
||||
|
||||
logger = get_logger("scripts.user_save.test1")
|
||||
# print(function_args, "=========================")
|
||||
try:
|
||||
# 获取参数
|
||||
task_code = function_args.get('task_code')
|
||||
task_type = function_args.get('task_type')
|
||||
end_node = function_args.get('end_node')
|
||||
|
||||
if not task_code:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "task_code参数为空"
|
||||
}
|
||||
|
||||
if not task_type:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "task_type参数为空"
|
||||
}
|
||||
|
||||
# 定义任务类型模板映射
|
||||
TASK_TYPE_TEMPLATE_MAPPING = {
|
||||
"GG2MP": "GG",
|
||||
"GGFK2MP": "GG",
|
||||
"GT2MP": "GT",
|
||||
"GTFK2MP": "GT",
|
||||
"ZG2MP": "ZG",
|
||||
"QZ2MP": "QZ",
|
||||
"LG2MP": "LG",
|
||||
"PHZ2MP": "PHZ",
|
||||
"MP2GG": "GG",
|
||||
"MP2GGFK": "GG",
|
||||
"MP2GT": "GT",
|
||||
"MP2GTFK": "GT",
|
||||
"MP2ZG": "ZG",
|
||||
"MP2QZ": "QZ",
|
||||
"MP2LG": "LG",
|
||||
"MP2PHZ": "PHZ"
|
||||
}
|
||||
|
||||
# 根据TaskCode查询external_task_record表获取task_record_id
|
||||
external_records = await ExternalTaskRecordService.get_task_records_by_task_code(task_code)
|
||||
logger.info(f"系统相关记录: {external_records}")
|
||||
|
||||
if not external_records:
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"找不到TaskCode={task_code}对应的叫料任务记录,无法监控叫料任务状态"
|
||||
}
|
||||
|
||||
# 获取最新的记录
|
||||
latest_record = max(external_records, key=lambda x: x.created_at)
|
||||
|
||||
if not latest_record.task_record_id:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "叫料任务记录对应关键字task_record_id值为空"
|
||||
}
|
||||
|
||||
# 获取TaskType对应的模板类型
|
||||
template_type = TASK_TYPE_TEMPLATE_MAPPING.get(task_type, "")
|
||||
logger.info(f"TaskCode={task_code}, TaskType={task_type}, TemplateType={template_type}")
|
||||
|
||||
# 如果是GT类型,需要验证end_node库位是否解锁
|
||||
if template_type == "GT":
|
||||
if not end_node:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "GT类型任务需要提供end_node参数"
|
||||
}
|
||||
|
||||
logger.info(f"GT类型任务,需要验证end_node库位解锁状态: TaskCode={task_code}")
|
||||
|
||||
# 验证end_node对应的库位是否解锁
|
||||
while True:
|
||||
try:
|
||||
async with get_async_session() as session:
|
||||
# 查询end_node对应的库位锁定状态
|
||||
stmt = select(OperatePointLayer).where(
|
||||
OperatePointLayer.layer_name == end_node,
|
||||
OperatePointLayer.is_deleted == False
|
||||
).limit(1)
|
||||
result = await session.execute(stmt)
|
||||
end_layer = result.scalar_one_or_none()
|
||||
task_detail_result = await TaskRecordService.get_task_record_detail(
|
||||
latest_record.task_record_id)
|
||||
task_detail = task_detail_result.get("data", {})
|
||||
task_status = task_detail.get("status", "")
|
||||
if task_status == TaskStatus.CANCELED:
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"任务被取消: TaskCode={task_code}, Status={task_status}"
|
||||
}
|
||||
if end_layer:
|
||||
if not end_layer.is_locked:
|
||||
logger.info(f"GT类型任务,end_node库位已解锁,可以执行: TaskCode={task_code}, end_node={end_node}")
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"GT类型任务验证通过,end_node库位已解锁: {end_node}"
|
||||
}
|
||||
else:
|
||||
logger.info(f"GT类型任务,end_node库位被锁定,等待解锁: TaskCode={task_code}, end_node={end_node}, locked_by={end_layer.locked_by}")
|
||||
await asyncio.sleep(2) # 等待2秒后重试
|
||||
else:
|
||||
logger.warning(f"GT类型任务,未找到end_node对应的库位,继续执行: TaskCode={task_code}, end_node={end_node}")
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"GT类型任务验证通过,未找到对应库位,继续执行: {end_node}"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"GT类型任务,检查end_node库位锁定状态时出现异常: {str(e)}, TaskCode={task_code}, end_node={end_node}")
|
||||
await asyncio.sleep(2) # 等待2秒后重试
|
||||
else:
|
||||
# 非GT类型,需要等待任务完成
|
||||
logger.info(f"非GT类型任务,需要等待关联任务完成: TaskCode={task_code}")
|
||||
|
||||
wait_count = 0
|
||||
|
||||
while True:
|
||||
# 调用get_task_record_detail查询任务运行状态
|
||||
task_detail_result = await TaskRecordService.get_task_record_detail(latest_record.task_record_id)
|
||||
|
||||
if task_detail_result.get("success", False):
|
||||
task_detail = task_detail_result.get("data", {})
|
||||
task_status = task_detail.get("status", "")
|
||||
|
||||
logger.info(f"检查任务状态: TaskCode={task_code}, Status={task_status}, WaitCount={wait_count}")
|
||||
|
||||
# 如果任务已完成(成功)
|
||||
if task_status == TaskStatus.COMPLETED:
|
||||
logger.info(f"关联任务已完成,继续执行AGV调度任务: TaskCode={task_code}")
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"任务验证通过,关联任务已完成: {task_code}"
|
||||
}
|
||||
|
||||
# 如果任务已失败
|
||||
elif task_status == TaskStatus.FAILED:
|
||||
logger.error(f"关联任务执行失败: TaskCode={task_code}, Status={task_status}")
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"关联任务执行失败: TaskCode={task_code}, Status={task_status}"
|
||||
}
|
||||
|
||||
elif task_status == TaskStatus.CANCELED:
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"任务被取消: TaskCode={task_code}, Status={task_status}"
|
||||
}
|
||||
# 任务还在运行中,继续等待
|
||||
else:
|
||||
logger.info(f"任务仍在执行中,继续等待: TaskCode={task_code}, Status={task_status}")
|
||||
await asyncio.sleep(1) # 等待1秒
|
||||
wait_count += 1
|
||||
|
||||
else:
|
||||
logger.warning(f"无法获取任务详情,继续等待: TaskCode={task_code}, TaskRecordId={latest_record.task_record_id}")
|
||||
await asyncio.sleep(1) # 等待1秒
|
||||
wait_count += 1
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"任务状态验证异常: {str(e)}")
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"任务状态验证异常: {str(e)}"
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -335,7 +335,7 @@ class EnhancedTaskScheduler:
|
||||
}
|
||||
|
||||
# 获取任务优先级
|
||||
priority = task_record.priority or PeriodicTaskStatus.PERIODIC # 如果为None或0,使用默认值1
|
||||
priority = task_record.priority
|
||||
|
||||
# 更新任务状态为排队中
|
||||
task_record.status = TaskStatus.QUEUED # 排队中
|
||||
@ -384,7 +384,7 @@ class EnhancedTaskScheduler:
|
||||
async def run_task(self, task_def_id: str, params: List[Dict[str, Any]] = None, parent_task_id: str = None,
|
||||
root_task_id: str = None, source_type: int = None, source_system: str = None,
|
||||
source_device: str = None, source_time: datetime = None, source_ip: str = None,
|
||||
source_client_info: str = None, tf_api_token: str = None, map_id: str = None) -> Dict[str, Any]:
|
||||
source_client_info: str = None, tf_api_token: str = None, map_id: str = None, set_priority: int=1) -> Dict[str, Any]:
|
||||
"""
|
||||
运行任务
|
||||
创建任务记录并提交到队列
|
||||
@ -402,6 +402,7 @@ class EnhancedTaskScheduler:
|
||||
source_client_info: 客户端设备信息
|
||||
tf_api_token: 主任务系统API Token
|
||||
map_id: 相关地图ID
|
||||
set_priority: 外部设置的调度任务优先级
|
||||
Returns:
|
||||
Dict[str, Any]: 运行结果
|
||||
"""
|
||||
@ -415,7 +416,7 @@ class EnhancedTaskScheduler:
|
||||
task_record = await self._create_task_record(
|
||||
task_def, params, parent_task_id, root_task_id,
|
||||
source_type, source_system, source_device, source_time,
|
||||
source_ip, source_client_info
|
||||
source_ip, source_client_info, set_priority
|
||||
)
|
||||
# 3. 同步到主任务系统
|
||||
sync_success, sync_message = await self._sync_task_to_main_system(task_record, task_def)
|
||||
@ -477,7 +478,7 @@ class EnhancedTaskScheduler:
|
||||
parent_task_id: str = None, root_task_id: str = None,
|
||||
source_type: int = None, source_system: str = None,
|
||||
source_device: str = None, source_time: datetime = None,
|
||||
source_ip: str = None, source_client_info: str = None) -> VWEDTaskRecord:
|
||||
source_ip: str = None, source_client_info: str = None, priority: int=1) -> VWEDTaskRecord:
|
||||
"""
|
||||
创建任务记录
|
||||
|
||||
@ -492,7 +493,7 @@ class EnhancedTaskScheduler:
|
||||
source_time: 任务下达时间
|
||||
source_ip: 下达任务的IP地址
|
||||
source_client_info: 客户端设备信息
|
||||
|
||||
priority: 任务优先级
|
||||
Returns:
|
||||
VWEDTaskRecord: 创建的任务记录
|
||||
"""
|
||||
@ -513,7 +514,8 @@ class EnhancedTaskScheduler:
|
||||
source_device=source_device,
|
||||
source_time=source_time,
|
||||
source_ip=source_ip,
|
||||
source_client_info=source_client_info
|
||||
source_client_info=source_client_info,
|
||||
priority=priority
|
||||
)
|
||||
|
||||
async with get_async_session() as session:
|
||||
@ -694,7 +696,119 @@ class EnhancedTaskScheduler:
|
||||
result["childTasksResults"] = child_tasks_results
|
||||
|
||||
return result
|
||||
|
||||
|
||||
async def set_task_error(self, task_record_id: str, failure_reason: str) -> Dict[str, Any]:
|
||||
"""
|
||||
取消任务
|
||||
|
||||
Args:
|
||||
task_record_id: 任务记录ID
|
||||
failure_reason : 错误原因
|
||||
Returns:
|
||||
Dict[str, Any]: 取消结果
|
||||
"""
|
||||
# 检查任务记录是否存在
|
||||
async with get_async_session() as session:
|
||||
result = await session.execute(
|
||||
select(VWEDTaskRecord).where(VWEDTaskRecord.id == task_record_id)
|
||||
)
|
||||
task_record = result.scalars().first()
|
||||
if not task_record:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "任务记录不存在",
|
||||
"taskRecordId": task_record_id
|
||||
}
|
||||
|
||||
# 如果任务已经结束,不能取消
|
||||
if task_record.status in [TaskStatus.COMPLETED, TaskStatus.FAILED, TaskStatus.CANCELED]: # 完成、失败、取消
|
||||
# if task_record.status in [TaskStatus.FAILED, TaskStatus.CANCELED]: # 完成、失败、取消
|
||||
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"任务已结束,状态码: {task_record.status}",
|
||||
"taskRecordId": task_record_id,
|
||||
"status": task_record.status
|
||||
}
|
||||
|
||||
# 查找所有正在运行的子任务
|
||||
child_tasks_query = await session.execute(
|
||||
select(VWEDTaskRecord).where(
|
||||
VWEDTaskRecord.parent_task_record_id == task_record_id,
|
||||
VWEDTaskRecord.status == TaskStatus.RUNNING # 执行中状态码
|
||||
)
|
||||
)
|
||||
child_tasks = child_tasks_query.scalars().all()
|
||||
|
||||
# 记录取消子任务的结果
|
||||
child_tasks_results = []
|
||||
|
||||
# 先设置错误所有子任务
|
||||
for child_task in child_tasks:
|
||||
logger.info(f"设置错误子任务: {child_task.id}, 父任务: {task_record_id}")
|
||||
|
||||
# 递归调用,取消子任务(可能有孙子任务)
|
||||
child_cancel_result = await self.set_task_error(child_task.id, failure_reason)
|
||||
|
||||
child_tasks_results.append({
|
||||
"taskRecordId": child_task.id,
|
||||
"success": child_cancel_result.get("success", False),
|
||||
"message": child_cancel_result.get("message", "未知结果")
|
||||
})
|
||||
|
||||
# 检查任务是否在执行中
|
||||
if task_record_id in self.running_tasks:
|
||||
executor = self.running_tasks[task_record_id]
|
||||
result = await executor.fail(failure_reason)
|
||||
# 从持久化管理器中移除
|
||||
await self.persistence_manager.remove_task(task_record_id)
|
||||
|
||||
# 添加子任务取消结果
|
||||
if child_tasks_results:
|
||||
result["childTasksResults"] = child_tasks_results
|
||||
|
||||
return result
|
||||
|
||||
# 如果不在执行中,直接更新状态
|
||||
async with get_async_session() as session:
|
||||
# 更新状态为取消
|
||||
task_record.status = TaskStatus.FAILED # 取消
|
||||
task_record.ended_reason = failure_reason
|
||||
task_record.ended_on = datetime.now()
|
||||
task_record.allow_restart_same_location = True # 设置为True,允许相同地址再次启动任务
|
||||
|
||||
await session.commit()
|
||||
|
||||
# 从持久化管理器中移除
|
||||
await self.persistence_manager.remove_task(task_record_id)
|
||||
|
||||
# 更新任务定义状态为普通状态(0)
|
||||
# 获取def_id
|
||||
def_id = task_record.def_id
|
||||
|
||||
# 使用新会话更新任务定义状态,避免事务冲突
|
||||
async with get_async_session() as new_session:
|
||||
await new_session.execute(
|
||||
update(VWEDTaskDef)
|
||||
.where(VWEDTaskDef.id == def_id)
|
||||
.values(status=TaskStatusEnum.PENDING)
|
||||
)
|
||||
await new_session.commit()
|
||||
logger.info(f"更新任务定义状态为普通状态: {def_id}")
|
||||
|
||||
result = {
|
||||
"success": True,
|
||||
"message": failure_reason,
|
||||
"taskRecordId": task_record_id
|
||||
}
|
||||
|
||||
# 添加子任务取消结果
|
||||
if child_tasks_results:
|
||||
result["childTasksResults"] = child_tasks_results
|
||||
|
||||
return result
|
||||
|
||||
|
||||
async def get_task_status(self, task_record_id: str) -> Dict[str, Any]:
|
||||
"""
|
||||
获取任务状态
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -42,6 +42,7 @@ class BlockExecutor:
|
||||
"""
|
||||
self.task_context = task_context
|
||||
self.is_canceled = False
|
||||
self.is_error = False
|
||||
|
||||
def cancel(self) -> None:
|
||||
"""
|
||||
@ -50,6 +51,17 @@ class BlockExecutor:
|
||||
self.is_canceled = True
|
||||
self.task_context.mark_canceled()
|
||||
|
||||
def fail(self, failure_reason: str = "任务设置为失败") -> None:
|
||||
"""
|
||||
将块执行设置为失败状态
|
||||
|
||||
Args:
|
||||
failure_reason: 失败原因
|
||||
"""
|
||||
self.is_canceled = True # 设置为取消状态以停止执行
|
||||
self.is_error = True
|
||||
self.task_context.mark_failed(failure_reason)
|
||||
|
||||
async def execute_block(self, block: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
执行任务块
|
||||
@ -60,13 +72,20 @@ class BlockExecutor:
|
||||
Returns:
|
||||
Dict[str, Any]: 执行结果
|
||||
"""
|
||||
# 检查是否已取消
|
||||
# 检查是否已取消或失败
|
||||
if self.is_canceled or self.task_context.is_task_canceled():
|
||||
return {
|
||||
"success": False,
|
||||
"message": "任务已被取消",
|
||||
"block_id": block.get("id", "unknown")
|
||||
}
|
||||
|
||||
if self.task_context.is_task_failed():
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"任务已失败: {self.task_context.failure_reason}",
|
||||
"block_id": block.get("id", "unknown")
|
||||
}
|
||||
if block is None:
|
||||
return {
|
||||
"success": False,
|
||||
@ -110,7 +129,10 @@ class BlockExecutor:
|
||||
# 检查结果
|
||||
if result.get("success", False):
|
||||
if self.is_canceled:
|
||||
await self._update_block_record(block_record_id, TaskBlockRecordStatus.CANCELED, "任务取消")
|
||||
if self.task_context.is_task_failed():
|
||||
await self._update_block_record(block_record_id, TaskBlockRecordStatus.FAILED, self.task_context.failure_reason or "任务失败")
|
||||
else:
|
||||
await self._update_block_record(block_record_id, TaskBlockRecordStatus.CANCELED, "任务取消")
|
||||
return result
|
||||
# 更新块状态为成功
|
||||
await self._update_block_record(
|
||||
@ -325,19 +347,34 @@ class BlockExecutor:
|
||||
if not result.get("success", False):
|
||||
if result.get("is_canceled", False):
|
||||
logger.warning(f"子块 {child_id} 执行失败: {result.get('message', '未知错误')}")
|
||||
# 更新块记录状态为取消
|
||||
await self._update_block_record(
|
||||
block_record_id,
|
||||
TaskBlockRecordStatus.CANCELED, # 取消
|
||||
result.get("message", "任务取消"),
|
||||
)
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"子块 {child_id} 执行失败: {result.get('message', '未知错误')}",
|
||||
"block_id": child_id,
|
||||
"is_canceled": True,
|
||||
"output": result.get("output", {})
|
||||
}
|
||||
# 检查是否是失败状态还是取消状态
|
||||
if self.task_context.is_task_failed():
|
||||
await self._update_block_record(
|
||||
block_record_id,
|
||||
TaskBlockRecordStatus.FAILED, # 失败
|
||||
self.task_context.failure_reason or "任务失败",
|
||||
)
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"子块 {child_id} 执行失败: {result.get('message', '未知错误')}",
|
||||
"block_id": child_id,
|
||||
"is_failed": True,
|
||||
"output": result.get("output", {})
|
||||
}
|
||||
else:
|
||||
# 更新块记录状态为取消
|
||||
await self._update_block_record(
|
||||
block_record_id,
|
||||
TaskBlockRecordStatus.CANCELED, # 取消
|
||||
result.get("message", "任务取消"),
|
||||
)
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"子块 {child_id} 执行失败: {result.get('message', '未知错误')}",
|
||||
"block_id": child_id,
|
||||
"is_canceled": True,
|
||||
"output": result.get("output", {})
|
||||
}
|
||||
else:
|
||||
logger.error(f"子块 {child_id} 执行失败: {result.get('message', '未知错误')}")
|
||||
# 更新块记录状态为失败
|
||||
@ -355,19 +392,35 @@ class BlockExecutor:
|
||||
}
|
||||
elif result.get("is_canceled", False) and result.get("success", False):
|
||||
# logger.warning(f"子块 {child_id} 执行失败: {result.get('message', '未知错误')}")
|
||||
await self._update_block_record(
|
||||
block_record_id,
|
||||
TaskBlockRecordStatus.CANCELED, # 取消
|
||||
result.get("message", "任务取消"),
|
||||
result.get("output", {}),
|
||||
)
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"子块 {child_id} 执行失败: {result.get('message', '未知错误')}",
|
||||
"block_id": child_id,
|
||||
"is_canceled": True,
|
||||
"output": result.get("output", {})
|
||||
}
|
||||
# 检查是否是失败状态还是取消状态
|
||||
if self.task_context.is_task_failed():
|
||||
await self._update_block_record(
|
||||
block_record_id,
|
||||
TaskBlockRecordStatus.FAILED, # 失败
|
||||
self.task_context.failure_reason or "任务失败",
|
||||
result.get("output", {}),
|
||||
)
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"子块 {child_id} 执行失败: {result.get('message', '未知错误')}",
|
||||
"block_id": child_id,
|
||||
"is_failed": True,
|
||||
"output": result.get("output", {})
|
||||
}
|
||||
else:
|
||||
await self._update_block_record(
|
||||
block_record_id,
|
||||
TaskBlockRecordStatus.CANCELED, # 取消
|
||||
result.get("message", "任务取消"),
|
||||
result.get("output", {}),
|
||||
)
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"子块 {child_id} 执行失败: {result.get('message', '未知错误')}",
|
||||
"block_id": child_id,
|
||||
"is_canceled": True,
|
||||
"output": result.get("output", {})
|
||||
}
|
||||
|
||||
# 更新块记录状态为成功
|
||||
await self._update_block_record(
|
||||
@ -598,13 +651,18 @@ class BlockExecutor:
|
||||
Dict[str, Any]: 解析后的参数
|
||||
"""
|
||||
def extract_items(input_str):
|
||||
"""将形如"[taskInputs.a,taskInputs.b]"的字符串转换为["taskInputs.a", "taskInputs.b"]"""
|
||||
# 使用正则表达式匹配中括号内的内容,然后分割
|
||||
match = re.search(r'\[(.*)\]', input_str)
|
||||
if match:
|
||||
# 获取括号内的内容并按逗号分割
|
||||
"""
|
||||
将形如"[taskInputs.a,taskInputs.b]"的字符串转换为["taskInputs.a", "taskInputs.b"]
|
||||
或将形如'{"key1":taskInputs.VALUE1, "key2":taskInputs.VALUE2}'的字符串转换为字典
|
||||
"""
|
||||
if not isinstance(input_str, str):
|
||||
return input_str
|
||||
|
||||
# 处理列表格式:[taskInputs.a,taskInputs.b]
|
||||
list_match = re.search(r'\[(.*)\]', input_str)
|
||||
if list_match:
|
||||
items = []
|
||||
for item in match.group(1).split(','):
|
||||
for item in list_match.group(1).split(','):
|
||||
try:
|
||||
json.loads(item)
|
||||
json_item = eval(item)
|
||||
@ -612,6 +670,64 @@ class BlockExecutor:
|
||||
json_item = item.strip()
|
||||
items.append(json_item)
|
||||
return items
|
||||
|
||||
# 处理字典格式:{"key1":taskInputs.VALUE1, "key2":taskInputs.VALUE2}
|
||||
dict_match = re.search(r'\{(.*)\}', input_str)
|
||||
if dict_match:
|
||||
try:
|
||||
# 尝试使用json.loads解析标准JSON格式
|
||||
return json.loads(input_str)
|
||||
except json.JSONDecodeError:
|
||||
# 如果标准JSON解析失败,手动解析包含表达式的字典
|
||||
dict_content = dict_match.group(1).strip()
|
||||
if not dict_content:
|
||||
return {}
|
||||
|
||||
result_dict = {}
|
||||
# 分割键值对,考虑嵌套的情况
|
||||
pairs = []
|
||||
current_pair = ""
|
||||
brace_count = 0
|
||||
quote_count = 0
|
||||
|
||||
for char in dict_content:
|
||||
if char == '"' and brace_count == 0:
|
||||
quote_count = (quote_count + 1) % 2
|
||||
elif char == '{' and quote_count == 0:
|
||||
brace_count += 1
|
||||
elif char == '}' and quote_count == 0:
|
||||
brace_count -= 1
|
||||
elif char == ',' and brace_count == 0 and quote_count == 0:
|
||||
pairs.append(current_pair.strip())
|
||||
current_pair = ""
|
||||
continue
|
||||
current_pair += char
|
||||
|
||||
if current_pair.strip():
|
||||
pairs.append(current_pair.strip())
|
||||
|
||||
# 解析每个键值对
|
||||
for pair in pairs:
|
||||
if ':' in pair:
|
||||
key_part, value_part = pair.split(':', 1)
|
||||
# 清理键名(去掉引号)
|
||||
key = key_part.strip().strip('"\'')
|
||||
# 清理值(去掉多余空格)
|
||||
value = value_part.strip()
|
||||
|
||||
# 如果值是字符串字面量(被引号包围),去掉引号
|
||||
if (value.startswith('"') and value.endswith('"')) or \
|
||||
(value.startswith("'") and value.endswith("'")):
|
||||
result_dict[key] = value[1:-1]
|
||||
else:
|
||||
# 保持表达式原样,后续会被_parse_expression处理
|
||||
result_dict[key] = value
|
||||
|
||||
return result_dict
|
||||
except Exception as e:
|
||||
logger.warning(f"解析字典格式失败: {input_str}, 错误: {str(e)}")
|
||||
return input_str
|
||||
|
||||
return input_str
|
||||
|
||||
if not params:
|
||||
@ -629,10 +745,20 @@ class BlockExecutor:
|
||||
elif param_type == TaskInputParamType.EXPRESSION:
|
||||
# 表达式,需要从上下文中获取值
|
||||
param_value = extract_items(param_value)
|
||||
# print("param_value:::::::::::====", param_value, type(param_value), "==========================")
|
||||
if isinstance(param_value, list):
|
||||
parsed_params[key] = [await self._parse_expression(key, c) for c in param_value]
|
||||
elif isinstance(param_value, dict):
|
||||
# 处理字典类型,对每个值进行表达式解析
|
||||
parsed_dict = {}
|
||||
for dict_key, dict_value in param_value.items():
|
||||
if isinstance(dict_value, str):
|
||||
parsed_dict[dict_key] = await self._parse_expression(f"{key}.{dict_key}", dict_value)
|
||||
else:
|
||||
parsed_dict[dict_key] = dict_value
|
||||
parsed_params[key] = parsed_dict
|
||||
else:
|
||||
express_result = await self._parse_expression(key, param_value)
|
||||
express_result = await self._parse_expression(key, param_value)
|
||||
parsed_params[key] = express_result
|
||||
elif param_type == TaskInputParamType.JSON:
|
||||
# JSON值,需要解析
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -505,8 +505,9 @@ class StringToJsonObjectBlockHandler(BlockHandler):
|
||||
return result
|
||||
|
||||
# 转换为JSON对象
|
||||
# print(":json_objectjson_object", convert_string, type(convert_string), "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[")
|
||||
|
||||
json_object = json.loads(convert_string)
|
||||
|
||||
# 设置输出参数
|
||||
output = {
|
||||
"convertObject": json_object
|
||||
|
Binary file not shown.
@ -59,7 +59,7 @@ class RobotBlockName(StrEnum):
|
||||
|
||||
class ScriptBlockName(StrEnum):
|
||||
"""脚本块名称枚举"""
|
||||
SET_TASK_VARIABLES = "SetTaskVariablesBp" # 设置任务变量
|
||||
SET_TASK_VARIABLES = "ScriptVariablesBp" # 设置任务变量
|
||||
SCRIPT = "ScriptBp" # 脚本
|
||||
|
||||
class StorageBlockName(StrEnum):
|
||||
|
@ -193,10 +193,10 @@ class RobotBlockHandler(BlockHandler):
|
||||
"""调用外部API的通用方法"""
|
||||
return await call_robot_api(api_name, params)
|
||||
|
||||
async def _validate_and_convert_key_route(self, key_route: str, map_id: str, flag: bool = False) -> tuple[bool, str, str]:
|
||||
async def _validate_and_convert_key_route(self, key_route: str, map_id: str, flag: bool = False) -> tuple[bool, str, str, bool]:
|
||||
"""
|
||||
校验并转换keyRoute参数
|
||||
|
||||
|
||||
Args:
|
||||
key_route: 传入的关键路径,可能是动作点名称或库位名称
|
||||
map_id: 地图ID,用于校验场景ID
|
||||
@ -225,7 +225,7 @@ class RobotBlockHandler(BlockHandler):
|
||||
|
||||
if operate_point:
|
||||
logger.info(f"keyRoute '{key_route}' 识别为动作点,场景ID: {map_id}")
|
||||
return True, key_route, ""
|
||||
return True, key_route, "", True
|
||||
|
||||
# 如果不是动作点,检查是否是库位(operate_point_layer表的layer_name字段)
|
||||
stmt = select(OperatePointLayer).where(
|
||||
@ -239,18 +239,18 @@ class RobotBlockHandler(BlockHandler):
|
||||
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, ""
|
||||
return True, station_name, "", False
|
||||
if flag:
|
||||
return True, key_route, ""
|
||||
return True, key_route, "", True
|
||||
# 都不匹配,返回错误
|
||||
error_msg = f"keyRoute '{key_route}' 在场景 {map_id} 中既不是有效的动作点名称也不是有效的库位名称"
|
||||
logger.error(error_msg)
|
||||
return False, "", error_msg
|
||||
return False, "", error_msg, True
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"校验keyRoute时发生异常: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
return False, "", error_msg
|
||||
return False, "", error_msg, True
|
||||
|
||||
def _analyze_affected_blocks(self, block: Dict[str, Any], current_block_id: str, current_block_name: str) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
@ -456,7 +456,7 @@ class AgvOperationBlockHandler(RobotBlockHandler):
|
||||
|
||||
# 获取关键参数用于验证
|
||||
target_site_label = input_params.get("targetSiteLabel")
|
||||
script_name = input_params.get("task")
|
||||
script_name = input_params.get("task", "Wait")
|
||||
map_id = context.map_id
|
||||
param = input_params.get("param", "")
|
||||
inspired_unique = input_params.get("inspired_unique", "")
|
||||
@ -482,15 +482,15 @@ class AgvOperationBlockHandler(RobotBlockHandler):
|
||||
|
||||
# 校验并转换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)
|
||||
is_valid, validated_station_name, error_msg, is_type = 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
|
||||
# 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")
|
||||
@ -516,32 +516,43 @@ class AgvOperationBlockHandler(RobotBlockHandler):
|
||||
return result
|
||||
# context.task_record_id
|
||||
from services.sync_service import add_action
|
||||
result = await add_action(
|
||||
task_id=agv_task_id,
|
||||
station_name=validated_station_name, # 使用校验后的站点名称
|
||||
action=script_name,
|
||||
token=context.token,
|
||||
param=params
|
||||
)
|
||||
if is_type:
|
||||
result = await add_action(
|
||||
task_id=agv_task_id,
|
||||
station_name=validated_station_name, # 使用校验后的站点名称
|
||||
action=script_name,
|
||||
token=context.token,
|
||||
param=params,
|
||||
store="",
|
||||
)
|
||||
else:
|
||||
result = await add_action(
|
||||
task_id=agv_task_id,
|
||||
station_name=validated_station_name, # 使用校验后的站点名称
|
||||
action=script_name,
|
||||
token=context.token,
|
||||
param=params,
|
||||
store=target_site_label)
|
||||
|
||||
# 调用外部API执行机器人通用动作
|
||||
if result.get("success", False):
|
||||
# 获取任务ID
|
||||
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"机器人通用动作取消,目标站点: {validated_station_name} 执行动作: {script_name}", "is_canceled": True}
|
||||
return {"success": True, "message": f"机器人通用动作取消,目标站点: {validated_station_name} 执行动作: {script_name},块id:{current_block_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"机器人通用动作成功,目标站点: {validated_station_name} 执行动作: {script_name}"
|
||||
result["message"] = f"机器人通用动作成功,目标站点: {validated_station_name} 执行动作: {script_name},块id:{current_block_name}"
|
||||
elif task_block_status == 4:
|
||||
result["message"] = f"机器人通用动作失败,目标站点: {validated_station_name} 执行动作: {script_name}:{task_block_result.get('message', '')}"
|
||||
result["message"] = f"机器人通用动作失败,目标站点: {validated_station_name} 执行动作: {script_name}:{task_block_result.get('message', '')},块id:{current_block_name}"
|
||||
result["success"] = False
|
||||
elif task_block_status == 5:
|
||||
result["message"] = f"机器人通用动作终止,目标站点: {validated_station_name} 执行动作: {script_name}"
|
||||
result["message"] = f"机器人通用动作终止,目标站点: {validated_station_name} 执行动作: {script_name},块id:{current_block_name}"
|
||||
else:
|
||||
result["message"] = f"机器人通用动作失败: {result.get('message', '未知错误')}"
|
||||
result["message"] = f"机器人通用动作失败: {result.get('message', '未知错误')},块id:{current_block_name}"
|
||||
|
||||
# 记录执行结果
|
||||
await self._record_task_log(block, result, context)
|
||||
@ -575,7 +586,7 @@ class VehicleStationBlockHandler(RobotBlockHandler):
|
||||
if not vehicle:
|
||||
result = {
|
||||
"success": False,
|
||||
"message": "指定机器人不能为空"
|
||||
"message": "指定机器人不能为空 "
|
||||
}
|
||||
await self._record_task_log(block, result, context)
|
||||
return result
|
||||
@ -738,7 +749,9 @@ class SelectAgvBlockHandler(RobotBlockHandler):
|
||||
amr_name = input_params.get("vehicle", "")
|
||||
amr_group_name = input_params.get("group", "")
|
||||
map_id = context.map_id
|
||||
|
||||
# 获取当前块信息
|
||||
current_block_id = block.get("id", "unknown")
|
||||
current_block_name = block.get("name", f"b{current_block_id}")
|
||||
# 确保priority是整数类型,默认为1
|
||||
try:
|
||||
priority = int(priority) if priority and str(priority).strip() else 1
|
||||
@ -753,14 +766,14 @@ class SelectAgvBlockHandler(RobotBlockHandler):
|
||||
if not key_route:
|
||||
result = {
|
||||
"success": False,
|
||||
"message": "关键路径不能为空"
|
||||
"message": f"关键路径不能为空, 块id:{current_block_name}"
|
||||
}
|
||||
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, flag=True)
|
||||
is_valid, key_station_name, error_msg, is_type = await self._validate_and_convert_key_route(key_route, map_id, flag=True)
|
||||
# print(f"output_params >>>>>>>>>>>>>>>>>>>>>>>>>>>> key_station_name: {key_station_name}, error_msg: {error_msg}")
|
||||
if not is_valid:
|
||||
result = {
|
||||
@ -780,7 +793,8 @@ class SelectAgvBlockHandler(RobotBlockHandler):
|
||||
token=context.token,
|
||||
priority=priority
|
||||
)
|
||||
|
||||
# logger.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>选择执行机器人::::::"+str(key_station_name)+"::::::::::::::::::::::::")
|
||||
# logger.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>优先级:::::::::::::"+str(priority)+":::::::::::::::::::::::::")
|
||||
if result.get("success", False):
|
||||
# 获取任务块ID
|
||||
task_block_id = result.get("result", {}).get("id", "")
|
||||
@ -788,7 +802,7 @@ class SelectAgvBlockHandler(RobotBlockHandler):
|
||||
if not task_block_id:
|
||||
result = {
|
||||
"success": False,
|
||||
"message": "创建选择AMR任务成功,但未返回任务块ID"
|
||||
"message": f"创建选择AMR任务成功,但未返回任务块ID, 块id:{current_block_name}"
|
||||
}
|
||||
await self._record_task_log(block, result, context)
|
||||
return result
|
||||
@ -801,12 +815,13 @@ class SelectAgvBlockHandler(RobotBlockHandler):
|
||||
token=context.token,
|
||||
context=context
|
||||
)
|
||||
|
||||
if task_block_result.get("is_canceled", False):
|
||||
return {"success": True, "message": f"选择执行机器人取消", "is_canceled": True}
|
||||
return {"success": True, "message": f"选择执行机器人取消, 块id:{current_block_name}", "is_canceled": True}
|
||||
if not task_block_result or not task_block_result.get("success", False):
|
||||
result = {
|
||||
"success": False,
|
||||
"message": f"等待AMR选择结果失败或超时,任务块ID: {task_block_id}"
|
||||
"message": task_block_result.get("message", "")
|
||||
}
|
||||
await self._record_task_log(block, result, context)
|
||||
return result
|
||||
@ -817,13 +832,13 @@ class SelectAgvBlockHandler(RobotBlockHandler):
|
||||
if not agv_id:
|
||||
result = {
|
||||
"success": False,
|
||||
"message": f"未能获取到选择的AMR ID,任务块ID: {task_block_id}"
|
||||
"message": f"未能获取到选择的AMR ID,任务块ID: {task_block_id}, 块id:{current_block_name}"
|
||||
}
|
||||
await self._record_task_log(block, result, context)
|
||||
return result
|
||||
results = {
|
||||
"success": True,
|
||||
"message": f"选择机器人块成功",
|
||||
"message": f"选择机器人块成功, 块id:{current_block_name}",
|
||||
"output": {
|
||||
"selectedAgvId": amr_name,
|
||||
}
|
||||
@ -862,13 +877,13 @@ class SelectAgvBlockHandler(RobotBlockHandler):
|
||||
tag = input_params.get("tag", "")
|
||||
|
||||
if vehicle:
|
||||
result["message"] = f"指定机器人 {vehicle} 选择成功"
|
||||
result["message"] = f"指定机器人 {vehicle} 选择成功, 块id:{current_block_name}"
|
||||
elif group:
|
||||
result["message"] = f"从机器人组 {group} 选择机器人成功: {amr_name}"
|
||||
result["message"] = f"从机器人组 {group} 选择机器人成功: {amr_name}, 块id:{current_block_name}"
|
||||
elif tag:
|
||||
result["message"] = f"根据标签 {tag} 选择机器人成功: {amr_name}"
|
||||
result["message"] = f"根据标签 {tag} 选择机器人成功: {amr_name}, 块id:{current_block_name}"
|
||||
else:
|
||||
result["message"] = f"选择执行机器人成功: {amr_name}"
|
||||
result["message"] = f"选择执行机器人成功: {amr_name}, 块id:{current_block_name}"
|
||||
|
||||
# 打印选择结果和影响的块
|
||||
logger.info(f"选择机器人块 {current_block_name}(ID:{current_block_id}) 选择的机器人: {amr_name}")
|
||||
@ -905,7 +920,7 @@ class SelectAgvBlockHandler(RobotBlockHandler):
|
||||
|
||||
# 如果原始消息中没有包含子块执行信息,添加这部分信息
|
||||
if "子块" not in result["message"]:
|
||||
result["message"] = f"{result['message']},子块执行成功"
|
||||
result["message"] = f"{result['message']},子块执行成功, 块id:{current_block_name}"
|
||||
else:
|
||||
# 子块执行失败,根据失败的子块更新消息
|
||||
logger.error(f"选择机器人块 {current_block_name} 的子块执行失败: {loop_result.get('message')}")
|
||||
@ -916,7 +931,7 @@ class SelectAgvBlockHandler(RobotBlockHandler):
|
||||
|
||||
result = {
|
||||
"success": False,
|
||||
"message": f"选择执行机器人成功,但子块执行失败: {error_msg},失败块ID: {failed_block_id}",
|
||||
"message": f"选择执行机器人成功 选择小车:{amr_name},但子块执行失败: {error_msg},失败块ID: {failed_block_id}",
|
||||
"output": {
|
||||
"selectedAgvId": amr_name,
|
||||
"childrenExecuted": False,
|
||||
@ -929,7 +944,7 @@ class SelectAgvBlockHandler(RobotBlockHandler):
|
||||
token=context.token
|
||||
)
|
||||
else:
|
||||
result["message"] = f"选择执行机器人失败: {result.get('message', '未知错误')}"
|
||||
result["message"] = f"选择执行机器人失败: {result.get('message', '未知错误')}, 块id:{current_block_name}"
|
||||
return result
|
||||
except Exception as e:
|
||||
result = {
|
||||
|
@ -45,11 +45,19 @@ class SetTaskVariablesBlockHandler(BlockHandler):
|
||||
return result
|
||||
|
||||
# 调用脚本执行方法
|
||||
logger.info("进入执行脚本功能块")
|
||||
exec_result = await self._execute_script(function_name, function_args, context)
|
||||
|
||||
# print(exec_result, "=------------------=-")
|
||||
exec_result_output = exec_result.get("output")
|
||||
if exec_result_output:
|
||||
save_result = exec_result_output.get("result")
|
||||
context.set_variable(function_name, save_result)
|
||||
# 如果执行成功,将变量保存到任务记录
|
||||
if exec_result.get("success", False):
|
||||
await self._save_variables_to_database(context)
|
||||
# if exec_result.get("success", False):
|
||||
# save_result = {function_name: exec_result}
|
||||
|
||||
await self._save_variables_to_database(context)
|
||||
|
||||
|
||||
# 记录执行结果
|
||||
await self._record_task_log(block, exec_result, context)
|
||||
@ -81,7 +89,7 @@ class SetTaskVariablesBlockHandler(BlockHandler):
|
||||
Dict[str, Any]: 执行结果
|
||||
"""
|
||||
# 固定加载scripts/user_save/test1.py文件
|
||||
script_file = r"D:\jsw_code\project\VWED_task\scripts\user_save\test1.py"
|
||||
script_file = "scripts/user_save/test1.py"
|
||||
logger.info(f"正在加载脚本文件: {script_file}")
|
||||
|
||||
try:
|
||||
@ -133,10 +141,20 @@ class SetTaskVariablesBlockHandler(BlockHandler):
|
||||
|
||||
# 调用函数
|
||||
logger.info(f"调用函数 {function_name} 参数: args={args}, kwargs={kwargs}")
|
||||
result_value = func(*args, **kwargs)
|
||||
|
||||
# 特殊处理:如果函数只接受一个参数且传入的是字典,直接传递字典
|
||||
import inspect
|
||||
sig = inspect.signature(func)
|
||||
params = list(sig.parameters.keys())
|
||||
|
||||
if len(params) == 1 and isinstance(function_args, dict) and not args:
|
||||
# 函数只有一个参数且传入的是字典,直接传递
|
||||
result_value = func(function_args)
|
||||
else:
|
||||
# 正常的参数传递
|
||||
result_value = func(*args, **kwargs)
|
||||
|
||||
# 检查是否是异步函数并等待结果
|
||||
import inspect
|
||||
if inspect.iscoroutine(result_value):
|
||||
import asyncio
|
||||
result_value = await result_value
|
||||
@ -194,7 +212,6 @@ class SetTaskVariablesBlockHandler(BlockHandler):
|
||||
|
||||
# 将任务变量转换为JSON字符串
|
||||
variables_json = json.dumps(context.variables, ensure_ascii=False)
|
||||
|
||||
# 更新数据库记录
|
||||
async with get_async_session() as session:
|
||||
stmt = update(VWEDTaskRecord).where(VWEDTaskRecord.id == task_record_id).values(
|
||||
@ -268,7 +285,7 @@ class ScriptBlockHandler(BlockHandler):
|
||||
Dict[str, Any]: 执行结果
|
||||
"""
|
||||
# 固定加载scripts/user_save/test1.py文件
|
||||
script_file = r"D:\jsw_code\project\VWED_task\scripts\user_save\test1.py"
|
||||
script_file = "scripts/user_save/test1.py"
|
||||
logger.info(f"正在加载脚本文件: {script_file}")
|
||||
|
||||
try:
|
||||
@ -327,10 +344,20 @@ class ScriptBlockHandler(BlockHandler):
|
||||
|
||||
# 调用函数
|
||||
logger.info(f"调用函数 {function_name} 参数: args={args}, kwargs={kwargs}")
|
||||
result_value = func(*args, **kwargs)
|
||||
|
||||
# 特殊处理:如果函数只接受一个参数且传入的是字典,直接传递字典
|
||||
import inspect
|
||||
sig = inspect.signature(func)
|
||||
params = list(sig.parameters.keys())
|
||||
|
||||
if len(params) == 1 and isinstance(function_args, dict) and not args:
|
||||
# 函数只有一个参数且传入的是字典,直接传递
|
||||
result_value = func(function_args)
|
||||
else:
|
||||
# 正常的参数传递
|
||||
result_value = func(*args, **kwargs)
|
||||
|
||||
# 检查是否是异步函数并等待结果
|
||||
import inspect
|
||||
if inspect.iscoroutine(result_value):
|
||||
import asyncio
|
||||
result_value = await result_value
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -9,6 +9,7 @@
|
||||
import json
|
||||
import logging
|
||||
import uuid
|
||||
import asyncio
|
||||
from typing import Dict, Any, Optional
|
||||
from datetime import datetime
|
||||
from services.execution.task_context import TaskContext
|
||||
@ -43,7 +44,9 @@ class CacheDataBlockHandler(BlockHandler):
|
||||
"success": True,
|
||||
"message": f"数据缓存成功: {key}"
|
||||
}
|
||||
|
||||
# context.set_variable("value", value)
|
||||
# context.set_block_output(block.get("name"), {"value": value})
|
||||
# print("context::::::::", context.block_outputs)
|
||||
# 记录执行结果
|
||||
await self._record_task_log(block, result, context)
|
||||
|
||||
@ -93,6 +96,7 @@ class CacheDataBlockHandler(BlockHandler):
|
||||
|
||||
await session.commit()
|
||||
|
||||
|
||||
@register_handler(TaskBlockName.CLEAR_CACHE_DATA)
|
||||
class ClearCacheDataBlockHandler(BlockHandler):
|
||||
"""清除缓存数据块处理器"""
|
||||
@ -189,7 +193,7 @@ class GetCacheDataBlockHandler(BlockHandler):
|
||||
value = await self._get_cache_data(key)
|
||||
|
||||
# 将获取的值设置到上下文变量中
|
||||
context.set_block_output(block.get("name"), {"cache_data": value})
|
||||
context.set_block_output(block.get("name"), {"value": value})
|
||||
|
||||
result = {
|
||||
"success": True,
|
||||
@ -426,4 +430,25 @@ class TaskStateBlockHandler(BlockHandler):
|
||||
)
|
||||
await session.execute(stmt)
|
||||
await session.commit()
|
||||
|
||||
# 异步调用设置任务描述接口,同步状态到系统
|
||||
asyncio.create_task(self._sync_task_description_to_system(task_record_id, state_msg))
|
||||
|
||||
async def _sync_task_description_to_system(self, task_record_id: str, description: str) -> None:
|
||||
"""异步同步任务描述到系统"""
|
||||
try:
|
||||
from services.sync_service import set_task_description
|
||||
from config.tf_api_config import TF_API_TOKEN
|
||||
|
||||
# 异步调用设置任务描述接口
|
||||
result = await set_task_description(task_record_id, description, TF_API_TOKEN)
|
||||
|
||||
if result and result.get("success"):
|
||||
logger.info(f"成功同步任务描述到系统: {task_record_id}")
|
||||
else:
|
||||
logger.warning(f"同步任务描述到系统失败: {result.get('message') if result else '接口调用失败'}")
|
||||
|
||||
except Exception as e:
|
||||
# 记录异常但不抛出,避免影响主流程
|
||||
logger.error(f"同步任务描述到系统异常: {str(e)}")
|
||||
|
||||
|
@ -50,6 +50,8 @@ class TaskContext:
|
||||
self.error = None # 错误信息
|
||||
self.start_time = datetime.now() # 开始时间
|
||||
self.is_canceled = False # 是否被取消
|
||||
self.is_failed = False # 是否失败
|
||||
self.failure_reason = None # 失败原因
|
||||
self.current_block_id = None # 当前正在执行的块ID
|
||||
self.current_block_name = None # 当前正在执行的块名称
|
||||
self.skip_to_component_id = None # 需要跳转到的块ID
|
||||
@ -123,7 +125,6 @@ class TaskContext:
|
||||
|
||||
# 设置变量
|
||||
self.variables[name] = value
|
||||
|
||||
# 标记需要同步变量
|
||||
self._variables_need_sync = True
|
||||
|
||||
@ -237,6 +238,16 @@ class TaskContext:
|
||||
"""标记任务为已取消状态"""
|
||||
self.is_canceled = True
|
||||
|
||||
def mark_failed(self, failure_reason: str) -> None:
|
||||
"""
|
||||
标记任务为失败状态
|
||||
|
||||
Args:
|
||||
failure_reason: 失败原因
|
||||
"""
|
||||
self.is_failed = True
|
||||
self.failure_reason = failure_reason
|
||||
|
||||
def is_task_canceled(self) -> bool:
|
||||
"""
|
||||
检查任务是否被取消
|
||||
@ -246,6 +257,15 @@ class TaskContext:
|
||||
"""
|
||||
return self.is_canceled
|
||||
|
||||
def is_task_failed(self) -> bool:
|
||||
"""
|
||||
检查任务是否失败
|
||||
|
||||
Returns:
|
||||
bool: 是否失败
|
||||
"""
|
||||
return self.is_failed
|
||||
|
||||
def get_execution_time(self) -> int:
|
||||
"""
|
||||
获取任务已执行时间(毫秒)
|
||||
@ -301,6 +321,8 @@ class TaskContext:
|
||||
"startTime": self.start_time.isoformat(),
|
||||
"executionTime": self.get_execution_time(),
|
||||
"isCanceled": self.is_canceled,
|
||||
"isFailed": self.is_failed,
|
||||
"failureReason": self.failure_reason,
|
||||
"skipToComponentId": self.skip_to_component_id
|
||||
}
|
||||
|
||||
|
@ -49,6 +49,7 @@ class TaskExecutor:
|
||||
self.error_message = None
|
||||
self.timeout = 3600*10 # 默认超时时间:10小时(秒)
|
||||
self.is_canceled = False
|
||||
self.is_error = False
|
||||
|
||||
def set_timeout(self, timeout_seconds: int) -> None:
|
||||
"""
|
||||
@ -199,17 +200,21 @@ class TaskExecutor:
|
||||
|
||||
# 开始计时
|
||||
start_time = datetime.now()
|
||||
|
||||
# self.
|
||||
# 获取根块
|
||||
task_detail = json.loads(self.task_def.detail)
|
||||
root_block = task_detail.get("rootBlock", {})
|
||||
release_sites = self.task_def.release_sites
|
||||
|
||||
# 更新任务状态为执行中
|
||||
async with get_async_session() as session:
|
||||
await self._update_task_status(session, TaskStatus.RUNNING, "任务执行中", task_detail=self.task_def.detail)
|
||||
# 执行根块,增加超时控制
|
||||
site_list = []
|
||||
try:
|
||||
# 直接执行块,不添加超时控制
|
||||
result = await self.block_executor.execute_block(root_block)
|
||||
site_list = [v.get("siteId") for k, v in dict(self.task_context.block_outputs).items()]
|
||||
except Exception as e:
|
||||
logger.error(f"任务 {self.task_record_id} 执行异常: {str(e)}")
|
||||
# 更新任务状态为失败
|
||||
@ -220,6 +225,12 @@ class TaskExecutor:
|
||||
f"任务执行异常: {str(e)}",
|
||||
task_detail=self.task_def.detail
|
||||
)
|
||||
|
||||
# 根据release_sites配置决定是否释放库位
|
||||
if release_sites == 1 and site_list:
|
||||
logger.info(f"具备自动释放库位锁定条件!释放一下库位:{site_list}")
|
||||
await self._release_storage_locations(site_list)
|
||||
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"任务执行异常: {str(e)}",
|
||||
@ -261,6 +272,10 @@ class TaskExecutor:
|
||||
executor_time,
|
||||
task_detail=self.task_def.detail
|
||||
)
|
||||
# 根据release_sites配置决定是否释放库位
|
||||
if release_sites == 1 and site_list:
|
||||
logger.info(f"具备自动释放库位锁定条件!释放一下库位:{site_list}")
|
||||
await self._release_storage_locations(site_list)
|
||||
return {
|
||||
"success": True,
|
||||
"message": "任务取消",
|
||||
@ -275,6 +290,11 @@ class TaskExecutor:
|
||||
else:
|
||||
# 更新任务状态为失败
|
||||
await self._update_task_status(session, TaskStatus.FAILED, error_msg, executor_time, task_detail=self.task_def.detail)
|
||||
|
||||
# 根据release_sites配置决定是否释放库位
|
||||
if release_sites == 1 and site_list:
|
||||
logger.info(f"具备自动释放库位锁定条件!释放一下库位:{site_list}")
|
||||
await self._release_storage_locations(site_list)
|
||||
|
||||
# 返回失败结果
|
||||
return {
|
||||
@ -293,6 +313,18 @@ class TaskExecutor:
|
||||
async with get_async_session() as session:
|
||||
await self._update_task_status(session, TaskStatus.FAILED, f"任务执行异常: {str(e)}", task_detail=self.task_def.detail)
|
||||
|
||||
# 如果有库位信息需要释放
|
||||
site_list = []
|
||||
try:
|
||||
site_list = [v.get("siteId") for k, v in dict(self.task_context.block_outputs).items() if v.get("siteId")]
|
||||
except:
|
||||
pass
|
||||
|
||||
# 根据release_sites配置决定是否释放库位
|
||||
if hasattr(self.task_def, 'release_sites') and self.task_def.release_sites == 1 and site_list:
|
||||
logger.info(f"具备自动释放库位锁定条件!释放一下库位:{site_list}")
|
||||
await self._release_storage_locations(site_list)
|
||||
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"任务执行异常: {str(e)}",
|
||||
@ -332,6 +364,110 @@ class TaskExecutor:
|
||||
"taskRecordId": self.task_record_id
|
||||
}
|
||||
|
||||
async def fail(self, failure_reason: str = "任务设置为失败") -> Dict[str, Any]:
|
||||
"""
|
||||
将任务设置为失败状态,并将失败原因设置到所有块的详情内
|
||||
|
||||
Args:
|
||||
failure_reason: 失败原因
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: 设置失败结果
|
||||
"""
|
||||
if not self.is_running:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "任务未在执行中,无法设置为失败",
|
||||
"taskRecordId": self.task_record_id
|
||||
}
|
||||
|
||||
# 更新任务状态为失败
|
||||
async with get_async_session() as session:
|
||||
await self._update_task_status(session, TaskStatus.FAILED, failure_reason, task_detail=self.task_def.detail)
|
||||
|
||||
# 根据release_sites配置决定是否释放库位
|
||||
if hasattr(self.task_def, 'release_sites') and self.task_def.release_sites == 1:
|
||||
try:
|
||||
site_list = [v.get("siteId") for k, v in dict(self.task_context.block_outputs).items() if v.get("siteId")]
|
||||
if site_list:
|
||||
await self._release_storage_locations(site_list)
|
||||
except Exception as e:
|
||||
logger.error(f"任务 {self.task_record_id} 释放库位时出错: {str(e)}")
|
||||
|
||||
# 尝试将块执行器设置为失败
|
||||
if self.block_executor:
|
||||
self.block_executor.fail(failure_reason)
|
||||
|
||||
self.is_running = False
|
||||
self.is_error = True
|
||||
|
||||
self.error_message = failure_reason
|
||||
return {
|
||||
"success": True,
|
||||
"message": "任务已设置为失败",
|
||||
"taskRecordId": self.task_record_id,
|
||||
"failureReason": failure_reason
|
||||
}
|
||||
|
||||
async def _release_storage_locations(self, site_list: List[str]) -> None:
|
||||
"""
|
||||
释放任务执行过程中选择的库位
|
||||
|
||||
Args:
|
||||
site_list: 需要释放的库位ID列表
|
||||
"""
|
||||
if not site_list:
|
||||
logger.debug(f"任务 {self.task_record_id} 没有需要释放的库位")
|
||||
return
|
||||
|
||||
try:
|
||||
from data.session import get_db
|
||||
from services.operate_point_service import OperatePointService
|
||||
from routes.model.operate_point_model import StorageLocationStatusUpdateRequest, StorageLocationActionEnum
|
||||
|
||||
# 获取数据库会话
|
||||
db = next(get_db())
|
||||
|
||||
success_count = 0
|
||||
failed_count = 0
|
||||
|
||||
# 逐个释放库位
|
||||
for site_id in site_list:
|
||||
if not site_id:
|
||||
continue
|
||||
|
||||
try:
|
||||
# 创建释放库位请求
|
||||
request = StorageLocationStatusUpdateRequest(
|
||||
layer_name=site_id,
|
||||
action=StorageLocationActionEnum.UNLOCK,
|
||||
reason=f"任务失败自动释放库位 - 任务ID: {self.task_record_id}"
|
||||
)
|
||||
|
||||
# 调用服务释放库位
|
||||
result = OperatePointService.update_storage_location_status(db=db, request=request)
|
||||
|
||||
if result.success:
|
||||
success_count += 1
|
||||
logger.info(f"任务 {self.task_record_id} 成功释放库位: {site_id}")
|
||||
else:
|
||||
failed_count += 1
|
||||
logger.warning(f"任务 {self.task_record_id} 释放库位失败: {site_id}, 原因: {result.message}")
|
||||
|
||||
except Exception as e:
|
||||
failed_count += 1
|
||||
logger.error(f"任务 {self.task_record_id} 释放库位异常: {site_id}, 错误: {str(e)}")
|
||||
|
||||
logger.info(f"任务 {self.task_record_id} 库位释放完成: 成功 {success_count} 个, 失败 {failed_count} 个")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"任务 {self.task_record_id} 批量释放库位异常: {str(e)}")
|
||||
finally:
|
||||
try:
|
||||
db.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
async def _update_task_status(
|
||||
self,
|
||||
session: AsyncSession,
|
||||
@ -348,21 +484,10 @@ class TaskExecutor:
|
||||
status: 任务状态码
|
||||
reason: 状态原因
|
||||
executor_time: 执行时间(毫秒)
|
||||
task_detail: 任务详情
|
||||
"""
|
||||
try:
|
||||
from services.sync_service import set_task_in_progress, set_task_completed, set_task_terminated, set_task_failed
|
||||
if not self.check_task_def_select_agv(task_detail):
|
||||
if status == TaskStatus.RUNNING:
|
||||
await set_task_in_progress(self.task_record_id, self.task_def.user_token)
|
||||
elif status == TaskStatus.COMPLETED:
|
||||
await set_task_completed(self.task_record_id, self.task_def.user_token)
|
||||
elif status == TaskStatus.FAILED:
|
||||
await set_task_failed(self.task_record_id, self.task_def.user_token)
|
||||
elif status == TaskStatus.CANCELED:
|
||||
await set_task_terminated(self.task_record_id, self.task_def.user_token)
|
||||
else:
|
||||
if status == TaskStatus.FAILED:
|
||||
await set_task_failed(self.task_record_id, self.task_def.user_token)
|
||||
# 先完成数据库更新,立即释放数据库连接
|
||||
if status in [TaskStatus.FAILED, TaskStatus.CANCELED, TaskStatus.COMPLETED]:
|
||||
update_values = {
|
||||
"status": status,
|
||||
@ -391,8 +516,40 @@ class TaskExecutor:
|
||||
)
|
||||
|
||||
await session.commit()
|
||||
|
||||
# 数据库更新完成后,异步处理外部API调用(不阻塞当前流程)
|
||||
if not self.is_error:
|
||||
asyncio.create_task(self._sync_external_api_async(status, task_detail))
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"更新任务状态失败: {str(e)}")
|
||||
await session.rollback()
|
||||
raise
|
||||
raise
|
||||
|
||||
async def _sync_external_api_async(self, status: int, task_detail: str = None) -> None:
|
||||
"""
|
||||
异步处理外部API同步,不阻塞主流程
|
||||
|
||||
Args:
|
||||
status: 任务状态码
|
||||
task_detail: 任务详情
|
||||
"""
|
||||
try:
|
||||
from services.sync_service import set_task_in_progress, set_task_completed, set_task_failed, set_task_terminated
|
||||
|
||||
if not self.check_task_def_select_agv(task_detail):
|
||||
if status == TaskStatus.RUNNING:
|
||||
await set_task_in_progress(self.task_record_id, self.task_def.user_token)
|
||||
elif status == TaskStatus.COMPLETED:
|
||||
await set_task_completed(self.task_record_id, self.task_def.user_token)
|
||||
elif status == TaskStatus.FAILED:
|
||||
await set_task_failed(self.task_record_id, self.task_def.user_token)
|
||||
elif status == TaskStatus.CANCELED:
|
||||
await set_task_terminated(self.task_record_id, self.task_def.user_token)
|
||||
else:
|
||||
if status == TaskStatus.FAILED:
|
||||
await set_task_failed(self.task_record_id, self.task_def.user_token)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"外部API同步失败: {str(e)}")
|
||||
# 外部API失败不应该影响主任务流程,只记录日志
|
349
services/external_task_record_service.py
Normal file
349
services/external_task_record_service.py
Normal file
@ -0,0 +1,349 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
外部任务记录服务模块
|
||||
提供外部任务记录相关的服务方法
|
||||
"""
|
||||
|
||||
import json
|
||||
import uuid
|
||||
from typing import Dict, List, Any, Optional
|
||||
from sqlalchemy import select, and_, or_
|
||||
import datetime
|
||||
|
||||
from data.models.external_task_record import VWEDExternalTaskRecord, ExternalTaskTypeEnum, ExternalTaskStatusEnum
|
||||
from data.session import get_async_session
|
||||
from utils.logger import get_logger
|
||||
|
||||
# 设置日志
|
||||
logger = get_logger("service.external_task_record_service")
|
||||
|
||||
class ExternalTaskRecordService:
|
||||
"""
|
||||
外部任务记录服务类
|
||||
提供与外部任务记录相关的方法
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
async def create_new_task_record(
|
||||
req_code: str,
|
||||
source_id: str,
|
||||
target_id: str,
|
||||
business_task_type: str,
|
||||
template_id: str,
|
||||
request_params: Dict[str, Any],
|
||||
client_ip: str = None,
|
||||
client_info: str = None
|
||||
) -> VWEDExternalTaskRecord:
|
||||
"""
|
||||
创建newTask任务记录
|
||||
|
||||
Args:
|
||||
req_code: 请求标识码
|
||||
source_id: 来源ID
|
||||
target_id: 目标ID
|
||||
business_task_type: 业务任务类型
|
||||
template_id: 任务模板ID
|
||||
request_params: 请求参数
|
||||
client_ip: 客户端IP
|
||||
client_info: 客户端信息
|
||||
|
||||
Returns:
|
||||
VWEDExternalTaskRecord: 创建的外部任务记录
|
||||
"""
|
||||
try:
|
||||
async with get_async_session() as session:
|
||||
# 生成唯一ID
|
||||
record_id = str(uuid.uuid4())
|
||||
|
||||
# 创建外部任务记录
|
||||
external_record = VWEDExternalTaskRecord(
|
||||
id=record_id,
|
||||
req_code=req_code,
|
||||
task_type=ExternalTaskTypeEnum.NEW_TASK,
|
||||
task_status=ExternalTaskStatusEnum.PENDING,
|
||||
source_id=source_id,
|
||||
target_id=target_id,
|
||||
business_task_type=business_task_type,
|
||||
template_id=template_id,
|
||||
request_params=json.dumps(request_params, ensure_ascii=False),
|
||||
client_ip=client_ip,
|
||||
client_info=client_info,
|
||||
start_time=datetime.datetime.now()
|
||||
)
|
||||
|
||||
session.add(external_record)
|
||||
await session.commit()
|
||||
await session.refresh(external_record)
|
||||
|
||||
logger.info(f"创建newTask任务记录成功: record_id={record_id}, req_code={req_code}")
|
||||
return external_record
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"创建newTask任务记录失败: {str(e)}, req_code={req_code}")
|
||||
raise
|
||||
|
||||
@staticmethod
|
||||
async def create_agv_scheduling_task_record(
|
||||
req_code: str,
|
||||
task_code: str,
|
||||
business_task_type: str,
|
||||
security_key: str,
|
||||
type_field: str,
|
||||
sub_type: str,
|
||||
area_position_code: str,
|
||||
area_position_name: str,
|
||||
position_code_path: List[Dict[str, Any]],
|
||||
template_id: str,
|
||||
request_params: Dict[str, Any],
|
||||
client_code: str = None,
|
||||
token_code: str = None,
|
||||
client_ip: str = None,
|
||||
client_info: str = None,
|
||||
related_req_code: str = None
|
||||
) -> VWEDExternalTaskRecord:
|
||||
"""
|
||||
创建GenAgvSchedulingTask任务记录
|
||||
|
||||
Args:
|
||||
req_code: 请求标识码
|
||||
task_code: 任务代码
|
||||
business_task_type: 业务任务类型
|
||||
security_key: 安全密钥
|
||||
type_field: 类型字段
|
||||
sub_type: 子类型
|
||||
area_position_code: 区域位置代码
|
||||
area_position_name: 区域位置名称
|
||||
position_code_path: 位置代码路径
|
||||
template_id: 任务模板ID
|
||||
request_params: 请求参数
|
||||
client_code: 客户端代码
|
||||
token_code: 令牌代码
|
||||
client_ip: 客户端IP
|
||||
client_info: 客户端信息
|
||||
related_req_code: 关联的ReqCode
|
||||
|
||||
Returns:
|
||||
VWEDExternalTaskRecord: 创建的外部任务记录
|
||||
"""
|
||||
try:
|
||||
async with get_async_session() as session:
|
||||
# 生成唯一ID
|
||||
record_id = str(uuid.uuid4())
|
||||
|
||||
# 创建外部任务记录
|
||||
external_record = VWEDExternalTaskRecord(
|
||||
id=record_id,
|
||||
req_code=req_code,
|
||||
task_type=ExternalTaskTypeEnum.GEN_AGV_SCHEDULING_TASK,
|
||||
task_status=ExternalTaskStatusEnum.PENDING,
|
||||
task_code=task_code,
|
||||
business_task_type=business_task_type,
|
||||
security_key=security_key,
|
||||
type_field=type_field,
|
||||
sub_type=sub_type,
|
||||
area_position_code=area_position_code,
|
||||
area_position_name=area_position_name,
|
||||
position_code_path=json.dumps(position_code_path, ensure_ascii=False),
|
||||
client_code=client_code,
|
||||
token_code=token_code,
|
||||
template_id=template_id,
|
||||
request_params=json.dumps(request_params, ensure_ascii=False),
|
||||
client_ip=client_ip,
|
||||
client_info=client_info,
|
||||
related_req_code=related_req_code,
|
||||
start_time=datetime.datetime.now()
|
||||
)
|
||||
|
||||
session.add(external_record)
|
||||
await session.commit()
|
||||
await session.refresh(external_record)
|
||||
|
||||
logger.info(f"创建GenAgvSchedulingTask任务记录成功: record_id={record_id}, req_code={req_code}, task_code={task_code}")
|
||||
return external_record
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"创建GenAgvSchedulingTask任务记录失败: {str(e)}, req_code={req_code}")
|
||||
raise
|
||||
|
||||
@staticmethod
|
||||
async def update_task_record_status(
|
||||
req_code: str,
|
||||
task_status: ExternalTaskStatusEnum,
|
||||
task_record_id: str = None,
|
||||
response_code: int = None,
|
||||
response_message: str = None,
|
||||
response_row_count: int = None,
|
||||
response_data: Dict[str, Any] = None,
|
||||
error_message: str = None,
|
||||
error_stack: str = None
|
||||
) -> bool:
|
||||
"""
|
||||
更新外部任务记录状态
|
||||
|
||||
Args:
|
||||
req_code: 请求标识码
|
||||
task_status: 任务状态
|
||||
task_record_id: 内部任务记录ID
|
||||
response_code: 响应状态码
|
||||
response_message: 响应消息
|
||||
response_row_count: 响应行数
|
||||
response_data: 响应数据
|
||||
error_message: 错误信息
|
||||
error_stack: 错误堆栈
|
||||
|
||||
Returns:
|
||||
bool: 更新是否成功
|
||||
"""
|
||||
try:
|
||||
async with get_async_session() as session:
|
||||
# 查询外部任务记录
|
||||
query = select(VWEDExternalTaskRecord).where(
|
||||
VWEDExternalTaskRecord.id == req_code
|
||||
)
|
||||
result = await session.execute(query)
|
||||
external_record = result.scalar_one_or_none()
|
||||
|
||||
if not external_record:
|
||||
logger.error(f"外部任务记录不存在: req_code={req_code}")
|
||||
return False
|
||||
# 更新状态和相关信息
|
||||
response_data_json = None
|
||||
if response_data:
|
||||
try:
|
||||
response_data_json = json.dumps(response_data, ensure_ascii=False, default=str)
|
||||
except Exception as e:
|
||||
# logger.warning(f"序列化response_data失败: {str(e)}")
|
||||
response_data_json = str(response_data)
|
||||
external_record.update_status(task_status, error_message, response_data_json)
|
||||
|
||||
if task_record_id:
|
||||
external_record.task_record_id = task_record_id
|
||||
if response_code is not None:
|
||||
external_record.response_code = response_code
|
||||
if response_message:
|
||||
external_record.response_message = json.dumps(response_message, ensure_ascii=False)
|
||||
if response_row_count is not None:
|
||||
external_record.response_row_count = response_row_count
|
||||
if error_stack:
|
||||
external_record.error_stack = error_stack
|
||||
|
||||
await session.commit()
|
||||
|
||||
logger.info(f"更新外部任务记录状态成功: req_code={req_code}, status={task_status}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"更新外部任务记录状态失败: {str(e)}, req_code={req_code}")
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
async def get_external_task_record(req_code: str) -> Optional[VWEDExternalTaskRecord]:
|
||||
"""
|
||||
根据ReqCode获取外部任务记录
|
||||
|
||||
Args:
|
||||
req_code: 请求标识码
|
||||
|
||||
Returns:
|
||||
Optional[VWEDExternalTaskRecord]: 外部任务记录或None
|
||||
"""
|
||||
try:
|
||||
async with get_async_session() as session:
|
||||
query = select(VWEDExternalTaskRecord).where(
|
||||
VWEDExternalTaskRecord.req_code == req_code
|
||||
).order_by(VWEDExternalTaskRecord.created_at.desc()).limit(1)
|
||||
result = await session.execute(query)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取外部任务记录失败: {str(e)}, req_code={req_code}")
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
async def get_related_tasks(req_code: str) -> List[VWEDExternalTaskRecord]:
|
||||
"""
|
||||
获取关联的任务记录(通过req_code和related_req_code关联)
|
||||
|
||||
Args:
|
||||
req_code: 请求标识码
|
||||
|
||||
Returns:
|
||||
List[VWEDExternalTaskRecord]: 关联的任务记录列表
|
||||
"""
|
||||
try:
|
||||
async with get_async_session() as session:
|
||||
query = select(VWEDExternalTaskRecord).where(
|
||||
or_(
|
||||
VWEDExternalTaskRecord.req_code == req_code,
|
||||
VWEDExternalTaskRecord.related_req_code == req_code
|
||||
)
|
||||
)
|
||||
result = await session.execute(query)
|
||||
return result.scalars().all()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取关联任务记录失败: {str(e)}, req_code={req_code}")
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
async def get_task_records_by_task_code(task_code: str) -> List[VWEDExternalTaskRecord]:
|
||||
"""
|
||||
根据TaskCode获取任务记录
|
||||
|
||||
Args:
|
||||
task_code: 任务代码
|
||||
|
||||
Returns:
|
||||
List[VWEDExternalTaskRecord]: 任务记录列表
|
||||
"""
|
||||
try:
|
||||
async with get_async_session() as session:
|
||||
query = select(VWEDExternalTaskRecord).where(
|
||||
VWEDExternalTaskRecord.req_code == task_code
|
||||
)
|
||||
result = await session.execute(query)
|
||||
return result.scalars().all()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"根据TaskCode获取任务记录失败: {str(e)}, task_code={task_code}")
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
async def check_task_completion_status(task_record_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
检查任务是否完成(通过内部任务记录ID查询)
|
||||
|
||||
Args:
|
||||
task_record_id: 内部任务记录ID
|
||||
|
||||
Returns:
|
||||
Optional[Dict[str, Any]]: 任务状态信息或None
|
||||
"""
|
||||
try:
|
||||
# 这里需要查询内部任务记录表来获取任务状态
|
||||
# 可以根据实际需求实现具体的查询逻辑
|
||||
from data.models.taskrecord import VWEDTaskRecord
|
||||
|
||||
async with get_async_session() as session:
|
||||
query = select(VWEDTaskRecord).where(
|
||||
VWEDTaskRecord.id == task_record_id
|
||||
)
|
||||
result = await session.execute(query)
|
||||
task_record = result.scalar_one_or_none()
|
||||
|
||||
if not task_record:
|
||||
return None
|
||||
|
||||
return {
|
||||
"task_record_id": task_record_id,
|
||||
"status": task_record.status,
|
||||
"ended_on": task_record.ended_on,
|
||||
"ended_reason": task_record.ended_reason,
|
||||
"state_description": task_record.state_description
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"检查任务完成状态失败: {str(e)}, task_record_id={task_record_id}")
|
||||
return None
|
@ -196,6 +196,12 @@ class MapDataService:
|
||||
"""
|
||||
增量更新动作点数据
|
||||
|
||||
支持以下功能:
|
||||
1. 库位可以不绑定库区(area_name为空或None)
|
||||
2. 基于station_name判断是否为已存在的库位
|
||||
3. 如果station_name存在但库位名称不同,则更新库位名称
|
||||
4. 如果库区名称不同,则更新库区关联
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
point_data: 动作点数据
|
||||
@ -213,7 +219,7 @@ class MapDataService:
|
||||
)
|
||||
).first()
|
||||
|
||||
# 根据库区名称获取库区信息
|
||||
# 根据库区名称获取库区信息(支持库位不绑定库区的情况)
|
||||
storage_area = None
|
||||
if point_data.area_name:
|
||||
storage_area = db.query(StorageArea).filter(
|
||||
@ -226,9 +232,12 @@ class MapDataService:
|
||||
|
||||
if existing_point:
|
||||
# 更新现有动作点
|
||||
# 支持库区关联的修改
|
||||
existing_point.storage_area_id = storage_area.id if storage_area else None
|
||||
existing_point.storage_area_type = storage_area.area_type if storage_area else None
|
||||
existing_point.area_name = point_data.area_name
|
||||
existing_point.area_name = point_data.area_name # 支持库区名称为空的情况
|
||||
|
||||
# 更新其他属性
|
||||
existing_point.max_layers = point_data.max_layers
|
||||
existing_point.position_x = point_data.position_x
|
||||
existing_point.position_y = point_data.position_y
|
||||
@ -238,7 +247,9 @@ class MapDataService:
|
||||
existing_point.description = point_data.description
|
||||
existing_point.updated_at = datetime.datetime.now()
|
||||
|
||||
logger.info(f"更新动作点: {point_data.station_name}")
|
||||
# 记录更新信息
|
||||
area_info = f"库区: {point_data.area_name}" if point_data.area_name else "未绑定库区"
|
||||
logger.info(f"更新动作点: {point_data.station_name}, {area_info}")
|
||||
return False, existing_point
|
||||
else:
|
||||
# 创建新动作点
|
||||
@ -246,9 +257,9 @@ class MapDataService:
|
||||
id=str(uuid.uuid4()),
|
||||
station_name=point_data.station_name,
|
||||
scene_id=scene_id,
|
||||
# storage_area_id=storage_area.id if storage_area else None,
|
||||
storage_area_id=storage_area.id if storage_area else None,
|
||||
storage_area_type=storage_area.area_type if storage_area else None,
|
||||
area_name=point_data.area_name,
|
||||
area_name=point_data.area_name, # 支持为None的情况
|
||||
max_layers=point_data.max_layers,
|
||||
position_x=point_data.position_x,
|
||||
position_y=point_data.position_y,
|
||||
@ -259,7 +270,9 @@ class MapDataService:
|
||||
)
|
||||
db.add(new_point)
|
||||
|
||||
logger.info(f"创建新动作点: {point_data.station_name}")
|
||||
# 记录创建信息
|
||||
area_info = f"库区: {point_data.area_name}" if point_data.area_name else "未绑定库区"
|
||||
logger.info(f"创建新动作点: {point_data.station_name}, {area_info}")
|
||||
return True, new_point
|
||||
|
||||
@staticmethod
|
||||
@ -268,6 +281,12 @@ class MapDataService:
|
||||
"""
|
||||
增量更新分层数据
|
||||
|
||||
支持以下功能:
|
||||
1. 基于动作点ID和层索引确定唯一层
|
||||
2. 支持库位名称更新(基于层位置)
|
||||
3. 更新库区关联信息
|
||||
4. 避免唯一约束冲突
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
operate_point: 动作点对象
|
||||
@ -282,22 +301,27 @@ class MapDataService:
|
||||
if not layers_data:
|
||||
return {'created': created_count, 'updated': updated_count}
|
||||
|
||||
# 获取该动作点所有现有的层,按layer_index排序
|
||||
existing_layers = db.query(OperatePointLayer).filter(
|
||||
and_(
|
||||
OperatePointLayer.operate_point_id == operate_point.id,
|
||||
OperatePointLayer.is_deleted == False
|
||||
)
|
||||
).order_by(OperatePointLayer.layer_index).all()
|
||||
|
||||
# 创建层索引到层对象的映射
|
||||
existing_layers_map = {layer.layer_index: layer for layer in existing_layers}
|
||||
|
||||
for index, layer_data in enumerate(layers_data, 1):
|
||||
# 自动生成层索引(从1开始)
|
||||
# 层索引从1开始
|
||||
layer_index = index
|
||||
|
||||
# 查找现有分层(基于layer_name和operate_point_id)
|
||||
existing_layer = db.query(OperatePointLayer).filter(
|
||||
and_(
|
||||
OperatePointLayer.layer_name == layer_data.layer_name,
|
||||
OperatePointLayer.operate_point_id == operate_point.id,
|
||||
OperatePointLayer.is_deleted == False
|
||||
)
|
||||
).first()
|
||||
|
||||
if existing_layer:
|
||||
# 更新现有分层
|
||||
existing_layer.layer_index = layer_index
|
||||
if layer_index in existing_layers_map:
|
||||
# 更新现有层
|
||||
existing_layer = existing_layers_map[layer_index]
|
||||
existing_layer.layer_name = layer_data.layer_name # 支持库位名称更新
|
||||
existing_layer.area_name = operate_point.area_name # 更新库区名称(支持为None)
|
||||
existing_layer.station_name = operate_point.station_name # 确保站点名称一致
|
||||
existing_layer.max_weight = layer_data.max_weight
|
||||
existing_layer.max_volume = layer_data.max_volume
|
||||
existing_layer.layer_height = layer_data.layer_height
|
||||
@ -305,15 +329,17 @@ class MapDataService:
|
||||
existing_layer.tags = layer_data.tags
|
||||
existing_layer.updated_at = datetime.datetime.now()
|
||||
|
||||
logger.debug(f"更新分层: {layer_data.layer_name} (layer_index={layer_index})")
|
||||
# 记录更新信息
|
||||
area_info = f", 库区: {operate_point.area_name}" if operate_point.area_name else ", 未绑定库区"
|
||||
logger.debug(f"更新分层: 站点={operate_point.station_name}, 库位={layer_data.layer_name}, 层={layer_index}{area_info}")
|
||||
updated_count += 1
|
||||
else:
|
||||
# 创建新分层
|
||||
# 创建新层 - 只有在该位置没有层时才创建
|
||||
new_layer = OperatePointLayer(
|
||||
id=str(uuid.uuid4()),
|
||||
operate_point_id=operate_point.id,
|
||||
station_name=operate_point.station_name,
|
||||
area_name=operate_point.area_name,
|
||||
area_name=operate_point.area_name, # 支持为None的情况
|
||||
scene_id=operate_point.scene_id,
|
||||
layer_index=layer_index,
|
||||
layer_name=layer_data.layer_name,
|
||||
@ -332,7 +358,9 @@ class MapDataService:
|
||||
except Exception as e:
|
||||
logger.error(f"为新库位层 {new_layer.id} 同步扩展属性失败: {str(e)}")
|
||||
|
||||
logger.debug(f"创建新分层: {layer_data.layer_name} (layer_index={layer_index})")
|
||||
# 记录创建信息
|
||||
area_info = f", 库区: {operate_point.area_name}" if operate_point.area_name else ", 未绑定库区"
|
||||
logger.debug(f"创建新分层: 站点={operate_point.station_name}, 库位={layer_data.layer_name}, 层={layer_index}{area_info}")
|
||||
created_count += 1
|
||||
|
||||
return {'created': created_count, 'updated': updated_count}
|
||||
|
@ -40,6 +40,7 @@ class TFApiConfig:
|
||||
SET_TASK_COMPLETED_PATH = tf_config["endpoints"]["set_task_completed"]
|
||||
SET_TASK_TERMINATED_PATH = tf_config["endpoints"]["set_task_terminated"]
|
||||
SET_TASK_FAILED_PATH = tf_config["endpoints"]["set_task_failed"]
|
||||
SET_TASK_DESCRIPTION_PATH = tf_config["endpoints"]["set_task_description"]
|
||||
# 超时设置(秒)
|
||||
TIMEOUT = tf_config["timeout"]
|
||||
|
||||
@ -76,6 +77,7 @@ class AddActionRequest(BaseModel):
|
||||
stationName: str
|
||||
action: str
|
||||
param: str
|
||||
store: str
|
||||
|
||||
class ApiResponse(BaseModel):
|
||||
"""API响应基础模型"""
|
||||
@ -124,12 +126,13 @@ async def create_task(task_record_id: str, task_name: str, is_periodic: bool, pr
|
||||
try:
|
||||
logger.info(f"正在同步创建任务到天风系统: {task_record_id}")
|
||||
logger.debug(f"创建任务请求参数: {request_data.model_dump_json()}")
|
||||
async with aiohttp.ClientSession() as session:
|
||||
# 优化超时配置,分别设置连接超时和总超时
|
||||
timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
|
||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||
async with session.post(
|
||||
url,
|
||||
json=request_data.model_dump(),
|
||||
headers=headers,
|
||||
timeout=TFApiConfig.TIMEOUT
|
||||
headers=headers
|
||||
) as response:
|
||||
# 读取响应内容
|
||||
response_text = await response.text()
|
||||
@ -186,12 +189,12 @@ async def create_choose_amr_task(task_id: str, key_station_name: str, amr_name:
|
||||
try:
|
||||
logger.info(f"正在创建选择AMR任务: {task_id}, 站点: {key_station_name}")
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
|
||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||
async with session.post(
|
||||
url,
|
||||
json=request_data.model_dump(),
|
||||
headers=headers,
|
||||
timeout=TFApiConfig.TIMEOUT
|
||||
headers=headers
|
||||
) as response:
|
||||
# 读取响应内容
|
||||
response_text = await response.json()
|
||||
@ -210,7 +213,7 @@ async def create_choose_amr_task(task_id: str, key_station_name: str, amr_name:
|
||||
logger.error(f"调用选择AMR接口失败: {str(e)}")
|
||||
return None
|
||||
|
||||
async def add_action(task_id: str, station_name: str, action: str, token: str = None, param: dict = None) -> Optional[ApiResponse]:
|
||||
async def add_action(task_id: str, station_name: str, action: str, token: str = None, param: dict = None, store: str = None) -> Optional[ApiResponse]:
|
||||
"""
|
||||
调用系统任务添加动作接口
|
||||
|
||||
@ -230,7 +233,8 @@ async def add_action(task_id: str, station_name: str, action: str, token: str =
|
||||
taskBlockId=task_id,
|
||||
stationName=station_name,
|
||||
action=action,
|
||||
param=json.dumps(param)
|
||||
param=json.dumps(param),
|
||||
store=store
|
||||
)
|
||||
|
||||
# 调用接口
|
||||
@ -244,12 +248,12 @@ async def add_action(task_id: str, station_name: str, action: str, token: str =
|
||||
try:
|
||||
logger.info(f"正在为任务添加动作: {task_id}, 站点: {station_name}, 动作: {action}")
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
|
||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||
async with session.post(
|
||||
url,
|
||||
json=request_data.model_dump(),
|
||||
headers=headers,
|
||||
timeout=TFApiConfig.TIMEOUT
|
||||
headers=headers
|
||||
) as response:
|
||||
# 读取响应内容
|
||||
response_text = await response.text()
|
||||
@ -292,11 +296,11 @@ async def closure_task(task_id: str, token: str = None) -> Optional[ApiResponse]
|
||||
try:
|
||||
logger.info(f"正在封口任务: {task_id}")
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
|
||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||
async with session.put(
|
||||
url,
|
||||
headers=headers,
|
||||
timeout=TFApiConfig.TIMEOUT
|
||||
headers=headers
|
||||
) as response:
|
||||
# 读取响应内容
|
||||
response_text = await response.json()
|
||||
@ -337,11 +341,11 @@ async def get_task_block_detail(task_block_id: str, token: str = None) -> Option
|
||||
try:
|
||||
logger.info(f"正在获取任务块详情: {task_block_id}")
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
|
||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||
async with session.get(
|
||||
url,
|
||||
headers=headers,
|
||||
timeout=TFApiConfig.TIMEOUT
|
||||
headers=headers
|
||||
) as response:
|
||||
# 读取响应内容
|
||||
response_text = await response.text()
|
||||
@ -386,11 +390,11 @@ async def get_task_block_action_detail(task_block_id: str, token: str = None) ->
|
||||
try:
|
||||
logger.info(f"正在获取任务块动作详情: {task_block_id}")
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
|
||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||
async with session.get(
|
||||
url,
|
||||
headers=headers,
|
||||
timeout=TFApiConfig.TIMEOUT
|
||||
headers=headers
|
||||
) as response:
|
||||
# 读取响应内容
|
||||
response_text = await response.text()
|
||||
@ -434,7 +438,16 @@ async def wait_for_amr_selection(task_block_id: str, token: str = None, context
|
||||
while True:
|
||||
retry_count += 1
|
||||
if context:
|
||||
|
||||
is_canceled = await context.is_task_canceled_async()
|
||||
# is_canceled = await context.is_task_canceled_async()
|
||||
if context.is_failed:
|
||||
return {
|
||||
"success": False,
|
||||
"message": context.failure_reason,
|
||||
"is_failed": True,
|
||||
"is_canceled": is_canceled
|
||||
}
|
||||
if is_canceled:
|
||||
logger.info(f"检测到任务已被取消,停止等待任务块 {task_block_id} 的AMR选择")
|
||||
return {
|
||||
@ -494,6 +507,13 @@ async def wait_for_task_block_action_completion(task_block_id: str, token: str =
|
||||
while True:
|
||||
if context:
|
||||
is_canceled = await context.is_task_canceled_async()
|
||||
if context.is_failed:
|
||||
return {
|
||||
"success": False,
|
||||
"message": context.failure_reason,
|
||||
"is_failed": True,
|
||||
"is_canceled": is_canceled
|
||||
}
|
||||
if is_canceled:
|
||||
logger.info(f"检测到任务已被取消,停止等待任务块 {task_block_id} 的AMR选择")
|
||||
return {
|
||||
@ -551,11 +571,11 @@ async def set_task_in_progress(task_id: str, token: str = None) -> Optional[ApiR
|
||||
try:
|
||||
logger.info(f"正在设置系统任务状态为执行中: {task_id}")
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
|
||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||
async with session.put(
|
||||
url,
|
||||
headers=headers,
|
||||
timeout=TFApiConfig.TIMEOUT
|
||||
headers=headers
|
||||
) as response:
|
||||
# 读取响应内容
|
||||
response_text = await response.json()
|
||||
@ -596,11 +616,11 @@ async def set_task_completed(task_id: str, token: str = None) -> Optional[ApiRes
|
||||
|
||||
try:
|
||||
logger.info(f"正在设置系统任务状态为已完成: {task_id}")
|
||||
async with aiohttp.ClientSession() as session:
|
||||
timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
|
||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||
async with session.put(
|
||||
url,
|
||||
headers=headers,
|
||||
timeout=TFApiConfig.TIMEOUT
|
||||
headers=headers
|
||||
) as response:
|
||||
# 读取响应内容
|
||||
response_text = await response.json()
|
||||
@ -641,11 +661,11 @@ async def set_task_terminated(task_id: str, token: str = None) -> Optional[ApiRe
|
||||
try:
|
||||
logger.info(f"正在设置系统任务状态为已终止: {task_id}")
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
|
||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||
async with session.put(
|
||||
url,
|
||||
headers=headers,
|
||||
timeout=TFApiConfig.TIMEOUT
|
||||
headers=headers
|
||||
) as response:
|
||||
# 读取响应内容
|
||||
response_text = await response.json()
|
||||
@ -685,11 +705,11 @@ async def set_task_failed(task_id: str, token: str = None) -> Optional[ApiRespon
|
||||
try:
|
||||
logger.info(f"正在设置任务状态为已失败: {task_id}")
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
|
||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||
async with session.put(
|
||||
url,
|
||||
headers=headers,
|
||||
timeout=TFApiConfig.TIMEOUT
|
||||
headers=headers
|
||||
) as response:
|
||||
# 读取响应内容
|
||||
response_text = await response.json()
|
||||
@ -708,6 +728,59 @@ async def set_task_failed(task_id: str, token: str = None) -> Optional[ApiRespon
|
||||
logger.error(f"调用设置任务状态接口失败: {str(e)}")
|
||||
return None
|
||||
|
||||
async def set_task_description(task_id: str, description: str, token: str = None) -> Optional[ApiResponse]:
|
||||
"""
|
||||
设置VWED任务描述
|
||||
|
||||
Args:
|
||||
task_id: VWED任务ID
|
||||
description: 任务描述
|
||||
token: 认证令牌
|
||||
|
||||
Returns:
|
||||
Optional[ApiResponse]: 接口响应,如果请求失败则返回None
|
||||
"""
|
||||
# 调用接口
|
||||
url = f"{TFApiConfig.BASE_URL}{TFApiConfig.SET_TASK_DESCRIPTION_PATH.format(id=task_id)}"
|
||||
|
||||
# 构建请求头
|
||||
headers = {}
|
||||
headers[TFApiConfig.TOKEN_HEADER] = token
|
||||
headers["x-tenant-id"] = "1000"
|
||||
headers["Content-Type"] = "application/json"
|
||||
|
||||
# 构建请求体
|
||||
request_data = {"description": description}
|
||||
|
||||
try:
|
||||
logger.info(f"正在设置VWED任务描述: {task_id}, 描述: {description}")
|
||||
|
||||
timeout = aiohttp.ClientTimeout(total=TFApiConfig.TIMEOUT, connect=5)
|
||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||
async with session.put(
|
||||
url,
|
||||
json=request_data,
|
||||
headers=headers
|
||||
) as response:
|
||||
# 读取响应内容
|
||||
response_text = await response.text()
|
||||
# 尝试解析JSON
|
||||
try:
|
||||
response_data = json.loads(response_text)
|
||||
|
||||
if response_data.get("success", False):
|
||||
logger.info(f"成功设置VWED任务描述: {task_id}")
|
||||
else:
|
||||
logger.error(f"设置VWED任务描述失败: {response_data.get('message', '未知错误')}")
|
||||
|
||||
return response_data
|
||||
except json.JSONDecodeError:
|
||||
logger.error(f"解析响应JSON失败: {response_text}")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"调用设置任务描述接口失败: {str(e)}")
|
||||
return None
|
||||
|
||||
|
||||
# import asyncio
|
||||
# task_block_id = "1947858858043117570"
|
||||
|
@ -357,6 +357,7 @@ class TaskEditService:
|
||||
map_id = task_def.map_id
|
||||
source_ip = client_ip # 获取IP地址
|
||||
source_time = now # 始终使用当前时间
|
||||
set_priority = run_request.priority
|
||||
|
||||
# 记录任务请求信息
|
||||
logger.info(f"准备启动任务: {run_request.taskId}, 来源: {source_system}, 设备: {source_device}")
|
||||
@ -400,7 +401,8 @@ class TaskEditService:
|
||||
source_time=source_time,
|
||||
source_client_info=client_info,
|
||||
tf_api_token=tf_api_token,
|
||||
map_id=map_id
|
||||
map_id=map_id,
|
||||
set_priority=set_priority
|
||||
)
|
||||
|
||||
if not result.get("success", False):
|
||||
@ -440,7 +442,8 @@ class TaskEditService:
|
||||
source_time=source_time,
|
||||
source_client_info=client_info,
|
||||
tf_api_token=tf_api_token,
|
||||
map_id=map_id
|
||||
map_id=map_id,
|
||||
set_priority=set_priority
|
||||
)
|
||||
|
||||
if not result.get("success", False):
|
||||
|
@ -249,10 +249,10 @@ class TaskRecordService:
|
||||
|
||||
# 如果任务正在运行,先取消它
|
||||
if task_record.status == TaskStatus.RUNNING:
|
||||
cancel_result = await scheduler.cancel_task(task_record_id)
|
||||
cancel_result = await scheduler.set_task_error(task_record_id, error_reason)
|
||||
if not cancel_result.get("success", False):
|
||||
logger.warning(f"取消任务 {task_record_id} 失败: {cancel_result}")
|
||||
|
||||
|
||||
# 设置任务状态为失败
|
||||
task_record.status = TaskStatus.FAILED
|
||||
task_record.ended_on = datetime.now()
|
||||
|
0
tests/logs/app.log
Normal file
0
tests/logs/app.log
Normal file
243
tests/test1.py
243
tests/test1.py
@ -1,11 +1,236 @@
|
||||
def natural_sort_key(layer_name):
|
||||
"""自然排序键函数,将字符串中的数字部分转换为整数进行排序"""
|
||||
import re
|
||||
def convert(text):
|
||||
return int(text) if text.isdigit() else text.lower()
|
||||
s = {
|
||||
"inputParams": [],
|
||||
"outputParams": [],
|
||||
"rootBlock": {
|
||||
"id": -1,
|
||||
"name": "-1",
|
||||
"blockType": "RootBp",
|
||||
"inputParams": {},
|
||||
"children": {
|
||||
"default": [
|
||||
{
|
||||
"id": 9,
|
||||
"name": "b5",
|
||||
"blockType": "GetIdleCrowdedSiteBp",
|
||||
"children": {},
|
||||
"inputParams": {
|
||||
"groupName": {
|
||||
"value": "[\"AS2_2\"]",
|
||||
"type": "Simple",
|
||||
"required": True
|
||||
},
|
||||
"filled": {
|
||||
"value": "true",
|
||||
"type": "Simple",
|
||||
"required": True
|
||||
},
|
||||
"content": {
|
||||
"value": "",
|
||||
"type": "Simple",
|
||||
"required": True
|
||||
},
|
||||
"lock": {
|
||||
"value": "",
|
||||
"type": "Simple",
|
||||
"required": True
|
||||
},
|
||||
"retry": {
|
||||
"value": "false",
|
||||
"type": "Simple",
|
||||
"required": True
|
||||
},
|
||||
"retryPeriod": {
|
||||
"value": "",
|
||||
"type": "Simple",
|
||||
"required": True
|
||||
},
|
||||
"retryNum": {
|
||||
"value": "",
|
||||
"type": "Simple",
|
||||
"required": True
|
||||
}
|
||||
},
|
||||
"refTaskDefId": "",
|
||||
"selected": True,
|
||||
"expanded": True,
|
||||
"label": "获取密集库位"
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"name": "b6",
|
||||
"blockType": "GetIdleCrowdedSiteBp",
|
||||
"children": {},
|
||||
"inputParams": {
|
||||
"groupName": {
|
||||
"value": "AS2_2",
|
||||
"type": "Simple",
|
||||
"required": True
|
||||
},
|
||||
"filled": {
|
||||
"value": "true",
|
||||
"type": "Simple",
|
||||
"required": True
|
||||
},
|
||||
"content": {
|
||||
"value": "",
|
||||
"type": "Simple",
|
||||
"required": True
|
||||
},
|
||||
"lock": {
|
||||
"value": "",
|
||||
"type": "Simple",
|
||||
"required": True
|
||||
},
|
||||
"retry": {
|
||||
"value": "true",
|
||||
"type": "Simple",
|
||||
"required": True
|
||||
},
|
||||
"retryPeriod": {
|
||||
"value": "",
|
||||
"type": "Simple",
|
||||
"required": True
|
||||
},
|
||||
"retryNum": {
|
||||
"value": "",
|
||||
"type": "Simple",
|
||||
"required": True
|
||||
}
|
||||
},
|
||||
"refTaskDefId": "",
|
||||
"selected": True,
|
||||
"expanded": True,
|
||||
"label": "获取密集库位"
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"name": "b7",
|
||||
"blockType": "CSelectAgvBp",
|
||||
"children": {
|
||||
"default": [
|
||||
{
|
||||
"id": 9,
|
||||
"name": "b8",
|
||||
"blockType": "CAgvOperationBp",
|
||||
"children": {},
|
||||
"inputParams": {
|
||||
"targetSiteLabel": {
|
||||
"value": "AP1",
|
||||
"type": "Simple",
|
||||
"required": True
|
||||
},
|
||||
"spin": {
|
||||
"value": True,
|
||||
"type": "Simple",
|
||||
"required": True
|
||||
},
|
||||
"task": {
|
||||
"value": "",
|
||||
"type": "Simple",
|
||||
"required": True
|
||||
}
|
||||
},
|
||||
"refTaskDefId": "",
|
||||
"selected": True,
|
||||
"expanded": True,
|
||||
"label": "机器人通用动作"
|
||||
}
|
||||
]
|
||||
},
|
||||
"inputParams": {
|
||||
"priority": {
|
||||
"value": "",
|
||||
"type": "Simple",
|
||||
"required": True
|
||||
},
|
||||
"vehicle": {
|
||||
"value": "",
|
||||
"type": "Simple",
|
||||
"required": True
|
||||
},
|
||||
"group": {
|
||||
"value": "",
|
||||
"type": "Simple",
|
||||
"required": True
|
||||
},
|
||||
"keyRoute": {
|
||||
"value": "blocks.b5.siteId",
|
||||
"type": "Expression",
|
||||
"required": True
|
||||
}
|
||||
},
|
||||
"refTaskDefId": "",
|
||||
"selected": True,
|
||||
"expanded": True,
|
||||
"label": "选择执行机器人"
|
||||
}
|
||||
]
|
||||
},
|
||||
"selected": True,
|
||||
"refTaskDefId": "",
|
||||
"expanded": True
|
||||
}
|
||||
}
|
||||
import json
|
||||
|
||||
return [convert(c) for c in re.split('([0-9]+)', layer_name)]
|
||||
|
||||
s = natural_sort_key("DSA_2_1_2")
|
||||
c = lambda x: natural_sort_key(x.layer_name)
|
||||
print(s)
|
||||
def check_task_def_select_agv(detail_json: str) -> int:
|
||||
"""
|
||||
检查任务定义详情中是否包含SELECT_AGV块类型
|
||||
|
||||
Args:
|
||||
detail_json: 任务定义详情JSON字符串
|
||||
|
||||
Returns:
|
||||
int: 存在返回1,不存在返回0
|
||||
"""
|
||||
# from services.execution.handlers.model.block_name import RobotBlockName
|
||||
try:
|
||||
# 解析JSON字符串
|
||||
print("---")
|
||||
if not detail_json:
|
||||
return 0
|
||||
print("========")
|
||||
detail = json.loads(detail_json)
|
||||
if not detail:
|
||||
return 0
|
||||
|
||||
# 选择AGV块类型常量
|
||||
SELECT_AGV_TYPE = "CSelectAgvBp"
|
||||
# print(SELECT_AGV_TYPE, "---")
|
||||
# 检查根块
|
||||
root_block = detail.get("rootBlock")
|
||||
if not root_block:
|
||||
return 0
|
||||
|
||||
# 递归检查块类型
|
||||
def check_block(block):
|
||||
# 检查当前块类型
|
||||
if block.get("blockType") == SELECT_AGV_TYPE:
|
||||
return True
|
||||
|
||||
# 递归检查子块
|
||||
children = block.get("children", {})
|
||||
for child_key, child_list in children.items():
|
||||
if isinstance(child_list, list):
|
||||
for child in child_list:
|
||||
if check_block(child):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
# 从根块开始检查
|
||||
if check_block(root_block):
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
except json.JSONDecodeError:
|
||||
# logger.error(f"解析任务定义详情JSON失败: {detail_json[:100]}...")
|
||||
return 0
|
||||
except Exception as e:
|
||||
# logger.error(f"检查任务定义中SELECT_AGV类型异常: {str(e)}")
|
||||
return 0
|
||||
# import VWED任务模块接口文/
|
||||
ds = check_task_def_select_agv(json.dumps(s))
|
||||
print(ds)
|
||||
|
BIN
utils/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
utils/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
utils/__pycache__/logger.cpython-313.pyc
Normal file
BIN
utils/__pycache__/logger.cpython-313.pyc
Normal file
Binary file not shown.
@ -2,7 +2,7 @@
|
||||
import logging
|
||||
import os
|
||||
import traceback
|
||||
from logging.handlers import RotatingFileHandler
|
||||
from logging.handlers import TimedRotatingFileHandler
|
||||
from config.settings import LogConfig
|
||||
|
||||
# 延迟导入告警同步服务,避免循环导入
|
||||
@ -116,12 +116,17 @@ def setup_logger():
|
||||
root_logger.addHandler(error_console_handler)
|
||||
|
||||
# 添加统一的文件处理器,使用基于级别的格式化器
|
||||
file_handler = RotatingFileHandler(
|
||||
# 使用TimedRotatingFileHandler实现每天一个日志文件
|
||||
file_handler = TimedRotatingFileHandler(
|
||||
LOG_CONFIG["file"],
|
||||
maxBytes=10 * 1024 * 1024, # 10MB
|
||||
backupCount=5,
|
||||
encoding='utf-8'
|
||||
when='midnight', # 每天午夜轮转
|
||||
interval=1, # 每1天轮转一次
|
||||
backupCount=30, # 保留30天的日志文件
|
||||
encoding='utf-8',
|
||||
utc=False # 使用本地时间
|
||||
)
|
||||
# 设置日志文件名后缀格式为日期
|
||||
file_handler.suffix = "%Y-%m-%d"
|
||||
# 使用自定义的格式化器,根据日志级别自动选择格式
|
||||
file_handler.setFormatter(LevelBasedFormatter())
|
||||
root_logger.addHandler(file_handler)
|
||||
|
Binary file not shown.
@ -21,6 +21,8 @@ async def is_blank(s: Optional[str]) -> bool:
|
||||
Returns:
|
||||
bool: 如果字符串为None、空字符串或只包含空白字符,则返回True;否则返回False
|
||||
"""
|
||||
s = await remove_quotes(s)
|
||||
|
||||
return s is None or (isinstance(s, str) and s.strip() == "")
|
||||
|
||||
|
||||
@ -34,6 +36,8 @@ async def is_not_blank(s: Optional[str]) -> bool:
|
||||
Returns:
|
||||
bool: 如果字符串不为None且不只包含空白字符,则返回True;否则返回False
|
||||
"""
|
||||
s = await remove_quotes(s)
|
||||
|
||||
return not await is_blank(s)
|
||||
|
||||
|
||||
@ -48,6 +52,7 @@ async def is_any_blank(*strings: Optional[str]) -> bool:
|
||||
bool: 如果任何一个字符串为空,则返回True;否则返回False
|
||||
"""
|
||||
for s in strings:
|
||||
s = await remove_quotes(s)
|
||||
if await is_blank(s):
|
||||
return True
|
||||
return False
|
||||
@ -63,7 +68,9 @@ async def is_all_blank(*strings: Optional[str]) -> bool:
|
||||
Returns:
|
||||
bool: 如果所有字符串都为空,则返回True;否则返回False
|
||||
"""
|
||||
|
||||
for s in strings:
|
||||
s = await remove_quotes(s)
|
||||
if not await is_blank(s):
|
||||
return False
|
||||
return True
|
||||
@ -79,6 +86,8 @@ async def is_numeric(s: Optional[str]) -> bool:
|
||||
Returns:
|
||||
bool: 如果字符串是数字,则返回True;否则返回False
|
||||
"""
|
||||
s = await remove_quotes(s)
|
||||
|
||||
if await is_blank(s):
|
||||
return False
|
||||
try:
|
||||
@ -98,6 +107,8 @@ async def is_alpha(s: Optional[str]) -> bool:
|
||||
Returns:
|
||||
bool: 如果字符串只包含字母,则返回True;否则返回False
|
||||
"""
|
||||
s = await remove_quotes(s)
|
||||
|
||||
if await is_blank(s):
|
||||
return False
|
||||
return isinstance(s, str) and s.isalpha()
|
||||
@ -114,6 +125,8 @@ async def starts_with(s: Optional[str], prefix: str) -> bool:
|
||||
Returns:
|
||||
bool: 如果字符串以指定前缀开头,则返回True;否则返回False
|
||||
"""
|
||||
s = await remove_quotes(s)
|
||||
|
||||
if await is_blank(s):
|
||||
return False
|
||||
return isinstance(s, str) and s.startswith(prefix)
|
||||
@ -130,6 +143,8 @@ async def ends_with(s: Optional[str], suffix: str) -> bool:
|
||||
Returns:
|
||||
bool: 如果字符串以指定后缀结尾,则返回True;否则返回False
|
||||
"""
|
||||
s = await remove_quotes(s)
|
||||
|
||||
if await is_blank(s):
|
||||
return False
|
||||
return isinstance(s, str) and s.endswith(suffix)
|
||||
@ -146,9 +161,14 @@ async def contains(s: Optional[str], sub: str) -> bool:
|
||||
Returns:
|
||||
bool: 如果字符串包含指定子字符串,则返回True;否则返回False
|
||||
"""
|
||||
if await is_blank(s):
|
||||
return False
|
||||
return isinstance(s, str) and sub in s
|
||||
# print(s, str(sub), "=====================")
|
||||
# if await is_blank(s):
|
||||
# return False
|
||||
s = await remove_quotes(s)
|
||||
sub = await remove_quotes(sub)
|
||||
|
||||
print(s, sub)
|
||||
return s.find(sub) != -1
|
||||
|
||||
|
||||
async def split(s: Optional[str], sep: str, maxsplit: int = -1) -> List[str]:
|
||||
@ -163,6 +183,8 @@ async def split(s: Optional[str], sep: str, maxsplit: int = -1) -> List[str]:
|
||||
Returns:
|
||||
List[str]: 拆分后的字符串列表
|
||||
"""
|
||||
s = await remove_quotes(s)
|
||||
|
||||
if await is_blank(s):
|
||||
return []
|
||||
return s.split(sep, maxsplit)
|
||||
@ -178,6 +200,8 @@ async def chop(s: Optional[str]) -> str:
|
||||
Returns:
|
||||
str: 处理后的字符串,如果原字符串为空,则返回空字符串
|
||||
"""
|
||||
s = await remove_quotes(s)
|
||||
|
||||
if await is_blank(s):
|
||||
return ""
|
||||
return s[:-1]
|
||||
@ -193,6 +217,8 @@ async def chomp(s: Optional[str]) -> str:
|
||||
Returns:
|
||||
str: 处理后的字符串,如果原字符串为空,则返回空字符串
|
||||
"""
|
||||
s = await remove_quotes(s)
|
||||
|
||||
if await is_blank(s):
|
||||
return ""
|
||||
if s.endswith("\r\n"):
|
||||
@ -215,6 +241,8 @@ async def index_of(s: Optional[str], sub: str, start: int = 0, end: Optional[int
|
||||
Returns:
|
||||
int: 子字符串在字符串中的索引位置,如果找不到则返回-1
|
||||
"""
|
||||
s = await remove_quotes(s)
|
||||
|
||||
if await is_blank(s) or await is_blank(sub):
|
||||
return -1
|
||||
try:
|
||||
@ -235,6 +263,9 @@ async def difference(str1: Optional[str], str2: Optional[str]) -> str:
|
||||
Returns:
|
||||
str: 不一样的部分,如果任一字符串为空或无差异,则返回空字符串
|
||||
"""
|
||||
str1 = await remove_quotes(str1)
|
||||
str2 = await remove_quotes(str2)
|
||||
|
||||
if await is_blank(str1) or await is_blank(str2):
|
||||
return ""
|
||||
|
||||
@ -265,6 +296,10 @@ async def replace(s: Optional[str], old: str, new: str, count: int = -1) -> str:
|
||||
Returns:
|
||||
str: 替换后的字符串
|
||||
"""
|
||||
s = await remove_quotes(s)
|
||||
old = await remove_quotes(old)
|
||||
new = await remove_quotes(new)
|
||||
|
||||
if await is_blank(s):
|
||||
return ""
|
||||
return s.replace(old, new, count)
|
||||
@ -281,6 +316,7 @@ async def remove(s: Optional[str], sub: str) -> str:
|
||||
Returns:
|
||||
str: 删除后的字符串
|
||||
"""
|
||||
s = await remove_quotes(s)
|
||||
return await replace(s, sub, "")
|
||||
|
||||
|
||||
@ -295,6 +331,7 @@ async def right(s: Optional[str], length: int) -> str:
|
||||
Returns:
|
||||
str: 截取的子字符串
|
||||
"""
|
||||
s = await remove_quotes(s)
|
||||
if await is_blank(s):
|
||||
return ""
|
||||
if length <= 0:
|
||||
@ -315,6 +352,7 @@ async def left(s: Optional[str], length: int) -> str:
|
||||
Returns:
|
||||
str: 截取的子字符串
|
||||
"""
|
||||
s = await remove_quotes(s)
|
||||
if await is_blank(s):
|
||||
return ""
|
||||
if length <= 0:
|
||||
@ -332,12 +370,14 @@ async def length(s: Optional[str]) -> int:
|
||||
Returns:
|
||||
int: 字符串的长度,如果为None则返回0
|
||||
"""
|
||||
s = await remove_quotes(s)
|
||||
if s is None:
|
||||
return 0
|
||||
return len(s)
|
||||
|
||||
|
||||
async def reverse(s: Optional[str]) -> str:
|
||||
|
||||
"""
|
||||
反转字符串
|
||||
|
||||
@ -347,6 +387,30 @@ async def reverse(s: Optional[str]) -> str:
|
||||
Returns:
|
||||
str: 反转后的字符串
|
||||
"""
|
||||
s = await remove_quotes(s)
|
||||
if await is_blank(s):
|
||||
return ""
|
||||
return s[::-1]
|
||||
|
||||
return s[::-1]
|
||||
|
||||
|
||||
async def remove_quotes(s: Optional[str]) -> str:
|
||||
"""
|
||||
去除字符串两端的双引号或单引号
|
||||
|
||||
Args:
|
||||
s: 要处理的字符串
|
||||
|
||||
Returns:
|
||||
str: 去除引号后的字符串
|
||||
"""
|
||||
s = s.strip()
|
||||
|
||||
# 去除双引号
|
||||
if s.startswith('"') and s.endswith('"') and len(s) >= 2:
|
||||
s = s[1:-1]
|
||||
# 去除单引号
|
||||
elif s.startswith("'") and s.endswith("'") and len(s) >= 2:
|
||||
s = s[1:-1]
|
||||
|
||||
return s
|
Loading…
x
Reference in New Issue
Block a user