CatPtain's picture
Upload 85 files
28e1dba verified
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;