282 lines
8.8 KiB
JavaScript
282 lines
8.8 KiB
JavaScript
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: 更新音乐配置(仅更新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 || '音乐文件上传失败'
|
||
});
|
||
}
|
||
});
|
||
|
||
// ===================== 原有图片上传/删除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); // 每分钟执行一次
|