CatPtain commited on
Commit
aaa3dc1
·
verified ·
1 Parent(s): d1278b8

Upload screenshotService.js

Browse files
backend/src/services/screenshotService.js CHANGED
@@ -4,56 +4,127 @@ class ScreenshotService {
4
  constructor() {
5
  this.browser = null;
6
  this.isInitialized = false;
 
 
 
 
7
  }
8
 
9
  async initBrowser() {
 
 
 
 
 
 
10
  if (!this.browser) {
11
- console.log('初始化Puppeteer浏览器...');
12
- this.browser = await puppeteer.launch({
13
- headless: 'new',
14
- args: [
15
- '--no-sandbox',
16
- '--disable-setuid-sandbox',
17
- '--disable-dev-shm-usage',
18
- '--disable-accelerated-2d-canvas',
19
- '--no-first-run',
20
- '--no-zygote',
21
- '--single-process',
22
- '--disable-gpu',
23
- '--disable-background-timer-throttling',
24
- '--disable-backgrounding-occluded-windows',
25
- '--disable-renderer-backgrounding',
26
- '--disable-features=TranslateUI',
27
- '--disable-extensions',
28
- '--hide-scrollbars',
29
- '--mute-audio',
30
- '--no-default-browser-check',
31
- '--disable-default-apps',
32
- '--disable-background-networking',
33
- '--disable-sync',
34
- '--metrics-recording-only',
35
- '--disable-domain-reliability',
36
- '--disable-component-extensions-with-background-pages',
37
- '--force-device-scale-factor=1',
38
- '--enable-precise-memory-info',
39
- '--disable-features=VizDisplayCompositor',
40
- '--run-all-compositor-stages-before-draw',
41
- '--disable-new-content-rendering-timeout'
42
- ]
43
- });
44
- console.log('Puppeteer浏览器初始化完成');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  }
 
 
 
 
 
 
 
 
 
 
46
  return this.browser;
47
  }
48
 
49
  async closeBrowser() {
50
- if (this.browser) {
51
- await this.browser.close();
52
- this.browser = null;
53
- console.log('Puppeteer浏览器已关闭');
 
 
 
 
 
 
 
 
54
  }
55
  }
56
 
 
 
 
 
 
57
  // 从HTML中提取PPT尺寸信息
58
  extractPPTDimensions(htmlContent) {
59
  try {
@@ -351,11 +422,11 @@ class ScreenshotService {
351
  return finalHtml;
352
  }
353
 
354
- async generateScreenshot(htmlContent, options = {}) {
355
  try {
356
  await this.initBrowser();
357
 
358
- console.log('开始生成截图...');
359
 
360
  // 从HTML中提取PPT的精确尺寸
361
  const dimensions = this.extractPPTDimensions(htmlContent);
@@ -365,9 +436,14 @@ class ScreenshotService {
365
  const optimizedHtml = this.optimizeHtmlForScreenshot(htmlContent, dimensions.width, dimensions.height);
366
 
367
  // 创建新页面
368
- const page = await this.browser.newPage();
369
-
370
  try {
 
 
 
 
 
 
371
  // 设置精确的viewport尺寸
372
  await page.setViewport({
373
  width: dimensions.width,
@@ -377,7 +453,7 @@ class ScreenshotService {
377
 
378
  // 设置页面内容
379
  await page.setContent(optimizedHtml, {
380
- waitUntil: ['load', 'domcontentloaded', 'networkidle0'],
381
  timeout: 30000
382
  });
383
 
@@ -465,20 +541,70 @@ class ScreenshotService {
465
  return screenshot;
466
 
467
  } finally {
468
- await page.close();
 
 
 
 
 
 
469
  }
470
 
471
  } catch (error) {
472
- console.error('截图生成失败:', error);
473
- throw new Error(`截图生成失败: ${error.message}`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
474
  }
475
  }
476
 
 
 
 
 
477
  // 兼容旧方法名
478
  async captureScreenshot(htmlContent, width, height, options = {}) {
479
  console.log('使用兼容方法captureScreenshot,建议使用generateScreenshot');
480
  return this.generateScreenshot(htmlContent, options);
481
  }
 
 
 
 
 
482
  }
483
 
484
- export default new ScreenshotService();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  constructor() {
5
  this.browser = null;
6
  this.isInitialized = false;
7
+ this.maxRetries = 3;
8
+ this.browserLaunchRetries = 0;
9
+ this.maxBrowserLaunchRetries = 3;
10
+ this.isClosing = false;
11
  }
12
 
13
  async initBrowser() {
14
+ if (this.isClosing) {
15
+ console.log('浏览器正在关闭中,等待重新初始化...');
16
+ this.browser = null;
17
+ this.isClosing = false;
18
+ }
19
+
20
  if (!this.browser) {
21
+ try {
22
+ console.log('初始化Puppeteer浏览器...');
23
+ this.browser = await puppeteer.launch({
24
+ headless: 'new',
25
+ args: [
26
+ '--no-sandbox',
27
+ '--disable-setuid-sandbox',
28
+ '--disable-dev-shm-usage',
29
+ '--disable-accelerated-2d-canvas',
30
+ '--no-first-run',
31
+ '--no-zygote',
32
+ '--single-process',
33
+ '--disable-gpu',
34
+ '--disable-background-timer-throttling',
35
+ '--disable-backgrounding-occluded-windows',
36
+ '--disable-renderer-backgrounding',
37
+ '--disable-features=TranslateUI',
38
+ '--disable-extensions',
39
+ '--hide-scrollbars',
40
+ '--mute-audio',
41
+ '--no-default-browser-check',
42
+ '--disable-default-apps',
43
+ '--disable-background-networking',
44
+ '--disable-sync',
45
+ '--metrics-recording-only',
46
+ '--disable-domain-reliability',
47
+ '--disable-component-extensions-with-background-pages',
48
+ '--force-device-scale-factor=1',
49
+ '--enable-precise-memory-info',
50
+ '--disable-features=VizDisplayCompositor',
51
+ '--run-all-compositor-stages-before-draw',
52
+ '--disable-new-content-rendering-timeout',
53
+ '--disable-ipc-flooding-protection',
54
+ '--disable-hang-monitor',
55
+ '--disable-prompt-on-repost',
56
+ '--max_old_space_size=512'
57
+ ],
58
+ timeout: 30000,
59
+ protocolTimeout: 30000
60
+ });
61
+
62
+ // 监听浏览器断开连接事件
63
+ this.browser.on('disconnected', () => {
64
+ console.log('Puppeteer浏览器连接断开');
65
+ this.browser = null;
66
+ this.isClosing = false;
67
+ });
68
+
69
+ // 监听目标创建事件
70
+ this.browser.on('targetcreated', (target) => {
71
+ console.log('新目标创建:', target.type());
72
+ });
73
+
74
+ // 监听目标销毁事件
75
+ this.browser.on('targetdestroyed', (target) => {
76
+ console.log('目标销毁:', target.type());
77
+ });
78
+
79
+ console.log('Puppeteer浏览器初始化完成');
80
+ this.browserLaunchRetries = 0;
81
+ } catch (error) {
82
+ console.error('Puppeteer浏览器初始化失败:', error);
83
+ this.browserLaunchRetries++;
84
+
85
+ if (this.browserLaunchRetries <= this.maxBrowserLaunchRetries) {
86
+ console.log(`尝试重新初始化浏览器 (${this.browserLaunchRetries}/${this.maxBrowserLaunchRetries})`);
87
+ await this.delay(2000); // 等待2秒后重试
88
+ return this.initBrowser();
89
+ } else {
90
+ throw new Error(`浏览器初始化失败,已重试${this.maxBrowserLaunchRetries}次: ${error.message}`);
91
+ }
92
+ }
93
  }
94
+
95
+ // 验证浏览器是否仍然连接
96
+ try {
97
+ await this.browser.version();
98
+ } catch (error) {
99
+ console.warn('浏览器连接已断开,重新初始化:', error.message);
100
+ this.browser = null;
101
+ return this.initBrowser();
102
+ }
103
+
104
  return this.browser;
105
  }
106
 
107
  async closeBrowser() {
108
+ if (this.browser && !this.isClosing) {
109
+ try {
110
+ this.isClosing = true;
111
+ console.log('正在关闭Puppeteer浏览器...');
112
+ await this.browser.close();
113
+ console.log('Puppeteer浏览器已关闭');
114
+ } catch (error) {
115
+ console.warn('关闭浏览器时出错:', error.message);
116
+ } finally {
117
+ this.browser = null;
118
+ this.isClosing = false;
119
+ }
120
  }
121
  }
122
 
123
+ // 延迟函数
124
+ delay(ms) {
125
+ return new Promise(resolve => setTimeout(resolve, ms));
126
+ }
127
+
128
  // 从HTML中提取PPT尺寸信息
129
  extractPPTDimensions(htmlContent) {
130
  try {
 
422
  return finalHtml;
423
  }
424
 
425
+ async generateScreenshotWithRetry(htmlContent, options = {}, retryCount = 0) {
426
  try {
427
  await this.initBrowser();
428
 
429
+ console.log(`开始生成截图... (尝试 ${retryCount + 1}/${this.maxRetries + 1})`);
430
 
431
  // 从HTML中提取PPT的精确尺寸
432
  const dimensions = this.extractPPTDimensions(htmlContent);
 
436
  const optimizedHtml = this.optimizeHtmlForScreenshot(htmlContent, dimensions.width, dimensions.height);
437
 
438
  // 创建新页面
439
+ let page = null;
 
440
  try {
441
+ page = await this.browser.newPage();
442
+
443
+ // 设置页面超时
444
+ page.setDefaultTimeout(30000);
445
+ page.setDefaultNavigationTimeout(30000);
446
+
447
  // 设置精确的viewport尺寸
448
  await page.setViewport({
449
  width: dimensions.width,
 
453
 
454
  // 设置页面内容
455
  await page.setContent(optimizedHtml, {
456
+ waitUntil: ['load', 'domcontentloaded'],
457
  timeout: 30000
458
  });
459
 
 
541
  return screenshot;
542
 
543
  } finally {
544
+ if (page) {
545
+ try {
546
+ await page.close();
547
+ } catch (error) {
548
+ console.warn('关闭页面时出错:', error.message);
549
+ }
550
+ }
551
  }
552
 
553
  } catch (error) {
554
+ console.error(`截图生成失败 (尝试 ${retryCount + 1}):`, error.message);
555
+
556
+ // 如果是目标关闭错误或连接断开,重置浏览器
557
+ if (error.message.includes('Target closed') ||
558
+ error.message.includes('Connection closed') ||
559
+ error.message.includes('Protocol error')) {
560
+ console.log('检测到浏览器连接问题,重置浏览器实例');
561
+ this.browser = null;
562
+ this.isClosing = false;
563
+ }
564
+
565
+ // 如果还有重试机会,进行重试
566
+ if (retryCount < this.maxRetries) {
567
+ console.log(`等待 ${(retryCount + 1) * 2} 秒后重试...`);
568
+ await this.delay((retryCount + 1) * 2000);
569
+ return this.generateScreenshotWithRetry(htmlContent, options, retryCount + 1);
570
+ }
571
+
572
+ throw new Error(`截图生成失败,已重试${this.maxRetries}次: ${error.message}`);
573
  }
574
  }
575
 
576
+ async generateScreenshot(htmlContent, options = {}) {
577
+ return this.generateScreenshotWithRetry(htmlContent, options, 0);
578
+ }
579
+
580
  // 兼容旧方法名
581
  async captureScreenshot(htmlContent, width, height, options = {}) {
582
  console.log('使用兼容方法captureScreenshot,建议使用generateScreenshot');
583
  return this.generateScreenshot(htmlContent, options);
584
  }
585
+
586
+ // 清理资源
587
+ async cleanup() {
588
+ await this.closeBrowser();
589
+ }
590
  }
591
 
592
+ // 创建单例实例
593
+ const screenshotService = new ScreenshotService();
594
+
595
+ // 进程退出时清理资源
596
+ process.on('exit', async () => {
597
+ await screenshotService.cleanup();
598
+ });
599
+
600
+ process.on('SIGINT', async () => {
601
+ await screenshotService.cleanup();
602
+ process.exit(0);
603
+ });
604
+
605
+ process.on('SIGTERM', async () => {
606
+ await screenshotService.cleanup();
607
+ process.exit(0);
608
+ });
609
+
610
+ export default screenshotService;