all in
This commit is contained in:
@@ -0,0 +1,310 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user