从GitHub上拉下一个DDD开源脚手架,想快速改造成自己的RBAC权限模块,结果发现AI生成的代码总是“看着对、跑不起来”——这个问题困扰了我整整两周。后来我悟出一个关键:问题不在脚手架,也不在AI,而在于我没有给AI一套“干活的标准动作”。直到我把网上找的DDD资料、开源项目的开发文档、RBAC设计规范统一整理成一个可复用的SKILL,让AI按这套技能去执行,才真正把脚手架变成了自己的东西。
这篇文章就是我把“DDD开源脚手架 + 自定义SKILL + RBAC业务规则”三者打通的全过程。它适合两类人看:一类是拿了开源脚手架但改不动、想借助AI加速的开发者,另一类是刚接触AI编程助手的Skill机制、想知道这东西到底怎么落地的人。我会从为什么非要造这个SKILL讲起,到选定脚手架、编写Skill、喂料、踩坑、复盘,完整走一遍。
1. 为什么非要自己造一个RBAC的SKILL——脚手架、DDD与AI编码的三角关系
很多人拿到开源脚手架后的第一反应是“跑起来再说”。但DDD脚手架和普通的CRUD脚手架不一样,它最大的价值在于分层约束和领域建模,而这两点恰恰是AI最不擅长凭空生成的。如果你只是开一个对话窗口,说“帮我写个用户角色权限模块”,AI大概率会给你一套标准的Controller-Service-Mapper三层代码,跟你拉的DDD脚手架完全不匹配。
1.1 开源脚手架的通病:能用但改不动
我拉过的DDD脚手架不下五个,它们有一个共同特点:示例代码非常完整,完整到我不敢动。有的把订单、商品、库存都写好了,我只需要删掉这些示例,换成自己的业务。但真到了删和改的时候,问题就来了——脚手架里自定义了一套BaseEntity、一套统一返回体、一套异常处理链,甚至还有自己的分页模型。
如果你不把这些抽象基类和基础设施层的约定告诉AI,它生成的代码就永远是“裸奔”的:
- 不理解脚手架里的
BaseRepository为什么存在,直接把业务代码写在Controller里。 - 不知道脚手架配好了
ApplicationEventPublisher,权限变更事件该怎么发完全没头绪。 - 分页返回用的是脚手架自定义的
PageResult<T>,AI却生成了Spring Data的Page<T>。
所以,改造脚手架的第一步,不是让AI去“理解”,而是先把脚手架的“脾气”摸清楚,再把它固化成一套可执行的规则交给AI。这其实就是SKILL的雏形。
1.2 SKILL的本质:把AI从“问答机器人”变成“按SOP干活的老员工”
Claude Code、Cursor、Codex这些AI编程助手,默认状态下是“你问一句、我答一句”的问答模式。而SKILL机制改变的是交互范式:它允许你把一整套路演、规则、参考文档、验收标准打包成一个独立的文件夹。AI遇到对应任务时,会自动加载这个文件夹里的所有材料,按里面写好的流程执行。
我自己的理解是,普通Prompt是“你帮我写一个RBAC模块”,SKILL是“你是一个熟悉我们公司DDD脚手架的资深后端工程师,请先查看脚手架的项目结构,再按DDD分层规范,在domain层建立Role与Permission聚合,在application层实现权限校验用例,在infrastructure层实现仓储,最后运行测试并对照验收清单逐项检查”。你们感受一下,这两种指令产生的代码质量差距有多大。
SKILL内部通常由三部分组成:
- 元信息:定义一个名称、一段描述,标明这个技能在什么时候被触发。
- 指令正文:告诉AI按什么顺序、用什么约束、输出什么格式来执行任务。
- 参考资料:把开发文档、架构规范、示例代码放到
references目录,让AI在做决策时能随时查阅。
也就是说,SKILL把一个灵活的大模型封装成了一个“有固定操作流程的业务工具”。你给它一个输入,它按你沉淀的SOP给你对应的输出。
1.3 为什么是RBAC:业务边界清晰,最适合当Skill的“试验田”
我第一次写SKILL选RBAC,不是因为它简单,而是因为它的业务边界足够清晰。一个标准的RBAC模型,核心就三个实体:用户、角色、权限,再加上用户-角色、角色-权限两组关系。权限的粒度又可以分为菜单权限、操作权限、数据权限。这套模型足够标准,任何资料里都能找到成熟的设计,喂料成本低。
同时RBAC又有足够的深度:它涉及领域服务的设计(比如角色分配权限时的校验、超级管理员保护)、跨聚合的事务(用户与角色的关联)、缓存策略(权限变更后缓存如何失效)、Spring Security的集成(如何把权限模型接入认证授权框架)。这样一个“入门不难、深挖不浅”的模块,非常适合验证SKILL机制到底能不能产出可运行、可维护的代码。
我踩完这轮坑之后最大的体会是:如果你也想尝试SKILL机制,不要一上来就做那种高度依赖业务上下文的复杂模块,选一个边界清晰、规则明确的模块练手,效率和成就感会高很多。
2. 第一步:选定DDD脚手架并摸清它的“脾气”
SKILL写得再好,也得落到一个具体的脚手架上。我建议你在写Skill之前,先把脚手架选型、骨架梳理、约束记录这三件事做完。这个过程有点像接手一套别人写了一半的代码库,你总得先弄清楚哪些是自由的、哪些是有约定俗成的规则。
2.1 我选择的脚手架与选型理由
这次我用的是一个以Maven多模块为基础、采用经典四层架构(接口层、应用层、领域层、基础设施层)的DDD脚手架。选它的理由很朴素:
- 模块边界清晰:每个层是一个独立的Maven module,依赖方向严格从上到下,领域层不依赖任何框架。
- 基础设施层足够厚:已经集成了MyBatis-Plus、Redis、Spring Security,省去我大量拼装工作。
- 示例代码覆盖全:包含了一个完整的示例业务,从Controller到Domain到Repository都能看到标准写法。
选型这件事不需要太纠结,关键是你要能看透它的骨架。如果一个脚手架你花了两天都看不明白它怎么把UserController的请求最终落到UserRepository的,那就直接换一个。看不透骨架的脚手架,喂给AI也只会产出更多混乱。
2.2 梳理脚手架的核心骨架:从分层到持久化
我一般会把脚手架的梳理结果记成一张表格,方便后续写SKILL时直接引用。以下是我当时整理的维度,你们可以参考:
| 层级 | 典型包名 | 职责 | 该写什么 / 不该写什么 |
|---|---|---|---|
| 接口层 | interfaces/controller | HTTP入口、参数校验、响应包装 | 不写业务规则,只做参数转换与调用应用服务 |
| 应用层 | application | 用例编排、事务管理、事件发布 | 不写具体算法,不直接操作数据库 |
| 领域层 | domain | 实体、值对象、聚合根、领域服务、领域事件 | 不依赖Spring、MyBatis等技术框架 |
| 基础设施层 | infrastructure | 仓储实现、外部服务适配、消息队列、缓存 | 不包含业务规则,只做技术适配 |
另外还有几条关键约定,比如统一响应体是Result<T>,统一异常码定义在ErrorCodeEnum里,分页查询使用脚手架自带的PageQuery对象。这些信息你不记下来,AI就只能在代码里瞎猜,猜到哪算哪。
2.3 把脚手架的关键约束写成“喂料清单”
梳理完骨架后,我建议你专门建一个scaffold-notes.md,把脚手架里那些“不写就会炸”的关键约定逐条摘出来。这份清单后面会成为SKILL中references目录的核心资料之一。
我那份notes大致包含以下内容:
- 领域对象的命名规范:聚合根用
XxxAggregate,仓储接口用XxxRepository,仓储实现用XxxRepositoryImpl。 - 统一响应体的类名与静态方法:
Result.success()、Result.fail(code, msg)。 - 领域事件的发布方式:在应用层通过
DomainEventPublisher发布,领域层不能直接持有该组件。 - 权限注解的使用方式:脚手架自带
@PreAuthorize的常量类AuthConstants。 - 数据库表命名习惯:多对多关系表直接用
user_role、role_permission这种下划线命名。
为什么要把这些单独摘出来?因为官方文档和Demo代码通常散落在项目的各个角落,AI直接读源代码也不是不行,但会消耗大量上下文窗口,导致它后面写代码时“忘记”关键约束。最后你会发现,喂给AI的不是越多越好,而是越“结构化”越好。
3. 第二步:按新SKILL造轮子——从零编写RBAC专属Skill
骨架摸清了,接下来就是核心环节:写一个属于自己的RBAC SKILL。这一步有两个关键词,一个是“新”——不要照抄网上现成的SKILL模板,要基于你自己的脚手架定制;另一个是“造”——你要把分散在脑子和资料里的经验,固化成一条可执行的路径。
3.1 SKILL文件的目录结构与配置写法
以Claude Code的SKILL机制为例,一个技能通常放在.claude/skills/目录下,Core Skills则放在~/.claude/skills/,这样所有项目都能用。我建议你放在项目里,因为RBAC这个SKILL高度依赖当前脚手架的目录结构,跟着项目走更合理。
推荐目录结构是这样的:
.claude/skills/rbac-builder/ ├── SKILL.md ├── references/ │ ├── scaffold-notes.md │ ├── ddd-layering.md │ ├── rbac-model.md │ └── security-integration.md └── scripts/ └── generate-tree.shSKILL.md是技能的入口文件,前面要用YAML格式写元信息:
--- name: rbac-builder description: 基于当前DDD脚手架生成RBAC权限管理模块,包括用户-角色-权限的领域模型、应用服务、接口层以及基础设施层实现。 ---需要注意的是,description要写得足够精确,因为AI就是靠扫描这个字段来决定什么情况下激活这个技能。如果你写得太泛,比如“生成权限系统”,反而容易在非相关任务里被误触发。
3.2 SKILL内部指令的设计:分为“业务规则”与“工程规范”两部分
SKILL.md的正文部分,我强烈建议拆成“业务规则”和“工程规范”两块。业务规则是RBAC本身的领域逻辑,工程规范是当前脚手架对代码的约束条件。这两者的性质完全不同,混在一起AI容易乱。
业务规则部分,我会明确写下:
- 模型包含五张表:
sys_user、sys_role、sys_permission、user_role、role_permission。 - 用户与角色、角色与权限都是多对多关系。
- 一个角色可以绑定若干权限,一个权限可以被多个角色引用。
- 超级管理员角色不能被删除,也不能被修改权限。
- 删除角色前需要先解绑用户与权限关联。
- 权限变更后必须发送
PermissionChangedEvent,由应用层监听并清除对应缓存。
工程规范部分,我会写下:
- 领域层禁止出现任何Spring相关注解。
- 仓储接口继承脚手架自带的
BaseRepository<T>。 - 应用服务命名以
XxxAppService结尾。 - Controller层只负责参数校验与返回
Result<T>包装结果。 - 所有异常必须通过
BizException抛出,全局异常处理器统一捕获。
这两块信息合在一起,SKILL才称得上“完整”。
3.3 让AI真正“吃透”DDD核心逻辑的三个技巧
第一,在SKILL里放置反例。只告诉AI“应该怎么做”是不够的,还要告诉它“绝对不要怎么做”。我会在工程规范里加一段注释:
注意:不要生成贫血模型。禁止把业务逻辑写在ServiceImpl里。如果发现某个方法只包含getter/setter和数据访问逻辑,请重新检查是否属于领域层职责。
反例对AI的纠偏效果极其明显。相比正面输出一堆“最佳实践”,直接把错误路径封杀掉,生成结果会干净得多。
第二,使用Ask模式兜底。当AI拿到一个需求但发现脚手架里没有对应的基础设施时,与其擅自做决定,不如停下来问。我会在SKILL里写一条指令:
当脚手架缺少某个必要的组件(如缓存管理器、分布式锁模板)时,先列出缺失清单并询问用户如何处理,不要自行引入新的依赖。
这一步能避免AI在生成代码时偷偷往pom.xml里塞一堆它觉得“应该有”的依赖,最后导致编译都过不了。
第三,设置验收标准。SKILL不能只负责生成,还得负责质检。我在文件末尾放了一个Checklist,要求AI在输出代码前逐项自检:
- 领域层是否不依赖Infrastructure类?
- 仓储实现是否已注入到Spring容器?
- 应用服务是否开启了事务?
- Controller是否统一使用
Result<T>返回? - 权限注解是否使用了脚手架定义的常量?
- 表名和字段名是否与SQL脚本一致?
有了验收标准,AI生成的代码会稳定很多,因为它被迫在“交付前”做一轮自查,而不是写完就跑。
4. 第三步:网上资料与开发文档如何喂给AI才有效
SKILL写完了,但这只搭好了框架。真正让AI“懂行”的,是你喂进去的网上资料和开发文档。这一步很多人会犯错,一上来就把下载的PDF、几十个网页链接、满屏的源码往对话窗口里丢。结果就是AI被大量的冗余信息干扰,生成的代码反而更差。
4.1 资料筛选策略:不是越多越好
我喂料前会先做一轮筛选,标准很简单:
- 优先选择官方开发文档和脚手架的Wiki,因为这类资料与你的工程环境最匹配。
- 权威博客中讲DDD落地经验和RBAC权限设计的内容可以选,但要过滤掉发布时间超过三年的旧文章。
- 过时版本的技术文章尽量不喂,否则AI会被老版本API带偏,生成一堆被废弃的调用。
- 凡是包含完整代码片段的文章,优先保留;只有概念没有代码的,作为背景知识保留。
我当时搭建的喂料库主要包括四类:脚手架自带的开发文档、DDD分层架构的实践笔记、RBAC权限模型的设计方案、Spring Security的官方参考文档。每一类都精炼成几百字到一千字不等的Markdown文件,放进SKILL的references目录。
4.2 喂料的格式与顺序
格式方面,我建议尽量统一成Markdown,因为AI对Markdown结构化内容的解析效果远好于PDF和网页截图。具体格式可以这样组织:
- 来源:文档标题或链接,方便溯源。
- 核心结论:这段资料最值得记住的一点。
- 关键代码:粘贴必要的代码片段并用注释标注出处。
- 与本项目的关系:说明这段资料怎么用在RBAC模块里。
顺序比内容更讲究。SKILL激活后,AI读取资料是有先后之分的。我会在SKILL.md里显式指定读取顺序:
- 先读
scaffold-notes.md,建立对工程结构的整体认知。 - 再读
ddd-layering.md,明确各层的代码边界。 - 然后读
rbac-model.md,确定领域模型与表结构。 - 最后读
security-integration.md,解决权限模型与Spring Security的对接问题。
这样AI就像一个有经验的工程师在写代码:先看大框架,再定核心模型,最后考虑技术集成。顺序反了,AI很容易陷入“先写Controller后补Domain”的怪圈。
4.3 带你看一次完整的“喂料→生成→验收”闭环
我完整走一遍当时的流程。激活命令很直接,就是拖一个需求进去:
@rbac-builder 请基于当前脚手架生成完整的RBAC权限管理模块,包含用户管理、角色管理、权限管理三个功能。AI会先加载.claude/skills/rbac-builder下的所有资料,然后按SKILL.md里定义的流程开始工作。它会先列出自己的执行计划,跟我确认脚手架根目录与包名,再按领域层、应用层、基础设施层、接口层的顺序逐一生成代码。
其中有一次,AI生成的RoleServiceImpl里包含了一个校验角色名是否存在的checkNameExisted方法,但它把这段业务逻辑写在了应用层,代码大致长这样:
@Override public void updateRole(RoleUpdateCommand cmd) { // 校验角色名唯一 Role role = roleRepository.findByName(cmd.getName()); if (role != null && !role.getId().equals(cmd.getId())) { throw new BizException(ErrorCodeEnum.ROLE_NAME_EXIST); } // ... }这其实属于领域服务的职责,不该写在应用层。我检查之后把这条规则加进了SKILL.md的反例清单里,下一次生成同类代码时,AI就自动把重名校验放到了领域层。这就是“验收闭环”的价值——你不只是在用AI,还在持续给它的行为纠偏。
5. 落地过程中的坑:从生成的代码到能跑起来的距离
SKILL再完善,AI生成的代码也不可能一次性跑通。从我这次实践来看,生成代码与脚手架能够“握手成功”之间,还有相当一段距离。这一段我列几个最有代表性的坑,方便大家遇到问题的时候有个排查方向。
5.1 最常见的几类生成错误
第一类是包名不一致。AI在读取资料时偶尔会把示例代码里的com.example.rbac直接带进生成结果,导致Controller里import的类和实际类不在同一个包下。这个问题在编译期就会暴露,算相对好修的。
第二类是仓储实现缺失。AI生成的领域层仓储接口没问题,但基础设施层它有时只给了一个空壳类,没有真正实现MyBatis-Plus的BaseMapper注入。这类问题通常在启动时暴露,报错信息会指向一个NullPointerException。
第三类是事务注解遗漏。RBAC模块里角色赋予权限、用户绑定角色都涉及多表操作,应用层必须开启事务。AI偶尔会漏掉@Transactional,数据写到一半报错无法回滚,这个坑隐蔽且致命。
| 错误类型 | 报错特征 | 定位方向 |
|---|---|---|
| 包名不一致 | 编译失败,import找不到类 | 检查pom.xml模块路径与生成的package |
| 仓储未实现 | 启动NPE,Mapper未注入 | 检查Impl类是否有@Repository注解与BaseMapper |
| 事务遗漏 | 数据一致性问题,部分成功 | 检查应用服务方法是否加@Transactional |
| 领域对象贫血 | 业务逻辑出现在Service中 | 对照SKILL的领域层规则回重构 |
5.2 让脚手架与生成代码“握手”的关键
很多朋友问我,为什么AI生成的单测都能过,一放到项目里就崩?这里的关键在于,单测是独立跑的,而脚手架里的代码是协作运行的。任何一个自定义类没接上,整个链路就断了。
我复盘下来,最容易“接不上”的节点有三个:
- 统一响应体:脚手架里的
Result<T>有自己的一套静态方法,AI默认会写Spring标准的ResponseEntity或Map,这会导致接口出参格式和项目里其他人写的不一致。 - 分页模型:脚手架自定义了
PageQuery和PageResult,AI如果用了Spring Data的Pageable,前端联调时拿到的分页字段就完全对不上。 - 异常体系:脚手架有全局异常处理器,业务异常统一抛
BizException。AI如果不按这个套路来,就会生成一堆try-catch把异常吞掉,导致错误信息失真。
怎么解决?三个词:约束、检查、回补。约束是在SKILL里写明必须使用脚手架的类;检查是生成完代码后全局搜索@Transactional、Result.success等关键调用;回补是发现AI没按规则走后,把缺失的代码手动补上,同时把这类错误写进SKILL的反例清单里。
5.3 从能跑到能上线的差距
代码能跑起来,离能上线还有一段路。RBAC模块上线前,我建议至少做三件事:
第一,补齐单元测试。领域层的测试优先写,尤其是角色删除、权限分配这些核心用例。AI生成的领域层代码被测出来后,你可以把测试用例也沉淀到SKILL里,下次生成模块时自动带上。
第二,补审计日志。谁在什么时候给哪个角色加了什么权限,这种审计信息在正式环境比业务逻辑还重要。SKILL里如果没有审计相关约束,AI生成的代码大概率不会主动写日志。
第三,验证权限缓存的一致性。RBAC模块通常会把用户权限缓存到Redis,生成代码里如果缺少缓存更新的逻辑,用户改完权限后要等缓存过期才能生效。我为了让AI在这个环节不掉链子,在SKILL里专门加了一句约束:凡是修改权限的接口,最后必须调用PermissionCacheService.reload(userId or roleId)。
6. 复盘与扩展:Skill的可复用性超出你的想象
当整个RBAC模块跑通之后,我发现自己得到的其实不止是一个权限模块,而是一套“生成DDD业务模块”的方法论。这套方法论一旦成型,复制到其他业务模块上,成本极低。
6.1 把RBAC Skill改造成其他业务模块Skill
RBAC SKILL里真正通用的不是用户、角色、权限这几个实体,而是“领域层建模 → 仓储接口 → 应用服务 → 接口层 → 基础设施实现”这条生产链路。我把这条链路抽象出来,改造成了一个名为ddd-module-builder的二级技能,RBAC只是它的一套具体案例。
再往深走一步,你会发现更值得沉淀的是脚手架规则,而不是业务规则。脚手架规则(哪些层能依赖哪些层、异常怎么抛、统一响应体叫什么)适用于这个脚手架上的所有业务模块;业务规则(RBAC的超级管理员保护、菜单权限树)才是某个模块独有的。我建议你写SKILL时就把这两部分拆成独立文件,后续新模块直接复用脚手架规则部分,只替换业务规则。
6.2 Skill与Agent的协作边界
很多人问我Skill和Agent到底是什么关系,我理解是这样:Agent是一个目标导向的规划者,给它一个目标,它会自己拆解步骤、调工具、做决策;Skill是一个固化的操作手册,把一套成熟流程固定下来,让模型按部就班执行。
在实际使用中,你可以让Agent负责“观察环境、判断何时调用哪个Skill”,而Skill负责“把指定任务完整做透”。比如“修复RBAC模块的编译错误”这个任务,可以让Agent先读错误日志、定位问题文件,再调用rbac-builder技能里“领域层规则”的参考资料来修正。
6.3 后续可以玩的方向
如果你也走通了这条“脚手架 + SKILL + 网上资料”的路线,我建议试试下面这几个方向:
- 把SKILL接入团队的Code Review标准:将检查清单换成公司自己的Sonar规则和Code Review checklist,让AI生成的代码从第一天起就符合团队要求。
- 让多个SKILL协作:一个负责生成业务模块,另一个负责生成对应的单元测试,再有一个负责写数据库变更脚本,三个Skill按顺序接力,效果比一个巨型Skill好。
- 用SKILL沉淀你自己的编码风格:不只是脚手架规则,连你写注释的习惯、命名偏好、异常处理偏好都可以写进SKILL。时间久了,AI生成的东西跟你自己写的几乎分不出来。
这次用SKILL改造DDD脚手架的最大收获,不是多了一个可用的RBAC模块,而是我找到了一个“把个人经验结构化、可复用”的工作方式。过去我积累经验靠笔记,现在我积累经验靠Skill。笔记是写给未来的自己看的,Skill是写给未来的AI执行的。后者让我从重复劳动里腾出了手,去做更需要判断力的事情。