|
@@ -1854,88 +1854,100 @@ def minimize_window(window_title: str):
|
|
|
return {"code": 0, "msg": "最小化失败", "data": {"status": False}}
|
|
return {"code": 0, "msg": "最小化失败", "data": {"status": False}}
|
|
|
|
|
|
|
|
|
|
|
|
|
-def _sync_import_images_logic(dir_path: str, specific_goods_art_no: str = None):
|
|
|
|
|
|
|
+def _sync_import_images_logic(dir_path: str, goods_art_nos: str = None):
|
|
|
"""
|
|
"""
|
|
|
同步执行的文件遍历和数据库插入逻辑
|
|
同步执行的文件遍历和数据库插入逻辑
|
|
|
:param dir_path: 图片根目录
|
|
:param dir_path: 图片根目录
|
|
|
- :param specific_goods_art_no: 指定货号。如果提供,所有图片归为此货号;如果为None,则尝试从子目录名获取货号
|
|
|
|
|
|
|
+ :param goods_art_nos: 指定货号。
|
|
|
"""
|
|
"""
|
|
|
if not os.path.exists(dir_path):
|
|
if not os.path.exists(dir_path):
|
|
|
- raise FileNotFoundError(f"目录不存在: {dir_path}")
|
|
|
|
|
|
|
+ raise UnicornException(f"目录不存在: {dir_path}")
|
|
|
|
|
|
|
|
session = SqlQuery()
|
|
session = SqlQuery()
|
|
|
- photo_record_crud = CRUD(PhotoRecord)
|
|
|
|
|
|
|
+ # photo_record_crud = CRUD(PhotoRecord)
|
|
|
|
|
|
|
|
# 支持的图片扩展名
|
|
# 支持的图片扩展名
|
|
|
image_extensions = ('.jpg', '.jpeg', '.png', '.bmp', '.gif', '.webp')
|
|
image_extensions = ('.jpg', '.jpeg', '.png', '.bmp', '.gif', '.webp')
|
|
|
|
|
|
|
|
success_count = 0
|
|
success_count = 0
|
|
|
fail_count = 0
|
|
fail_count = 0
|
|
|
- skipped_count = 0
|
|
|
|
|
|
|
|
|
|
try:
|
|
try:
|
|
|
- # 1. 预检查:如果指定了货号,先检查数据库中是否已存在该货号的活跃记录
|
|
|
|
|
- # 参考 message_handler 213-275 行的逻辑
|
|
|
|
|
- # 如果没有指定货号,且我们假设根目录下的第一层子目录是货号,
|
|
|
|
|
- # 这里很难在不遍历的情况下知道有哪些货号。
|
|
|
|
|
- # 策略:如果 specific_goods_art_no 为空,我们在遍历每个子目录时单独检查。
|
|
|
|
|
|
|
+ # ================== 阶段 1: 收集所有待处理的唯一货号 ==================
|
|
|
|
|
+ goods_art_nos_to_check = set()
|
|
|
|
|
|
|
|
- # 2. 遍历目录
|
|
|
|
|
- # os.walk 会递归遍历所有子目录
|
|
|
|
|
|
|
+ # 用于后续遍历时的映射关系: {root_path: goods_art_no}
|
|
|
|
|
+ # 避免在第二阶段再次进行复杂的路径解析
|
|
|
|
|
+ dir_goods_map = {}
|
|
|
|
|
+
|
|
|
for root, dirs, files in os.walk(dir_path):
|
|
for root, dirs, files in os.walk(dir_path):
|
|
|
-
|
|
|
|
|
- current_goods_art_no = specific_goods_art_no
|
|
|
|
|
|
|
+ current_goods_art_no = None
|
|
|
|
|
|
|
|
# 如果没有指定全局货号,尝试从相对路径提取货号
|
|
# 如果没有指定全局货号,尝试从相对路径提取货号
|
|
|
- # 假设结构为: dir_path / goods_art_no / images...
|
|
|
|
|
if not current_goods_art_no:
|
|
if not current_goods_art_no:
|
|
|
relative_path = os.path.relpath(root, dir_path)
|
|
relative_path = os.path.relpath(root, dir_path)
|
|
|
parts = relative_path.split(os.sep)
|
|
parts = relative_path.split(os.sep)
|
|
|
- # 只有当 root 是 dir_path 的直接子目录时,才将其视为货号文件夹
|
|
|
|
|
- # 如果 relative_path 是 ".",说明还在根目录,跳过或报错
|
|
|
|
|
|
|
+
|
|
|
|
|
+ # 跳过根目录本身,只处理子目录作为货号的情况
|
|
|
if relative_path == ".":
|
|
if relative_path == ".":
|
|
|
- # 根目录下的文件可能没有货号,视业务逻辑而定,这里选择跳过或赋予默认值
|
|
|
|
|
- # 如果要求必须有货号,可以 continue 或 raise
|
|
|
|
|
continue
|
|
continue
|
|
|
|
|
|
|
|
# 取第一层目录名作为货号
|
|
# 取第一层目录名作为货号
|
|
|
current_goods_art_no = parts[0]
|
|
current_goods_art_no = parts[0]
|
|
|
-
|
|
|
|
|
- # 针对当前提取到的货号进行存在性检查 (仅在该货号的第一层目录入口处检查一次效率更高,但为了简单放在这里)
|
|
|
|
|
- # 优化:可以使用一个 set 记录已经检查过的货号,避免重复查询数据库
|
|
|
|
|
- if not hasattr(_sync_import_images_logic, 'checked_nos'):
|
|
|
|
|
- _sync_import_images_logic.checked_nos = set()
|
|
|
|
|
-
|
|
|
|
|
- if current_goods_art_no and current_goods_art_no not in _sync_import_images_logic.checked_nos:
|
|
|
|
|
- existing_record = photo_record_crud.read(
|
|
|
|
|
- session,
|
|
|
|
|
- conditions={"goods_art_no": current_goods_art_no, "delete_time": None}
|
|
|
|
|
- )
|
|
|
|
|
- if existing_record:
|
|
|
|
|
- raise UnicornException(f"货号 [{current_goods_art_no}] 已存在,请删除后重新导入~")
|
|
|
|
|
- _sync_import_images_logic.checked_nos.add(current_goods_art_no)
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if current_goods_art_no:
|
|
|
|
|
+ if current_goods_art_no not in goods_art_nos:
|
|
|
|
|
+ raise UnicornException(f"可用货号超出权限范围: {root}")
|
|
|
|
|
+ goods_art_nos_to_check.add(current_goods_art_no)
|
|
|
|
|
+ # 记录该目录下对应的货号,方便后续直接使用,无需再次计算
|
|
|
|
|
+ dir_goods_map[root] = current_goods_art_no
|
|
|
|
|
+
|
|
|
|
|
+ # ================== 阶段 2: 批量查询数据库中已存在的货号 ==================
|
|
|
|
|
+ existing_goods_nos = set()
|
|
|
|
|
+ if goods_art_nos_to_check:
|
|
|
|
|
+ # 使用 in_ 进行批量查询,只获取货号字段即可,减少数据传输
|
|
|
|
|
+ # 注意:这里假设 PhotoRecord 有 goods_art_no 字段
|
|
|
|
|
+ statement = select(PhotoRecord.goods_art_no).where(
|
|
|
|
|
+ and_(
|
|
|
|
|
+ PhotoRecord.goods_art_no.in_(list(goods_art_nos_to_check)),
|
|
|
|
|
+ PhotoRecord.delete_time == None
|
|
|
|
|
+ )
|
|
|
|
|
+ ).distinct()
|
|
|
|
|
+
|
|
|
|
|
+ db_results = session.exec(statement).all()
|
|
|
|
|
+ existing_goods_nos = set(db_results)
|
|
|
|
|
|
|
|
|
|
+ # ================== 阶段 3: 预校验,如果有已存在的货号,直接报错 ==================
|
|
|
|
|
+ if existing_goods_nos:
|
|
|
|
|
+ # 将集合转换为列表或字符串展示给用户
|
|
|
|
|
+ existing_list = ", ".join(existing_goods_nos)
|
|
|
|
|
+ raise UnicornException(f"以下货号在数据库中已存在,请删除后重新导入: [{existing_list}]")
|
|
|
|
|
+
|
|
|
|
|
+ # ================== 阶段 4: 执行文件遍历和数据插入 ==================
|
|
|
|
|
+ # 此时可以确定所有待处理的货号都是干净的,无需再在循环中查库
|
|
|
|
|
+
|
|
|
|
|
+ for root, dirs, files in os.walk(dir_path):
|
|
|
|
|
+ # 获取当前目录对应的货号
|
|
|
|
|
+ current_goods_art_no = dir_goods_map.get(root)
|
|
|
|
|
+
|
|
|
|
|
+ # 如果是根目录且没有指定特定货号,或者映射中找不到(理论上不会发生,因为上面已经过滤),则跳过
|
|
|
if not current_goods_art_no:
|
|
if not current_goods_art_no:
|
|
|
- logger.warning(f"无法确定货号,跳过目录: {root}")
|
|
|
|
|
- continue
|
|
|
|
|
|
|
+ continue
|
|
|
|
|
|
|
|
- for file in files:
|
|
|
|
|
|
|
+ for idx, file in enumerate(files):
|
|
|
if file.lower().endswith(image_extensions):
|
|
if file.lower().endswith(image_extensions):
|
|
|
full_path = os.path.join(root, file)
|
|
full_path = os.path.join(root, file)
|
|
|
|
|
|
|
|
try:
|
|
try:
|
|
|
- # 可选:检查单张图片路径是否已存在,避免重复插入同一条记录
|
|
|
|
|
- # 如果业务允许同一个货号有多张图片,则不需要检查 image_path,只需要检查货号是否存在(上面已做)
|
|
|
|
|
-
|
|
|
|
|
- # 构造插入数据
|
|
|
|
|
- # 注意:PhotoRecord 模型可能需要 create_time, action_id 等其他字段
|
|
|
|
|
- # 这里根据最小可行性原则,只填入必填项,其他依赖数据库默认值或后续更新
|
|
|
|
|
data_to_insert = {
|
|
data_to_insert = {
|
|
|
"image_path": full_path,
|
|
"image_path": full_path,
|
|
|
"goods_art_no": current_goods_art_no,
|
|
"goods_art_no": current_goods_art_no,
|
|
|
- # 如果有 action_id 要求,可能需要查询默认 action 或设为 NULL
|
|
|
|
|
- # "action_id": None,
|
|
|
|
|
- # "create_time": datetime.datetime.now(),
|
|
|
|
|
|
|
+ "action_id": 0,
|
|
|
|
|
+ "image_index": idx,
|
|
|
|
|
+ 'image_deal_mode':0,
|
|
|
|
|
+ 'photo_create_time':datetime.datetime.now(),
|
|
|
|
|
+ "create_time": datetime.datetime.now(),
|
|
|
|
|
+ "update_time": datetime.datetime.now(),
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
new_record = PhotoRecord(**data_to_insert)
|
|
new_record = PhotoRecord(**data_to_insert)
|
|
@@ -1948,21 +1960,16 @@ def _sync_import_images_logic(dir_path: str, specific_goods_art_no: str = None):
|
|
|
|
|
|
|
|
# 提交事务
|
|
# 提交事务
|
|
|
session.commit()
|
|
session.commit()
|
|
|
- logger.info(f"导入完成: 成功 {success_count}, 失败 {fail_count}, 跳过 {skipped_count}")
|
|
|
|
|
-
|
|
|
|
|
- # 清理静态变量,防止内存泄漏或状态污染(如果是长期运行的服务)
|
|
|
|
|
- if hasattr(_sync_import_images_logic, 'checked_nos'):
|
|
|
|
|
- del _sync_import_images_logic.checked_nos
|
|
|
|
|
|
|
+ logger.info(f"导入完成: 成功 {success_count}, 失败 {fail_count}")
|
|
|
|
|
|
|
|
return {
|
|
return {
|
|
|
"message": "导入完成",
|
|
"message": "导入完成",
|
|
|
"success": success_count,
|
|
"success": success_count,
|
|
|
"failed": fail_count,
|
|
"failed": fail_count,
|
|
|
- "skipped": skipped_count
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
except UnicornException:
|
|
except UnicornException:
|
|
|
- # 重新抛出自定义异常,以便上层捕获
|
|
|
|
|
|
|
+ # 重新抛出自定义异常
|
|
|
raise
|
|
raise
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
session.rollback()
|
|
session.rollback()
|
|
@@ -1970,23 +1977,22 @@ def _sync_import_images_logic(dir_path: str, specific_goods_art_no: str = None):
|
|
|
raise e
|
|
raise e
|
|
|
finally:
|
|
finally:
|
|
|
session.close()
|
|
session.close()
|
|
|
-@app.post("/import-images-from-dir")
|
|
|
|
|
-async def import_images_from_dir():
|
|
|
|
|
|
|
+@app.post("/import_dirs")
|
|
|
|
|
+async def import_images_from_dir(dir_path=None,goods_art_nos=["BH73323",'BH94727']):
|
|
|
"""
|
|
"""
|
|
|
遍历指定目录及其子目录,将图片路径导入数据库
|
|
遍历指定目录及其子目录,将图片路径导入数据库
|
|
|
"""
|
|
"""
|
|
|
- dir_path = None
|
|
|
|
|
# 基本安全检查,防止路径遍历攻击或无效路径
|
|
# 基本安全检查,防止路径遍历攻击或无效路径
|
|
|
if not os.path.isdir(dir_path):
|
|
if not os.path.isdir(dir_path):
|
|
|
- raise HTTPException(status_code=400, detail=f"无效目录: {dir_path}")
|
|
|
|
|
|
|
+ raise UnicornException(f"无效目录: {dir_path}")
|
|
|
|
|
|
|
|
try:
|
|
try:
|
|
|
# 将阻塞的 IO 和 DB 操作放在线程池中执行
|
|
# 将阻塞的 IO 和 DB 操作放在线程池中执行
|
|
|
- result = _sync_import_images_logic(dir_path)
|
|
|
|
|
|
|
+ result = _sync_import_images_logic(dir_path,goods_art_nos)
|
|
|
return {"code": 0, "msg": "操作成功", "data": result}
|
|
return {"code": 0, "msg": "操作成功", "data": result}
|
|
|
|
|
|
|
|
except FileNotFoundError as e:
|
|
except FileNotFoundError as e:
|
|
|
- raise HTTPException(status_code=404, detail=str(e))
|
|
|
|
|
|
|
+ raise UnicornException(str(e))
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
logger.error(f"API 调用异常: {str(e)}")
|
|
logger.error(f"API 调用异常: {str(e)}")
|
|
|
- raise HTTPException(status_code=500, detail=f"服务器内部错误: {str(e)}")
|
|
|
|
|
|
|
+ raise UnicornException(f"{str(e)}")
|