ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringBoot+Vue代码生成器:从CRUD到快速开发实战

SpringBoot+Vue代码生成器:从CRUD到快速开发实战 简介在前后端分离开发中CRUD接口和页面代码的重复劳动消耗大量时间。代码生成器通过读取数据库表结构结合模板引擎自动生成实体、Mapper、Service、Controller及Vue页面等样板代码有效提升开发效率并统一编码规范。其核心原理是将表结构抽象为元数据再通过Velocity等模板渲染出项目文件。这类工具适合快速搭建管理后台、课程设计及企业内部系统尤其适用于SpringBoot与Vue技术栈。本文基于SpringBootVueElement UI从模板设计、文件落盘、前端路由挂载到实战订单模块完整展示了代码生成器的构建与运用帮助开发者摆脱繁琐CRUD聚焦业务逻辑。1. 为什么前后端分离项目需要一套代码生成器接手过几个 SpringBoot Vue 的老项目后你会发现最耗时间的不是业务逻辑而是 CRUD 的重复劳动。每建一张表就要写一遍 Entity、Mapper、Service、Controller、Vue 列表页、表单页、API 请求文件逻辑几乎一样字段一多就容易漏改。更麻烦的是不同人写的命名习惯还不一样联调时接口和前端字段对不上是常事。这套基于 SpringBoot Vue Element UI 的代码生成器快速开发平台核心思路是让机器去生成这些样板代码开发者只关注表结构和业务差异点。它适合毕设、课设也适合企业内部快速搭建管理后台。需要注意的是这类生成器解决的是“标准模块”的速度问题不是业务设计问题——表设计烂生成出来的代码只是把烂结构复制得更快而已。2. SpringBoot 端生成引擎设计与模板选型生成器的服务端必须解决三件事怎么读表结构、怎么组织模板、怎么输出文件。这一步做扎实了后面扩展才有基础。2.1 生成器的核心抽象表结构到元数据先把数据库表结构读出来转换成统一的元数据对象。不管是 MySQL 还是 PostgreSQL都要经过这一层抽象。常见做法是查询information_schema.columns拿到字段名、类型、注释再拼接出 Java 类型、Vue 组件类型、表单校验规则。public class TableMeta { private String tableName; private String className; // 表名转驼峰如 sys_user - SysUser private String moduleName; // 模块名用于包路径 private ListFieldMeta fields; } public class FieldMeta { private String columnName; // 数据库列名 private String fieldName; // 属性名如 user_name - userName private String javaType; // String、Integer、LocalDateTime private String comment; // 字段注释 private Boolean primaryKey; }这段代码是生成器的基础数据结构。className和fieldName需要自己写驼峰转换工具注意处理下划线、表前缀、逻辑删除字段。javaType的映射是关键比如datetime映射为LocalDateTimedecimal映射为BigDecimaltinyint(1)映射为Boolean。映射规则要集中管理不要散落在代码里。2.2 Velocity 与 FreeMarker 模板引擎选型SpringBoot 环境下模板引擎主要用 Velocity 或 FreeMarker。Velocity 语法简洁适合生成纯文本文件FreeMarker 功能更强支持宏、函数但语法稍显繁琐。这里推荐 Velocity理由是在代码生成场景下你只需要#foreach、#if、${}这几个基础指令Velocity 加载快模板文件一眼能看懂。关键配置如下# application.yml spring.velocity.enabledtrue spring.velocity.charsetUTF-8 spring.velocity.resource-loader-pathclasspath:/templates/gen/如果是较新版本的 SpringBoot可能需要手动引入velocity-spring-boot-starter或直接使用VelocityEngine。建议直接注入 Velocity 引擎不用走 Spring 的自动配置控制更直观。Configuration public class VelocityConfig { Bean public VelocityEngine velocityEngine() { Properties props new Properties(); props.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, classpath:/templates/gen); props.setProperty(Velocity.INPUT_ENCODING, UTF-8); props.setProperty(Velocity.OUTPUT_ENCODING, UTF-8); return new VelocityEngine(props); } }这里要说明resource-loader-path指向的是src/main/resources/templates/gen模板按包名建子目录。VelocityEngine初始化时如果找不到模板通常是因为路径前缀写错或者是 IDEA 没有重新编译资源目录刷新 Maven 即可。2.3 代码模板该怎么拆以标准三层加 VO 为例模板不能一个文件写到底否则后期很难维护。按分层拆成五个模板Controller、Service 接口、ServiceImpl、Mapper 接口、Mapper XML、实体类、VO 对象。这里以 Controller 和 Service 为示例。Controller 模板#set($className ${table.className}) package ${packageName}.controller; import ${packageName}.common.Result; import ${packageName}.entity.${className}; import ${packageName}.service.${className}Service; import org.springframework.web.bind.annotation.*; import org.springframework.beans.factory.annotation.Autowired; RestController RequestMapping(/${table.moduleName}/${table.tableName}) public class ${className}Controller { Autowired private ${className}Service ${className.toLowerCase()}Service; GetMapping public Result list(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize) { return Result.success(${className.toLowerCase()}Service.page(pageNum, pageSize)); } PostMapping public Result save(RequestBody ${className} entity) { ${className.toLowerCase()}Service.save(entity); return Result.success(); } }这个模板输出的是一个最朴素的 RESTful 接口。${table.moduleName}是模块名通常对应包名下的子包Result是你项目里统一的返回值封装。pageNum和pageSize用RequestParam接收前端分页组件会按这两个参数名传值。注意RequestBody必须写否则前端传 JSON 时后端拿不到数据。Service 与 Mapper 模板Service 接口模板很简单只定义page、save、update、delete四个方法。关键在 ServiceImpl 模板要自动注入 Mapper并处理分页逻辑。package ${packageName}.service.impl; import ${packageName}.mapper.${className}Mapper; import ${packageName}.entity.${className}; import ${packageName}.service.${className}Service; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; Service public class ${className}ServiceImpl extends ServiceImpl${className}Mapper, ${className} implements ${className}Service { }这里直接用 MyBatis-Plus 的ServiceImpl可以减少大量样板代码。page方法直接调用page(new Page(pageNum, pageSize))即可save、update靠主键判断是 insert 还是 update。用 MyBatis-Plus 还是原生 MyBatis取决于你的项目基础建议在生成器里做成可配置项。Mapper 接口模板更简单继承BaseMapperT就行。但 Mapper XML 模板要注意只生成基础的BaseColumnList、BaseResultMap和selectByPrimaryKey复杂 SQL 不要自动生成生成器管不了业务逻辑硬生成反而碍事。2.4 生成后的文件落盘与覆盖策略模板渲染完要把字符串写到磁盘。常见做法是配置一个输出根目录按包名生成目录结构。public void generate(TableMeta tableMeta, String outputDir) { VelocityContext context new VelocityContext(); context.put(table, tableMeta); context.put(packageName, com.example.demo); Template tpl velocityEngine.getTemplate(controller.vm); StringWriter writer new StringWriter(); tpl.merge(context, writer); String filePath outputDir /controller/ tableMeta.getClassName() Controller.java; File file new File(filePath); if (file.exists() isOverwrite false) { return; } FileUtils.writeStringToFile(file, writer.toString(), UTF-8); }isOverwrite是控制是否覆盖已有文件的关键开关。建议设计一个配置项第一次生成时覆盖之后手动修改的代码不要再覆盖。否则你调整了模板重新生成一遍手写的业务逻辑瞬间消失。文件写入用FileUtils.writeStringToFile或Files.write都行注意先创建父目录。3. Vue Element UI 前端页面生成与路由挂载后端代码生成完毕前端才是重头戏。Element UI 的表格、表单、分页组合起来代码量不比后端少。前端生成器要产出四个文件API 请求模块、列表页、表单弹窗、路由配置。3.1 列表页模板el-table 加分页加搜索列表页的核心是el-table绑定数据源el-pagination控制分页顶部搜索区根据查询字段生成。template div classapp-container el-form :inlinetrue :modelqueryParams el-form-item label用户名 el-input v-modelqueryParams.userName placeholder请输入用户名 clearable / /el-form-item el-form-item el-button typeprimary clickhandleQuery搜索/el-button /el-form-item /el-form el-table :datatableData v-loadingloading border el-table-column propuserName label用户名 / el-table-column propphone label手机号 / el-table-column label操作 template slot-scopescope el-button typetext clickhandleEdit(scope.row)编辑/el-button el-button typetext stylecolor:red clickhandleDelete(scope.row)删除/el-button /template /el-table-column /el-table el-pagination :current-pagequeryParams.pageNum :page-sizequeryParams.pageSize :totaltotal layouttotal, prev, pager, next current-changehandlePageChange / /div /template这段模板里queryParams是后端接口接收的参数对象userName、phone这些 prop 是根据表字段动态生成的。handleQuery方法负责重置页码并拉数据handlePageChange更新页码后重新请求。handleEdit和handleDelete是操作列的两个标准动作。3.2 表单弹窗模板el-dialog 加 el-form 校验表单页建议用el-dialog包裹el-form方便做新增和编辑复用。模板需要根据字段类型决定控件String用el-inputInteger用el-input-numberDate用el-date-picker字典字段用el-select。el-dialog :titledialogTitle :visible.syncdialogVisible width600px el-form refformRef :modelform :rulesrules label-width100px el-form-item label用户名 propuserName el-input v-modelform.userName / /el-form-item el-form-item label手机号 propphone el-input v-modelform.phone / /el-form-item /el-form div slotfooter el-button clickdialogVisible false取 消/el-button el-button typeprimary clickhandleSubmit确 定/el-button /div /el-dialogprop必须和v-model绑定的字段名一致否则校验不生效。校验规则从后端字段的非空属性生成比如数据库字段是NOT NULL就生成required: true加一条提示。注意rules不能直接写在data里让模板拼字符串要生成一个独立的 JS 对象否则引号转义会把人逼疯。3.3 API 请求模块与 Token 处理前端生成器必须生成对应的 API 文件统一封装 axios 请求。这里要处理前后端分离项目最关键的 Token 头。import request from /utils/request export function listUser(params) { return request({ url: /sys/user, method: get, params }) } export function saveUser(data) { return request({ url: /sys/user, method: post, data }) } export function deleteUser(id) { return request({ url: /sys/user/ id, method: delete }) }/utils/request是你项目里已有的 axios 实例。生成器只需要生成这四个函数不需要重新写拦截器。Token 处理在request.js中统一完成常见写法是请求拦截器里从localStorage取 token加到Authorization头。如果你们用 JWT 登录还需要在响应拦截器里处理 401 过期跳转。这一块生成器不建议生成不同项目差异太大。3.4 生成后的路由与菜单注册生成 Vue 文件后别忘了把路由挂上。如果你用的是若依或自研后台框架通常菜单表里配置路由地址、组件路径、权限标识。生成器可以顺带生成一条 SQL 插入菜单记录。INSERT INTO sys_menu (menu_name, component, perms, path, menu_type) VALUES (用户管理, system/user/index, system:user:list, user, C);component对应前端的 Vue 文件路径path是浏览器访问地址perms是按钮权限标识。如果你的框架支持动态路由这一步做完刷新页面菜单就出来了。注意menu_type是C目录还是F按钮生成时要有区分。4. 实战用生成器快速产出一个订单管理模块理论说再多不如动手跑一遍。下面用一个订单表完整演示从建表到生成代码再到联调验证的流程。4.1 建表与元数据配置先建一张订单表字段要覆盖常见类型方便看效果。CREATE TABLE order_info ( id bigint(20) NOT NULL AUTO_INCREMENT, order_no varchar(64) NOT NULL COMMENT 订单编号, customer_name varchar(128) DEFAULT NULL COMMENT 客户名称, order_amount decimal(10,2) DEFAULT NULL COMMENT 订单金额, status tinyint(1) DEFAULT NULL COMMENT 状态0待支付 1已支付, create_time datetime DEFAULT NULL COMMENT 创建时间, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;然后需要在生成器界面或配置文件里填写表名order_info、模块名order。注意表名和模块名不要混淆order_info是数据库表order是包名和前端路由前缀。4.2 执行生成与目录结构对比运行生成器后对比生成产物src/main/java/com/example/demo/ ├── controller/OrderInfoController.java ├── entity/OrderInfo.java ├── mapper/OrderInfoMapper.java ├── service/OrderInfoService.java └── service/impl/OrderInfoServiceImpl.java src/main/resources/ ├── mapper/OrderInfoMapper.xml src/main/java/com/example/demo/../../vue/ ├── api/orderInfo.js ├── views/order/orderInfoList.vue └── views/order/orderInfoForm.vue看一下生成的OrderInfo.javaorderAmount应该是BigDecimalstatus应该是Boolean或Integer取决于你的tinyint(1)映射配置。如果字段类型不对先查元数据读取部分不要急着改模板。4.3 启动 SpringBoot 并验证接口启动项目后用 curl 验证接口是否正常返回。curl -X GET http://localhost:8080/order/order_info?pageNum1pageSize10 -H Authorization: Bearer your-jwt-token -H Content-Type: application/json返回结果应该是统一格式的 JSON例如{ code: 200, msg: 操作成功, data: { records: [], total: 0 } }如果返回 404检查一下RequestMapping的路径是否和前端请求的 URL 完全一致。很多项目在这里踩坑——后端路径是/order/orderInfo前端请求的是/order/order_info。生成器里要统一路径风格建议表名原样映射避免转换歧义。如果返回 500大概率是 Mapper XML 里的BaseColumnList字段写错或者 MyBatis-Plus 的order_info表名识别成了OrderInfo。需要在实体类上确认TableName(order_info)注解是否正确生成。4.4 前端联调与常见报错排查前端启动后打开订单管理页面。如果表格空白按 F12 看网络请求。常见问题有三个跨域前后端分离部署时需要在 SpringBoot 里配置 CORS。最简单的做法是用CrossOrigin加在 Controller 类上但我一般建议用全局 CorsFilter 统一处理。Token 未携带axios 拦截器没生效检查request.js里是否正确读取了 token。字段名不一致后端实体字段是customerName前端表格 prop 写成了customer_name导致数据显示不出来。这里要依靠生成器的统一命名规则避免人为修改。分页组件还有一个隐性坑el-pagination默认传的current-page是 1 起步但后端分页页码也是从 1 开始两者一致。如果你换用 PageHelper它默认页码从 1 开始没问题但有的框架起始页是 0就需要前端减一。5. 进阶自定义模板与生成参数调优最后这部分介绍几个让生成器更贴手的小技巧适合已经跑通基础流程的人。5.1 给生成器加一个字段类型映射扩展默认的字段映射表可能不够用。比如你有一个json类型的字段希望映射成ListSomeObject或者text字段映射成Lob注解。可以在元数据读取阶段增加一个扩展点。public class TypeMapping { private static final MapString, String MAPPINGS new HashMap(); static { MAPPINGS.put(varchar, String); MAPPINGS.put(bigint, Long); MAPPINGS.put(int, Integer); MAPPINGS.put(decimal, BigDecimal); MAPPINGS.put(datetime, LocalDateTime); // 扩展 MAPPINGS.put(json, String); } }这样改的好处是基于 SpringBoot 的生成器配置集中在一个类里加字段类型只需要加一行。注意LocalDateTime需要导入java.time.LocalDateTime模板里要设置自动导包。另外数据库里unsigned int可能要映射为Longtinyint(1)有时需要映射为Boolean这属于业务约定不要一刀切。5.2 让生成的代码自动带上 Swagger 注解如果公司要求接口文档自动生成可以在 Controller 模板中引入 Swagger 注解。Api(tags ${table.className} 管理) RestController RequestMapping(/${table.moduleName}/${table.tableName}) public class ${className}Controller { ApiOperation(分页查询) GetMapping public Result list(...) { ... } ApiOperation(新增或更新) PostMapping public Result save(RequestBody Validated ${className} entity) { ... } }这时要在模板中增加一个开关useSwaggertrue时输出注解。生成器既能出纯后端接口也能出带 Swagger 的文档版本。注意Validated加上后实体类字段需要配合NotBlank、NotNull等校验注解否则参数校验不生效。校验注解的生成要基于数据库字段的 nullable 约束但注意update和insert场景的校验规则可能不同我的做法是只给通用必填字段加不细分场景。5.3 验证生成结果的小技巧生成的代码不能只看能编译通过还要验证命名规范和字段映射是否正确。我一般在生成器里加一个 dry-run 模式只输出文件内容到控制台不写盘。java -jar generator.jar --dry-run --tableorder_info这样能快速检查模板渲染结果改模板后不用反复删文件、重新生成。另一个技巧是生成后自动做一次全量git diff对比手写代码和生成代码的差异防止模板里不小心混入手写内容。如果框架里已经有静态代码检查工具可以把生成的代码跑一遍mvn compile和 eslint确保基本质量。最后如果你扩展了模板记得把模板目录单独抽成 git 仓库方便团队共享。代码生成器本身就是一个普通的 SpringBoot 项目模板就是资源文件做到这一步生成器才算真正融入到你的日常开发流程里。本文还有配套的精品资源点击获取
返回列表