1108 lines
45 KiB
Python
1108 lines
45 KiB
Python
#!/usr/bin/env python
|
||
# -*- coding: utf-8 -*-
|
||
|
||
"""
|
||
外部任务接口API模块
|
||
提供外部系统调用的任务创建接口
|
||
"""
|
||
|
||
import json
|
||
import asyncio
|
||
import aiohttp
|
||
from typing import Dict, Any
|
||
from fastapi import APIRouter, Body, Request, Path
|
||
from routes.model.external_task_model import ExternalTaskRequest, ExternalTaskResponse, TaskTypeEnum, GenAgvSchedulingTaskRequest, CancelTaskRequest
|
||
from routes.model.task_edit_model import TaskEditRunRequest, TaskInputParamNew, InputParamType
|
||
from services.task_edit_service import TaskEditService
|
||
from services.external_task_record_service import ExternalTaskRecordService
|
||
from services.task_record_service import TaskRecordService
|
||
from services.sync_service import set_task_terminated
|
||
from routes.common_api import format_response, error_response
|
||
from utils.logger import get_logger
|
||
from data.enum.task_record_enum import SourceType, TaskStatus
|
||
from data.models.external_task_record import ExternalTaskStatusEnum
|
||
from config.tf_api_config import TF_API_TOKEN, TF_API_BASE_URL, CM_ID, DG_ID, TASK_TYPE_PRIORITY, TASK_TYPE_AREA, TF_WEB_POST, sync_disabled_label
|
||
|
||
# 创建路由
|
||
router = APIRouter(
|
||
prefix="",
|
||
tags=["外部任务接口"]
|
||
)
|
||
|
||
# 设置日志
|
||
logger = get_logger("app.external_task_api")
|
||
|
||
# 外部回调接口URL
|
||
EXTERNAL_CALLBACK_URL = "http://roh.vwfawedl.mobi:9001/AGVService/ContainerSendBackRequest" # 生产线到毛坯库任务
|
||
AGV_GOODS_MOVE_URL = "http://roh.vwfawedl.mobi:9001/AGVService/HUGoodsMoveRequest" # 毛坯库到产线任务
|
||
|
||
|
||
async def call_external_callback(arrival_no: str, arrival_user: str = "000307") -> bool:
|
||
"""
|
||
调用外部回调接口
|
||
|
||
Args:
|
||
arrival_no: 到货编号(ReqCode)
|
||
arrival_user: 到货用户,固定值 000307
|
||
|
||
Returns:
|
||
bool: 调用是否成功(返回result为0)
|
||
"""
|
||
payload = {
|
||
"arrival_no": arrival_no,
|
||
"arrival_user": arrival_user
|
||
}
|
||
|
||
max_retries = 1000 # 最大重试次数,防止无限循环
|
||
retry_count = 0
|
||
|
||
while retry_count < max_retries:
|
||
try:
|
||
async with aiohttp.ClientSession() as session:
|
||
async with session.post(EXTERNAL_CALLBACK_URL, json=payload) as response:
|
||
result = await response.json()
|
||
logger.info(f"外部接口调用响应: {result}, arrival_no={arrival_no}, 重试次数={retry_count}")
|
||
|
||
# 检查响应结果
|
||
if result.get("result") == "0":
|
||
logger.info(f"外部接口调用成功: arrival_no={arrival_no}, 总重试次数={retry_count}")
|
||
return True
|
||
elif result.get("result") == "1":
|
||
logger.info(f"外部接口返回result=1,继续重试: arrival_no={arrival_no}, 重试次数={retry_count}")
|
||
retry_count += 1
|
||
await asyncio.sleep(5) # 等待5秒后重试
|
||
else:
|
||
logger.error(f"外部接口返回异常结果: {result}, arrival_no={arrival_no}")
|
||
retry_count += 1
|
||
await asyncio.sleep(5)
|
||
|
||
except Exception as e:
|
||
logger.error(f"调用外部接口异常: {str(e)}, arrival_no={arrival_no}, 重试次数={retry_count}")
|
||
retry_count += 1
|
||
await asyncio.sleep(5) # 等待5秒后重试
|
||
|
||
logger.error(f"外部接口调用失败,已达到最大重试次数: arrival_no={arrival_no}, 最大重试次数={max_retries}")
|
||
return False
|
||
|
||
async def call_agv_goods_move_callback(pid: str, user_id: str = "000307") -> bool:
|
||
"""
|
||
调用AGV货物移动回调接口
|
||
|
||
Args:
|
||
pid: 对应的req_code
|
||
user_id: 用户ID,固定值 000307
|
||
|
||
Returns:
|
||
bool: 调用是否成功(返回result为0)
|
||
"""
|
||
payload = {
|
||
"PID": pid,
|
||
"UserID": user_id
|
||
}
|
||
|
||
max_retries = 1000 # 最大重试次数,防止无限循环
|
||
retry_count = 0
|
||
|
||
while retry_count < max_retries:
|
||
try:
|
||
async with aiohttp.ClientSession() as session:
|
||
async with session.post(AGV_GOODS_MOVE_URL, json=payload) as response:
|
||
result = await response.json()
|
||
logger.info(f"AGV货物移动接口调用响应: {result}, PID={pid}, 重试次数={retry_count}")
|
||
|
||
# 检查响应结果
|
||
if result.get("result") == "0":
|
||
logger.info(f"AGV货物移动接口调用成功: PID={pid}, 总重试次数={retry_count}")
|
||
return True
|
||
elif result.get("result") == "1":
|
||
logger.info(f"AGV货物移动接口返回result=1,继续重试: PID={pid}, 重试次数={retry_count}")
|
||
retry_count += 1
|
||
await asyncio.sleep(5) # 等待5秒后重试
|
||
else:
|
||
logger.error(f"AGV货物移动接口返回异常结果: {result}, PID={pid}")
|
||
retry_count += 1
|
||
await asyncio.sleep(5)
|
||
|
||
except Exception as e:
|
||
logger.error(f"调用AGV货物移动接口异常: {str(e)}, PID={pid}, 重试次数={retry_count}")
|
||
retry_count += 1
|
||
await asyncio.sleep(5) # 等待5秒后重试
|
||
|
||
logger.error(f"AGV货物移动接口调用失败,已达到最大重试次数: PID={pid}, 最大重试次数={max_retries}")
|
||
return False
|
||
|
||
async def monitor_task_and_callback(task_record_id: str, req_code: str):
|
||
"""
|
||
异步监控任务状态并在成功时调用外部回调接口
|
||
|
||
Args:
|
||
task_record_id: 任务记录ID
|
||
req_code: 请求码(用作arrival_no)
|
||
"""
|
||
logger.info(f"开始监控任务状态: task_record_id={task_record_id}, req_code={req_code}")
|
||
|
||
# max_wait_time = 1800 # 最大等待时间30分钟
|
||
# wait_count = 0
|
||
|
||
while True:
|
||
try:
|
||
task_detail_result = await TaskRecordService.get_task_record_detail(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"监控任务状态: task_record_id={task_record_id}, status={task_status}")
|
||
|
||
# 如果任务已完成(成功)
|
||
if task_status == TaskStatus.COMPLETED:
|
||
logger.info(f"任务执行成功,开始调用外部回调接口: task_record_id={task_record_id}, req_code={req_code}")
|
||
# 调用外部回调接口
|
||
success = await call_external_callback(req_code)
|
||
if success:
|
||
logger.info(f"外部回调接口调用成功: task_record_id={task_record_id}, req_code={req_code}")
|
||
else:
|
||
logger.error(f"外部回调接口调用失败: task_record_id={task_record_id}, req_code={req_code}")
|
||
break
|
||
|
||
# 如果任务已失败或取消
|
||
elif task_status in [TaskStatus.FAILED, TaskStatus.CANCELED]:
|
||
logger.info(f"任务执行失败或取消,不调用外部回调接口: task_record_id={task_record_id}, status={task_status}")
|
||
break
|
||
|
||
# 任务还在运行中,继续等待
|
||
else:
|
||
logger.debug(f"任务仍在执行中,继续等待: task_record_id={task_record_id}, status={task_status}")
|
||
await asyncio.sleep(2) # 等待10秒
|
||
# wait_count += 10
|
||
|
||
else:
|
||
logger.warning(f"无法获取任务详情,继续等待: task_record_id={task_record_id}")
|
||
await asyncio.sleep(2) # 等待10秒
|
||
# wait_count += 10
|
||
|
||
except Exception as e:
|
||
logger.error(f"监控任务状态时出现异常: {str(e)}, task_record_id={task_record_id}")
|
||
await asyncio.sleep(2) # 等待10秒
|
||
# wait_count += 10
|
||
|
||
async def monitor_agv_task_and_callback(task_record_id: str, req_code: str):
|
||
"""
|
||
异步监控AGV调度任务状态并在成功时调用AGV货物移动回调接口
|
||
|
||
Args:
|
||
task_record_id: 任务记录ID
|
||
req_code: 请求码(用作PID)
|
||
"""
|
||
logger.info(f"开始监控AGV调度任务状态: task_record_id={task_record_id}, req_code={req_code}")
|
||
|
||
while True:
|
||
try:
|
||
task_detail_result = await TaskRecordService.get_task_record_detail(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"监控AGV调度任务状态: task_record_id={task_record_id}, status={task_status}")
|
||
|
||
# 如果任务已完成(成功)
|
||
if task_status == TaskStatus.COMPLETED:
|
||
logger.info(f"AGV调度任务执行成功,开始调用AGV货物移动回调接口: task_record_id={task_record_id}, req_code={req_code}")
|
||
# 调用AGV货物移动回调接口
|
||
success = await call_agv_goods_move_callback(req_code)
|
||
if success:
|
||
logger.info(f"AGV货物移动回调接口调用成功: task_record_id={task_record_id}, req_code={req_code}")
|
||
else:
|
||
logger.error(f"AGV货物移动回调接口调用失败: task_record_id={task_record_id}, req_code={req_code}")
|
||
break
|
||
|
||
# 如果任务已失败或取消
|
||
elif task_status in [TaskStatus.FAILED, TaskStatus.CANCELED]:
|
||
logger.info(f"AGV调度任务执行失败或取消,不调用AGV货物移动回调接口: task_record_id={task_record_id}, status={task_status}")
|
||
break
|
||
|
||
# 任务还在运行中,继续等待
|
||
else:
|
||
logger.debug(f"AGV调度任务仍在执行中,继续等待: task_record_id={task_record_id}, status={task_status}")
|
||
await asyncio.sleep(2) # 等待2秒
|
||
|
||
else:
|
||
logger.warning(f"无法获取AGV调度任务详情,继续等待: task_record_id={task_record_id}")
|
||
await asyncio.sleep(2) # 等待2秒
|
||
|
||
except Exception as e:
|
||
logger.error(f"监控AGV调度任务状态时出现异常: {str(e)}, task_record_id={task_record_id}")
|
||
await asyncio.sleep(2) # 等待2秒
|
||
|
||
async def check_task_permission(tf_api_token: str, tf_api_base_url: str, module_name: str = "其他") -> bool:
|
||
"""
|
||
检查是否允许处理任务
|
||
调用参数配置-三方接口调用接口检查系统限制
|
||
|
||
Args:
|
||
tf_api_token: API访问令牌
|
||
tf_api_base_url: API基础URL
|
||
module_name: 模块名称,默认为"其他"
|
||
|
||
Returns:
|
||
bool: True表示允许处理任务,False表示被限制
|
||
"""
|
||
headers = {
|
||
"X-Access-Token": tf_api_token,
|
||
"Content-Type": "text/plain"
|
||
}
|
||
|
||
# 构建 API URL
|
||
api_url = f"{tf_api_base_url}/parameter/getByModule"
|
||
|
||
try:
|
||
async with aiohttp.ClientSession() as session:
|
||
async with session.get(api_url, data=module_name, headers=headers) as response:
|
||
if response.status == 200:
|
||
result = await response.json()
|
||
logger.info(f"参数配置接口调用成功: result={result}")
|
||
|
||
# 检查响应格式
|
||
if result.get("success", False):
|
||
parameter_result = result.get("result", {})
|
||
sync_disabled = parameter_result.get(sync_disabled_label, "false")
|
||
|
||
# 如果 sync_disabled 为 "true",则被限制
|
||
if sync_disabled == "true":
|
||
logger.warning("系统限制创建任务: sync_disabled=true")
|
||
return False
|
||
else:
|
||
logger.info("系统允许创建任务: sync_disabled=false")
|
||
return True
|
||
else:
|
||
# 如果接口调用失败,默认允许处理任务
|
||
logger.warning(f"参数配置接口调用失败: {result.get('message', '未知错误')}")
|
||
return True
|
||
else:
|
||
logger.error(f"参数配置接口调用失败: status={response.status}")
|
||
response_text = await response.text()
|
||
logger.error(f"响应内容: {response_text}")
|
||
# 如果接口调用失败,默认允许处理任务
|
||
return True
|
||
except Exception as e:
|
||
logger.error(f"检查任务权限异常: error={str(e)}")
|
||
# 如果出现异常,默认允许处理任务
|
||
return True
|
||
|
||
async def get_amr_loading_state(task_record_id: str, tf_api_token: str) -> Dict[str, Any]:
|
||
"""
|
||
获取任务中小车负载状态
|
||
|
||
Args:
|
||
task_record_id: 天风任务ID
|
||
tf_api_token: API访问令牌
|
||
|
||
Returns:
|
||
Dict[str, Any]: 包含小车负载状态的响应数据
|
||
"""
|
||
headers = {
|
||
"X-Access-Token": tf_api_token,
|
||
"Content-Type": "application/json"
|
||
}
|
||
|
||
# 构建 API URL
|
||
api_url = f"{TF_API_BASE_URL}/task/vwedtask/{task_record_id}/getAmrState"
|
||
|
||
try:
|
||
async with aiohttp.ClientSession() as session:
|
||
async with session.get(api_url, headers=headers) as response:
|
||
if response.status == 200:
|
||
result = await response.json()
|
||
logger.info(f"获取小车负载状态成功: task_record_id={task_record_id}, result={result}")
|
||
return result
|
||
else:
|
||
logger.error(f"获取小车负载状态失败: task_record_id={task_record_id}, status={response.status}")
|
||
response_text = await response.text()
|
||
logger.error(f"响应内容: {response_text}")
|
||
return {
|
||
"success": False,
|
||
"message": f"HTTP {response.status}: {response_text}",
|
||
"code": response.status
|
||
}
|
||
except Exception as e:
|
||
logger.error(f"获取小车负载状态异常: task_record_id={task_record_id}, error={str(e)}")
|
||
return {
|
||
"success": False,
|
||
"message": f"请求异常: {str(e)}",
|
||
"code": 500
|
||
}
|
||
|
||
# # 任务类型到任务优先级
|
||
TASK_TYPE_TEMPLATE_MAPPING = {
|
||
TaskTypeEnum.GG2MP: "GG",
|
||
TaskTypeEnum.GGFK2MP: "GG",
|
||
TaskTypeEnum.GT2MP: "GT",
|
||
TaskTypeEnum.GTFK2MP: "GT",
|
||
TaskTypeEnum.ZG2MP: "ZG",
|
||
TaskTypeEnum.QZ2MP: "QZ",
|
||
TaskTypeEnum.LG2MP: "LG",
|
||
TaskTypeEnum.PHZ2MP: "PHZ",
|
||
TaskTypeEnum.MP2GG: "GG",
|
||
TaskTypeEnum.MP2GGFK: "GG",
|
||
TaskTypeEnum.MP2GT: "GT",
|
||
TaskTypeEnum.MP2GTFK: "GT",
|
||
TaskTypeEnum.MP2ZG: "ZG",
|
||
TaskTypeEnum.MP2QZ: "QZ",
|
||
TaskTypeEnum.MP2LG: "LG",
|
||
TaskTypeEnum.MP2PHZ: "PHZ",
|
||
}
|
||
|
||
TASK_TYPE_REMARK = {
|
||
TaskTypeEnum.GG2MP: "缸盖:{0}-毛坯库:{1}",
|
||
TaskTypeEnum.GGFK2MP: "缸盖返空:{0}-毛坯库:{1}",
|
||
TaskTypeEnum.GT2MP: "缸体:{0}-毛坯库:{1}",
|
||
TaskTypeEnum.GTFK2MP: "缸体返空:{0}-毛坯库:{1}",
|
||
TaskTypeEnum.ZG2MP: "罩盖:{0}-毛坯库:{1}",
|
||
TaskTypeEnum.QZ2MP: "曲轴:{0}-毛坯库:{1}",
|
||
TaskTypeEnum.LG2MP: "连杆:{0}-毛坯库:{1}",
|
||
TaskTypeEnum.PHZ2MP: "平衡轴:{0}-毛坯库:{1}",
|
||
TaskTypeEnum.MP2GG: "毛坯库:{0}-缸盖:{1}",
|
||
TaskTypeEnum.MP2GGFK: "毛坯库:{0}-缸盖返空:{1}",
|
||
TaskTypeEnum.MP2GT: "毛坯库:{0}-缸体:{1}",
|
||
TaskTypeEnum.MP2GTFK: "毛坯库:{0}-缸体返空:{1}",
|
||
TaskTypeEnum.MP2ZG: "毛坯库:{0}-罩盖:{1}",
|
||
TaskTypeEnum.MP2QZ: "毛坯库:{0}-曲轴:{1}",
|
||
TaskTypeEnum.MP2LG: "毛坯库:{0}-连杆:{1}",
|
||
TaskTypeEnum.MP2PHZ: "毛坯库:{0}-平衡轴:{1}",
|
||
}
|
||
@router.post("/newTask")
|
||
async def create_new_task(request: Request, task_request: ExternalTaskRequest = Body(...)):
|
||
"""
|
||
创建新任务接口
|
||
根据任务类型自动选择对应的任务模板并执行任务
|
||
|
||
Args:
|
||
task_request: 外部任务创建请求,包含ReqCode、SourceID、TargetID、TaskType
|
||
|
||
Returns:
|
||
ExternalTaskResponse: 包含code、reqCode、message、rowCount的响应
|
||
"""
|
||
external_record = None
|
||
try:
|
||
logger.info(f"收到外部任务创建请求: {task_request}")
|
||
|
||
# 检查系统是否允许处理任务
|
||
tf_api_token = TF_API_TOKEN
|
||
is_allowed = await check_task_permission(tf_api_token, TF_API_BASE_URL)
|
||
if not is_allowed:
|
||
logger.error(f"系统限制创建任务: ReqCode={task_request.ReqCode}")
|
||
return ExternalTaskResponse(
|
||
code=500,
|
||
reqCode=task_request.ReqCode,
|
||
message="由于系统限制创建任务失败,请联系管理员",
|
||
rowCount=0
|
||
)
|
||
|
||
# 获取客户端信息
|
||
client_ip = request.client.host if request.client else None
|
||
user_agent = request.headers.get("user-agent", "")
|
||
client_info = {
|
||
"user_agent": user_agent,
|
||
"headers": dict(request.headers),
|
||
"method": request.method,
|
||
"url": str(request.url)
|
||
}
|
||
client_info_str = json.dumps(client_info, ensure_ascii=False)
|
||
|
||
# 根据任务类型获取对应的模板ID
|
||
template_id = CM_ID
|
||
if not template_id:
|
||
logger.error(f"不支持的任务类型: {task_request.TaskType}")
|
||
return ExternalTaskResponse(
|
||
code=400,
|
||
reqCode=task_request.ReqCode,
|
||
message=f"不支持的任务类型: {task_request.TaskType}",
|
||
rowCount=0
|
||
)
|
||
|
||
# 创建外部任务记录
|
||
external_record = await ExternalTaskRecordService.create_new_task_record(
|
||
req_code=task_request.ReqCode,
|
||
source_id=task_request.SourceID or "",
|
||
target_id=task_request.TargetID,
|
||
business_task_type=task_request.TaskType.value,
|
||
template_id=template_id,
|
||
request_params=task_request.dict(),
|
||
client_ip=client_ip,
|
||
client_info=client_info_str
|
||
)
|
||
|
||
# 根据站点名称查询对应的库位ID
|
||
from data.session import get_async_session
|
||
from data.models.operate_point_layer import OperatePointLayer
|
||
from sqlalchemy import select
|
||
|
||
target_storage_location_id = ""
|
||
|
||
async with get_async_session() as session:
|
||
# 查询target_id对应的库位ID(选择第一个)
|
||
stmt = select(OperatePointLayer).where(
|
||
OperatePointLayer.station_name == task_request.TargetID,
|
||
OperatePointLayer.is_deleted == False
|
||
).limit(1)
|
||
result = await session.execute(stmt)
|
||
target_layer = result.scalar_one_or_none()
|
||
if target_layer:
|
||
target_storage_location_id = target_layer.layer_name
|
||
storage_area = TASK_TYPE_AREA.get(TASK_TYPE_TEMPLATE_MAPPING.get(task_request.TaskType))
|
||
priority = TASK_TYPE_PRIORITY.get(TASK_TYPE_TEMPLATE_MAPPING.get(task_request.TaskType, "OR"))
|
||
remark = TASK_TYPE_REMARK.get(task_request.TaskType)
|
||
remark = remark.format(storage_area, target_storage_location_id)
|
||
area_obj = TaskInputParamNew(
|
||
name="area",
|
||
type=InputParamType.STRING,
|
||
label="库区",
|
||
required=True,
|
||
defaultValue=storage_area,
|
||
remark="任务类型对应所属库区")
|
||
target_obj = TaskInputParamNew(
|
||
name="target_id",
|
||
type=InputParamType.STRING,
|
||
label="库位id",
|
||
required=True,
|
||
defaultValue=target_storage_location_id,
|
||
remark="取货id")
|
||
|
||
priority_obj = TaskInputParamNew(
|
||
name="priority",
|
||
type=InputParamType.STRING,
|
||
label="优先级",
|
||
required=True,
|
||
defaultValue=priority,
|
||
remark="任务级优先级")
|
||
remark_obj = TaskInputParamNew(
|
||
name="REMARK",
|
||
type=InputParamType.STRING,
|
||
label="任务备注",
|
||
required=True,
|
||
defaultValue=remark,
|
||
remark="任务详情备注")
|
||
task_params = [area_obj, target_obj, priority_obj, remark_obj]
|
||
|
||
# 构造任务执行请求
|
||
run_request = TaskEditRunRequest(
|
||
taskId=template_id,
|
||
params=task_params,
|
||
source_type=SourceType.SYSTEM_SCHEDULING, # 第三方系统
|
||
source_system="EXTERNAL_API", # 外部接口系统标识
|
||
source_device=request.client.host if request.client else "unknown", # 使用客户端IP作为设备标识
|
||
use_modbus=False,
|
||
modbus_timeout=5000,
|
||
priority = priority
|
||
)
|
||
|
||
# 更新外部任务记录状态为运行中
|
||
await ExternalTaskRecordService.update_task_record_status(
|
||
req_code=external_record.id,
|
||
task_status=ExternalTaskStatusEnum.RUNNING
|
||
)
|
||
|
||
tf_api_token = TF_API_TOKEN
|
||
|
||
# 调用任务执行服务
|
||
result = await TaskEditService.run_task(
|
||
run_request,
|
||
client_ip=client_ip,
|
||
client_info=client_info_str,
|
||
tf_api_token=tf_api_token,
|
||
# priority=priority
|
||
)
|
||
if result is None:
|
||
logger.error(f"任务启动失败: ReqCode={task_request.ReqCode}")
|
||
# 更新外部任务记录状态为失败
|
||
await ExternalTaskRecordService.update_task_record_status(
|
||
req_code=external_record.id,
|
||
task_status=ExternalTaskStatusEnum.FAILED,
|
||
response_code=500,
|
||
response_message="任务启动失败",
|
||
response_row_count=0,
|
||
error_message="任务启动失败"
|
||
)
|
||
return ExternalTaskResponse(
|
||
code=500,
|
||
reqCode=task_request.ReqCode,
|
||
message="任务启动失败",
|
||
rowCount=0
|
||
)
|
||
|
||
if not result.get("success", False):
|
||
error_msg = result.get("message", "任务启动失败")
|
||
logger.error(f"任务启动失败: {error_msg}, ReqCode={task_request.ReqCode}")
|
||
# 更新外部任务记录状态为失败
|
||
await ExternalTaskRecordService.update_task_record_status(
|
||
req_code=external_record.id,
|
||
task_status=ExternalTaskStatusEnum.FAILED,
|
||
response_code=500,
|
||
response_message=error_msg,
|
||
response_row_count=0,
|
||
error_message=error_msg
|
||
)
|
||
return ExternalTaskResponse(
|
||
code=500,
|
||
reqCode=task_request.ReqCode,
|
||
message=error_msg,
|
||
rowCount=0
|
||
)
|
||
# 更新外部任务记录状态为成功
|
||
|
||
task_record_id = result.get('taskRecordId')
|
||
await ExternalTaskRecordService.update_task_record_status(
|
||
req_code=external_record.id,
|
||
task_status=ExternalTaskStatusEnum.SUCCESS,
|
||
task_record_id=task_record_id,
|
||
response_code=0,
|
||
response_message="成功",
|
||
response_row_count=1,
|
||
response_data=result
|
||
)
|
||
logger.info(f"任务启动成功: ReqCode={task_request.ReqCode}, TaskRecordId={task_record_id}, task_request.TaskType={task_request.TaskType}")
|
||
# 启动异步任务监控,不阻塞当前接口
|
||
if task_record_id and task_request.TaskType in ["GTFK2MP", "GGFK2MP"] and TF_WEB_POST:
|
||
|
||
asyncio.create_task(monitor_task_and_callback(
|
||
task_record_id=task_record_id,
|
||
req_code=task_request.ReqCode
|
||
))
|
||
|
||
return ExternalTaskResponse(
|
||
code=0,
|
||
reqCode=task_request.ReqCode,
|
||
message="成功",
|
||
rowCount=1
|
||
)
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建外部任务异常: {str(e)}, ReqCode={task_request.ReqCode}")
|
||
# 如果已创建外部任务记录,更新状态为失败
|
||
if external_record:
|
||
try:
|
||
await ExternalTaskRecordService.update_task_record_status(
|
||
req_code=external_record.id,
|
||
task_status=ExternalTaskStatusEnum.FAILED,
|
||
response_code=500,
|
||
response_message=f"创建任务失败: {str(e)}",
|
||
response_row_count=0,
|
||
error_message=str(e)
|
||
)
|
||
except Exception as update_error:
|
||
logger.error(f"更新外部任务记录状态失败: {str(update_error)}")
|
||
|
||
return ExternalTaskResponse(
|
||
code=500,
|
||
reqCode=task_request.ReqCode,
|
||
message=f"创建任务失败: {str(e)}",
|
||
rowCount=0
|
||
)
|
||
|
||
@router.post("/GenAgvSchedulingTask")
|
||
async def gen_agv_scheduling_task(request: Request, task_request: GenAgvSchedulingTaskRequest = Body(...)):
|
||
"""
|
||
AGV调度任务接口
|
||
用于生成AGV调度任务
|
||
|
||
逻辑:
|
||
1. 根据 taskcode 参数查询 external_task_record 表获取对应的 task_record_id
|
||
2. 调用 get_task_record_detail 接口查询任务运行状态
|
||
3. 如果 TaskTyp 对应的 TASK_TYPE_TEMPLATE_MAPPING 值不等于 "GT",需要等任务运行成功后才能运行本接口
|
||
4. 如果是 "GT" 类型,直接运行任务
|
||
|
||
Args:
|
||
task_request: AGV调度任务请求,包含ReqCode、TaskTyp、SecurityKey等参数
|
||
|
||
Returns:
|
||
ExternalTaskResponse: 包含code、reqCode、message、rowCount的响应
|
||
"""
|
||
priority = TASK_TYPE_PRIORITY.get(TASK_TYPE_TEMPLATE_MAPPING.get(task_request.TaskTyp, "OR"))
|
||
remark = TASK_TYPE_REMARK.get(task_request.TaskTyp)
|
||
|
||
|
||
external_record = None
|
||
try:
|
||
logger.info(f"收到AGV调度任务请求:{task_request}")
|
||
|
||
# 检查系统是否允许处理任务
|
||
tf_api_token = TF_API_TOKEN
|
||
is_allowed = await check_task_permission(tf_api_token, TF_API_BASE_URL)
|
||
if not is_allowed:
|
||
logger.error(f"系统限制创建AGV调度任务: ReqCode={task_request.ReqCode}")
|
||
return ExternalTaskResponse(
|
||
code=500,
|
||
reqCode=task_request.ReqCode,
|
||
message="由于系统限制创建任务失败,请联系管理员",
|
||
rowCount=0
|
||
)
|
||
|
||
# 导入数据库相关模块
|
||
from data.session import get_async_session
|
||
from data.models.operate_point_layer import OperatePointLayer
|
||
from sqlalchemy import select
|
||
|
||
# 验证任务条件已移至脚本处理器中,此处保留简单检查
|
||
if not task_request.TaskCode:
|
||
return ExternalTaskResponse(
|
||
code=500,
|
||
reqCode=task_request.ReqCode,
|
||
message=f"创建任务失败: task_request.TaskCode 任务id为空",
|
||
rowCount=0
|
||
)
|
||
# 获取客户端信息
|
||
client_ip = request.client.host if request.client else None
|
||
user_agent = request.headers.get("user-agent", "")
|
||
client_info = {
|
||
"user_agent": user_agent,
|
||
"headers": dict(request.headers),
|
||
"method": request.method,
|
||
"url": str(request.url)
|
||
}
|
||
client_info_str = json.dumps(client_info, ensure_ascii=False)
|
||
|
||
|
||
# 根据任务类型获取对应的模板ID
|
||
template_id = DG_ID
|
||
|
||
|
||
# 创建外部任务记录
|
||
external_record = await ExternalTaskRecordService.create_agv_scheduling_task_record(
|
||
req_code=task_request.ReqCode,
|
||
task_code=task_request.TaskCode,
|
||
business_task_type=task_request.TaskTyp.value if hasattr(task_request.TaskTyp, 'value') else str(task_request.TaskTyp),
|
||
security_key=task_request.SecurityKey or "",
|
||
type_field=task_request.Type,
|
||
sub_type=task_request.SubType,
|
||
area_position_code=task_request.AreaPositonCode,
|
||
area_position_name=task_request.AreaPositonName,
|
||
position_code_path=[path.dict() for path in task_request.PositionCodePath],
|
||
template_id=template_id or "",
|
||
request_params=task_request.dict(),
|
||
client_code=task_request.ClientCode or "",
|
||
token_code=task_request.TokenCode or "",
|
||
client_ip=client_ip,
|
||
client_info=client_info_str
|
||
)
|
||
start_node_obj, end_node_obj = task_request.PositionCodePath
|
||
start_node = start_node_obj.PositionCode
|
||
end_node = end_node_obj.PositionCode
|
||
|
||
# 根据站点名称查询对应的库位ID
|
||
|
||
start_storage_location_id = ""
|
||
end_storage_location_id = ""
|
||
|
||
async with get_async_session() as session:
|
||
# 查询start_node对应的库位ID(选择第一个)
|
||
stmt = select(OperatePointLayer).where(
|
||
OperatePointLayer.station_name == start_node,
|
||
OperatePointLayer.is_deleted == False
|
||
).limit(1)
|
||
result = await session.execute(stmt)
|
||
start_layer = result.scalar_one_or_none()
|
||
if start_layer:
|
||
start_storage_location_id = start_layer.layer_name
|
||
|
||
# 查询end_node对应的库位ID(选择第一个)
|
||
stmt = select(OperatePointLayer).where(
|
||
OperatePointLayer.station_name == end_node,
|
||
OperatePointLayer.is_deleted == False
|
||
).limit(1)
|
||
result = await session.execute(stmt)
|
||
end_layer = result.scalar_one_or_none()
|
||
if end_layer:
|
||
end_storage_location_id = end_layer.layer_name
|
||
|
||
# 构造任务运行参数
|
||
task_params = []
|
||
remark = remark.format(start_node, end_node)
|
||
# 添加任务代码参数
|
||
task_params.append(TaskInputParamNew(
|
||
name="START_WL",
|
||
type=InputParamType.STRING,
|
||
label="取货库位id",
|
||
required=False,
|
||
defaultValue=start_storage_location_id,
|
||
remark="要取货所属库位"
|
||
))
|
||
|
||
# 添加类型参数
|
||
task_params.append(TaskInputParamNew(
|
||
name="END_WL",
|
||
type=InputParamType.STRING,
|
||
label="放货库位id",
|
||
required=False,
|
||
defaultValue=end_storage_location_id,
|
||
remark="要放货所属库位"
|
||
))
|
||
|
||
# 添加子类型参数
|
||
task_params.append(TaskInputParamNew(
|
||
name="priority",
|
||
type=InputParamType.STRING,
|
||
label="优先级",
|
||
required=False,
|
||
defaultValue=priority,
|
||
remark="选车优先级"
|
||
))
|
||
task_params.append(TaskInputParamNew(
|
||
name="TASK_CODE",
|
||
type=InputParamType.STRING,
|
||
label="任务id",
|
||
required=False,
|
||
defaultValue=task_request.TaskCode,
|
||
remark="创建任务时任务id"
|
||
))
|
||
task_params.append(TaskInputParamNew(
|
||
name="TASK_TYPE",
|
||
type=InputParamType.STRING,
|
||
label="任务类型",
|
||
required=False,
|
||
defaultValue=task_request.TaskTyp,
|
||
remark="创建任务时任务类型"
|
||
))
|
||
task_params.append(TaskInputParamNew(
|
||
name="REMARK",
|
||
type=InputParamType.STRING,
|
||
label="任务备注",
|
||
required=False,
|
||
defaultValue=remark,
|
||
remark="详细业务描述"
|
||
))
|
||
# 构造任务执行请求
|
||
run_request = TaskEditRunRequest(
|
||
taskId=template_id,
|
||
params=task_params,
|
||
source_type=SourceType.SYSTEM_SCHEDULING, # 第三方系统
|
||
source_system="AGV_SCHEDULING", # AGV调度系统标识
|
||
source_device=request.client.host if request.client else "unknown", # 使用客户端IP作为设备标识
|
||
use_modbus=False,
|
||
modbus_timeout=5000,
|
||
priority = priority
|
||
)
|
||
|
||
# 更新外部任务记录状态为运行中
|
||
await ExternalTaskRecordService.update_task_record_status(
|
||
req_code=external_record.id,
|
||
task_status=ExternalTaskStatusEnum.RUNNING
|
||
)
|
||
|
||
tf_api_token = TF_API_TOKEN
|
||
|
||
# 调用任务执行服务
|
||
result = await TaskEditService.run_task(
|
||
run_request,
|
||
client_ip=client_ip,
|
||
client_info=client_info_str,
|
||
tf_api_token=tf_api_token
|
||
)
|
||
|
||
if result is None:
|
||
logger.error(f"AGV调度任务启动失败: ReqCode={task_request.ReqCode}")
|
||
# 更新外部任务记录状态为失败
|
||
await ExternalTaskRecordService.update_task_record_status(
|
||
req_code=external_record.id,
|
||
task_status=ExternalTaskStatusEnum.FAILED,
|
||
response_code=500,
|
||
response_message="任务启动失败",
|
||
response_row_count=0,
|
||
error_message="AGV调度任务启动失败"
|
||
)
|
||
return ExternalTaskResponse(
|
||
code=500,
|
||
reqCode=task_request.ReqCode,
|
||
message="任务启动失败",
|
||
rowCount=0
|
||
)
|
||
|
||
if not result.get("success", False):
|
||
error_msg = result.get("message", "任务启动失败")
|
||
logger.error(f"AGV调度任务启动失败: {error_msg}, ReqCode={task_request.ReqCode}")
|
||
# 更新外部任务记录状态为失败
|
||
await ExternalTaskRecordService.update_task_record_status(
|
||
req_code=external_record.id,
|
||
task_status=ExternalTaskStatusEnum.FAILED,
|
||
response_code=500,
|
||
response_message=error_msg,
|
||
response_row_count=0,
|
||
error_message=error_msg
|
||
)
|
||
return ExternalTaskResponse(
|
||
code=500,
|
||
reqCode=task_request.ReqCode,
|
||
message=error_msg,
|
||
rowCount=0
|
||
)
|
||
|
||
# 更新外部任务记录状态为成功
|
||
task_record_id = result.get('taskRecordId')
|
||
await ExternalTaskRecordService.update_task_record_status(
|
||
req_code=external_record.id,
|
||
task_status=ExternalTaskStatusEnum.SUCCESS,
|
||
task_record_id=task_record_id,
|
||
response_code=0,
|
||
response_message="成功",
|
||
response_row_count=0,
|
||
response_data=result
|
||
)
|
||
|
||
logger.info(f"AGV调度任务启动成功: ReqCode={task_request.ReqCode}, TaskRecordId={task_record_id}")
|
||
|
||
# 定义需要监控的任务类型
|
||
agv_callback_task_types = ["MP2GG", "MP2GT", "MP2ZG", "MP2QZ", "MP2LG", "MP2PHZ"]
|
||
|
||
# 启动异步任务监控,不阻塞当前接口
|
||
if task_record_id and task_request.TaskTyp in agv_callback_task_types and TF_WEB_POST:
|
||
asyncio.create_task(monitor_agv_task_and_callback(
|
||
task_record_id=task_record_id,
|
||
req_code=task_request.TaskCode
|
||
))
|
||
logger.info(f"已启动AGV调度任务监控: TaskType={task_request.TaskTyp}, TaskRecordId={task_record_id}")
|
||
|
||
return ExternalTaskResponse(
|
||
code=0,
|
||
reqCode=task_request.TaskCode,
|
||
message="成功",
|
||
rowCount=0
|
||
)
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建AGV调度任务异常: {str(e)}, ReqCode={task_request.ReqCode}")
|
||
# 如果已创建外部任务记录,更新状态为失败
|
||
if external_record:
|
||
try:
|
||
await ExternalTaskRecordService.update_task_record_status(
|
||
req_code=external_record.id,
|
||
task_status=ExternalTaskStatusEnum.FAILED,
|
||
response_code=500,
|
||
response_message=f"创建任务失败: {str(e)}",
|
||
response_row_count=0,
|
||
error_message=str(e)
|
||
)
|
||
except Exception as update_error:
|
||
logger.error(f"更新外部任务记录状态失败: {str(update_error)}")
|
||
|
||
return ExternalTaskResponse(
|
||
code=500,
|
||
reqCode=task_request.ReqCode,
|
||
message=f"创建任务失败: {str(e)}",
|
||
rowCount=0
|
||
)
|
||
|
||
@router.post("/cancelTask")
|
||
async def cancel_task(request: Request, cancel_request: CancelTaskRequest = Body(...)):
|
||
"""
|
||
取消任务接口
|
||
根据ReqCode查询对应的task_record_id,然后调用内部接口终止任务并通知主系统
|
||
|
||
Args:
|
||
cancel_request: 取消任务请求,包含ReqCode
|
||
|
||
Returns:
|
||
ExternalTaskResponse: 包含code、reqCode、message、rowCount的响应
|
||
"""
|
||
try:
|
||
logger.info(f"收到取消任务请求: {cancel_request}")
|
||
|
||
# 检查系统是否允许处理任务
|
||
tf_api_token = TF_API_TOKEN
|
||
is_allowed = await check_task_permission(tf_api_token, TF_API_BASE_URL)
|
||
if not is_allowed:
|
||
logger.error(f"系统限制取消任务: ReqCode={cancel_request.ReqCode}")
|
||
return ExternalTaskResponse(
|
||
code=500,
|
||
reqCode=cancel_request.ReqCode,
|
||
message="由于系统限制创建任务失败,请联系管理员",
|
||
rowCount=0
|
||
)
|
||
|
||
req_code = cancel_request.ReqCode
|
||
|
||
# 根据req_code查询external_task_record获取task_record_id
|
||
external_record = await ExternalTaskRecordService.get_external_task_record(req_code)
|
||
if not external_record:
|
||
logger.error(f"未找到对应的外部任务记录: ReqCode={req_code}")
|
||
return ExternalTaskResponse(
|
||
code=1,
|
||
reqCode=req_code,
|
||
message="未找到对应的任务记录",
|
||
rowCount=0
|
||
)
|
||
|
||
task_record_id = external_record.task_record_id
|
||
if not task_record_id:
|
||
logger.error(f"外部任务记录中没有关联的task_record_id: ReqCode={req_code}")
|
||
return ExternalTaskResponse(
|
||
code=1,
|
||
reqCode=req_code,
|
||
message="任务记录中没有关联的内部任务ID",
|
||
rowCount=0
|
||
)
|
||
|
||
# 通过task_record_id查询任务详情,检查任务状态
|
||
task_detail_result = await TaskRecordService.get_task_record_detail(task_record_id)
|
||
if not task_detail_result.get("success", False):
|
||
logger.error(f"获取任务详情失败: ReqCode={req_code}, task_record_id={task_record_id}")
|
||
return ExternalTaskResponse(
|
||
code=1,
|
||
reqCode=req_code,
|
||
message="获取任务详情失败",
|
||
rowCount=0
|
||
)
|
||
|
||
task_detail = task_detail_result.get("data", {})
|
||
task_status = task_detail.get("status", "")
|
||
|
||
# 检查任务状态,只有运行状态的任务才允许取消
|
||
if task_status in [TaskStatus.COMPLETED, TaskStatus.FAILED, TaskStatus.CANCELED]:
|
||
logger.warning(f"任务已处于终止状态,无法取消: ReqCode={req_code}, TaskStatus={task_status}")
|
||
return ExternalTaskResponse(
|
||
code=1,
|
||
reqCode=req_code,
|
||
message=f"任务已处于{task_status}状态,无法取消",
|
||
rowCount=0
|
||
)
|
||
|
||
# 检查小车负载状态
|
||
tf_api_token = TF_API_TOKEN
|
||
logger.info(f"检查小车负载状态: task_record_id={task_record_id}")
|
||
amr_state_result = await get_amr_loading_state(task_record_id, tf_api_token)
|
||
|
||
if amr_state_result.get("success", False):
|
||
amr_state_data = amr_state_result.get("result", {})
|
||
amr_loading = amr_state_data.get("amr_loading", False)
|
||
amr_name = amr_state_data.get("amr_name", "")
|
||
|
||
logger.info(f"小车负载状态: task_record_id={task_record_id}, amr_loading={amr_loading}, amr_name={amr_name}")
|
||
|
||
# 如果小车处于负载状态,不允许取消任务
|
||
if amr_loading:
|
||
logger.warning(f"小车处于负载状态,不允许终止任务: ReqCode={req_code}, AMR={amr_name}")
|
||
return ExternalTaskResponse(
|
||
code=1,
|
||
reqCode=req_code,
|
||
message="已载货,请人工干预",
|
||
rowCount=0
|
||
)
|
||
else:
|
||
# 如果获取小车状态失败,记录警告但继续执行取消操作
|
||
logger.warning(f"获取小车负载状态失败,继续执行取消操作: {amr_state_result.get('message', '')}")
|
||
|
||
# 调用内部接口停止任务
|
||
logger.info(f"调用内部接口停止任务: task_record_id={task_record_id}")
|
||
stop_result = await TaskRecordService.stop_task_record(task_record_id)
|
||
|
||
if not stop_result.get("success", False):
|
||
error_msg = stop_result.get("message", "停止任务失败")
|
||
logger.error(f"停止任务失败: {error_msg}, task_record_id={task_record_id}")
|
||
|
||
# 检查是否是"已载货,请人工干预"的情况
|
||
if "已载货" in error_msg or "人工干预" in error_msg:
|
||
return ExternalTaskResponse(
|
||
code=1,
|
||
reqCode=req_code,
|
||
message="已载货,请人工干预",
|
||
rowCount=0
|
||
)
|
||
|
||
return ExternalTaskResponse(
|
||
code=1,
|
||
reqCode=req_code,
|
||
message=error_msg,
|
||
rowCount=0
|
||
)
|
||
|
||
# 通知主系统任务已终止
|
||
logger.info(f"通知主系统任务已终止: task_record_id={task_record_id}")
|
||
|
||
try:
|
||
await set_task_terminated(task_record_id, tf_api_token)
|
||
logger.info(f"成功通知主系统任务已终止: task_record_id={task_record_id}")
|
||
except Exception as sync_error:
|
||
logger.warning(f"通知主系统失败,但任务已成功取消: {str(sync_error)}, task_record_id={task_record_id}")
|
||
|
||
# 更新外部任务记录状态为已取消
|
||
await ExternalTaskRecordService.update_task_record_status(
|
||
req_code=external_record.id,
|
||
task_status=ExternalTaskStatusEnum.CANCELLED,
|
||
response_code=0,
|
||
response_message="任务已取消",
|
||
response_row_count=0
|
||
)
|
||
|
||
logger.info(f"任务取消成功: ReqCode={req_code}, TaskRecordId={task_record_id}")
|
||
return ExternalTaskResponse(
|
||
code=0,
|
||
reqCode=req_code,
|
||
message="成功",
|
||
rowCount=0
|
||
)
|
||
|
||
except Exception as e:
|
||
logger.error(f"取消任务异常: {str(e)}, ReqCode={cancel_request.ReqCode}")
|
||
return ExternalTaskResponse(
|
||
code=1,
|
||
reqCode=cancel_request.ReqCode,
|
||
message=f"取消任务失败: {str(e)}",
|
||
rowCount=0
|
||
)
|
||
|
||
|
||
@router.get("/api/external-task-record/by-req-code/{req_code}")
|
||
async def get_external_task_record_by_req_code(
|
||
req_code: str = Path(..., description="请求标识码")
|
||
):
|
||
"""
|
||
根据ReqCode查询外部任务记录
|
||
|
||
Args:
|
||
req_code: 请求标识码
|
||
|
||
Returns:
|
||
包含外部任务记录信息的响应
|
||
"""
|
||
try:
|
||
# 查询外部任务记录
|
||
external_record = await ExternalTaskRecordService.get_external_task_record(req_code)
|
||
|
||
if not external_record:
|
||
return error_response(
|
||
message=f"未找到ReqCode为 {req_code} 的外部任务记录",
|
||
code=404
|
||
)
|
||
|
||
# 构建响应数据
|
||
response_data = {
|
||
"id": external_record.id,
|
||
"req_code": external_record.req_code,
|
||
"task_type": external_record.task_type.value if external_record.task_type else None,
|
||
"task_status": external_record.task_status.value if external_record.task_status else None,
|
||
"task_record_id": external_record.task_record_id,
|
||
"task_code": external_record.task_code,
|
||
"related_req_code": external_record.related_req_code,
|
||
"source_id": external_record.source_id,
|
||
"target_id": external_record.target_id,
|
||
"business_task_type": external_record.business_task_type,
|
||
"template_id": external_record.template_id,
|
||
"response_code": external_record.response_code,
|
||
"response_message": external_record.response_message,
|
||
"start_time": external_record.start_time.isoformat() if external_record.start_time else None,
|
||
"end_time": external_record.end_time.isoformat() if external_record.end_time else None,
|
||
"duration": external_record.duration,
|
||
"created_at": external_record.created_at.isoformat() if external_record.created_at else None,
|
||
"updated_at": external_record.updated_at.isoformat() if external_record.updated_at else None
|
||
}
|
||
|
||
return format_response(
|
||
data=response_data,
|
||
message="成功获取外部任务记录"
|
||
)
|
||
|
||
except Exception as e:
|
||
logger.error(f"查询外部任务记录异常: {str(e)}, req_code={req_code}")
|
||
return error_response(
|
||
message=f"查询外部任务记录失败: {str(e)}",
|
||
code=500
|
||
) |