Compare commits

...

65 Commits

Author SHA1 Message Date
bunny d1371cac46 Merge branch 'dev' 2025-07-02 21:32:54 +08:00
bunny 5e0770af79 🔥 删除不用图片 2025-07-02 21:32:46 +08:00
bunny d9cced0eba Merge branch 'dev'
# Conflicts:
#	README.md
#	generator-code-server/generator-code/pom.xml
#	generator-code-server/generator-code/src/main/java/cn/bunny/core/factory/AbstractDatabaseInfo.java
#	generator-code-server/generator-code/src/main/java/cn/bunny/core/factory/ConcreteDatabaseInfo.java
#	generator-code-server/generator-code/src/main/java/cn/bunny/service/VmsService.java
#	generator-code-server/generator-code/src/main/java/cn/bunny/service/impl/SqlParserServiceImpl.java
#	generator-code-server/generator-code/src/main/java/cn/bunny/service/impl/TableServiceImpl.java
#	generator-code-server/generator-code/src/main/java/cn/bunny/service/impl/VmsServiceImpl.java
#	generator-code-server/generator-code/src/main/java/cn/bunny/utils/VmsUtil.java
#	src/main/java/cn/bunny/controller/IndexController.java
#	src/main/java/cn/bunny/controller/TableController.java
#	src/main/java/cn/bunny/controller/VmsController.java
#	src/main/java/cn/bunny/core/provider/SqlMetadataProvider.java
#	src/main/java/cn/bunny/core/template/VmsTBaseTemplateGenerator.java
#	src/main/java/cn/bunny/domain/dto/VmsArgumentDto.java
#	src/main/resources/application-prod.yml
#	src/main/resources/logback.xml
#	src/main/resources/static/src/components/AppGeneratorPage.js
#	src/main/resources/static/src/config/axios-config.js
#	src/main/resources/static/src/config/highlight-config.js
#	src/main/resources/static/src/config/popper-config.js
#	src/main/resources/static/src/lib/css/bootstrap/bootstrap.min.css
#	src/main/resources/static/src/lib/css/fonts/bootstrap-icons.woff
#	src/main/resources/static/src/lib/css/fonts/bootstrap-icons.woff2
#	src/main/resources/static/src/lib/css/highlight/atom-one-dark.min.css
#	src/main/resources/static/src/lib/js/axios/axios.min.js
#	src/main/resources/static/src/lib/js/boostrap/bootstrap.bundle.min.js
#	src/main/resources/static/src/lib/js/boostrap/popper.min.js
#	src/main/resources/static/src/lib/js/dayjs/advancedFormat.js
#	src/main/resources/static/src/lib/js/dayjs/antd.min.js
#	src/main/resources/static/src/lib/js/dayjs/customParseFormat.js
#	src/main/resources/static/src/lib/js/dayjs/dayjs.min.js
#	src/main/resources/static/src/lib/js/dayjs/localeData.js
#	src/main/resources/static/src/lib/js/dayjs/quarterOfYear.js
#	src/main/resources/static/src/lib/js/dayjs/weekOfYear.js
#	src/main/resources/static/src/lib/js/dayjs/weekYear.js
#	src/main/resources/static/src/lib/js/dayjs/weekday.js
#	src/main/resources/static/src/lib/js/highlightjs/highlight.min.js
#	src/main/resources/static/src/lib/js/highlightjs/javascript.min.js
#	src/main/resources/static/src/lib/js/vue/vue.global.js
#	src/main/resources/static/src/lib/js/vue/vue.global.prod.js
#	src/main/resources/static/src/views/database/DatabaseCard.js
#	src/main/resources/static/src/views/database/DatabaseForm.js
#	src/main/resources/templates/database.html
2025-07-02 21:28:51 +08:00
bunny 229cce3c31 保存master-v0.0.5 2025-07-02 21:23:40 +08:00
bunny ad5bc29cb3 📝 更新和完善ReadMe文档; 2025-07-02 21:10:23 +08:00
bunny 464a419d9a 🔥 删除冗余内容 2025-07-02 21:09:42 +08:00
bunny 25f46606fc 加载提示信息;控制台文本; 2025-07-02 20:00:27 +08:00
bunny 7f55cecb7a 添加获取当前数据表列信息 2025-07-02 19:29:44 +08:00
Bunny 395ec89666 添加Sql页面生成;Sql页面逻辑完成 2025-07-02 17:14:46 +08:00
Bunny d13451c1f9 🎨 修改生成函数 2025-07-02 17:13:21 +08:00
Bunny 68f9cda260 ♻️ 拆分生成逻辑和业务 2025-07-02 15:54:48 +08:00
Bunny d6f783c481 📦 将项目移到最外层 2025-07-02 13:39:16 +08:00
Bunny f1e8f48c73 💄 修改AppHeader头部央视 2025-07-02 13:32:44 +08:00
Bunny 9baf682f63 ♻️ 重构后端代码;添加注释l;优化可读性 2025-07-02 13:25:21 +08:00
Bunny a38cdde759 💬 修改前端文件提示title信息 2025-07-02 10:18:04 +08:00
Bunny 9ce378320b 🚚 移动vm路径 2025-07-02 10:17:33 +08:00
bunny 4e438bea86 🔥 删除vue脚手架项目 2025-07-01 22:09:33 +08:00
bunny 3742a7ebca 🚚 移动访问控制器和生成服务 2025-07-01 22:09:08 +08:00
bunny 4c56bb6890 💬 更新头部页面文字 2025-07-01 21:37:47 +08:00
bunny 30a37837c7 💬 更新文本数据库所有页面名称 2025-07-01 21:29:29 +08:00
bunny 5804e6fe9b 💬 访问页面使用重定向 2025-07-01 21:23:13 +08:00
bunny 654019f77b 💄 整理前端样式合并一起 2025-07-01 21:22:11 +08:00
bunny cd9c3b4dd5 🔀 合并远程分支 2025-07-01 20:59:43 +08:00
bunny 71f2aac2ad Merge remote-tracking branch 'raw/dev' into dev
# Conflicts:
#	README.md
#	generator-code-server/pom.xml
2025-07-01 20:58:25 +08:00
Bunny ec4379abe4 📝 添加ReadMe文档 2025-07-01 17:14:54 +08:00
Bunny 2a829e477b 🎨 修改前端访问名称 2025-07-01 16:41:38 +08:00
Bunny 41ce317249 💡 更新注释信息和文件命名方式 2025-07-01 16:39:16 +08:00
Bunny 9b6938b46e 💬 更新前端表格选择方式和提示信息 2025-07-01 16:37:37 +08:00
Bunny 2e714dc080 💩 改进异常处理体系 2025-07-01 16:18:39 +08:00
Bunny 74ff03182e 💩 异常处理方式 2025-07-01 15:31:58 +08:00
Bunny 08c4ff9b6b 💩 改进命名方式 2025-07-01 15:28:18 +08:00
Bunny 1e07831818 💩 重构抽象层次;解决资源泄漏;优化元数据获取性能 2025-07-01 15:17:56 +08:00
Bunny 5ef135de32 🚧 优化后端代码 2025-07-01 15:05:27 +08:00
bunny deffcb8fb1 填写生成表单信息 2025-06-28 01:13:56 +08:00
bunny c47a65b5de 💩 修改冗余的代码 2025-06-27 22:10:31 +08:00
bunny 7633593bfc 💄 主页面组件 2025-06-25 21:48:12 +08:00
bunny 0cd9129efa 💄 主页面组件 2025-06-25 21:48:07 +08:00
bunny f8467610f6 💩 主页数据展示优化 2025-06-25 20:29:37 +08:00
bunny 16991d5b95 主页基础渲染 2025-06-24 23:28:20 +08:00
bunny 42f1a7edfe 🐛 build文件确实 2025-06-04 13:29:15 +08:00
bunny f95dc4d947 🐛 生成字段丢失 2025-06-01 19:42:07 +08:00
bunny 11b20ba6f5 🎨 feat: 生成器代码格式 2025-05-11 15:49:26 +08:00
bunny 9356d39347 feat: 打包经过 2025-05-01 16:17:16 +08:00
bunny 8f3ea1e87f fix: 修复转大驼峰命名失败 2025-04-23 12:37:16 +08:00
bunny 16a8170e86 build: 打包 2025-04-23 11:28:18 +08:00
bunny d38844eea5 feat: 调整目录结构 2025-04-23 11:22:46 +08:00
bunny e832b5ffab docs: 更新文档 2025-04-22 20:30:20 +08:00
bunny fd722d2515 build: 构建前端 2025-04-22 20:21:58 +08:00
bunny b6e263d114 build: 构建前端 2025-04-22 20:20:18 +08:00
bunny ff2f3144dc feat: 新增解析SQL 2025-04-22 19:17:25 +08:00
bunny 4f0007ee01 feat: 添加当前连接数据库信息 2025-04-21 13:18:09 +08:00
bunny faaca67cc6 style: 修改代码样式 2025-04-21 11:24:30 +08:00
bunny 3d3540e3e9 feat: 优化代码 2025-04-20 22:28:45 +08:00
bunny 2325754ede docs: 更新文档 2025-04-19 13:37:20 +08:00
bunny 617ff87736 feat: 更新前端文件 2025-04-15 20:26:45 +08:00
bunny 474f3ab89b fix: 修复生成bug和缺陷 2025-04-15 19:11:38 +08:00
bunny e2b6d49518 🎉 feat: 前端页面更新 2025-04-11 22:10:24 +08:00
bunny 5c241728f0 feat: 生成文件名优化;大小写驼峰名转换优化 2025-04-11 22:06:47 +08:00
bunny be1b09dc70 feat: 更新前端文件 2025-04-10 23:35:51 +08:00
bunny ea18ded70c feat: 使用zip打包下载生成内容 2025-04-09 22:21:28 +08:00
bunny dfdf9546b0 perf: 优化代码 2025-04-09 21:23:26 +08:00
bunny f95b65c1e0 🎉 feat: push脚本 2025-04-06 19:17:04 +08:00
bunny 679f418387 feat: 修改数据库连接 2025-04-06 15:02:58 +08:00
bunny 74de80bf68 docs: 添加文档 2025-04-06 14:25:10 +08:00
bunny e4288b00e3 feat: init 2025-04-06 13:23:00 +08:00
136 changed files with 2891 additions and 1930 deletions

433
README.md
View File

@ -1,43 +1,402 @@
# 代码生成器
# 🚀 Bunny Code Generator 代码生成器系统文档
## 内置字段
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)[![Java Version](https://img.shields.io/badge/JDK-17-green.svg)]()[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.4.3-6DB33F.svg)]()
```java
// vm 不能直接写 `{` 需要转换下
context.put("leftBrace", "{");
## 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]
```
![wx+alipay](images/wx_alipay.png)
### 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. 支持项目☕
如果这个项目对您有帮助,可以考虑支持我们:
![WeChat & Alipay](D:\Project\FullStack\GeneratorCode\images\wx_alipay.png)
**Happy Coding!** 🎉

View File

@ -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);
}
}

View File

@ -1,70 +0,0 @@
package cn.bunny.core.factory;
import cn.bunny.domain.entity.ColumnMetaData;
import cn.bunny.domain.entity.TableMetaData;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Component
public abstract class AbstractDatabaseInfo {
public DataSource dataSource;
@Autowired
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 获取表的所有主键列名
*
* @param tableName 表名
* @return 主键列名的集合
*/
@SneakyThrows
public Set<String> findPrimaryKeyColumns(String tableName) {
// 主键的key
Set<String> primaryKeys = new HashSet<>();
try (Connection connection = dataSource.getConnection()) {
DatabaseMetaData metaData = connection.getMetaData();
// 当前表的主键
ResultSet pkResultSet = metaData.getPrimaryKeys(null, null, tableName);
while (pkResultSet.next()) {
// 列字段
String columnName = pkResultSet.getString("COLUMN_NAME").toLowerCase();
primaryKeys.add(columnName);
}
return primaryKeys;
}
}
/**
* 解析 sql 表信息
*
* @param name 表名称或sql
* @return 表西悉尼
*/
public abstract TableMetaData getTableMetadata(String name);
/**
* 获取当前表的列属性
*
* @param name 表名称或sql
* @return 当前表所有的列内容
*/
public abstract List<ColumnMetaData> tableColumnInfo(String name);
}

View File

@ -1,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());
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

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

View File

@ -1,40 +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();
}

View File

@ -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);
}

View File

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

View File

@ -1,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;
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}
}

View File

@ -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("-", "");
}
}

View File

@ -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";
}
}

View File

@ -1,10 +0,0 @@
_ _
| |__ _ _ _ __ _ __ _ _ (_) __ ___ ____ _
| '_ \| | | | '_ \| '_ \| | | | | |/ _` \ \ / / _` |
| |_) | |_| | | | | | | | |_| | | | (_| |\ V | (_| |
|_.__/ \__,_|_| |_|_| |_|\__, | _/ |\__,_| \_/ \__,_|
|___/ |__/
Service Name${spring.application.name}
SpringBoot Version: ${spring-boot.version}${spring-boot.formatted-version}
SpringActive${spring.profiles.active}

View File

@ -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

View File

@ -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();
}
}
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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";
}
}

View File

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

View File

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

View File

@ -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;
}
}

View File

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

View File

@ -1,8 +1,12 @@
package cn.bunny.core.factory;
package cn.bunny.core.provider;
import cn.bunny.core.dialect.DatabaseDialect;
import cn.bunny.domain.entity.ColumnMetaData;
import cn.bunny.domain.entity.TableMetaData;
import cn.bunny.utils.TypeConvertUtil;
import cn.bunny.exception.GeneratorCodeException;
import cn.bunny.exception.SqlParseException;
import cn.bunny.utils.MysqlTypeConvertUtil;
import lombok.RequiredArgsConstructor;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
@ -11,29 +15,33 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Component
public class ConcreteSqlParserDatabaseInfo extends AbstractDatabaseInfo {
@RequiredArgsConstructor
public class SqlMetadataProvider implements IMetadataProvider {
private final DatabaseDialect dialect;
/**
* 解析 sql 表信息
* 先解析SQL语句解析列字段信息
*
* @param sql 表名称或sql
* @param 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();

View File

@ -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);
}

View File

@ -2,7 +2,7 @@ package cn.bunny.core.template;
import cn.bunny.domain.dto.VmsArgumentDto;
import cn.bunny.domain.entity.TableMetaData;
import cn.bunny.utils.TypeConvertUtil;
import cn.bunny.utils.MysqlTypeConvertUtil;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
@ -15,7 +15,7 @@ import java.util.Date;
* 使用模板方法方便扩展
* 如果需要继承 AbstractVmsGenerator
*/
public class VmsArgumentDtoBaseVmsGeneratorTemplate extends AbstractVmsGeneratorTemplate {
public class VmsTBaseTemplateGenerator extends AbstractTemplateGenerator {
private final VmsArgumentDto dto;
private final String path;
@ -26,7 +26,7 @@ public class VmsArgumentDtoBaseVmsGeneratorTemplate extends AbstractVmsGenerator
* @param path 当前路径
* @param tableMetaData 表名称
*/
public VmsArgumentDtoBaseVmsGeneratorTemplate(VmsArgumentDto dto, String path, TableMetaData tableMetaData) {
public VmsTBaseTemplateGenerator(VmsArgumentDto dto, String path, TableMetaData tableMetaData) {
this.dto = dto;
this.path = path;
this.tableMetaData = tableMetaData;
@ -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);
}

View File

@ -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 语句")

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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, "邮箱验证码已发送"),

View File

@ -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);
}
}

View File

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

View File

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

View File

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

View File

@ -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);
}

View File

@ -0,0 +1,14 @@
package cn.bunny.service;
import cn.bunny.domain.entity.DatabaseInfoMetaData;
public interface TableService {
/**
* 数据库所有的信息
*
* @return 当前连接的数据库信息属性
*/
DatabaseInfoMetaData databaseInfoMetaData();
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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();
});
}
}

View File

@ -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;
}
}

View File

@ -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路径Mapkey为类型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);
}
}
}

View File

@ -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);

View File

@ -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);
}
}
}

View File

@ -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

View File

@ -0,0 +1,13 @@
____ _____ _
| _ \ / ____| | |
| |_) |_ _ _ __ _ __ _ _ | | __ ___ _ __ ___ _ __ __ _| |_ ___ _ __
| _ <| | | | '_ \| '_ \| | | | | | |_ |/ _ \ '_ \ / _ \ '__/ _` | __/ _ \| '__|
| |_) | |_| | | | | | | | |_| | | |__| | __/ | | | __/ | | (_| | || (_) | |
|____/ \__,_|_| |_|_| |_|\__, | \_____|\___|_| |_|\___|_| \__,_|\__\___/|_|
__/ |
|___/
Service Name${spring.application.name}
SpringBoot Version: ${spring-boot.version}${spring-boot.formatted-version}
Spring Active${spring.profiles.active}

View File

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View File

@ -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>

View File

@ -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

View File

@ -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");

View File

@ -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