import express from 'express'; import cors from 'cors'; import path from 'path'; import fs from 'fs'; import { fileURLToPath } from 'url'; import multer from 'multer'; // 获取当前文件的目录路径 const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const app = express(); const PORT = 3000; const CONFIG_FILE_PATH = path.join(__dirname, 'data', 'config.json'); // ===================== 原有图片上传配置(保留) ===================== // 创建图片上传目录 const uploadDir = path.join(__dirname, 'uploads'); if (!fs.existsSync(uploadDir)) { fs.mkdirSync(uploadDir, { recursive: true }); } // 图片上传multer配置 const imageStorage = multer.diskStorage({ destination: (req, file, cb) => { cb(null, uploadDir); }, filename: (req, file, cb) => { const timestamp = Date.now(); const ext = path.extname(file.originalname); const filename = `${timestamp}${ext}`; cb(null, filename); } }); const imageUpload = multer({ storage: imageStorage, limits: { fileSize: 5 * 1024 * 1024 }, // 5MB限制 fileFilter: (req, file, cb) => { const allowedTypes = /jpeg|jpg|png|gif/; const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase()); const mimetype = allowedTypes.test(file.mimetype); if (extname && mimetype) { return cb(null, true); } else { cb(new Error('只允许上传图片文件(JPEG、JPG、PNG、GIF)')); } } }); // ===================== 新增:音乐上传配置 ===================== // 创建音乐上传目录(对应前端访问路径) const musicUploadDir = path.join(__dirname, 'public', 'assets', 'music'); // 确保目录存在(不存在则创建) if (!fs.existsSync(musicUploadDir)) { fs.mkdirSync(musicUploadDir, { recursive: true }); } // 音乐上传multer配置 const musicStorage = multer.diskStorage({ destination: (req, file, cb) => { cb(null, musicUploadDir); // 保存到public/assets/music }, filename: (req, file, cb) => { // 保留原文件名+时间戳,避免重复 const timestamp = Date.now(); const ext = path.extname(file.originalname); const filename = `${timestamp}${ext}`; cb(null, filename); } }); const musicUpload = multer({ storage: musicStorage, limits: { fileSize: 10 * 1024 * 1024 }, // 10MB音乐文件限制 fileFilter: (req, file, cb) => { // 仅允许MP3格式 const allowedTypes = /mp3/; const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase()); const mimetype = allowedTypes.test(file.mimetype) || file.mimetype === 'audio/mpeg'; if (extname && mimetype) { return cb(null, true); } else { cb(new Error('只允许上传MP3格式的音频文件!')); } } }); // ===================== 中间件(保留+优化) ===================== app.use(cors()); app.use(express.json()); // 静态文件服务(Vue应用、上传的图片、音乐文件) app.use(express.static(path.join(__dirname, 'dist'))); app.use('/uploads', express.static(uploadDir)); app.use('/assets/music', express.static(musicUploadDir)); // 新增:音乐文件静态访问 // ===================== 原有API(保留) ===================== // API: 获取整体配置数据 app.get('/api/config', (req, res) => { try { const configData = fs.readFileSync(CONFIG_FILE_PATH, 'utf8'); res.json(JSON.parse(configData)); } catch (error) { console.error('读取配置文件失败:', error); res.status(500).json({ error: '读取配置文件失败' }); } }); // API: 保存整体配置数据 app.post('/api/config', (req, res) => { try { fs.writeFileSync(CONFIG_FILE_PATH, JSON.stringify(req.body, null, 2), 'utf8'); res.json({ success: true }); } catch (error) { console.error('保存配置文件失败:', error); res.status(500).json({ error: '保存配置文件失败' }); } }); // ===================== 新增:音乐专属API ===================== // API: 获取音乐配置(单独返回musicConfig) app.get('/api/musicConfig', (req, res) => { try { const configData = fs.readFileSync(CONFIG_FILE_PATH, 'utf8'); const config = JSON.parse(configData); // 兜底:如果没有musicConfig,返回默认值 res.json(config.musicConfig || { enabled: false, filePath: '/assets/music/background.mp3' }); } catch (error) { console.error('读取音乐配置失败:', error); res.status(500).json({ enabled: false, filePath: '/assets/music/background.mp3' }); } }); // ===================== 新增:获取已上传音乐列表API ===================== app.get('/api/music/list', (req, res) => { try { // 音乐文件存储目录(和之前配置的一致) const musicDir = path.join(__dirname, 'public', 'assets', 'music'); // 先判断目录是否存在,避免报错 if (!fs.existsSync(musicDir)) { return res.json({ success: true, data: [] }); } // 读取目录下的所有MP3文件 const files = fs.readdirSync(musicDir).filter(file => file.endsWith('.mp3')); // 返回文件名+访问路径 const musicList = files.map(file => ({ filename: file, filePath: `/assets/music/${file}` })); res.json({ success: true, data: musicList }); } catch (error) { console.error('读取音乐列表失败:', error); res.status(500).json({ success: false, error: '读取音乐列表失败' }); } }); // ===================== 新增结束 ===================== // API: 更新音乐配置(仅更新musicConfig节点,不影响其他配置) app.post('/api/musicConfig', (req, res) => { try { // 1. 读取原有配置 const configData = fs.readFileSync(CONFIG_FILE_PATH, 'utf8'); const config = JSON.parse(configData); // 2. 校验参数 const { enabled, filePath } = req.body; if (typeof enabled !== 'boolean' || !filePath) { return res.status(400).json({ success: false, error: '参数错误:enabled必须为布尔值,filePath不能为空' }); } // 3. 更新musicConfig节点(保留其他配置不变) config.musicConfig = { enabled, filePath }; // 4. 写入配置文件 fs.writeFileSync(CONFIG_FILE_PATH, JSON.stringify(config, null, 2), 'utf8'); res.json({ success: true, data: config.musicConfig }); } catch (error) { console.error('更新音乐配置失败:', error); res.status(500).json({ success: false, error: '更新音乐配置失败' }); } }); // API: 上传音乐文件 app.post('/api/upload/music', musicUpload.single('musicFile'), (req, res) => { try { if (!req.file) { return res.status(400).json({ success: false, error: '没有选择要上传的音乐文件' }); } // 返回前端可访问的音乐路径(对应public/assets/music) const relativePath = `/assets/music/${req.file.filename}`; res.json({ success: true, filePath: relativePath, // 音乐访问路径 filename: req.file.filename, originalName: req.file.originalname }); } catch (error) { console.error('音乐文件上传失败:', error); res.status(500).json({ success: false, error: error.message || '音乐文件上传失败' }); } }); // ===================== 兼容前端旧路径 /upload-music ===================== app.post('/upload-music', (req, res) => { musicUpload.single('musicFile')(req, res, (err) => { if (err) { return res.status(500).json({ success: false, error: err.message || '音乐文件上传失败' }); } if (!req.file) { return res.status(400).json({ success: false, error: '没有选择要上传的音乐文件' }); } const relativePath = `/assets/music/${req.file.filename}`; res.json({ success: true, filePath: relativePath, filename: req.file.filename, originalName: req.file.originalname }); }); }); // ===================== 原有图片上传/删除API(保留) ===================== // API: 上传图片 app.post('/api/upload', imageUpload.single('image'), (req, res) => { try { if (!req.file) { return res.status(400).json({ error: '没有文件上传' }); } // 返回文件的相对路径 const relativePath = `/uploads/${req.file.filename}`; res.json({ success: true, filePath: relativePath, filename: req.file.filename }); } catch (error) { console.error('文件上传失败:', error); res.status(500).json({ error: error.message || '文件上传失败' }); } }); // API: 删除图片 app.delete('/api/upload/:filename', (req, res) => { try { const filename = req.params.filename; const filePath = path.join(uploadDir, filename); if (fs.existsSync(filePath)) { fs.unlinkSync(filePath); res.json({ success: true }); } else { res.status(404).json({ error: '文件不存在' }); } } catch (error) { console.error('文件删除失败:', error); res.status(500).json({ error: '文件删除失败' }); } }); // ===================== 前端路由兼容(保留) ===================== // 处理Vue Router历史模式 - 使用正则表达式代替通配符 app.get(/^((?!\/api).)*$/, (req, res) => { res.sendFile(path.join(__dirname, 'dist', 'index.html')); }); // ===================== 服务器启动(保留+优化) ===================== // 启动服务器并监听错误 const server = app.listen(PORT, '0.0.0.0', () => { console.log(`服务器运行在 http://localhost:${PORT}`); console.log('服务器已成功启动,可以访问 http://localhost:3000'); console.log('API端点:'); console.log(' - 整体配置: GET/POST /api/config'); console.log(' - 音乐配置: GET/POST /api/musicConfig'); console.log(' - 图片上传: POST /api/upload'); console.log(' - 音乐上传: POST /api/upload/music'); console.log(' - 图片删除: DELETE /api/upload/:filename'); }); // 监听服务器错误 server.on('error', (error) => { console.error('服务器错误:', error); if (error.code === 'EADDRINUSE') { console.error(`端口 ${PORT} 已被占用,请尝试其他端口。`); } }); // 监听SIGINT信号(Ctrl+C) process.on('SIGINT', () => { console.log('正在关闭服务器...'); server.close(() => { console.log('服务器已关闭'); process.exit(0); }); }); // 确保服务器持续运行 setInterval(() => { // 保持服务器活动的空操作 }, 60000); // 每分钟执行一次