gongxiangp2/instrumentation.ts

144 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Next.js Instrumentation
*
* 这个文件在应用启动时执行,用于:
* 1. 启动临时文件定时清理任务
* 2. 初始化其他需要在启动时运行的服务
*/
export async function register() {
// 只在 Node.js 服务端运行(不在 Edge 运行时)
if (process.env.NEXT_RUNTIME === 'nodejs') {
console.log('[Instrumentation] Next.js 服务端启动...')
// 启动临时文件清理任务
try {
const { startCleanupScheduler } = await import('./lib/temp-cleanup')
startCleanupScheduler()
console.log('[Instrumentation] 临时文件清理任务已启动')
} catch (error) {
console.error('[Instrumentation] 启动清理任务失败:', error)
}
const recoveryGlobal = globalThis as typeof globalThis & {
__taskDetailUploadRecoveryStarted?: boolean
}
if (!recoveryGlobal.__taskDetailUploadRecoveryStarted) {
recoveryGlobal.__taskDetailUploadRecoveryStarted = true
// 先注册周期任务,再做首次恢复。数据库启动瞬时不可用时,后续周期仍会重试,
// 不会因为一次失败让 processing/queued 任务永久无人处理。
setInterval(() => {
void import('./lib/task-detail-upload-jobs')
.then(({ recoverTaskDetailUploadJobs }) => recoverTaskDetailUploadJobs())
.catch((error) => {
console.error('[Instrumentation] 任务-详情后台队列周期恢复失败:', error)
})
}, 60_000)
}
try {
const { recoverTaskDetailUploadJobs } = await import('./lib/task-detail-upload-jobs')
await recoverTaskDetailUploadJobs()
console.log('[Instrumentation] 任务-详情后台队列恢复检查完成')
} catch (error) {
// 迁移未执行或数据库暂不可用时只记录;周期恢复已提前注册,会继续重试。
console.error('[Instrumentation] 任务-详情后台队列恢复失败:', error)
}
const videoRecoveryGlobal = globalThis as typeof globalThis & {
__videoTranscodeRecoveryScheduler?: {
initial: Promise<number>
}
}
if (!videoRecoveryGlobal.__videoTranscodeRecoveryScheduler) {
const { createVideoTranscodeRecoveryScheduler } = await import('./lib/video-transcode-recovery')
const scheduler = createVideoTranscodeRecoveryScheduler()
videoRecoveryGlobal.__videoTranscodeRecoveryScheduler = scheduler
try {
const recovered = await scheduler.initial
console.log(`[Instrumentation] 视频转码启动恢复完成,重新入队 ${recovered} 个任务`)
} catch (error) {
// 周期调度器已先注册;数据库瞬时不可用时,60 秒后会继续重试。
console.error('[Instrumentation] 视频转码启动恢复失败,已保留周期重试:', error)
}
}
try {
const { ensureWorkReviewNotificationWorkerStarted } = await import('./lib/work-review-notifications')
ensureWorkReviewNotificationWorkerStarted()
console.log('[Instrumentation] 作品过稿通知与每小时提醒任务已启动')
} catch (error) {
console.error('[Instrumentation] 启动作作品过稿通知任务失败:', error)
}
try {
const { ensureTaskDetailChangeNotificationWorkerStarted } = await import('./lib/task-detail-change-notifications')
const {
expireDueTaskDetailChanges,
recoverMissingTaskDetailChangeRequestNotifications,
recoverStaleApplyingTaskDetailChanges,
} = await import('./lib/task-detail-change-approval')
ensureTaskDetailChangeNotificationWorkerStarted()
const approvalGlobal = globalThis as typeof globalThis & {
__taskDetailChangeExpiryStarted?: boolean
}
if (!approvalGlobal.__taskDetailChangeExpiryStarted) {
approvalGlobal.__taskDetailChangeExpiryStarted = true
setInterval(() => {
void expireDueTaskDetailChanges().catch((error) => {
console.error('[Instrumentation] 图片变更审批超时扫描失败:', error)
})
void recoverMissingTaskDetailChangeRequestNotifications().catch((error) => {
console.error('[Instrumentation] 图片变更审批缺失通知补偿失败:', error)
})
void recoverStaleApplyingTaskDetailChanges().catch((error) => {
console.error('[Instrumentation] 图片变更审批应用中断恢复失败:', error)
})
}, 60_000).unref?.()
}
void expireDueTaskDetailChanges().catch((error) => {
console.error('[Instrumentation] 图片变更审批首次超时扫描失败:', error)
})
void recoverMissingTaskDetailChangeRequestNotifications().catch((error) => {
console.error('[Instrumentation] 图片变更审批首次缺失通知补偿失败:', error)
})
void recoverStaleApplyingTaskDetailChanges().catch((error) => {
console.error('[Instrumentation] 图片变更审批首次应用中断恢复失败:', error)
})
console.log('[Instrumentation] 图片变更审批通知与超时否决任务已启动')
} catch (error) {
console.error('[Instrumentation] 启动图片变更审批任务失败:', error)
}
// 组织架构/在职状态/离职联动每日同步:应用启动即调度(每日 02:00 Asia/Shanghai),
// 不再依赖用户访问考核调研懒启动,容器重启后自动恢复。
try {
const { ensureDailyOrganizationSyncStarted } = await import('./lib/organization-sync')
ensureDailyOrganizationSyncStarted()
console.log('[Instrumentation] 组织架构每日同步任务已调度(每日 02:00 Asia/Shanghai)')
} catch (error) {
console.error('[Instrumentation] 调度组织架构每日同步任务失败:', error)
}
// 钉钉审批流程快照:启动后建立基线,之后每小时增量同步。
// 看板只读本地 PostgreSQL 快照,避免用户请求直接扫描上游无索引大表。
try {
const { ensureHourlyDingWorkflowSyncStarted } = await import('./lib/ding-workflow-sync')
ensureHourlyDingWorkflowSyncStarted()
console.log('[Instrumentation] 钉钉流程每小时增量同步任务已调度')
} catch (error) {
console.error('[Instrumentation] 调度钉钉流程每小时同步任务失败:', error)
}
// 作品过稿后台是首轮通过指标的唯一权威源;应用启动后增量检查项目、
// V1 版本和评审记录,避免看板退化为只在有人打开时才知道数据是否更新。
try {
const { ensureHourlyWorkReviewDashboardSyncStarted } = await import('./lib/work-review-dashboard-sync')
ensureHourlyWorkReviewDashboardSyncStarted()
console.log('[Instrumentation] 作品过稿后台每小时增量检查任务已调度')
} catch (error) {
console.error('[Instrumentation] 调度作品过稿每小时增量检查任务失败:', error)
}
}
}