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 |
});
|