Skip to content

Commit ecca6fe

Browse files
committed
fix(desktop-runtime): 修复桌面端重装后仍命中旧 UI 缓存
- 启动打包版时按 UI buildId 检测并清理 renderer 缓存 - 记录 lastSeenUiBuildId,避免每次启动都重复清缓存 - 调整 SPA 静态资源缓存策略,index.html/200.html/_payload.json 禁止缓存 - 保留 _nuxt 哈希资源长期缓存,减少必须在卸载时删除数据后才生效的问题
1 parent 098d9cd commit ecca6fe

2 files changed

Lines changed: 90 additions & 3 deletions

File tree

desktop/src/main.cjs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const {
77
globalShortcut,
88
dialog,
99
shell,
10+
session,
1011
} = require("electron");
1112
let autoUpdater = null;
1213
let autoUpdaterLoadError = null;
@@ -465,6 +466,34 @@ function getDesktopSettingsPath() {
465466
return path.join(dir, "desktop-settings.json");
466467
}
467468

469+
function getPackagedUiDir() {
470+
if (!app.isPackaged) return null;
471+
try {
472+
return path.join(process.resourcesPath, "ui");
473+
} catch {
474+
return null;
475+
}
476+
}
477+
478+
function readPackagedUiBuildId() {
479+
const uiDir = getPackagedUiDir();
480+
if (!uiDir) return "";
481+
482+
try {
483+
const indexPath = path.join(uiDir, "index.html");
484+
if (!fs.existsSync(indexPath)) return "";
485+
const html = fs.readFileSync(indexPath, { encoding: "utf8" });
486+
const match =
487+
html.match(/buildId:"([^"]+)"/) ||
488+
html.match(/\/_payload\.json\?([^"'&<>\s]+)/) ||
489+
html.match(/data-src="\/_payload\.json\?([^"]+)"/);
490+
return String(match?.[1] || "").trim();
491+
} catch (err) {
492+
logMain(`[main] failed to read packaged UI build id: ${err?.message || err}`);
493+
return "";
494+
}
495+
}
496+
468497
function loadDesktopSettings() {
469498
if (desktopSettings) return desktopSettings;
470499

@@ -476,6 +505,9 @@ function loadDesktopSettings() {
476505
ignoredUpdateVersion: "",
477506
// Backend (FastAPI) listens on this port. Used in packaged builds.
478507
backendPort: DEFAULT_BACKEND_PORT,
508+
// Tracks the packaged UI build so we can invalidate Chromium's HTTP cache
509+
// after upgrades without wiping user data/localStorage.
510+
lastSeenUiBuildId: "",
479511
};
480512

481513
const p = getDesktopSettingsPath();
@@ -539,6 +571,33 @@ function setIgnoredUpdateVersion(version) {
539571
return desktopSettings.ignoredUpdateVersion;
540572
}
541573

574+
async function refreshRendererCacheForPackagedUi() {
575+
if (!app.isPackaged) return;
576+
577+
const nextBuildId = readPackagedUiBuildId();
578+
if (!nextBuildId) return;
579+
580+
const prevBuildId = String(loadDesktopSettings()?.lastSeenUiBuildId || "").trim();
581+
if (prevBuildId === nextBuildId) return;
582+
583+
try {
584+
const ses = session?.defaultSession;
585+
if (ses) {
586+
await ses.clearCache();
587+
try {
588+
await ses.clearStorageData({ storages: ["serviceworkers"] });
589+
} catch {}
590+
}
591+
logMain(`[main] cleared renderer cache for UI build change: ${prevBuildId || "(none)"} -> ${nextBuildId}`);
592+
} catch (err) {
593+
logMain(`[main] failed to clear renderer cache for UI build change: ${err?.message || err}`);
594+
}
595+
596+
loadDesktopSettings();
597+
desktopSettings.lastSeenUiBuildId = nextBuildId;
598+
persistDesktopSettings();
599+
}
600+
542601
function parseEnvBool(value) {
543602
if (value == null) return null;
544603
const v = String(value).trim().toLowerCase();
@@ -1614,6 +1673,7 @@ function registerWindowIpc() {
16141673

16151674
async function main() {
16161675
await app.whenReady();
1676+
await refreshRendererCacheForPackagedUi();
16171677
Menu.setApplicationMenu(null);
16181678
registerWindowIpc();
16191679
registerDebugShortcuts();

src/wechat_decrypt_tool/api.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,36 @@ def __init__(self, *args, **kwargs):
9999
self._fallback_200 = Path(str(self.directory)) / "200.html"
100100
self._fallback_index = Path(str(self.directory)) / "index.html"
101101

102+
@staticmethod
103+
def _normalize_path(path: str) -> str:
104+
return str(path or "").strip().lstrip("/")
105+
106+
@classmethod
107+
def _is_shell_path(cls, path: str) -> bool:
108+
normalized = cls._normalize_path(path)
109+
return normalized in {"", "index.html", "200.html", "_payload.json"} or normalized.startswith(
110+
"_payload.json/"
111+
)
112+
113+
@classmethod
114+
def _apply_cache_headers(cls, path: str, response):
115+
normalized = cls._normalize_path(path)
116+
try:
117+
if cls._is_shell_path(normalized):
118+
response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate"
119+
response.headers["Pragma"] = "no-cache"
120+
response.headers["Expires"] = "0"
121+
elif normalized.startswith("_nuxt/"):
122+
response.headers.setdefault("Cache-Control", "public, max-age=31536000, immutable")
123+
except Exception:
124+
pass
125+
return response
126+
102127
async def get_response(self, path: str, scope): # type: ignore[override]
128+
normalized = self._normalize_path(path)
103129
try:
104-
return await super().get_response(path, scope)
130+
response = await super().get_response(path, scope)
131+
return self._apply_cache_headers(normalized, response)
105132
except StarletteHTTPException as exc:
106133
if exc.status_code != 404:
107134
raise
@@ -112,8 +139,8 @@ async def get_response(self, path: str, scope): # type: ignore[override]
112139
raise
113140

114141
if self._fallback_200.exists():
115-
return FileResponse(str(self._fallback_200))
116-
return FileResponse(str(self._fallback_index))
142+
return self._apply_cache_headers("200.html", FileResponse(str(self._fallback_200)))
143+
return self._apply_cache_headers("index.html", FileResponse(str(self._fallback_index)))
117144

118145

119146
def _maybe_mount_frontend() -> None:

0 commit comments

Comments
 (0)