♻️ 拆分生成逻辑和业务

This commit is contained in:
Bunny 2025-07-02 15:54:48 +08:00
parent d6f783c481
commit 68f9cda260
17 changed files with 467 additions and 417 deletions

View File

@ -0,0 +1,61 @@
package cn.bunny.controller;
import cn.bunny.domain.dto.VmsArgumentDto;
import cn.bunny.domain.result.Result;
import cn.bunny.domain.vo.GeneratorVo;
import cn.bunny.service.GeneratorService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.apache.logging.log4j.util.Strings;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
/**
* 代码生成器控制器
* 提供代码生成和打包下载功能
*/
@Tag(name = "生成模板", description = "生成模板接口")
@RestController
@RequestMapping("/api/generator")
@RequiredArgsConstructor
public class GeneratorController {
private final GeneratorService generatorService;
/**
* 生成代码
*
* @param dto 生成参数DTO
* @return 生成的代码结果按表名分组
*/
@Operation(summary = "生成代码", description = "根据SQL或数据库表生成代码")
@PostMapping
public Result<Map<String, List<GeneratorVo>>> generator(@Valid @RequestBody VmsArgumentDto dto) {
Map<String, List<GeneratorVo>> result = Strings.isEmpty(dto.getSql())
? generatorService.generateCodeByDatabase(dto)
: generatorService.generateCodeBySql(dto);
return Result.success(result);
}
/**
* 打包代码为ZIP下载
*
* @param dto 生成参数DTO
* @return ZIP文件响应实体
*/
@Operation(summary = "打包下载", description = "将生成的代码打包为ZIP文件下载")
@PostMapping("downloadByZip")
public ResponseEntity<byte[]> downloadByZip(@Valid @RequestBody VmsArgumentDto dto) {
return Strings.isEmpty(dto.getSql())
? generatorService.downloadByZipByDatabase(dto)
: generatorService.downloadByZipBySqL(dto);
}
}

View File

@ -13,26 +13,39 @@ import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* SQL解析控制器
* 提供SQL语句解析功能提取表和列元数据
*/
@Tag(name = "解析SQL", description = "解析SQL接口")
@RestController
@RequestMapping("/api/sqlParser")
@RequiredArgsConstructor
public class SqlParserController {
private final SqlMetadataProvider sqlParserService;
private final SqlMetadataProvider sqlMetadataProvider;
@Operation(summary = "解析SQL成表信息", description = "解析SQL成表信息")
/**
* 解析SQL获取表信息
*
* @param sql SQL语句
* @return 表元数据
*/
@Operation(summary = "解析SQL表信息", description = "解析SQL语句提取表结构信息")
@PostMapping("tableInfo")
public Result<TableMetaData> tableInfo(String sql) {
TableMetaData vo = sqlParserService.getTableMetadata(sql);
return Result.success(vo);
return Result.success(sqlMetadataProvider.getTableMetadata(sql));
}
@Operation(summary = "解析SQL成列数据", description = "解析SQL成列数据")
/**
* 解析SQL获取列信息
*
* @param sql SQL语句
* @return 列元数据列表
*/
@Operation(summary = "解析SQL列数据", description = "解析SQL语句提取列结构信息")
@PostMapping("columnMetaData")
public Result<List<ColumnMetaData>> columnMetaData(String sql) {
List<ColumnMetaData> vo = sqlParserService.getColumnInfoList(sql);
return Result.success(vo);
return Result.success(sqlMetadataProvider.getColumnInfoList(sql));
}
}
}

View File

@ -1,16 +1,14 @@
package cn.bunny.controller;
import cn.bunny.domain.dto.VmsArgumentDto;
import cn.bunny.domain.result.Result;
import cn.bunny.domain.vo.GeneratorVo;
import cn.bunny.domain.vo.VmsPathVo;
import cn.bunny.service.VmsService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
@ -30,17 +28,4 @@ public class VmsController {
return Result.success(list);
}
@Operation(summary = "生成代码", description = "生成代码")
@PostMapping("generator")
public Result<Map<String, List<GeneratorVo>>> generator(@Valid @RequestBody VmsArgumentDto dto) {
Map<String, List<GeneratorVo>> list = vmsService.generator(dto);
return Result.success(list);
}
@Operation(summary = "打包成zip下载", description = "打包成zip下载")
@PostMapping("downloadByZip")
public ResponseEntity<byte[]> downloadByZip(@Valid @RequestBody VmsArgumentDto dto) {
return vmsService.downloadByZip(dto);
}
}

View File

@ -1,6 +1,5 @@
package cn.bunny.core.vms;
package cn.bunny.core;
import cn.bunny.domain.dto.VmsArgumentDto;
import cn.bunny.domain.vo.GeneratorVo;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@ -8,7 +7,6 @@ import org.springframework.stereotype.Service;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@ -20,29 +18,23 @@ import java.util.zip.ZipOutputStream;
*/
@Service
@RequiredArgsConstructor
public class VmsZipService {
private final VmsCodeGeneratorService codeGeneratorService;
public class ZipFileService {
private static final String FILE_EXTENSION = ".vm";
private static final String UTF_8 = StandardCharsets.UTF_8.name();
/**
* 将生成的代码模板打包为ZIP文件
* 创建ZIP文件
*
* @param dto 代码生成参数DTO包含表名包名等配置
* @return ZIP文件字节数组可直接用于下载
* @throws RuntimeException 当ZIP打包过程中发生IO异常时抛出
* @param generatorVoList 生成的代码列表
* @return ZIP文件字节数组
* @throws RuntimeException 打包失败时抛出
*/
public byte[] createZipFile(VmsArgumentDto dto) {
// 将二维代码生成结果扁平化为一维列表
List<GeneratorVo> generatorVoList = codeGeneratorService.generateCode(dto)
.values().stream()
.flatMap(Collection::stream)
.toList();
public byte[] createZipFile(List<GeneratorVo> generatorVoList) {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream, StandardCharsets.UTF_8)) {
// 将所有生成的文件添加到ZIP包中
generatorVoList.forEach(generatorVo -> addToZip(zipOutputStream, generatorVo));
generatorVoList.forEach(vo -> addToZip(zipOutputStream, vo));
return byteArrayOutputStream.toByteArray();
} catch (IOException e) {
throw new RuntimeException("Failed to create ZIP file", e);
@ -50,27 +42,20 @@ public class VmsZipService {
}
/**
* 将单个代码文件添加到ZIP输出流
* 添加文件到ZIP 将单个代码文件添加到ZIP输出流
*
* @param zipOutputStream ZIP文件输出流
* @param generatorVo 代码生成结果对象包含文件路径和内容
* @throws RuntimeException 当文件添加失败时抛出包含失败文件路径信息
*/
private void addToZip(ZipOutputStream zipOutputStream, GeneratorVo generatorVo) {
final String FILE_EXTENSION = ".vm";
String voPath = generatorVo.getPath();
// 标准化文件路径移除Velocity模板扩展名
String path = voPath.replace(FILE_EXTENSION, "");
try {
zipOutputStream.putNextEntry(new ZipEntry(path));
// 以UTF-8编码写入文件内容避免乱码问题
String entryPath = generatorVo.getPath().replace(FILE_EXTENSION, "");
zipOutputStream.putNextEntry(new ZipEntry(entryPath));
zipOutputStream.write(generatorVo.getCode().getBytes(StandardCharsets.UTF_8));
zipOutputStream.closeEntry();
} catch (IOException e) {
throw new RuntimeException("Failed to add file to ZIP: " + voPath, e);
throw new RuntimeException("Failed to add file to ZIP: " + generatorVo.getPath(), e);
}
}
}

View File

@ -18,7 +18,12 @@ import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.stream.Collectors;
/**
* 数据库元数据提供者
* 提供从数据库获取表结构信息的实现
*/
@Component
@RequiredArgsConstructor
public class DatabaseMetadataProvider implements IMetadataProvider {
@ -29,31 +34,108 @@ public class DatabaseMetadataProvider implements IMetadataProvider {
private String currentDatabase;
/**
* 获取表的所有主键列名
* 根据表名标识符获取单个表的元数据信息
*
* @param tableName 要查询的表名大小写敏感度取决于数据库实现
* @return TableMetaData 包含表元数据的对象包括
* - 表名
* - 表注释/备注
* - 表所属目录通常是数据库名
* - 表类型通常为"TABLE"
* @throws MetadataNotFoundException 当指定的表不存在时抛出
* @throws GeneratorCodeException 当查询过程中发生其他异常时抛出
*/
@Override
public TableMetaData getTableMetadata(String tableName) {
try (Connection connection = dataSource.getConnection()) {
DatabaseMetaData metaData = connection.getMetaData();
ResultSet tables = metaData.getTables(null, null, tableName, new String[]{"TABLE"});
if (tables.next()) {
return TableMetaData.builder()
.tableName(tableName)
.comment(tables.getString("REMARKS"))
.tableCat(tables.getString("TABLE_CAT"))
.tableType(tables.getString("TABLE_TYPE"))
.build();
}
throw new MetadataNotFoundException("Table not found: " + tableName);
} catch (SQLException e) {
throw new GeneratorCodeException("Failed to get metadata for table: " + tableName, e);
}
}
/**
* 获取指定表的所有列信息列表
*
* @param tableName 要查询的表名大小写敏感度取决于数据库实现
* @return 包含所有列元数据的列表每个列的信息封装在ColumnMetaData对象中
* @throws MetadataProviderException 当获取列元数据过程中发生异常时抛出
*/
@Override
public List<ColumnMetaData> getColumnInfoList(String tableName) {
try (Connection connection = dataSource.getConnection()) {
Set<String> primaryKeys = getPrimaryKeys(tableName);
DatabaseMetaData metaData = connection.getMetaData();
return getColumnMetaData(metaData, tableName, primaryKeys);
} catch (SQLException e) {
throw new GeneratorCodeException("Failed to get column info for table: " + tableName, e);
}
}
/**
* 获取表的所有主键列名 获取表的主键列名集合
*
* @param tableName 表名
* @return 主键列名的集合
*/
public Set<String> getPrimaryKeys(String tableName) {
// 主键的key
private Set<String> getPrimaryKeys(String tableName) {
Set<String> primaryKeys = new HashSet<>();
try (Connection connection = dataSource.getConnection()) {
DatabaseMetaData metaData = connection.getMetaData();
// 当前表的主键
ResultSet pkResultSet = metaData.getPrimaryKeys(null, null, tableName);
ResultSet pkResultSet = connection.getMetaData().getPrimaryKeys(null, null, tableName);
while (pkResultSet.next()) {
// 列字段
String columnName = pkResultSet.getString("COLUMN_NAME").toLowerCase();
primaryKeys.add(columnName);
primaryKeys.add(pkResultSet.getString("COLUMN_NAME").toLowerCase());
}
return primaryKeys;
} catch (SQLException e) {
throw new GeneratorCodeException("Get primary key error:" + e.getMessage());
throw new GeneratorCodeException("Get primary key error: " + e.getMessage());
}
return primaryKeys;
}
/**
* 获取列元数据列表
*/
private List<ColumnMetaData> getColumnMetaData(DatabaseMetaData metaData, String tableName, Set<String> primaryKeys) throws SQLException {
Map<String, ColumnMetaData> columnMap = new LinkedHashMap<>();
try (ResultSet columnsRs = metaData.getColumns(null, null, tableName, null)) {
while (columnsRs.next()) {
ColumnMetaData column = buildColumnMetaData(columnsRs, primaryKeys);
columnMap.putIfAbsent(column.getColumnName(), column);
}
}
return columnMap.values().stream().distinct().collect(Collectors.toList());
}
/**
* 构建列元数据对象
*/
private ColumnMetaData buildColumnMetaData(ResultSet columnsRs, Set<String> primaryKeys) throws SQLException {
String columnName = columnsRs.getString("COLUMN_NAME");
String typeName = columnsRs.getString("TYPE_NAME");
ColumnMetaData column = new ColumnMetaData();
column.setColumnName(columnName);
column.setLowercaseName(MysqlTypeConvertUtil.convertToCamelCase(columnName, false));
column.setUppercaseName(MysqlTypeConvertUtil.convertToCamelCase(columnName, true));
column.setJdbcType(typeName);
column.setJavaType(MysqlTypeConvertUtil.convertToJavaType(typeName));
column.setJavascriptType(StringUtils.uncapitalize(column.getJavaType()));
column.setComment(columnsRs.getString("REMARKS"));
column.setIsPrimaryKey(primaryKeys.contains(columnName));
return column;
}
/**
@ -96,67 +178,6 @@ public class DatabaseMetadataProvider implements IMetadataProvider {
}
}
/**
* 根据表名标识符获取单个表的元数据信息
*
* @param identifier 要查询的表名大小写敏感度取决于数据库实现
* @return TableMetaData 包含表元数据的对象包括
* - 表名
* - 表注释/备注
* - 表所属目录通常是数据库名
* - 表类型通常为"TABLE"
* @throws MetadataNotFoundException 当指定的表不存在时抛出
* @throws GeneratorCodeException 当查询过程中发生其他异常时抛出
*/
public TableMetaData getTableMetadata(String identifier) {
// 声明返回对象
TableMetaData tableMetaData;
// 使用try-with-resources自动管理数据库连接
try (Connection connection = dataSource.getConnection()) {
// 获取数据库元数据对象
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"});
// 检查是否找到匹配的表
if (tables.next()) {
// 获取表的注释/备注可能为null
String remarks = tables.getString("REMARKS");
// 获取表所属的目录通常对应数据库名
String tableCat = tables.getString("TABLE_CAT");
// 获取表类型正常表应为"TABLE"
String tableType = tables.getString("TABLE_TYPE");
// 使用Builder模式构建表元数据对象
tableMetaData = TableMetaData.builder()
.tableName(identifier) // 使用传入的表名标识符
.comment(remarks) // 设置表注释
.tableCat(tableCat) // 设置表所属目录
.tableType(tableType) // 设置表类型
.build();
} else {
// 如果结果集为空抛出表不存在异常
throw new MetadataNotFoundException("Table not found: " + identifier);
}
return tableMetaData;
} catch (Exception e) {
// 捕获所有异常并转换为业务异常
throw new GeneratorCodeException("Failed to get metadata for table: " + identifier + ". Error: " + e.getMessage(), e);
}
}
/**
* 批量获取指定数据库中所有表的元数据信息
* 获取指定数据库中的所有表信息
@ -211,82 +232,4 @@ public class DatabaseMetadataProvider implements IMetadataProvider {
return allTableInfo;
}
/**
* 获取指定表的所有列信息列表
*
* @param identifier 要查询的表名大小写敏感度取决于数据库实现
* @return 包含所有列元数据的列表每个列的信息封装在ColumnMetaData对象中
* @throws MetadataProviderException 当获取列元数据过程中发生异常时抛出
*/
public List<ColumnMetaData> getColumnInfoList(String identifier) {
// 使用try-with-resources确保Connection自动关闭
try (Connection connection = dataSource.getConnection()) {
// 获取数据库元数据对象
DatabaseMetaData metaData = connection.getMetaData();
// 使用LinkedHashMap保持列的顺序与数据库一致
Map<String, ColumnMetaData> map = new LinkedHashMap<>();
// 获取当前表的所有主键列名集合
Set<String> primaryKeyColumns = getPrimaryKeys(identifier);
/*
获取指定表的所有列信息
参数说明
1. null - 不限制数据库目录catalog
2. null - 不限制模式schema
3. identifier - 要查询的表名
4. null - 不限制列名模式获取所有列
*/
try (ResultSet columnsRs = metaData.getColumns(null, null, identifier, null)) {
// 遍历结果集中的所有列
while (columnsRs.next()) {
ColumnMetaData column = new ColumnMetaData();
// 获取列的基本信息
String columnName = columnsRs.getString("COLUMN_NAME"); // 列名原始字段名
String typeName = columnsRs.getString("TYPE_NAME"); // 数据库类型名称
// 设置列基本信息
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);
// 数据库类型 -> Java类型转换
String javaType = MysqlTypeConvertUtil.convertToJavaType(typeName);
column.setJavaType(javaType);
// Java类型 -> JavaScript类型转换首字母小写
column.setJavascriptType(StringUtils.uncapitalize(javaType));
// 设置列注释可能为null
column.setComment(columnsRs.getString("REMARKS"));
// 如果主键集合不为空检查当前列是否是主键
if (!primaryKeyColumns.isEmpty()) {
boolean isPrimaryKey = primaryKeyColumns.contains(columnName);
column.setIsPrimaryKey(isPrimaryKey);
}
// 将列信息存入Map避免重复使用putIfAbsent保证不覆盖已有值
map.putIfAbsent(column.getColumnName(), column);
}
}
// 将Map中的值转换为List返回
return map.values().stream().distinct().toList();
} catch (Exception e) {
// 捕获所有异常并转换为自定义异常包含表名和原始异常信息
throw new MetadataProviderException("Failed to get table metadata for: " + identifier, e);
}
}
}

View File

@ -26,18 +26,18 @@ public class SqlMetadataProvider implements IMetadataProvider {
* 解析 sql 表信息
* 先解析SQL语句解析列字段信息
*
* @param identifier 表名称或sql
* @param sqlStatement sql语句
* @return 表西悉尼
* @see CCJSqlParserUtil 使用这个工具进行SQL的解析
*/
@Override
public TableMetaData getTableMetadata(String identifier) {
public TableMetaData getTableMetadata(String sqlStatement) {
TableMetaData tableInfo = new TableMetaData();
// 解析sql
Statement statement;
try {
statement = CCJSqlParserUtil.parse(identifier);
statement = CCJSqlParserUtil.parse(sqlStatement);
} catch (JSQLParserException e) {
throw new GeneratorCodeException("SQL解析失败");
}
@ -62,15 +62,15 @@ public class SqlMetadataProvider implements IMetadataProvider {
/**
* 获取当前表的列属性
*
* @param identifier 表名称或sql
* @param sqlStatement sql语句
* @return 当前表所有的列内容
*/
@Override
public List<ColumnMetaData> getColumnInfoList(String identifier) {
public List<ColumnMetaData> getColumnInfoList(String sqlStatement) {
// 解析sql
Statement statement;
try {
statement = CCJSqlParserUtil.parse(identifier);
statement = CCJSqlParserUtil.parse(sqlStatement);
} catch (JSQLParserException e) {
throw new SqlParseException("Fail parse sql", e.getCause());
}
@ -101,7 +101,7 @@ public class SqlMetadataProvider implements IMetadataProvider {
// 列字段转成 下划线 -> 小驼峰
String lowercaseName = MysqlTypeConvertUtil.convertToCamelCase(column.getColumnName(), false);
columnInfo.setLowercaseName(lowercaseName);
// 列字段转成 下划线 -> 大驼峰名称
String uppercaseName = MysqlTypeConvertUtil.convertToCamelCase(column.getColumnName(), true);
columnInfo.setUppercaseName(uppercaseName);

View File

@ -9,24 +9,11 @@ import java.util.List;
import java.util.stream.Collectors;
/**
* 模板方法模式
* 如果需要继承 AbstractVmsGenerator
* 模板生成抽象基类
* 定义代码生成的模板方法流程
*/
public abstract class AbstractTemplateGenerator {
/**
* 添加生成内容
*/
protected abstract void addContext(VelocityContext context);
/**
* Velocity 生成模板
*
* @param context VelocityContext
* @param writer StringWriter 写入
*/
protected abstract void templateMerge(VelocityContext context, StringWriter writer);
/**
* 生成代码模板
*
@ -44,7 +31,6 @@ public abstract class AbstractTemplateGenerator {
* 准备Velocity上下文数据
*/
private void prepareVelocityContext(VelocityContext context, TableMetaData tableMeta, List<ColumnMetaData> columns) {
// 特殊字符处理
context.put("leftBrace", "{");
context.put("tableName", tableMeta.getTableName());
context.put("columnInfoList", columns);
@ -71,4 +57,13 @@ public abstract class AbstractTemplateGenerator {
return writer;
}
/**
* 添加生成内容由子类实现
*/
protected abstract void addContext(VelocityContext context);
/**
* 模板合并由子类实现
*/
protected abstract void templateMerge(VelocityContext context, StringWriter writer);
}

View File

@ -1,89 +0,0 @@
package cn.bunny.core.vms;
import cn.bunny.core.provider.DatabaseMetadataProvider;
import cn.bunny.core.provider.SqlMetadataProvider;
import cn.bunny.core.template.VmsTBaseTemplateGenerator;
import cn.bunny.domain.dto.VmsArgumentDto;
import cn.bunny.domain.entity.ColumnMetaData;
import cn.bunny.domain.entity.TableMetaData;
import cn.bunny.domain.vo.GeneratorVo;
import cn.bunny.utils.VmsUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 代码生成服务负责根据数据库表结构生成各种代码模板
*/
@Service
@RequiredArgsConstructor
public class VmsCodeGeneratorService {
private final DatabaseMetadataProvider databaseMetadataProvider;
private final SqlMetadataProvider sqlMetadataProvider;
/**
* 根据参数生成代码模板
*
* @param dto 包含表名路径SQL等生成参数
* @return 按表名分组的代码模板列表
*/
public Map<String, List<GeneratorVo>> generateCode(VmsArgumentDto dto) {
return dto.getTableNames().parallelStream() // 并行处理多表
.flatMap(tableName -> generateTemplatesForTable(dto, tableName))
.collect(Collectors.groupingBy(GeneratorVo::getTableName));
}
/**
* 为单个表生成所有路径的模板
*/
private Stream<GeneratorVo> generateTemplatesForTable(VmsArgumentDto dto, String tableName) {
TableMetaData tableMeta = getTableMetadata(dto, tableName);
List<ColumnMetaData> columns = getColumnInfoList(dto, tableName);
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()
.id(UUID.randomUUID().toString())
.code(code)
.comment(tableMeta.getComment())
.tableName(tableMeta.getTableName())
.path(processedPath)
.build();
}
/**
* 获取表元数据根据是否有SQL选择不同数据源
*/
private TableMetaData getTableMetadata(VmsArgumentDto dto, String tableName) {
return StringUtils.hasText(dto.getSql())
? sqlMetadataProvider.getTableMetadata(dto.getSql())
: databaseMetadataProvider.getTableMetadata(tableName);
}
/**
* 获取列信息根据是否有SQL选择不同数据源
*/
private List<ColumnMetaData> getColumnInfoList(VmsArgumentDto dto, String tableName) {
return StringUtils.hasText(dto.getSql())
? sqlMetadataProvider.getColumnInfoList(dto.getSql())
: databaseMetadataProvider.getColumnInfoList(tableName);
}
}

View File

@ -1,8 +1,8 @@
package cn.bunny.domain.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@ -21,13 +21,13 @@ public class VmsArgumentDto {
String author;
@Schema(name = "packageName", description = "包名称")
@NotBlank(message = "包名不能为空")
String packageName;
@Schema(name = "requestMapping", description = "requestMapping 名称")
String requestMapping;
@NotNull(message = "表名称不能为空")
@NotEmpty(message = "表名称不能为空")
@Schema(name = "tableNames", description = "表名列表")
private List<String> tableNames;
@Schema(name = "simpleDateFormat", description = "时间格式")
@ -37,6 +37,7 @@ public class VmsArgumentDto {
private String tablePrefixes;
@Schema(name = "path", description = "路径")
@NotEmpty(message = "表名称不能为空")
private List<String> path;
@Schema(name = "sql", description = "SQL 语句")

View File

@ -0,0 +1,9 @@
package cn.bunny.domain.group;
public interface RequestGroup {
void DataBaseGroup();
void SqlGroup();
}

View File

@ -0,0 +1,49 @@
package cn.bunny.service;
import cn.bunny.domain.dto.VmsArgumentDto;
import cn.bunny.domain.vo.GeneratorVo;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import java.util.List;
import java.util.Map;
/**
* 代码生成服务接口
* 提供基于数据库和SQL的代码生成功能
*/
public interface GeneratorService {
/**
* 根据数据库表生成代码
*
* @param dto 生成参数
* @return 生成的代码按表名分组
*/
Map<String, List<GeneratorVo>> generateCodeByDatabase(VmsArgumentDto dto);
/**
* 根据SQL语句生成代码
*
* @param dto 生成参数
* @return 生成的代码按表名分组
*/
Map<String, List<GeneratorVo>> generateCodeBySql(VmsArgumentDto dto);
/**
* 打包数据库生成的代码为ZIP下载
*
* @param dto 生成参数
* @return ZIP文件响应实体
*/
ResponseEntity<byte[]> downloadByZipByDatabase(@Valid VmsArgumentDto dto);
/**
* 打包SQL生成的代码为ZIP下载
*
* @param dto 生成参数
* @return ZIP文件响应实体
*/
ResponseEntity<byte[]> downloadByZipBySqL(@Valid VmsArgumentDto dto);
}

View File

@ -1,22 +1,11 @@
package cn.bunny.service;
import cn.bunny.domain.dto.VmsArgumentDto;
import cn.bunny.domain.vo.GeneratorVo;
import cn.bunny.domain.vo.VmsPathVo;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import java.util.List;
import java.util.Map;
public interface VmsService {
/**
* 生成服务端代码
*
* @param dto VmsArgumentDto
* @return 生成内容
*/
Map<String, List<GeneratorVo>> generator(VmsArgumentDto dto);
/**
* 获取vms文件路径
@ -25,13 +14,4 @@ public interface VmsService {
*/
Map<String, List<VmsPathVo>> vmsResourcePathList();
/**
* 打包成zip下载
*
* @param dto VmsArgumentDto
* @return zip 文件
*/
ResponseEntity<byte[]> downloadByZip(@Valid VmsArgumentDto dto);
}

View File

@ -0,0 +1,70 @@
package cn.bunny.service.helper;
import cn.bunny.core.template.VmsTBaseTemplateGenerator;
import cn.bunny.domain.dto.VmsArgumentDto;
import cn.bunny.domain.entity.ColumnMetaData;
import cn.bunny.domain.entity.TableMetaData;
import cn.bunny.domain.vo.GeneratorVo;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import java.util.List;
import java.util.UUID;
import java.util.stream.Stream;
/**
* 代码生成服务辅助类
* 提供生成器和响应实体的构建方法
*/
public class GeneratorServiceImplHelper {
private static final String ZIP_FILE_PREFIX = "code-";
private static final String ZIP_FILE_SUFFIX = ".zip";
/**
* 获取生成器流
*
* @param dto 生成参数
* @param tableMeta 表元数据
* @param columns 列信息
* @return 生成器流
*/
public static Stream<GeneratorVo> getGeneratorStream(VmsArgumentDto dto, TableMetaData tableMeta, List<ColumnMetaData> columns) {
return dto.getPath().parallelStream().map(path -> {
VmsTBaseTemplateGenerator generator = new VmsTBaseTemplateGenerator(dto, path, tableMeta);
String code = generator.generateCode(tableMeta, columns).toString();
return GeneratorVo.builder()
.id(UUID.randomUUID().toString())
.code(code)
.comment(tableMeta.getComment())
.tableName(tableMeta.getTableName())
.path(VmsGeneratorPathHelper.processVmPath(dto, path, tableMeta.getTableName()))
.build();
});
}
/**
* 构建ZIP下载响应实体
*
* @param zipBytes ZIP文件字节数组
* @return 响应实体
*/
public static ResponseEntity<byte[]> getResponseEntity(byte[] zipBytes) {
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition", "attachment; filename=" + generateZipFilename());
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
headers.add("Pragma", "no-cache");
headers.add("Expires", "0");
return new ResponseEntity<>(zipBytes, headers, HttpStatus.OK);
}
/**
* 生成ZIP文件名
*/
private static String generateZipFilename() {
return ZIP_FILE_PREFIX + UUID.randomUUID().toString().split("-")[0] + ZIP_FILE_SUFFIX;
}
}

View File

@ -1,6 +1,7 @@
package cn.bunny.utils;
package cn.bunny.service.helper;
import cn.bunny.domain.dto.VmsArgumentDto;
import cn.bunny.utils.MysqlTypeConvertUtil;
import com.google.common.base.CaseFormat;
import java.util.Map;
@ -8,7 +9,7 @@ import java.util.Map;
/**
* 代码生成工具类
*/
public class VmsUtil {
public class VmsGeneratorPathHelper {
private static final Map<String, String> FILE_TYPE_SUFFIXES = Map.of(
"controller", "Controller",
"service", "Service",

View File

@ -0,0 +1,86 @@
package cn.bunny.service.impl;
import cn.bunny.core.ZipFileService;
import cn.bunny.core.provider.IMetadataProvider;
import cn.bunny.domain.dto.VmsArgumentDto;
import cn.bunny.domain.entity.ColumnMetaData;
import cn.bunny.domain.entity.TableMetaData;
import cn.bunny.domain.vo.GeneratorVo;
import cn.bunny.service.GeneratorService;
import cn.bunny.service.helper.GeneratorServiceImplHelper;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 代码生成服务实现类
* 实现基于数据库和SQL的代码生成逻辑
*/
@Service
@RequiredArgsConstructor
public class GeneratorServiceImpl implements GeneratorService {
private final IMetadataProvider databaseMetadataProvider;
private final IMetadataProvider sqlMetadataProvider;
private final ZipFileService zipFileService;
@Override
public Map<String, List<GeneratorVo>> generateCodeByDatabase(VmsArgumentDto dto) {
return generateCode(dto, databaseMetadataProvider);
}
@Override
public Map<String, List<GeneratorVo>> generateCodeBySql(VmsArgumentDto dto) {
return generateCode(dto, sqlMetadataProvider);
}
@Override
public ResponseEntity<byte[]> downloadByZipByDatabase(VmsArgumentDto dto) {
return downloadByZip(dto, this::generateCodeByDatabase);
}
@Override
public ResponseEntity<byte[]> downloadByZipBySqL(VmsArgumentDto dto) {
return downloadByZip(dto, this::generateCodeBySql);
}
/**
* 通用代码生成方法
*
* @param dto 生成参数
* @param provider 元数据提供者
* @return 生成的代码按表名分组
*/
private Map<String, List<GeneratorVo>> generateCode(VmsArgumentDto dto, IMetadataProvider provider) {
return dto.getTableNames().parallelStream()
.flatMap(tableName -> {
TableMetaData tableMeta = provider.getTableMetadata(tableName);
List<ColumnMetaData> columns = provider.getColumnInfoList(tableName);
return GeneratorServiceImplHelper.getGeneratorStream(dto, tableMeta, columns);
})
.collect(Collectors.groupingBy(GeneratorVo::getTableName));
}
/**
* 通用ZIP打包下载方法
*
* @param dto 生成参数
* @param generator 代码生成函数
* @return ZIP文件响应实体
*/
private ResponseEntity<byte[]> downloadByZip(VmsArgumentDto dto, Function<VmsArgumentDto, Map<String, List<GeneratorVo>>> generator) {
List<GeneratorVo> generatorVoList = generator.apply(dto)
.values().stream()
.flatMap(Collection::stream)
.toList();
byte[] zipBytes = zipFileService.createZipFile(generatorVoList);
return GeneratorServiceImplHelper.getResponseEntity(zipBytes);
}
}

View File

@ -33,7 +33,8 @@ public class TableServiceImpl implements TableService {
TableMetaData tableInfoVo = tableInfoVos.get(0);
tableInfoVo.setTableName(null);
return tableInfoVo;
}).toList();
})
.toList();
DatabaseInfoMetaData databaseInfoMetaData = databaseMetadataProvider.databaseInfoMetaData();
databaseInfoMetaData.setDatabaseList(databaseList);

View File

@ -1,16 +1,9 @@
package cn.bunny.service.impl;
import cn.bunny.core.vms.VmsCodeGeneratorService;
import cn.bunny.core.vms.VmsZipService;
import cn.bunny.domain.dto.VmsArgumentDto;
import cn.bunny.domain.vo.GeneratorVo;
import cn.bunny.domain.vo.VmsPathVo;
import cn.bunny.service.VmsService;
import cn.bunny.utils.ResourceFileUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import java.util.List;
@ -25,81 +18,48 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor
public class VmsServiceImpl implements VmsService {
private final VmsCodeGeneratorService codeGeneratorService;
private final VmsZipService zipService;
@Override
public Map<String, List<GeneratorVo>> generator(VmsArgumentDto dto) {
return codeGeneratorService.generateCode(dto);
}
/**
* 获取VMS资源文件路径列表并按类型分组
*
* @return 按类型分组的VMS路径Mapkey为类型value为对应类型的VMS路径列表
* @throws RuntimeException 当获取资源路径失败时抛出
*/
@Override
public Map<String, List<VmsPathVo>> vmsResourcePathList() {
try {
// 1. 获取vms目录下所有相对路径文件列表
List<String> vmsRelativeFiles = ResourceFileUtil.getRelativeFiles("vms");
// 2. 处理文件路径并分组
return vmsRelativeFiles.stream()
.map(this::convertToVmsPathVo) // 转换为VO对象
// 2. 处理文件路径并分组将文件路径字符串转换为VmsPathVo对象
return vmsRelativeFiles.parallelStream()
.map(vmFile -> {
// 分割文件路径
String[] filepathList = vmFile.split("/");
// 获取文件名不含扩展名
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();
})
// 转换为VO对象
.collect(Collectors.groupingBy(VmsPathVo::getType)); // 按类型分组
} catch (Exception e) {
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("/");
// 获取文件名不含扩展名
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
public ResponseEntity<byte[]> downloadByZip(VmsArgumentDto dto) {
// 创建ZIP文件
byte[] zipBytes = zipService.createZipFile(dto);
// 下载文件名称
String uuid = UUID.randomUUID().toString().split("-")[0];
String generateZipFilename = "code-" + uuid + ".zip";
// 设置响应头
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition", "attachment; filename=" + generateZipFilename);
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
headers.add("Pragma", "no-cache");
headers.add("Expires", "0");
return new ResponseEntity<>(zipBytes, headers, HttpStatus.OK);
}
}