#!/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.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") # 获取地图ID map_id = context.map_id # 参数检查 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不能为空" } 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") # 获取地图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) 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): 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)}" } # 获取任务实例加锁库位处理器 @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.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)}" } # 查询库位处理器 @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查询库位 # result = await self._call_external_api("query_idle_site", input_params) 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") 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.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 # 设置库位货物处理器 @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.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)}" } # 设置库位为空处理器 @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.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)}" } # 设置库位为占用处理器 @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.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)}" } # 锁定库位处理器 @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.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)}" } # 设置库位标签处理器 @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.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)}" } # 解锁库位处理器 @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.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)}" }