
1. 一套CRUD反复手写才是SaaS交付里最贵的隐形成本先说个我反复见到的现象。判断一个SaaS团队是否真的具备持续交付能力不用看架构图只翻一眼代码库就够了——如果每张业务表的 Controller 长得几乎一模一样只是把类名从 OrderController 换成 RefundController那这个团队大概率还在用手工作业解决本该自动化的问题。谷雨代码生成工具要解决的正是这件事把规范的CRUD从个人手写的肌肉记忆变成一次配置、可重复产出、可被流水线校验的确定性工序。先把标题里的几个词拆开讲清楚。**可持续交付指的是同一套代码规范能在第1个模块和第80个模块上保持一致新人接手不靠口口相传规范CRUD不止增删改查五个接口还包含多租户隔离、审计留痕、逻辑删除、分页约定、统一异常码、权限点注册这些横切关注点AI时代的合规利器**里的合规我按工程口径理解为两层意思——符合团队既定的编码与安全基线以及生成结果可追溯、可评审、不引入不可解释的黑盒逻辑。适合谁看正在被重复劳动拖慢迭代的后端团队、接手存量项目需要快速补齐规范的维护者以及已经在用AI补全代码但发现质量不稳的开发者。1.1 手写CRUD的真实成本远不止首次开发那点时间很多人算成本只算第一次写要多久这是最容易低估的地方。一个中等规模的业务SaaS核心业务表普遍在30到60张之间。按每张表一套标准实现需要 Entity、Mapper接口、Mapper XML、Service 接口、Service 实现、Controller、DTO、VO、转换器、单元测试骨架、接口文档共 10 到 11 个文件算40 张表就是 400 多个文件、两万多行样板代码。手写一版带分页、校验、异常处理的CRUD熟手也要 40 分钟到 90 分钟其中至少有 60% 的时间花在复制上一张表的代码然后改名字。真正贵的是第二次、第三次改动。业务提订单表加个渠道来源字段你要同步改的落点包括数据库DDL、Entity 字段、DTO 与 VO、Mapper XML 的 resultMap 与列清单、查询条件拼装、接口文档、前端类型定义。一个字段平均 6 个落点漏一个就是线上问题——最常见的漏法是 resultMap 没加插入正常、查询回来是 null测试环境数据量小不容易发现上线三天后运营反馈某些订单的渠道是空的。对比项纯手写元数据驱动生成单表首次实现耗时40 到 90 分钟30 秒到 2 分钟含预览与合并字段变更同步落点5 到 6 处全靠人记重新生成差异一目了然规范一致性依赖评审与个人习惯模板决定天然一致评审成本需要逐行看样板代码只审业务逻辑与差异块新人上手成本先读三个模块才敢动手读一遍元数据定义即可这张表里我最想强调最后一行。规范的价值不在于写得多漂亮而在于新人在没有老员工指导的情况下产出的东西默认就是对的。手写模式下规范靠评审兜底评审者疲劳了规范就开始滑坡生成模式下规范写在模板里滑坡需要主动改模板才会发生阻力方向反过来了。1.2 先划边界什么该生成什么必须人来写上线谷雨之前团队里一定有人担心生成的东西不灵活。这种担心通常来自把生成器当成了低代码平台期待它连业务规则一起吐出来。我的建议是先把边界谈清楚边界清了争论就少了。适合生成的部分数据访问层与传输层骨架、参数基础校验、分页与排序、多租户条件注入、审计字段填充、逻辑删除、统一响应包装、异常码声明、权限点占位、单元测试骨架、接口文档骨架。这些内容的共同特征是结构确定、重复度高、写错代价大。必须手写的部分状态流转规则、跨聚合的业务编排、金额与库存这类强一致性计算、外部系统交互、幂等与补偿策略、复杂查询的索引设计。这些的共同特征是每次都不一样且错了业务就错了。提示判断标准可以简化成一句话——如果这段逻辑在第二张表上能原封不动复用它就该进模板如果每次都要根据业务重新想就别硬塞进生成器。我见过一个反面案例某团队为了让生成率好看把审批流的节点判断也写进了模板结果每张表都要在元数据里写一大段表达式维护成本比手写还高最后这套模板没人敢动。生成器的目标不是覆盖率是让重复的部分彻底消失让需要思考的部分有干净的落笔点。2. 谷雨的工作模型Schema先行为什么比模板填空更可靠市面上大量代码生成器本质是模板填空选一张表勾几个选项点生成产出文件。这类工具第一次用很爽第二次就出问题——因为它的输入是数据库表的物理结构输出是文件中间没有可维护的中间层。谷雨的做法是把中间层显式做出来元数据描述 → 模板渲染 → 受控落盘三段分离每一段都能单独版本化和校验。这个模型值得花点篇幅讲透因为它决定了工具能不能活过项目的第二年。2.1 从表结构到元数据中间层到底存了什么直接从 DDL 生成代码的工具最大的问题是数据库里没有业务语义。字段叫status类型tinyint注释写着状态生成器不知道 0 和 1 分别是什么也不知道要不要生成枚举、要不要生成字典翻译、要不要在列表接口返回中文描述。这些信息只能靠人补补在哪儿如果补在生成后的代码里下次重新生成就被覆盖了。谷雨的元数据文件用 YAML 描述物理结构之外还承接业务语义module: order table: t_order tenantField: tenant_id # 多租户隔离字段生成器自动注入查询条件 auditFields: [created_by, created_at, updated_by, updated_at] softDeleteField: deleted permissionPrefix: order:item fields: - name: order_no type: string length: 32 required: true unique: true query: eq # 生成等值查询条件 - name: channel type: int dict: order_channel # 字典编码自动生成翻译逻辑 query: eq - name: amount type: decimal precision: 18 scale: 2 required: true sensitive: false - name: buyer_phone type: string length: 20 sensitive: mask_phone # 脱敏策略序列化时自动打码这份文件是整个生成的唯一输入。它进 Git跟着代码一起评审字段变更走 MR谁改了什么、为什么改全在提交历史里。这一点比直接在库里改表然后重新生成重要得多——元数据可评审意味着规范可评审。从 DDL 到元数据我一般不做全自动转换只做半自动用一条命令扫描表结构生成初稿然后人工补齐字典、脱敏、查询方式这些语义字段。全自动看着省事实际上会把脏信息一路带到生成物里比如把数据库里的varchar(255)不加判断地转成无长度校验的字符串校验就白做了。2.2 三层模板共享片段、模块模板、项目覆写模板只有一个层级会很快失控。所有模块共用一份模板业务差异没地方放每个模块各自一份模板等于把重复劳动从代码搬到了模板里。谷雨用的是三层结构共享片段层分页参数对象、统一响应体、异常码基类、审计字段填充切面、租户条件注入逻辑。这一层几乎不变改一次影响全局改动必须走回归。模块模板层Controller、Service、Mapper 的整体骨架模块级差异通过模板变量控制比如某模块需要导出、某模块需要批量接口。项目覆写层允许对单个模板做局部覆写覆写文件单独放置并打标记评审时一眼能看出这个模块偏离了标准模板原因是接了外部对账系统。三层的关键约束是覆写必须显式且可统计。我习惯在流水线里加一条统计覆写文件数量超过模块总数的 20% 就报警。覆写太多说明标准模板和实际业务脱节该回去改模板了而不是让每个模块各自为政。2.3 生成物与手写代码的隔离契约这是整个模型里我最看重的一条生成的文件和手写的文件必须物理分离生成区域和手写区域必须标记分离。落地上有两类做法谷雨两种都支持按模块选。第一类是按文件分离。生成器只写xxx_generated.java这类带后缀的文件业务实现写在xxx_service.java里继承或调用生成物。好处是永不冲突坏处是文件数翻倍、继承链变长。第二类是按区块分离适合 Controller 这类必须合成一个文件的场景生成器只维护被标记包裹的区块区块之外的内容原样保留。// generated by guyu v1.7.2, templatecontroller2.3.0 GetMapping(/page) public ResultPageResultOrderVO page(OrderQuery query) { return Result.ok(orderService.page(query)); } // /generated注意注释里的版本信息它不只是好看。模板升级以后靠这段指纹可以判断哪些文件是旧版本模板产出的需要重新生成评审时也能快速定位这个文件是机器写的还是人写的。我一般要求生成区块只放路由和参数转发任何带 if 的逻辑都挪到 Service这样即使生成区块被整体覆盖业务逻辑也不会丢。3. 三分钟跑完一次生成完整操作链路拆解3分钟这个说法常被当成营销话术其实拆开看是可信的准备环境 1 分钟首次除外写元数据 1 分钟出头字段多的表更久执行生成加预览 20 秒剩下时间用来处理差异。真正花时间的从来不是生成是生成之后要不要合并、怎么合并。这一节按实际操作顺序走一遍把每一步的意图和容易出错的点都标出来。3.1 环境准备版本锁定比装得快更重要生成器最怕的是同一个元数据两个人生成出不同结果。所以第一件事不是装工具是把生成器和模板包版本钉住。谷雨的配置放在项目根目录# .guyu/config.yaml generatorVersion: 1.7.2 templatePack: guyu-spring-boot2.3.0 output: baseDir: src/main/java packageRoot: com.example.saas overwrite: merge # overwrite | skip | merge checks: requireDryRun: true # 必须先生成到临时目录预览 forbidDirectCommit: true # 禁止生成器直接提交生成器本体我建议用固定版本的二进制或容器镜像分发不要用最新版。模板包指的是模板文件的集合和生成器本体分开版本因为模板改动频率远高于生成器引擎。两者的版本号都写进生成物的文件头出问题时能立刻定位是哪一版模板产出的代码。注意forbidDirectCommit这一项务必打开。生成器自动提交看起来省事实际上会让生成物变更和业务变更混在同一个提交里评审时根本分不清哪部分是机器改的、哪部分是人改的。首次环境准备还包括一次基线生成拿一张已经手写好的标准表用生成器产出对比看看与现有代码的差距在哪儿。这个动作的目的是校准模板而不是校准代码。差距通常集中在命名习惯createTime还是createdAt、响应体结构、异常码风格这三处一次性在模板里对齐后面就不用反复扯皮。3.2 元数据填写字段标注里藏着后面所有麻烦写元数据看着像填空实际上是整套生成里最需要判断力的环节。我把常填的标注分成四类每一类都有明确的为什么填标注类型典型字段不填的后果隔离标注tenantField生成物全部按单租户处理跨租户数据混查审计标注auditFields、softDeleteField删除变物理删除历史状态无从追溯语义标注dict、enum列表返回 0/1前端自己翻译口径不统一安全标注sensitive、query方式手机号全量返回或者模糊查询走全表扫描关于query标注我要多说一句因为这里最容易踩坑。标成like的字段如果前端传了空字符串生成的 SQL 会变成like %%等于全表匹配数据量一上来查询就崩。谷雨默认对空值做了跳过处理但如果你手写 SQL 片段绕过了生成层这个保护就失效了。我一般要求模糊查询只允许标在真正需要模糊的字段上标了之后必须在集成测试里加一个空参数用例。还有一类容易忽略的是权限点前缀permissionPrefix。填order:item生成器会自动声明order:item:list、order:item:create、order:item:update、order:item:delete、order:item:export这几颗权限点并把注解加到对应方法上。这些权限点还会同步输出一份 SQL 片段供权限表初始化。手写模式下权限点经常是想起来才加结果上线后审计发现某个导出接口谁都能调——这类问题靠生成器统一声明能从根上减少漏配。3.3 生成与预览dry-run 是必须养成的手感命令本身很短# 预览不落盘 gy gen --module order --schema .guyu/schema/order.yaml --dry-run # 正式生成merge 模式 gy gen --module order --schema .guyu/schema/order.yaml # 只生成指定类型的文件 gy gen --module order --schema .guyu/schema/order.yaml --only controller,service--dry-run会输出一份差异摘要新增哪些文件、哪些文件有变更、哪些文件因为保护区块被跳过、哪些文件检测到手写内容无法自动合并。我的习惯是先看最后一项它是唯一需要人工介入的部分其余都是机器活。差异摘要有两种呈现方式行级 diff 和结构级 diff。行级 diff 更通用但生成物的行级 diff 往往噪音很大因为模板里一个循环顺序变了整段都算变更。结构级 diff 按类 → 方法 → 字段对比能看出到底是新增了一个方法还是只改了缩进。谷雨两种都支持我的用法是日常用结构级快速判断影响面涉及模板升级导致的批量变更时切行级逐行确认。3.4 生成完的那五分钟四件必须人工补齐的事生成完毕不等于能提交。有四件事生成器做不了也不能指望它做第一业务校验。生成器只做基础校验非空、长度、格式、范围跨字段规则、跨表唯一性校验、业务前置条件必须手写。比如金额必须大于退款金额、订单状态必须允许当前操作。第二权限点入库。生成器输出了权限点 SQL 片段但要人工确认哪些角色默认拥有。这一步经常被跳过结果接口有了权限注解但权限表里没有对应记录所有人都调不通排查半天。第三接口文档的业务示例。字段说明能生成示例值生成不了。给一个真实的请求样例比十行字段描述有用。第四关键分支的单元测试。生成的测试只覆盖能跑通覆盖不了异常路径对不对。我的经验是至少补三类非法状态流转、重复提交、越权访问。4. 规范从靠人记变成默认产出谷雨内置的合规约束规范这件事的难点从来不是制定是执行。写一份编码规范文档第一个月大家都看第三个月没人提第六个月新来的同事根本不知道有这东西。把规范写进代码生成模板效果完全不同——规范不再需要被记住它变成了默认产出违反规范反而需要额外动手。这一节讲谷雨把哪些规范固化成了生成行为以及哪些地方仍然需要人工兜底。4.1 多租户隔离把最容易漏的条件做成不可漏多租户SaaS最典型的事故是数据越权而越权的成因高度雷同某个查询忘了带租户条件。手写模式下这种遗漏靠代码评审发现评审者的注意力一旦被业务逻辑占满就很容易漏掉。谷雨的处理是三层兜底。第一层元数据标了tenantField所有生成的查询语句自动带上租户条件包括分页、详情、更新、删除。第二层生成一个租户上下文拦截器从登录态中取租户标识写入线程上下文SQL 改写层统一注入即使有人手写 SQL 忘了加条件也能兜住。第三层生成的实体类里租户字段设置为不可外部赋值防止前端传参伪造。// 生成的实体中租户字段只允许框架赋值 TenantIgnoreWrite private Long tenantId; // 生成的分页查询租户条件由拦截器注入不由业务代码拼装 public PageResultOrderVO page(OrderQuery query) { PageOrder page new Page(query.getPageNo(), query.getPageSize()); return PageResult.of(orderMapper.selectPage(page, query.toWrapper()), OrderVO.class); }注意第三层的不可赋值要小心处理导入场景。批量导入时租户字段由框架填不要让 Excel 里的列覆盖。我踩过一次坑——导入模板里带了一列tenant_id运营不懂复制了别的租户的数据直接写进去了。后来的做法是导入解析阶段就对租户字段做白名单过滤。还有跨租户查询的情况比如平台运营需要看全量数据。这类需求不能靠临时去掉拦截器注解实现必须走显式的跨租户通道并且把这个通道记入审计。生成器会为每张表额外生成一个selectAcrossTenant方法加注解标记调用时强制要求传入操作原因。多这一步看着麻烦但出了问题能查到人。4.2 审计、逻辑删除与脱敏三件事后才想起的事这三件事的共同点是平时不重要出事时最重要。所以适合做成默认行为。审计字段创建人、创建时间、更新人、更新时间由生成器在实体上声明、在切面里自动填充业务代码一行都不用写。逻辑删除由生成器把删除方法实现成更新deleted字段并且在所有查询条件里默认追加deleted 0。这两件事手写也能做问题是只要有一张表漏了数据口径就不一致。我曾经接手一个项目80% 的表是逻辑删除20% 是物理删除对账时发现两边的数据总量永远对不上查了两天才定位到原因。脱敏的落地方式是在元数据里标sensitive生成器在 VO 上自动加序列化注解按策略打码。手机号保留前三后四证件号保留前六后四邮箱保留首字符和域名。关键设计点是脱敏发生在序列化层而不是查询层。如果为了脱敏把查询语句改成substring(phone,1,3)那导出、对账、风控这些需要原文的场景就全废了。public class OrderVO { Sensitive(strategy SensitiveStrategy.PHONE) private String buyerPhone; }和脱敏配套的还有一个常被忘掉的点导出接口要不要脱敏。默认策略应该是脱敏需要原文的导出走单独的权限点并记审计。谷雨生成的导出方法默认带脱敏开关开关的默认值是关闭原文人工确认后才放开。4.3 统一分页、异常码段位与幂等键这三样是团队协作摩擦的高发区统一了以后省下的沟通成本比省下的代码量更值钱。分页统一到一套参数名pageNo/pageSize/sortBy/sortOrder前端不用为每个模块适配不同的命名每页上限做硬性限制默认 200防止有人传pageSize99999排序字段做白名单防止 SQL 注入和按未索引字段排序拖垮数据库。这几条写进生成的分页基类里所有模块自动继承。异常码按段位分配模块号占前两位错误类型占后三位比如10开头是订单模块10001是订单不存在、10002是状态不允许。生成器按模块自动分配段位并写进配置避免两个人同时开发两个模块用同一个码。段位表放在元数据仓库里统一管理这比口头约定靠谱得多。幂等键的处理分两种场景。写接口默认支持Idempotency-Key请求头生成器在拦截器里做去重把键和结果缓存一段时间。这个能力对重试友好尤其是支付、下单这类调用。但要注意幂等键的正确实现依赖存储单机内存缓存扛不住多实例得用共享存储生成的默认实现是走缓存中间件如果项目没有需要在配置里显式关掉不能让它静默降级成内存版。规范项谷雨默认产出仍需人工确认多租户隔离查询条件注入、上下文拦截、跨租户通道导入场景的租户字段过滤审计留痕四个审计字段与自动填充切面关键操作的业务级日志如状态变更原因逻辑删除删除方法改写、查询默认过滤唯一索引与软删除的冲突处理字段脱敏序列化注解与策略导出场景是否放开原文分页排序统一参数、上限、字段白名单复杂查询的索引评估异常码按模块分段、统一异常基类业务语义级别的错误归类幂等请求头去重拦截器幂等窗口时长与业务键选择5. AI补全代码的两个风险面以及代码生成怎么兜住现在团队里几乎没人不用AI辅助写代码了我也不例外。问题在于AI擅长的和不擅长的与项目里最不能出错的部分恰好错位。AI很会写看起来对的代码而生产事故往往就藏在这些看起来对里。代码生成器和AI不是竞争关系它们解决的是两个不同的问题生成器保证下限AI抬高上限。5.1 为什么AI写出来的接口经常看起来对我做过一个小统计把AI生成的CRUD接口拿去评审高频问题集中在五处而且这五处几乎不会导致编译失败所以很难在编码阶段发现第一漏租户条件。AI不知道你的项目是多租户的它按通用写法拼where order_no ?代码能跑数据串了。第二异常被吞。为了代码更健壮它喜欢把异常 catch 住打日志然后返回 null调用方拿到 null 继续走最后空指针出现在离现场很远的地方。第三缺事务。多表写入没有Transactional或者加在私有方法上不生效。第四无幂等。同一个请求重试两次数据写两条。第五N1 查询。列表接口里循环查关联表测试环境数据少一点问题没有。这五个问题的共性是它们属于项目约定不属于编程通用知识。AI没有读过你团队的规范文档也不知道你们的租户拦截器是怎么实现的它只能按最常见的写法猜。你越是让它直接写一个完整接口它猜的空间就越大。5.2 让确定性的部分由模板锁住让AI只做思考型补全我的用法是把一次开发切成两段。第一段用谷雨生成骨架骨架里已经包含了租户、事务、异常、幂等、分页、权限这些约定这些内容不需要AI参与也不该让它参与。第二段才是让AI在骨架里填业务逻辑此时上下文已经收窄给它具体的 Service 方法签名、相关的实体字段、明确的业务规则描述让它只写方法体。// 生成器产出的骨架事务、租户、异常、审计已就位 Transactional(rollbackFor Exception.class) RequirePermission(order:item:update) public void cancel(Long id, String reason) { // custom // 这里是AI或人手写的业务逻辑生成器不会覆盖 // /custom }这样切的好处很直接AI即使写错了业务逻辑错的也只是业务逻辑不会顺带把事务和权限一起搞坏。而且生成的骨架本身就是一份上下文提示——AI看到方法上已经有Transactional和RequirePermission它就不会再重复加也不会自作主张换一种事务传播行为。还有一处很适合交给AI单元测试的异常分支、接口文档的示例值、DTO 与实体之间的转换代码。这三类内容写错代价低、写得慢、模式固定是AI的舒适区。5.3 让生成结果可追溯模板版本、提交粒度与评审清单合规这个词落到工程上最实的一道就是可追溯。三个月后线上出问题要能回答三个问题这段代码是谁写的、用什么模板哪一版生成的、当时改了什么。谷雨的答案是三件套文件头的模板指纹templatecontroller2.3.0能定位到具体模板版本生成动作与业务改动分开提交生成提交的 message 里带gy gen标记方便过滤模板仓库的变更与代码仓库的变更关联模板升级时在代码仓库留一条记录。评审清单我也固定下来了贴在 MR 模板里就四条生成区块之外的手写逻辑是否都写了测试保护区块有没有被误删新表的权限点是否已入库导出接口是否确认脱敏策略。四条不多但每一轮评审都过一遍能拦掉大部分低级问题。6. 二次生成冲突这是代码生成工具最容易翻车的地方第一次生成谁都会做真正决定工具能不能长期用的是第三次、第十次生成。表结构改了重新生成手写的业务逻辑还在不在这是团队接受度的分水岭。我见过不止一个团队在第二次生成把代码覆盖掉之后就把生成器彻底弃用了。这一节专门讲冲突处理。6.1 覆盖、跳过、合并三种策略其实只有一种能用覆盖模式overwrite重新生成时整文件重写。优点是永远和模板一致缺点是把手写代码一起干掉。适合纯生成、不含手写内容的文件比如 DTO、VO、Mapper XML。跳过模式skip文件已存在就跳过。优点是绝不破坏手写代码缺点是表结构变更同步不过来——你给表加了字段DTO 里不会出现得手工补补一次两次还行补十次就退化成手写了。合并模式merge只更新保护区块之外、由生成器维护的部分区块内的手写内容原样保留。这是唯一能长期用的策略但前提是保护区块必须严格执行。我的要求是只要文件里出现生成区块标记标记内的内容一律不许手改手改等于自杀下次生成就没了。实践中三种模式要混用按文件类型分配文件类型推荐策略理由DTO、VO、Query覆盖纯数据结构手写内容应为零Mapper XML 基础语句覆盖复杂查询另开扩展文件Controller合并路由是生成的个别接口手写Service 实现合并 保护区块业务逻辑主体单元测试跳过首次生成骨架后续全人工维护6.2 保护区块的落地写法与三条铁律区块标记要简单到不可能写错。我用成对注释标记内部是手写区生成器遇到就原样保留public void cancel(Long id, String reason) { Order order orderMapper.selectById(id); if (order null) { throw new BizException(OrderErrorCode.NOT_FOUND); } // custom if (!OrderStatus.canCancel(order.getStatus())) { throw new BizException(OrderErrorCode.STATUS_NOT_ALLOWED, order.getStatus()); } // 释放占用库存走领域服务而非直接改表 inventoryService.release(order.getId()); // /custom order.setStatus(OrderStatus.CANCELED.getCode()); orderMapper.updateById(order); }三条铁律区块不能嵌套嵌套了合并算法就不可预测区块不能跨方法跨方法后方法级别的重排会让区块错位区块标记不能出现在字符串或注释里会被解析器误判。第三条最容易犯有人写文档注释时顺手贴了标记示例结果被当真了。6.3 表结构变更之后的同步清单字段变更有四种情形处理方式完全不同我按踩坑顺序列新增字段最安全。改元数据、重新生成、检查保护区块是否保留、补业务逻辑、补测试。整个过程十分钟内。字段类型变更次安全但影响面大。比如金额从int改decimalDTO、VO、转换器都要跟着变前端也要同步。生成器会给出类型不兼容提示这一类必须人工确认后再落。字段重命名最危险。生成器会当成删一个加一个手写代码里的引用会编译失败。我的做法是先加新字段、双写、数据迁移、再删旧字段分三个版本走完绝不在一个提交里完成。字段删除第二危险。生成器删掉字段后手写逻辑里的引用同样会崩。好在有编译期兜底比运行时错误好查。关键是数据库字段不要急着删先让代码不再引用观察一个发布周期再删。提示每次重新生成前先跑一次 dry-run重点看无法自动合并那一栏。这一栏非空说明有人在不该手改的地方动手了先解决它再继续不要带着冲突往下走。7. 把生成器接进流水线从本地命令到CI门禁生成器在本地用得再顺只要没有流水线校验几个月后一定会出现元数据和代码不一致的情况。典型症状是改了实体没改元数据下次别人重新生成你的改动被覆盖或者元数据里写了字段但代码里没有新人看着两份不一致的东西无从下手。解决办法很朴素——让不一致直接构建失败。7.1 生成物一致性检查一条命令卡住漂移流水线里加一步按当前元数据重新生成到临时目录和仓库里的代码做差异对比差异非空就失败。# CI 中的一致性门禁 gy gen --all --out /tmp/guyu-check --overwrite diff -r --exclude*.md /tmp/guyu-check ./src/main/java if [ $? -ne 0 ]; then echo 生成物与元数据不一致请本地执行 gy gen --all 并提交 exit 1 fi这一步上线初期会引起一些抱怨因为总有人忘了本地重新生成。但两周之后效果非常明显没人敢跳过生成直接改生成物了因为过不了流水线。规范从道德要求变成了物理约束这是我觉得这条门禁最大的价值。对比时要排除的东西要配清楚手写文件、覆写文件、文档、测试资源。排除规则本身也要评审别把该检查的文件排除了。7.2 模板自身的版本化与回归测试模板是代码就得按代码管。谷雨的模板仓库有自己的版本号、变更记录和回归用例。回归的做法是准备一组黄金用例五到八个典型表简单的、带字典的、带多租户的、带软删除的、带树形结构的、带大字段的每次模板变更后跑一遍生成把产出和基线文件做对比差异必须逐行解释。这部分投入看着重实际上比我预想的省事。因为模板变更频率很低——一个稳定运行的项目模板可能三个月才动一次。而这三次改动每次都有明确理由比如新增了导出能力、适配了新的框架版本改完跑一遍黄金用例半小时的事。还有一条经验模板升级要和业务迭代错开。模板升级会导致大量生成文件变更如果同时还有业务需求在跑评审会变成灾难。我的做法是模板升级单独排一个小版本只做升级不做业务一次性把差异消化掉。模板的版本号我建议和生成器版本号解耦但在项目的配置文件里成对锁定形如generatorVersion: 1.7.2 / templatePack: guyu-spring-boot2.3.0。升级时两个版本一起测别只升一个。8. 几个我在实际落地中反复验证的判断写到这里把几条偏主观但我觉得最值钱的经验摆出来都是踩过之后才想明白的。元数据的评审优先级应该高于代码评审。一份元数据错了会按表数量成倍地复制错误一段业务代码错了影响面通常就是那一个方法。所以我把元数据的 MR 设置了两人评审业务代码一人评审即可。这个优先级很多人搞反了。生成器不要追求一次生成完整可上线。我见过为了做到这一点把业务规则、审批流、通知都塞进模板的尝试结果模板复杂度超过了被替代的代码维护它需要专门的负责人。生成器最舒服的定位是把地基打平上面的房子让人来盖。保护区块的纪律比工具本身更重要。只要团队里有一个人习惯先手改生成物回头再说这套机制就会慢慢失效因为其他人看到手改没出事就会跟着学。这条纪律要在第一次培训时讲清楚讲清楚的方式不是讲道理是演示一次手改被覆盖、三个小时白干的现场。别把AI当生成器的替代品。有同行问过既然AI能写CRUD为什么还要维护一套生成器我的回答是AI写的是一个像CRUD的东西生成器产出的是这个项目里的CRUD。前者需要你逐行验证它有没有遵守项目约定后者本身就是约定。两者配合的姿势是——骨架交给生成器血肉交给AI和人验收交给流水线。最后分享一个用起来很顺手的小习惯每次新增一张表我会先把元数据写完、生成、跑通然后花五分钟把这次遇到的模板没覆盖到的点记到一个待办清单里。攒够三五条再回头改模板而不是当场改。当场改模板容易情绪化攒够了再看往往能发现其中有一半是偶发问题不值得进模板。这个节奏坚持下来模板会变得越来越贴合业务而不是越来越臃肿。