最近一段时间,我一直想把后台管理系统里那些机械重复的增删改查代码交给AI去写。框架用的是若依,按理说这类业务代码模式非常固定,应该是AI最擅长的事情。可真试下来却不是那么回事——你让AI生成一个用户管理模块,它给出的Controller方法命名一会儿对一会儿错,Mapper XML动不动少一段resultMap,前端页面更是经常跟菜单配置对不上。反复调提示词折腾了好几个版本,效果始终不稳定。
后来我换了个思路:不再把需求当作一次性对话丢给AI,而是把“写若依代码”这件事整体打包成一个Agent Skill。配合我自己总结的黄金三层结构来做内部设计,再用一套五步法逐步打磨,AI输出的代码质量一下子稳定了很多,基本能直接进编译环节。这篇文章就把这套方法完整拆开讲,包括三层结构里每一层该放什么内容、五步法落地时的具体操作,以及我在实测过程中反复踩过的坑。
1. 直接让AI写若依代码,为什么总是差一口气
先聊聊为什么原来的方式不work。很多人跟我一开始一样,把建表语句贴在对话框里,然后写一大段“帮我生成若依的增删改查”,AI确实能给你吐出一堆代码,看着结构挺完整,可用起来就露馅了。
1.1 一次性提示词的三个死穴
第一个问题是规则没有显式化。若依框架看着简单,里面的隐含约定非常多:Service接口要带I前缀,Mapper接口和XML的namespace必须全限定名对应,Controller继承BaseController之后返回AjaxResult还是TableDataInfo有严格区分。这些规则分散在AI的“印象”里而不是你的提示词里,它就很容易凭感觉输出。
第二个问题是流程不可控。生成代码本身是有顺序的:先分析表结构,再做类型映射,然后生成实体类,再生成Mapper、Service、Controller,最后才是前端页面和SQL脚本。如果你不把顺序定死,AI经常东一榔头西一棒子,生成到一半还会自己“优化”掉某些步骤。
第三个问题是经验无法沉淀。就算你一次性调出了好结果,下次换一张表、换一个对话,一切重新碰运气。你在调试过程中积累的那点修正经验,全留在聊天记录里,没变成可复用的资产。
1.2 把“问AI要答案”变成“让AI调技能”
对比一下直接对话和Skill方式的差异,我用一张表来说明:
| 对比项 | 直接对话方式 | Agent Skill方式 |
|---|---|---|
| 输出稳定性 | 换一次上下文就飘 | 规范固定在Skill内部,稳定输出 |
| 若依规则掌握 | 时好时坏,靠运气 | 规则显式写入规范层,强制执行 |
| 复用成本 | 每次重新描述需求 | 一次构建,长期复用 |
| 质量保障 | 凭输出后人工检查 | Skill自带自查清单,生成时同步检查 |
| 扩展能力 | 换个框架或风格等于重来 | 维护多个Skill即可应对不同场景 |
Skill的本质,是把一套“怎么做好某件事”的经验结构化,让AI在调用时严格按照这套结构来执行。它不是一段简单的提示词,而是包含触发条件、执行流程、规范约束、示例样例的完整“技能包”。所以我现在不跟AI说“帮我写个订单模块”,而是把一个写好的单表CRUD生成Skill挂载好,然后把表结构丢给它。
2. 黄金三层结构拆解:一个若依生成Skill的内部构造
我总结的三层结构,分别解决三个问题:什么时候调用这个技能、按什么步调做事、用什么风格产出。这三层缺一不可,我管它们叫技能描述层、流程编排层、规范与样例层。
2.1 第一层:技能描述层
这一层说白了就是Skill的“门面”。它要让AI和人都能一眼看懂这个技能管什么、不管什么、需要什么输入、产出什么结果。
我一般用类似下面的内容填充这一层:
name: ruoyi-single-table-crud description: 根据数据库建表语句,生成若依框架下单表CRUD全套代码。 适用于业务模块的后台管理功能,输出后端分层的Java代码、前端Vue页面和菜单SQL。 不适用于复杂业务逻辑、流程审批、主子表结构。 input: table_name: 数据库表名,例如 biz_order table_comment: 表业务注释,例如 订单信息表 columns: - field: 字段英文名 type: 数据库字段类型 comment: 字段注释 required: 是否必填 optional: author: 代码作者注释,默认 某开发者 output: - BizOrder.java 实体类 - BizOrderMapper.java 与 BizOrderMapper.xml - IBizOrderService.java 与 BizOrderServiceImpl.java - BizOrderController.java - index.vue 与 form.vue 页面文件 - menu.sql 菜单初始化SQL你别小看这一层。AI拿到你的Skill时,首先读的就是这段描述。如果描述里没有明确“只处理单表”,遇到主子表结构它就会不管三七二十一硬上,最后生成一堆没法看的代码。边界写得越清楚,AI越不会越界操作。
2.2 第二层:流程编排层
第二层负责把生成过程变成一步步可执行的流水线。生成Java代码这件事,最怕AI跳步骤。比如实体类还没定好,它就先写了Mapper XML,结果字段一对不上。
我的流程编排层长这样:
- 解析建表语句,提取表名、表注释、所有字段的名称、类型、注释与主键。
- 将数据库字段类型映射为Java类型,并确定实体属性命名。
- 按顺序生成实体类、Mapper接口、Mapper XML、Service接口、Service实现类、Controller。
- 根据后端字段信息生成前端Vue页面,包括搜索条件、表格列、表单验证规则。
- 生成菜单权限SQL,菜单地址与Controller的RequestMapping保持一致。
- 对照规范层执行自查,输出完整的生成清单。
每一步之间是强依赖关系。AI在这一层的引导下,不会突然去研究时间复杂度,也不会自作主张加一个跟若依毫无关系的工具类。整个过程就像工厂里的流水线,每个工位做固定的事。
2.3 第三层:规范与样例层
第三层存放的是“若依风格”本身。AI生成代码像不像若依,就看你这一层喂了多少规则和样例。规则部分要细到字段级别。
我整理了一套最基础的映射规范,放在Skill里:
| 数据库类型 | Java类型 | 若依风格约定 |
|---|---|---|
| varchar / char | String | 较长字段增加@Size长度校验 |
| int / integer | Integer | 直接映射即可 |
| bigint | Long | 主键字段加@TableId(type = IdType.AUTO) |
| decimal / numeric | BigDecimal | 涉及金额时保留两位小数 |
| datetime / timestamp | Date | 前端默认用日期时间选择器 |
| text / longtext | String | Mapper XML中用#{remark}正常取值 |
除了映射,还需要放若干段完整样例代码。样例不用多,一个典型的单表模块彻底吃透就够了。我会在规范层放一段标准的Controller代码,让AI在生成时严格对齐:
@RestController @RequestMapping("/business/order") public class BizOrderController extends BaseController { @Autowired private IBizOrderService bizOrderService; @GetMapping("/list") public TableDataInfo list(BizOrder bizOrder) { startPage(); List<BizOrder> list = bizOrderService.selectBizOrderList(bizOrder); return getDataTable(list); } }样例的作用是给AI一个“锚”。纯文字规则它可能理解偏差,但给它一段地地道道的若依风格代码,它就能从中提取出返回类型、分页写法、注解风格这些信息,生成的东西自然靠谱得多。
3. 五步法落地:从零构建可复用的若依代码生成Skill
有了三层结构做骨架,具体怎么把Skill造出来?我总结了一套五步法,每一步都有明确的交付物。
3.1 第一步:把输入边界和输出约定写成文档
动手写Skill之前,先拿一张纸把“这个技能做什么、不做什么”写清楚。我最初的版本野心太大,想兼顾单表、树表、主子表,结果哪个都做不精。后来砍到只管单表CRUD,效果一下就上来了。
这一步的交付物是一份边界文档,核心内容包括:
- 支持的输入格式:只接受建表语句或字段清单,不接受模糊描述。
- 支持的输出范围:后端五层代码、前端两个Vue文件、菜单SQL。
- 明确不做的事:不生成业务校验之外的复杂规则、不处理主子表分页查询。
- 默认约定:包名、模块名、作者名等参数未提供时使用预设值。
边界定得越死,Skill之后维护起来越轻松。AI一旦接收了清晰的约束,就不会把精力浪费在那些不该它操心的方向上。
3.2 第二步:把若依生成器的规则反向翻译成文本
这一步是核心中的核心。若依框架自带代码生成器,里面已经沉淀了一套完整的代码模板逻辑,我们做的就是把那套逻辑“反向工程”成文字规则。
我对照生成器的模板思路,整理出一套规则草案,内容包括:
- 类名生成规则:表名去掉下划线并转大驼峰,比如 biz_order 变为 BizOrder,sys_user 变为 SysUser。
- 分层类命名:实体类直接用BizOrder;Mapper接口叫BizOrderMapper;Service接口叫IBizOrderService;实现类叫BizOrderServiceImpl。
- XML命名空间:必须是Mapper接口的全限定名,写错一个字符整个项目启动就会报绑定错误。
- Controller路由:RequestMapping用小写复数形式,例如 /business/order,与菜单地址保持一致。
这些规则看起来琐碎,但恰恰是AI最容易出错的地方。把规则文本写进Skill的规范层之后,AI相当于有了一本“操作手册”,不用每次都在脑海里猜若依是怎么约定的。
3.3 第三步:搭一个只覆盖单表CRUD的最小原型
不要一上来就求全,先把最小可用版本搭起来。我会挑选一张最简单的表,比如一张只有主键、名称、状态、备注的字典类型表,然后手动生成一遍完整的若依代码,把这段代码当作标准答案。
接着把三层结构按最小原型填写进去:描述层写清楚输入模板,流程编排层写六步流水线,规范层放入映射表和几个关键样例。第一版Skill不需要考虑太多异常情况,能用起来就行。
我建议在这一步把所有内容放到同一个Skill配置文件里,不要拆散。你可以用Markdown、YAML或者JSON都行,重点是让AI在一个上下文里同时读到三层内容。拆太散的话,AI阅读时容易丢失关键信息。
3.4 第四步:准备自测集,正反例都要有
Skill写完了,不能拿同一张表测,那样测不出问题。我准备了三张结构差异很大的表:
- 一张纯单表,字段基本只有字符串和数字,用于验证基础CRUD。
- 一张包含BigDecimal金额、Date时间、长文本备注的表,用来验证字段映射。
- 一张字段很多、注释不全的“坏表”,用来验证Skill在输入信息缺失时的表现。
正例用来确认功能正常,反例用来确认边界约束生效。比如对字段注释不全的表,Skill应该明确提示补齐信息,而不是自己脑补一堆含义。
自测的方式很简单:把这些表结构依次丢给挂了Skill的AI,看它产出的代码能不能通过IDE编译,能不能对应上若依的规范。第一版跑下来通常会发现不少问题,比如类型映射漏了BigDecimal,或者前端页面缺失时间日期选择器。这些问题会直接反馈到第三步的规则补充里。
3.5 第五步:用真实业务表做回归验收
最小原型通过了,就要拉真实业务表来验收。我个人偏好拿项目里新开发的业务模块来测试,因为真实表的复杂程度远高于手工造的测试样例。
验收标准我定得很硬性:
- 生成的后端代码在IDE中零错误编译通过。
- Mapper XML与Mapper接口全部正确绑定。
- 前端Vue页面打开后能正常渲染,查询、新增、编辑、删除功能可操作。
- 菜单SQL执行后能正常挂载到角色权限。
就这样跑十几个真实表之后,规则库会越来越厚,Skill的稳定度才会真正上来。我目前的规则库里已经有二十多条补充约定,都是靠这一步慢慢积累的。
4. 实战记录:一个订单表从建表语句到全套代码
光讲原理有点虚,我拿一张实际开发中常见的订单表走一遍完整的生成过程。
4.1 输入:一张订单表的建表语句
我准备的输入如下:
CREATE TABLE biz_order ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '订单ID', order_no VARCHAR(64) NOT NULL COMMENT '订单编号', customer_name VARCHAR(64) DEFAULT NULL COMMENT '客户名称', total_amount DECIMAL(10,2) DEFAULT NULL COMMENT '订单总金额', status VARCHAR(16) DEFAULT NULL COMMENT '订单状态', create_by VARCHAR(64) DEFAULT NULL COMMENT '创建者', create_time DATETIME DEFAULT NULL COMMENT '创建时间', update_by VARCHAR(64) DEFAULT NULL COMMENT '更新者', update_time DATETIME DEFAULT NULL COMMENT '更新时间', remark VARCHAR(500) DEFAULT NULL COMMENT '备注', PRIMARY KEY (id) ) COMMENT='订单信息表';把这段SQL丢给AI,同时点名“用挂载的ruoyi-single-table-crud技能处理”,AI就会自动进入技能流程。
4.2 Skill内部的执行流程是怎样的
AI根据第二层的流程编排逐步处理:
第一步解析表结构,明确表名biz_order、注释“订单信息表”、主键id、10个业务字段。第二步做类型映射,其中total_amount映射BigDecimal,create_time映射Date,其余字符映射String,id从bigint映射为Long并标注主键。第三步生成实体类,带好@TableName注解:
@TableName("biz_order") public class BizOrder extends BaseEntity { private static final long serialVersionUID = 1L; @TableId(type = IdType.AUTO) private Long id; private String orderNo; private String customerName; private BigDecimal totalAmount; private String status; private String remark; }因为createBy、createTime、updateBy、updateTime这几个字段和若依BaseEntity里自带的字段重名了,Skill在规范层里有约定,直接交由父类维护,所以实体类里不再重复定义。这一步很关键,很多第一次尝鲜的人在这里就翻车了,要么重复定义字段,要么漏了继承BaseEntity。
之后AI继续按流水线生成Mapper接口、Mapper XML、Service、Service实现和Controller,再根据字段信息生成前端页面和菜单SQL。
4.3 生成结果长什么样,质量怎么查
生成的Controller代码风格对齐了若依规范:
@RestController @RequestMapping("/business/order") public class BizOrderController extends BaseController { @Autowired private IBizOrderService bizOrderService; @GetMapping("/list") public TableDataInfo list(BizOrder bizOrder) { startPage(); List<BizOrder> list = bizOrderService.selectBizOrderList(bizOrder); return getDataTable(list); } @PostMapping public AjaxResult add(@RequestBody BizOrder bizOrder) { return toAjax(bizOrderService.insertBizOrder(bizOrder)); } }生成完之后,我会按一张固定的自查表逐项过:
| 检查项 | 判定标准 |
|---|---|
| 实体注解 | @TableName与@TableId齐全,继承关系正确 |
| Mapper XML | namespace匹配接口全限定名,字段映射完整 |
| Service层 | 接口带I前缀,实现类注入Mapper并加@Service |
| Controller | 统一返回AjaxResult或TableDataInfo,分页用startPage |
| 前端页面 | 搜索区、表格区、分页区齐全,字段与后端完全对应 |
| 菜单SQL | 菜单路径与Controller的RequestMapping保持一致 |
只要这一层检查通过,我才会把代码合进工程。不是不信AI,而是生成逻辑本身无法保证100%覆盖所有场景,校验环节绝不能省。
5. 反复踩过的坑:给正在调Skill的人提个醒
方案很顺,但落地过程中坑也不少。我挑几个印象最深的说说,免得你再踩一遍。
5.1 表信息给半截,AI就自动“补脑”
我一开始偷懒,传建表语句时漏了几列注释,想着AI这么智能,应该能猜出来。结果它真开始猜了:把amount字段脑补成“余额”,把status脑补成“启用禁用枚举”,生成的前端页面对不上原始业务含义,返工成本相当高。
对策就是输入模板严格化。描述层明确写上“必须提供完整字段清单,包括字段名、类型、注释、是否必填;缺失信息时先向用户索要,不得自行假设”。这一步能拦住大多数“自由发挥”。
5.2 模板样例和项目版本漂移了
Skill里存的样例代码,跟实际项目的若依版本是强绑定的。我最初用一套老版本样例,里面Controller还在用旧的返回结构,而项目已经切换成新风格了,生成的代码自然不合拍。
解决办法是在规范层顶部标注版本信息,比如“适配若依前后端分离版本V3.x系列”,并且每次升级框架后同步更新样例代码。版本漂移这个问题很隐蔽,建议在自测集的验收标准里加入“对照当前项目源码风格”这一项。
5.3 生成不等于编译通过
AI生成的代码不能当作文档看。我第一次测试时,生成结果看着非常顺眼,一编译直接红了,原因是Mapper XML里某个字段类型写成了java.lang.String,跟实际映射不一致。
所以我现在的流程里固定加一步:生成完代码后,立即放到Maven工程里编译验证,不通过就回去改Skill规则。编译本身就是最好的校验器,它能把99%的隐藏问题暴露出来。
5.4 多张表共用一个会话,上下文串了
在同一个对话里连续生成多张表的代码,Skill容易出现上下文污染:生成订单表时,前面客户表的字段名偶尔会混进来,特别是字段名相似的场景。
我把这个问题想明白了之后,就在流程编排层加了一条硬性规定:每次生成新表前,必须重新完整解析并复述本次的建表语句,确认表名与字段与上一次无关。同时建议每张表单独开一个会话,彻底切断干扰源,需要对比时再手动汇总。
个人现在实际操作中的感受是,这套三层加五步的打法,真正帮我把AI从“偶尔给个惊喜”变成了“稳定给个及格”,而及格才是工程化的起点。我最近还在做第二个Skill,目标是让AI根据需求文档自动把表结构设计出来,再顺手接上这个若依生成技能,两条流水线串起来,开发效率又能往上走一截。如果你也在折腾类似的事情,强烈建议从一张最简单的表开始,把三层结构填满,再用五步法慢慢磨,别急着上来就搞全家桶。