ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AI辅助开发实战:用Claude Code搭建Spring Boot用户管理

AI辅助开发实战:用Claude Code搭建Spring Boot用户管理

接到“Claude Code + Spring Boot 用户管理”这个项目时,我第一反应是:一边是热得发烫的AI编程助手,一边是Java后端开发者闭着眼睛都能写的用户CRUD,这俩凑一起有什么可讲的?但真正动起手来才发现,用户管理这套东西恰恰是检验AI辅助开发最狠的试金石——它看起来简单,实际牵扯到数据建模、接口校验、认证授权、异常处理一大堆细节,任何一个环节偷懒,都会在联调时爆雷。

这篇文章想记录我用Claude Code从零搭一个Spring Boot用户管理模块的全过程,包括模块设计、代码生成、Spring Security接入、JWT鉴权,以及Claude Code里的skills功能到底怎么定义、怎么用才能真正把效率提上去。想用AI辅助写后端、又担心它输出一堆“看着能用、跑起来就挂”的代码的同学,这篇应该能给你不少参考。

我先说清楚定位:我不打算把这篇文章写成Claude Code的官方教程,也不会复读Spring Boot的入门文档。我写的是实操里真正会遇到的选择题——比如用户表到底要不要软删除、密码加密选BCrypt还是别的、JWT的过期策略怎么定、skills文件里该写什么不该写什么。这些决策背后都有理由,你拿去就能在自己项目里落地。

1. 项目开局:为什么用户管理是检验AI辅助开发的试金石

1.1 这个项目到底要做什么

用户管理模块在业务系统里的地位,相当于新手村的第一个任务。它覆盖面广但不至于失控:注册、登录、查询用户列表、编辑用户信息、删除或禁用用户,再往后加角色和权限。单独拆开看每个接口都不复杂,组合起来却能把后端的常见知识点全部串一遍。

这次项目的目标很明确:用Claude Code作为主力编码工具,Spring Boot 3.x + Java 17做技术底座,MySQL存数据,Spring Security负责认证授权,JWT做无状态登录态管理。最终交付一个能跑、能测、能接前端的用户管理API,并且把整套开发过程沉淀成可复用的skills配置,下次做类似模块时不需要从零开始。

有一点我特别想强调:用Claude Code做这类模块,真正的价值不在于“帮你把代码写出来”——代码本来也不难写,而在于它能把那些重复性、模板化的决策固化下来。比如实体类怎么写、异常怎么统一处理、分页查询的参数怎么约定,这些如果每次都靠人肉复制粘贴,项目多了以后风格会持续漂移。用skills统一约束以后,输出质量稳定得多,审查代码时也更省心。

1.2 为什么选Claude Code而不是纯手写

说实话,写用户管理这种模块,纯手写我也就多花两三个小时,表面上看AI辅助的优势没那么明显。但实际用下来,差别主要在三件事上。

第一是上下文记忆。用户管理不是只写Controller就完事的,前面要设计表结构、写Repository、配安全过滤链、调登录逻辑,中间夹着各种实体字段和业务规则。Claude Code能在会话里记住我们前面定的表结构、字段命名规则、异常处理约定,后续生成的代码会自动对齐这些约束,不需要我反复强调。

第二是批量重构的效率。用户模块在中途改需求是家常便饭,比如“用户名从唯一改成允许重复”“密码字段要从明文换成加密存储”“新增一个状态字段用于账号禁用”。这种改动往往牵连好几个文件,纯手写需要逐个定位,用Claude Code做批量修改时,它能把牵连的文件一起改掉,并且保持接口风格一致。

第三就是skills的复用。这是我有段时间用得比较深的功能:把一套“用户模块开发规范”写成skill文件,里面包含实体类模板、Controller命名规则、异常处理建议、安全配置要点,下次再开新项目时,只需要加载这个skill,Claude Code生成的代码就会自动带上这些规范。这相当于给自己配了一个“带项目规范的结对编程伙伴”。

提示:如果你第一次接触Claude Code的skills,可以把它理解成给AI预装的“操作手册”。它不是模型本身,而是挂在每次生成请求前面的上下文指令,告诉AI在这个项目里应该遵循什么规则、优先使用什么方案。

2. 设计先行:用户表结构和技术选型背后的考虑

2.1 用户表到底该有哪些字段

动手生成代码之前,我先把表结构定了。因为后面所有代码都是围绕这张表长的,表设计错了,改起来最痛。

我这里用的是比较常规的方案,但每个字段都有讲究:

字段类型说明设计理由
idbigint主键自增即可,单库场景最简单可靠
usernamevarchar(50)登录名唯一索引,大小写敏感问题靠业务层约束
passwordvarchar(100)密码哈希存BCrypt加密结果,不存明文
nicknamevarchar(50)昵称可空,用户不填时默认用用户名
emailvarchar(100)邮箱唯一索引,后续找回密码要用
statustinyint状态1正常 0禁用,不做物理删除
created_atdatetime创建时间数据库默认值或ORM自动填充
updated_atdatetime更新时间审计需要,所有表最好都有

两个容易被新手忽略的点我得单独说。第一是password字段长度必须留够,BCrypt哈希输出是60个字符,留60正好,但考虑到后面可能换算法,我习惯了直接varchar(100)。第二是status字段代替物理删除,这是很多老项目的教训——用户删了之后,历史订单、操作日志的外键全断了,后续统计和审计都查不了。所以禁用而不是删除,是用户管理模块里的一个明智默认。

2.2 技术选型:为什么用Spring Security而不是自己写拦截器

登录鉴权这块,网上有大量“自己写个拦截器+JWT工具类”的教程,看着代码少、逻辑透明,但真放到生产里会碰到一堆边角问题:token过期后怎么处理、哪些接口要放行哪些要拦截、密码错误次数限制怎么做、权限变更后token要不要失效。这些自己从零实现,坑远比想象的多。

Spring Security虽然在初学者眼里“配置复杂、过滤链像黑盒子”,但它把这些边界问题都考虑过了。尤其是Spring Security 6以后,配置方式比老版本清晰了不少,SecurityFilterChain的Bean定义一目了然。我这次选的就是Spring Boot 3.2 + Spring Security 6的组合,认证方式用JWT。

这里有几个决策和大家同步一下:

  • 密码加密用BCryptPasswordEncoder,这是Spring Security内置的,自带盐值,不需要额外处理。
  • JWT用jjwt库,版本选0.12.x,新版的API重新设计过,比老版直观得多。
  • token有效期设置2小时,refresh token机制这次先不做,单模块够用,但代码里预留了扩展点。

为什么这么选?BCrypt是行业默认,安全性有保障,唯一要注意的是加密操作比MD5慢,但用户登录场景完全能接受。JWT的无状态特性适合前后端分离,服务端不用存session,水平扩展时不用考虑会话同步问题,这对以后部署多实例很友好。

2.3 代码结构怎么组织才不臃肿

我见过太多用户管理模块把全部逻辑压进Controller里,一个类七八百行,看着能跑,维护起来想哭。这次我按经典分层来组织:

com.example.user ├── controller // 接口层,只做参数接收和响应封装 ├── service // 业务层,事务、校验、业务流程 ├── repository // 数据访问层,Spring Data JPA的Repository ├── entity // 实体映射 ├── dto // 请求/响应对象,不直接暴露实体 ├── security // Security配置、JWT过滤器、UserDetailsService └── common // 统一响应、异常处理、常量

这种结构的好处是每一层只干一件事,Claude Code在生成代码时也更容易对齐层次边界。比如它知道Controller里不应该出现具体的SQL操作,知道DTO的字段校验注解应该放在哪一层,生成出来的代码就不会跑偏。真遇到“Controller里直接写查询逻辑”的情况,我审查时一眼就能揪出来。

3. 实操闭环:用Claude Code把模块一步一步跑起来

3.1 初始化项目与基础依赖

第一步我用Spring Initializr初始化了项目骨架,选好Spring Web、Spring Security、Spring Data JPA、MySQL Driver、Lombok这几个依赖。然后打开Claude Code的终端会话,把项目路径告诉它,接下来的工作基本就是“对话式开发”。

我给Claude Code的第一个指令是这样的:

请在这个Spring Boot项目中创建用户管理模块的基础结构,包含: - User实体类,映射到user表(表结构我已经在文档中定义) - UserRepository接口,继承JpaRepository - UserService接口和UserServiceImpl实现类 - UserController,提供注册、登录、查询用户列表、更新用户、禁用用户的接口 - 统一响应体Result<T>和全局异常处理

这里有个经验:指令里说得越具体,AI的输出越贴合需求。尤其要把“表结构字段定义”和“分层规范”写清楚,否则它可能会按照自己的理解造一套字段出来,回头对不上表。

Claude Code生成完代码后,我不会直接跑。我先做一轮人工审查,重点看三样东西:实体类字段和表结构是否一致、Controller是否只做了参数转发、异常处理有没有漏掉业务异常类型。用Claude Code做审查也很方便,直接让它“检查UserController中是否有事务操作泄漏到Controller层”就行,它能把问题点和对应文件列出来。

3.2 接入Spring Security和JWT

依赖有了、基础CRUD跑通之后,最核心的一步是接入认证授权。这一步我没让Claude Code自由发挥,而是先自己定了方案,再让它按方案实现。

方案是这样的:

  • SecurityConfig里配置SecurityFilterChain,放行注册接口、登录接口,其余接口需要认证。
  • JwtAuthenticationFilter继承OncePerRequestFilter,从请求头Authorization里取token,解析成功后把用户信息塞进SecurityContext。
  • UserDetailsServiceImpl从数据库加载用户,返回Spring Security的UserDetails对象。
  • 自定义AuthenticationEntryPoint,处理未登录返回401。
  • 自定义AccessDeniedHandler,处理无权限返回403。

这些配置点我一条一条列给Claude Code,它很快就把完整代码拼好了。过程中出现了一个值得说的细节:新版Spring Security把WebSecurityConfigurerAdapter废弃了,网上大量老教程还在用这种写法,直接复制会编译报错。Claude Code对版本差异的处理比较到位,生成的是基于SecurityFilterChain的配置方式,帮我省了不少排查时间。

JWT这块我单独说下怎么生成的。登录成功之后,把用户ID和用户名塞进token的claims里,用HS256签名,密钥是一串至少32字节的随机字符串,放在配置文件中。校验的时候先验签名,再查过期时间,最后把claims里的用户ID拿出来加载完整用户信息。

// 登录成功的核心逻辑(节选) String token = Jwts.builder() .claims(Map.of("uid", user.getId(), "username", user.getUsername())) .issuedAt(new Date()) .expiration(new Date(System.currentTimeMillis() + 7200000L)) .signWith(secretKey) .compact();

这代码看着简单,但有一个很容易踩的坑:signWith方法的参数在新旧版本里不一样,旧版传String密钥,新版要求先通过Keys.hmacShaKeyFor把字节数组转成SecretKey,否则抛异常。这种版本差异正是AI辅助开发容易“看起来对、跑起来错”的重灾区,后面我专门写了排查思路。

3.3 注册登录的业务细节

注册逻辑不是插入一条记录那么简单,至少要考虑这些问题:

  • 用户名查重:注册前先检查用户名是否已存在,存在则抛业务异常。
  • 密码加密:密码必须BCrypt加密后入库,绝不能明文保存。
  • 默认状态:新用户默认status=1,nickname为空则取username。
  • 唯一约束冲突兜底:并发情况下,即使业务层查重了,数据库唯一索引也能拦住,但异常要转成友好提示。

登录逻辑也类似,先查用户是否存在,再用passwordEncoder.matches校验密码,最后判断status是否为1,禁用的账号直接拒绝登录。这里我特别提醒一句:查不到用户和密码错误,最好返回同样的提示,比如“用户名或密码错误”,避免被用来做账号枚举。

这些细节我同样整理成要点发给Claude Code,让它实现的时候一条都不要漏。它把校验逻辑放在了Service层而不是Controller层,这也是我期望的——Controller只负责接收参数和返回结果,业务校验留在Service层,这样单元测试和复用都更方便。

3.4 接口自测与常见问题验证

代码生成后,我一般不急着写前端,先用接口测试工具把后端整个链路打一遍。我习惯用IDEA自带的HTTP Client或者直接curl命令,测以下几组场景:

  • 注册成功:返回用户信息,密码字段不能出现在响应里。
  • 重复用户名注册:返回友好错误提示。
  • 未登录访问用户列表:返回401。
  • 登录成功:返回token,token能访问受保护接口。
  • 禁用账号登录:返回账号已被禁用。
  • 伪造token访问:返回401。

这组测试用例跑下来,基本能覆盖模块80%的问题。Claude Code在这一步也能帮上忙,比如让它生成一组HTTP测试请求脚本,或者让它检查响应结构是否泄漏了敏感字段。

有一点经验必须分享:响应体里不能直接返回User实体,否则password字段会被序列化出去,这是新手最容易犯的安全问题。我的做法是定义UserVO,只返回id、username、nickname、email、status、createdAt这些字段。Claude Code生成查询接口时会按这个约定来做,但我每次还是会多看一眼响应JSON,确认没有密码字段混在里面。

4. Claude Code Skills的落地用法:把经验固化成可复用的资产

4.1 Skills到底是什么,和普通提示词有什么区别

很多用过Claude Code的朋友会问,skills和直接在对话里写“你是一个Java后端专家,请帮我写代码”有什么区别。我觉得最核心的区别在两点:复用性和作用域。

普通提示词是一次性的,每次新开会话都要重新输入,而且不同会话之间语气、格式、约束很容易漂移。Skills则是一个独立配置文件,放在项目的.claude/skills目录下,Claude Code在运行时能自动加载,里面的规则会被持续应用到整个会话里。你甚至可以在新项目中直接引用同一个skill目录,让多个项目共用一套规范。

你可以把skills理解成“给Claude Code装上的项目专属插件”。它不只是引导AI写代码,还能定义工作流、规定输出格式、约束技术方案。比如我这次就写了一个user-management的skill,内容涵盖以下要点:

name: user-management description: 用户管理模块开发规范,适用于Spring Boot + JPA + Spring Security项目 instructions: | 1. 实体类字段必须与user表结构一致,字段命名使用驼峰 2. Controller只做参数接收和响应封装,不写业务逻辑 3. Service层负责业务校验、事务、异常抛出 4. 所有响应统一使用Result<T>,业务异常使用BizException 5. 密码必须BCrypt加密存储,任何接口不得返回password字段 6. 查询接口必须分页,禁止一次性全量返回 7. 用户状态字段0为禁用,1为正常,不做物理删除

这个文件写好后,新开会话或者让Claude Code继续工作时,它会自动按照这套规范来。我后来在一个新项目里复用同一个skill,生成的用户模块代码风格和第一个项目几乎一致,维护成本直线下降。

4.2 一个实用的Skill是怎么设计出来的

写skill不是把想法堆上去就完事,我总结了一个设计流程:先回顾痛点,再抽象规则,最后验证效果。

痛点回顾阶段,我会把做过的用户模块里所有“返工”记录翻出来,看看哪些问题反复出现。比如实体类字段对不上表、Controller里写了一大坨业务逻辑、异常处理风格不统一、密码字段泄漏到响应里——这些都是真实发生过的返工点。把它们一条条列出来,就是skill里要约束的核心内容。

规则抽象阶段,我不写过于具体的代码模板,而是写“判断原则”。因为代码模板换个数据库、换个ORM框架可能就不适用了,但“Controller不写业务逻辑”这种原则是跨项目通用的。当然,实体类这类高度模板化的内容,给个参考模板也没问题,方便AI快速对齐风格。

验证阶段最关键。我会故意在一个小项目里加载这个skill,然后让它生成几个接口,逐条检查输出是否符合规则。如果发现有规则没生效,就调整skill的措辞,让它更明确、更可执行。比如我最初写的“密码要加密存储”不够明确,后来改成“密码字段必须使用BCryptPasswordEncoder进行加密,数据库中不得存储明文密码”,生效率高很多——AI对具体的、带工具名的指令,执行得比抽象的指令好得多。

4.3 Skills使用中的边界与避坑

Skills不是万能的,用了一段时间后我摸出几个边界。

第一,skills不能替代需求澄清。如果业务需求本身模糊,比如“用户管理要支持多角色”,但没说明角色和权限的关系是单角色还是多角色、角色表怎么设计,skill再完善也帮不上忙。必须先想清楚方案,再靠skill保证执行的一致性。

第二,skill规则过多会互相打架。我一开始把很多零散要求都塞进skill里,比如“不要使用Lombok”“所有注释用中文”“JWT有效期2小时”,结果生成出来的代码有时候会顾此失彼。后来我做了个取舍:只放那些“违反会造成返工或安全风险”的硬规则,其余风格类的偏好通过对话指定就好。

第三,skill不要写成百科全书。真正的经验在于“项目特有的规范”,而不是“Java基础知识”。“Java实体类用@Entity注解”这种内容写进去没有任何意义,纯属浪费上下文窗口。好的skill每一条规则都能追溯到一次实际踩过的坑。

5. 常见问题与排查技巧实录

5.1 JWT签名报错:签名密钥格式不对

这个坑我上面提到过,值得展开讲。在jjwt 0.12.x版本里,用字符串密钥直接调signWith会编译失败或者运行时报错。新版API要求:

SecretKey key = Keys.hmacShaKeyFor(jwtSecret.getBytes(StandardCharsets.UTF_8));

然后signWith(key),而不是signWith("字符串")。这个改动让很多从老项目搬代码的人中招。排查的办法很简单,看堆栈里有没有“SecretKey”或“illegal argument”字样,再确认jwtSecret长度至少32字节,不然签名算法会直接抛异常。

5.2 Security过滤链配置后接口全被拦截

Spring Security默认会拦住所有请求,如果你只是加了starter依赖但没配放行规则,注册接口也会返回401。排查时先看SecurityFilterChain里的requestMatchers配置,明确哪些接口permitAll,哪些需要认证。另一个隐藏问题是静态资源和错误页也会被拦截,必要时也要放行。我习惯把所有白名单路径统一列在一个配置常量里,改动时只动一个地方。

5.3 前端拿不到自定义异常信息

这个坑非常常见。业务异常抛出后,如果全局异常处理器没有拦截到,Spring Security的过滤器链会在进入Controller之前就把请求拦下来,返回的可能是默认的401页面或者空响应。解决办法是自定义AuthenticationEntryPoint和AccessDeniedHandler,在里面用ObjectMapper手动写出JSON格式的统一响应体。Claude Code生成这块时我也会专门检查一下,确保异常响应格式和正常响应一致,前端才能统一处理。

5.4 Lombok版本冲突

Spring Boot 3.x对Lombok版本有要求,用旧版Lombok会导致编译时报“找不到符号”或者注解处理器异常。这个没什么玄学,直接升级到和Spring Boot版本兼容的Lombok版本就行。如果出现编译报错但代码看起来没问题,优先怀疑Lombok版本和JDK版本的匹配关系。

5.5 分页查询返回结构不一致

用Spring Data JPA的Page接口返回数据时,默认结构里包含totalPages、totalElements、pageNumber等字段,但如果直接把这个Page对象塞进统一响应体,前端拿到的格式和你约定好的分页结构可能对不上。我的做法是手动构建一个PageVO,只包含list、total、pageNum、pageSize四个字段,前端对接起来统一。Claude Code生成分页接口时也会按这个约定来,省得每次联调时扯皮。

5.6 常见问题速查表

症状大概率原因解决办法
注册接口返回401Security过滤链未放行配置requestMatchers放行注册登录接口
JWT签名时报错密钥长度不足或API版本不匹配使用Keys.hmacShaKeyFor并保证密钥至少32字节
响应里出现密码字段直接返回了实体对象使用VO/DTO返回,显式排除敏感字段
编译报“找不到符号”Lombok版本与JDK/Spring版本不兼容升级Lombok版本
接口返回500但日志没异常异常被框架吞掉检查全局异常处理器是否覆盖了ControllerAdvice
禁用账号仍能登录登录逻辑没校验status字段在登录校验和token签发前统一判断用户状态

6. 实操心得与后续扩展方向

这一轮做下来,我最大的体会有两点。

第一,Claude Code这类工具真正的杠杆在于“规范沉淀”,而不只是“代码生成”。同样是用AI写用户管理模块,不加skill的情况下,它每次生成的代码风格都不同,你要花时间审查和修改;加了skill之后,它输出的代码从一开始就符合你的项目约定,审查成本低了一个量级。这个收益在项目数量增多后会越来越明显。

第二,用户管理模块虽然简单,但它是很多系统的骨架。把认证方式、异常约定、统一响应、分页结构这些基础决策做好,后面加角色权限、菜单管理、部门管理都只是顺着同样的路子扩展而已。我在这次项目里没有做refresh token,但在JWT工具类里预留了刷新的方法;也没有做角色权限表,但用户表里预留了role字段——这些扩展点都是为了让后续迭代不用动大手术。

最后分享一个我实际操作中的小技巧:在把项目交给Claude Code之前,先花15分钟把“表结构定义”“分层规范”“统一响应约定”这几个核心约束写进项目文档,然后让Claude Code读取这些文档再开始生成代码。这个习惯比任何提示词都管用,因为AI对项目上下文的利用能力远被大多数开发者低估了——它读得懂你写的东西,只要你愿意先写清楚。

如果你也想在自己的项目里试这套玩法,我建议从一个小模块开始,比如就做一个最简单的用户注册登录,把skill定义、代码生成、安全接入、问题排查整个流程走一遍,再决定要不要规模化使用。工具终究只是工具,真正决定项目质量的,还是你定义的规范和判断力。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进