Upload public.js
Browse files- backend/src/routes/public.js +117 -53
backend/src/routes/public.js
CHANGED
@@ -357,7 +357,8 @@ router.post('/generate-share-link', async (req, res, next) => {
|
|
357 |
}
|
358 |
|
359 |
const baseUrl = process.env.PUBLIC_URL || req.get('host');
|
360 |
-
|
|
|
361 |
|
362 |
const shareLinks = {
|
363 |
// Single page share link
|
@@ -475,20 +476,13 @@ router.get('/screenshot/:userId/:pptId/:slideIndex?', async (req, res, next) =>
|
|
475 |
}
|
476 |
});
|
477 |
|
478 |
-
// 🔥 关键修改:image
|
479 |
router.get('/image/:userId/:pptId/:slideIndex?', async (req, res, next) => {
|
480 |
-
// 🚨 强制直接返回SVG图片 - 修复Hugging Face部署问题
|
481 |
try {
|
482 |
const { userId, pptId, slideIndex = 0 } = req.params;
|
|
|
483 |
|
484 |
-
console.log(`🔥
|
485 |
-
|
486 |
-
// 立即设置SVG响应头,确保浏览器知道这是图片
|
487 |
-
res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
|
488 |
-
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
489 |
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
490 |
-
res.setHeader('X-Force-Update', 'true');
|
491 |
-
res.setHeader('X-Timestamp', new Date().toISOString());
|
492 |
|
493 |
// Get PPT data
|
494 |
const fileName = `${pptId}.json`;
|
@@ -509,61 +503,131 @@ router.get('/image/:userId/:pptId/:slideIndex?', async (req, res, next) => {
|
|
509 |
|
510 |
if (!pptData) {
|
511 |
console.log(`❌ PPT not found: ${pptId}`);
|
512 |
-
const notFoundSvg = `<svg width="800" height="450" xmlns="http://www.w3.org/2000/svg">
|
513 |
-
<rect width="100%" height="100%" fill="#f8f9fa"/>
|
514 |
-
<text x="400" y="200" text-anchor="middle" font-family="Arial, sans-serif" font-size="24" fill="#6c757d">PPT Not Found</text>
|
515 |
-
<text x="400" y="250" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" fill="#6c757d">PPT ${pptId} does not exist</text>
|
516 |
-
<text x="400" y="300" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#999">${new Date().toISOString()}</text>
|
517 |
-
</svg>`;
|
518 |
|
519 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
520 |
}
|
521 |
|
522 |
const slideIdx = parseInt(slideIndex);
|
523 |
if (slideIdx >= pptData.slides.length || slideIdx < 0) {
|
524 |
console.log(`❌ Invalid slide index: ${slideIndex}`);
|
525 |
-
const invalidSlideSvg = `<svg width="800" height="450" xmlns="http://www.w3.org/2000/svg">
|
526 |
-
<rect width="100%" height="100%" fill="#f8f9fa"/>
|
527 |
-
<text x="400" y="200" text-anchor="middle" font-family="Arial, sans-serif" font-size="24" fill="#6c757d">Invalid Slide</text>
|
528 |
-
<text x="400" y="250" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" fill="#6c757d">Slide ${slideIndex} not found</text>
|
529 |
-
<text x="400" y="300" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#999">${new Date().toISOString()}</text>
|
530 |
-
</svg>`;
|
531 |
|
532 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
533 |
}
|
534 |
|
535 |
-
|
536 |
-
|
537 |
-
|
538 |
-
|
539 |
-
|
540 |
-
|
541 |
-
|
542 |
-
|
543 |
-
|
544 |
-
|
545 |
-
|
546 |
-
|
547 |
-
|
548 |
-
|
549 |
-
|
550 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
551 |
|
552 |
} catch (error) {
|
553 |
console.error('❌ Image generation failed:', error);
|
554 |
|
555 |
-
//
|
556 |
-
|
557 |
-
|
558 |
-
|
559 |
-
|
560 |
-
|
561 |
-
|
562 |
-
|
563 |
-
|
564 |
-
|
565 |
-
|
566 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
567 |
}
|
568 |
});
|
569 |
|
|
|
357 |
}
|
358 |
|
359 |
const baseUrl = process.env.PUBLIC_URL || req.get('host');
|
360 |
+
// 修复协议判断:localhost 始终使用 http,生产环境使用 https
|
361 |
+
const protocol = baseUrl.includes('localhost') ? 'http' : 'https';
|
362 |
|
363 |
const shareLinks = {
|
364 |
// Single page share link
|
|
|
476 |
}
|
477 |
});
|
478 |
|
479 |
+
// 🔥 关键修改:image端点现在支持多种格式,包括SVG和JPG
|
480 |
router.get('/image/:userId/:pptId/:slideIndex?', async (req, res, next) => {
|
|
|
481 |
try {
|
482 |
const { userId, pptId, slideIndex = 0 } = req.params;
|
483 |
+
const { format = 'svg', quality = 90, width: requestWidth, height: requestHeight } = req.query;
|
484 |
|
485 |
+
console.log(`🔥 Image request: userId=${userId}, pptId=${pptId}, slideIndex=${slideIndex}, format=${format}`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
486 |
|
487 |
// Get PPT data
|
488 |
const fileName = `${pptId}.json`;
|
|
|
503 |
|
504 |
if (!pptData) {
|
505 |
console.log(`❌ PPT not found: ${pptId}`);
|
|
|
|
|
|
|
|
|
|
|
|
|
506 |
|
507 |
+
if (format === 'svg') {
|
508 |
+
const notFoundSvg = `<svg width="800" height="450" xmlns="http://www.w3.org/2000/svg">
|
509 |
+
<rect width="100%" height="100%" fill="#f8f9fa"/>
|
510 |
+
<text x="400" y="200" text-anchor="middle" font-family="Arial, sans-serif" font-size="24" fill="#6c757d">PPT Not Found</text>
|
511 |
+
<text x="400" y="250" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" fill="#6c757d">PPT ${pptId} does not exist</text>
|
512 |
+
<text x="400" y="300" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#999">${new Date().toISOString()}</text>
|
513 |
+
</svg>`;
|
514 |
+
|
515 |
+
res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
|
516 |
+
return res.send(notFoundSvg);
|
517 |
+
} else {
|
518 |
+
// 对于JPG/PNG格式,返回截图工具页面
|
519 |
+
const errorPage = generateErrorPage('PPT Not Found', `PPT ${pptId} not found. Please check the PPT ID.`);
|
520 |
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
521 |
+
return res.status(404).send(errorPage);
|
522 |
+
}
|
523 |
}
|
524 |
|
525 |
const slideIdx = parseInt(slideIndex);
|
526 |
if (slideIdx >= pptData.slides.length || slideIdx < 0) {
|
527 |
console.log(`❌ Invalid slide index: ${slideIndex}`);
|
|
|
|
|
|
|
|
|
|
|
|
|
528 |
|
529 |
+
if (format === 'svg') {
|
530 |
+
const invalidSlideSvg = `<svg width="800" height="450" xmlns="http://www.w3.org/2000/svg">
|
531 |
+
<rect width="100%" height="100%" fill="#f8f9fa"/>
|
532 |
+
<text x="400" y="200" text-anchor="middle" font-family="Arial, sans-serif" font-size="24" fill="#6c757d">Invalid Slide</text>
|
533 |
+
<text x="400" y="250" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" fill="#6c757d">Slide ${slideIndex} not found</text>
|
534 |
+
<text x="400" y="300" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#999">${new Date().toISOString()}</text>
|
535 |
+
</svg>`;
|
536 |
+
|
537 |
+
res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
|
538 |
+
return res.send(invalidSlideSvg);
|
539 |
+
} else {
|
540 |
+
const errorPage = generateErrorPage('Invalid Slide', `Slide ${slideIndex} not found in this PPT.`);
|
541 |
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
542 |
+
return res.status(404).send(errorPage);
|
543 |
+
}
|
544 |
}
|
545 |
|
546 |
+
// 根据格式处理
|
547 |
+
if (format === 'svg') {
|
548 |
+
// SVG格式 - 直接生成并返回
|
549 |
+
console.log(`✅ Generating SVG for slide ${slideIdx}`);
|
550 |
+
|
551 |
+
const defaultWidth = pptData.viewportSize || 1000;
|
552 |
+
const defaultHeight = Math.ceil(defaultWidth * (pptData.viewportRatio || 0.5625));
|
553 |
+
const finalWidth = requestWidth ? parseInt(requestWidth) : defaultWidth;
|
554 |
+
const finalHeight = requestHeight ? parseInt(requestHeight) : defaultHeight;
|
555 |
+
|
556 |
+
const slide = pptData.slides[slideIdx];
|
557 |
+
const svgContent = generateSlideSVG(slide, pptData, {
|
558 |
+
width: finalWidth,
|
559 |
+
height: finalHeight
|
560 |
+
});
|
561 |
+
|
562 |
+
console.log(`🎉 SVG generated successfully, returning image data`);
|
563 |
+
|
564 |
+
res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
|
565 |
+
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
566 |
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
567 |
+
res.setHeader('X-Force-Update', 'true');
|
568 |
+
res.setHeader('X-Generation-Time', '< 10ms');
|
569 |
+
res.send(svgContent);
|
570 |
+
|
571 |
+
} else {
|
572 |
+
// JPG/PNG格式 - 直接生成并返回图片
|
573 |
+
console.log(`✅ Generating ${format} image directly`);
|
574 |
+
|
575 |
+
try {
|
576 |
+
// 使用共享模块生成HTML用于后端截图
|
577 |
+
const htmlContent = generateSlideHTML(pptData, slideIdx, { format: 'screenshot' });
|
578 |
+
|
579 |
+
const screenshot = await screenshotService.generateScreenshot(htmlContent, {
|
580 |
+
format: format === 'jpg' ? 'jpeg' : format,
|
581 |
+
quality: parseInt(quality),
|
582 |
+
width: pptData.viewportSize || 1000,
|
583 |
+
height: Math.ceil((pptData.viewportSize || 1000) * (pptData.viewportRatio || 0.5625))
|
584 |
+
});
|
585 |
+
|
586 |
+
res.setHeader('Content-Type', `image/${format === 'jpg' ? 'jpeg' : format}`);
|
587 |
+
res.setHeader('X-Screenshot-Type', 'backend-generated');
|
588 |
+
res.setHeader('X-Generation-Time', '2-5s');
|
589 |
+
res.setHeader('Cache-Control', 'public, max-age=3600'); // 缓存1小时
|
590 |
+
res.send(screenshot);
|
591 |
+
|
592 |
+
} catch (error) {
|
593 |
+
console.error(`Backend ${format} generation failed:`, error);
|
594 |
+
|
595 |
+
// 生成后备图片
|
596 |
+
const fallbackImage = screenshotService.generateFallbackImage(
|
597 |
+
pptData.viewportSize || 1000,
|
598 |
+
Math.ceil((pptData.viewportSize || 1000) * (pptData.viewportRatio || 0.5625)),
|
599 |
+
`PPT ${format.toUpperCase()} Image`,
|
600 |
+
'Image generation failed'
|
601 |
+
);
|
602 |
+
|
603 |
+
res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
|
604 |
+
res.setHeader('X-Screenshot-Type', 'fallback-generated');
|
605 |
+
res.setHeader('X-Generation-Time', '< 50ms');
|
606 |
+
res.send(fallbackImage);
|
607 |
+
}
|
608 |
+
}
|
609 |
|
610 |
} catch (error) {
|
611 |
console.error('❌ Image generation failed:', error);
|
612 |
|
613 |
+
// 根据格式返回错误
|
614 |
+
if (req.query.format === 'svg') {
|
615 |
+
res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
|
616 |
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
617 |
+
|
618 |
+
const errorSvg = `<svg width="800" height="450" xmlns="http://www.w3.org/2000/svg">
|
619 |
+
<rect width="100%" height="100%" fill="#ffe6e6"/>
|
620 |
+
<text x="400" y="200" text-anchor="middle" font-family="Arial, sans-serif" font-size="24" fill="#cc0000">Generation Error</text>
|
621 |
+
<text x="400" y="250" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" fill="#cc0000">Failed to generate image</text>
|
622 |
+
<text x="400" y="300" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#999">${new Date().toISOString()}</text>
|
623 |
+
</svg>`;
|
624 |
+
|
625 |
+
res.send(errorSvg);
|
626 |
+
} else {
|
627 |
+
const errorPage = generateErrorPage('Generation Error', 'Failed to generate the requested image format.');
|
628 |
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
629 |
+
res.status(500).send(errorPage);
|
630 |
+
}
|
631 |
}
|
632 |
});
|
633 |
|