ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

若依框架AI代码生成不稳定?用Agent Skill三层结构+五步法搞定

若依框架AI代码生成不稳定?用Agent Skill三层结构+五步法搞定

最近一段时间,我一直想把后台管理系统里那些机械重复的增删改查代码交给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,结果字段一对不上。

我的流程编排层长这样:

  1. 解析建表语句,提取表名、表注释、所有字段的名称、类型、注释与主键。
  2. 将数据库字段类型映射为Java类型,并确定实体属性命名。
  3. 按顺序生成实体类、Mapper接口、Mapper XML、Service接口、Service实现类、Controller。
  4. 根据后端字段信息生成前端Vue页面,包括搜索条件、表格列、表单验证规则。
  5. 生成菜单权限SQL,菜单地址与Controller的RequestMapping保持一致。
  6. 对照规范层执行自查,输出完整的生成清单。

每一步之间是强依赖关系。AI在这一层的引导下,不会突然去研究时间复杂度,也不会自作主张加一个跟若依毫无关系的工具类。整个过程就像工厂里的流水线,每个工位做固定的事。

2.3 第三层:规范与样例层

第三层存放的是“若依风格”本身。AI生成代码像不像若依,就看你这一层喂了多少规则和样例。规则部分要细到字段级别。

我整理了一套最基础的映射规范,放在Skill里:

数据库类型Java类型若依风格约定
varchar / charString较长字段增加@Size长度校验
int / integerInteger直接映射即可
bigintLong主键字段加@TableId(type = IdType.AUTO)
decimal / numericBigDecimal涉及金额时保留两位小数
datetime / timestampDate前端默认用日期时间选择器
text / longtextStringMapper 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 XMLnamespace匹配接口全限定名,字段映射完整
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根据需求文档自动把表结构设计出来,再顺手接上这个若依生成技能,两条流水线串起来,开发效率又能往上走一截。如果你也在折腾类似的事情,强烈建议从一张最简单的表开始,把三层结构填满,再用五步法慢慢磨,别急着上来就搞全家桶。

RELATED READING

延伸阅读

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