Browse Source

chore(instrumentation): skip initialization in CI environment

- Added checks to skip database migrations, price seeding, and queue scheduling when running in a CI environment.
- Updated AsyncTaskManager to avoid unnecessary initialization during CI/build phases.
- Implemented lazy loading for Redis client to prevent connection attempts during CI/build.
ding113 3 months ago
parent
commit
7dfb547ffc
4 changed files with 31 additions and 6 deletions
  1. 8 0
      src/instrumentation.ts
  2. 14 5
      src/lib/async-task-manager.ts
  3. 4 1
      src/lib/rate-limit/service.ts
  4. 5 0
      src/lib/redis/client.ts

+ 8 - 0
src/instrumentation.ts

@@ -8,6 +8,14 @@ import { logger } from "@/lib/logger";
 export async function register() {
   // 仅在服务器端执行
   if (process.env.NEXT_RUNTIME === "nodejs") {
+    // Skip initialization in CI environment (no DB connection needed)
+    if (process.env.CI === "true") {
+      logger.warn(
+        "[Instrumentation] CI environment detected: skipping DB migrations, price seeding and queue scheduling"
+      );
+      return;
+    }
+
     // 生产环境: 执行完整初始化(迁移 + 价格表 + 清理任务 + 通知任务)
     if (process.env.NODE_ENV === "production" && process.env.AUTO_MIGRATE !== "false") {
       const { checkDatabaseConnection, runMigrations } = await import("@/lib/migrate");

+ 14 - 5
src/lib/async-task-manager.ts

@@ -28,6 +28,12 @@ class AsyncTaskManagerClass {
   private cleanupInterval: NodeJS.Timeout | null = null;
 
   constructor() {
+    // Skip initialization during CI/build phase to avoid unnecessary logs and side effects
+    if (process.env.CI === "true" || process.env.NEXT_PHASE === "phase-production-build") {
+      logger.debug("[AsyncTaskManager] Skipping initialization in CI/build environment");
+      return;
+    }
+
     // 定义统一的清理处理器
     const exitHandler = (signal: string) => {
       logger.info(`[AsyncTaskManager] Received ${signal}, cleaning up all tasks`, {
@@ -37,9 +43,10 @@ class AsyncTaskManagerClass {
     };
 
     // 监听所有退出信号(确保 Docker 环境下优雅关闭)
-    process.on("SIGTERM", () => exitHandler("SIGTERM")); // Docker stop
-    process.on("SIGINT", () => exitHandler("SIGINT")); // Ctrl+C
-    process.on("beforeExit", () => exitHandler("beforeExit")); // 正常退出
+    // 使用 once 而非 on,避免重复注册(特别是热重载场景)
+    process.once("SIGTERM", () => exitHandler("SIGTERM")); // Docker stop
+    process.once("SIGINT", () => exitHandler("SIGINT")); // Ctrl+C
+    process.once("beforeExit", () => exitHandler("beforeExit")); // 正常退出
 
     // 每分钟检查并清理超时任务(>10 分钟未完成,防止内存泄漏)
     this.cleanupInterval = setInterval(() => {
@@ -220,5 +227,7 @@ class AsyncTaskManagerClass {
   }
 }
 
-// 导出单例
-export const AsyncTaskManager = new AsyncTaskManagerClass();
+// 导出单例(使用 globalThis 缓存避免热重载时重复实例化)
+const g = globalThis as unknown as { __ASYNC_TASK_MANAGER__?: AsyncTaskManagerClass };
+export const AsyncTaskManager =
+  g.__ASYNC_TASK_MANAGER__ ?? (g.__ASYNC_TASK_MANAGER__ = new AsyncTaskManagerClass());

+ 4 - 1
src/lib/rate-limit/service.ts

@@ -16,7 +16,10 @@ interface CostLimit {
 }
 
 export class RateLimitService {
-  private static redis = getRedisClient();
+  // 使用 getter 实现懒加载,避免模块加载时立即连接 Redis(构建阶段触发)
+  private static get redis() {
+    return getRedisClient();
+  }
 
   /**
    * 检查金额限制(Key 或 Provider)

+ 5 - 0
src/lib/redis/client.ts

@@ -4,6 +4,11 @@ import { logger } from "@/lib/logger";
 let redisClient: Redis | null = null;
 
 export function getRedisClient(): Redis | null {
+  // Skip Redis connection during CI/build phase (avoid connection attempts)
+  if (process.env.CI === "true" || process.env.NEXT_PHASE === "phase-production-build") {
+    return null;
+  }
+
   const redisUrl = process.env.REDIS_URL;
   const isEnabled = process.env.ENABLE_RATE_LIMIT === "true";