185 lines
5.0 KiB
TypeScript
185 lines
5.0 KiB
TypeScript
/**
|
||
* 维护模式检测服务
|
||
*
|
||
* 功能:
|
||
* 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();
|