1052 lines
44 KiB
Python
1052 lines
44 KiB
Python
|
#!/usr/bin/env python
|
|||
|
# -*- coding: utf-8 -*-
|
|||
|
|
|||
|
"""
|
|||
|
VWED.task 模块 - 任务系统集成
|
|||
|
"""
|
|||
|
|
|||
|
import json
|
|||
|
from typing import Dict, Any, Optional, List
|
|||
|
from utils.logger import get_logger
|
|||
|
from utils.json_parser import safe_parse_dict, safe_parse_list
|
|||
|
|
|||
|
|
|||
|
class VWEDTaskModule:
|
|||
|
"""VWED.task 模块 - 任务系统集成"""
|
|||
|
|
|||
|
def __init__(self, script_id: str):
|
|||
|
self.script_id = script_id
|
|||
|
self.logger = get_logger(f"script.{script_id}.task")
|
|||
|
|
|||
|
async def _get_auth_token(self, auto_get_token: bool = True) -> Optional[str]:
|
|||
|
"""获取认证token的通用方法
|
|||
|
|
|||
|
Args:
|
|||
|
auto_get_token: 是否自动获取登录token,默认true
|
|||
|
|
|||
|
Returns:
|
|||
|
Optional[str]: 认证令牌,获取失败返回None
|
|||
|
"""
|
|||
|
if not auto_get_token:
|
|||
|
return None
|
|||
|
|
|||
|
try:
|
|||
|
from services.sync_service import refresh_token_if_needed
|
|||
|
token = await refresh_token_if_needed()
|
|||
|
if token:
|
|||
|
self.logger.info(f"脚本 {self.script_id} 成功获取到登录token")
|
|||
|
return token
|
|||
|
else:
|
|||
|
self.logger.warning(f"脚本 {self.script_id} 获取登录token失败,继续使用空token")
|
|||
|
return None
|
|||
|
except Exception as e:
|
|||
|
self.logger.warning(f"脚本 {self.script_id} 获取token异常: {str(e)},继续使用空token")
|
|||
|
return None
|
|||
|
|
|||
|
async def create_task(
|
|||
|
self,
|
|||
|
label: str,
|
|||
|
task_type: int = 1,
|
|||
|
remark: Optional[str] = None,
|
|||
|
period: Optional[int] = None,
|
|||
|
delay: Optional[int] = 3000,
|
|||
|
release_sites: Optional[bool] = True,
|
|||
|
tenant_id: str = "default",
|
|||
|
map_id: Optional[str] = None,
|
|||
|
auto_get_token: bool = True
|
|||
|
) -> Dict[str, Any]:
|
|||
|
"""创建VWED任务
|
|||
|
|
|||
|
Args:
|
|||
|
label: 任务名称
|
|||
|
task_type: 任务类型,1-普通任务,2-定时任务
|
|||
|
remark: 任务备注
|
|||
|
period: 周期时间(毫秒),定时任务必填
|
|||
|
delay: 延迟时间(毫秒),默认3000
|
|||
|
release_sites: 是否释放站点,默认true
|
|||
|
tenant_id: 租户ID,默认"default"
|
|||
|
map_id: 相关地图ID
|
|||
|
auto_get_token: 是否自动获取登录token,默认true
|
|||
|
|
|||
|
Returns:
|
|||
|
Dict[str, Any]: 创建结果
|
|||
|
"""
|
|||
|
try:
|
|||
|
from services.task_service import TaskService, TaskNameExistsError
|
|||
|
from data.session import get_db
|
|||
|
|
|||
|
# 获取登录token
|
|||
|
token = await self._get_auth_token(auto_get_token)
|
|||
|
|
|||
|
# 获取数据库会话
|
|||
|
db = next(get_db())
|
|||
|
|
|||
|
try:
|
|||
|
# 调用TaskService.create_task方法
|
|||
|
result = TaskService.create_task(
|
|||
|
db=db,
|
|||
|
label=label,
|
|||
|
task_type=task_type,
|
|||
|
remark=remark,
|
|||
|
period=period,
|
|||
|
delay=delay,
|
|||
|
release_sites=release_sites,
|
|||
|
token=token, # 使用获取到的token
|
|||
|
tenant_id=tenant_id,
|
|||
|
map_id=map_id
|
|||
|
)
|
|||
|
|
|||
|
self.logger.info(f"脚本 {self.script_id} 成功创建任务: {label} (ID: {result['id']})")
|
|||
|
return {
|
|||
|
"success": True,
|
|||
|
"message": "任务创建成功",
|
|||
|
"data": result
|
|||
|
}
|
|||
|
|
|||
|
except TaskNameExistsError as e:
|
|||
|
self.logger.warning(f"脚本 {self.script_id} 创建任务失败: {str(e)}")
|
|||
|
return {
|
|||
|
"success": False,
|
|||
|
"message": f"任务名称已存在: {str(e)}",
|
|||
|
"error_type": "name_exists"
|
|||
|
}
|
|||
|
except Exception as e:
|
|||
|
self.logger.error(f"脚本 {self.script_id} 创建任务失败: {str(e)}")
|
|||
|
return {
|
|||
|
"success": False,
|
|||
|
"message": f"创建任务失败: {str(e)}",
|
|||
|
"error_type": "create_error"
|
|||
|
}
|
|||
|
finally:
|
|||
|
db.close()
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
self.logger.error(f"脚本 {self.script_id} 任务创建异常: {str(e)}")
|
|||
|
return {
|
|||
|
"success": False,
|
|||
|
"message": f"任务创建异常: {str(e)}",
|
|||
|
"error_type": "system_error"
|
|||
|
}
|
|||
|
|
|||
|
async def execute_task(
|
|||
|
self,
|
|||
|
task_id: str,
|
|||
|
parameters: Optional[List[Dict[str, Any]]] = None,
|
|||
|
source_system: str = "script",
|
|||
|
source_device: Optional[str] = None,
|
|||
|
auto_get_token: bool = True
|
|||
|
) -> Dict[str, Any]:
|
|||
|
"""执行VWED任务
|
|||
|
|
|||
|
Args:
|
|||
|
task_id: 任务ID
|
|||
|
parameters: 任务输入参数列表
|
|||
|
source_system: 来源系统,默认"script"
|
|||
|
source_device: 来源设备标识
|
|||
|
auto_get_token: 是否自动获取登录token,默认true
|
|||
|
|
|||
|
Returns:
|
|||
|
Dict[str, Any]: 执行结果
|
|||
|
"""
|
|||
|
try:
|
|||
|
from services.task_edit_service import TaskEditService
|
|||
|
from routes.model.task_edit_model import TaskEditRunRequest, TaskInputParam
|
|||
|
from data.enum.task_record_enum import SourceType
|
|||
|
|
|||
|
# 获取登录token
|
|||
|
token = await self._get_auth_token(auto_get_token)
|
|||
|
|
|||
|
# 构建运行请求
|
|||
|
input_params = []
|
|||
|
if parameters:
|
|||
|
for param in parameters:
|
|||
|
if isinstance(param, dict) and "name" in param and "defaultValue" in param:
|
|||
|
input_params.append(TaskInputParam(
|
|||
|
name=param["name"],
|
|||
|
defaultValue=param["defaultValue"]
|
|||
|
))
|
|||
|
|
|||
|
run_request = TaskEditRunRequest(
|
|||
|
taskId=task_id,
|
|||
|
params=input_params,
|
|||
|
source_type=SourceType.SYSTEM_SCHEDULING,
|
|||
|
source_system=source_system,
|
|||
|
source_device=source_device or f"script_{self.script_id}"
|
|||
|
)
|
|||
|
|
|||
|
# 调用TaskEditService.run_task方法
|
|||
|
result = await TaskEditService.run_task(
|
|||
|
run_request=run_request,
|
|||
|
client_ip="127.0.0.1", # 本地脚本调用
|
|||
|
client_info=f'{{"script_id": "{self.script_id}", "source": "VWED.task.execute_task"}}',
|
|||
|
tf_api_token=token # 使用获取到的token
|
|||
|
)
|
|||
|
|
|||
|
if result and result.get("success"):
|
|||
|
self.logger.info(f"脚本 {self.script_id} 成功启动任务: {task_id}")
|
|||
|
return {
|
|||
|
"success": True,
|
|||
|
"message": "任务启动成功",
|
|||
|
"data": result
|
|||
|
}
|
|||
|
else:
|
|||
|
error_msg = result.get("message", "任务启动失败") if result else "任务启动失败"
|
|||
|
self.logger.error(f"脚本 {self.script_id} 启动任务失败: {error_msg}")
|
|||
|
return {
|
|||
|
"success": False,
|
|||
|
"message": error_msg,
|
|||
|
"error_type": "execute_error"
|
|||
|
}
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
self.logger.error(f"脚本 {self.script_id} 执行任务异常: {str(e)}")
|
|||
|
return {
|
|||
|
"success": False,
|
|||
|
"message": f"执行任务异常: {str(e)}",
|
|||
|
"error_type": "system_error"
|
|||
|
}
|
|||
|
|
|||
|
async def execute_task_with_record_id(
|
|||
|
self,
|
|||
|
task_id: str,
|
|||
|
task_record_id: Optional[str] = None,
|
|||
|
parameters: Optional[List[Dict[str, Any]]] = None,
|
|||
|
source_system: str = "script",
|
|||
|
source_device: Optional[str] = None,
|
|||
|
auto_get_token: bool = True
|
|||
|
) -> Dict[str, Any]:
|
|||
|
"""执行VWED任务,支持指定task_record_id
|
|||
|
|
|||
|
Args:
|
|||
|
task_id: 任务ID
|
|||
|
task_record_id: 指定的任务记录ID,为空时自动生成
|
|||
|
parameters: 任务输入参数列表
|
|||
|
source_system: 来源系统,默认"script"
|
|||
|
source_device: 来源设备标识
|
|||
|
auto_get_token: 是否自动获取登录token,默认true
|
|||
|
|
|||
|
Returns:
|
|||
|
Dict[str, Any]: 执行结果
|
|||
|
"""
|
|||
|
try:
|
|||
|
from services.task_edit_service import TaskEditService
|
|||
|
from routes.model.task_edit_model import TaskEditRunRequest, TaskInputParamNew
|
|||
|
from data.enum.task_record_enum import SourceType
|
|||
|
|
|||
|
# 获取登录token
|
|||
|
token = await self._get_auth_token(auto_get_token)
|
|||
|
|
|||
|
# 构建运行请求
|
|||
|
input_params = []
|
|||
|
if parameters:
|
|||
|
for param in parameters:
|
|||
|
if isinstance(param, dict) and "name" in param and "defaultValue" in param:
|
|||
|
input_params.append(TaskInputParamNew(
|
|||
|
name=param["name"],
|
|||
|
type="STRING", # 默认字符串类型
|
|||
|
label=param["name"],
|
|||
|
defaultValue=param["defaultValue"]
|
|||
|
))
|
|||
|
|
|||
|
run_request = TaskEditRunRequest(
|
|||
|
taskId=task_id,
|
|||
|
params=input_params,
|
|||
|
source_type=SourceType.SYSTEM_SCHEDULING,
|
|||
|
source_system=source_system,
|
|||
|
source_device=source_device or f"script_{self.script_id}",
|
|||
|
task_record_id=task_record_id # 传递指定的task_record_id
|
|||
|
)
|
|||
|
|
|||
|
# 调用TaskEditService.run_task方法
|
|||
|
result = await TaskEditService.run_task(
|
|||
|
run_request=run_request,
|
|||
|
client_ip="127.0.0.1", # 本地脚本调用
|
|||
|
client_info=f'{{"script_id": "{self.script_id}", "source": "VWED.task.execute_task_with_record_id"}}',
|
|||
|
tf_api_token=token # 使用获取到的token
|
|||
|
)
|
|||
|
|
|||
|
if result and result.get("success"):
|
|||
|
self.logger.info(f"脚本 {self.script_id} 成功启动任务: {task_id} (记录ID: {result.get('taskRecordId', task_record_id)})")
|
|||
|
return {
|
|||
|
"success": True,
|
|||
|
"message": "任务启动成功",
|
|||
|
"data": result
|
|||
|
}
|
|||
|
else:
|
|||
|
error_msg = result.get("message", "任务启动失败") if result else "任务启动失败"
|
|||
|
self.logger.error(f"脚本 {self.script_id} 启动任务失败: {error_msg}")
|
|||
|
return {
|
|||
|
"success": False,
|
|||
|
"message": error_msg,
|
|||
|
"error_type": "execute_error"
|
|||
|
}
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
self.logger.error(f"脚本 {self.script_id} 执行任务异常: {str(e)}")
|
|||
|
return {
|
|||
|
"success": False,
|
|||
|
"message": f"执行任务异常: {str(e)}",
|
|||
|
"error_type": "system_error"
|
|||
|
}
|
|||
|
|
|||
|
async def get_task_status(self, task_record_id: str) -> Dict[str, Any]:
|
|||
|
"""获取任务执行状态
|
|||
|
|
|||
|
Args:
|
|||
|
task_record_id: 任务记录ID(不是任务定义ID)
|
|||
|
|
|||
|
Returns:
|
|||
|
Dict[str, Any]: 任务状态信息
|
|||
|
"""
|
|||
|
try:
|
|||
|
from data.session import get_async_session
|
|||
|
from data.models.taskrecord import VWEDTaskRecord
|
|||
|
from sqlalchemy import select
|
|||
|
|
|||
|
async with get_async_session() as session:
|
|||
|
result = await session.execute(
|
|||
|
select(VWEDTaskRecord).where(VWEDTaskRecord.id == task_record_id)
|
|||
|
)
|
|||
|
task_record = result.scalar_one_or_none()
|
|||
|
|
|||
|
if not task_record:
|
|||
|
return {
|
|||
|
"success": False,
|
|||
|
"message": "任务记录不存在",
|
|||
|
"error_type": "not_found"
|
|||
|
}
|
|||
|
|
|||
|
# 构建状态信息
|
|||
|
status_info = {
|
|||
|
"task_record_id": task_record.id,
|
|||
|
"task_def_id": task_record.task_def_id,
|
|||
|
"status": task_record.status,
|
|||
|
"progress": task_record.progress,
|
|||
|
"start_time": task_record.start_time.isoformat() if task_record.start_time else None,
|
|||
|
"end_time": task_record.end_time.isoformat() if task_record.end_time else None,
|
|||
|
"error_message": task_record.error_message,
|
|||
|
"variables": task_record.variables
|
|||
|
}
|
|||
|
|
|||
|
self.logger.info(f"脚本 {self.script_id} 获取任务状态: {task_record_id}")
|
|||
|
return {
|
|||
|
"success": True,
|
|||
|
"message": "获取任务状态成功",
|
|||
|
"data": status_info
|
|||
|
}
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
self.logger.error(f"脚本 {self.script_id} 获取任务状态异常: {str(e)}")
|
|||
|
return {
|
|||
|
"success": False,
|
|||
|
"message": f"获取任务状态异常: {str(e)}",
|
|||
|
"error_type": "system_error"
|
|||
|
}
|
|||
|
|
|||
|
async def check_task_param(self, param: str) -> bool:
|
|||
|
"""校验任务参数
|
|||
|
|
|||
|
校验输入参数中的 task_id 和 task_label 字段对应的数据是否在数据库中存在。
|
|||
|
|
|||
|
Args:
|
|||
|
param: JSON字符串或Python字典字符串,包含task_id和task_label字段
|
|||
|
|
|||
|
Returns:
|
|||
|
bool: True表示数据存在,False表示数据不存在
|
|||
|
"""
|
|||
|
try:
|
|||
|
# 使用通用解析方法
|
|||
|
data = safe_parse_dict(param, self.script_id)
|
|||
|
if data is None:
|
|||
|
return False
|
|||
|
|
|||
|
task_id = data.get("task_id", "")
|
|||
|
task_label = data.get("task_label", "")
|
|||
|
|
|||
|
# 如果task_id和task_label都为空,返回False
|
|||
|
if not task_id and not task_label:
|
|||
|
self.logger.warning(f"脚本 {self.script_id} task_id和task_label都为空")
|
|||
|
return False
|
|||
|
|
|||
|
from data.session import get_async_session
|
|||
|
from data.models.taskdef import VWEDTaskDef
|
|||
|
from sqlalchemy import select, or_
|
|||
|
|
|||
|
async with get_async_session() as session:
|
|||
|
# 构建查询条件:task_id或task_label匹配即可
|
|||
|
conditions = []
|
|||
|
if task_id:
|
|||
|
conditions.append(VWEDTaskDef.id == task_id)
|
|||
|
if task_label:
|
|||
|
conditions.append(VWEDTaskDef.label == task_label)
|
|||
|
|
|||
|
# 查询数据库
|
|||
|
query = select(VWEDTaskDef.id).where(
|
|||
|
or_(*conditions),
|
|||
|
VWEDTaskDef.is_deleted == False
|
|||
|
)
|
|||
|
result = await session.execute(query)
|
|||
|
task_def = result.scalar_one_or_none()
|
|||
|
|
|||
|
exists = task_def is not None
|
|||
|
self.logger.info(f"脚本 {self.script_id} 任务参数校验结果: task_id={task_id}, task_label={task_label}, exists={exists}")
|
|||
|
return exists
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
self.logger.error(f"脚本 {self.script_id} 校验任务参数异常: {str(e)}")
|
|||
|
return False
|
|||
|
|
|||
|
async def create_wind_task(self, param: str) -> str:
|
|||
|
"""异步创建并运行一个任务
|
|||
|
|
|||
|
本方法是非阻塞方法,将在脚本中创建一个线程,来执行天风任务的实例。
|
|||
|
|
|||
|
Args:
|
|||
|
param: JSON字符串,包含任务创建参数
|
|||
|
|
|||
|
Returns:
|
|||
|
str: JSON格式的创建结果
|
|||
|
"""
|
|||
|
try:
|
|||
|
# 使用通用解析方法
|
|||
|
data = safe_parse_dict(param, self.script_id)
|
|||
|
if data is None:
|
|||
|
return ""
|
|||
|
task_id = data.get("task_id")
|
|||
|
task_label = data.get("task_label")
|
|||
|
task_record_id = data.get("task_record_id")
|
|||
|
input_params = data.get("input_params", [])
|
|||
|
|
|||
|
# 验证必选参数
|
|||
|
if not task_id and not task_label:
|
|||
|
return json.dumps({"code": -1, "msg": "task_id和task_label至少需要提供一个"}, ensure_ascii=False)
|
|||
|
|
|||
|
# 解析输入参数
|
|||
|
parameters = []
|
|||
|
if input_params:
|
|||
|
# 如果input_params是字符串,尝试解析为JSON
|
|||
|
if isinstance(input_params, str):
|
|||
|
try:
|
|||
|
input_params_data = json.loads(input_params)
|
|||
|
except:
|
|||
|
input_params_data = []
|
|||
|
else:
|
|||
|
# 如果input_params已经是列表,直接使用
|
|||
|
input_params_data = input_params
|
|||
|
|
|||
|
# 处理参数列表,保留所有字段信息
|
|||
|
if isinstance(input_params_data, list):
|
|||
|
# 如果是完整的参数配置列表:[{"name": "param1", "label": "参数1", "type": "STRING", "defaultValue": "", ...}, ...]
|
|||
|
for param in input_params_data:
|
|||
|
if isinstance(param, dict) and "name" in param:
|
|||
|
# 保留完整的参数配置信息
|
|||
|
param_config = {
|
|||
|
"name": param["name"],
|
|||
|
"defaultValue": str(param.get("defaultValue", ""))
|
|||
|
}
|
|||
|
# 如果有其他字段也保留
|
|||
|
if "label" in param:
|
|||
|
param_config["label"] = param["label"]
|
|||
|
if "type" in param:
|
|||
|
param_config["type"] = param["type"]
|
|||
|
if "required" in param:
|
|||
|
param_config["required"] = param["required"]
|
|||
|
if "remark" in param:
|
|||
|
param_config["remark"] = param["remark"]
|
|||
|
if "checked" in param:
|
|||
|
param_config["checked"] = param["checked"]
|
|||
|
|
|||
|
parameters.append(param_config)
|
|||
|
elif isinstance(input_params_data, dict):
|
|||
|
# 如果是简单的键值对格式:{"param1": "value1", "param2": "value2"}
|
|||
|
for key, value in input_params_data.items():
|
|||
|
parameters.append({
|
|||
|
"name": key,
|
|||
|
"defaultValue": str(value)
|
|||
|
})
|
|||
|
|
|||
|
# 使用task_id或task_label查找任务并执行
|
|||
|
target_task_id = task_id if task_id else None
|
|||
|
if not target_task_id and task_label:
|
|||
|
# 根据task_label查找task_id的逻辑
|
|||
|
from data.session import get_async_session
|
|||
|
from data.models.taskdef import VWEDTaskDef
|
|||
|
from sqlalchemy import select
|
|||
|
|
|||
|
async with get_async_session() as session:
|
|||
|
result = await session.execute(select(VWEDTaskDef.id).where(
|
|||
|
VWEDTaskDef.label == task_label,
|
|||
|
VWEDTaskDef.is_deleted == False
|
|||
|
))
|
|||
|
task_def = result.scalar_one_or_none()
|
|||
|
if task_def:
|
|||
|
target_task_id = task_def
|
|||
|
|
|||
|
if not target_task_id:
|
|||
|
return json.dumps({"code": -1, "msg": "未找到指定的任务"}, ensure_ascii=False)
|
|||
|
|
|||
|
# 如果提供了task_record_id,检查是否已存在
|
|||
|
if task_record_id and await self.is_task_record_id_exist(task_record_id):
|
|||
|
return json.dumps({"code": -1, "msg": f"任务记录ID {task_record_id} 已存在"}, ensure_ascii=False)
|
|||
|
|
|||
|
# 执行任务
|
|||
|
result = await self.execute_task_with_record_id(
|
|||
|
task_id=target_task_id,
|
|||
|
task_record_id=task_record_id,
|
|||
|
parameters=parameters,
|
|||
|
source_system="script",
|
|||
|
source_device=f"script_{self.script_id}"
|
|||
|
)
|
|||
|
|
|||
|
if result.get("success"):
|
|||
|
return json.dumps({"code": 200, "msg": "任务创建成功"}, ensure_ascii=False)
|
|||
|
else:
|
|||
|
return json.dumps({"code": -1, "msg": result.get("message", "任务创建失败")}, ensure_ascii=False)
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
self.logger.error(f"脚本 {self.script_id} 创建任务异常: {str(e)}")
|
|||
|
return json.dumps({"code": -1, "msg": f"创建任务异常: {str(e)}"}, ensure_ascii=False)
|
|||
|
|
|||
|
async def get_task_record_by_id(self, task_record_id: str) -> str:
|
|||
|
"""根据任务实例ID查询任务实例
|
|||
|
|
|||
|
Args:
|
|||
|
task_record_id: 任务实例的唯一ID
|
|||
|
|
|||
|
Returns:
|
|||
|
str: JSON格式的任务实例信息,不存在则返回"null"
|
|||
|
"""
|
|||
|
try:
|
|||
|
from data.session import get_async_session
|
|||
|
from data.models.taskrecord import VWEDTaskRecord
|
|||
|
from sqlalchemy import select
|
|||
|
|
|||
|
async with get_async_session() as session:
|
|||
|
result = await session.execute(
|
|||
|
select(VWEDTaskRecord).where(VWEDTaskRecord.id == task_record_id)
|
|||
|
)
|
|||
|
task_record = result.scalar_one_or_none()
|
|||
|
|
|||
|
if not task_record:
|
|||
|
return "null"
|
|||
|
|
|||
|
# 构建返回数据,按照文档格式
|
|||
|
task_info = {
|
|||
|
"id": task_record.id,
|
|||
|
"def_id": task_record.def_id,
|
|||
|
"def_label": task_record.def_label,
|
|||
|
"status": task_record.status,
|
|||
|
"created_on": task_record.created_at.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3] if task_record.created_at else None,
|
|||
|
"ended_on": task_record.ended_on.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3] if task_record.ended_on else None,
|
|||
|
"state_description": task_record.state_description or "",
|
|||
|
"agv_id": task_record.agv_id or ""
|
|||
|
}
|
|||
|
|
|||
|
return json.dumps(task_info, ensure_ascii=False)
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
self.logger.error(f"脚本 {self.script_id} 查询任务实例异常: {str(e)}")
|
|||
|
raise e
|
|||
|
|
|||
|
# def get_task_record_list_by_out_order_no(self, out_order_no: str) -> str:
|
|||
|
# """根据外部单号查询任务实例列表
|
|||
|
#
|
|||
|
# Args:
|
|||
|
# out_order_no: 外部单号ID
|
|||
|
#
|
|||
|
# Returns:
|
|||
|
# str: JSON数组格式的任务实例列表
|
|||
|
# """
|
|||
|
# try:
|
|||
|
# # 暂时返回空数组,因为当前表结构中没有外部订单号字段
|
|||
|
# # 需要在VWEDTaskRecord模型中添加out_order_no字段
|
|||
|
# return "[]"
|
|||
|
#
|
|||
|
# except Exception as e:
|
|||
|
# self.logger.error(f"脚本 {self.script_id} 根据外部单号查询任务异常: {str(e)}")
|
|||
|
# raise e
|
|||
|
|
|||
|
async def get_task_record_by_agv_id(self, agv_id: str) -> str:
|
|||
|
"""根据机器人ID查询任务实例列表
|
|||
|
|
|||
|
Args:
|
|||
|
agv_id: 机器人ID
|
|||
|
|
|||
|
Returns:
|
|||
|
str: JSON数组格式的任务实例列表
|
|||
|
"""
|
|||
|
try:
|
|||
|
from data.session import get_async_session
|
|||
|
from data.models.taskrecord import VWEDTaskRecord
|
|||
|
from sqlalchemy import select
|
|||
|
|
|||
|
async with get_async_session() as session:
|
|||
|
result = await session.execute(
|
|||
|
select(VWEDTaskRecord).where(VWEDTaskRecord.agv_id == agv_id)
|
|||
|
)
|
|||
|
task_records = result.scalars().all()
|
|||
|
|
|||
|
if not task_records:
|
|||
|
return "[]"
|
|||
|
|
|||
|
# 构建返回数据列表
|
|||
|
task_list = []
|
|||
|
for task_record in task_records:
|
|||
|
task_info = {
|
|||
|
"id": task_record.id,
|
|||
|
"def_id": task_record.def_id,
|
|||
|
"def_label": task_record.def_label,
|
|||
|
"status": task_record.status,
|
|||
|
"created_on": task_record.created_at.strftime("%Y-%m-%d %H:%M:%S") if task_record.created_at else None,
|
|||
|
"ended_reason": task_record.ended_reason or "运行结束",
|
|||
|
"ended_on": task_record.ended_on.strftime("%Y-%m-%d %H:%M:%S") if task_record.ended_on else None,
|
|||
|
"state_description": task_record.state_description or "",
|
|||
|
}
|
|||
|
task_list.append(task_info)
|
|||
|
|
|||
|
return json.dumps(task_list, ensure_ascii=False)
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
self.logger.error(f"脚本 {self.script_id} 根据机器人ID查询任务异常: {str(e)}")
|
|||
|
raise e
|
|||
|
|
|||
|
async def mark_complete(self, task_id: str, auto_get_token: bool = True) -> Dict[str, Any]:
|
|||
|
"""运行任务封口
|
|||
|
|
|||
|
|
|||
|
Args:
|
|||
|
task_id: VWED任务系统任务ID
|
|||
|
auto_get_token: 是否自动获取登录token,默认true
|
|||
|
|
|||
|
Returns:
|
|||
|
Dict[str, Any]: 封口结果
|
|||
|
"""
|
|||
|
try:
|
|||
|
# 获取认证token
|
|||
|
token = await self._get_auth_token(auto_get_token)
|
|||
|
|
|||
|
# 调用 closure_task 接口
|
|||
|
from services.sync_service import closure_task
|
|||
|
|
|||
|
result = await closure_task(task_id=task_id, token=token)
|
|||
|
|
|||
|
if result and result.get("success"):
|
|||
|
self.logger.info(f"脚本 {self.script_id} 成功封口任务: {task_id}")
|
|||
|
return {
|
|||
|
"success": True,
|
|||
|
"message": "任务封口成功",
|
|||
|
"data": result
|
|||
|
}
|
|||
|
else:
|
|||
|
error_msg = result.get("message", "任务封口失败") if result else "任务封口失败"
|
|||
|
self.logger.error(f"脚本 {self.script_id} 封口任务失败: {task_id}, 错误: {error_msg}")
|
|||
|
return {
|
|||
|
"success": False,
|
|||
|
"message": error_msg,
|
|||
|
"error_type": "closure_error"
|
|||
|
}
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
self.logger.error(f"脚本 {self.script_id} 标记运单完成异常: {str(e)}")
|
|||
|
return {
|
|||
|
"success": False,
|
|||
|
"message": f"标记运单完成异常: {str(e)}",
|
|||
|
"error_type": "system_error"
|
|||
|
}
|
|||
|
|
|||
|
async def is_task_record_id_exist(self, task_record_id: str) -> bool:
|
|||
|
"""校验任务实例ID是否存在
|
|||
|
|
|||
|
Args:
|
|||
|
task_record_id: 任务实例id
|
|||
|
|
|||
|
Returns:
|
|||
|
bool: True表示存在,False表示不存在
|
|||
|
"""
|
|||
|
try:
|
|||
|
from data.session import get_async_session
|
|||
|
from data.models.taskrecord import VWEDTaskRecord
|
|||
|
from sqlalchemy import select
|
|||
|
|
|||
|
async with get_async_session() as session:
|
|||
|
result = await session.execute(
|
|||
|
select(VWEDTaskRecord.id).where(VWEDTaskRecord.id == task_record_id)
|
|||
|
)
|
|||
|
task_record = result.scalar_one_or_none()
|
|||
|
return task_record is not None
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
self.logger.error(f"脚本 {self.script_id} 校验任务实例ID异常: {str(e)}")
|
|||
|
raise e
|
|||
|
|
|||
|
async def is_task_label_exist(self, task_name: str) -> bool:
|
|||
|
"""校验任务名称是否存在
|
|||
|
|
|||
|
Args:
|
|||
|
task_name: 任务名称
|
|||
|
|
|||
|
Returns:
|
|||
|
bool: True表示存在,False表示不存在
|
|||
|
"""
|
|||
|
try:
|
|||
|
from data.session import get_async_session
|
|||
|
from data.models.taskdef import VWEDTaskDef
|
|||
|
from sqlalchemy import select
|
|||
|
|
|||
|
async with get_async_session() as session:
|
|||
|
result = await session.execute(
|
|||
|
select(VWEDTaskDef.id).where(
|
|||
|
VWEDTaskDef.label == task_name,
|
|||
|
VWEDTaskDef.is_deleted == False
|
|||
|
)
|
|||
|
)
|
|||
|
task_def = result.scalar_one_or_none()
|
|||
|
return task_def is not None
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
self.logger.error(f"脚本 {self.script_id} 校验任务名称异常: {str(e)}")
|
|||
|
raise e
|
|||
|
|
|||
|
async def query_task_record(self, param: str) -> str:
|
|||
|
"""根据查询条件获取任务实例
|
|||
|
|
|||
|
Args:
|
|||
|
param: JSON字符串,包含查询条件
|
|||
|
|
|||
|
Returns:
|
|||
|
str: JSON数组格式的任务实例列表
|
|||
|
"""
|
|||
|
try:
|
|||
|
data = safe_parse_dict(param, self.script_id)
|
|||
|
if data is None:
|
|||
|
return "[]"
|
|||
|
|
|||
|
from data.session import get_async_session
|
|||
|
from data.models.taskrecord import VWEDTaskRecord
|
|||
|
from sqlalchemy import select, and_
|
|||
|
from datetime import datetime
|
|||
|
|
|||
|
async with get_async_session() as session:
|
|||
|
# 构建查询条件
|
|||
|
conditions = []
|
|||
|
|
|||
|
# 任务ID条件
|
|||
|
if data.get("task_id"):
|
|||
|
conditions.append(VWEDTaskRecord.def_id == data["task_id"])
|
|||
|
|
|||
|
# 任务实例ID条件
|
|||
|
if data.get("task_record_id"):
|
|||
|
conditions.append(VWEDTaskRecord.id == data["task_record_id"])
|
|||
|
|
|||
|
# 状态条件
|
|||
|
if data.get("status"):
|
|||
|
conditions.append(VWEDTaskRecord.status == int(data["status"]))
|
|||
|
|
|||
|
# 机器人ID条件
|
|||
|
if data.get("agv_id"):
|
|||
|
conditions.append(VWEDTaskRecord.agv_id == data["agv_id"])
|
|||
|
|
|||
|
# 时间范围条件
|
|||
|
if data.get("start_date"):
|
|||
|
start_time = datetime.strptime(data["start_date"], "%Y-%m-%d %H:%M:%S")
|
|||
|
conditions.append(VWEDTaskRecord.created_at >= start_time)
|
|||
|
|
|||
|
if data.get("end_date"):
|
|||
|
end_time = datetime.strptime(data["end_date"], "%Y-%m-%d %H:%M:%S")
|
|||
|
conditions.append(VWEDTaskRecord.created_at <= end_time)
|
|||
|
|
|||
|
# 执行查询
|
|||
|
query = select(VWEDTaskRecord)
|
|||
|
if conditions:
|
|||
|
query = query.where(and_(*conditions))
|
|||
|
|
|||
|
result = await session.execute(query)
|
|||
|
task_records = result.scalars().all()
|
|||
|
|
|||
|
if not task_records:
|
|||
|
return "[]"
|
|||
|
|
|||
|
# 构建返回数据列表
|
|||
|
task_list = []
|
|||
|
for task_record in task_records:
|
|||
|
task_info = {
|
|||
|
"id": task_record.id,
|
|||
|
"def_id": task_record.def_id,
|
|||
|
"def_label": task_record.def_label,
|
|||
|
"status": task_record.status,
|
|||
|
"created_on": task_record.created_at.strftime("%Y-%m-%d %H:%M:%S") if task_record.created_at else None,
|
|||
|
"ended_reason": task_record.ended_reason or "运行结束",
|
|||
|
"ended_on": task_record.ended_on.strftime("%Y-%m-%d %H:%M:%S") if task_record.ended_on else None,
|
|||
|
"state_description": task_record.state_description or "",
|
|||
|
}
|
|||
|
task_list.append(task_info)
|
|||
|
|
|||
|
return json.dumps(task_list, ensure_ascii=False)
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
self.logger.error(f"脚本 {self.script_id} 查询任务实例异常: {str(e)}")
|
|||
|
raise e
|
|||
|
|
|||
|
# def receive_third_order(self, order_param: str) -> bool:
|
|||
|
# """接收并持久化第三方系统任务请求
|
|||
|
|
|||
|
# Args:
|
|||
|
# order_param: JSON字符串,第三方请求参数
|
|||
|
|
|||
|
# Returns:
|
|||
|
# bool: True表示成功,False表示失败
|
|||
|
# """
|
|||
|
# try:
|
|||
|
# # 这里需要实现第三方订单表的创建和插入逻辑
|
|||
|
# # 暂时返回True,表示接收成功
|
|||
|
# self.logger.info(f"脚本 {self.script_id} 接收第三方订单: {order_param}")
|
|||
|
# return True
|
|||
|
|
|||
|
# except Exception as e:
|
|||
|
# self.logger.error(f"脚本 {self.script_id} 接收第三方订单异常: {str(e)}")
|
|||
|
# return False
|
|||
|
|
|||
|
def save_task_log(self, log_param: str) -> None:
|
|||
|
"""保存任务运行时日志
|
|||
|
|
|||
|
新增RDS任务实例的运行日志,该日志将显示在任务实例的监控界面中。
|
|||
|
|
|||
|
Args:
|
|||
|
log_param: JSON字符串,包含日志信息
|
|||
|
"""
|
|||
|
try:
|
|||
|
data = safe_parse_dict(log_param, self.script_id)
|
|||
|
if data is None:
|
|||
|
return
|
|||
|
|
|||
|
# 记录到系统日志
|
|||
|
level = data.get("level", 1)
|
|||
|
message = data.get("message", "")
|
|||
|
task_id = data.get("task_id", "")
|
|||
|
task_record_id = data.get("task_record_id", "")
|
|||
|
block_id = data.get("block_id", "")
|
|||
|
|
|||
|
log_msg = f"任务日志 [任务ID:{task_id}] [实例ID:{task_record_id}] [块ID:{block_id}] {message}"
|
|||
|
|
|||
|
if level == 1: # 正常运行
|
|||
|
self.logger.info(log_msg)
|
|||
|
elif level == 2: # 正常停止
|
|||
|
self.logger.warning(log_msg)
|
|||
|
elif level == 3: # 异常中断
|
|||
|
self.logger.error(log_msg)
|
|||
|
else:
|
|||
|
self.logger.info(log_msg)
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
self.logger.error(f"脚本 {self.script_id} 保存任务日志异常: {str(e)}")
|
|||
|
|
|||
|
async def sync_create_wind_task(self, task_param: str) -> str:
|
|||
|
"""创建并运行一个任务
|
|||
|
|
|||
|
本方法是阻塞方法,可以在脚本中创建一个天风任务的实例,并在任务执行结束后返回。
|
|||
|
|
|||
|
Args:
|
|||
|
task_param: JSON字符串,包含任务创建参数
|
|||
|
|
|||
|
Returns:
|
|||
|
str: "9013"表示任务正常结束,"9014"表示任务异常结束
|
|||
|
"""
|
|||
|
try:
|
|||
|
# 使用create_wind_task的逻辑创建任务
|
|||
|
result_json = await self.create_wind_task(task_param)
|
|||
|
result = json.loads(result_json)
|
|||
|
|
|||
|
if result.get("code") != 200:
|
|||
|
return "9014"
|
|||
|
|
|||
|
# 解析参数获取task_record_id用于监控
|
|||
|
data = safe_parse_dict(task_param, self.script_id)
|
|||
|
if data is None:
|
|||
|
return "9014"
|
|||
|
|
|||
|
task_record_id = data.get("task_record_id")
|
|||
|
if not task_record_id:
|
|||
|
self.logger.warning(f"脚本 {self.script_id} 无法获取task_record_id,无法监控任务状态")
|
|||
|
return "9014"
|
|||
|
|
|||
|
# 等待任务完成
|
|||
|
return await self._wait_for_task_completion(task_record_id)
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
self.logger.error(f"脚本 {self.script_id} 同步创建任务异常: {str(e)}")
|
|||
|
return "9014"
|
|||
|
|
|||
|
async def _wait_for_task_completion(self, task_record_id: str, check_interval: int = 1, timeout: int = 3600) -> str:
|
|||
|
"""等待任务完成
|
|||
|
|
|||
|
Args:
|
|||
|
task_record_id: 任务记录ID
|
|||
|
check_interval: 检查间隔(秒)
|
|||
|
timeout: 超时时间(秒)
|
|||
|
|
|||
|
Returns:
|
|||
|
str: "9013"表示任务正常结束,"9014"表示任务异常结束
|
|||
|
"""
|
|||
|
import asyncio
|
|||
|
from data.enum.task_record_enum import TaskStatus
|
|||
|
|
|||
|
start_time = asyncio.get_event_loop().time()
|
|||
|
|
|||
|
while True:
|
|||
|
try:
|
|||
|
# 检查是否超时
|
|||
|
if asyncio.get_event_loop().time() - start_time > timeout:
|
|||
|
self.logger.warning(f"脚本 {self.script_id} 等待任务完成超时: {task_record_id}")
|
|||
|
return "9014"
|
|||
|
|
|||
|
# 获取任务状态
|
|||
|
status_result = await self.get_task_status(task_record_id)
|
|||
|
if not status_result.get("success"):
|
|||
|
self.logger.error(f"脚本 {self.script_id} 获取任务状态失败: {task_record_id}")
|
|||
|
return "9014"
|
|||
|
|
|||
|
task_data = status_result.get("data", {})
|
|||
|
status = task_data.get("status")
|
|||
|
|
|||
|
# 检查任务状态
|
|||
|
if status == TaskStatus.COMPLETED:
|
|||
|
self.logger.info(f"脚本 {self.script_id} 任务正常完成: {task_record_id}")
|
|||
|
return "9013"
|
|||
|
elif status in [TaskStatus.FAILED, TaskStatus.CANCELED]:
|
|||
|
self.logger.warning(f"脚本 {self.script_id} 任务异常结束: {task_record_id}, status: {status}")
|
|||
|
return "9014"
|
|||
|
elif status in [TaskStatus.RUNNING, TaskStatus.QUEUED, TaskStatus.PAUSED]:
|
|||
|
# 任务还在执行中,继续等待
|
|||
|
self.logger.debug(f"脚本 {self.script_id} 任务执行中: {task_record_id}, status: {status}")
|
|||
|
await asyncio.sleep(check_interval)
|
|||
|
else:
|
|||
|
# 未知状态
|
|||
|
self.logger.warning(f"脚本 {self.script_id} 任务状态未知: {task_record_id}, status: {status}")
|
|||
|
return "9014"
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
self.logger.error(f"脚本 {self.script_id} 监控任务状态异常: {task_record_id}, 错误: {str(e)}")
|
|||
|
return "9014"
|
|||
|
|
|||
|
async def terminate_tasks(self, param: str) -> None:
|
|||
|
"""终止任务
|
|||
|
|
|||
|
本方法是非阻塞方法,可用于终止多个正在运行的天风任务实例。
|
|||
|
|
|||
|
Args:
|
|||
|
param: JSON字符串,包含要终止的任务实例ID列表
|
|||
|
"""
|
|||
|
try:
|
|||
|
data = safe_parse_list(param, self.script_id)
|
|||
|
if data is None:
|
|||
|
return
|
|||
|
|
|||
|
# 获取认证token
|
|||
|
token = await self._get_auth_token(auto_get_token=True)
|
|||
|
|
|||
|
for task_info in data:
|
|||
|
task_record_id = task_info.get("task_record_id")
|
|||
|
if task_record_id:
|
|||
|
try:
|
|||
|
# 1. 调用调度器取消本地任务
|
|||
|
from services.enhanced_scheduler import scheduler
|
|||
|
scheduler_result = await scheduler.cancel_task(task_record_id)
|
|||
|
|
|||
|
# 2. 调用天风系统接口设置任务为终止状态
|
|||
|
from services.sync_service import set_task_terminated
|
|||
|
tf_result = await set_task_terminated(task_record_id, token)
|
|||
|
|
|||
|
# 记录结果
|
|||
|
if scheduler_result.get("success"):
|
|||
|
self.logger.info(f"脚本 {self.script_id} 成功在调度器中终止任务: {task_record_id}")
|
|||
|
else:
|
|||
|
self.logger.warning(f"脚本 {self.script_id} 调度器中终止任务失败: {task_record_id}")
|
|||
|
|
|||
|
if tf_result and tf_result.get("success"):
|
|||
|
self.logger.info(f"脚本 {self.script_id} 成功在天风系统中设置任务终止状态: {task_record_id}")
|
|||
|
else:
|
|||
|
self.logger.warning(f"脚本 {self.script_id} 天风系统中设置任务终止状态失败: {task_record_id}")
|
|||
|
|
|||
|
# 只要任一操作成功就认为整体成功
|
|||
|
if scheduler_result.get("success") or (tf_result and tf_result.get("success")):
|
|||
|
self.logger.info(f"脚本 {self.script_id} 任务终止操作完成: {task_record_id}")
|
|||
|
else:
|
|||
|
self.logger.error(f"脚本 {self.script_id} 任务终止操作全部失败: {task_record_id}")
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
self.logger.error(f"脚本 {self.script_id} 终止任务异常: {task_record_id}, 错误: {str(e)}")
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
self.logger.error(f"脚本 {self.script_id} 终止任务异常: {str(e)}")
|
|||
|
raise e
|
|||
|
|
|||
|
async def get_children_task_record_id(self, record_id: str) -> str:
|
|||
|
"""查询子任务的任务id列表
|
|||
|
|
|||
|
本方法是非阻塞方法,可根据父任务的id查询所有子任务的id。
|
|||
|
|
|||
|
Args:
|
|||
|
record_id: 父任务的record_id
|
|||
|
|
|||
|
Returns:
|
|||
|
str: 包含子任务id的JSON字符串
|
|||
|
"""
|
|||
|
try:
|
|||
|
from data.session import get_async_session
|
|||
|
from data.models.taskrecord import VWEDTaskRecord
|
|||
|
from sqlalchemy import select
|
|||
|
|
|||
|
async with get_async_session() as session:
|
|||
|
result = await session.execute(
|
|||
|
select(VWEDTaskRecord.id).where(VWEDTaskRecord.parent_task_record_id == record_id)
|
|||
|
)
|
|||
|
child_ids = [row[0] for row in result.fetchall()]
|
|||
|
|
|||
|
return json.dumps(child_ids, ensure_ascii=False)
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
self.logger.error(f"脚本 {self.script_id} 查询子任务ID列表异常: {str(e)}")
|
|||
|
raise e
|
|||
|
|
|||
|
async def set_task_priority(self, task_record_id: str, priority: int) -> None:
|
|||
|
"""设置任务优先级
|
|||
|
|
|||
|
本方法是非阻塞方法,可根据设置任务的优先级。
|
|||
|
|
|||
|
Args:
|
|||
|
task_record_id: 任务的实例id
|
|||
|
priority: 任务的优先级
|
|||
|
|
|||
|
Raises:
|
|||
|
Exception: 当任务记录ID不存在时抛出异常
|
|||
|
"""
|
|||
|
try:
|
|||
|
from data.session import get_async_session
|
|||
|
from data.models.taskrecord import VWEDTaskRecord
|
|||
|
from sqlalchemy import select, update
|
|||
|
|
|||
|
async with get_async_session() as session:
|
|||
|
# 首先查询任务记录是否存在
|
|||
|
result = await session.execute(
|
|||
|
select(VWEDTaskRecord.id).where(VWEDTaskRecord.id == task_record_id)
|
|||
|
)
|
|||
|
task_record = result.scalar_one_or_none()
|
|||
|
|
|||
|
if not task_record:
|
|||
|
error_msg = f"任务记录ID不存在: {task_record_id}"
|
|||
|
self.logger.error(f"脚本 {self.script_id} {error_msg}")
|
|||
|
raise Exception(error_msg)
|
|||
|
|
|||
|
# 更新任务优先级
|
|||
|
await session.execute(
|
|||
|
update(VWEDTaskRecord)
|
|||
|
.where(VWEDTaskRecord.id == task_record_id)
|
|||
|
.values(priority=priority)
|
|||
|
)
|
|||
|
await session.commit()
|
|||
|
|
|||
|
self.logger.info(f"脚本 {self.script_id} 设置任务优先级成功: {task_record_id} -> {priority}")
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
self.logger.error(f"脚本 {self.script_id} 设置任务优先级异常: {str(e)}")
|
|||
|
raise e
|