311 lines
7.3 KiB
Markdown
311 lines
7.3 KiB
Markdown
# RBAC 路由权限集成 - 修复说明
|
||
|
||
## 🐛 问题描述
|
||
|
||
在集成 RBAC 路由权限时遇到以下错误:
|
||
|
||
```
|
||
TypeError: endpoint.startsWith is not a function
|
||
at buildUrl (axios-client.ts:168:16)
|
||
at apiRequest (axios-client.ts:275:17)
|
||
```
|
||
|
||
---
|
||
|
||
## 🔍 问题原因
|
||
|
||
### 1. `apiRequest` 调用方式错误
|
||
|
||
**错误代码**:
|
||
```typescript
|
||
const response = await apiRequest<BackendRoutesResponse>({
|
||
method: 'GET',
|
||
url: '/rbac/user/routes', // ❌ 错误:将对象作为第一个参数
|
||
headers: {
|
||
'Authorization': `Bearer ${jwt}`
|
||
}
|
||
});
|
||
```
|
||
|
||
**问题**:
|
||
- `apiRequest` 函数的第一个参数应该是 `endpoint` 字符串
|
||
- 我却传递了一个包含 `method`、`url`、`headers` 的对象
|
||
- 导致 `buildUrl` 函数接收到对象而不是字符串,调用 `endpoint.startsWith` 时报错
|
||
|
||
---
|
||
|
||
### 2. 响应数据访问方式错误
|
||
|
||
**`apiRequest` 函数签名**:
|
||
```typescript
|
||
export async function apiRequest<T>(
|
||
endpoint: string, // 第一个参数:端点路径
|
||
options: ExtendedAxiosRequestConfig, // 第二个参数:配置对象
|
||
params?: QueryParams // 第三个参数:查询参数(可选)
|
||
): Promise<ApiResponse<T>>
|
||
```
|
||
|
||
**返回值类型**:
|
||
```typescript
|
||
type ApiResponse<T> = {
|
||
data?: T; // 后端返回的完整响应对象
|
||
error?: string; // 错误信息
|
||
status: number; // HTTP 状态码
|
||
headers?: Record<string, string>;
|
||
};
|
||
```
|
||
|
||
**后端返回格式**:
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"msg": "成功",
|
||
"data": {
|
||
"user_id": 5,
|
||
"username": "admin",
|
||
"routes": [...]
|
||
}
|
||
}
|
||
```
|
||
|
||
**数据访问层级**:
|
||
```typescript
|
||
response.data // 整个后端响应对象 { code, msg, data }
|
||
response.data.code // 业务状态码
|
||
response.data.msg // 提示信息
|
||
response.data.data // 实际数据 { user_id, username, routes }
|
||
response.data.data.routes // 路由数组
|
||
```
|
||
|
||
---
|
||
|
||
## ✅ 修复方案
|
||
|
||
### 修复 1:正确调用 `apiRequest`
|
||
|
||
**修复后代码**:
|
||
```typescript
|
||
const response = await apiRequest<BackendRoutesResponse>(
|
||
'/rbac/user/routes', // ✅ 第一个参数:endpoint 字符串
|
||
{
|
||
method: 'GET',
|
||
headers: {
|
||
'Authorization': `Bearer ${jwt}`
|
||
}
|
||
} // ✅ 第二个参数:配置对象
|
||
);
|
||
```
|
||
|
||
**关键点**:
|
||
- 第一个参数:端点路径字符串(如 `'/rbac/user/routes'`)
|
||
- 第二个参数:配置对象(包含 method、headers 等)
|
||
- 第三个参数:查询参数对象(可选)
|
||
|
||
---
|
||
|
||
### 修复 2:正确访问响应数据
|
||
|
||
**修复后代码**:
|
||
```typescript
|
||
// 检查响应是否成功
|
||
if (response.error) {
|
||
console.error('❌ [User Routes] API 请求失败:', response.error);
|
||
return { success: false, error: response.error };
|
||
}
|
||
|
||
// 检查响应数据
|
||
if (!response.data) {
|
||
console.error('❌ [User Routes] 后端未返回数据');
|
||
return { success: false, error: "后端未返回数据" };
|
||
}
|
||
|
||
const backendResponse = response.data; // 整个后端响应对象
|
||
|
||
// 检查业务状态码
|
||
if (backendResponse.code !== 0 && backendResponse.code !== 200) {
|
||
console.error(`❌ [User Routes] 后端返回错误: ${backendResponse.msg}`);
|
||
return { success: false, error: backendResponse.msg };
|
||
}
|
||
|
||
// 访问实际数据
|
||
const routes = backendResponse.data.routes; // 路由数组
|
||
```
|
||
|
||
**关键点**:
|
||
- `response.data` → 后端返回的完整对象(包含 code、msg、data)
|
||
- `backendResponse.code` → 业务状态码(0 或 200 表示成功)
|
||
- `backendResponse.data.routes` → 路由数组
|
||
|
||
---
|
||
|
||
## 📊 数据流图
|
||
|
||
```
|
||
apiRequest<BackendRoutesResponse>(endpoint, options)
|
||
↓
|
||
axios.request(config)
|
||
↓
|
||
后端响应 (HTTP 200)
|
||
↓
|
||
response.data = {
|
||
code: 0,
|
||
msg: "成功",
|
||
data: {
|
||
user_id: 5,
|
||
username: "admin",
|
||
routes: [...]
|
||
}
|
||
}
|
||
↓
|
||
ApiResponse<BackendRoutesResponse> = {
|
||
data: { ← response.data 就是整个后端响应
|
||
code: 0,
|
||
msg: "成功",
|
||
data: {
|
||
user_id: 5,
|
||
username: "admin",
|
||
routes: [...]
|
||
}
|
||
},
|
||
status: 200,
|
||
headers: {...}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🧪 测试验证
|
||
|
||
### 1. 清除缓存
|
||
|
||
```javascript
|
||
localStorage.clear()
|
||
location.reload()
|
||
```
|
||
|
||
### 2. 重新登录
|
||
|
||
使用测试账号登录(如 `000 / admin06111`)
|
||
|
||
### 3. 观察控制台日志
|
||
|
||
**预期输出**:
|
||
```
|
||
🔍 [User Routes] 获取用户路由,角色: admin
|
||
🔍 [User Routes] 后端返回: { data: { code: 0, msg: "成功", ... }, status: 200 }
|
||
✅ [User Routes] 成功获取 29 个路由
|
||
📋 [User Routes] 菜单数据: [...]
|
||
✅ [Sidebar] 用户路由权限加载成功: [...]
|
||
```
|
||
|
||
**如果仍然报错**,检查:
|
||
1. 后端接口 `/rbac/user/routes` 是否正常运行
|
||
2. JWT token 是否有效
|
||
3. 后端返回的数据格式是否正确
|
||
|
||
---
|
||
|
||
## 📁 修改的文件
|
||
|
||
### `app/api/auth/user-routes.ts`
|
||
|
||
**修改位置**:`getUserRoutesByRole` 函数
|
||
|
||
**修改内容**:
|
||
1. 修正 `apiRequest` 调用方式(第一个参数是 endpoint 字符串)
|
||
2. 修正响应数据访问方式(通过 `response.data.data.routes` 访问路由数组)
|
||
|
||
**Git Diff**:
|
||
```diff
|
||
- const response = await apiRequest<BackendRoutesResponse>({
|
||
- method: 'GET',
|
||
- url: '/rbac/user/routes',
|
||
- headers: { 'Authorization': `Bearer ${jwt}` }
|
||
- });
|
||
|
||
+ const response = await apiRequest<BackendRoutesResponse>(
|
||
+ '/rbac/user/routes', // endpoint (第一个参数)
|
||
+ {
|
||
+ method: 'GET',
|
||
+ headers: { 'Authorization': `Bearer ${jwt}` }
|
||
+ } // options (第二个参数)
|
||
+ );
|
||
|
||
- if (response.code !== 0 && response.code !== 200) {
|
||
- // ...
|
||
- }
|
||
|
||
+ const backendResponse = response.data;
|
||
+ if (backendResponse.code !== 0 && backendResponse.code !== 200) {
|
||
+ // ...
|
||
+ }
|
||
|
||
- const routes = response.data.routes;
|
||
+ const routes = backendResponse.data.routes;
|
||
```
|
||
|
||
---
|
||
|
||
## 💡 经验总结
|
||
|
||
### 1. 仔细检查函数签名
|
||
|
||
在调用函数之前,务必确认:
|
||
- 参数的数量和顺序
|
||
- 每个参数的类型
|
||
- 返回值的结构
|
||
|
||
### 2. 理解数据嵌套结构
|
||
|
||
后端返回的数据通常有多层嵌套,需要逐层访问:
|
||
```
|
||
response (ApiResponse)
|
||
└─ data (后端完整响应)
|
||
├─ code (业务状态码)
|
||
├─ msg (提示信息)
|
||
└─ data (实际数据)
|
||
└─ routes (路由数组)
|
||
```
|
||
|
||
### 3. 使用 TypeScript 类型检查
|
||
|
||
如果正确定义了类型,TypeScript 会在编译时发现这类错误:
|
||
```typescript
|
||
// 如果这样调用,TypeScript 应该报错
|
||
apiRequest<BackendRoutesResponse>({
|
||
method: 'GET',
|
||
url: '/rbac/user/routes'
|
||
});
|
||
// ❌ Argument of type '{ method: string; url: string; }' is not assignable to parameter of type 'string'
|
||
```
|
||
|
||
### 4. 添加详细的调试日志
|
||
|
||
在关键步骤添加日志,方便定位问题:
|
||
```typescript
|
||
console.log('🔍 [User Routes] 后端返回:', response);
|
||
console.log('📋 [User Routes] 路由数组:', routes);
|
||
```
|
||
|
||
---
|
||
|
||
## ⚠️ 注意事项
|
||
|
||
1. **axios 拦截器会自动添加 Authorization 头**
|
||
- 从 localStorage 读取 `access_token`
|
||
- 但为了确保使用正确的 token,这里仍然显式传递
|
||
|
||
2. **后端接口必须返回标准格式**
|
||
- `code`: 业务状态码(0 或 200 表示成功)
|
||
- `msg`: 提示信息
|
||
- `data`: 实际数据(包含 `routes` 数组)
|
||
|
||
3. **Element UI 图标映射**
|
||
- 后端返回的是 Element UI 图标(如 `el-icon-s-home`)
|
||
- 前端会自动转换为 RemixIcon(如 `ri-home-line`)
|
||
|
||
---
|
||
|
||
**修复完成时间**: 2025-11-17
|
||
**修复者**: Claude Code
|