import express from 'express'; import cors from 'cors'; import helmet from 'helmet'; import rateLimit from 'express-rate-limit'; import dotenv from 'dotenv'; import path from 'path'; import { fileURLToPath } from 'url'; import axios from 'axios'; import fs from 'fs/promises'; // 添加ES模块fs导入 const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // 首先加载环境变量 - 必须在导入服务之前 dotenv.config({ path: path.join(__dirname, '../../.env') }); // 验证环境变量是否正确加载 console.log('=== Environment Variables Check ==='); console.log('GITHUB_TOKEN configured:', !!process.env.GITHUB_TOKEN); console.log('GITHUB_TOKEN length:', process.env.GITHUB_TOKEN ? process.env.GITHUB_TOKEN.length : 0); console.log('GITHUB_REPOS configured:', !!process.env.GITHUB_REPOS); console.log('GITHUB_REPOS value:', process.env.GITHUB_REPOS); // 现在导入需要环境变量的模块 import authRoutes from './routes/auth.js'; import pptRoutes from './routes/ppt.js'; import publicRoutes from './routes/public.js'; import imagesRoutes from './routes/images.js'; import persistentImagesRoutes from './routes/persistentImages.js'; import persistentLinksRoutes from './routes/persistentLinks.js'; import exportRoutes from './routes/export.js'; import { authenticateToken } from './middleware/auth.js'; import { errorHandler } from './middleware/errorHandler.js'; // 导入新的服务 import huggingfaceStorageService from './services/huggingfaceStorageService.js'; import backupSchedulerService from './services/backupSchedulerService.js'; import persistentImageLinkService from './services/persistentImageLinkService.js'; import autoBackupService from './services/autoBackupService.js'; import githubService from './services/githubService.js'; const app = express(); const PORT = process.env.PORT || 7861; // 修改为7861端口 // 设置trust proxy用于Huggingface Space app.set('trust proxy', true); // 安全中间件 app.use(helmet({ contentSecurityPolicy: false, // 为了兼容前端静态文件 })); // 修复限流配置 - 针对Huggingface Space生产环境 const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15分钟 max: 100, // 每个IP每15分钟最多100个请求 message: 'Too many requests from this IP, please try again later.', trustProxy: true, // 与Express trust proxy设置保持一致 validate: { trustProxy: false // 禁用trust proxy验证以避免生产环境警告 }, standardHeaders: true, legacyHeaders: false }); // 应用限流中间件 app.use('/api', limiter); // CORS配置 app.use(cors({ origin: process.env.FRONTEND_URL || '*', credentials: true })); app.use(express.json({ limit: '50mb' })); app.use(express.urlencoded({ extended: true, limit: '50mb' })); // 提供前端静态文件 - 修复路径配置,避免与API路由冲突 const frontendDistPath = path.join(__dirname, '../../frontend/dist'); console.log('Frontend dist path:', frontendDistPath); // API路由 - 必须在静态文件服务之前注册 console.log('Registering API routes...'); // 认证相关路由(不需要认证) app.use('/api/auth', authRoutes); // 公共访问路由(不需要认证) app.use('/api/public', publicRoutes); // 添加测试路由来验证 PPT 路由是否工作(不需要认证) app.get('/api/ppt/test', (req, res) => { res.json({ message: 'PPT routes are working', timestamp: new Date().toISOString() }); }); // PPT管理路由(需要认证) app.use('/api/ppt', (req, res, next) => { console.log(`PPT route accessed: ${req.method} ${req.path}`); next(); }, authenticateToken, pptRoutes); // 图片管理路由(部分需要认证) app.use('/api/images', imagesRoutes); // 持久化图片链接路由 app.use('/api/persistent-images', persistentImagesRoutes); // 持久化链接管理路由(需要认证) app.use('/api/persistent-links', persistentLinksRoutes); // 添加 /api/persistent/ 路由来处理前端生成的永久链接格式 app.get('/api/persistent/:pptId/:slideId', async (req, res) => { try { const { pptId, slideId } = req.params; console.log(`🔗 Accessing persistent link: pptId=${pptId}, slideId=${slideId}`); // 通过 pptId 和 slideId 查找对应的 linkId const linkInfo = await persistentImageLinkService.findLinkByPptAndSlide(pptId, slideId); if (!linkInfo) { console.log(`❌ No persistent link found for pptId=${pptId}, slideId=${slideId}`); return res.status(404).json({ error: 'Persistent link not found' }); } // 检查图片是否存在,如果不存在则生成占位图片 let imageResult; try { imageResult = await persistentImageLinkService.getPersistentImage(linkInfo.linkId); } catch (error) { if (error.message.includes('Image not available')) { console.log(`⚠️ Image not available for linkId=${linkInfo.linkId}, generating placeholder`); // 生成占位图片 const placeholderBuffer = await persistentImageLinkService.generatePlaceholderImage({ width: 1920, height: 1080, format: 'jpeg' }); // 保存占位图片到文件系统 const imagePath = path.join(persistentImageLinkService.linksDir, `${linkInfo.linkId}.jpeg`); await fs.writeFile(imagePath, placeholderBuffer); // 更新链接信息 linkInfo.hasImage = true; linkInfo.imagePath = imagePath; linkInfo.format = 'jpeg'; linkInfo.imageSize = placeholderBuffer.length; linkInfo.lastUpdated = new Date().toISOString(); // 保存更新后的链接映射 await persistentImageLinkService.saveLinkMap(); imageResult = { success: true, data: placeholderBuffer, metadata: { linkId: linkInfo.linkId, format: 'jpeg', size: placeholderBuffer.length, createdAt: linkInfo.createdAt, lastUpdated: linkInfo.lastUpdated } }; } else { throw error; } } if (!imageResult || !imageResult.success) { console.log(`❌ Failed to get image for linkId=${linkInfo.linkId}`); return res.status(404).json({ error: 'Image not found' }); } // 设置正确的 Content-Type const format = imageResult.metadata?.format || linkInfo.format || 'jpeg'; res.setHeader('Content-Type', `image/${format}`); res.setHeader('Cache-Control', 'public, max-age=31536000'); // 缓存1年 console.log(`✅ Serving persistent image: linkId=${linkInfo.linkId}, format=${format}`); res.send(imageResult.data); } catch (error) { console.error('❌ Error serving persistent image:', error); res.status(500).json({ error: 'Internal server error', message: error.message }); } }); // 导出功能路由 app.use('/api/export', exportRoutes); console.log('All API routes registered successfully'); // 添加调试中间件 - 处理未匹配的API路由 app.use('/api/*', (req, res) => { console.log(`Unmatched API route: ${req.method} ${req.path}`); res.status(404).json({ error: 'API route not found', path: req.path }); }); // 静态文件服务 - 在API路由之后 app.use((req, res, next) => { // 如果请求路径以 /api/ 开头,跳过静态文件服务 if (req.path.startsWith('/api/')) { return next(); } // 否则使用静态文件服务 express.static(frontendDistPath)(req, res, next); }); // 提供数据文件 app.use('/data', express.static(path.join(__dirname, '../../frontend/public/mocks'))); // 健康检查 - 需要在其他路由之前 app.get('/api/health', (req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString() }); }); // GitHub连接状态检查 app.get('/api/github/status', async (req, res) => { try { const { default: githubService } = await import('./services/githubService.js'); const validation = await githubService.validateConnection(); res.json({ github: validation, environment: { tokenConfigured: !!process.env.GITHUB_TOKEN, tokenPreview: process.env.GITHUB_TOKEN ? `${process.env.GITHUB_TOKEN.substring(0, 8)}...` : 'Not set', reposConfigured: !!process.env.GITHUB_REPOS, reposList: process.env.GITHUB_REPOS ? process.env.GITHUB_REPOS.split(',') : [], nodeEnv: process.env.NODE_ENV } }); } catch (error) { res.status(500).json({ error: error.message, stack: error.stack }); } }); // 添加GitHub测试路由 app.get('/api/github/test', async (req, res) => { try { console.log('=== GitHub Connection Test ==='); console.log('GITHUB_TOKEN exists:', !!process.env.GITHUB_TOKEN); console.log('GITHUB_REPOS:', process.env.GITHUB_REPOS); const { default: githubService } = await import('./services/githubService.js'); // 测试基本配置 const config = { hasToken: !!githubService.token, useMemoryStorage: githubService.useMemoryStorage, repositories: githubService.repositories, apiUrl: githubService.apiUrl }; console.log('GitHub Service Config:', config); // 如果有token,测试连接 let connectionTest = null; if (githubService.token) { console.log('Testing GitHub API connection...'); connectionTest = await githubService.validateConnection(); console.log('Connection test result:', connectionTest); } res.json({ timestamp: new Date().toISOString(), config, connectionTest, environment: { tokenLength: process.env.GITHUB_TOKEN ? process.env.GITHUB_TOKEN.length : 0, nodeEnv: process.env.NODE_ENV } }); } catch (error) { console.error('GitHub test error:', error); res.status(500).json({ error: error.message, stack: process.env.NODE_ENV === 'development' ? error.stack : undefined }); } }); // 添加GitHub调试路由 app.get('/api/debug/github', async (req, res) => { try { console.log('=== GitHub Debug Information ==='); const { default: githubService } = await import('./services/githubService.js'); const debugInfo = { timestamp: new Date().toISOString(), environment: { tokenConfigured: !!process.env.GITHUB_TOKEN, tokenLength: process.env.GITHUB_TOKEN ? process.env.GITHUB_TOKEN.length : 0, reposConfigured: !!process.env.GITHUB_REPOS, reposList: process.env.GITHUB_REPOS ? process.env.GITHUB_REPOS.split(',') : [], nodeEnv: process.env.NODE_ENV }, service: { hasToken: !!githubService.token, repositoriesCount: githubService.repositories?.length || 0, repositories: githubService.repositories || [], apiUrl: githubService.apiUrl } }; // 测试连接 try { const connectionTest = await githubService.validateConnection(); debugInfo.connectionTest = connectionTest; } catch (connError) { debugInfo.connectionError = connError.message; } console.log('Debug info:', debugInfo); res.json(debugInfo); } catch (error) { console.error('Debug route error:', error); res.status(500).json({ error: error.message, stack: process.env.NODE_ENV === 'development' ? error.stack : undefined }); } }); // 添加PPT调试路由 app.get('/api/debug/ppt/:userId', async (req, res) => { try { const { userId } = req.params; console.log(`=== PPT Debug for User: ${userId} ===`); const { default: githubService } = await import('./services/githubService.js'); const debugInfo = { timestamp: new Date().toISOString(), userId: userId, repositories: [] }; // Check if GitHub repositories are configured if (!githubService.repositories || githubService.repositories.length === 0) { return res.json({ ...debugInfo, error: 'GitHub repositories not configured', details: 'No GitHub repositories available for PPT storage' }); } // 检查每个仓库中用户的文件 for (let i = 0; i < githubService.repositories.length; i++) { const repoInfo = { index: i, url: githubService.repositories[i], accessible: false, userDirectoryExists: false, files: [] }; try { const { owner, repo } = githubService.parseRepoUrl(githubService.repositories[i]); // 检查仓库是否可访问 await axios.get(`https://api.github.com/repos/${owner}/${repo}`, { headers: { 'Authorization': `token ${githubService.token}`, 'Accept': 'application/vnd.github.v3+json' } }); repoInfo.accessible = true; // 检查用户目录 try { const userDirResponse = await axios.get( `https://api.github.com/repos/${owner}/${repo}/contents/users/${userId}`, { headers: { 'Authorization': `token ${githubService.token}`, 'Accept': 'application/vnd.github.v3+json' } } ); repoInfo.userDirectoryExists = true; repoInfo.files = userDirResponse.data .filter(item => item.type === 'file' && item.name.endsWith('.json')) .map(file => ({ name: file.name, size: file.size, sha: file.sha })); } catch (userDirError) { repoInfo.userDirectoryError = userDirError.response?.status === 404 ? 'Directory not found' : userDirError.message; } } catch (repoError) { repoInfo.error = repoError.message; } debugInfo.repositories.push(repoInfo); } console.log('PPT Debug info:', debugInfo); res.json(debugInfo); } catch (error) { console.error('PPT Debug route error:', error); res.status(500).json({ error: error.message, stack: process.env.NODE_ENV === 'development' ? error.stack : undefined }); } }); // 添加仓库初始化端点 app.post('/api/github/initialize', async (req, res) => { try { console.log('=== Manual Repository Initialization ==='); const { default: githubService } = await import('./services/githubService.js'); if (githubService.useMemoryStorage) { return res.status(400).json({ error: 'Cannot initialize repository: using memory storage mode', reason: 'GitHub token not configured' }); } const { repoIndex = 0 } = req.body; console.log(`Initializing repository at index: ${repoIndex}`); const result = await githubService.initializeRepository(repoIndex); if (result.success) { res.json({ success: true, message: 'Repository initialized successfully', commit: result.commit, timestamp: new Date().toISOString() }); } else { res.status(500).json({ success: false, error: result.error, reason: result.reason }); } } catch (error) { console.error('Repository initialization error:', error); res.status(500).json({ error: error.message, stack: process.env.NODE_ENV === 'development' ? error.stack : undefined }); } }); // 添加GitHub权限详细检查路由 app.get('/api/debug/github-permissions', async (req, res) => { try { console.log('=== GitHub Token Permissions Check ==='); const { default: githubService } = await import('./services/githubService.js'); if (!githubService.token) { return res.status(400).json({ error: 'No GitHub token configured' }); } const debugInfo = { timestamp: new Date().toISOString(), tokenInfo: {}, repositoryTests: [] }; // 1. 检查token基本信息和权限 try { const userResponse = await axios.get('https://api.github.com/user', { headers: { 'Authorization': `token ${githubService.token}`, 'Accept': 'application/vnd.github.v3+json' } }); debugInfo.tokenInfo = { login: userResponse.data.login, id: userResponse.data.id, type: userResponse.data.type, company: userResponse.data.company, publicRepos: userResponse.data.public_repos, privateRepos: userResponse.data.total_private_repos, tokenScopes: userResponse.headers['x-oauth-scopes'] || 'Unknown' }; console.log('Token info:', debugInfo.tokenInfo); } catch (tokenError) { debugInfo.tokenError = { status: tokenError.response?.status, message: tokenError.message }; } // Check if GitHub repositories are configured if (!githubService.repositories || githubService.repositories.length === 0) { debugInfo.repositoryError = 'GitHub repositories not configured'; return res.json(debugInfo); } // 2. 检查每个仓库的详细状态 for (let i = 0; i < githubService.repositories.length; i++) { const repoUrl = githubService.repositories[i]; const repoTest = { index: i, url: repoUrl, tests: {} }; try { const { owner, repo } = githubService.parseRepoUrl(repoUrl); repoTest.owner = owner; repoTest.repo = repo; // 测试1: 基本仓库访问 try { const repoResponse = await axios.get(`https://api.github.com/repos/${owner}/${repo}`, { headers: { 'Authorization': `token ${githubService.token}`, 'Accept': 'application/vnd.github.v3+json' } }); repoTest.tests.basicAccess = { success: true, repoExists: true, private: repoResponse.data.private, permissions: repoResponse.data.permissions, defaultBranch: repoResponse.data.default_branch, size: repoResponse.data.size }; } catch (repoError) { repoTest.tests.basicAccess = { success: false, status: repoError.response?.status, message: repoError.message, details: repoError.response?.data }; // 如果是404,检查是否是权限问题还是仓库不存在 if (repoError.response?.status === 404) { // 尝试不使用认证访问(如果是公开仓库应该能访问) try { await axios.get(`https://api.github.com/repos/${owner}/${repo}`); repoTest.tests.basicAccess.possibleCause = 'Repository exists but token lacks permission'; } catch (publicError) { if (publicError.response?.status === 404) { repoTest.tests.basicAccess.possibleCause = 'Repository does not exist'; } } } } // 测试2: 检查是否能列出用户的仓库 try { const userReposResponse = await axios.get(`https://api.github.com/users/${owner}/repos?per_page=100`, { headers: { 'Authorization': `token ${githubService.token}`, 'Accept': 'application/vnd.github.v3+json' } }); const hasRepo = userReposResponse.data.some(r => r.name === repo); repoTest.tests.userReposList = { success: true, totalRepos: userReposResponse.data.length, targetRepoFound: hasRepo, repoNames: userReposResponse.data.slice(0, 10).map(r => ({ name: r.name, private: r.private })) }; } catch (userReposError) { repoTest.tests.userReposList = { success: false, status: userReposError.response?.status, message: userReposError.message }; } } catch (parseError) { repoTest.parseError = parseError.message; } debugInfo.repositoryTests.push(repoTest); } // 3. 提供修复建议 const suggestions = []; if (debugInfo.tokenError) { suggestions.push('Token authentication failed - check if GITHUB_TOKEN is valid'); } debugInfo.repositoryTests.forEach((repoTest, index) => { if (!repoTest.tests.basicAccess?.success) { if (repoTest.tests.basicAccess?.status === 404) { if (repoTest.tests.basicAccess?.possibleCause === 'Repository does not exist') { suggestions.push(`Repository ${repoTest.url} does not exist - please create it on GitHub`); } else { suggestions.push(`Repository ${repoTest.url} exists but token lacks permission - check token scopes`); } } else if (repoTest.tests.basicAccess?.status === 403) { suggestions.push(`Permission denied for ${repoTest.url} - check if token has 'repo' scope`); } } }); debugInfo.suggestions = suggestions; console.log('GitHub permissions debug completed'); res.json(debugInfo); } catch (error) { console.error('GitHub permissions check error:', error); res.status(500).json({ error: error.message, stack: process.env.NODE_ENV === 'development' ? error.stack : undefined }); } }); // 添加大文件处理调试端点 app.get('/api/debug/large-files/:userId', async (req, res) => { try { const { userId } = req.params; console.log(`=== Large Files Debug for User: ${userId} ===`); const { default: githubService } = await import('./services/githubService.js'); const debugInfo = { timestamp: new Date().toISOString(), userId: userId, fileAnalysis: [] }; // 检查用户的所有PPT文件 const pptList = await githubService.getUserPPTList(userId); for (const ppt of pptList) { const fileInfo = { pptId: ppt.name, title: ppt.title, fileName: `${ppt.name}.json`, analysis: {} }; try { // 获取文件内容 const result = await githubService.getFile(userId, `${ppt.name}.json`, ppt.repoIndex || 0); if (result && result.content) { const content = result.content; const jsonString = JSON.stringify(content); const fileSize = Buffer.byteLength(jsonString, 'utf8'); fileInfo.analysis = { fileSize: fileSize, fileSizeKB: (fileSize / 1024).toFixed(2), slidesCount: content.slides?.length || 0, isChunked: !!content.isChunked, chunkedInfo: content.isChunked ? { totalChunks: content.totalChunks, totalSlides: content.totalSlides } : null, wasReassembled: !!result.isReassembled, metadata: content.metadata || 'No metadata', status: fileSize > 1024 * 1024 ? 'LARGE' : fileSize > 800 * 1024 ? 'MEDIUM' : 'NORMAL' }; // 分析每个slide的大小 if (content.slides && content.slides.length > 0) { const slideSizes = content.slides.map((slide, index) => { const slideJson = JSON.stringify(slide); const slideSize = Buffer.byteLength(slideJson, 'utf8'); return { index: index, size: slideSize, sizeKB: (slideSize / 1024).toFixed(2), elementsCount: slide.elements?.length || 0 }; }); // 找出最大的slides const largestSlides = slideSizes .sort((a, b) => b.size - a.size) .slice(0, 3); fileInfo.analysis.slideSummary = { averageSlideSize: (fileSize / content.slides.length).toFixed(0), largestSlides: largestSlides }; } } else { fileInfo.analysis = { error: 'Could not read file content' }; } } catch (error) { fileInfo.analysis = { error: error.message, errorType: error.name }; } debugInfo.fileAnalysis.push(fileInfo); } // 添加统计摘要 debugInfo.summary = { totalFiles: debugInfo.fileAnalysis.length, largeFiles: debugInfo.fileAnalysis.filter(f => f.analysis.status === 'LARGE').length, chunkedFiles: debugInfo.fileAnalysis.filter(f => f.analysis.isChunked).length, errors: debugInfo.fileAnalysis.filter(f => f.analysis.error).length }; console.log('Large files debug completed'); res.json(debugInfo); } catch (error) { console.error('Large files debug error:', error); res.status(500).json({ error: error.message, stack: process.env.NODE_ENV === 'development' ? error.stack : undefined }); } }); // 添加分块文件修复端点 app.post('/api/debug/fix-chunked-file/:userId/:pptId', async (req, res) => { try { const { userId, pptId } = req.params; console.log(`=== Fixing Chunked File: ${userId}/${pptId} ===`); const { default: githubService } = await import('./services/githubService.js'); const fileName = `${pptId}.json`; const result = { timestamp: new Date().toISOString(), userId, pptId, fileName, status: 'unknown', details: {}, actions: [] }; // Check if GitHub repositories are configured if (!githubService.repositories || githubService.repositories.length === 0) { return res.status(500).json({ ...result, status: 'error', error: 'GitHub repositories not configured' }); } // 1. 检查主文件是否存在 let mainFile = null; let mainFileRepo = -1; for (let i = 0; i < githubService.repositories.length; i++) { try { const fileResult = await githubService.getFile(userId, fileName, i); if (fileResult) { mainFile = fileResult; mainFileRepo = i; result.details.mainFileFound = true; result.details.mainFileRepo = i; result.actions.push(`Main file found in repository ${i}`); break; } } catch (error) { continue; } } if (!mainFile) { result.status = 'error'; result.details.error = 'Main file not found in any repository'; return res.json(result); } const content = mainFile.content; // 2. 检查是否是分块文件 if (!content.isChunked) { result.status = 'normal'; result.details.isChunked = false; result.details.slideCount = content.slides?.length || 0; result.actions.push('File is not chunked, no action needed'); return res.json(result); } // 3. 分析分块文件状态 result.details.isChunked = true; result.details.totalChunks = content.totalChunks; result.details.totalSlides = content.totalSlides; result.details.mainFileSlides = content.slides?.length || 0; // 4. 检查所有chunk文件 const chunkStatus = []; let totalFoundSlides = content.slides?.length || 0; for (let i = 1; i < content.totalChunks; i++) { const chunkFileName = fileName.replace('.json', `_chunk_${i}.json`); const chunkInfo = { index: i, fileName: chunkFileName, found: false, slides: 0, error: null }; try { const repoUrl = githubService.repositories[mainFileRepo]; const { owner, repo } = githubService.parseRepoUrl(repoUrl); const path = `users/${userId}/${chunkFileName}`; const response = await axios.get( `${githubService.apiUrl}/repos/${owner}/${repo}/contents/${path}`, { headers: { 'Authorization': `token ${githubService.token}`, 'Accept': 'application/vnd.github.v3+json' }, timeout: 30000 } ); const chunkContent = Buffer.from(response.data.content, 'base64').toString('utf8'); const chunkData = JSON.parse(chunkContent); chunkInfo.found = true; chunkInfo.slides = chunkData.slides?.length || 0; totalFoundSlides += chunkInfo.slides; result.actions.push(`Chunk ${i} found: ${chunkInfo.slides} slides`); } catch (error) { chunkInfo.error = error.message; result.actions.push(`Chunk ${i} missing or error: ${error.message}`); } chunkStatus.push(chunkInfo); } result.details.chunks = chunkStatus; result.details.totalFoundSlides = totalFoundSlides; result.details.missingSlides = content.totalSlides - totalFoundSlides; // 5. 判断状态和建议修复方案 const missingChunks = chunkStatus.filter(chunk => !chunk.found); if (missingChunks.length === 0 && totalFoundSlides === content.totalSlides) { result.status = 'healthy'; result.actions.push('All chunks found, file should load correctly'); } else if (missingChunks.length > 0) { result.status = 'incomplete'; result.details.missingChunks = missingChunks.map(c => c.index); result.actions.push(`Missing chunks: ${missingChunks.map(c => c.index).join(', ')}`); // 提供修复建议 if (totalFoundSlides >= content.totalSlides * 0.8) { result.actions.push('Recommendation: Reassemble available slides into single file'); result.details.recommendation = 'reassemble'; } else { result.actions.push('Recommendation: File may be corrupted, consider restoration from backup'); result.details.recommendation = 'restore'; } } else { result.status = 'mismatch'; result.actions.push('Slide count mismatch detected'); } console.log('Chunked file analysis completed:', result); res.json(result); } catch (error) { console.error('Chunked file fix error:', error); res.status(500).json({ error: error.message, stack: process.env.NODE_ENV === 'development' ? error.stack : undefined }); } }); // 添加分块文件重组端点 app.post('/api/debug/reassemble-chunked-file/:userId/:pptId', async (req, res) => { try { const { userId, pptId } = req.params; console.log(`=== Reassembling Chunked File: ${userId}/${pptId} ===`); const { default: githubService } = await import('./services/githubService.js'); const fileName = `${pptId}.json`; // 强制重新组装文件 const result = await githubService.getFile(userId, fileName, 0); if (!result) { return res.status(404).json({ error: 'File not found' }); } if (result.isReassembled) { res.json({ success: true, message: 'File reassembled successfully', slideCount: result.content.slides?.length || 0, wasChunked: !!result.content.reassembledInfo, reassembledInfo: result.content.reassembledInfo }); } else { res.json({ success: true, message: 'File was not chunked', slideCount: result.content.slides?.length || 0, wasChunked: false }); } } catch (error) { console.error('File reassembly error:', error); res.status(500).json({ error: error.message, details: error.stack }); } }); // 添加路由注册日志 console.log('Importing route modules...'); console.log('Auth routes imported:', !!authRoutes); console.log('PPT routes imported:', !!pptRoutes); console.log('Public routes imported:', !!publicRoutes); // 这些路由已经在前面注册过了,删除重复的注册 // 前端路由处理 - 修复ES模块兼容性 app.get('*', (req, res) => { const indexPath = path.join(frontendDistPath, 'index.html'); console.log(`Serving frontend route: ${req.path}, index.html path: ${indexPath}`); // 使用ES模块的fs检查文件是否存在 if (fs.existsSync(indexPath)) { res.sendFile(indexPath); } else { console.error('index.html not found at:', indexPath); res.status(404).send(`
index.html路径: ${indexPath}
请确保前端已正确构建
访问API测试页面 `); } }); // 错误处理中间件 app.use(errorHandler); // 初始化服务 async function initializeServices() { console.log('🚀 Initializing services...'); let servicesInitialized = 0; let totalServices = 5; // 初始化GitHub服务 try { await githubService.initialize(); servicesInitialized++; } catch (error) { console.error('❌ Failed to initialize GitHubService:', error); console.log('⚠️ Continuing without GitHubService...'); } // 初始化Huggingface存储服务 try { await huggingfaceStorageService.initialize(); servicesInitialized++; } catch (error) { console.error('❌ Failed to initialize HuggingfaceStorageService:', error); console.log('⚠️ Continuing without HuggingfaceStorageService...'); } // 初始化备份调度服务 try { await backupSchedulerService.initialize(); servicesInitialized++; } catch (error) { console.error('❌ Failed to initialize BackupSchedulerService:', error); console.log('⚠️ Continuing without BackupSchedulerService...'); } // 初始化持久化图片链接服务 try { await persistentImageLinkService.initialize(); servicesInitialized++; } catch (error) { console.error('❌ Failed to initialize PersistentImageLinkService:', error); console.log('⚠️ Continuing without PersistentImageLinkService...'); } // 初始化自动备份服务 try { await autoBackupService.initialize(); servicesInitialized++; } catch (error) { console.error('❌ Failed to initialize AutoBackupService:', error); console.log('⚠️ Continuing without AutoBackupService...'); } console.log(`✅ ${servicesInitialized}/${totalServices} services initialized successfully`); if (servicesInitialized === 0) { console.error('❌ No services could be initialized. Exiting...'); process.exit(1); } } // 启动服务器 async function startServer() { await initializeServices(); app.listen(PORT, '0.0.0.0', () => { console.log(`Server is running on port ${PORT}`); console.log(`Environment: ${process.env.NODE_ENV || 'development'}`); console.log(`💾 Huggingface storage service ready`); console.log(`⏰ Backup scheduler service ready`); console.log(`🔗 Persistent image link service ready`); console.log(`🔄 Auto backup service ready (every 8 hours)`); }); } // 启动服务器 startServer().catch(error => { console.error('❌ Failed to start server:', error); process.exit(1); }); // 优雅关闭处理 process.on('SIGTERM', async () => { console.log('🛑 Received SIGTERM, shutting down gracefully...'); await backupSchedulerService.cleanup(); await autoBackupService.cleanup(); process.exit(0); }); process.on('SIGINT', async () => { console.log('🛑 Received SIGINT, shutting down gracefully...'); await backupSchedulerService.cleanup(); await autoBackupService.cleanup(); process.exit(0); });