|
|
@@ -35,6 +35,9 @@ class RunMain:
|
|
|
# dialog_result_signal = Signal(str)
|
|
|
|
|
|
# dialog_result_signal = Signal(str)
|
|
|
+
|
|
|
+ # 重试机制配置:最大重试次数(固定为1,避免死循环)
|
|
|
+ MAX_RETRY_COUNT = 1
|
|
|
|
|
|
def __init__(self, windows, token, uuid):
|
|
|
super().__init__()
|
|
|
@@ -259,6 +262,147 @@ class RunMain:
|
|
|
print("已结束抠图处理")
|
|
|
return True
|
|
|
|
|
|
+ def validate_folder_integrity(self, folder_path, folder_name, expected_output_count=None):
|
|
|
+ """
|
|
|
+ 验证货号文件夹的完整性
|
|
|
+ 检查800x800目录中的文件数量是否等于预期数量
|
|
|
+
|
|
|
+ Args:
|
|
|
+ folder_path: 货号文件夹路径
|
|
|
+ folder_name: 货号名称
|
|
|
+ expected_output_count: 预期的输出文件数量(如果为None则根据原始图数量计算)
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ tuple: (is_valid, original_count, processed_count, expected_count, message)
|
|
|
+ """
|
|
|
+ from logger import logger
|
|
|
+ import settings
|
|
|
+
|
|
|
+ original_dir = "{}/原始图".format(folder_path)
|
|
|
+ processed_dir = "{}/800x800".format(folder_path)
|
|
|
+
|
|
|
+ # 检查目录是否存在
|
|
|
+ if not os.path.exists(original_dir):
|
|
|
+ logger.warning(f"[目录校验] 货号 {folder_name} - 原始图目录不存在: {original_dir}")
|
|
|
+ return False, 0, 0, 0, f"原始图目录不存在"
|
|
|
+
|
|
|
+ if not os.path.exists(processed_dir):
|
|
|
+ logger.warning(f"[目录校验] 货号 {folder_name} - 800x800目录不存在: {processed_dir}")
|
|
|
+ return False, 0, 0, 0, f"800x800目录不存在"
|
|
|
+
|
|
|
+ # 统计原始图文件数量(只统计图片文件)
|
|
|
+ _Type = [".png", ".PNG", ".jpg", ".JPG", ".gif", ".GIF", ".jpge", ".JPGE"]
|
|
|
+ original_files = []
|
|
|
+ for f in os.listdir(original_dir):
|
|
|
+ _, ext = os.path.splitext(f)
|
|
|
+ if ext in _Type:
|
|
|
+ original_files.append(f)
|
|
|
+
|
|
|
+ original_count = len(original_files)
|
|
|
+
|
|
|
+ # 统计800x800目录文件数量
|
|
|
+ processed_files = []
|
|
|
+ for f in os.listdir(processed_dir):
|
|
|
+ _, ext = os.path.splitext(f)
|
|
|
+ if ext in _Type:
|
|
|
+ processed_files.append(f)
|
|
|
+
|
|
|
+ processed_count = len(processed_files)
|
|
|
+
|
|
|
+ # 计算预期的输出文件数量
|
|
|
+ if expected_output_count is None:
|
|
|
+ # 从配置中获取主图尺寸列表
|
|
|
+ try:
|
|
|
+ out_pic_size_list = settings.getSysConfigs("basic_configs", "main_image_size", [1600])
|
|
|
+ # 如果是字符串,尝试解析为列表
|
|
|
+ if isinstance(out_pic_size_list, str):
|
|
|
+ import json
|
|
|
+ try:
|
|
|
+ out_pic_size_list = json.loads(out_pic_size_list)
|
|
|
+ except:
|
|
|
+ out_pic_size_list = [1600]
|
|
|
+ # 确保是列表
|
|
|
+ if not isinstance(out_pic_size_list, list):
|
|
|
+ out_pic_size_list = [out_pic_size_list]
|
|
|
+ # 过滤空值
|
|
|
+ out_pic_size_list = [x for x in out_pic_size_list if x]
|
|
|
+
|
|
|
+ if not out_pic_size_list:
|
|
|
+ out_pic_size_list = [1600]
|
|
|
+
|
|
|
+ # 预期输出数量 = 原始图数量 * 尺寸数量
|
|
|
+ expected_count = original_count * len(out_pic_size_list)
|
|
|
+ logger.info(f"[目录校验] 货号 {folder_name} - 配置的输出尺寸: {out_pic_size_list}, 共{len(out_pic_size_list)}个尺寸")
|
|
|
+ except Exception as e:
|
|
|
+ logger.warning(f"[目录校验] 货号 {folder_name} - 获取配置失败,使用默认值: {e}")
|
|
|
+ expected_count = original_count # 默认1倍
|
|
|
+ else:
|
|
|
+ expected_count = expected_output_count
|
|
|
+
|
|
|
+ logger.info(f"[目录校验] 货号 {folder_name} - 原始图: {original_count}张, 800x800: {processed_count}张, 预期: {expected_count}张")
|
|
|
+
|
|
|
+ # 如果原始图为空,认为无效
|
|
|
+ if original_count == 0:
|
|
|
+ logger.error(f"[目录校验] 货号 {folder_name} - 原始图目录为空")
|
|
|
+ return False, original_count, processed_count, expected_count, "原始图目录为空"
|
|
|
+
|
|
|
+ # 严格检查:处理后的文件数量必须等于预期数量
|
|
|
+ if processed_count != expected_count:
|
|
|
+ logger.error(f"[目录校验] 货号 {folder_name} - 处理失败: 预期{expected_count}张,实际{processed_count}张 (原始图{original_count}张 × {len(out_pic_size_list) if 'out_pic_size_list' in locals() else '?'}个尺寸)")
|
|
|
+ return False, original_count, processed_count, expected_count, f"处理文件数量不匹配: 预期{expected_count}张,实际{processed_count}张"
|
|
|
+
|
|
|
+ logger.info(f"[目录校验] 货号 {folder_name} - 校验通过 ✓")
|
|
|
+ return True, original_count, processed_count, expected_count, "校验通过"
|
|
|
+
|
|
|
+ def retry_single_folder(self, goods_art_no_folder_data, image_order_list, cutout_mode, resize_image_view, logo_path, callback_func):
|
|
|
+ """
|
|
|
+ 重试单个货号的处理(仅执行一次,不会递归或循环)
|
|
|
+
|
|
|
+ Args:
|
|
|
+ goods_art_no_folder_data: 货号文件夹数据
|
|
|
+ image_order_list: 图片顺序列表
|
|
|
+ cutout_mode: 抠图模式
|
|
|
+ resize_image_view: 缩放视角
|
|
|
+ logo_path: Logo路径
|
|
|
+ callback_func: 回调函数
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ bool: 重试是否成功
|
|
|
+ """
|
|
|
+ from logger import logger
|
|
|
+
|
|
|
+ folder_name = goods_art_no_folder_data["folder_name"]
|
|
|
+ folder_path = goods_art_no_folder_data["folder_path"]
|
|
|
+
|
|
|
+ logger.info(f"[重试处理] 开始重试货号: {folder_name} (仅此一次,不会重复重试)")
|
|
|
+ callback_func(f"正在重试货号: {folder_name}")
|
|
|
+
|
|
|
+ deal = BaseDealImage(token=self.token)
|
|
|
+ try:
|
|
|
+ # 只处理这一个货号 - 注意:这里直接调用处理逻辑,不会再触发校验和重试
|
|
|
+ result = deal.shoes_run_one_folder_to_deal(
|
|
|
+ goods_art_no_folder_data=goods_art_no_folder_data,
|
|
|
+ resize_image_view=resize_image_view,
|
|
|
+ logo_path=logo_path,
|
|
|
+ image_order_list=image_order_list,
|
|
|
+ callback_func=callback_func,
|
|
|
+ windows=None,
|
|
|
+ )
|
|
|
+
|
|
|
+ if result:
|
|
|
+ logger.info(f"[重试处理] 货号 {folder_name} 重试成功 ✓")
|
|
|
+ callback_func(f"货号 {folder_name} 重试成功")
|
|
|
+ return True
|
|
|
+ else:
|
|
|
+ logger.error(f"[重试处理] 货号 {folder_name} 重试失败 (不再重试)")
|
|
|
+ callback_func(f"货号 {folder_name} 重试失败")
|
|
|
+ return False
|
|
|
+ except Exception as e:
|
|
|
+ import traceback
|
|
|
+ logger.error(f"[重试处理] 货号 {folder_name} 重试异常: {e}\n{traceback.format_exc()}")
|
|
|
+ callback_func(f"货号 {folder_name} 重试异常: {e}")
|
|
|
+ return False
|
|
|
+
|
|
|
def do_run_cutout_image(
|
|
|
self,
|
|
|
all_goods_art_no_folder_data,
|
|
|
@@ -270,43 +414,171 @@ class RunMain:
|
|
|
logo_path,
|
|
|
config_data,
|
|
|
):
|
|
|
- try:
|
|
|
- loop = asyncio.get_event_loop()
|
|
|
- except:
|
|
|
- loop = asyncio.new_event_loop()
|
|
|
- executor = ThreadPoolExecutor(max_workers=10)
|
|
|
+ from logger import logger
|
|
|
+
|
|
|
goods_arts = [
|
|
|
goods_art_no_folder_data["folder_name"]
|
|
|
for goods_art_no_folder_data in all_goods_art_no_folder_data
|
|
|
]
|
|
|
print("BaseDealImage().run_main========>>>>")
|
|
|
deal = BaseDealImage(token=self.token)
|
|
|
- try:
|
|
|
- loop.run_in_executor(
|
|
|
- executor,
|
|
|
- deal.run_main(
|
|
|
- all_goods_art_no_folder_data=all_goods_art_no_folder_data,
|
|
|
+
|
|
|
+ # 重试机制:最多重试1次,避免死循环
|
|
|
+ max_retry_count = self.MAX_RETRY_COUNT
|
|
|
+ retry_count = 0
|
|
|
+ is_success = False
|
|
|
+ failed_folders_from_run_main = [] # 从 run_main 返回的失败货号列表
|
|
|
+
|
|
|
+ while retry_count <= max_retry_count and not is_success:
|
|
|
+ try:
|
|
|
+ if retry_count > 0:
|
|
|
+ logger.warning(f"[主流程重试] 第 {retry_count} 次重试,重新执行抠图处理")
|
|
|
+ callback_func(f"处理失败,正在重试(第{retry_count}次)...")
|
|
|
+
|
|
|
+ # 重试时只处理之前失败的货号
|
|
|
+ if failed_folders_from_run_main:
|
|
|
+ logger.info(f"[主流程重试] 将重试以下货号: {[f['folder_name'] for f in failed_folders_from_run_main]}")
|
|
|
+ # 临时替换为只包含失败货号的数据
|
|
|
+ retry_folders = failed_folders_from_run_main
|
|
|
+ failed_folders_from_run_main = [] # 清空,准备接收新的失败列表
|
|
|
+ else:
|
|
|
+ retry_folders = all_goods_art_no_folder_data
|
|
|
+ else:
|
|
|
+ retry_folders = all_goods_art_no_folder_data
|
|
|
+
|
|
|
+ # 执行主流程,获取返回结果
|
|
|
+ result = deal.run_main(
|
|
|
+ all_goods_art_no_folder_data=retry_folders,
|
|
|
callback_func=callback_func,
|
|
|
image_order_list=image_order_list,
|
|
|
cutout_mode=cutout_mode,
|
|
|
resize_image_view=resize_image_view,
|
|
|
windows=windows,
|
|
|
logo_path=logo_path,
|
|
|
- ),
|
|
|
+ )
|
|
|
+
|
|
|
+ # 检查返回结果
|
|
|
+ if result and isinstance(result, dict):
|
|
|
+ is_success = result.get('success', False)
|
|
|
+ failed_folders_from_run_main = result.get('failed_folders', [])
|
|
|
+ successful_num = result.get('successful_num', 0)
|
|
|
+ error_num = result.get('error_num', 0)
|
|
|
+
|
|
|
+ logger.info(f"[主流程完成] 成功: {successful_num}, 失败: {error_num}")
|
|
|
+
|
|
|
+ if is_success:
|
|
|
+ logger.info("[主流程完成] 所有货号处理成功 ✓")
|
|
|
+ else:
|
|
|
+ # 有失败的货号,需要重试
|
|
|
+ retry_count += 1 # 增加重试计数
|
|
|
+ logger.warning(f"[主流程完成] 有 {error_num} 个货号失败,准备重试")
|
|
|
+ callback_func(f"有 {error_num} 个货号处理失败,准备重试...")
|
|
|
+
|
|
|
+ if retry_count > max_retry_count:
|
|
|
+ # 已达到最大重试次数
|
|
|
+ logger.error(f"[主流程重试] 已达到最大重试次数 {max_retry_count},放弃重试")
|
|
|
+ callback_func(f"处理失败,已重试{max_retry_count}次仍无法完成")
|
|
|
+ # 不抛出异常,继续执行后置校验
|
|
|
+ is_success = True # 强制退出循环,让后置校验处理
|
|
|
+ else:
|
|
|
+ logger.warning(f"[主流程重试] 准备第 {retry_count} 次重试,将只处理失败的货号")
|
|
|
+ callback_func(f"准备重试失败的货号...")
|
|
|
+ else:
|
|
|
+ # 兼容旧版本,如果没有返回值则认为成功
|
|
|
+ is_success = True
|
|
|
+
|
|
|
+ except UnicornException as e:
|
|
|
+ retry_count += 1
|
|
|
+ logger.error(f"[主流程重试] 第 {retry_count} 次尝试失败: {e.msg}")
|
|
|
+
|
|
|
+ if retry_count > max_retry_count:
|
|
|
+ logger.error(f"[主流程重试] 已达到最大重试次数 {max_retry_count},放弃重试")
|
|
|
+ callback_func(f"处理失败,已重试{max_retry_count}次仍无法完成")
|
|
|
+ raise UnicornException(e.msg)
|
|
|
+ else:
|
|
|
+ logger.warning(f"[主流程重试] 准备第 {retry_count} 次重试")
|
|
|
+ callback_func(f"处理异常,准备重试...")
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ import traceback
|
|
|
+ retry_count += 1
|
|
|
+ logger.error(f"[主流程重试] 第 {retry_count} 次尝试异常: {e}\n{traceback.format_exc()}")
|
|
|
+
|
|
|
+ if retry_count > max_retry_count:
|
|
|
+ logger.error(f"[主流程重试] 已达到最大重试次数 {max_retry_count},放弃重试")
|
|
|
+ callback_func(f"处理异常,已重试{max_retry_count}次仍无法完成")
|
|
|
+ raise UnicornException(e)
|
|
|
+ else:
|
|
|
+ logger.warning(f"[主流程重试] 准备第 {retry_count} 次重试")
|
|
|
+ callback_func(f"处理异常,准备重试...")
|
|
|
+
|
|
|
+ # ========== 后置校验:检查所有货号的目录完整性 ==========
|
|
|
+ logger.info("=" * 50)
|
|
|
+ logger.info("[后置校验] 开始检查所有货号的目录完整性")
|
|
|
+ logger.info("=" * 50)
|
|
|
+
|
|
|
+ failed_folders = []
|
|
|
+ for goods_art_no_folder_data in all_goods_art_no_folder_data:
|
|
|
+ if goods_art_no_folder_data["label"] != "待处理":
|
|
|
+ continue
|
|
|
+
|
|
|
+ folder_name = goods_art_no_folder_data["folder_name"]
|
|
|
+ folder_path = goods_art_no_folder_data["folder_path"]
|
|
|
+
|
|
|
+ is_valid, original_count, processed_count, expected_count, message = self.validate_folder_integrity(
|
|
|
+ folder_path, folder_name
|
|
|
)
|
|
|
- except UnicornException as e:
|
|
|
- raise UnicornException(e.msg)
|
|
|
- except Exception as e:
|
|
|
- raise UnicornException(e)
|
|
|
- # deal.run_main(
|
|
|
- # all_goods_art_no_folder_data=all_goods_art_no_folder_data,
|
|
|
- # callback_func=callback_func,
|
|
|
- # image_order_list=image_order_list,
|
|
|
- # cutout_mode=cutout_mode,
|
|
|
- # resize_image_view=resize_image_view,
|
|
|
- # windows=windows,
|
|
|
- # logo_path=logo_path,
|
|
|
- # )
|
|
|
+
|
|
|
+ if not is_valid:
|
|
|
+ logger.warning(f"[后置校验] 货号 {folder_name} 校验失败: {message}")
|
|
|
+ failed_folders.append({
|
|
|
+ "data": goods_art_no_folder_data,
|
|
|
+ "reason": message,
|
|
|
+ "original_count": original_count,
|
|
|
+ "processed_count": processed_count,
|
|
|
+ "expected_count": expected_count
|
|
|
+ })
|
|
|
+
|
|
|
+ # ========== 如果有失败的货号,进行重试(仅重试一次,避免死循环)==========
|
|
|
+ if failed_folders:
|
|
|
+ logger.info("=" * 50)
|
|
|
+ logger.info(f"[重试机制] 发现 {len(failed_folders)} 个货号需要重试")
|
|
|
+ logger.info(f"[重试机制] 重要提示:每个货号最多重试 {self.MAX_RETRY_COUNT} 次,不会无限重试")
|
|
|
+ logger.info("=" * 50)
|
|
|
+
|
|
|
+ retry_success_count = 0
|
|
|
+ retry_failed_count = 0
|
|
|
+
|
|
|
+ for failed_item in failed_folders:
|
|
|
+ folder_name = failed_item["data"]["folder_name"]
|
|
|
+ logger.info(f"[重试机制] 开始重试货号: {folder_name}, 原因: {failed_item['reason']}")
|
|
|
+ callback_func(f"检测到 {folder_name} 处理不完整,正在重试...")
|
|
|
+
|
|
|
+ # 执行单次重试(retry_single_folder 内部不会再触发校验和重试)
|
|
|
+ # 注意:这里只调用一次,不会循环或递归
|
|
|
+ retry_result = self.retry_single_folder(
|
|
|
+ goods_art_no_folder_data=failed_item["data"],
|
|
|
+ image_order_list=image_order_list,
|
|
|
+ cutout_mode=cutout_mode,
|
|
|
+ resize_image_view=resize_image_view,
|
|
|
+ logo_path=logo_path,
|
|
|
+ callback_func=callback_func
|
|
|
+ )
|
|
|
+
|
|
|
+ if retry_result:
|
|
|
+ retry_success_count += 1
|
|
|
+ else:
|
|
|
+ retry_failed_count += 1
|
|
|
+ logger.warning(f"[重试机制] 货号 {folder_name} 重试后仍然失败,不再继续重试(已达最大重试次数 {self.MAX_RETRY_COUNT})")
|
|
|
+
|
|
|
+ logger.info("=" * 50)
|
|
|
+ logger.info(f"[重试机制] 重试完成: 成功 {retry_success_count} 个, 失败 {retry_failed_count} 个")
|
|
|
+ logger.info(f"[重试机制] 所有货号处理结束(包含一次重试),流程终止")
|
|
|
+ logger.info("=" * 50)
|
|
|
+
|
|
|
+ callback_func(f"重试完成: 成功 {retry_success_count} 个, 失败 {retry_failed_count} 个")
|
|
|
+ else:
|
|
|
+ logger.info("[后置校验] 所有货号校验通过,无需重试 ✓")
|
|
|
|
|
|
recordDataPoint(
|
|
|
token=self.token,
|
|
|
@@ -314,31 +586,6 @@ class RunMain:
|
|
|
page="抠图结束",
|
|
|
data=goods_arts,
|
|
|
)
|
|
|
- # try:
|
|
|
- # loop = asyncio.get_event_loop()
|
|
|
- # loop.create_task(
|
|
|
- # sendSocketMessage(
|
|
|
- # code=0,
|
|
|
- # msg="抠图结束",
|
|
|
- # data={
|
|
|
- # "status": "已完成",
|
|
|
- # "goods_art_nos": goods_arts,
|
|
|
- # },
|
|
|
- # msg_type="segment_progress",
|
|
|
- # )
|
|
|
- # )
|
|
|
- # except:
|
|
|
- # asyncio.run(
|
|
|
- # sendSocketMessage(
|
|
|
- # code=0,
|
|
|
- # msg="抠图结束",
|
|
|
- # data={
|
|
|
- # "status": "已完成",
|
|
|
- # "goods_art_nos": goods_arts,
|
|
|
- # },
|
|
|
- # msg_type="segment_progress",
|
|
|
- # )
|
|
|
- # )
|
|
|
callback_func("已结束抠图处理")
|
|
|
return True
|
|
|
|