#!/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)}" }