|
| 1 | +# React 版 API 对接说明 |
| 2 | + |
| 3 | +## 概述 |
| 4 | + |
| 5 | +本项目支持 **Mock 模式** 和 **真实 API 模式** 两种运行方式,通过环境变量 `VITE_MOCK` 控制。 |
| 6 | + |
| 7 | +## 环境配置 |
| 8 | + |
| 9 | +### Mock 模式 (开发/演示) |
| 10 | + |
| 11 | +```env |
| 12 | +VITE_MOCK=true |
| 13 | +VITE_API_URL=/api # 或留空 |
| 14 | +``` |
| 15 | + |
| 16 | +### 真实 API 模式 (生产/对接后端) |
| 17 | + |
| 18 | +```env |
| 19 | +VITE_MOCK=false |
| 20 | +VITE_API_URL=http://localhost:3000/api # 后端 API 地址 |
| 21 | +``` |
| 22 | + |
| 23 | +## 后端 API 规范 |
| 24 | + |
| 25 | +### 认证接口 |
| 26 | + |
| 27 | +| 接口 | 方法 | 说明 | 请求体 | 响应体 | |
| 28 | +|------|------|------|--------|--------| |
| 29 | +| `/api/auth/login` | POST | 用户登录 | `{email, password}` | `{accessToken, refreshToken, user}` | |
| 30 | +| `/api/auth/register` | POST | 用户注册 | `{email, name, password}` | `{accessToken, refreshToken, user}` | |
| 31 | +| `/api/auth/me` | GET | 获取当前用户 | - | `{id, email, name, ..., roles}` | |
| 32 | +| `/api/auth/refresh` | POST | 刷新令牌 | `{refreshToken}` | `{accessToken, refreshToken}` | |
| 33 | +| `/api/auth/logout` | POST | 退出登录 | - | - | |
| 34 | + |
| 35 | +### 用户状态枚举 |
| 36 | + |
| 37 | +```typescript |
| 38 | +type UserStatus = "ACTIVE" | "INACTIVE" | "SUSPENDED" // 大写 |
| 39 | +``` |
| 40 | +
|
| 41 | +### 权限格式 |
| 42 | +
|
| 43 | +- 后端: `{id, resource, action, description}` |
| 44 | +- 前端: `"resource:action"` (如 `"users:view"`) |
| 45 | +
|
| 46 | +## 技术实现 |
| 47 | +
|
| 48 | +### 目录结构 |
| 49 | +
|
| 50 | +``` |
| 51 | +src/ |
| 52 | +├── lib/api/ |
| 53 | +│ ├── backend-types.ts # 后端 API 原始类型定义 |
| 54 | +│ ├── adapters.ts # 前后端数据格式转换 |
| 55 | +│ ├── client.ts # API 客户端 (axios + 拦截器) |
| 56 | +│ ├── services.ts # 业务服务封装 |
| 57 | +│ └── types.ts # 前端业务类型 |
| 58 | +├── stores/ |
| 59 | +│ └── auth-store.ts # Zustand 认证状态管理 |
| 60 | +└── mock/ # Mock 数据 |
| 61 | +``` |
| 62 | +
|
| 63 | +### 核心功能 |
| 64 | +
|
| 65 | +#### 1. Token 自动刷新 |
| 66 | +
|
| 67 | +`src/lib/api/client.ts` 实现了完整的 Token 刷新机制: |
| 68 | +
|
| 69 | +- 响应拦截器捕获 401 错误 |
| 70 | +- 自动调用 `/api/auth/refresh` 刷新令牌 |
| 71 | +- 排队等待刷新完成后重放请求 |
| 72 | +- 刷新失败后清理 Cookies 并跳转登录页 |
| 73 | +
|
| 74 | +#### 2. 请求拦截器 |
| 75 | +
|
| 76 | +自动为所有请求添加 `Authorization: Bearer {token}` 头: |
| 77 | +
|
| 78 | +- Mock 模式: 读取 `token` cookie |
| 79 | +- 真实模式: 读取 `accessToken` cookie |
| 80 | +
|
| 81 | +#### 3. Cookie 管理 |
| 82 | +
|
| 83 | +| 模式 | Cookie 键名 | 有效期 | |
| 84 | +|------|------------|--------| |
| 85 | +| Mock | `token` | 记住我: 7天 / 普通: 1天 | |
| 86 | +| 真实 | `accessToken` | 7天 | |
| 87 | +| 真实 | `refreshToken` | 30天 | |
| 88 | +
|
| 89 | +#### 4. 数据适配器 |
| 90 | +
|
| 91 | +`src/lib/api/adapters.ts` 负责前后端数据格式转换: |
| 92 | +
|
| 93 | +- 用户状态: 大写枚举直接透传 |
| 94 | +- 角色权限: 后端对象数组 → 前端字符串数组 |
| 95 | +- 多角色用户: 取第一个角色作为主角色 |
| 96 | +- 分页数据: 后端 `{data, meta}` → 前端 `{list, total}` |
| 97 | +
|
| 98 | +### 状态管理 (Zustand) |
| 99 | +
|
| 100 | +`src/stores/auth-store.ts` 提供的方法: |
| 101 | +
|
| 102 | +| 方法 | Mock 支持 | 真实 API 支持 | 说明 | |
| 103 | +|------|-----------|---------------|------| |
| 104 | +| `login` | ✅ | ✅ | 登录 | |
| 105 | +| `register` | ✅ | ✅ | 注册 | |
| 106 | +| `logout` | ✅ | ✅ | 退出 | |
| 107 | +| `checkAuth` | ✅ | ✅ | 检查认证状态 | |
| 108 | +| `forgotPassword` | ✅ | ✅ | 忘记密码 | |
| 109 | +| `resetPassword` | ✅ | ✅ | 重置密码 | |
| 110 | +| `switchAccount` | ✅ | ❌ | 切换账号 (仅 Mock) | |
| 111 | +| `loadAccounts` | ✅ | ❌ | 加载账号列表 (仅 Mock) | |
| 112 | +
|
| 113 | +#### checkAuth 优化 |
| 114 | +
|
| 115 | +为减少网络请求,`checkAuth` 优先从缓存恢复: |
| 116 | +
|
| 117 | +1. 检查 Cookie 中是否有 token |
| 118 | +2. 尝试从 `accounts` 缓存匹配 token |
| 119 | +3. 缓存命中 → 直接恢复状态 |
| 120 | +4. 缓存未命中 → 调用 `/api/auth/me` |
| 121 | +
|
| 122 | +## 切换模式 |
| 123 | +
|
| 124 | +### 开发时使用 Mock |
| 125 | +
|
| 126 | +1. 设置 `.env.development`: |
| 127 | + ```env |
| 128 | + VITE_MOCK=true |
| 129 | + ``` |
| 130 | +2. 运行 `pnpm dev` |
| 131 | +3. 使用 Mock 账号登录 (见 `src/lib/api/client.ts`) |
| 132 | +
|
| 133 | +### 对接真实后端 |
| 134 | +
|
| 135 | +1. 设置 `.env.production`: |
| 136 | + ```env |
| 137 | + VITE_MOCK=false |
| 138 | + VITE_API_URL=http://localhost:3000/api |
| 139 | + ``` |
| 140 | +2. 确保后端运行在 `http://localhost:3000` |
| 141 | +3. 运行 `pnpm build && pnpm preview` |
| 142 | + |
| 143 | +## 常见问题 |
| 144 | + |
| 145 | +### 1. 401 后无限刷新循环 |
| 146 | + |
| 147 | +**原因**: 刷新接口 `/api/auth/refresh` 也返回 401 |
| 148 | + |
| 149 | +**解决**: 确保刷新接口使用独立的 axios 实例,避免触发拦截器 |
| 150 | + |
| 151 | +### 2. 多账号切换失败 |
| 152 | + |
| 153 | +**原因**: 真实模式不支持多账号切换 |
| 154 | + |
| 155 | +**解决**: 在 UI 中隐藏多账号切换功能,或调用后端多租户 API |
| 156 | + |
| 157 | +### 3. 权限校验失败 |
| 158 | + |
| 159 | +**原因**: 前端权限格式 `"resource:action"` 与后端不匹配 |
| 160 | + |
| 161 | +**解决**: 检查 `adapters.ts` 中的 `adaptBackendPermissions` 函数 |
| 162 | + |
| 163 | +## 测试场景 |
| 164 | + |
| 165 | +### Mock 模式 |
| 166 | + |
| 167 | +- ✅ 登录 / 注册 |
| 168 | +- ✅ 多账号切换 |
| 169 | +- ✅ Token 刷新 (模拟) |
| 170 | +- ✅ 退出登录 |
| 171 | + |
| 172 | +### 真实 API 模式 |
| 173 | + |
| 174 | +- ✅ 登录 / 注册 → 存储 accessToken + refreshToken |
| 175 | +- ✅ 请求自动附带 Authorization 头 |
| 176 | +- ✅ 401 错误 → 自动刷新 token → 重放请求 |
| 177 | +- ✅ 刷新失败 → 清理 Cookies → 跳转登录 |
| 178 | +- ✅ 获取当前用户信息 (`/api/auth/me`) |
| 179 | +- ✅ 退出登录 → 清理所有 Cookies |
| 180 | + |
| 181 | +## 维护建议 |
| 182 | + |
| 183 | +1. **类型定义**: 后端 API 变更时,先更新 `backend-types.ts` |
| 184 | +2. **数据适配**: 格式差异在 `adapters.ts` 中处理,保持前端类型稳定 |
| 185 | +3. **Mock 数据**: 定期同步 Mock 数据与真实 API 响应格式 |
| 186 | +4. **Cookie 策略**: 生产环境设置 `secure: true, sameSite: 'strict'` |
| 187 | +5. **错误处理**: 统一在 `services.ts` 中处理业务错误 |
| 188 | +6. **UI 适配**: 真实模式下隐藏/禁用多账号切换功能 |
| 189 | +7. **状态字段**: `AuthState.token` 同时承载 Mock token 和真实 accessToken,注意语义区分 |
| 190 | + |
| 191 | +## 安全提示 |
| 192 | + |
| 193 | +- 清理 Cookie 会导致需要重新登录(即使 localStorage 中有缓存) |
| 194 | +- 真实模式完全依赖 Cookie 存储 token,不要在客户端代码中暴露 refreshToken |
| 195 | +- 生产环境建议使用 HTTPS + secure cookie |
| 196 | + |
| 197 | +## 参考文件 |
| 198 | + |
| 199 | +- API 客户端: `src/lib/api/client.ts` |
| 200 | +- 类型定义: `src/lib/api/backend-types.ts` |
| 201 | +- 数据适配: `src/lib/api/adapters.ts` |
| 202 | +- 状态管理: `src/stores/auth-store.ts` |
| 203 | +- 环境配置: `.env.example` |
0 commit comments