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;