diff --git a/generator-code/pom.xml b/generator-code/pom.xml index 09992f8..b787906 100644 --- a/generator-code/pom.xml +++ b/generator-code/pom.xml @@ -64,6 +64,11 @@ org.springframework.boot spring-boot-starter-jdbc + + com.github.jsqlparser + jsqlparser + 5.1 + @@ -74,7 +79,7 @@ cn.hutool hutool-all - + com.github.xiaoymin diff --git a/generator-code/src/main/java/cn/bunny/controller/SqlParserController.java b/generator-code/src/main/java/cn/bunny/controller/SqlParserController.java new file mode 100644 index 0000000..6441078 --- /dev/null +++ b/generator-code/src/main/java/cn/bunny/controller/SqlParserController.java @@ -0,0 +1,38 @@ +package cn.bunny.controller; + +import cn.bunny.core.SqlParserCore; +import cn.bunny.dao.entity.ColumnMetaData; +import cn.bunny.dao.result.Result; +import cn.bunny.dao.vo.TableInfoVo; +import cn.bunny.service.SqlParserService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@Tag(name = "解析SQL", description = "解析SQL接口") +@RestController +@RequestMapping("/api/sqlParser") +public class SqlParserController { + + @Resource + private SqlParserService sqlParserService; + + @Operation(summary = "解析SQL成表信息", description = "解析SQL成表信息") + @PostMapping("tableInfo") + public Result tableInfo(String sql) { + TableInfoVo vo = sqlParserService.tableInfo(sql); + return Result.success(vo); + } + + @Operation(summary = "解析SQL成列数据", description = "解析SQL成列数据") + @PostMapping("columnMetaData") + public Result> columnMetaData(String sql) { + List vo = SqlParserCore.parserColumnInfo(sql); + return Result.success(vo); + } +} diff --git a/generator-code/src/main/java/cn/bunny/core/DatabaseInfoCore.java b/generator-code/src/main/java/cn/bunny/core/DatabaseInfoCore.java index 4a11b02..58c7789 100644 --- a/generator-code/src/main/java/cn/bunny/core/DatabaseInfoCore.java +++ b/generator-code/src/main/java/cn/bunny/core/DatabaseInfoCore.java @@ -1,15 +1,20 @@ package cn.bunny.core; +import cn.bunny.dao.entity.ColumnMetaData; +import cn.bunny.dao.entity.DatabaseInfoMetaData; import cn.bunny.dao.entity.TableMetaData; import jakarta.annotation.Resource; import lombok.SneakyThrows; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import javax.sql.DataSource; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Set; /* 数据库信息内容 */ @@ -84,4 +89,108 @@ public class DatabaseInfoCore { return tableMetaData; } } + + /** + * 获取[当前/所有]数据库表 + * + * @return 所有表信息 + */ + @SneakyThrows + public List databaseTableList(String dbName) { + // 当前数据库数据库所有的表 + List allTableInfo = new ArrayList<>(); + + try (Connection connection = dataSource.getConnection()) { + DatabaseMetaData metaData = connection.getMetaData(); + + // 当前数据库中所有的表 + ResultSet tables = metaData.getTables(dbName, null, "%", new String[]{"TABLE"}); + + while (tables.next()) { + // 表名称 + dbName = tables.getString("TABLE_NAME"); + + // 设置表信息 + TableMetaData tableMetaData = tableInfoMetaData(dbName); + + allTableInfo.add(tableMetaData); + } + } + + return allTableInfo; + } + + /** + * 获取当前表的列属性 + * + * @param tableName 表名称 + * @return 当前表所有的列内容 + */ + @SneakyThrows + public List tableColumnInfo(String tableName) { + try (Connection connection = dataSource.getConnection()) { + DatabaseMetaData metaData = connection.getMetaData(); + List columns = new ArrayList<>(); + // 当前表的主键 + Set primaryKeyColumns = getPrimaryKeyColumns(tableName); + + // 当前表的列信息 + try (ResultSet columnsRs = metaData.getColumns(null, null, tableName, null)) { + while (columnsRs.next()) { + ColumnMetaData column = new ColumnMetaData(); + // 列字段 + String columnName = columnsRs.getString("COLUMN_NAME"); + // 将当前表的列类型转成 Java 类型 + String javaType = TypeConvertCore.convertToJavaType(column.getJdbcType()); + + // 设置列字段 + column.setColumnName(columnName); + // 列字段转成 下划线 -> 小驼峰 + column.setLowercaseName(TypeConvertCore.convertToCamelCase(column.getColumnName())); + // 列字段转成 下划线 -> 大驼峰名称 + column.setUppercaseName(TypeConvertCore.convertToCamelCase(column.getColumnName(), true)); + // 字段类型 + column.setJdbcType(columnsRs.getString("TYPE_NAME")); + // 字段类型转 Java 类型 + column.setJavaType(javaType); + // 字段类型转 JavaScript 类型 + column.setJavascriptType(StringUtils.uncapitalize(javaType)); + // 备注信息 + column.setComment(columnsRs.getString("REMARKS")); + + // 确保 primaryKeyColumns 不为空 + if (!primaryKeyColumns.isEmpty()) { + // 是否是主键 + boolean isPrimaryKey = primaryKeyColumns.contains(columnName); + column.setIsPrimaryKey(isPrimaryKey); + } + columns.add(column); + } + } + + columns.get(0).setIsPrimaryKey(true); + return columns; + } + } + + /** + * 数据库所有的信息 + * + * @return 当前连接的数据库信息属性 + */ + @SneakyThrows + public DatabaseInfoMetaData databaseInfoMetaData() { + try (Connection connection = dataSource.getConnection()) { + DatabaseMetaData metaData = connection.getMetaData(); + + return DatabaseInfoMetaData.builder() + .databaseProductName(metaData.getDatabaseProductName()) + .databaseProductVersion(metaData.getDatabaseProductVersion()) + .driverName(metaData.getDriverName()) + .driverVersion(metaData.getDriverVersion()) + .url(metaData.getURL()) + .username(metaData.getUserName()) + .build(); + } + } } diff --git a/generator-code/src/main/java/cn/bunny/core/SqlParserCore.java b/generator-code/src/main/java/cn/bunny/core/SqlParserCore.java new file mode 100644 index 0000000..43a914d --- /dev/null +++ b/generator-code/src/main/java/cn/bunny/core/SqlParserCore.java @@ -0,0 +1,110 @@ +package cn.bunny.core; + +import cn.bunny.dao.entity.ColumnMetaData; +import cn.bunny.dao.entity.TableMetaData; +import lombok.SneakyThrows; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.create.table.CreateTable; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SqlParserCore { + + /** + * 解析 sql 表信息 + * + * @param sql sql字符串 + * @return 表西悉尼 + */ + @SneakyThrows + public static TableMetaData parserTableInfo(String sql) { + TableMetaData tableInfo = new TableMetaData(); + + // 解析sql + Statement statement; + try { + statement = CCJSqlParserUtil.parse(sql); + } catch (JSQLParserException e) { + throw new RuntimeException("SQL解析失败"); + } + if (!(statement instanceof CreateTable createTable)) { + throw new IllegalArgumentException("缺少SQL语句"); + } + + // 设置表基本信息 + String tableName = createTable.getTable().getName().replaceAll("`", ""); + tableInfo.setTableName(tableName); + tableInfo.setTableType("TABLE"); + String tableOptionsStrings = String.join(" ", createTable.getTableOptionsStrings()); + + // 注释信息 + Pattern pattern = Pattern.compile("COMMENT\\s*=\\s*'(.*?)'", Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(tableOptionsStrings); + if (matcher.find()) { + tableInfo.setComment(matcher.group(1)); + } + + return tableInfo; + } + + /** + * 解析 sql 列信息 + * + * @param sql sql字符串 + * @return 列属性列表 + */ + @SneakyThrows + public static List parserColumnInfo(String sql) { + // 解析sql + Statement statement; + try { + statement = CCJSqlParserUtil.parse(sql); + } catch (JSQLParserException e) { + throw new RuntimeException("SQL解析失败"); + } + + if (!(statement instanceof CreateTable createTable)) { + throw new IllegalArgumentException("缺少SQL语句"); + } + + return createTable.getColumnDefinitions() + .stream().map(column -> { + // 列信息 + ColumnMetaData columnInfo = new ColumnMetaData(); + + // 列名称 + columnInfo.setColumnName(column.getColumnName()); + + // 设置 JDBC 类型 + String dataType = column.getColDataType().getDataType(); + columnInfo.setJdbcType(dataType); + + // 设置 Java 类型 + String javaType = TypeConvertCore.convertToJavaType(dataType.contains("varchar") ? "varchar" : dataType); + columnInfo.setJavaType(javaType); + + // 设置 JavaScript 类型 + columnInfo.setJavascriptType(StringUtils.uncapitalize(javaType)); + + // 列字段转成 下划线 -> 小驼峰 + columnInfo.setLowercaseName(TypeConvertCore.convertToCamelCase(column.getColumnName())); + // 列字段转成 下划线 -> 大驼峰名称 + columnInfo.setUppercaseName(TypeConvertCore.convertToCamelCase(column.getColumnName(), true)); + + // 解析注释 + List columnSpecs = column.getColumnSpecs(); + String columnSpecsString = String.join(" ", columnSpecs); + Matcher columnSpecsStringMatcher = Pattern.compile("COMMENT\\s*'(.*?)'", Pattern.CASE_INSENSITIVE).matcher(columnSpecsString); + if (columnSpecsStringMatcher.find()) { + columnInfo.setComment(columnSpecsStringMatcher.group(1)); + } + + return columnInfo; + }).toList(); + } +} diff --git a/generator-code/src/main/java/cn/bunny/core/vms/AbstractVmsGenerator.java b/generator-code/src/main/java/cn/bunny/core/vms/AbstractVmsGenerator.java new file mode 100644 index 0000000..ae67134 --- /dev/null +++ b/generator-code/src/main/java/cn/bunny/core/vms/AbstractVmsGenerator.java @@ -0,0 +1,62 @@ +package cn.bunny.core.vms; + +import cn.bunny.dao.entity.ColumnMetaData; +import cn.bunny.dao.entity.TableMetaData; +import org.apache.velocity.VelocityContext; + +import java.io.StringWriter; +import java.util.List; + +/** + * 模板方法模式 + * 如果需要继承 AbstractVmsGenerator + */ +public abstract class AbstractVmsGenerator { + + /** + * 添加生成内容 + */ + abstract void addContext(VelocityContext context); + + /** + * Velocity 生成模板 + * + * @param context + * @param writer StringWriter 写入 + */ + abstract void templateMerge(VelocityContext context, StringWriter writer); + + /** + * 生成模板 + * + * @param tableMetaData 表属性 + * @param columnInfoList 列属性数组 + * @return StringWriter + */ + public final StringWriter generatorCodeTemplate(TableMetaData tableMetaData, List columnInfoList) { + VelocityContext context = new VelocityContext(); + + // 添加要生成的属性 + StringWriter writer = new StringWriter(); + List list = columnInfoList.stream().map(ColumnMetaData::getColumnName).toList(); + + // vm 不能直接写 `{` 需要转换下 + context.put("leftBrace", "{"); + + // 当前的表名 + context.put("tableName", tableMetaData.getTableName()); + + // 当前表的列信息 + context.put("columnInfoList", columnInfoList); + + // 数据库sql列 + context.put("baseColumnList", String.join(",", list)); + + // 添加需要生成的内容 + addContext(context); + + templateMerge(context, writer); + + return writer; + } +} diff --git a/generator-code/src/main/java/cn/bunny/core/vms/VmsArgumentDtoBaseVmsGenerator.java b/generator-code/src/main/java/cn/bunny/core/vms/VmsArgumentDtoBaseVmsGenerator.java new file mode 100644 index 0000000..4165fb7 --- /dev/null +++ b/generator-code/src/main/java/cn/bunny/core/vms/VmsArgumentDtoBaseVmsGenerator.java @@ -0,0 +1,84 @@ +package cn.bunny.core.vms; + +import cn.bunny.core.TypeConvertCore; +import cn.bunny.dao.dto.VmsArgumentDto; +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.Velocity; + +import java.io.StringWriter; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * 使用模板方法,方便扩展 + * 如果需要继承 AbstractVmsGenerator + */ +public class VmsArgumentDtoBaseVmsGenerator extends AbstractVmsGenerator { + + private final VmsArgumentDto dto; + private final String path; + + /** + * @param dto 类名称可以自定义,格式为 xxx_xxx + * @param path 当前路径 + */ + public VmsArgumentDtoBaseVmsGenerator(VmsArgumentDto dto, String path) { + this.dto = dto; + this.path = path; + } + + /** + * 添加生成内容 + */ + @Override + void addContext(VelocityContext context) { + // 当前日期 + String date = new SimpleDateFormat(dto.getSimpleDateFormat()).format(new Date()); + context.put("date", date); + + // 作者名字 + context.put("author", dto.getAuthor()); + + // 每个 Controller 上的请求前缀 + context.put("requestMapping", dto.getRequestMapping()); + + // 类名称如果是小驼峰,需要 [手写] 为 [下划线] 之后由 [代码 -> 小驼峰/大驼峰] + String className = dto.getClassName(); + + // 去除表开头前缀 + String tablePrefixes = dto.getTablePrefixes(); + + // 表字段的注释内容 + context.put("comment", dto.getComment()); + + // 设置包名称 + context.put("package", dto.getPackageName()); + + // 将 表前缀 转成数组 + String replaceTableName = ""; + for (String prefix : tablePrefixes.split("[,,]")) { + replaceTableName = className.replace(prefix, ""); + } + + // 将类名称转成小驼峰 + String toCamelCase = TypeConvertCore.convertToCamelCase(replaceTableName); + context.put("classLowercaseName", toCamelCase); + + // 将类名称转成大驼峰 + String convertToCamelCase = TypeConvertCore.convertToCamelCase(replaceTableName, true); + context.put("classUppercaseName", convertToCamelCase); + } + + /** + * Velocity 生成模板 + * + * @param writer StringWriter 写入 + */ + @Override + void templateMerge(VelocityContext context, StringWriter writer) { + // Velocity 生成模板 + Template servicePathTemplate = Velocity.getTemplate("vms/" + path, "UTF-8"); + servicePathTemplate.merge(context, writer); + } +} diff --git a/generator-code/src/main/java/cn/bunny/dao/dto/VmsArgumentDto.java b/generator-code/src/main/java/cn/bunny/dao/dto/VmsArgumentDto.java index adbaf4a..d096a44 100644 --- a/generator-code/src/main/java/cn/bunny/dao/dto/VmsArgumentDto.java +++ b/generator-code/src/main/java/cn/bunny/dao/dto/VmsArgumentDto.java @@ -26,15 +26,15 @@ public class VmsArgumentDto { String requestMapping = "/api"; /* 类名称,格式为:xxx xxx_xxx */ - @NotBlank(message = "类名称不能为空" ) - @NotNull(message = "类名称不能为空" ) - @Pattern(regexp = "^(?:[a-z][a-z0-9_]*|[_a-z][a-z0-9_]*)$" , message = "类名称不合法" ) + @NotBlank(message = "类名称不能为空") + @NotNull(message = "类名称不能为空") + @Pattern(regexp = "^(?:[a-z][a-z0-9_]*|[_a-z][a-z0-9_]*)$", message = "类名称不合法") private String className; /* 表名称 */ - @NotBlank(message = "表名称不能为空" ) - @NotNull(message = "表名称不能为空" ) - @Pattern(regexp = "^(?:[a-z][a-z0-9_]*|[_a-z][a-z0-9_]*)$" , message = "表名称不合法" ) + @NotBlank(message = "表名称不能为空") + @NotNull(message = "表名称不能为空") + @Pattern(regexp = "^(?:[a-z][a-z0-9_]*|[_a-z][a-z0-9_]*)$", message = "表名称不合法") private String tableName; /* 时间格式 */ @@ -48,4 +48,8 @@ public class VmsArgumentDto { /* 路径 */ private List path; + + /* SQL 语句 */ + private String sql; } + diff --git a/generator-code/src/main/java/cn/bunny/service/SqlParserService.java b/generator-code/src/main/java/cn/bunny/service/SqlParserService.java new file mode 100644 index 0000000..ba63a2f --- /dev/null +++ b/generator-code/src/main/java/cn/bunny/service/SqlParserService.java @@ -0,0 +1,13 @@ +package cn.bunny.service; + +import cn.bunny.dao.vo.TableInfoVo; + +public interface SqlParserService { + /** + * 解析SQL内容 + * + * @param sql Sql语句 + * @return 表信息内容 + */ + TableInfoVo tableInfo(String sql); +} diff --git a/generator-code/src/main/java/cn/bunny/service/VmsService.java b/generator-code/src/main/java/cn/bunny/service/VmsService.java index 8bb1b91..12d9c1b 100644 --- a/generator-code/src/main/java/cn/bunny/service/VmsService.java +++ b/generator-code/src/main/java/cn/bunny/service/VmsService.java @@ -32,4 +32,6 @@ public interface VmsService { * @return zip 文件 */ ResponseEntity downloadByZip(@Valid VmsArgumentDto dto); + + } diff --git a/generator-code/src/main/java/cn/bunny/service/impl/SqlParserServiceImpl.java b/generator-code/src/main/java/cn/bunny/service/impl/SqlParserServiceImpl.java new file mode 100644 index 0000000..72378f4 --- /dev/null +++ b/generator-code/src/main/java/cn/bunny/service/impl/SqlParserServiceImpl.java @@ -0,0 +1,27 @@ +package cn.bunny.service.impl; + +import cn.bunny.core.SqlParserCore; +import cn.bunny.dao.entity.TableMetaData; +import cn.bunny.dao.vo.TableInfoVo; +import cn.bunny.service.SqlParserService; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; + +@Service +public class SqlParserServiceImpl implements SqlParserService { + + /** + * 解析SQL内容 + * + * @param sql Sql语句 + * @return 表信息内容 + */ + @Override + public TableInfoVo tableInfo(String sql) { + TableInfoVo tableInfoVo = new TableInfoVo(); + + TableMetaData tableMetaData = SqlParserCore.parserTableInfo(sql); + BeanUtils.copyProperties(tableMetaData, tableInfoVo); + return tableInfoVo; + } +} diff --git a/generator-code/src/main/java/cn/bunny/service/impl/TableServiceImpl.java b/generator-code/src/main/java/cn/bunny/service/impl/TableServiceImpl.java index db6781c..c34420f 100644 --- a/generator-code/src/main/java/cn/bunny/service/impl/TableServiceImpl.java +++ b/generator-code/src/main/java/cn/bunny/service/impl/TableServiceImpl.java @@ -1,7 +1,6 @@ package cn.bunny.service.impl; import cn.bunny.core.DatabaseInfoCore; -import cn.bunny.core.TypeConvertCore; import cn.bunny.dao.entity.ColumnMetaData; import cn.bunny.dao.entity.DatabaseInfoMetaData; import cn.bunny.dao.entity.TableMetaData; @@ -9,24 +8,14 @@ import cn.bunny.dao.vo.TableInfoVo; import cn.bunny.service.TableService; import jakarta.annotation.Resource; import lombok.SneakyThrows; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; -import javax.sql.DataSource; -import java.sql.Connection; -import java.sql.DatabaseMetaData; -import java.sql.ResultSet; -import java.util.ArrayList; import java.util.List; -import java.util.Set; @Service public class TableServiceImpl implements TableService { - @Resource - private DataSource dataSource; - @Resource private DatabaseInfoCore databaseInfoCore; @@ -55,26 +44,7 @@ public class TableServiceImpl implements TableService { @SneakyThrows @Override public List databaseTableList(String dbName) { - - // 当前数据库数据库所有的表 - List allTableInfo = new ArrayList<>(); - - try (Connection connection = dataSource.getConnection()) { - DatabaseMetaData metaData = connection.getMetaData(); - - // 当前数据库中所有的表 - ResultSet tables = metaData.getTables(dbName, null, "%", new String[]{"TABLE"}); - - while (tables.next()) { - // 表名称 - dbName = tables.getString("TABLE_NAME"); - - // 设置表信息 - TableMetaData tableMetaData = databaseInfoCore.tableInfoMetaData(dbName); - allTableInfo.add(tableMetaData); - } - - } + List allTableInfo = databaseInfoCore.databaseTableList(dbName); return allTableInfo.stream().map(tableMetaData -> { TableInfoVo tableInfoVo = new TableInfoVo(); @@ -93,60 +63,7 @@ public class TableServiceImpl implements TableService { @SneakyThrows @Override public List tableColumnInfo(String tableName) { - try (Connection connection = dataSource.getConnection()) { - DatabaseMetaData metaData = connection.getMetaData(); - - List columns = new ArrayList<>(); - - // 当前表的主键 - Set primaryKeyColumns = databaseInfoCore.getPrimaryKeyColumns(tableName); - - // 当前表的列信息 - try (ResultSet columnsRs = metaData.getColumns(null, null, tableName, null)) { - while (columnsRs.next()) { - ColumnMetaData column = new ColumnMetaData(); - - // 列字段 - String columnName = columnsRs.getString("COLUMN_NAME"); - - // 将当前表的列类型转成 Java 类型 - String javaType = TypeConvertCore.convertToJavaType(column.getJdbcType()); - - // 设置列字段 - column.setColumnName(columnName); - - // 列字段转成 下划线 -> 小驼峰 - column.setLowercaseName(TypeConvertCore.convertToCamelCase(column.getColumnName())); - - // 列字段转成 下划线 -> 大驼峰名称 - column.setUppercaseName(TypeConvertCore.convertToCamelCase(column.getColumnName(), true)); - - // 字段类型 - column.setJdbcType(columnsRs.getString("TYPE_NAME")); - - // 字段类型转 Java 类型 - column.setJavaType(javaType); - - // 字段类型转 JavaScript 类型 - column.setJavascriptType(StringUtils.uncapitalize(javaType)); - - // 备注信息 - column.setComment(columnsRs.getString("REMARKS")); - - // 确保 primaryKeyColumns 不为空 - if (!primaryKeyColumns.isEmpty()) { - // 是否是主键 - boolean isPrimaryKey = primaryKeyColumns.contains(columnName); - column.setIsPrimaryKey(isPrimaryKey); - } - columns.add(column); - } - } - - columns.get(0).setIsPrimaryKey(true); - - return columns; - } + return databaseInfoCore.tableColumnInfo(tableName); } /** @@ -157,17 +74,6 @@ public class TableServiceImpl implements TableService { @SneakyThrows @Override public DatabaseInfoMetaData databaseInfoMetaData() { - try (Connection connection = dataSource.getConnection()) { - DatabaseMetaData metaData = connection.getMetaData(); - - return DatabaseInfoMetaData.builder() - .databaseProductName(metaData.getDatabaseProductName()) - .databaseProductVersion(metaData.getDatabaseProductVersion()) - .driverName(metaData.getDriverName()) - .driverVersion(metaData.getDriverVersion()) - .url(metaData.getURL()) - .username(metaData.getUserName()) - .build(); - } + return databaseInfoCore.databaseInfoMetaData(); } } diff --git a/generator-code/src/main/java/cn/bunny/service/impl/VmsServiceImpl.java b/generator-code/src/main/java/cn/bunny/service/impl/VmsServiceImpl.java index 947f284..ddeaeea 100644 --- a/generator-code/src/main/java/cn/bunny/service/impl/VmsServiceImpl.java +++ b/generator-code/src/main/java/cn/bunny/service/impl/VmsServiceImpl.java @@ -2,12 +2,13 @@ package cn.bunny.service.impl; import cn.bunny.core.DatabaseInfoCore; import cn.bunny.core.ResourceFileCore; +import cn.bunny.core.SqlParserCore; +import cn.bunny.core.vms.VmsArgumentDtoBaseVmsGenerator; import cn.bunny.dao.dto.VmsArgumentDto; import cn.bunny.dao.entity.ColumnMetaData; +import cn.bunny.dao.entity.TableMetaData; import cn.bunny.dao.vo.GeneratorVo; -import cn.bunny.dao.vo.TableInfoVo; import cn.bunny.dao.vo.VmsPathVo; -import cn.bunny.service.TableService; import cn.bunny.service.VmsService; import cn.bunny.utils.VmsUtil; import cn.hutool.crypto.digest.MD5; @@ -17,6 +18,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -32,9 +34,6 @@ import java.util.zip.ZipOutputStream; @Service public class VmsServiceImpl implements VmsService { - @Resource - private TableService tableService; - @Resource private DatabaseInfoCore databaseInfoCore; @@ -47,14 +46,25 @@ public class VmsServiceImpl implements VmsService { @Override public List generator(VmsArgumentDto dto) { String tableName = dto.getTableName(); + String sql = dto.getSql(); + + // 表格属性名 和 列信息 + TableMetaData tableMetaData; + List columnInfoList; + + // 判断是否有 SQL 如果有SQL 优先解析并生成SQL相关内容 + if (StringUtils.hasText(sql)) { + tableMetaData = SqlParserCore.parserTableInfo(sql); + columnInfoList = SqlParserCore.parserColumnInfo(sql); + } else { + tableMetaData = databaseInfoCore.tableInfoMetaData(tableName); + columnInfoList = databaseInfoCore.tableColumnInfo(tableName).stream().distinct().toList(); + } return dto.getPath().stream().map(path -> { - // 表格属性名 和 列信息 - TableInfoVo tableMetaData = tableService.tableMetaData(tableName); - List columnInfoList = tableService.tableColumnInfo(tableName).stream().distinct().toList(); - // 生成模板 - StringWriter writer = VmsUtil.buildGeneratorCodeTemplate(dto, path, tableMetaData, columnInfoList); + VmsArgumentDtoBaseVmsGenerator vmsArgumentDtoBaseVmsGenerator = new VmsArgumentDtoBaseVmsGenerator(dto, path); + StringWriter writer = vmsArgumentDtoBaseVmsGenerator.generatorCodeTemplate(tableMetaData, columnInfoList); // 处理 vm 文件名 path = VmsUtil.handleVmFilename(path, dto.getClassName()); diff --git a/generator-code/src/main/java/cn/bunny/utils/VmsUtil.java b/generator-code/src/main/java/cn/bunny/utils/VmsUtil.java index d65a5ae..66b788f 100644 --- a/generator-code/src/main/java/cn/bunny/utils/VmsUtil.java +++ b/generator-code/src/main/java/cn/bunny/utils/VmsUtil.java @@ -1,18 +1,8 @@ package cn.bunny.utils; import cn.bunny.core.TypeConvertCore; -import cn.bunny.dao.dto.VmsArgumentDto; -import cn.bunny.dao.entity.ColumnMetaData; -import cn.bunny.dao.vo.TableInfoVo; import com.google.common.base.CaseFormat; -import org.apache.velocity.Template; -import org.apache.velocity.VelocityContext; -import org.apache.velocity.app.Velocity; -import java.io.StringWriter; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.List; import java.util.Map; public class VmsUtil { @@ -25,66 +15,6 @@ public class VmsUtil { "resourceMapper", "Mapper" ); - /** - * 生成模板 - * - * @param dto 类名称可以自定义,格式为 xxx_xxx - * @param path 当前路径 - * @param tableMetaData 表属性 - * @param columnInfoList 列属性数组 - * @return StringWriter - */ - public static StringWriter buildGeneratorCodeTemplate(VmsArgumentDto dto, String path, TableInfoVo tableMetaData, List columnInfoList) { - StringWriter writer = new StringWriter(); - String date = new SimpleDateFormat(dto.getSimpleDateFormat()).format(new Date()); - List list = columnInfoList.stream().map(ColumnMetaData::getColumnName).toList(); - - // 添加要生成的属性 - VelocityContext context = new VelocityContext(); - - // 类名称如果是小驼峰,需要 [手写] 为 [下划线] 之后由 [代码 -> 小驼峰/大驼峰] - String className = dto.getClassName(); - // 去除表开头前缀 - String tablePrefixes = dto.getTablePrefixes(); - - // vm 不能直接写 `{` 需要转换下 - context.put("leftBrace", "{"); - // 当前日期 - context.put("date", date); - // 作者名字 - context.put("author", dto.getAuthor()); - // 每个 Controller 上的请求前缀 - context.put("requestMapping", dto.getRequestMapping()); - // 当前的表名 - context.put("tableName", tableMetaData.getTableName()); - // 表字段的注释内容 - context.put("comment", dto.getComment()); - // 设置包名称 - context.put("package", dto.getPackageName()); - // 当前表的列信息 - context.put("columnInfoList", columnInfoList); - // 数据库sql列 - context.put("baseColumnList", String.join(",", list)); - - // 将 表前缀 转成数组 - String replaceTableName = ""; - for (String prefix : tablePrefixes.split("[,,]")) { - replaceTableName = className.replace(prefix, ""); - } - - // 将类名称转成小驼峰 - String toCamelCase = TypeConvertCore.convertToCamelCase(replaceTableName); - context.put("classLowercaseName", toCamelCase); - // 将类名称转成大驼峰 - String convertToCamelCase = TypeConvertCore.convertToCamelCase(replaceTableName, true); - context.put("classUppercaseName", convertToCamelCase); - // Velocity 生成模板 - Template servicePathTemplate = Velocity.getTemplate("vms/" + path, "UTF-8"); - servicePathTemplate.merge(context, writer); - - return writer; - } - /** * 处理 vm 文件名 * diff --git a/generator-code/src/test/java/cn/bunny/SqlParserTest.java b/generator-code/src/test/java/cn/bunny/SqlParserTest.java new file mode 100644 index 0000000..859ef28 --- /dev/null +++ b/generator-code/src/test/java/cn/bunny/SqlParserTest.java @@ -0,0 +1,105 @@ +package cn.bunny; + +import cn.bunny.core.TypeConvertCore; +import cn.bunny.dao.entity.ColumnMetaData; +import cn.bunny.dao.entity.TableMetaData; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.create.table.CreateTable; +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SqlParserTest { + + @Test + public void test() throws JSQLParserException { + String sql = """ + CREATE TABLE `sys_files` ( + `id` bigint NOT NULL COMMENT '文件的唯一标识符,自动递增', + `filename` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '文件的名称', + `filepath` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '文件在服务器上的存储路径', + `file_size` int NOT NULL COMMENT '文件的大小,以字节为单位', + `file_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '文件的MIME类型', + `download_count` int NULL DEFAULT 0 COMMENT '下载数量', + `create_user` bigint NOT NULL COMMENT '创建用户', + `update_user` bigint NULL DEFAULT NULL COMMENT '操作用户', + `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录文件最后修改的时间戳', + `is_deleted` tinyint(1) UNSIGNED ZEROFILL NOT NULL DEFAULT 0 COMMENT '文件是否被删除', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_filename`(`filename` ASC) USING BTREE COMMENT '索引文件名', + INDEX `idx_filepath`(`filepath` ASC) USING BTREE COMMENT '索引文件路径', + INDEX `idx_file_type`(`file_type` ASC) USING BTREE COMMENT '索引文件类型', + INDEX `idx_update_user`(`update_user` ASC) USING BTREE COMMENT '索引创更新用户', + INDEX `idx_create_user`(`create_user` ASC) USING BTREE COMMENT '索引创建用户', + INDEX `idx_user`(`update_user` ASC, `create_user` ASC) USING BTREE COMMENT '索引创建用户和更新用户', + INDEX `idx_time`(`update_time` ASC, `create_time` ASC) USING BTREE COMMENT '索引创建时间和更新时间' + ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '系统文件表' ROW_FORMAT = DYNAMIC; + """; + + TableMetaData tableInfo = new TableMetaData(); + + // 解析sql + Statement statement = CCJSqlParserUtil.parse(sql); + if (!(statement instanceof CreateTable createTable)) { + throw new IllegalArgumentException("Not a CREATE TABLE statement"); + } + + // 设置表基本信息 + tableInfo.setTableName(createTable.getTable().getName()); + tableInfo.setTableType("TABLE"); + String tableOptionsStrings = String.join(" ", createTable.getTableOptionsStrings()); + + // 注释信息 + Pattern pattern = Pattern.compile("COMMENT\\s*=\\s*'(.*?)'", Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(tableOptionsStrings); + if (matcher.find()) { + tableInfo.setComment(matcher.group(1)); + } + + // 解析列信息 + List columnMetaData = createTable.getColumnDefinitions() + .stream().map(column -> { + // 列信息 + ColumnMetaData columnInfo = new ColumnMetaData(); + + // 列名称 + columnInfo.setColumnName(column.getColumnName()); + + // 设置 JDBC 类型 + String dataType = column.getColDataType().getDataType(); + columnInfo.setJdbcType(dataType); + + // 设置 Java 类型 + String javaType = TypeConvertCore.convertToJavaType(dataType.contains("varchar") ? "varchar" : dataType); + columnInfo.setJavaType(javaType); + + // 设置 JavaScript 类型 + columnInfo.setJavascriptType(StringUtils.uncapitalize(javaType)); + + // 列字段转成 下划线 -> 小驼峰 + columnInfo.setLowercaseName(TypeConvertCore.convertToCamelCase(column.getColumnName())); + // 列字段转成 下划线 -> 大驼峰名称 + columnInfo.setUppercaseName(TypeConvertCore.convertToCamelCase(column.getColumnName(), true)); + + // 解析注释 + List columnSpecs = column.getColumnSpecs(); + String columnSpecsString = String.join(" ", columnSpecs); + Matcher columnSpecsStringMatcher = Pattern.compile("COMMENT\\s*'(.*?)'", Pattern.CASE_INSENSITIVE).matcher(columnSpecsString); + if (columnSpecsStringMatcher.find()) { + columnInfo.setComment(columnSpecsStringMatcher.group(1)); + } + + return columnInfo; + }).toList(); + + System.out.println(tableInfo); + System.out.println("----------------------------------------------------------------------------------------"); + System.out.println(columnMetaData); + } +}