Compare commits

..

No commits in common. "395ec89666af01e27ce0d9a472be08955bc95524" and "4e438bea8600af21c8a921ad70088710df422366" have entirely different histories.

119 changed files with 867 additions and 1657 deletions

View File

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

View File

@ -1,14 +1,16 @@
package cn.bunny.controller; package cn.bunny.controller;
import cn.bunny.domain.dto.VmsArgumentDto;
import cn.bunny.domain.result.Result; import cn.bunny.domain.result.Result;
import cn.bunny.domain.vo.GeneratorVo;
import cn.bunny.domain.vo.VmsPathVo; import cn.bunny.domain.vo.VmsPathVo;
import cn.bunny.service.VmsService; import cn.bunny.service.VmsService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping; import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.*;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -28,4 +30,17 @@ public class VmsController {
return Result.success(list); 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

@ -0,0 +1,212 @@
package cn.bunny.core.provider;
import cn.bunny.domain.entity.ColumnMetaData;
import cn.bunny.domain.entity.DatabaseInfoMetaData;
import cn.bunny.domain.entity.TableMetaData;
import cn.bunny.exception.GeneratorCodeException;
import cn.bunny.exception.MetadataNotFoundException;
import cn.bunny.exception.MetadataProviderException;
import cn.bunny.utils.MysqlTypeConvertUtil;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
@Component
@RequiredArgsConstructor
public class DatabaseMetadataProvider implements IMetadataProvider {
private final DataSource dataSource;
@Value("${bunny.master.database}")
private String currentDatabase;
/**
* 获取表的所有主键列名
*
* @param tableName 表名
* @return 主键列名的集合
*/
public Set<String> getPrimaryKeys(String tableName) {
// 主键的key
Set<String> primaryKeys = new HashSet<>();
try (Connection connection = dataSource.getConnection()) {
DatabaseMetaData metaData = connection.getMetaData();
// 当前表的主键
ResultSet pkResultSet = metaData.getPrimaryKeys(null, null, tableName);
while (pkResultSet.next()) {
// 列字段
String columnName = pkResultSet.getString("COLUMN_NAME").toLowerCase();
primaryKeys.add(columnName);
}
return primaryKeys;
} catch (SQLException e) {
throw new GeneratorCodeException("Get primary key error:" + e.getMessage());
}
}
/**
* 数据库所有的信息
*
* @return 当前连接的数据库信息属性
*/
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())
.currentDatabase(currentDatabase)
.build();
} catch (SQLException e) {
throw new GeneratorCodeException("Get database info error:" + e.getMessage());
}
}
/**
* 解析 sql 表信息
*
* @param identifier 表名称或sql
* @return 表西悉尼
*/
@Override
public TableMetaData getTableMetadata(String identifier) {
TableMetaData tableMetaData;
try (Connection connection = dataSource.getConnection()) {
DatabaseMetaData metaData = connection.getMetaData();
ResultSet tables = metaData.getTables(null, null, identifier, new String[]{"TABLE"});
// 获取表的注释信息
if (tables.next()) {
// 备注信息
String remarks = tables.getString("REMARKS");
// 数组名称
String tableCat = tables.getString("TABLE_CAT");
// 通常是"TABLE"
String tableType = tables.getString("TABLE_TYPE");
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(e.getMessage());
}
}
/**
* 获取[当前/所有]数据库表
*
* @return 所有表信息
*/
public List<TableMetaData> getTableMetadataBatch(String dbName) {
List<TableMetaData> allTableInfo = new ArrayList<>();
try (Connection conn = dataSource.getConnection()) {
DatabaseMetaData metaData = conn.getMetaData();
ResultSet tables = metaData.getTables(dbName, null, "%", new String[]{"TABLE"});
while (tables.next()) {
String tableName = tables.getString("TABLE_NAME");
String remarks = tables.getString("REMARKS");
String tableCat = tables.getString("TABLE_CAT");
String tableType = tables.getString("TABLE_TYPE");
TableMetaData tableMetaData = TableMetaData.builder()
.tableName(tableName)
.comment(remarks)
.tableCat(tableCat)
.tableType(tableType)
.build();
allTableInfo.add(tableMetaData);
}
} catch (Exception e) {
throw new MetadataProviderException("Failed to get batch table metadata", e);
}
return allTableInfo;
}
/**
* 获取当前表的列属性
*
* @param identifier 表名称或sql
* @return 当前表所有的列内容
*/
@Override
public List<ColumnMetaData> getColumnInfoList(String identifier) {
try (Connection connection = dataSource.getConnection()) {
DatabaseMetaData metaData = connection.getMetaData();
Map<String, ColumnMetaData> map = new LinkedHashMap<>();
// 当前表的主键
Set<String> primaryKeyColumns = getPrimaryKeys(identifier);
// 当前表的列信息
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);
// 列字段转成 下划线 -> 小驼峰
column.setLowercaseName(MysqlTypeConvertUtil.convertToCamelCase(column.getColumnName()));
// 列字段转成 下划线 -> 大驼峰名称
column.setUppercaseName(MysqlTypeConvertUtil.convertToCamelCase(column.getColumnName(), true));
// 字段类型
column.setJdbcType(typeName);
// 字段类型转 Java 类型
String javaType = MysqlTypeConvertUtil.convertToJavaType(typeName);
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);
}
map.putIfAbsent(column.getColumnName(), column);
}
}
return new ArrayList<>(map.values());
} 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 表信息
* 先解析SQL语句解析列字段信息 * 先解析SQL语句解析列字段信息
* *
* @param sqlStatement sql语句 * @param identifier 表名称或sql
* @return 表西悉尼 * @return 表西悉尼
* @see CCJSqlParserUtil 使用这个工具进行SQL的解析 * @see CCJSqlParserUtil 使用这个工具进行SQL的解析
*/ */
@Override @Override
public TableMetaData getTableMetadata(String sqlStatement) { public TableMetaData getTableMetadata(String identifier) {
TableMetaData tableInfo = new TableMetaData(); TableMetaData tableInfo = new TableMetaData();
// 解析sql // 解析sql
Statement statement; Statement statement;
try { try {
statement = CCJSqlParserUtil.parse(sqlStatement); statement = CCJSqlParserUtil.parse(identifier);
} catch (JSQLParserException e) { } catch (JSQLParserException e) {
throw new GeneratorCodeException("SQL解析失败"); throw new GeneratorCodeException("SQL解析失败");
} }
@ -62,15 +62,15 @@ public class SqlMetadataProvider implements IMetadataProvider {
/** /**
* 获取当前表的列属性 * 获取当前表的列属性
* *
* @param sqlStatement sql语句 * @param identifier 表名称或sql
* @return 当前表所有的列内容 * @return 当前表所有的列内容
*/ */
@Override @Override
public List<ColumnMetaData> getColumnInfoList(String sqlStatement) { public List<ColumnMetaData> getColumnInfoList(String identifier) {
// 解析sql // 解析sql
Statement statement; Statement statement;
try { try {
statement = CCJSqlParserUtil.parse(sqlStatement); statement = CCJSqlParserUtil.parse(identifier);
} catch (JSQLParserException e) { } catch (JSQLParserException e) {
throw new SqlParseException("Fail parse sql", e.getCause()); throw new SqlParseException("Fail parse sql", e.getCause());
} }
@ -99,12 +99,9 @@ public class SqlMetadataProvider implements IMetadataProvider {
columnInfo.setJavascriptType(StringUtils.uncapitalize(javaType)); columnInfo.setJavascriptType(StringUtils.uncapitalize(javaType));
// 列字段转成 下划线 -> 小驼峰 // 列字段转成 下划线 -> 小驼峰
String lowercaseName = MysqlTypeConvertUtil.convertToCamelCase(column.getColumnName(), false); columnInfo.setLowercaseName(MysqlTypeConvertUtil.convertToCamelCase(column.getColumnName()));
columnInfo.setLowercaseName(lowercaseName);
// 列字段转成 下划线 -> 大驼峰名称 // 列字段转成 下划线 -> 大驼峰名称
String uppercaseName = MysqlTypeConvertUtil.convertToCamelCase(column.getColumnName(), true); columnInfo.setUppercaseName(MysqlTypeConvertUtil.convertToCamelCase(column.getColumnName(), true));
columnInfo.setUppercaseName(uppercaseName);
// 解析注释 // 解析注释
List<String> columnSpecs = column.getColumnSpecs(); List<String> columnSpecs = column.getColumnSpecs();

View File

@ -0,0 +1,62 @@
package cn.bunny.core.template;
import cn.bunny.domain.entity.ColumnMetaData;
import cn.bunny.domain.entity.TableMetaData;
import org.apache.velocity.VelocityContext;
import java.io.StringWriter;
import java.util.List;
/**
* 模板方法模式
* 如果需要继承 AbstractVmsGenerator
*/
public abstract class AbstractTemplateGenerator {
/**
* 添加生成内容
*/
abstract void addContext(VelocityContext context);
/**
* Velocity 生成模板
*
* @param context VelocityContext
* @param writer StringWriter 写入
*/
abstract void templateMerge(VelocityContext context, StringWriter writer);
/**
* 生成模板
*
* @param tableMetaData 表属性
* @param columnInfoList 列属性数组
* @return StringWriter
*/
public final StringWriter generatorCodeTemplate(TableMetaData tableMetaData, List<ColumnMetaData> columnInfoList) {
VelocityContext context = new VelocityContext();
// 添加要生成的属性
StringWriter writer = new StringWriter();
List<String> list = columnInfoList.stream().map(ColumnMetaData::getColumnName).distinct().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;
}
}

View File

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

View File

@ -0,0 +1,102 @@
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.io.StringWriter;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* 负责处理代码生成逻辑的服务类
*/
@Service
@RequiredArgsConstructor
public class VmsCodeGeneratorService {
private final DatabaseMetadataProvider databaseMetadataProvider;
private final SqlMetadataProvider sqlMetadataProvider;
/**
* 根据DTO生成代码模板
*
* @param dto 包含生成参数的数据传输对象
* @return 按表名分组的生成的代码模板列表
*/
public Map<String, List<GeneratorVo>> generateCode(VmsArgumentDto dto) {
// 提前获取可复用的数据
final String sql = dto.getSql();
final List<String> tableNames = dto.getTableNames();
final List<String> paths = dto.getPath();
return tableNames.parallelStream() // 使用并行流提高多表处理效率
.map(tableName -> {
// 获取表元数据和列信息
TableMetaData tableMetaData = getTableMetadata(dto, tableName);
List<ColumnMetaData> columnInfoList = getColumnInfoList(sql, tableName);
// 为每个路径生成模板
return paths.stream()
.map(path -> generateTemplate(dto, path, tableMetaData, columnInfoList))
.toList();
})
.flatMap(List::stream)
.collect(Collectors.groupingBy(GeneratorVo::getTableName));
}
/**
* 生成单个模板
*/
private GeneratorVo generateTemplate(VmsArgumentDto dto, String path,
TableMetaData tableMetaData, List<ColumnMetaData> columnInfoList) {
VmsTBaseTemplateGenerator generator = new VmsTBaseTemplateGenerator(dto, path, tableMetaData);
StringWriter writer = generator.generatorCodeTemplate(tableMetaData, columnInfoList);
String processedPath = VmsUtil.handleVmFilename(path, tableMetaData.getTableName());
return GeneratorVo.builder()
.id(UUID.randomUUID().toString())
.code(writer.toString())
.comment(tableMetaData.getComment())
.tableName(tableMetaData.getTableName())
.path(processedPath)
.build();
}
/**
* 获取表元数据
*
* @param dto 生成参数
* @param tableName 表名
* @return 表元数据对象
*/
private TableMetaData getTableMetadata(VmsArgumentDto dto, String tableName) {
return StringUtils.hasText(dto.getSql())
? sqlMetadataProvider.getTableMetadata(dto.getSql())
: databaseMetadataProvider.getTableMetadata(tableName);
}
/**
* 获取列信息列表
*
* @param sql SQL语句
* @param tableName 表名
* @return 列元数据列表
*/
private List<ColumnMetaData> getColumnInfoList(String sql, String tableName) {
return StringUtils.hasText(sql)
? sqlMetadataProvider.getColumnInfoList(sql)
: databaseMetadataProvider.getColumnInfoList(tableName).stream().distinct().toList();
}
}

View File

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

View File

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

View File

@ -0,0 +1,40 @@
package cn.bunny.domain.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class DatabaseInfoMetaData {
/* 数据库所有的数据库 */
List<TableMetaData> databaseList;
/* 数据库产品名称 */
private String databaseProductName;
/* 数据库产品版本 */
private String databaseProductVersion;
/* 驱动名称 */
private String driverName;
/* 数据库驱动版本 */
private String driverVersion;
/* 数据链接url */
private String url;
/* 数据库用户 */
private String username;
/* 当前数据库名称 */
private String currentDatabase;
}

View File

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

View File

@ -0,0 +1,37 @@
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文件路径
*
* @return vms下的文件路径
*/
Map<String, List<VmsPathVo>> vmsResourcePathList();
/**
* 打包成zip下载
*
* @param dto VmsArgumentDto
* @return zip 文件
*/
ResponseEntity<byte[]> downloadByZip(@Valid VmsArgumentDto dto);
}

View File

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

View File

@ -0,0 +1,88 @@
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 cn.bunny.utils.VmsUtil;
import cn.hutool.crypto.digest.MD5;
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;
import java.util.Map;
import java.util.stream.Collectors;
/**
* VMS服务主实现类负责协调各子服务完成代码生成资源管理和打包下载功能
*/
@Service
@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下的文件路径
*/
@Override
public Map<String, List<VmsPathVo>> vmsResourcePathList() {
List<String> vmsRelativeFiles;
Map<String, List<VmsPathVo>> listMap;
try {
vmsRelativeFiles = ResourceFileUtil.getRelativeFiles("vms");
listMap = vmsRelativeFiles.stream()
.map(vmFile -> {
String[] filepathList = vmFile.split("/");
String filename = filepathList[filepathList.length - 1].replace(".vm", "");
return VmsPathVo.builder()
.id(VmsUtil.generateDivId())
.name(vmFile)
.label(filename)
.type(filepathList[0])
.build();
})
.collect(Collectors.groupingBy(VmsPathVo::getType));
} catch (Exception e) {
throw new RuntimeException("Get error of VMS path:" + e.getMessage());
}
return listMap;
}
@Override
public ResponseEntity<byte[]> downloadByZip(VmsArgumentDto dto) {
byte[] zipBytes = zipService.createZipFile(dto);
// 下载文件名称
long currentTimeMillis = System.currentTimeMillis();
String digestHex = MD5.create().digestHex(currentTimeMillis + "");
String generateZipFilename = "code-" + digestHex.substring(0, 6) + ".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);
}
}

View File

@ -5,7 +5,7 @@ import org.assertj.core.util.introspection.CaseFormatUtils;
/* 类型转换数据库转Java类型等 */ /* 类型转换数据库转Java类型等 */
public class MysqlTypeConvertUtil { public class MysqlTypeConvertUtil {
/** /**
* 将数据库类型转换为Java类型 * 将数据库类型转换为Java类型
*/ */
@ -29,6 +29,13 @@ public class MysqlTypeConvertUtil {
}; };
} }
/**
* 下划线命名转驼峰命名
*/
public static String convertToCamelCase(String name) {
return convertToCamelCase(name, false);
}
/** /**
* 下划线命名转驼峰命名 * 下划线命名转驼峰命名
* *
@ -44,7 +51,9 @@ public class MysqlTypeConvertUtil {
String lowerCamelCase = CaseFormatUtils.toCamelCase(name); String lowerCamelCase = CaseFormatUtils.toCamelCase(name);
// 首字母不大写 // 首字母不大写
if (!firstLetterCapital) return lowerCamelCase; if (!firstLetterCapital) {
return lowerCamelCase;
}
// 将小驼峰转成大驼峰 // 将小驼峰转成大驼峰
return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, lowerCamelCase); return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, lowerCamelCase);

View File

@ -0,0 +1,72 @@
package cn.bunny.utils;
import com.google.common.base.CaseFormat;
import java.util.Map;
import java.util.UUID;
public class VmsUtil {
private static final Map<String, String> TYPE_MAPPINGS = Map.of(
"controller", "Controller",
"service", "Service",
"serviceImpl", "ServiceImpl",
"mapper", "Mapper",
"resourceMapper", "Mapper",
"dto", "Dto",
"vo", "Vo"
);
/**
* 处理 vm 文件名
*
* @param path 文件路径
* @param className 类名
*/
public static String handleVmFilename(String path, String className) {
String[] splitPaths = path.split("/");
int splitPathsSize = splitPaths.length - 1;
// 大驼峰名称
String CamelCase = MysqlTypeConvertUtil.convertToCamelCase(className, true);
// 小驼峰名称
String smallCamelCase = MysqlTypeConvertUtil.convertToCamelCase(className);
// 当前文件名
String filename = splitPaths[splitPathsSize];
filename = filename.replace(".vm", "");
String[] split = filename.split("\\.");
// 文件名称
String name = split[0];
// 文件扩展名
String extension = "";
if (split.length >= 2) {
extension = split[1];
}
// 判断是否是 Java 或者 xml 文件
String typeMappingsFilename = TYPE_MAPPINGS.get(name);
typeMappingsFilename = typeMappingsFilename == null ? "" : typeMappingsFilename;
if (filename.contains("java") || filename.contains("xml")) {
filename = CamelCase + typeMappingsFilename + "." + extension;
}
if ((filename.contains("vue") || filename.contains("ts") || filename.contains("js"))
&& !filename.contains("index")) {
filename = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, smallCamelCase) + "-" + name + "." + extension;
}
splitPaths[splitPathsSize] = filename;
return String.join("/", splitPaths);
}
/**
* 生成前端标签上的id
*
* @return id-UUID
*/
public static String generateDivId() {
return "id-" + UUID.randomUUID().toString().replace("-", "");
}
}

View File

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View File

@ -0,0 +1,28 @@
// 如果一开始定义过了 defineComponent 就不要在下 script 标签中再写了
const {defineComponent} = Vue;
// 定义 Header 组件
const AppHeader = defineComponent({
name: "AppHeader",
template: `
<header class="app-header">
<div class="header-content text-center mb-4 p-4 bg-light rounded shadow-sm">
<h2 class="text-primary fw-bold mb-3">
<i class="bi bi-code-square me-2"></i>
Bunny{{ title || '代码生成器' }}
</h2>
<p class="text-muted mb-0">
快速生成数据库表对应的代码这里可以跳转到
<a href="/database" class="text-decoration-none"><i class="bi bi-database-fill"></i></a>
<a href="/sql" class="text-decoration-none"><i class="bi bi-filetype-sql"></i>SQL</a>
</p>
</div>
</header>
`,
data() {
return {
title: document.title,
}
}
});

View File

@ -4,4 +4,4 @@
/* 引入Highlight.js的CSS */ /* 引入Highlight.js的CSS */
@import url("highlight/atom-one-dark.min.css"); @import url("highlight/atom-one-dark.min.css");
/* 添加自定义样式 */ /* 添加自定义样式 */
@import url("./style/style.css"); @import url("./style/style.css");

View File

@ -0,0 +1,21 @@
/* 提高 Antd Message 的 z-index */
.ant-message {
z-index: 1100 !important;
}
/* 响应式 OffCanvas 宽度 */
.offcanvas.offcanvas-start {
width: 50%;
}
@media (max-width: 991.98px) {
.offcanvas.offcanvas-start {
width: 75%;
}
}
@media (max-width: 767.98px) {
.offcanvas.offcanvas-start {
width: 100%;
}
}

View File

@ -27,9 +27,36 @@ const DatabaseCard = defineComponent({
<code class="bg-primary bg-opacity-10">生成</code> <code class="bg-primary bg-opacity-10">生成</code>
</p> </p>
</div> </div>
<!-- 代码仓库链接区域包含GitHub和Gitee仓库链接 -->
<div class="card-body">
<div class="d-flex flex-wrap align-items-center gap-3">
<!-- GitHub 仓库链接 -->
<a class="btn btn-outline-dark d-flex align-items-center gap-2"
href="https://github.com/BunnyMaster/generator-code-server" target="_blank">
<svg class="feather feather-github" fill="none" height="20" stroke="currentColor"
stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24"
width="20"
xmlns="http://www.w3.org/2000/svg">
<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path>
</svg>
GitHub 仓库
</a>
<!-- Gitee 仓库链接 -->
<a class="btn btn-outline-danger d-flex align-items-center gap-2"
href="https://gitee.com/BunnyBoss/generator-code-server" target="_blank">
<svg height="20" viewBox="0 0 24 24" width="20" xmlns="http://www.w3.org/2000/svg">
<path d="M11.984 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12a12 12 0 0 0 12-12A12 12 0 0 0 12 0zm6.09 5.333c.328 0 .593.266.592.593v1.482a.594.594 0 0 1-.593.592H9.777c-.982 0-1.778.796-1.778 1.778v5.63c0 .327.266.592.593.592h5.63c.982 0 1.778-.796 1.778-1.778v-.296a.593.593 0 0 0-.592-.593h-4.15a.59.59 0 0 1-.592-.592v-1.482a.593.593 0 0 1 .593-.592h6.815c.327 0 .593.265.593.592v3.408a4 4 0 0 1-4 4H5.926a.593.593 0 0 1-.593-.593V9.778a4.444 4.444 0 0 1 4.445-4.444h8.296Z"
fill="currentColor"/>
</svg>
Gitee 仓库
</a>
</div>
</div>
<!-- 数据库操作区域包含数据库选择表选择和查询功能 --> <!-- 数据库操作区域包含数据库选择表选择和查询功能 -->
<div class="card-body bg-light"> <div class="card-footer bg-light">
<div class="row g-3 align-items-end"> <div class="row g-3 align-items-end">
<!-- 数据库选择下拉框 --> <!-- 数据库选择下拉框 -->
<div class="col-md-5"> <div class="col-md-5">

View File

@ -63,93 +63,94 @@ const DatabaseForm = {
</div> </div>
</div> </div>
<!-- 已选择的表 -->
<div class="col-md-12" v-show="form.tableNames.length > 0"> <div class="col-md-12" v-show="form.tableNames.length > 0">
<label class="form-label fw-medium" for="tableNames">已选择的要生成表({{form.tableNames.length}})</label> <label class="form-label fw-medium" for="tableNames">已选择的要生成表({{form.tableNames.length}})</label>
<span class="badge rounded-pill text-bg-dark me-1" v-for="(item,index) in form.tableNames" :ket="index">{{item}}</span> <span class="badge rounded-pill text-bg-dark me-1" v-for="(item,index) in form.tableNames" :ket="index">{{item}}</span>
</div> </div>
<!-- 前端模板选择区域 --> <!-- 前端模板选择区域 -->
<div class="col-12 mt-3 mb-3 p-3 bg-light rounded"> <div class="col-12 mt-3 mb-3 p-3 bg-light rounded">
<label class="form-check-inline col-form-label fw-medium">生成前端模板</label> <label class="form-check-inline col-form-label fw-medium">生成前端模板</label>
<div class="form-check form-check-inline" v-for="(web,index) in webList" :key="index"> <div class="form-check form-check-inline" v-for="(web,index) in webList" :key="index">
<input class="form-check-input border-secondary" :class="{ 'is-invalid': errors.webTemplates }" <input class="form-check-input border-secondary" :class="{ 'is-invalid': errors.webTemplates }"
:id="web.id" type="checkbox" v-model="web.checked" :id="web.id" type="checkbox" v-model="web.checked"
@change="validateTemplates"> @change="validateTemplates">
<label class="form-check-label" :for="web.id" :title="web.name">{{web.label}}</label> <label class="form-check-label" :for="web.id" :title="web.name">{{web.label}}</label>
</div>
<div class="form-check form-check-inline btn-group ms-2">
<button class="btn btn-outline-primary btn-sm" type="button"
@click="onTableSelectAll(webList)">全选</button>
<button class="btn btn-outline-secondary btn-sm" type="button"
@click="onTableInvertSelection(webList)">反选</button>
<button class="btn btn-outline-danger btn-sm" type="button"
@click="onTableClearAll(webList)">全不选</button>
</div>
<div v-if="errors.webTemplates" class="invalid-feedback d-block">
{{ errors.webTemplates }}
</div>
</div> </div>
<div class="form-check form-check-inline btn-group ms-2">
<button class="btn btn-outline-primary btn-sm" type="button" <!-- 后端模板选择区域 -->
@click="onTableSelectAll(webList)">全选</button> <div class="col-12 mt-2 mb-3 p-3 bg-light rounded">
<button class="btn btn-outline-secondary btn-sm" type="button" <label class="form-check-inline col-form-label fw-medium">生成后端模板</label>
@click="onTableInvertSelection(webList)">反选</button> <div class="form-check form-check-inline" v-for="(server,index) in serverList" :key="index">
<button class="btn btn-outline-danger btn-sm" type="button" <input class="form-check-input border-secondary"
@click="onTableClearAll(webList)">全不选</button> :class="{ 'is-invalid': errors.serverTemplates }" :id="server.id" type="checkbox"
v-model="server.checked" @change="validateTemplates">
<label class="form-check-label" :title="server.name" :for="server.id">{{server.label}}</label>
</div>
<div class="form-check form-check-inline btn-group ms-2">
<button class="btn btn-outline-primary btn-sm" type="button"
@click="onTableSelectAll(serverList)">全选</button>
<button class="btn btn-outline-secondary btn-sm" type="button"
@click="onTableInvertSelection(serverList)">反选</button>
<button class="btn btn-outline-danger btn-sm" type="button"
@click="onTableClearAll(serverList)">全不选</button>
</div>
<div v-if="errors.serverTemplates" class="invalid-feedback d-block">
{{ errors.serverTemplates }}
</div>
</div> </div>
<div v-if="errors.webTemplates" class="invalid-feedback d-block">
{{ errors.webTemplates }} <!-- 操作按钮区域 -->
<div class="row mt-4">
<div class="col-md-4 btn-group">
<button class="btn btn-outline-primary" data-bs-title="选择数据表中所有的内容" data-bs-toggle="tooltip"
type="button" @click="onSelectAll">
全部选择
</button>
<button class="btn btn-outline-secondary" data-bs-title="将选择的表格内容反向选择" data-bs-toggle="tooltip"
type="button" @click="onInvertSelection">
全部反选
</button>
<button class="btn btn-outline-danger" data-bs-title="取消全部已经选择的数据表" data-bs-toggle="tooltip"
type="button" @click="onClearAll">
全部取消
</button>
</div>
<div class="col-md-4 btn-group">
<button class="btn btn-primary shadow-sm" disabled type="button"
v-if="generatorCodeLoading">
<span aria-hidden="true" class="spinner-grow spinner-grow-sm"></span>
<span role="status">生成选中表...</span>
</button>
<button v-else class="btn btn-primary" data-bs-title="生成全部已经选择的数据表" data-bs-toggle="tooltip"
type="submit">
生成选中表
</button>
<button class="btn btn-warning" type="button" data-bs-title="取消全部已经选择的数据表" data-bs-toggle="tooltip"
@click="onClearGeneratorData">清空生成记录</button>
</div>
<div class="col-md-4 d-grid gap-2">
<button class="btn btn-primary shadow-sm" disabled type="button"
v-if="downloadLoading">
<span aria-hidden="true" class="spinner-grow spinner-grow-sm"></span>
<span role="status">下载ZIP...</span>
</button>
<button v-else class="btn btn-primary text-white" type="button" @click="onDownloadZip">下载ZIP</button>
</div>
</div> </div>
</div>
<!-- 后端模板选择区域 -->
<div class="col-12 mt-2 mb-3 p-3 bg-light rounded">
<label class="form-check-inline col-form-label fw-medium">生成后端模板</label>
<div class="form-check form-check-inline" v-for="(server,index) in serverList" :key="index">
<input class="form-check-input border-secondary"
:class="{ 'is-invalid': errors.serverTemplates }" :id="server.id" type="checkbox"
v-model="server.checked" @change="validateTemplates">
<label class="form-check-label" :title="server.name" :for="server.id">{{server.label}}</label>
</div>
<div class="form-check form-check-inline btn-group ms-2">
<button class="btn btn-outline-primary btn-sm" type="button"
@click="onTableSelectAll(serverList)">全选</button>
<button class="btn btn-outline-secondary btn-sm" type="button"
@click="onTableInvertSelection(serverList)">反选</button>
<button class="btn btn-outline-danger btn-sm" type="button"
@click="onTableClearAll(serverList)">全不选</button>
</div>
<div v-if="errors.serverTemplates" class="invalid-feedback d-block">
{{ errors.serverTemplates }}
</div>
</div>
<!-- 操作按钮区域 -->
<div class="row mt-4">
<div class="col-md-4 btn-group">
<button class="btn btn-outline-primary" data-bs-title="选择数据表中所有的内容" data-bs-toggle="tooltip"
type="button" @click="onSelectAll">
全部选择
</button>
<button class="btn btn-outline-secondary" data-bs-title="将选择的表格内容反向选择" data-bs-toggle="tooltip"
type="button" @click="onInvertSelection">
全部反选
</button>
<button class="btn btn-outline-danger" data-bs-title="取消全部已经选择的数据表" data-bs-toggle="tooltip"
type="button" @click="onClearAll">
全部取消
</button>
</div>
<div class="col-md-4 btn-group">
<button class="btn btn-primary shadow-sm" disabled type="button"
v-if="generatorCodeLoading">
<span aria-hidden="true" class="spinner-grow spinner-grow-sm"></span>
<span role="status">生成选中表...</span>
</button>
<button v-else class="btn btn-primary" type="submit">
生成选中表
</button>
<button class="btn btn-warning" type="button" @click="onClearGeneratorData">清空生成记录</button>
</div>
<div class="col-md-4 d-grid gap-2">
<button class="btn btn-primary shadow-sm" disabled type="button"
v-if="downloadLoading">
<span aria-hidden="true" class="spinner-grow spinner-grow-sm"></span>
<span role="status">下载ZIP...</span>
</button>
<button v-else class="btn btn-primary text-white" type="button" @click="onDownloadZip">下载ZIP</button>
</div>
</div>
</form> </form>
</div> </div>
</div> </div>
@ -289,7 +290,7 @@ const DatabaseForm = {
this.downloadLoading = true; this.downloadLoading = true;
try { try {
const response = await axiosInstance({ const response = await axiosInstance({
url: "/generator/downloadByZip", url: "/vms/downloadByZip",
method: "POST", method: "POST",
data: this.form, data: this.form,
responseType: 'blob' // 重要指定响应类型为blob responseType: 'blob' // 重要指定响应类型为blob

View File

@ -1,4 +1,4 @@
const AppGeneratorPage = defineComponent({ const DatabaseGeneratorPage = defineComponent({
name: "MainGeneratorPage", name: "MainGeneratorPage",
template: ` template: `
<div class="offcanvas offcanvas-start" data-bs-scroll="false" tabindex="-1" ref="offcanvasElementRef"> <div class="offcanvas offcanvas-start" data-bs-scroll="false" tabindex="-1" ref="offcanvasElementRef">
@ -30,7 +30,7 @@ const AppGeneratorPage = defineComponent({
role="button" data-bs-toggle="collapse" :data-bs-target="'#collapse-' + item.id" aria-expanded="false"> role="button" data-bs-toggle="collapse" :data-bs-target="'#collapse-' + item.id" aria-expanded="false">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<i class="bi bi-bi-file-earmark-code me-2 text-primary fs-5"></i> <i class="bi bi-bi-file-earmark-code me-2 text-primary fs-5"></i>
<span class="text-truncate" style="max-width: 99%" :title="item.path"> <span class="text-truncate" style="max-width: 90%">
{{item.comment}}{{item.path}} {{item.comment}}{{item.path}}
</span> </span>
</div> </div>

View File

@ -45,8 +45,8 @@
:raw-table-list="rawTableList" :table-list="tableList" v-model:form="form"></database-table> :raw-table-list="rawTableList" :table-list="tableList" v-model:form="form"></database-table>
<!-- 模板生成页面 --> <!-- 模板生成页面 -->
<app-generator-page :generator-data="generatorData" <database-generator-page :generator-data="generatorData" ref="mainGeneratorRef"
v-model:generator-page-flag="generatorPageFlag"></app-generator-page> v-model:generator-page-flag="generatorPageFlag"></database-generator-page>
</div> </div>
</div> </div>
</body> </body>
@ -66,7 +66,7 @@
<script th:src="@{/src/views/database/DatabaseCard.js}"></script> <script th:src="@{/src/views/database/DatabaseCard.js}"></script>
<script th:src="@{/src/views/database/DatabaseForm.js}"></script> <script th:src="@{/src/views/database/DatabaseForm.js}"></script>
<script th:src="@{/src/views/database/DatabaseTable.js}"></script> <script th:src="@{/src/views/database/DatabaseTable.js}"></script>
<script th:src="@{/src/components/AppGeneratorPage.js}"></script> <script th:src="@{/src/views/database/DatabaseGeneratorPage.js}"></script>
<script> <script>
const {createApp, ref} = Vue const {createApp, ref} = Vue
@ -143,7 +143,7 @@
if (!isValidate) return; if (!isValidate) return;
// 请求生成模板 // 请求生成模板
const {data, code, message} = await axiosInstance.post("/generator", this.form); const {data, code, message} = await axiosInstance.post("/vms/generator", this.form);
// 判断是否请求成功 // 判断是否请求成功
if (code !== 200) { if (code !== 200) {
@ -164,7 +164,6 @@
/* 清空已经生成的内容 */ /* 清空已经生成的内容 */
onClearGeneratorData() { onClearGeneratorData() {
this.generatorData = {}; this.generatorData = {};
antd.message.success("清除完成");
}, },
}, },
watch: { watch: {
@ -191,7 +190,7 @@
app.component('DatabaseCard', DatabaseCard); app.component('DatabaseCard', DatabaseCard);
app.component('DatabaseForm', DatabaseForm); app.component('DatabaseForm', DatabaseForm);
app.component('DatabaseTable', DatabaseTable); app.component('DatabaseTable', DatabaseTable);
app.component('AppGeneratorPage', AppGeneratorPage); app.component('DatabaseGeneratorPage', DatabaseGeneratorPage);
app.mount('#app'); app.mount('#app');
</script> </script>

View File

@ -32,16 +32,6 @@
<!-- 主标题 --> <!-- 主标题 -->
<app-header></app-header> <app-header></app-header>
<sql-form :generator-code-loading="generatorCodeLoading" :on-clear-generator-data="onClearGeneratorData"
:on-generator-code="onGeneratorCode" ref="mainFormRef" v-model:column-meta-list="columnMetaList"
v-model:form="form" v-model:table-info="tableInfo"></sql-form>
<!-- 数据库信息展示区域 -->
<sql-parser-info :column-meta-list="columnMetaList" :table-info="tableInfo"></sql-parser-info>
<!-- 模板生成页面 -->
<app-generator-page :generator-data="generatorData"
v-model:generator-page-flag="generatorPageFlag"></app-generator-page>
</div> </div>
</div> </div>
</body> </body>
@ -58,23 +48,22 @@
<!-- 引入组件 --> <!-- 引入组件 -->
<script th:src="@{/src/components/AppHeader.js}"></script> <script th:src="@{/src/components/AppHeader.js}"></script>
<script th:src="@{/src/views/sql/SqlForm.js}"></script>
<script th:src="@{/src/views/sql/SqlParserInfo.js}"></script>
<script th:src="@{/src/components/AppGeneratorPage.js}"></script>
<script> <script>
const {createApp, ref} = Vue const {createApp, ref} = Vue
const app = createApp({ const app = createApp({
setup() { setup() {
return { return {
// 是否正在生成 loading: ref(false),
generatorCodeLoading: ref(false),
// 提交的表单 // 提交的表单
form: ref({ form: ref({
// 作者名称 // 作者名称
author: "Bunny", author: "Bunny",
// requestMapping名称 // requestMapping名称
requestMapping: "/api", requestMapping: "/api",
// 表名称
tableNames: [],
// 包名称 // 包名称
packageName: "cn.bunny", packageName: "cn.bunny",
// 时间格式 // 时间格式
@ -83,62 +72,16 @@
tablePrefixes: "t_,sys_,qrtz_,log_", tablePrefixes: "t_,sys_,qrtz_,log_",
// 生成代码路径 // 生成代码路径
path: [], path: [],
sql: "",
}), }),
// 是否显示生成页面
generatorPageFlag: ref(false),
// 生成的数据 // 生成的数据
generatorData: ref({}), generatorData: ref({}),
// sql语句中表信息
tableInfo: ref({}),
// sql语句中列信息
columnMetaList: ref([]),
}; };
}, },
methods: { methods: {},
/**
* 生成代码
* @returns {Promise<void>}
*/
async onGeneratorCode() {
this.generatorCodeLoading = true;
// 验证表单是否通过
const isValidate = this.$refs.mainFormRef.validateForm();
if (!isValidate) return;
// 请求生成模板
const {data, code, message} = await axiosInstance.post("/generator", this.form);
// 判断是否请求成功
if (code !== 200) {
this.generatorCodeLoading = false;
return;
} else antd.message.success(message);
// 显示生成的页面
this.generatorData = data;
this.generatorPageFlag = true;
this.generatorCodeLoading = false;
// 等待 DOM 更新,之后手动更新代码高亮
await this.$nextTick();
hljs.highlightAll();
},
/* 清空已经生成的内容 */
onClearGeneratorData() {
this.generatorData = {};
antd.message.success("清除完成")
},
},
}); });
// 注册组件 // 注册组件
app.component('AppHeader', AppHeader); app.component('AppHeader', AppHeader);
app.component('SqlForm', SqlForm);
app.component('SqlParserInfo', SqlParserInfo);
app.component('AppGeneratorPage', AppGeneratorPage);
app.mount('#app'); app.mount('#app');
</script> </script>

Some files were not shown because too many files have changed in this diff Show More