Browse Source

feat(publish): 发布失败时自动保存截图到文件

- 添加 save_screenshot_to_file 方法保存截图到 uploads/screenshots 目录
- PublishResult 添加 screenshot_path 字段
- run 方法在发布失败或异常时自动保存页面截图
- /publish 和 /publish/ai-assisted 接口返回 screenshot_path
Ethanfly 2 ngày trước cách đây
mục cha
commit
dfd30492f3
2 tập tin đã thay đổi với 57 bổ sung9 xóa
  1. 4 2
      server/python/app.py
  2. 53 7
      server/python/platforms/base.py

+ 4 - 2
server/python/app.py

@@ -571,7 +571,8 @@ def publish_video():
             "captcha_type": result.captcha_type,
             "screenshot_base64": result.screenshot_base64,
             "page_url": result.page_url,
-            "status": result.status
+            "status": result.status,
+            "screenshot_path": getattr(result, 'screenshot_path', '') or ''
         }
         
         # 如果需要验证码,打印明确的日志
@@ -715,7 +716,8 @@ def publish_ai_assisted():
             "need_captcha": result.need_captcha,
             "captcha_type": result.captcha_type,
             "status": result.status or ("success" if result.success else "failed"),
-            "page_url": result.page_url
+            "page_url": result.page_url,
+            "screenshot_path": getattr(result, 'screenshot_path', '') or ''
         }
         
         # 如果请求返回截图

+ 53 - 7
server/python/platforms/base.py

@@ -40,11 +40,12 @@ class PublishResult:
     video_url: str = ""
     message: str = ""
     error: str = ""
-    need_captcha: bool = False  # 是否需要验证码
-    captcha_type: str = ""  # 验证码类型: phone, slider, image
-    screenshot_base64: str = ""  # 页面截图(Base64)
-    page_url: str = ""  # 当前页面 URL
-    status: str = ""  # 状态: uploading, processing, success, failed, need_captcha, need_action
+    need_captcha: bool = False
+    captcha_type: str = ""
+    screenshot_base64: str = ""
+    page_url: str = ""
+    status: str = ""
+    screenshot_path: str = ""
 
 
 @dataclass
@@ -344,6 +345,41 @@ class BasePublisher(ABC):
             print(f"[{self.platform_name}] 截图失败: {e}")
             return ""
 
+    async def save_screenshot_to_file(self, directory: str = None, filename_prefix: str = "publish_failed") -> str:
+        """
+        保存截图到指定目录,返回文件路径
+        
+        Args:
+            directory: 截图保存目录,默认为 uploads/screenshots
+            filename_prefix: 文件名前缀
+            
+        Returns:
+            str: 保存的文件路径,失败返回空字符串
+        """
+        if not self.page:
+            return ""
+        
+        try:
+            if directory is None:
+                current_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+                directory = os.path.join(current_dir, '..', '..', 'uploads', 'screenshots')
+            
+            directory = os.path.abspath(directory)
+            os.makedirs(directory, exist_ok=True)
+            
+            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
+            task_info = f"_task{self.publish_task_id}" if self.publish_task_id else ""
+            account_info = f"_acc{self.publish_account_id}" if self.publish_account_id else ""
+            filename = f"{filename_prefix}_{self.platform_name}{task_info}{account_info}_{timestamp}.png"
+            filepath = os.path.join(directory, filename)
+            
+            await self.page.screenshot(path=filepath, type="png")
+            print(f"[{self.platform_name}] 截图已保存: {filepath}")
+            return filepath
+        except Exception as e:
+            print(f"[{self.platform_name}] 保存截图失败: {e}")
+            return ""
+
     async def request_sms_code_from_frontend(self, phone: str = "", timeout_seconds: int = 120, message: str = "") -> str:
         node_api_url = os.environ.get('NODEJS_API_URL', 'http://localhost:3000').rstrip('/')
         internal_api_key = os.environ.get('INTERNAL_API_KEY', 'internal-api-key-default')
@@ -1035,16 +1071,26 @@ class BasePublisher(ABC):
         """
         运行发布任务
         包装了 publish 方法,添加了异常处理和资源清理
+        发布失败时自动保存截图到 uploads/screenshots 目录
         """
         try:
-            return await self.publish(cookies, params)
+            result = await self.publish(cookies, params)
+            if not result.success and self.page:
+                screenshot_path = await self.save_screenshot_to_file()
+                if screenshot_path:
+                    result.screenshot_path = screenshot_path
+            return result
         except Exception as e:
             import traceback
             traceback.print_exc()
+            screenshot_path = ""
+            if self.page:
+                screenshot_path = await self.save_screenshot_to_file()
             return PublishResult(
                 success=False,
                 platform=self.platform_name,
-                error=str(e)
+                error=str(e),
+                screenshot_path=screenshot_path
             )
         finally:
             await self.close_browser()