From 4ee8ac3ee7602c6326e6bf760d6d59f69706b0ab Mon Sep 17 00:00:00 2001
From: ouyangxq <775153912@qq.com>
Date: Wed, 2 Sep 2026 20:41:51 +0800
Subject: [PATCH 1/2] fix(deploy): prevent stale SPA entry caching
---
nginx/default.conf.template | 14 ++++++++++++++
src/deployment/nginxConfig.test.mjs | 6 ++++++
2 files changed, 20 insertions(+)
diff --git a/nginx/default.conf.template b/nginx/default.conf.template
index 2c714ab..68ea42b 100644
--- a/nginx/default.conf.template
+++ b/nginx/default.conf.template
@@ -11,6 +11,19 @@ server {
return 200 "ok\n";
}
+ # SPA 入口每次重新校验,避免发布后浏览器继续运行旧资源清单。
+ location = /index.html {
+ add_header Cache-Control "no-store, no-cache, must-revalidate" always;
+ add_header Pragma "no-cache" always;
+ add_header Expires "0" always;
+ }
+
+ # Vite 产物文件名带内容哈希,可安全长期缓存。
+ location /assets/ {
+ try_files $uri =404;
+ add_header Cache-Control "public, max-age=31536000, immutable" always;
+ }
+
# 前端统一请求 /api,保持原始路径转发给 Gateway。
location /api/ {
client_max_body_size 55m;
@@ -36,6 +49,7 @@ server {
# 支持 Vue Router history 路由直接刷新。
location / {
+ add_header Cache-Control "no-store, no-cache, must-revalidate" always;
try_files $uri $uri/ /index.html;
}
}
diff --git a/src/deployment/nginxConfig.test.mjs b/src/deployment/nginxConfig.test.mjs
index c32b26a..aab81bb 100644
--- a/src/deployment/nginxConfig.test.mjs
+++ b/src/deployment/nginxConfig.test.mjs
@@ -13,6 +13,12 @@ test('allows 50 MiB uploads with multipart overhead through both API proxies', (
assert.equal(config.match(/client_max_body_size 55m;/g)?.length, 2)
})
+test('does not cache the SPA entry while keeping hashed assets immutable', () => {
+ assert.match(config, /location = \/index\.html[\s\S]*no-store, no-cache, must-revalidate/)
+ assert.match(config, /location \/assets\/[\s\S]*max-age=31536000, immutable/)
+ assert.match(config, /location \/ \{[\s\S]*no-store, no-cache, must-revalidate[\s\S]*try_files \$uri \$uri\/ \/index\.html/)
+})
+
test('routes domestic and overseas browser APIs to their test gateways without duplicate api prefixes', () => {
assert.match(testCompose, /API_UPSTREAM: "http:\/\/test-api-cn\.buggoglobal\.com"/)
assert.match(testCompose, /OVERSEAS_API_UPSTREAM: "http:\/\/test-api-global\.buggoglobal\.com"/)
From a7c36771e422f686c12dd7de0c282a0254ea3677 Mon Sep 17 00:00:00 2001
From: ouyangxq <775153912@qq.com>
Date: Wed, 2 Sep 2026 20:49:13 +0800
Subject: [PATCH 2/2] =?UTF-8?q?=E4=BC=98=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/App.vue | 42 +++++++++++++++++------
src/modules/auth/store/auth.ts | 9 -----
src/modules/auth/views/AuthViews.test.mjs | 9 ++++-
src/router/index.ts | 3 +-
4 files changed, 41 insertions(+), 22 deletions(-)
diff --git a/src/App.vue b/src/App.vue
index f38f8d2..77311e0 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,32 +1,52 @@
diff --git a/src/modules/auth/store/auth.ts b/src/modules/auth/store/auth.ts
index 574c1f4..76e63fb 100644
--- a/src/modules/auth/store/auth.ts
+++ b/src/modules/auth/store/auth.ts
@@ -223,15 +223,6 @@ export const useAuthStore = defineStore('client-auth', () => {
}
window.addEventListener('cc-client-session-expired', clearSession)
- window.addEventListener('message', (event: MessageEvent) => {
- const configuredAdminOrigin = String(import.meta.env.VITE_ADMIN_WEB_ORIGIN || '').trim()
- const expectedAdminOrigin = configuredAdminOrigin || window.location.origin
- const message = event.data as { source?: string; type?: string; code?: string } | null
- if (event.source !== window.opener || event.origin !== expectedAdminOrigin || message?.source !== 'chongchong-admin' || message.type !== 'impersonation-ticket' || !message.code) return
- void acceptImpersonationCode(message.code)
- .then(() => window.location.replace('/'))
- .catch(() => window.location.replace('/login?impersonation=failed'))
- })
window.addEventListener('storage', (event) => {
if (event.key === 'cc-client-access-token' || event.key === 'cc-client-refresh-token') applyStoredSession()
})
diff --git a/src/modules/auth/views/AuthViews.test.mjs b/src/modules/auth/views/AuthViews.test.mjs
index 6adc8fb..e9d911c 100644
--- a/src/modules/auth/views/AuthViews.test.mjs
+++ b/src/modules/auth/views/AuthViews.test.mjs
@@ -13,6 +13,8 @@ const httpSource = readFileSync(new URL('../../../api/http.ts', import.meta.url)
const sessionSource = readFileSync(new URL('../../../api/session.ts', import.meta.url), 'utf8')
const authApiSource = readFileSync(new URL('../api/auth.ts', import.meta.url), 'utf8')
const mainSource = readFileSync(new URL('../../../main.ts', import.meta.url), 'utf8')
+const appSource = readFileSync(new URL('../../../App.vue', import.meta.url), 'utf8')
+const routerSource = readFileSync(new URL('../../../router/index.ts', import.meta.url), 'utf8')
const stylesSource = readFileSync(new URL('../../../styles/index.css', import.meta.url), 'utf8')
test('uses one prototype-inspired responsive shell for login and registration', () => {
@@ -105,7 +107,12 @@ test('uses real auth endpoints and single-flight refresh handling', () => {
test('handles impersonation exchange as public authentication and exposes failures', () => {
assert.match(httpSource, /publicAuthPaths = \[[^\]]*'\/auth\/impersonations\/exchange'/)
- assert.match(authStoreSource, /\/login\?impersonation=failed/)
+ assert.match(appSource, /impersonationOrigin/)
+ assert.match(appSource, /await auth\.acceptImpersonationCode\(message\.code\)/)
+ assert.match(appSource, /type: 'impersonation-complete'/)
+ assert.match(appSource, /type: 'impersonation-failed'/)
+ assert.doesNotMatch(authStoreSource, /message\.type !== 'impersonation-ticket'/)
+ assert.match(routerSource, /isImpersonationHandoff[\s\S]*!isImpersonationHandoff/)
assert.match(loginSource, /route\.query\.impersonation === 'failed'/)
assert.match(loginSource, /代登录失败或授权已失效,请返回管理后台重新发起/)
})
diff --git a/src/router/index.ts b/src/router/index.ts
index 0280d8c..679f504 100644
--- a/src/router/index.ts
+++ b/src/router/index.ts
@@ -32,7 +32,8 @@ router.beforeEach(async (to) => {
if (to.meta.requiresAuth && !auth.isAuthenticated) {
return { name: 'login', query: { redirect: to.fullPath } }
}
- if ((to.name === 'login' || to.name === 'register') && auth.isAuthenticated) {
+ const isImpersonationHandoff = to.name === 'login' && typeof to.query.impersonationOrigin === 'string'
+ if ((to.name === 'login' || to.name === 'register') && auth.isAuthenticated && !isImpersonationHandoff) {
return getFirstAccessiblePath(auth.permissionCodes)
}
const permission = typeof to.meta.permission === 'string' ? to.meta.permission : undefined