本文共 9126 字,大约阅读时间需要 30 分钟。
历史文章
本篇文章将会讲解ruoyi-vue系统下代码生成器的使用、原理分析以及将这部分代码抽离出来形成独立版的代码生成器。
原则上,我们的业务代码和若依系统本身的系统代码是要做隔离的,一方面是易于之后随着若依系统升级而升级,另一方面则是纯粹的合理性考虑。
这里新建一个ruoyi-business模块作为业务代码模块,新建完ruoyi-business模块之后添加ruoyi-framework依赖,pom文件如下所示
ruoyi com.ruoyi 3.4.0 4.0.0 com.kdyzm ruoyi-business 1.0.0-SNAPSHOT com.ruoyi ruoyi-framework
之后在ruoyi-admin添加ruoyi-business模块的依赖
com.kdyzm ruoyi-business 1.0.0-SNAPSHOT
ruoyi-vue系统中代码生成器相代码都在ruoyi-generator模块中,代码生成的配置在resources/generator.yml
文件中,由于要在新的模块ruoyi-business中做开发,要有个新包名,包名取作com.kdyzm.business
,所以generator.yml配置文件内容如下:
# 代码生成gen: # 作者 author: kdyzm # 默认生成包路径 system 需改成自己的模块名称 如 system monitor tool packageName: com.kdyzm.business # 自动去除表前缀,默认是false autoRemovePre: false # 表前缀(生成类名不会包含表前缀,多个用逗号分隔) tablePrefix: sys_
另外,这里要使用自定义包名com.kdyzm.business,所以若依系统中mybatis也要做相应的修改
修改mybatis别名配置,增加对com.kdyzm包名的识别
# MyBatis配置mybatis: # 搜索指定包别名 typeAliasesPackage: com.ruoyi.**.domain,com.kdyzm.**.domain
修改mybatis的mapper扫描包路径
修改com.ruoyi.framework.config.ApplicationConfig类的MapperScan注解,增加对com.kdyzm包的扫描
@MapperScan({"com.ruoyi.**.mapper","com.kdyzm.**.mapper"})public class ApplicationConfig{ ...}
最后,在ruoyi-admin新增一个Config类,扫描com.kdyzm包,以将ruoyi-business模块中的所有组件纳入spring管理。
package com.ruoyi.config;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;/** * @author kdyzm */@Configuration@ComponentScan(basePackages = "com.kdyzm")public class Config {}
这里新建一张商品表作为示例,注意,这里的字段和表都要加上注释
CREATE TABLE `goods` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', `GOODS_NAME` varchar(255) DEFAULT NULL COMMENT '商品名字', `put_way_flag` tinyint(1) DEFAULT NULL COMMENT '商品是否上架,0:下架,1:上架', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `create_by` varchar(64) DEFAULT NULL COMMENT '创建人', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `update_by` varchar(64) DEFAULT NULL COMMENT '更新人', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表'
进入系统工具-代码生成页面,点击导入按钮,找到goods表并导入,如下图所示
然后点击生成代码按钮
之后,就可以得到一个ruoyi.zip压缩文件,压缩文件中包含生成的前后端代码以及sql语句文件,生成的代码目录结构如下所示
├── goodsMenu.sql├── main│ ├── java│ │ └── com│ │ └── kdyzm│ │ └── business│ │ ├── controller│ │ │ └── GoodsController.java│ │ ├── domain│ │ │ └── Goods.java│ │ ├── mapper│ │ │ └── GoodsMapper.java│ │ └── service│ │ ├── IGoodsService.java│ │ └── impl│ │ └── GoodsServiceImpl.java│ └── resources│ └── mapper│ └── business│ └── GoodsMapper.xml└── vue ├── api │ └── business │ └── goods.js └── views └── business └── goods └── index.vue
将生成代码中的main目录直接拷贝到ruoyi-business模块下的src目录,可以看到生成的代码是典型的三层架构,从controller到mapper都已经帮我们生成好了。
前端代码对应着生成目录中的vue目录,这里将vue/api目录中的内容拷贝到ruoyi-ui/src/api目录中,将vue/views中的内容拷贝到ruoyi-ui/src/views目录,操作上,直接将生成的api和views目录拷贝到src目录即可。
生成的sql代码是创建菜单和按钮权限使用的,直接在ruoyi数据库下执行 goodsMenu.sql 文件中的内容即可。
-- 菜单 SQLinsert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)values('商品', '3', '1', 'goods', 'business/goods/index', 1, 0, 'C', '0', '0', 'business:goods:list', '#', 'admin', sysdate(), '', null, '商品菜单');-- 按钮父菜单IDSELECT @parentId := LAST_INSERT_ID();-- 按钮 SQLinsert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)values('商品查询', @parentId, '1', '#', '', 1, 0, 'F', '0', '0', 'business:goods:query', '#', 'admin', sysdate(), '', null, '');insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)values('商品新增', @parentId, '2', '#', '', 1, 0, 'F', '0', '0', 'business:goods:add', '#', 'admin', sysdate(), '', null, '');insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)values('商品修改', @parentId, '3', '#', '', 1, 0, 'F', '0', '0', 'business:goods:edit', '#', 'admin', sysdate(), '', null, '');insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)values('商品删除', @parentId, '4', '#', '', 1, 0, 'F', '0', '0', 'business:goods:remove', '#', 'admin', sysdate(), '', null, '');insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)values('商品导出', @parentId, '5', '#', '', 1, 0, 'F', '0', '0', 'business:goods:export', '#', 'admin', sysdate(), '', null, '');
重新运行前后端代码,打开系统工具菜单(为何在这里?这是若依vue的bug,已经提到gitee,bug链接: )的商品菜单
可以看到,前后端代码只是复制粘贴,后端接口、前端菜单、权限等等一切都已经被代码生成器帮我们做完了,可以说代码生成器极大的简化了我们的开发。
在三的测试过程中,我们发现,商品是否上架这个字段对应的前端表单是个文本输入框,但是实际上这里应该是个下拉列表框或者单选按钮才对,因为它只有两个值:0或者1,ruoyi-vue代码生成器实际上是支持这种操作的。之前生成代码的时候导入表之后直接点击了下载按钮生成了代码,实际上跳过了一个重要的步骤,那就是编辑代码生成选项。
点击编辑按钮之后,跳转修改生成配置页面。
在这个页面中,可以修改字段在前端的显示类型以及字典类型,比如,要想“商品是否上架”在前端展示为单选框,就可以修改显示类型为“单选框”,字典类型设置为“业务是否(需要新增数据字典,0表示下架,1表示上架)”。
提交之后重新生成的代码样式:
可以看到,商品是否字段变成了下拉列表和单选框的样式。
另外,若依代码生成器支持三种数据格式模板的代码生成:单表、树表、主子表,这里默认使用的是单表模板,也是最常使用的模板。
Velocity是一个基于Java的模板引擎,其提供了一个Context容器,在java代码里面我们可以往容器中存值,然后在vm文件中使用特定的语法获取,这是velocity基本的用法,其与jsp、freemarker并称为三大视图展现技术。作为一个模块引擎,除了作为前后端分离的MVC展现层,Velocity还有一些其他用途,比如源代码生成。
在若依Vue系统中,正是使用了Velocity技术实现的源代码生成。大体上,源代码生成只需三步走:
三步走完之后源代码就生成了,说起来是很简单的,但是实际上做起来会比较麻烦,特别是第一步创建模板文件是最复杂的,以下为index.vue模板文件部分源代码:
#foreach($column in $columns)#if($column.query)#set($dictType=$column.dictType)#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})#set($parentheseIndex=$column.columnComment.indexOf("("))#if($parentheseIndex != -1)#set($comment=$column.columnComment.substring(0, $parentheseIndex))#else#set($comment=$column.columnComment)#end#if($column.htmlType == "input")...
可以看到,该vue模板文件中充斥着大量Velocity的if-else语法,嵌套在一起更是显得无比复杂。
总之,整体上来看,java代码的模板比较简单,vue和mybatis mapper的模板文件比较复杂。
mysql数据库中有一个information_schema数据库,它是mysql的系统数据库之一,它里面存储着两个表TABLES
以及COLUMNS
,这两个表分别存储着所有的表信息以及所有表中的列信息,代码生成器正是以两张表的信息为核心实现的。
ruoyi-vue代码生成器相关代码均位于ruoyi-generator模块中,根据之前的实际操作体验上来看,最简单的情况,前端页面只需要两步即可完成代码生成
实际上这两步对应着后端的两个接口:com.ruoyi.generator.controller.GenController#importTableSave
和 com.ruoyi.generator.controller.GenController#batchGenCode
,生成源码的步骤就要从这两步下手。
首先看com.ruoyi.generator.controller.GenController#importTableSave
接口,它做了以下这些事情
接下来看下 com.ruoyi.generator.controller.GenController#batchGenCode
接口,它做了以下这些事情
完毕。
作为一个后端开发,我最经常做的事情不是搞啥系统架构,而是最简单的CRUD。。。若是能有一个代码一键生成工具自动根据已经创建的表信息生成CRUD后端代码,那岂不是能节省老鼻子功夫了——若依系统已经实现了这个代码生成器的工具,但是它依赖于前端页面,必须有权限访问“系统工具-代码生成”菜单才行,而这在企业中像我这种普通研发往往是是没有权限访问的。但是我有权限访问表,查看表结构。作为一个有追求的开发,既不肯开口问别人要权限,还想要实现代码生成器,该怎么做?
自己搞一个呗。
在这里封装的代码生成器不考虑前端页面调整功能,其实现的功能更加注重于后端代码,其作用和“系统工具-代码生成”页面中最简单的生成代码的两步(导入表和下载代码,无编辑)结果等效。
ruoyi-vue中的ruoyi-generator模块有着完整的代码生成逻辑,但是它依托于ruoyi-admin的spring-boot框架才能运行,现在我要将ruoyi-generator模块的功能独立于spring-boot,让其作为一个普通的spring的程序,只有一个普通的main方法,实现和原来等效的功能。
这里的做法是直接修改ruoyi-generator模块,删除spring-boot的相关功能,但是保留spring、mybatis、druid等基础组件的依赖,然后将这些组件手动重新纳入spring容器中进行管理,最后通过main方法调用到相关模块。
具体就不展开讲了,有兴趣的可以看看我的源代码:
该配置文件的内容如下:
mysql.username=${dbUserName}mysql.password=${dbPassword}mysql.connectionUrl=${dbUrl}mysql.driverClass=${dbDriverClassName}gen.author=kdyzmgen.packageName=com.kdyzm.businessgen.autoRemovePre=falsegen.tableName=newsgen.tablePrefix=sys_
上半部分是数据库配置,连接的是ruoyi-vue数据库,正常配置即可;下半部分是生成配置,除了gen.tableName
,其它配置和原ruoyi-vue代码生成器的配置相同。
要注意,代码生成结果仅使用用ruoyi-vue项目,如需自定义模板,需要修改源代码。
在项目根目录运行命令mvn clean package
,打包完成之后切换到target目录,使用命令java -jar ruoyi-vue-gen-1.0-SNAPSHOT.jar
运行jar包得到ruoyi.zip压缩文件
独立版代码生成器源代码:
好了,ruoyi-vue代码生成器篇就到此结束了,欢迎关注我的博客 ~
转载地址:http://phdyz.baihongyu.com/