我让 AI 从零做了一个用户权限系统,这是全过程记录
摘要 :本文记录了作者使用 Claude AI 从零开发一个完整 RBAC 权限系统的全过程。文章详细介绍了从需求分析、方案规划、规则制定到代码实现的完整流程,重点分享了 AI 开发中的实践经验:1) 使用
/plan命令先规划再开发,避免方向错误;2) 通过 CLAUDE.md 明确技术栈和代码规范;3) 采用三阶段开发法(需求分析→定规则→写代码);4) 实现基于注解+AOP的权限拦截器;5) 处理审计日志和异常统一化。文章还总结了 AI 开发的优缺点:AI 擅长标准模板代码(数据模型、实体类、AOP),但在技术栈对齐、业务判断和性能优化等方面仍需人工介入。最终结论是:AI 把标准活干得又快又好,但非标准的坑你得自己趟,趟完把经验写回规则文件,下次 AI 就不会再踩。
接一个需求
从零开始------一个刚初始化完的空白仓库,要让 AI 从零搭出一套完整的 RBAC 权限系统。
前端用 Vue,后端 Java 17 + Spring Boot 3,数据库 MySQL,认证用 SaToken。需求包括:用户登录、用户管理、角色管理、权限分配、操作审计日志。
以前这种活儿我得手动写几天:设计表结构、写实体类、Service 层、Controller 层、单元测试,然后 code review、改 bug。这次我想试试让 Claude 从需求分析到代码生成完整走一遍,顺便验证一下之前文章里讲的规则管理方法,在实际项目里到底好不好用。
第一阶段:先搞清楚要做什么
接手这种需求,我不会一上来就让 AI 写代码。超过 3 个文件需要改动的任务,先用 /plan 让它规划方案------这是我之前文章里总结的经验,改到一半发现方向不对,整个上下文废了只能重新开始,这种情况我遇到过两次。
我让 Claude 读了项目现有代码结构,然后给它这个需求:
diff
/plan 这是一个AI开发一个rbac系统示例,现在处于刚刚初始化仓库,还没进行任何开发的状态。
前端可以使用vue,后端使用java,版本使用17,数据库使用mysql。
增加用户登录、用户管理与权限管理功能。
RBAC 权限系统,要求:
- 支持用户多角色分配
- 基于角色的细粒度权限控制
- 权限校验用 AOP 拦截,不在每个方法里手写
- 角色和权限变更必须记录审计日志
- 数据库用 MySQL
它给出的方案对比了三种实现:
- 基于角色的 RBAC------用户关联角色,角色关联权限点
- 基于策略的策略模式------每个权限定义一个策略类
- 基于表达式语言的 SpEL------用 Spring Expression Language 写权限表达式
我选了第一种------RBAC 是标准方案,SaToken 原生支持角色和权限判断,团队里其他人也好接手。后两种更灵活,但学习成本也高。
确认后,Claude 把方案拆成了任务清单,按依赖关系排序:
markdown
1. 设计数据模型(User-Role-Permission 五张关系表)
2. 写实体类和 Mapper(MyBatis-Plus)
3. 写 Service 层(权限校验、角色分配)
4. 写 AOP 拦截器(基于注解的权限校验)
5. 写 Controller(用户/角色/权限 CRUD)
6. 写审计日志
7. 写全局异常处理
这个任务清单就是后面开发过程的"施工图"。
第二阶段:定规则
这次没有搞三层规则分层,只用了一个 CLAUDE.md。原因很简单------这个权限模块是一次性开发任务,不是要长期维护的独立子系统。专门为它建一个 L3 文件,后期维护成本大于收益。
CLAUDE.md 里的核心规则:
markdown
## 技术栈
- Java 17, Spring Boot 3.x
- MyBatis-Plus 3.5.x, SaToken 1.38.x
- Hutool 5.8.x, Lombok 1.18.x
## 代码规范
- 使用 @RequirePermission 注解 + AOP 做权限校验
- 禁止在 Controller 方法体内写权限判断逻辑
- 使用 BusinessException 抛业务异常
- 统一用 ApiResponse 包装返回结果
## 查询规范
- 使用 MyBatis-Plus LambdaQueryWrapper
- 禁止手写 SQL 字符串拼接
核心就一条:你不说清楚,AI 就按它的训练数据来,而它的训练数据是全网代码的平均值,不是你团队的规范。
第三阶段:开始写代码
数据模型
先跑 /init 让 Claude 确认项目结构,然后从任务 1 开始。
RBAC 的标准数据模型是 5 张表:
scss
┌─────────┐ ┌──────────────┐ ┌─────────┐
│ t_user │ ────▶│ t_user_role │◀────│ t_role │
│ (用户) │ │ (用户-角色) │ │ (角色) │
└─────────┘ └──────────────┘ └────┬────┘
│
┌─────────────────┐
│t_role_permission│
│ (角色-权限) │
└─────────────────┘
│
┌──────┴─────┐
│t_permission│
│ (权限) │
└────────────┘
Claude 生成的实体类用的是 MyBatis-Plus 注解,不是 JPA------这也是提前在 CLAUDE.md 里说清楚技术栈的好处:
java
@Data
@TableName("user_role")
public class UserRole {
@TableId(type = IdType.AUTO)
private Long id;
@TableField("user_id")
private Long userId;
@TableField("role_id")
private Long roleId;
@TableField(value = "create_time", fill = FieldFill.INSERT)
private LocalDateTime createTime;
}
用 MyBatis-Plus 的好处是不需要操心 EAGER/LAZY 这些 JPA 的加载策略------每次查询都是主动调用,N+1 问题靠手动优化查询逻辑来避免,后面会说。
AOP 权限拦截器
先说 AOP 部分,因为这个是权限系统的核心。
@RequirePermission 注解定义得很简单:
java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequirePermission {
/**
* 权限标识,格式为 "resource:action"
*/
String value();
}
切面实现:
java
@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("未登录或登录已过期");
}
String permissionCode = requirePermission.value();
long userId = StpUtil.getLoginIdAsLong();
boolean hasPermission = permissionService.hasPermission(userId, permissionCode);
if (!hasPermission) {
log.warn("用户 {} 尝试访问权限 {} 被拒绝", userId, permissionCode);
throw new PermissionDeniedException("没有权限执行此操作");
}
return joinPoint.proceed();
}
}
用法就是在 Controller 方法上贴注解:
java
@RequirePermission("role:create")
@PostMapping
public ApiResponse<Role> createRole(@Valid @RequestBody CreateRoleRequest request) {
Role role = roleService.createRole(request);
auditLogService.logAudit(...);
return ApiResponse.success(role);
}
这个部分 Claude 生成的质量很高------AOP + 自定义注解是 Spring 生态里的标准模式,训练数据里样本多。而且用了 SaToken 的 StpUtil.isLogin() 做登录检查,说明 AI 读了一下项目里的依赖。
权限校验的 Service 层
这里是第一个值得说的地方。
AI 生成的 hasPermission 方法:
java
@Override
public boolean hasPermission(Long userId, String permissionCode) {
// 获取用户所有角色
List<String> roleCodes = userRoleService.getUserRoleCodes(userId);
if (roleCodes.isEmpty()) {
return false;
}
// 获取用户所有角色的权限
List<String> userPermissions = getUserPermissionCodes(userId);
return userPermissions.contains(permissionCode);
}
然后是 getUserPermissionCodes 的具体实现:
java
@Override
public List<String> getUserPermissionCodes(Long userId) {
List<Role> roles = userRoleService.getUserRoles(userId);
if (CollUtil.isEmpty(roles)) {
return List.of();
}
Set<String> permissions = new HashSet<>();
for (Role role : roles) {
if (role.getStatus() != 1) continue;
List<Long> permissionIds = rolePermissionService.getPermissionIdsByRoleId(role.getId());
if (!permissionIds.isEmpty()) {
List<Permission> perms = listByIds(permissionIds);
perms.stream()
.filter(p -> p.getStatus() == 1)
.map(Permission::getCode)
.forEach(permissions::add);
}
}
return List.copyOf(permissions);
}
这段代码其实有 N+1 查询的问题。 对每个角色调用 getPermissionIdsByRoleId,再对每个角色调用 listByIds 批量查权限。用户有 3 个角色就是 6 次数据库查询(3 次查权限 ID + 3 次批量查权限详情)。
不过这个项目是个人演示项目,数据量小,暂时没到必须优化的程度。如果放到真实生产环境,我会让 AI 改成一次 JOIN 查询或者用 IN 子句批量查------这就是我之前文章里说的,AI 不会主动考虑数据量增长后的性能问题。
审计日志
审计日志这块,AI 最初的方案是用 AOP 切面自动记录。后来改成了在 Controller 里手动调用 auditLogService.logAudit()。
原因是:AOP 切面虽然看起来省事,但很难记录变更前后的具体值。比如"把用户从'普通用户'改成了'管理员'"这种信息,AOP 只能拿到方法名和参数,拿不到变更前的数据。手动调用虽然多写几行,但记录的精度更高。
实际的 AuditLog 实体:
java
@Data
@TableName("audit_log")
public class AuditLog {
@TableId(type = IdType.AUTO)
private Long id;
@TableField("operator_id")
private Long operatorId;
@TableField("operator_name")
private String operatorName;
@TableField("action")
private String action;
@TableField("target_type")
private String targetType;
@TableField("target_id")
private Long targetId;
@TableField("detail")
private String detail;
@TableField("ip_address")
private String ipAddress;
@TableField(value = "create_time", fill = FieldFill.INSERT)
private LocalDateTime createTime;
}
Controller 里调用的方式:
java
auditLogService.logAudit(
StpUtil.getLoginIdAsLong(),
StpUtil.getLoginIdAsString(),
AuditAction.CREATE_ROLE.name(),
"role",
role.getId(),
"创建角色: " + role.getName(),
null
);
AI 开发很少一把过
这篇权限模块的代码,从写完到能跑,中间改了好几个回合。这其实挺正常的------用 AI 写代码,一次需求描述清楚、AI 一次写完、你一次 review 通过,这种情况很少见。
原因也简单:AI 不会主动想边界场景。你让它"做一套 RBAC 权限系统",它会给你一个标准的 RBAC 实现,但标准实现不等于适合你的场景。
这次主要的反复集中在几个问题上:
技术栈对齐
最开始 AI 生成的代码混用了 JPA 和 MyBatis-Plus 两种 ORM------有些实体用了 @Entity,有些用了 @TableName。原因是在最初的 CLAUDE.md 里我没写清楚技术栈,AI 按自己的训练数据默认选了 JPA(Spring Boot 教程里 JPA 出现频率高)。
后来在 CLAUDE.md 里加了一条"使用 MyBatis-Plus 3.5.x,禁止使用 JPA",之后生成的代码就统一了。
审计日志的记录方式
上面说过了,AI 一开始用 AOP 切面自动记录审计日志。看起来省事,但实际效果是记录的信息太粗糙------只能拿到方法名,拿不到变更前后的具体值。
后来改成了 Controller 里手动调用 auditLogService.logAudit(),虽然多写几行,但每条审计日志的 detail 字段都能写清楚具体做了什么操作。
异常处理统一化
AI 生成的 Controller 里,有的地方抛 RuntimeException,有的地方返回 null。后来在 CLAUDE.md 里加了"使用 BusinessException 抛业务异常",同时写了 GlobalExceptionHandler 统一处理:
java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public ApiResponse<Void> handleBusinessException(BusinessException e) {
return ApiResponse.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(PermissionDeniedException.class)
public ApiResponse<Void> handlePermissionDenied(PermissionDeniedException e) {
return ApiResponse.error(403, e.getMessage());
}
}
需求写得越细,改的次数越少
这三个问题回头看,都不是技术难题,都是"初始需求描述不够细"导致的。
所以我现在养成了一个习惯:让 AI 写代码之前,先花 10 分钟把需求写到"不需要追问"的粒度。 不是写大段文档,而是在对话里把关键点说清楚:
markdown
/implement 按任务清单实现权限模块,注意以下几点:
1. 使用 MyBatis-Plus,禁止使用 JPA
2. 权限校验用 @RequirePermission 注解 + AOP 拦截
3. 统一用 BusinessException 抛业务异常
4. 审计日志在 Controller 里手动调用 logAudit 记录
5. 所有返回结果用 ApiResponse 包装
这几条看起来都是废话("用 MyBatis-Plus"、"抛 BusinessException"谁不知道),但你不说 AI 就不会做。AI 不是不懂 MyBatis-Plus,是它不知道你的项目已经选定了技术栈,需要主动遵守。
需求写得越细,改的次数越少,上下文 token 浪费也越少。 这个道理跟带新人其实一样------你交代任务时不说清楚,新人做出来肯定不是你要的,然后反复沟通,双方都累。
写完之后的一些感受
这次全流程跑下来,AI 真正帮上忙的是这些环节:
做得好的:
- 数据模型设计------标准 RBAC 模型,AI 生成的表结构和关联关系没出过错
- 实体类和 Mapper------MyBatis-Plus 代码是 AI 最擅长的,生成质量和手写差不多
- AOP 拦截器------标准模式,训练数据多,一次写对
- 全局异常处理------RestControllerAdvice 模板代码,AI 生成的很完整
必须人来做:
- 方案选择(RBAC vs 策略 vs SpEL)------这是架构判断,AI 只能列出选项
- 技术栈对齐------AI 默认混用 JPA/MyBatis,需要人提前说清楚
- 审计日志的记录方式------AOP 自动记录还是手动调用,需要业务判断
- 异常类型的统一化------AI 会混用 RuntimeException 和自定义异常,需要人规范
我的结论是:AI 把标准活干得又快又好,但非标准的坑你得自己趟。趟完把经验写回规则文件,下次 AI 就不会再踩。 这个闭环跑顺了,开发效率确实比以前高。
本文对应的代码仓库开源在 Gitee:gitee.com/tangyuewei/...,包含 RBAC 权限系统的完整实现、CLAUDE.md 规则文件,以及开发过程中的提示词记录。可以直接拉下来跑。
tangyuewei,从后端出发,用 AI 拓展到全栈的工程师。