CatPtain commited on
Commit
b63441d
·
verified ·
1 Parent(s): d80434c

Upload 11 files

Browse files
backend/package.json CHANGED
@@ -10,18 +10,18 @@
10
  "build": "echo 'No build step required'"
11
  },
12
  "dependencies": {
13
- "express": "^4.21.1",
 
14
  "cors": "^2.8.5",
 
 
 
15
  "helmet": "^8.0.0",
16
- "bcryptjs": "^2.4.3",
17
  "jsonwebtoken": "^9.0.2",
18
  "multer": "^1.4.5-lts.1",
19
- "axios": "^1.7.9",
20
- "dotenv": "^16.4.5",
21
- "express-rate-limit": "^7.4.1",
22
- "uuid": "^10.0.0",
23
  "puppeteer": "^21.0.0",
24
- "playwright": "^1.40.0"
25
  },
26
  "devDependencies": {
27
  "nodemon": "^3.1.7"
@@ -29,4 +29,4 @@
29
  "engines": {
30
  "node": ">=18.0.0"
31
  }
32
- }
 
10
  "build": "echo 'No build step required'"
11
  },
12
  "dependencies": {
13
+ "axios": "^1.7.9",
14
+ "bcryptjs": "^2.4.3",
15
  "cors": "^2.8.5",
16
+ "dotenv": "^16.4.5",
17
+ "express": "^4.21.1",
18
+ "express-rate-limit": "^7.4.1",
19
  "helmet": "^8.0.0",
 
20
  "jsonwebtoken": "^9.0.2",
21
  "multer": "^1.4.5-lts.1",
22
+ "playwright": "^1.53.1",
 
 
 
23
  "puppeteer": "^21.0.0",
24
+ "uuid": "^10.0.0"
25
  },
26
  "devDependencies": {
27
  "nodemon": "^3.1.7"
 
29
  "engines": {
30
  "node": ">=18.0.0"
31
  }
32
+ }
backend/src/routes/public.js CHANGED
@@ -6,6 +6,8 @@ import { generateSlideHTML, generateExportPage, exportPPTToJSON, generateHTMLPre
6
 
7
  const router = express.Router();
8
 
 
 
9
  // 辅助函数:生成SVG - 改进版,支持更精确的PPT渲染
10
  function generateSlideSVG(slide, pptData, options = {}) {
11
  const { width = 1000, height = 562 } = options;
@@ -640,8 +642,8 @@ router.get('/image/:userId/:pptId/:slideIndex?', async (req, res, next) => {
640
  res.send(svgContent);
641
 
642
  } else {
643
- // JPG/PNG格式 - 直接生成并返回图片
644
- console.log(`✅ Generating ${format} image directly`);
645
 
646
  try {
647
  // 使用共享模块生成HTML用于后端截图
@@ -656,7 +658,7 @@ router.get('/image/:userId/:pptId/:slideIndex?', async (req, res, next) => {
656
 
657
  res.setHeader('Content-Type', `image/${format === 'jpg' ? 'jpeg' : format}`);
658
  res.setHeader('X-Screenshot-Type', 'backend-generated');
659
- res.setHeader('X-Generation-Time', '2-5s');
660
  res.setHeader('Cache-Control', 'public, max-age=3600'); // 缓存1小时
661
  res.send(screenshot);
662
 
 
6
 
7
  const router = express.Router();
8
 
9
+ // 前端导出图片功能已移除,直接使用screenshotService
10
+
11
  // 辅助函数:生成SVG - 改进版,支持更精确的PPT渲染
12
  function generateSlideSVG(slide, pptData, options = {}) {
13
  const { width = 1000, height = 562 } = options;
 
642
  res.send(svgContent);
643
 
644
  } else {
645
+ // JPG/PNG格式 - 使用优化的截图方法
646
+ console.log(`✅ Generating ${format} image using optimized screenshot method`);
647
 
648
  try {
649
  // 使用共享模块生成HTML用于后端截图
 
658
 
659
  res.setHeader('Content-Type', `image/${format === 'jpg' ? 'jpeg' : format}`);
660
  res.setHeader('X-Screenshot-Type', 'backend-generated');
661
+ res.setHeader('X-Generation-Time', '1-3s'); // 优化后的时间估计
662
  res.setHeader('Cache-Control', 'public, max-age=3600'); // 缓存1小时
663
  res.send(screenshot);
664
 
backend/src/services/screenshotService.js CHANGED
@@ -1,5 +1,12 @@
1
  import puppeteer from 'puppeteer';
2
  import playwright from 'playwright';
 
 
 
 
 
 
 
3
 
4
  class ScreenshotService {
5
  constructor() {
@@ -9,23 +16,54 @@ class ScreenshotService {
9
  this.puppeteerReady = false;
10
  this.playwrightReady = false;
11
 
12
- console.log('Screenshot service initialized');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  }
14
 
15
- // Initialize Puppeteer browser
16
  async initPuppeteer() {
17
  if (this.puppeteerBrowser || this.isInitializing) return;
18
 
19
  this.isInitializing = true;
20
 
21
  try {
22
- console.log('🚀 Starting Puppeteer browser...');
23
 
 
24
  const launchOptions = {
25
- headless: 'new',
26
  args: [
 
27
  '--no-sandbox',
28
  '--disable-setuid-sandbox',
 
 
29
  '--disable-dev-shm-usage',
30
  '--disable-gpu',
31
  '--disable-extensions',
@@ -37,18 +75,48 @@ class ScreenshotService {
37
  '--disable-default-apps',
38
  '--disable-features=TranslateUI',
39
  '--disable-ipc-flooding-protection',
 
 
 
40
  '--memory-pressure-off',
41
- '--max_old_space_size=4096'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  ],
43
- timeout: 30000
 
 
 
 
 
 
 
44
  };
45
 
46
- // Hugging Face Spaces optimizations
47
- if (process.env.SPACE_ID) {
 
 
48
  launchOptions.args.push(
49
- '--single-process',
50
- '--disable-background-networking',
51
- '--disable-background-mode'
52
  );
53
  }
54
 
@@ -56,6 +124,12 @@ class ScreenshotService {
56
  this.puppeteerReady = true;
57
  console.log('✅ Puppeteer browser started successfully');
58
 
 
 
 
 
 
 
59
  // Cleanup when process exits
60
  process.on('exit', () => this.cleanup());
61
  process.on('SIGINT', () => this.cleanup());
@@ -68,22 +142,175 @@ class ScreenshotService {
68
  this.isInitializing = false;
69
  }
70
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
 
72
- // Initialize Playwright browser (fallback)
73
  async initPlaywright() {
74
  if (this.playwrightBrowser) return;
75
 
76
  try {
77
- console.log('🎭 Starting Playwright browser...');
78
 
79
- this.playwrightBrowser = await playwright.chromium.launch({
 
80
  headless: true,
81
  args: [
 
82
  '--no-sandbox',
83
  '--disable-setuid-sandbox',
84
- '--disable-dev-shm-usage'
85
- ]
86
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
 
88
  this.playwrightReady = true;
89
  console.log('✅ Playwright browser started successfully');
@@ -93,6 +320,64 @@ class ScreenshotService {
93
  this.playwrightReady = false;
94
  }
95
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
  // Generate screenshot
98
  async generateScreenshot(htmlContent, options = {}) {
@@ -137,36 +422,72 @@ class ScreenshotService {
137
  return this.generateFallbackImage(width, height, 'Screenshot Service', 'Browser unavailable');
138
  }
139
 
140
- // Generate screenshot using Puppeteer
141
  async generateWithPuppeteer(htmlContent, options) {
142
  const { format, quality, width, height, timeout } = options;
143
  let page = null;
 
144
 
145
  try {
146
- page = await this.puppeteerBrowser.newPage();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
 
148
- // Set viewport
149
  await page.setViewport({ width, height });
150
 
151
- // Set content
152
  await page.setContent(htmlContent, {
153
  waitUntil: 'networkidle0',
154
- timeout: timeout
155
  });
156
 
157
- // Wait for fonts
158
  await page.evaluate(() => {
159
  return document.fonts ? document.fonts.ready : Promise.resolve();
160
  });
161
 
162
- // Additional wait for rendering
163
- await page.waitForTimeout(300);
 
 
 
 
 
164
 
165
- // Take screenshot
166
  const screenshotOptions = {
167
  type: format,
168
  quality: format === 'jpeg' ? quality : undefined,
169
  fullPage: false,
 
170
  clip: { x: 0, y: 0, width, height }
171
  };
172
 
@@ -175,40 +496,83 @@ class ScreenshotService {
175
  console.log(`✅ Puppeteer screenshot generated: ${screenshot.length} bytes`);
176
  return screenshot;
177
 
 
 
 
178
  } finally {
179
  if (page) {
180
- await page.close().catch(console.error);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  }
182
  }
183
  }
184
 
185
- // Generate screenshot using Playwright
186
  async generateWithPlaywright(htmlContent, options) {
187
  const { format, quality, width, height, timeout } = options;
188
  let page = null;
 
189
 
190
  try {
191
- page = await this.playwrightBrowser.newPage();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
 
193
- // Set viewport
194
  await page.setViewportSize({ width, height });
195
 
196
- // Set content
197
  await page.setContent(htmlContent, {
198
  waitUntil: 'networkidle',
199
- timeout: timeout
200
  });
201
 
202
- // Wait for fonts
203
  await page.evaluate(() => {
204
  return document.fonts ? document.fonts.ready : Promise.resolve();
205
  });
206
 
207
- // Take screenshot
 
 
 
 
 
 
 
 
208
  const screenshotOptions = {
209
  type: format,
210
  quality: format === 'jpeg' ? quality : undefined,
211
  fullPage: false,
 
212
  clip: { x: 0, y: 0, width, height }
213
  };
214
 
@@ -217,9 +581,26 @@ class ScreenshotService {
217
  console.log(`✅ Playwright screenshot generated: ${screenshot.length} bytes`);
218
  return screenshot;
219
 
 
 
 
220
  } finally {
221
  if (page) {
222
- await page.close().catch(console.error);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  }
224
  }
225
  }
 
1
  import puppeteer from 'puppeteer';
2
  import playwright from 'playwright';
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ // 获取当前文件的目录路径
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
 
11
  class ScreenshotService {
12
  constructor() {
 
16
  this.puppeteerReady = false;
17
  this.playwrightReady = false;
18
 
19
+ // 页面池 - 重用页面以提高性能
20
+ this.puppeteerPagePool = [];
21
+ this.playwrightPagePool = [];
22
+ this.maxPoolSize = 3; // 最大页面池大小
23
+
24
+ // 字体配置
25
+ this.googleFonts = [
26
+ 'Noto+Sans+SC:400,700',
27
+ 'Noto+Serif+SC:400,700'
28
+ ];
29
+
30
+ console.log('📸 Screenshot service initialized (optimized version)');
31
+
32
+ // 启动时预热浏览器
33
+ this.warmupBrowsers();
34
+ }
35
+
36
+ // 预热浏览器实例
37
+ async warmupBrowsers() {
38
+ setTimeout(async () => {
39
+ try {
40
+ console.log('🔥 Warming up browser instances...');
41
+ await this.initPuppeteer();
42
+ console.log('✅ Browser warmup complete');
43
+ } catch (error) {
44
+ console.error('❌ Browser warmup failed:', error.message);
45
+ }
46
+ }, 1000); // 延迟1秒启动,避免与服务器启动冲突
47
  }
48
 
49
+ // Initialize Puppeteer browser with optimizations
50
  async initPuppeteer() {
51
  if (this.puppeteerBrowser || this.isInitializing) return;
52
 
53
  this.isInitializing = true;
54
 
55
  try {
56
+ console.log('🚀 Starting Puppeteer browser (optimized)...');
57
 
58
+ // 优化的启动选项
59
  const launchOptions = {
60
+ headless: 'new', // 使用新的无头模式
61
  args: [
62
+ // 安全性设置
63
  '--no-sandbox',
64
  '--disable-setuid-sandbox',
65
+
66
+ // 内存和性能优化
67
  '--disable-dev-shm-usage',
68
  '--disable-gpu',
69
  '--disable-extensions',
 
75
  '--disable-default-apps',
76
  '--disable-features=TranslateUI',
77
  '--disable-ipc-flooding-protection',
78
+
79
+ // 内存限制优化
80
+ '--js-flags=--max-old-space-size=512', // 降低JS内存使用
81
  '--memory-pressure-off',
82
+
83
+ // 进程优化
84
+ '--single-process', // 使用单进程模式减少资源消耗
85
+ '--disable-background-networking',
86
+ '--disable-background-mode',
87
+
88
+ // 字体渲染优化
89
+ '--font-render-hinting=none',
90
+
91
+ // 禁用不必要的功能
92
+ '--disable-breakpad',
93
+ '--disable-component-update',
94
+ '--disable-domain-reliability',
95
+ '--disable-sync',
96
+ '--disable-hang-monitor',
97
+ '--disable-prompt-on-repost',
98
+ '--disable-client-side-phishing-detection',
99
+ '--disable-component-extensions-with-background-pages',
100
+ '--blink-settings=imagesEnabled=true'
101
  ],
102
+ timeout: 20000, // 减少超时时间
103
+ ignoreHTTPSErrors: true,
104
+ defaultViewport: null, // 动态设置视口
105
+ handleSIGINT: false, // 由我们自己处理
106
+ handleSIGTERM: false,
107
+ handleSIGHUP: false,
108
+ dumpio: false, // 禁用浏览器日志输出到控制台
109
+ protocolTimeout: 15000
110
  };
111
 
112
+ // 检测环境并应用特定优化
113
+ const isLowMemoryEnv = process.env.LOW_MEMORY === 'true';
114
+ if (isLowMemoryEnv) {
115
+ console.log('🔧 Applying low memory optimizations');
116
  launchOptions.args.push(
117
+ '--disable-javascript',
118
+ '--disable-images',
119
+ '--disable-css-animations'
120
  );
121
  }
122
 
 
124
  this.puppeteerReady = true;
125
  console.log('✅ Puppeteer browser started successfully');
126
 
127
+ // 预创建页面池
128
+ await this.initPagePool();
129
+
130
+ // 监控浏览器健康状态
131
+ this.startBrowserMonitoring();
132
+
133
  // Cleanup when process exits
134
  process.on('exit', () => this.cleanup());
135
  process.on('SIGINT', () => this.cleanup());
 
142
  this.isInitializing = false;
143
  }
144
  }
145
+
146
+ // 初始化页面池
147
+ async initPagePool() {
148
+ if (!this.puppeteerReady || !this.puppeteerBrowser) return;
149
+
150
+ try {
151
+ console.log(`🔄 Initializing page pool (size: ${this.maxPoolSize})...`);
152
+
153
+ // 清空现有池
154
+ for (const page of this.puppeteerPagePool) {
155
+ await page.close().catch(console.error);
156
+ }
157
+ this.puppeteerPagePool = [];
158
+
159
+ // 创建新页面
160
+ for (let i = 0; i < this.maxPoolSize; i++) {
161
+ const page = await this.puppeteerBrowser.newPage();
162
+
163
+ // 优化页面设置
164
+ await page.setRequestInterception(true);
165
+ page.on('request', (request) => {
166
+ // 阻止不必要的资源加载
167
+ const resourceType = request.resourceType();
168
+ if (resourceType === 'media' || resourceType === 'font') {
169
+ // 允许字体资源加载
170
+ request.continue();
171
+ } else if (resourceType === 'stylesheet' || resourceType === 'script' || resourceType === 'image') {
172
+ // 允许基本资源
173
+ request.continue();
174
+ } else {
175
+ // 阻止其他资源
176
+ request.abort();
177
+ }
178
+ });
179
+
180
+ // 注入Google字体
181
+ await this.injectGoogleFonts(page);
182
+
183
+ // 设置默认视口
184
+ await page.setViewport({ width: 1000, height: 562 });
185
+
186
+ // 添加到池中
187
+ this.puppeteerPagePool.push(page);
188
+ }
189
+
190
+ console.log(`✅ Page pool initialized with ${this.puppeteerPagePool.length} pages`);
191
+ } catch (error) {
192
+ console.error('❌ Failed to initialize page pool:', error.message);
193
+ }
194
+ }
195
+
196
+ // 注入Google字体
197
+ async injectGoogleFonts(page) {
198
+ try {
199
+ // 构建Google字体URL
200
+ const googleFontsUrl = `https://fonts.googleapis.com/css2?family=${this.googleFonts.join('&family=')}&display=swap`;
201
+
202
+ // 注入字体CSS
203
+ await page.evaluateOnNewDocument((fontsUrl) => {
204
+ // 创建link元素加载Google字体
205
+ const link = document.createElement('link');
206
+ link.rel = 'stylesheet';
207
+ link.href = fontsUrl;
208
+
209
+ // 当文档创建时添加到head
210
+ document.addEventListener('DOMContentLoaded', () => {
211
+ document.head.appendChild(link);
212
+ });
213
+ }, googleFontsUrl);
214
+ } catch (error) {
215
+ console.warn('⚠️ Failed to inject Google Fonts:', error.message);
216
+ }
217
+ }
218
+
219
+ // 监控浏览器健康状态
220
+ startBrowserMonitoring() {
221
+ const checkInterval = 5 * 60 * 1000; // 5分钟检查一次
222
+
223
+ setInterval(async () => {
224
+ if (!this.puppeteerBrowser) return;
225
+
226
+ try {
227
+ // 检查浏览器是否响应
228
+ await this.puppeteerBrowser.version();
229
+
230
+ // 检查内存使用情况
231
+ const pages = await this.puppeteerBrowser.pages();
232
+ console.log(`🔍 Browser health check: ${pages.length} pages open`);
233
+
234
+ // 如果页面过多,关闭多余页面
235
+ if (pages.length > this.maxPoolSize * 2) {
236
+ console.log(`⚠️ Too many pages open (${pages.length}), cleaning up...`);
237
+
238
+ // 保留页面池中的页面,关闭其他页面
239
+ const poolPageIds = this.puppeteerPagePool.map(p => p.target()._targetId);
240
+
241
+ for (const page of pages) {
242
+ const pageId = page.target()._targetId;
243
+ if (!poolPageIds.includes(pageId)) {
244
+ await page.close().catch(console.error);
245
+ }
246
+ }
247
+ }
248
+ } catch (error) {
249
+ console.error('❌ Browser health check failed:', error.message);
250
+
251
+ // 尝试重启浏览器
252
+ await this.restartBrowser();
253
+ }
254
+ }, checkInterval);
255
+ }
256
+
257
+ // 重启浏览器
258
+ async restartBrowser() {
259
+ console.log('🔄 Attempting to restart browser...');
260
+
261
+ try {
262
+ // 清理现有资源
263
+ await this.cleanup();
264
+
265
+ // 重新初始化
266
+ this.puppeteerReady = false;
267
+ this.playwrightReady = false;
268
+ await this.initPuppeteer();
269
+
270
+ console.log('✅ Browser successfully restarted');
271
+ } catch (error) {
272
+ console.error('❌ Browser restart failed:', error.message);
273
+ }
274
+ }
275
 
276
+ // Initialize Playwright browser (fallback) with optimizations
277
  async initPlaywright() {
278
  if (this.playwrightBrowser) return;
279
 
280
  try {
281
+ console.log('🎭 Starting Playwright browser (optimized)...');
282
 
283
+ // 优化的启动选项
284
+ const launchOptions = {
285
  headless: true,
286
  args: [
287
+ // 安全性设置
288
  '--no-sandbox',
289
  '--disable-setuid-sandbox',
290
+
291
+ // 内存和性能优化
292
+ '--disable-dev-shm-usage',
293
+ '--disable-gpu',
294
+ '--disable-extensions',
295
+ '--js-flags=--max-old-space-size=512',
296
+ '--single-process',
297
+ '--disable-background-networking',
298
+
299
+ // 字体渲染优化
300
+ '--font-render-hinting=none'
301
+ ],
302
+ ignoreDefaultArgs: ['--enable-automation'],
303
+ chromiumSandbox: false,
304
+ handleSIGINT: false,
305
+ handleSIGTERM: false,
306
+ handleSIGHUP: false,
307
+ timeout: 15000
308
+ };
309
+
310
+ this.playwrightBrowser = await playwright.chromium.launch(launchOptions);
311
+
312
+ // 初始化页面池
313
+ await this.initPlaywrightPagePool();
314
 
315
  this.playwrightReady = true;
316
  console.log('✅ Playwright browser started successfully');
 
320
  this.playwrightReady = false;
321
  }
322
  }
323
+
324
+ // 初始化Playwright页面池
325
+ async initPlaywrightPagePool() {
326
+ if (!this.playwrightBrowser) return;
327
+
328
+ try {
329
+ console.log(`🔄 Initializing Playwright page pool...`);
330
+
331
+ // 清空现有池
332
+ for (const page of this.playwrightPagePool) {
333
+ await page.close().catch(console.error);
334
+ }
335
+ this.playwrightPagePool = [];
336
+
337
+ // 创建新页面
338
+ for (let i = 0; i < this.maxPoolSize; i++) {
339
+ const context = await this.playwrightBrowser.newContext({
340
+ viewport: { width: 1000, height: 562 },
341
+ ignoreHTTPSErrors: true
342
+ });
343
+
344
+ const page = await context.newPage();
345
+
346
+ // 注入Google字体
347
+ await this.injectPlaywrightGoogleFonts(page);
348
+
349
+ // 添加到池中
350
+ this.playwrightPagePool.push(page);
351
+ }
352
+
353
+ console.log(`✅ Playwright page pool initialized with ${this.playwrightPagePool.length} pages`);
354
+ } catch (error) {
355
+ console.error('❌ Failed to initialize Playwright page pool:', error.message);
356
+ }
357
+ }
358
+
359
+ // 注入Google字体到Playwright页面
360
+ async injectPlaywrightGoogleFonts(page) {
361
+ try {
362
+ // 构建Google字体URL
363
+ const googleFontsUrl = `https://fonts.googleapis.com/css2?family=${this.googleFonts.join('&family=')}&display=swap`;
364
+
365
+ // 注入字体CSS
366
+ await page.addInitScript(({ fontsUrl }) => {
367
+ // 创建link元素加载Google字体
368
+ const link = document.createElement('link');
369
+ link.rel = 'stylesheet';
370
+ link.href = fontsUrl;
371
+
372
+ // 当文档创建时添加到head
373
+ document.addEventListener('DOMContentLoaded', () => {
374
+ document.head.appendChild(link);
375
+ });
376
+ }, { fontsUrl: googleFontsUrl });
377
+ } catch (error) {
378
+ console.warn('⚠️ Failed to inject Google Fonts to Playwright:', error.message);
379
+ }
380
+ }
381
 
382
  // Generate screenshot
383
  async generateScreenshot(htmlContent, options = {}) {
 
422
  return this.generateFallbackImage(width, height, 'Screenshot Service', 'Browser unavailable');
423
  }
424
 
425
+ // Generate screenshot using Puppeteer with page pool optimization
426
  async generateWithPuppeteer(htmlContent, options) {
427
  const { format, quality, width, height, timeout } = options;
428
  let page = null;
429
+ let pageFromPool = false;
430
 
431
  try {
432
+ // 尝试从页面池获取页面
433
+ if (this.puppeteerPagePool && this.puppeteerPagePool.length > 0) {
434
+ page = this.puppeteerPagePool.shift();
435
+ pageFromPool = true;
436
+ console.log(`📄 Using page from Puppeteer pool (${this.puppeteerPagePool.length} remaining)`);
437
+ } else {
438
+ // 如果池为空,创建新页面
439
+ page = await this.puppeteerBrowser.newPage();
440
+ console.log(`📄 Created new Puppeteer page (pool empty)`);
441
+
442
+ // 优化新页面设置
443
+ await page.setRequestInterception(true);
444
+ page.on('request', (request) => {
445
+ // 阻止不必要的资源加载
446
+ const resourceType = request.resourceType();
447
+ if (resourceType === 'media' || resourceType === 'font') {
448
+ // 允许字体资源加载
449
+ request.continue();
450
+ } else if (resourceType === 'stylesheet' || resourceType === 'script' || resourceType === 'image') {
451
+ // 允许基本资源
452
+ request.continue();
453
+ } else {
454
+ // 阻止其他资源
455
+ request.abort();
456
+ }
457
+ });
458
+
459
+ // 为新创建的页面注入Google字体
460
+ await this.injectGoogleFonts(page);
461
+ }
462
 
463
+ // 设置视口大小(即使从池中获取的页面也需要重新设置,因为尺寸可能不同)
464
  await page.setViewport({ width, height });
465
 
466
+ // 设置内容,使用更短的超时时间
467
  await page.setContent(htmlContent, {
468
  waitUntil: 'networkidle0',
469
+ timeout: Math.min(timeout, 10000) // 使用较短的超时时间
470
  });
471
 
472
+ // 等待字体加载
473
  await page.evaluate(() => {
474
  return document.fonts ? document.fonts.ready : Promise.resolve();
475
  });
476
 
477
+ // 减少等待时间
478
+ await page.waitForTimeout(200);
479
+
480
+ // 优化内存使用
481
+ if (global.gc && Math.random() < 0.1) { // 随机触发GC,避免每次都执行
482
+ global.gc();
483
+ }
484
 
485
+ // 截图选项
486
  const screenshotOptions = {
487
  type: format,
488
  quality: format === 'jpeg' ? quality : undefined,
489
  fullPage: false,
490
+ omitBackground: format === 'png', // PNG格式可以透明背景
491
  clip: { x: 0, y: 0, width, height }
492
  };
493
 
 
496
  console.log(`✅ Puppeteer screenshot generated: ${screenshot.length} bytes`);
497
  return screenshot;
498
 
499
+ } catch (error) {
500
+ console.error(`❌ Puppeteer screenshot failed: ${error.message}`);
501
+ throw error;
502
  } finally {
503
  if (page) {
504
+ if (pageFromPool && this.puppeteerPagePool.length < this.maxPoolSize) {
505
+ // 重置页面状态
506
+ try {
507
+ await page.goto('about:blank').catch(() => {});
508
+ // 将页面放回池中
509
+ this.puppeteerPagePool.push(page);
510
+ console.log(`♻️ Returned Puppeteer page to pool (${this.puppeteerPagePool.length} total)`);
511
+ } catch (e) {
512
+ console.warn('⚠️ Failed to reset page, closing it:', e.message);
513
+ await page.close().catch(console.error);
514
+ }
515
+ } else if (!pageFromPool) {
516
+ // 关闭非池中的页面
517
+ await page.close().catch(console.error);
518
+ }
519
  }
520
  }
521
  }
522
 
523
+ // Generate screenshot using Playwright with page pool optimization
524
  async generateWithPlaywright(htmlContent, options) {
525
  const { format, quality, width, height, timeout } = options;
526
  let page = null;
527
+ let pageFromPool = false;
528
 
529
  try {
530
+ // 尝试从页面池获取页面
531
+ if (this.playwrightPagePool && this.playwrightPagePool.length > 0) {
532
+ page = this.playwrightPagePool.shift();
533
+ pageFromPool = true;
534
+ console.log(`📄 Using page from Playwright pool (${this.playwrightPagePool.length} remaining)`);
535
+ } else {
536
+ // 如果池为空,创建新页面
537
+ const context = await this.playwrightBrowser.newContext({
538
+ viewport: { width, height },
539
+ ignoreHTTPSErrors: true
540
+ });
541
+ page = await context.newPage();
542
+ console.log(`📄 Created new Playwright page (pool empty)`);
543
+
544
+ // 为新创建的页面注入Google字体
545
+ await this.injectPlaywrightGoogleFonts(page);
546
+ }
547
 
548
+ // 设置视口大小(即使从池中获取的页面也需要重新设置,因为尺寸可能不同)
549
  await page.setViewportSize({ width, height });
550
 
551
+ // 设置内容,使用更短的超时时间
552
  await page.setContent(htmlContent, {
553
  waitUntil: 'networkidle',
554
+ timeout: Math.min(timeout, 10000) // 使用较短的超时时间
555
  });
556
 
557
+ // 等待字体加载
558
  await page.evaluate(() => {
559
  return document.fonts ? document.fonts.ready : Promise.resolve();
560
  });
561
 
562
+ // 减少等待时间
563
+ await page.waitForTimeout(200);
564
+
565
+ // 优化内存使用
566
+ if (global.gc && Math.random() < 0.1) { // 随机触发GC,避免每次都执行
567
+ global.gc();
568
+ }
569
+
570
+ // 截图选项
571
  const screenshotOptions = {
572
  type: format,
573
  quality: format === 'jpeg' ? quality : undefined,
574
  fullPage: false,
575
+ omitBackground: format === 'png', // PNG格式可以透明背景
576
  clip: { x: 0, y: 0, width, height }
577
  };
578
 
 
581
  console.log(`✅ Playwright screenshot generated: ${screenshot.length} bytes`);
582
  return screenshot;
583
 
584
+ } catch (error) {
585
+ console.error(`❌ Playwright screenshot failed: ${error.message}`);
586
+ throw error;
587
  } finally {
588
  if (page) {
589
+ if (pageFromPool && this.playwrightPagePool.length < this.maxPoolSize) {
590
+ // 重置页面状态
591
+ try {
592
+ await page.goto('about:blank').catch(() => {});
593
+ // 将页面放回池中
594
+ this.playwrightPagePool.push(page);
595
+ console.log(`♻️ Returned Playwright page to pool (${this.playwrightPagePool.length} total)`);
596
+ } catch (e) {
597
+ console.warn('⚠️ Failed to reset page, closing it:', e.message);
598
+ await page.close().catch(console.error);
599
+ }
600
+ } else if (!pageFromPool) {
601
+ // 关闭非池中的页面
602
+ await page.close().catch(console.error);
603
+ }
604
  }
605
  }
606
  }