瀏覽代碼

日志埋点以及优化接口

袁威 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 json
 import copy
+import hashlib
 from enum import Enum
+from time import perf_counter
 
 from fastapi import APIRouter, Depends, HTTPException, Query, Body
 from fastapi.concurrency import run_in_threadpool
@@ -17,7 +19,6 @@ from app.utils.scheme import (
 )
 from app.crud import crud_card
 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.utils.rating_report_utils import crop_defect_image
 
@@ -142,8 +143,22 @@ def _sanitize_defects_for_recalculate(defects: list):
         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:
+        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
     defect_result = json_data["result"].get("defect_result", {})
     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_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:
                     s_img_type = getattr(s_img, 'image_type', '')
                     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_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 格式。
     直接修改传入的 card_data 字典。
     """
+    start_time = perf_counter()
     card_id = card_data.get("id")
     all_images = card_data.get("images", [])
     if all_images:
@@ -246,7 +274,11 @@ def _process_images_to_xy_format(card_data: dict):
                 d_internal = json.loads(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)
             else:
                 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)
 
             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)
             else:
                 m_fallback = copy.deepcopy(d_internal) if d_internal else {}
                 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
 
 
@@ -280,6 +320,7 @@ def get_card_details(
     """
     target_id = card_id
     cursor = None
+    start_time = perf_counter()
 
     try:
         cursor = db_conn.cursor(dictionary=True)
@@ -298,7 +339,8 @@ def get_card_details(
 
             if not row:
                 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']
 
@@ -325,7 +367,11 @@ def get_card_details(
         card_data['id_next'] = row_next['id'] if row_next else None
 
         # 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 重复验证导致类型异常
         if "images" in card_data:
@@ -343,6 +389,10 @@ def get_card_details(
         logger.error(f"查询卡牌详情失败 (Mode: {mode}, BaseID: {card_id}): {e}")
         raise HTTPException(status_code=500, detail="数据库查询失败")
     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:
             cursor.close()
 

+ 13 - 2
app/utils/rating_report_utils.py

@@ -1,6 +1,7 @@
 import copy
 import io
 import json
+from time import perf_counter
 from collections import Counter
 from datetime import datetime
 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。
     """
+    start_time = perf_counter()
     try:
         # 把完整 URL 转成 MinIO 对象路径,例如 /Data/xxx.jpg。
         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"
             )
 
-            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:
         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 ""