From 27b801ea3d653b7d6489f1149ff01cdcd5008c92 Mon Sep 17 00:00:00 2001 From: Snowz <372492339@qq.com> Date: Sun, 25 Jan 2026 03:09:00 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=9C=AA=E5=A4=84?= =?UTF-8?q?=E7=90=86=E7=9A=84Promise=E6=8B=92=E7=BB=9D=E5=92=8C=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E6=B5=81=E9=94=99=E8=AF=AF=E5=AF=BC=E8=87=B4=E7=9A=84?= =?UTF-8?q?=E8=BF=9B=E7=A8=8B=E5=B4=A9=E6=BA=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加全局异常捕获(uncaughtException, unhandledRejection)防止进程意外退出 - 修复请求合并逻辑中Promise链处理不当导致的Unhandled Promise Rejection - 为文件读取流添加错误监听,防止文件系统异常导致进程崩溃 - 更新README.md文档以反映稳定性修复 - 将*.log添加到.gitignore忽略日志文件 --- .gitignore | 3 ++- README.md | 6 +++++- server.js | 28 ++++++++++++++++++++++------ 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 18cd82b..0302272 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ .env -cache/ \ No newline at end of file +cache/ +*.log \ No newline at end of file diff --git a/README.md b/README.md index ce0a790..607e17f 100644 --- a/README.md +++ b/README.md @@ -58,4 +58,8 @@ - 用户体验优化:当在浏览器中直接访问 API (Accept: text/html) 时,返回一个带有加载动画的 HTML 页面,解决等待过程中的白屏问题。 - 并发健壮性提升: - 实现 **请求合并 (Request Coalescing)**:当多个客户端同时请求同一个未缓存的 URL 时,复用同一个回源请求,避免瞬间高并发流量击穿上游 (Thundering Herd)。 - - 实现 **原子化缓存写入 (Atomic Write)**:使用“写临时文件 + 重命名”策略,确保缓存文件在写入过程中不会被读取到不完整的数据,彻底解决并发读写导致的文件损坏问题。 + - 实现 **原子化缓存写入 (Atomic Write)**:使用“写临时文件 + 重命名”策略,确保缓存文件在写入过程中不会被读取到不完整的数据,彻底解决并发读写导致的文件损坏问题。 + - **稳定性修复**: + - 修复了在请求合并逻辑中因 Promise 链处理不当导致的 `Unhandled Promise Rejection` 崩溃问题。 + - 增加了文件流读取的错误监听,防止因文件系统异常导致的进程退出。 + - 增加了全局异常捕获 (`uncaughtException`, `unhandledRejection`),确保服务在极端异常下记录日志而不崩溃。 diff --git a/server.js b/server.js index 9a27097..fa5549a 100644 --- a/server.js +++ b/server.js @@ -6,6 +6,15 @@ const path = require('path'); const crypto = require('crypto'); const net = require('net'); +// 全局错误捕获,防止进程退出 +process.on('uncaughtException', (err) => { + console.error('[FATAL] Uncaught Exception:', err); +}); + +process.on('unhandledRejection', (reason, promise) => { + console.error('[FATAL] Unhandled Rejection:', reason); +}); + require('dotenv').config(); const app = express(); const PORT = Number(process.env.PORT) || 11489; @@ -301,6 +310,10 @@ async function handleProxyRequest(res, upstreamUrl, targetUrl) { } const stream = fs.createReadStream(dataPath); + stream.on('error', (streamErr) => { + console.error(`[stream-error] ${streamErr.message}`); + if (!res.headersSent) res.end(); + }); stream.pipe(res); return; } else { @@ -319,13 +332,12 @@ async function handleProxyRequest(res, upstreamUrl, targetUrl) { console.log(`[coalesce-hit] joining pending request for ${upstreamUrl}`); resultPromise = pendingRequests.get(key); } else { - resultPromise = fetchAndCache(upstreamUrl, targetUrl, key); + // 创建 Promise 链,确保 finally 包含在内,防止 Unhandled Rejection + resultPromise = fetchAndCache(upstreamUrl, targetUrl, key) + .finally(() => { + pendingRequests.delete(key); + }); pendingRequests.set(key, resultPromise); - - // 无论成功失败,结束后移除 map - resultPromise.finally(() => { - pendingRequests.delete(key); - }); } const result = await resultPromise; @@ -353,6 +365,10 @@ async function handleProxyRequest(res, upstreamUrl, targetUrl) { res.set('Cache-Control', 'public, max-age=315360000, immutable'); res.type(meta.contentType); const stream = fs.createReadStream(dataPath); + stream.on('error', (streamErr) => { + console.error(`[stream-error] ${streamErr.message}`); + if (!res.headersSent) res.end(); + }); stream.pipe(res); return; }