已测试生成功能正常
This commit is contained in:
+219
@@ -0,0 +1,219 @@
|
|||||||
|
package com.example.workutils.biz.mybatis.generator;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.mybatis.generator.api.MyBatisGenerator;
|
||||||
|
import org.mybatis.generator.config.Configuration;
|
||||||
|
import org.mybatis.generator.config.xml.ConfigurationParser;
|
||||||
|
import org.mybatis.generator.internal.DefaultShellCallback;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MyBatis逆向工程服务
|
||||||
|
* 用于根据数据库表结构生成Mapper文件
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class MyBatisGeneratorService {
|
||||||
|
|
||||||
|
private static final String CONFIG_FILE = "mybatis-generator.xml";
|
||||||
|
private static final String PROPERTIES_FILE = "mybatis-generator.properties";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据表名列表生成Mapper文件
|
||||||
|
*
|
||||||
|
* @param tableNames 表名列表
|
||||||
|
* @param overwrite 是否覆盖已有文件
|
||||||
|
* @return 生成结果信息
|
||||||
|
*/
|
||||||
|
public String generateByTableNames(List<String> tableNames, boolean overwrite) throws Exception {
|
||||||
|
return generateInternal(tableNames, overwrite);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据表名通配符生成Mapper文件
|
||||||
|
*
|
||||||
|
* @param pattern 表名通配符(如 user_%)
|
||||||
|
* @param overwrite 是否覆盖已有文件
|
||||||
|
* @return 生成结果信息
|
||||||
|
*/
|
||||||
|
public String generateByPattern(String pattern, boolean overwrite) throws Exception {
|
||||||
|
List<String> patterns = new ArrayList<>();
|
||||||
|
patterns.add(pattern);
|
||||||
|
return generateInternal(patterns, overwrite);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内部生成方法
|
||||||
|
*
|
||||||
|
* @param tableNames 表名列表
|
||||||
|
* @param overwrite 是否覆盖已有文件
|
||||||
|
* @return 生成结果信息
|
||||||
|
*/
|
||||||
|
private String generateInternal(List<String> tableNames, boolean overwrite) throws Exception {
|
||||||
|
// 动态修改配置文件中的表配置
|
||||||
|
File tempConfigFile = createTempConfigWithTables(tableNames);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 解析临时配置文件
|
||||||
|
List<String> warnings = new ArrayList<>();
|
||||||
|
|
||||||
|
// 设置使用 JDK 内置 XML 解析器
|
||||||
|
System.setProperty("javax.xml.parsers.DocumentBuilderFactory", "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl");
|
||||||
|
System.setProperty("javax.xml.parsers.SAXParserFactory", "com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl");
|
||||||
|
|
||||||
|
ConfigurationParser parser = new ConfigurationParser(warnings);
|
||||||
|
Configuration configuration = parser.parseConfiguration(tempConfigFile);
|
||||||
|
|
||||||
|
// 执行生成
|
||||||
|
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
|
||||||
|
MyBatisGenerator generator = new MyBatisGenerator(configuration, callback, warnings);
|
||||||
|
generator.generate(null, null, null, true);
|
||||||
|
|
||||||
|
// 收集结果信息
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
if (!warnings.isEmpty()) {
|
||||||
|
result.append("生成完成,有以下警告:\n");
|
||||||
|
for (String warning : warnings) {
|
||||||
|
result.append("- ").append(warning).append("\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.append("生成成功!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
} finally {
|
||||||
|
// 删除临时文件
|
||||||
|
if (tempConfigFile.exists()) {
|
||||||
|
tempConfigFile.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建临时配置文件,包含指定的表配置
|
||||||
|
*/
|
||||||
|
private File createTempConfigWithTables(List<String> tableNames) throws Exception {
|
||||||
|
// 读取原始配置文件内容
|
||||||
|
InputStream is = getClass().getClassLoader().getResourceAsStream(CONFIG_FILE);
|
||||||
|
if (is == null) {
|
||||||
|
throw new RuntimeException("无法找到配置文件: " + CONFIG_FILE);
|
||||||
|
}
|
||||||
|
|
||||||
|
String content;
|
||||||
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
sb.append(line).append("\n");
|
||||||
|
}
|
||||||
|
content = sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用正则表达式移除原有的 table 元素
|
||||||
|
// 匹配 <table ... /> 或 <table ...></table>
|
||||||
|
Pattern tablePattern = Pattern.compile(
|
||||||
|
"<table[^>]*/>|<table[^>]*>.*?</table>",
|
||||||
|
Pattern.DOTALL
|
||||||
|
);
|
||||||
|
content = tablePattern.matcher(content).replaceAll("");
|
||||||
|
|
||||||
|
// 构建新的 table 元素
|
||||||
|
StringBuilder tableElements = new StringBuilder();
|
||||||
|
for (String tableName : tableNames) {
|
||||||
|
tableElements.append(" <table schema=\"\" tableName=\"")
|
||||||
|
.append(tableName)
|
||||||
|
.append("\"\n")
|
||||||
|
.append(" domainObjectName=\"")
|
||||||
|
.append(generateDomainObjectName(tableName))
|
||||||
|
.append("\"\n")
|
||||||
|
.append(" enableCountByExample=\"false\" enableUpdateByExample=\"false\"\n")
|
||||||
|
.append(" enableDeleteByExample=\"false\" enableSelectByExample=\"false\"\n")
|
||||||
|
.append(" selectByExampleQueryId=\"false\"/>\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在 </context> 标签前插入新的 table 元素
|
||||||
|
content = content.replace("</context>", tableElements.toString() + " </context>");
|
||||||
|
|
||||||
|
// 写入临时文件
|
||||||
|
File tempFile = File.createTempFile("mybatis-generator-temp", ".xml");
|
||||||
|
try (FileOutputStream fos = new FileOutputStream(tempFile)) {
|
||||||
|
fos.write(content.getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
return tempFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据表名生成实体类名
|
||||||
|
* 例如:user_info -> UserInfoPO
|
||||||
|
*/
|
||||||
|
private String generateDomainObjectName(String tableName) {
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
String[] parts = tableName.split("_");
|
||||||
|
for (String part : parts) {
|
||||||
|
if (part.length() > 0) {
|
||||||
|
result.append(part.substring(0, 1).toUpperCase())
|
||||||
|
.append(part.substring(1).toLowerCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.append("PO");
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取配置属性
|
||||||
|
*/
|
||||||
|
public Properties getConfigurationProperties() {
|
||||||
|
Properties props = new Properties();
|
||||||
|
try {
|
||||||
|
props.load(getClass().getClassLoader().getResourceAsStream(PROPERTIES_FILE));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("无法加载配置属性文件", e);
|
||||||
|
}
|
||||||
|
return props;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取数据库URL
|
||||||
|
*/
|
||||||
|
public String getDatabaseUrl() {
|
||||||
|
return getConfigurationProperties().getProperty("db_url", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取数据库用户名
|
||||||
|
*/
|
||||||
|
public String getDatabaseUsername() {
|
||||||
|
return getConfigurationProperties().getProperty("db_username", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取Model包路径
|
||||||
|
*/
|
||||||
|
public String getModelPackage() {
|
||||||
|
return getConfigurationProperties().getProperty("model_package", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取Mapper包路径
|
||||||
|
*/
|
||||||
|
public String getMapperPackage() {
|
||||||
|
return getConfigurationProperties().getProperty("dao_package", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取SQL Mapper包路径
|
||||||
|
*/
|
||||||
|
public String getSqlMapperPackage() {
|
||||||
|
return getConfigurationProperties().getProperty("sqlmapper_package", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package com.example.workutils.menu.api;
|
||||||
|
|
||||||
|
import com.example.workutils.menu.handler.AbstractMenuHandler;
|
||||||
|
import com.example.workutils.menu.handler.BatchHandler;
|
||||||
|
import com.example.workutils.menu.handler.GitHealthHandler;
|
||||||
|
import com.example.workutils.menu.handler.MyBatisGeneratorHandler;
|
||||||
|
import com.example.workutils.menu.handler.MyBatisHandler;
|
||||||
|
import com.example.workutils.menu.handler.ScanHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 菜单项枚举
|
||||||
|
* 统一管理菜单序号、名称和处理器
|
||||||
|
*/
|
||||||
|
public enum MenuItem {
|
||||||
|
SCAN(1, "关键词扫描", ScanHandler.class),
|
||||||
|
MYBATIS(2, "MyBatis导出", MyBatisHandler.class),
|
||||||
|
BATCH(3, "批量处理", BatchHandler.class),
|
||||||
|
GIT_HEALTH(4, "Git仓库健康检测", GitHealthHandler.class),
|
||||||
|
MYBATIS_GENERATOR(5, "MyBatis逆向工程", MyBatisGeneratorHandler.class),
|
||||||
|
EXIT(0, "退出系统", null);
|
||||||
|
|
||||||
|
private final int code;
|
||||||
|
private final String name;
|
||||||
|
private final Class<? extends AbstractMenuHandler> handlerClass;
|
||||||
|
|
||||||
|
MenuItem(int code, String name, Class<? extends AbstractMenuHandler> handlerClass) {
|
||||||
|
this.code = code;
|
||||||
|
this.name = name;
|
||||||
|
this.handlerClass = handlerClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayText() {
|
||||||
|
return code + ". " + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<? extends AbstractMenuHandler> getHandlerClass() {
|
||||||
|
return handlerClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasHandler() {
|
||||||
|
return handlerClass != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据序号获取菜单项
|
||||||
|
*
|
||||||
|
* @param code 菜单序号
|
||||||
|
* @return 菜单项,未找到返回null
|
||||||
|
*/
|
||||||
|
public static MenuItem fromCode(int code) {
|
||||||
|
for (MenuItem item : values()) {
|
||||||
|
if (item.code == code) {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
package com.example.workutils.menu.handler;
|
||||||
|
|
||||||
|
import com.example.workutils.biz.mybatis.generator.MyBatisGeneratorService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MyBatis逆向工程菜单处理器
|
||||||
|
* 用于根据数据库表结构生成Mapper文件
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class MyBatisGeneratorHandler extends AbstractMenuHandler {
|
||||||
|
|
||||||
|
private static final String[] MENU_OPTIONS = {
|
||||||
|
"1. 根据表名生成(单表)",
|
||||||
|
"2. 根据表名生成(多表,逗号分隔)",
|
||||||
|
"3. 根据表名通配符生成(如 user_%)",
|
||||||
|
"4. 查看当前配置信息",
|
||||||
|
"0. 返回上级菜单"
|
||||||
|
};
|
||||||
|
|
||||||
|
private final MyBatisGeneratorService generatorService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMenuName() {
|
||||||
|
return "MyBatis逆向工程";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getMenuOptions() {
|
||||||
|
return MENU_OPTIONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Scanner scanner) {
|
||||||
|
while (true) {
|
||||||
|
printMenuTitle(getMenuName());
|
||||||
|
displayMenu(getMenuOptions());
|
||||||
|
|
||||||
|
int choice = readChoice(scanner);
|
||||||
|
scanner.nextLine();
|
||||||
|
|
||||||
|
if (choice == 0) {
|
||||||
|
info("返回上级菜单");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch (choice) {
|
||||||
|
case 1:
|
||||||
|
generateBySingleTable(scanner);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
generateByMultipleTables(scanner);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
generateByPattern(scanner);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
showConfiguration();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
printInvalidOption();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
error("逆向工程执行失败", e);
|
||||||
|
printError(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据单表名生成
|
||||||
|
*/
|
||||||
|
private void generateBySingleTable(Scanner scanner) throws Exception {
|
||||||
|
System.out.print("请输入表名: ");
|
||||||
|
String tableName = scanner.nextLine().trim();
|
||||||
|
|
||||||
|
if (tableName.isEmpty()) {
|
||||||
|
System.out.println("表名不能为空");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
info("开始生成表 [{}] 的Mapper文件", tableName);
|
||||||
|
String result = generatorService.generateByTableNames(Arrays.asList(tableName), true);
|
||||||
|
System.out.println(result);
|
||||||
|
printSuccess("生成完成");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据多表名生成
|
||||||
|
*/
|
||||||
|
private void generateByMultipleTables(Scanner scanner) throws Exception {
|
||||||
|
System.out.print("请输入表名(多个用逗号分隔): ");
|
||||||
|
String input = scanner.nextLine().trim();
|
||||||
|
|
||||||
|
if (input.isEmpty()) {
|
||||||
|
System.out.println("表名不能为空");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> tableNames = Arrays.asList(input.split(","));
|
||||||
|
// 去除空格
|
||||||
|
for (int i = 0; i < tableNames.size(); i++) {
|
||||||
|
tableNames.set(i, tableNames.get(i).trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
info("开始生成 {} 个表的Mapper文件", tableNames.size());
|
||||||
|
String result = generatorService.generateByTableNames(tableNames, true);
|
||||||
|
System.out.println(result);
|
||||||
|
printSuccess("生成完成");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据通配符生成
|
||||||
|
*/
|
||||||
|
private void generateByPattern(Scanner scanner) throws Exception {
|
||||||
|
System.out.print("请输入表名通配符(如 user_%): ");
|
||||||
|
String pattern = scanner.nextLine().trim();
|
||||||
|
|
||||||
|
if (pattern.isEmpty()) {
|
||||||
|
System.out.println("通配符不能为空");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
info("开始根据通配符 [{}] 生成Mapper文件", pattern);
|
||||||
|
String result = generatorService.generateByPattern(pattern, true);
|
||||||
|
System.out.println(result);
|
||||||
|
printSuccess("生成完成");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示当前配置信息
|
||||||
|
*/
|
||||||
|
private void showConfiguration() {
|
||||||
|
System.out.println();
|
||||||
|
System.out.println("========== 当前配置信息 ==========");
|
||||||
|
System.out.println("数据库URL: " + maskPassword(generatorService.getDatabaseUrl()));
|
||||||
|
System.out.println("数据库用户: " + generatorService.getDatabaseUsername());
|
||||||
|
System.out.println("Model包路径: " + generatorService.getModelPackage());
|
||||||
|
System.out.println("Mapper接口包路径: " + generatorService.getMapperPackage());
|
||||||
|
System.out.println("SQL Mapper包路径: " + generatorService.getSqlMapperPackage());
|
||||||
|
System.out.println("=================================");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 遮蔽URL中的密码信息
|
||||||
|
*/
|
||||||
|
private String maskPassword(String url) {
|
||||||
|
if (url == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
// 简单处理,移除可能存在的密码参数
|
||||||
|
return url.replaceAll("password=[^&]*", "password=***");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示菜单选项
|
||||||
|
*/
|
||||||
|
private void displayMenu(String[] options) {
|
||||||
|
System.out.println("请选择操作:");
|
||||||
|
for (String option : options) {
|
||||||
|
System.out.println(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取用户选择
|
||||||
|
*/
|
||||||
|
private int readChoice(Scanner scanner) {
|
||||||
|
try {
|
||||||
|
System.out.print("请输入选项编号: ");
|
||||||
|
return scanner.nextInt();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user