mirror of
https://github.com/t8y2/dbx.git
synced 2026-11-01 16:28:46 +00:00
fix: 修复应用长时间挂后台后恢复时表格数据永久加载的问题
三层防护: - MySQL 连接获取时执行 ping 健康检查,死连接自动重连 - macOS Reopen 事件 + 前端 visibilitychange 触发连接刷新 - 前端 Promise.race 超时保护,确保 loading 状态不会超过 60s
This commit is contained in:
@@ -25,6 +25,7 @@ import { useDialogSources } from "@/composables/useDialogSources";
|
||||
import { useNavigationTargets } from "@/composables/useNavigationTargets";
|
||||
import { useDataGridActions } from "@/composables/useDataGridActions";
|
||||
import { useTauriEvents } from "@/composables/useTauriEvents";
|
||||
import { useVisibilityChange } from "@/composables/useVisibilityChange";
|
||||
import "@/i18n";
|
||||
import { translateBackendError } from "@/i18n/backend-errors";
|
||||
import * as api from "@/lib/api";
|
||||
@@ -211,6 +212,7 @@ const { setupTauriListeners, cleanupTauriListeners } = useTauriEvents({
|
||||
openSqlFilePath,
|
||||
openConnectionDeepLink,
|
||||
});
|
||||
useVisibilityChange();
|
||||
|
||||
const appVersion = ref("");
|
||||
const isClassicLayout = computed(() => settingsStore.editorSettings.appLayout === "classic");
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { onMounted, onUnmounted } from "vue";
|
||||
import { refreshConnections } from "@/lib/api";
|
||||
import { useQueryStore } from "@/stores/queryStore";
|
||||
|
||||
let hiddenAt: number | null = null;
|
||||
|
||||
function handleVisibilityChange() {
|
||||
if (document.hidden) {
|
||||
hiddenAt = Date.now();
|
||||
} else {
|
||||
const wasHidden = hiddenAt;
|
||||
hiddenAt = null;
|
||||
if (wasHidden && Date.now() - wasHidden > 30_000) {
|
||||
refreshConnections().catch(() => {});
|
||||
const queryStore = useQueryStore();
|
||||
const stuckTabs = queryStore.tabs.filter((t) => t.isExecuting);
|
||||
if (stuckTabs.length > 0) {
|
||||
queryStore.notifyConnectionMayBeLost();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function useVisibilityChange() {
|
||||
onMounted(() => {
|
||||
document.addEventListener("visibilitychange", handleVisibilityChange);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
||||
});
|
||||
}
|
||||
@@ -708,14 +708,12 @@ export const useQueryStore = defineStore("query", () => {
|
||||
clientSessionId: tab.id,
|
||||
timeoutSecs: queryTimeoutSecs,
|
||||
};
|
||||
const results = await api.executeMulti(
|
||||
tab.connectionId,
|
||||
tab.database,
|
||||
sqlToExecute,
|
||||
tab.schema,
|
||||
executionId,
|
||||
executionOptions,
|
||||
);
|
||||
const frontendTimeoutSecs = Math.max(queryTimeoutSecs * 2, 60);
|
||||
const timeoutError = new Error(`查询超时 (${frontendTimeoutSecs}s),请检查数据库连接是否正常`);
|
||||
const results = await Promise.race([
|
||||
api.executeMulti(tab.connectionId, tab.database, sqlToExecute, tab.schema, executionId, executionOptions),
|
||||
new Promise<never>((_, reject) => setTimeout(() => reject(timeoutError), frontendTimeoutSecs * 1000)),
|
||||
]);
|
||||
console.info("[DBX][executeTabSql:execute-multi:done]", {
|
||||
traceId,
|
||||
resultCount: results.length,
|
||||
@@ -916,6 +914,18 @@ export const useQueryStore = defineStore("query", () => {
|
||||
tab.queryEditabilityReason = undefined;
|
||||
}
|
||||
|
||||
function notifyConnectionMayBeLost() {
|
||||
const stuck = tabs.value.filter((t) => t.isExecuting);
|
||||
if (stuck.length > 0) {
|
||||
stuck.forEach((t) => {
|
||||
t.isExecuting = false;
|
||||
t.isCancelling = false;
|
||||
t.executionId = undefined;
|
||||
t.result = toErrorResult(new Error("连接可能已断开,请刷新数据重试"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function trimResultCache() {
|
||||
const inactive = tabs.value.filter((t) => t.id !== activeTabId.value && (t.result || t.results));
|
||||
if (inactive.length > MAX_CACHED_RESULTS) {
|
||||
@@ -964,5 +974,6 @@ export const useQueryStore = defineStore("query", () => {
|
||||
cancelTabExecution,
|
||||
cancelTabExplain,
|
||||
reloadEvictedTab,
|
||||
notifyConnectionMayBeLost,
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user