Kaynağa Gözat

```
feat(api): 重构图片导入逻辑支持多货号权限控制

- 修改_sync_import_images_logic函数参数,从specific_goods_art_no改为goods_art_nos数组
- 新增四个处理阶段:收集货号、批量查询、预校验和执行导入
- 实现批量数据库查询替代逐个检查,提升性能
- 添加货号权限验证功能,限制导入范围
- 为PhotoRecord模型增加action_id、image_index等必要字段
- 重构API端点/impotr-images-from-dir为/import_dirs并添加货号参数
- 统一异常处理机制,替换HTTPException为UnicornException
- 移除废弃的skipped计数逻辑,简化返回结果结构
```

rambo 1 ay önce
ebeveyn
işleme
560d72c534
1 değiştirilmiş dosya ile 65 ekleme ve 59 silme
  1. 65 59
      python/api.py

+ 65 - 59
python/api.py

@@ -1854,88 +1854,100 @@ def minimize_window(window_title: str):
     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 specific_goods_art_no: 指定货号。如果提供,所有图片归为此货号;如果为None,则尝试从子目录名获取货号
+    :param goods_art_nos: 指定货号。
     """
     if not os.path.exists(dir_path):
-        raise FileNotFoundError(f"目录不存在: {dir_path}")
+        raise UnicornException(f"目录不存在: {dir_path}")
     
     session = SqlQuery()
-    photo_record_crud = CRUD(PhotoRecord)
+    # photo_record_crud = CRUD(PhotoRecord)
     
     # 支持的图片扩展名
     image_extensions = ('.jpg', '.jpeg', '.png', '.bmp', '.gif', '.webp')
     
     success_count = 0
     fail_count = 0
-    skipped_count = 0
     
     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):
-            
-            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:
                 relative_path = os.path.relpath(root, dir_path)
                 parts = relative_path.split(os.sep)
-                # 只有当 root 是 dir_path 的直接子目录时,才将其视为货号文件夹
-                # 如果 relative_path 是 ".",说明还在根目录,跳过或报错
+                
+                # 跳过根目录本身,只处理子目录作为货号的情况
                 if relative_path == ".":
-                    # 根目录下的文件可能没有货号,视业务逻辑而定,这里选择跳过或赋予默认值
-                    # 如果要求必须有货号,可以 continue 或 raise
                     continue 
                 
                 # 取第一层目录名作为货号
                 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:
-                logger.warning(f"无法确定货号,跳过目录: {root}")
-                continue
+                    continue
 
-            for file in files:
+            for idx, file in enumerate(files):
                 if file.lower().endswith(image_extensions):
                     full_path = os.path.join(root, file)
                     
                     try:
-                        # 可选:检查单张图片路径是否已存在,避免重复插入同一条记录
-                        # 如果业务允许同一个货号有多张图片,则不需要检查 image_path,只需要检查货号是否存在(上面已做)
-                        
-                        # 构造插入数据
-                        # 注意:PhotoRecord 模型可能需要 create_time, action_id 等其他字段
-                        # 这里根据最小可行性原则,只填入必填项,其他依赖数据库默认值或后续更新
                         data_to_insert = {
                             "image_path": full_path,
                             "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)
@@ -1948,21 +1960,16 @@ def _sync_import_images_logic(dir_path: str, specific_goods_art_no: str = None):
         
         # 提交事务
         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 {
             "message": "导入完成",
             "success": success_count,
             "failed": fail_count,
-            "skipped": skipped_count
         }
         
     except UnicornException:
-        # 重新抛出自定义异常,以便上层捕获
+        # 重新抛出自定义异常
         raise
     except Exception as e:
         session.rollback()
@@ -1970,23 +1977,22 @@ def _sync_import_images_logic(dir_path: str, specific_goods_art_no: str = None):
         raise e
     finally:
         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):
-        raise HTTPException(status_code=400, detail=f"无效目录: {dir_path}")
+        raise UnicornException(f"无效目录: {dir_path}")
 
     try:
         # 将阻塞的 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}
     
     except FileNotFoundError as e:
-        raise HTTPException(status_code=404, detail=str(e))
+        raise UnicornException(str(e))
     except Exception as e:
         logger.error(f"API 调用异常: {str(e)}")
-        raise HTTPException(status_code=500, detail=f"服务器内部错误: {str(e)}")
+        raise UnicornException(f"{str(e)}")