feat(监控): 完善系统监控、登录状态与访问审计能力
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
import { computed, readonly, ref } from "vue";
|
||||
import { heartbeatSession } from "../api/authClient";
|
||||
import { getToken } from "../utils/auth";
|
||||
|
||||
export type ConnectionStatus = "checking" | "online" | "degraded" | "offline";
|
||||
|
||||
const HEARTBEAT_INTERVAL_MS = 30_000;
|
||||
|
||||
const status = ref<ConnectionStatus>("checking");
|
||||
const latencyMs = ref<number | null>(null);
|
||||
const lastSuccessAt = ref<number | null>(null);
|
||||
const lastCheckedAt = ref<number | null>(null);
|
||||
let consumerCount = 0;
|
||||
let heartbeatTimer: number | undefined;
|
||||
let heartbeatPromise: Promise<void> | null = null;
|
||||
let listenersRegistered = false;
|
||||
|
||||
const checkConnection = async () => {
|
||||
if (typeof navigator !== "undefined" && !navigator.onLine) {
|
||||
status.value = "offline";
|
||||
latencyMs.value = null;
|
||||
lastCheckedAt.value = Date.now();
|
||||
return;
|
||||
}
|
||||
if (heartbeatPromise) return heartbeatPromise;
|
||||
heartbeatPromise = (async () => {
|
||||
const startedAt = performance.now();
|
||||
try {
|
||||
const token = getToken();
|
||||
if (!token) {
|
||||
status.value = "offline";
|
||||
latencyMs.value = null;
|
||||
return;
|
||||
}
|
||||
await heartbeatSession(token);
|
||||
latencyMs.value = Math.max(1, Math.round(performance.now() - startedAt));
|
||||
lastSuccessAt.value = Date.now();
|
||||
status.value = "online";
|
||||
} catch (error: any) {
|
||||
latencyMs.value = null;
|
||||
status.value = error?.response ? "degraded" : "offline";
|
||||
} finally {
|
||||
lastCheckedAt.value = Date.now();
|
||||
heartbeatPromise = null;
|
||||
}
|
||||
})();
|
||||
return heartbeatPromise;
|
||||
};
|
||||
|
||||
const handleOnline = () => {
|
||||
status.value = "checking";
|
||||
void checkConnection();
|
||||
};
|
||||
|
||||
const handleOffline = () => {
|
||||
status.value = "offline";
|
||||
latencyMs.value = null;
|
||||
lastCheckedAt.value = Date.now();
|
||||
};
|
||||
|
||||
const registerNetworkListeners = () => {
|
||||
if (listenersRegistered || typeof window === "undefined") return;
|
||||
listenersRegistered = true;
|
||||
window.addEventListener("online", handleOnline);
|
||||
window.addEventListener("offline", handleOffline);
|
||||
};
|
||||
|
||||
const startConnectionMonitor = () => {
|
||||
consumerCount += 1;
|
||||
registerNetworkListeners();
|
||||
if (consumerCount === 1) {
|
||||
void checkConnection();
|
||||
heartbeatTimer = window.setInterval(() => void checkConnection(), HEARTBEAT_INTERVAL_MS);
|
||||
}
|
||||
return () => {
|
||||
consumerCount = Math.max(0, consumerCount - 1);
|
||||
if (consumerCount === 0 && heartbeatTimer) {
|
||||
window.clearInterval(heartbeatTimer);
|
||||
heartbeatTimer = undefined;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const statusLabel = computed(() => {
|
||||
if (status.value === "online") return "在线";
|
||||
if (status.value === "degraded") return "连接异常";
|
||||
if (status.value === "offline") return "已断开";
|
||||
return "检测中";
|
||||
});
|
||||
|
||||
export const useConnectionStatus = () => ({
|
||||
status: readonly(status),
|
||||
statusLabel,
|
||||
latencyMs: readonly(latencyMs),
|
||||
lastSuccessAt: readonly(lastSuccessAt),
|
||||
lastCheckedAt: readonly(lastCheckedAt),
|
||||
checkConnection,
|
||||
startConnectionMonitor,
|
||||
});
|
||||
Reference in New Issue
Block a user