增加库位库区模块

This commit is contained in:
靳中伟 2025-08-13 15:27:04 +08:00
parent 1185f25fd3
commit 7ef023842b
84 changed files with 630327 additions and 153058 deletions

View File

@ -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
View File

@ -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()

Binary file not shown.

Binary file not shown.

View File

@ -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": []
}
]
}
]
}
],

View File

@ -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": []
}
],

View File

@ -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

View File

@ -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')

View File

@ -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 {

Binary file not shown.

Binary file not shown.

View File

@ -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'
]

View 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='任务代码TaskCodeGenAgvSchedulingTask专用')
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='来源IDSourceID')
target_id = Column(String(255), comment='目标IDTargetID')
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='位置代码路径JSONPositionCodePath')
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()

View File

@ -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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

2396
logs/app.log.2025-08-12 Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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="请求唯一标识码")

View File

@ -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")
# 重新定义更准确的任务参数模型,确保包含所有必要字段

View 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())

View File

@ -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

View 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 - 这是一条普通信息,不应该同步到主系统

View 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
View 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()

View File

@ -1,5 +0,0 @@
def name(a: int, b: int) -> int:
return a + b
def name1():
print('=====')

View File

@ -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)}"
}

View File

@ -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]:
"""
获取任务状态

View File

@ -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值需要解析

View File

@ -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

View File

@ -59,7 +59,7 @@ class RobotBlockName(StrEnum):
class ScriptBlockName(StrEnum):
"""脚本块名称枚举"""
SET_TASK_VARIABLES = "SetTaskVariablesBp" # 设置任务变量
SET_TASK_VARIABLES = "ScriptVariablesBp" # 设置任务变量
SCRIPT = "ScriptBp" # 脚本
class StorageBlockName(StrEnum):

View File

@ -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 = {

View File

@ -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

View File

@ -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)}")

View File

@ -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
}

View File

@ -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失败不应该影响主任务流程只记录日志

View 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

View File

@ -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}

View File

@ -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"

View File

@ -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):

View File

@ -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
View File

View File

@ -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)

Binary file not shown.

Binary file not shown.

View File

@ -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)

View File

@ -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