ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Claude Code 100个真实案例 - 用AI搭建React Admin后台系统(RBAC权限全搞定)

Claude Code 100个真实案例 - 用AI搭建React Admin后台系统(RBAC权限全搞定) 1. 为什么我劝你别再手写 RBAC 权限代码React Admin 后台系统里权限控制是最容易写成一团乱麻的部分。我见过太多项目用户、角色、菜单三张表在数据库里躺得好好的一到前端就退化成if (user.role admin)这种硬编码判断。加一个角色要改代码、重新部署按钮级权限靠role admin || role editor拼字符串路由守卫只判断有没有 token 不判断有没有权限——直接输入 URL 就能绕过。这套东西的本质问题在于权限是数据不是逻辑。用户拥有哪些角色、角色拥有哪些权限、权限对应哪个菜单或按钮这些都应该由后端下发、前端动态渲染而不是写死在组件里。RBACRole-Based Access Control模型就是干这个的用户 → 角色 → 权限三层解耦改权限只改数据不改代码。但真要从零搭一套完整的 RBAC 前端骨架涉及 TypeScript 类型定义、Zustand 状态管理、动态菜单树构建、路由守卫、按钮级权限组件、权限树勾选分配……光是理清这些模块的依赖关系就得花两三天。这也是为什么我把 Claude Code 拉进来它能根据一段结构化的提示词一次性生成类型定义、状态管理、权限组件、路由配置这一整套互相咬合的代码而且 TypeScript 类型是连贯的不会出现 A 文件定义了Permission接口、B 文件又自己写一个不兼容的版本。这篇要交付的是一套能直接跑起来的 React Admin 后台骨架技术栈是 React 18 TypeScript 5 Ant Design 5 React Router 6 Zustand 4 Axios。核心目标有三个RBAC 权限模型用户-角色-权限三级、动态菜单根据角色生成侧边栏、三层权限控制路由级 按钮级 逻辑级。适合正在做后台系统、被权限问题反复折磨的前端同学也适合想看看 Claude Code 在真实工程里怎么用的开发者。我会把每一步的提示词、生成的代码、验证命令都写清楚你跟着敲就能在本地跑通。权限配置片段、角色-菜单映射表、验证步骤都会给全不玩虚的。2. 用 TaoToken 给 Claude Code 接上稳定通道Claude Code 本身是个命令行工具它需要调用 Claude 的模型能力来生成代码。如果你直接用它默认的接入方式在国内网络环境下经常会遇到请求超时、连接中断的问题尤其是生成大段代码的时候一次断连就得重来。我试过在生成UserList.tsx这种几百行的组件时被中断三次体验很差。解决办法是给 Claude Code 配置一个稳定的 API 通道。TaoToken 提供的就是这个能力它兼容 Anthropic 的 API 协议你只需要把 Claude Code 的 Base URL 指向 TaoToken 的接口地址再配一个 API Key就能稳定调用 Claude 模型。整个过程不涉及任何网络工具就是标准的 API 配置。具体操作分三步。第一步去 TaoToken 控制台创建一个 API Key。打开 https://taotoken.net/api-keys 登录后点「创建密钥」复制生成的 Key形如sk-xxxxxxxx这个 Key 只显示一次记得存好。第二步配置 Claude Code 的环境变量。Claude Code 读取的是ANTHROPIC_BASE_URL和ANTHROPIC_API_KEY这两个变量。在终端里执行export ANTHROPIC_BASE_URLhttps://taotoken.net/api export ANTHROPIC_API_KEYsk-你的密钥如果你想让配置持久化把这两行写进~/.bashrc或~/.zshrc然后source一下。Windows 用户可以在系统环境变量里添加或者用 PowerShell 的$env:ANTHROPIC_BASE_URLhttps://taotoken.net/api。第三步验证配置是否生效。执行claude --version确认 Claude Code 已安装然后随便问一句claude 你好如果能正常返回内容说明通道通了。如果报 401多半是 Key 复制错了或者没生效重新source一下配置文件。这里有个细节要注意Base URL 填的是https://taotoken.net/api不要带多余的路径。有些同学会习惯性加上/v1反而会导致 404。TaoToken 的接口已经做了路径兼容直接填这个地址就行。配好之后Claude Code 的响应速度会明显稳定很多。我实测下来生成一个 300 行的 React 组件从发出提示词到完整返回基本在 20 秒内完成中途不会断。这对于需要连续生成多个文件的场景很关键——你总不希望生成到一半连接断了还得重新描述需求。另外如果你后续要做更复杂的 Agent 任务比如让 Claude Code 自动跑测试、自动修 bug可以考虑 TaoToken 的 Coding Plan它在长会话和连续任务上的稳定性更好。不过对于本篇这种「生成一套后台骨架」的需求基础的 API Key 就够了。3. 可复制的 RBAC 类型定义与权限配置片段这一步是整个系统的地基。RBAC 的核心是三个实体User用户、Role角色、Permission权限。用户通过roleIds关联角色角色通过permissionIds关联权限权限本身是一棵树菜单有子菜单菜单下有按钮。先建项目。用 Vite 起一个 React TypeScript 的架子比 CRA 快很多npm create vitelatest react-admin -- --template react-ts cd react-admin npm install antd ant-design/icons react-router-dom zustand axios dayjs然后让 Claude Code 生成类型定义。提示词要写清楚三件事实体关系、权限分类、需要模拟数据。我是这么写的请帮我定义 React Admin 系统的 RBAC 权限模型1. 用户、角色、权限三个核心实体2. 权限分为菜单权限和按钮权限用 type 字段区分3. 定义完整的 TypeScript 类型包含分页请求和响应类型4. 生成模拟数据包含管理员、编辑者、查看者三种角色。Claude Code 生成的src/types/auth.ts核心部分如下。注意Permission接口里的parentId和children这是构建权限树的关键// src/types/auth.ts export interface User { id: string; username: string; nickname: string; email: string; phone: string; status: active | disabled; roleIds: string[]; roles?: Role[]; createdAt: string; updatedAt: string; } export interface Role { id: string; name: string; displayName: string; description: string; permissionIds: string[]; permissions?: Permission[]; status: active | disabled; createdAt: string; } export type PermissionType menu | button | api; export interface Permission { id: string; parentId: string | null; name: string; displayName: string; type: PermissionType; path?: string; component?: string; icon?: string; sort: number; visible: boolean; children?: Permission[]; } export interface LoginResponse { token: string; refreshToken: string; user: User; permissions: string[]; menus: Permission[]; }接下来是模拟数据。这里有个设计要点权限的name字段用冒号分隔的命名空间比如system:user:create表示「系统管理-用户管理-新增」。这种命名方式在后续做按钮级权限判断时非常直观hasPermission(system:user:create)一眼就能看出是哪个按钮。角色-权限的映射关系我用一张表说清楚这也是你后续对接真实后端时的数据结构参考角色标识显示名称拥有的权限name说明admin超级管理员全部权限*拥有所有菜单和按钮editor内容编辑dashboard、content:article、content:article:create、content:article:update、system:user仅查看能管内容能看用户但不能改viewer普通查看者dashboard、content:article只能看不能做任何写操作对应的模拟数据片段// src/mock/data.ts export const mockRoles: Role[] [ { id: r001, name: admin, displayName: 超级管理员, description: 拥有系统所有权限, permissionIds: mockPermissions.map(p p.id), status: active, createdAt: 2024-01-01, }, { id: r002, name: editor, displayName: 内容编辑, description: 负责内容管理可查看用户但不能修改, permissionIds: [ p001, p200, p210, p211, p212, p213, p214, p100, p110, ], status: active, createdAt: 2024-01-02, }, { id: r003, name: viewer, displayName: 普通查看者, description: 只能查看数据不能修改, permissionIds: [p001, p200, p210], status: active, createdAt: 2024-01-03, }, ];权限树的数据结构也要定义好。菜单权限有path和component按钮权限没有path但visible为false不显示在菜单里。这样一棵树既能渲染侧边栏又能作为权限判断的数据源export const mockPermissions: Permission[] [ { id: p001, parentId: null, name: dashboard, displayName: 仪表盘, type: menu, path: /dashboard, component: Dashboard, icon: DashboardOutlined, sort: 1, visible: true }, { id: p100, parentId: null, name: system, displayName: 系统管理, type: menu, path: /system, component: Layout, icon: SettingOutlined, sort: 100, visible: true }, { id: p110, parentId: p100, name: system:user, displayName: 用户管理, type: menu, path: /system/users, component: UserList, icon: UserOutlined, sort: 1, visible: true }, { id: p111, parentId: p110, name: system:user:create, displayName: 新增用户, type: button, sort: 1, visible: false }, { id: p112, parentId: p110, name: system:user:update, displayName: 编辑用户, type: button, sort: 2, visible: false }, { id: p113, parentId: p110, name: system:user:delete, displayName: 删除用户, type: button, sort: 3, visible: false }, // ... 角色管理、菜单管理、内容管理、日志管理同理 ];写完这些跑一下验证命令确认数据没问题npx ts-node -e const { mockPermissions, mockRoles, mockUsers } require(./src/mock/data); console.log(权限总数:, mockPermissions.length); console.log(角色数:, mockRoles.length); console.log(管理员权限数:, mockRoles[0].permissionIds.length); console.log(编辑者权限数:, mockRoles[1].permissionIds.length); console.log(查看者权限数:, mockRoles[2].permissionIds.length); 预期输出是权限总数 21、角色数 3、管理员 21、编辑者 10、查看者 3。如果数字对不上检查一下mockPermissions数组是不是漏了项。这一步的产物是类型定义 模拟数据它们是后面所有模块的基础。类型定义保证了后续代码的连贯性——Claude Code 在生成 Store 和组件时会引用这些类型不会出现字段名对不上的情况。4. 权限状态管理与路由守卫的完整配置类型定义好了接下来是状态管理。RBAC 系统里登录后需要把用户信息、权限列表、菜单树存到全局状态里供各个组件读取。我用 Zustand因为它比 Redux 轻量得多而且配合persist中间件能自动做 localStorage 持久化刷新页面不掉登录态。提示词这样写请帮我用 Zustand 创建权限状态管理1. 存储当前用户信息、权限列表、菜单树2. 登录/登出操作3. 权限判断方法hasPermission、hasAnyPermission、hasAllPermissions4. 菜单树构建方法将扁平权限转为树形结构5. Token 持久化到 localStorage。Claude Code 生成的src/store/auth-store.ts里有两个函数是核心。一个是buildPermissionTree把后端返回的扁平权限数组转成树另一个是filterVisibleMenus过滤出type menu且visible true的节点用于渲染侧边栏// src/store/auth-store.ts function buildPermissionTree(permissions: Permission[]): Permission[] { const map new Mapstring, Permission(); const tree: Permission[] []; permissions.forEach(p { map.set(p.id, { ...p, children: [] }); }); permissions.forEach(p { const node map.get(p.id)!; if (p.parentId map.has(p.parentId)) { map.get(p.parentId)!.children!.push(node); } else if (!p.parentId) { tree.push(node); } }); const sortTree (nodes: Permission[]) { nodes.sort((a, b) a.sort - b.sort); nodes.forEach(node { if (node.children?.length) sortTree(node.children); }); }; sortTree(tree); return tree; }权限判断方法里有个细节要处理超级管理员。如果用户的权限列表里包含*说明是超管所有权限判断都返回true。这样就不用给超管逐个分配权限了hasPermission: (permission: string) { const { permissions } get(); if (permissions.includes(*)) return true; return permissions.includes(permission); },Store 定义好之后路由守卫是下一个关键点。路由守卫要做两件事未登录跳登录页已登录但无权限显示 403。AuthGuard组件接收permission或permissions参数内部调用 Store 的判断方法// src/components/auth/AuthGuard.tsx export const AuthGuard: React.FCAuthGuardProps ({ children, permission, permissions, requireAll false, fallback, }) { const { isLoggedIn, hasPermission, hasAnyPermission, hasAllPermissions } useAuthStore(); const location useLocation(); if (!isLoggedIn) { return Navigate to/login state{{ from: location }} replace /; } let authorized true; if (permission) { authorized hasPermission(permission); } else if (permissions permissions.length 0) { authorized requireAll ? hasAllPermissions(permissions) : hasAnyPermission(permissions); } if (!authorized) { return fallback ? {fallback}/ : ForbiddenPage /; } return {children}/; };路由配置里每个需要权限的页面都用AuthGuard包一层。注意permission参数填的是权限的name比如用户管理页填system:user角色管理页填system:role// src/router/index.tsx { path: system/users, element: ( AuthGuard permissionsystem:user {LazyLoad(UserListPage)} /AuthGuard ), }, { path: system/roles, element: ( AuthGuard permissionsystem:role {LazyLoad(RoleListPage)} /AuthGuard ), },按钮级权限用PermissionButton组件。它接收permission参数无权限时默认隐藏fallbackhide也可以设为禁用fallbackdisable并显示 Tooltip 提示// src/components/auth/PermissionButton.tsx export const PermissionButton: React.FCPermissionButtonProps ({ permission, fallback hide, tooltipText 您没有此操作的权限, children, ...buttonProps }) { const hasPermission useAuthStore(state state.hasPermission); const authorized hasPermission(permission); if (!authorized fallback hide) return null; if (!authorized fallback disable) { return ( Tooltip title{tooltipText} Button {...buttonProps} disabled{children}/Button /Tooltip ); } return Button {...buttonProps}{children}/Button; };用的时候很直观比如用户列表页的「新增用户」按钮PermissionButton permissionsystem:user:create typeprimary icon{PlusOutlined /} onClick{() openModal()} 新增用户 /PermissionButton如果当前用户没有system:user:create权限这个按钮直接不渲染。编辑按钮用permissionsystem:user:update删除按钮用permissionsystem:user:delete一目了然。这里要提醒一个容易踩的坑PermissionButton的permission参数必须和权限数据里的name完全一致包括大小写和冒号。我见过有人写成system:user:add但数据里是system:user:create结果按钮死活不显示排查半天。建议把权限name定义成常量用的时候引用常量而不是手写字符串。验证一下 Store 和权限判断是否正常npx ts-node -e const permissions [dashboard, system:user, system:user:create, content:article]; const has (p) permissions.includes(p); console.log(有用户创建权限:, has(system:user:create)); console.log(有用户删除权限:, has(system:user:delete)); console.log(有角色管理权限:, has(system:role)); 预期输出true、false、false。如果结果不对检查一下模拟数据里editor角色的permissionIds是否包含了正确的权限 ID。5. 跑通登录与动态菜单验证权限隔离效果前面把类型、Store、守卫都配好了这一步把它们串起来跑通完整的登录流程和动态菜单渲染。登录页要做三件事表单校验、调用登录接口、把返回的用户信息和权限写入 Store。登录接口这里用模拟函数代替真实后端。核心逻辑是根据用户名找到用户拿到用户的roleIds再根据角色拿到permissionIds最后从权限表里查出完整的权限对象返回给前端// src/pages/Login.tsx async function mockLogin(username: string, password: string) { await new Promise(resolve setTimeout(resolve, 800)); const user mockUsers.find(u u.username username); if (!user) throw new Error(用户不存在); if (password ! 123456) throw new Error(密码错误); if (user.status disabled) throw new Error(账号已被禁用); const userRoles mockRoles.filter(r user.roleIds.includes(r.id)); const permissionIds new Setstring(); userRoles.forEach(role { role.permissionIds.forEach(pid permissionIds.add(pid)); }); const userPermissions mockPermissions.filter(p permissionIds.has(p.id)); const permissionNames userPermissions.map(p p.name); return { token: jwt_token_${Date.now()}, refreshToken: refresh_${Date.now()}, user: { ...user, roles: userRoles }, permissions: permissionNames, menus: userPermissions, }; }登录成功后login(data)会把数据写入 StorebuildPermissionTree自动构建菜单树filterVisibleMenus过滤出可见菜单。侧边栏组件从 Store 读取menuTree转换成 Ant Design 的 Menu items 渲染。动态菜单的关键在于不同角色登录后menuTree的内容不同侧边栏自然就不同。管理员能看到「系统管理」下的用户、角色、菜单三个子菜单编辑者只能看到「用户管理」且没有操作按钮查看者只能看到「仪表盘」和「文章管理」。用三个账号分别登录验证一下。启动项目npm run dev打开浏览器用admin / 123456登录侧边栏应该显示完整的菜单树。退出用editor01 / 123456登录侧边栏少了「角色管理」和「菜单管理」进入用户管理页「新增用户」按钮还在因为 editor 有system:user:create权限但「删除用户」按钮消失了editor 没有system:user:delete权限。再用viewer01 / 123456登录侧边栏只剩「仪表盘」和「文章管理」用户管理页直接进不去手动输入/system/users会显示 403 页面。这个验证过程可以用一段脚本模拟权限判断的结果npx ts-node -e const adminPerms [system:user:create, system:user:update, system:user:delete]; const editorPerms [system:user:create]; const viewerPerms []; const buttons [system:user:create, system:user:update, system:user:delete]; console.log( 管理员看到的按钮 ); buttons.forEach(b console.log(b, adminPerms.includes(b) ? 显示 : 隐藏)); console.log( 编辑者看到的按钮 ); buttons.forEach(b console.log(b, editorPerms.includes(b) ? 显示 : 隐藏)); console.log( 查看者看到的按钮 ); buttons.forEach(b console.log(b, viewerPerms.includes(b) ? 显示 : 隐藏)); 预期输出管理员三个按钮全显示编辑者只显示「新增用户」查看者全隐藏。如果结果不符检查mockRoles里各角色的permissionIds是否配置正确。这里有个实测经验动态菜单渲染时defaultOpenKeys要根据当前路径自动展开对应的父菜单。我一开始写死了[/system]结果在内容管理页时系统管理菜单也展开着很别扭。后来改成根据location.pathname动态计算defaultOpenKeys{[/${location.pathname.split(/)[1]}]}这样在/system/users时展开/system在/content/articles时展开/content符合直觉。另外菜单的selectedKeys也要和当前路径同步否则点击菜单后高亮状态不对。用selectedKeys{[location.pathname]}即可因为菜单项的key就是权限的path。跑通这一步一套带权限控制的后台骨架就成型了。你可以用三个账号反复切换观察菜单、按钮、路由访问权限的变化确认权限隔离生效。6. 本篇常见报错与排查清单即使代码是 Claude Code 生成的实际跑起来还是会遇到各种报错。我把这一篇里最容易踩的坑列出来对照着排查。401 Unauthorized / invalid api key这个报错出现在 Claude Code 调用模型时说明 API Key 没配好。检查三件事ANTHROPIC_API_KEY环境变量是否设置、Key 是否复制完整有没有漏掉sk-前缀、是否执行了source ~/.bashrc让配置生效。如果用的是 TaoToken 的 Key确认 Base URL 填的是https://taotoken.net/api不要加/v1。local proxy failed / connection refused这个报错说明 Claude Code 尝试连接的地址不对。检查ANTHROPIC_BASE_URL是否设置正确有没有多余的空格或换行。在终端执行echo $ANTHROPIC_BASE_URL确认输出是https://taotoken.net/api。如果输出为空说明环境变量没生效重新 export 一次。Cannot read properties of undefined (reading choices)这个报错通常出现在 API 返回格式不符合预期时。Claude Code 期望的是 Anthropic 格式的响应如果 Base URL 指向了一个不兼容的接口就会解析失败。确认你用的是 TaoToken 的 API 地址它兼容 Anthropic 协议。如果问题持续检查 API Key 是否有余额或权限。OAuth error / authentication failed如果你之前用 Claude Code 登录过官方账号可能会残留 OAuth 凭证和 API Key 模式冲突。执行claude logout清除旧凭证然后重新用 API Key 模式配置。确认ANTHROPIC_API_KEY已设置且没有同时设置ANTHROPIC_AUTH_TOKEN之类的变量。菜单不显示 / 侧边栏空白检查filterVisibleMenus的过滤条件。菜单项必须同时满足type menu和visible true。如果某个菜单的visible是false它不会出现在侧边栏。另外父菜单如果没有path但有children也会被保留如果既没有path也没有children会被过滤掉。按钮权限不生效 / 按钮一直显示最常见的原因是permission参数和权限数据的name不一致。比如数据里是system:user:create组件里写成了system:user:add。建议把权限name定义成常量文件所有地方引用常量。另外检查hasPermission方法里超管判断逻辑如果permissions包含*所有按钮都会显示这是预期行为。路由守卫不拦截 / 直接输入 URL 能访问检查AuthGuard是否正确包裹了路由元素。如果element直接是UserListPage /而没有包AuthGuard守卫不会生效。另外确认permission参数填的是权限name不是路由路径。路由路径是/system/users权限name是system:user两者不要混淆。TypeScript 类型报错 / 属性不存在Claude Code 生成的代码引用了src/types/auth.ts里的类型。如果报错说某个属性不存在检查类型定义文件是否完整有没有被误删。另外确认tsconfig.json的strict模式是否开启严格模式下children?: Permission[]这样的可选属性需要做空值判断。登录后刷新页面登录态丢失Zustand 的persist中间件默认存储到 localStoragekey 是auth-store。如果刷新后丢失检查partialize函数是否包含了所有需要持久化的字段。另外如果浏览器禁用了 localStorage持久化会失败需要降级到 sessionStorage 或内存存储。菜单树构建顺序错乱buildPermissionTree里有两轮遍历第一轮创建所有节点并存入 Map第二轮根据parentId建立父子关系。如果顺序反了子节点找不到父节点树就构建不出来。另外sortTree递归排序时要确保sort字段是数字类型字符串排序会得到错误结果。排查的时候善用浏览器控制台的console.log。在login方法里打印data.permissions和data.menus确认后端返回的数据结构符合预期。在buildPermissionTree里打印tree看看树形结构是否正确。大部分权限问题都是数据问题代码逻辑本身很少出错。7. 继续深入从骨架到生产可用到这里一套带 RBAC 权限控制的 React Admin 后台骨架已经跑通了。你有了类型定义、状态管理、动态菜单、三层权限控制路由级 按钮级 逻辑级以及可复制的权限配置片段和角色-菜单映射表。这套骨架可以直接作为新项目的起点把模拟数据换成真实后端接口即可。但生产环境还有几件事要做。后端接口校验是必须的——前端的权限控制只是体验优化真正的安全边界在后端。每个 API 请求都要带上 token后端根据 token 解析用户角色校验该用户是否有权限执行该操作。前端隐藏了删除按钮不代表后端可以不校验删除接口的权限。操作日志审计也是生产环境的刚需。谁在什么时候删了哪个用户、改了哪个角色都要记录下来。这部分可以在后端做也可以在前端埋点上报。日志管理页面的权限name是log:operation只有管理员能看到。如果你想让 Claude Code 继续帮你完善这套系统可以给它更具体的任务比如「给用户管理页加上导出 Excel 功能导出按钮需要system:user:export权限」或者「给角色管理页加上权限变更预览勾选权限树时实时显示变更前后的差异」。Claude Code 在已有代码基础上做增量开发的效果很好因为它能读取现有文件理解类型定义和组件结构。对于需要长期做后台系统开发的团队TaoToken 的 Coding Plan 在连续任务和长会话上更稳定适合让 Claude Code 持续参与项目迭代。如果只是偶尔生成代码片段基础的 API Key 就够了。最后留一个实用技巧把权限name定义成常量文件比如src/constants/permissions.ts导出PERM.USER_CREATE system:user:create这样的常量。所有组件引用常量而不是手写字符串这样改权限命名时只需要改一个文件不会漏掉某个组件。这个习惯能帮你省下大量排查「按钮为什么不显示」的时间。
返回列表