Files
Nexus/docs/design/plans/2026-06-02-composer-task-P0.md
Your Name 4b5602a719 feat(files): fix editor freeze, EOL gates, server form and session UX
- 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>
2026-06-03 00:42:55 +08:00

5.0 KiB
Raw Permalink Blame History

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:从 tokenJWTpayload 解码 expunix 秒),无 token 返回 0
  • isTokenExpiringSoon(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.tsfrontend/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

  1. browser_navigatehttps://api.synaglobal.vip/app/#/login
  2. CDP Runtime.evaluate 填入 admin / Nexus@2026 → 点击登录
  3. 注入 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);
    };
    
  4. browser_navigatehttps://api.synaglobal.vip/app/#/files?server_id=8
  5. 程序化点击 5 个不同目录:
    document.querySelector('table tbody tr').click()
    
    每次等 2s
  6. 读取 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/browse401 → refresh → 重试)。
验证:cursor-ide-browser CDP hook fetch,进 5 目录 ≤ 5 次请求。"
git push origin main

不要做

  • 不要重构 FilesPage.vueP0 不动)
  • 不要改后端
  • 不要删除现有 _browseSeq 序号保护
  • 不要把 401 兜底重试逻辑去掉(保留作为防御)

完成标志

  • tsc --noEmit 通过
  • npx vite build 成功
  • 部署成功(health check OK
  • 浏览器验证:5 次点击 = 5 次 browse 请求(不是 10
  • changelog ≥ 10 行
  • 验证报告含截图与 fetch log
  • git push 完成

完成后回报

列出:

  1. 实际改动文件清单
  2. 浏览器验证日志(apiLog 完整 JSON
  3. 部署后的 bundle hashFilesPage-.js 与 index-.js
  4. git commit hash