Spaces:
Running
Running
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; |