♻️ 重构后端代码;添加注释l;优化可读性

This commit is contained in:
Bunny 2025-07-02 11:23:32 +08:00
parent a38cdde759
commit 9baf682f63
11 changed files with 338 additions and 224 deletions

View File

@ -7,12 +7,12 @@ import cn.bunny.exception.GeneratorCodeException;
import cn.bunny.exception.MetadataNotFoundException; import cn.bunny.exception.MetadataNotFoundException;
import cn.bunny.exception.MetadataProviderException; import cn.bunny.exception.MetadataProviderException;
import cn.bunny.utils.MysqlTypeConvertUtil; import cn.bunny.utils.MysqlTypeConvertUtil;
import com.zaxxer.hikari.HikariDataSource;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection; import java.sql.Connection;
import java.sql.DatabaseMetaData; import java.sql.DatabaseMetaData;
import java.sql.ResultSet; import java.sql.ResultSet;
@ -23,7 +23,7 @@ import java.util.*;
@RequiredArgsConstructor @RequiredArgsConstructor
public class DatabaseMetadataProvider implements IMetadataProvider { public class DatabaseMetadataProvider implements IMetadataProvider {
private final DataSource dataSource; private final HikariDataSource dataSource;
@Value("${bunny.master.database}") @Value("${bunny.master.database}")
private String currentDatabase; private String currentDatabase;
@ -57,155 +57,235 @@ public class DatabaseMetadataProvider implements IMetadataProvider {
} }
/** /**
* 数据库所有的信息 * 获取数据库的元数据信息
* *
* @return 当前连接的数据库信息属性 * @return DatabaseInfoMetaData 包含数据库基本信息的对象包括
* - 数据库产品名称和版本
* - JDBC驱动名称和版本
* - 连接URL
* - 用户名
* - 当前数据库名称
* @throws GeneratorCodeException 如果获取数据库信息时发生SQL异常
*/ */
public DatabaseInfoMetaData databaseInfoMetaData() { public DatabaseInfoMetaData databaseInfoMetaData() {
// 使用try-with-resources确保Connection自动关闭
try (Connection connection = dataSource.getConnection()) { try (Connection connection = dataSource.getConnection()) {
// 获取数据库的元数据对象
DatabaseMetaData metaData = connection.getMetaData(); DatabaseMetaData metaData = connection.getMetaData();
// 使用Builder模式构建数据库信息对象
return DatabaseInfoMetaData.builder() return DatabaseInfoMetaData.builder()
// 数据库产品名称(如MySQL, Oracle等)
.databaseProductName(metaData.getDatabaseProductName()) .databaseProductName(metaData.getDatabaseProductName())
// 数据库产品版本号
.databaseProductVersion(metaData.getDatabaseProductVersion()) .databaseProductVersion(metaData.getDatabaseProductVersion())
// JDBC驱动名称
.driverName(metaData.getDriverName()) .driverName(metaData.getDriverName())
// JDBC驱动版本
.driverVersion(metaData.getDriverVersion()) .driverVersion(metaData.getDriverVersion())
// 数据库连接URL
.url(metaData.getURL()) .url(metaData.getURL())
// 连接使用的用户名
.username(metaData.getUserName()) .username(metaData.getUserName())
// 当前使用的数据库名称(由类成员变量提供)
.currentDatabase(currentDatabase) .currentDatabase(currentDatabase)
.build(); .build();
} catch (SQLException e) { } catch (SQLException e) {
// 将SQL异常转换为自定义的业务异常
throw new GeneratorCodeException("Get database info error:" + e.getMessage()); throw new GeneratorCodeException("Get database info error:" + e.getMessage());
} }
} }
/** /**
* 解析 sql 信息 * 根据表名标识符获取单个表的元数据信息
* *
* @param identifier 表名称或sql * @param identifier 要查询的表名大小写敏感度取决于数据库实现
* @return 表西悉尼 * @return TableMetaData 包含表元数据的对象包括
* - 表名
* - 表注释/备注
* - 表所属目录通常是数据库名
* - 表类型通常为"TABLE"
* @throws MetadataNotFoundException 当指定的表不存在时抛出
* @throws GeneratorCodeException 当查询过程中发生其他异常时抛出
*/ */
@Override
public TableMetaData getTableMetadata(String identifier) { public TableMetaData getTableMetadata(String identifier) {
// 声明返回对象
TableMetaData tableMetaData; TableMetaData tableMetaData;
// 使用try-with-resources自动管理数据库连接
try (Connection connection = dataSource.getConnection()) { try (Connection connection = dataSource.getConnection()) {
// 获取数据库元数据对象
DatabaseMetaData metaData = connection.getMetaData(); DatabaseMetaData metaData = connection.getMetaData();
/*
查询指定表名的元数据信息
参数说明
1. null - 不限制数据库目录catalog
2. null - 不限制模式schema
3. identifier - 要查询的精确表名
4. new String[]{"TABLE"} - 只查询基本表类型排除视图等
*/
ResultSet tables = metaData.getTables(null, null, identifier, new String[]{"TABLE"}); ResultSet tables = metaData.getTables(null, null, identifier, new String[]{"TABLE"});
// 获取表的注释信息 // 检查是否找到匹配的表
if (tables.next()) { if (tables.next()) {
// 备注信息 // 获取表的注释/备注可能为null
String remarks = tables.getString("REMARKS"); String remarks = tables.getString("REMARKS");
// 数组名称 // 获取表所属的目录通常对应数据库名
String tableCat = tables.getString("TABLE_CAT"); String tableCat = tables.getString("TABLE_CAT");
// 通常是"TABLE" // 获取表类型正常表应为"TABLE"
String tableType = tables.getString("TABLE_TYPE"); String tableType = tables.getString("TABLE_TYPE");
// 使用Builder模式构建表元数据对象
tableMetaData = TableMetaData.builder() tableMetaData = TableMetaData.builder()
.tableName(identifier) .tableName(identifier) // 使用传入的表名标识符
.comment(remarks) .comment(remarks) // 设置表注释
.tableCat(tableCat) .tableCat(tableCat) // 设置表所属目录
.tableType(tableType) .tableType(tableType) // 设置表类型
.build(); .build();
} else { } else {
// 如果结果集为空抛出表不存在异常
throw new MetadataNotFoundException("Table not found: " + identifier); throw new MetadataNotFoundException("Table not found: " + identifier);
} }
return tableMetaData; return tableMetaData;
} catch (Exception e) { } catch (Exception e) {
throw new GeneratorCodeException(e.getMessage()); // 捕获所有异常并转换为业务异常
throw new GeneratorCodeException("Failed to get metadata for table: " + identifier + ". Error: " + e.getMessage(), e);
} }
} }
/** /**
* 获取[当前/所有]数据库表 * 批量获取指定数据库中所有表的元数据信息
* 获取指定数据库中的所有表信息
* *
* @return 所有表信息 * @param dbName 数据库名称
* @return 包含所有表元数据的列表每个表的信息封装在TableMetaData对象中
* @throws MetadataProviderException 如果获取元数据过程中发生异常
*/ */
public List<TableMetaData> getTableMetadataBatch(String dbName) { public List<TableMetaData> getTableMetadataBatch(String dbName) {
// 初始化返回结果列表
List<TableMetaData> allTableInfo = new ArrayList<>(); List<TableMetaData> allTableInfo = new ArrayList<>();
// 使用try-with-resources确保Connection资源自动关闭
try (Connection conn = dataSource.getConnection()) { try (Connection conn = dataSource.getConnection()) {
// 获取数据库的元数据对象
DatabaseMetaData metaData = conn.getMetaData(); DatabaseMetaData metaData = conn.getMetaData();
/*
参数说明
1. dbName - 数据库/目录名称null表示忽略
2. null - 模式/模式名称null表示忽略
3. "%" - 表名称模式"%"表示所有表
4. new String[]{"TABLE"} - 类型数组这里只查询普通表
*/
ResultSet tables = metaData.getTables(dbName, null, "%", new String[]{"TABLE"}); ResultSet tables = metaData.getTables(dbName, null, "%", new String[]{"TABLE"});
// 遍历查询结果集中的所有表
while (tables.next()) { while (tables.next()) {
String tableName = tables.getString("TABLE_NAME"); // 从结果集中获取表的各种属性
String remarks = tables.getString("REMARKS"); String tableName = tables.getString("TABLE_NAME"); // 表名
String tableCat = tables.getString("TABLE_CAT"); String remarks = tables.getString("REMARKS"); // 表备注/注释
String tableType = tables.getString("TABLE_TYPE"); String tableCat = tables.getString("TABLE_CAT"); // 表所属的目录(通常是数据库名)
String tableType = tables.getString("TABLE_TYPE"); // 表类型(这里应该是"TABLE")
// 使用Builder模式创建TableMetaData对象并设置属性
TableMetaData tableMetaData = TableMetaData.builder() TableMetaData tableMetaData = TableMetaData.builder()
.tableName(tableName) .tableName(tableName) // 设置表名
.comment(remarks) .comment(remarks) // 设置表注释
.tableCat(tableCat) .tableCat(tableCat) // 设置表所属目录
.tableType(tableType) .tableType(tableType) // 设置表类型
.build(); .build();
// 将表元数据对象添加到结果列表
allTableInfo.add(tableMetaData); allTableInfo.add(tableMetaData);
} }
} catch (Exception e) { } catch (Exception e) {
// 捕获任何异常并转换为自定义异常抛出
throw new MetadataProviderException("Failed to get batch table metadata", e); throw new MetadataProviderException("Failed to get batch table metadata", e);
} }
// 返回包含所有表元数据的列表
return allTableInfo; return allTableInfo;
} }
/** /**
* 获取当前表的列属性 * 获取指定表的所有列信息列表
* *
* @param identifier 表名称或sql * @param identifier 要查询的表名大小写敏感度取决于数据库实现
* @return 当前表所有的列内容 * @return 包含所有列元数据的列表每个列的信息封装在ColumnMetaData对象中
* @throws MetadataProviderException 当获取列元数据过程中发生异常时抛出
*/ */
@Override
public List<ColumnMetaData> getColumnInfoList(String identifier) { public List<ColumnMetaData> getColumnInfoList(String identifier) {
// 使用try-with-resources确保Connection自动关闭
try (Connection connection = dataSource.getConnection()) { try (Connection connection = dataSource.getConnection()) {
// 获取数据库元数据对象
DatabaseMetaData metaData = connection.getMetaData(); DatabaseMetaData metaData = connection.getMetaData();
// 使用LinkedHashMap保持列的顺序与数据库一致
Map<String, ColumnMetaData> map = new LinkedHashMap<>(); Map<String, ColumnMetaData> map = new LinkedHashMap<>();
// 当前表的主键
// 获取当前表的所有主键列名集合
Set<String> primaryKeyColumns = getPrimaryKeys(identifier); Set<String> primaryKeyColumns = getPrimaryKeys(identifier);
// 当前表的列信息 /*
获取指定表的所有列信息
参数说明
1. null - 不限制数据库目录catalog
2. null - 不限制模式schema
3. identifier - 要查询的表名
4. null - 不限制列名模式获取所有列
*/
try (ResultSet columnsRs = metaData.getColumns(null, null, identifier, null)) { try (ResultSet columnsRs = metaData.getColumns(null, null, identifier, null)) {
// 遍历结果集中的所有列
while (columnsRs.next()) { while (columnsRs.next()) {
ColumnMetaData column = new ColumnMetaData(); ColumnMetaData column = new ColumnMetaData();
// 列字段
String columnName = columnsRs.getString("COLUMN_NAME");
// 数据库类型
String typeName = columnsRs.getString("TYPE_NAME");
// 设置列字段 // 获取列的基本信息
column.setColumnName(columnName); String columnName = columnsRs.getString("COLUMN_NAME"); // 列名原始字段名
// 列字段转成 下划线 -> 小驼峰 String typeName = columnsRs.getString("TYPE_NAME"); // 数据库类型名称
column.setLowercaseName(MysqlTypeConvertUtil.convertToCamelCase(column.getColumnName()));
// 列字段转成 下划线 -> 大驼峰名称 // 设置列基本信息
column.setUppercaseName(MysqlTypeConvertUtil.convertToCamelCase(column.getColumnName(), true)); column.setColumnName(columnName); // 设置原始列名
// 字段类型
// 列名格式转换下划线命名 -> 小驼峰命名首字母小写
String lowercaseName = MysqlTypeConvertUtil.convertToCamelCase(column.getColumnName(), false);
column.setLowercaseName(lowercaseName);
// 列名格式转换下划线命名 -> 大驼峰命名首字母大写
String uppercaseName = MysqlTypeConvertUtil.convertToCamelCase(column.getColumnName(), true);
column.setUppercaseName(uppercaseName);
// 设置数据库类型
column.setJdbcType(typeName); column.setJdbcType(typeName);
// 字段类型转 Java 类型
// 数据库类型 -> Java类型转换
String javaType = MysqlTypeConvertUtil.convertToJavaType(typeName); String javaType = MysqlTypeConvertUtil.convertToJavaType(typeName);
column.setJavaType(javaType); column.setJavaType(javaType);
// 字段类型转 JavaScript 类型
// Java类型 -> JavaScript类型转换首字母小写
column.setJavascriptType(StringUtils.uncapitalize(javaType)); column.setJavascriptType(StringUtils.uncapitalize(javaType));
// 备注信息
// 设置列注释可能为null
column.setComment(columnsRs.getString("REMARKS")); column.setComment(columnsRs.getString("REMARKS"));
// 确保 primaryKeyColumns 不为空 // 如果主键集合不为空检查当前列是否是主键
if (!primaryKeyColumns.isEmpty()) { if (!primaryKeyColumns.isEmpty()) {
// 是否是主键
boolean isPrimaryKey = primaryKeyColumns.contains(columnName); boolean isPrimaryKey = primaryKeyColumns.contains(columnName);
column.setIsPrimaryKey(isPrimaryKey); column.setIsPrimaryKey(isPrimaryKey);
} }
// 将列信息存入Map避免重复使用putIfAbsent保证不覆盖已有值
map.putIfAbsent(column.getColumnName(), column); map.putIfAbsent(column.getColumnName(), column);
} }
} }
return new ArrayList<>(map.values()); // 将Map中的值转换为List返回
return map.values().stream().distinct().toList();
} catch (Exception e) { } catch (Exception e) {
// 捕获所有异常并转换为自定义异常包含表名和原始异常信息
throw new MetadataProviderException("Failed to get table metadata for: " + identifier, e); throw new MetadataProviderException("Failed to get table metadata for: " + identifier, e);
} }
} }

View File

@ -99,9 +99,12 @@ public class SqlMetadataProvider implements IMetadataProvider {
columnInfo.setJavascriptType(StringUtils.uncapitalize(javaType)); columnInfo.setJavascriptType(StringUtils.uncapitalize(javaType));
// 列字段转成 下划线 -> 小驼峰 // 列字段转成 下划线 -> 小驼峰
columnInfo.setLowercaseName(MysqlTypeConvertUtil.convertToCamelCase(column.getColumnName())); String lowercaseName = MysqlTypeConvertUtil.convertToCamelCase(column.getColumnName(), false);
columnInfo.setLowercaseName(lowercaseName);
// 列字段转成 下划线 -> 大驼峰名称 // 列字段转成 下划线 -> 大驼峰名称
columnInfo.setUppercaseName(MysqlTypeConvertUtil.convertToCamelCase(column.getColumnName(), true)); String uppercaseName = MysqlTypeConvertUtil.convertToCamelCase(column.getColumnName(), true);
columnInfo.setUppercaseName(uppercaseName);
// 解析注释 // 解析注释
List<String> columnSpecs = column.getColumnSpecs(); List<String> columnSpecs = column.getColumnSpecs();

View File

@ -6,6 +6,7 @@ import org.apache.velocity.VelocityContext;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
/** /**
* 模板方法模式 * 模板方法模式
@ -16,7 +17,7 @@ public abstract class AbstractTemplateGenerator {
/** /**
* 添加生成内容 * 添加生成内容
*/ */
abstract void addContext(VelocityContext context); protected abstract void addContext(VelocityContext context);
/** /**
* Velocity 生成模板 * Velocity 生成模板
@ -24,39 +25,50 @@ public abstract class AbstractTemplateGenerator {
* @param context VelocityContext * @param context VelocityContext
* @param writer StringWriter 写入 * @param writer StringWriter 写入
*/ */
abstract void templateMerge(VelocityContext context, StringWriter writer); protected abstract void templateMerge(VelocityContext context, StringWriter writer);
/** /**
* 生成模板 * 生成代码模板
* *
* @param tableMetaData 表属性 * @param tableMeta 表元数据
* @param columnInfoList 列属性数组 * @param columns 列信息列表
* @return StringWriter * @return 生成的代码内容
*/ */
public final StringWriter generatorCodeTemplate(TableMetaData tableMetaData, List<ColumnMetaData> columnInfoList) { public StringWriter generateCode(TableMetaData tableMeta, List<ColumnMetaData> columns) {
VelocityContext context = new VelocityContext(); VelocityContext context = new VelocityContext();
prepareVelocityContext(context, tableMeta, columns);
return mergeTemplate(context);
}
// 添加要生成的属性 /**
StringWriter writer = new StringWriter(); * 准备Velocity上下文数据
List<String> list = columnInfoList.stream().map(ColumnMetaData::getColumnName).distinct().toList(); */
private void prepareVelocityContext(VelocityContext context, TableMetaData tableMeta, List<ColumnMetaData> columns) {
// vm 不能直接写 `{` 需要转换下 // 特殊字符处理
context.put("leftBrace", "{"); context.put("leftBrace", "{");
context.put("tableName", tableMeta.getTableName());
context.put("columnInfoList", columns);
context.put("baseColumnList", getDistinctColumnNames(columns));
addContext(context); // 子类可扩展
}
// 当前的表名 /**
context.put("tableName", tableMetaData.getTableName()); * 获取去重的列名列表
*/
// 当前表的列信息 private String getDistinctColumnNames(List<ColumnMetaData> columns) {
context.put("columnInfoList", columnInfoList); return columns.stream()
.map(ColumnMetaData::getColumnName)
// 数据库sql列 .distinct()
context.put("baseColumnList", String.join(",", list)); .collect(Collectors.joining(","));
}
// 添加需要生成的内容
addContext(context);
/**
* 合并Velocity模板
*/
private StringWriter mergeTemplate(VelocityContext context) {
StringWriter writer = new StringWriter();
templateMerge(context, writer); templateMerge(context, writer);
return writer; return writer;
} }
} }

View File

@ -38,7 +38,7 @@ public class VmsTBaseTemplateGenerator extends AbstractTemplateGenerator {
* @param context VelocityContext * @param context VelocityContext
*/ */
@Override @Override
void addContext(VelocityContext context) { public void addContext(VelocityContext context) {
// 当前的表名 // 当前的表名
String tableName = tableMetaData.getTableName(); String tableName = tableMetaData.getTableName();
// 表的注释内容 // 表的注释内容
@ -61,12 +61,12 @@ public class VmsTBaseTemplateGenerator extends AbstractTemplateGenerator {
context.put("package", dto.getPackageName()); context.put("package", dto.getPackageName());
// 将类名称转成小驼峰 // 将类名称转成小驼峰
String toCamelCase = MysqlTypeConvertUtil.convertToCamelCase(tableName); String lowerCamelCase = MysqlTypeConvertUtil.convertToCamelCase(tableName, false);
context.put("classLowercaseName", toCamelCase); context.put("classLowercaseName", lowerCamelCase);
// 将类名称转成大驼峰 // 将类名称转成大驼峰
String convertToCamelCase = MysqlTypeConvertUtil.convertToCamelCase(tableName, true); String upperCameCase = MysqlTypeConvertUtil.convertToCamelCase(tableName, true);
context.put("classUppercaseName", convertToCamelCase); context.put("classUppercaseName", upperCameCase);
} }
/** /**
@ -76,7 +76,7 @@ public class VmsTBaseTemplateGenerator extends AbstractTemplateGenerator {
* @param writer StringWriter 写入 * @param writer StringWriter 写入
*/ */
@Override @Override
void templateMerge(VelocityContext context, StringWriter writer) { public void templateMerge(VelocityContext context, StringWriter writer) {
Template servicePathTemplate = Velocity.getTemplate("vms/" + path, "UTF-8"); Template servicePathTemplate = Velocity.getTemplate("vms/" + path, "UTF-8");
servicePathTemplate.merge(context, writer); servicePathTemplate.merge(context, writer);
} }

View File

@ -12,73 +12,64 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import java.io.StringWriter;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
/** /**
* 负责处理代码生成逻辑的服务类 * 代码生成服务负责根据数据库表结构生成各种代码模板
*/ */
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class VmsCodeGeneratorService { public class VmsCodeGeneratorService {
private final DatabaseMetadataProvider databaseMetadataProvider; private final DatabaseMetadataProvider databaseMetadataProvider;
private final SqlMetadataProvider sqlMetadataProvider; private final SqlMetadataProvider sqlMetadataProvider;
/** /**
* 根据DTO生成代码模板 * 根据参数生成代码模板
* *
* @param dto 包含生成参数的数据传输对象 * @param dto 包含表名路径SQL等生成参数
* @return 按表名分组的生成的代码模板列表 * @return 按表名分组的代码模板列表
*/ */
public Map<String, List<GeneratorVo>> generateCode(VmsArgumentDto dto) { public Map<String, List<GeneratorVo>> generateCode(VmsArgumentDto dto) {
// 提前获取可复用的数据 return dto.getTableNames().parallelStream() // 并行处理多表
final String sql = dto.getSql(); .flatMap(tableName -> generateTemplatesForTable(dto, tableName))
final List<String> tableNames = dto.getTableNames();
final List<String> paths = dto.getPath();
return tableNames.parallelStream() // 使用并行流提高多表处理效率
.map(tableName -> {
// 获取表元数据和列信息
TableMetaData tableMetaData = getTableMetadata(dto, tableName);
List<ColumnMetaData> columnInfoList = getColumnInfoList(sql, tableName);
// 为每个路径生成模板
return paths.stream()
.map(path -> generateTemplate(dto, path, tableMetaData, columnInfoList))
.toList();
})
.flatMap(List::stream)
.collect(Collectors.groupingBy(GeneratorVo::getTableName)); .collect(Collectors.groupingBy(GeneratorVo::getTableName));
} }
/** /**
* 生成单个模板 * 为单个表生成所有路径的模板
*/ */
private GeneratorVo generateTemplate(VmsArgumentDto dto, String path, private Stream<GeneratorVo> generateTemplatesForTable(VmsArgumentDto dto, String tableName) {
TableMetaData tableMetaData, List<ColumnMetaData> columnInfoList) { TableMetaData tableMeta = getTableMetadata(dto, tableName);
VmsTBaseTemplateGenerator generator = new VmsTBaseTemplateGenerator(dto, path, tableMetaData); List<ColumnMetaData> columns = getColumnInfoList(dto, tableName);
StringWriter writer = generator.generatorCodeTemplate(tableMetaData, columnInfoList);
String processedPath = VmsUtil.handleVmFilename(path, tableMetaData.getTableName()); return dto.getPath().stream()
.map(path -> buildTemplateVo(dto, path, tableMeta, columns));
}
/**
* 构建单个模板VO对象
*/
private GeneratorVo buildTemplateVo(VmsArgumentDto dto, String path,
TableMetaData tableMeta, List<ColumnMetaData> columns) {
VmsTBaseTemplateGenerator generator = new VmsTBaseTemplateGenerator(dto, path, tableMeta);
String code = generator.generateCode(tableMeta, columns).toString();
String processedPath = VmsUtil.processVmPath(dto, path, tableMeta.getTableName());
return GeneratorVo.builder() return GeneratorVo.builder()
.id(UUID.randomUUID().toString()) .id(UUID.randomUUID().toString())
.code(writer.toString()) .code(code)
.comment(tableMetaData.getComment()) .comment(tableMeta.getComment())
.tableName(tableMetaData.getTableName()) .tableName(tableMeta.getTableName())
.path(processedPath) .path(processedPath)
.build(); .build();
} }
/** /**
* 获取表元数据 * 获取表元数据根据是否有SQL选择不同数据源
*
* @param dto 生成参数
* @param tableName 表名
* @return 表元数据对象
*/ */
private TableMetaData getTableMetadata(VmsArgumentDto dto, String tableName) { private TableMetaData getTableMetadata(VmsArgumentDto dto, String tableName) {
return StringUtils.hasText(dto.getSql()) return StringUtils.hasText(dto.getSql())
@ -87,16 +78,12 @@ public class VmsCodeGeneratorService {
} }
/** /**
* 获取列信息列表 * 获取列信息根据是否有SQL选择不同数据源
*
* @param sql SQL语句
* @param tableName 表名
* @return 列元数据列表
*/ */
private List<ColumnMetaData> getColumnInfoList(String sql, String tableName) { private List<ColumnMetaData> getColumnInfoList(VmsArgumentDto dto, String tableName) {
return StringUtils.hasText(sql) return StringUtils.hasText(dto.getSql())
? sqlMetadataProvider.getColumnInfoList(sql) ? sqlMetadataProvider.getColumnInfoList(dto.getSql())
: databaseMetadataProvider.getColumnInfoList(tableName).stream().distinct().toList(); : databaseMetadataProvider.getColumnInfoList(tableName);
} }
} }

View File

@ -33,7 +33,8 @@ public class VmsZipService {
*/ */
public byte[] createZipFile(VmsArgumentDto dto) { public byte[] createZipFile(VmsArgumentDto dto) {
// 将二维代码生成结果扁平化为一维列表 // 将二维代码生成结果扁平化为一维列表
List<GeneratorVo> generatorVoList = codeGeneratorService.generateCode(dto).values().stream() List<GeneratorVo> generatorVoList = codeGeneratorService.generateCode(dto)
.values().stream()
.flatMap(Collection::stream) .flatMap(Collection::stream)
.toList(); .toList();
@ -56,17 +57,20 @@ public class VmsZipService {
* @throws RuntimeException 当文件添加失败时抛出包含失败文件路径信息 * @throws RuntimeException 当文件添加失败时抛出包含失败文件路径信息
*/ */
private void addToZip(ZipOutputStream zipOutputStream, GeneratorVo generatorVo) { private void addToZip(ZipOutputStream zipOutputStream, GeneratorVo generatorVo) {
try { final String FILE_EXTENSION = ".vm";
// 标准化文件路径移除Velocity模板扩展名
String path = generatorVo.getPath().replace(".vm", "");
String voPath = generatorVo.getPath();
// 标准化文件路径移除Velocity模板扩展名
String path = voPath.replace(FILE_EXTENSION, "");
try {
zipOutputStream.putNextEntry(new ZipEntry(path)); zipOutputStream.putNextEntry(new ZipEntry(path));
// 以UTF-8编码写入文件内容避免乱码问题 // 以UTF-8编码写入文件内容避免乱码问题
zipOutputStream.write(generatorVo.getCode().getBytes(StandardCharsets.UTF_8)); zipOutputStream.write(generatorVo.getCode().getBytes(StandardCharsets.UTF_8));
zipOutputStream.closeEntry(); zipOutputStream.closeEntry();
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException("Failed to add file to ZIP: " + generatorVo.getPath(), e); throw new RuntimeException("Failed to add file to ZIP: " + voPath, e);
} }
} }
} }

View File

@ -1,5 +1,6 @@
package cn.bunny.domain.entity; package cn.bunny.domain.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
@ -11,30 +12,31 @@ import java.util.List;
@Builder @Builder
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
@Schema(name = "DatabaseInfoMetaData", description = "数据库信息")
public class DatabaseInfoMetaData { public class DatabaseInfoMetaData {
/* 数据库所有的数据库 */ @Schema(name = "databaseList", description = "数据库所有的数据库")
List<TableMetaData> databaseList; List<TableMetaData> databaseList;
/* 数据库产品名称 */ @Schema(name = "databaseProductName", description = "数据库产品名称")
private String databaseProductName; private String databaseProductName;
/* 数据库产品版本 */ @Schema(name = "databaseProductVersion", description = "数据库产品版本")
private String databaseProductVersion; private String databaseProductVersion;
/* 驱动名称 */ @Schema(name = "driverName", description = "驱动名称")
private String driverName; private String driverName;
/* 数据库驱动版本 */ @Schema(name = "driverVersion", description = "数据库驱动版本")
private String driverVersion; private String driverVersion;
/* 数据链接url */ @Schema(name = "url", description = "数据链接url")
private String url; private String url;
/* 数据库用户 */ @Schema(name = "username", description = "数据库用户")
private String username; private String username;
/* 当前数据库名称 */ @Schema(name = "currentDatabase", description = "当前数据库名称")
private String currentDatabase; private String currentDatabase;
} }

View File

@ -37,4 +37,10 @@ public class GeneratorCodeException extends RuntimeException {
this.message = codeEnum.getMessage(); this.message = codeEnum.getMessage();
this.resultCodeEnum = codeEnum; this.resultCodeEnum = codeEnum;
} }
public GeneratorCodeException(String message, Exception exception) {
super(message);
this.message = message;
log.error(message, exception);
}
} }

View File

@ -7,8 +7,6 @@ import cn.bunny.domain.vo.GeneratorVo;
import cn.bunny.domain.vo.VmsPathVo; import cn.bunny.domain.vo.VmsPathVo;
import cn.bunny.service.VmsService; import cn.bunny.service.VmsService;
import cn.bunny.utils.ResourceFileUtil; import cn.bunny.utils.ResourceFileUtil;
import cn.bunny.utils.VmsUtil;
import cn.hutool.crypto.digest.MD5;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@ -17,6 +15,7 @@ import org.springframework.stereotype.Service;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -35,46 +34,64 @@ public class VmsServiceImpl implements VmsService {
} }
/** /**
* 获取vms文件路径 * 获取VMS资源文件路径列表并按类型分组
* *
* @return vms下的文件路径 * @return 按类型分组的VMS路径Mapkey为类型value为对应类型的VMS路径列表
* @throws RuntimeException 当获取资源路径失败时抛出
*/ */
@Override
public Map<String, List<VmsPathVo>> vmsResourcePathList() { public Map<String, List<VmsPathVo>> vmsResourcePathList() {
List<String> vmsRelativeFiles;
Map<String, List<VmsPathVo>> listMap;
try { try {
vmsRelativeFiles = ResourceFileUtil.getRelativeFiles("vms"); // 1. 获取vms目录下所有相对路径文件列表
listMap = vmsRelativeFiles.stream() List<String> vmsRelativeFiles = ResourceFileUtil.getRelativeFiles("vms");
.map(vmFile -> {
String[] filepathList = vmFile.split("/");
String filename = filepathList[filepathList.length - 1].replace(".vm", "");
return VmsPathVo.builder() // 2. 处理文件路径并分组
.id(VmsUtil.generateDivId()) return vmsRelativeFiles.stream()
.name(vmFile) .map(this::convertToVmsPathVo) // 转换为VO对象
.label(filename) .collect(Collectors.groupingBy(VmsPathVo::getType)); // 按类型分组
.type(filepathList[0])
.build();
})
.collect(Collectors.groupingBy(VmsPathVo::getType));
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("Get error of VMS path:" + e.getMessage()); throw new RuntimeException("Failed to get VMS resource paths: " + e.getMessage(), e);
}
} }
/**
* 将文件路径字符串转换为VmsPathVo对象
*
* @param vmFile 文件相对路径字符串
* @return 转换后的VmsPathVo对象
*/
private VmsPathVo convertToVmsPathVo(String vmFile) {
// 分割文件路径
String[] filepathList = vmFile.split("/");
return listMap; // 获取文件名不含扩展名
String filename = filepathList[filepathList.length - 1].replace(".vm", "");
/*
生成前端可用的唯一DOM元素ID
格式: "id-" + 无横线的UUID (例如: "id-550e8400e29b41d4a716446655440000")
用途:
1. 用于关联label标签和input元素的for属性
2. 确保列表项在前端有唯一标识
*/
String id = "id-" + UUID.randomUUID().toString().replace("-", "");
return VmsPathVo.builder()
.id(id)
.name(vmFile)
.label(filename)
.type(filepathList[0]) // 使用路径的第一部分作为类型
.build();
} }
@Override @Override
public ResponseEntity<byte[]> downloadByZip(VmsArgumentDto dto) { public ResponseEntity<byte[]> downloadByZip(VmsArgumentDto dto) {
// 创建ZIP文件
byte[] zipBytes = zipService.createZipFile(dto); byte[] zipBytes = zipService.createZipFile(dto);
// 下载文件名称 // 下载文件名称
long currentTimeMillis = System.currentTimeMillis(); String uuid = UUID.randomUUID().toString().split("-")[0];
String digestHex = MD5.create().digestHex(currentTimeMillis + ""); String generateZipFilename = "code-" + uuid + ".zip";
String generateZipFilename = "code-" + digestHex.substring(0, 6) + ".zip";
// 设置响应头 // 设置响应头
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();

View File

@ -29,13 +29,6 @@ public class MysqlTypeConvertUtil {
}; };
} }
/**
* 下划线命名转驼峰命名
*/
public static String convertToCamelCase(String name) {
return convertToCamelCase(name, false);
}
/** /**
* 下划线命名转驼峰命名 * 下划线命名转驼峰命名
* *
@ -51,9 +44,7 @@ public class MysqlTypeConvertUtil {
String lowerCamelCase = CaseFormatUtils.toCamelCase(name); String lowerCamelCase = CaseFormatUtils.toCamelCase(name);
// 首字母不大写 // 首字母不大写
if (!firstLetterCapital) { if (!firstLetterCapital) return lowerCamelCase;
return lowerCamelCase;
}
// 将小驼峰转成大驼峰 // 将小驼峰转成大驼峰
return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, lowerCamelCase); return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, lowerCamelCase);

View File

@ -1,13 +1,15 @@
package cn.bunny.utils; package cn.bunny.utils;
import cn.bunny.domain.dto.VmsArgumentDto;
import com.google.common.base.CaseFormat; import com.google.common.base.CaseFormat;
import java.util.Map; import java.util.Map;
import java.util.UUID;
/**
* 代码生成工具类
*/
public class VmsUtil { public class VmsUtil {
private static final Map<String, String> FILE_TYPE_SUFFIXES = Map.of(
private static final Map<String, String> TYPE_MAPPINGS = Map.of(
"controller", "Controller", "controller", "Controller",
"service", "Service", "service", "Service",
"serviceImpl", "ServiceImpl", "serviceImpl", "ServiceImpl",
@ -18,55 +20,65 @@ public class VmsUtil {
); );
/** /**
* 处理 vm 文件 * 处理模板文件路径和命
* *
* @param path 文件路径 * @param dto 生成参数
* @param className 类名 * @param path 原始模板路径
* @param tableName 数据库表名
* @return 处理后的文件路径
*/ */
public static String handleVmFilename(String path, String className) { public static String processVmPath(VmsArgumentDto dto, String path, String tableName) {
String[] splitPaths = path.split("/"); String className = removeTablePrefixes(dto, tableName);
int splitPathsSize = splitPaths.length - 1; String lowerCamelCase = MysqlTypeConvertUtil.convertToCamelCase(tableName, false);
String[] pathParts = path.replace("$className", lowerCamelCase).split("/");
// 大驼峰名称 // 处理文件名
String CamelCase = MysqlTypeConvertUtil.convertToCamelCase(className, true); pathParts[pathParts.length - 1] = processFilename(
// 小驼峰名称 pathParts[pathParts.length - 1],
String smallCamelCase = MysqlTypeConvertUtil.convertToCamelCase(className); className
);
// 当前文件名 return String.join("/", pathParts);
String filename = splitPaths[splitPathsSize];
filename = filename.replace(".vm", "");
String[] split = filename.split("\\.");
// 文件名称
String name = split[0];
// 文件扩展名
String extension = "";
if (split.length >= 2) {
extension = split[1];
}
// 判断是否是 Java 或者 xml 文件
String typeMappingsFilename = TYPE_MAPPINGS.get(name);
typeMappingsFilename = typeMappingsFilename == null ? "" : typeMappingsFilename;
if (filename.contains("java") || filename.contains("xml")) {
filename = CamelCase + typeMappingsFilename + "." + extension;
}
if ((filename.contains("vue") || filename.contains("ts") || filename.contains("js"))
&& !filename.contains("index")) {
filename = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, smallCamelCase) + "-" + name + "." + extension;
}
splitPaths[splitPathsSize] = filename;
return String.join("/", splitPaths);
} }
/** /**
* 生成前端标签上的id * 移除表前缀
*
* @return id-UUID
*/ */
public static String generateDivId() { private static String removeTablePrefixes(VmsArgumentDto dto, String tableName) {
return "id-" + UUID.randomUUID().toString().replace("-", ""); String[] prefixes = dto.getTablePrefixes().split("[,]");
for (String prefix : prefixes) {
if (tableName.startsWith(prefix)) {
return tableName.substring(prefix.length());
}
}
return tableName;
}
/**
* 处理文件名生成
*/
private static String processFilename(String filename, String tableName) {
filename = filename.replace(".vm", "");
String[] parts = filename.split("\\.");
String baseName = parts[0];
String extension = parts.length > 1 ? parts[1] : "";
String upperCamelCase = MysqlTypeConvertUtil.convertToCamelCase(tableName, true);
String lowerCamelCase = MysqlTypeConvertUtil.convertToCamelCase(tableName, false);
// 如果包含Java和xml需要进行处理
if (filename.contains("java") || filename.contains("xml")) {
return upperCamelCase + FILE_TYPE_SUFFIXES.getOrDefault(baseName, "") + "." + extension;
}
if (filename.equals("api.ts") || filename.equals("store.ts")) {
return lowerCamelCase + ".ts";
}
if (filename.equals("dialog.vue")) {
return CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, lowerCamelCase) + "-dialog.vue";
}
return filename;
} }
} }