From 3c32d41347f180dcc3ac8324a4e16b6e24402837 Mon Sep 17 00:00:00 2001 From: Xc Date: Fri, 9 Jan 2026 16:19:34 +0800 Subject: [PATCH 1/3] 4.0.32-dev --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index ba22a17..c1e7e31 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@xuchangzju/oak-cli", - "version": "4.0.31", + "version": "4.0.32", "description": "client for oak framework", "main": "lib/index.js", "scripts": { @@ -112,9 +112,9 @@ "lodash": "^4.17.21", "mini-css-extract-plugin": "^2.5.3", "node-watch": "^0.7.4", - "oak-backend-base": "^4.1.26", - "oak-domain": "^5.1.33", - "oak-frontend-base": "^5.3.45", + "oak-backend-base": "file:../oak-backend-base", + "oak-domain": "file:../oak-domain", + "oak-frontend-base": "file:../oak-frontend-base", "parse-asn1": "5.1.6", "postcss": "^8.4.4", "postcss-flexbugs-fixes": "^5.0.2", From 89fe961434716cc969a1d124ed1d19945b62695d Mon Sep 17 00:00:00 2001 From: qcqcqc <1220204124@zust.edu.cn> Date: Thu, 15 Jan 2026 14:50:52 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=9C=A8watch?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E4=B8=8B=E4=BC=9A=E9=87=8D=E5=A4=8D=E6=B3=A8?= =?UTF-8?q?=E5=86=8C=E5=AE=9A=E6=97=B6=E5=99=A8=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/server/timer-manager.d.ts | 45 ++++++++ lib/server/timer-manager.js | 162 ++++++++++++++++++++++++++ lib/server/watch.js | 4 + src/server/timer-manager.ts | 211 ++++++++++++++++++++++++++++++++++ src/server/watch.ts | 5 + 5 files changed, 427 insertions(+) create mode 100644 lib/server/timer-manager.d.ts create mode 100644 lib/server/timer-manager.js create mode 100644 src/server/timer-manager.ts diff --git a/lib/server/timer-manager.d.ts b/lib/server/timer-manager.d.ts new file mode 100644 index 0000000..4714d23 --- /dev/null +++ b/lib/server/timer-manager.d.ts @@ -0,0 +1,45 @@ +type TimerType = 'timeout' | 'interval' | 'immediate'; +interface TimerRecord { + id: NodeJS.Timeout | NodeJS.Immediate; + type: TimerType; + createdAt: number; +} +declare class GlobalTimerManager { + private timers; + private isHooked; + private readonly original; + /** + * 开始拦截全局定时器 + */ + hook(): void; + /** + * 恢复原始的全局定时器函数 + */ + unhook(): void; + /** + * 清除所有被追踪的定时器 + */ + clearAll(): number; + /** + * 按类型清除定时器 + */ + clearByType(type: TimerType): number; + /** + * 获取当前活跃的定时器数量 + */ + getActiveCount(): number; + /** + * 获取定时器统计信息 + */ + getStats(): Record; + /** + * 获取所有定时器的详细信息(用于调试) + */ + getTimers(): TimerRecord[]; +} +export declare const timerManager: GlobalTimerManager; +export declare const hookTimers: () => void; +export declare const unhookTimers: () => void; +export declare const clearAllTimers: () => number; +export declare const getTimerStats: () => Record; +export {}; diff --git a/lib/server/timer-manager.js b/lib/server/timer-manager.js new file mode 100644 index 0000000..c8dfa5e --- /dev/null +++ b/lib/server/timer-manager.js @@ -0,0 +1,162 @@ +"use strict"; +// timer-manager.ts +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getTimerStats = exports.clearAllTimers = exports.unhookTimers = exports.hookTimers = exports.timerManager = void 0; +class GlobalTimerManager { + timers = new Map(); + isHooked = false; + // 保存原始函数 + original = { + setTimeout: global.setTimeout, + setInterval: global.setInterval, + setImmediate: global.setImmediate, clearTimeout: global.clearTimeout, + clearInterval: global.clearInterval, + clearImmediate: global.clearImmediate, + }; + /** + * 开始拦截全局定时器 + */ + hook() { + if (this.isHooked) { + console.warn('[TimerManager] Already hooked'); + return; + } + const self = this; + // Hook setTimeout + global.setTimeout = function (callback, ms, ...args) { + const id = self.original.setTimeout((...callbackArgs) => { + self.timers.delete(id); // 执行完后移除 + callback(...callbackArgs); + }, ms, ...args); + self.timers.set(id, { + id, + type: 'timeout', + createdAt: Date.now(), + }); + return id; + }; + // Hook setInterval + global.setInterval = function (callback, ms, ...args) { + const id = self.original.setInterval(callback, ms, ...args); + self.timers.set(id, { + id, + type: 'interval', + createdAt: Date.now(), + }); + return id; + }; + // Hook setImmediate + global.setImmediate = function (callback, ...args) { + const id = self.original.setImmediate((...callbackArgs) => { + self.timers.delete(id); // 执行完后移除 + callback(...callbackArgs); + }, ...args); + self.timers.set(id, { + id, + type: 'immediate', + createdAt: Date.now(), + }); + return id; + }; + // Hook clear方法(确保从追踪中移除) + global.clearTimeout = ((id) => { + self.timers.delete(id); + return self.original.clearTimeout(id); + }); + global.clearInterval = ((id) => { + self.timers.delete(id); + return self.original.clearInterval(id); + }); + global.clearImmediate = ((id) => { + self.timers.delete(id); + return self.original.clearImmediate(id); + }); + this.isHooked = true; + console.log('[TimerManager] Hooked global timer functions'); + } + /** + * 恢复原始的全局定时器函数 + */ + unhook() { + if (!this.isHooked) { + return; + } + global.setTimeout = this.original.setTimeout; + global.setInterval = this.original.setInterval; + global.setImmediate = this.original.setImmediate; + global.clearTimeout = this.original.clearTimeout; + global.clearInterval = this.original.clearInterval; + global.clearImmediate = this.original.clearImmediate; + this.isHooked = false; + } + /** + * 清除所有被追踪的定时器 + */ + clearAll() { + const count = this.timers.size; + this.timers.forEach((record) => { + if (record.type === 'immediate') { + this.original.clearImmediate.call(null, record.id); + } + else { + this.original.clearTimeout.call(null, record.id); + } + }); + this.timers.clear(); + return count; + } + /** + * 按类型清除定时器 + */ + clearByType(type) { + let count = 0; + this.timers.forEach((record, id) => { + if (record.type === type) { + if (type === 'immediate') { + this.original.clearImmediate.call(null, id); + } + else { + this.original.clearTimeout.call(null, id); + } + this.timers.delete(id); + count++; + } + }); + return count; + } + /** + * 获取当前活跃的定时器数量 + */ + getActiveCount() { + return this.timers.size; + } + /** + * 获取定时器统计信息 + */ + getStats() { + const stats = { + timeout: 0, + interval: 0, + immediate: 0, + }; + this.timers.forEach((record) => { + stats[record.type]++; + }); + return stats; + } + /** + * 获取所有定时器的详细信息(用于调试) + */ + getTimers() { + return Array.from(this.timers.values()); + } +} +exports.timerManager = new GlobalTimerManager(); +const hookTimers = () => exports.timerManager.hook(); +exports.hookTimers = hookTimers; +const unhookTimers = () => exports.timerManager.unhook(); +exports.unhookTimers = unhookTimers; +const clearAllTimers = () => exports.timerManager.clearAll(); +exports.clearAllTimers = clearAllTimers; +const getTimerStats = () => exports.timerManager.getStats(); +exports.getTimerStats = getTimerStats; diff --git a/lib/server/watch.js b/lib/server/watch.js index d0dae67..67209ec 100644 --- a/lib/server/watch.js +++ b/lib/server/watch.js @@ -9,6 +9,7 @@ const dayjs_1 = tslib_1.__importDefault(require("dayjs")); const fs_1 = tslib_1.__importDefault(require("fs")); const polyfill_1 = require("./polyfill"); const tsc_alias_1 = require("tsc-alias"); +const timer_manager_1 = require("./timer-manager"); // 创建事件发射器 const createEventEmitter = () => { const listeners = new Map(); @@ -228,6 +229,7 @@ const createServerManager = (projectPath, eventEmitter, config) => { const simpleConnector = require(path_1.default.join(projectPath, "lib/config/connector")).default; console.warn("----> Starting service......"); try { + (0, timer_manager_1.clearAllTimers)(); // 这里注意要在require之前,因为require会触发编译 const { startup } = require('./start'); shutdown = await startup(pwd, simpleConnector).then((shutdown) => { @@ -606,6 +608,8 @@ const watch = async (projectPath, config) => { // 初始化polyfill polyfillLoader(); realConfig.polyfill.console.enable && (0, polyfill_1.polyfillConsole)("watch", enableTrace, realConfig.polyfill?.console?.formatter); + // 监听定时器相关的API,确保定时器在重启后不会重复触发 + (0, timer_manager_1.hookTimers)(); // 初始编译检查 const initialCompile = () => { const serverConfigFile = path_1.default.join(projectPath, "lib/configuration/server.js"); diff --git a/src/server/timer-manager.ts b/src/server/timer-manager.ts new file mode 100644 index 0000000..e49b24f --- /dev/null +++ b/src/server/timer-manager.ts @@ -0,0 +1,211 @@ +// timer-manager.ts + +type TimerType = 'timeout' | 'interval' | 'immediate'; + +interface TimerRecord { + id: NodeJS.Timeout | NodeJS.Immediate; + type: TimerType; + createdAt: number; +} + +class GlobalTimerManager { + private timers = new Map(); + private isHooked = false; + + // 保存原始函数 + private readonly original = { + setTimeout: global.setTimeout, + setInterval: global.setInterval, + setImmediate: global.setImmediate,clearTimeout: global.clearTimeout, + clearInterval: global.clearInterval, + clearImmediate: global.clearImmediate,}; + + /** + * 开始拦截全局定时器 + */ + hook(): void { + if (this.isHooked) { + console.warn('[TimerManager] Already hooked'); + return; + } + + const self = this; + + // Hook setTimeout + global.setTimeout = function ( + callback: (...args: any[]) => void, + ms?: number, + ...args: any[] + ): any { + const id = self.original.setTimeout( + (...callbackArgs: any[]) => { + self.timers.delete(id); // 执行完后移除 + callback(...callbackArgs); + }, + ms, + ...args + ); + + self.timers.set(id, { + id, + type: 'timeout', + createdAt: Date.now(), + }); + + return id; + } as any; + + // Hook setInterval + global.setInterval = function ( + callback: (...args: any[]) => void, + ms?: number, + ...args: any[] + ): any { + const id = self.original.setInterval(callback, ms, ...args); + + self.timers.set(id, { + id, + type: 'interval', + createdAt: Date.now(), + }); + + return id; + } as any; + + // Hook setImmediate + global.setImmediate = function ( + callback: (...args: any[]) => void, + ...args: any[] + ): any { + const id = self.original.setImmediate( + (...callbackArgs: any[]) => { + self.timers.delete(id); // 执行完后移除 + callback(...callbackArgs); + }, + ...args + ); + + self.timers.set(id, { + id, + type: 'immediate', + createdAt: Date.now(), + }); + + return id; + } as any; + + // Hook clear方法(确保从追踪中移除) + global.clearTimeout = ((id: any) => { + self.timers.delete(id); + return self.original.clearTimeout(id); + }) as any; + + global.clearInterval = ((id: any) => { + self.timers.delete(id); + return self.original.clearInterval(id); + }) as any; + + global.clearImmediate = ((id: any) => { + self.timers.delete(id); + return self.original.clearImmediate(id); + }) as any; + + this.isHooked = true; + console.log('[TimerManager] Hooked global timer functions'); + } + + /** + * 恢复原始的全局定时器函数 + */ + unhook(): void { + if (!this.isHooked) { + return; + } + + global.setTimeout = this.original.setTimeout; + global.setInterval = this.original.setInterval; + global.setImmediate = this.original.setImmediate; + global.clearTimeout = this.original.clearTimeout; + global.clearInterval = this.original.clearInterval; + global.clearImmediate = this.original.clearImmediate; + + this.isHooked = false; + } + + /** + * 清除所有被追踪的定时器 + */ + clearAll(): number { + const count = this.timers.size; + + this.timers.forEach((record) => { + if (record.type === 'immediate') { + this.original.clearImmediate.call(null, record.id as NodeJS.Immediate); + } else { + this.original.clearTimeout.call(null, record.id as NodeJS.Timeout); + } + }); + + this.timers.clear(); + return count; + } + + /** + * 按类型清除定时器 + */ + clearByType(type: TimerType): number { + let count = 0; + + this.timers.forEach((record, id) => { + if (record.type === type) { + if (type === 'immediate') { + this.original.clearImmediate.call(null, id as NodeJS.Immediate); + } else { + this.original.clearTimeout.call(null, id as NodeJS.Timeout); + } + this.timers.delete(id); + count++; + } + }); + + return count; + } + + /** + * 获取当前活跃的定时器数量 + */ + getActiveCount(): number { + return this.timers.size; + } + + /** + * 获取定时器统计信息 + */ + getStats(): Record { + const stats: Record = { + timeout: 0, + interval: 0, + immediate: 0, + }; + + this.timers.forEach((record) => { + stats[record.type]++; + }); + + return stats; + } + + /** + * 获取所有定时器的详细信息(用于调试) + */ + getTimers(): TimerRecord[] { + return Array.from(this.timers.values()); + } +} + +export const timerManager = new GlobalTimerManager(); + +export const hookTimers = () => timerManager.hook(); +export const unhookTimers = () => timerManager.unhook(); +export const clearAllTimers = () => timerManager.clearAll(); +export const getTimerStats = () => timerManager.getStats(); \ No newline at end of file diff --git a/src/server/watch.ts b/src/server/watch.ts index dfed001..de75ddc 100644 --- a/src/server/watch.ts +++ b/src/server/watch.ts @@ -7,6 +7,7 @@ import fs from "fs"; import { AsyncContext } from "oak-domain/lib/store/AsyncRowStore"; import { LogFormatter, LogFormatterProp, polyfillConsole } from "./polyfill"; import { prepareSingleFileReplaceTscAliasPaths, SingleFileReplacer } from 'tsc-alias'; +import { clearAllTimers, hookTimers } from "./timer-manager"; /* * 工作流程 @@ -399,6 +400,7 @@ const createServerManager = ( console.warn("----> Starting service......"); try { + clearAllTimers(); // 这里注意要在require之前,因为require会触发编译 const { startup } = require('./start') as { startup: (pwd: string, connector: any) => Promise<() => Promise>; @@ -879,6 +881,9 @@ export const watch = async ( polyfillLoader(); realConfig.polyfill.console.enable && polyfillConsole("watch", enableTrace, realConfig.polyfill?.console?.formatter); + // 监听定时器相关的API,确保定时器在重启后不会重复触发 + hookTimers() + // 初始编译检查 const initialCompile = () => { const serverConfigFile = pathLib.join(projectPath, "lib/configuration/server.js"); From 96d4337b5989f42eb0153aef8509d7764c0df680 Mon Sep 17 00:00:00 2001 From: qcqcqc <1220204124@zust.edu.cn> Date: Thu, 22 Jan 2026 10:42:11 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20=E5=9C=A8=E5=A4=84=E7=90=86=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E7=9A=84=E4=B8=AD=E9=97=B4=E4=BB=B6=E4=B8=AD=E6=9C=AA?= =?UTF-8?q?=E6=AD=A3=E7=A1=AE=E5=A4=84=E7=90=86serializeException=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=E7=9A=84headers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/server/start.js | 6 +++++- src/server/start.ts | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/server/start.js b/lib/server/start.js index fded2be..4a85b66 100644 --- a/lib/server/start.js +++ b/lib/server/start.js @@ -204,8 +204,12 @@ async function startup(path, connector, omitWatchers, omitTimers, routine) { ? err : new types_1.OakException(serverConfiguration?.internalExceptionMask || ExceptionMask); - const { body } = connector.serializeException(exception, request.headers, request.body); + const { body, headers } = connector.serializeException(exception, request.headers, request.body); ctx.response.body = body; + // headers 要拼上 + Object.keys(headers || {}).forEach(key => { + ctx.set(key, headers?.[key]); + }); return; } }); diff --git a/src/server/start.ts b/src/server/start.ts index 126bcd9..541098f 100644 --- a/src/server/start.ts +++ b/src/server/start.ts @@ -251,12 +251,16 @@ export async function startup { + ctx.set(key, headers?.[key]) + }) return; } });