Files
pangolin/web/usercenter/README.md
wangjia e2646346a6 feat(web/usercenter): Next.js 用户中心静态导出 + mock/http 双数据层 (tsk_3FIPC8lSnAfJ)
新建 web/usercenter/:Next.js App Router + output:'export' 纯静态导出,
直接复用 design/ui_kits/usercenter React 源码(概览/订阅/兑换/邀请/设置)。

阶段 A(mock,本提交):
- 复刻五大页面,明/暗 × zh/en 四态;colors_and_type.css 原样链入;
  顶栏主题切换 + 语言段控,移动端底部 Tab + 左右滑动切换。
- 设置页新增:偏好(语言/主题) + 设备管理(列表/移除/二次确认+刷新) +
  TOTP 2FA(绑定二维码占位+密钥/验证/解禁),登录二段式 TOTP。
- 数据层 lib/api:ApiClient 抽象 + MockClient/HttpClient 双实现,构建期
  NEXT_PUBLIC_API_MODE 切换;统一错误体 {code,message_zh,message_en} → 双语映射。
- 会话:access token 仅内存,refresh header token + localStorage,静默续期,
  登出失效(取舍:静态导出无服务端 cookie 能力,详见 README)。
- 安全:构建期注入 SRI(sha384);_headers 严格 CSP + 安全基线;
  红线词扫描(铁律13) CI 红线,零命中。

阶段 B(占位待联调):HttpClient 已写好域名池+退避重试+401 续期;
TOTP/登录二段式端点占位待 #1 契约增补;me/redeem/devices 切真实链路依赖 #2/#3/#4。

验证:npm run lint / redline / build 均通过,静态产物 out/ 无服务端依赖。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 15:23:18 +08:00

79 lines
4.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 穿山甲 · Web 用户中心(Next.js 静态导出)
`design/ui_kits/usercenter/` 设计稿的生产实现。Next.js App Router + `output: 'export'`
**纯静态导出**(无任何 Node 服务端能力),动态数据全走客户端 API,可任意静态托管直接跑。
## 页面
概览 · 订阅导入 · 兑换 & 购买 · 邀请返利 · 设置(语言/主题 + 设备管理 + TOTP 2FA)。
明/暗主题 × 中/英单显四态,移动端底部 Tab + 左右滑动切换。
## 运行
```bash
cp .env.example .env.local # 默认 NEXT_PUBLIC_API_MODE=mock
npm install
npm run dev # http://localhost:3000
```
## 构建(静态导出 → out/
```bash
npm run redline # 铁律 13 红线词扫描(CI 红线)
npm run build # next build + 导出 + 注入 SRI
# 产物在 out/,可直接静态托管(Cloudflare Pages 多镜像,同官网管线)
```
`npm run check` = 红线扫描 + 构建。
## 数据层(mock / http 双实现)
`lib/api/` 抽象 `ApiClient`,构建期由 `NEXT_PUBLIC_API_MODE` 切换实现:
- **MockClient**(默认):演示数据,零网络依赖,供阶段 A 的 UI 验收。
- 演示触发:密码 `wrong`→凭证错误,`locked`→账户锁定+倒计时;兑换码 `INVALID`/`USED`/`LOCKED`→对应错误;TOTP 码 `000000`→验证失败。
- **HttpClient**(阶段 B):真实后端薄客户端 —— API 域名池故障转移 + 指数退避重试 +
统一错误体 `{code, message_zh, message_en}` 解析 + 401 静默续期重放。
域名池由 `NEXT_PUBLIC_API_DOMAINS`(逗号分隔,N≥3)配置。
接口:`login / loginTotp(登录二段式)/ refresh / getMe / getSubscription /
resetSubscription / listDevices / removeDevice / redeem / totpSetup / totpVerify /
totpDisable / logout`
> **契约增补(依赖 #1 openapi**v1 暂无 TOTP 端点,已在 `lib/api/http.ts` 占位
> `POST /v1/me/totp/setup|verify|disable` + 登录二段式 `/v1/auth/login/totp`。增补合入前
> 2FA 仅 mock 验收,不阻塞本任务。
## 会话与安全
- **access token 仅存内存**`lib/api/session.ts`),不落盘,降低 XSS 持久化窃取面。
- **refresh token 走 header token + localStorage**,静默续期(401 自动 refresh 重放一次)。
- 取舍说明:doc/05 §2 给「HttpOnly+Secure cookie 或 header token」两选。纯静态导出**无服务端
设置 cookie 的能力**,故取 header token 方案,配合内存 access + 登出使后端 refresh 白名单失效收敛风险。
- **登出**调用 `POST /v1/auth/logout` 使 refresh 失效,并清本地令牌。
- **登录限流在服务端**;前端只做失败提示与锁定倒计时。
## 红线 / 合规
- **SRI**:构建期 `scripts/add-sri.mjs` 为导出 HTML 的本地 `<script>/<link>` 注入
`integrity`(sha384) + `crossorigin`
- **CSP**`public/_headers`Cloudflare Pages)下发严格 CSP + 安全基线头。
- 取舍:静态导出无 nonce 能力,Next 注入少量内联 bootstrap 脚本,故 `script-src` 保留
`'unsafe-inline'`,以 SRI 补足自有 chunk 完整性;`style-src` 因设计稿大量内联样式同样需 `'unsafe-inline'`
- **红线词扫描**(铁律 13):`scripts/redline-scan.mjs``app/ components/ lib/`,命中即非零退出(CI 红线)。
- 设计令牌 `colors_and_type.css` 原样链入(`public/`,单一真相源);颜色全部走语义 token。
## 目录
```
app/ layout + page(静态外壳)
components/ UserCenter(外壳) + Login/Overview/Subscription/Redeem/Invite/Settings + icons/shared
lib/ i18n · theme(语言/主题 context) · api/(client 工厂 + mock + http + types + errors + session)
scripts/ add-sri.mjs · redline-scan.mjs
public/ colors_and_type.css(原样) · _headers(CSP)
```
## 部署(灾备口径同官网)
构建产物 `out/` 为纯静态,可在干净环境重放部署到任意新域名(Cloudflare Pages 多镜像)。
用户中心被打击时,客户端内同功能仍可用(doc/05 §1 灾备模型)。