4b5602a719
- FilesPage storeToRefs + remotePath coercion; Monaco preload; list action buttons - text_io for UTF-8/LF; install/sync EOL; check_shell_eol + check_text_eol gates - ServerFormDialog, hash login redirect, AppAuth keep-session; design docs and audits Co-authored-by: Cursor <cursoragent@cursor.com>
5.0 KiB
5.0 KiB
Composer 2.5 — 文件管理器重构 P0 执行任务
立即任务:JWT Token 提前续期(消除进目录双刷新)
必读
- 设计文档:
docs/design/specs/2026-06-02-files-refactor-design.md - 重点章节:§1.2 根因、§3 P0、§8 立即执行项
根因摘要
用 cursor-ide-browser 实测:每次进目录发 2 次 /api/sync/browse(间隔 ~1.3s)。
原因:access token 过期 → 第 1 次 401 → /auth/refresh → 重试。
解决:发请求前检查 token exp,距过期 < 5 min 主动刷新。
改动清单(按顺序)
步骤 1 — frontend/src/stores/auth.ts
新增:
tokenExp:从token(JWT)payload 解码exp(unix 秒),无 token 返回 0isTokenExpiringSoon(thresholdSec = 300):exp > 0 && exp - Math.floor(Date.now()/1000) < thresholdSec
JWT 解码(不验签):
function decodeJwtExp(token: string): number {
try {
const payload = token.split('.')[1]
const json = atob(payload.replace(/-/g, '+').replace(/_/g, '/'))
const obj = JSON.parse(json) as { exp?: number }
return typeof obj.exp === 'number' ? obj.exp : 0
} catch { return 0 }
}
步骤 2 — frontend/src/api/index.ts
在 fetchAuthed 函数内、fetch(url, ...) 之前插入:
// 主动续期:距过期 < 5min 且非 auth 路径 → 先刷新再发请求
if (!_retry && !AUTH_SKIP_REFRESH.has(path)) {
const auth = useAuthStore()
if (auth.token && auth.isTokenExpiringSoon(300)) {
await tryRefreshSession()
}
}
保留现有 401 兜底重试不变(兜底用)。
步骤 3 — 验证 frontend/src/pages/FilesPage.vue
确认:
- 无
console.trace残留 _browseSeq序号保护保留(防极端并发兜底)
步骤 4 — changelog
创建 docs/changelog/2026-06-02-files-token-proactive-refresh.md,至少 10 行:
- 日期、变更摘要、动机(双刷新根因)
- 涉及文件:
frontend/src/stores/auth.ts、frontend/src/api/index.ts - 验证方式:cursor-ide-browser hook
window.fetch监控 - 是否需迁移:否
- 是否需重启:仅前端重新部署
步骤 5 — 构建 + 部署
cd c:\Users\uzuma\Desktop\svn\Nexus\frontend
npx vite build
# WSL 中执行
cd /mnt/c/Users/uzuma/Desktop/svn/Nexus
tar czf /tmp/nf.tar.gz -C web/app index.html assets/
scp -i /tmp/nexus_key2 -o StrictHostKeyChecking=no /tmp/nf.tar.gz root@47.254.123.106:/tmp/
ssh -i /tmp/nexus_key2 -o StrictHostKeyChecking=no root@47.254.123.106 \
'cd /www/wwwroot/api.synaglobal.vip/web/app && \
rm -f assets/FilesPage-*.js assets/FilesPage-*.css && \
rm -f assets/index-*.js assets/index-*.css && \
tar xzf /tmp/nf.tar.gz && rm /tmp/nf.tar.gz'
步骤 6 — 浏览器验证(必跑)
用 cursor-ide-browser MCP:
browser_navigate到https://api.synaglobal.vip/app/#/login- CDP
Runtime.evaluate填入 admin / Nexus@2026 → 点击登录 - 注入 fetch 监控:
window.__realFetch = window.fetch.bind(window); window.__apiLog = []; window.fetch = (...args) => { const url = typeof args[0]==='string' ? args[0] : args[0]?.url; if (url?.includes('/sync/browse') || url?.includes('/auth/refresh')) { window.__apiLog.push({t: Date.now(), url}); } return window.__realFetch(...args); }; browser_navigate到https://api.synaglobal.vip/app/#/files?server_id=8- 程序化点击 5 个不同目录:
每次等 2s
document.querySelector('table tbody tr').click() - 读取
JSON.stringify(window.__apiLog)
验收:
/sync/browse出现次数 ≤ 5(每个目录 1 次)/auth/refresh出现次数 = 1(启动时主动续期 1 次,或 0 次如未临近过期)- 不出现
/sync/browse和/auth/refresh两两紧贴的模式(旧行为:browse → refresh → browse)
步骤 7 — 提交
git add frontend/src/stores/auth.ts frontend/src/api/index.ts \
docs/changelog/2026-06-02-files-token-proactive-refresh.md \
docs/reports/2026-06-02-token-refresh-verification.md
git commit -m "fix(auth): JWT 主动续期,消除文件管理器进目录双 HTTP 请求
距过期 5min 主动刷新,避免业务请求触发 401 重试。
根因:每次进目录发 2 次 /sync/browse(401 → refresh → 重试)。
验证:cursor-ide-browser CDP hook fetch,进 5 目录 ≤ 5 次请求。"
git push origin main
不要做
- 不要重构 FilesPage.vue(P0 不动)
- 不要改后端
- 不要删除现有
_browseSeq序号保护 - 不要把 401 兜底重试逻辑去掉(保留作为防御)
完成标志
tsc --noEmit通过npx vite build成功- 部署成功(health check OK)
- 浏览器验证:5 次点击 = 5 次 browse 请求(不是 10)
- changelog ≥ 10 行
- 验证报告含截图与 fetch log
- git push 完成
完成后回报
列出:
- 实际改动文件清单
- 浏览器验证日志(apiLog 完整 JSON)
- 部署后的 bundle hash(FilesPage-.js 与 index-.js)
- git commit hash