Upload public.js
Browse files- backend/src/routes/public.js +133 -23
backend/src/routes/public.js
CHANGED
@@ -352,13 +352,13 @@ router.get('/screenshot/:userId/:pptId/:slideIndex?', async (req, res, next) =>
|
|
352 |
}
|
353 |
});
|
354 |
|
355 |
-
// Image endpoint -
|
356 |
router.get('/image/:userId/:pptId/:slideIndex?', async (req, res, next) => {
|
357 |
try {
|
358 |
const { userId, pptId, slideIndex = 0 } = req.params;
|
359 |
-
const { format = '
|
360 |
|
361 |
-
console.log(
|
362 |
|
363 |
// Get PPT data
|
364 |
const fileName = `${pptId}.json`;
|
@@ -369,6 +369,7 @@ router.get('/image/:userId/:pptId/:slideIndex?', async (req, res, next) => {
|
|
369 |
const result = await githubService.getFile(userId, fileName, i);
|
370 |
if (result) {
|
371 |
pptData = result.content;
|
|
|
372 |
break;
|
373 |
}
|
374 |
} catch (error) {
|
@@ -377,32 +378,78 @@ router.get('/image/:userId/:pptId/:slideIndex?', async (req, res, next) => {
|
|
377 |
}
|
378 |
|
379 |
if (!pptData) {
|
380 |
-
|
381 |
-
|
382 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
383 |
}
|
384 |
|
385 |
const slideIdx = parseInt(slideIndex);
|
386 |
if (slideIdx >= pptData.slides.length || slideIdx < 0) {
|
387 |
-
|
388 |
-
|
389 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
390 |
}
|
391 |
|
392 |
-
|
393 |
-
|
394 |
-
|
395 |
-
|
396 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
397 |
});
|
398 |
|
399 |
-
|
400 |
-
|
401 |
-
|
402 |
-
res.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
403 |
|
404 |
} catch (error) {
|
405 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
406 |
}
|
407 |
});
|
408 |
|
@@ -508,6 +555,61 @@ router.get('/screenshot-health', async (req, res, next) => {
|
|
508 |
}
|
509 |
});
|
510 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
511 |
// 新增:PPT JSON导出端点
|
512 |
router.get('/export-json/:userId/:pptId', async (req, res, next) => {
|
513 |
try {
|
@@ -624,13 +726,13 @@ router.get('/presentation/:userId/:pptId', async (req, res, next) => {
|
|
624 |
}
|
625 |
});
|
626 |
|
627 |
-
// 新增:直接返回图片的端点 -
|
628 |
router.get('/direct-image/:userId/:pptId/:slideIndex?', async (req, res, next) => {
|
629 |
try {
|
630 |
const { userId, pptId, slideIndex = 0 } = req.params;
|
631 |
const { format = 'svg', quality = 90, width: requestWidth, height: requestHeight } = req.query;
|
632 |
|
633 |
-
console.log(
|
634 |
|
635 |
// Get PPT data
|
636 |
const fileName = `${pptId}.json`;
|
@@ -641,6 +743,7 @@ router.get('/direct-image/:userId/:pptId/:slideIndex?', async (req, res, next) =
|
|
641 |
const result = await githubService.getFile(userId, fileName, i);
|
642 |
if (result) {
|
643 |
pptData = result.content;
|
|
|
644 |
break;
|
645 |
}
|
646 |
} catch (error) {
|
@@ -649,6 +752,7 @@ router.get('/direct-image/:userId/:pptId/:slideIndex?', async (req, res, next) =
|
|
649 |
}
|
650 |
|
651 |
if (!pptData) {
|
|
|
652 |
// 返回404图片SVG
|
653 |
const notFoundSvg = `<svg width="800" height="450" xmlns="http://www.w3.org/2000/svg">
|
654 |
<rect width="100%" height="100%" fill="#f8f9fa"/>
|
@@ -658,12 +762,13 @@ router.get('/direct-image/:userId/:pptId/:slideIndex?', async (req, res, next) =
|
|
658 |
|
659 |
res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
|
660 |
res.setHeader('Cache-Control', 'no-cache');
|
661 |
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
662 |
return res.send(notFoundSvg);
|
663 |
}
|
664 |
|
665 |
const slideIdx = parseInt(slideIndex);
|
666 |
if (slideIdx >= pptData.slides.length || slideIdx < 0) {
|
|
|
667 |
// 返回404幻灯片SVG
|
668 |
const invalidSlideSvg = `<svg width="800" height="450" xmlns="http://www.w3.org/2000/svg">
|
669 |
<rect width="100%" height="100%" fill="#f8f9fa"/>
|
@@ -677,6 +782,8 @@ router.get('/direct-image/:userId/:pptId/:slideIndex?', async (req, res, next) =
|
|
677 |
return res.send(invalidSlideSvg);
|
678 |
}
|
679 |
|
|
|
|
|
680 |
// 计算图片尺寸
|
681 |
const defaultWidth = pptData.viewportSize || 1000;
|
682 |
const defaultHeight = Math.ceil(defaultWidth * (pptData.viewportRatio || 0.5625));
|
@@ -690,18 +797,21 @@ router.get('/direct-image/:userId/:pptId/:slideIndex?', async (req, res, next) =
|
|
690 |
height: finalHeight
|
691 |
});
|
692 |
|
|
|
|
|
693 |
// 设置正确的响应头,使其可以被其他网站作为图片引用
|
694 |
res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
|
695 |
res.setHeader('Cache-Control', 'public, max-age=3600'); // 缓存1小时
|
696 |
res.setHeader('Access-Control-Allow-Origin', '*'); // 允许跨域引用
|
697 |
res.setHeader('X-Generation-Time', '< 10ms');
|
698 |
res.setHeader('X-Content-Source', 'direct-svg-generation');
|
|
|
699 |
|
700 |
// 直接返回SVG图片内容
|
701 |
res.send(svgContent);
|
702 |
|
703 |
} catch (error) {
|
704 |
-
console.error('Direct image generation failed:', error);
|
705 |
|
706 |
// 返回错误图片SVG
|
707 |
const errorSvg = `<svg width="800" height="450" xmlns="http://www.w3.org/2000/svg">
|
|
|
352 |
}
|
353 |
});
|
354 |
|
355 |
+
// Image endpoint - 修改为直接返回图片,而不是截图工具页面
|
356 |
router.get('/image/:userId/:pptId/:slideIndex?', async (req, res, next) => {
|
357 |
try {
|
358 |
const { userId, pptId, slideIndex = 0 } = req.params;
|
359 |
+
const { format = 'svg', quality = 90, width: requestWidth, height: requestHeight } = req.query;
|
360 |
|
361 |
+
console.log(`🖼️ Image request (direct): userId=${userId}, pptId=${pptId}, slideIndex=${slideIndex}, format=${format}`);
|
362 |
|
363 |
// Get PPT data
|
364 |
const fileName = `${pptId}.json`;
|
|
|
369 |
const result = await githubService.getFile(userId, fileName, i);
|
370 |
if (result) {
|
371 |
pptData = result.content;
|
372 |
+
console.log(`✅ PPT data found in repository ${i}`);
|
373 |
break;
|
374 |
}
|
375 |
} catch (error) {
|
|
|
378 |
}
|
379 |
|
380 |
if (!pptData) {
|
381 |
+
console.log(`❌ PPT not found: ${pptId}`);
|
382 |
+
// 返回404图片SVG
|
383 |
+
const notFoundSvg = `<svg width="800" height="450" xmlns="http://www.w3.org/2000/svg">
|
384 |
+
<rect width="100%" height="100%" fill="#f8f9fa"/>
|
385 |
+
<text x="400" y="200" text-anchor="middle" font-family="Arial, sans-serif" font-size="24" fill="#6c757d">PPT Not Found</text>
|
386 |
+
<text x="400" y="250" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" fill="#6c757d">PPT ${pptId} does not exist</text>
|
387 |
+
</svg>`;
|
388 |
+
|
389 |
+
res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
|
390 |
+
res.setHeader('Cache-Control', 'no-cache');
|
391 |
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
392 |
+
return res.send(notFoundSvg);
|
393 |
}
|
394 |
|
395 |
const slideIdx = parseInt(slideIndex);
|
396 |
if (slideIdx >= pptData.slides.length || slideIdx < 0) {
|
397 |
+
console.log(`❌ Invalid slide index: ${slideIndex}`);
|
398 |
+
// 返回404幻灯片SVG
|
399 |
+
const invalidSlideSvg = `<svg width="800" height="450" xmlns="http://www.w3.org/2000/svg">
|
400 |
+
<rect width="100%" height="100%" fill="#f8f9fa"/>
|
401 |
+
<text x="400" y="200" text-anchor="middle" font-family="Arial, sans-serif" font-size="24" fill="#6c757d">Invalid Slide</text>
|
402 |
+
<text x="400" y="250" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" fill="#6c757d">Slide ${slideIndex} not found</text>
|
403 |
+
</svg>`;
|
404 |
+
|
405 |
+
res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
|
406 |
+
res.setHeader('Cache-Control', 'no-cache');
|
407 |
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
408 |
+
return res.send(invalidSlideSvg);
|
409 |
}
|
410 |
|
411 |
+
console.log(`✅ Generating SVG for slide ${slideIdx}`);
|
412 |
+
|
413 |
+
// 计算图片尺寸
|
414 |
+
const defaultWidth = pptData.viewportSize || 1000;
|
415 |
+
const defaultHeight = Math.ceil(defaultWidth * (pptData.viewportRatio || 0.5625));
|
416 |
+
const finalWidth = requestWidth ? parseInt(requestWidth) : defaultWidth;
|
417 |
+
const finalHeight = requestHeight ? parseInt(requestHeight) : defaultHeight;
|
418 |
+
|
419 |
+
// 生成PPT幻灯片的SVG图片
|
420 |
+
const slide = pptData.slides[slideIdx];
|
421 |
+
const svgContent = generateSlideSVG(slide, pptData, {
|
422 |
+
width: finalWidth,
|
423 |
+
height: finalHeight
|
424 |
});
|
425 |
|
426 |
+
console.log(`✅ SVG generated successfully, size: ${svgContent.length} characters`);
|
427 |
+
|
428 |
+
// 设置正确的响应头,使其可以被其他网站作为图片引用
|
429 |
+
res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
|
430 |
+
res.setHeader('Cache-Control', 'public, max-age=3600'); // 缓存1小时
|
431 |
+
res.setHeader('Access-Control-Allow-Origin', '*'); // 允许跨域引用
|
432 |
+
res.setHeader('X-Generation-Time', '< 10ms');
|
433 |
+
res.setHeader('X-Content-Source', 'direct-svg-generation');
|
434 |
+
res.setHeader('Content-Disposition', 'inline'); // 确保浏览器内联显示图片
|
435 |
+
|
436 |
+
// 直接返回SVG图片内容
|
437 |
+
res.send(svgContent);
|
438 |
|
439 |
} catch (error) {
|
440 |
+
console.error('❌ Image generation failed:', error);
|
441 |
+
|
442 |
+
// 返回错误图片SVG
|
443 |
+
const errorSvg = `<svg width="800" height="450" xmlns="http://www.w3.org/2000/svg">
|
444 |
+
<rect width="100%" height="100%" fill="#ffe6e6"/>
|
445 |
+
<text x="400" y="200" text-anchor="middle" font-family="Arial, sans-serif" font-size="24" fill="#cc0000">Generation Error</text>
|
446 |
+
<text x="400" y="250" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" fill="#cc0000">Failed to generate image: ${error.message}</text>
|
447 |
+
</svg>`;
|
448 |
+
|
449 |
+
res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
|
450 |
+
res.setHeader('Cache-Control', 'no-cache');
|
451 |
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
452 |
+
res.send(errorSvg);
|
453 |
}
|
454 |
});
|
455 |
|
|
|
555 |
}
|
556 |
});
|
557 |
|
558 |
+
// 新增:截图工具页面端点(原来的image端点功能)
|
559 |
+
router.get('/screenshot-tool/:userId/:pptId/:slideIndex?', async (req, res, next) => {
|
560 |
+
try {
|
561 |
+
const { userId, pptId, slideIndex = 0 } = req.params;
|
562 |
+
const { format = 'jpeg', quality = 90 } = req.query;
|
563 |
+
|
564 |
+
console.log(`Screenshot tool request: userId=${userId}, pptId=${pptId}, slideIndex=${slideIndex}`);
|
565 |
+
|
566 |
+
// Get PPT data
|
567 |
+
const fileName = `${pptId}.json`;
|
568 |
+
let pptData = null;
|
569 |
+
|
570 |
+
for (let i = 0; i < githubService.repositories.length; i++) {
|
571 |
+
try {
|
572 |
+
const result = await githubService.getFile(userId, fileName, i);
|
573 |
+
if (result) {
|
574 |
+
pptData = result.content;
|
575 |
+
console.log(`✅ PPT data found in repository ${i}`);
|
576 |
+
break;
|
577 |
+
}
|
578 |
+
} catch (error) {
|
579 |
+
continue;
|
580 |
+
}
|
581 |
+
}
|
582 |
+
|
583 |
+
if (!pptData) {
|
584 |
+
const errorPage = generateErrorPage('PPT Not Found', `PPT ${pptId} not found for user ${userId}`);
|
585 |
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
586 |
+
return res.status(404).send(errorPage);
|
587 |
+
}
|
588 |
+
|
589 |
+
const slideIdx = parseInt(slideIndex);
|
590 |
+
if (slideIdx >= pptData.slides.length || slideIdx < 0) {
|
591 |
+
const errorPage = generateErrorPage('Invalid Slide', `Slide ${slideIndex} not found in PPT`);
|
592 |
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
593 |
+
return res.status(404).send(errorPage);
|
594 |
+
}
|
595 |
+
|
596 |
+
// 使用共享模块生成导出页面
|
597 |
+
const htmlPage = generateExportPage(pptData, slideIdx, {
|
598 |
+
format,
|
599 |
+
quality: parseInt(quality),
|
600 |
+
autoDownload: false // 不自动下载,需要用户手动操作
|
601 |
+
});
|
602 |
+
|
603 |
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
604 |
+
res.setHeader('X-Screenshot-Strategy', 'frontend-screenshot-tool');
|
605 |
+
res.setHeader('X-Generation-Time', '< 50ms');
|
606 |
+
res.send(htmlPage);
|
607 |
+
|
608 |
+
} catch (error) {
|
609 |
+
next(error);
|
610 |
+
}
|
611 |
+
});
|
612 |
+
|
613 |
// 新增:PPT JSON导出端点
|
614 |
router.get('/export-json/:userId/:pptId', async (req, res, next) => {
|
615 |
try {
|
|
|
726 |
}
|
727 |
});
|
728 |
|
729 |
+
// 新增:直接返回图片的端点 - 必须放在其他路由之前避免被覆盖
|
730 |
router.get('/direct-image/:userId/:pptId/:slideIndex?', async (req, res, next) => {
|
731 |
try {
|
732 |
const { userId, pptId, slideIndex = 0 } = req.params;
|
733 |
const { format = 'svg', quality = 90, width: requestWidth, height: requestHeight } = req.query;
|
734 |
|
735 |
+
console.log(`🖼️ Direct image request: userId=${userId}, pptId=${pptId}, slideIndex=${slideIndex}, format=${format}`);
|
736 |
|
737 |
// Get PPT data
|
738 |
const fileName = `${pptId}.json`;
|
|
|
743 |
const result = await githubService.getFile(userId, fileName, i);
|
744 |
if (result) {
|
745 |
pptData = result.content;
|
746 |
+
console.log(`✅ PPT data found in repository ${i}`);
|
747 |
break;
|
748 |
}
|
749 |
} catch (error) {
|
|
|
752 |
}
|
753 |
|
754 |
if (!pptData) {
|
755 |
+
console.log(`❌ PPT not found: ${pptId}`);
|
756 |
// 返回404图片SVG
|
757 |
const notFoundSvg = `<svg width="800" height="450" xmlns="http://www.w3.org/2000/svg">
|
758 |
<rect width="100%" height="100%" fill="#f8f9fa"/>
|
|
|
762 |
|
763 |
res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
|
764 |
res.setHeader('Cache-Control', 'no-cache');
|
765 |
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
766 |
return res.send(notFoundSvg);
|
767 |
}
|
768 |
|
769 |
const slideIdx = parseInt(slideIndex);
|
770 |
if (slideIdx >= pptData.slides.length || slideIdx < 0) {
|
771 |
+
console.log(`❌ Invalid slide index: ${slideIndex}`);
|
772 |
// 返回404幻灯片SVG
|
773 |
const invalidSlideSvg = `<svg width="800" height="450" xmlns="http://www.w3.org/2000/svg">
|
774 |
<rect width="100%" height="100%" fill="#f8f9fa"/>
|
|
|
782 |
return res.send(invalidSlideSvg);
|
783 |
}
|
784 |
|
785 |
+
console.log(`✅ Generating SVG for slide ${slideIdx}`);
|
786 |
+
|
787 |
// 计算图片尺寸
|
788 |
const defaultWidth = pptData.viewportSize || 1000;
|
789 |
const defaultHeight = Math.ceil(defaultWidth * (pptData.viewportRatio || 0.5625));
|
|
|
797 |
height: finalHeight
|
798 |
});
|
799 |
|
800 |
+
console.log(`✅ SVG generated successfully, size: ${svgContent.length} characters`);
|
801 |
+
|
802 |
// 设置正确的响应头,使其可以被其他网站作为图片引用
|
803 |
res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
|
804 |
res.setHeader('Cache-Control', 'public, max-age=3600'); // 缓存1小时
|
805 |
res.setHeader('Access-Control-Allow-Origin', '*'); // 允许跨域引用
|
806 |
res.setHeader('X-Generation-Time', '< 10ms');
|
807 |
res.setHeader('X-Content-Source', 'direct-svg-generation');
|
808 |
+
res.setHeader('Content-Disposition', 'inline'); // 确保浏览器内联显示图片
|
809 |
|
810 |
// 直接返回SVG图片内容
|
811 |
res.send(svgContent);
|
812 |
|
813 |
} catch (error) {
|
814 |
+
console.error('❌ Direct image generation failed:', error);
|
815 |
|
816 |
// 返回错误图片SVG
|
817 |
const errorSvg = `<svg width="800" height="450" xmlns="http://www.w3.org/2000/svg">
|