// 确保在最开始就加载环境变量 require('dotenv').config(); console.log('Environment loaded:', { DB_HOST: process.env.DB_HOST, DB_USER: process.env.DB_USER, DB_DATABASE: process.env.DB_DATABASE, PORT: process.env.PORT }); const express = require('express'); const http = require('http'); const cors = require('cors'); const multer = require('multer'); const path = require('path'); const jwt = require('jsonwebtoken'); const bcrypt = require('bcryptjs'); const auth = require('./middleware/auth'); const checkPermission = require('./middleware/checkPermission'); const db = require('./utils/db'); const app = express(); const systemSettingsRoutes = require('./routes/systemSettings'); const messagesRoutes = require('./routes/messages'); const tagsRoutes = require('./routes/tags'); const adminRoutes = require('./routes/admin'); const server = http.createServer(app); const { initWebSocket } = require('./websocket'); // 请求日志中间件 app.use((req, res, next) => { console.log(`${new Date().toISOString()} ${req.method} ${req.url}`); next(); }); // 中间件 app.use(cors()); app.use(express.json()); // 配置文件上传 const storage = multer.diskStorage({ destination: (req, file, cb) => { let uploadPath = 'uploads'; if (file.mimetype.startsWith('image/')) { uploadPath += '/images'; } else if (file.mimetype.startsWith('video/')) { uploadPath += '/videos'; } // 确保目录存在 require('fs').mkdirSync(path.join(__dirname, uploadPath), { recursive: true }); cb(null, uploadPath); }, filename: (req, file, cb) => { cb(null, Date.now() + path.extname(file.originalname)); } }); const upload = multer({ storage, limits: { fileSize: 10 * 1024 * 1024, // 10MB limit }, fileFilter: (req, file, cb) => { const allowedTypes = /jpeg|jpg|png|gif|mp4|webm/; const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase()); const mimetype = allowedTypes.test(file.mimetype); if (extname && mimetype) { return cb(null, true); } cb(new Error('只支持图片和视频文件!')); } }); // API路由 app.get('/api/categories', async (req, res) => { try { const [categories] = await db.query('SELECT * FROM categories'); res.json(categories); } catch (error) { res.status(500).json({ message: error.message }); } }); app.get('/api/games', async (req, res) => { try { const [games] = await db.query(` SELECT g.*, c.name as category_name, GROUP_CONCAT(DISTINCT gp.platform) as platforms, GROUP_CONCAT(DISTINCT gt.tag) as tags FROM games g LEFT JOIN categories c ON g.category_id = c.id LEFT JOIN game_platforms gp ON g.id = gp.game_id LEFT JOIN game_tags gt ON g.id = gt.game_id GROUP BY g.id `); // 处理平台和标签字符串 games.forEach(game => { game.platforms = game.platforms ? game.platforms.split(',') : []; game.tags = game.tags ? game.tags.split(',') : []; }); res.json(games); } catch (error) { res.status(500).json({ message: error.message }); } }); app.get('/api/games/:id', async (req, res) => { try { const game = await Game.findById(req.params.id); if (!game) { return res.status(404).json({ message: 'Game not found' }); } // 获取游戏对应的分类信息 const category = await Category.findById(game.categoryId); const gameWithCategory = { ...game.toObject(), category }; res.json(gameWithCategory); } catch (error) { res.status(500).json({ message: error.message }); } }); app.get('/api/games/search', async (req, res) => { try { const { query, category } = req.query; let searchQuery = {}; if (query) { searchQuery.$or = [ { title: new RegExp(query, 'i') }, { description: new RegExp(query, 'i') } ]; } if (category) { searchQuery.categoryId = category; } const games = await Game.find(searchQuery); res.json(games); } catch (error) { res.status(500).json({ message: error.message }); } }); // 添加游戏 app.post('/api/games', auth, upload.single('image'), async (req, res) => { try { const { title, description, category_id, developer, release_date, platforms, tags } = req.body; // 验证必填字段 if (!title || !category_id) { return res.status(400).json({ message: '标题和分类为必填项' }); } // 解析平台和标签数据(因为是通过FormData传输的) const platformsArray = Array.isArray(platforms) ? platforms : platforms ? [platforms] : []; const tagsArray = Array.isArray(tags) ? tags : tags ? [tags] : []; const [result] = await db.query( 'INSERT INTO games (title, description, category_id, developer, release_date, image) VALUES (?, ?, ?, ?, ?, ?)', [ title, description || '', parseInt(category_id), developer || '', release_date || null, req.file ? req.file.path : null ] ); const gameId = result.insertId; // 添加平台信息 if (platformsArray.length > 0) { const platformValues = platformsArray.map(platform => [gameId, platform]); await db.query( 'INSERT INTO game_platforms (game_id, platform) VALUES ?', [platformValues] ); } // 添加标签信息 if (tagsArray.length > 0) { const tagValues = tagsArray.map(tag => [gameId, tag]); await db.query( 'INSERT INTO game_tags (game_id, tag) VALUES ?', [tagValues] ); } res.status(201).json({ message: '游戏添加成功', id: gameId }); } catch (error) { console.error('Error adding game:', error); res.status(500).json({ message: '添加游戏失败', error: process.env.NODE_ENV === 'development' ? error.message : undefined }); } }); // 管理员注册 app.post('/api/admin/register', async (req, res) => { try { const { username, password, email } = req.body; // 验证必填字段 if (!username || !password || !email) { return res.status(400).json({ message: '用户名、密码和邮箱都是必填项' }); } // 检查用户名是否已存在 const [existingUsers] = await db.query( 'SELECT id FROM admins WHERE username = ?', [username] ); if (existingUsers.length > 0) { return res.status(400).json({ message: '用户名已存在' }); } // 检查邮箱是否已存在 const [existingEmails] = await db.query( 'SELECT id FROM admins WHERE email = ?', [email] ); if (existingEmails.length > 0) { return res.status(400).json({ message: '邮箱已被使用' }); } // 加密密码 const hashedPassword = await bcrypt.hash(password, 10); console.log('Hashed password:', hashedPassword); // 开始事务 const connection = await db.getConnection(); await connection.beginTransaction(); try { // 插入管理员记录 const [result] = await connection.query( 'INSERT INTO admins (username, password, email, role, status) VALUES (?, ?, ?, ?, ?)', [username, hashedPassword, email, 'editor', 'active'] ); // 插入默认权限 await connection.query( 'INSERT INTO admin_permissions (admin_id, permission) VALUES (?, ?), (?, ?)', [result.insertId, 'game:manage', result.insertId, 'media:manage'] ); await connection.commit(); // 生成JWT令牌 const token = jwt.sign( { id: result.insertId, role: 'editor' }, process.env.JWT_SECRET, { expiresIn: '24h' } ); res.status(201).json({ message: '注册成功', token, admin: { username, role: 'editor', permissions: ['game:manage', 'media:manage'] } }); } catch (error) { await connection.rollback(); throw error; } finally { connection.release(); } } catch (error) { console.error('Registration error:', error); res.status(500).json({ message: '注册失败', error: error.message }); } }); // 管理员登录 app.post('/api/admin/login', async (req, res) => { try { const { username, password } = req.body; console.log('Login attempt:', { username }); // 添加登录尝试日志 const [admins] = await db.query( 'SELECT a.*, GROUP_CONCAT(ap.permission) as permissions FROM admins a ' + 'LEFT JOIN admin_permissions ap ON a.id = ap.admin_id ' + 'WHERE a.username = ? AND a.status = "active" ' + 'GROUP BY a.id', [username] ); console.log('Query result:', admins); // 添加查询结果日志 const admin = admins[0]; if (!admin) { console.log('Admin not found'); return res.status(401).json({ message: '用户名或密码错误' }); } console.log('Comparing password for:', admin.username); console.log('Encrypted password:', admin.password); const hashedPassword = await bcrypt.hash(password, 10); console.log('Hashed password:', hashedPassword); const isMatch = await bcrypt.compare(password, admin.password); console.log('Password match result:', isMatch); if (!isMatch) { console.log('Password does not match'); return res.status(401).json({ message: '用户名或密码错误' }); } // 更新最后登录时间 await db.query( 'UPDATE admins SET last_login = NOW() WHERE id = ?', [admin.id] ); const token = jwt.sign( { id: admin.id, role: admin.role }, process.env.JWT_SECRET, { expiresIn: '24h' } ); console.log('Login successful for:', admin.username); res.json({ token, admin: { username: admin.username, role: admin.role, permissions: admin.permissions ? admin.permissions.split(',') : [] } }); } catch (error) { console.error('Login error:', error); res.status(500).json({ message: error.message }); } }); // 媒体文件上传 app.post('/api/media/upload', auth, upload.single('file'), async (req, res) => { try { if (!req.file) { return res.status(400).json({ message: '请选择要上传的文件' }); } const media = new Media({ filename: req.file.filename, originalname: req.file.originalname, mimetype: req.file.mimetype, size: req.file.size, url: `/uploads/${req.file.filename}`, type: req.file.mimetype.startsWith('image/') ? 'image' : 'video', uploadedBy: req.admin.id }); await media.save(); res.json(media); } catch (error) { res.status(500).json({ message: error.message }); } }); // 获取媒体文件列表 app.get('/api/media', auth, async (req, res) => { try { const { type, gameId } = req.query; let query = {}; if (type) query.type = type; if (gameId) query.gameId = gameId; const media = await Media.find(query) .populate('uploadedBy', 'username') .sort('-uploadedAt'); res.json(media); } catch (error) { res.status(500).json({ message: error.message }); } }); // 删除媒体文件 app.delete('/api/media/:id', auth, async (req, res) => { try { const media = await Media.findById(req.params.id); if (!media) { return res.status(404).json({ message: '文件不存在' }); } // 删除物理文件 const filePath = path.join(__dirname, media.url); fs.unlink(filePath, async (err) => { if (err) { console.error('Error deleting file:', err); } await media.remove(); res.json({ message: '文件已删除' }); }); } catch (error) { res.status(500).json({ message: error.message }); } }); // 更新游戏 app.put('/api/games/:id', auth, upload.single('image'), async (req, res) => { try { const { title, description, category_id, developer, release_date, platforms, tags } = req.body; // 验证游戏是否存在 const [existingGame] = await db.query( 'SELECT * FROM games WHERE id = ?', [req.params.id] ); if (!existingGame.length) { return res.status(404).json({ message: '游戏不存在' }); } // 更新游戏基本信息 await db.query( `UPDATE games SET title = ?, description = ?, category_id = ?, developer = ?, release_date = ?, image = ? WHERE id = ?`, [ title, description || '', parseInt(category_id), developer || '', release_date || null, req.file ? req.file.path : existingGame[0].image, req.params.id ] ); // 更新平台信息 if (platforms) { // 先删除旧的平台信息 await db.query('DELETE FROM game_platforms WHERE game_id = ?', [req.params.id]); // 添加新的平台信息 if (platforms.length > 0) { const platformValues = platforms.map(platform => [req.params.id, platform]); await db.query( 'INSERT INTO game_platforms (game_id, platform) VALUES ?', [platformValues] ); } } // 更新标签信息 if (tags) { // 先删除旧的标签信息 await db.query('DELETE FROM game_tags WHERE game_id = ?', [req.params.id]); // 添加新的标签信息 if (tags.length > 0) { const tagValues = tags.map(tag => [req.params.id, tag]); await db.query( 'INSERT INTO game_tags (game_id, tag) VALUES ?', [tagValues] ); } } res.json({ message: '游戏更新成功' }); } catch (error) { console.error('Error updating game:', error); res.status(500).json({ message: error.message }); } }); // 添加统计API路由 app.get('/api/stats', auth, async (req, res) => { try { const [gamesCount, categoriesCount, mediaCount] = await Promise.all([ Game.countDocuments(), Category.countDocuments(), Media.countDocuments() ]); res.json({ gamesCount, categoriesCount, mediaCount }); } catch (error) { res.status(500).json({ message: error.message }); } }); // 用户管理API app.get('/api/admins', auth, checkPermission('user:manage'), async (req, res) => { try { const admins = await Admin.find() .select('-password') .populate('createdBy', 'username'); res.json(admins); } catch (error) { res.status(500).json({ message: error.message }); } }); app.post('/api/admins', auth, checkPermission('user:manage'), async (req, res) => { try { const { username, email, password, role } = req.body; const admin = new Admin({ username, email, password, role, createdBy: req.admin.id }); await admin.save(); res.status(201).json({ message: '用户创建成功' }); } catch (error) { res.status(500).json({ message: error.message }); } }); app.put('/api/admins/:id', auth, checkPermission('user:manage'), async (req, res) => { try { const { id } = req.params; const updates = req.body; // 不允许修改超级管理员 const admin = await Admin.findById(id); if (admin.role === 'superadmin' && req.admin.role !== 'superadmin') { return res.status(403).json({ message: '无权修改超级管理员' }); } // 如果要修改密码,需要加密 if (updates.password) { updates.password = await bcrypt.hash(updates.password, 10); } const updatedAdmin = await Admin.findByIdAndUpdate( id, updates, { new: true } ).select('-password'); res.json(updatedAdmin); } catch (error) { res.status(500).json({ message: error.message }); } }); app.delete('/api/admins/:id', auth, checkPermission('user:manage'), async (req, res) => { try { const { id } = req.params; // 不允许删除超级管理员 const admin = await Admin.findById(id); if (admin.role === 'superadmin') { return res.status(403).json({ message: '不能删除超级管理员' }); } // 不能删除自己 if (id === req.admin.id) { return res.status(400).json({ message: '不能删除自己的账号' }); } await Admin.findByIdAndDelete(id); res.json({ message: '用户删除成功' }); } catch (error) { res.status(500).json({ message: error.message }); } }); // 获取当前用户信息 app.get('/api/admins/me', auth, async (req, res) => { try { const admin = await Admin.findById(req.admin.id) .select('-password'); res.json(admin); } catch (error) { res.status(500).json({ message: error.message }); } }); // 修改密码 app.post('/api/admins/change-password', auth, async (req, res) => { try { const { oldPassword, newPassword } = req.body; const admin = await Admin.findById(req.admin.id); const isMatch = await bcrypt.compare(oldPassword, admin.password); if (!isMatch) { return res.status(400).json({ message: '原密码错误' }); } admin.password = newPassword; await admin.save(); res.json({ message: '密码修改成功' }); } catch (error) { res.status(500).json({ message: error.message }); } }); // 系统设置路由 app.use('/api/settings', systemSettingsRoutes); // 留言管理路由 app.use('/api/messages', messagesRoutes); // 标签管理路由 app.use('/api/tags', tagsRoutes); // 用户管理路由 app.use('/api/admin', adminRoutes); // 提供静态文件访问 app.use('/uploads', express.static(path.join(__dirname, 'uploads'))); // 删除游戏 app.delete('/api/games/:id', auth, async (req, res) => { try { // 验证游戏是否存在 const [existingGame] = await db.query( 'SELECT * FROM games WHERE id = ?', [req.params.id] ); if (!existingGame.length) { return res.status(404).json({ message: '游戏不存在' }); } // 开始事务 const connection = await db.getConnection(); await connection.beginTransaction(); try { // 删除游戏图片 if (existingGame[0].image) { const imagePath = path.join(__dirname, existingGame[0].image); try { if (require('fs').existsSync(imagePath)) { require('fs').unlinkSync(imagePath); console.log(`Deleted image file: ${imagePath}`); } } catch (error) { console.error('Error deleting image file:', error); // 继续执行,不因文件删除失败而中断整个操作 } } // 删除相关的平台信息 await connection.query('DELETE FROM game_platforms WHERE game_id = ?', [req.params.id]); // 删除相关的标签信息 await connection.query('DELETE FROM game_tags WHERE game_id = ?', [req.params.id]); // 删除游戏记录 await connection.query('DELETE FROM games WHERE id = ?', [req.params.id]); await connection.commit(); console.log(`Successfully deleted game with ID: ${req.params.id}`); res.json({ message: '游戏删除成功' }); } catch (error) { await connection.rollback(); console.error('Transaction failed:', error); throw error; } finally { connection.release(); } } catch (error) { console.error('Error deleting game:', error); res.status(500).json({ message: '删除游戏失败', error: process.env.NODE_ENV === 'development' ? error.message : undefined }); } }); // 获取管理员数量 app.get('/api/admin/count', auth, async (req, res) => { try { const [result] = await db.query('SELECT COUNT(*) as count FROM admins'); res.json({ count: result[0].count }); } catch (error) { console.error('Error getting admin count:', error); res.status(500).json({ message: error.message }); } }); // 错误处理中间件 app.use((err, req, res, next) => { console.error('Server error:', err); res.status(500).json({ message: 'Internal server error', error: process.env.NODE_ENV === 'development' ? err.message : undefined }); }); // 启动服务器 const PORT = process.env.PORT || 3000; server.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); }); // 初始化 WebSocket initWebSocket(server);