ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

我让 Claude 从零搭了一套 RBAC 用户权限系统:全过程配置与验证记录

我让 Claude 从零搭了一套 RBAC 用户权限系统:全过程配置与验证记录 1. 从零搭 RBAC 权限系统为什么我这次交给 Claude 来写RBAC 用户权限系统说白了就是「谁能对什么资源做什么操作」这件事的工程化表达。它由三块拼起来用户、角色、权限点用户挂角色角色挂权限点鉴权时只认权限点不认人。适合谁适合任何有后台管理、有多角色协作、需要审计留痕的项目——小到一个内部工具大到 SaaS 多租户骨架都是这套。我这次的任务背景很典型一个刚初始化完的空白仓库前端 Vue后端 Java 17 Spring Boot 3数据库 MySQL认证用 SaToken需求是用户登录、用户管理、角色管理、权限分配、操作审计日志。以前这种活我得手动写几天这次我想完整走一遍「让 Claude 从需求分析到代码生成」的流程顺便验证一下规则文件管理在实际项目里到底好不好用。整篇我会交付四样东西可复制的角色-权限表结构骨架、CLAUDE.md关键片段、AOP 鉴权中间件的完整代码、以及逐条可执行的验证动作。你照着做能在本地把权限拦截跑通看到 403 是怎么被拦下来的。2. 前置准备TaoToken 接入与 Claude 调用环境要让 Claude 稳定参与这种多轮、长上下文的编码任务第一步是把调用通道配好。我用的是 TaoToken 的 API 通道官网在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 入口是 https://taotoken.net/api 。接入前先拿 Key进控制台创建https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 然后在 API Keys 页面生成密钥https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。密钥只显示一次复制后立刻存到本地环境变量别写进代码仓库。如果你只是想先验证模型通不通可以直接在模型对话页试一句https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。确认能正常返回后再进入编码环节。环境变量这样配Linux/macOS 写进~/.zshrc或~/.bashrcexport TAOTOKEN_API_KEYsk-你的密钥 export TAOTOKEN_BASE_URLhttps://taotoken.net/apiWindows PowerShell 用$env:TAOTOKEN_API_KEYsk-你的密钥 $env:TAOTOKEN_BASE_URLhttps://taotoken.net/api配完执行echo $TAOTOKEN_API_KEY确认能打印出来。这一步别跳过后面所有请求都依赖它。3. 可复制的 RBAC 骨架表结构、CLAUDE.md 与鉴权中间件3.1 五张表的数据模型RBAC 标准模型是五张表t_user、t_role、t_permission加两张关联表t_user_role、t_role_permission。关系是用户多对多角色角色多对多权限点。CREATE TABLE t_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(64) NOT NULL UNIQUE, password VARCHAR(128) NOT NULL, status TINYINT DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE t_role ( id BIGINT PRIMARY KEY AUTO_INCREMENT, code VARCHAR(64) NOT NULL UNIQUE, name VARCHAR(64) NOT NULL, status TINYINT DEFAULT 1 ); CREATE TABLE t_permission ( id BIGINT PRIMARY KEY AUTO_INCREMENT, code VARCHAR(128) NOT NULL UNIQUE, name VARCHAR(64) NOT NULL, status TINYINT DEFAULT 1 ); CREATE TABLE t_user_role ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, role_id BIGINT NOT NULL, UNIQUE KEY uk_user_role (user_id, role_id) ); CREATE TABLE t_role_permission ( id BIGINT PRIMARY KEY AUTO_INCREMENT, role_id BIGINT NOT NULL, permission_id BIGINT NOT NULL, UNIQUE KEY uk_role_perm (role_id, permission_id) );权限点的code用resource:action格式比如role:create、user:delete、audit:read。这个命名约定很重要后面注解里直接引用它。3.2 CLAUDE.md 关键片段规则文件是让 Claude 不跑偏的核心。我这次只用一个CLAUDE.md因为权限模块是一次性开发任务不值得建多层规则。核心内容## 技术栈 - Java 17, Spring Boot 3.x - MyBatis-Plus 3.5.x禁止使用 JPA - SaToken 1.38.x, Hutool 5.8.x, Lombok 1.18.x ## 代码规范 - 权限校验用 RequirePermission 注解 AOP禁止在 Controller 方法体内手写判断 - 统一用 BusinessException 抛业务异常 - 所有返回结果用 ApiResponse 包装 - 审计日志在 Controller 里手动调用 logAudit 记录 ## 查询规范 - 使用 MyBatis-Plus LambdaQueryWrapper禁止手写 SQL 字符串拼接核心就一条你不说清楚AI 就按训练数据的平均值来而那个平均值不是你团队的规范。我实测下来加了「禁止使用 JPA」这条之后生成的实体类才统一用TableName。3.3 AOP 鉴权中间件注解定义Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) Documented public interface RequirePermission { String value(); }切面实现Slf4j Aspect Component RequiredArgsConstructor public class PermissionAspect { private final PermissionService permissionService; Around(annotation(requirePermission)) public Object checkPermission(ProceedingJoinPoint joinPoint, RequirePermission requirePermission) throws Throwable { if (!StpUtil.isLogin()) { throw new PermissionDeniedException(未登录或登录已过期); } long userId StpUtil.getLoginIdAsLong(); String permissionCode requirePermission.value(); if (!permissionService.hasPermission(userId, permissionCode)) { log.warn(用户 {} 尝试访问权限 {} 被拒绝, userId, permissionCode); throw new PermissionDeniedException(没有权限执行此操作); } return joinPoint.proceed(); } }用法就是在 Controller 方法上贴注解RequirePermission(role:create) PostMapping public ApiResponseRole createRole(Valid RequestBody CreateRoleRequest request) { Role role roleService.createRole(request); auditLogService.logAudit(StpUtil.getLoginIdAsLong(), StpUtil.getLoginIdAsString(), CREATE_ROLE, role, role.getId(), 创建角色: role.getName(), null); return ApiResponse.success(role); }3.4 权限校验 ServiceOverride public boolean hasPermission(Long userId, String permissionCode) { ListString userPermissions getUserPermissionCodes(userId); return userPermissions.contains(permissionCode); } Override public ListString getUserPermissionCodes(Long userId) { ListRole roles userRoleService.getUserRoles(userId); if (CollUtil.isEmpty(roles)) return List.of(); SetString permissions new HashSet(); for (Role role : roles) { if (role.getStatus() ! 1) continue; ListLong permissionIds rolePermissionService.getPermissionIdsByRoleId(role.getId()); if (!permissionIds.isEmpty()) { listByIds(permissionIds).stream() .filter(p - p.getStatus() 1) .map(Permission::getCode) .forEach(permissions::add); } } return List.copyOf(permissions); }注意这段代码有 N1 查询问题用户有 3 个角色就是 6 次数据库查询。演示项目数据量小可以先跑生产环境要改成 JOIN 或 IN 批量查。4. 逐条验证确认权限拦截真的生效代码写完不算完得逐条验证拦截是否生效。我按这个顺序走第一步启动应用确认 SaToken 拦截器已注册。访问一个需要登录的接口未登录时应返回 401。第二步用管理员账号登录拿到 tokencurl -X POST https://localhost:8080/api/login \ -H Content-Type: application/json \ -d {username:admin,password:123456}返回体里拿到 token 后带上它请求创建角色接口curl -X POST https://localhost:8080/api/role \ -H Authorization: Bearer 你的token \ -H Content-Type: application/json \ -d {code:editor,name:编辑}管理员有role:create权限应返回 200 和创建成功的角色对象。第三步换一个只有查看权限的普通账号登录用同样的请求打创建角色接口。预期返回 403body 是{code:403,message:没有权限执行此操作}同时后端日志打印用户 X 尝试访问权限 role:create 被拒绝。第四步验证审计日志。用管理员创建角色后查audit_log表应有一条actionCREATE_ROLE、detail创建角色: 编辑的记录。第五步验证角色变更后权限实时生效。给普通账号加上role:create权限重新登录或清缓存再打一次创建接口这次应返回 200。这一步能确认权限不是写死在 token 里的。五步走完登录、鉴权、拦截、审计四条链路都验证过了。5. 本篇常见错排查报错一NoSuchMethodError: StpUtil.isLogin()SaToken 版本和 Spring Boot 3 不匹配。Spring Boot 3 要用sa-token-spring-boot3-starter不是sa-token-spring-boot-starter。检查pom.xml里的 artifactId。报错二AOP 切面不生效权限注解贴了但没拦截三个可能切面类没加Component启动类没开EnableAspectJAutoProxy或者注解加在了 private 方法上Spring AOP 只拦 public 方法。逐个排查。报错三实体类混用 JPA 和 MyBatis-Plus 注解这是CLAUDE.md没写清楚技术栈导致的。在规则文件里明确「使用 MyBatis-Plus禁止 JPA」重新生成实体类。我踩过这个坑一开始没写AI 默认给了 JPA。报错四审计日志 detail 字段为空如果用的是 AOP 自动记录切面拿不到变更前后的值。改成在 Controller 里手动调用logAudit把具体操作写进 detail。报错五权限校验返回 403 但日志没打印检查PermissionAspect里的log.warn是否被日志级别过滤了。默认warn级别应该能输出如果用了自定义 logback 配置确认com.yourpackage的级别不是ERROR。报错六getUserPermissionCodes返回空列表先查t_user_role表里该用户有没有角色记录再查t_role_permission里角色有没有权限点。常见原因是角色status不是 1被if (role.getStatus() ! 1) continue;跳过了。6. 后续怎么用把经验写回规则文件这次全流程跑下来我的结论是AI 把标准活干得又快又好但非标准的坑你得自己趟。数据模型、实体类、Mapper、AOP 拦截器这些标准模式Claude 生成的质量和手写差不多但方案选择、技术栈对齐、审计日志的记录方式、异常类型统一化这些需要人来判断。趟完坑之后把经验写回CLAUDE.md下次 AI 就不会再踩。这个闭环跑顺了效率确实比以前高。如果你要长期做编码和 Agent 类任务建议直接上 Coding Plan额度更划算https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite Claude Code 相关配置参考 https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaude_code_anthropicutm_campaignrewrite 。最后留一个实用技巧每次让 AI 写代码前先花 10 分钟把需求写到「不需要追问」的粒度。不是写大段文档而是在对话里把关键约束列清楚——用哪个 ORM、异常怎么抛、日志怎么记、返回怎么包装。这几条看起来像废话但你不说 AI 就不会做。需求写得越细改的次数越少上下文 token 浪费也越少。
返回列表