Compare commits

..

9 Commits

Author SHA1 Message Date
Bunny ec4379abe4 📝 添加ReadMe文档 2025-07-01 17:14:54 +08:00
Bunny 2a829e477b 🎨 修改前端访问名称 2025-07-01 16:41:38 +08:00
Bunny 41ce317249 💡 更新注释信息和文件命名方式 2025-07-01 16:39:16 +08:00
Bunny 9b6938b46e 💬 更新前端表格选择方式和提示信息 2025-07-01 16:37:37 +08:00
Bunny 2e714dc080 💩 改进异常处理体系 2025-07-01 16:18:39 +08:00
Bunny 74ff03182e 💩 异常处理方式 2025-07-01 15:31:58 +08:00
Bunny 08c4ff9b6b 💩 改进命名方式 2025-07-01 15:28:18 +08:00
Bunny 1e07831818 💩 重构抽象层次;解决资源泄漏;优化元数据获取性能 2025-07-01 15:17:56 +08:00
Bunny 5ef135de32 🚧 优化后端代码 2025-07-01 15:05:27 +08:00
37 changed files with 467 additions and 760 deletions

107
README.md
View File

@ -1,43 +1,90 @@
# 代码生成器
# Bunny Code Generator 🚀
## 内置字段
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)[![Java Version](https://img.shields.io/badge/JDK-17-green.svg)]()[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.4.3-6DB33F.svg)]()
```java
// vm 不能直接写 `{` 需要转换下
context.put("leftBrace", "{");
Bunny Code Generator 是一个高效的数据库表结构到代码的生成工具,帮助开发者快速生成高质量的服务端代码,显著提升开发效率。
// 当前的表名
context.put("tableName", tableMetaData.getTableName());
## ✨ 核心特性
// 当前表的列信息
context.put("columnInfoList", columnInfoList);
- **多数据源支持** - 支持通过数据库连接或直接解析SQL语句生成代码
- **模板引擎自由** - 基于Velocity模板引擎支持完全自定义代码模板
- **灵活输出方式** - 支持即时代码预览和ZIP打包下载两种输出模式
- **智能命名转换** - 自动将表名转换为大/小驼峰命名的类名
- **动态模板选择** - 前端可交互式选择需要生成的模板文件组合
- **注释保留** - 自动将表字段注释带入生成的代码中
// 数据库sql列
context.put("baseColumnList", String.join(",", list));
## 🚀 快速入门
// 当前日期
String date = new SimpleDateFormat(dto.getSimpleDateFormat()).format(new Date());
context.put("date", date);
### 前置要求
// 作者名字
context.put("author", dto.getAuthor());
- JDK 1.8 或更高版本
- MySQL 5.7+ (或其他支持的数据库)
- Spring Boot 2.7+
// 每个 Controller 上的请求前缀
context.put("requestMapping", dto.getRequestMapping());
### 安装步骤
// 表字段的注释内容
context.put("comment", dto.getComment());
1. 克隆仓库:
```bash
git clone https://github.com/yourusername/bunny-code-generator.git
```
// 设置包名称
context.put("package", dto.getPackageName());
2. 配置数据库连接 (`application.yml`)
```yaml
bunny:
master:
database: your_database
spring:
datasource:
url: jdbc:mysql://localhost:3306/your_database
username: your_username
password: your_password
driver-class-name: com.mysql.cj.jdbc.Driver
```
// 将类名称转成小驼峰
String toCamelCase = TypeConvertCore.convertToCamelCase(replaceTableName);
context.put("classLowercaseName", toCamelCase);
3. 启动应用:
```bash
mvn spring-boot:run
```
// 将类名称转成大驼峰
String convertToCamelCase = TypeConvertCore.convertToCamelCase(replaceTableName, true);
context.put("classUppercaseName", convertToCamelCase);
```
4. 访问 `http://localhost:8080` 开始使用
![wx+alipay](images/wx_alipay.png)
## 🛠️ 模板变量参考
模板中可使用以下预定义变量:
| 变量名 | 描述 | 示例值 |
| -------------------- | ------------------ | ------------------ |
| `tableName` | 原始表名 | `user_info` |
| `classLowercaseName` | 小驼峰类名 | `userInfo` |
| `classUppercaseName` | 大驼峰类名 | `UserInfo` |
| `columnInfoList` | 列信息列表 | `List<ColumnInfo>` |
| `baseColumnList` | SQL列名字符串 | `id,name,age` |
| `package` | 包名 | `com.example.demo` |
| `author` | 作者名 | `YourName` |
| `date` | 当前日期 | `2023-07-20` |
| `comment` | 表注释 | `用户信息表` |
| `requestMapping` | Controller请求前缀 | `/api/user` |
## 🤝 参与贡献
我们欢迎各种形式的贡献!请阅读 [贡献指南](CONTRIBUTING.md) 了解如何参与项目开发。
1. Fork 项目仓库
2. 创建您的特性分支 (`git checkout -b feature/AmazingFeature`)
3. 提交您的更改 (`git commit -m 'Add some AmazingFeature'`)
4. 推送到分支 (`git push origin feature/AmazingFeature`)
5. 发起 Pull Request
## 📄 许可证
本项目采用 MIT 许可证 - 详情请参阅 [LICENSE](LICENSE) 文件。
## ☕ 支持项目
如果这个项目对您有帮助,可以考虑支持我们:
![WeChat & Alipay](images/wx_alipay.png)
---
**Happy Coding!** 🎉

View File

@ -1,13 +1,12 @@
package cn.bunny.controller;
import cn.bunny.core.factory.ConcreteSqlParserDatabaseInfo;
import cn.bunny.core.provider.SqlMetadataProvider;
import cn.bunny.domain.entity.ColumnMetaData;
import cn.bunny.domain.entity.TableMetaData;
import cn.bunny.domain.result.Result;
import cn.bunny.service.SqlParserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@ -17,23 +16,23 @@ import java.util.List;
@Tag(name = "解析SQL", description = "解析SQL接口")
@RestController
@RequestMapping("/api/sqlParser")
@RequiredArgsConstructor
public class SqlParserController {
@Resource
private SqlParserService sqlParserService;
private final SqlMetadataProvider sqlParserService;
@Operation(summary = "解析SQL成表信息", description = "解析SQL成表信息")
@PostMapping("tableInfo")
public Result<TableMetaData> tableInfo(String sql) {
TableMetaData vo = sqlParserService.tableInfo(sql);
TableMetaData vo = sqlParserService.getTableMetadata(sql);
return Result.success(vo);
}
@Operation(summary = "解析SQL成列数据", description = "解析SQL成列数据")
@PostMapping("columnMetaData")
public Result<List<ColumnMetaData>> columnMetaData(String sql) {
ConcreteSqlParserDatabaseInfo databaseInfoCore = new ConcreteSqlParserDatabaseInfo();
List<ColumnMetaData> vo = databaseInfoCore.tableColumnInfo(sql);
List<ColumnMetaData> vo = sqlParserService.getColumnInfoList(sql);
return Result.success(vo);
}
}

View File

@ -1,5 +1,6 @@
package cn.bunny.controller;
import cn.bunny.core.provider.DatabaseMetadataProvider;
import cn.bunny.domain.entity.ColumnMetaData;
import cn.bunny.domain.entity.DatabaseInfoMetaData;
import cn.bunny.domain.entity.TableMetaData;
@ -7,7 +8,7 @@ import cn.bunny.domain.result.Result;
import cn.bunny.service.TableService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@ -17,11 +18,11 @@ import java.util.List;
@Tag(name = "数据库表控制器", description = "数据库表信息接口")
@RestController
@RequestMapping("/api/table")
@RequiredArgsConstructor
public class TableController {
@Resource
private TableService tableService;
private final TableService tableService;
private final DatabaseMetadataProvider databaseMetadataProvider;
@Operation(summary = "当前数据库信息", description = "当前连接的数据库信息")
@GetMapping("databaseInfoMetaData")
@ -33,21 +34,22 @@ public class TableController {
@Operation(summary = "数据库所有的表", description = "获取[当前/所有]数据库表")
@GetMapping("databaseTableList")
public Result<List<TableMetaData>> databaseTableList(String dbName) {
List<TableMetaData> list = tableService.databaseTableList(dbName);
List<TableMetaData> list = databaseMetadataProvider.getTableMetadataBatch(dbName);
return Result.success(list);
}
@Operation(summary = "表属性", description = "获取当前查询表属性")
@GetMapping("tableMetaData")
public Result<TableMetaData> tableMetaData(String tableName) {
TableMetaData tableMetaData = tableService.tableMetaData(tableName);
TableMetaData tableMetaData = databaseMetadataProvider.getTableMetadata(tableName);
return Result.success(tableMetaData);
}
@Operation(summary = "表的列属性", description = "获取当前查询表中列属性")
@GetMapping("tableColumnInfo")
public Result<List<ColumnMetaData>> tableColumnInfo(String tableName) {
List<ColumnMetaData> columnInfo = tableService.tableColumnInfo(tableName);
List<ColumnMetaData> columnInfo = databaseMetadataProvider.getColumnInfoList(tableName);
return Result.success(columnInfo);
}
}

View File

@ -42,4 +42,5 @@ public class VmsController {
public ResponseEntity<byte[]> downloadByZip(@Valid @RequestBody VmsArgumentDto dto) {
return vmsService.downloadByZip(dto);
}
}

View File

@ -0,0 +1,26 @@
package cn.bunny.core.dialect;
import java.util.List;
/**
* 数据库方言
*/
public interface DatabaseDialect {
/**
* 提取表注释
*
* @param tableOptions 选项
* @return 注释信息
*/
String extractTableComment(String tableOptions);
/**
* 提取列注释
*
* @param columnSpecs 列规格
* @return 注释信息
*/
String extractColumnComment(List<String> columnSpecs);
}

View File

@ -0,0 +1,37 @@
package cn.bunny.core.dialect;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Component
public class MySqlDialect implements DatabaseDialect {
/**
* 提取表注释
*
* @param tableOptions 选项
* @return 注释信息
*/
@Override
public String extractTableComment(String tableOptions) {
Pattern pattern = Pattern.compile("COMMENT\\s*=\\s*'(.*?)'", Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(tableOptions);
return matcher.find() ? matcher.group(1) : null;
}
/**
* 提取列注释
*
* @param columnSpecs 列规格
* @return 注释信息
*/
@Override
public String extractColumnComment(List<String> columnSpecs) {
String columnSpecsString = String.join(" ", columnSpecs);
Matcher columnSpecsStringMatcher = Pattern.compile("COMMENT\\s*'(.*?)'", Pattern.CASE_INSENSITIVE).matcher(columnSpecsString);
return columnSpecsStringMatcher.find() ? columnSpecsStringMatcher.group(1) : null;
}
}

View File

@ -1,70 +0,0 @@
package cn.bunny.core.factory;
import cn.bunny.domain.entity.ColumnMetaData;
import cn.bunny.domain.entity.TableMetaData;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Component
public abstract class AbstractDatabaseInfo {
public DataSource dataSource;
@Autowired
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 获取表的所有主键列名
*
* @param tableName 表名
* @return 主键列名的集合
*/
@SneakyThrows
public Set<String> findPrimaryKeyColumns(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;
}
}
/**
* 解析 sql 表信息
*
* @param name 表名称或sql
* @return 表西悉尼
*/
public abstract TableMetaData getTableMetadata(String name);
/**
* 获取当前表的列属性
*
* @param name 表名称或sql
* @return 当前表所有的列内容
*/
public abstract List<ColumnMetaData> tableColumnInfo(String name);
}

View File

@ -1,31 +1,66 @@
package cn.bunny.core.factory;
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.utils.TypeConvertUtil;
import lombok.SneakyThrows;
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
public class ConcreteDatabaseInfo extends AbstractDatabaseInfo {
@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 当前连接的数据库信息属性
*/
@SneakyThrows
public DatabaseInfoMetaData databaseInfoMetaData() {
try (Connection connection = dataSource.getConnection()) {
DatabaseMetaData metaData = connection.getMetaData();
@ -39,23 +74,24 @@ public class ConcreteDatabaseInfo extends AbstractDatabaseInfo {
.username(metaData.getUserName())
.currentDatabase(currentDatabase)
.build();
} catch (SQLException e) {
throw new GeneratorCodeException("Get database info error:" + e.getMessage());
}
}
/**
* 解析 sql 表信息
*
* @param tableName 表名称或sql
* @param identifier 表名称或sql
* @return 表西悉尼
*/
@SneakyThrows
@Override
public TableMetaData getTableMetadata(String tableName) {
public TableMetaData getTableMetadata(String identifier) {
TableMetaData tableMetaData;
try (Connection connection = dataSource.getConnection()) {
DatabaseMetaData metaData = connection.getMetaData();
ResultSet tables = metaData.getTables(null, null, tableName, new String[]{"TABLE"});
ResultSet tables = metaData.getTables(null, null, identifier, new String[]{"TABLE"});
// 获取表的注释信息
if (tables.next()) {
@ -69,16 +105,18 @@ public class ConcreteDatabaseInfo extends AbstractDatabaseInfo {
String tableType = tables.getString("TABLE_TYPE");
tableMetaData = TableMetaData.builder()
.tableName(tableName)
.tableName(identifier)
.comment(remarks)
.tableCat(tableCat)
.tableType(tableType)
.build();
} else {
throw new RuntimeException("数据表不存在");
throw new MetadataNotFoundException("Table not found: " + identifier);
}
return tableMetaData;
} catch (Exception e) {
throw new GeneratorCodeException(e.getMessage());
}
}
@ -87,26 +125,30 @@ public class ConcreteDatabaseInfo extends AbstractDatabaseInfo {
*
* @return 所有表信息
*/
@SneakyThrows
public List<TableMetaData> databaseTableList(String dbName) {
// 当前数据库数据库所有的表
public List<TableMetaData> getTableMetadataBatch(String dbName) {
List<TableMetaData> allTableInfo = new ArrayList<>();
try (Connection connection = dataSource.getConnection()) {
DatabaseMetaData metaData = connection.getMetaData();
// 当前数据库中所有的表
try (Connection conn = dataSource.getConnection()) {
DatabaseMetaData metaData = conn.getMetaData();
ResultSet tables = metaData.getTables(dbName, null, "%", new String[]{"TABLE"});
while (tables.next()) {
// 表名称
dbName = tables.getString("TABLE_NAME");
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 = getTableMetadata(dbName);
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;
@ -115,20 +157,19 @@ public class ConcreteDatabaseInfo extends AbstractDatabaseInfo {
/**
* 获取当前表的列属性
*
* @param tableName 表名称或sql
* @param identifier 表名称或sql
* @return 当前表所有的列内容
*/
@SneakyThrows
@Override
public List<ColumnMetaData> tableColumnInfo(String tableName) {
public List<ColumnMetaData> getColumnInfoList(String identifier) {
try (Connection connection = dataSource.getConnection()) {
DatabaseMetaData metaData = connection.getMetaData();
Map<String, ColumnMetaData> map = new LinkedHashMap<>();
// 当前表的主键
Set<String> primaryKeyColumns = findPrimaryKeyColumns(tableName);
Set<String> primaryKeyColumns = getPrimaryKeys(identifier);
// 当前表的列信息
try (ResultSet columnsRs = metaData.getColumns(null, null, tableName, null)) {
try (ResultSet columnsRs = metaData.getColumns(null, null, identifier, null)) {
while (columnsRs.next()) {
ColumnMetaData column = new ColumnMetaData();
// 列字段
@ -139,13 +180,13 @@ public class ConcreteDatabaseInfo extends AbstractDatabaseInfo {
// 设置列字段
column.setColumnName(columnName);
// 列字段转成 下划线 -> 小驼峰
column.setLowercaseName(TypeConvertUtil.convertToCamelCase(column.getColumnName()));
column.setLowercaseName(MysqlTypeConvertUtil.convertToCamelCase(column.getColumnName()));
// 列字段转成 下划线 -> 大驼峰名称
column.setUppercaseName(TypeConvertUtil.convertToCamelCase(column.getColumnName(), true));
column.setUppercaseName(MysqlTypeConvertUtil.convertToCamelCase(column.getColumnName(), true));
// 字段类型
column.setJdbcType(typeName);
// 字段类型转 Java 类型
String javaType = TypeConvertUtil.convertToJavaType(typeName);
String javaType = MysqlTypeConvertUtil.convertToJavaType(typeName);
column.setJavaType(javaType);
// 字段类型转 JavaScript 类型
column.setJavascriptType(StringUtils.uncapitalize(javaType));
@ -164,6 +205,8 @@ public class ConcreteDatabaseInfo extends AbstractDatabaseInfo {
}
return new ArrayList<>(map.values());
} catch (Exception e) {
throw new MetadataProviderException("Failed to get table metadata for: " + identifier, e);
}
}
}

View File

@ -0,0 +1,26 @@
package cn.bunny.core.provider;
import cn.bunny.domain.entity.ColumnMetaData;
import cn.bunny.domain.entity.TableMetaData;
import java.util.List;
public interface IMetadataProvider {
/**
* 解析 sql 表信息
*
* @param identifier 表名称或sql
* @return 表西悉尼
*/
TableMetaData getTableMetadata(String identifier);
/**
* 获取当前表的列属性
*
* @param identifier 表名称或sql
* @return 当前表所有的列内容
*/
List<ColumnMetaData> getColumnInfoList(String identifier);
}

View File

@ -1,8 +1,12 @@
package cn.bunny.core.factory;
package cn.bunny.core.provider;
import cn.bunny.core.dialect.DatabaseDialect;
import cn.bunny.domain.entity.ColumnMetaData;
import cn.bunny.domain.entity.TableMetaData;
import cn.bunny.utils.TypeConvertUtil;
import cn.bunny.exception.GeneratorCodeException;
import cn.bunny.exception.SqlParseException;
import cn.bunny.utils.MysqlTypeConvertUtil;
import lombok.RequiredArgsConstructor;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
@ -11,29 +15,33 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Component
public class ConcreteSqlParserDatabaseInfo extends AbstractDatabaseInfo {
@RequiredArgsConstructor
public class SqlMetadataProvider implements IMetadataProvider {
private final DatabaseDialect dialect;
/**
* 解析 sql 表信息
* 先解析SQL语句解析列字段信息
*
* @param sql 表名称或sql
* @param identifier 表名称或sql
* @return 表西悉尼
* @see CCJSqlParserUtil 使用这个工具进行SQL的解析
*/
@Override
public TableMetaData getTableMetadata(String sql) {
public TableMetaData getTableMetadata(String identifier) {
TableMetaData tableInfo = new TableMetaData();
// 解析sql
Statement statement;
try {
statement = CCJSqlParserUtil.parse(sql);
statement = CCJSqlParserUtil.parse(identifier);
} catch (JSQLParserException e) {
throw new RuntimeException("SQL解析失败");
throw new GeneratorCodeException("SQL解析失败");
}
if (!(statement instanceof CreateTable createTable)) {
throw new IllegalArgumentException("缺少SQL语句");
}
@ -45,11 +53,8 @@ public class ConcreteSqlParserDatabaseInfo extends AbstractDatabaseInfo {
String tableOptionsStrings = String.join(" ", createTable.getTableOptionsStrings());
// 注释信息
Pattern pattern = Pattern.compile("COMMENT\\s*=\\s*'(.*?)'", Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(tableOptionsStrings);
if (matcher.find()) {
tableInfo.setComment(matcher.group(1));
}
String comment = dialect.extractTableComment(tableOptionsStrings);
tableInfo.setComment(comment);
return tableInfo;
}
@ -57,21 +62,21 @@ public class ConcreteSqlParserDatabaseInfo extends AbstractDatabaseInfo {
/**
* 获取当前表的列属性
*
* @param sql 表名称或sql
* @param identifier 表名称或sql
* @return 当前表所有的列内容
*/
@Override
public List<ColumnMetaData> tableColumnInfo(String sql) {
public List<ColumnMetaData> getColumnInfoList(String identifier) {
// 解析sql
Statement statement;
try {
statement = CCJSqlParserUtil.parse(sql);
statement = CCJSqlParserUtil.parse(identifier);
} catch (JSQLParserException e) {
throw new RuntimeException("SQL解析失败");
throw new SqlParseException("Fail parse sql", e.getCause());
}
if (!(statement instanceof CreateTable createTable)) {
throw new IllegalArgumentException("缺少SQL语句");
throw new IllegalArgumentException("Lack of Sql Statement");
}
return createTable.getColumnDefinitions()
@ -87,24 +92,23 @@ public class ConcreteSqlParserDatabaseInfo extends AbstractDatabaseInfo {
columnInfo.setJdbcType(dataType);
// 设置 Java 类型
String javaType = TypeConvertUtil.convertToJavaType(dataType.contains("varchar") ? "varchar" : dataType);
String javaType = MysqlTypeConvertUtil.convertToJavaType(dataType.contains("varchar") ? "varchar" : dataType);
columnInfo.setJavaType(javaType);
// 设置 JavaScript 类型
columnInfo.setJavascriptType(StringUtils.uncapitalize(javaType));
// 列字段转成 下划线 -> 小驼峰
columnInfo.setLowercaseName(TypeConvertUtil.convertToCamelCase(column.getColumnName()));
columnInfo.setLowercaseName(MysqlTypeConvertUtil.convertToCamelCase(column.getColumnName()));
// 列字段转成 下划线 -> 大驼峰名称
columnInfo.setUppercaseName(TypeConvertUtil.convertToCamelCase(column.getColumnName(), true));
columnInfo.setUppercaseName(MysqlTypeConvertUtil.convertToCamelCase(column.getColumnName(), true));
// 解析注释
List<String> columnSpecs = column.getColumnSpecs();
String columnSpecsString = String.join(" ", columnSpecs);
Matcher columnSpecsStringMatcher = Pattern.compile("COMMENT\\s*'(.*?)'", Pattern.CASE_INSENSITIVE).matcher(columnSpecsString);
if (columnSpecsStringMatcher.find()) {
columnInfo.setComment(columnSpecsStringMatcher.group(1));
}
// 设置列属性信息
String comment = dialect.extractColumnComment(columnSpecs);
columnInfo.setComment(comment);
return columnInfo;
}).toList();

View File

@ -11,7 +11,7 @@ import java.util.List;
* 模板方法模式
* 如果需要继承 AbstractVmsGenerator
*/
public abstract class AbstractVmsGeneratorTemplate {
public abstract class AbstractTemplateGenerator {
/**
* 添加生成内容

View File

@ -2,7 +2,7 @@ package cn.bunny.core.template;
import cn.bunny.domain.dto.VmsArgumentDto;
import cn.bunny.domain.entity.TableMetaData;
import cn.bunny.utils.TypeConvertUtil;
import cn.bunny.utils.MysqlTypeConvertUtil;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
@ -15,7 +15,7 @@ import java.util.Date;
* 使用模板方法方便扩展
* 如果需要继承 AbstractVmsGenerator
*/
public class VmsArgumentDtoBaseVmsGeneratorTemplate extends AbstractVmsGeneratorTemplate {
public class VmsTBaseTemplateGenerator extends AbstractTemplateGenerator {
private final VmsArgumentDto dto;
private final String path;
@ -26,7 +26,7 @@ public class VmsArgumentDtoBaseVmsGeneratorTemplate extends AbstractVmsGenerator
* @param path 当前路径
* @param tableMetaData 表名称
*/
public VmsArgumentDtoBaseVmsGeneratorTemplate(VmsArgumentDto dto, String path, TableMetaData tableMetaData) {
public VmsTBaseTemplateGenerator(VmsArgumentDto dto, String path, TableMetaData tableMetaData) {
this.dto = dto;
this.path = path;
this.tableMetaData = tableMetaData;
@ -61,11 +61,11 @@ public class VmsArgumentDtoBaseVmsGeneratorTemplate extends AbstractVmsGenerator
context.put("package", dto.getPackageName());
// 将类名称转成小驼峰
String toCamelCase = TypeConvertUtil.convertToCamelCase(tableName);
String toCamelCase = MysqlTypeConvertUtil.convertToCamelCase(tableName);
context.put("classLowercaseName", toCamelCase);
// 将类名称转成大驼峰
String convertToCamelCase = TypeConvertUtil.convertToCamelCase(tableName, true);
String convertToCamelCase = MysqlTypeConvertUtil.convertToCamelCase(tableName, true);
context.put("classUppercaseName", convertToCamelCase);
}

View File

@ -1,5 +1,6 @@
package cn.bunny.domain.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@ -11,23 +12,25 @@ import java.util.List;
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Schema(name = "TableMetaData", description = "表信息数据")
public class TableMetaData {
/* 表名 */
@Schema(name = "tableName", description = "表名")
private String tableName;
/* 注释内容 */
@Schema(name = "comment", description = "注释内容")
private String comment;
/* 表目录 */
@Schema(name = "tableCats", description = "表目录")
private String tableCat;
/* 表类型(通常是"TABLE" */
@Schema(name = "tableType", description = "表类型(通常是\"TABLE\"")
private String tableType;
/* 类名 */
@Schema(name = "className", description = "类名")
private String className;
/* 列名称 */
@Schema(name = "columns", description = "列名称")
private List<ColumnMetaData> columns;
}

View File

@ -0,0 +1,7 @@
package cn.bunny.exception;
public class MetadataNotFoundException extends RuntimeException {
public MetadataNotFoundException(String message) {
super(message);
}
}

View File

@ -0,0 +1,7 @@
package cn.bunny.exception;
public class MetadataProviderException extends RuntimeException {
public MetadataProviderException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,7 @@
package cn.bunny.exception;
public class SqlParseException extends MetadataProviderException {
public SqlParseException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -1,13 +0,0 @@
package cn.bunny.service;
import cn.bunny.domain.entity.TableMetaData;
public interface SqlParserService {
/**
* 解析SQL内容
*
* @param sql Sql语句
* @return 表信息内容
*/
TableMetaData tableInfo(String sql);
}

View File

@ -1,40 +1,14 @@
package cn.bunny.service;
import cn.bunny.domain.entity.ColumnMetaData;
import cn.bunny.domain.entity.DatabaseInfoMetaData;
import cn.bunny.domain.entity.TableMetaData;
import java.util.List;
public interface TableService {
/**
* 获取表属性
*
* @param tableName 表名称
* @return 表属性
*/
TableMetaData tableMetaData(String tableName);
/**
* 获取所有数据库
*
* @return 所有表信息
*/
List<TableMetaData> databaseTableList(String tableName);
/**
* 获取列属性
*
* @param tableName 表名称
* @return 当前表所有的列内容
*/
List<ColumnMetaData> tableColumnInfo(String tableName);
/**
* 数据库所有的信息
*
* @return 当前连接的数据库信息属性
*/
DatabaseInfoMetaData databaseInfoMetaData();
}

View File

@ -1,30 +0,0 @@
package cn.bunny.service.impl;
import cn.bunny.core.factory.ConcreteSqlParserDatabaseInfo;
import cn.bunny.domain.entity.TableMetaData;
import cn.bunny.service.SqlParserService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class SqlParserServiceImpl implements SqlParserService {
private final ConcreteSqlParserDatabaseInfo sqlParserDatabaseInfo;
/**
* 解析SQL内容
*
* @param sql Sql语句
* @return 表信息内容
*/
@Override
public TableMetaData tableInfo(String sql) {
TableMetaData tableInfoVo = new TableMetaData();
TableMetaData tableMetaData = sqlParserDatabaseInfo.getTableMetadata(sql);
BeanUtils.copyProperties(tableMetaData, tableInfoVo);
return tableInfoVo;
}
}

View File

@ -1,12 +1,10 @@
package cn.bunny.service.impl;
import cn.bunny.core.factory.ConcreteDatabaseInfo;
import cn.bunny.domain.entity.ColumnMetaData;
import cn.bunny.core.provider.DatabaseMetadataProvider;
import cn.bunny.domain.entity.DatabaseInfoMetaData;
import cn.bunny.domain.entity.TableMetaData;
import cn.bunny.service.TableService;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.stereotype.Service;
import java.util.List;
@ -16,49 +14,16 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor
public class TableServiceImpl implements TableService {
private final ConcreteDatabaseInfo databaseInfoCore;
/**
* 获取表属性
*
* @param tableName 表名称
* @return 表属性
*/
@Override
public TableMetaData tableMetaData(String tableName) {
return databaseInfoCore.getTableMetadata(tableName);
}
/**
* 获取[当前/所有]数据库表
*
* @return 所有表信息
*/
@Override
public List<TableMetaData> databaseTableList(String dbName) {
return databaseInfoCore.databaseTableList(dbName);
}
/**
* 获取当前表的列属性
*
* @param tableName 表名称
* @return 当前表所有的列内容
*/
@Override
public List<ColumnMetaData> tableColumnInfo(String tableName) {
return databaseInfoCore.tableColumnInfo(tableName);
}
private final DatabaseMetadataProvider databaseMetadataProvider;
/**
* 数据库所有的信息
*
* @return 当前连接的数据库信息属性
*/
@SneakyThrows
@Override
public DatabaseInfoMetaData databaseInfoMetaData() {
List<TableMetaData> databaseTableList = databaseTableList(null);
List<TableMetaData> databaseTableList = databaseMetadataProvider.getTableMetadataBatch(null);
// 将当前数据库表分组以数据库名称为key
List<TableMetaData> databaseList = databaseTableList.stream()
@ -70,7 +35,7 @@ public class TableServiceImpl implements TableService {
return tableInfoVo;
}).toList();
DatabaseInfoMetaData databaseInfoMetaData = databaseInfoCore.databaseInfoMetaData();
DatabaseInfoMetaData databaseInfoMetaData = databaseMetadataProvider.databaseInfoMetaData();
databaseInfoMetaData.setDatabaseList(databaseList);
return databaseInfoMetaData;

View File

@ -10,7 +10,6 @@ import cn.bunny.utils.ResourceFileUtil;
import cn.bunny.utils.VmsUtil;
import cn.hutool.crypto.digest.MD5;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@ -35,24 +34,37 @@ public class VmsServiceImpl implements VmsService {
return codeGeneratorService.generateCode(dto);
}
@SneakyThrows
/**
* 获取vms文件路径
*
* @return vms下的文件路径
*/
@Override
public Map<String, List<VmsPathVo>> vmsResourcePathList() {
List<String> vmsRelativeFiles = ResourceFileUtil.getRelativeFiles("vms");
List<String> vmsRelativeFiles;
Map<String, List<VmsPathVo>> listMap;
return vmsRelativeFiles.stream()
.map(vmFile -> {
String[] filepathList = vmFile.split("/");
String filename = filepathList[filepathList.length - 1].replace(".vm", "");
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));
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
@ -62,7 +74,7 @@ public class VmsServiceImpl implements VmsService {
// 下载文件名称
long currentTimeMillis = System.currentTimeMillis();
String digestHex = MD5.create().digestHex(currentTimeMillis + "");
String generateZipFilename = "generator-" + digestHex.substring(0, 4) + ".zip";
String generateZipFilename = "code-" + digestHex.substring(0, 6) + ".zip";
// 设置响应头
HttpHeaders headers = new HttpHeaders();

View File

@ -1,8 +1,8 @@
package cn.bunny.service.impl.vms;
import cn.bunny.core.factory.ConcreteDatabaseInfo;
import cn.bunny.core.factory.ConcreteSqlParserDatabaseInfo;
import cn.bunny.core.template.VmsArgumentDtoBaseVmsGeneratorTemplate;
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;
@ -25,43 +25,54 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor
public class VmsCodeGeneratorService {
private final ConcreteDatabaseInfo databaseInfoCore;
private final ConcreteSqlParserDatabaseInfo sqlParserDatabaseInfo;
private final DatabaseMetadataProvider databaseMetadataProvider;
private final SqlMetadataProvider sqlMetadataProvider;
/**
* 根据DTO生成代码模板
*
* @param dto 包含生成参数的数据传输对象
* @return 生成的代码模板列表
* @return 按表名分组的生成的代码模板列表
*/
public Map<String, List<GeneratorVo>> generateCode(VmsArgumentDto dto) {
String sql = dto.getSql();
// 提前获取可复用的数据
final String sql = dto.getSql();
final List<String> tableNames = dto.getTableNames();
final List<String> paths = dto.getPath();
return dto.getTableNames().stream()
return tableNames.parallelStream() // 使用并行流提高多表处理效率
.map(tableName -> {
// 获取表元数据和列信息
TableMetaData tableMetaData = getTableMetadata(dto, tableName);
List<ColumnMetaData> columnInfoList = getColumnInfoList(sql, tableName);
return dto.getPath().stream()
.map(path -> {
VmsArgumentDtoBaseVmsGeneratorTemplate generator = new VmsArgumentDtoBaseVmsGeneratorTemplate(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();
})
// 为每个路径生成模板
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();
}
/**
* 获取表元数据
*
@ -71,8 +82,8 @@ public class VmsCodeGeneratorService {
*/
private TableMetaData getTableMetadata(VmsArgumentDto dto, String tableName) {
return StringUtils.hasText(dto.getSql())
? sqlParserDatabaseInfo.getTableMetadata(dto.getSql())
: databaseInfoCore.getTableMetadata(tableName);
? sqlMetadataProvider.getTableMetadata(dto.getSql())
: databaseMetadataProvider.getTableMetadata(tableName);
}
/**
@ -84,8 +95,8 @@ public class VmsCodeGeneratorService {
*/
private List<ColumnMetaData> getColumnInfoList(String sql, String tableName) {
return StringUtils.hasText(sql)
? sqlParserDatabaseInfo.tableColumnInfo(sql)
: databaseInfoCore.tableColumnInfo(tableName).stream().distinct().toList();
? sqlMetadataProvider.getColumnInfoList(sql)
: databaseMetadataProvider.getColumnInfoList(tableName).stream().distinct().toList();
}
}

View File

@ -14,7 +14,9 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* 负责处理ZIP打包和下载逻辑的服务类
* VMS代码生成系统的ZIP打包服务
* <p>
* 提供将生成的代码模板打包为ZIP文件并支持下载的功能
*/
@Service
@RequiredArgsConstructor
@ -23,17 +25,22 @@ public class VmsZipService {
private final VmsCodeGeneratorService codeGeneratorService;
/**
* 创建ZIP文件字节数组
* 将生成的代码模板打包为ZIP文件
*
* @param dto 生成参数
* @return ZIP文件字节数组
* @param dto 代码生成参数DTO包含表名包名等配置
* @return ZIP文件字节数组可直接用于下载
* @throws RuntimeException 当ZIP打包过程中发生IO异常时抛出
*/
public byte[] createZipFile(VmsArgumentDto dto) {
List<GeneratorVo> generatorVoList = codeGeneratorService.generateCode(dto).values().stream().flatMap(Collection::stream).toList();
// 将二维代码生成结果扁平化为一维列表
List<GeneratorVo> generatorVoList = codeGeneratorService.generateCode(dto).values().stream()
.flatMap(Collection::stream)
.toList();
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
// 将所有生成的文件添加到ZIP包中
generatorVoList.forEach(generatorVo -> addToZip(zipOutputStream, generatorVo));
return byteArrayOutputStream.toByteArray();
} catch (IOException e) {
@ -42,21 +49,24 @@ public class VmsZipService {
}
/**
* 将单个生成结果添加到ZIP文件
* 将单个代码文件添加到ZIP输出流
*
* @param zipOutputStream ZIP输出流
* @param generatorVo 生成结果对象
* @param zipOutputStream ZIP文件输出流
* @param generatorVo 代码生成结果对象包含文件路径和内容
* @throws RuntimeException 当文件添加失败时抛出包含失败文件路径信息
*/
private void addToZip(ZipOutputStream zipOutputStream, GeneratorVo generatorVo) {
try {
// 标准化文件路径移除Velocity模板扩展名
String path = generatorVo.getPath().replace(".vm", "");
ZipEntry zipEntry = new ZipEntry(path);
zipOutputStream.putNextEntry(zipEntry);
zipOutputStream.putNextEntry(new ZipEntry(path));
// 以UTF-8编码写入文件内容避免乱码问题
zipOutputStream.write(generatorVo.getCode().getBytes(StandardCharsets.UTF_8));
zipOutputStream.closeEntry();
} catch (IOException e) {
throw new RuntimeException("Failed to add file to ZIP: " + generatorVo.getPath(), e);
}
}
}

View File

@ -4,8 +4,8 @@ import com.google.common.base.CaseFormat;
import org.assertj.core.util.introspection.CaseFormatUtils;
/* 类型转换数据库转Java类型等 */
public class TypeConvertUtil {
public class MysqlTypeConvertUtil {
/**
* 将数据库类型转换为Java类型
*/

View File

@ -28,9 +28,9 @@ public class VmsUtil {
int splitPathsSize = splitPaths.length - 1;
// 大驼峰名称
String CamelCase = TypeConvertUtil.convertToCamelCase(className, true);
String CamelCase = MysqlTypeConvertUtil.convertToCamelCase(className, true);
// 小驼峰名称
String camelCase = TypeConvertUtil.convertToCamelCase(className);
String smallCamelCase = MysqlTypeConvertUtil.convertToCamelCase(className);
// 当前文件名
String filename = splitPaths[splitPathsSize];
@ -52,8 +52,9 @@ public class VmsUtil {
filename = CamelCase + typeMappingsFilename + "." + extension;
}
if (filename.contains("vue") && !filename.contains("index")) {
filename = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, camelCase) + "-" + name + "." + 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;

View File

@ -8,6 +8,7 @@ public class IndexController {
@GetMapping("/")
public String index() {
return "home";
return "main";
}
}

View File

@ -24,11 +24,11 @@ const MainCard = defineComponent({
<p class="card-subtitle mt-2 text-muted">
<i class="bi bi-mouse me-1"></i>
点击 <code class="bg-primary bg-opacity-10">表名</code>
<code class="bg-primary bg-opacity-10">生成</code>
<code class="bg-primary bg-opacity-10">生成</code>
</p>
</div>
<!-- 代码仓库链接区域包含GitHub和Gitee仓库链接 -->
<!-- 代码仓库链接区域包含GitHub和Gitee仓库链接 -->
<div class="card-body">
<div class="d-flex flex-wrap align-items-center gap-3">
<!-- GitHub 仓库链接 -->

View File

@ -129,7 +129,12 @@ const MainForm = {
</button>
</div>
<div class="col-md-4 btn-group">
<button class="btn btn-primary" data-bs-title="生成全部已经选择的数据表" data-bs-toggle="tooltip"
<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>
@ -137,7 +142,13 @@ const MainForm = {
@click="onClearGeneratorData">清空生成记录</button>
</div>
<div class="col-md-4 d-grid gap-2">
<button class="btn btn-primary text-white" type="button" @click="onDownloadZip">下载ZIP</button>
<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>
@ -151,6 +162,8 @@ const MainForm = {
onGeneratorCode: {type: Function, required: true},
// 清空生成记录
onClearGeneratorData: {type: Function, required: true},
// 生成代码加载
generatorCodeLoading: {type: Boolean, required: true},
},
data() {
return {
@ -169,7 +182,8 @@ const MainForm = {
tablePrefixes: '',
webTemplates: '',
serverTemplates: ''
}
},
downloadLoading: ref(false),
}
},
methods: {
@ -273,6 +287,7 @@ const MainForm = {
* @returns {Promise<void>}
*/
async onDownloadZip() {
this.downloadLoading = true;
try {
const response = await axiosInstance({
url: "/vms/downloadByZip",
@ -314,6 +329,8 @@ const MainForm = {
} catch (error) {
console.error('下载失败:', error);
}
this.downloadLoading = false;
},
/**

View File

@ -57,19 +57,19 @@ const MainTable = defineComponent({
<tr :key="table.tableName" v-for="(table, index) in paginatedTableList">
<td>
<input class="form-check-input border-secondary" type="checkbox"
v-model="table.checked" @change="onSelectTable($event, table)">
v-model="table.checked" @change="onSelectTable($event.target.checked, table)">
</td>
<td>{{ (currentPage - 1) * itemsPerPage + index + 1 }}</td>
<td>
<a class="text-decoration-none" href="#" @click.prevent="onGeneratorCode(table)">
<a class="text-decoration-none" href="#" @click.prevent="onSelectTable(!table.checked, table)">
{{ table.tableName }}
</a>
</td>
<td>{{ table.comment || '无注释' }}</td>
<td>{{ table.tableCat }}</td>
<td class="text-center">
<button class="btn btn-sm btn-outline-primary" @click="onGeneratorCode(table)">
<i class="bi bi-gear"></i>
<button class="btn btn-sm btn-outline-primary" @click="onSelectTable(!table.checked, table)">
<i class="bi bi-gear"></i>
</button>
</td>
</tr>
@ -249,11 +249,11 @@ const MainTable = defineComponent({
/**
* 选择单个表
* @param {Event} event - 事件对象
* @param {Boolean} checked - 事件对象
* @param {Object} table - 表数据对象
*/
onSelectTable(event, table) {
table.checked = event.target.checked;
onSelectTable(checked, table) {
table.checked = checked;
this.updateFormSelectedTables();
// 更新表头全选状态

View File

@ -65,9 +65,8 @@
v-model:db-select="dbSelect" v-model:table-select="tableSelect"></main-card>
<!-- 填写生成表单 -->
<main-form :on-clear-generator-data="onClearGeneratorData" :on-generator-code="onGeneratorCode"
ref="mainFormRef"
v-model:form="form"></main-form>
<main-form :generator-code-loading="generatorCodeLoading" :on-clear-generator-data="onClearGeneratorData"
:on-generator-code="onGeneratorCode" ref="mainFormRef" v-model:form="form"></main-form>
<!-- 表格显示 -->
<main-table :db-select="dbSelect" :loading="loading" :on-generator-code="onGeneratorCode"
@ -92,10 +91,10 @@
<!-- 引入组件 -->
<script th:src="@{/src/components/AppHeader.js}"></script>
<script th:src="@{/src/views/home/MainCard.js}"></script>
<script th:src="@{/src/views/home/MainForm.js}"></script>
<script th:src="@{/src/views/home/MainTable.js}"></script>
<script th:src="@{/src/views/home/MainGeneratorPage.js}"></script>
<script th:src="@{/src/views/main/MainCard.js}"></script>
<script th:src="@{/src/views/main/MainForm.js}"></script>
<script th:src="@{/src/views/main/MainTable.js}"></script>
<script th:src="@{/src/views/main/MainGeneratorPage.js}"></script>
<script>
const {createApp, ref} = Vue
@ -113,6 +112,8 @@
rawTableList: ref([]),
// 是否加载
loading: ref(false),
// 是否正在生成
generatorCodeLoading: ref(false),
// 提交的表单
form: ref({
// 作者名称
@ -164,6 +165,7 @@
* @returns {Promise<void>}
*/
async onGeneratorCode() {
this.generatorCodeLoading = true;
// 验证表单是否通过
const isValidate = this.$refs.mainFormRef.validateForm();
if (!isValidate) return;
@ -172,12 +174,15 @@
const {data, code, message} = await axiosInstance.post("/vms/generator", this.form);
// 判断是否请求成功
if (code !== 200) return;
else antd.message.success(message);
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();

View File

@ -1,98 +0,0 @@
package cn.bunny;
import cn.bunny.domain.entity.ColumnMetaData;
import cn.bunny.domain.entity.TableMetaData;
import cn.bunny.utils.TypeConvertUtil;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
@SpringBootTest
public class JDBCTest {
private final DataSource dataSource;
DatabaseMetaData metaData;
public JDBCTest(DataSource dataSource) {
this.dataSource = dataSource;
}
@BeforeEach
public void setUp() throws Exception {
try (Connection connection = dataSource.getConnection()) {
metaData = connection.getMetaData();
}
}
@Test
void testComment() throws SQLException {
String tableName = "sys_i18n";
TableMetaData tableMetaData;
ResultSet tables = metaData.getTables(null, null, tableName, new String[]{"TABLE"});
// 获取表的注释信息
if (tables.next()) {
String remarks = tables.getString("REMARKS");
String tableCat = tables.getString("TABLE_CAT");
tableMetaData = TableMetaData.builder()
.tableName(tableName)
.comment(remarks)
.tableCat(tableCat)
.build();
System.out.println(tableMetaData);
}
}
@Test
void testAllTableComment() throws SQLException {
ResultSet tables = metaData.getTables(null, null, "%", new String[]{"TABLE"});
List<TableMetaData> list = new ArrayList<>();
while (tables.next()) {
String tableName = tables.getString("TABLE_NAME");
String remarks = tables.getString("REMARKS");
String tableCat = tables.getString("TABLE_CAT");
TableMetaData tableMetaData = TableMetaData.builder()
.tableName(tableName).comment(remarks)
.tableCat(tableCat)
.build();
list.add(tableMetaData);
}
System.out.println(list);
}
@Test
void testColumnInfo() throws SQLException {
List<ColumnMetaData> columns = new ArrayList<>();
try (ResultSet columnsRs = metaData.getColumns(null, null, "sys_i18n", null)) {
while (columnsRs.next()) {
ColumnMetaData column = new ColumnMetaData();
column.setColumnName(columnsRs.getString("COLUMN_NAME"));
column.setLowercaseName(TypeConvertUtil.convertToCamelCase(column.getColumnName()));
column.setJdbcType(columnsRs.getString("TYPE_NAME"));
column.setJavaType(TypeConvertUtil.convertToJavaType(column.getJdbcType()));
column.setComment(columnsRs.getString("REMARKS"));
columns.add(column);
System.out.println(column);
}
}
System.out.println(columns);
}
}

View File

@ -1,105 +0,0 @@
package cn.bunny;
import cn.bunny.domain.entity.ColumnMetaData;
import cn.bunny.domain.entity.TableMetaData;
import cn.bunny.utils.TypeConvertUtil;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.create.table.CreateTable;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class SqlParserTest {
@Test
public void test() throws JSQLParserException {
String sql = """
CREATE TABLE `sys_files` (
`id` bigint NOT NULL COMMENT '文件的唯一标识符自动递增',
`filename` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '文件的名称',
`filepath` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '文件在服务器上的存储路径',
`file_size` int NOT NULL COMMENT '文件的大小以字节为单位',
`file_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '文件的MIME类型',
`download_count` int NULL DEFAULT 0 COMMENT '下载数量',
`create_user` bigint NOT NULL COMMENT '创建用户',
`update_user` bigint NULL DEFAULT NULL COMMENT '操作用户',
`create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录文件最后修改的时间戳',
`is_deleted` tinyint(1) UNSIGNED ZEROFILL NOT NULL DEFAULT 0 COMMENT '文件是否被删除',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_filename`(`filename` ASC) USING BTREE COMMENT '索引文件名',
INDEX `idx_filepath`(`filepath` ASC) USING BTREE COMMENT '索引文件路径',
INDEX `idx_file_type`(`file_type` ASC) USING BTREE COMMENT '索引文件类型',
INDEX `idx_update_user`(`update_user` ASC) USING BTREE COMMENT '索引创更新用户',
INDEX `idx_create_user`(`create_user` ASC) USING BTREE COMMENT '索引创建用户',
INDEX `idx_user`(`update_user` ASC, `create_user` ASC) USING BTREE COMMENT '索引创建用户和更新用户',
INDEX `idx_time`(`update_time` ASC, `create_time` ASC) USING BTREE COMMENT '索引创建时间和更新时间'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '系统文件表' ROW_FORMAT = DYNAMIC;
""";
TableMetaData tableInfo = new TableMetaData();
// 解析sql
Statement statement = CCJSqlParserUtil.parse(sql);
if (!(statement instanceof CreateTable createTable)) {
throw new IllegalArgumentException("Not a CREATE TABLE statement");
}
// 设置表基本信息
tableInfo.setTableName(createTable.getTable().getName());
tableInfo.setTableType("TABLE");
String tableOptionsStrings = String.join(" ", createTable.getTableOptionsStrings());
// 注释信息
Pattern pattern = Pattern.compile("COMMENT\\s*=\\s*'(.*?)'", Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(tableOptionsStrings);
if (matcher.find()) {
tableInfo.setComment(matcher.group(1));
}
// 解析列信息
List<ColumnMetaData> columnMetaData = createTable.getColumnDefinitions()
.stream().map(column -> {
// 列信息
ColumnMetaData columnInfo = new ColumnMetaData();
// 列名称
columnInfo.setColumnName(column.getColumnName());
// 设置 JDBC 类型
String dataType = column.getColDataType().getDataType();
columnInfo.setJdbcType(dataType);
// 设置 Java 类型
String javaType = TypeConvertUtil.convertToJavaType(dataType.contains("varchar") ? "varchar" : dataType);
columnInfo.setJavaType(javaType);
// 设置 JavaScript 类型
columnInfo.setJavascriptType(StringUtils.uncapitalize(javaType));
// 列字段转成 下划线 -> 小驼峰
columnInfo.setLowercaseName(TypeConvertUtil.convertToCamelCase(column.getColumnName()));
// 列字段转成 下划线 -> 大驼峰名称
columnInfo.setUppercaseName(TypeConvertUtil.convertToCamelCase(column.getColumnName(), true));
// 解析注释
List<String> columnSpecs = column.getColumnSpecs();
String columnSpecsString = String.join(" ", columnSpecs);
Matcher columnSpecsStringMatcher = Pattern.compile("COMMENT\\s*'(.*?)'", Pattern.CASE_INSENSITIVE).matcher(columnSpecsString);
if (columnSpecsStringMatcher.find()) {
columnInfo.setComment(columnSpecsStringMatcher.group(1));
}
return columnInfo;
}).toList();
System.out.println(tableInfo);
System.out.println("----------------------------------------------------------------------------------------");
System.out.println(columnMetaData);
}
}

View File

@ -1,43 +0,0 @@
package cn.bunny;
import cn.bunny.utils.TypeConvertUtil;
import org.apache.commons.lang3.StringUtils;
import org.assertj.core.util.introspection.CaseFormatUtils;
import org.junit.jupiter.api.Test;
public class StringFormatTest {
@Test
void test1() {
System.out.println(CaseFormatUtils.toCamelCase("user_login"));
System.out.println(CaseFormatUtils.toCamelCase("userLogin"));
System.out.println(CaseFormatUtils.toCamelCase("UserLogin"));
System.out.println("--------------------------------");
System.out.println(StringUtils.lowerCase("user_login"));
System.out.println(StringUtils.lowerCase("userLogin"));
System.out.println(StringUtils.lowerCase("UserLogin"));
System.out.println("--------------------------------");
System.out.println(StringUtils.upperCase("user_login"));
System.out.println(StringUtils.upperCase("userLogin"));
System.out.println(StringUtils.upperCase("UserLogin"));
}
@Test
void test2() {
System.out.println(TypeConvertUtil.convertToCamelCase("user_login_A"));
System.out.println(TypeConvertUtil.convertToCamelCase("User_Login_A"));
System.out.println(TypeConvertUtil.convertToCamelCase("userLoginA"));
System.out.println(TypeConvertUtil.convertToCamelCase("UserLoginA"));
System.out.println("--------------------------------");
System.out.println(TypeConvertUtil.convertToCamelCase("i18n_type_A", true));
System.out.println(TypeConvertUtil.convertToCamelCase("User_Login_A", true));
System.out.println(TypeConvertUtil.convertToCamelCase("userLoginA", true));
System.out.println(TypeConvertUtil.convertToCamelCase("UserLoginA", true));
}
}

View File

@ -1,14 +0,0 @@
package cn.bunny;
import cn.hutool.crypto.digest.MD5;
import org.junit.jupiter.api.Test;
public class TimeTest {
@Test
void timeTest() {
long currentTimeMillis = System.currentTimeMillis();
String digestHex = MD5.create().digestHex(currentTimeMillis + "");
System.out.println(currentTimeMillis);
System.out.println(digestHex);
}
}

View File

@ -1,50 +0,0 @@
package cn.bunny.service.impl;
import cn.bunny.domain.vo.VmsPathVo;
import cn.bunny.utils.ResourceFileUtil;
import com.alibaba.fastjson2.JSON;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
class VmsServiceImplTest {
@Test
void vmsResourcePathList() throws IOException, URISyntaxException {
List<String> vmsFiles = ResourceFileUtil.getAbsoluteFiles("vms");
System.out.println(vmsFiles);
System.out.println("--------------------------------------------------------------");
List<String> vmsRelativeFiles = ResourceFileUtil.getRelativeFiles("vms");
System.out.println(vmsRelativeFiles);
System.out.println("--------------------------集合对象模式------------------------------------");
Map<String, List<VmsPathVo>> map = vmsRelativeFiles.stream().map(vmFile -> {
String[] filepathList = vmFile.split("/");
String filename = filepathList[filepathList.length - 1].replace(".vm", "");
return VmsPathVo.builder().name(vmFile).label(filename).type(filepathList[0]).build();
}).collect(Collectors.groupingBy(VmsPathVo::getType));
System.out.println(JSON.toJSONString(map));
System.out.println("----------------------------二维数组格式----------------------------------");
List<List<VmsPathVo>> listMap = vmsRelativeFiles.stream().map(vmFile -> {
String[] filepathList = vmFile.split("/");
String filename = filepathList[filepathList.length - 1].replace(".vm", "");
return VmsPathVo.builder().name(vmFile).label(filename).type(filepathList[0]).build();
})
.collect(Collectors.groupingBy(VmsPathVo::getType))
.values().stream().toList();
System.out.println(JSON.toJSONString(listMap));
}
}

View File

@ -1,75 +0,0 @@
package cn.bunny.utils;
import cn.bunny.domain.entity.TableMetaData;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
@SpringBootTest
class ConcreteDatabaseInfoCoreTest {
String tableName = "sys_i18n";
@Autowired
private DataSource dataSource;
@Test
void testTableInfoMetaData() {
TableMetaData tableMetaData;
try (Connection connection = dataSource.getConnection()) {
DatabaseMetaData metaData = connection.getMetaData();
ResultSet tables = metaData.getTables(null, null, tableName, new String[]{"TABLE"});
// 获取表的注释信息
if (tables.next()) {
String remarks = tables.getString("REMARKS");
String tableCat = tables.getString("TABLE_CAT");
String tableType = tables.getString("TABLE_TYPE");
tableMetaData = TableMetaData.builder()
.tableName(tableName)
.comment(remarks)
.tableCat(tableCat)
.tableType(tableType)
.build();
} else {
throw new RuntimeException("数据表不存在");
}
System.out.println(tableMetaData);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@SneakyThrows
@Test
void getDbTableList() {
String dbName = "auth_admin";
try (Connection connection = dataSource.getConnection()) {
DatabaseMetaData metaData = connection.getMetaData();
ResultSet tables = metaData.getTables(dbName, null, "%", new String[]{"TABLE"});
List<String> list = new ArrayList<>();
while (tables.next()) {
dbName = tables.getString("TABLE_NAME");
list.add(dbName);
}
System.out.println(list);
}
}
}