「用着用着登录已过期」的根因,不是超时也不是网络,是 10103。 生产 client-diag 实录(2026-08-20,两次): ev=start accessValid=N ← 隔了 2 小时回到页面,access 到点过期 ev=refresh-ok ← bootstrap 自己换了一次,成功 ev=runtime-refresh-fail err=code=10103 ← 首屏请求 401 兜底刷,拿的是同一张旧票 ev=runtime-auth-clear err=code=10106 ← 清凭据 → 弹「登录已过期」 服务端的 refresh token 是**一次性**的:换票时 `redis.del(旧 jti)` 再发新的 (auth.service.ts:468)。同一张票被两条路各换一次,后到的那条必得 10103。⚠ ️ 这个坑**被发现过一次,只修了一半**:本文件末尾"过期前 60s 定时刷"那段 早就改成走 `tryRefresh` 了,注释把根因写得清清楚楚 —— 唯独 bootstrap 这条漏了。 而它恰恰是**每次冷启动**都会走的路径,比定时器常见得多。 改法:bootstrap 第 3 步(仅 refresh token 还在 → silent refresh)改走 api-client 的共享单飞 `tryRefresh()`,与请求 401 兜底刷共用同一个"在途 refresh"。⛔ 别改成"把 refresh token 做成可重复使用":一次性是安全设计(泄露的旧票立即失效)。⛔ 也别在 doRefresh 里对 10103 重试:票已经死了,重试只会多一次 401。 顺带核实 doRefresh 的清除边界是对的,⛔ 别动: 5xx → 不清 store(可能是暂时的) 网络异常 → 不清 服务端明确拒绝(10103)→ 才清 所以掉线主因确实只有这一个竞态,不是网络抖动。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
| Name |
Last commit
|
Last update |
|---|---|---|
| .claude | Loading commit data... | |
| .design-sync | Loading commit data... | |
| apps | Loading commit data... | |
| clickhouse/config.d | Loading commit data... | |
| deploy | Loading commit data... | |
| docs | Loading commit data... | |
| packages | Loading commit data... | |
| scripts | Loading commit data... | |
| .gitignore | Loading commit data... | |
| .gitlab-ci.yml | Loading commit data... | |
| .npmrc | Loading commit data... | |
| .prettierrc | Loading commit data... | |
| README.md | Loading commit data... | |
| docker-compose.expose.yml | Loading commit data... | |
| docker-compose.managed.yml | Loading commit data... | |
| docker-compose.prod.yml | Loading commit data... | |
| docker-compose.yml | Loading commit data... | |
| eslint.config.mjs | Loading commit data... | |
| liu.cjs | Loading commit data... | |
| package.json | Loading commit data... | |
| pnpm-lock.yaml | Loading commit data... | |
| pnpm-workspace.yaml | Loading commit data... | |
| tsconfig.base.json | Loading commit data... | |
| turbo.json | Loading commit data... |