Files
leaudit-platform-frontend/docs/RBAC路由权限集成_修复说明.md
2025-12-05 00:09:32 +08:00

7.3 KiB
Raw Permalink Blame History

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 调用方式错误

错误代码

const response = await apiRequest<BackendRoutesResponse>({
  method: 'GET',
  url: '/rbac/user/routes',  // ❌ 错误:将对象作为第一个参数
  headers: {
    'Authorization': `Bearer ${jwt}`
  }
});

问题

  • apiRequest 函数的第一个参数应该是 endpoint 字符串
  • 我却传递了一个包含 methodurlheaders 的对象
  • 导致 buildUrl 函数接收到对象而不是字符串,调用 endpoint.startsWith 时报错

2. 响应数据访问方式错误

apiRequest 函数签名

export async function apiRequest<T>(
  endpoint: string,                    // 第一个参数:端点路径
  options: ExtendedAxiosRequestConfig, // 第二个参数:配置对象
  params?: QueryParams                 // 第三个参数:查询参数(可选)
): Promise<ApiResponse<T>>

返回值类型

type ApiResponse<T> = {
  data?: T;        // 后端返回的完整响应对象
  error?: string;  // 错误信息
  status: number;  // HTTP 状态码
  headers?: Record<string, string>;
};

后端返回格式

{
  "code": 0,
  "msg": "成功",
  "data": {
    "user_id": 5,
    "username": "admin",
    "routes": [...]
  }
}

数据访问层级

response.data           // 整个后端响应对象 { code, msg, data }
response.data.code      // 业务状态码
response.data.msg       // 提示信息
response.data.data      // 实际数据 { user_id, username, routes }
response.data.data.routes  // 路由数组

修复方案

修复 1:正确调用 apiRequest

修复后代码

const response = await apiRequest<BackendRoutesResponse>(
  '/rbac/user/routes',  // ✅ 第一个参数:endpoint 字符串
  {
    method: 'GET',
    headers: {
      'Authorization': `Bearer ${jwt}`
    }
  }  // ✅ 第二个参数:配置对象
);

关键点

  • 第一个参数:端点路径字符串(如 '/rbac/user/routes'
  • 第二个参数:配置对象(包含 method、headers 等)
  • 第三个参数:查询参数对象(可选)

修复 2:正确访问响应数据

修复后代码

// 检查响应是否成功
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. 清除缓存

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

- 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 应该报错
apiRequest<BackendRoutesResponse>({
  method: 'GET',
  url: '/rbac/user/routes'
});
// ❌ Argument of type '{ method: string; url: string; }' is not assignable to parameter of type 'string'

4. 添加详细的调试日志

在关键步骤添加日志,方便定位问题:

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