用Node.js和Handlebars打造自己的代码生成器,告别重复CRUD

发布时间:2026/10/28 5:42:59

用Node.js和Handlebars打造自己的代码生成器,告别重复CRUD 是不是每个搞开发的都会在某个深夜冒出一个念头这破代码我不想再写一遍了。我在带一个后端小组的时候这种念头几乎每周都要出现几次。业务系统越做越复杂但代码增长的套路却高度重复每新加一张业务表我们就要手工写一遍 controller、service、mapper、model再配一套接口文档、参数校验和分页查询。一个稍微完整点的模块光代码骨架就要敲一千多行而且大部分字符都是一模一样的模板。后来我实在受不了花了两天时间自己搭了一个命令行小工具代号就叫t3code。它的核心作用很简单把“重复写代码”这件事变成“填参数生成代码”。这篇文章就是来聊聊 t3code 的设计思路、核心实现和实际踩坑记录。如果你也是那种被重复 CRUD 折磨的人或者正在考虑自己做一套内部脚手架这应该是一份足够接地气的参考。1. 设计与思路拆解1.1 被重复业务代码逼出来的工具先说说初期观察。我们项目里的代码结构其实是规范的但规范本身就意味着可预测可预测就意味着可以用模板替代。比如一个 user 模块不管业务怎么变文件结构永远是这样的src/main/java/com/example/controller/UserController.java src/main/java/com/example/service/UserService.java src/main/java/com/example/service/impl/UserServiceImpl.java src/main/java/com/example/mapper/UserMapper.java src/main/java/com/example/model/User.java点开这些文件里面的内容更是高度相似。Controller 无非是RestController加一堆GetMapping、PostMappingService 接口加几个方法签名Mapper 里面躺着一堆 MyBatis 注解或 XML 标签。当时我统计了一下一个新模块大概有 35% 的代码是完全一样的45% 是换汤不换药只有剩下的 20% 才是真正需要人动脑子的活。与其让团队里每个成员都把这些模板文件重复敲一遍不如写个工具把这 80% 的体力活自动化。1.2 t3 这个代号想表达什么很多同事问我 t3code 这个名字的来历。起名的逻辑其实很朴素t3 代表“Top 3 Three steps Turbo”也就是“三个步骤搞定核心代码”的意思。三步指的是写配置。敲一条命令。拿到一套可以直接编译的代码骨架。turbo 部分则是目标希望从“手工写一个模块需要一下午”压到“生成一个模块只需要几秒钟”把省下来的时间拿去写真正有业务价值的部分。当然还有一个隐藏原因好拼。命令行工具名字短一点团队成员用起来不用翻文档。1.3 技术路线的选择逻辑定了目标之后就是技术选型。这块我纠结过一阵。一开始考虑过用 Java Velocity因为团队主力语言是 Java生成出来的代码又是 Java感觉同语言更亲切。但很快就发现问题我们团队里不是所有人都愿意为了一个内部工具去搞一套 Maven 工程而且 Velocity 模板语法虽然成熟但写起来不够轻快。后来也试过用 Python 的 Jinja2确实舒服但跨平台分发给非 Python 背景的同事又有点麻烦。最后选了 Node.js Handlebars。原因有三个安装即用Node.js 的生态里一个 CLI 工具打成 npm 全局包成员一行命令就能装好。Handlebars 语法简单{{name}}这种变量替换哪怕是没写过前端的后端同事也能看懂。文件操作能力强fs模块处理递归创建目录、遍历文件极其方便不用引什么重量级依赖。这个选择让我在整个工具的开发过程中几乎没有被环境问题卡住过。2. 核心细节解析与实操要点2.1 配置文件的骨架设计t3code 的使用方式是按项目走的。每个项目根目录下放一个t3config.yaml这个文件决定了工具的“个性”。一个典型的配置文件长这样project: name: mall-service packageBase: com.example.mall pathStyle: java templates: dir: .t3/templates output: root: src/main/java resourceRoot: src/main/resources presets: module: - name: controller template: controller.hbs output: {{packagePath}}/controller/{{EntityName}}Controller.java - name: service template: service.hbs output: {{packagePath}}/service/{{EntityName}}Service.java - name: serviceImpl template: serviceImpl.hbs output: {{packagePath}}/service/impl/{{EntityName}}ServiceImpl.java - name: mapper template: mapper.hbs output: {{packagePath}}/mapper/{{EntityName}}Mapper.java - name: model template: model.hbs output: {{packagePath}}/model/{{EntityName}}.java hooks: afterGenerate: - cd {{projectRoot}} npm run lint:java重点说一下这里面的设计意图。packageBase和pathStyle是配套的。pathStyle: java会把com.example.mall转成com/example/mall这样的目录层级同时把模块名user转成User这样的类名。实际使用中你可以通过pathStyle: node之类扩展名目适配不同语言的工程结构。presets是整个配置的灵魂。它定义了一套“模块生成预设”每一条记录都包含使用哪个模板文件生成到哪个路径输出文件叫什么名字为什么要单独拿出一节讲配置因为我见过太多脚手架工具把逻辑都写死在代码里结果想改动一个命名规则都要重新发布一次。配置化的好处是你可以把工具理解成一台“模板打印机”打印什么内容、印在哪个位置都是由配置决定的工具本身不关心业务。2.2 模板引擎与变量注入规则Handlebars 的模板语法非常直白。以下是一个简化版的 Controller 模板package {{packageName}}; import org.springframework.web.bind.annotation.*; import org.springframework.beans.factory.annotation.Autowired; import {{packageName}}.service.{{EntityName}}Service; RestController RequestMapping(/api/{{resourceName}}) public class {{EntityName}}Controller { Autowired private {{EntityName}}Service {{entityName}}Service; GetMapping(/{{resourceName}}/{{primaryKey}}) public {{EntityName}} getById({{primaryKeyType}} {{primaryKey}}) { return {{entityName}}Service.getById({{primaryKey}}); } PostMapping(/{{resourceName}}) public {{EntityName}} create(RequestBody {{EntityName}} {{entityName}}) { {{entityName}}Service.create({{entityName}}); return {{entityName}}; } }这里的{{packageName}}、{{EntityName}}、{{resourceName}}都是运行时注入的变量。变量来源有三个渠道命令行参数比如你输入用户英文名和表名。配置文件里写死的公共值例如packageBase。内置函数计算的派生值例如把user_profile转成UserProfile。这里要重点讲讲命名转换。因为业务里最常见的就是“输入一个 snake_case 的表名生成一堆 CamelCase 的类名”。t3code 内置了几个转换函数函数名输入示例输出示例用途pascalCaseuser_profileUserProfile类名camelCaseuser_profileuserProfile变量名、方法名snakeCaseUserProfileuser_profile表名、列名kebabCaseuserProfileuser-profileURL、文件名在模板中调用public class {{pascalCase tableName}} { private {{primaryKeyType}} {{camelCase primaryKey}}; }这套设计看起来简单但在实际使用中极其重要。很多代码生成工具做得复杂就是因为没有在“命名统一”这一层做好抽象。一旦做好你会发现模板表达式的可读性会大幅提升。2.3 文件冲突与覆盖策略代码生成工具最容易出事的地方就是文件覆盖。头一天生成好的代码今天因为改了模板重新生成结果把人工修改的部分直接冲掉了。我设计 t3code 的默认策略是不覆盖任何已存在文件。也就是说如果UserController.java已经存在于磁盘上默认情况下工具会跳过这个文件并在终端里打印一段黄色提醒告诉你哪些文件被跳过了。你可以在命令后面加--force强制执行覆盖也可以加--diff先把差异打印出来看看再决定。这里有一个我后来加进去的功能生成前自动备份。执行带覆盖的生成时工具会把目标文件复制到一个.t3/backup目录并带上时间戳。虽然听着土但已经救过我至少两次尤其是模板改到一半发现逻辑写错的时候。3. 实操过程与核心环节实现3.1 初始化项目与全局命令安装t3code 发布为一个 npm CLI 工具。安装过程很寻常npm install -g t3code然后进入你的项目目录执行t3code init这个命令会做三件事在项目根目录生成一个t3config.yaml配置模板。在项目根目录创建一个.t3隐藏目录里面放默认模板。如果检测到项目是 Maven 结构会自动识别src/main/java等路径。这里有个小细节值得留意.t3目录在 Git 仓库里要不要提交我的建议是提交。虽然有人认为工具配置是个人偏好但站在团队角度模板和配置的一致性是代码风格统一的前提。新人拉下仓库后只需要安装全局命令再执行t3code make module就能生成和其他成员一模一样的代码结构。这种“团队级的一致性”带来的协作收益远超那一点模板文件的维护成本。3.2 从零生成一个完整业务模块假设现在要新增一个“收货地址”模块表名叫shipping_address。一条命令搞定t3code make module shipping_address --primary-key id --primary-key-type Long这条命令执行后工具的处理流程大致如下。首先解析参数生成一组上下文变量tableName: shipping_address entityName: shippingAddress EntityName: ShippingAddress resourceName: shipping-address packageName: com.example.mall.model packagePath: com/example/mall/model primaryKey: id primaryKeyType: Long然后遍历presets中的五条记录对每条记录执行三步定位模板文件渲染模板内容写入目标文件核心实现伪代码如下function makeModule(name, options) { const ctx buildContext(name, options); const presets config.presets.module; for (const item of presets) { const content renderTemplate(item.template, ctx); const targetPath resolveOutput(item.output, ctx); writeFileSmart(targetPath, content, options); } runHooks(afterGenerate, ctx); }这段逻辑看起来简单但里面有几个容易被忽略的细节。第一个细节是buildContext的健壮性。用户可能输入ShippingAddress、shippingAddress、shipping_address我需要工具能够标准化地识别用户意图。我的做法是先用一个小函数做输入清理把非字母数字的字符统一替换为空格再按空格或驼峰拆分单词最后重新拼接成四个命名风格。第二个细节是resolveOutput时路径分隔符的跨平台问题。一开始我在 Windows 上跑测试时遇到了路径错误后来统一用path.join来处理问题就消失了。这个坑在后面常见问题里会再提。3.3 生成结果演示执行完命令后项目里会多出这样一组文件src/main/java/com/example/mall/controller/ShippingAddressController.java src/main/java/com/example/mall/service/ShippingAddressService.java src/main/java/com/example/mall/service/impl/ShippingAddressServiceImpl.java src/main/java/com/example/mall/mapper/ShippingAddressMapper.java src/main/java/com/example/mall/model/ShippingAddress.java以生成的 Model 为例package com.example.mall.model; import lombok.Data; Data public class ShippingAddress { private Long id; private String receiverName; private String receiverPhone; private String province; private String city; private String detailAddress; }字段列表哪来的这就要说到第二个命令了。3.4 从数据库表结构直接生成字段手写模板只能处理公共结构字段还是要一个个填。我后来又加了sync-table子命令t3code sync-table shipping_address --table-schema mall这个命令会连接数据库读取目标表的列信息然后自动更新 Model 和 Mapper XML。它内部做的工作是查information_schema.columns把receiver_name转成receiverName根据data_type映射 Java 类型生成字段列表并注入上下文数据库连接配置放在t3config.yaml里只用于开发环境。这里有一个安全提示千万不要在公共配置里写生产库的地址这种代码生成工具只应该连接开发或测试库不然一个手滑可能线上数据就危险了。4. 常见问题与排查技巧实录4.1 高频报错与对应处理任何一个工具用的人多了遇到的问题就千奇百怪。t3code 上线后我收集到的高频问题主要集中在以下几个方面整理成速查表问题现象可能原因解决办法生成的类名不对比如UserProfile变成了Userprofile输入命名不规范或转换函数使用错误确认输入使用snake_case表名模板内统一用pascalCase模板中{{#if}}判断不生效上下文变量名拼写错误先执行t3code debug查看完整变量列表Windows 上生成后文件路径带了反斜杠\代码里硬编码了/分隔符升级到使用path.join的版本生成文件是 UTF-8 但编译乱码Windows 记事本保存时带了 BOM在模板文件头部不允许 BOMt3code 读取时自动剥离重复执行后文件被跳过默认覆盖策略生效用--diff查看差异再用--force覆盖模块名是class等 Java 关键字用户没做检查t3code 内置关键字黑名单此时自动拼接前缀Model这里重点提一下调试技巧。我发现不少人用代码生成工具遇到问题时第一反应是百度但大多数时候只需要一个命令就能定位问题t3code debug。它会打印出当前上下文里所有的变量和值你一眼就能看出是EntityName拼错了还是tableName解析错了。这个命令我后来建议团队里每个人都先跑一遍再报 bug反馈效率直接翻倍。还有一个常见问题来自集成开发环境的缓存。改了模板重新生成代码后IDE 偶尔会提示“文件被外部修改”要求手动同步。通常重启 IDE 或执行mvn compile就能激活全量刷新。这一点在 IntelliJ IDEA 里尤其明显我后来在工具帮助文档里特意加了一条说明。4.2 值得长期养成的使用习惯工具做好只是第一步让团队养成正确的使用姿势是更重要的一步。我这里分享几个自己用出来的习惯。第一个习惯是把生成操作纳入代码提交的必经流程。比如我们约定新增业务表后必须跑一次t3code sync-table并把生成的代码一并提交。这样做的结果是代码评审时不会再出现“这个 Model 缺少字段”这类低级问题因为字段是从数据库同步过来的和表结构天然一致。第二个习惯是模板要纳入 Git 管理并定期 review。模板是工具的“灵魂”如果模板里写死了过时的包名或过时的注解风格生成出来的代码也会带着臭味。我们团队每季度会做一次模板 review把新发现的最佳实践沉淀进去。第三个习惯是用 dry-run 模式规范操作。我在工具里加入了--dry-run参数执行时只打印“将要写哪些文件、内容长什么样”但不真正写入磁盘。这个参数在模板大改时格外有用可以在不污染代码仓库的情况下提前预览效果。4.3 代码生成后的二次修改底线必须要说的是代码生成工具的目标是帮你完成枯燥的骨架搭建但不是让你完全丢掉思考。我们团队有一条不成文的约定生成的代码允许二次修改但修改后要把差异点反馈到模板里。举个例子有一次生成出来的分页查询条件漏掉了一个deleted逻辑删除判断同事在生成的代码里手动补上了。按规矩他需要同步修改 Mapper 模板让后续所有生成的模块都带上这个逻辑。如果只改生成结果而不同步模板下次重新生成时问题还会复现。这个机制保证了工具在团队里是持续进化的而不是生成一次就死在角落。5. 后续可以怎么扩展t3code 做到现在这个程度已经能覆盖我们团队 80% 的重复编码场景。不过它的扩展空间还很大我简单列几个方向供参考。一个是支持多语言模板包。目前内置的模板主要面向 Java Spring Boot但只要你自己在.t3/templates里新增模板并配置新的 preset完全可以生成 Python FastAPI、Go Gin 甚至前端页面代码。它本质上是一个“模板渲染引擎 文件搬运器”语言无关。另一个是结合自动化测试。我的设想是每改一次模板自动生成一个模块样例然后跑一遍编译和单测确保模板没有引入语法错误。这个现在还在规划中但思路是成熟的等于给代码生成工具也加上 CI。还有一个方向是埋点统计比如记录每个团队成员的生成频率、常用模块类型。一开始觉得这是噱头但后来发现统计结果能反哺模板优化比如某个模块生成频率陡然升高说明业务正在向某个方向集中值得提前准备更细化的模板。从我个人实际操作的角度讲做 t3code 最大的收获不是代码量减少了多少而是让团队开始认真思考“哪些代码是真正需要人写的哪些只是机械重复”。这个思考过程本身往往比工具本身更有价值。如果你也想搞一套自己的代码生成器建议从小处起步先挑一个你每周都要手写两三次的模块把模板做出来跑通一条生成流程剩下的自然就会慢慢长出来。
延伸阅读

更多相关文章

2026/10/28 5:37:59

AI团队能力建模:从JD到能力图谱的完整技术路线

1. 从一份岗位说明书说起:为什么传统JD撑不起AI团队的能力管理我做过一段时间AI团队的招聘和技术管理,最头疼的事情不是招不到人,而是招进来之后发现“岗位说明书”和“实际要干的活”完全是两码事。一份典型的AI岗位JD大概长这样&#xff1a…

2026/10/28 5:37:59

24C256 EEPROM页写回卷原理与I2C驱动避坑

简介:围绕24C256存储器的说明与使用展开,面向使用I2C接口EEPROM的嵌入式初学者、电子设计人员及需要排查存储地址问题的开发者,重点厘清容量、页地址与页内地址关系以及连续存储模式下的常见误区。内容涵盖24C256的51264存储结构、16位地址管…

2026/10/28 5:37:59

ACC自适应巡航算法原理与工程实践解析

1. ACC自适应巡航算法基础解析ACC(Adaptive Cruise Control)自适应巡航系统是L2级自动驾驶的核心功能模块,其算法架构主要由感知层、决策层和控制层组成。不同于传统定速巡航,ACC通过毫米波雷达(77GHz/79GHz&#xff0…

2026/10/28 6:38:03

C#解析CAN DBC文件:从格式到上位机报文收发完整指南

简介:面向汽车电子与嵌入式总线开发者的CAN DBC解析查看工具,基于C#实现并附带完整工程源码,适合需要理解DBC格式、快速上手总线数据解析或进行二次定制的工程师。压缩包共含36个文件,以7个.cs源文件为核心,覆盖界面交…

2026/10/28 6:38:03

MATLAB实现GPS TEC与DCB联合估计的完整流程

简介:面向GPS电离层研究与导航定位应用人员,这份Matlab程序集利用GPS观测数据与精密星历,基于多项式或球谐函数模型,实现接收机与卫星DCB的同时估计,并进一步计算电离层TEC,为导航单位提供电离层延迟改正。…

2026/10/28 6:38:03

ADMap高精地图工程实践:分层建模、空间索引与车端加载

1. 先把 ADMap 这个词拆开看:它到底是个什么东西ADMap 这个代号我第一次见到时,第一反应是某个投放平台的数据看板,点进去才发现是一套面向智能驾驶的高精地图工程。拆开就是 Autonomous Driving Map,圈内更习惯直呼高精地图或 HD…

2026/10/28 6:38:03

Java金融数据分析系统:架构设计与量化实现

1. 项目概述:金融数据分析系统的价值与定位这个基于Java的股票基金交易分析系统,本质上是一个面向个人投资者和金融研究者的智能分析工具。我在金融科技领域深耕多年,见过太多投资者因为缺乏专业分析工具而盲目操作。这个系统正是为了解决这个…

2026/10/28 6:33:03

Linux I/O模型演进与性能优化实践

1. Linux I/O 演进的技术脉络在服务端开发领域,I/O性能优化始终是系统设计的核心命题。从早期的管道通信到如今的零拷贝技术,Linux I/O模型经历了数次重大革新。这种演进并非偶然,而是随着网络应用规模扩大和硬件性能提升的必然结果。我曾在多…

2026/10/27 14:24:03

全国计算机二级C语言真题PDF高效刷题指南

简介:这份PDF是全国计算机二级C语言考试的试题与答案解析,专为备考二级C语言的考生准备。内容涵盖C语言特点与设计理念、程序基本组成单位、main函数执行流程、函数嵌套调用规则、标识符命名规范、整型实型字符型数据类型、八进制十六进制数合法形式、运…

2026/10/27 9:34:54

requests+BeautifulSoup:爬虫抓取h1标题的入门实战指南

简介:一份以实战为导向的Python爬虫入门资源,面向具备基本编程基础、希望快速掌握网络数据抓取的初学者,可解决从零编写简单爬虫的常见困惑。实例以requests库发送HTTP请求、BeautifulSoup解析HTML文档,完整演示从网页中提取 标题…

2026/10/28 3:32:29

递推最小二乘参数辨识MATLAB编程:二阶系统与渐消记忆实现

简介:这是一篇面向控制系统、信号处理与机器学习等领域学习者的最小二乘参数辨识程序说明文档。资源共包含一个docx格式文档,压缩包大小七十八KB,内容围绕递推最小二乘算法和渐消记忆最小二乘算法展开:先构造一个极点位于单位圆内…

2026/10/28 0:02:13

Python股票自动交易系统源码解析:从行情获取到风控的完整链路

简介:这份资源是面向高校计算机、金融相关专业毕业设计场景的完整项目源码包,主题为基于Python的股票自动交易系统,适合需要完成毕设选题、课程设计或自学金融量化交易开发的学生与开发者参考。压缩包共收录687个文件,整体约14.93…

2026/10/28 0:02:13

视酷酷信IM源码部署指南:Java 17+MySQL 8.0.33实战配置

简介:这是一套2024年亲测可部署的视酷酷信IM即时通讯系统源码,面向具备Java、OC、C#及Vue开发能力的技术人员,解决私有化即时通讯平台快速搭建与二次定制需求,尤其适用于需集成商城、保障数据主权的中小型企业或独立开发者。资源共…

2026/10/28 0:02:13

Python爬取百度迁徙数据:接口解析、批量采集与避坑指南

简介:这份资源是一个用Python爬取百度迁徙数据的脚本压缩包,面向具备Python基础、希望入门网络爬虫与数据解析的开发者,解决按日期获取全国热门城市人口迁徙数据的问题。包内共1个文件,为单个py脚本,压缩包约1KB&#…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑