一、功能说明
- 数据源管理:实现多个数据库的表代码生成
- 表管理:从数据源导入表,配置表和字段
- 默认配置:配置项目默认信息,配置字段数据类型映射
- 操作日志
功能截图
二、代码实现
- 基于velocity-engine模板代码生成
package com.qiangesoft.bootcodegen.utils;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.config.ConstVal;
import com.baomidou.mybatisplus.generator.util.FileUtils;
import com.qiangesoft.bootcodegen.constant.CodegenConstant;
import com.qiangesoft.bootcodegen.entity.BcgTableColumn;
import com.qiangesoft.bootcodegen.framework.config.CodeGenParam;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* 代码生成工具类
*
* @author qiangesoft
* @date 2023-09-29
*/
public class CodeGenUtil {
/**
* 路径分隔符
*/
public static final String FILE_SEP = "/";
/**
* java路径
*/
public static final String JAVA_PATH = FILE_SEP + "src" + FILE_SEP + "main" + FILE_SEP + "java";
/**
* 资源文件路径
*/
public static final String RESOURCES_PATH = FILE_SEP + "src" + FILE_SEP + "main" + FILE_SEP + "resources";
/**
* 控制层
*/
public static String CONTROLLER = "controller";
/**
* 实体类
*/
public static String ENTITY = "entity";
/**
* 服务层接口
*/
public static String SERVICE = "service";
/**
* 服务层实现类
*/
public static String SERVICE_IMPL = "serviceImpl";
/**
* 持久层
*/
public static String MAPPER = "mapper";
/**
* 持久层xml
*/
public static String MAPPER_XML = "mapperXml";
/**
* 查询对象
*/
public static String QUERY = "query";
/**
* 传输对象
*/
public static String DTO = "dto";
/**
* 对象转换
*/
public static String CONVERT = "convert";
/**
* 视图对象
*/
public static String VO = "vo";
/**
* vue页面
*/
public static String PAGE = "page";
/**
* js代码
*/
public static String API_JS = "api";
/**
* 模板文件
*/
public static final Map<String, String> TEMPLATE_MAP = new HashMap<>();
/**
* 模板文件位置
*/
static {
TEMPLATE_MAP.put(CONTROLLER, FILE_SEP + "templates" + FILE_SEP + "controller.java.vm");
TEMPLATE_MAP.put(ENTITY, FILE_SEP + "templates" + FILE_SEP + "entity.java.vm");
TEMPLATE_MAP.put(SERVICE, FILE_SEP + "templates" + FILE_SEP + "service.java.vm");
TEMPLATE_MAP.put(SERVICE_IMPL, FILE_SEP + "templates" + FILE_SEP + "serviceImpl.java.vm");
TEMPLATE_MAP.put(MAPPER, FILE_SEP + "templates" + FILE_SEP + "mapper.java.vm");
TEMPLATE_MAP.put(QUERY, FILE_SEP + "templates" + FILE_SEP + "Query.java.vm");
TEMPLATE_MAP.put(DTO, FILE_SEP + "templates" + FILE_SEP + "DTO.java.vm");
TEMPLATE_MAP.put(VO, FILE_SEP + "templates" + FILE_SEP + "VO.java.vm");
TEMPLATE_MAP.put(CONVERT, FILE_SEP + "templates" + FILE_SEP + "Convert.java.vm");
TEMPLATE_MAP.put(MAPPER_XML, FILE_SEP + "templates" + FILE_SEP + "mapper.xml.vm");
TEMPLATE_MAP.put(PAGE, FILE_SEP + "templates" + FILE_SEP + "page.vue.vm");
TEMPLATE_MAP.put(API_JS, FILE_SEP + "templates" + FILE_SEP + "api.js.vm");
}
/**
* 代码存放路径
*/
public static Map<String, String> codeGenFilePath(String genModule, String genPackage) {
Map<String, String> codeGenFilePackage = codeGenFilePackage(genModule, genPackage);
Map<String, String> map = new HashMap<>();
String controllerPath = JAVA_PATH + FILE_SEP + codeGenFilePackage.get(CONTROLLER).replace(".", FILE_SEP) + FILE_SEP;
String entityPath = JAVA_PATH + FILE_SEP + codeGenFilePackage.get(ENTITY).replace(".", FILE_SEP) + FILE_SEP;
String servicePath = JAVA_PATH + FILE_SEP + codeGenFilePackage.get(SERVICE).replace(".", FILE_SEP) + FILE_SEP;
String serviceImplPath = JAVA_PATH + FILE_SEP + codeGenFilePackage.get(SERVICE_IMPL).replace(".", FILE_SEP) + FILE_SEP;
String mapperPath = JAVA_PATH + FILE_SEP + codeGenFilePackage.get(MAPPER).replace(".", FILE_SEP) + FILE_SEP;
String queryPath = JAVA_PATH + FILE_SEP + codeGenFilePackage.get(QUERY).replace(".", FILE_SEP) + FILE_SEP;
String dtoPath = JAVA_PATH + FILE_SEP + codeGenFilePackage.get(DTO).replace(".", FILE_SEP) + FILE_SEP;
String voPath = JAVA_PATH + FILE_SEP + codeGenFilePackage.get(VO).replace(".", FILE_SEP) + FILE_SEP;
String convertPath = JAVA_PATH + FILE_SEP + codeGenFilePackage.get(CONVERT).replace(".", FILE_SEP) + FILE_SEP;
String mapperXmlPath = RESOURCES_PATH + FILE_SEP + "mapper" + FILE_SEP + (StringUtils.isNotBlank(genModule) ? genModule + FILE_SEP : "");
String pagePath = FILE_SEP + "page" + FILE_SEP + "views" + FILE_SEP;
String apiJsPath = FILE_SEP + "page" + FILE_SEP + "api" + FILE_SEP;
map.put(CONTROLLER, controllerPath);
map.put(ENTITY, entityPath);
map.put(SERVICE, servicePath);
map.put(SERVICE_IMPL, serviceImplPath);
map.put(MAPPER, mapperPath);
map.put(QUERY, queryPath);
map.put(DTO, dtoPath);
map.put(VO, voPath);
map.put(CONVERT, convertPath);
map.put(MAPPER_XML, mapperXmlPath);
map.put(PAGE, pagePath);
map.put(API_JS, apiJsPath);
return map;
}
/**
* java代码包路径
*/
public static Map<String, String> codeGenFilePackage(String genModule, String genPackage) {
Map<String, String> map = new HashMap<>();
String packagePath = StringUtils.isNotBlank(genModule) ? genPackage + "." + genModule : genPackage;
String controllerPath = packagePath + ".controller";
String entityPath = packagePath + ".entity";
String servicePath = packagePath + ".service";
String serviceImplPath = servicePath + ".impl";
String mapperPath = packagePath + ".mapper";
String queryPath = packagePath + ".pojo.query";
String dtoPath = packagePath + ".pojo.dto";
String voPath = packagePath + ".pojo.vo";
String convertPath = packagePath + ".pojo.convert";
map.put(CONTROLLER, controllerPath);
map.put(ENTITY, entityPath);
map.put(SERVICE, servicePath);
map.put(SERVICE_IMPL, serviceImplPath);
map.put(MAPPER, mapperPath);
map.put(QUERY, queryPath);
map.put(DTO, dtoPath);
map.put(VO, voPath);
map.put(CONVERT, convertPath);
return map;
}
/**
* 本地项目生成
*/
public static void genLocal(CodeGenParam codeGenParam) throws IOException {
// 1.初始化Velocity引擎
initVelocity();
// 2.加载velocity容器
VelocityContext velocityContext = buildVelocityContext(codeGenParam);
// 3.生成文件
String genClass = codeGenParam.getGenClass();
Map<String, String> map = codeGenFilePath(codeGenParam.getGenModule(), codeGenParam.getGenPackage());
for (String key : map.keySet()) {
String templatePath = TEMPLATE_MAP.get(key);
String filePath = map.get(key);
String fileName = getFileName(genClass, key, templatePath, filePath);
File file = new File(codeGenParam.getGenPath() + fileName);
boolean exist = file.exists();
if (!exist) {
File parentFile = file.getParentFile();
FileUtils.forceMkdir(parentFile);
}
Template template = Velocity.getTemplate(templatePath, ConstVal.UTF8);
try (FileOutputStream fos = new FileOutputStream(file);
OutputStreamWriter ow = new OutputStreamWriter(fos, ConstVal.UTF8);
BufferedWriter writer = new BufferedWriter(ow)) {
template.merge(velocityContext, writer);
} catch (Exception e) {
throw new RuntimeException("代码生成失败");
}
}
}
/**
* 下载代码
*
* @param codeGenParam
* @param response
*/
public static void genDownload(CodeGenParam codeGenParam, HttpServletResponse response) {
// 1.初始化Velocity引擎
initVelocity();
// 2.加载velocity容器
VelocityContext velocityContext = buildVelocityContext(codeGenParam);
// 3.生成文件
String genClass = codeGenParam.getGenClass();
Map<String, String> map = codeGenFilePath(codeGenParam.getGenModule(), codeGenParam.getGenPackage());
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream);
for (String key : map.keySet()) {
String templatePath = TEMPLATE_MAP.get(key);
String filePath = map.get(key);
String fileName = getFileName(genClass, key, templatePath, filePath).replaceFirst("/", "");
creatZipEntry(templatePath, fileName, velocityContext, zipOutputStream);
}
IOUtils.closeQuietly(zipOutputStream);
byte[] bytes = outputStream.toByteArray();
response.reset();
response.setHeader("Content-Disposition", "attachment; filename=code.zip");
response.addHeader("Content-Length", "" + bytes.length);
response.setContentType("application/octet-stream; charset=UTF-8");
IOUtils.write(bytes, response.getOutputStream());
} catch (Exception e) {
throw new RuntimeException("代码生成失败");
}
}
/**
* 初始化vm
*/
private static void initVelocity() {
Properties properties = new Properties();
properties.setProperty(ConstVal.VM_LOAD_PATH_KEY, ConstVal.VM_LOAD_PATH_VALUE);
properties.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, StringPool.EMPTY);
properties.setProperty(Velocity.ENCODING_DEFAULT, ConstVal.UTF8);
properties.setProperty(Velocity.INPUT_ENCODING, ConstVal.UTF8);
properties.setProperty("file.resource.loader.unicode", StringPool.TRUE);
Velocity.init(properties);
}
/**
* 变量替换参数
*
* @param codeGenParam
* @return
*/
private static VelocityContext buildVelocityContext(CodeGenParam codeGenParam) {
VelocityContext velocityContext = new VelocityContext();
velocityContext.put("tableName", codeGenParam.getTableName());
velocityContext.put("genAuthor", codeGenParam.getGenAuthor());
velocityContext.put("genDate", codeGenParam.getGenDate());
velocityContext.put("genFunction", codeGenParam.getGenFunction());
velocityContext.put("genClass", codeGenParam.getGenClass());
char firstChar = codeGenParam.getGenClass().charAt(0);
velocityContext.put("genClassCamel", Character.toLowerCase(firstChar) + codeGenParam.getGenClass().substring(1));
List<BcgTableColumn> columnList = codeGenParam.getColumnList();
velocityContext.put("columnList", columnList);
velocityContext.put("controllerMapping", TableColumnInitUtil.camelToChar(codeGenParam.getGenClass(), '-'));
velocityContext.put("packageConfig", codeGenFilePackage(codeGenParam.getGenModule(), codeGenParam.getGenPackage()));
velocityContext.put("columnConfig", CodegenConstant.COLUMN_CONFIG);
for (BcgTableColumn tableColumn : columnList) {
if (tableColumn.getColumnPk()) {
velocityContext.put("idConfig", tableColumn);
break;
}
}
velocityContext.put("hutoolStrUtil", cn.hutool.core.util.StrUtil.class);
return velocityContext;
}
/**
* 文件信息
*
* @param genClass
* @param key
* @param templatePath
* @param filePath
* @return
*/
private static String getFileName(String genClass, String key, String templatePath, String filePath) {
String fileBaseName = templatePath.substring(templatePath.lastIndexOf('/') + 1, templatePath.lastIndexOf('.'));
String fileName;
if (ENTITY.equals(key)) {
fileName = filePath + genClass + ".java";
} else if (PAGE.equals(key)) {
fileName = filePath + genClass + ".vue";
} else if (API_JS.equals(key)) {
fileName = filePath + genClass + ".js";
} else if (SERVICE.equals(key)) {
fileName = filePath + "I" + genClass + Character.toUpperCase(fileBaseName.charAt(0)) + fileBaseName.substring(1);
} else {
fileName = filePath + genClass + Character.toUpperCase(fileBaseName.charAt(0)) + fileBaseName.substring(1);
}
return fileName;
}
/**
* 合成单个文件
*
* @param templatePath
* @param fileName
* @param velocityContext
* @param zipOutputStream
*/
private static void creatZipEntry(String templatePath, String fileName, VelocityContext velocityContext, ZipOutputStream zipOutputStream) throws IOException {
// 加载模板并替换变量
try (StringWriter stringWriter = new StringWriter()) {
Template template = Velocity.getTemplate(templatePath, ConstVal.UTF8);
template.merge(velocityContext, stringWriter);
zipOutputStream.putNextEntry(new ZipEntry(fileName));
String content = stringWriter.toString();
zipOutputStream.write(content.getBytes(ConstVal.UTF8));
zipOutputStream.flush();
zipOutputStream.closeEntry();
}
}
}