Upload public.js
Browse files- backend/src/routes/public.js +53 -75
backend/src/routes/public.js
CHANGED
@@ -624,13 +624,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 = '
|
632 |
|
633 |
-
console.log(`Direct image request: userId=${userId}, pptId=${pptId}, slideIndex=${slideIndex}`);
|
634 |
|
635 |
// Get PPT data
|
636 |
const fileName = `${pptId}.json`;
|
@@ -649,92 +649,70 @@ router.get('/direct-image/:userId/:pptId/:slideIndex?', async (req, res, next) =
|
|
649 |
}
|
650 |
|
651 |
if (!pptData) {
|
652 |
-
// 返回404
|
653 |
-
const notFoundSvg =
|
654 |
-
<
|
655 |
-
|
656 |
-
|
657 |
-
|
658 |
-
|
659 |
-
|
660 |
-
const svgBuffer = Buffer.from(notFoundSvg);
|
661 |
-
res.setHeader('Content-Type', 'image/svg+xml');
|
662 |
res.setHeader('Cache-Control', 'no-cache');
|
663 |
-
|
|
|
664 |
}
|
665 |
|
666 |
const slideIdx = parseInt(slideIndex);
|
667 |
if (slideIdx >= pptData.slides.length || slideIdx < 0) {
|
668 |
-
// 返回404
|
669 |
-
const invalidSlideSvg =
|
670 |
-
<
|
671 |
-
|
672 |
-
|
673 |
-
|
674 |
-
|
675 |
-
|
676 |
-
const svgBuffer = Buffer.from(invalidSlideSvg);
|
677 |
-
res.setHeader('Content-Type', 'image/svg+xml');
|
678 |
res.setHeader('Cache-Control', 'no-cache');
|
679 |
-
|
|
|
680 |
}
|
681 |
|
682 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
683 |
const slide = pptData.slides[slideIdx];
|
684 |
-
const
|
685 |
-
|
686 |
-
|
687 |
-
|
688 |
-
|
689 |
-
|
690 |
-
|
691 |
-
|
692 |
-
|
693 |
-
res.setHeader('X-Generation-Time', '< 10ms');
|
694 |
-
return res.send(svgContent);
|
695 |
-
}
|
696 |
-
|
697 |
-
// 对于其他格式,返回占位图片
|
698 |
-
const placeholderSvg = `
|
699 |
-
<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
|
700 |
-
<rect width="100%" height="100%" fill="${slide.background?.color || '#ffffff'}"/>
|
701 |
-
<g>
|
702 |
-
${slide.elements.map(el => {
|
703 |
-
if (el.type === 'text') {
|
704 |
-
return `<text x="${el.left + 10}" y="${el.top + (el.fontSize || 16)}"
|
705 |
-
font-family="Arial" font-size="${el.fontSize || 16}"
|
706 |
-
fill="${el.defaultColor || el.color || '#000'}">${el.content || ''}</text>`;
|
707 |
-
}
|
708 |
-
if (el.type === 'shape') {
|
709 |
-
return `<rect x="${el.left}" y="${el.top}" width="${el.width}" height="${el.height}"
|
710 |
-
fill="${el.fill || '#cccccc'}" stroke="${el.outline?.color || 'none'}"/>`;
|
711 |
-
}
|
712 |
-
return '';
|
713 |
-
}).join('')}
|
714 |
-
</g>
|
715 |
-
<text x="${width/2}" y="${height-20}" text-anchor="middle" font-family="Arial" font-size="12" fill="#999">
|
716 |
-
${pptData.title} - Slide ${slideIdx + 1}
|
717 |
-
</text>
|
718 |
-
</svg>
|
719 |
-
`;
|
720 |
-
|
721 |
-
res.setHeader('Content-Type', 'image/svg+xml');
|
722 |
-
res.setHeader('Cache-Control', 'public, max-age=3600');
|
723 |
res.setHeader('X-Generation-Time', '< 10ms');
|
724 |
-
res.
|
|
|
|
|
|
|
725 |
|
726 |
} catch (error) {
|
727 |
console.error('Direct image generation failed:', error);
|
728 |
|
729 |
-
// 返回错误图片
|
730 |
-
const errorSvg =
|
731 |
-
<
|
732 |
-
|
733 |
-
|
734 |
-
|
735 |
-
|
736 |
-
|
737 |
-
res.setHeader('
|
|
|
738 |
res.send(errorSvg);
|
739 |
}
|
740 |
});
|
|
|
624 |
}
|
625 |
});
|
626 |
|
627 |
+
// 新增:直接返回图片的端点 - 返回纯SVG图片,可被其他网站直接引用
|
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(`Direct image request: userId=${userId}, pptId=${pptId}, slideIndex=${slideIndex}, format=${format}`);
|
634 |
|
635 |
// Get PPT data
|
636 |
const fileName = `${pptId}.json`;
|
|
|
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"/>
|
655 |
+
<text x="400" y="200" text-anchor="middle" font-family="Arial, sans-serif" font-size="24" fill="#6c757d">PPT Not Found</text>
|
656 |
+
<text x="400" y="250" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" fill="#6c757d">PPT ${pptId} does not exist</text>
|
657 |
+
</svg>`;
|
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"/>
|
670 |
+
<text x="400" y="200" text-anchor="middle" font-family="Arial, sans-serif" font-size="24" fill="#6c757d">Invalid Slide</text>
|
671 |
+
<text x="400" y="250" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" fill="#6c757d">Slide ${slideIndex} not found</text>
|
672 |
+
</svg>`;
|
673 |
+
|
674 |
+
res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
|
|
|
|
|
675 |
res.setHeader('Cache-Control', 'no-cache');
|
676 |
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
677 |
+
return res.send(invalidSlideSvg);
|
678 |
}
|
679 |
|
680 |
+
// 计算图片尺寸
|
681 |
+
const defaultWidth = pptData.viewportSize || 1000;
|
682 |
+
const defaultHeight = Math.ceil(defaultWidth * (pptData.viewportRatio || 0.5625));
|
683 |
+
const finalWidth = requestWidth ? parseInt(requestWidth) : defaultWidth;
|
684 |
+
const finalHeight = requestHeight ? parseInt(requestHeight) : defaultHeight;
|
685 |
+
|
686 |
+
// 生成PPT幻灯片的SVG图片
|
687 |
const slide = pptData.slides[slideIdx];
|
688 |
+
const svgContent = generateSlideSVG(slide, pptData, {
|
689 |
+
width: finalWidth,
|
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">
|
708 |
+
<rect width="100%" height="100%" fill="#ffe6e6"/>
|
709 |
+
<text x="400" y="200" text-anchor="middle" font-family="Arial, sans-serif" font-size="24" fill="#cc0000">Generation Error</text>
|
710 |
+
<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>
|
711 |
+
</svg>`;
|
712 |
+
|
713 |
+
res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
|
714 |
+
res.setHeader('Cache-Control', 'no-cache');
|
715 |
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
716 |
res.send(errorSvg);
|
717 |
}
|
718 |
});
|