Skip to content

认证与请求

这一页说明前端如何维护登录态,以及请求层如何与后端接口协作。

登录态管理

  • 通过 useAuth hook 管理认证状态
  • Token 存储在 localStorage,key 来自 @zenith/shared/src/constants.ts
常量Key说明
TOKEN_KEYzenith_tokenAccess Token
REFRESH_TOKEN_KEYzenith_refresh_tokenRefresh Token,用于自动续期
PREFERENCES_KEYzenith_preferences用户偏好设置(主题、布局等)
TABS_STORAGE_KEYzenith_tabs多标签页状态

useAuth 初始化时读取 TOKEN_KEY,存在 token 时请求 GET /api/auth/me 获取当前用户与权限;登录和注册成功后写入 access token / refresh token,再刷新用户信息。退出登录时清理 token、偏好设置和标签页缓存,并以静默请求通知 /api/auth/logout

请求封装

前端 HTTP 请求统一封装在:

packages/web/src/utils/request.ts

主要职责:

  • 自动附加 Bearer Token
  • FormData 请求自动设置 Content-Type: application/json
  • 统一处理接口响应
  • Access Token 过期时,自动用 Refresh Token 换取新 token 并重试
  • 在 401(无法续期)场景下跳转登录页
  • 支持 silent 静默错误提示、skipAuth 跳过 401 自动刷新、postForm 上传进度和 download 文件下载

request.ts 只是传输层

业务代码不直接调用 request.get 拉取页面数据。服务端状态(列表、详情、下拉源等)统一通过 TanStack Query 的域 hooks 管理(hooks/queries/),queryFn 内部才使用 request.get(url).then(unwrap)。完整规范见数据获取与服务端状态

与后端的协作方式

请求头

需要认证的请求应自动带上:

http
Authorization: Bearer <token>

FormData 上传不手动设置 Content-Type,由浏览器自动补充 multipart boundary。

401 刷新流程

请求返回 401 时,请求封装会读取 REFRESH_TOKEN_KEY 并调用:

http
POST /api/auth/refresh

请求体为:

json
{
  "refreshToken": "<refresh-token>"
}

刷新成功后更新 TOKEN_KEY 中的 access token,并重试原请求;刷新失败或重试仍为 401 时,清理 TOKEN_KEY / REFRESH_TOKEN_KEY 并跳转到 ${BASE_URL}/login。并发刷新由内部 refreshing Promise 复用,避免同时发起多次刷新请求。

响应读取

优先按统一响应格式读取:

json
{
  "code": 0,
  "message": "success",
  "data": {}
}

429 响应会读取 Retry-After 头并返回 retryAfterSeconds;503 响应会触发 maintenance:enabled 事件,用于展示维护模式覆盖层。

共享类型

接口类型、实体定义和校验 schema 尽量复用 @zenith/shared,避免前后端各写一套。

开发环境代理配置

开发环境下,前端 Vite Dev Server 通过内置代理将 /api/* 请求转发到后端,避免跨域问题,同时让前端无需感知后端地址。

相关文件:

  • packages/web/.env.development — 开发环境变量
  • packages/web/vite.config.ts — 代理规则定义

关键环境变量:

变量说明默认值
VITE_API_PROXY_TARGET代理目标(后端地址),仅在 Vite Dev Server 中生效,不会暴露到客户端http://localhost:3300
VITE_API_BASE_URL客户端 API 基础 URL。开发时留空,请求走相对路径 /api/... 由代理转发;生产部署时填写后端完整地址,如 https://api.yourdomain.com空(开发)
VITE_WS_BASE_URLWebSocket 基础 URL。开发时留空,由 useWebSocket.ts 自动从当前 Origin 推导并经代理转发;生产时填写如 wss://api.yourdomain.com空(开发)

代理规则(vite.config.ts):

ts
server: {
  proxy: {
    '/api': {
      target: apiTarget, // 来自 VITE_API_PROXY_TARGET
      changeOrigin: true,
      ws: true, // 同时代理 WebSocket(/api/ws)
    },
  },
},

各环境配置示例:

dotenv
VITE_API_BASE_URL=
VITE_WS_BASE_URL=
VITE_API_PROXY_TARGET=http://localhost:3300
dotenv
VITE_API_BASE_URL=https://api.yourdomain.com
VITE_WS_BASE_URL=wss://api.yourdomain.com
# 生产构建无 Dev Server,无需 VITE_API_PROXY_TARGET

注意VITE_API_PROXY_TARGET 只在 vite.config.ts 中通过 loadEnv 读取,用于 Dev Server 代理配置;业务代码不通过 import.meta.env 读取它,后端代理地址不会进入生产包。

会员端独立请求实例

会员前台使用独立请求封装:

packages/web/src/member/utils/member-request.ts

会员 token 与后台管理员 token 隔离:

常量Key说明
MEMBER_TOKEN_KEYzenith_member_token会员 Access Token
MEMBER_REFRESH_TOKEN_KEYzenith_member_refresh_token会员 Refresh Token

memberRequest 携带会员 token;401 时调用 POST /api/member/auth/refresh 刷新,失败后清理会员 token 并跳转 /member.html#/login。会员端不要复用后台的 request.ts。会员端服务端状态同样由 TanStack Query 管理,使用独立的 memberQueryClientmember/lib/member-query.ts)与域 hooks(member/hooks/queries.ts)。

开发建议

  • 新增接口前,先确认是否已有共享类型或校验 schema
  • 对需要登录的页面,优先复用现有登录态与跳转机制
  • 请求错误处理尽量集中在封装层,不把每个页面都写成“各自为战”
  • 页面数据获取遵循数据获取与服务端状态中的域 hooks 模式,不在组件里手写 fetch 状态机

Built with VitePress for local documentation preview.