代码生成器
1. 代码解读
-
代码生成器表结构说明
-
gen_table用于存储业务表的基本信息 -
gen_table_column用于存储业务表的字段信息gen_table与gen_table_column+的 关系为一对多

-
-
代码生成器目录结构

-
查询数据库
-
前端
-
@/views/tool/index.vue<!-- 导入数据表弹框组件 --> <import-table ref="importRef" @ok="handleQuery" />/** 打开导入表弹窗 */ function openImportTable() { proxy.$refs["importRef"].show(); }@/views/tool/importTable.vue,查询数据并打开弹窗/** 查询参数列表 */ function show() { getList(); visible.value = true; } /** 查询表数据 */ function getList() { listDbTable(queryParams).then(res => { dbTableList.value = res.rows; total.value = res.total; }); }src\api\tool\gen.js,发送api请求export function listDbTable(query) { return request({ url: '/tool/gen/db/list', method: 'get', params: query }) }
-
-
后端

-
generator模块的
GenController.java、/** * 查询数据库列表 */ @PreAuthorize("@ss.hasPermi('tool:gen:list')") @GetMapping("/db/list") public TableDataInfo dataList(GenTable genTable) { startPage(); List<GenTable> list = genTableService.selectDbTableList(genTable); return getDataTable(list); }底层的xml如下
<select id="selectDbTableList" parameterType="GenTable" resultMap="GenTableResult"> select table_name, table_comment, create_time, update_time from information_schema.tables where table_schema = (select database()) AND table_name NOT LIKE 'qrtz_%' AND table_name NOT LIKE 'gen_%' AND table_name NOT IN (select table_name from gen_table) <if test="tableName != null and tableName != ''"> AND lower(table_name) like lower(concat('%', #{tableName}, '%')) </if> <if test="tableComment != null and tableComment != ''"> AND lower(table_comment) like lower(concat('%', #{tableComment}, '%')) </if> <if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 --> AND date_format(create_time,'%y%m%d') >= date_format(#{params.beginTime},'%y%m%d') </if> <if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 --> AND date_format(create_time,'%y%m%d') <= date_format(#{params.endTime},'%y%m%d') </if> order by create_time desc </select>information_schema.tables表是mysql自带的表,用于查询该数据库中所有表的信息。
在上面的查询语句中,使用了通配符
qrtz_%、gen_%'排除了和定时任务以及代码生成器相关的表。AND table_name NOT LIKE 'qrtz_%' AND table_name NOT LIKE 'gen_%'并且排除了在gen_table中已经存在的表名(排除已经导入的表名)
AND table_name NOT IN (select table_name from gen_table)
-
-
-
导入表结构
-
前端
-
在
importTable.vue<el-button type="primary" @click="handleImportTable">确 定</el-button>要将选中的表的表明用
,连接成字符串并传递给后端/** 导入按钮操作 */ function handleImportTable() { const tableNames = tables.value.join(","); if (tableNames == "") { proxy.$modal.msgError("请选择要导入的表"); return; } importTable({ tables: tableNames }).then(res => { proxy.$modal.msgSuccess(res.msg); if (res.code === 200) { visible.value = false; emit("ok"); } }); }gen.js// 导入表 export function importTable(data) { return request({ url: '/tool/gen/importTable', method: 'post', params: data }) }
-
-
后端

-
Controller
将字符串拆分成数组并查询响应的数据库字段
/** * 导入表结构(保存) */ @PreAuthorize("@ss.hasPermi('tool:gen:import')") @Log(title = "代码生成", businessType = BusinessType.IMPORT) @PostMapping("/importTable") public AjaxResult importTableSave(String tables) { String[] tableNames = Convert.toStrArray(tables); // 查询表信息 List<GenTable> tableList = genTableService.selectDbTableListByNames(tableNames); genTableService.importGenTable(tableList, SecurityUtils.getUsername()); return success(); }importGenTable方法用于导入代码生成表配置,注意table变量的作用域包含该次的for循环,也就是说在
GenUtils.initTable方法可以直接改变table变量,而不用返回,genTableMapper.insertGenTable(table)中的table,就是已经是被修改后的table。tableList 为表格列表,包含需要导入的表结构信息
username 为当前操作用户的用户名,用于记录操作日志和权限验证
@Override @Transactional public void importGenTable(List<GenTable> tableList, String operName) { try { for (GenTable table : tableList) { String tableName = table.getTableName(); GenUtils.initTable(table, operName); int row = genTableMapper.insertGenTable(table); if (row > 0) { // 保存列信息 List<GenTableColumn> genTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName); for (GenTableColumn column : genTableColumns) { GenUtils.initColumnField(column, table); genTableColumnMapper.insertGenTableColumn(column); } } } } catch (Exception e) { throw new ServiceException("导入失败:" + e.getMessage()); } }通过项目配置,初始化代码生成表的基本信息
/** * 初始化代码生成表的基本信息 * 设置类名、包名、模块名、业务名、功能名、作者、创建者等基础属性 * * @param genTable 代码生成表对象,用于存储表的配置信息 * @param operName 操作人员名称,用于记录创建者信息 */ public static void initTable(GenTable genTable, String operName) { // 根据表明上传类名,用于代码上传时的类名识别 genTable.setClassName(convertClassName(genTable.getTableName())); // 设置包名,根据项目配置的包名进行设置 genTable.setPackageName(GenConfig.getPackageName()); // 设置模块名,通过解析包得到 genTable.setModuleName(getModuleName(GenConfig.getPackageName())); // 根据表名设置业务名,用于生成代码时的功能描述 genTable.setBusinessName(getBusinessName(genTable.getTableName())); // 设置功能名,通过解析表名得到 genTable.setFunctionName(replaceText(genTable.getTableComment())); // 设置生成代码的作战,根据配置文件中的作者信息进行设置 genTable.setFunctionAuthor(GenConfig.getAuthor()); // 将操作人设置为当前操作的用户 genTable.setCreateBy(operName); }GenConfig,生成代码的配置类,配置类的变量取决于配置文件
generator.yml/** * 读取代码生成相关配置 * * @author ruoyi */ @Component @ConfigurationProperties(prefix = "gen") @PropertySource(value = { "classpath:generator.yml" }) public class GenConfig { /** 作者 */ public static String author; /** 生成包路径 */ public static String packageName; /** 自动去除表前缀,默认是false */ public static boolean autoRemovePre; /** 表前缀(类名不会包含表前缀) */ public static String tablePrefix; public static String getAuthor() { return author; } @Value("${author}") public void setAuthor(String author) { GenConfig.author = author; } public static String getPackageName() { return packageName; } @Value("${packageName}") public void setPackageName(String packageName) { GenConfig.packageName = packageName; } public static boolean getAutoRemovePre() { return autoRemovePre; } @Value("${autoRemovePre}") public void setAutoRemovePre(boolean autoRemovePre) { GenConfig.autoRemovePre = autoRemovePre; } public static String getTablePrefix() { return tablePrefix; } @Value("${tablePrefix}") public void setTablePrefix(String tablePrefix) { GenConfig.tablePrefix = tablePrefix; } }配置文件
generator.yml,我们可以修改这个文件,来减少代码生成的修改项(注意,这个配置文件在导入时生效,已经导入的表格,数据已经被记录,配置文件不会影响)# 代码生成 gen: # 作者 author: ruoyi # 默认生成包路径 system 需改成自己的模块名称 如 system monitor tool packageName: com.dkd.system # 自动去除表前缀,默认是false autoRemovePre: false # 表前缀(生成类名不会包含表前缀,多个用逗号分隔) tablePrefix: sys_ -
xml
根据表名查询数据库信息
<select id="selectDbTableListByNames" resultMap="GenTableResult"> select table_name, table_comment, create_time, update_time from information_schema.tables where table_name NOT LIKE 'qrtz_%' and table_name NOT LIKE 'gen_%' and table_schema = (select database()) and table_name in <foreach collection="array" item="name" open="(" separator="," close=")"> #{name} </foreach> </select>根据表名查询数据库表列信息,包括列名、是否必填、是否主键、排序位置、列注释、是否自增、列类型等信息
<!-- 根据表名查询数据库表列信息 查询指定表的所有列信息, 参数说明: - tableName: 表名,用于查询指定表的列信息 返回值说明: - 返回GenTableColumnResult映射的结果集,包含列的详细信息 --> <select id="selectDbTableColumnsByName" parameterType="String" resultMap="GenTableColumnResult"> select column_name, (case when (is_nullable = 'no' <![CDATA[ && ]]> column_key != 'PRI') then '1' else '0' end) as is_required, (case when column_key = 'PRI' then '1' else '0' end) as is_pk, ordinal_position as sort, column_comment, (case when extra = 'auto_increment' then '1' else '0' end) as is_increment, column_type from information_schema.columns where table_schema = (select database()) and table_name = (#{tableName}) order by ordinal_position </select>我们这里执行一下sql
select column_name, (case when (is_nullable = 'no' && column_key != 'PRI') then '1' else '0' end) as is_required, (case when column_key = 'PRI' then '1' else '0' end) as is_pk, ordinal_position as sort, column_comment, (case when extra = 'auto_increment' then '1' else '0' end) as is_increment, column_type from information_schema.columns where table_schema = (select database()) and table_name = ('sku') order by ordinal_position可以看到sku(商品表)的列配置信息被查询出来了,并且和字段信息的表格相对应

-
初始化并保存列信息
GenUtils.initColumnField(column, table);这个方法就是初始化、添加上面查询没有对应到的字段或数据
/** * 初始化列属性字段 */ public static void initColumnField(GenTableColumn column, GenTable table) { String dataType = getDbType(column.getColumnType()); String columnName = column.getColumnName(); column.setTableId(table.getTableId()); column.setCreateBy(table.getCreateBy()); // 设置java字段名 column.setJavaField(StringUtils.toCamelCase(columnName)); // 设置默认类型 column.setJavaType(GenConstants.TYPE_STRING); column.setQueryType(GenConstants.QUERY_EQ); if (arraysContains(GenConstants.COLUMNTYPE_STR, dataType) || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType)) { // 字符串长度超过500设置为文本域 Integer columnLength = getColumnLength(column.getColumnType()); String htmlType = columnLength >= 500 || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType) ? GenConstants.HTML_TEXTAREA : GenConstants.HTML_INPUT; column.setHtmlType(htmlType); } else if (arraysContains(GenConstants.COLUMNTYPE_TIME, dataType)) { column.setJavaType(GenConstants.TYPE_DATE); column.setHtmlType(GenConstants.HTML_DATETIME); } else if (arraysContains(GenConstants.COLUMNTYPE_NUMBER, dataType)) { column.setHtmlType(GenConstants.HTML_INPUT); // 如果是浮点型 统一用BigDecimal String[] str = StringUtils.split(StringUtils.substringBetween(column.getColumnType(), "(", ")"), ","); if (str != null && str.length == 2 && Integer.parseInt(str[1]) > 0) { column.setJavaType(GenConstants.TYPE_BIGDECIMAL); } // 如果是整形 else if (str != null && str.length == 1 && Integer.parseInt(str[0]) <= 10) { column.setJavaType(GenConstants.TYPE_INTEGER); } // 长整形 else { column.setJavaType(GenConstants.TYPE_LONG); } } // 插入字段(默认所有字段都需要插入) column.setIsInsert(GenConstants.REQUIRE); // 编辑字段 if (!arraysContains(GenConstants.COLUMNNAME_NOT_EDIT, columnName) && !column.isPk()) { column.setIsEdit(GenConstants.REQUIRE); } // 列表字段 if (!arraysContains(GenConstants.COLUMNNAME_NOT_LIST, columnName) && !column.isPk()) { column.setIsList(GenConstants.REQUIRE); } // 查询字段 if (!arraysContains(GenConstants.COLUMNNAME_NOT_QUERY, columnName) && !column.isPk()) { column.setIsQuery(GenConstants.REQUIRE); } // 查询字段类型 if (StringUtils.endsWithIgnoreCase(columnName, "name")) { column.setQueryType(GenConstants.QUERY_LIKE); } // 状态字段设置单选框 if (StringUtils.endsWithIgnoreCase(columnName, "status")) { column.setHtmlType(GenConstants.HTML_RADIO); } // 类型&性别字段设置下拉框 else if (StringUtils.endsWithIgnoreCase(columnName, "type") || StringUtils.endsWithIgnoreCase(columnName, "sex")) { column.setHtmlType(GenConstants.HTML_SELECT); } // 图片字段设置图片上传控件 else if (StringUtils.endsWithIgnoreCase(columnName, "image")) { column.setHtmlType(GenConstants.HTML_IMAGE_UPLOAD); } // 文件字段设置文件上传控件 else if (StringUtils.endsWithIgnoreCase(columnName, "file")) { column.setHtmlType(GenConstants.HTML_FILE_UPLOAD); } // 内容字段设置富文本控件 else if (StringUtils.endsWithIgnoreCase(columnName, "content")) { column.setHtmlType(GenConstants.HTML_EDITOR); } }根据数据库的列表转换为字段的名称(转换为小驼峰)
// 设置java字段名 column.setJavaField(StringUtils.toCamelCase(columnName));设置默认类型,默认类型为字符串,查询方式为相等
// 设置默认类型 column.setJavaType(GenConstants.TYPE_STRING); column.setQueryType(GenConstants.QUERY_EQ);其他的太多了,就不一一列举了,建议从上到下看一遍。
-
-
-
上传代码

-
Controller
/** * 生成代码(下载方式) */ @PreAuthorize("@ss.hasPermi('tool:gen:code')") @Log(title = "代码生成", businessType = BusinessType.GENCODE) @GetMapping("/download/{tableName}") public void download(HttpServletResponse response, @PathVariable("tableName") String tableName) throws IOException { byte[] data = genTableService.downloadCode(tableName); genCode(response, data); } -
Service
/** * 生成代码(下载方式) * * @param tableName 表名称 * @return 包含生成代码的字节数组,用于下载 */ @Override public byte[] downloadCode(String tableName) { // 创建字节数组输出流,用于存储生成的代码内容 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); // 创建ZIP压缩输出流,将生成的代码文件压缩成ZIP格式 ZipOutputStream zip = new ZipOutputStream(outputStream); // 生成代码,并将代码内容写入ZIP流中 generatorCode(tableName, zip); // 安静地关闭ZIP流,释放资源 IOUtils.closeQuietly(zip); // 将输出流中的字节转换为字节数组并返回,用于下载 return outputStream.toByteArray(); }/** * 查询表信息并生成代码 */ private void generatorCode(String tableName, ZipOutputStream zip) { // 查询表信息 GenTable table = genTableMapper.selectGenTableByName(tableName); // 设置主子表信息 setSubTable(table); // 设置主键列信息 setPkColumn(table); VelocityInitializer.initVelocity(); VelocityContext context = VelocityUtils.prepareContext(table); // 获取模板列表 List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType()); for (String template : templates) { // 渲染模板 StringWriter sw = new StringWriter(); Template tpl = Velocity.getTemplate(template, Constants.UTF8); tpl.merge(context, sw); try { // 添加到zip zip.putNextEntry(new ZipEntry(VelocityUtils.getFileName(template, table))); IOUtils.write(sw.toString(), zip, Constants.UTF8); IOUtils.closeQuietly(sw); zip.flush(); zip.closeEntry(); } catch (IOException e) { log.error("渲染模板失败,表名:" + table.getTableName(), e); } } } -
查询表信息
GenTable table = genTableMapper.selectGenTableByName(tableName);<select id="selectGenTableByName" parameterType="String" resultMap="GenTableResult"> SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.tpl_web_type, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.gen_type, t.gen_path, t.options, t.remark, c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort FROM gen_table t LEFT JOIN gen_table_column c ON t.table_id = c.table_id where t.table_name = #{tableName} order by c.sort </select>
-
2. velocity模板
Velocity是一个Java模板引擎,它使用特定语法在模板中嵌入Java对象数据,实现界面与代码的分离。
-
Velocity官网:https://velocity.apache.org/

-
入门案例
代码目录结构

模板文件(test.html.vm)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <h1>Hello ${name}</h1> </body> </html>写入测试(VelocityDemo.java)
public static void main(String[] args) throws IOException { // 1. 初始化模板引擎 VelocityInitializer.initVelocity(); // 2. 创建Velocity上下文对象 VelocityContext context = new VelocityContext(); // 3. 添加数据到上下文对象中 context.put("name", "Velocity"); // 4. 读取模板 Template template = Velocity.getTemplate("vm/test.html.vm", "UTF-8"); // 5. 输出结果(渲染模板) FileWriter fileWriter = new FileWriter("D:\\velocity-test.html"); template.merge(context, fileWriter); fileWriter.close(); }效果

-
变量声明
-
在模板中定义变量:#set开头,比如#set($name = “velocity”)
-
获取变量的值:
$name或者${name}但是
$name不能进行字符串的拼接,推荐使用${name}## 定义变量 #set($name = "Veolicty") ## 输出变量 第一种情况:${name} <br/> 第二种情况:$name <br/> -
对象变量
Region.java@Data @NoArgsConstructor @AllArgsConstructor @ApiModel("区域管理对象") public class Region extends BaseEntity { private static final long serialVersionUID = 1L; /** 区域主键ID(自增) */ private Long id; /** 区域名称(如:朝阳区、海淀区) */ private String regionName; }模板文件(test.html.vm)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> ## 获取对象中的数据 $region <br/> 区域ID:$region.id <br/> 区域名称:$region.regionName <br/> </body> </html>写入测试(VelocityDemo.java)
public class VelocityDemo { public static void main(String[] args) throws IOException { // 1. 初始化模板引擎 VelocityInitializer.initVelocity(); // 2. 创建Velocity上下文对象 VelocityContext context = new VelocityContext(); // 3. 添加数据到上下文对象中 Region region = new Region(1L, "大兴"); context.put("region", region); // 4. 读取模板 Template template = Velocity.getTemplate("vm/test.html.vm", "UTF-8"); // 5. 输出结果(渲染模板) FileWriter fileWriter = new FileWriter("D:\\velocity-test.html"); template.merge(context, fileWriter); fileWriter.close(); } }运行效果:

-
-
循环判断
-
循环
模板文件(test.html.vm)
## 定义一个集合 #set($list=["春", "夏", "秋", "冬"]) ## 遍历循环 #foreach($item in $list) [$foreach.index]$item <br/> #end$foreach.index是从0开始的,$foreach.count是从1开始的输出效果:

-
判断
判断的语法:#if(condition) … #elseif(condition) … #else … #end

-
-
模板阅读
-
domain

-
其他controller、service、mapper类似
-
-
lombok集成
首先,确认在common模块中导入了坐标
<!-- lombok工具--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>在
domain.java.vm中,导包import lombok.Builder; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;写注解
## lombook集成 @Data @NoArgsConstructor @AllArgsConstructor @Builder删除手写get和set(包括子表)
#foreach ($column in $columns) #if(!$table.isSuperColumn($column.javaField)) #if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) #set($AttrName=$column.javaField) #else #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) #end public void set${AttrName}($column.javaType $column.javaField) { this.$column.javaField = $column.javaField; } public $column.javaType get${AttrName}() { return $column.javaField; } #end #end #if($table.sub) public List<${subClassName}> get${subClassName}List() { return ${subclassName}List; } public void set${subClassName}List(List<${subClassName}> ${subclassName}List) { this.${subclassName}List = ${subclassName}List; } #end重写toString的方法,以及相关的导包也要删除
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle;@Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) #foreach ($column in $columns) #if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) #set($AttrName=$column.javaField) #else #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) #end .append("${column.javaField}", get${AttrName}()) #end #if($table.sub) .append("${subclassName}List", get${subClassName}List()) #end .toString(); }从代码预览可以看出,lombok集成成功

-
swagger集成
首先,确认在common模块中导入了坐标
<!-- knife4j --> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-boot-starter</artifactId> <version>3.0.3</version> </dependency>在controller中集成,
@Api(tags = "${functionName}") @RestController @RequestMapping("/${moduleName}/${businessName}") public class ${ClassName}Controller extends BaseController { // ... }/** * 查询${functionName}列表 */ @ApiOperation("查询${functionName}列表") @PreAuthorize("@ss.hasPermi('${permissionPrefix}:list')") @GetMapping("/list") #if($table.crud || $table.sub) public TableDataInfo list(${ClassName} ${className}) { // ... }同样,其他方法也添加相应的swagger注解,用于描述该方法的功能
但是,返回对象
AjaxResult默认继承了HashMap,与swagger不兼容,我们需要将返回类型改为R类型返回public R add(@RequestBody ${ClassName} ${className}) { // 核心修改:toAjax()改为R.ok() return R.ok(${className}Service.insert${ClassName}(${className})); }

被折叠的 条评论
为什么被折叠?



