diff --git a/src/main/java/cn/bunny/controller/GeneratorController.java b/src/main/java/cn/bunny/controller/GeneratorController.java new file mode 100644 index 0000000..273605a --- /dev/null +++ b/src/main/java/cn/bunny/controller/GeneratorController.java @@ -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>> generator(@Valid @RequestBody VmsArgumentDto dto) { + Map> 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 downloadByZip(@Valid @RequestBody VmsArgumentDto dto) { + return Strings.isEmpty(dto.getSql()) + ? generatorService.downloadByZipByDatabase(dto) + : generatorService.downloadByZipBySqL(dto); + } +} \ No newline at end of file diff --git a/src/main/java/cn/bunny/controller/SqlParserController.java b/src/main/java/cn/bunny/controller/SqlParserController.java index a8de999..bf59be5 100644 --- a/src/main/java/cn/bunny/controller/SqlParserController.java +++ b/src/main/java/cn/bunny/controller/SqlParserController.java @@ -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 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> columnMetaData(String sql) { - List vo = sqlParserService.getColumnInfoList(sql); - return Result.success(vo); + return Result.success(sqlMetadataProvider.getColumnInfoList(sql)); } - -} +} \ No newline at end of file diff --git a/src/main/java/cn/bunny/controller/VmsController.java b/src/main/java/cn/bunny/controller/VmsController.java index 6f436fd..f60f77a 100644 --- a/src/main/java/cn/bunny/controller/VmsController.java +++ b/src/main/java/cn/bunny/controller/VmsController.java @@ -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>> generator(@Valid @RequestBody VmsArgumentDto dto) { - Map> list = vmsService.generator(dto); - return Result.success(list); - } - - @Operation(summary = "打包成zip下载", description = "打包成zip下载") - @PostMapping("downloadByZip") - public ResponseEntity downloadByZip(@Valid @RequestBody VmsArgumentDto dto) { - return vmsService.downloadByZip(dto); - } - } diff --git a/src/main/java/cn/bunny/core/vms/VmsZipService.java b/src/main/java/cn/bunny/core/ZipFileService.java similarity index 51% rename from src/main/java/cn/bunny/core/vms/VmsZipService.java rename to src/main/java/cn/bunny/core/ZipFileService.java index a5444fa..8b386ba 100644 --- a/src/main/java/cn/bunny/core/vms/VmsZipService.java +++ b/src/main/java/cn/bunny/core/ZipFileService.java @@ -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 generatorVoList = codeGeneratorService.generateCode(dto) - .values().stream() - .flatMap(Collection::stream) - .toList(); - + public byte[] createZipFile(List 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); } } } \ No newline at end of file diff --git a/src/main/java/cn/bunny/core/provider/DatabaseMetadataProvider.java b/src/main/java/cn/bunny/core/provider/DatabaseMetadataProvider.java index 50cfee9..0ff067c 100644 --- a/src/main/java/cn/bunny/core/provider/DatabaseMetadataProvider.java +++ b/src/main/java/cn/bunny/core/provider/DatabaseMetadataProvider.java @@ -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 getColumnInfoList(String tableName) { + try (Connection connection = dataSource.getConnection()) { + Set 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 getPrimaryKeys(String tableName) { - // 主键的key + private Set getPrimaryKeys(String tableName) { Set 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 getColumnMetaData(DatabaseMetaData metaData, String tableName, Set primaryKeys) throws SQLException { + Map 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 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 getColumnInfoList(String identifier) { - // 使用try-with-resources确保Connection自动关闭 - try (Connection connection = dataSource.getConnection()) { - // 获取数据库元数据对象 - DatabaseMetaData metaData = connection.getMetaData(); - - // 使用LinkedHashMap保持列的顺序与数据库一致 - Map map = new LinkedHashMap<>(); - - // 获取当前表的所有主键列名集合 - Set 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); - } - } } diff --git a/src/main/java/cn/bunny/core/provider/SqlMetadataProvider.java b/src/main/java/cn/bunny/core/provider/SqlMetadataProvider.java index dd7ea82..2ae9c2e 100644 --- a/src/main/java/cn/bunny/core/provider/SqlMetadataProvider.java +++ b/src/main/java/cn/bunny/core/provider/SqlMetadataProvider.java @@ -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 getColumnInfoList(String identifier) { + public List 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); diff --git a/src/main/java/cn/bunny/core/template/AbstractTemplateGenerator.java b/src/main/java/cn/bunny/core/template/AbstractTemplateGenerator.java index bc65880..3661670 100644 --- a/src/main/java/cn/bunny/core/template/AbstractTemplateGenerator.java +++ b/src/main/java/cn/bunny/core/template/AbstractTemplateGenerator.java @@ -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 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); } diff --git a/src/main/java/cn/bunny/core/vms/VmsCodeGeneratorService.java b/src/main/java/cn/bunny/core/vms/VmsCodeGeneratorService.java deleted file mode 100644 index 9f4f616..0000000 --- a/src/main/java/cn/bunny/core/vms/VmsCodeGeneratorService.java +++ /dev/null @@ -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> generateCode(VmsArgumentDto dto) { - return dto.getTableNames().parallelStream() // 并行处理多表 - .flatMap(tableName -> generateTemplatesForTable(dto, tableName)) - .collect(Collectors.groupingBy(GeneratorVo::getTableName)); - } - - /** - * 为单个表生成所有路径的模板 - */ - private Stream generateTemplatesForTable(VmsArgumentDto dto, String tableName) { - TableMetaData tableMeta = getTableMetadata(dto, tableName); - List 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 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 getColumnInfoList(VmsArgumentDto dto, String tableName) { - return StringUtils.hasText(dto.getSql()) - ? sqlMetadataProvider.getColumnInfoList(dto.getSql()) - : databaseMetadataProvider.getColumnInfoList(tableName); - } - -} \ No newline at end of file diff --git a/src/main/java/cn/bunny/domain/dto/VmsArgumentDto.java b/src/main/java/cn/bunny/domain/dto/VmsArgumentDto.java index aa7f7c3..c47f9e8 100644 --- a/src/main/java/cn/bunny/domain/dto/VmsArgumentDto.java +++ b/src/main/java/cn/bunny/domain/dto/VmsArgumentDto.java @@ -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 tableNames; @Schema(name = "simpleDateFormat", description = "时间格式") @@ -37,6 +37,7 @@ public class VmsArgumentDto { private String tablePrefixes; @Schema(name = "path", description = "路径") + @NotEmpty(message = "表名称不能为空") private List path; @Schema(name = "sql", description = "SQL 语句") diff --git a/src/main/java/cn/bunny/domain/group/RequestGroup.java b/src/main/java/cn/bunny/domain/group/RequestGroup.java new file mode 100644 index 0000000..497e902 --- /dev/null +++ b/src/main/java/cn/bunny/domain/group/RequestGroup.java @@ -0,0 +1,9 @@ +package cn.bunny.domain.group; + +public interface RequestGroup { + + void DataBaseGroup(); + + void SqlGroup(); + +} diff --git a/src/main/java/cn/bunny/service/GeneratorService.java b/src/main/java/cn/bunny/service/GeneratorService.java new file mode 100644 index 0000000..95a162f --- /dev/null +++ b/src/main/java/cn/bunny/service/GeneratorService.java @@ -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> generateCodeByDatabase(VmsArgumentDto dto); + + /** + * 根据SQL语句生成代码 + * + * @param dto 生成参数 + * @return 生成的代码,按表名分组 + */ + Map> generateCodeBySql(VmsArgumentDto dto); + + /** + * 打包数据库生成的代码为ZIP下载 + * + * @param dto 生成参数 + * @return ZIP文件响应实体 + */ + ResponseEntity downloadByZipByDatabase(@Valid VmsArgumentDto dto); + + /** + * 打包SQL生成的代码为ZIP下载 + * + * @param dto 生成参数 + * @return ZIP文件响应实体 + */ + ResponseEntity downloadByZipBySqL(@Valid VmsArgumentDto dto); + +} diff --git a/src/main/java/cn/bunny/service/VmsService.java b/src/main/java/cn/bunny/service/VmsService.java index 0a4a5b9..545231c 100644 --- a/src/main/java/cn/bunny/service/VmsService.java +++ b/src/main/java/cn/bunny/service/VmsService.java @@ -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> generator(VmsArgumentDto dto); /** * 获取vms文件路径 @@ -25,13 +14,4 @@ public interface VmsService { */ Map> vmsResourcePathList(); - /** - * 打包成zip下载 - * - * @param dto VmsArgumentDto - * @return zip 文件 - */ - ResponseEntity downloadByZip(@Valid VmsArgumentDto dto); - - } diff --git a/src/main/java/cn/bunny/service/helper/GeneratorServiceImplHelper.java b/src/main/java/cn/bunny/service/helper/GeneratorServiceImplHelper.java new file mode 100644 index 0000000..68bfaaa --- /dev/null +++ b/src/main/java/cn/bunny/service/helper/GeneratorServiceImplHelper.java @@ -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 getGeneratorStream(VmsArgumentDto dto, TableMetaData tableMeta, List 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 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; + } +} \ No newline at end of file diff --git a/src/main/java/cn/bunny/utils/VmsUtil.java b/src/main/java/cn/bunny/service/helper/VmsGeneratorPathHelper.java similarity index 95% rename from src/main/java/cn/bunny/utils/VmsUtil.java rename to src/main/java/cn/bunny/service/helper/VmsGeneratorPathHelper.java index c04f1b5..4354019 100644 --- a/src/main/java/cn/bunny/utils/VmsUtil.java +++ b/src/main/java/cn/bunny/service/helper/VmsGeneratorPathHelper.java @@ -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 FILE_TYPE_SUFFIXES = Map.of( "controller", "Controller", "service", "Service", diff --git a/src/main/java/cn/bunny/service/impl/GeneratorServiceImpl.java b/src/main/java/cn/bunny/service/impl/GeneratorServiceImpl.java new file mode 100644 index 0000000..d11e796 --- /dev/null +++ b/src/main/java/cn/bunny/service/impl/GeneratorServiceImpl.java @@ -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> generateCodeByDatabase(VmsArgumentDto dto) { + return generateCode(dto, databaseMetadataProvider); + } + + @Override + public Map> generateCodeBySql(VmsArgumentDto dto) { + return generateCode(dto, sqlMetadataProvider); + } + + @Override + public ResponseEntity downloadByZipByDatabase(VmsArgumentDto dto) { + return downloadByZip(dto, this::generateCodeByDatabase); + } + + @Override + public ResponseEntity downloadByZipBySqL(VmsArgumentDto dto) { + return downloadByZip(dto, this::generateCodeBySql); + } + + /** + * 通用代码生成方法 + * + * @param dto 生成参数 + * @param provider 元数据提供者 + * @return 生成的代码,按表名分组 + */ + private Map> generateCode(VmsArgumentDto dto, IMetadataProvider provider) { + return dto.getTableNames().parallelStream() + .flatMap(tableName -> { + TableMetaData tableMeta = provider.getTableMetadata(tableName); + List columns = provider.getColumnInfoList(tableName); + return GeneratorServiceImplHelper.getGeneratorStream(dto, tableMeta, columns); + }) + .collect(Collectors.groupingBy(GeneratorVo::getTableName)); + } + + /** + * 通用ZIP打包下载方法 + * + * @param dto 生成参数 + * @param generator 代码生成函数 + * @return ZIP文件响应实体 + */ + private ResponseEntity downloadByZip(VmsArgumentDto dto, Function>> generator) { + List generatorVoList = generator.apply(dto) + .values().stream() + .flatMap(Collection::stream) + .toList(); + + byte[] zipBytes = zipFileService.createZipFile(generatorVoList); + return GeneratorServiceImplHelper.getResponseEntity(zipBytes); + } +} diff --git a/src/main/java/cn/bunny/service/impl/TableServiceImpl.java b/src/main/java/cn/bunny/service/impl/TableServiceImpl.java index 8e3bc43..229a03c 100644 --- a/src/main/java/cn/bunny/service/impl/TableServiceImpl.java +++ b/src/main/java/cn/bunny/service/impl/TableServiceImpl.java @@ -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); diff --git a/src/main/java/cn/bunny/service/impl/VmsServiceImpl.java b/src/main/java/cn/bunny/service/impl/VmsServiceImpl.java index ddcdbf5..de77d9f 100644 --- a/src/main/java/cn/bunny/service/impl/VmsServiceImpl.java +++ b/src/main/java/cn/bunny/service/impl/VmsServiceImpl.java @@ -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> generator(VmsArgumentDto dto) { - return codeGeneratorService.generateCode(dto); - } - /** * 获取VMS资源文件路径列表并按类型分组 * * @return 按类型分组的VMS路径Map,key为类型,value为对应类型的VMS路径列表 * @throws RuntimeException 当获取资源路径失败时抛出 */ + @Override public Map> vmsResourcePathList() { try { // 1. 获取vms目录下所有相对路径文件列表 List 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 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); - } } \ No newline at end of file