Compare commits
65 Commits
132c03a4ef
...
d1371cac46
Author | SHA1 | Date |
---|---|---|
|
d1371cac46 | |
|
5e0770af79 | |
|
d9cced0eba | |
|
229cce3c31 | |
|
ad5bc29cb3 | |
|
464a419d9a | |
|
25f46606fc | |
|
7f55cecb7a | |
|
395ec89666 | |
|
d13451c1f9 | |
|
68f9cda260 | |
|
d6f783c481 | |
|
f1e8f48c73 | |
|
9baf682f63 | |
|
a38cdde759 | |
|
9ce378320b | |
|
4e438bea86 | |
|
3742a7ebca | |
|
4c56bb6890 | |
|
30a37837c7 | |
|
5804e6fe9b | |
|
654019f77b | |
|
cd9c3b4dd5 | |
|
71f2aac2ad | |
|
ec4379abe4 | |
|
2a829e477b | |
|
41ce317249 | |
|
9b6938b46e | |
|
2e714dc080 | |
|
74ff03182e | |
|
08c4ff9b6b | |
|
1e07831818 | |
|
5ef135de32 | |
|
deffcb8fb1 | |
|
c47a65b5de | |
|
7633593bfc | |
|
0cd9129efa | |
|
f8467610f6 | |
|
16991d5b95 | |
|
42f1a7edfe | |
|
f95dc4d947 | |
|
11b20ba6f5 | |
|
9356d39347 | |
|
8f3ea1e87f | |
|
16a8170e86 | |
|
d38844eea5 | |
|
e832b5ffab | |
|
fd722d2515 | |
|
b6e263d114 | |
|
ff2f3144dc | |
|
4f0007ee01 | |
|
faaca67cc6 | |
|
3d3540e3e9 | |
|
2325754ede | |
|
617ff87736 | |
|
474f3ab89b | |
|
e2b6d49518 | |
|
5c241728f0 | |
|
be1b09dc70 | |
|
ea18ded70c | |
|
dfdf9546b0 | |
|
f95b65c1e0 | |
|
679f418387 | |
|
74de80bf68 | |
|
e4288b00e3 |
433
README.md
433
README.md
|
@ -1,43 +1,402 @@
|
|||
# 代码生成器
|
||||
# 🚀 Bunny Code Generator 代码生成器系统文档
|
||||
|
||||
## 内置字段
|
||||
[](LICENSE)[]()[]()
|
||||
|
||||
```java
|
||||
// vm 不能直接写 `{` 需要转换下
|
||||
context.put("leftBrace", "{");
|
||||
## 1. 系统架构 🏗️
|
||||
|
||||
// 当前的表名
|
||||
context.put("tableName", tableMetaData.getTableName());
|
||||
### 1.1 架构图
|
||||
|
||||
// 当前表的列信息
|
||||
context.put("columnInfoList", columnInfoList);
|
||||
|
||||
// 数据库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());
|
||||
|
||||
// 每个 Controller 上的请求前缀
|
||||
context.put("requestMapping", dto.getRequestMapping());
|
||||
|
||||
// 表字段的注释内容
|
||||
context.put("comment", dto.getComment());
|
||||
|
||||
// 设置包名称
|
||||
context.put("package", dto.getPackageName());
|
||||
|
||||
// 将类名称转成小驼峰
|
||||
String toCamelCase = TypeConvertCore.convertToCamelCase(replaceTableName);
|
||||
context.put("classLowercaseName", toCamelCase);
|
||||
|
||||
// 将类名称转成大驼峰
|
||||
String convertToCamelCase = TypeConvertCore.convertToCamelCase(replaceTableName, true);
|
||||
context.put("classUppercaseName", convertToCamelCase);
|
||||
```mermaid
|
||||
graph TD
|
||||
A[🖥️ 前端界面] -->|HTTP请求| B[🌐 WebController]
|
||||
A -->|API调用| C[⚙️ GeneratorController]
|
||||
A -->|SQL解析| D[🔍 SqlParserController]
|
||||
A -->|元数据查询| E[🗃️ TableController]
|
||||
A -->|模板管理| F[📂 VmsController]
|
||||
|
||||
B -->|页面跳转| G[📝 Thymeleaf模板]
|
||||
C -->|生成请求| H[🏭 GeneratorService]
|
||||
D -->|SQL解析| I[✂️ SqlMetadataProvider]
|
||||
E -->|数据库查询| J[🔗 DatabaseMetadataProvider]
|
||||
F -->|模板扫描| K[🗃️ VmsService]
|
||||
|
||||
H -->|模板渲染| L[🌀 Velocity引擎]
|
||||
H -->|打包下载| M[📦 ZipFileUtil]
|
||||
I & J -->|数据源| N[💾 MySQL数据库]
|
||||
J -->|连接池| O[🛟 HikariCP]
|
||||
```
|
||||
|
||||

|
||||
### 1.2 核心分层 🔍
|
||||
|
||||
| 层级 | 组件 | 技术实现 |
|
||||
| ------------ | --------------- | -------------------- |
|
||||
| **接入层** 🚪 | Controllers | Spring Web, Swagger |
|
||||
| **业务层** ⚙️ | Services | 并行流, 设计模式 |
|
||||
| **核心层** 🧠 | 模板生成/元数据 | Velocity, JSqlParser |
|
||||
| **数据层** 💾 | 数据源/连接池 | HikariCP, JDBC |
|
||||
|
||||
---
|
||||
|
||||
## 2. 核心控制器详解 🎯
|
||||
|
||||
### 2.1 GeneratorController ⚡
|
||||
|
||||
**核心业务流:**
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
A[接收VmsArgumentDto] --> B{判断生成方式}
|
||||
B -->|SQL生成| C[调用generateCodeBySql]
|
||||
B -->|数据库生成| D[调用generateCodeByDatabase]
|
||||
C/D --> E[模板渲染]
|
||||
E --> F{是否打包下载}
|
||||
F -->|是| G[生成ZIP响应]
|
||||
F -->|否| H[返回代码预览]
|
||||
```
|
||||
|
||||
**关键方法说明:**
|
||||
|
||||
1. `generator()` 方法:
|
||||
|
||||
- 🔄 根据`sql`参数判断生成方式
|
||||
|
||||
- 📊 使用`VmsArgumentDto`接收模板选择、包名等参数
|
||||
|
||||
- 🌟 核心逻辑:
|
||||
|
||||
```java
|
||||
Strings.isEmpty(sql)
|
||||
? generatorService.generateCodeByDatabase(dto)
|
||||
: generatorService.generateCodeBySql(dto);
|
||||
```
|
||||
|
||||
2. `downloadByZip()` 方法:
|
||||
|
||||
- 🗜️ 使用`ZipFileUtil`进行内存压缩
|
||||
|
||||
- ⚡ 响应头设置:
|
||||
|
||||
```java
|
||||
headers.add("Content-Disposition", "attachment; filename=" + zipFilename);
|
||||
```
|
||||
|
||||
### 2.2 TableController 🗃️
|
||||
|
||||
**元数据查询体系:**
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
TableController --> TableService : 依赖
|
||||
TableService --> DatabaseMetadataProvider : 调用
|
||||
DatabaseMetadataProvider --> HikariDataSource : 使用
|
||||
|
||||
class TableController{
|
||||
+databaseInfoMetaData() 数据库基础信息
|
||||
+databaseTableList() 表清单
|
||||
+tableMetaData() 单表结构
|
||||
+tableColumnInfo() 列详情
|
||||
}
|
||||
```
|
||||
|
||||
**典型调用链:**
|
||||
|
||||
1. 获取数据库信息:
|
||||
|
||||
```java
|
||||
DatabaseMetaData metaData = connection.getMetaData();
|
||||
return DatabaseInfoMetaData.builder()
|
||||
.databaseProductName(metaData.getDatabaseProductName())
|
||||
.driverVersion(metaData.getDriverVersion())
|
||||
.build();
|
||||
```
|
||||
|
||||
2. 查询表结构:
|
||||
|
||||
```java
|
||||
ResultSet tables = metaData.getTables(dbName, null, "%", new String[]{"TABLE"});
|
||||
while(tables.next()){
|
||||
// 构建TableMetaData对象
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 关键技术实现 🔧
|
||||
|
||||
### 3.1 模板生成流程 🌀
|
||||
|
||||
**抽象模板方法模式:**
|
||||
|
||||
```java
|
||||
public abstract class AbstractTemplateGenerator {
|
||||
// 模板方法
|
||||
public final StringWriter generateCode() {
|
||||
prepareContext(); // 准备数据
|
||||
return mergeTemplate(); // 渲染模板
|
||||
}
|
||||
protected abstract void addContext(); // 子类实现
|
||||
}
|
||||
```
|
||||
|
||||
**Velocity上下文示例:**
|
||||
|
||||
```java
|
||||
context.put("classLowercaseName", "userInfo");
|
||||
context.put("columns", columnList);
|
||||
context.put("date", "2023-01-01");
|
||||
```
|
||||
|
||||
### 3.2 类型转换策略 🔄
|
||||
|
||||
**MySQL类型映射:**
|
||||
|
||||
| 数据库类型 | Java类型 | 转换方法 |
|
||||
| ---------- | --------------- | ------------------------------------------ |
|
||||
| `varchar` | `String` | `MysqlTypeConvertUtil.convertToJavaType()` |
|
||||
| `datetime` | `LocalDateTime` | 同上 |
|
||||
| `tinyint` | `Integer` | 同上 |
|
||||
|
||||
**命名转换示例:**
|
||||
|
||||
```java
|
||||
// 下划线转驼峰
|
||||
convertToCamelCase("user_name", true); // → "UserName"
|
||||
convertToCamelCase("user_name", false); // → "userName"
|
||||
```
|
||||
|
||||
## 4. 核心业务逻辑说明
|
||||
|
||||
### 4.1 代码生成流程
|
||||
|
||||
#### 4.1.1 简洁流畅
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant F as 前端
|
||||
participant C as Controller
|
||||
participant S as Service
|
||||
participant T as Template
|
||||
|
||||
F->>C: 提交生成请求(VmsArgumentDto)
|
||||
C->>S: 调用generateCodeBy[Sql/Database]
|
||||
S->>T: 准备Velocity上下文
|
||||
T->>S: 返回渲染结果
|
||||
S->>C: 返回Map<String, List<GeneratorVo>>
|
||||
C->>F: 返回结果或ZIP包
|
||||
```
|
||||
|
||||
### 4.1.2 详细流程
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant 前端
|
||||
participant GeneratorController
|
||||
participant GeneratorService
|
||||
participant MetadataProvider
|
||||
participant TemplateEngine
|
||||
|
||||
前端->>GeneratorController: 提交生成请求
|
||||
GeneratorController->>GeneratorService: 调用生成方法
|
||||
GeneratorService->>MetadataProvider: 获取表/列元数据
|
||||
MetadataProvider-->>GeneratorService: 返回元数据
|
||||
GeneratorService->>TemplateEngine: 填充模板
|
||||
TemplateEngine-->>GeneratorService: 生成代码
|
||||
GeneratorService-->>GeneratorController: 返回生成结果
|
||||
GeneratorController-->>前端: 返回代码/ZIP包
|
||||
```
|
||||
|
||||
### 4.2 关键类协作
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class GeneratorController {
|
||||
+generateCode()
|
||||
+downloadByZip()
|
||||
}
|
||||
|
||||
class GeneratorService {
|
||||
+generateCodeByDatabase()
|
||||
+generateCodeBySql()
|
||||
}
|
||||
|
||||
class AbstractTemplateGenerator {
|
||||
+generateCode()
|
||||
#addContext()
|
||||
#templateMerge()
|
||||
}
|
||||
|
||||
class VmsTBaseTemplateGenerator {
|
||||
+addContext()
|
||||
+templateMerge()
|
||||
}
|
||||
|
||||
GeneratorController --> GeneratorService
|
||||
GeneratorService --> AbstractTemplateGenerator
|
||||
AbstractTemplateGenerator <|-- VmsTBaseTemplateGenerator
|
||||
```
|
||||
|
||||
## 5. 扩展指南 🛠️
|
||||
|
||||
### 5.1 添加新数据库支持
|
||||
|
||||
1. 实现`DatabaseDialect`接口 ✏️
|
||||
|
||||
2. 配置新的`IMetadataProvider` ⚙️
|
||||
|
||||
3. 示例代码结构:
|
||||
|
||||
```java
|
||||
@Component
|
||||
public class OracleDialect implements DatabaseDialect {
|
||||
// 实现提取注释等方法
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 依赖管理 📦
|
||||
|
||||
**关键依赖说明:**
|
||||
|
||||
```xml
|
||||
<!-- SQL解析 -->
|
||||
<dependency>
|
||||
<groupId>com.github.jsqlparser</groupId>
|
||||
<artifactId>jsqlparser</artifactId>
|
||||
<version>4.9</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 模板引擎 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.velocity</groupId>
|
||||
<artifactId>velocity-engine-core</artifactId>
|
||||
<version>2.2</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## 6. 注意事项 ⚠️
|
||||
|
||||
### 6.1 性能优化点 🚀
|
||||
|
||||
- `parallelStream()` 用于批量生成
|
||||
|
||||
- `HikariCP` 配置:
|
||||
|
||||
```yaml
|
||||
hikari:
|
||||
maximum-pool-size: 20
|
||||
connection-timeout: 30000
|
||||
```
|
||||
|
||||
### 6.2 版本兼容性 🔗
|
||||
|
||||
| 组件 | 已验证版本 |
|
||||
| ----------- | ---------- |
|
||||
| MySQL | 8.0+ |
|
||||
| JDK | 17+ |
|
||||
| Spring Boot | 3.4.3 |
|
||||
|
||||
### 6.3 文件名重复问题解决方案⚠️
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[用户选择模板] --> B{是否包含$className}
|
||||
B -->|是| C[替换为表名的小驼峰格式]
|
||||
B -->|否| D[保持原文件名]
|
||||
C --> E[生成唯一文件路径]
|
||||
D --> E
|
||||
```
|
||||
|
||||
**核心逻辑实现**(位于`VmsGeneratorPathHelper.java`):
|
||||
|
||||
```java
|
||||
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("/");
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
**必须遵守的规则**:
|
||||
|
||||
> [!Note]
|
||||
>
|
||||
> 如果不想使用`$className`可自己修改源码,进行更改。
|
||||
|
||||
1. 前端文件必须使用`$className`作为动态目录名(如:`web/$className/api.ts`)
|
||||
2. 相同基础名称的文件必须放在不同目录下
|
||||
3. Java/XML文件会自动添加类型后缀(如`UserController.java`)
|
||||
|
||||
### 6.4 文件命名冲突场景示例
|
||||
|
||||
| 错误案例 | 正确方案 | 原因 |
|
||||
| ----------------------- | -------------------------- | -------------------------- |
|
||||
| `web/index.vue` | `web/$className/index.vue` | 多表生成时会冲突 |
|
||||
| `mapper/UserMapper.xml` | 自动处理 | 系统会自动添加表名前缀 |
|
||||
| `service/Service.java` | 自动处理 | 会转换为`UserService.java` |
|
||||
|
||||
## 7. 代码质量评估 🔍
|
||||
|
||||
### 7.1 后端代码评估(85/100)
|
||||
|
||||
**优势**:
|
||||
|
||||
- ✅ 清晰的层次划分(Controller/Service/Provider)
|
||||
- ✅ 合理使用设计模式(模板方法模式)
|
||||
- ✅ 完善的异常处理体系
|
||||
- ✅ 类型转换工具类封装良好
|
||||
|
||||
**待改进**:
|
||||
|
||||
- ⚠️ 部分SQL解析逻辑可抽取为策略模式
|
||||
- ⚠️ 元数据提供者接口可进一步抽象
|
||||
- ⚠️ ZIP打包逻辑与业务耦合稍紧
|
||||
|
||||
**坏味道检测**:
|
||||
|
||||
```mermaid
|
||||
pie
|
||||
title 后端代码坏味道分布
|
||||
"重复代码" : 15
|
||||
"过长方法" : 10
|
||||
"过度耦合" : 5
|
||||
"其他" : 70
|
||||
```
|
||||
|
||||
### 7.2 前端代码评估(78/100)
|
||||
|
||||
**亮点**:
|
||||
|
||||
- ✅ 组件化设计合理(表单/表格分离)
|
||||
- ✅ 响应式状态管理有效
|
||||
- ✅ 良好的用户交互反馈
|
||||
- ✅ 类型提示完善
|
||||
|
||||
**问题点**:
|
||||
|
||||
- ⚠️ 部分表单验证逻辑重复
|
||||
- ⚠️ 表格分页逻辑可抽取为独立组件
|
||||
|
||||
**复杂度分析**:
|
||||
|
||||
| 文件 | 方法数 | 平均行数 | 复杂度 |
|
||||
| ------------------- | ------ | -------- | ------ |
|
||||
| DatabaseTable.js | 12 | 18 | 中等 |
|
||||
| DatabaseForm.js | 25 | 12 | 较高 |
|
||||
| AppGeneratorPage.js | 8 | 15 | 低 |
|
||||
|
||||
### 7.3 总结评价
|
||||
|
||||
**整体评分**:82/100
|
||||
|
||||
**可维护性**:⭐️⭐️⭐️⭐️
|
||||
|
||||
**扩展性**:⭐️⭐️⭐️⭐️
|
||||
|
||||
**代码规范**:⭐️⭐️⭐️⭐️⭐️
|
||||
|
||||
## 8. 支持项目☕
|
||||
|
||||
如果这个项目对您有帮助,可以考虑支持我们:
|
||||
|
||||

|
||||
|
||||
**Happy Coding!** 🎉
|
|
@ -1,39 +0,0 @@
|
|||
package cn.bunny.controller;
|
||||
|
||||
import cn.bunny.core.factory.ConcreteSqlParserDatabaseInfo;
|
||||
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 org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Tag(name = "解析SQL", description = "解析SQL接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/sqlParser")
|
||||
public class SqlParserController {
|
||||
|
||||
@Resource
|
||||
private SqlParserService sqlParserService;
|
||||
|
||||
@Operation(summary = "解析SQL成表信息", description = "解析SQL成表信息")
|
||||
@PostMapping("tableInfo")
|
||||
public Result<TableMetaData> tableInfo(String sql) {
|
||||
TableMetaData vo = sqlParserService.tableInfo(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);
|
||||
return Result.success(vo);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -1,169 +0,0 @@
|
|||
package cn.bunny.core.factory;
|
||||
|
||||
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 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.util.*;
|
||||
|
||||
@Component
|
||||
public class ConcreteDatabaseInfo extends AbstractDatabaseInfo {
|
||||
|
||||
@Value("${bunny.master.database}")
|
||||
private String currentDatabase;
|
||||
|
||||
/**
|
||||
* 数据库所有的信息
|
||||
*
|
||||
* @return 当前连接的数据库信息属性
|
||||
*/
|
||||
@SneakyThrows
|
||||
public DatabaseInfoMetaData databaseInfoMetaData() {
|
||||
try (Connection connection = dataSource.getConnection()) {
|
||||
DatabaseMetaData metaData = connection.getMetaData();
|
||||
|
||||
return DatabaseInfoMetaData.builder()
|
||||
.databaseProductName(metaData.getDatabaseProductName())
|
||||
.databaseProductVersion(metaData.getDatabaseProductVersion())
|
||||
.driverName(metaData.getDriverName())
|
||||
.driverVersion(metaData.getDriverVersion())
|
||||
.url(metaData.getURL())
|
||||
.username(metaData.getUserName())
|
||||
.currentDatabase(currentDatabase)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 sql 表信息
|
||||
*
|
||||
* @param tableName 表名称或sql
|
||||
* @return 表西悉尼
|
||||
*/
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public TableMetaData getTableMetadata(String tableName) {
|
||||
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");
|
||||
|
||||
// 通常是"TABLE"
|
||||
String tableType = tables.getString("TABLE_TYPE");
|
||||
|
||||
tableMetaData = TableMetaData.builder()
|
||||
.tableName(tableName)
|
||||
.comment(remarks)
|
||||
.tableCat(tableCat)
|
||||
.tableType(tableType)
|
||||
.build();
|
||||
} else {
|
||||
throw new RuntimeException("数据表不存在");
|
||||
}
|
||||
|
||||
return tableMetaData;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取[当前/所有]数据库表
|
||||
*
|
||||
* @return 所有表信息
|
||||
*/
|
||||
@SneakyThrows
|
||||
public List<TableMetaData> databaseTableList(String dbName) {
|
||||
// 当前数据库数据库所有的表
|
||||
List<TableMetaData> allTableInfo = new ArrayList<>();
|
||||
|
||||
try (Connection connection = dataSource.getConnection()) {
|
||||
DatabaseMetaData metaData = connection.getMetaData();
|
||||
|
||||
// 当前数据库中所有的表
|
||||
ResultSet tables = metaData.getTables(dbName, null, "%", new String[]{"TABLE"});
|
||||
|
||||
while (tables.next()) {
|
||||
// 表名称
|
||||
dbName = tables.getString("TABLE_NAME");
|
||||
|
||||
// 设置表信息
|
||||
TableMetaData tableMetaData = getTableMetadata(dbName);
|
||||
|
||||
allTableInfo.add(tableMetaData);
|
||||
}
|
||||
}
|
||||
|
||||
return allTableInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前表的列属性
|
||||
*
|
||||
* @param tableName 表名称或sql
|
||||
* @return 当前表所有的列内容
|
||||
*/
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public List<ColumnMetaData> tableColumnInfo(String tableName) {
|
||||
try (Connection connection = dataSource.getConnection()) {
|
||||
DatabaseMetaData metaData = connection.getMetaData();
|
||||
Map<String, ColumnMetaData> map = new LinkedHashMap<>();
|
||||
// 当前表的主键
|
||||
Set<String> primaryKeyColumns = findPrimaryKeyColumns(tableName);
|
||||
|
||||
// 当前表的列信息
|
||||
try (ResultSet columnsRs = metaData.getColumns(null, null, tableName, 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(TypeConvertUtil.convertToCamelCase(column.getColumnName()));
|
||||
// 列字段转成 下划线 -> 大驼峰名称
|
||||
column.setUppercaseName(TypeConvertUtil.convertToCamelCase(column.getColumnName(), true));
|
||||
// 字段类型
|
||||
column.setJdbcType(typeName);
|
||||
// 字段类型转 Java 类型
|
||||
String javaType = TypeConvertUtil.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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 AbstractVmsGeneratorTemplate {
|
||||
|
||||
/**
|
||||
* 添加生成内容
|
||||
*/
|
||||
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,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,33 +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 TableMetaData {
|
||||
|
||||
/* 表名 */
|
||||
private String tableName;
|
||||
|
||||
/* 注释内容 */
|
||||
private String comment;
|
||||
|
||||
/* 表目录 */
|
||||
private String tableCat;
|
||||
|
||||
/* 表类型(通常是"TABLE") */
|
||||
private String tableType;
|
||||
|
||||
/* 类名 */
|
||||
private String className;
|
||||
|
||||
/* 列名称 */
|
||||
private List<ColumnMetaData> columns;
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
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();
|
||||
}
|
|
@ -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,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;
|
||||
}
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
package cn.bunny.service.impl;
|
||||
|
||||
import cn.bunny.core.factory.ConcreteDatabaseInfo;
|
||||
import cn.bunny.domain.entity.ColumnMetaData;
|
||||
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;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库所有的信息
|
||||
*
|
||||
* @return 当前连接的数据库信息属性
|
||||
*/
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public DatabaseInfoMetaData databaseInfoMetaData() {
|
||||
List<TableMetaData> databaseTableList = databaseTableList(null);
|
||||
|
||||
// 将当前数据库表分组,以数据库名称为key
|
||||
List<TableMetaData> databaseList = databaseTableList.stream()
|
||||
.collect(Collectors.groupingBy(TableMetaData::getTableCat))
|
||||
.values().stream()
|
||||
.map(tableInfoVos -> {
|
||||
TableMetaData tableInfoVo = tableInfoVos.get(0);
|
||||
tableInfoVo.setTableName(null);
|
||||
return tableInfoVo;
|
||||
}).toList();
|
||||
|
||||
DatabaseInfoMetaData databaseInfoMetaData = databaseInfoCore.databaseInfoMetaData();
|
||||
databaseInfoMetaData.setDatabaseList(databaseList);
|
||||
|
||||
return databaseInfoMetaData;
|
||||
}
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
package cn.bunny.service.impl;
|
||||
|
||||
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.service.impl.vms.VmsCodeGeneratorService;
|
||||
import cn.bunny.service.impl.vms.VmsZipService;
|
||||
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;
|
||||
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);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public Map<String, List<VmsPathVo>> vmsResourcePathList() {
|
||||
List<String> vmsRelativeFiles = ResourceFileUtil.getRelativeFiles("vms");
|
||||
|
||||
return 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));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<byte[]> downloadByZip(VmsArgumentDto dto) {
|
||||
byte[] zipBytes = zipService.createZipFile(dto);
|
||||
|
||||
// 下载文件名称
|
||||
long currentTimeMillis = System.currentTimeMillis();
|
||||
String digestHex = MD5.create().digestHex(currentTimeMillis + "");
|
||||
String generateZipFilename = "generator-" + digestHex.substring(0, 4) + ".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,91 +0,0 @@
|
|||
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.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 ConcreteDatabaseInfo databaseInfoCore;
|
||||
private final ConcreteSqlParserDatabaseInfo sqlParserDatabaseInfo;
|
||||
|
||||
/**
|
||||
* 根据DTO生成代码模板
|
||||
*
|
||||
* @param dto 包含生成参数的数据传输对象
|
||||
* @return 生成的代码模板列表
|
||||
*/
|
||||
public Map<String, List<GeneratorVo>> generateCode(VmsArgumentDto dto) {
|
||||
String sql = dto.getSql();
|
||||
|
||||
return dto.getTableNames().stream()
|
||||
.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();
|
||||
})
|
||||
.toList();
|
||||
})
|
||||
.flatMap(List::stream)
|
||||
.collect(Collectors.groupingBy(GeneratorVo::getTableName));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表元数据
|
||||
*
|
||||
* @param dto 生成参数
|
||||
* @param tableName 表名
|
||||
* @return 表元数据对象
|
||||
*/
|
||||
private TableMetaData getTableMetadata(VmsArgumentDto dto, String tableName) {
|
||||
return StringUtils.hasText(dto.getSql())
|
||||
? sqlParserDatabaseInfo.getTableMetadata(dto.getSql())
|
||||
: databaseInfoCore.getTableMetadata(tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取列信息列表
|
||||
*
|
||||
* @param sql SQL语句
|
||||
* @param tableName 表名
|
||||
* @return 列元数据列表
|
||||
*/
|
||||
private List<ColumnMetaData> getColumnInfoList(String sql, String tableName) {
|
||||
return StringUtils.hasText(sql)
|
||||
? sqlParserDatabaseInfo.tableColumnInfo(sql)
|
||||
: databaseInfoCore.tableColumnInfo(tableName).stream().distinct().toList();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
package cn.bunny.service.impl.vms;
|
||||
|
||||
import cn.bunny.domain.dto.VmsArgumentDto;
|
||||
import cn.bunny.domain.vo.GeneratorVo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
/**
|
||||
* 负责处理ZIP打包和下载逻辑的服务类
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class VmsZipService {
|
||||
|
||||
private final VmsCodeGeneratorService codeGeneratorService;
|
||||
|
||||
/**
|
||||
* 创建ZIP文件字节数组
|
||||
*
|
||||
* @param dto 生成参数
|
||||
* @return ZIP文件字节数组
|
||||
*/
|
||||
public byte[] createZipFile(VmsArgumentDto dto) {
|
||||
List<GeneratorVo> generatorVoList = codeGeneratorService.generateCode(dto).values().stream().flatMap(Collection::stream).toList();
|
||||
|
||||
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
|
||||
|
||||
generatorVoList.forEach(generatorVo -> addToZip(zipOutputStream, generatorVo));
|
||||
return byteArrayOutputStream.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to create ZIP file", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将单个生成结果添加到ZIP文件
|
||||
*
|
||||
* @param zipOutputStream ZIP输出流
|
||||
* @param generatorVo 生成结果对象
|
||||
*/
|
||||
private void addToZip(ZipOutputStream zipOutputStream, GeneratorVo generatorVo) {
|
||||
try {
|
||||
String path = generatorVo.getPath().replace(".vm", "");
|
||||
ZipEntry zipEntry = new ZipEntry(path);
|
||||
zipOutputStream.putNextEntry(zipEntry);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,71 +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 = TypeConvertUtil.convertToCamelCase(className, true);
|
||||
// 小驼峰名称
|
||||
String camelCase = TypeConvertUtil.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("index")) {
|
||||
filename = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, camelCase) + "-" + name + "." + extension;
|
||||
}
|
||||
|
||||
splitPaths[splitPathsSize] = filename;
|
||||
return String.join("/", splitPaths);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成前端标签上的id
|
||||
*
|
||||
* @return id-UUID
|
||||
*/
|
||||
public static String generateDivId() {
|
||||
return "id-" + UUID.randomUUID().toString().replace("-", "");
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
package cn.bunny.web;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@Controller
|
||||
public class IndexController {
|
||||
|
||||
@GetMapping("/")
|
||||
public String index() {
|
||||
return "home";
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
_ _
|
||||
| |__ _ _ _ __ _ __ _ _ (_) __ ___ ____ _
|
||||
| '_ \| | | | '_ \| '_ \| | | | | |/ _` \ \ / / _` |
|
||||
| |_) | |_| | | | | | | | |_| | | | (_| |\ V | (_| |
|
||||
|_.__/ \__,_|_| |_|_| |_|\__, | _/ |\__,_| \_/ \__,_|
|
||||
|___/ |__/
|
||||
|
||||
Service Name${spring.application.name}
|
||||
SpringBoot Version: ${spring-boot.version}${spring-boot.formatted-version}
|
||||
SpringActive:${spring.profiles.active}
|
|
@ -1,16 +0,0 @@
|
|||
// 如果一开始定义过了 defineComponent 就不要在下 script 标签中再写了
|
||||
const {defineComponent} = Vue;
|
||||
|
||||
// 定义 Header 组件
|
||||
const AppHeader = defineComponent({
|
||||
name: "AppHeader",
|
||||
template: `
|
||||
<div class="text-center mb-4">
|
||||
<h2 class="text-primary fw-bold">
|
||||
<i class="bi bi-code-square me-2"></i>
|
||||
Bunny代码生成器
|
||||
</h2>
|
||||
<p class="text-muted">快速生成数据库表对应的代码</p>
|
||||
</div>
|
||||
`
|
||||
});
|
File diff suppressed because one or more lines are too long
|
@ -1,317 +0,0 @@
|
|||
const MainTable = defineComponent({
|
||||
name: "MainTable",
|
||||
template: `
|
||||
<div class="card mt-2 shadow-sm">
|
||||
<!-- 表格标题和选择选项 -->
|
||||
<div class="d-flex justify-content-between card-header bg-primary bg-opacity-10">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="bi bi-table me-2"></i>数据表列表
|
||||
</h5>
|
||||
|
||||
<div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" name="TableSelectRadios" type="radio" id="radioSelectAll"
|
||||
value="all" v-model="selectedOption">
|
||||
<label class="form-check-label" for="radioSelectAll">选择全部</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" name="TableSelectRadios" type="radio" id="radioSelectCurrentPage"
|
||||
value="current" v-model="selectedOption">
|
||||
<label class="form-check-label" for="radioSelectCurrentPage">选择当前页</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表格内容区域 -->
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<!-- 加载状态 -->
|
||||
<div class="p-5 text-center" v-if="loading">
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态提示 -->
|
||||
<div class="p-5 text-center" v-else-if="tableList.length === 0">
|
||||
<i class="bi bi-exclamation-circle fs-1 text-muted"></i>
|
||||
<p class="mt-2 text-muted">没有找到数据表</p>
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<table class="table table-striped table-bordered table-hover mb-0" v-else>
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th scope="col" width="2%">
|
||||
<input class="form-check-input border-secondary" type="checkbox"
|
||||
v-model="tableSelectAllChecked" @change="onSelectAllTable">
|
||||
</th>
|
||||
<th scope="col" width="3%">#</th>
|
||||
<th scope="col" width="30%">表名</th>
|
||||
<th scope="col" width="35%">注释</th>
|
||||
<th scope="col" width="20%">所属数据库</th>
|
||||
<th class="text-center" scope="col" width="10%">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<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)">
|
||||
</td>
|
||||
<td>{{ (currentPage - 1) * itemsPerPage + index + 1 }}</td>
|
||||
<td>
|
||||
<a class="text-decoration-none" href="#" @click.prevent="onGeneratorCode(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>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分页控件 -->
|
||||
<div class="card-footer bg-light" v-if="tableList.length > 0">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="form-text">
|
||||
显示 {{ (currentPage - 1) * itemsPerPage + 1 }}~{{ Math.min(currentPage * itemsPerPage, rawTableList.length) }} 条,共 {{ rawTableList.length }} 条
|
||||
</div>
|
||||
|
||||
<nav aria-label="Page navigation">
|
||||
<ul class="pagination mb-0">
|
||||
<li :class="{ disabled: currentPage === 1 }" class="page-item">
|
||||
<a @click.prevent="goToPage(1)" class="page-link" href="#">
|
||||
<i class="bi bi-chevron-double-left"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li :class="{ disabled: currentPage === 1 }" class="page-item">
|
||||
<a @click.prevent="goToPage(currentPage - 1)" class="page-link" href="#">
|
||||
<i class="bi bi-chevron-left"></i>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- 显示页码 -->
|
||||
<li v-for="page in visiblePages" :key="page"
|
||||
:class="{ active: currentPage === page }" class="page-item">
|
||||
<a @click.prevent="goToPage(page)" class="page-link" href="#">{{ page }}</a>
|
||||
</li>
|
||||
|
||||
<li :class="{ disabled: currentPage === totalPages }" class="page-item">
|
||||
<a @click.prevent="goToPage(currentPage + 1)" class="page-link" href="#">
|
||||
<i class="bi bi-chevron-right"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li :class="{ disabled: currentPage === totalPages }" class="page-item">
|
||||
<a @click.prevent="goToPage(totalPages)" class="page-link" href="#">
|
||||
<i class="bi bi-chevron-double-right"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div class="dropdown">
|
||||
<button aria-expanded="false" class="btn btn-outline-secondary dropdown-toggle"
|
||||
data-bs-toggle="dropdown" id="itemsPerPageDropdown" type="button">
|
||||
每页 {{ itemsPerPage }} 条
|
||||
</button>
|
||||
<ul aria-labelledby="itemsPerPageDropdown" class="dropdown-menu">
|
||||
<li v-for="option in tablePageOptions" :key="option">
|
||||
<a @click.prevent="changeItemsPerPage(option)" class="dropdown-item" href="#">
|
||||
{{ option }} 条/页
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
props: {
|
||||
// 加载状态
|
||||
loading: {type: Boolean, default: false},
|
||||
// 处理后的表格数据(包含checked状态)
|
||||
tableList: {type: Array, required: true, default: () => []},
|
||||
// 原始表格数据(不包含checked状态)
|
||||
rawTableList: {type: Array, required: true, default: () => []},
|
||||
// 生成代码的回调函数
|
||||
onGeneratorCode: {type: Function, required: true},
|
||||
// 表单数据
|
||||
form: {type: Object, required: true},
|
||||
// 数据库选择发生变化
|
||||
dbSelect: {type: String, required: true},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 当前页码
|
||||
currentPage: 1,
|
||||
// 每页显示条数
|
||||
itemsPerPage: 30,
|
||||
// 每页条数选项
|
||||
tablePageOptions: [5, 10, 15, 30, 50, 100, 150, 200],
|
||||
// 选择模式:all-全选 current-当前页
|
||||
selectedOption: "current",
|
||||
// 表头全选状态
|
||||
tableSelectAllChecked: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/**
|
||||
* 计算总页数
|
||||
* @returns {number} 总页数
|
||||
*/
|
||||
totalPages() {
|
||||
return Math.ceil(this.rawTableList.length / this.itemsPerPage);
|
||||
},
|
||||
|
||||
/**
|
||||
* 当前页的数据
|
||||
* @returns {Array} 分页后的数据
|
||||
*/
|
||||
paginatedTableList() {
|
||||
const start = (this.currentPage - 1) * this.itemsPerPage;
|
||||
const end = start + this.itemsPerPage;
|
||||
return this.tableList.slice(start, end);
|
||||
},
|
||||
|
||||
/**
|
||||
* 当前选中的表名数组
|
||||
* @returns {Array} 选中的表名
|
||||
*/
|
||||
selectedTableNames() {
|
||||
return this.tableList.filter(table => table.checked).map(table => table.tableName);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 跳转到指定页码
|
||||
* @param {number} page - 目标页码
|
||||
*/
|
||||
goToPage(page) {
|
||||
if (page >= 1 && page <= this.totalPages) {
|
||||
this.currentPage = page;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 更改每页显示条数
|
||||
* @param {number} size - 每页条数
|
||||
*/
|
||||
changeItemsPerPage(size) {
|
||||
this.itemsPerPage = size;
|
||||
// 重置到第一页
|
||||
this.currentPage = 1;
|
||||
},
|
||||
|
||||
/**
|
||||
* 计算可见的页码范围
|
||||
* @returns {Array} 可见页码数组
|
||||
*/
|
||||
visiblePages() {
|
||||
// 显示当前页前后各2页
|
||||
const range = 2;
|
||||
const start = Math.max(1, this.currentPage - range);
|
||||
const end = Math.min(this.totalPages, this.currentPage + range);
|
||||
|
||||
const pages = [];
|
||||
for (let i = start; i <= end; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
return pages;
|
||||
},
|
||||
|
||||
/**
|
||||
* 表头全选/取消全选
|
||||
* 当前选择的是所有书库,操作列表为 this.tableList
|
||||
* 当前选择的是当前页的数据库表,操作列表为 this.paginatedTableList
|
||||
*/
|
||||
onSelectAllTable() {
|
||||
const checked = this.tableSelectAllChecked;
|
||||
|
||||
// 选择是否是所有的数据库
|
||||
const tablesToUpdate = this.selectedOption === "all"
|
||||
? this.tableList
|
||||
: this.paginatedTableList;
|
||||
|
||||
// 将数据库变为当前选中的状态
|
||||
tablesToUpdate.forEach(table => table.checked = checked);
|
||||
|
||||
// 更新父级列表状态
|
||||
this.updateFormSelectedTables();
|
||||
},
|
||||
|
||||
/**
|
||||
* 选择单个表
|
||||
* @param {Event} event - 事件对象
|
||||
* @param {Object} table - 表数据对象
|
||||
*/
|
||||
onSelectTable(event, table) {
|
||||
table.checked = event.target.checked;
|
||||
this.updateFormSelectedTables();
|
||||
|
||||
// 更新表头全选状态
|
||||
if (!table.checked) {
|
||||
this.tableSelectAllChecked = false;
|
||||
} else {
|
||||
this.tableSelectAllChecked = this.paginatedTableList.every(t => t.checked);
|
||||
}
|
||||
},
|
||||
|
||||
/* 更新表单中选中的表名 */
|
||||
updateFormSelectedTables() {
|
||||
const payload = {
|
||||
...this.form,
|
||||
tableNames: this.selectedTableNames
|
||||
}
|
||||
// 更新父级 form 的内容
|
||||
this.$emit("update:form", payload);
|
||||
},
|
||||
|
||||
/**
|
||||
* 重置父级表单
|
||||
* 如果要同时更新会变得很复杂所以在这里讲逻辑定义为:
|
||||
* 选型变化时直接取消全部,之后重新选择
|
||||
*/
|
||||
restForm() {
|
||||
this.tableSelectAllChecked = false;
|
||||
this.tableList.forEach(table => table.checked = false);
|
||||
this.updateFormSelectedTables();
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
/**
|
||||
* 监听选择模式变化
|
||||
*/
|
||||
selectedOption() {
|
||||
this.restForm();
|
||||
},
|
||||
|
||||
/**
|
||||
* 监听当前页变化
|
||||
*/
|
||||
currentPage() {
|
||||
this.restForm();
|
||||
},
|
||||
|
||||
/**
|
||||
* 监听每页条数变化
|
||||
*/
|
||||
itemsPerPage() {
|
||||
this.restForm();
|
||||
},
|
||||
|
||||
/**
|
||||
* 数据库选择发生变化也重置表单
|
||||
*/
|
||||
dbSelect() {
|
||||
this.restForm();
|
||||
}
|
||||
}
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
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) {
|
||||
// 判断当前是使用 SQL语句 生成还是 数据库生成
|
||||
String sql = dto.getSql();
|
||||
|
||||
Map<String, List<GeneratorVo>> result = Strings.isEmpty(sql)
|
||||
? 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) {
|
||||
// 判断当前是使用 SQL语句 生成还是 数据库生成
|
||||
String sql = dto.getSql();
|
||||
|
||||
return Strings.isEmpty(sql)
|
||||
? generatorService.downloadByZipByDatabase(dto)
|
||||
: generatorService.downloadByZipBySqL(dto);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package cn.bunny.controller;
|
||||
|
||||
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 io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* SQL解析控制器
|
||||
* 提供SQL语句解析功能,提取表和列元数据
|
||||
*/
|
||||
@Tag(name = "解析SQL", description = "解析SQL接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/sqlParser")
|
||||
@RequiredArgsConstructor
|
||||
public class SqlParserController {
|
||||
|
||||
private final SqlMetadataProvider sqlMetadataProvider;
|
||||
|
||||
/**
|
||||
* 解析SQL获取表信息
|
||||
*
|
||||
* @param sql SQL语句
|
||||
* @return 表元数据
|
||||
*/
|
||||
@Operation(summary = "解析SQL表信息", description = "解析SQL语句提取表结构信息")
|
||||
@PostMapping("tableInfo")
|
||||
public Result<TableMetaData> tableInfo(String sql) {
|
||||
return Result.success(sqlMetadataProvider.getTableMetadata(sql));
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析SQL获取列信息
|
||||
*
|
||||
* @param sql SQL语句
|
||||
* @return 列元数据列表
|
||||
*/
|
||||
@Operation(summary = "解析SQL列数据", description = "解析SQL语句提取列结构信息")
|
||||
@PostMapping("columnMetaData")
|
||||
public Result<List<ColumnMetaData>> columnMetaData(String sql) {
|
||||
return Result.success(sqlMetadataProvider.getColumnInfoList(sql));
|
||||
}
|
||||
}
|
|
@ -1,13 +1,15 @@
|
|||
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;
|
||||
import cn.bunny.domain.result.Result;
|
||||
import cn.bunny.domain.result.ResultCodeEnum;
|
||||
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 +19,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 +35,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);
|
||||
return Result.success(columnInfo);
|
||||
List<ColumnMetaData> columnInfo = databaseMetadataProvider.getColumnInfoList(tableName);
|
||||
return Result.success(columnInfo, ResultCodeEnum.LOAD_FINISHED);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,16 +1,14 @@
|
|||
package cn.bunny.controller;
|
||||
|
||||
import cn.bunny.domain.dto.VmsArgumentDto;
|
||||
import cn.bunny.domain.result.Result;
|
||||
import cn.bunny.domain.vo.GeneratorVo;
|
||||
import cn.bunny.domain.vo.VmsPathVo;
|
||||
import cn.bunny.service.VmsService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -30,16 +28,4 @@ public class VmsController {
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package cn.bunny.controller;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@Controller
|
||||
public class WebController {
|
||||
|
||||
@GetMapping("/")
|
||||
public String indexPage() {
|
||||
return "redirect:/database";
|
||||
}
|
||||
|
||||
@GetMapping("database")
|
||||
public String databasePage() {
|
||||
return "database";
|
||||
}
|
||||
|
||||
@GetMapping("/sql")
|
||||
public String sqlPage() {
|
||||
return "sql";
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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 sqlStatement sql语句
|
||||
* @return 表西悉尼
|
||||
* @see CCJSqlParserUtil 使用这个工具进行SQL的解析
|
||||
*/
|
||||
@Override
|
||||
public TableMetaData getTableMetadata(String sql) {
|
||||
public TableMetaData getTableMetadata(String sqlStatement) {
|
||||
TableMetaData tableInfo = new TableMetaData();
|
||||
|
||||
// 解析sql
|
||||
Statement statement;
|
||||
try {
|
||||
statement = CCJSqlParserUtil.parse(sql);
|
||||
statement = CCJSqlParserUtil.parse(sqlStatement);
|
||||
} 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 sqlStatement sql语句
|
||||
* @return 当前表所有的列内容
|
||||
*/
|
||||
@Override
|
||||
public List<ColumnMetaData> tableColumnInfo(String sql) {
|
||||
public List<ColumnMetaData> getColumnInfoList(String sqlStatement) {
|
||||
// 解析sql
|
||||
Statement statement;
|
||||
try {
|
||||
statement = CCJSqlParserUtil.parse(sql);
|
||||
statement = CCJSqlParserUtil.parse(sqlStatement);
|
||||
} 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,26 @@ 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()));
|
||||
String lowercaseName = MysqlTypeConvertUtil.convertToCamelCase(column.getColumnName(), false);
|
||||
columnInfo.setLowercaseName(lowercaseName);
|
||||
|
||||
// 列字段转成 下划线 -> 大驼峰名称
|
||||
columnInfo.setUppercaseName(TypeConvertUtil.convertToCamelCase(column.getColumnName(), true));
|
||||
String uppercaseName = MysqlTypeConvertUtil.convertToCamelCase(column.getColumnName(), true);
|
||||
columnInfo.setUppercaseName(uppercaseName);
|
||||
|
||||
// 解析注释
|
||||
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();
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
|
@ -38,7 +38,7 @@ public class VmsArgumentDtoBaseVmsGeneratorTemplate extends AbstractVmsGenerator
|
|||
* @param context VelocityContext
|
||||
*/
|
||||
@Override
|
||||
void addContext(VelocityContext context) {
|
||||
public void addContext(VelocityContext context) {
|
||||
// 当前的表名
|
||||
String tableName = tableMetaData.getTableName();
|
||||
// 表的注释内容
|
||||
|
@ -61,12 +61,12 @@ public class VmsArgumentDtoBaseVmsGeneratorTemplate extends AbstractVmsGenerator
|
|||
context.put("package", dto.getPackageName());
|
||||
|
||||
// 将类名称转成小驼峰
|
||||
String toCamelCase = TypeConvertUtil.convertToCamelCase(tableName);
|
||||
context.put("classLowercaseName", toCamelCase);
|
||||
String lowerCamelCase = MysqlTypeConvertUtil.convertToCamelCase(tableName, false);
|
||||
context.put("classLowercaseName", lowerCamelCase);
|
||||
|
||||
// 将类名称转成大驼峰
|
||||
String convertToCamelCase = TypeConvertUtil.convertToCamelCase(tableName, true);
|
||||
context.put("classUppercaseName", convertToCamelCase);
|
||||
String upperCameCase = MysqlTypeConvertUtil.convertToCamelCase(tableName, true);
|
||||
context.put("classUppercaseName", upperCameCase);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -76,7 +76,7 @@ public class VmsArgumentDtoBaseVmsGeneratorTemplate extends AbstractVmsGenerator
|
|||
* @param writer StringWriter 写入
|
||||
*/
|
||||
@Override
|
||||
void templateMerge(VelocityContext context, StringWriter writer) {
|
||||
public void templateMerge(VelocityContext context, StringWriter writer) {
|
||||
Template servicePathTemplate = Velocity.getTemplate("vms/" + path, "UTF-8");
|
||||
servicePathTemplate.merge(context, writer);
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
package cn.bunny.domain.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
@ -18,25 +18,26 @@ import java.util.List;
|
|||
public class VmsArgumentDto {
|
||||
|
||||
@Schema(name = "author", description = "作者名称")
|
||||
String author;
|
||||
String author = "";
|
||||
|
||||
@Schema(name = "packageName", description = "包名称")
|
||||
@NotBlank(message = "包名不能为空")
|
||||
String packageName;
|
||||
|
||||
@Schema(name = "requestMapping", description = "requestMapping 名称")
|
||||
String requestMapping;
|
||||
String requestMapping = "";
|
||||
|
||||
@NotNull(message = "表名称不能为空")
|
||||
@NotEmpty(message = "表名称不能为空")
|
||||
@Schema(name = "tableNames", description = "表名列表")
|
||||
private List<String> tableNames;
|
||||
|
||||
@Schema(name = "simpleDateFormat", description = "时间格式")
|
||||
private String simpleDateFormat;
|
||||
private String simpleDateFormat = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
@Schema(name = "tablePrefixes", description = "去除表前缀")
|
||||
private String tablePrefixes;
|
||||
private String tablePrefixes = "";
|
||||
|
||||
@Schema(name = "path", description = "路径")
|
||||
@NotEmpty(message = "表名称不能为空")
|
||||
private List<String> path;
|
||||
|
||||
@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;
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
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 = "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;
|
||||
|
||||
@Schema(name = "tableType", description = "表类型(通常是\"TABLE\")")
|
||||
private String tableType;
|
||||
|
||||
@Schema(name = "className", description = "类名")
|
||||
private String className;
|
||||
|
||||
@Schema(name = "columns", description = "列名称")
|
||||
private List<ColumnMetaData> columns = List.of();
|
||||
|
||||
}
|
|
@ -9,13 +9,13 @@ import lombok.Getter;
|
|||
public enum ResultCodeEnum {
|
||||
// 成功操作 200
|
||||
SUCCESS(200, "操作成功"),
|
||||
LOAD_FINISHED(200, "加载完成"),
|
||||
ADD_SUCCESS(200, "添加成功"),
|
||||
UPDATE_SUCCESS(200, "修改成功"),
|
||||
DELETE_SUCCESS(200, "删除成功"),
|
||||
SORT_SUCCESS(200, "排序成功"),
|
||||
SUCCESS_UPLOAD(200, "上传成功"),
|
||||
SUCCESS_LOGOUT(200, "退出成功"),
|
||||
LOGOUT_SUCCESS(200, "退出成功"),
|
||||
EMAIL_CODE_REFRESH(200, "邮箱验证码已刷新"),
|
||||
EMAIL_CODE_SEND_SUCCESS(200, "邮箱验证码已发送"),
|
||||
|
|
@ -37,4 +37,10 @@ public class GeneratorCodeException extends RuntimeException {
|
|||
this.message = codeEnum.getMessage();
|
||||
this.resultCodeEnum = codeEnum;
|
||||
}
|
||||
|
||||
public GeneratorCodeException(String message, Exception exception) {
|
||||
super(message);
|
||||
this.message = message;
|
||||
log.error(message, exception);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package cn.bunny.exception;
|
||||
|
||||
public class MetadataNotFoundException extends RuntimeException {
|
||||
public MetadataNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package cn.bunny.exception;
|
||||
|
||||
public class MetadataProviderException extends RuntimeException {
|
||||
public MetadataProviderException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package cn.bunny.exception;
|
||||
|
||||
public class SqlParseException extends MetadataProviderException {
|
||||
public SqlParseException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
|
@ -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,14 @@
|
|||
package cn.bunny.service;
|
||||
|
||||
import cn.bunny.domain.entity.DatabaseInfoMetaData;
|
||||
|
||||
public interface TableService {
|
||||
|
||||
/**
|
||||
* 数据库所有的信息
|
||||
*
|
||||
* @return 当前连接的数据库信息属性
|
||||
*/
|
||||
DatabaseInfoMetaData databaseInfoMetaData();
|
||||
|
||||
}
|
|
@ -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,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,133 @@
|
|||
package cn.bunny.service.impl;
|
||||
|
||||
import cn.bunny.core.provider.IMetadataProvider;
|
||||
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.service.GeneratorService;
|
||||
import cn.bunny.service.helper.VmsGeneratorPathHelper;
|
||||
import cn.bunny.utils.ZipFileUtil;
|
||||
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.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 代码生成服务实现类
|
||||
* 实现基于数据库和SQL的代码生成逻辑
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class GeneratorServiceImpl implements GeneratorService {
|
||||
|
||||
private final IMetadataProvider databaseMetadataProvider;
|
||||
private final IMetadataProvider sqlMetadataProvider;
|
||||
|
||||
/**
|
||||
* 代码生成方法---数据库生成
|
||||
*
|
||||
* @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 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 = 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();
|
||||
|
||||
// 创建Zip文件
|
||||
byte[] zipBytes = ZipFileUtil.createZipFile(generatorVoList);
|
||||
|
||||
// 设置返回给前端的文件名
|
||||
String zipFilename = "code-" + UUID.randomUUID().toString().split("-")[0] + ".zip";
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add("Content-Disposition", "attachment; filename=" + zipFilename);
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取生成器流
|
||||
*
|
||||
* @param dto 生成参数
|
||||
* @param tableMeta 表元数据
|
||||
* @param columns 列信息
|
||||
* @return 生成器流
|
||||
*/
|
||||
public 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();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package cn.bunny.service.impl;
|
||||
|
||||
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 org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class TableServiceImpl implements TableService {
|
||||
|
||||
private final DatabaseMetadataProvider databaseMetadataProvider;
|
||||
|
||||
/**
|
||||
* 数据库所有的信息
|
||||
*
|
||||
* @return 当前连接的数据库信息属性
|
||||
*/
|
||||
@Override
|
||||
public DatabaseInfoMetaData databaseInfoMetaData() {
|
||||
List<TableMetaData> databaseTableList = databaseMetadataProvider.getTableMetadataBatch(null);
|
||||
|
||||
// 将当前数据库表分组,以数据库名称为key
|
||||
List<TableMetaData> databaseList = databaseTableList.stream()
|
||||
.collect(Collectors.groupingBy(TableMetaData::getTableCat))
|
||||
.values().stream()
|
||||
.map(tableInfoVos -> {
|
||||
TableMetaData tableInfoVo = tableInfoVos.get(0);
|
||||
tableInfoVo.setTableName(null);
|
||||
return tableInfoVo;
|
||||
})
|
||||
.toList();
|
||||
|
||||
DatabaseInfoMetaData databaseInfoMetaData = databaseMetadataProvider.databaseInfoMetaData();
|
||||
databaseInfoMetaData.setDatabaseList(databaseList);
|
||||
|
||||
return databaseInfoMetaData;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -4,7 +4,7 @@ import com.google.common.base.CaseFormat;
|
|||
import org.assertj.core.util.introspection.CaseFormatUtils;
|
||||
|
||||
/* 类型转换,数据库转Java类型等 */
|
||||
public class TypeConvertUtil {
|
||||
public class MysqlTypeConvertUtil {
|
||||
|
||||
/**
|
||||
* 将数据库类型转换为Java类型
|
||||
|
@ -29,13 +29,6 @@ public class TypeConvertUtil {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 下划线命名转驼峰命名
|
||||
*/
|
||||
public static String convertToCamelCase(String name) {
|
||||
return convertToCamelCase(name, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下划线命名转驼峰命名
|
||||
*
|
||||
|
@ -51,9 +44,7 @@ public class TypeConvertUtil {
|
|||
String lowerCamelCase = CaseFormatUtils.toCamelCase(name);
|
||||
|
||||
// 首字母不大写
|
||||
if (!firstLetterCapital) {
|
||||
return lowerCamelCase;
|
||||
}
|
||||
if (!firstLetterCapital) return lowerCamelCase;
|
||||
|
||||
// 将小驼峰转成大驼峰
|
||||
return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, lowerCamelCase);
|
|
@ -0,0 +1,57 @@
|
|||
package cn.bunny.utils;
|
||||
|
||||
import cn.bunny.domain.vo.GeneratorVo;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
/**
|
||||
* VMS代码生成系统的ZIP打包服务
|
||||
* <p>
|
||||
* 提供将生成的代码模板打包为ZIP文件并支持下载的功能
|
||||
*/
|
||||
public class ZipFileUtil {
|
||||
|
||||
private static final String FILE_EXTENSION = ".vm";
|
||||
private static final String UTF_8 = StandardCharsets.UTF_8.name();
|
||||
|
||||
/**
|
||||
* 创建ZIP文件
|
||||
*
|
||||
* @param generatorVoList 生成的代码列表
|
||||
* @return ZIP文件字节数组
|
||||
* @throws RuntimeException 打包失败时抛出
|
||||
*/
|
||||
public static byte[] createZipFile(List<GeneratorVo> generatorVoList) {
|
||||
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream, StandardCharsets.UTF_8)) {
|
||||
|
||||
generatorVoList.forEach(vo -> addToZip(zipOutputStream, vo));
|
||||
return byteArrayOutputStream.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to create ZIP file", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加文件到ZIP 将单个代码文件添加到ZIP输出流
|
||||
*
|
||||
* @param zipOutputStream ZIP文件输出流
|
||||
* @param generatorVo 代码生成结果对象,包含文件路径和内容
|
||||
* @throws RuntimeException 当文件添加失败时抛出,包含失败文件路径信息
|
||||
*/
|
||||
private static void addToZip(ZipOutputStream zipOutputStream, GeneratorVo generatorVo) {
|
||||
try {
|
||||
String entryPath = generatorVo.getPath().replace(FILE_EXTENSION, "");
|
||||
zipOutputStream.putNextEntry(new ZipEntry(entryPath));
|
||||
zipOutputStream.write(generatorVo.getCode().getBytes(UTF_8));
|
||||
zipOutputStream.closeEntry();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to add file to ZIP: " + generatorVo.getPath(), e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,4 +14,7 @@ spring:
|
|||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://${bunny.master.host}:${bunny.master.port}/${bunny.master.database}?serverTimezone=GMT%2B8&useSSL=false&characterEncoding=utf-8&allowPublicKeyRetrieval=true
|
||||
username: ${bunny.master.username}
|
||||
password: ${bunny.master.password}
|
||||
password: ${bunny.master.password}
|
||||
hikari:
|
||||
maximum-pool-size: 20
|
||||
connection-timeout: 30000
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
____ _____ _
|
||||
| _ \ / ____| | |
|
||||
| |_) |_ _ _ __ _ __ _ _ | | __ ___ _ __ ___ _ __ __ _| |_ ___ _ __
|
||||
| _ <| | | | '_ \| '_ \| | | | | | |_ |/ _ \ '_ \ / _ \ '__/ _` | __/ _ \| '__|
|
||||
| |_) | |_| | | | | | | | |_| | | |__| | __/ | | | __/ | | (_| | || (_) | |
|
||||
|____/ \__,_|_| |_|_| |_|\__, | \_____|\___|_| |_|\___|_| \__,_|\__\___/|_|
|
||||
__/ |
|
||||
|___/
|
||||
|
||||
Service Name${spring.application.name}
|
||||
SpringBoot Version: ${spring-boot.version}${spring-boot.formatted-version}
|
||||
Spring Active:${spring.profiles.active}
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
|
@ -1,4 +1,4 @@
|
|||
const MainGeneratorPage = defineComponent({
|
||||
const AppGeneratorPage = defineComponent({
|
||||
name: "MainGeneratorPage",
|
||||
template: `
|
||||
<div class="offcanvas offcanvas-start" data-bs-scroll="false" tabindex="-1" ref="offcanvasElementRef">
|
||||
|
@ -30,7 +30,7 @@ const MainGeneratorPage = defineComponent({
|
|||
role="button" data-bs-toggle="collapse" :data-bs-target="'#collapse-' + item.id" aria-expanded="false">
|
||||
<div class="d-flex align-items-center">
|
||||
<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}}
|
||||
</span>
|
||||
</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,
|
||||
}
|
||||
}
|
||||
});
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,7 @@
|
|||
/* 添加 bootstrap 样式 */
|
||||
@import url("bootstrap/bootstrap.min.css");
|
||||
@import url("bootstrap/bootstrap-icons.min.css");
|
||||
/* 引入Highlight.js的CSS */
|
||||
@import url("highlight/atom-one-dark.min.css");
|
||||
/* 添加自定义样式 */
|
||||
@import url("./style/style.css");
|
|
@ -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;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue