ソースを参照

日志埋点以及优化接口

袁威 2 週間 前
コミット
10df1f036b
2 ファイル変更83 行追加22 行削除
  1. 70 20
      app/api/formate_xy.py
  2. 13 2
      app/utils/rating_report_utils.py

+ 70 - 20
app/api/formate_xy.py

@@ -1,7 +1,9 @@
 import requests
 import requests
 import json
 import json
 import copy
 import copy
+import hashlib
 from enum import Enum
 from enum import Enum
+from time import perf_counter
 
 
 from fastapi import APIRouter, Depends, HTTPException, Query, Body
 from fastapi import APIRouter, Depends, HTTPException, Query, Body
 from fastapi.concurrency import run_in_threadpool
 from fastapi.concurrency import run_in_threadpool
@@ -17,7 +19,6 @@ from app.utils.scheme import (
 )
 )
 from app.crud import crud_card
 from app.crud import crud_card
 from app.utils.xy_process import convert_internal_to_xy_format, convert_xy_to_internal_format
 from app.utils.xy_process import convert_internal_to_xy_format, convert_xy_to_internal_format
-import hashlib
 from app.core.minio_client import minio_client
 from app.core.minio_client import minio_client
 from app.utils.rating_report_utils import crop_defect_image
 from app.utils.rating_report_utils import crop_defect_image
 
 
@@ -142,8 +143,22 @@ def _sanitize_defects_for_recalculate(defects: list):
         d.pop("new_score", None)
         d.pop("new_score", None)
 
 
 
 
-def _process_defects_for_json(card_id: int, img_id: int, img_path: str, json_data: dict, side: str, all_images: list = None):
+def _process_defects_for_json(
+        card_id: int,
+        img_id: int,
+        img_path: str,
+        json_data: dict,
+        side: str,
+        all_images: list = None,
+        generate_defect_img: bool = True,
+        generate_related_images: bool = True
+):
+    start_time = perf_counter()
     if not json_data or "result" not in json_data:
     if not json_data or "result" not in json_data:
+        logger.info(
+            "耗时埋点 _process_defects_for_json: card_id=%s image_id=%s side=%s defects=0 elapsed_ms=%.2f",
+            card_id, img_id, side, (perf_counter() - start_time) * 1000
+        )
         return
         return
     defect_result = json_data["result"].get("defect_result", {})
     defect_result = json_data["result"].get("defect_result", {})
     defects = defect_result.get("defects", [])
     defects = defect_result.get("defects", [])
@@ -165,21 +180,25 @@ def _process_defects_for_json(card_id: int, img_id: int, img_path: str, json_dat
             # 使用坐标哈希作为缓存文件名,避免重复裁剪
             # 使用坐标哈希作为缓存文件名,避免重复裁剪
             rect_str = str(min_rect)
             rect_str = str(min_rect)
             rect_hash = hashlib.md5(rect_str.encode('utf-8')).hexdigest()[:8]
             rect_hash = hashlib.md5(rect_str.encode('utf-8')).hexdigest()[:8]
-            filename = f"xy_{card_id}_{img_id}_{idx}_{rect_hash}.jpg"
-            
-            out_rel_path = f"/DefectImage/{filename}"
-            out_object_name = f"{settings.MINIO_BASE_PREFIX}{out_rel_path}"
-            try:
-                # 检查 MinIO 中是否已有该截图,有则直接使用
-                minio_client.stat_object(settings.MINIO_BUCKET, out_object_name)
-                defect_img_url = settings.get_full_url(out_rel_path)
-            except Exception:
-                # 不存在或异常,则执行裁剪并上传
-                defect_img_url = crop_defect_image(img_path, min_rect, filename)
+            if generate_defect_img:
+                filename = f"xy_{card_id}_{img_id}_{idx}_{rect_hash}.jpg"
+                
+                out_rel_path = f"/DefectImage/{filename}"
+                out_object_name = f"{settings.MINIO_BASE_PREFIX}{out_rel_path}"
+                try:
+                    # 检查 MinIO 中是否已有该截图,有则直接使用
+                    minio_client.stat_object(settings.MINIO_BUCKET, out_object_name)
+                    defect_img_url = settings.get_full_url(out_rel_path)
+                except Exception:
+                    # 不存在或异常,则执行裁剪并上传
+                    defect_img_url = crop_defect_image(img_path, min_rect, filename)
             
             
             # 把同面的其他类型图在同样位置截图(不论是不是融合图都截)
             # 把同面的其他类型图在同样位置截图(不论是不是融合图都截)
-            if all_images:
-                same_side_images = [img for img in all_images if getattr(img, 'image_type', '').startswith(side_prefix)]
+            if generate_related_images and all_images:
+                same_side_images = [
+                    img for img in all_images
+                    if getattr(img, 'image_type', '').startswith(side_prefix) and getattr(img, 'id', None) != img_id
+                ]
                 for s_img in same_side_images:
                 for s_img in same_side_images:
                     s_img_type = getattr(s_img, 'image_type', '')
                     s_img_type = getattr(s_img, 'image_type', '')
                     s_img_path = getattr(s_img, 'image_path', '')
                     s_img_path = getattr(s_img, 'image_path', '')
@@ -230,13 +249,22 @@ def _process_defects_for_json(card_id: int, img_id: int, img_path: str, json_dat
         defect_detail_list.append(detail_item)
         defect_detail_list.append(detail_item)
         
         
     defect_result["defectDetailList"] = defect_detail_list
     defect_result["defectDetailList"] = defect_detail_list
+    logger.info(
+        "耗时埋点 _process_defects_for_json: card_id=%s image_id=%s side=%s defects=%s elapsed_ms=%.2f",
+        card_id, img_id, side, len(defects), (perf_counter() - start_time) * 1000
+    )
 
 
 
 
-def _process_images_to_xy_format(card_data: dict):
+def _process_images_to_xy_format(
+        card_data: dict,
+        generate_defect_img: bool = True,
+        generate_related_images: bool = True
+):
     """
     """
     内部辅助函数:遍历卡牌数据中的图片,将 JSON 格式转换为前端需要的 XY 格式。
     内部辅助函数:遍历卡牌数据中的图片,将 JSON 格式转换为前端需要的 XY 格式。
     直接修改传入的 card_data 字典。
     直接修改传入的 card_data 字典。
     """
     """
+    start_time = perf_counter()
     card_id = card_data.get("id")
     card_id = card_data.get("id")
     all_images = card_data.get("images", [])
     all_images = card_data.get("images", [])
     if all_images:
     if all_images:
@@ -246,7 +274,11 @@ def _process_images_to_xy_format(card_data: dict):
                 d_internal = json.loads(d_internal)
                 d_internal = json.loads(d_internal)
 
 
             if d_internal:
             if d_internal:
-                _process_defects_for_json(card_id, img.id, img.image_path, d_internal, img.image_type, all_images)
+                _process_defects_for_json(
+                    card_id, img.id, img.image_path, d_internal, img.image_type, all_images,
+                    generate_defect_img=generate_defect_img,
+                    generate_related_images=generate_related_images
+                )
                 img.detection_json = convert_internal_to_xy_format(d_internal)
                 img.detection_json = convert_internal_to_xy_format(d_internal)
             else:
             else:
                 img.detection_json = convert_internal_to_xy_format({})
                 img.detection_json = convert_internal_to_xy_format({})
@@ -256,11 +288,19 @@ def _process_images_to_xy_format(card_data: dict):
                 m_internal = json.loads(m_internal)
                 m_internal = json.loads(m_internal)
 
 
             if m_internal:
             if m_internal:
-                _process_defects_for_json(card_id, img.id, img.image_path, m_internal, img.image_type, all_images)
+                _process_defects_for_json(
+                    card_id, img.id, img.image_path, m_internal, img.image_type, all_images,
+                    generate_defect_img=generate_defect_img,
+                    generate_related_images=generate_related_images
+                )
                 img.modified_json = convert_internal_to_xy_format(m_internal)
                 img.modified_json = convert_internal_to_xy_format(m_internal)
             else:
             else:
                 m_fallback = copy.deepcopy(d_internal) if d_internal else {}
                 m_fallback = copy.deepcopy(d_internal) if d_internal else {}
                 img.modified_json = convert_internal_to_xy_format(m_fallback)
                 img.modified_json = convert_internal_to_xy_format(m_fallback)
+    logger.info(
+        "耗时埋点 _process_images_to_xy_format: card_id=%s image_count=%s elapsed_ms=%.2f",
+        card_id, len(all_images), (perf_counter() - start_time) * 1000
+    )
     return card_data
     return card_data
 
 
 
 
@@ -280,6 +320,7 @@ def get_card_details(
     """
     """
     target_id = card_id
     target_id = card_id
     cursor = None
     cursor = None
+    start_time = perf_counter()
 
 
     try:
     try:
         cursor = db_conn.cursor(dictionary=True)
         cursor = db_conn.cursor(dictionary=True)
@@ -298,7 +339,8 @@ def get_card_details(
 
 
             if not row:
             if not row:
                 msg = "没有下一张" if mode == QueryMode.next else "没有上一张"
                 msg = "没有下一张" if mode == QueryMode.next else "没有上一张"
-                raise HTTPException(status_code=200, detail=msg)
+                # 边界场景返回 404,避免与 response_model=CardDetailResponse 冲突
+                raise HTTPException(status_code=404, detail=msg)
 
 
             target_id = row['id']
             target_id = row['id']
 
 
@@ -325,7 +367,11 @@ def get_card_details(
         card_data['id_next'] = row_next['id'] if row_next else None
         card_data['id_next'] = row_next['id'] if row_next else None
 
 
         # 4. 遍历图片,转换格式 (使用抽取出的辅助函数)
         # 4. 遍历图片,转换格式 (使用抽取出的辅助函数)
-        _process_images_to_xy_format(card_data)
+        _process_images_to_xy_format(
+            card_data,
+            generate_defect_img=False,
+            generate_related_images=True
+        )
 
 
         # 5. 将 images 从 Pydantic 对象转为 dict,避免 model_validate 重复验证导致类型异常
         # 5. 将 images 从 Pydantic 对象转为 dict,避免 model_validate 重复验证导致类型异常
         if "images" in card_data:
         if "images" in card_data:
@@ -343,6 +389,10 @@ def get_card_details(
         logger.error(f"查询卡牌详情失败 (Mode: {mode}, BaseID: {card_id}): {e}")
         logger.error(f"查询卡牌详情失败 (Mode: {mode}, BaseID: {card_id}): {e}")
         raise HTTPException(status_code=500, detail="数据库查询失败")
         raise HTTPException(status_code=500, detail="数据库查询失败")
     finally:
     finally:
+        logger.info(
+            "耗时埋点 get_card_details: base_card_id=%s target_card_id=%s mode=%s elapsed_ms=%.2f",
+            card_id, target_id, mode, (perf_counter() - start_time) * 1000
+        )
         if cursor:
         if cursor:
             cursor.close()
             cursor.close()
 
 

+ 13 - 2
app/utils/rating_report_utils.py

@@ -1,6 +1,7 @@
 import copy
 import copy
 import io
 import io
 import json
 import json
+from time import perf_counter
 from collections import Counter
 from collections import Counter
 from datetime import datetime
 from datetime import datetime
 from typing import Any, Dict, List, Optional, Tuple
 from typing import Any, Dict, List, Optional, Tuple
@@ -245,6 +246,7 @@ def crop_defect_image(original_image_path_str: str, min_rect: List, output_filen
     """
     """
     通过 MinIO 裁剪缺陷图,生成正方形缺陷图并回传最终 URL。
     通过 MinIO 裁剪缺陷图,生成正方形缺陷图并回传最终 URL。
     """
     """
+    start_time = perf_counter()
     try:
     try:
         # 把完整 URL 转成 MinIO 对象路径,例如 /Data/xxx.jpg。
         # 把完整 URL 转成 MinIO 对象路径,例如 /Data/xxx.jpg。
         rel_path = original_image_path_str.replace(settings.DATA_HOST_URL, "")
         rel_path = original_image_path_str.replace(settings.DATA_HOST_URL, "")
@@ -303,7 +305,16 @@ def crop_defect_image(original_image_path_str: str, min_rect: List, output_filen
                 content_type="image/jpeg"
                 content_type="image/jpeg"
             )
             )
 
 
-            return settings.get_full_url(out_rel_path)
+            result_url = settings.get_full_url(out_rel_path)
+            logger.info(
+                "耗时埋点 crop_defect_image: source=%s output=%s elapsed_ms=%.2f",
+                object_name, out_object_name, (perf_counter() - start_time) * 1000
+            )
+            return result_url
     except Exception as e:
     except Exception as e:
         logger.error(f"裁剪并上传缺陷图失败: {e}")
         logger.error(f"裁剪并上传缺陷图失败: {e}")
-        return ""
+    logger.info(
+        "耗时埋点 crop_defect_image: source=%s output=%s elapsed_ms=%.2f",
+        original_image_path_str, output_filename, (perf_counter() - start_time) * 1000
+    )
+    return ""