web_ppt_7.7 / backend /src /routes /persistentImages.js
CatPtain's picture
Upload 85 files
28e1dba verified
import express from 'express';
import { authenticateToken } from '../middleware/auth.js';
import persistentImageLinkService from '../services/persistentImageLinkService.js';
import githubService from '../services/githubService.js';
const router = express.Router();
// 添加路由级别的日志中间件
router.use((req, res, next) => {
console.log(`Persistent Images Router - ${req.method} ${req.path}`);
next();
});
/**
* 获取或创建PPT页面的持久化链接
* POST /api/persistent-images/create
*/
router.post('/create', authenticateToken, async (req, res, next) => {
try {
const userId = req.user.userId;
const {
pptId,
pageIndex,
slideId,
slideData,
options = {}
} = req.body;
// 验证必需参数 - 需要slideId或pageIndex其中之一
if (!pptId || (slideId === undefined && pageIndex === undefined)) {
return res.status(400).json({
error: 'Missing required parameters: pptId and (slideId or pageIndex)'
});
}
// 优先使用slideId,如果不存在则使用pageIndex
const uniqueId = slideId || pageIndex;
console.log(`🔗 Creating/getting persistent link for PPT ${pptId}, uniqueId ${uniqueId}`);
// 获取或创建持久化链接
const result = await persistentImageLinkService.getOrCreatePersistentLink(
userId,
pptId,
uniqueId,
slideData,
options
);
res.json({
success: true,
message: 'Persistent link created/retrieved successfully',
...result
});
} catch (error) {
console.error('❌ Failed to create persistent link:', error);
next(error);
}
});
/**
* 批量创建PPT所有页面的持久化链接
* POST /api/persistent-images/create-all
*/
router.post('/create-all', authenticateToken, async (req, res, next) => {
try {
const userId = req.user.userId;
const {
pptId,
slides,
options = {}
} = req.body;
// 验证必需参数
if (!pptId || !slides || !Array.isArray(slides)) {
return res.status(400).json({
error: 'Missing required parameters: pptId, slides (array)'
});
}
console.log(`🔗 Creating persistent links for ${slides.length} pages of PPT ${pptId}`);
// 批量创建持久化链接
const results = await persistentImageLinkService.updateAllPersistentLinks(
userId,
pptId,
slides,
options
);
// 统计结果
const successCount = results.filter(r => r.success).length;
const failureCount = results.length - successCount;
console.log(`✅ Created ${successCount} persistent links, ${failureCount} failed`);
// 转换结果格式以匹配前端期望的数据结构
const links = results.filter(r => r.success).map(result => ({
linkId: result.linkId,
url: result.url,
publicUrl: result.publicUrl,
pageIndex: result.pageIndex,
slideId: result.slideId,
lastUpdated: result.lastUpdated
}));
res.json({
success: true,
message: `Created ${successCount} persistent links successfully`,
totalPages: slides.length,
successCount,
failureCount,
links,
results
});
} catch (error) {
console.error('❌ Failed to create persistent links:', error);
next(error);
}
});
/**
* 更新PPT页面图片
* PUT /api/persistent-images/:linkId
*/
router.put('/:linkId', authenticateToken, async (req, res, next) => {
try {
const { linkId } = req.params;
const {
slideData,
options = {}
} = req.body;
// 验证必需参数
if (!slideData) {
return res.status(400).json({
error: 'Missing required parameter: slideData'
});
}
console.log(`🔄 Updating image for persistent link: ${linkId}`);
// 更新图片
await persistentImageLinkService.updatePageImage(linkId, slideData, options);
res.json({
success: true,
message: 'Image updated successfully',
linkId,
url: `/api/persistent-images/${linkId}`
});
} catch (error) {
console.error(`❌ Failed to update persistent link ${req.params.linkId}:`, error);
next(error);
}
});
/**
* 更新持久化链接的图片内容(通过图片数据)
* POST /api/persistent-images/update-image
*/
router.post('/update-image', authenticateToken, async (req, res, next) => {
try {
const {
linkId,
imageData,
format = 'jpeg'
} = req.body;
// 验证必需参数
if (!linkId || !imageData) {
return res.status(400).json({
error: 'Missing required parameters: linkId, imageData'
});
}
console.log(`🔄 Updating image content for persistent link: ${linkId}`);
// 将base64图片数据转换为Buffer
let imageBuffer;
try {
// 移除data:image/jpeg;base64,前缀(如果存在)
const base64Data = imageData.replace(/^data:image\/[a-z]+;base64,/, '');
imageBuffer = Buffer.from(base64Data, 'base64');
} catch (error) {
return res.status(400).json({
error: 'Invalid image data format'
});
}
// 直接更新持久化链接的图片内容
const result = await persistentImageLinkService.updateImageContent(linkId, imageBuffer, format);
if (!result.success) {
return res.status(404).json({
error: 'Persistent link not found',
linkId
});
}
res.json({
success: true,
message: 'Image content updated successfully',
linkId,
url: `/api/persistent-images/${linkId}`,
size: imageBuffer.length,
format
});
} catch (error) {
console.error(`❌ Failed to update image content for link ${req.body.linkId}:`, error);
next(error);
}
});
/**
* 获取用户的所有持久化链接
* GET /api/persistent-images/user/list
*/
router.get('/user/list', authenticateToken, async (req, res, next) => {
try {
const userId = req.user.userId;
const { pptId } = req.query;
console.log(`📋 Getting persistent links for user ${userId}${pptId ? `, PPT ${pptId}` : ''}`);
const userLinks = persistentImageLinkService.getUserPersistentLinks(userId, pptId);
res.json({
success: true,
userId,
pptId: pptId || null,
linkCount: userLinks.length,
links: userLinks
});
} catch (error) {
console.error(`❌ Failed to get user persistent links:`, error);
next(error);
}
});
/**
* 获取服务统计信息
* GET /api/persistent-images/stats
*/
router.get('/stats', authenticateToken, async (req, res, next) => {
try {
console.log('📊 Getting persistent image service stats');
const stats = persistentImageLinkService.getStats();
res.json({
success: true,
stats
});
} catch (error) {
console.error('❌ Failed to get persistent image stats:', error);
next(error);
}
});
/**
* 获取持久化链接的图片 (通过 pptId 和 slideId)
* GET /api/persistent-images/:pptId/:slideId
*/
router.get('/:pptId/:slideId', async (req, res, next) => {
try {
const { pptId, slideId } = req.params;
const { download } = req.query;
// 验证pptId格式 (应该是UUID格式)
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
if (!uuidRegex.test(pptId)) {
return next(); // 不是UUID格式,继续到下一个路由
}
// 验证slideId格式 (不应该包含文件扩展名)
if (slideId.includes('.')) {
return next(); // 包含文件扩展名,可能是静态文件请求
}
console.log(`🖼️ Getting persistent image by pptId: ${pptId}, slideId: ${slideId}`);
// 通过 pptId 和 slideId 查找对应的链接
const linkInfo = await persistentImageLinkService.findLinkByPptAndSlide(pptId, slideId);
if (!linkInfo) {
return res.status(404).json({
error: 'Persistent image not found for the specified PPT and slide',
pptId,
slideId
});
}
// 获取图片
const imageResult = await persistentImageLinkService.getPersistentImage(linkInfo.linkId);
if (!imageResult.success) {
return res.status(404).json({
error: 'Persistent image not found',
pptId,
slideId,
linkId: linkInfo.linkId
});
}
const { data: imageBuffer, metadata } = imageResult;
// 设置响应头
const contentType = metadata.format === 'jpg' ? 'image/jpeg' : `image/${metadata.format}`;
res.set({
'Content-Type': contentType,
'Content-Length': metadata.size,
'Cache-Control': 'public, max-age=3600', // 缓存1小时
'ETag': `"${linkInfo.linkId}"`,
'Last-Modified': new Date(metadata.lastUpdated).toUTCString()
});
// 如果是下载请求,设置下载头
if (download) {
const fileName = `persistent-slide-${pptId}-${slideId}.${metadata.format}`;
res.set('Content-Disposition', `attachment; filename="${fileName}"`);
}
// 检查条件请求
const ifNoneMatch = req.get('If-None-Match');
if (ifNoneMatch === `"${linkInfo.linkId}"`) {
return res.status(304).end();
}
res.send(imageBuffer);
} catch (error) {
console.error(`❌ Failed to get persistent image ${req.params.pptId}/${req.params.slideId}:`, error);
if (error.message.includes('not found')) {
return res.status(404).json({
error: 'Persistent image not found',
pptId: req.params.pptId,
slideId: req.params.slideId
});
}
next(error);
}
});
/**
* 获取持久化链接的图片 (通过 pptId 或 linkId)
* GET /api/persistent-images/:id
*/
router.get('/:id', async (req, res, next) => {
try {
const { id } = req.params;
const { download } = req.query;
console.log(`🖼️ Getting persistent image: ${id}`);
// 检查是否是PPT ID格式(通常比linkId短,且可能包含特殊字符)
// 如果是PPT ID,返回该PPT的第一页图片
let imageResult;
let isLinkId = false;
try {
// 首先尝试作为linkId获取图片
imageResult = await persistentImageLinkService.getPersistentImage(id);
isLinkId = true;
} catch (error) {
// 如果作为linkId失败,尝试作为PPT ID获取第一页图片
console.log(`⚠️ Not found as linkId, trying as PPT ID: ${id}`);
// 查找该PPT的第一页链接
const links = [];
for (const [linkId, linkInfo] of persistentImageLinkService.persistentLinks) {
if (linkInfo.pptId === id && linkInfo.hasImage) {
links.push({ linkId, pageIndex: linkInfo.pageIndex || 0 });
}
}
if (links.length === 0) {
return res.status(404).json({
error: 'No persistent images found for PPT',
pptId: id
});
}
// 按页面索引排序,取第一页
links.sort((a, b) => a.pageIndex - b.pageIndex);
const firstPageLinkId = links[0].linkId;
imageResult = await persistentImageLinkService.getPersistentImage(firstPageLinkId);
}
if (!imageResult.success) {
return res.status(404).json({
error: 'Persistent image not found',
id
});
}
const { data: imageBuffer, metadata } = imageResult;
// 设置响应头
const contentType = metadata.format === 'jpg' ? 'image/jpeg' : `image/${metadata.format}`;
res.set({
'Content-Type': contentType,
'Content-Length': metadata.size,
'Cache-Control': 'public, max-age=3600', // 缓存1小时
'ETag': `"${id}"`,
'Last-Modified': new Date(metadata.lastUpdated).toUTCString()
});
// 如果是下载请求,设置下载头
if (download) {
const fileName = isLinkId ? `persistent-slide-${id}.${metadata.format}` : `persistent-ppt-${id}.${metadata.format}`;
res.set('Content-Disposition', `attachment; filename="${fileName}"`);
}
// 检查条件请求
const ifNoneMatch = req.get('If-None-Match');
if (ifNoneMatch === `"${id}"`) {
return res.status(304).end();
}
res.send(imageBuffer);
} catch (error) {
console.error(`❌ Failed to get persistent image ${req.params.id}:`, error);
if (error.message.includes('not found')) {
return res.status(404).json({
error: 'Persistent image not found',
id: req.params.id
});
}
next(error);
}
});
/**
* 删除持久化链接
* DELETE /api/persistent-images/:linkId
*/
router.delete('/:linkId', authenticateToken, async (req, res, next) => {
try {
const { linkId } = req.params;
console.log(`🗑️ Deleting persistent link: ${linkId}`);
// 删除持久化链接
const deleteResult = await persistentImageLinkService.deletePersistentLink(linkId);
if (deleteResult) {
res.json({
success: true,
message: 'Persistent link deleted successfully',
linkId
});
} else {
res.status(404).json({
error: 'Persistent link not found',
linkId
});
}
} catch (error) {
console.error(`❌ Failed to delete persistent link ${req.params.linkId}:`, error);
next(error);
}
});
/**
* 从PPT数据同步创建持久化链接
* POST /api/persistent-images/sync-from-ppt
*/
router.post('/sync-from-ppt', authenticateToken, async (req, res, next) => {
try {
const userId = req.user.userId;
const { pptId } = req.body;
// 验证必需参数
if (!pptId) {
return res.status(400).json({
error: 'Missing required parameter: pptId'
});
}
console.log(`🔄 Syncing persistent links from PPT data: ${pptId}`);
// 从GitHub服务获取PPT数据
const pptData = await githubService.getPPT(userId, pptId);
if (!pptData || !pptData.slides) {
return res.status(404).json({
error: 'PPT not found or has no slides',
pptId
});
}
// 创建持久化链接(不生成图片,只创建链接)
const results = [];
for (let pageIndex = 0; pageIndex < pptData.slides.length; pageIndex++) {
try {
const result = await persistentImageLinkService.getOrCreatePersistentLink(
userId,
pptId,
pageIndex,
null, // 不提供slideData,只创建链接
{}
);
results.push({
pageIndex,
success: true,
...result
});
} catch (error) {
console.error(`❌ Failed to sync persistent link for page ${pageIndex}:`, error);
results.push({
pageIndex,
success: false,
error: error.message
});
}
}
const successCount = results.filter(r => r.success).length;
const failureCount = results.length - successCount;
console.log(`✅ Synced ${successCount} persistent links, ${failureCount} failed`);
res.json({
success: true,
message: `Synced ${successCount} persistent links from PPT data`,
pptId,
totalPages: pptData.slides.length,
successCount,
failureCount,
results
});
} catch (error) {
console.error('❌ Failed to sync persistent links from PPT:', error);
next(error);
}
});
export default router;