import express from 'express'; import { authenticateToken } from '../middleware/auth.js'; import huggingfaceStorageService from '../services/huggingfaceStorageService.js'; import backupSchedulerService from '../services/backupSchedulerService.js'; import githubService from '../services/githubService.js'; const router = express.Router(); // 添加路由级别的日志中间件 router.use((req, res, next) => { console.log(`Images Router - ${req.method} ${req.path}`); next(); }); /** * 生成单页PPT图片 * POST /api/images/generate */ router.post('/generate', authenticateToken, async (req, res, next) => { try { const userId = req.user.userId; const { pptId, pageIndex, slideData, options = {} } = req.body; // 验证必需参数 if (!pptId || pageIndex === undefined || !slideData) { return res.status(400).json({ error: 'Missing required parameters: pptId, pageIndex, slideData' }); } console.log(`🖼️ Generating image for PPT ${pptId}, page ${pageIndex}`); // 设置默认选项 const generateOptions = { format: 'png', quality: 0.9, width: 1920, height: 1080, viewportSize: 1000, viewportRatio: 0.5625, ...options }; // 图片生成服务已被移除 return res.status(503).json({ success: false, error: 'Image generation service unavailable', message: 'Browser dependencies have been removed' }); // 存储图片 const storeResult = await huggingfaceStorageService.storeImage( userId, pptId, pageIndex, imageBuffer, { format: generateOptions.format, quality: generateOptions.quality } ); if (storeResult.success) { // 调度备份 backupSchedulerService.scheduleUserBackup(userId); res.json({ success: true, imageId: storeResult.imageId, versionedImageId: storeResult.versionedImageId, url: storeResult.url, versionedUrl: storeResult.versionedUrl, version: storeResult.version, size: storeResult.size, format: generateOptions.format }); } else { throw new Error('Failed to store image'); } } catch (error) { console.error('❌ Failed to generate image:', error); next(error); } }); /** * 批量生成PPT所有页面图片 * POST /api/images/generate-all */ router.post('/generate-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(`🖼️ Generating ${slides.length} images for PPT ${pptId}`); // 设置默认选项 const generateOptions = { format: 'png', quality: 0.9, width: 1920, height: 1080, viewportSize: 1000, viewportRatio: 0.5625, ...options }; // 批量生成图片 const imageResults = await imageGenerationService.generateAllSlideImages( slides, generateOptions ); // 批量存储图片 const storeResults = await huggingfaceStorageService.storeAllImages( userId, pptId, imageResults, { format: generateOptions.format, quality: generateOptions.quality } ); // 统计结果 const successCount = storeResults.filter(r => r.success).length; const failureCount = storeResults.length - successCount; console.log(`✅ Generated ${successCount} images, ${failureCount} failed`); // 调度备份 if (successCount > 0) { backupSchedulerService.scheduleUserBackup(userId); } res.json({ success: true, totalSlides: slides.length, successCount, failureCount, results: storeResults.map(result => ({ pageIndex: result.pageIndex, success: result.success, imageId: result.imageId, url: result.url, error: result.error })) }); } catch (error) { console.error('❌ Failed to generate all images:', error); next(error); } }); /** * 获取图片文件 * GET /api/images/:imageId */ router.get('/:imageId', async (req, res, next) => { try { const { imageId } = req.params; const { download = false } = req.query; console.log(`📷 Requesting image: ${imageId}`); // 获取图片 const imageResult = await huggingfaceStorageService.getImage(imageId); if (!imageResult.success) { return res.status(404).json({ error: 'Image not found', imageId }); } const { data: imageBuffer, metadata } = imageResult; // 设置响应头 const mimeType = metadata.format === 'png' ? 'image/png' : 'image/jpeg'; res.set({ 'Content-Type': mimeType, 'Content-Length': imageBuffer.length, 'Cache-Control': 'public, max-age=31536000', // 1年缓存 'ETag': `"${imageId}"`, 'Last-Modified': new Date(metadata.updatedAt).toUTCString() }); // 如果是下载请求,设置下载头 if (download) { const fileName = `slide-${metadata.imageId}.${metadata.format}`; res.set('Content-Disposition', `attachment; filename="${fileName}"`); } // 检查条件请求 const ifNoneMatch = req.get('If-None-Match'); if (ifNoneMatch === `"${imageId}"`) { return res.status(304).end(); } res.send(imageBuffer); } catch (error) { console.error(`❌ Failed to get image ${req.params.imageId}:`, error); next(error); } }); /** * 删除图片 * DELETE /api/images/:imageId */ router.delete('/:imageId', authenticateToken, async (req, res, next) => { try { const userId = req.user.userId; const { imageId } = req.params; console.log(`🗑️ Deleting image: ${imageId}`); // 验证图片所有权(通过获取图片信息) try { const imageResult = await huggingfaceStorageService.getImage(imageId); if (!imageResult.success) { return res.status(404).json({ error: 'Image not found', imageId }); } } catch (error) { return res.status(404).json({ error: 'Image not found', imageId }); } // 删除图片 const deleteResult = await huggingfaceStorageService.deleteImage(imageId); if (deleteResult) { // 调度备份 backupSchedulerService.scheduleUserBackup(userId); res.json({ success: true, message: 'Image deleted successfully', imageId }); } else { res.status(500).json({ error: 'Failed to delete image', imageId }); } } catch (error) { console.error(`❌ Failed to delete image ${req.params.imageId}:`, error); next(error); } }); /** * 获取用户的所有图片 * GET /api/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 images for user ${userId}${pptId ? `, PPT ${pptId}` : ''}`); const userImages = await huggingfaceStorageService.getUserImages(userId, pptId); res.json({ success: true, userId, pptId: pptId || null, imageCount: userImages.length, images: userImages }); } catch (error) { console.error(`❌ Failed to get user images:`, error); next(error); } }); /** * 重新生成PPT页面图片 * PUT /api/images/regenerate */ router.put('/regenerate', authenticateToken, async (req, res, next) => { try { const userId = req.user.userId; const { pptId, pageIndex, slideData, options = {} } = req.body; // 验证必需参数 if (!pptId || pageIndex === undefined || !slideData) { return res.status(400).json({ error: 'Missing required parameters: pptId, pageIndex, slideData' }); } console.log(`🔄 Regenerating image for PPT ${pptId}, page ${pageIndex}`); // 设置默认选项 const generateOptions = { format: 'png', quality: 0.9, width: 1920, height: 1080, viewportSize: 1000, viewportRatio: 0.5625, ...options }; // 生成新图片 const imageBuffer = await imageGenerationService.generateSlideImage( slideData, generateOptions ); // 存储图片(会自动覆盖旧图片,但保持相同的链接) const storeResult = await huggingfaceStorageService.storeImage( userId, pptId, pageIndex, imageBuffer, { format: generateOptions.format, quality: generateOptions.quality, updateExisting: true } ); if (storeResult.success) { // 调度备份 backupSchedulerService.scheduleUserBackup(userId); res.json({ success: true, message: 'Image regenerated successfully', imageId: storeResult.imageId, url: storeResult.url, version: storeResult.version, size: storeResult.size }); } else { throw new Error('Failed to store regenerated image'); } } catch (error) { console.error('❌ Failed to regenerate image:', error); next(error); } }); /** * 获取存储统计信息 * GET /api/images/stats */ router.get('/stats', authenticateToken, async (req, res, next) => { try { const userId = req.user.userId; console.log(`📊 Getting storage stats for user ${userId}`); const storageStats = await huggingfaceStorageService.getStorageStats(); const backupStatus = backupSchedulerService.getBackupStatus(); // 过滤用户特定的统计信息 const userStats = storageStats.userStats[userId] || { imageCount: 0, totalSize: 0, pptCount: 0 }; res.json({ success: true, userId, userStats: { ...userStats, totalSizeMB: Math.round(userStats.totalSize / 1024 / 1024 * 100) / 100 }, systemStats: { totalImages: storageStats.totalImages, totalSizeMB: storageStats.totalSizeMB, userCount: storageStats.userCount }, backupStatus: { initialized: backupStatus.initialized, scheduledBackups: backupStatus.scheduledBackups, backupsInProgress: backupStatus.backupsInProgress } }); } catch (error) { console.error('❌ Failed to get storage stats:', error); next(error); } }); /** * 手动触发备份 * POST /api/images/backup */ router.post('/backup', authenticateToken, async (req, res, next) => { try { const userId = req.user.userId; console.log(`💾 Manual backup triggered for user ${userId}`); // 立即执行备份 const backupResult = await backupSchedulerService.backupUserData(userId); if (backupResult) { res.json({ success: true, message: 'Backup completed successfully', userId, timestamp: new Date().toISOString() }); } else { res.status(500).json({ error: 'Backup failed', userId }); } } catch (error) { console.error('❌ Manual backup failed:', error); next(error); } }); export default router;