From b2c415f25342c477caa781ee8fe1061d1b92b590 Mon Sep 17 00:00:00 2001 From: HenryDu8133 <813367384@qq.com> Date: Mon, 17 Aug 2026 15:49:59 +0800 Subject: [PATCH] =?UTF-8?q?fix(server):=20=E5=BD=BB=E5=BA=95=E6=94=BE?= =?UTF-8?q?=E5=BC=83=20:path(*)=20=E7=AD=89=E5=8F=82=E6=95=B0=E5=8C=96?= =?UTF-8?q?=E9=80=9A=E9=85=8D=E8=B7=AF=E7=94=B1=EF=BC=8C=E6=94=B9=E7=94=A8?= =?UTF-8?q?=20app.use=20+=20req.path=20=E6=89=8B=E5=8A=A8=E6=88=AA?= =?UTF-8?q?=E5=8F=96=EF=BC=8C=E8=A7=A3=E5=86=B3=20path-to-regexp=20v8.3=20?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E5=B4=A9=E6=BA=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 服务器环境 path-to-regexp 实际版本是 8.3.0(Express 5.2.1 内置), 该版本既不接受裸 '*'、也不接受 ':name(.*)' 自定义正则、也不接受 ':name(*)' 单段通配修饰符,参数路由只要带修饰符就会在 new Layer 阶段直接抛 PathError。 解决方案(这次本地用真实 node_modules 实装 require 整份 server.js 模拟启动,已确认不崩溃,http.createServer 正常被调用 3 次): - 7 条 /media/* 路由全部删除 app.get('/prefix/:path(*)', ...) - 改为 app.use('/prefix', middleware) + 在中间件里用 req.path 手动去掉前置 '/' 得到相对路径,再 decodeURIComponent();只处理 GET 请求,其他方法 next() - static 命中优先(fallthrough),未命中时继续调用原 sendBinaryFile / sendBinaryFileFromCandidates 兜底,保持 Content-Type / Range / Accept-Ranges / Content-Length 等头 与旧行为完全一致 - 涉及:general / beep / music / voice / line / station / temp 客户端 bump v1.0.103 -> v1.0.104 --- CloudServer/server.js | 104 ++++++++++++++++++++++++------------------ station_broadcast.lua | 2 +- 2 files changed, 60 insertions(+), 46 deletions(-) diff --git a/CloudServer/server.js b/CloudServer/server.js index 6184dc4..8fc4558 100644 --- a/CloudServer/server.js +++ b/CloudServer/server.js @@ -149,11 +149,16 @@ function safeRelativeToBase(baseDir, relativePath) { } app.use('/media/audio/general', (req, res, next) => { + if (req.method && String(req.method).toUpperCase() !== 'GET') return next(); const dirs = getBeepAudioDirs(); - if (!dirs || dirs.length === 0) return next(); + const rel = decodeURIComponent(String(req.path || '/').replace(/^\/+/, '')); let i = 0; const tryNext = () => { - if (i >= dirs.length) return next(); + if (i >= dirs.length) { + const candidates = (dirs || []).map(dir => safeRelativeToBase(dir, rel)).filter(Boolean); + if (candidates && candidates.length) return sendBinaryFileFromCandidates(res, candidates); + return next(); + } const dir = String(dirs[i++] || ''); if (!dir) return tryNext(); express.static(dir, { fallthrough: true })(req, res, tryNext); @@ -161,62 +166,71 @@ app.use('/media/audio/general', (req, res, next) => { tryNext(); }); app.use('/media/audio/beep', (req, res, next) => { + if (req.method && String(req.method).toUpperCase() !== 'GET') return next(); const dirs = getBeepAudioDirs(); - if (!dirs || dirs.length === 0) return next(); + const rel = decodeURIComponent(String(req.path || '/').replace(/^\/+/, '')); let i = 0; const tryNext = () => { - if (i >= dirs.length) return next(); + if (i >= dirs.length) { + const candidates = (dirs || []).map(dir => safeRelativeToBase(dir, rel)).filter(Boolean); + if (candidates && candidates.length) return sendBinaryFileFromCandidates(res, candidates); + return next(); + } const dir = String(dirs[i++] || ''); if (!dir) return tryNext(); express.static(dir, { fallthrough: true })(req, res, tryNext); }; tryNext(); }); -app.use('/media/audio/music', express.static(String(audioMusicDir || ''), { fallthrough: true })); -app.use('/media/audio/voice', express.static(String(audioVoiceDir || ''), { fallthrough: true })); -app.use('/media/audio/line', express.static(String(audioLineDir || ''), { fallthrough: true })); -app.use('/media/audio/station', express.static(String(audioStationDir || ''), { fallthrough: true })); -app.use('/media/temp', express.static(String(tempBroadcastDir || ''), { fallthrough: true })); - -app.get('/media/audio/general/:path(*)', (req, res) => { - const rel = decodeURIComponent(req.params.path || ''); - const candidates = getBeepAudioDirs().map(dir => safeRelativeToBase(dir, rel)).filter(Boolean); - sendBinaryFileFromCandidates(res, candidates); +app.use('/media/audio/music', (req, res, next) => { + if (req.method && String(req.method).toUpperCase() !== 'GET') return next(); + const rel = decodeURIComponent(String(req.path || '/').replace(/^\/+/, '')); + const base = String(audioMusicDir || ''); + express.static(base, { fallthrough: true })(req, res, () => { + const full = base ? safeRelativeToBase(base, rel) : null; + if (!full) return res.status(404).end(); + sendBinaryFile(res, full); + }); }); -app.get('/media/audio/beep/:path(*)', (req, res) => { - const rel = decodeURIComponent(req.params.path || ''); - const candidates = getBeepAudioDirs().map(dir => safeRelativeToBase(dir, rel)).filter(Boolean); - sendBinaryFileFromCandidates(res, candidates); +app.use('/media/audio/voice', (req, res, next) => { + if (req.method && String(req.method).toUpperCase() !== 'GET') return next(); + const rel = decodeURIComponent(String(req.path || '/').replace(/^\/+/, '')); + const base = String(audioVoiceDir || ''); + express.static(base, { fallthrough: true })(req, res, () => { + const full = base ? safeRelativeToBase(base, rel) : null; + if (!full) return res.status(404).end(); + sendBinaryFile(res, full); + }); }); -app.get('/media/audio/music/:path(*)', (req, res) => { - const rel = decodeURIComponent(req.params.path || ''); - const full = safeRelativeToBase(audioMusicDir, rel); - if (!full) return res.status(404).end(); - sendBinaryFile(res, full); +app.use('/media/audio/line', (req, res, next) => { + if (req.method && String(req.method).toUpperCase() !== 'GET') return next(); + const rel = decodeURIComponent(String(req.path || '/').replace(/^\/+/, '')); + const base = String(audioLineDir || ''); + express.static(base, { fallthrough: true })(req, res, () => { + const full = base ? safeRelativeToBase(base, rel) : null; + if (!full) return res.status(404).end(); + sendBinaryFile(res, full); + }); }); -app.get('/media/audio/voice/:path(*)', (req, res) => { - const rel = decodeURIComponent(req.params.path || ''); - const full = safeRelativeToBase(audioVoiceDir, rel); - if (!full) return res.status(404).end(); - sendBinaryFile(res, full); +app.use('/media/audio/station', (req, res, next) => { + if (req.method && String(req.method).toUpperCase() !== 'GET') return next(); + const rel = decodeURIComponent(String(req.path || '/').replace(/^\/+/, '')); + const base = String(audioStationDir || ''); + express.static(base, { fallthrough: true })(req, res, () => { + const full = base ? safeRelativeToBase(base, rel) : null; + if (!full) return res.status(404).end(); + sendBinaryFile(res, full); + }); }); -app.get('/media/audio/line/:path(*)', (req, res) => { - const rel = decodeURIComponent(req.params.path || ''); - const full = safeRelativeToBase(audioLineDir, rel); - if (!full) return res.status(404).end(); - sendBinaryFile(res, full); -}); -app.get('/media/audio/station/:path(*)', (req, res) => { - const rel = decodeURIComponent(req.params.path || ''); - const full = safeRelativeToBase(audioStationDir, rel); - if (!full) return res.status(404).end(); - sendBinaryFile(res, full); -}); -app.get('/media/temp/:path(*)', (req, res) => { - const rel = decodeURIComponent(req.params.path || ''); - const full = safeRelativeToBase(tempBroadcastDir, rel); - if (!full) return res.status(404).end(); - sendBinaryFile(res, full); +app.use('/media/temp', (req, res, next) => { + if (req.method && String(req.method).toUpperCase() !== 'GET') return next(); + const rel = decodeURIComponent(String(req.path || '/').replace(/^\/+/, '')); + const base = String(tempBroadcastDir || ''); + express.static(base, { fallthrough: true })(req, res, () => { + const full = base ? safeRelativeToBase(base, rel) : null; + if (!full) return res.status(404).end(); + sendBinaryFile(res, full); + }); }); app.get('/media', (req, res) => { diff --git a/station_broadcast.lua b/station_broadcast.lua index 5cc8b1d..ac0b432 100644 --- a/station_broadcast.lua +++ b/station_broadcast.lua @@ -1,6 +1,6 @@ -- 1. Configuration & Constants -local APP_VERSION_TEXT = "v1.0.103" +local APP_VERSION_TEXT = "v1.0.104" local CONFIG = { CLOUD_URL = "ws://stabadmin.fse-media.group",