CatPtain commited on
Commit
a174e32
·
verified ·
1 Parent(s): 2fdedce

Upload screenshotService.js

Browse files
backend/src/services/screenshotService.js CHANGED
@@ -25,8 +25,8 @@ class ScreenshotService {
25
 
26
  const launchOptions = {
27
  headless: 'new',
28
- timeout: 60000,
29
- protocolTimeout: 60000,
30
  args: [
31
  '--no-sandbox',
32
  '--disable-setuid-sandbox',
@@ -55,7 +55,7 @@ class ScreenshotService {
55
  '--disable-hang-monitor',
56
  '--disable-prompt-on-repost',
57
  '--memory-pressure-off',
58
- '--max_old_space_size=1024'
59
  ]
60
  };
61
 
@@ -72,6 +72,7 @@ class ScreenshotService {
72
  }
73
 
74
  this.browser = await puppeteer.launch(launchOptions);
 
75
 
76
  // 监听浏览器断开连接事件
77
  this.browser.on('disconnected', () => {
@@ -80,19 +81,18 @@ class ScreenshotService {
80
  this.isClosing = false;
81
  });
82
 
83
- console.log('Puppeteer浏览器初始化完成');
84
  this.browserLaunchRetries = 0;
85
  } catch (error) {
86
- console.error('Puppeteer浏览器初始化失败:', error);
87
  this.browserLaunchRetries++;
88
 
89
  if (this.browserLaunchRetries <= this.maxBrowserLaunchRetries) {
90
  console.log(`尝试重新初始化浏览器 (${this.browserLaunchRetries}/${this.maxBrowserLaunchRetries})`);
91
- await this.delay(3000); // 等待3秒后重试
92
  return this.initBrowser();
93
  } else {
94
  // 如果Puppeteer完全失败,返回null,使用fallback方法
95
- console.warn('Puppeteer初始化完全失败,将使用fallback方法');
96
  return null;
97
  }
98
  }
@@ -102,6 +102,7 @@ class ScreenshotService {
102
  if (this.browser) {
103
  try {
104
  await this.browser.version();
 
105
  } catch (error) {
106
  console.warn('浏览器连接已断开,重新初始化:', error.message);
107
  this.browser = null;
@@ -164,25 +165,32 @@ class ScreenshotService {
164
 
165
  // 生成简化的PNG图片作为fallback
166
  generateFallbackImage(width, height, message = 'Screenshot not available') {
 
 
167
  // 创建一个简单的SVG作为fallback
168
  const svg = `
169
  <svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
170
- <rect width="100%" height="100%" fill="#f0f0f0"/>
 
 
 
 
 
 
171
  <text x="50%" y="50%" text-anchor="middle" dominant-baseline="middle"
172
- font-family="Arial, sans-serif" font-size="24" fill="#666">
173
  ${message}
174
  </text>
175
  <text x="50%" y="60%" text-anchor="middle" dominant-baseline="middle"
176
- font-family="Arial, sans-serif" font-size="16" fill="#999">
177
- Size: ${width}x${height}
178
  </text>
 
 
 
179
  </svg>
180
  `;
181
 
182
- // 将SVG转换为base64编码的PNG
183
- const base64Svg = Buffer.from(svg).toString('base64');
184
- const dataUrl = `data:image/svg+xml;base64,${base64Svg}`;
185
-
186
  // 返回简单的占位图片数据
187
  return Buffer.from(svg, 'utf8');
188
  }
@@ -286,16 +294,16 @@ class ScreenshotService {
286
 
287
  async generateScreenshotWithPuppeteer(htmlContent, options = {}, retryCount = 0) {
288
  try {
 
 
289
  const browser = await this.initBrowser();
290
  if (!browser) {
291
  throw new Error('浏览器初始化失败');
292
  }
293
 
294
- console.log(`开始生成截图... (Puppeteer尝试 ${retryCount + 1}/${this.maxRetries + 1})`);
295
-
296
  // 从HTML中提取PPT的精确尺寸
297
  const dimensions = this.extractPPTDimensions(htmlContent);
298
- console.log(`检测到PPT尺寸: ${dimensions.width}x${dimensions.height}`);
299
 
300
  // 优化HTML内容以确保精确截图
301
  const optimizedHtml = this.optimizeHtmlForScreenshot(htmlContent, dimensions.width, dimensions.height);
@@ -304,31 +312,36 @@ class ScreenshotService {
304
  let page = null;
305
  try {
306
  page = await browser.newPage();
 
307
 
308
  // 设置页面超时
309
- page.setDefaultTimeout(45000);
310
- page.setDefaultNavigationTimeout(45000);
311
 
312
  // 设置精确的viewport尺寸
313
  await page.setViewport({
314
  width: dimensions.width,
315
  height: dimensions.height,
316
- deviceScaleFactor: options.deviceScaleFactor || 1, // 降低设备缩放因子以减少内存使用
317
  });
 
318
 
319
  // 设置页面内容
320
  await page.setContent(optimizedHtml, {
321
  waitUntil: ['load', 'domcontentloaded'],
322
- timeout: 45000
323
  });
 
324
 
325
  // 等待页面完全渲染
326
- await page.waitForTimeout(1500);
 
327
 
328
  // 执行截图,使用精确的剪裁区域
 
329
  const screenshot = await page.screenshot({
330
- type: options.format || 'jpeg',
331
- quality: options.quality || 90,
332
  clip: {
333
  x: 0,
334
  y: 0,
@@ -339,13 +352,14 @@ class ScreenshotService {
339
  captureBeyondViewport: false,
340
  });
341
 
342
- console.log(`Puppeteer截图成功生成,尺寸: ${dimensions.width}x${dimensions.height}, 数据大小: ${screenshot.length} 字节`);
343
  return screenshot;
344
 
345
  } finally {
346
  if (page) {
347
  try {
348
  await page.close();
 
349
  } catch (error) {
350
  console.warn('关闭页面时出错:', error.message);
351
  }
@@ -353,21 +367,22 @@ class ScreenshotService {
353
  }
354
 
355
  } catch (error) {
356
- console.error(`Puppeteer截图生成失败 (尝试 ${retryCount + 1}):`, error.message);
357
 
358
  // 如果是目标关闭错误或连接断开,重置浏览器
359
  if (error.message.includes('Target closed') ||
360
  error.message.includes('Connection closed') ||
361
  error.message.includes('Protocol error')) {
362
- console.log('检测到浏览器连接问题,重置浏览器实例');
363
  this.browser = null;
364
  this.isClosing = false;
365
  }
366
 
367
  // 如果还有重试机会,进行重试
368
  if (retryCount < this.maxRetries) {
369
- console.log(`等待 ${(retryCount + 1) * 3} 秒后重试...`);
370
- await this.delay((retryCount + 1) * 3000);
 
371
  return this.generateScreenshotWithPuppeteer(htmlContent, options, retryCount + 1);
372
  }
373
 
@@ -376,21 +391,26 @@ class ScreenshotService {
376
  }
377
 
378
  async generateScreenshot(htmlContent, options = {}) {
 
 
379
  try {
380
  // 首先尝试使用Puppeteer
381
- return await this.generateScreenshotWithPuppeteer(htmlContent, options);
 
 
 
382
  } catch (puppeteerError) {
383
- console.warn('Puppeteer截图失败,使用fallback方法:', puppeteerError.message);
384
 
385
  // 如果Puppeteer失败,生成fallback图片
386
  const dimensions = this.extractPPTDimensions(htmlContent);
387
  const fallbackImage = this.generateFallbackImage(
388
  dimensions.width,
389
  dimensions.height,
390
- 'PPT Preview'
391
  );
392
 
393
- console.log(`生成fallback图片,尺寸: ${dimensions.width}x${dimensions.height}`);
394
  return fallbackImage;
395
  }
396
  }
 
25
 
26
  const launchOptions = {
27
  headless: 'new',
28
+ timeout: 30000, // 减少超时时间
29
+ protocolTimeout: 30000,
30
  args: [
31
  '--no-sandbox',
32
  '--disable-setuid-sandbox',
 
55
  '--disable-hang-monitor',
56
  '--disable-prompt-on-repost',
57
  '--memory-pressure-off',
58
+ '--max_old_space_size=512' // 减少内存使用
59
  ]
60
  };
61
 
 
72
  }
73
 
74
  this.browser = await puppeteer.launch(launchOptions);
75
+ console.log('✅ Puppeteer浏览器初始化成功');
76
 
77
  // 监听浏览器断开连接事件
78
  this.browser.on('disconnected', () => {
 
81
  this.isClosing = false;
82
  });
83
 
 
84
  this.browserLaunchRetries = 0;
85
  } catch (error) {
86
+ console.error('Puppeteer浏览器初始化失败:', error.message);
87
  this.browserLaunchRetries++;
88
 
89
  if (this.browserLaunchRetries <= this.maxBrowserLaunchRetries) {
90
  console.log(`尝试重新初始化浏览器 (${this.browserLaunchRetries}/${this.maxBrowserLaunchRetries})`);
91
+ await this.delay(2000); // 等待2秒后重试
92
  return this.initBrowser();
93
  } else {
94
  // 如果Puppeteer完全失败,返回null,使用fallback方法
95
+ console.warn('⚠️ Puppeteer初始化完全失败,将使用fallback方法');
96
  return null;
97
  }
98
  }
 
102
  if (this.browser) {
103
  try {
104
  await this.browser.version();
105
+ console.log('✅ 浏览器连接正常');
106
  } catch (error) {
107
  console.warn('浏览器连接已断开,重新初始化:', error.message);
108
  this.browser = null;
 
165
 
166
  // 生成简化的PNG图片作为fallback
167
  generateFallbackImage(width, height, message = 'Screenshot not available') {
168
+ console.log(`🔄 生成fallback图片: ${width}x${height}`);
169
+
170
  // 创建一个简单的SVG作为fallback
171
  const svg = `
172
  <svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
173
+ <rect width="100%" height="100%" fill="#f8f9fa"/>
174
+ <rect x="10" y="10" width="${width-20}" height="${height-20}"
175
+ fill="none" stroke="#dee2e6" stroke-width="2" stroke-dasharray="10,5"/>
176
+ <text x="50%" y="40%" text-anchor="middle" dominant-baseline="middle"
177
+ font-family="Arial, sans-serif" font-size="28" fill="#495057" font-weight="bold">
178
+ PPT 预览图
179
+ </text>
180
  <text x="50%" y="50%" text-anchor="middle" dominant-baseline="middle"
181
+ font-family="Arial, sans-serif" font-size="16" fill="#6c757d">
182
  ${message}
183
  </text>
184
  <text x="50%" y="60%" text-anchor="middle" dominant-baseline="middle"
185
+ font-family="Arial, sans-serif" font-size="14" fill="#adb5bd">
186
+ 尺寸: ${width} × ${height}
187
  </text>
188
+ <circle cx="50%" cy="70%" r="20" fill="none" stroke="#28a745" stroke-width="3">
189
+ <animate attributeName="stroke-dasharray" values="0,126;126,126" dur="2s" repeatCount="indefinite"/>
190
+ </circle>
191
  </svg>
192
  `;
193
 
 
 
 
 
194
  // 返回简单的占位图片数据
195
  return Buffer.from(svg, 'utf8');
196
  }
 
294
 
295
  async generateScreenshotWithPuppeteer(htmlContent, options = {}, retryCount = 0) {
296
  try {
297
+ console.log(`🎯 开始Puppeteer截图生成... (尝试 ${retryCount + 1}/${this.maxRetries + 1})`);
298
+
299
  const browser = await this.initBrowser();
300
  if (!browser) {
301
  throw new Error('浏览器初始化失败');
302
  }
303
 
 
 
304
  // 从HTML中提取PPT的精确尺寸
305
  const dimensions = this.extractPPTDimensions(htmlContent);
306
+ console.log(`📐 检测到PPT尺寸: ${dimensions.width}x${dimensions.height}`);
307
 
308
  // 优化HTML内容以确保精确截图
309
  const optimizedHtml = this.optimizeHtmlForScreenshot(htmlContent, dimensions.width, dimensions.height);
 
312
  let page = null;
313
  try {
314
  page = await browser.newPage();
315
+ console.log('📄 创建新页面成功');
316
 
317
  // 设置页面超时
318
+ page.setDefaultTimeout(20000); // 减少超时时间
319
+ page.setDefaultNavigationTimeout(20000);
320
 
321
  // 设置精确的viewport尺寸
322
  await page.setViewport({
323
  width: dimensions.width,
324
  height: dimensions.height,
325
+ deviceScaleFactor: 1, // 固定为1避免缩放问题
326
  });
327
+ console.log(`🖥️ 设置viewport: ${dimensions.width}x${dimensions.height}`);
328
 
329
  // 设置页面内容
330
  await page.setContent(optimizedHtml, {
331
  waitUntil: ['load', 'domcontentloaded'],
332
+ timeout: 20000
333
  });
334
+ console.log('📝 页面内容设置完成');
335
 
336
  // 等待页面完全渲染
337
+ await page.waitForTimeout(1000); // 减少等待时间
338
+ console.log('⏱️ 等待渲染完成');
339
 
340
  // 执行截图,使用精确的剪裁区域
341
+ console.log('📸 开始执行截图...');
342
  const screenshot = await page.screenshot({
343
+ type: 'jpeg',
344
+ quality: 85, // 稍微降低质量提高速度
345
  clip: {
346
  x: 0,
347
  y: 0,
 
352
  captureBeyondViewport: false,
353
  });
354
 
355
+ console.log(`✅ Puppeteer截图成功生成,尺寸: ${dimensions.width}x${dimensions.height}, 数据大小: ${screenshot.length} 字节`);
356
  return screenshot;
357
 
358
  } finally {
359
  if (page) {
360
  try {
361
  await page.close();
362
+ console.log('📄 页面已关闭');
363
  } catch (error) {
364
  console.warn('关闭页面时出错:', error.message);
365
  }
 
367
  }
368
 
369
  } catch (error) {
370
+ console.error(`❌ Puppeteer截图生成失败 (尝试 ${retryCount + 1}):`, error.message);
371
 
372
  // 如果是目标关闭错误或连接断开,重置浏览器
373
  if (error.message.includes('Target closed') ||
374
  error.message.includes('Connection closed') ||
375
  error.message.includes('Protocol error')) {
376
+ console.log('🔄 检测到浏览器连接问题,重置浏览器实例');
377
  this.browser = null;
378
  this.isClosing = false;
379
  }
380
 
381
  // 如果还有重试机会,进行重试
382
  if (retryCount < this.maxRetries) {
383
+ const waitTime = (retryCount + 1) * 2;
384
+ console.log(`⏳ 等待 ${waitTime} 秒后重试...`);
385
+ await this.delay(waitTime * 1000);
386
  return this.generateScreenshotWithPuppeteer(htmlContent, options, retryCount + 1);
387
  }
388
 
 
391
  }
392
 
393
  async generateScreenshot(htmlContent, options = {}) {
394
+ console.log('🎯 开始生成截图...');
395
+
396
  try {
397
  // 首先尝试使用Puppeteer
398
+ console.log('🚀 尝试使用Puppeteer生成截图');
399
+ const screenshot = await this.generateScreenshotWithPuppeteer(htmlContent, options);
400
+ console.log('✅ Puppeteer截图生成成功');
401
+ return screenshot;
402
  } catch (puppeteerError) {
403
+ console.warn('⚠️ Puppeteer截图失败,使用fallback方法:', puppeteerError.message);
404
 
405
  // 如果Puppeteer失败,生成fallback图片
406
  const dimensions = this.extractPPTDimensions(htmlContent);
407
  const fallbackImage = this.generateFallbackImage(
408
  dimensions.width,
409
  dimensions.height,
410
+ 'Puppeteer不可用,��示占位图'
411
  );
412
 
413
+ console.log(`📋 生成fallback图片,尺寸: ${dimensions.width}x${dimensions.height}`);
414
  return fallbackImage;
415
  }
416
  }