fix(server): 彻底放弃 :path(*) 等参数化通配路由,改用 app.use + req.path 手动截取,解决 path-to-regexp v8.3 启动崩溃

服务器环境 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
This commit is contained in:
2026-08-17 15:49:59 +08:00
parent a6d9b6c418
commit b2c415f253
2 changed files with 60 additions and 46 deletions
+59 -45
View File
@@ -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) => {