VWED_server/services/execution/handlers/storage_location.py

1598 lines
68 KiB
Python
Raw Normal View History

2025-04-30 16:57:46 +08:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
库位处理器模块
2025-07-14 10:29:37 +08:00
提供与库区操作相关的各种处理器
2025-04-30 16:57:46 +08:00
"""
import logging
import json
import asyncio
import aiohttp
import uuid
from typing import Dict, Any, List, Optional
2025-07-14 10:29:37 +08:00
from sqlalchemy import and_, or_, desc, asc, select, update
from sqlalchemy.orm import Session
2025-04-30 16:57:46 +08:00
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
2025-07-14 10:29:37 +08:00
from data.session import get_async_session
from data.models.operate_point_layer import OperatePointLayer
from data.models.extended_property import ExtendedProperty, ExtendedPropertyTypeEnum
2025-04-30 16:57:46 +08:00
# 获取日志记录器
logger = get_logger("services.execution.handlers.storage_location")
# 创建一个基础库位处理器类,包含通用方法
class StorageBlockHandler(BlockHandler):
2025-07-14 10:29:37 +08:00
pass
2025-04-30 16:57:46 +08:00
# 批量设置库位处理器
@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")
2025-07-14 10:29:37 +08:00
# 获取地图ID
map_id = context.map_id
2025-04-30 16:57:46 +08:00
# 参数检查
if not site_ids and not group_names:
result = {
"success": False,
"message": "库位ID和库区集不能同时为空"
}
await self._record_task_log(block, result, context)
return result
2025-07-14 10:29:37 +08:00
if not map_id:
result = {
"success": False,
"message": "地图ID不能为空"
}
2025-04-30 16:57:46 +08:00
if filled is None:
result = {
"success": False,
"message": "占用参数不能为空"
}
await self._record_task_log(block, result, context)
return result
# 直接调用外部API执行批量设置传递所有input_params
result = await self._call_external_api("batch_setting_site", input_params)
if result.get("success", False):
result["message"] = f"批量设置库位成功,共设置 {len(site_ids) or len(group_names)} 个库位"
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_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:
# 获取关键参数用于验证
group_name = input_params.get("groupName", [])
filled = input_params.get("filled")
retry = input_params.get("retry", False)
retry_num = input_params.get("retryNum", 1)
# 参数检查
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
# 确保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._call_external_api("get_idle_crowded_site", input_params)
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})
result["message"] = f"获取密集库位成功库位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
# 获取库位处理器
@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")
2025-07-14 10:29:37 +08:00
# 获取地图ID
map_id = context.map_id
logger.info(f"获取库位处理器参数: {input_params}")
2025-04-30 16:57:46 +08:00
# 必填参数检查
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: # 无限循环,直到找到库位
# 尝试获取库位
2025-07-14 10:29:37 +08:00
result = await self._query_idle_site_from_db(input_params, context.task_record_id, map_id)
2025-04-30 16:57:46 +08:00
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
2025-07-14 10:29:37 +08:00
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):
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}")
async with get_async_session() as session:
# 构建查询条件
query = select(OperatePointLayer)
conditions = []
conditions.append(OperatePointLayer.is_empty_tray == False)
# 必填条件:是否已锁定
conditions.append(OperatePointLayer.is_locked == locked)
conditions.append(OperatePointLayer.is_disabled == False)
# 可选条件库位ID
if site_id:
conditions.append(OperatePointLayer.id == 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))
# 排序按station_name排序
if order_desc:
query = query.order_by(desc(OperatePointLayer.station_name))
else:
query = query.order_by(asc(OperatePointLayer.station_name))
# 只获取第一个匹配的库位
query = query.limit(1)
# 执行查询
result = await session.execute(query)
layer = result.scalar_one_or_none()
if layer:
found_site_id = layer.id
# 如果需要在获取后锁定库位
if lock_after_get:
try:
# 更新库位锁定状态
from sqlalchemy import update
update_stmt = update(OperatePointLayer).where(
OperatePointLayer.id == 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)}"
}
2025-04-30 16:57:46 +08:00
# 获取任务实例加锁库位处理器
@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")
2025-07-14 10:29:37 +08:00
map_id = context.map_id
2025-04-30 16:57:46 +08:00
# 必填参数检查
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
2025-07-14 10:29:37 +08:00
# 直接从数据库获取扩展属性值
result = await self._get_site_attr_from_db(site_id, attr_name, map_id)
2025-04-30 16:57:46 +08:00
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
2025-07-14 10:29:37 +08:00
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.id == 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)}"
}
2025-04-30 16:57:46 +08:00
# 查询库位处理器
@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:
# 直接调用外部API查询库位
2025-07-14 10:29:37 +08:00
# result = await self._call_external_api("query_idle_site", input_params)
2025-04-30 16:57:46 +08:00
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("id", "未知")
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
# 设置库位扩展属性处理器
@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")
2025-07-14 10:29:37 +08:00
map_id = context.map_id
2025-04-30 16:57:46 +08:00
# 必填参数检查
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
2025-07-14 10:29:37 +08:00
# 直接操作数据库设置扩展属性值
result = await self._set_site_attr_in_db(site_id, attr_name, attr_value, map_id)
2025-04-30 16:57:46 +08:00
# 记录执行结果
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
2025-07-14 10:29:37 +08:00
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.id == 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.id == 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)}"
}
# def _infer_property_type(self, value: Any):
# """推断属性类型"""
# if isinstance(value, bool):
# return ExtendedPropertyTypeEnum.BOOLEAN
# elif isinstance(value, int):
# return ExtendedPropertyTypeEnum.INTEGER
# elif isinstance(value, float):
# return ExtendedPropertyTypeEnum.NUMBER
# elif isinstance(value, str):
# # 尝试判断是否为日期
# try:
# from datetime import datetime
# datetime.fromisoformat(value.replace('Z', '+00:00'))
# return ExtendedPropertyTypeEnum.DATE
# except:
# return ExtendedPropertyTypeEnum.STRING
# elif isinstance(value, list):
# return ExtendedPropertyTypeEnum.ARRAY
# elif isinstance(value, dict):
# return ExtendedPropertyTypeEnum.OBJECT
# else:
# return ExtendedPropertyTypeEnum.STRING
2025-04-30 16:57:46 +08:00
# 设置库位货物处理器
@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")
2025-07-14 10:29:37 +08:00
map_id = context.map_id
2025-04-30 16:57:46 +08:00
# 必填参数检查
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,
2025-07-14 10:29:37 +08:00
"message": "货物内容不能为空"
2025-04-30 16:57:46 +08:00
}
await self._record_task_log(block, result, context)
return result
2025-07-14 10:29:37 +08:00
# 直接操作数据库设置库位货物
result = await self._set_site_content_in_db(site_id, content, map_id)
2025-04-30 16:57:46 +08:00
# 记录执行结果
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
2025-07-14 10:29:37 +08:00
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.id == 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.id == 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(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)}"
}
2025-04-30 16:57:46 +08:00
# 设置库位为空处理器
@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")
2025-07-14 10:29:37 +08:00
map_id = context.map_id
2025-04-30 16:57:46 +08:00
# 必填参数检查
if not site_id:
result = {
"success": False,
"message": "库位ID不能为空"
}
await self._record_task_log(block, result, context)
return result
2025-07-14 10:29:37 +08:00
result = await self._set_site_empty_in_db(site_id, map_id)
2025-04-30 16:57:46 +08:00
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
2025-07-14 10:29:37 +08:00
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.id == 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.id == 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)}"
}
2025-04-30 16:57:46 +08:00
# 设置库位为占用处理器
@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")
2025-07-14 10:29:37 +08:00
map_id = context.map_id
2025-04-30 16:57:46 +08:00
# 必填参数检查
if not site_id:
result = {
"success": False,
"message": "库位ID不能为空"
}
await self._record_task_log(block, result, context)
return result
2025-07-14 10:29:37 +08:00
# 直接操作数据库设置库位为占用
result = await self._set_site_filled_in_db(site_id, map_id)
2025-04-30 16:57:46 +08:00
# 记录执行结果
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
2025-07-14 10:29:37 +08:00
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.id == 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.id == site_id,
OperatePointLayer.scene_id == map_id
).values(
is_occupied=True,
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)}"
}
2025-04-30 16:57:46 +08:00
# 锁定库位处理器
@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) # 是否为公平锁
2025-07-14 10:29:37 +08:00
retry_times = input_params.get("retryTimes", 0) # 重试次数默认为0表示不重试
map_id = context.map_id
2025-04-30 16:57:46 +08:00
# 必填参数检查
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:
2025-07-14 10:29:37 +08:00
locked_id = context.block_record_id
2025-04-30 16:57:46 +08:00
input_params["lockedId"] = locked_id
2025-07-14 10:29:37 +08:00
# 确保retry_times是整数类型
2025-04-30 16:57:46 +08:00
try:
2025-07-14 10:29:37 +08:00
retry_times = int(retry_times)
2025-04-30 16:57:46 +08:00
except (ValueError, TypeError):
2025-07-14 10:29:37 +08:00
logger.warning(f"retryTimes参数类型错误或无法转换为整数: {retry_times}, 将使用默认值0")
retry_times = 0
2025-04-30 16:57:46 +08:00
2025-07-14 10:29:37 +08:00
logger.info(f"锁定库位参数: site_id={site_id}, locked_id={locked_id}, if_fair={if_fair}, retry_times={retry_times}")
2025-04-30 16:57:46 +08:00
2025-07-14 10:29:37 +08:00
# 直接从数据库锁定库位
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})
2025-04-30 16:57:46 +08:00
# 记录最终执行结果
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
2025-07-14 10:29:37 +08:00
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.id == 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.id == 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.id == 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)}"
}
2025-04-30 16:57:46 +08:00
# 设置库位标签处理器
@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")
2025-07-14 10:29:37 +08:00
replace_mode = input_params.get("replaceMode", False) # 是否替换模式,默认为追加模式
map_id = context.map_id
2025-04-30 16:57:46 +08:00
# 必填参数检查
if not site_id:
result = {
"success": False,
"message": "库位ID不能为空"
}
await self._record_task_log(block, result, context)
return result
2025-07-14 10:29:37 +08:00
# 转换字符串类型的布尔值
if isinstance(replace_mode, str):
replace_mode = replace_mode.lower() in ('true', '1', 'yes')
2025-04-30 16:57:46 +08:00
2025-07-14 10:29:37 +08:00
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)
2025-04-30 16:57:46 +08:00
# 记录执行结果
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
2025-07-14 10:29:37 +08:00
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.id == 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.id == 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)}"
}
2025-04-30 16:57:46 +08:00
# 解锁库位处理器
@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")
2025-07-14 10:29:37 +08:00
map_id = context.map_id
2025-04-30 16:57:46 +08:00
# 必填参数检查
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}")
2025-07-14 10:29:37 +08:00
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)
2025-04-30 16:57:46 +08:00
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)
2025-07-14 10:29:37 +08:00
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.id == 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.id == 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)}"
}