This commit is contained in:
2025-12-05 00:09:32 +08:00
parent bb3d22eabf
commit 3d1dbb3f97
214 changed files with 113060 additions and 1232 deletions
+310
View File
@@ -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