Compare commits
8 Commits
4e438bea86
...
395ec89666
Author | SHA1 | Date |
---|---|---|
|
395ec89666 | |
|
d13451c1f9 | |
|
68f9cda260 | |
|
d6f783c481 | |
|
f1e8f48c73 | |
|
9baf682f63 | |
|
a38cdde759 | |
|
9ce378320b |
|
@ -1,212 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,102 +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.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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
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);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,88 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
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("-", "");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
// 如果一开始定义过了 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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,21 +0,0 @@
|
||||||
/* 提高 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%;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
package cn.bunny.controller;
|
||||||
|
|
||||||
|
import cn.bunny.domain.dto.VmsArgumentDto;
|
||||||
|
import cn.bunny.domain.result.Result;
|
||||||
|
import cn.bunny.domain.vo.GeneratorVo;
|
||||||
|
import cn.bunny.service.GeneratorService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.logging.log4j.util.Strings;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码生成器控制器
|
||||||
|
* 提供代码生成和打包下载功能
|
||||||
|
*/
|
||||||
|
@Tag(name = "生成模板", description = "生成模板接口")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/generator")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class GeneratorController {
|
||||||
|
|
||||||
|
private final GeneratorService generatorService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成代码
|
||||||
|
*
|
||||||
|
* @param dto 生成参数DTO
|
||||||
|
* @return 生成的代码结果,按表名分组
|
||||||
|
*/
|
||||||
|
@Operation(summary = "生成代码", description = "根据SQL或数据库表生成代码")
|
||||||
|
@PostMapping
|
||||||
|
public Result<Map<String, List<GeneratorVo>>> generator(@Valid @RequestBody VmsArgumentDto dto) {
|
||||||
|
Map<String, List<GeneratorVo>> result = Strings.isEmpty(dto.getSql())
|
||||||
|
? generatorService.generateCodeByDatabase(dto)
|
||||||
|
: generatorService.generateCodeBySql(dto);
|
||||||
|
return Result.success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打包代码为ZIP下载
|
||||||
|
*
|
||||||
|
* @param dto 生成参数DTO
|
||||||
|
* @return ZIP文件响应实体
|
||||||
|
*/
|
||||||
|
@Operation(summary = "打包下载", description = "将生成的代码打包为ZIP文件下载")
|
||||||
|
@PostMapping("downloadByZip")
|
||||||
|
public ResponseEntity<byte[]> downloadByZip(@Valid @RequestBody VmsArgumentDto dto) {
|
||||||
|
return Strings.isEmpty(dto.getSql())
|
||||||
|
? generatorService.downloadByZipByDatabase(dto)
|
||||||
|
: generatorService.downloadByZipBySqL(dto);
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,26 +13,39 @@ 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 sqlParserService;
|
private final SqlMetadataProvider sqlMetadataProvider;
|
||||||
|
|
||||||
@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) {
|
||||||
TableMetaData vo = sqlParserService.getTableMetadata(sql);
|
return Result.success(sqlMetadataProvider.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) {
|
||||||
List<ColumnMetaData> vo = sqlParserService.getColumnInfoList(sql);
|
return Result.success(sqlMetadataProvider.getColumnInfoList(sql));
|
||||||
return Result.success(vo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,16 +1,14 @@
|
||||||
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 jakarta.validation.Valid;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -30,17 +28,4 @@ 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -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 cn.bunny.domain.vo.GeneratorVo;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
@ -8,7 +7,6 @@ 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;
|
||||||
|
@ -20,28 +18,23 @@ import java.util.zip.ZipOutputStream;
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class VmsZipService {
|
public class ZipFileService {
|
||||||
|
|
||||||
private final VmsCodeGeneratorService codeGeneratorService;
|
private static final String FILE_EXTENSION = ".vm";
|
||||||
|
private static final String UTF_8 = StandardCharsets.UTF_8.name();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将生成的代码模板打包为ZIP文件
|
* 创建ZIP文件
|
||||||
*
|
*
|
||||||
* @param dto 代码生成参数DTO,包含表名、包名等配置
|
* @param generatorVoList 生成的代码列表
|
||||||
* @return ZIP文件字节数组,可直接用于下载
|
* @return ZIP文件字节数组
|
||||||
* @throws RuntimeException 当ZIP打包过程中发生IO异常时抛出
|
* @throws RuntimeException 打包失败时抛出
|
||||||
*/
|
*/
|
||||||
public byte[] createZipFile(VmsArgumentDto dto) {
|
public byte[] createZipFile(List<GeneratorVo> generatorVoList) {
|
||||||
// 将二维代码生成结果扁平化为一维列表
|
|
||||||
List<GeneratorVo> generatorVoList = codeGeneratorService.generateCode(dto).values().stream()
|
|
||||||
.flatMap(Collection::stream)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||||
ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
|
ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream, StandardCharsets.UTF_8)) {
|
||||||
|
|
||||||
// 将所有生成的文件添加到ZIP包中
|
generatorVoList.forEach(vo -> addToZip(zipOutputStream, vo));
|
||||||
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);
|
||||||
|
@ -49,7 +42,7 @@ public class VmsZipService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将单个代码文件添加到ZIP输出流
|
* 添加文件到ZIP 将单个代码文件添加到ZIP输出流
|
||||||
*
|
*
|
||||||
* @param zipOutputStream ZIP文件输出流
|
* @param zipOutputStream ZIP文件输出流
|
||||||
* @param generatorVo 代码生成结果对象,包含文件路径和内容
|
* @param generatorVo 代码生成结果对象,包含文件路径和内容
|
||||||
|
@ -57,13 +50,9 @@ public class VmsZipService {
|
||||||
*/
|
*/
|
||||||
private void addToZip(ZipOutputStream zipOutputStream, GeneratorVo generatorVo) {
|
private void addToZip(ZipOutputStream zipOutputStream, GeneratorVo generatorVo) {
|
||||||
try {
|
try {
|
||||||
// 标准化文件路径:移除Velocity模板扩展名
|
String entryPath = generatorVo.getPath().replace(FILE_EXTENSION, "");
|
||||||
String path = generatorVo.getPath().replace(".vm", "");
|
zipOutputStream.putNextEntry(new ZipEntry(entryPath));
|
||||||
|
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);
|
|
@ -0,0 +1,235 @@
|
||||||
|
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 com.zaxxer.hikari.HikariDataSource;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
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 {
|
||||||
|
|
||||||
|
private final HikariDataSource dataSource;
|
||||||
|
|
||||||
|
@Value("${bunny.master.database}")
|
||||||
|
private String currentDatabase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据表名标识符获取单个表的元数据信息
|
||||||
|
*
|
||||||
|
* @param tableName 要查询的表名(大小写敏感度取决于数据库实现)
|
||||||
|
* @return TableMetaData 包含表元数据的对象,包括:
|
||||||
|
* - 表名
|
||||||
|
* - 表注释/备注
|
||||||
|
* - 表所属目录(通常是数据库名)
|
||||||
|
* - 表类型(通常为"TABLE")
|
||||||
|
* @throws MetadataNotFoundException 当指定的表不存在时抛出
|
||||||
|
* @throws GeneratorCodeException 当查询过程中发生其他异常时抛出
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public TableMetaData getTableMetadata(String tableName) {
|
||||||
|
try (Connection connection = dataSource.getConnection()) {
|
||||||
|
DatabaseMetaData metaData = connection.getMetaData();
|
||||||
|
ResultSet tables = metaData.getTables(null, null, tableName, new String[]{"TABLE"});
|
||||||
|
|
||||||
|
if (tables.next()) {
|
||||||
|
return TableMetaData.builder()
|
||||||
|
.tableName(tableName)
|
||||||
|
.comment(tables.getString("REMARKS"))
|
||||||
|
.tableCat(tables.getString("TABLE_CAT"))
|
||||||
|
.tableType(tables.getString("TABLE_TYPE"))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
throw new MetadataNotFoundException("Table not found: " + tableName);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new GeneratorCodeException("Failed to get metadata for table: " + tableName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定表的所有列信息列表
|
||||||
|
*
|
||||||
|
* @param tableName 要查询的表名(大小写敏感度取决于数据库实现)
|
||||||
|
* @return 包含所有列元数据的列表,每个列的信息封装在ColumnMetaData对象中
|
||||||
|
* @throws MetadataProviderException 当获取列元数据过程中发生异常时抛出
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<ColumnMetaData> getColumnInfoList(String tableName) {
|
||||||
|
try (Connection connection = dataSource.getConnection()) {
|
||||||
|
Set<String> primaryKeys = getPrimaryKeys(tableName);
|
||||||
|
DatabaseMetaData metaData = connection.getMetaData();
|
||||||
|
|
||||||
|
return getColumnMetaData(metaData, tableName, primaryKeys);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new GeneratorCodeException("Failed to get column info for table: " + tableName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取表的所有主键列名 获取表的主键列名集合
|
||||||
|
*
|
||||||
|
* @param tableName 表名
|
||||||
|
* @return 主键列名的集合
|
||||||
|
*/
|
||||||
|
private Set<String> getPrimaryKeys(String tableName) {
|
||||||
|
Set<String> primaryKeys = new HashSet<>();
|
||||||
|
try (Connection connection = dataSource.getConnection()) {
|
||||||
|
ResultSet pkResultSet = connection.getMetaData().getPrimaryKeys(null, null, tableName);
|
||||||
|
while (pkResultSet.next()) {
|
||||||
|
primaryKeys.add(pkResultSet.getString("COLUMN_NAME").toLowerCase());
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new GeneratorCodeException("Get primary key error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
return primaryKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取列元数据列表
|
||||||
|
*/
|
||||||
|
private List<ColumnMetaData> getColumnMetaData(DatabaseMetaData metaData, String tableName, Set<String> primaryKeys) throws SQLException {
|
||||||
|
Map<String, ColumnMetaData> columnMap = new LinkedHashMap<>();
|
||||||
|
try (ResultSet columnsRs = metaData.getColumns(null, null, tableName, null)) {
|
||||||
|
while (columnsRs.next()) {
|
||||||
|
ColumnMetaData column = buildColumnMetaData(columnsRs, primaryKeys);
|
||||||
|
columnMap.putIfAbsent(column.getColumnName(), column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return columnMap.values().stream().distinct().collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建列元数据对象
|
||||||
|
*/
|
||||||
|
private ColumnMetaData buildColumnMetaData(ResultSet columnsRs, Set<String> primaryKeys) throws SQLException {
|
||||||
|
String columnName = columnsRs.getString("COLUMN_NAME");
|
||||||
|
String typeName = columnsRs.getString("TYPE_NAME");
|
||||||
|
|
||||||
|
ColumnMetaData column = new ColumnMetaData();
|
||||||
|
column.setColumnName(columnName);
|
||||||
|
column.setLowercaseName(MysqlTypeConvertUtil.convertToCamelCase(columnName, false));
|
||||||
|
column.setUppercaseName(MysqlTypeConvertUtil.convertToCamelCase(columnName, true));
|
||||||
|
column.setJdbcType(typeName);
|
||||||
|
column.setJavaType(MysqlTypeConvertUtil.convertToJavaType(typeName));
|
||||||
|
column.setJavascriptType(StringUtils.uncapitalize(column.getJavaType()));
|
||||||
|
column.setComment(columnsRs.getString("REMARKS"));
|
||||||
|
column.setIsPrimaryKey(primaryKeys.contains(columnName));
|
||||||
|
|
||||||
|
return column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取数据库的元数据信息
|
||||||
|
*
|
||||||
|
* @return DatabaseInfoMetaData 包含数据库基本信息的对象,包括:
|
||||||
|
* - 数据库产品名称和版本
|
||||||
|
* - JDBC驱动名称和版本
|
||||||
|
* - 连接URL
|
||||||
|
* - 用户名
|
||||||
|
* - 当前数据库名称
|
||||||
|
* @throws GeneratorCodeException 如果获取数据库信息时发生SQL异常
|
||||||
|
*/
|
||||||
|
public DatabaseInfoMetaData databaseInfoMetaData() {
|
||||||
|
// 使用try-with-resources确保Connection自动关闭
|
||||||
|
try (Connection connection = dataSource.getConnection()) {
|
||||||
|
// 获取数据库的元数据对象
|
||||||
|
DatabaseMetaData metaData = connection.getMetaData();
|
||||||
|
|
||||||
|
// 使用Builder模式构建数据库信息对象
|
||||||
|
return DatabaseInfoMetaData.builder()
|
||||||
|
// 数据库产品名称(如MySQL, Oracle等)
|
||||||
|
.databaseProductName(metaData.getDatabaseProductName())
|
||||||
|
// 数据库产品版本号
|
||||||
|
.databaseProductVersion(metaData.getDatabaseProductVersion())
|
||||||
|
// JDBC驱动名称
|
||||||
|
.driverName(metaData.getDriverName())
|
||||||
|
// JDBC驱动版本
|
||||||
|
.driverVersion(metaData.getDriverVersion())
|
||||||
|
// 数据库连接URL
|
||||||
|
.url(metaData.getURL())
|
||||||
|
// 连接使用的用户名
|
||||||
|
.username(metaData.getUserName())
|
||||||
|
// 当前使用的数据库名称(由类成员变量提供)
|
||||||
|
.currentDatabase(currentDatabase)
|
||||||
|
.build();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// 将SQL异常转换为自定义的业务异常
|
||||||
|
throw new GeneratorCodeException("Get database info error:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量获取指定数据库中所有表的元数据信息
|
||||||
|
* 获取指定数据库中的所有表信息
|
||||||
|
*
|
||||||
|
* @param dbName 数据库名称
|
||||||
|
* @return 包含所有表元数据的列表,每个表的信息封装在TableMetaData对象中
|
||||||
|
* @throws MetadataProviderException 如果获取元数据过程中发生异常
|
||||||
|
*/
|
||||||
|
public List<TableMetaData> getTableMetadataBatch(String dbName) {
|
||||||
|
// 初始化返回结果列表
|
||||||
|
List<TableMetaData> allTableInfo = new ArrayList<>();
|
||||||
|
|
||||||
|
// 使用try-with-resources确保Connection资源自动关闭
|
||||||
|
try (Connection conn = dataSource.getConnection()) {
|
||||||
|
// 获取数据库的元数据对象
|
||||||
|
DatabaseMetaData metaData = conn.getMetaData();
|
||||||
|
|
||||||
|
/*
|
||||||
|
参数说明:
|
||||||
|
1. dbName - 数据库/目录名称,null表示忽略
|
||||||
|
2. null - 模式/模式名称,null表示忽略
|
||||||
|
3. "%" - 表名称模式,"%"表示所有表
|
||||||
|
4. new String[]{"TABLE"} - 类型数组,这里只查询普通表
|
||||||
|
*/
|
||||||
|
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"); // 表类型(这里应该是"TABLE")
|
||||||
|
|
||||||
|
// 使用Builder模式创建TableMetaData对象并设置属性
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -26,18 +26,18 @@ public class SqlMetadataProvider implements IMetadataProvider {
|
||||||
* 解析 sql 表信息
|
* 解析 sql 表信息
|
||||||
* 先解析SQL语句,解析列字段信息
|
* 先解析SQL语句,解析列字段信息
|
||||||
*
|
*
|
||||||
* @param identifier 表名称或sql
|
* @param sqlStatement sql语句
|
||||||
* @return 表西悉尼
|
* @return 表西悉尼
|
||||||
* @see CCJSqlParserUtil 使用这个工具进行SQL的解析
|
* @see CCJSqlParserUtil 使用这个工具进行SQL的解析
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public TableMetaData getTableMetadata(String identifier) {
|
public TableMetaData getTableMetadata(String sqlStatement) {
|
||||||
TableMetaData tableInfo = new TableMetaData();
|
TableMetaData tableInfo = new TableMetaData();
|
||||||
|
|
||||||
// 解析sql
|
// 解析sql
|
||||||
Statement statement;
|
Statement statement;
|
||||||
try {
|
try {
|
||||||
statement = CCJSqlParserUtil.parse(identifier);
|
statement = CCJSqlParserUtil.parse(sqlStatement);
|
||||||
} catch (JSQLParserException e) {
|
} catch (JSQLParserException e) {
|
||||||
throw new GeneratorCodeException("SQL解析失败");
|
throw new GeneratorCodeException("SQL解析失败");
|
||||||
}
|
}
|
||||||
|
@ -62,15 +62,15 @@ public class SqlMetadataProvider implements IMetadataProvider {
|
||||||
/**
|
/**
|
||||||
* 获取当前表的列属性
|
* 获取当前表的列属性
|
||||||
*
|
*
|
||||||
* @param identifier 表名称或sql
|
* @param sqlStatement sql语句
|
||||||
* @return 当前表所有的列内容
|
* @return 当前表所有的列内容
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<ColumnMetaData> getColumnInfoList(String identifier) {
|
public List<ColumnMetaData> getColumnInfoList(String sqlStatement) {
|
||||||
// 解析sql
|
// 解析sql
|
||||||
Statement statement;
|
Statement statement;
|
||||||
try {
|
try {
|
||||||
statement = CCJSqlParserUtil.parse(identifier);
|
statement = CCJSqlParserUtil.parse(sqlStatement);
|
||||||
} catch (JSQLParserException e) {
|
} catch (JSQLParserException e) {
|
||||||
throw new SqlParseException("Fail parse sql", e.getCause());
|
throw new SqlParseException("Fail parse sql", e.getCause());
|
||||||
}
|
}
|
||||||
|
@ -99,9 +99,12 @@ public class SqlMetadataProvider implements IMetadataProvider {
|
||||||
columnInfo.setJavascriptType(StringUtils.uncapitalize(javaType));
|
columnInfo.setJavascriptType(StringUtils.uncapitalize(javaType));
|
||||||
|
|
||||||
// 列字段转成 下划线 -> 小驼峰
|
// 列字段转成 下划线 -> 小驼峰
|
||||||
columnInfo.setLowercaseName(MysqlTypeConvertUtil.convertToCamelCase(column.getColumnName()));
|
String lowercaseName = MysqlTypeConvertUtil.convertToCamelCase(column.getColumnName(), false);
|
||||||
|
columnInfo.setLowercaseName(lowercaseName);
|
||||||
|
|
||||||
// 列字段转成 下划线 -> 大驼峰名称
|
// 列字段转成 下划线 -> 大驼峰名称
|
||||||
columnInfo.setUppercaseName(MysqlTypeConvertUtil.convertToCamelCase(column.getColumnName(), true));
|
String uppercaseName = MysqlTypeConvertUtil.convertToCamelCase(column.getColumnName(), true);
|
||||||
|
columnInfo.setUppercaseName(uppercaseName);
|
||||||
|
|
||||||
// 解析注释
|
// 解析注释
|
||||||
List<String> columnSpecs = column.getColumnSpecs();
|
List<String> columnSpecs = column.getColumnSpecs();
|
|
@ -0,0 +1,69 @@
|
||||||
|
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;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模板生成抽象基类
|
||||||
|
* 定义代码生成的模板方法流程
|
||||||
|
*/
|
||||||
|
public abstract class AbstractTemplateGenerator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成代码模板
|
||||||
|
*
|
||||||
|
* @param tableMeta 表元数据
|
||||||
|
* @param columns 列信息列表
|
||||||
|
* @return 生成的代码内容
|
||||||
|
*/
|
||||||
|
public StringWriter generateCode(TableMetaData tableMeta, List<ColumnMetaData> columns) {
|
||||||
|
VelocityContext context = new VelocityContext();
|
||||||
|
prepareVelocityContext(context, tableMeta, columns);
|
||||||
|
return mergeTemplate(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 准备Velocity上下文数据
|
||||||
|
*/
|
||||||
|
private void prepareVelocityContext(VelocityContext context, TableMetaData tableMeta, List<ColumnMetaData> columns) {
|
||||||
|
context.put("leftBrace", "{");
|
||||||
|
context.put("tableName", tableMeta.getTableName());
|
||||||
|
context.put("columnInfoList", columns);
|
||||||
|
context.put("baseColumnList", getDistinctColumnNames(columns));
|
||||||
|
addContext(context); // 子类可扩展
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取去重的列名列表
|
||||||
|
*/
|
||||||
|
private String getDistinctColumnNames(List<ColumnMetaData> columns) {
|
||||||
|
return columns.stream()
|
||||||
|
.map(ColumnMetaData::getColumnName)
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.joining(","));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合并Velocity模板
|
||||||
|
*/
|
||||||
|
private StringWriter mergeTemplate(VelocityContext context) {
|
||||||
|
StringWriter writer = new StringWriter();
|
||||||
|
templateMerge(context, writer);
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加生成内容(由子类实现)
|
||||||
|
*/
|
||||||
|
protected abstract void addContext(VelocityContext context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模板合并(由子类实现)
|
||||||
|
*/
|
||||||
|
protected abstract void templateMerge(VelocityContext context, StringWriter writer);
|
||||||
|
}
|
|
@ -38,7 +38,7 @@ public class VmsTBaseTemplateGenerator extends AbstractTemplateGenerator {
|
||||||
* @param context VelocityContext
|
* @param context VelocityContext
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
void addContext(VelocityContext context) {
|
public void addContext(VelocityContext context) {
|
||||||
// 当前的表名
|
// 当前的表名
|
||||||
String tableName = tableMetaData.getTableName();
|
String tableName = tableMetaData.getTableName();
|
||||||
// 表的注释内容
|
// 表的注释内容
|
||||||
|
@ -61,12 +61,12 @@ public class VmsTBaseTemplateGenerator extends AbstractTemplateGenerator {
|
||||||
context.put("package", dto.getPackageName());
|
context.put("package", dto.getPackageName());
|
||||||
|
|
||||||
// 将类名称转成小驼峰
|
// 将类名称转成小驼峰
|
||||||
String toCamelCase = MysqlTypeConvertUtil.convertToCamelCase(tableName);
|
String lowerCamelCase = MysqlTypeConvertUtil.convertToCamelCase(tableName, false);
|
||||||
context.put("classLowercaseName", toCamelCase);
|
context.put("classLowercaseName", lowerCamelCase);
|
||||||
|
|
||||||
// 将类名称转成大驼峰
|
// 将类名称转成大驼峰
|
||||||
String convertToCamelCase = MysqlTypeConvertUtil.convertToCamelCase(tableName, true);
|
String upperCameCase = MysqlTypeConvertUtil.convertToCamelCase(tableName, true);
|
||||||
context.put("classUppercaseName", convertToCamelCase);
|
context.put("classUppercaseName", upperCameCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -76,7 +76,7 @@ public class VmsTBaseTemplateGenerator extends AbstractTemplateGenerator {
|
||||||
* @param writer StringWriter 写入
|
* @param writer StringWriter 写入
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
void templateMerge(VelocityContext context, StringWriter writer) {
|
public void templateMerge(VelocityContext context, StringWriter writer) {
|
||||||
Template servicePathTemplate = Velocity.getTemplate("vms/" + path, "UTF-8");
|
Template servicePathTemplate = Velocity.getTemplate("vms/" + path, "UTF-8");
|
||||||
servicePathTemplate.merge(context, writer);
|
servicePathTemplate.merge(context, writer);
|
||||||
}
|
}
|
|
@ -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;
|
||||||
|
|
||||||
@NotNull(message = "表名称不能为空")
|
@Schema(name = "tableNames", description = "表名列表")
|
||||||
@NotEmpty(message = "表名称不能为空")
|
|
||||||
private List<String> tableNames;
|
private List<String> tableNames;
|
||||||
|
|
||||||
@Schema(name = "simpleDateFormat", description = "时间格式")
|
@Schema(name = "simpleDateFormat", description = "时间格式")
|
||||||
|
@ -37,6 +37,7 @@ 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 语句")
|
|
@ -0,0 +1,42 @@
|
||||||
|
package cn.bunny.domain.entity;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Schema(name = "DatabaseInfoMetaData", description = "数据库信息")
|
||||||
|
public class DatabaseInfoMetaData {
|
||||||
|
|
||||||
|
@Schema(name = "databaseList", description = "数据库所有的数据库")
|
||||||
|
List<TableMetaData> databaseList;
|
||||||
|
|
||||||
|
@Schema(name = "databaseProductName", description = "数据库产品名称")
|
||||||
|
private String databaseProductName;
|
||||||
|
|
||||||
|
@Schema(name = "databaseProductVersion", description = "数据库产品版本")
|
||||||
|
private String databaseProductVersion;
|
||||||
|
|
||||||
|
@Schema(name = "driverName", description = "驱动名称")
|
||||||
|
private String driverName;
|
||||||
|
|
||||||
|
@Schema(name = "driverVersion", description = "数据库驱动版本")
|
||||||
|
private String driverVersion;
|
||||||
|
|
||||||
|
@Schema(name = "url", description = "数据链接url")
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
@Schema(name = "username", description = "数据库用户")
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@Schema(name = "currentDatabase", description = "当前数据库名称")
|
||||||
|
private String currentDatabase;
|
||||||
|
|
||||||
|
}
|
|
@ -37,4 +37,10 @@ public class GeneratorCodeException extends RuntimeException {
|
||||||
this.message = codeEnum.getMessage();
|
this.message = codeEnum.getMessage();
|
||||||
this.resultCodeEnum = codeEnum;
|
this.resultCodeEnum = codeEnum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GeneratorCodeException(String message, Exception exception) {
|
||||||
|
super(message);
|
||||||
|
this.message = message;
|
||||||
|
log.error(message, exception);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
package cn.bunny.service;
|
||||||
|
|
||||||
|
import cn.bunny.domain.dto.VmsArgumentDto;
|
||||||
|
import cn.bunny.domain.vo.GeneratorVo;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码生成服务接口
|
||||||
|
* 提供基于数据库和SQL的代码生成功能
|
||||||
|
*/
|
||||||
|
public interface GeneratorService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据数据库表生成代码
|
||||||
|
*
|
||||||
|
* @param dto 生成参数
|
||||||
|
* @return 生成的代码,按表名分组
|
||||||
|
*/
|
||||||
|
Map<String, List<GeneratorVo>> generateCodeByDatabase(VmsArgumentDto dto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据SQL语句生成代码
|
||||||
|
*
|
||||||
|
* @param dto 生成参数
|
||||||
|
* @return 生成的代码,按表名分组
|
||||||
|
*/
|
||||||
|
Map<String, List<GeneratorVo>> generateCodeBySql(VmsArgumentDto dto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打包数据库生成的代码为ZIP下载
|
||||||
|
*
|
||||||
|
* @param dto 生成参数
|
||||||
|
* @return ZIP文件响应实体
|
||||||
|
*/
|
||||||
|
ResponseEntity<byte[]> downloadByZipByDatabase(@Valid VmsArgumentDto dto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打包SQL生成的代码为ZIP下载
|
||||||
|
*
|
||||||
|
* @param dto 生成参数
|
||||||
|
* @return ZIP文件响应实体
|
||||||
|
*/
|
||||||
|
ResponseEntity<byte[]> downloadByZipBySqL(@Valid VmsArgumentDto dto);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package cn.bunny.service;
|
||||||
|
|
||||||
|
import cn.bunny.domain.vo.VmsPathVo;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public interface VmsService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取vms文件路径
|
||||||
|
*
|
||||||
|
* @return vms下的文件路径
|
||||||
|
*/
|
||||||
|
Map<String, List<VmsPathVo>> vmsResourcePathList();
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
package cn.bunny.service.helper;
|
||||||
|
|
||||||
|
import cn.bunny.core.template.VmsTBaseTemplateGenerator;
|
||||||
|
import cn.bunny.domain.dto.VmsArgumentDto;
|
||||||
|
import cn.bunny.domain.entity.ColumnMetaData;
|
||||||
|
import cn.bunny.domain.entity.TableMetaData;
|
||||||
|
import cn.bunny.domain.vo.GeneratorVo;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码生成服务辅助类
|
||||||
|
* 提供生成器和响应实体的构建方法
|
||||||
|
*/
|
||||||
|
public class GeneratorServiceImplHelper {
|
||||||
|
|
||||||
|
private static final String ZIP_FILE_PREFIX = "code-";
|
||||||
|
private static final String ZIP_FILE_SUFFIX = ".zip";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取生成器流
|
||||||
|
*
|
||||||
|
* @param dto 生成参数
|
||||||
|
* @param tableMeta 表元数据
|
||||||
|
* @param columns 列信息
|
||||||
|
* @return 生成器流
|
||||||
|
*/
|
||||||
|
public static Stream<GeneratorVo> getGeneratorStream(VmsArgumentDto dto, TableMetaData tableMeta, List<ColumnMetaData> columns) {
|
||||||
|
return dto.getPath().parallelStream().map(path -> {
|
||||||
|
VmsTBaseTemplateGenerator generator = new VmsTBaseTemplateGenerator(dto, path, tableMeta);
|
||||||
|
String code = generator.generateCode(tableMeta, columns).toString();
|
||||||
|
|
||||||
|
return GeneratorVo.builder()
|
||||||
|
.id(UUID.randomUUID().toString())
|
||||||
|
.code(code)
|
||||||
|
.comment(tableMeta.getComment())
|
||||||
|
.tableName(tableMeta.getTableName())
|
||||||
|
.path(VmsGeneratorPathHelper.processVmPath(dto, path, tableMeta.getTableName()))
|
||||||
|
.build();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建ZIP下载响应实体
|
||||||
|
*
|
||||||
|
* @param zipBytes ZIP文件字节数组
|
||||||
|
* @return 响应实体
|
||||||
|
*/
|
||||||
|
public static ResponseEntity<byte[]> getResponseEntity(byte[] zipBytes) {
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.add("Content-Disposition", "attachment; filename=" + generateZipFilename());
|
||||||
|
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||||
|
headers.add("Pragma", "no-cache");
|
||||||
|
headers.add("Expires", "0");
|
||||||
|
|
||||||
|
return new ResponseEntity<>(zipBytes, headers, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成ZIP文件名
|
||||||
|
*/
|
||||||
|
private static String generateZipFilename() {
|
||||||
|
return ZIP_FILE_PREFIX + UUID.randomUUID().toString().split("-")[0] + ZIP_FILE_SUFFIX;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码生成工具类
|
||||||
|
*/
|
||||||
|
public class VmsGeneratorPathHelper {
|
||||||
|
private static final Map<String, String> FILE_TYPE_SUFFIXES = Map.of(
|
||||||
|
"controller", "Controller",
|
||||||
|
"service", "Service",
|
||||||
|
"serviceImpl", "ServiceImpl",
|
||||||
|
"mapper", "Mapper",
|
||||||
|
"resourceMapper", "Mapper",
|
||||||
|
"dto", "Dto",
|
||||||
|
"vo", "Vo"
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理模板文件路径和命名
|
||||||
|
*
|
||||||
|
* @param dto 生成参数
|
||||||
|
* @param path 原始模板路径
|
||||||
|
* @param tableName 数据库表名
|
||||||
|
* @return 处理后的文件路径
|
||||||
|
*/
|
||||||
|
public static String processVmPath(VmsArgumentDto dto, String path, String tableName) {
|
||||||
|
String className = removeTablePrefixes(dto, tableName);
|
||||||
|
String lowerCamelCase = MysqlTypeConvertUtil.convertToCamelCase(tableName, false);
|
||||||
|
String[] pathParts = path.replace("$className", lowerCamelCase).split("/");
|
||||||
|
|
||||||
|
// 处理文件名
|
||||||
|
pathParts[pathParts.length - 1] = processFilename(
|
||||||
|
pathParts[pathParts.length - 1],
|
||||||
|
className
|
||||||
|
);
|
||||||
|
|
||||||
|
return String.join("/", pathParts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除表前缀
|
||||||
|
*/
|
||||||
|
private static String removeTablePrefixes(VmsArgumentDto dto, String tableName) {
|
||||||
|
String[] prefixes = dto.getTablePrefixes().split("[,,]");
|
||||||
|
for (String prefix : prefixes) {
|
||||||
|
if (tableName.startsWith(prefix)) {
|
||||||
|
return tableName.substring(prefix.length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tableName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理文件名生成
|
||||||
|
*/
|
||||||
|
private static String processFilename(String filename, String tableName) {
|
||||||
|
filename = filename.replace(".vm", "");
|
||||||
|
String[] parts = filename.split("\\.");
|
||||||
|
String baseName = parts[0];
|
||||||
|
String extension = parts.length > 1 ? parts[1] : "";
|
||||||
|
|
||||||
|
String upperCamelCase = MysqlTypeConvertUtil.convertToCamelCase(tableName, true);
|
||||||
|
String lowerCamelCase = MysqlTypeConvertUtil.convertToCamelCase(tableName, false);
|
||||||
|
|
||||||
|
// 如果包含Java和xml需要进行处理
|
||||||
|
if (filename.contains("java") || filename.contains("xml")) {
|
||||||
|
return upperCamelCase + FILE_TYPE_SUFFIXES.getOrDefault(baseName, "") + "." + extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filename.equals("api.ts") || filename.equals("store.ts")) {
|
||||||
|
return lowerCamelCase + ".ts";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filename.equals("dialog.vue")) {
|
||||||
|
return CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, lowerCamelCase) + "-dialog.vue";
|
||||||
|
}
|
||||||
|
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
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.HashMap;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码生成方法---数据库生成
|
||||||
|
*
|
||||||
|
* @param dto 生成参数
|
||||||
|
* @return 生成的代码,按表名分组
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Map<String, List<GeneratorVo>> generateCodeByDatabase(VmsArgumentDto dto) {
|
||||||
|
return dto.getTableNames().parallelStream()
|
||||||
|
.flatMap(tableName -> {
|
||||||
|
TableMetaData tableMeta = databaseMetadataProvider.getTableMetadata(tableName);
|
||||||
|
List<ColumnMetaData> columns = databaseMetadataProvider.getColumnInfoList(tableName);
|
||||||
|
return GeneratorServiceImplHelper.getGeneratorStream(dto, tableMeta, columns);
|
||||||
|
})
|
||||||
|
.collect(Collectors.groupingBy(GeneratorVo::getTableName));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码生成方法---Sql语句生成
|
||||||
|
*
|
||||||
|
* @param dto 生成参数
|
||||||
|
* @return 生成的代码,按表名分组
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Map<String, List<GeneratorVo>> generateCodeBySql(VmsArgumentDto dto) {
|
||||||
|
String sql = dto.getSql();
|
||||||
|
TableMetaData tableMeta = sqlMetadataProvider.getTableMetadata(sql);
|
||||||
|
List<ColumnMetaData> columns = sqlMetadataProvider.getColumnInfoList(sql);
|
||||||
|
|
||||||
|
List<GeneratorVo> generatorVoList = GeneratorServiceImplHelper.getGeneratorStream(dto, tableMeta, columns).toList();
|
||||||
|
|
||||||
|
Map<String, List<GeneratorVo>> map = new HashMap<>();
|
||||||
|
map.put(tableMeta.getTableName(), generatorVoList);
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<byte[]> downloadByZipByDatabase(VmsArgumentDto dto) {
|
||||||
|
return downloadByZip(dto, this::generateCodeByDatabase);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<byte[]> downloadByZipBySqL(VmsArgumentDto dto) {
|
||||||
|
return downloadByZip(dto, this::generateCodeBySql);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用ZIP打包下载方法
|
||||||
|
*
|
||||||
|
* @param dto 生成参数
|
||||||
|
* @param generator 代码生成函数
|
||||||
|
* @return ZIP文件响应实体
|
||||||
|
*/
|
||||||
|
private ResponseEntity<byte[]> downloadByZip(VmsArgumentDto dto,
|
||||||
|
Function<VmsArgumentDto, Map<String, List<GeneratorVo>>> generator) {
|
||||||
|
List<GeneratorVo> generatorVoList = generator.apply(dto)
|
||||||
|
.values().stream()
|
||||||
|
.flatMap(Collection::stream)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
byte[] zipBytes = zipFileService.createZipFile(generatorVoList);
|
||||||
|
return GeneratorServiceImplHelper.getResponseEntity(zipBytes);
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,7 +33,8 @@ 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);
|
|
@ -0,0 +1,65 @@
|
||||||
|
package cn.bunny.service.impl;
|
||||||
|
|
||||||
|
import cn.bunny.domain.vo.VmsPathVo;
|
||||||
|
import cn.bunny.service.VmsService;
|
||||||
|
import cn.bunny.utils.ResourceFileUtil;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VMS服务主实现类,负责协调各子服务完成代码生成、资源管理和打包下载功能
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class VmsServiceImpl implements VmsService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取VMS资源文件路径列表并按类型分组
|
||||||
|
*
|
||||||
|
* @return 按类型分组的VMS路径Map,key为类型,value为对应类型的VMS路径列表
|
||||||
|
* @throws RuntimeException 当获取资源路径失败时抛出
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Map<String, List<VmsPathVo>> vmsResourcePathList() {
|
||||||
|
try {
|
||||||
|
// 1. 获取vms目录下所有相对路径文件列表
|
||||||
|
List<String> vmsRelativeFiles = ResourceFileUtil.getRelativeFiles("vms");
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -29,13 +29,6 @@ public class MysqlTypeConvertUtil {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 下划线命名转驼峰命名
|
|
||||||
*/
|
|
||||||
public static String convertToCamelCase(String name) {
|
|
||||||
return convertToCamelCase(name, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下划线命名转驼峰命名
|
* 下划线命名转驼峰命名
|
||||||
*
|
*
|
||||||
|
@ -51,9 +44,7 @@ public class MysqlTypeConvertUtil {
|
||||||
String lowerCamelCase = CaseFormatUtils.toCamelCase(name);
|
String lowerCamelCase = CaseFormatUtils.toCamelCase(name);
|
||||||
|
|
||||||
// 首字母不大写
|
// 首字母不大写
|
||||||
if (!firstLetterCapital) {
|
if (!firstLetterCapital) return lowerCamelCase;
|
||||||
return lowerCamelCase;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将小驼峰转成大驼峰
|
// 将小驼峰转成大驼峰
|
||||||
return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, lowerCamelCase);
|
return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, lowerCamelCase);
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
|
@ -1,4 +1,4 @@
|
||||||
const DatabaseGeneratorPage = defineComponent({
|
const AppGeneratorPage = 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 DatabaseGeneratorPage = 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: 90%">
|
<span class="text-truncate" style="max-width: 99%" :title="item.path">
|
||||||
【{{item.comment}}】{{item.path}}
|
【{{item.comment}}】{{item.path}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
|
@ -0,0 +1,54 @@
|
||||||
|
// 如果一开始定义过了 defineComponent 就不要在下 script 标签中再写了
|
||||||
|
const {defineComponent} = Vue;
|
||||||
|
|
||||||
|
// 定义 Header 组件
|
||||||
|
const AppHeader = defineComponent({
|
||||||
|
name: "AppHeader",
|
||||||
|
template: `
|
||||||
|
<header>
|
||||||
|
<!-- 头部 -->
|
||||||
|
<div class="header-content text-center mb-2 p-2 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>
|
||||||
|
|
||||||
|
<!-- 代码仓库链接区域:包含GitHub和Gitee仓库链接 -->
|
||||||
|
<div class="d-flex justify-content-center align-items-center mt-2 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>
|
||||||
|
</header>
|
||||||
|
`,
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
title: document.title,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,63 @@
|
||||||
|
/* 提高 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%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 添加自定义样式 */
|
||||||
|
.database-info-card {
|
||||||
|
border-left: 4px solid #0d6efd;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-info-section {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-list {
|
||||||
|
max-height: 500px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-item {
|
||||||
|
border-left: 3px solid #6c757d;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-item:hover {
|
||||||
|
border-left-color: #0d6efd;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-java {
|
||||||
|
background-color: #5382a1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-jdbc {
|
||||||
|
background-color: #4479a1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-js {
|
||||||
|
background-color: #f7df1e;
|
||||||
|
color: #000;
|
||||||
|
}
|
|
@ -28,35 +28,8 @@ const DatabaseCard = defineComponent({
|
||||||
</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-footer bg-light">
|
<div class="card-body 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">
|
|
@ -63,6 +63,7 @@ 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>
|
||||||
|
@ -134,12 +135,10 @@ const DatabaseForm = {
|
||||||
<span aria-hidden="true" class="spinner-grow spinner-grow-sm"></span>
|
<span aria-hidden="true" class="spinner-grow spinner-grow-sm"></span>
|
||||||
<span role="status">生成选中表...</span>
|
<span role="status">生成选中表...</span>
|
||||||
</button>
|
</button>
|
||||||
<button v-else class="btn btn-primary" data-bs-title="生成全部已经选择的数据表" data-bs-toggle="tooltip"
|
<button v-else class="btn btn-primary" type="submit">
|
||||||
type="submit">
|
|
||||||
生成选中表
|
生成选中表
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-warning" type="button" data-bs-title="取消全部已经选择的数据表" data-bs-toggle="tooltip"
|
<button class="btn btn-warning" type="button" @click="onClearGeneratorData">清空生成记录</button>
|
||||||
@click="onClearGeneratorData">清空生成记录</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 d-grid gap-2">
|
<div class="col-md-4 d-grid gap-2">
|
||||||
<button class="btn btn-primary shadow-sm" disabled type="button"
|
<button class="btn btn-primary shadow-sm" disabled type="button"
|
||||||
|
@ -290,7 +289,7 @@ const DatabaseForm = {
|
||||||
this.downloadLoading = true;
|
this.downloadLoading = true;
|
||||||
try {
|
try {
|
||||||
const response = await axiosInstance({
|
const response = await axiosInstance({
|
||||||
url: "/vms/downloadByZip",
|
url: "/generator/downloadByZip",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
data: this.form,
|
data: this.form,
|
||||||
responseType: 'blob' // 重要:指定响应类型为blob
|
responseType: 'blob' // 重要:指定响应类型为blob
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue