/** * QZMusic Web 静态文件服务器(生产用) * 监听 0.0.0.0:1219,支持公网访问 */ const http = require('http'); const fs = require('fs'); const path = require('path'); const url = require('url'); const PORT = parseInt(process.env.QZMUSIC_PORT || '1219', 10); const HOST = process.env.QZMUSIC_HOST || '0.0.0.0'; const ROOT = path.join(__dirname, 'dist'); const mimeTypes = { '.html': 'text/html; charset=utf-8', '.htm': 'text/html; charset=utf-8', '.js': 'application/javascript; charset=utf-8', '.mjs': 'application/javascript; charset=utf-8', '.css': 'text/css; charset=utf-8', '.json': 'application/json; charset=utf-8', '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.webp': 'image/webp', '.svg': 'image/svg+xml', '.ico': 'image/x-icon', '.ttf': 'font/ttf', '.woff': 'font/woff', '.woff2':'font/woff2', '.map': 'application/json; charset=utf-8', '.txt': 'text/plain; charset=utf-8' }; function log(...args) { const ts = new Date().toISOString().replace('T', ' ').substring(0, 19); console.log(`[${ts}]`, ...args); } const server = http.createServer((req, res) => { const parsed = url.parse(req.url); let pathname = decodeURIComponent(parsed.pathname || '/'); if (pathname === '/') pathname = '/index.html'; let filePath = path.join(ROOT, pathname); // 防止路径穿越 if (!filePath.startsWith(ROOT)) { res.writeHead(403); res.end('Forbidden'); return; } const extname = String(path.extname(filePath)).toLowerCase(); const contentType = mimeTypes[extname] || 'application/octet-stream'; // SPA 路由:如果不是带扩展名的文件且文件不存在,则回退到 index.html const tryStatic = (cb) => { fs.stat(filePath, (err, stat) => { if (!err && stat.isDirectory()) { filePath = path.join(filePath, 'index.html'); return fs.stat(filePath, (e2, s2) => { if (!e2 && s2.isFile()) return cb(true); return cb(false); }); } if (!err && stat.isFile()) return cb(true); return cb(false); }); }; tryStatic((found) => { if (!found) { filePath = path.join(ROOT, 'index.html'); } fs.readFile(filePath, (error, content) => { if (error) { log(`${req.method} ${req.url} -> 500 (${error.code})`); res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' }); res.end('Server Error: ' + error.code); } else { const finalType = found ? contentType : 'text/html; charset=utf-8'; res.writeHead(200, { 'Content-Type': finalType, 'Cache-Control': extname === '.html' || extname === '.htm' ? 'no-cache, no-store, must-revalidate' : 'public, max-age=3600' }); res.end(content); log(`${req.method} ${req.url} -> 200 (${pathname})`); } }); }); }); server.listen(PORT, HOST, () => { const banner = ` ╔══════════════════════════════════════════════════════════════╗ ║ ║ ║ QZMusic Web Server 启动成功! ║ ║ ║ ║ 监听地址: http://${HOST}:${PORT} ║ ║ 本地访问: http://localhost:${PORT} ║ ║ 公网访问: http://[你的公网IP]:${PORT} ║ ║ ║ ╚══════════════════════════════════════════════════════════════╝ `; console.log(banner); }); process.on('SIGINT', () => { log('收到 SIGINT,正在关闭服务器...'); server.close(() => process.exit(0)); }); process.on('SIGTERM', () => { log('收到 SIGTERM,正在关闭服务器...'); server.close(() => process.exit(0)); });