deploy-ease-platform/frontend/src/services/maintenanceDetector.ts
2025-11-20 13:52:03 +08:00

185 lines
5.0 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.

/**
* 维护模式检测服务
*
* 功能:
* 1. 检测后端服务是否可用
* 2. 连续失败达到阈值时判定为维护模式
* 3. 自动轮询检测服务恢复
*/
class MaintenanceDetector {
private failureCount = 0;
private readonly FAILURE_THRESHOLD = 3; // 连续失败3次判定为维护
private readonly RESET_INTERVAL = 60000; // 60秒后重置计数
private resetTimer: NodeJS.Timeout | null = null;
private recoveryCheckTimer: NodeJS.Timeout | null = null;
/**
* 记录请求失败
*/
recordFailure() {
this.failureCount++;
console.log(`[MaintenanceDetector] 请求失败次数: ${this.failureCount}/${this.FAILURE_THRESHOLD}`);
// 清除之前的重置计时器
if (this.resetTimer) {
clearTimeout(this.resetTimer);
}
// 60秒后自动重置计数避免误判网络抖动
this.resetTimer = setTimeout(() => {
console.log('[MaintenanceDetector] 重置失败计数');
this.failureCount = 0;
}, this.RESET_INTERVAL);
// 达到阈值,判定为维护模式
if (this.failureCount >= this.FAILURE_THRESHOLD) {
this.enterMaintenanceMode();
}
}
/**
* 记录请求成功
*/
recordSuccess() {
if (this.failureCount > 0) {
console.log('[MaintenanceDetector] 请求成功,重置失败计数');
this.failureCount = 0;
}
// 清除重置计时器
if (this.resetTimer) {
clearTimeout(this.resetTimer);
this.resetTimer = null;
}
}
/**
* 进入维护模式
*/
private enterMaintenanceMode() {
console.log('[MaintenanceDetector] ========== 进入维护模式 ==========');
console.log('[MaintenanceDetector] 当前路径:', window.location.pathname);
// 标记维护状态到 sessionStorage
sessionStorage.setItem('maintenance_mode', 'true');
sessionStorage.setItem('maintenance_start_time', Date.now().toString());
console.log('[MaintenanceDetector] sessionStorage 已设置');
// 跳转到维护页面
if (window.location.pathname !== '/maintenance') {
console.log('[MaintenanceDetector] 准备跳转到 /maintenance');
console.log('[MaintenanceDetector] 使用 window.location.replace 跳转...');
// 使用 setTimeout 确保跳转执行
setTimeout(() => {
window.location.replace('/maintenance');
}, 100);
} else {
console.log('[MaintenanceDetector] 已在维护页面,无需跳转');
}
// 开始轮询检测恢复
this.startRecoveryCheck();
}
/**
* 定期检测服务恢复
*/
private startRecoveryCheck() {
// 避免重复启动
if (this.recoveryCheckTimer) {
return;
}
console.log('[MaintenanceDetector] 开始轮询检测服务恢复每30秒');
this.recoveryCheckTimer = setInterval(async () => {
try {
// 尝试请求 Spring Boot Actuator 健康检查端点(无需认证)
// 使用原生fetch避免触发axios拦截器
const response = await fetch('/actuator/health', {
method: 'GET',
cache: 'no-cache',
signal: AbortSignal.timeout(3000)
});
// 只有返回 200-299 才算真正恢复
// 500+ 错误不算恢复
if (response.ok) {
// 服务恢复
console.log('[MaintenanceDetector] 服务已恢复!');
this.exitMaintenanceMode();
} else {
console.log(`[MaintenanceDetector] 服务仍不可用 (状态码: ${response.status})`);
}
} catch (error) {
console.log('[MaintenanceDetector] 服务检测失败,继续等待...');
}
}, 30000); // 每30秒检查一次
}
/**
* 退出维护模式
*/
private exitMaintenanceMode() {
console.log('[MaintenanceDetector] 退出维护模式');
// 清除维护状态
sessionStorage.removeItem('maintenance_mode');
sessionStorage.removeItem('maintenance_start_time');
// 停止轮询
if (this.recoveryCheckTimer) {
clearInterval(this.recoveryCheckTimer);
this.recoveryCheckTimer = null;
}
// 重置失败计数
this.failureCount = 0;
// 刷新页面(返回应用)
window.location.href = '/';
}
/**
* 手动检测恢复(供维护页面调用)
*/
async checkRecovery(): Promise<boolean> {
try {
const response = await fetch('/actuator/health', {
method: 'GET',
cache: 'no-cache',
signal: AbortSignal.timeout(3000)
});
// 只有返回 200-299 才算真正恢复
if (response.ok) {
this.exitMaintenanceMode();
return true;
}
return false;
} catch {
return false;
}
}
/**
* 获取维护开始时间
*/
getMaintenanceStartTime(): number | null {
const startTime = sessionStorage.getItem('maintenance_start_time');
return startTime ? parseInt(startTime, 10) : null;
}
/**
* 检查是否在维护模式
*/
isInMaintenanceMode(): boolean {
return sessionStorage.getItem('maintenance_mode') === 'true';
}
}
// 导出单例
export const maintenanceDetector = new MaintenanceDetector();