diff --git a/CLAUDE.md b/CLAUDE.md index 09b80b9..297dc26 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -151,6 +151,7 @@ 7. ~~锁屏功能已摘除入口~~ + ~~通知组件摘除~~(锁屏 Phase 3 摘入口,Phase 4 Task 5F 删代码:art-screen-lock/art-notification 组件目录、component.ts/headerBar.ts 配置项、useHeaderBar.shouldShowNotification、类型字段、components.d.ts 条目、crypto-js 依赖(env.d.ts 声明/package.json/optimizeDeps/lockfile)、en/zh 孤儿语言键全部清除,grep 零残留)。 8. ~~租户切换器 composable 收拢~~(Phase 4 Task 5E 完成:`admin/src/composables/useTenantOptions.ts` 收拢会话/配置/统计三页租户下拉(默认形态选中首个 + bootstrapTenantContext 引导;stats 用 selectFirst:false 纯数据源);dashboard 卡片墙形态不在此列)。 9. ~~ECharts 按需收窄~~(Phase 4 Task 5D 完成:echarts.use 收窄为 Bar/Line/Pie + Title/Tooltip/Grid/Legend + CanvasRenderer——全仓 grep 证实存活消费方仅 stats 页与 useChart;echarts chunk 767,864 B → 559,737 B(-27%),dist 总体积 -273 KB)。 + 10. **引擎日志含会话摘录 vs spec §9 承诺**(终审挂账)——观察期收口为 session_id 索引(手册 §6.2 有详述;收口前日志文件按含 PII 对待,备份/外发注意)。 ## Phase 4 完成状态(2026-10-14 收尾核对) diff --git a/admin/.env.development b/admin/.env.development index 49c0be7..0ada9af 100644 --- a/admin/.env.development +++ b/admin/.env.development @@ -23,9 +23,6 @@ VITE_WITH_CREDENTIALS = false # 是否打开路由信息 VITE_OPEN_ROUTE_INFO = false -# 锁屏加密密钥(锁屏入口已摘除——Phase 3 终审 M-1;密钥保留防遗留代码异常) -VITE_LOCK_ENCRYPT_KEY = s3cur3k3y4adpro - # API 请求基础路径(开发环境使用 / 走 Vite 代理,见 vite.config.ts 的 server.proxy) VITE_API_URL = / diff --git a/admin/.env.production b/admin/.env.production index 1761828..84085d4 100644 --- a/admin/.env.production +++ b/admin/.env.production @@ -22,9 +22,6 @@ VITE_WITH_CREDENTIALS = false # 是否打开路由信息 VITE_OPEN_ROUTE_INFO = false -# 锁屏加密密钥(锁屏入口已摘除——Phase 3 终审 M-1;密钥保留防遗留代码异常) -VITE_LOCK_ENCRYPT_KEY = s3cur3k3y4adpro - # API 地址前缀(生产由反代把 /api 与 /static 指向后端,前端同源相对路径) VITE_API_URL = / diff --git a/app/main.py b/app/main.py index 23d87c6..55db38c 100644 --- a/app/main.py +++ b/app/main.py @@ -257,9 +257,14 @@ app.mount("/admin", AdminSPA(), name="admin") async def static_no_cache(request, call_next): """静态资源协商缓存(2026-09-30 线上事件): 无 Cache-Control 时浏览器对 widget.js 走启发式缓存,修复部署后用户端仍跑旧脚本。no-cache=每次用 - etag 轻量协商,有更新即时生效,未变则 304 几乎零开销。""" + etag 轻量协商,有更新即时生效,未变则 304 几乎零开销。 + /admin 同覆盖(终审微修): SPA 的 index.html 引用带 hash 的 assets—— + 管理员端旧 index 被启发式缓存会引旧 assets 白屏(与 widget.js 事件 + 同类);index 每次协商,hash 资源未变则 304,开销可忽略。""" resp = await call_next(request) - if request.url.path.startswith("/static/"): + path = request.url.path + if (path.startswith("/static/") + or path == "/admin" or path.startswith("/admin/")): resp.headers["Cache-Control"] = "no-cache" return resp diff --git a/tests/test_admin_serving.py b/tests/test_admin_serving.py index d317319..bd24bea 100644 --- a/tests/test_admin_serving.py +++ b/tests/test_admin_serving.py @@ -80,6 +80,36 @@ def test_admin_assets_served(admin_client): assert "admin bundle" in r.text +# ---- no-cache 协商缓存(终审微修) -------------------------------------------- + + +def test_admin_responses_no_cache(admin_client): + # /admin 全前缀(含重定向、index、hash assets)带 no-cache: SPA 的 + # index.html 引用带 hash 的 assets,发版后管理员端旧 index 被启发式 + # 缓存会引旧 assets 白屏——与 2026-09-30 widget.js 线上事件同类, + # 协商缓存让 index 每次轻量校验(未变 304) + r = admin_client.get("/admin", follow_redirects=True) + assert r.status_code == 200 + assert r.headers["cache-control"] == "no-cache" + r = admin_client.get("/admin/") + assert r.status_code == 200 + assert r.headers["cache-control"] == "no-cache" + r = admin_client.get("/admin/assets/app.js") + assert r.status_code == 200 + assert r.headers["cache-control"] == "no-cache" + + +def test_platform_api_not_no_cache(admin_client): + # 负面钉位: 中间件只覆盖静态面(/static/ 与 /admin),/api/platform + # 响应不带 no-cache——API 的缓存语义归端点自己 + r = admin_client.post( + "/api/platform/auth/login", + json={"username": "nope", "password": "bad"}, + ) + assert r.status_code == 401 + assert "cache-control" not in r.headers + + # ---- SPA fallback 边界 -------------------------------------------------------