一、配置文件
1-配置文件: src/main/resources/generator.yml
2-配置生成表的基础信息:包路径 前缀规则等
# 代码生成
gen:
# 作者
author: ruoyi
# 默认生成包路径 system 需改成自己的模块名称 如 system monitor tool
packageName: com.ruoyi.system
# 自动去除表前缀,默认是false
autoRemovePre: false
# 表前缀(生成类名不会包含表前缀,多个用逗号分隔)
tablePrefix: sys_
配置类:GenConfig
路径 : src/main/java/com/ruoyi/generator/config/GenConfig.java
package com.ruoyi.generator.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
/**
* 读取代码生成相关配置
*
* @author ruoyi
*/
@Component
@ConfigurationProperties(prefix = "gen")
@PropertySource(value = {"classpath:generator.yml"}, encoding = "UTF-8")
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;
}
//省略。。。。。。。。
}
二、导入表逻辑
功能:导入需要生成的表数据到以下2张表
gen_table 导入的表
gen_table_column 导入的表列名
页面位置:src/views/tool/gen/importTable.vue
2-1 导入按钮:
上面 导入按钮组件 ,在index页面中
按钮逻辑:
1-点击导入按钮,调用弹框页面show方法
2-show 展示导入选表页面,渲染所有表列表
//0-引入导入页面组件
import importTable from "./importTable";
export default {
name: "Gen",
components: { importTable },
//1-点击导入按钮,触发 openImportTable方法
<el-button
type="info"
plain
icon="el-icon-upload"
size="mini"
@click="openImportTable"
v-hasPermi="['tool:gen:import']"
>导入</el-button>
//2-js方法:openImportTable
/** 打开导入表弹窗 调用了import组件里面的show方法 */
openImportTable() {
this.$refs.import.show();
},
//3-import组件中show方法:调用后台接口: /db/list 查询数据,并展示弹框页面
show() {
this.getList();
this.visible = true;
},
// 查询表数据
getList() {
listDbTable(this.queryParams).then(res => {
if (res.code === 200) {
this.dbTableList = res.rows;
this.total = res.total;
}
});
},
后台查询列表中
根据 <if>条件 判断数据库类型,执行对应sql,查询数据库所有表
<if test="@com.ruoyi.common.helper.DataBaseHelper@isMySql()">:判断是myql
<if test="@com.ruoyi.common.helper.DataBaseHelper@isOracle()">:判断oracle查询对应库博
<select id="selectPageDbTableList" resultMap="GenTableResult">
<if test="@com.ruoyi.common.helper.DataBaseHelper@isMySql()">
select table_name, table_comment, create_time, update_time
from information_schema.tables
where table_schema = (select database())
AND table_name NOT LIKE 'xxl_job_%' AND table_name NOT LIKE 'gen_%'
AND table_name NOT IN (select table_name from gen_table)
<if test="genTable.tableName != null and genTable.tableName != ''">
AND lower(table_name) like lower(concat('%', #{genTable.tableName}, '%'))
</if>
<if test="genTable.tableComment != null and genTable.tableComment != ''">
AND lower(table_comment) like lower(concat('%', #{genTable.tableComment}, '%'))
</if>
order by create_time desc
</if>
<if test="@com.ruoyi.common.helper.DataBaseHelper@isOracle()">
select lower(dt.table_name) as table_name, dtc.comments as table_comment, uo.created as create_time, uo.last_ddl_time as update_time
from user_tables dt, user_tab_comments dtc, user_objects uo
where dt.table_name = dtc.table_name
and dt.table_name = uo.object_name
and uo.object_type = 'TABLE'
AND dt.table_name NOT LIKE 'XXL_JOB_%' AND dt.table_name NOT LIKE 'GEN_%'
AND lower(dt.table_name) NOT IN (select table_name from gen_table)
<if test="genTable.tableName != null and genTable.tableName != ''">
AND lower(dt.table_name) like lower(concat(concat('%', #{genTable.tableName}), '%'))
</if>
<if test="genTable.tableComment != null and genTable.tableComment != ''">
AND lower(dtc.comments) like lower(concat(concat('%', #{genTable.tableComment}), '%'))
</if>
order by create_time desc
</if>
</select>
2-2 确认按钮
功能:插入选中表,以及表字段到代码生成数据库。
前端逻辑:
确定按钮调用了:handleImportTable JS方法,并请求后台接口 /tool/gen/importTable 保存数据,
成功: this.$emit("ok"); 通知父组件的 ok方法,触发 handleQuery渲染保存数据
子组件 src/views/tool/gen/importTable.vue 方法:
/** 导入按钮操作 */
handleImportTable() {
const tableNames = this.tables.join(",");
if (tableNames == "") {
this.$modal.msgError("请选择要导入的表");
return;
}
importTable({ tables: tableNames }).then(res => {
this.$modal.msgSuccess(res.msg);
if (res.code === 200) {
this.visible = false;
this.$emit("ok");
}
});
}
//调用后台接口
export function importTable(data) {
return request({
headers: { 'datasource': localStorage.getItem("dataName") },
url: '/tool/gen/importTable',
method: 'post',
params: data
})
}
父组件:handleQuery
重新渲染上面插入的数据,到列表
<import-table ref="import" @ok="handleQuery" />
/** 搜索按钮操作 */
handleQuery() {
localStorage.setItem("dataName", this.queryParams.dataName);
this.queryParams.pageNum = 1;
this.getList();
},
后台逻辑:
请看注释,已经详细写了每一步:
主要是 :初始化表信息 实体类驼峰,以及java实体类类型
生成页面的表单 、列表、 查询栏等显示
/**
* 导入表结构(保存)
*
* @param tables 表名串
*/
@SaCheckPermission("tool:gen:import")
@Log(title = "代码生成", businessType = BusinessType.IMPORT)
@PostMapping("/importTable")
public R<Void> importTableSave(String tables) {
String[] tableNames = Convert.toStrArray(tables);
//1- 根据传入的表名称,查询information_schema.tables表信息
List<GenTable> tableList = genTableService.selectDbTableListByNames(tableNames);
//2- 保存表信息到GenTable 表存标字段信息到GenTableColumn
genTableService.importGenTable(tableList);
return R.ok();
}
service层:
@Transactional(rollbackFor = Exception.class)
@Override
public void importGenTable(List<GenTable> tableList) {
String operName = LoginHelper.getUsername();
try {
for (GenTable table : tableList) {
//1-得到表名称
String tableName = table.getTableName();
//2-初始化表信息 (表面转类名称 去除表前缀f_转驼峰 设置包名称 创建人 就是yml 配置)
GenUtils.initTable(table, operName);
//3-插入表信息
int row = baseMapper.insert(table);
if (row > 0) {
//4- 查询表字段信息
List<GenTableColumn> genTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName);
List<GenTableColumn> saveColumns = new ArrayList<>();
for (GenTableColumn column : genTableColumns) {
//5-初始化表字段,数据库字段类型设置对应java字段类型,以及页面字段类型,查询条件,上传文件 等
GenUtils.initColumnField(column, table);
saveColumns.add(column);
}
if (CollUtil.isNotEmpty(saveColumns)) {
//5-插入 列表
genTableColumnMapper.insertBatch(saveColumns);
}
}
}
} catch (Exception e) {
throw new ServiceException("导入失败:" + e.getMessage());
}
上面步骤4: //4- 查询表字段信息
调用selectDbTableColumnsByName 方法, 执行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 = ('test_demo') order by ordinal_position
结果:
最后插入到表 gen_table_column 中
三、修改操作
3.1页面以及逻辑展示
功能:
1-定义代码生成的实体类
2-定义前端泰马是否显示
编辑页面,前端路径:src/views/tool/gen/editTable.vue
功能:页面会初始化字段信息,以及基础数据下拉
created() {
const tableId = this.$route.params && this.$route.params.tableId;
if (tableId) {
// 获取表详细信息
getGenTable(tableId).then(res => {
this.columns = res.data.rows;
this.info = res.data.info;
this.tables = res.data.tables;
});
/** 查询字典下拉列表 */
getDictOptionselect().then(response => {
this.dictOptions = response.data;
});
/** 查询菜单下拉列表 */
getMenuTreeselect().then(response => {
this.menus = this.handleTree(response.data, "menuId");
});
}
},
提交按钮逻辑:
校验表单数据,成功则组装表单数据发送后台保存
/** 提交按钮 */
submitForm() {
const basicForm = this.$refs.basicInfo.$refs.basicInfoForm;
const genForm = this.$refs.genInfo.$refs.genInfoForm;
Promise.all([basicForm, genForm].map(this.getFormPromise)).then(res => {
// 校验表单basicForm、genForm 成功后执行
const validateResult = res.every(item => !!item);
if (validateResult) {
//basicForm.model, genForm.model 设置到 {}中得到 genTable对象
const genTable = Object.assign({}, basicForm.model, genForm.model);
genTable.columns = this.columns;
genTable.params = {
treeCode: genTable.treeCode,
treeName: genTable.treeName,
treeParentCode: genTable.treeParentCode,
parentMenuId: genTable.parentMenuId
};
//请求后台 修改代码生成信息
updateGenTable(genTable).then(res => {
this.$modal.msgSuccess(res.msg);
if (res.code === 200) {
this.close();
}
});
} else {
this.$modal.msgError("表单校验未通过,请重新检查提交内容");
}
});
}
3.2 树表代码生成
如图对比表字段
四、预览(Velocity生成预览)
4.1功能展示
点击预览展示生成的前后端代码
如图:
可以预览前后端页面代码,以及生成测试数据sql
4.2预览页面前端代码:
点击 src/views/tool/gen/index.vue 页面的预览按钮:
/** 预览按钮 */ handlePreview(row) { previewTable(row.tableId).then(response => { this.preview.data = response.data; this.preview.open = true; this.preview.activeName = "domain.java"; }); },
,然后渲染弹框页面如上图 4.1展示。
渲染页面展示代码:
<el-dialog :title="preview.title" :visible.sync="preview.open" width="80%" top="5vh" append-to-body class="scrollbar">
<el-tabs v-model="preview.activeName">
<el-tab-pane
v-for="(value, key) in preview.data"
:label="key.substring(key.lastIndexOf('/')+1,key.indexOf('.vm'))"
:name="key.substring(key.lastIndexOf('/')+1,key.indexOf('.vm'))"
:key="key"
>
<el-link :underline="false" icon="el-icon-document-copy" v-clipboard:copy="value" v-clipboard:success="clipboardSuccess" style="float:right">复制</el-link>
<!-- <pre> 标签 保留所有空格换行 -->
<pre><code class="hljs" v-html="highlightedCode(value, key)"></code></pre>
</el-tab-pane>
</el-tabs>
</el-dialog>
备注渲染代码:
1-展示代码用到 :<pre> 标签 保留所有空格换行,文本格式原样子输出
2-代码高亮展示 :highlightedCode 方法,更具语言高亮展示代码:import hljs from "highlight.js/lib/highlight"; import "highlight.js/styles/github-gist.css"; /** 高亮显示 */ highlightedCode(code, key) { const vmName = key.substring(key.lastIndexOf("/") + 1, key.indexOf(".vm")); var language = vmName.substring(vmName.indexOf(".") + 1, vmName.length); const result = hljs.highlight(language, code || "", true); return result.value || ' '; },
4.3 后端代码
后端使用Velocity生成预览的模版
请求了:@GetMapping("/preview/{tableId}") 接口,其实现类主要逻辑如下:
1-拿到要生成的表和标数据
2-合并表数据和Velocity模版
private void generatorCode(String tableName, ZipOutputStream zip) {
// 查询表信息
GenTable table = baseMapper.selectGenTableByName(tableName);
List<Long> menuIds = new ArrayList<>();
for (int i = 0; i < 6; i++) {
menuIds.add(identifierGenerator.nextId(null).longValue());
}
table.setMenuIds(menuIds);
// 设置主子表信息
setSubTable(table);
// 设置主键列信息
setPkColumn(table);
/**
* 生成Velocity模版步骤
*/
//1-初始化Velocity
VelocityInitializer.initVelocity();
//2-准备Velocity上下文
VelocityContext context = VelocityUtils.prepareContext(table);
//3-获取模板列表
List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());
for (String template : templates) {
// 渲染模板
StringWriter sw = new StringWriter();
//4-根据路径拿到模板
Template tpl = Velocity.getTemplate(template, Constants.UTF8);
//5-StringWriter流,合并上下文和模版
tpl.merge(context, sw);
//6-生成压缩包
try {
// 添加到zip
zip.putNextEntry(new ZipEntry(VelocityUtils.getFileName(template, table)));
IoUtil.write(zip, StandardCharsets.UTF_8, false, sw.toString());
IoUtil.close(sw);
zip.flush();
zip.closeEntry();
} catch (IOException e) {
log.error("渲染模板失败,表名:" + table.getTableName(), e);
}
}
}
上面getTemplateList方法,对应的vm模版位置:
实体类 模版展示:
具体Velocity生成代码学习可以看我文章:
package ${packageName}.domain;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.util.Date;
import java.math.BigDecimal;
#foreach ($import in $importList)
import ${import};
#end
#if($table.crud || $table.sub)
import com.ruoyi.common.core.domain.BaseEntity;
#elseif($table.tree)
import com.ruoyi.common.core.domain.TreeEntity;
#end
/**
* ${functionName}对象 ${tableName}
*
* @author ${author}
* @date ${datetime}
*/
#if($table.crud || $table.sub)
#set($Entity="BaseEntity")
#elseif($table.tree)
#set($Entity="TreeEntity<${ClassName}>")
#end
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("${tableName}")
public class ${ClassName} extends ${Entity} {
private static final long serialVersionUID=1L;
#foreach ($column in $columns)
#if(!$table.isSuperColumn($column.javaField))
/**
* $column.columnComment
*/
#if($column.javaField=='delFlag')
@TableLogic
#end
#if($column.javaField=='version')
@Version
#end
#if($column.pk)
@TableId(value = "$column.columnName")
#end
private $column.javaType $column.javaField;
#end
#end
}
4.3下载压缩包
选择zip压缩包下载
逻辑和4.3预览一样,多了转为字节流下载
/**
* 批量生成代码
*
* @param tables 表名串
*/
@SaCheckPermission("tool:gen:code")
@Log(title = "代码生成", businessType = BusinessType.GENCODE)
@GetMapping("/batchGenCode")
public void batchGenCode(HttpServletResponse response, String tables) throws IOException {
String[] tableNames = Convert.toStrArray(tables);
byte[] data = genTableService.downloadCode(tableNames);
genCode(response, data);
}
/**
* 生成zip文件
*/
private void genCode(HttpServletResponse response, byte[] data) throws IOException {
response.reset();
response.addHeader("Access-Control-Allow-Origin", "*");
response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");
response.setHeader("Content-Disposition", "attachment; filename=\"ruoyi.zip\"");
response.addHeader("Content-Length", "" + data.length);
response.setContentType("application/octet-stream; charset=UTF-8");
IoUtil.write(response.getOutputStream(), false, data);
}
}
service层
/**
* 批量生成代码(下载方式)
*
* @param tableNames 表数组
* @return 数据
*/
@Override
public byte[] downloadCode(String[] tableNames) {
//创建字节数组输出流
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
//根据字节数组输出流,创建ZipOutputStream输出流
ZipOutputStream zip = new ZipOutputStream(outputStream);
//生成模版
for (String tableName : tableNames) {
generatorCode(tableName, zip);
}
//关闭流
IoUtil.close(zip);
//转为字节数组
return outputStream.toByteArray();
}
前端:
判断是二进制数据,进行下载
/** 生成代码操作 */
handleGenTable(row) {
const tableNames = row.tableName || this.tableNames;
if (tableNames == "") {
this.$modal.msgError("请选择要生成的数据");
return;
}
if(row.genType === "1") {
genCode(row.tableName).then(response => {
this.$modal.msgSuccess("成功生成到自定义路径:" + row.genPath);
});
} else {
this.$download.zip("/tool/gen/batchGenCode?tables=" + tableNames, "ruoyi.zip");
}
},
zip(url, name) {
var url = baseURL + url
downloadLoadingInstance = Loading.service({ text: "正在下载数据,请稍候", spinner: "el-icon-loading", background: "rgba(0, 0, 0, 0.7)", })
axios({
method: 'get',
url: url,
responseType: 'blob',
headers: {
'Authorization': 'Bearer ' + getToken(),
'datasource': localStorage.getItem("dataName")
}
}).then((res) => {
//是否是二进制
const isBlob = blobValidate(res.data);
if (isBlob) {
const blob = new Blob([res.data], { type: 'application/zip' })
this.saveAs(blob, name)
} else {
this.printErrMsg(res.data);
}
downloadLoadingInstance.close();
}).catch((r) => {
console.error(r)
Message.error('下载文件出现错误,请联系管理员!')
downloadLoadingInstance.close();
})
},
五、同步功能
数据库字段改动点击同步,会按照最新字段生成代码,保留之前的未修改字段逻辑