2303 lines
104 KiB
Python
2303 lines
104 KiB
Python
#!/usr/bin/env python
|
||
# -*- coding: utf-8 -*-
|
||
|
||
"""
|
||
库位处理器模块
|
||
提供与库区操作相关的各种处理器
|
||
"""
|
||
|
||
import logging
|
||
import json
|
||
import asyncio
|
||
import aiohttp
|
||
import uuid
|
||
from typing import Dict, Any, List, Optional
|
||
from sqlalchemy import and_, or_, desc, asc, select, update
|
||
from sqlalchemy.orm import Session
|
||
from services.execution.task_context import TaskContext
|
||
from .base import BlockHandler, register_handler
|
||
from config.settings import settings
|
||
from utils.logger import get_logger
|
||
from .model.block_name import StorageBlockName
|
||
from data.session import get_async_session
|
||
from data.models.operate_point_layer import OperatePointLayer
|
||
from data.models.storage_area import StorageArea
|
||
from data.models.extended_property import ExtendedProperty, ExtendedPropertyTypeEnum
|
||
|
||
# 获取日志记录器
|
||
logger = get_logger("services.execution.handlers.storage_location")
|
||
|
||
# 创建一个基础库位处理器类,包含通用方法
|
||
class StorageBlockHandler(BlockHandler):
|
||
pass
|
||
|
||
# 批量设置库位处理器
|
||
@register_handler(StorageBlockName.BATCH_SETTING_SITE)
|
||
class BatchSettingSiteBlockHandler(StorageBlockHandler):
|
||
"""批量设置库位处理器"""
|
||
|
||
async def execute(
|
||
self,
|
||
block: Dict[str, Any],
|
||
input_params: Dict[str, Any],
|
||
context: TaskContext
|
||
) -> Dict[str, Any]:
|
||
"""执行批量设置库位操作"""
|
||
try:
|
||
# 获取关键参数用于验证
|
||
site_ids = input_params.get("siteIds", [])
|
||
group_names = input_params.get("groupNames", [])
|
||
filled = input_params.get("filled")
|
||
content = input_params.get("content")
|
||
# 获取地图ID
|
||
map_id = context.map_id
|
||
# print(f"批量设置库位处理器参数>>>>>: {input_params}<<<<<<=======================")
|
||
# 校验 siteIds 参数格式(可以为空或null,但有值时必须是列表)
|
||
if site_ids is not None and site_ids != "" and site_ids != []:
|
||
if not isinstance(site_ids, list):
|
||
# 如果是字符串,尝试解析为列表
|
||
if isinstance(site_ids, str):
|
||
# 检查是否为空字符串
|
||
if site_ids.strip() == "":
|
||
site_ids = []
|
||
input_params["siteIds"] = site_ids
|
||
else:
|
||
try:
|
||
import json
|
||
site_ids = json.loads(site_ids)
|
||
if not isinstance(site_ids, list):
|
||
raise ValueError("解析后不是列表格式")
|
||
input_params["siteIds"] = site_ids
|
||
except (json.JSONDecodeError, ValueError):
|
||
result = {
|
||
"success": False,
|
||
"message": "siteIds参数必须是列表格式或能解析为列表的字符串"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
else:
|
||
result = {
|
||
"success": False,
|
||
"message": "siteIds参数必须是列表格式"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
# 校验 groupNames 参数格式(可以为空或null,但有值时必须是列表)
|
||
if group_names is not None and group_names != "" and group_names != []:
|
||
if not isinstance(group_names, list):
|
||
# 如果是字符串,尝试解析为列表
|
||
if isinstance(group_names, str):
|
||
# 检查是否为空字符串
|
||
if group_names.strip() == "":
|
||
group_names = []
|
||
input_params["groupNames"] = group_names
|
||
else:
|
||
try:
|
||
import json
|
||
group_names = json.loads(group_names)
|
||
if not isinstance(group_names, list):
|
||
raise ValueError("解析后不是列表格式")
|
||
input_params["groupNames"] = group_names
|
||
except (json.JSONDecodeError, ValueError):
|
||
result = {
|
||
"success": False,
|
||
"message": "groupNames参数必须是列表格式或能解析为列表的字符串"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
else:
|
||
result = {
|
||
"success": False,
|
||
"message": "groupNames参数必须是列表格式"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
# 参数检查
|
||
if not site_ids and not group_names:
|
||
result = {
|
||
"success": False,
|
||
"message": "库位ID和库区集不能同时为空"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
if not map_id:
|
||
result = {
|
||
"success": False,
|
||
"message": "地图ID不能为空"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
if filled is None:
|
||
result = {
|
||
"success": False,
|
||
"message": "占用参数不能为空"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
# 校验并转换 filled 参数为布尔类型
|
||
if isinstance(filled, str):
|
||
filled_lower = filled.lower()
|
||
if filled_lower in ('true', '1', 'yes', 'on'):
|
||
filled = True
|
||
elif filled_lower in ('false', '0', 'no', 'off'):
|
||
filled = False
|
||
else:
|
||
result = {
|
||
"success": False,
|
||
"message": "filled参数必须是布尔类型或可转换为布尔类型的字符串"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
input_params["filled"] = filled
|
||
elif not isinstance(filled, bool):
|
||
result = {
|
||
"success": False,
|
||
"message": "filled参数必须是布尔类型"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
# 直接操作数据库执行批量设置
|
||
result = await self._batch_setting_site_in_db(site_ids, group_names, filled, content, map_id)
|
||
|
||
# 记录执行结果
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
except Exception as e:
|
||
result = {
|
||
"success": False,
|
||
"message": f"批量设置库位执行异常: {str(e)}"
|
||
}
|
||
# 记录异常
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
async def _batch_setting_site_in_db(self, site_ids: List[str], group_names: List[str], filled: bool, content: str, map_id: str) -> Dict[str, Any]:
|
||
"""在数据库中批量设置库位状态"""
|
||
try:
|
||
async with get_async_session() as session:
|
||
# 收集所有需要更新的库位ID
|
||
target_site_ids = []
|
||
|
||
# 1. 校验并收集指定的库位ID
|
||
if site_ids:
|
||
site_check_query = select(OperatePointLayer.layer_name).where(
|
||
OperatePointLayer.layer_name.in_(site_ids),
|
||
OperatePointLayer.scene_id == map_id,
|
||
OperatePointLayer.is_disabled == False,
|
||
OperatePointLayer.is_deleted == False
|
||
)
|
||
site_check_result = await session.execute(site_check_query)
|
||
existing_site_ids = [row[0] for row in site_check_result.fetchall()]
|
||
|
||
# 检查是否有不存在的库位
|
||
missing_sites = set(site_ids) - set(existing_site_ids)
|
||
if missing_sites:
|
||
return {
|
||
"success": False,
|
||
"message": f"以下库位不存在或已禁用: {', '.join(str(site) for site in missing_sites)}"
|
||
}
|
||
|
||
target_site_ids.extend(existing_site_ids)
|
||
|
||
# 2. 校验并收集库区下的所有库位
|
||
if group_names:
|
||
# 校验库区是否存在
|
||
area_check_query = select(StorageArea.area_name).where(
|
||
StorageArea.area_name.in_(group_names),
|
||
StorageArea.scene_id == map_id,
|
||
StorageArea.is_deleted == False
|
||
)
|
||
area_check_result = await session.execute(area_check_query)
|
||
existing_area_names = [row[0] for row in area_check_result.fetchall()]
|
||
|
||
# 检查是否有不存在的库区
|
||
missing_areas = set(group_names) - set(existing_area_names)
|
||
if missing_areas:
|
||
return {
|
||
"success": False,
|
||
"message": f"以下库区不存在: {', '.join(str(area) for area in missing_areas)}"
|
||
}
|
||
|
||
# 获取库区下的所有库位
|
||
area_sites_query = select(OperatePointLayer.layer_name).where(
|
||
OperatePointLayer.area_name.in_(existing_area_names),
|
||
OperatePointLayer.scene_id == map_id,
|
||
OperatePointLayer.is_disabled == False,
|
||
OperatePointLayer.is_deleted == False
|
||
)
|
||
area_sites_result = await session.execute(area_sites_query)
|
||
area_site_ids = [row[0] for row in area_sites_result.fetchall()]
|
||
|
||
target_site_ids.extend(area_site_ids)
|
||
|
||
# 去重
|
||
target_site_ids = list(set(target_site_ids))
|
||
|
||
if not target_site_ids:
|
||
return {
|
||
"success": False,
|
||
"message": "没有找到符合条件的库位"
|
||
}
|
||
|
||
# 3. 根据filled参数决定更新逻辑
|
||
import datetime
|
||
now = datetime.datetime.now()
|
||
|
||
if filled:
|
||
# 设置为占用状态
|
||
# 先检查哪些库位已经被占用
|
||
occupied_check_query = select(OperatePointLayer.layer_name).where(
|
||
OperatePointLayer.layer_name.in_(target_site_ids),
|
||
OperatePointLayer.is_occupied == True
|
||
)
|
||
occupied_check_result = await session.execute(occupied_check_query)
|
||
already_occupied = [row[0] for row in occupied_check_result.fetchall()]
|
||
|
||
# 更新未占用的库位
|
||
update_values = {
|
||
'is_occupied': True,
|
||
'last_access_at': now
|
||
}
|
||
|
||
# 如果提供了content,则设置货物内容
|
||
if content is not None:
|
||
update_values['goods_content'] = content
|
||
|
||
update_stmt = update(OperatePointLayer).where(
|
||
OperatePointLayer.layer_name.in_(target_site_ids),
|
||
OperatePointLayer.is_occupied == False # 只更新未占用的库位
|
||
).values(update_values)
|
||
|
||
result = await session.execute(update_stmt)
|
||
await session.commit()
|
||
|
||
updated_count = result.rowcount
|
||
already_occupied_count = len(already_occupied)
|
||
|
||
if already_occupied_count > 0:
|
||
message = f"批量设置库位为占用成功,更新了 {updated_count} 个库位,{already_occupied_count} 个库位已是占用状态"
|
||
else:
|
||
message = f"批量设置库位为占用成功,更新了 {updated_count} 个库位"
|
||
|
||
else:
|
||
# 设置为空闲状态
|
||
# 先检查哪些库位已经是空闲状态
|
||
empty_check_query = select(OperatePointLayer.layer_name).where(
|
||
OperatePointLayer.layer_name.in_(target_site_ids),
|
||
OperatePointLayer.is_occupied == False
|
||
)
|
||
empty_check_result = await session.execute(empty_check_query)
|
||
already_empty = [row[0] for row in empty_check_result.fetchall()]
|
||
|
||
# 更新占用的库位
|
||
update_values = {
|
||
'is_occupied': False,
|
||
'goods_content': '', # 清空货物内容
|
||
'goods_retrieved_at': now,
|
||
'last_access_at': now
|
||
}
|
||
|
||
update_stmt = update(OperatePointLayer).where(
|
||
OperatePointLayer.layer_name.in_(target_site_ids),
|
||
OperatePointLayer.is_occupied == True # 只更新占用的库位
|
||
).values(update_values)
|
||
|
||
result = await session.execute(update_stmt)
|
||
await session.commit()
|
||
|
||
updated_count = result.rowcount
|
||
already_empty_count = len(already_empty)
|
||
|
||
if already_empty_count > 0:
|
||
message = f"批量设置库位为空闲成功,更新了 {updated_count} 个库位,{already_empty_count} 个库位已是空闲状态"
|
||
else:
|
||
message = f"批量设置库位为空闲成功,更新了 {updated_count} 个库位"
|
||
|
||
logger.info(f"批量设置库位完成: {message}")
|
||
return {
|
||
"success": True,
|
||
"message": message,
|
||
"data": {
|
||
"totalSites": len(target_site_ids),
|
||
"updatedSites": updated_count
|
||
}
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"批量设置库位异常: {str(e)}")
|
||
return {
|
||
"success": False,
|
||
"message": f"批量设置库位异常: {str(e)}"
|
||
}
|
||
|
||
# 从密集库区中获取库位处理器
|
||
@register_handler(StorageBlockName.GET_IDLE_CROWDED_SITE)
|
||
class GetIdleCrowdedSiteBlockHandler(StorageBlockHandler):
|
||
"""从密集库区中获取库位处理器"""
|
||
|
||
async def execute(
|
||
self,
|
||
block: Dict[str, Any],
|
||
input_params: Dict[str, Any],
|
||
context: TaskContext
|
||
) -> Dict[str, Any]:
|
||
"""执行获取密集库位操作"""
|
||
try:
|
||
print(f"从密集库区中获取库位处理器参数>>>>>: {input_params}<<<<<<=======================")
|
||
# 获取关键参数用于验证
|
||
# {'groupName': '["a", "b"]', 'filled': 'true', 'content': '1321', 'lock': 'true', 'retry': 'true', 'retryPeriod': '1000', 'retryNum': '3'}
|
||
group_name = input_params.get("groupName", [])
|
||
filled = input_params.get("filled")
|
||
content = input_params.get("content")
|
||
lock = input_params.get("lock", False)
|
||
retry = input_params.get("retry", False)
|
||
retry_num = input_params.get("retryNum", 1)
|
||
# 获取地图ID
|
||
map_id = context.map_id
|
||
|
||
# 校验并转换 groupName 参数格式(必须是列表)
|
||
if group_name is not None and group_name != "" and group_name != []:
|
||
if not isinstance(group_name, list):
|
||
# 如果是字符串,尝试解析为列表
|
||
if isinstance(group_name, str):
|
||
# 检查是否为空字符串
|
||
if group_name.strip() == "":
|
||
group_name = []
|
||
else:
|
||
try:
|
||
import json
|
||
group_name = json.loads(group_name)
|
||
if not isinstance(group_name, list):
|
||
raise ValueError("解析后不是列表格式")
|
||
input_params["groupName"] = group_name
|
||
except (json.JSONDecodeError, ValueError):
|
||
result = {
|
||
"success": False,
|
||
"message": "groupName参数必须是列表格式或能解析为列表的字符串"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
else:
|
||
result = {
|
||
"success": False,
|
||
"message": "groupName参数必须是列表格式"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
# 参数检查
|
||
if not group_name:
|
||
result = {
|
||
"success": False,
|
||
"message": "库区集不能为空"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
if filled is None:
|
||
result = {
|
||
"success": False,
|
||
"message": "取/放参数不能为空"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
# 校验并转换 filled 参数为布尔类型
|
||
if isinstance(filled, str):
|
||
filled_lower = filled.lower()
|
||
if filled_lower in ('true', '1', 'yes', 'on'):
|
||
filled = True
|
||
elif filled_lower in ('false', '0', 'no', 'off'):
|
||
filled = False
|
||
else:
|
||
result = {
|
||
"success": False,
|
||
"message": "filled参数必须是布尔类型或可转换为布尔类型的字符串"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
input_params["filled"] = filled
|
||
elif not isinstance(filled, bool):
|
||
result = {
|
||
"success": False,
|
||
"message": "filled参数必须是布尔类型"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
# 校验并转换 lock 参数为布尔类型
|
||
if isinstance(lock, str):
|
||
# 如果是空字符串,映射为 false
|
||
if lock.strip() == "":
|
||
lock = False
|
||
else:
|
||
lock_lower = lock.lower()
|
||
if lock_lower in ('true', '1', 'yes', 'on'):
|
||
lock = True
|
||
elif lock_lower in ('false', '0', 'no', 'off'):
|
||
lock = False
|
||
else:
|
||
result = {
|
||
"success": False,
|
||
"message": "lock参数必须是布尔类型或可转换为布尔类型的字符串"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
input_params["lock"] = lock
|
||
elif not isinstance(lock, bool):
|
||
result = {
|
||
"success": False,
|
||
"message": "lock参数必须是布尔类型"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
# 校验并转换 retry 参数为布尔类型
|
||
if isinstance(retry, str):
|
||
retry_lower = retry.lower()
|
||
if retry_lower in ('true', '1', 'yes', 'on'):
|
||
retry = True
|
||
elif retry_lower in ('false', '0', 'no', 'off'):
|
||
retry = False
|
||
else:
|
||
result = {
|
||
"success": False,
|
||
"message": "retry参数必须是布尔类型或可转换为布尔类型的字符串"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
input_params["retry"] = retry
|
||
elif not isinstance(retry, bool):
|
||
result = {
|
||
"success": False,
|
||
"message": "retry参数必须是布尔类型"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
# 确保retry_num是整数类型
|
||
try:
|
||
retry_num = int(retry_num)
|
||
except (ValueError, TypeError):
|
||
logger.warning(f"retry_num参数类型错误或无法转换为整数: {retry_num}, 将使用默认值1")
|
||
retry_num = 1
|
||
input_params["retryNum"] = retry_num
|
||
|
||
# 确保retry_period是数字
|
||
retry_period = input_params.get("retryPeriod")
|
||
if retry_period is not None:
|
||
try:
|
||
retry_sleep = float(retry_period) / 1000
|
||
except (ValueError, TypeError):
|
||
logger.warning(f"retry_period参数类型错误或无法转换为数字: {retry_period}, 使用默认值1秒")
|
||
retry_sleep = 1
|
||
else:
|
||
retry_sleep = 1
|
||
|
||
# 调用外部API执行获取密集库位
|
||
attempts = 1
|
||
# 确保max_attempts是整数
|
||
max_attempts = retry_num if retry and retry_num is not None else 1
|
||
|
||
result = None
|
||
while attempts <= max_attempts:
|
||
# 调用数据库查询获取密集库位
|
||
result = await self._get_idle_crowded_site_from_db(group_name, filled, content, lock, map_id, context.task_record_id)
|
||
# print(f"从密集库区中获取库位结果>>>>>: {result}<<<<<<=======================")
|
||
if result.get("success", False) and result.get("data", {}).get("siteId"):
|
||
# 获取成功
|
||
site_id = result.get("data", {}).get("siteId")
|
||
# 设置上下文变量
|
||
context.set_variable("siteId", site_id)
|
||
context.set_block_output(block.get("name"), {"siteId": site_id})
|
||
|
||
if attempts == 1:
|
||
result["message"] = f"从密集库区中获取库位成功,库位ID: {site_id}"
|
||
else:
|
||
result["message"] = f"第{attempts}次重试从密集库区中获取库位成功,库位ID: {site_id}"
|
||
break
|
||
else:
|
||
# 获取失败,判断是否需要重试
|
||
if retry and attempts < max_attempts:
|
||
logger.info(f"从密集库区中获取库位失败,第 {attempts} 次重试,等待 {retry_sleep} 秒后重试")
|
||
await asyncio.sleep(retry_sleep)
|
||
attempts += 1
|
||
else:
|
||
result["message"] = f"从密集库区中获取库位失败: {result.get('message', '未找到合适的库位')}"
|
||
break
|
||
|
||
# 记录执行结果
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
except Exception as e:
|
||
result = {
|
||
"success": False,
|
||
"message": f"从密集库区中获取库位执行异常: {str(e)}"
|
||
}
|
||
# 记录异常
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
async def _get_idle_crowded_site_from_db(self, group_names: List[str], filled: bool, content: str, lock: bool, map_id: str, task_record_id: str) -> Dict[str, Any]:
|
||
"""从密集库区中获取库位"""
|
||
try:
|
||
async with get_async_session() as session:
|
||
# 按库区优先级逐个查询
|
||
for area_name in group_names:
|
||
logger.info(f"正在查询库区: {area_name}")
|
||
|
||
# 先获取库区信息,确定选择库位的逻辑
|
||
area_query = select(StorageArea).where(
|
||
StorageArea.area_name == area_name,
|
||
StorageArea.scene_id == map_id,
|
||
StorageArea.is_deleted == False
|
||
)
|
||
area_result = await session.execute(area_query)
|
||
area = area_result.scalar_one_or_none()
|
||
|
||
if not area:
|
||
logger.warning(f"库区 {area_name} 不存在,跳过")
|
||
continue
|
||
|
||
# 检查库区是否有锁定的库位
|
||
locked_count_query = select(OperatePointLayer).where(
|
||
OperatePointLayer.area_name == area_name,
|
||
OperatePointLayer.scene_id == map_id,
|
||
OperatePointLayer.is_locked == True,
|
||
OperatePointLayer.is_disabled == False,
|
||
OperatePointLayer.is_deleted == False
|
||
)
|
||
locked_count_result = await session.execute(locked_count_query)
|
||
locked_layers = locked_count_result.fetchall()
|
||
|
||
if locked_layers:
|
||
logger.info(f"库区 {area_name} 有 {len(locked_layers)} 个锁定的库位,跳过此库区")
|
||
continue
|
||
|
||
# 获取库区内所有库位,用于确定禁用库位的影响范围
|
||
all_layers_query = select(OperatePointLayer).where(
|
||
OperatePointLayer.area_name == area_name,
|
||
OperatePointLayer.scene_id == map_id,
|
||
OperatePointLayer.is_deleted == False
|
||
)
|
||
|
||
all_layers_result = await session.execute(all_layers_query)
|
||
all_layers = all_layers_result.scalars().all()
|
||
|
||
# 自然排序函数,适配包含数字的库位名称
|
||
def natural_sort_key(layer_name):
|
||
"""自然排序键函数,将字符串中的数字部分转换为整数进行排序"""
|
||
import re
|
||
def convert(text):
|
||
return int(text) if text.isdigit() else text.lower()
|
||
return [convert(c) for c in re.split('([0-9]+)', layer_name)]
|
||
|
||
import datetime
|
||
|
||
# 对所有库位进行自然排序
|
||
all_layers.sort(key=lambda x: natural_sort_key(x.layer_name))
|
||
|
||
# 找出禁用的库位
|
||
disabled_layers = [layer for layer in all_layers if layer.is_disabled]
|
||
|
||
# 构建查询条件 - 查找符合条件的库位
|
||
query = select(OperatePointLayer).where(
|
||
OperatePointLayer.area_name == area_name,
|
||
OperatePointLayer.scene_id == map_id,
|
||
OperatePointLayer.is_disabled == False,
|
||
OperatePointLayer.is_deleted == False,
|
||
OperatePointLayer.is_locked == False,
|
||
)
|
||
|
||
# 如果有禁用的库位,只能使用第一个禁用库位之后的库位
|
||
if disabled_layers:
|
||
first_disabled_layer = disabled_layers[0] # 获取第一个禁用库位对象
|
||
logger.info(f"库区 {area_name} 存在禁用库位 {first_disabled_layer.layer_name},只能使用其后的库位")
|
||
|
||
# 获取第一个禁用库位在所有库位中的位置
|
||
disabled_position = None
|
||
for i, layer in enumerate(all_layers):
|
||
if layer.layer_name == first_disabled_layer.layer_name:
|
||
disabled_position = i
|
||
break
|
||
|
||
if disabled_position is not None:
|
||
# 获取禁用库位之后的所有库位名称
|
||
valid_layer_names = [layer.layer_name for layer in all_layers[disabled_position + 1:]]
|
||
|
||
if valid_layer_names:
|
||
# 添加条件:只能使用禁用库位之后的库位
|
||
query = query.where(OperatePointLayer.layer_name.in_(valid_layer_names))
|
||
logger.info(f"库区 {area_name} 可用库位范围: {valid_layer_names}")
|
||
else:
|
||
# 禁用库位之后没有库位了,跳过这个库区
|
||
logger.info(f"库区 {area_name} 禁用库位之后没有可用库位,跳过")
|
||
continue
|
||
else:
|
||
logger.warning(f"库区 {area_name} 无法确定禁用库位位置,跳过")
|
||
continue
|
||
else:
|
||
logger.info(f"库区 {area_name} 没有禁用库位,可以使用所有符合条件的库位")
|
||
|
||
# 根据取放货需求添加条件
|
||
if filled:
|
||
# 取货:需要有货的库位
|
||
query = query.where(OperatePointLayer.is_occupied == True)
|
||
|
||
else:
|
||
# 放货:需要没货的库位
|
||
query = query.where(OperatePointLayer.is_occupied == False)
|
||
|
||
# 注意:不在数据库层面限制结果数量,因为需要在Python中进行自然排序
|
||
|
||
# 执行查询
|
||
result = await session.execute(query)
|
||
candidate_layers = result.scalars().all()
|
||
|
||
# 对候选库位进行自然排序,确保正确的顺序
|
||
if candidate_layers:
|
||
if filled:
|
||
# 取货:根据库区的选择逻辑进行排序,需要考虑时间
|
||
if area.select_logic == 1:
|
||
# 先进先出:按存放时间升序,时间相同时按库位名称自然排序
|
||
candidate_layers.sort(key=lambda x: (
|
||
x.goods_stored_at or datetime.datetime.min,
|
||
natural_sort_key(x.layer_name)
|
||
))
|
||
elif area.select_logic == 2:
|
||
# 先进后出:按存放时间降序,时间相同时按库位名称自然排序
|
||
candidate_layers.sort(key=lambda x: (
|
||
-(x.goods_stored_at or datetime.datetime.min).timestamp(),
|
||
natural_sort_key(x.layer_name)
|
||
))
|
||
else:
|
||
# 默认按库位名称自然排序
|
||
candidate_layers.sort(key=lambda x: natural_sort_key(x.layer_name))
|
||
else:
|
||
# 放货:只按库位名称自然排序,不考虑时间
|
||
candidate_layers.sort(key=lambda x: natural_sort_key(x.layer_name))
|
||
|
||
layer = candidate_layers[0] # 取第一个
|
||
else:
|
||
layer = None
|
||
|
||
if layer:
|
||
site_id = layer.layer_name
|
||
logger.info(f"在库区 {area_name} 中找到合适的库位: {site_id}")
|
||
|
||
# 如果需要锁定库位
|
||
if lock:
|
||
try:
|
||
# 锁定库位
|
||
import datetime
|
||
lock_stmt = update(OperatePointLayer).where(
|
||
OperatePointLayer.layer_name == site_id,
|
||
OperatePointLayer.scene_id == map_id,
|
||
OperatePointLayer.is_locked == False # 乐观锁
|
||
).values(
|
||
is_locked=True,
|
||
locked_by=f"task_{task_record_id}",
|
||
last_access_at=datetime.datetime.now()
|
||
)
|
||
|
||
lock_result = await session.execute(lock_stmt)
|
||
|
||
if lock_result.rowcount > 0:
|
||
# 如果是放货且指定了货物内容,更新货物内容
|
||
if not filled and content:
|
||
content_stmt = update(OperatePointLayer).where(
|
||
OperatePointLayer.layer_name == site_id,
|
||
OperatePointLayer.scene_id == map_id
|
||
).values(
|
||
goods_content=content,
|
||
goods_stored_at=datetime.datetime.now(),
|
||
last_access_at=datetime.datetime.now()
|
||
)
|
||
await session.execute(content_stmt)
|
||
|
||
await session.commit()
|
||
|
||
return {
|
||
"success": True,
|
||
"message": f"从密集库区 {area_name} 获取库位成功并已锁定,库位ID: {site_id}",
|
||
"data": {
|
||
"siteId": site_id,
|
||
"areaName": area_name,
|
||
"locked": True
|
||
}
|
||
}
|
||
else:
|
||
# 锁定失败,可能被其他进程抢占
|
||
logger.warning(f"库位 {site_id} 锁定失败,可能被其他进程抢占")
|
||
continue
|
||
|
||
except Exception as e:
|
||
logger.error(f"锁定库位 {site_id} 失败: {str(e)}")
|
||
continue
|
||
else:
|
||
# 不需要锁定,直接返回
|
||
# 如果是放货且指定了货物内容,更新货物内容
|
||
if not filled and content:
|
||
try:
|
||
import datetime
|
||
content_stmt = update(OperatePointLayer).where(
|
||
OperatePointLayer.layer_name == site_id,
|
||
OperatePointLayer.scene_id == map_id
|
||
).values(
|
||
goods_content=content,
|
||
goods_stored_at=datetime.datetime.now(),
|
||
last_access_at=datetime.datetime.now()
|
||
)
|
||
await session.execute(content_stmt)
|
||
await session.commit()
|
||
except Exception as e:
|
||
logger.error(f"更新库位 {site_id} 货物内容失败: {str(e)}")
|
||
# 即使更新失败,也返回库位信息
|
||
|
||
return {
|
||
"success": True,
|
||
"message": f"从密集库区 {area_name} 获取库位成功,库位ID: {site_id}",
|
||
"data": {
|
||
"siteId": site_id,
|
||
"areaName": area_name,
|
||
"locked": False
|
||
}
|
||
}
|
||
else:
|
||
logger.info(f"库区 {area_name} 中没有找到合适的库位")
|
||
continue
|
||
|
||
# 所有库区都没有找到合适的库位
|
||
return {
|
||
"success": False,
|
||
"message": f"在指定的库区集 {group_names} 中没有找到合适的库位"
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"从密集库区中获取库位异常: {str(e)}")
|
||
return {
|
||
"success": False,
|
||
"message": f"从密集库区中获取库位异常: {str(e)}"
|
||
}
|
||
|
||
# 获取库位处理器
|
||
@register_handler(StorageBlockName.GET_IDLE_SITE)
|
||
class GetIdleSiteBlockHandler(StorageBlockHandler):
|
||
"""获取库位处理器"""
|
||
|
||
async def execute(
|
||
self,
|
||
block: Dict[str, Any],
|
||
input_params: Dict[str, Any],
|
||
context: TaskContext
|
||
) -> Dict[str, Any]:
|
||
"""执行获取库位操作"""
|
||
try:
|
||
# 获取关键参数用于验证
|
||
locked = input_params.get("locked")
|
||
retry_period = input_params.get("retryPeriod")
|
||
# 获取地图ID
|
||
map_id = context.map_id
|
||
logger.info(f"获取库位处理器参数: {input_params}")
|
||
|
||
# 必填参数检查
|
||
if locked is None:
|
||
result = {
|
||
"success": False,
|
||
"message": "是否已锁定参数不能为空"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
# 确保retry_period是数字类型
|
||
if retry_period is not None:
|
||
try:
|
||
retry_period = float(retry_period)
|
||
retry_sleep = retry_period / 1000
|
||
except (ValueError, TypeError):
|
||
logger.warning(f"retry_period参数类型错误或无法转换为数字: {retry_period}, 将使用默认值1000")
|
||
retry_sleep = 1
|
||
else:
|
||
retry_sleep = 1
|
||
|
||
# 设置重试计数器(用于日志记录)
|
||
retry_count = 0
|
||
|
||
while True: # 无限循环,直到找到库位
|
||
# 尝试获取库位
|
||
result = await self._query_idle_site_from_db(input_params, context.task_record_id, map_id)
|
||
logger.info(f"获取库位处理器结果: {result}")
|
||
if result.get("success", False) and result.get("data", {}).get("siteId"):
|
||
# 获取成功
|
||
site_id = result.get("data", {}).get("siteId")
|
||
# 设置上下文变量
|
||
context.set_variable("siteId", site_id)
|
||
context.set_block_output(block.get("name"), {"siteId": site_id})
|
||
|
||
# 根据重试次数设置不同的成功消息
|
||
if retry_count == 0:
|
||
result["message"] = f"获取库位成功,库位ID: {site_id}"
|
||
else:
|
||
result["message"] = f"第{retry_count}次重试获取库位成功,库位ID: {site_id}"
|
||
|
||
# 记录执行结果并退出循环
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
else:
|
||
# 获取失败,记录日志并继续重试
|
||
retry_count += 1
|
||
logger.info(f"获取库位失败,第{retry_count}次重试,等待 {retry_sleep} 秒后继续")
|
||
|
||
# 更新临时结果消息(仅用于日志记录)
|
||
temp_result = {
|
||
"success": False,
|
||
"message": f"第{retry_count}次尝试获取库位失败: {result.get('message', '未找到合适的库位')},将继续重试"
|
||
}
|
||
await self._record_task_log(block, temp_result, context)
|
||
|
||
# 等待指定时间后继续重试
|
||
await asyncio.sleep(retry_sleep)
|
||
|
||
except Exception as e:
|
||
result = {
|
||
"success": False,
|
||
"message": f"获取库位执行异常: {str(e)}"
|
||
}
|
||
# 记录异常
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
async def _query_idle_site_from_db(self, input_params: Dict[str, Any], task_record_id: str, map_id:str) -> Dict[str, Any]:
|
||
"""从数据库查询空闲库位"""
|
||
try:
|
||
# 解析输入参数
|
||
site_id = input_params.get("siteId") # 库位ID
|
||
content = input_params.get("content") # 货物
|
||
filled = input_params.get("filled") # 是否有货物
|
||
locked = input_params.get("locked") # 是否已锁定(必填)
|
||
group_name = input_params.get("groupName") # 库区名
|
||
order_desc = input_params.get("orderDesc", True) # 是否为降序
|
||
lock_after_get = input_params.get("lock") # 获取库位后是否锁定
|
||
|
||
# 转换字符串类型的布尔值
|
||
if isinstance(filled, str):
|
||
filled = filled.lower() in ('true', '1', 'yes')
|
||
if isinstance(locked, str):
|
||
locked = locked.lower() in ('true', '1', 'yes')
|
||
if isinstance(order_desc, str):
|
||
order_desc = order_desc.lower() in ('true', '1', 'yes')
|
||
if isinstance(lock_after_get, str):
|
||
# 如果是空字符串,映射为 false
|
||
if lock_after_get.strip() == "":
|
||
lock_after_get = False
|
||
else:
|
||
lock_after_get = lock_after_get.lower() in ('true', '1', 'yes')
|
||
|
||
logger.info(f"查询库位参数: site_id={site_id}, content={content}, filled={filled}, locked={locked}, group_name={group_name}, order_desc={order_desc}, lock_after_get={lock_after_get}")
|
||
|
||
# 自然排序函数,适配包含数字的库位名称
|
||
def natural_sort_key(layer_name):
|
||
"""自然排序键函数,将字符串中的数字部分转换为整数进行排序"""
|
||
import re
|
||
def convert(text):
|
||
return int(text) if text.isdigit() else text.lower()
|
||
return [convert(c) for c in re.split('([0-9]+)', layer_name)]
|
||
|
||
async with get_async_session() as session:
|
||
# 构建查询条件
|
||
query = select(OperatePointLayer)
|
||
conditions = []
|
||
# 必填条件:是否已锁定
|
||
conditions.append(OperatePointLayer.is_locked == locked)
|
||
conditions.append(OperatePointLayer.is_disabled == False)
|
||
conditions.append(OperatePointLayer.scene_id == map_id)
|
||
# 可选条件:库位ID
|
||
if site_id:
|
||
conditions.append(OperatePointLayer.layer_name == site_id)
|
||
|
||
# 可选条件:货物内容
|
||
if content:
|
||
conditions.append(OperatePointLayer.goods_content == content)
|
||
|
||
# 可选条件:是否有货物
|
||
if filled:
|
||
conditions.append(OperatePointLayer.is_occupied == filled)
|
||
|
||
# 可选条件:库区名
|
||
if group_name:
|
||
conditions.append(OperatePointLayer.area_name == group_name)
|
||
|
||
# 应用所有条件
|
||
if conditions:
|
||
query = query.where(and_(*conditions))
|
||
|
||
# 不在数据库层面排序,改为在Python中使用自然排序
|
||
|
||
# 执行查询获取所有候选库位
|
||
result = await session.execute(query)
|
||
candidate_layers = result.scalars().all()
|
||
|
||
# 如果有候选库位,使用自然排序
|
||
if candidate_layers:
|
||
# 使用自然排序,确保正确的顺序
|
||
candidate_layers.sort(key=lambda x: natural_sort_key(x.layer_name))
|
||
|
||
# 根据order_desc参数决定排序方向
|
||
if order_desc:
|
||
candidate_layers.reverse()
|
||
|
||
# 取第一个库位
|
||
layer = candidate_layers[0]
|
||
else:
|
||
layer = None
|
||
|
||
if layer:
|
||
found_site_id = layer.layer_name
|
||
|
||
# 如果需要在获取后锁定库位
|
||
if lock_after_get:
|
||
try:
|
||
# 更新库位锁定状态
|
||
from sqlalchemy import update
|
||
update_stmt = update(OperatePointLayer).where(
|
||
OperatePointLayer.layer_name == found_site_id
|
||
).values(
|
||
is_locked=True,
|
||
locked_by=f"task_{task_record_id}" # 生成一个锁定者ID
|
||
)
|
||
await session.execute(update_stmt)
|
||
await session.commit()
|
||
|
||
logger.info(f"库位 {found_site_id} 已成功锁定")
|
||
|
||
return {
|
||
"success": True,
|
||
"message": f"查询库位成功并已锁定,库位ID: {found_site_id}",
|
||
"data": {
|
||
"siteId": found_site_id
|
||
}
|
||
}
|
||
except Exception as lock_error:
|
||
logger.error(f"锁定库位失败: {str(lock_error)}")
|
||
# 即使锁定失败,也返回查询成功的结果
|
||
return {
|
||
"success": True,
|
||
"message": f"查询库位成功,但锁定失败: {str(lock_error)},库位ID: {found_site_id}",
|
||
"data": {
|
||
"siteId": found_site_id
|
||
}
|
||
}
|
||
else:
|
||
# 不需要锁定,直接返回查询结果
|
||
return {
|
||
"success": True,
|
||
"message": f"查询库位成功,库位ID: {found_site_id}",
|
||
"data": {
|
||
"siteId": found_site_id
|
||
}
|
||
}
|
||
else:
|
||
# 未找到匹配的库位
|
||
return {
|
||
"success": False,
|
||
"message": "未找到符合条件的库位"
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"查询库位异常: {str(e)}")
|
||
return {
|
||
"success": False,
|
||
"message": f"查询库位异常: {str(e)}"
|
||
}
|
||
|
||
# 获取任务实例加锁库位处理器
|
||
@register_handler(StorageBlockName.GET_LOCKED_SITES_BY_TASK_RECORD_ID)
|
||
class GetLockedSitesByTaskRecordIdBlockHandler(StorageBlockHandler):
|
||
"""根据任务实例ID获取所有加锁库位处理器"""
|
||
|
||
async def execute(
|
||
self,
|
||
block: Dict[str, Any],
|
||
input_params: Dict[str, Any],
|
||
context: TaskContext
|
||
) -> Dict[str, Any]:
|
||
"""获取任务实例关联的所有已锁库位"""
|
||
try:
|
||
# 获取参数进行验证
|
||
task_record_id = input_params.get("taskRecordId")
|
||
|
||
# 必填参数检查
|
||
if not task_record_id:
|
||
result = {
|
||
"success": False,
|
||
"message": "任务实例ID不能为空"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
# 直接调用外部API获取库位列表
|
||
result = await self._call_external_api("get_locked_sites_by_task_record_id", input_params)
|
||
|
||
if result.get("success", False):
|
||
# 获取成功,设置上下文变量
|
||
locked_site_list = result.get("data", {}).get("lockedSiteIdList", [])
|
||
context.set_variable("lockedSiteIdList", locked_site_list)
|
||
context.set_block_output(block.get("name"), {"lockedSiteIdList": locked_site_list})
|
||
|
||
result["message"] = f"获取任务锁定库位成功,共 {len(locked_site_list)} 个库位"
|
||
else:
|
||
result["message"] = f"获取任务锁定库位失败: {result.get('message', '未知错误')}"
|
||
|
||
# 记录执行结果
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
except Exception as e:
|
||
result = {
|
||
"success": False,
|
||
"message": f"获取任务锁定库位执行异常: {str(e)}"
|
||
}
|
||
# 记录异常
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
# 获取库位扩展属性处理器
|
||
@register_handler(StorageBlockName.GET_SITE_ATTR)
|
||
class GetSiteAttrBlockHandler(StorageBlockHandler):
|
||
"""获取库位扩展属性处理器"""
|
||
|
||
async def execute(
|
||
self,
|
||
block: Dict[str, Any],
|
||
input_params: Dict[str, Any],
|
||
context: TaskContext
|
||
) -> Dict[str, Any]:
|
||
"""获取库位扩展属性值"""
|
||
try:
|
||
# 获取参数进行验证
|
||
site_id = input_params.get("siteId")
|
||
attr_name = input_params.get("attrName")
|
||
map_id = context.map_id
|
||
# 必填参数检查
|
||
if not site_id:
|
||
result = {
|
||
"success": False,
|
||
"message": "库位ID不能为空"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
if not attr_name:
|
||
result = {
|
||
"success": False,
|
||
"message": "属性名称不能为空"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
# 直接从数据库获取扩展属性值
|
||
result = await self._get_site_attr_from_db(site_id, attr_name, map_id)
|
||
|
||
if result.get("success", False):
|
||
# 获取成功,设置上下文变量
|
||
attr_value = result.get("data", {}).get("attrValue")
|
||
context.set_variable("attrValue", attr_value)
|
||
context.set_block_output(block.get("name"), {"attrValue": attr_value})
|
||
|
||
# 记录执行结果
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
except Exception as e:
|
||
result = {
|
||
"success": False,
|
||
"message": f"获取库位属性值执行异常: {str(e)}"
|
||
}
|
||
# 记录异常
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
async def _get_site_attr_from_db(self, site_id: str, attr_name: str, map_id: str) -> Dict[str, Any]:
|
||
"""从数据库获取库位扩展属性值"""
|
||
try:
|
||
async with get_async_session() as session:
|
||
# 先检查库位是否存在
|
||
site_check_query = select(OperatePointLayer).where(
|
||
OperatePointLayer.layer_name == site_id,
|
||
OperatePointLayer.is_deleted == False,
|
||
OperatePointLayer.scene_id == map_id,
|
||
OperatePointLayer.is_disabled == False
|
||
)
|
||
site_check_result = await session.execute(site_check_query)
|
||
existing_layer = site_check_result.scalar_one_or_none()
|
||
|
||
if not existing_layer:
|
||
return {
|
||
"success": False,
|
||
"message": f"库位不存在: {site_id}"
|
||
}
|
||
|
||
# 检查扩展属性是否已定义
|
||
extended_property_query = select(ExtendedProperty).where(
|
||
ExtendedProperty.property_name == attr_name,
|
||
ExtendedProperty.is_deleted == False
|
||
)
|
||
extended_property_result = await session.execute(extended_property_query)
|
||
extended_property = extended_property_result.scalar_one_or_none()
|
||
|
||
if not extended_property:
|
||
return {
|
||
"success": False,
|
||
"message": f"扩展属性 '{attr_name}' 不存在"
|
||
}
|
||
|
||
# 解析config_json获取扩展属性值
|
||
import json
|
||
|
||
attr_value = None
|
||
attr_type = extended_property.property_type.value
|
||
is_required = extended_property.is_required
|
||
|
||
if existing_layer.config_json:
|
||
try:
|
||
config = json.loads(existing_layer.config_json)
|
||
if 'extended_fields' in config and attr_name in config['extended_fields']:
|
||
attr_info = config['extended_fields'][attr_name]
|
||
attr_value = attr_info.get('value')
|
||
# 如果config中有类型信息,使用config中的类型
|
||
if 'type' in attr_info:
|
||
attr_type = attr_info['type']
|
||
# 如果config中有required信息,使用config中的required
|
||
if 'is_required' in attr_info:
|
||
is_required = attr_info['is_required']
|
||
except Exception as e:
|
||
logger.error(f"解析库位层 {site_id} 的config_json失败: {str(e)}")
|
||
|
||
# 如果没有找到属性值,使用默认值
|
||
if attr_value is None:
|
||
attr_value = extended_property.default_value
|
||
|
||
logger.info(f"获取库位 {site_id} 扩展属性 {attr_name} 成功: {attr_value}")
|
||
|
||
return {
|
||
"success": True,
|
||
"message": f"获取库位扩展属性成功,{attr_name}: {attr_value}",
|
||
"data": {
|
||
"attrValue": attr_value,
|
||
"attrType": attr_type,
|
||
"isRequired": is_required,
|
||
"propertyName": attr_name
|
||
}
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取库位扩展属性异常: {str(e)}")
|
||
return {
|
||
"success": False,
|
||
"message": f"获取库位扩展属性异常: {str(e)}"
|
||
}
|
||
|
||
# 查询库位处理器
|
||
@register_handler(StorageBlockName.QUERY_IDLE_SITE)
|
||
class QueryIdleSiteBlockHandler(StorageBlockHandler):
|
||
"""查询库位处理器"""
|
||
|
||
async def execute(
|
||
self,
|
||
block: Dict[str, Any],
|
||
input_params: Dict[str, Any],
|
||
context: TaskContext
|
||
) -> Dict[str, Any]:
|
||
"""查询库位"""
|
||
try:
|
||
# print(f"查询库位处理器参数>>>>>: {input_params}<<<<<<=======================")
|
||
|
||
# 获取地图ID
|
||
map_id = context.map_id
|
||
|
||
# 获取参数
|
||
site_id = input_params.get("siteId")
|
||
content = input_params.get("content")
|
||
filled = input_params.get("filled")
|
||
locked = input_params.get("locked")
|
||
group_name = input_params.get("groupName")
|
||
order_desc = input_params.get("orderDesc", True)
|
||
|
||
# 参数类型转换
|
||
if filled is not None:
|
||
if isinstance(filled, str):
|
||
filled = filled.lower() in ('true', '1', 'yes', 'on')
|
||
|
||
if locked is not None:
|
||
if isinstance(locked, str):
|
||
locked = locked.lower() in ('true', '1', 'yes', 'on')
|
||
|
||
if isinstance(order_desc, str):
|
||
order_desc = order_desc.lower() in ('true', '1', 'yes', 'on')
|
||
|
||
# 执行数据库查询
|
||
result = await self._query_site_from_db(input_params, map_id, site_id, content, filled, locked, group_name, order_desc)
|
||
|
||
if result.get("success", False) and result.get("data", {}).get("site"):
|
||
# 查询成功,设置上下文变量
|
||
site = result.get("data", {}).get("site")
|
||
context.set_variable("site", site)
|
||
context.set_block_output(block.get("name"), {"site": site})
|
||
|
||
site_id = site.get("layer_name", "未知")
|
||
result["message"] = f"查询库位成功,库位ID: {site_id}"
|
||
else:
|
||
result["message"] = f"查询库位失败: {result.get('message', '未找到符合条件的库位')}"
|
||
|
||
# 记录执行结果
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
except Exception as e:
|
||
result = {
|
||
"success": False,
|
||
"message": f"查询库位执行异常: {str(e)}"
|
||
}
|
||
# 记录异常
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
async def _query_site_from_db(self, input_params: Dict[str, Any], map_id: str, site_id: str, content: str, filled: bool, locked: bool, group_name: str, order_desc: bool) -> Dict[str, Any]:
|
||
"""从数据库查询库位"""
|
||
try:
|
||
async with get_async_session() as session:
|
||
# 构建查询条件
|
||
query = select(OperatePointLayer)
|
||
conditions = []
|
||
|
||
# 基础条件
|
||
conditions.append(OperatePointLayer.is_deleted == False)
|
||
conditions.append(OperatePointLayer.scene_id == map_id)
|
||
conditions.append(OperatePointLayer.is_disabled == False)
|
||
|
||
# 可选条件:库位ID
|
||
if site_id:
|
||
conditions.append(OperatePointLayer.layer_name == site_id)
|
||
|
||
# 可选条件:货物内容
|
||
if content:
|
||
conditions.append(OperatePointLayer.goods_content == content)
|
||
|
||
# 可选条件:是否有货物/占用状态
|
||
if filled is not None:
|
||
conditions.append(OperatePointLayer.is_occupied == filled)
|
||
|
||
# 可选条件:是否锁定
|
||
if locked is not None:
|
||
conditions.append(OperatePointLayer.is_locked == locked)
|
||
|
||
# 可选条件:库区名
|
||
if group_name:
|
||
conditions.append(OperatePointLayer.area_name == group_name)
|
||
|
||
# 应用所有条件
|
||
if conditions:
|
||
query = query.where(and_(*conditions))
|
||
|
||
# 排序:按station_name排序
|
||
if order_desc:
|
||
query = query.order_by(desc(OperatePointLayer.layer_name))
|
||
else:
|
||
query = query.order_by(asc(OperatePointLayer.layer_name))
|
||
|
||
# 只获取第一个匹配的库位
|
||
query = query.limit(1)
|
||
|
||
logger.info(f"查询库位SQL条件: site_id={site_id}, content={content}, filled={filled}, locked={locked}, group_name={group_name}, order_desc={order_desc}")
|
||
|
||
# 执行查询
|
||
result = await session.execute(query)
|
||
layer = result.scalar_one_or_none()
|
||
|
||
if layer:
|
||
# 构造返回的库位信息
|
||
site_info = {
|
||
"layer_name": layer.layer_name,
|
||
}
|
||
|
||
return {
|
||
"success": True,
|
||
"message": f"查询库位成功,库位ID: {layer.layer_name}",
|
||
"data": {
|
||
"site": site_info
|
||
}
|
||
}
|
||
else:
|
||
# 未找到匹配的库位
|
||
return {
|
||
"success": False,
|
||
"message": "未找到符合条件的库位"
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"查询库位异常: {str(e)}")
|
||
return {
|
||
"success": False,
|
||
"message": f"查询库位异常: {str(e)}"
|
||
}
|
||
|
||
# 设置库位扩展属性处理器
|
||
@register_handler(StorageBlockName.SET_SITE_ATTR)
|
||
class SetSiteAttrBlockHandler(StorageBlockHandler):
|
||
"""设置库位扩展属性处理器"""
|
||
|
||
async def execute(
|
||
self,
|
||
block: Dict[str, Any],
|
||
input_params: Dict[str, Any],
|
||
context: TaskContext
|
||
) -> Dict[str, Any]:
|
||
"""设置库位扩展属性值"""
|
||
try:
|
||
# 获取参数进行验证
|
||
site_id = input_params.get("siteId")
|
||
attr_name = input_params.get("attrName")
|
||
attr_value = input_params.get("attrValue")
|
||
map_id = context.map_id
|
||
|
||
# 必填参数检查
|
||
if not site_id:
|
||
result = {
|
||
"success": False,
|
||
"message": "库位ID不能为空"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
if not attr_name:
|
||
result = {
|
||
"success": False,
|
||
"message": "属性名称不能为空"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
# 直接操作数据库设置扩展属性值
|
||
result = await self._set_site_attr_in_db(site_id, attr_name, attr_value, map_id)
|
||
|
||
# 记录执行结果
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
except Exception as e:
|
||
result = {
|
||
"success": False,
|
||
"message": f"设置库位属性值执行异常: {str(e)}"
|
||
}
|
||
# 记录异常
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
async def _set_site_attr_in_db(self, site_id: str, attr_name: str, attr_value: Any, map_id:str) -> Dict[str, Any]:
|
||
"""在数据库中设置库位扩展属性值"""
|
||
try:
|
||
async with get_async_session() as session:
|
||
# 先检查库位是否存在
|
||
site_check_query = select(OperatePointLayer).where(
|
||
OperatePointLayer.layer_name == site_id,
|
||
OperatePointLayer.is_deleted == False,
|
||
OperatePointLayer.scene_id == map_id
|
||
)
|
||
site_check_result = await session.execute(site_check_query)
|
||
existing_layer = site_check_result.scalar_one_or_none()
|
||
|
||
if not existing_layer:
|
||
return {
|
||
"success": False,
|
||
"message": f"库位不存在: {site_id}"
|
||
}
|
||
|
||
# 检查扩展属性是否已定义
|
||
extended_property_query = select(ExtendedProperty).where(
|
||
ExtendedProperty.property_name == attr_name,
|
||
ExtendedProperty.is_deleted == False
|
||
)
|
||
extended_property_result = await session.execute(extended_property_query)
|
||
extended_property = extended_property_result.scalar_one_or_none()
|
||
|
||
# 如果扩展属性不存在,则创建新的扩展属性
|
||
if not extended_property:
|
||
try:
|
||
# 推断属性类型
|
||
# property_type = self._infer_property_type(attr_value)
|
||
|
||
# 创建新的扩展属性
|
||
extended_property = ExtendedProperty(
|
||
property_key=attr_name,
|
||
property_name=attr_name,
|
||
property_type=ExtendedPropertyTypeEnum.STRING,
|
||
)
|
||
|
||
session.add(extended_property)
|
||
await session.commit()
|
||
await session.refresh(extended_property)
|
||
|
||
logger.info(f"自动创建扩展属性: {attr_name}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建扩展属性失败: {str(e)}")
|
||
return {
|
||
"success": False,
|
||
"message": f"创建扩展属性失败: {str(e)}"
|
||
}
|
||
|
||
# 如果扩展属性存在但被禁用,启用它
|
||
elif not extended_property.is_enabled:
|
||
try:
|
||
# 启用扩展属性
|
||
enable_stmt = update(ExtendedProperty).where(
|
||
ExtendedProperty.id == extended_property.id
|
||
).values(
|
||
is_enabled=True,
|
||
updated_at=datetime.datetime.now()
|
||
)
|
||
|
||
await session.execute(enable_stmt)
|
||
await session.commit()
|
||
|
||
# 刷新对象状态
|
||
await session.refresh(extended_property)
|
||
|
||
logger.info(f"启用扩展属性: {attr_name}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"启用扩展属性失败: {str(e)}")
|
||
return {
|
||
"success": False,
|
||
"message": f"启用扩展属性失败: {str(e)}"
|
||
}
|
||
|
||
# 解析现有的config_json
|
||
import json
|
||
import datetime
|
||
|
||
config = {}
|
||
if existing_layer.config_json:
|
||
try:
|
||
config = json.loads(existing_layer.config_json)
|
||
except Exception as e:
|
||
logger.error(f"解析库位层 {site_id} 的config_json失败: {str(e)}")
|
||
config = {}
|
||
|
||
# 确保extended_fields字段存在
|
||
if 'extended_fields' not in config:
|
||
config['extended_fields'] = {}
|
||
|
||
# 更新扩展属性值
|
||
config['extended_fields'][attr_name] = {
|
||
'value': attr_value,
|
||
'type': extended_property.property_type.value,
|
||
'is_required': extended_property.is_required,
|
||
'updated_at': datetime.datetime.now().isoformat()
|
||
}
|
||
|
||
# 更新config_json
|
||
try:
|
||
updated_config_json = json.dumps(config, ensure_ascii=False, indent=2)
|
||
|
||
# 更新数据库
|
||
update_stmt = update(OperatePointLayer).where(
|
||
OperatePointLayer.layer_name == site_id
|
||
).values(
|
||
config_json=updated_config_json,
|
||
updated_at=datetime.datetime.now()
|
||
)
|
||
|
||
await session.execute(update_stmt)
|
||
await session.commit()
|
||
|
||
value_display = attr_value if attr_value is not None else "null"
|
||
logger.info(f"库位 {site_id} 扩展属性 {attr_name} 已成功设置为: {value_display}")
|
||
|
||
return {
|
||
"success": True,
|
||
"message": f"设置库位扩展属性成功,{site_id}.{attr_name} = {value_display}"
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"序列化库位层 {site_id} 的config_json失败: {str(e)}")
|
||
return {
|
||
"success": False,
|
||
"message": f"更新扩展属性失败: {str(e)}"
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"设置库位扩展属性异常: {str(e)}")
|
||
return {
|
||
"success": False,
|
||
"message": f"设置库位扩展属性异常: {str(e)}"
|
||
}
|
||
|
||
|
||
# 设置库位货物处理器
|
||
@register_handler(StorageBlockName.SET_SITE_CONTENT)
|
||
class SetSiteContentBlockHandler(StorageBlockHandler):
|
||
"""设置库位货物处理器"""
|
||
|
||
async def execute(
|
||
self,
|
||
block: Dict[str, Any],
|
||
input_params: Dict[str, Any],
|
||
context: TaskContext
|
||
) -> Dict[str, Any]:
|
||
"""设置库位货物"""
|
||
try:
|
||
# 获取参数进行验证
|
||
site_id = input_params.get("siteId")
|
||
content = input_params.get("content")
|
||
map_id = context.map_id
|
||
# 必填参数检查
|
||
if not site_id:
|
||
result = {
|
||
"success": False,
|
||
"message": "库位ID不能为空"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
if content is None:
|
||
result = {
|
||
"success": False,
|
||
"message": "货物内容不能为空"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
# 直接操作数据库设置库位货物
|
||
result = await self._set_site_content_in_db(site_id, content, map_id)
|
||
|
||
# 记录执行结果
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
except Exception as e:
|
||
result = {
|
||
"success": False,
|
||
"message": f"设置库位货物执行异常: {str(e)}"
|
||
}
|
||
# 记录异常
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
async def _set_site_content_in_db(self, site_id: str, content: str, map_id:str) -> Dict[str, Any]:
|
||
"""在数据库中设置库位货物内容"""
|
||
try:
|
||
async with get_async_session() as session:
|
||
# 先检查库位是否存在
|
||
site_check_query = select(OperatePointLayer).where(
|
||
OperatePointLayer.layer_name == site_id,
|
||
OperatePointLayer.scene_id == map_id
|
||
)
|
||
site_check_result = await session.execute(site_check_query)
|
||
existing_layer = site_check_result.scalar_one_or_none()
|
||
|
||
if not existing_layer:
|
||
return {
|
||
"success": False,
|
||
"message": f"库位不存在: {site_id}"
|
||
}
|
||
|
||
# 库位存在,设置货物内容
|
||
from sqlalchemy import update
|
||
import datetime
|
||
|
||
update_stmt = update(OperatePointLayer).where(
|
||
OperatePointLayer.layer_name == site_id,
|
||
OperatePointLayer.scene_id == map_id
|
||
).values(
|
||
goods_content=content,
|
||
is_occupied=True,
|
||
goods_stored_at=datetime.datetime.now(), # 记录货物存放时间
|
||
last_access_at=datetime.datetime.now() # 更新最后访问时间
|
||
)
|
||
|
||
await session.execute(update_stmt)
|
||
await session.commit()
|
||
|
||
logger.info(f"库位 {site_id} 货物内容已成功设置为: {content}")
|
||
|
||
return {
|
||
"success": True,
|
||
"message": f"设置库位货物成功,库位ID: {site_id},货物内容: {content}"
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"设置库位货物异常: {str(e)}")
|
||
return {
|
||
"success": False,
|
||
"message": f"设置库位货物异常: {str(e)}"
|
||
}
|
||
|
||
# 设置库位为空处理器
|
||
@register_handler(StorageBlockName.SET_SITE_EMPTY)
|
||
class SetSiteEmptyBlockHandler(StorageBlockHandler):
|
||
"""设置库位为空处理器"""
|
||
|
||
async def execute(
|
||
self,
|
||
block: Dict[str, Any],
|
||
input_params: Dict[str, Any],
|
||
context: TaskContext
|
||
) -> Dict[str, Any]:
|
||
"""设置库位为空"""
|
||
try:
|
||
# 获取参数进行验证
|
||
site_id = input_params.get("siteId")
|
||
map_id = context.map_id
|
||
# 必填参数检查
|
||
if not site_id:
|
||
result = {
|
||
"success": False,
|
||
"message": "库位ID不能为空"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
result = await self._set_site_empty_in_db(site_id, map_id)
|
||
|
||
if result.get("success", False):
|
||
result["message"] = f"设置库位为空成功,库位ID: {site_id}"
|
||
else:
|
||
result["message"] = f"设置库位为空失败: {result.get('message', '未知错误')}"
|
||
|
||
# 记录执行结果
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
except Exception as e:
|
||
result = {
|
||
"success": False,
|
||
"message": f"设置库位为空执行异常: {str(e)}"
|
||
}
|
||
# 记录异常
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
async def _set_site_empty_in_db(self, site_id: str, map_id:str) -> Dict[str, Any]:
|
||
"""在数据库中设置库位为非占用状态"""
|
||
try:
|
||
async with get_async_session() as session:
|
||
# 先检查库位是否存在
|
||
site_check_query = select(OperatePointLayer).where(
|
||
OperatePointLayer.layer_name == site_id,
|
||
OperatePointLayer.scene_id == map_id
|
||
)
|
||
site_check_result = await session.execute(site_check_query)
|
||
existing_layer = site_check_result.scalar_one_or_none()
|
||
|
||
if not existing_layer:
|
||
return {
|
||
"success": False,
|
||
"message": f"库位不存在: {site_id}"
|
||
}
|
||
|
||
# 检查库位是否已经被占用
|
||
if not existing_layer.is_occupied:
|
||
return {
|
||
"success": False,
|
||
"message": f"库位 {site_id} 已处于非占用状态,无法重复设置"
|
||
}
|
||
|
||
# 库位存在且未被占用,设置为占用状态
|
||
from sqlalchemy import update
|
||
import datetime
|
||
|
||
update_stmt = update(OperatePointLayer).where(
|
||
OperatePointLayer.layer_name == site_id,
|
||
OperatePointLayer.scene_id == map_id
|
||
).values(
|
||
is_occupied=False,
|
||
goods_content='', # 清空货物内容
|
||
goods_retrieved_at=datetime.datetime.now(), # 记录货物取出时间
|
||
last_access_at=datetime.datetime.now()
|
||
)
|
||
|
||
await session.execute(update_stmt)
|
||
await session.commit()
|
||
|
||
logger.info(f"库位 {site_id} 已成功设置为空闲状态")
|
||
|
||
return {
|
||
"success": True,
|
||
"message": f"设置库位为空成功,库位ID: {site_id}"
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"设置库位为非占用异常: {str(e)}")
|
||
return {
|
||
"success": False,
|
||
"message": f"设置库位为非占用异常: {str(e)}"
|
||
}
|
||
|
||
# 设置库位为占用处理器
|
||
@register_handler(StorageBlockName.SET_SITE_FILLED)
|
||
class SetSiteFilledBlockHandler(StorageBlockHandler):
|
||
"""设置库位为占用处理器"""
|
||
|
||
async def execute(
|
||
self,
|
||
block: Dict[str, Any],
|
||
input_params: Dict[str, Any],
|
||
context: TaskContext
|
||
) -> Dict[str, Any]:
|
||
"""设置库位为占用"""
|
||
try:
|
||
# 获取参数进行验证
|
||
site_id = input_params.get("siteId")
|
||
map_id = context.map_id
|
||
# 必填参数检查
|
||
if not site_id:
|
||
result = {
|
||
"success": False,
|
||
"message": "库位ID不能为空"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
# 直接操作数据库设置库位为占用
|
||
result = await self._set_site_filled_in_db(site_id, map_id)
|
||
# 记录执行结果
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
except Exception as e:
|
||
result = {
|
||
"success": False,
|
||
"message": f"设置库位为占用执行异常: {str(e)}"
|
||
}
|
||
# 记录异常
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
async def _set_site_filled_in_db(self, site_id: str, map_id:str) -> Dict[str, Any]:
|
||
"""在数据库中设置库位为占用状态"""
|
||
try:
|
||
async with get_async_session() as session:
|
||
# 先检查库位是否存在
|
||
site_check_query = select(OperatePointLayer).where(
|
||
OperatePointLayer.layer_name == site_id,
|
||
OperatePointLayer.scene_id == map_id
|
||
)
|
||
site_check_result = await session.execute(site_check_query)
|
||
existing_layer = site_check_result.scalar_one_or_none()
|
||
|
||
if not existing_layer:
|
||
return {
|
||
"success": False,
|
||
"message": f"库位不存在: {site_id}"
|
||
}
|
||
|
||
# 检查库位是否已经被占用
|
||
if existing_layer.is_occupied:
|
||
return {
|
||
"success": False,
|
||
"message": f"库位 {site_id} 已处于占用状态,无法重复设置"
|
||
}
|
||
|
||
# 库位存在且未被占用,设置为占用状态
|
||
from sqlalchemy import update
|
||
import datetime
|
||
|
||
update_stmt = update(OperatePointLayer).where(
|
||
OperatePointLayer.layer_name == site_id,
|
||
OperatePointLayer.scene_id == map_id
|
||
).values(
|
||
is_occupied=True,
|
||
goods_content='',
|
||
goods_stored_at=datetime.datetime.now(),
|
||
last_access_at=datetime.datetime.now(),
|
||
)
|
||
|
||
await session.execute(update_stmt)
|
||
await session.commit()
|
||
|
||
logger.info(f"库位 {site_id} 已成功设置为占用状态")
|
||
|
||
return {
|
||
"success": True,
|
||
"message": f"设置库位为占用成功,库位ID: {site_id}"
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"设置库位为占用异常: {str(e)}")
|
||
return {
|
||
"success": False,
|
||
"message": f"设置库位为占用异常: {str(e)}"
|
||
}
|
||
|
||
# 锁定库位处理器
|
||
@register_handler(StorageBlockName.SET_SITE_LOCKED)
|
||
class SetSiteLockedBlockHandler(StorageBlockHandler):
|
||
"""锁定库位处理器"""
|
||
|
||
async def execute(
|
||
self,
|
||
block: Dict[str, Any],
|
||
input_params: Dict[str, Any],
|
||
context: TaskContext
|
||
) -> Dict[str, Any]:
|
||
"""锁定库位"""
|
||
try:
|
||
# 获取参数进行验证
|
||
site_id = input_params.get("siteId")
|
||
locked_id = input_params.get("lockedId")
|
||
if_fair = input_params.get("ifFair", False) # 是否为公平锁
|
||
retry_times = input_params.get("retryTimes", 0) # 重试次数,默认为0表示不重试
|
||
map_id = context.map_id
|
||
# 必填参数检查
|
||
if not site_id:
|
||
result = {
|
||
"success": False,
|
||
"message": "库位ID不能为空"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
# 如果未指定锁定者ID,则使用当前任务ID
|
||
if not locked_id:
|
||
locked_id = context.block_record_id
|
||
input_params["lockedId"] = locked_id
|
||
|
||
# 确保retry_times是整数类型
|
||
try:
|
||
retry_times = int(retry_times)
|
||
except (ValueError, TypeError):
|
||
logger.warning(f"retryTimes参数类型错误或无法转换为整数: {retry_times}, 将使用默认值0")
|
||
retry_times = 0
|
||
|
||
logger.info(f"锁定库位参数: site_id={site_id}, locked_id={locked_id}, if_fair={if_fair}, retry_times={retry_times}")
|
||
|
||
# 直接从数据库锁定库位
|
||
result = await self._set_site_locked_in_db(site_id, locked_id, if_fair, retry_times, map_id)
|
||
|
||
if result.get("success", False):
|
||
# 设置上下文变量
|
||
context.set_variable("success", True)
|
||
context.set_block_output(block.get("name"), {"success": True})
|
||
else:
|
||
# 设置上下文变量
|
||
context.set_variable("success", False)
|
||
context.set_block_output(block.get("name"), {"success": False})
|
||
|
||
# 记录最终执行结果
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
except Exception as e:
|
||
result = {
|
||
"success": False,
|
||
"message": f"锁定库位执行异常: {str(e)}"
|
||
}
|
||
# 记录异常
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
async def _set_site_locked_in_db(self, site_id: str, locked_id: str, if_fair: bool, retry_times: int, map_id:str) -> Dict[str, Any]:
|
||
"""在数据库中锁定库位"""
|
||
try:
|
||
async with get_async_session() as session:
|
||
# 先检查库位是否存在
|
||
site_check_query = select(OperatePointLayer).where(
|
||
OperatePointLayer.layer_name == site_id,
|
||
OperatePointLayer.scene_id == map_id
|
||
)
|
||
site_check_result = await session.execute(site_check_query)
|
||
existing_layer = site_check_result.scalar_one_or_none()
|
||
|
||
if not existing_layer:
|
||
return {
|
||
"success": False,
|
||
"message": f"库位不存在: {site_id}"
|
||
}
|
||
|
||
# 实现重试逻辑
|
||
attempts = 0
|
||
max_attempts = retry_times + 1 # 包括首次尝试
|
||
retry_sleep = 1 # 默认重试间隔为1秒
|
||
|
||
while attempts < max_attempts:
|
||
attempts += 1
|
||
|
||
# 重新查询库位当前状态
|
||
current_site_query = select(OperatePointLayer).where(
|
||
OperatePointLayer.layer_name == site_id,
|
||
OperatePointLayer.scene_id == map_id,
|
||
OperatePointLayer.is_disabled == False,
|
||
# OperatePointLayer.is_empty_tray == False
|
||
)
|
||
current_site_result = await session.execute(current_site_query)
|
||
current_layer = current_site_result.scalar_one_or_none()
|
||
|
||
if not current_layer:
|
||
return {
|
||
"success": False,
|
||
"message": f"库位不存在: {site_id}"
|
||
}
|
||
|
||
# 检查库位是否已被锁定
|
||
if current_layer.is_locked:
|
||
# 如果是同一个锁定者,视为已锁定成功
|
||
if current_layer.locked_by == locked_id:
|
||
logger.info(f"库位 {site_id} 已被同一锁定者 {locked_id} 锁定")
|
||
return {
|
||
"success": True,
|
||
"message": f"库位已被同一锁定者锁定,库位ID: {site_id}"
|
||
}
|
||
|
||
# 被其他锁定者锁定,需要重试
|
||
if attempts < max_attempts:
|
||
logger.info(f"库位 {site_id} 已被其他锁定者 {current_layer.locked_by} 锁定,第 {attempts} 次尝试,等待 {retry_sleep} 秒后重试")
|
||
await asyncio.sleep(retry_sleep)
|
||
continue
|
||
else:
|
||
# 达到最大重试次数,返回失败
|
||
return {
|
||
"success": False,
|
||
"message": f"库位 {site_id} 已被其他锁定者 {current_layer.locked_by} 锁定,重试 {retry_times} 次后仍然失败"
|
||
}
|
||
|
||
# 库位未被锁定,尝试锁定
|
||
try:
|
||
import datetime
|
||
update_stmt = update(OperatePointLayer).where(
|
||
OperatePointLayer.layer_name == site_id,
|
||
OperatePointLayer.scene_id == map_id,
|
||
OperatePointLayer.is_locked == False # 乐观锁:只有未锁定时才能锁定
|
||
).values(
|
||
is_locked=True,
|
||
locked_by=locked_id,
|
||
last_access_at=datetime.datetime.now()
|
||
)
|
||
|
||
result = await session.execute(update_stmt)
|
||
|
||
# 检查是否成功更新
|
||
if result.rowcount > 0:
|
||
await session.commit()
|
||
|
||
if attempts == 1:
|
||
success_msg = f"锁定库位成功,库位ID: {site_id}"
|
||
else:
|
||
success_msg = f"第{attempts}次尝试锁定库位成功,库位ID: {site_id}"
|
||
|
||
logger.info(success_msg)
|
||
return {
|
||
"success": True,
|
||
"message": success_msg
|
||
}
|
||
else:
|
||
# 更新失败,可能在更新期间被其他进程锁定
|
||
if attempts < max_attempts:
|
||
logger.info(f"库位 {site_id} 锁定失败(可能被其他进程抢占),第 {attempts} 次尝试,等待 {retry_sleep} 秒后重试")
|
||
await asyncio.sleep(retry_sleep)
|
||
continue
|
||
else:
|
||
return {
|
||
"success": False,
|
||
"message": f"库位 {site_id} 锁定失败,可能被其他进程抢占,重试 {retry_times} 次后仍然失败"
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"锁定库位时发生异常: {str(e)}")
|
||
if attempts < max_attempts:
|
||
logger.info(f"第 {attempts} 次尝试发生异常,等待 {retry_sleep} 秒后重试")
|
||
await asyncio.sleep(retry_sleep)
|
||
continue
|
||
else:
|
||
return {
|
||
"success": False,
|
||
"message": f"锁定库位时发生异常: {str(e)}"
|
||
}
|
||
|
||
# 不应该到达这里
|
||
return {
|
||
"success": False,
|
||
"message": f"锁定库位失败,未知错误"
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"锁定库位异常: {str(e)}")
|
||
return {
|
||
"success": False,
|
||
"message": f"锁定库位异常: {str(e)}"
|
||
}
|
||
|
||
# 设置库位标签处理器
|
||
@register_handler(StorageBlockName.SET_SITE_TAGS)
|
||
class SetSiteTagsBlockHandler(StorageBlockHandler):
|
||
"""设置库位标签处理器"""
|
||
|
||
async def execute(
|
||
self,
|
||
block: Dict[str, Any],
|
||
input_params: Dict[str, Any],
|
||
context: TaskContext
|
||
) -> Dict[str, Any]:
|
||
"""设置库位标签"""
|
||
try:
|
||
# 获取参数进行验证
|
||
site_id = input_params.get("siteId")
|
||
tags = input_params.get("tags")
|
||
replace_mode = input_params.get("replaceMode", False) # 是否替换模式,默认为追加模式
|
||
map_id = context.map_id
|
||
# 必填参数检查
|
||
if not site_id:
|
||
result = {
|
||
"success": False,
|
||
"message": "库位ID不能为空"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
# 转换字符串类型的布尔值
|
||
if isinstance(replace_mode, str):
|
||
replace_mode = replace_mode.lower() in ('true', '1', 'yes')
|
||
|
||
logger.info(f"设置库位标签参数: site_id={site_id}, tags={tags}, replace_mode={replace_mode}")
|
||
|
||
# 直接从数据库设置库位标签
|
||
result = await self._set_site_tags_in_db(site_id, tags, replace_mode, map_id)
|
||
|
||
# 记录执行结果
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
except Exception as e:
|
||
result = {
|
||
"success": False,
|
||
"message": f"设置库位标签执行异常: {str(e)}"
|
||
}
|
||
# 记录异常
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
async def _set_site_tags_in_db(self, site_id: str, tags: str, replace_mode: bool = False, map_id:str=None) -> Dict[str, Any]:
|
||
"""在数据库中设置库位标签"""
|
||
try:
|
||
async with get_async_session() as session:
|
||
# 先检查库位是否存在
|
||
site_check_query = select(OperatePointLayer).where(
|
||
OperatePointLayer.layer_name == site_id,
|
||
OperatePointLayer.scene_id == map_id,
|
||
OperatePointLayer.is_disabled == False
|
||
)
|
||
site_check_result = await session.execute(site_check_query)
|
||
existing_layer = site_check_result.scalar_one_or_none()
|
||
|
||
if not existing_layer:
|
||
return {
|
||
"success": False,
|
||
"message": f"库位不存在: {site_id}"
|
||
}
|
||
|
||
# 处理标签数据
|
||
new_tags_set = set()
|
||
|
||
# 解析新输入的标签
|
||
if tags:
|
||
# 支持多种分隔符:逗号、分号、空格等
|
||
import re
|
||
tag_list = re.split(r'[,,;;\s]+', str(tags).strip())
|
||
new_tags_set.update(tag.strip() for tag in tag_list if tag.strip())
|
||
|
||
# 处理现有标签
|
||
final_tags_set = set()
|
||
if not replace_mode and existing_layer.tags:
|
||
# 追加模式:合并现有标签
|
||
existing_tag_list = re.split(r'[,,;;\s]+', existing_layer.tags.strip())
|
||
final_tags_set.update(tag.strip() for tag in existing_tag_list if tag.strip())
|
||
|
||
# 合并新标签
|
||
final_tags_set.update(new_tags_set)
|
||
|
||
# 去重并用逗号连接
|
||
final_tags = ','.join(sorted(final_tags_set)) if final_tags_set else ''
|
||
|
||
# 执行更新操作
|
||
try:
|
||
import datetime
|
||
update_stmt = update(OperatePointLayer).where(
|
||
OperatePointLayer.layer_name == site_id,
|
||
OperatePointLayer.scene_id == map_id
|
||
).values(
|
||
tags=final_tags,
|
||
last_access_at=datetime.datetime.now()
|
||
)
|
||
|
||
result = await session.execute(update_stmt)
|
||
|
||
# 检查是否成功更新
|
||
if result.rowcount > 0:
|
||
await session.commit()
|
||
|
||
tags_display = final_tags if final_tags else "空"
|
||
operation_mode = "替换" if replace_mode else "追加"
|
||
success_msg = f"设置库位标签成功({operation_mode}模式),库位ID: {site_id},标签: {tags_display}"
|
||
logger.info(success_msg)
|
||
return {
|
||
"success": True,
|
||
"message": success_msg
|
||
}
|
||
else:
|
||
# 更新失败
|
||
return {
|
||
"success": False,
|
||
"message": f"设置库位标签失败,库位 {site_id} 可能不存在或已被删除"
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"设置库位标签时发生异常: {str(e)}")
|
||
return {
|
||
"success": False,
|
||
"message": f"设置库位标签时发生异常: {str(e)}"
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"设置库位标签异常: {str(e)}")
|
||
return {
|
||
"success": False,
|
||
"message": f"设置库位标签异常: {str(e)}"
|
||
}
|
||
|
||
# 解锁库位处理器
|
||
@register_handler(StorageBlockName.SET_SITE_UNLOCKED)
|
||
class SetSiteUnlockedBlockHandler(StorageBlockHandler):
|
||
"""解锁库位处理器"""
|
||
|
||
async def execute(
|
||
self,
|
||
block: Dict[str, Any],
|
||
input_params: Dict[str, Any],
|
||
context: TaskContext
|
||
) -> Dict[str, Any]:
|
||
"""解锁库位"""
|
||
try:
|
||
# 获取参数进行验证
|
||
site_id = input_params.get("siteId")
|
||
un_locked_id = input_params.get("unLockedId")
|
||
map_id = context.map_id
|
||
# 必填参数检查
|
||
if not site_id:
|
||
result = {
|
||
"success": False,
|
||
"message": "库位ID不能为空"
|
||
}
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
# 如果未指定解锁者ID,则使用当前任务ID
|
||
if not un_locked_id:
|
||
un_locked_id = context.get_task_record_id()
|
||
input_params["unLockedId"] = un_locked_id
|
||
logger.info(f"未指定解锁者ID,使用当前任务ID: {un_locked_id}")
|
||
|
||
logger.info(f"解锁库位参数: site_id={site_id}, un_locked_id={un_locked_id}")
|
||
|
||
# 直接从数据库解锁库位
|
||
result = await self._set_site_unlocked_in_db(site_id, un_locked_id, map_id)
|
||
|
||
if result.get("success", False):
|
||
# 设置上下文变量
|
||
context.set_variable("unlockSuccess", True)
|
||
context.set_block_output(block.get("name"), {"unlockSuccess": True})
|
||
else:
|
||
# 设置上下文变量
|
||
context.set_variable("unlockSuccess", False)
|
||
context.set_block_output(block.get("name"), {"unlockSuccess": False})
|
||
|
||
# 记录执行结果
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
except Exception as e:
|
||
result = {
|
||
"success": False,
|
||
"message": f"解锁库位执行异常: {str(e)}"
|
||
}
|
||
# 记录异常
|
||
await self._record_task_log(block, result, context)
|
||
return result
|
||
|
||
async def _set_site_unlocked_in_db(self, site_id: str, un_locked_id: str, map_id:str) -> Dict[str, Any]:
|
||
"""在数据库中解锁库位"""
|
||
try:
|
||
async with get_async_session() as session:
|
||
# 先检查库位是否存在
|
||
site_check_query = select(OperatePointLayer).where(
|
||
OperatePointLayer.layer_name == site_id,
|
||
OperatePointLayer.scene_id == map_id,
|
||
OperatePointLayer.is_disabled == False
|
||
)
|
||
site_check_result = await session.execute(site_check_query)
|
||
existing_layer = site_check_result.scalar_one_or_none()
|
||
|
||
if not existing_layer:
|
||
return {
|
||
"success": False,
|
||
"message": f"库位不存在: {site_id}"
|
||
}
|
||
|
||
# 检查库位是否已被锁定
|
||
if not existing_layer.is_locked:
|
||
# 库位未被锁定,直接返回成功
|
||
logger.info(f"库位 {site_id} 未被锁定,解锁操作视为成功")
|
||
return {
|
||
"success": True,
|
||
"message": f"库位未被锁定,解锁操作成功,库位ID: {site_id}"
|
||
}
|
||
|
||
# 检查解锁者权限
|
||
if existing_layer.locked_by != un_locked_id:
|
||
# 解锁者不是当前锁定者,执行失败
|
||
logger.error(f"解锁者 {un_locked_id} 不是库位 {site_id} 的锁定者 {existing_layer.locked_by},解锁失败")
|
||
return {
|
||
"success": False,
|
||
"message": f"解锁失败,解锁者 {un_locked_id} 不是库位 {site_id} 的锁定者 {existing_layer.locked_by}"
|
||
}
|
||
|
||
# 执行解锁操作
|
||
try:
|
||
import datetime
|
||
update_stmt = update(OperatePointLayer).where(
|
||
OperatePointLayer.layer_name == site_id,
|
||
OperatePointLayer.scene_id == map_id,
|
||
OperatePointLayer.is_locked == True # 只有锁定的库位才能解锁
|
||
).values(
|
||
is_locked=False,
|
||
locked_by=None, # 清空锁定者
|
||
last_access_at=datetime.datetime.now()
|
||
)
|
||
|
||
result = await session.execute(update_stmt)
|
||
|
||
# 检查是否成功更新
|
||
if result.rowcount > 0:
|
||
await session.commit()
|
||
|
||
success_msg = f"解锁库位成功,库位ID: {site_id}"
|
||
logger.info(success_msg)
|
||
return {
|
||
"success": True,
|
||
"message": success_msg
|
||
}
|
||
else:
|
||
# 更新失败,可能在更新期间库位状态发生变化
|
||
return {
|
||
"success": False,
|
||
"message": f"解锁库位失败,库位 {site_id} 可能已被其他进程解锁"
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"解锁库位时发生异常: {str(e)}")
|
||
return {
|
||
"success": False,
|
||
"message": f"解锁库位时发生异常: {str(e)}"
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"解锁库位异常: {str(e)}")
|
||
return {
|
||
"success": False,
|
||
"message": f"解锁库位异常: {str(e)}"
|
||
} |