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
This commit is contained in:
commit
d9cced0eba
|
@ -12,7 +12,7 @@ dist-ssr
|
||||||
.eslintcache
|
.eslintcache
|
||||||
report.html
|
report.html
|
||||||
vite.config.*.timestamp*
|
vite.config.*.timestamp*
|
||||||
|
application-prod.yml
|
||||||
bunny-web.site.csr
|
bunny-web.site.csr
|
||||||
bunny-web.site.key
|
bunny-web.site.key
|
||||||
bunny-web.site_bundle.crt
|
bunny-web.site_bundle.crt
|
||||||
|
|
443
README.md
443
README.md
|
@ -1,53 +1,402 @@
|
||||||
# 代码生成器
|
# 🚀 Bunny Code Generator 代码生成器系统文档
|
||||||
|
|
||||||
## 功能展示
|
[](LICENSE)[]()[]()
|
||||||
|
|
||||||
点击 `表名` 或 `注释内容` 跳转到另一个页面
|
## 1. 系统架构 🏗️
|
||||||
|
|
||||||

|
### 1.1 架构图
|
||||||
|
|
||||||

|
```mermaid
|
||||||
|
graph TD
|
||||||

|
A[🖥️ 前端界面] -->|HTTP请求| B[🌐 WebController]
|
||||||
|
A -->|API调用| C[⚙️ GeneratorController]
|
||||||
## 内置字段
|
A -->|SQL解析| D[🔍 SqlParserController]
|
||||||
|
A -->|元数据查询| E[🗃️ TableController]
|
||||||
```java
|
A -->|模板管理| F[📂 VmsController]
|
||||||
// vm 不能直接写 `{` 需要转换下
|
|
||||||
context.put("leftBrace", "{");
|
B -->|页面跳转| G[📝 Thymeleaf模板]
|
||||||
|
C -->|生成请求| H[🏭 GeneratorService]
|
||||||
// 当前的表名
|
D -->|SQL解析| I[✂️ SqlMetadataProvider]
|
||||||
context.put("tableName", tableMetaData.getTableName());
|
E -->|数据库查询| J[🔗 DatabaseMetadataProvider]
|
||||||
|
F -->|模板扫描| K[🗃️ VmsService]
|
||||||
// 当前表的列信息
|
|
||||||
context.put("columnInfoList", columnInfoList);
|
H -->|模板渲染| L[🌀 Velocity引擎]
|
||||||
|
H -->|打包下载| M[📦 ZipFileUtil]
|
||||||
// 数据库sql列
|
I & J -->|数据源| N[💾 MySQL数据库]
|
||||||
context.put("baseColumnList", String.join(",", list));
|
J -->|连接池| O[🛟 HikariCP]
|
||||||
|
|
||||||
// 当前日期
|
|
||||||
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);
|
|
||||||
```
|
```
|
||||||
|
|
||||||

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

|
||||||
|
|
||||||
|
**Happy Coding!** 🎉
|
|
@ -1,113 +0,0 @@
|
||||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<parent>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>generator-code-server</artifactId>
|
|
||||||
<version>3.4.3</version>
|
|
||||||
<relativePath>../pom.xml</relativePath>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<artifactId>generator-code</artifactId>
|
|
||||||
<packaging>jar</packaging>
|
|
||||||
|
|
||||||
<name>generator-code</name>
|
|
||||||
<url>https://maven.apache.org</url>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-devtools</artifactId>
|
|
||||||
<scope>runtime</scope>
|
|
||||||
<optional>true</optional>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-validation</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>junit</groupId>
|
|
||||||
<artifactId>junit</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-test</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.mysql</groupId>
|
|
||||||
<artifactId>mysql-connector-j</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.zaxxer</groupId>
|
|
||||||
<artifactId>HikariCP</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-jdbc</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.github.jsqlparser</groupId>
|
|
||||||
<artifactId>jsqlparser</artifactId>
|
|
||||||
<version>5.1</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- lombok -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.projectlombok</groupId>
|
|
||||||
<artifactId>lombok</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>cn.hutool</groupId>
|
|
||||||
<artifactId>hutool-all</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- knife4j -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.github.xiaoymin</groupId>
|
|
||||||
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springdoc</groupId>
|
|
||||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
|
||||||
<version>2.8.6</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>io.swagger</groupId>
|
|
||||||
<artifactId>swagger-annotations</artifactId>
|
|
||||||
<version>1.6.14</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- fastjson2 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.alibaba.fastjson2</groupId>
|
|
||||||
<artifactId>fastjson2</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- 小驼峰 和 大驼峰之间互转 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.google.guava</groupId>
|
|
||||||
<artifactId>guava</artifactId>
|
|
||||||
<version>33.4.7-jre</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.velocity</groupId>
|
|
||||||
<artifactId>velocity-engine-core</artifactId>
|
|
||||||
<version>2.3</version>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
</project>
|
|
|
@ -1,13 +0,0 @@
|
||||||
package cn.bunny.controller;
|
|
||||||
|
|
||||||
import org.springframework.stereotype.Controller;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
|
|
||||||
@Controller
|
|
||||||
public class IndexController {
|
|
||||||
|
|
||||||
@GetMapping("/")
|
|
||||||
public String index() {
|
|
||||||
return "index";
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
package cn.bunny.controller;
|
|
||||||
|
|
||||||
import cn.bunny.core.factory.ConcreteSqlParserDatabaseInfo;
|
|
||||||
import cn.bunny.domain.entity.ColumnMetaData;
|
|
||||||
import cn.bunny.domain.entity.TableMetaData;
|
|
||||||
import cn.bunny.domain.result.Result;
|
|
||||||
import cn.bunny.service.SqlParserService;
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Tag(name = "解析SQL", description = "解析SQL接口")
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/sqlParser")
|
|
||||||
public class SqlParserController {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private SqlParserService sqlParserService;
|
|
||||||
|
|
||||||
@Operation(summary = "解析SQL成表信息", description = "解析SQL成表信息")
|
|
||||||
@PostMapping("tableInfo")
|
|
||||||
public Result<TableMetaData> tableInfo(String sql) {
|
|
||||||
TableMetaData vo = sqlParserService.tableInfo(sql);
|
|
||||||
return Result.success(vo);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "解析SQL成列数据", description = "解析SQL成列数据")
|
|
||||||
@PostMapping("columnMetaData")
|
|
||||||
public Result<List<ColumnMetaData>> columnMetaData(String sql) {
|
|
||||||
ConcreteSqlParserDatabaseInfo databaseInfoCore = new ConcreteSqlParserDatabaseInfo();
|
|
||||||
List<ColumnMetaData> vo = databaseInfoCore.tableColumnInfo(sql);
|
|
||||||
return Result.success(vo);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
package cn.bunny.core.factory;
|
|
||||||
|
|
||||||
import cn.bunny.domain.entity.ColumnMetaData;
|
|
||||||
import cn.bunny.domain.entity.TableMetaData;
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
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 {
|
|
||||||
@Resource
|
|
||||||
public DataSource dataSource;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取表的所有主键列名
|
|
||||||
*
|
|
||||||
* @param tableName 表名
|
|
||||||
* @return 主键列名的集合
|
|
||||||
*/
|
|
||||||
@SneakyThrows
|
|
||||||
public Set<String> getPrimaryKeyColumns(String tableName) {
|
|
||||||
// 主键的key
|
|
||||||
Set<String> primaryKeys = new HashSet<>();
|
|
||||||
|
|
||||||
try (Connection connection = dataSource.getConnection()) {
|
|
||||||
DatabaseMetaData metaData = connection.getMetaData();
|
|
||||||
|
|
||||||
// 当前表的主键
|
|
||||||
ResultSet pkResultSet = metaData.getPrimaryKeys(null, null, tableName);
|
|
||||||
|
|
||||||
while (pkResultSet.next()) {
|
|
||||||
// 列字段
|
|
||||||
String columnName = pkResultSet.getString("COLUMN_NAME").toLowerCase();
|
|
||||||
primaryKeys.add(columnName);
|
|
||||||
}
|
|
||||||
|
|
||||||
return primaryKeys;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 解析 sql 表信息
|
|
||||||
*
|
|
||||||
* @param name 表名称或sql
|
|
||||||
* @return 表西悉尼
|
|
||||||
*/
|
|
||||||
public abstract TableMetaData getTableMetadata(String name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前表的列属性
|
|
||||||
*
|
|
||||||
* @param name 表名称或sql
|
|
||||||
* @return 当前表所有的列内容
|
|
||||||
*/
|
|
||||||
public abstract List<ColumnMetaData> tableColumnInfo(String name);
|
|
||||||
}
|
|
|
@ -1,169 +0,0 @@
|
||||||
package cn.bunny.core.factory;
|
|
||||||
|
|
||||||
import cn.bunny.domain.entity.ColumnMetaData;
|
|
||||||
import cn.bunny.domain.entity.DatabaseInfoMetaData;
|
|
||||||
import cn.bunny.domain.entity.TableMetaData;
|
|
||||||
import cn.bunny.utils.TypeConvertUtil;
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.sql.DatabaseMetaData;
|
|
||||||
import java.sql.ResultSet;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class ConcreteDatabaseInfo extends AbstractDatabaseInfo {
|
|
||||||
|
|
||||||
@Value("${bunny.master.database}")
|
|
||||||
private String currentDatabase;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 数据库所有的信息
|
|
||||||
*
|
|
||||||
* @return 当前连接的数据库信息属性
|
|
||||||
*/
|
|
||||||
@SneakyThrows
|
|
||||||
public DatabaseInfoMetaData databaseInfoMetaData() {
|
|
||||||
try (Connection connection = dataSource.getConnection()) {
|
|
||||||
DatabaseMetaData metaData = connection.getMetaData();
|
|
||||||
|
|
||||||
return DatabaseInfoMetaData.builder()
|
|
||||||
.databaseProductName(metaData.getDatabaseProductName())
|
|
||||||
.databaseProductVersion(metaData.getDatabaseProductVersion())
|
|
||||||
.driverName(metaData.getDriverName())
|
|
||||||
.driverVersion(metaData.getDriverVersion())
|
|
||||||
.url(metaData.getURL())
|
|
||||||
.username(metaData.getUserName())
|
|
||||||
.currentDatabase(currentDatabase)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 解析 sql 表信息
|
|
||||||
*
|
|
||||||
* @param tableName 表名称或sql
|
|
||||||
* @return 表西悉尼
|
|
||||||
*/
|
|
||||||
@SneakyThrows
|
|
||||||
@Override
|
|
||||||
public TableMetaData getTableMetadata(String tableName) {
|
|
||||||
TableMetaData tableMetaData;
|
|
||||||
|
|
||||||
try (Connection connection = dataSource.getConnection()) {
|
|
||||||
DatabaseMetaData metaData = connection.getMetaData();
|
|
||||||
ResultSet tables = metaData.getTables(null, null, tableName, new String[]{"TABLE"});
|
|
||||||
|
|
||||||
// 获取表的注释信息
|
|
||||||
if (tables.next()) {
|
|
||||||
// 备注信息
|
|
||||||
String remarks = tables.getString("REMARKS");
|
|
||||||
|
|
||||||
// 数组名称
|
|
||||||
String tableCat = tables.getString("TABLE_CAT");
|
|
||||||
|
|
||||||
// 通常是"TABLE"
|
|
||||||
String tableType = tables.getString("TABLE_TYPE");
|
|
||||||
|
|
||||||
tableMetaData = TableMetaData.builder()
|
|
||||||
.tableName(tableName)
|
|
||||||
.comment(remarks)
|
|
||||||
.tableCat(tableCat)
|
|
||||||
.tableType(tableType)
|
|
||||||
.build();
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException("数据表不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
return tableMetaData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取[当前/所有]数据库表
|
|
||||||
*
|
|
||||||
* @return 所有表信息
|
|
||||||
*/
|
|
||||||
@SneakyThrows
|
|
||||||
public List<TableMetaData> databaseTableList(String dbName) {
|
|
||||||
// 当前数据库数据库所有的表
|
|
||||||
List<TableMetaData> allTableInfo = new ArrayList<>();
|
|
||||||
|
|
||||||
try (Connection connection = dataSource.getConnection()) {
|
|
||||||
DatabaseMetaData metaData = connection.getMetaData();
|
|
||||||
|
|
||||||
// 当前数据库中所有的表
|
|
||||||
ResultSet tables = metaData.getTables(dbName, null, "%", new String[]{"TABLE"});
|
|
||||||
|
|
||||||
while (tables.next()) {
|
|
||||||
// 表名称
|
|
||||||
dbName = tables.getString("TABLE_NAME");
|
|
||||||
|
|
||||||
// 设置表信息
|
|
||||||
TableMetaData tableMetaData = getTableMetadata(dbName);
|
|
||||||
|
|
||||||
allTableInfo.add(tableMetaData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return allTableInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前表的列属性
|
|
||||||
*
|
|
||||||
* @param tableName 表名称或sql
|
|
||||||
* @return 当前表所有的列内容
|
|
||||||
*/
|
|
||||||
@SneakyThrows
|
|
||||||
@Override
|
|
||||||
public List<ColumnMetaData> tableColumnInfo(String tableName) {
|
|
||||||
try (Connection connection = dataSource.getConnection()) {
|
|
||||||
DatabaseMetaData metaData = connection.getMetaData();
|
|
||||||
Map<String, ColumnMetaData> map = new LinkedHashMap<>();
|
|
||||||
// 当前表的主键
|
|
||||||
Set<String> primaryKeyColumns = getPrimaryKeyColumns(tableName);
|
|
||||||
|
|
||||||
// 当前表的列信息
|
|
||||||
try (ResultSet columnsRs = metaData.getColumns(null, null, tableName, null)) {
|
|
||||||
while (columnsRs.next()) {
|
|
||||||
ColumnMetaData column = new ColumnMetaData();
|
|
||||||
// 列字段
|
|
||||||
String columnName = columnsRs.getString("COLUMN_NAME");
|
|
||||||
// 数据库类型
|
|
||||||
String typeName = columnsRs.getString("TYPE_NAME");
|
|
||||||
|
|
||||||
// 设置列字段
|
|
||||||
column.setColumnName(columnName);
|
|
||||||
// 列字段转成 下划线 -> 小驼峰
|
|
||||||
column.setLowercaseName(TypeConvertUtil.convertToCamelCase(column.getColumnName()));
|
|
||||||
// 列字段转成 下划线 -> 大驼峰名称
|
|
||||||
column.setUppercaseName(TypeConvertUtil.convertToCamelCase(column.getColumnName(), true));
|
|
||||||
// 字段类型
|
|
||||||
column.setJdbcType(typeName);
|
|
||||||
// 字段类型转 Java 类型
|
|
||||||
String javaType = TypeConvertUtil.convertToJavaType(typeName);
|
|
||||||
column.setJavaType(javaType);
|
|
||||||
// 字段类型转 JavaScript 类型
|
|
||||||
column.setJavascriptType(StringUtils.uncapitalize(javaType));
|
|
||||||
// 备注信息
|
|
||||||
column.setComment(columnsRs.getString("REMARKS"));
|
|
||||||
|
|
||||||
// 确保 primaryKeyColumns 不为空
|
|
||||||
if (!primaryKeyColumns.isEmpty()) {
|
|
||||||
// 是否是主键
|
|
||||||
boolean isPrimaryKey = primaryKeyColumns.contains(columnName);
|
|
||||||
column.setIsPrimaryKey(isPrimaryKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
map.putIfAbsent(column.getColumnName(), column);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ArrayList<>(map.values());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
package cn.bunny.core.template;
|
|
||||||
|
|
||||||
import cn.bunny.domain.entity.ColumnMetaData;
|
|
||||||
import cn.bunny.domain.entity.TableMetaData;
|
|
||||||
import org.apache.velocity.VelocityContext;
|
|
||||||
|
|
||||||
import java.io.StringWriter;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 模板方法模式
|
|
||||||
* 如果需要继承 AbstractVmsGenerator
|
|
||||||
*/
|
|
||||||
public abstract class AbstractVmsGeneratorTemplate {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加生成内容
|
|
||||||
*/
|
|
||||||
abstract void addContext(VelocityContext context);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Velocity 生成模板
|
|
||||||
*
|
|
||||||
* @param context VelocityContext
|
|
||||||
* @param writer StringWriter 写入
|
|
||||||
*/
|
|
||||||
abstract void templateMerge(VelocityContext context, StringWriter writer);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成模板
|
|
||||||
*
|
|
||||||
* @param tableMetaData 表属性
|
|
||||||
* @param columnInfoList 列属性数组
|
|
||||||
* @return StringWriter
|
|
||||||
*/
|
|
||||||
public final StringWriter generatorCodeTemplate(TableMetaData tableMetaData, List<ColumnMetaData> columnInfoList) {
|
|
||||||
VelocityContext context = new VelocityContext();
|
|
||||||
|
|
||||||
// 添加要生成的属性
|
|
||||||
StringWriter writer = new StringWriter();
|
|
||||||
List<String> list = columnInfoList.stream().map(ColumnMetaData::getColumnName).distinct().toList();
|
|
||||||
|
|
||||||
// vm 不能直接写 `{` 需要转换下
|
|
||||||
context.put("leftBrace", "{");
|
|
||||||
|
|
||||||
// 当前的表名
|
|
||||||
context.put("tableName", tableMetaData.getTableName());
|
|
||||||
|
|
||||||
// 当前表的列信息
|
|
||||||
context.put("columnInfoList", columnInfoList);
|
|
||||||
|
|
||||||
// 数据库sql列
|
|
||||||
context.put("baseColumnList", String.join(",", list));
|
|
||||||
|
|
||||||
// 添加需要生成的内容
|
|
||||||
addContext(context);
|
|
||||||
|
|
||||||
templateMerge(context, writer);
|
|
||||||
|
|
||||||
return writer;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
package cn.bunny.domain.dto;
|
|
||||||
|
|
||||||
import jakarta.validation.constraints.NotBlank;
|
|
||||||
import jakarta.validation.constraints.NotNull;
|
|
||||||
import jakarta.validation.constraints.Pattern;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@AllArgsConstructor
|
|
||||||
@NoArgsConstructor
|
|
||||||
public class VmsArgumentDto {
|
|
||||||
|
|
||||||
/* 作者名称 */
|
|
||||||
String author = "Bunny";
|
|
||||||
|
|
||||||
/* 包名称 */
|
|
||||||
String packageName = "cn.bunny.services";
|
|
||||||
|
|
||||||
/* requestMapping 名称 */
|
|
||||||
String requestMapping = "/api";
|
|
||||||
|
|
||||||
/* 类名称,格式为:xxx xxx_xxx */
|
|
||||||
@NotBlank(message = "类名称不能为空")
|
|
||||||
@NotNull(message = "类名称不能为空")
|
|
||||||
@Pattern(regexp = "^(?:[a-z][a-z0-9_]*|[_a-z][a-z0-9_]*)$", message = "类名称不合法")
|
|
||||||
private String className;
|
|
||||||
|
|
||||||
/* 表名称 */
|
|
||||||
@NotBlank(message = "表名称不能为空")
|
|
||||||
@NotNull(message = "表名称不能为空")
|
|
||||||
@Pattern(regexp = "^(?:[a-z][a-z0-9_]*|[_a-z][a-z0-9_]*)$", message = "表名称不合法")
|
|
||||||
private String tableName;
|
|
||||||
|
|
||||||
/* 时间格式 */
|
|
||||||
private String simpleDateFormat = "yyyy-MM-dd HH:mm:ss";
|
|
||||||
|
|
||||||
/* 去除表前缀 */
|
|
||||||
private String tablePrefixes = "t_,sys_,qrtz_,log_";
|
|
||||||
|
|
||||||
/* 注释内容 */
|
|
||||||
private String comment;
|
|
||||||
|
|
||||||
/* 路径 */
|
|
||||||
private List<String> path;
|
|
||||||
|
|
||||||
/* SQL 语句 */
|
|
||||||
private String sql;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
package cn.bunny.domain.entity;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Builder;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@AllArgsConstructor
|
|
||||||
@NoArgsConstructor
|
|
||||||
public class DatabaseInfoMetaData {
|
|
||||||
|
|
||||||
/* 数据库所有的数据库 */
|
|
||||||
List<TableMetaData> databaseList;
|
|
||||||
|
|
||||||
/* 数据库产品名称 */
|
|
||||||
private String databaseProductName;
|
|
||||||
|
|
||||||
/* 数据库产品版本 */
|
|
||||||
private String databaseProductVersion;
|
|
||||||
|
|
||||||
/* 驱动名称 */
|
|
||||||
private String driverName;
|
|
||||||
|
|
||||||
/* 数据库驱动版本 */
|
|
||||||
private String driverVersion;
|
|
||||||
|
|
||||||
/* 数据链接url */
|
|
||||||
private String url;
|
|
||||||
|
|
||||||
/* 数据库用户 */
|
|
||||||
private String username;
|
|
||||||
|
|
||||||
/* 当前数据库名称 */
|
|
||||||
private String currentDatabase;
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
package cn.bunny.domain.entity;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Builder;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@AllArgsConstructor
|
|
||||||
@NoArgsConstructor
|
|
||||||
public class TableMetaData {
|
|
||||||
|
|
||||||
/* 表名 */
|
|
||||||
private String tableName;
|
|
||||||
|
|
||||||
/* 注释内容 */
|
|
||||||
private String comment;
|
|
||||||
|
|
||||||
/* 表目录 */
|
|
||||||
private String tableCat;
|
|
||||||
|
|
||||||
/* 表类型(通常是"TABLE") */
|
|
||||||
private String tableType;
|
|
||||||
|
|
||||||
/* 类名 */
|
|
||||||
private String className;
|
|
||||||
|
|
||||||
/* 列名称 */
|
|
||||||
private List<ColumnMetaData> columns;
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
package cn.bunny.domain.vo;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Builder;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@AllArgsConstructor
|
|
||||||
@NoArgsConstructor
|
|
||||||
public class GeneratorVo {
|
|
||||||
|
|
||||||
/* 生成的代码 */
|
|
||||||
private String code;
|
|
||||||
|
|
||||||
/* 表名 */
|
|
||||||
private String tableName;
|
|
||||||
|
|
||||||
/* 注释内容 */
|
|
||||||
private String comment;
|
|
||||||
|
|
||||||
/* 生成类型路径 */
|
|
||||||
private String path;
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
package cn.bunny.domain.vo;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Builder;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@AllArgsConstructor
|
|
||||||
@NoArgsConstructor
|
|
||||||
public class VmsPathVo {
|
|
||||||
|
|
||||||
/* 路径名称 */
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
/* 显示的label */
|
|
||||||
private String label;
|
|
||||||
|
|
||||||
/* 文件夹最上级目录名称 */
|
|
||||||
private String type;
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
package cn.bunny.service;
|
|
||||||
|
|
||||||
import cn.bunny.domain.entity.TableMetaData;
|
|
||||||
|
|
||||||
public interface SqlParserService {
|
|
||||||
/**
|
|
||||||
* 解析SQL内容
|
|
||||||
*
|
|
||||||
* @param sql Sql语句
|
|
||||||
* @return 表信息内容
|
|
||||||
*/
|
|
||||||
TableMetaData tableInfo(String sql);
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
package cn.bunny.service;
|
|
||||||
|
|
||||||
import cn.bunny.domain.entity.ColumnMetaData;
|
|
||||||
import cn.bunny.domain.entity.DatabaseInfoMetaData;
|
|
||||||
import cn.bunny.domain.entity.TableMetaData;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface TableService {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取表属性
|
|
||||||
*
|
|
||||||
* @param tableName 表名称
|
|
||||||
* @return 表属性
|
|
||||||
*/
|
|
||||||
TableMetaData tableMetaData(String tableName);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取所有数据库
|
|
||||||
*
|
|
||||||
* @return 所有表信息
|
|
||||||
*/
|
|
||||||
List<TableMetaData> databaseTableList(String tableName);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取列属性
|
|
||||||
*
|
|
||||||
* @param tableName 表名称
|
|
||||||
* @return 当前表所有的列内容
|
|
||||||
*/
|
|
||||||
List<ColumnMetaData> tableColumnInfo(String tableName);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 数据库所有的信息
|
|
||||||
*
|
|
||||||
* @return 当前连接的数据库信息属性
|
|
||||||
*/
|
|
||||||
DatabaseInfoMetaData databaseInfoMetaData();
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
package cn.bunny.service;
|
|
||||||
|
|
||||||
import cn.bunny.domain.dto.VmsArgumentDto;
|
|
||||||
import cn.bunny.domain.vo.GeneratorVo;
|
|
||||||
import cn.bunny.domain.vo.VmsPathVo;
|
|
||||||
import jakarta.validation.Valid;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public interface VmsService {
|
|
||||||
/**
|
|
||||||
* 生成服务端代码
|
|
||||||
*
|
|
||||||
* @param dto VmsArgumentDto
|
|
||||||
* @return 生成内容
|
|
||||||
*/
|
|
||||||
List<GeneratorVo> generator(VmsArgumentDto dto);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取vms文件路径
|
|
||||||
*
|
|
||||||
* @return vms下的文件路径
|
|
||||||
*/
|
|
||||||
Map<String, List<VmsPathVo>> vmsResourcePathList();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 打包成zip下载
|
|
||||||
*
|
|
||||||
* @param dto VmsArgumentDto
|
|
||||||
* @return zip 文件
|
|
||||||
*/
|
|
||||||
ResponseEntity<byte[]> downloadByZip(@Valid VmsArgumentDto dto);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
package cn.bunny.service.impl;
|
|
||||||
|
|
||||||
import cn.bunny.core.factory.ConcreteSqlParserDatabaseInfo;
|
|
||||||
import cn.bunny.domain.entity.TableMetaData;
|
|
||||||
import cn.bunny.service.SqlParserService;
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import org.springframework.beans.BeanUtils;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class SqlParserServiceImpl implements SqlParserService {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private ConcreteSqlParserDatabaseInfo sqlParserDatabaseInfo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 解析SQL内容
|
|
||||||
*
|
|
||||||
* @param sql Sql语句
|
|
||||||
* @return 表信息内容
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public TableMetaData tableInfo(String sql) {
|
|
||||||
TableMetaData tableInfoVo = new TableMetaData();
|
|
||||||
|
|
||||||
TableMetaData tableMetaData = sqlParserDatabaseInfo.getTableMetadata(sql);
|
|
||||||
BeanUtils.copyProperties(tableMetaData, tableInfoVo);
|
|
||||||
return tableInfoVo;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,94 +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 jakarta.annotation.Resource;
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
import org.springframework.beans.BeanUtils;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class TableServiceImpl implements TableService {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private ConcreteDatabaseInfo databaseInfoCore;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取表属性
|
|
||||||
*
|
|
||||||
* @param tableName 表名称
|
|
||||||
* @return 表属性
|
|
||||||
*/
|
|
||||||
@SneakyThrows
|
|
||||||
@Override
|
|
||||||
public TableMetaData tableMetaData(String tableName) {
|
|
||||||
TableMetaData tableInfoVo = new TableMetaData();
|
|
||||||
|
|
||||||
TableMetaData tableMetaData = databaseInfoCore.getTableMetadata(tableName);
|
|
||||||
BeanUtils.copyProperties(tableMetaData, tableInfoVo);
|
|
||||||
|
|
||||||
return tableInfoVo;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取[当前/所有]数据库表
|
|
||||||
*
|
|
||||||
* @return 所有表信息
|
|
||||||
*/
|
|
||||||
@SneakyThrows
|
|
||||||
@Override
|
|
||||||
public List<TableMetaData> databaseTableList(String dbName) {
|
|
||||||
List<TableMetaData> allTableInfo = databaseInfoCore.databaseTableList(dbName);
|
|
||||||
|
|
||||||
return allTableInfo.stream().map(tableMetaData -> {
|
|
||||||
TableMetaData tableInfoVo = new TableMetaData();
|
|
||||||
BeanUtils.copyProperties(tableMetaData, tableInfoVo);
|
|
||||||
|
|
||||||
return tableInfoVo;
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前表的列属性
|
|
||||||
*
|
|
||||||
* @param tableName 表名称
|
|
||||||
* @return 当前表所有的列内容
|
|
||||||
*/
|
|
||||||
@SneakyThrows
|
|
||||||
@Override
|
|
||||||
public List<ColumnMetaData> tableColumnInfo(String tableName) {
|
|
||||||
return databaseInfoCore.tableColumnInfo(tableName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 数据库所有的信息
|
|
||||||
*
|
|
||||||
* @return 当前连接的数据库信息属性
|
|
||||||
*/
|
|
||||||
@SneakyThrows
|
|
||||||
@Override
|
|
||||||
public DatabaseInfoMetaData databaseInfoMetaData() {
|
|
||||||
List<TableMetaData> databaseTableList = databaseTableList(null);
|
|
||||||
|
|
||||||
// 将当前数据库表分组,以数据库名称为key
|
|
||||||
List<TableMetaData> databaseList = databaseTableList.stream()
|
|
||||||
.collect(Collectors.groupingBy(TableMetaData::getTableCat))
|
|
||||||
.values().stream()
|
|
||||||
.map(tableInfoVos -> {
|
|
||||||
TableMetaData tableInfoVo = tableInfoVos.get(0);
|
|
||||||
tableInfoVo.setTableName(null);
|
|
||||||
return tableInfoVo;
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
DatabaseInfoMetaData databaseInfoMetaData = databaseInfoCore.databaseInfoMetaData();
|
|
||||||
databaseInfoMetaData.setDatabaseList(databaseList);
|
|
||||||
|
|
||||||
return databaseInfoMetaData;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,153 +0,0 @@
|
||||||
package cn.bunny.service.impl;
|
|
||||||
|
|
||||||
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.domain.vo.VmsPathVo;
|
|
||||||
import cn.bunny.service.VmsService;
|
|
||||||
import cn.bunny.utils.ResourceFileUtil;
|
|
||||||
import cn.bunny.utils.VmsUtil;
|
|
||||||
import cn.hutool.crypto.digest.MD5;
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.zip.ZipEntry;
|
|
||||||
import java.util.zip.ZipOutputStream;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class VmsServiceImpl implements VmsService {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
ConcreteDatabaseInfo databaseInfoCore;
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
ConcreteSqlParserDatabaseInfo sqlParserDatabaseInfo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成服务端代码
|
|
||||||
*
|
|
||||||
* @param dto VmsArgumentDto
|
|
||||||
* @return 生成内容
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public List<GeneratorVo> generator(VmsArgumentDto dto) {
|
|
||||||
String tableName = dto.getTableName();
|
|
||||||
String sql = dto.getSql();
|
|
||||||
|
|
||||||
// 表格属性名 和 列信息
|
|
||||||
TableMetaData tableMetaData = StringUtils.hasText(dto.getSql())
|
|
||||||
? sqlParserDatabaseInfo.getTableMetadata(dto.getSql())
|
|
||||||
: databaseInfoCore.getTableMetadata(dto.getTableName());
|
|
||||||
|
|
||||||
List<ColumnMetaData> columnInfoList = StringUtils.hasText(sql)
|
|
||||||
? sqlParserDatabaseInfo.tableColumnInfo(sql)
|
|
||||||
: databaseInfoCore.tableColumnInfo(tableName).stream().distinct().toList();
|
|
||||||
|
|
||||||
|
|
||||||
return dto.getPath().stream().map(path -> {
|
|
||||||
// 生成模板
|
|
||||||
VmsArgumentDtoBaseVmsGeneratorTemplate vmsArgumentDtoBaseVmsGenerator = new VmsArgumentDtoBaseVmsGeneratorTemplate(dto, path);
|
|
||||||
StringWriter writer = vmsArgumentDtoBaseVmsGenerator.generatorCodeTemplate(tableMetaData, columnInfoList);
|
|
||||||
|
|
||||||
// 处理 vm 文件名
|
|
||||||
path = VmsUtil.handleVmFilename(path, dto.getClassName());
|
|
||||||
|
|
||||||
return GeneratorVo.builder()
|
|
||||||
.code(writer.toString())
|
|
||||||
.comment(tableMetaData.getComment())
|
|
||||||
.tableName(tableMetaData.getTableName())
|
|
||||||
.path(path)
|
|
||||||
.build();
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取vms文件路径
|
|
||||||
*
|
|
||||||
* @return vms下的文件路径
|
|
||||||
*/
|
|
||||||
@SneakyThrows
|
|
||||||
@Override
|
|
||||||
public Map<String, List<VmsPathVo>> vmsResourcePathList() {
|
|
||||||
// 读取当前项目中所有的 vm 模板发给前端
|
|
||||||
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().name(vmFile).label(filename).type(filepathList[0]).build();
|
|
||||||
})
|
|
||||||
.collect(Collectors.groupingBy(VmsPathVo::getType));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 打包成zip下载
|
|
||||||
*
|
|
||||||
* @param dto VmsArgumentDto
|
|
||||||
* @return zip 文件
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public ResponseEntity<byte[]> downloadByZip(VmsArgumentDto dto) {
|
|
||||||
// 需要下载的数据
|
|
||||||
List<GeneratorVo> generatorVoList = generator(dto);
|
|
||||||
|
|
||||||
// 1. 创建临时ZIP文件
|
|
||||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
|
||||||
try (ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
|
|
||||||
// 2. 遍历并创建
|
|
||||||
generatorVoList.forEach(generatorVo -> {
|
|
||||||
// zip中的路径
|
|
||||||
String path = generatorVo.getPath().replace(".vm", "");
|
|
||||||
|
|
||||||
// zip中的文件
|
|
||||||
String code = generatorVo.getCode();
|
|
||||||
|
|
||||||
ZipEntry zipEntry = new ZipEntry(path);
|
|
||||||
try {
|
|
||||||
// 如果有 / 会转成文件夹
|
|
||||||
zipOutputStream.putNextEntry(zipEntry);
|
|
||||||
|
|
||||||
// 写入文件
|
|
||||||
zipOutputStream.write(code.getBytes(StandardCharsets.UTF_8));
|
|
||||||
zipOutputStream.closeEntry();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2.1 文件不重名
|
|
||||||
long currentTimeMillis = System.currentTimeMillis();
|
|
||||||
String digestHex = MD5.create().digestHex(currentTimeMillis + "");
|
|
||||||
|
|
||||||
// 3. 准备响应
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
headers.add("Content-Disposition", "attachment; filename=" + "vms-" + digestHex + ".zip");
|
|
||||||
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
|
|
||||||
headers.add("Pragma", "no-cache");
|
|
||||||
headers.add("Expires", "0");
|
|
||||||
|
|
||||||
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
|
|
||||||
return new ResponseEntity<>(byteArrayInputStream.readAllBytes(), headers, HttpStatus.OK);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
package cn.bunny.utils;
|
|
||||||
|
|
||||||
import com.google.common.base.CaseFormat;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
bunny:
|
|
||||||
master:
|
|
||||||
host: 192.168.3.137
|
|
||||||
port: 3306
|
|
||||||
database: auth_admin
|
|
||||||
username: root
|
|
||||||
password: "123456"
|
|
||||||
# connect:
|
|
||||||
# url: jdbc:sqlite::resource:database.sqlite
|
|
||||||
# username: root
|
|
||||||
# password: "123456"
|
|
|
@ -1,10 +0,0 @@
|
||||||
_ _
|
|
||||||
| |__ _ _ _ __ _ __ _ _ (_) __ ___ ____ _
|
|
||||||
| '_ \| | | | '_ \| '_ \| | | | | |/ _` \ \ / / _` |
|
|
||||||
| |_) | |_| | | | | | | | |_| | | | (_| |\ V | (_| |
|
|
||||||
|_.__/ \__,_|_| |_|_| |_|\__, | _/ |\__,_| \_/ \__,_|
|
|
||||||
|___/ |__/
|
|
||||||
|
|
||||||
Service Name${spring.application.name}
|
|
||||||
SpringBoot Version: ${spring-boot.version}${spring-boot.formatted-version}
|
|
||||||
SpringActive:${spring.profiles.active}
|
|
|
@ -1,69 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<configuration>
|
|
||||||
<contextName>logback</contextName>
|
|
||||||
|
|
||||||
<!-- 格式化 年-月-日 输出 -->
|
|
||||||
<timestamp key="datetime" datePattern="yyyy-MM-dd"/>
|
|
||||||
|
|
||||||
<!--编码-->
|
|
||||||
<property name="ENCODING" value="UTF-8"/>
|
|
||||||
|
|
||||||
<!-- 控制台日志 -->
|
|
||||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
|
||||||
<!-- 临界值过滤器 -->
|
|
||||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
|
||||||
<level>INFO</level>
|
|
||||||
</filter>
|
|
||||||
<encoder>
|
|
||||||
<pattern>%cyan([%thread %d{yyyy-MM-dd HH:mm:ss}]) %yellow(%-5level) %green(%logger{100}).%boldRed(%method)-%boldMagenta(%line)-%blue(%msg%n)
|
|
||||||
</pattern>
|
|
||||||
<charset>${ENCODING}</charset>
|
|
||||||
</encoder>
|
|
||||||
</appender>
|
|
||||||
|
|
||||||
<!-- 文件日志 -->
|
|
||||||
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
|
|
||||||
<file>logs/${datetime}/financial-server.log</file>
|
|
||||||
<append>true</append>
|
|
||||||
<encoder>
|
|
||||||
<pattern>%date{yyyy-MM-dd HH:mm:ss} [%-5level] %thread %file:%line %logger %msg%n</pattern>
|
|
||||||
<charset>${ENCODING}</charset>
|
|
||||||
</encoder>
|
|
||||||
</appender>
|
|
||||||
|
|
||||||
<!-- 让SpringBoot内部日志ERROR级别 减少日志输出 -->
|
|
||||||
<logger name="org.springframework" level="ERROR" additivity="false">
|
|
||||||
<appender-ref ref="STOUT"/>
|
|
||||||
</logger>
|
|
||||||
|
|
||||||
<!-- 让mybatis整合包日志ERROR 减少日志输出 -->
|
|
||||||
<logger name="org.mybatis" level="ERROR" additivity="false">
|
|
||||||
<appender-ref ref="STOUT"/>
|
|
||||||
</logger>
|
|
||||||
|
|
||||||
<!-- 让ibatis 日志ERROR 减少日志输出 -->
|
|
||||||
<logger name="org.apache.ibatis" level="ERROR" additivity="false">
|
|
||||||
<appender-ref ref="STOUT"/>
|
|
||||||
</logger>
|
|
||||||
|
|
||||||
<!-- 让 tomcat包打印日志 日志ERROR 减少日志输出 -->
|
|
||||||
<logger name="org.apache" level="ERROR" additivity="false">
|
|
||||||
<appender-ref ref="STOUT"/>
|
|
||||||
</logger>
|
|
||||||
|
|
||||||
<!-- 我们自己开发的程序为DEBUG -->
|
|
||||||
<logger name="cn.bunny" level="DEBUG" additivity="false">
|
|
||||||
<appender-ref ref="STOUT"/>
|
|
||||||
</logger>
|
|
||||||
|
|
||||||
<logger name="com.baomidou" level="ERROR" additivity="false">
|
|
||||||
<appender-ref ref="STOUT"/>
|
|
||||||
</logger>
|
|
||||||
|
|
||||||
<!-- 根日志记录器:INFO级别 -->
|
|
||||||
<root level="INFO">
|
|
||||||
<appender-ref ref="CONSOLE"/>
|
|
||||||
<appender-ref ref="FILE"/>
|
|
||||||
</root>
|
|
||||||
|
|
||||||
</configuration>
|
|
|
@ -1,98 +0,0 @@
|
||||||
package cn.bunny;
|
|
||||||
|
|
||||||
|
|
||||||
import cn.bunny.domain.entity.ColumnMetaData;
|
|
||||||
import cn.bunny.domain.entity.TableMetaData;
|
|
||||||
import cn.bunny.utils.TypeConvertUtil;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.sql.DatabaseMetaData;
|
|
||||||
import java.sql.ResultSet;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@SpringBootTest
|
|
||||||
public class JDBCTest {
|
|
||||||
|
|
||||||
private final DataSource dataSource;
|
|
||||||
DatabaseMetaData metaData;
|
|
||||||
|
|
||||||
public JDBCTest(DataSource dataSource) {
|
|
||||||
this.dataSource = dataSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
try (Connection connection = dataSource.getConnection()) {
|
|
||||||
metaData = connection.getMetaData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testComment() throws SQLException {
|
|
||||||
String tableName = "sys_i18n";
|
|
||||||
TableMetaData tableMetaData;
|
|
||||||
ResultSet tables = metaData.getTables(null, null, tableName, new String[]{"TABLE"});
|
|
||||||
|
|
||||||
// 获取表的注释信息
|
|
||||||
if (tables.next()) {
|
|
||||||
String remarks = tables.getString("REMARKS");
|
|
||||||
String tableCat = tables.getString("TABLE_CAT");
|
|
||||||
|
|
||||||
tableMetaData = TableMetaData.builder()
|
|
||||||
.tableName(tableName)
|
|
||||||
.comment(remarks)
|
|
||||||
.tableCat(tableCat)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
System.out.println(tableMetaData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testAllTableComment() throws SQLException {
|
|
||||||
ResultSet tables = metaData.getTables(null, null, "%", new String[]{"TABLE"});
|
|
||||||
List<TableMetaData> list = new ArrayList<>();
|
|
||||||
|
|
||||||
while (tables.next()) {
|
|
||||||
String tableName = tables.getString("TABLE_NAME");
|
|
||||||
String remarks = tables.getString("REMARKS");
|
|
||||||
String tableCat = tables.getString("TABLE_CAT");
|
|
||||||
|
|
||||||
TableMetaData tableMetaData = TableMetaData.builder()
|
|
||||||
.tableName(tableName).comment(remarks)
|
|
||||||
.tableCat(tableCat)
|
|
||||||
.build();
|
|
||||||
list.add(tableMetaData);
|
|
||||||
}
|
|
||||||
|
|
||||||
System.out.println(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testColumnInfo() throws SQLException {
|
|
||||||
List<ColumnMetaData> columns = new ArrayList<>();
|
|
||||||
|
|
||||||
try (ResultSet columnsRs = metaData.getColumns(null, null, "sys_i18n", null)) {
|
|
||||||
while (columnsRs.next()) {
|
|
||||||
ColumnMetaData column = new ColumnMetaData();
|
|
||||||
column.setColumnName(columnsRs.getString("COLUMN_NAME"));
|
|
||||||
column.setLowercaseName(TypeConvertUtil.convertToCamelCase(column.getColumnName()));
|
|
||||||
column.setJdbcType(columnsRs.getString("TYPE_NAME"));
|
|
||||||
column.setJavaType(TypeConvertUtil.convertToJavaType(column.getJdbcType()));
|
|
||||||
column.setComment(columnsRs.getString("REMARKS"));
|
|
||||||
|
|
||||||
columns.add(column);
|
|
||||||
System.out.println(column);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
System.out.println(columns);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,105 +0,0 @@
|
||||||
package cn.bunny;
|
|
||||||
|
|
||||||
import cn.bunny.domain.entity.ColumnMetaData;
|
|
||||||
import cn.bunny.domain.entity.TableMetaData;
|
|
||||||
import cn.bunny.utils.TypeConvertUtil;
|
|
||||||
import net.sf.jsqlparser.JSQLParserException;
|
|
||||||
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
|
|
||||||
import net.sf.jsqlparser.statement.Statement;
|
|
||||||
import net.sf.jsqlparser.statement.create.table.CreateTable;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
public class SqlParserTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test() throws JSQLParserException {
|
|
||||||
String sql = """
|
|
||||||
CREATE TABLE `sys_files` (
|
|
||||||
`id` bigint NOT NULL COMMENT '文件的唯一标识符,自动递增',
|
|
||||||
`filename` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '文件的名称',
|
|
||||||
`filepath` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '文件在服务器上的存储路径',
|
|
||||||
`file_size` int NOT NULL COMMENT '文件的大小,以字节为单位',
|
|
||||||
`file_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '文件的MIME类型',
|
|
||||||
`download_count` int NULL DEFAULT 0 COMMENT '下载数量',
|
|
||||||
`create_user` bigint NOT NULL COMMENT '创建用户',
|
|
||||||
`update_user` bigint NULL DEFAULT NULL COMMENT '操作用户',
|
|
||||||
`create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
|
||||||
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录文件最后修改的时间戳',
|
|
||||||
`is_deleted` tinyint(1) UNSIGNED ZEROFILL NOT NULL DEFAULT 0 COMMENT '文件是否被删除',
|
|
||||||
PRIMARY KEY (`id`) USING BTREE,
|
|
||||||
INDEX `idx_filename`(`filename` ASC) USING BTREE COMMENT '索引文件名',
|
|
||||||
INDEX `idx_filepath`(`filepath` ASC) USING BTREE COMMENT '索引文件路径',
|
|
||||||
INDEX `idx_file_type`(`file_type` ASC) USING BTREE COMMENT '索引文件类型',
|
|
||||||
INDEX `idx_update_user`(`update_user` ASC) USING BTREE COMMENT '索引创更新用户',
|
|
||||||
INDEX `idx_create_user`(`create_user` ASC) USING BTREE COMMENT '索引创建用户',
|
|
||||||
INDEX `idx_user`(`update_user` ASC, `create_user` ASC) USING BTREE COMMENT '索引创建用户和更新用户',
|
|
||||||
INDEX `idx_time`(`update_time` ASC, `create_time` ASC) USING BTREE COMMENT '索引创建时间和更新时间'
|
|
||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '系统文件表' ROW_FORMAT = DYNAMIC;
|
|
||||||
""";
|
|
||||||
|
|
||||||
TableMetaData tableInfo = new TableMetaData();
|
|
||||||
|
|
||||||
// 解析sql
|
|
||||||
Statement statement = CCJSqlParserUtil.parse(sql);
|
|
||||||
if (!(statement instanceof CreateTable createTable)) {
|
|
||||||
throw new IllegalArgumentException("Not a CREATE TABLE statement");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置表基本信息
|
|
||||||
tableInfo.setTableName(createTable.getTable().getName());
|
|
||||||
tableInfo.setTableType("TABLE");
|
|
||||||
String tableOptionsStrings = String.join(" ", createTable.getTableOptionsStrings());
|
|
||||||
|
|
||||||
// 注释信息
|
|
||||||
Pattern pattern = Pattern.compile("COMMENT\\s*=\\s*'(.*?)'", Pattern.CASE_INSENSITIVE);
|
|
||||||
Matcher matcher = pattern.matcher(tableOptionsStrings);
|
|
||||||
if (matcher.find()) {
|
|
||||||
tableInfo.setComment(matcher.group(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析列信息
|
|
||||||
List<ColumnMetaData> columnMetaData = createTable.getColumnDefinitions()
|
|
||||||
.stream().map(column -> {
|
|
||||||
// 列信息
|
|
||||||
ColumnMetaData columnInfo = new ColumnMetaData();
|
|
||||||
|
|
||||||
// 列名称
|
|
||||||
columnInfo.setColumnName(column.getColumnName());
|
|
||||||
|
|
||||||
// 设置 JDBC 类型
|
|
||||||
String dataType = column.getColDataType().getDataType();
|
|
||||||
columnInfo.setJdbcType(dataType);
|
|
||||||
|
|
||||||
// 设置 Java 类型
|
|
||||||
String javaType = TypeConvertUtil.convertToJavaType(dataType.contains("varchar") ? "varchar" : dataType);
|
|
||||||
columnInfo.setJavaType(javaType);
|
|
||||||
|
|
||||||
// 设置 JavaScript 类型
|
|
||||||
columnInfo.setJavascriptType(StringUtils.uncapitalize(javaType));
|
|
||||||
|
|
||||||
// 列字段转成 下划线 -> 小驼峰
|
|
||||||
columnInfo.setLowercaseName(TypeConvertUtil.convertToCamelCase(column.getColumnName()));
|
|
||||||
// 列字段转成 下划线 -> 大驼峰名称
|
|
||||||
columnInfo.setUppercaseName(TypeConvertUtil.convertToCamelCase(column.getColumnName(), true));
|
|
||||||
|
|
||||||
// 解析注释
|
|
||||||
List<String> columnSpecs = column.getColumnSpecs();
|
|
||||||
String columnSpecsString = String.join(" ", columnSpecs);
|
|
||||||
Matcher columnSpecsStringMatcher = Pattern.compile("COMMENT\\s*'(.*?)'", Pattern.CASE_INSENSITIVE).matcher(columnSpecsString);
|
|
||||||
if (columnSpecsStringMatcher.find()) {
|
|
||||||
columnInfo.setComment(columnSpecsStringMatcher.group(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
return columnInfo;
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
System.out.println(tableInfo);
|
|
||||||
System.out.println("----------------------------------------------------------------------------------------");
|
|
||||||
System.out.println(columnMetaData);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
package cn.bunny;
|
|
||||||
|
|
||||||
import cn.bunny.utils.TypeConvertUtil;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.assertj.core.util.introspection.CaseFormatUtils;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
public class StringFormatTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void test1() {
|
|
||||||
System.out.println(CaseFormatUtils.toCamelCase("user_login"));
|
|
||||||
System.out.println(CaseFormatUtils.toCamelCase("userLogin"));
|
|
||||||
System.out.println(CaseFormatUtils.toCamelCase("UserLogin"));
|
|
||||||
|
|
||||||
System.out.println("--------------------------------");
|
|
||||||
|
|
||||||
System.out.println(StringUtils.lowerCase("user_login"));
|
|
||||||
System.out.println(StringUtils.lowerCase("userLogin"));
|
|
||||||
System.out.println(StringUtils.lowerCase("UserLogin"));
|
|
||||||
|
|
||||||
System.out.println("--------------------------------");
|
|
||||||
|
|
||||||
System.out.println(StringUtils.upperCase("user_login"));
|
|
||||||
System.out.println(StringUtils.upperCase("userLogin"));
|
|
||||||
System.out.println(StringUtils.upperCase("UserLogin"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void test2() {
|
|
||||||
System.out.println(TypeConvertUtil.convertToCamelCase("user_login_A"));
|
|
||||||
System.out.println(TypeConvertUtil.convertToCamelCase("User_Login_A"));
|
|
||||||
System.out.println(TypeConvertUtil.convertToCamelCase("userLoginA"));
|
|
||||||
System.out.println(TypeConvertUtil.convertToCamelCase("UserLoginA"));
|
|
||||||
|
|
||||||
System.out.println("--------------------------------");
|
|
||||||
|
|
||||||
System.out.println(TypeConvertUtil.convertToCamelCase("i18n_type_A", true));
|
|
||||||
System.out.println(TypeConvertUtil.convertToCamelCase("User_Login_A", true));
|
|
||||||
System.out.println(TypeConvertUtil.convertToCamelCase("userLoginA", true));
|
|
||||||
System.out.println(TypeConvertUtil.convertToCamelCase("UserLoginA", true));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
package cn.bunny;
|
|
||||||
|
|
||||||
import cn.hutool.crypto.digest.MD5;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
public class TimeTest {
|
|
||||||
@Test
|
|
||||||
void timeTest() {
|
|
||||||
long currentTimeMillis = System.currentTimeMillis();
|
|
||||||
String digestHex = MD5.create().digestHex(currentTimeMillis + "");
|
|
||||||
System.out.println(currentTimeMillis);
|
|
||||||
System.out.println(digestHex);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
package cn.bunny.service.impl;
|
|
||||||
|
|
||||||
import cn.bunny.domain.vo.VmsPathVo;
|
|
||||||
import cn.bunny.utils.ResourceFileUtil;
|
|
||||||
import com.alibaba.fastjson2.JSON;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
class VmsServiceImplTest {
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void vmsResourcePathList() throws IOException, URISyntaxException {
|
|
||||||
List<String> vmsFiles = ResourceFileUtil.getAbsoluteFiles("vms");
|
|
||||||
System.out.println(vmsFiles);
|
|
||||||
|
|
||||||
System.out.println("--------------------------------------------------------------");
|
|
||||||
|
|
||||||
List<String> vmsRelativeFiles = ResourceFileUtil.getRelativeFiles("vms");
|
|
||||||
System.out.println(vmsRelativeFiles);
|
|
||||||
|
|
||||||
System.out.println("--------------------------集合对象模式------------------------------------");
|
|
||||||
|
|
||||||
Map<String, List<VmsPathVo>> map = vmsRelativeFiles.stream().map(vmFile -> {
|
|
||||||
String[] filepathList = vmFile.split("/");
|
|
||||||
String filename = filepathList[filepathList.length - 1].replace(".vm", "");
|
|
||||||
|
|
||||||
return VmsPathVo.builder().name(vmFile).label(filename).type(filepathList[0]).build();
|
|
||||||
}).collect(Collectors.groupingBy(VmsPathVo::getType));
|
|
||||||
|
|
||||||
System.out.println(JSON.toJSONString(map));
|
|
||||||
|
|
||||||
System.out.println("----------------------------二维数组格式----------------------------------");
|
|
||||||
List<List<VmsPathVo>> listMap = vmsRelativeFiles.stream().map(vmFile -> {
|
|
||||||
String[] filepathList = vmFile.split("/");
|
|
||||||
String filename = filepathList[filepathList.length - 1].replace(".vm", "");
|
|
||||||
|
|
||||||
return VmsPathVo.builder().name(vmFile).label(filename).type(filepathList[0]).build();
|
|
||||||
})
|
|
||||||
.collect(Collectors.groupingBy(VmsPathVo::getType))
|
|
||||||
.values().stream().toList();
|
|
||||||
|
|
||||||
System.out.println(JSON.toJSONString(listMap));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,75 +0,0 @@
|
||||||
package cn.bunny.utils;
|
|
||||||
|
|
||||||
import cn.bunny.domain.entity.TableMetaData;
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.sql.DatabaseMetaData;
|
|
||||||
import java.sql.ResultSet;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@SpringBootTest
|
|
||||||
class ConcreteDatabaseInfoCoreTest {
|
|
||||||
|
|
||||||
String tableName = "sys_i18n";
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private DataSource dataSource;
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testTableInfoMetaData() {
|
|
||||||
TableMetaData tableMetaData;
|
|
||||||
|
|
||||||
try (Connection connection = dataSource.getConnection()) {
|
|
||||||
DatabaseMetaData metaData = connection.getMetaData();
|
|
||||||
ResultSet tables = metaData.getTables(null, null, tableName, new String[]{"TABLE"});
|
|
||||||
|
|
||||||
// 获取表的注释信息
|
|
||||||
if (tables.next()) {
|
|
||||||
String remarks = tables.getString("REMARKS");
|
|
||||||
String tableCat = tables.getString("TABLE_CAT");
|
|
||||||
String tableType = tables.getString("TABLE_TYPE");
|
|
||||||
|
|
||||||
tableMetaData = TableMetaData.builder()
|
|
||||||
.tableName(tableName)
|
|
||||||
.comment(remarks)
|
|
||||||
.tableCat(tableCat)
|
|
||||||
.tableType(tableType)
|
|
||||||
.build();
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException("数据表不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
System.out.println(tableMetaData);
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
@Test
|
|
||||||
void getDbTableList() {
|
|
||||||
String dbName = "auth_admin";
|
|
||||||
|
|
||||||
try (Connection connection = dataSource.getConnection()) {
|
|
||||||
DatabaseMetaData metaData = connection.getMetaData();
|
|
||||||
ResultSet tables = metaData.getTables(dbName, null, "%", new String[]{"TABLE"});
|
|
||||||
|
|
||||||
List<String> list = new ArrayList<>();
|
|
||||||
|
|
||||||
while (tables.next()) {
|
|
||||||
dbName = tables.getString("TABLE_NAME");
|
|
||||||
list.add(dbName);
|
|
||||||
}
|
|
||||||
|
|
||||||
System.out.println(list);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
node_modules
|
|
||||||
.DS_Store
|
|
||||||
dist
|
|
||||||
dist-ssr
|
|
||||||
*.local
|
|
||||||
.eslintcache
|
|
||||||
report.html
|
|
||||||
|
|
||||||
yarn.lock
|
|
||||||
npm-debug.log*
|
|
||||||
.pnpm-error.log*
|
|
||||||
.pnpm-debug.log
|
|
||||||
tests/**/coverage/
|
|
||||||
|
|
||||||
# Editor directories and files
|
|
||||||
.idea
|
|
||||||
*.suo
|
|
||||||
*.ntvs*
|
|
||||||
*.njsproj
|
|
||||||
*.sln
|
|
||||||
tsconfig.tsbuildinfo
|
|
|
@ -1,29 +0,0 @@
|
||||||
# 应用名称
|
|
||||||
VITE_APP_TITLE="代码生成器"
|
|
||||||
|
|
||||||
# 平台本地运行端口号
|
|
||||||
VITE_PORT=7000
|
|
||||||
|
|
||||||
# 开发环境读取配置文件路径
|
|
||||||
VITE_PUBLIC_PATH=/
|
|
||||||
|
|
||||||
# 跨域代理地址
|
|
||||||
VITE_APP_URL=http://localhost:9999
|
|
||||||
|
|
||||||
# 如果端口被占用会直接退出,而不是尝试下一个端口
|
|
||||||
VITE_STRICT_PORT=false
|
|
||||||
|
|
||||||
# 是否在打包时使用cdn替换本地库 替换 true 不替换 false
|
|
||||||
VITE_CDN=false
|
|
||||||
|
|
||||||
# 是否使用Mock
|
|
||||||
VITE_MOCK_DEV_SERVER=true
|
|
||||||
|
|
||||||
# mock地址
|
|
||||||
VITE_MOCK_BASE_API=/mock
|
|
||||||
|
|
||||||
# 基础请求路径
|
|
||||||
VITE_APP_BASE_API=/api
|
|
||||||
|
|
||||||
# 是否启用gzip压缩
|
|
||||||
VITE_COMPRESSION=gzip
|
|
|
@ -1,26 +0,0 @@
|
||||||
# 平台本地运行端口号
|
|
||||||
VITE_PORT=7000
|
|
||||||
|
|
||||||
# 开发环境读取配置文件路径
|
|
||||||
VITE_PUBLIC_PATH=/
|
|
||||||
|
|
||||||
# 跨域代理地址
|
|
||||||
VITE_APP_URL=http://localhost:8800
|
|
||||||
|
|
||||||
# 基础请求路径
|
|
||||||
VITE_APP_BASE_API=/api
|
|
||||||
|
|
||||||
# mock地址
|
|
||||||
VITE_MOCK_BASE_API=/mock
|
|
||||||
|
|
||||||
# 是否使用Mock
|
|
||||||
VITE_MOCK_DEV_SERVER=true
|
|
||||||
|
|
||||||
# 如果端口被占用会直接退出,而不是尝试下一个端口
|
|
||||||
VITE_STRICT_PORT=false
|
|
||||||
|
|
||||||
# 是否在打包时使用cdn替换本地库 替换 true 不替换 false
|
|
||||||
VITE_CDN=false
|
|
||||||
|
|
||||||
# 是否启用gzip压缩
|
|
||||||
VITE_COMPRESSION=gzip
|
|
|
@ -1,26 +0,0 @@
|
||||||
# 平台本地运行端口号
|
|
||||||
VITE_PORT=7000
|
|
||||||
|
|
||||||
# 开发环境读取配置文件路径
|
|
||||||
VITE_PUBLIC_PATH=/
|
|
||||||
|
|
||||||
# 跨域代理地址
|
|
||||||
VITE_APP_URL=http://localhost:8800
|
|
||||||
|
|
||||||
# 基础请求路径
|
|
||||||
VITE_APP_BASE_API=/api
|
|
||||||
|
|
||||||
# 是否使用Mock
|
|
||||||
VITE_MOCK_DEV_SERVER=true
|
|
||||||
|
|
||||||
# mock地址
|
|
||||||
VITE_MOCK_BASE_API=/mock
|
|
||||||
|
|
||||||
# 如果端口被占用会直接退出,而不是尝试下一个端口
|
|
||||||
VITE_STRICT_PORT=false
|
|
||||||
|
|
||||||
# 是否在打包时使用cdn替换本地库 替换 true 不替换 false
|
|
||||||
VITE_CDN=false
|
|
||||||
|
|
||||||
# 是否启用gzip压缩
|
|
||||||
VITE_COMPRESSION=gzip
|
|
|
@ -1,11 +0,0 @@
|
||||||
dist
|
|
||||||
node_modules
|
|
||||||
public
|
|
||||||
.husky
|
|
||||||
.vscode
|
|
||||||
.idea
|
|
||||||
*.sh
|
|
||||||
*.md
|
|
||||||
|
|
||||||
src/assets
|
|
||||||
stats.html
|
|
|
@ -1,46 +0,0 @@
|
||||||
export default {
|
|
||||||
// (x)=>{},单个参数箭头函数是否显示小括号。(always:始终显示;avoid:省略括号。默认:always)
|
|
||||||
arrowParens: "always",
|
|
||||||
// 开始标签的右尖括号是否跟随在最后一行属性末尾,默认false
|
|
||||||
bracketSameLine: false,
|
|
||||||
// 对象字面量的括号之间打印空格 (true - Example: { foo: bar } ; false - Example: {foo:bar})
|
|
||||||
bracketSpacing: true,
|
|
||||||
// 是否格式化一些文件中被嵌入的代码片段的风格(auto|off;默认auto)
|
|
||||||
embeddedLanguageFormatting: "auto",
|
|
||||||
// 指定 HTML 文件的空格敏感度 (css|strict|ignore;默认css)
|
|
||||||
htmlWhitespaceSensitivity: "ignore",
|
|
||||||
// 当文件已经被 Prettier 格式化之后,是否会在文件顶部插入一个特殊的 @format 标记,默认false
|
|
||||||
insertPragma: false,
|
|
||||||
// 在 JSX 中使用单引号替代双引号,默认false
|
|
||||||
jsxSingleQuote: false,
|
|
||||||
// 每行最多字符数量,超出换行(默认100)
|
|
||||||
printWidth: 100,
|
|
||||||
// 超出打印宽度 (always | never | preserve )
|
|
||||||
proseWrap: "preserve",
|
|
||||||
// 对象属性是否使用引号(as-needed | consistent | preserve;默认as-needed:对象的属性需要加引号才添加;)
|
|
||||||
quoteProps: "as-needed",
|
|
||||||
// 是否只格式化在文件顶部包含特定注释(@prettier| @format)的文件,默认false
|
|
||||||
requirePragma: false,
|
|
||||||
// 结尾添加分号
|
|
||||||
semi: true,
|
|
||||||
// 使用单引号 (true:单引号;false:双引号)
|
|
||||||
singleQuote: true,
|
|
||||||
// 缩进空格数,默认2个空格
|
|
||||||
tabWidth: 2,
|
|
||||||
// 元素末尾是否加逗号,默认es5: ES5中的 objects, arrays 等会添加逗号,TypeScript 中的 type 后不加逗号
|
|
||||||
trailingComma: "es5",
|
|
||||||
// 指定缩进方式,空格或tab,默认false,即使用空格
|
|
||||||
useTabs: false,
|
|
||||||
// vue 文件中是否缩进 <style> 和 <script> 标签,默认 false
|
|
||||||
vueIndentScriptAndStyle: false,
|
|
||||||
|
|
||||||
endOfLine: "auto",
|
|
||||||
overrides: [
|
|
||||||
{
|
|
||||||
files: "*.html",
|
|
||||||
options: {
|
|
||||||
parser: "html",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
|
@ -1,11 +0,0 @@
|
||||||
dist
|
|
||||||
node_modules
|
|
||||||
public
|
|
||||||
.husky
|
|
||||||
.vscode
|
|
||||||
.idea
|
|
||||||
*.sh
|
|
||||||
*.md
|
|
||||||
|
|
||||||
src/assets
|
|
||||||
stats.html
|
|
|
@ -1,8 +0,0 @@
|
||||||
{
|
|
||||||
"hash": "727e0168",
|
|
||||||
"configHash": "10198a12",
|
|
||||||
"lockfileHash": "530f8857",
|
|
||||||
"browserHash": "41b39cb9",
|
|
||||||
"optimized": {},
|
|
||||||
"chunks": {}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"type": "module"
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
import type { BuildOptions } from 'vite';
|
|
||||||
|
|
||||||
import { pathResolve } from './utils';
|
|
||||||
|
|
||||||
export const buildEnv = (): BuildOptions => {
|
|
||||||
return {
|
|
||||||
target: 'es2015',
|
|
||||||
assetsInlineLimit: 20000,
|
|
||||||
// 构建输出的目录,默认值为"dist"
|
|
||||||
outDir: 'docker/dist',
|
|
||||||
// 用于指定使用的代码压缩工具。在这里,minify 被设置为 'terser',表示使用 Terser 进行代码压缩。默认值terser
|
|
||||||
// esbuild 打包更快,但是不能去除 console.log,terser打包慢,但能去除 console.log
|
|
||||||
minify: 'terser', // "esbuild"
|
|
||||||
// 用于配置 Terser 的选项
|
|
||||||
terserOptions: {
|
|
||||||
// 用于配置压缩选项
|
|
||||||
compress: {
|
|
||||||
drop_console: true, // 是否删除代码中的 console 语句, 默认值false
|
|
||||||
drop_debugger: true, // 是否删除代码中的 debugger 语句, 默认值false
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// 禁用 gzip 压缩大小报告,可略微减少打包时间
|
|
||||||
reportCompressedSize: false,
|
|
||||||
// 用于指定是否生成源映射文件。源映射文件可以帮助调试和定位源代码中的错误。当设置为false时,构建过程不会生成源映射文件
|
|
||||||
sourcemap: false,
|
|
||||||
// 用于配置 CommonJS 模块的选项
|
|
||||||
commonjsOptions: {
|
|
||||||
// 用于指定是否忽略 CommonJS 模块中的 try-catch 语句。当设置为false时,构建过程会保留 CommonJS 模块中的 try-catch 语句
|
|
||||||
ignoreTryCatch: false,
|
|
||||||
},
|
|
||||||
// 规定触发警告的 chunk 大小, 当某个代码分块的大小超过该限制时,Vite 会发出警告
|
|
||||||
chunkSizeWarningLimit: 2000,
|
|
||||||
rollupOptions: {
|
|
||||||
external: ['md-editor-v3', 'echarts'],
|
|
||||||
input: {
|
|
||||||
// @ts-ignore
|
|
||||||
index: pathResolve('../index.html', import.meta.url),
|
|
||||||
},
|
|
||||||
// 静态资源分类打包
|
|
||||||
output: {
|
|
||||||
chunkFileNames: 'static/js/[name]-[hash].js',
|
|
||||||
entryFileNames: 'static/js/[name]-[hash].js',
|
|
||||||
assetFileNames: 'static/[ext]/[name]-[hash].[ext]',
|
|
||||||
manualChunks: (id) => {
|
|
||||||
// 如果是包含在包中则打包成 vendor
|
|
||||||
if (id.includes('node_modules')) {
|
|
||||||
return `vendor`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,47 +0,0 @@
|
||||||
import { Plugin as importToCDN } from 'vite-plugin-cdn-import';
|
|
||||||
|
|
||||||
import { wrapperEnv } from './utils';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 打包时采用`cdn`模式,仅限外网使用(默认不采用,如果需要采用cdn模式,请在 .env.production 文件,将 VITE_CDN 设置成true)
|
|
||||||
* 平台采用国内cdn:https://www.bootcdn.cn,当然你也可以选择 https://unpkg.com 或者 https://www.jsdelivr.com
|
|
||||||
* 注意:上面提到的仅限外网使用也不是完全肯定的,如果你们公司内网部署的有相关js、css文件,也可以将下面配置对应改一下,整一套内网版cdn
|
|
||||||
*/
|
|
||||||
export const cdn = importToCDN({
|
|
||||||
//(prodUrl解释: name: 对应下面modules的name,version: 自动读取本地package.json中dependencies依赖中对应包的版本号,path: 对应下面modules的path,当然也可写完整路径,会替换prodUrl)
|
|
||||||
// prodUrl: 'https://cdn.bootcdn.net/ajax/libs/{name}/{version}/{path}',
|
|
||||||
prodUrl: 'https://unpkg.com/{name}@{version}/{path}',
|
|
||||||
modules: [
|
|
||||||
{
|
|
||||||
name: 'vue',
|
|
||||||
var: 'Vue',
|
|
||||||
path: 'dist/vue.global.prod.js',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'vue-router',
|
|
||||||
var: 'VueRouter',
|
|
||||||
path: 'dist/vue-router.global.js',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'pinia',
|
|
||||||
var: 'Pinia',
|
|
||||||
path: 'dist/pinia.iife.js',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'axios',
|
|
||||||
var: 'axios',
|
|
||||||
path: 'dist/axios.min.js',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'dayjs',
|
|
||||||
var: 'dayjs',
|
|
||||||
path: 'dayjs.min.js',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
/* 是否使用CDN加速 */
|
|
||||||
export const useCDN = (mode) => {
|
|
||||||
const env = wrapperEnv(mode, 'VITE');
|
|
||||||
return env.VITE_CDN ? cdn : null;
|
|
||||||
};
|
|
|
@ -1,12 +0,0 @@
|
||||||
import type { CSSOptions } from 'vite';
|
|
||||||
|
|
||||||
|
|
||||||
export const css = (mode: string): CSSOptions => {
|
|
||||||
return {
|
|
||||||
preprocessorOptions: {
|
|
||||||
scss: {
|
|
||||||
additionalData: `@use "@/assets/styles/minix/sidebar" as *;`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,14 +0,0 @@
|
||||||
import dayjs from 'dayjs';
|
|
||||||
|
|
||||||
import { dependencies, devDependencies, engines, name, version } from '../package.json';
|
|
||||||
|
|
||||||
const __APP_INFO__ = {
|
|
||||||
pkg: { name, version, engines, dependencies, devDependencies },
|
|
||||||
lastBuildTime: dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss'),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const define = () => {
|
|
||||||
return {
|
|
||||||
__APP_INFO__: JSON.stringify(__APP_INFO__),
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,58 +0,0 @@
|
||||||
import boxen, { type Options as BoxenOptions } from 'boxen';
|
|
||||||
import dayjs, { type Dayjs } from 'dayjs';
|
|
||||||
import duration from 'dayjs/plugin/duration';
|
|
||||||
import gradientString from 'gradient-string';
|
|
||||||
|
|
||||||
import { logOutputSize, wrapperEnv } from './utils';
|
|
||||||
|
|
||||||
dayjs.extend(duration);
|
|
||||||
|
|
||||||
const boxenOptions: BoxenOptions = {
|
|
||||||
padding: 0.94,
|
|
||||||
borderColor: 'cyan',
|
|
||||||
borderStyle: 'round',
|
|
||||||
textAlignment: 'left',
|
|
||||||
};
|
|
||||||
|
|
||||||
/* 输出日志信息 */
|
|
||||||
const printLogMessage = (VITE_PORT: number) => {
|
|
||||||
return gradientString('cyan', 'magenta').multiline(
|
|
||||||
`欢迎使用此项目,项目访问地址如下:
|
|
||||||
http://localhost:${VITE_PORT}`
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const viteConsoleLog = (mode: string) => {
|
|
||||||
const { VITE_PORT } = wrapperEnv(mode);
|
|
||||||
|
|
||||||
let config: { command: string };
|
|
||||||
let startTime: Dayjs;
|
|
||||||
let endTime: Dayjs;
|
|
||||||
return {
|
|
||||||
name: 'vite:buildInfo',
|
|
||||||
configResolved(resolvedConfig) {
|
|
||||||
config = resolvedConfig;
|
|
||||||
},
|
|
||||||
buildStart() {
|
|
||||||
console.log(boxen(printLogMessage(VITE_PORT), boxenOptions));
|
|
||||||
if (config.command === 'build') {
|
|
||||||
startTime = dayjs(new Date());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
closeBundle() {
|
|
||||||
if (config.command === 'build') {
|
|
||||||
endTime = dayjs(new Date());
|
|
||||||
const format = dayjs.duration(endTime.diff(startTime)).format('mm分ss秒');
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
boxen(
|
|
||||||
gradientString('cyan', 'magenta').multiline(
|
|
||||||
`🎉 恭喜打包完成(总用时${format})打包大小(${logOutputSize()})`
|
|
||||||
),
|
|
||||||
boxenOptions
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,14 +0,0 @@
|
||||||
/**
|
|
||||||
* 此文件作用于 `vite.config.ts` 的 `optimizeDeps.include` 依赖预构建配置项
|
|
||||||
* 依赖预构建,`vite` 启动时会将下面 include 里的模块,编译成 esm 格式并缓存到 node_modules/.vite 文件夹,页面加载到对应模块时如果浏览器有缓存就读取浏览器缓存,如果没有会读取本地缓存并按需加载
|
|
||||||
* 尤其当您禁用浏览器缓存时(这种情况只应该发生在调试阶段)必须将对应模块加入到 include里,否则会遇到开发环境切换页面卡顿的问题(vite 会认为它是一个新的依赖包会重新加载并强制刷新页面),因为它既无法使用浏览器缓存,又没有在本地 node_modules/.vite 里缓存
|
|
||||||
* 温馨提示:如果您使用的第三方库是全局引入,也就是引入到 src/main.ts 文件里,就不需要再添加到 include 里了,因为 vite 会自动将它们缓存到 node_modules/.vite
|
|
||||||
*/
|
|
||||||
const include = ['vue', 'vue-router', 'dayjs', 'axios', 'pinia', 'vue-types', 'js-cookie'];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 在预构建中强制排除的依赖项
|
|
||||||
*/
|
|
||||||
const exclude: string[] = [];
|
|
||||||
|
|
||||||
export { exclude, include };
|
|
|
@ -1,45 +0,0 @@
|
||||||
import UnoCssIcons from '@unocss/preset-icons';
|
|
||||||
import vue from '@vitejs/plugin-vue';
|
|
||||||
import vueJsx from '@vitejs/plugin-vue-jsx';
|
|
||||||
import { presetIcons } from 'unocss';
|
|
||||||
import UnoCSS from 'unocss/vite';
|
|
||||||
import type { PluginOption } from 'vite';
|
|
||||||
import removeConsole from 'vite-plugin-remove-console';
|
|
||||||
import Inspector from 'vite-plugin-vue-inspector';
|
|
||||||
|
|
||||||
import { useCDN } from './cdn';
|
|
||||||
import { viteConsoleLog } from './info';
|
|
||||||
import { compressPack, report } from './utils';
|
|
||||||
|
|
||||||
export const plugins = (mode: string): PluginOption[] => {
|
|
||||||
return [
|
|
||||||
vue(),
|
|
||||||
vueJsx(),
|
|
||||||
Inspector(),
|
|
||||||
report(),
|
|
||||||
removeConsole(),
|
|
||||||
useCDN(mode),
|
|
||||||
viteConsoleLog(mode),
|
|
||||||
UnoCSS({
|
|
||||||
hmrTopLevelAwait: false,
|
|
||||||
inspector: true, // 控制台是否打印 UnoCSS inspector
|
|
||||||
presets: [
|
|
||||||
presetIcons({
|
|
||||||
prefix: '',
|
|
||||||
extraProperties: {
|
|
||||||
display: 'inline-block',
|
|
||||||
'vertical-align': 'middle',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
UnoCssIcons({
|
|
||||||
prefix: '',
|
|
||||||
extraProperties: {
|
|
||||||
display: 'inline-block',
|
|
||||||
'vertical-align': 'middle',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
compressPack(mode),
|
|
||||||
];
|
|
||||||
};
|
|
|
@ -1,9 +0,0 @@
|
||||||
import { pathResolve } from './utils';
|
|
||||||
|
|
||||||
export const resolve = () => {
|
|
||||||
return {
|
|
||||||
alias: {
|
|
||||||
'@': pathResolve('../src'),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,34 +0,0 @@
|
||||||
import type { ServerOptions } from 'vite';
|
|
||||||
|
|
||||||
import { wrapperEnv } from './utils';
|
|
||||||
|
|
||||||
/* 开发服务配置 */
|
|
||||||
export const server = (mode: string) => {
|
|
||||||
const { VITE_PORT, VITE_APP_URL, VITE_STRICT_PORT } = wrapperEnv(mode);
|
|
||||||
|
|
||||||
const options: ServerOptions = {
|
|
||||||
strictPort: VITE_STRICT_PORT,
|
|
||||||
port: VITE_PORT,
|
|
||||||
host: '0.0.0.0',
|
|
||||||
open: true,
|
|
||||||
cors: true,
|
|
||||||
proxy: {
|
|
||||||
'/api': {
|
|
||||||
target: VITE_APP_URL,
|
|
||||||
changeOrigin: true,
|
|
||||||
rewrite: (path: string) => path.replace(/^\/api/, '/api'),
|
|
||||||
},
|
|
||||||
'/mock': {
|
|
||||||
target: VITE_APP_URL,
|
|
||||||
changeOrigin: true,
|
|
||||||
rewrite: (path: string) => path.replace(/^\/mock/, '/mock'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// 预热文件以提前转换和缓存结果,降低启动期间的初始页面加载时长并防止转换瀑布
|
|
||||||
warmup: {
|
|
||||||
clientFiles: ['./index.html', './src/{views,components}/*'],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return options;
|
|
||||||
};
|
|
|
@ -1,118 +0,0 @@
|
||||||
import { dirname, resolve } from 'node:path';
|
|
||||||
import { fileURLToPath } from 'node:url';
|
|
||||||
|
|
||||||
import fs from 'fs';
|
|
||||||
import path from 'path';
|
|
||||||
import { visualizer } from 'rollup-plugin-visualizer';
|
|
||||||
import { loadEnv } from 'vite';
|
|
||||||
import viteCompression from 'vite-plugin-compression';
|
|
||||||
|
|
||||||
import { buildEnv } from './buildEnv';
|
|
||||||
|
|
||||||
export const root: string = process.cwd();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 根据可选的路径片段生成一个新的绝对路径
|
|
||||||
* @param dir 路径片段,默认`build`
|
|
||||||
* @param metaUrl 模块的完整`url`,如果在`build`目录外调用必传`import.meta.url`
|
|
||||||
*/
|
|
||||||
// @ts-ignore
|
|
||||||
export const pathResolve = (dir = '.', metaUrl = import.meta.url) => {
|
|
||||||
// 当前文件目录的绝对路径
|
|
||||||
const currentFileDir = dirname(fileURLToPath(metaUrl));
|
|
||||||
// build 目录的绝对路径
|
|
||||||
const buildDir = resolve(currentFileDir, 'build');
|
|
||||||
// 解析的绝对路径
|
|
||||||
const resolvedPath = resolve(currentFileDir, dir);
|
|
||||||
// 检查解析的绝对路径是否在 build 目录内
|
|
||||||
if (resolvedPath.startsWith(buildDir)) {
|
|
||||||
// 在 build 目录内,返回当前文件路径
|
|
||||||
return fileURLToPath(metaUrl);
|
|
||||||
}
|
|
||||||
// 不在 build 目录内,返回解析后的绝对路径
|
|
||||||
return resolvedPath;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 封装环境变量配置
|
|
||||||
* @param mode 当前模式
|
|
||||||
* @param prefix 需要过滤的前缀
|
|
||||||
* @link 参考:https://cn.vite.dev/config/#using-environment-variables-in-config
|
|
||||||
*/
|
|
||||||
// @ts-ignore
|
|
||||||
export const wrapperEnv = (mode: string, prefix: string = ''): ViteEnv => {
|
|
||||||
const env: any = loadEnv(mode, root, prefix);
|
|
||||||
|
|
||||||
// 将变量转换指定类型
|
|
||||||
for (const envName of Object.keys(env)) {
|
|
||||||
let realName: string | boolean | number = env[envName].replace(/\\n/g, '\n');
|
|
||||||
realName = realName === 'true' ? true : realName === 'false' ? false : realName;
|
|
||||||
|
|
||||||
if (envName === 'VITE_PORT') {
|
|
||||||
realName = Number(realName);
|
|
||||||
}
|
|
||||||
env[envName] = realName;
|
|
||||||
// @ts-ignore
|
|
||||||
process.env[envName] = realName;
|
|
||||||
}
|
|
||||||
return env;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* 打包分析 */
|
|
||||||
export const report = () => {
|
|
||||||
const lifecycle = process.env.npm_lifecycle_event;
|
|
||||||
return lifecycle === 'report'
|
|
||||||
? visualizer({ open: true, brotliSize: true, filename: 'report.html' })
|
|
||||||
: (null as any);
|
|
||||||
};
|
|
||||||
|
|
||||||
/* 启用gzip压缩 */
|
|
||||||
export const compressPack = (mode: string) => {
|
|
||||||
const { VITE_COMPRESSION } = wrapperEnv(mode);
|
|
||||||
|
|
||||||
return VITE_COMPRESSION == 'gzip' ? viteCompression({ threshold: 1024000 }) : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算打包后文件夹大小
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const logOutputSize = (): string => {
|
|
||||||
const outDir = `../${buildEnv().outDir}`;
|
|
||||||
|
|
||||||
function convertSize(size: number) {
|
|
||||||
const units: Array<string> = ['byte', 'KB', 'MB', 'GB'];
|
|
||||||
|
|
||||||
// 输入的单位是否存在
|
|
||||||
let index = 0;
|
|
||||||
|
|
||||||
while (size >= 1024) {
|
|
||||||
size /= 1024;
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${size.toFixed(2)} ${units[index]}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算文件夹字节大小
|
|
||||||
function getFolderSize(folderPath: string) {
|
|
||||||
let size = 0;
|
|
||||||
|
|
||||||
fs.readdirSync(folderPath).forEach((fileName: string) => {
|
|
||||||
const filePath = path.join(folderPath, fileName);
|
|
||||||
const stats = fs.statSync(filePath);
|
|
||||||
|
|
||||||
if (stats.isFile()) {
|
|
||||||
size += stats.size;
|
|
||||||
} else if (stats.isDirectory()) {
|
|
||||||
size += getFolderSize(filePath);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
const folderSize = getFolderSize(path.resolve(__dirname, outDir));
|
|
||||||
|
|
||||||
return convertSize(folderSize);
|
|
||||||
};
|
|
|
@ -1,31 +0,0 @@
|
||||||
# 使用官方的 Nginx 镜像作为基础镜像
|
|
||||||
FROM nginx:1.27.3
|
|
||||||
|
|
||||||
# 删除默认的 Nginx 配置文件
|
|
||||||
RUN rm /etc/nginx/conf.d/default.conf
|
|
||||||
|
|
||||||
# 将自定义的 Nginx 配置文件复制到容器中
|
|
||||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
|
||||||
#COPY bunny-web.site.csr /etc/nginx/bunny-web.site.csr
|
|
||||||
#COPY bunny-web.site.key /etc/nginx/bunny-web.site.key
|
|
||||||
#COPY bunny-web.site_bundle.crt /etc/nginx/bunny-web.site_bundle.crt
|
|
||||||
#COPY bunny-web.site_bundle.pem /etc/nginx/bunny-web.site_bundle.pem
|
|
||||||
|
|
||||||
# 设置时区,构建镜像时执行的命令
|
|
||||||
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
|
|
||||||
RUN echo "Asia/Shanghai" > /etc/timezone
|
|
||||||
|
|
||||||
# 创建一个目录来存放前端项目文件
|
|
||||||
WORKDIR /usr/share/nginx/html
|
|
||||||
|
|
||||||
# 将前端项目打包文件复制到 Nginx 的默认静态文件目录
|
|
||||||
COPY dist/ /usr/share/nginx/html
|
|
||||||
# 复制到nginx目录下
|
|
||||||
COPY dist/ /etc/nginx/html
|
|
||||||
|
|
||||||
# 暴露 Nginx 的默认端口
|
|
||||||
EXPOSE 80
|
|
||||||
#EXPOSE 443
|
|
||||||
|
|
||||||
# 自动启动 Nginx
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
|
|
@ -1,32 +0,0 @@
|
||||||
map $http_upgrade $connection_upgrade {
|
|
||||||
default upgrade;
|
|
||||||
'' close;
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 80 ;
|
|
||||||
listen [::]:80;
|
|
||||||
server_name localhost;
|
|
||||||
client_max_body_size 5M; # 最大文件上传设置
|
|
||||||
|
|
||||||
location / {
|
|
||||||
root /etc/nginx/html;
|
|
||||||
index index.html index.htm;
|
|
||||||
try_files $uri /index.html;
|
|
||||||
}
|
|
||||||
|
|
||||||
# 后端跨域请求
|
|
||||||
location ~/api/ {
|
|
||||||
proxy_pass http://172.17.0.1:8000;
|
|
||||||
proxy_set_header Host $http_host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
}
|
|
||||||
|
|
||||||
error_page 404 404.html;
|
|
||||||
|
|
||||||
location = /50x.html {
|
|
||||||
root html;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,177 +0,0 @@
|
||||||
import js from '@eslint/js';
|
|
||||||
import pluginTypeScript from '@typescript-eslint/eslint-plugin';
|
|
||||||
import * as parserTypeScript from '@typescript-eslint/parser';
|
|
||||||
import configPrettier from 'eslint-config-prettier';
|
|
||||||
import { defineFlatConfig } from 'eslint-define-config';
|
|
||||||
import pluginPrettier from 'eslint-plugin-prettier';
|
|
||||||
import eslintPluginSimpleImportSort from 'eslint-plugin-simple-import-sort';
|
|
||||||
import pluginVue from 'eslint-plugin-vue';
|
|
||||||
import * as parserVue from 'vue-eslint-parser';
|
|
||||||
|
|
||||||
export default defineFlatConfig([
|
|
||||||
{
|
|
||||||
...js.configs.recommended,
|
|
||||||
ignores: ['**/.*', 'dist/*', '*.d.ts', 'public/*', 'src/assets/**', 'src/**/iconfont/**'],
|
|
||||||
languageOptions: {
|
|
||||||
globals: {
|
|
||||||
// index.d.ts
|
|
||||||
RefType: 'readonly',
|
|
||||||
EmitType: 'readonly',
|
|
||||||
TargetContext: 'readonly',
|
|
||||||
ComponentRef: 'readonly',
|
|
||||||
ElRef: 'readonly',
|
|
||||||
ForDataType: 'readonly',
|
|
||||||
AnyFunction: 'readonly',
|
|
||||||
PropType: 'readonly',
|
|
||||||
Writable: 'readonly',
|
|
||||||
Nullable: 'readonly',
|
|
||||||
NonNullable: 'readonly',
|
|
||||||
Recordable: 'readonly',
|
|
||||||
ReadonlyRecordable: 'readonly',
|
|
||||||
Indexable: 'readonly',
|
|
||||||
DeepPartial: 'readonly',
|
|
||||||
Without: 'readonly',
|
|
||||||
Exclusive: 'readonly',
|
|
||||||
TimeoutHandle: 'readonly',
|
|
||||||
IntervalHandle: 'readonly',
|
|
||||||
Effect: 'readonly',
|
|
||||||
ChangeEvent: 'readonly',
|
|
||||||
WheelEvent: 'readonly',
|
|
||||||
ImportMetaEnv: 'readonly',
|
|
||||||
Fn: 'readonly',
|
|
||||||
PromiseFn: 'readonly',
|
|
||||||
ComponentElRef: 'readonly',
|
|
||||||
parseInt: 'readonly',
|
|
||||||
parseFloat: 'readonly',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
prettier: pluginPrettier,
|
|
||||||
'simple-import-sort': eslintPluginSimpleImportSort,
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
...configPrettier.rules,
|
|
||||||
...pluginPrettier.configs.recommended.rules,
|
|
||||||
'simple-import-sort/imports': 'error',
|
|
||||||
'no-debugger': 'off',
|
|
||||||
'no-unused-vars': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
argsIgnorePattern: '^_',
|
|
||||||
varsIgnorePattern: '^_',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'prettier/prettier': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
endOfLine: 'auto',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ['**/*.?([cm])ts', '**/*.?([cm])tsx'],
|
|
||||||
languageOptions: {
|
|
||||||
parser: parserTypeScript,
|
|
||||||
parserOptions: {
|
|
||||||
sourceType: 'module',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
'@typescript-eslint': pluginTypeScript,
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
...pluginTypeScript.configs.strict.rules,
|
|
||||||
'@typescript-eslint/ban-types': 'off',
|
|
||||||
'@typescript-eslint/no-redeclare': 'error',
|
|
||||||
'@typescript-eslint/ban-ts-comment': 'off',
|
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
|
||||||
'@typescript-eslint/prefer-as-const': 'warn',
|
|
||||||
'@typescript-eslint/no-empty-function': 'off',
|
|
||||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
|
||||||
'@typescript-eslint/no-import-type-side-effects': 'error',
|
|
||||||
'@typescript-eslint/prefer-literal-enum-member': 'off',
|
|
||||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
||||||
'@typescript-eslint/consistent-type-imports': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
disallowTypeAnnotations: false,
|
|
||||||
fixStyle: 'inline-type-imports',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'@typescript-eslint/no-unused-vars': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
argsIgnorePattern: '^_',
|
|
||||||
varsIgnorePattern: '^_',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ['**/*.d.ts'],
|
|
||||||
rules: {
|
|
||||||
'eslint-comments/no-unlimited-disable': 'off',
|
|
||||||
'import/no-duplicates': 'off',
|
|
||||||
'unused-imports/no-unused-vars': 'off',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ['**/*.?([cm])js'],
|
|
||||||
rules: {
|
|
||||||
'@typescript-eslint/no-require-imports': 'off',
|
|
||||||
'@typescript-eslint/no-var-requires': 'off',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ['**/*.vue'],
|
|
||||||
languageOptions: {
|
|
||||||
globals: {
|
|
||||||
$: 'readonly',
|
|
||||||
$$: 'readonly',
|
|
||||||
$computed: 'readonly',
|
|
||||||
$customRef: 'readonly',
|
|
||||||
$ref: 'readonly',
|
|
||||||
$shallowRef: 'readonly',
|
|
||||||
$toRef: 'readonly',
|
|
||||||
},
|
|
||||||
parser: parserVue,
|
|
||||||
parserOptions: {
|
|
||||||
ecmaFeatures: {
|
|
||||||
jsx: true,
|
|
||||||
},
|
|
||||||
extraFileExtensions: ['.vue'],
|
|
||||||
parser: '@typescript-eslint/parser',
|
|
||||||
sourceType: 'module',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
vue: pluginVue,
|
|
||||||
},
|
|
||||||
processor: pluginVue.processors['.vue'],
|
|
||||||
rules: {
|
|
||||||
...pluginVue.configs.base.rules,
|
|
||||||
...pluginVue.configs['vue3-essential'].rules,
|
|
||||||
...pluginVue.configs['vue3-recommended'].rules,
|
|
||||||
'no-undef': 'off',
|
|
||||||
'no-unused-vars': 'off',
|
|
||||||
'vue/no-v-html': 'off',
|
|
||||||
'vue/require-default-prop': 'off',
|
|
||||||
'vue/require-explicit-emits': 'off',
|
|
||||||
'vue/multi-word-component-names': 'off',
|
|
||||||
'vue/no-setup-props-reactivity-loss': 'off',
|
|
||||||
'vue/html-self-closing': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
html: {
|
|
||||||
void: 'always',
|
|
||||||
normal: 'always',
|
|
||||||
component: 'always',
|
|
||||||
},
|
|
||||||
svg: 'always',
|
|
||||||
math: 'always',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
|
@ -1,13 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8"/>
|
|
||||||
<link href="/favicon.ico" rel="icon" type="image/svg+xml"/>
|
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
|
||||||
<title>%VITE_APP_TITLE%</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="app"></div>
|
|
||||||
<script src="/src/main.ts" type="module"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,93 +0,0 @@
|
||||||
{
|
|
||||||
"name": "generator-code-web",
|
|
||||||
"private": true,
|
|
||||||
"version": "1.0.0",
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "vite",
|
|
||||||
"build": "vite build",
|
|
||||||
"preview": "vite preview",
|
|
||||||
"report": "rimraf dist && vite build",
|
|
||||||
"lint:stylelint": "stylelint --cache --fix \"**/*.{html,vue,css,scss}\" --cache-location node_modules/.cache/stylelint/",
|
|
||||||
"lint": "pnpm lint:eslint && pnpm lint:prettier && pnpm lint:stylelint"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@eslint/js": "^9.21.0",
|
|
||||||
"@highlightjs/vue-plugin": "^2.1.0",
|
|
||||||
"@types/node": "^22.13.10",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^8.24.1",
|
|
||||||
"@typescript-eslint/parser": "^8.24.1",
|
|
||||||
"@unocss/preset-icons": "^66.0.0",
|
|
||||||
"@unocss/reset": "^66.0.0",
|
|
||||||
"@vicons/ionicons5": "^0.13.0",
|
|
||||||
"@vitejs/plugin-vue-jsx": "^4.1.1",
|
|
||||||
"animate.css": "^4.1.1",
|
|
||||||
"axios": "^1.7.9",
|
|
||||||
"boxen": "^8.0.1",
|
|
||||||
"dayjs": "^1.11.13",
|
|
||||||
"esbuild": "^0.25.1",
|
|
||||||
"eslint": "^9.9.1",
|
|
||||||
"eslint-config-prettier": "^9.1.0",
|
|
||||||
"eslint-define-config": "^2.1.0",
|
|
||||||
"eslint-plugin-prettier": "^5.2.1",
|
|
||||||
"eslint-plugin-vue": "^9.27.0",
|
|
||||||
"gradient-string": "^3.0.0",
|
|
||||||
"highlight.js": "^11.11.1",
|
|
||||||
"naive-ui": "^2.41.0",
|
|
||||||
"nprogress": "^0.2.0",
|
|
||||||
"pinia": "^2.3.1",
|
|
||||||
"pinia-plugin-persistedstate": "^3.2.3",
|
|
||||||
"postcss": "^8.5.3",
|
|
||||||
"prettier": "^3.3.3",
|
|
||||||
"qs": "^6.14.0",
|
|
||||||
"rimraf": "^5.0.10",
|
|
||||||
"rollup-plugin-visualizer": "^5.14.0",
|
|
||||||
"sass": "^1.77.8",
|
|
||||||
"stylelint": "^16.14.1",
|
|
||||||
"stylelint-config-recess-order": "^6.0.0",
|
|
||||||
"stylelint-config-recommended-vue": "^1.6.0",
|
|
||||||
"stylelint-config-standard-scss": "^14.0.0",
|
|
||||||
"stylelint-prettier": "^5.0.3",
|
|
||||||
"terser": "^5.39.0",
|
|
||||||
"unocss": "^66.0.0",
|
|
||||||
"vite-plugin-cdn-import": "^1.0.1",
|
|
||||||
"vite-plugin-remove-console": "^2.2.0",
|
|
||||||
"vite-plugin-vue-inspector": "^5.3.1",
|
|
||||||
"vue": "^3.5.13",
|
|
||||||
"vue-demi": "^0.14.10",
|
|
||||||
"vue-eslint-parser": "^9.4.3",
|
|
||||||
"vue-router": "^4.4.3",
|
|
||||||
"vue-types": "^6.0.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@iconify/json": "^2.2.310",
|
|
||||||
"@types/nprogress": "^0.2.3",
|
|
||||||
"@types/qs": "^6.9.18",
|
|
||||||
"@vitejs/plugin-vue": "^5.2.1",
|
|
||||||
"@vue/tsconfig": "^0.7.0",
|
|
||||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
||||||
"typescript": "~5.7.2",
|
|
||||||
"vite": "^6.1.0",
|
|
||||||
"vite-plugin-compression": "^0.5.1",
|
|
||||||
"vue-tsc": "^2.2.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0",
|
|
||||||
"pnpm": ">=8.6.10"
|
|
||||||
},
|
|
||||||
"pnpm": {
|
|
||||||
"allowedDeprecatedVersions": {
|
|
||||||
"sourcemap-codec": "*",
|
|
||||||
"domexception": "*",
|
|
||||||
"w3c-hr-time": "*",
|
|
||||||
"stable": "*",
|
|
||||||
"abab": "*"
|
|
||||||
},
|
|
||||||
"peerDependencyRules": {
|
|
||||||
"allowedVersions": {
|
|
||||||
"eslint": "9"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packageManager": "pnpm@10.8.1+sha512.c50088ba998c67b8ca8c99df8a5e02fd2ae2e2b29aaf238feaa9e124248d3f48f9fb6db2424949ff901cffbb5e0f0cc1ad6aedb602cd29450751d11c35023677"
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Before Width: | Height: | Size: 66 KiB |
|
@ -1,19 +0,0 @@
|
||||||
<template>
|
|
||||||
<n-config-provider>
|
|
||||||
<n-message-provider>
|
|
||||||
<content />
|
|
||||||
<n-dialog-provider>
|
|
||||||
<router-view v-slot="{ Component, route }">
|
|
||||||
<transition :name="route.meta.transition || 'fade-transform'" mode="out-in">
|
|
||||||
<component :is="Component" :key="route.path" />
|
|
||||||
</transition>
|
|
||||||
</router-view>
|
|
||||||
</n-dialog-provider>
|
|
||||||
</n-message-provider>
|
|
||||||
</n-config-provider>
|
|
||||||
</template>
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { NConfigProvider, NDialogProvider, NMessageProvider } from 'naive-ui';
|
|
||||||
|
|
||||||
import Content from '@/views/content.vue';
|
|
||||||
</script>
|
|
|
@ -1,64 +0,0 @@
|
||||||
import axios, { type AxiosResponse, type InternalAxiosRequestConfig } from 'axios';
|
|
||||||
import qs from 'qs';
|
|
||||||
|
|
||||||
import { TOKEN_KEY } from '@/enums/CacheEnum';
|
|
||||||
|
|
||||||
// 创建 axios 实例
|
|
||||||
const service = axios.create({
|
|
||||||
baseURL: import.meta.env.VITE_APP_BASE_API,
|
|
||||||
timeout: 50000,
|
|
||||||
headers: { 'Content-Type': 'application/json;charset=utf-8' },
|
|
||||||
paramsSerializer: (params) => {
|
|
||||||
return qs.stringify(params);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// 请求拦截器
|
|
||||||
service.interceptors.request.use(
|
|
||||||
(config: InternalAxiosRequestConfig) => {
|
|
||||||
const accessToken = localStorage.getItem(TOKEN_KEY);
|
|
||||||
if (accessToken) {
|
|
||||||
config.headers.Authorization = accessToken;
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// 响应拦截器
|
|
||||||
service.interceptors.response.use(
|
|
||||||
(response: AxiosResponse) => {
|
|
||||||
// 检查配置的响应类型是否为二进制类型('blob' 或 'arraybuffer'), 如果是,直接返回响应对象
|
|
||||||
if (response.config.responseType === 'blob' || response.config.responseType === 'arraybuffer') {
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.status === 200) {
|
|
||||||
const { code, message } = response.data;
|
|
||||||
if (code !== 200) {
|
|
||||||
(window as any).$message.error(message);
|
|
||||||
}
|
|
||||||
return response.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 系统出错
|
|
||||||
return Promise.reject(response.data.message || 'Error');
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
// 异常处理
|
|
||||||
if (error.response.data) {
|
|
||||||
const { code, message } = error.response.data;
|
|
||||||
if (code === 500) {
|
|
||||||
(window as any).$message.error(message);
|
|
||||||
} else {
|
|
||||||
(window as any).$message.error(message || '系统出错');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Promise.reject(error.message);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// 导出 axios 实例
|
|
||||||
export default service;
|
|
|
@ -1,68 +0,0 @@
|
||||||
import axios, { type AxiosResponse, type InternalAxiosRequestConfig } from 'axios';
|
|
||||||
import qs from 'qs';
|
|
||||||
|
|
||||||
import { TOKEN_KEY } from '@/enums/CacheEnum';
|
|
||||||
|
|
||||||
// 创建 axios 实例
|
|
||||||
const service = axios.create({
|
|
||||||
baseURL: import.meta.env.VITE_MOCK_BASE_API,
|
|
||||||
timeout: 50000,
|
|
||||||
headers: { 'Content-Type': 'application/json;charset=utf-8' },
|
|
||||||
paramsSerializer: (params) => {
|
|
||||||
return qs.stringify(params);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// 请求拦截器
|
|
||||||
service.interceptors.request.use(
|
|
||||||
(config: InternalAxiosRequestConfig) => {
|
|
||||||
const accessToken = localStorage.getItem(TOKEN_KEY);
|
|
||||||
if (accessToken) {
|
|
||||||
config.headers.Authorization = accessToken;
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// 响应拦截器
|
|
||||||
service.interceptors.response.use(
|
|
||||||
(response: AxiosResponse) => {
|
|
||||||
// 检查配置的响应类型是否为二进制类型('blob' 或 'arraybuffer'), 如果是,直接返回响应对象
|
|
||||||
if (response.config.responseType === 'blob' || response.config.responseType === 'arraybuffer') {
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
// const { code, data, msg } = response.data;
|
|
||||||
// if (code === ResultEnum.SUCCESS) {
|
|
||||||
// return data;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (response.status === 200) {
|
|
||||||
return response.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ElMessage.error(msg || '系统出错');
|
|
||||||
return Promise.reject(response.data.message || 'Error');
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
// 异常处理
|
|
||||||
if (error.response.data) {
|
|
||||||
// const { code, msg } = error.response.data;
|
|
||||||
// if (code === ResultEnum.TOKEN_INVALID) {
|
|
||||||
// ElNotification({
|
|
||||||
// title: '提示',
|
|
||||||
// message: '您的会话已过期,请重新登录',
|
|
||||||
// type: 'info',
|
|
||||||
// });
|
|
||||||
// } else {
|
|
||||||
// ElMessage.error(msg || '系统出错');
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
return Promise.reject(error.message);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// 导出 axios 实例
|
|
||||||
export default service;
|
|
|
@ -1,20 +0,0 @@
|
||||||
import request from '@/api/server/request';
|
|
||||||
import type { BaseResult } from '@/types/request';
|
|
||||||
|
|
||||||
/* 当前数据库信息 */
|
|
||||||
export const fetchTableInfo = (params: any) => {
|
|
||||||
return request<any, BaseResult<any>>({
|
|
||||||
url: '/sqlParser/tableInfo',
|
|
||||||
method: 'POST',
|
|
||||||
params,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/* 当前数据库信息 */
|
|
||||||
export const fetchColumnMetaData = (params: any) => {
|
|
||||||
return request<any, BaseResult<any>>({
|
|
||||||
url: '/sqlParser/columnMetaData',
|
|
||||||
method: 'POST',
|
|
||||||
params,
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,22 +0,0 @@
|
||||||
import request from '@/api/server/request';
|
|
||||||
import type { BaseResult } from '@/types/request';
|
|
||||||
|
|
||||||
/* 当前数据库信息 */
|
|
||||||
export const getDatabaseInfoMetaData = () => {
|
|
||||||
return request<any, BaseResult<any>>({ url: '/table/databaseInfoMetaData', method: 'GET' });
|
|
||||||
};
|
|
||||||
|
|
||||||
/* 数据库所有的表 */
|
|
||||||
export const getDatabaseTableList = (params: any) => {
|
|
||||||
return request<any, BaseResult<any>>({ url: '/table/databaseTableList', method: 'get', params });
|
|
||||||
};
|
|
||||||
|
|
||||||
/* 获取表属性 */
|
|
||||||
export const getTableMetaData = (params: object) => {
|
|
||||||
return request<any, BaseResult<any>>({ url: '/table/tableMetaData', method: 'get', params });
|
|
||||||
};
|
|
||||||
|
|
||||||
/* 获取列属性 */
|
|
||||||
export const getTableColumnInfo = (params: object) => {
|
|
||||||
return request<any, BaseResult<any>>({ url: '/table/tableColumnInfo', method: 'get', params });
|
|
||||||
};
|
|
|
@ -1,22 +0,0 @@
|
||||||
import request from '@/api/server/request';
|
|
||||||
import type { BaseResult } from '@/types/request'; /* 获取所有数据表 */
|
|
||||||
|
|
||||||
/* 获取所有数据表 */
|
|
||||||
export const generator = (data: any) => {
|
|
||||||
return request<any, BaseResult<any>>({ url: '/vms/generator', method: 'post', data });
|
|
||||||
};
|
|
||||||
|
|
||||||
/* 获取vms文件路径 */
|
|
||||||
export const getVmsResourcePathList = () => {
|
|
||||||
return request<any, BaseResult<any>>({ url: '/vms/vmsResourcePathList', method: 'get' });
|
|
||||||
};
|
|
||||||
|
|
||||||
/* 打包成zip下载 */
|
|
||||||
export const downloadByZip = (data: any) => {
|
|
||||||
return request<any, any>({
|
|
||||||
url: '/vms/downloadByZip',
|
|
||||||
method: 'POST',
|
|
||||||
data,
|
|
||||||
responseType: 'blob',
|
|
||||||
});
|
|
||||||
};
|
|
Binary file not shown.
Before Width: | Height: | Size: 20 KiB |
Binary file not shown.
Before Width: | Height: | Size: 2.6 KiB |
|
@ -1,2 +0,0 @@
|
||||||
@use "src/rotate";
|
|
||||||
@use "src/transition";
|
|
|
@ -1,19 +0,0 @@
|
||||||
/* 旋转动画 */
|
|
||||||
@keyframes rotate {
|
|
||||||
from {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 反向旋转动画 */
|
|
||||||
@keyframes rotate-reverse {
|
|
||||||
from {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(-360deg);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
/* fade */
|
|
||||||
.fade-enter-active,
|
|
||||||
.fade-leave-active {
|
|
||||||
transition: opacity 0.28s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-enter,
|
|
||||||
.fade-leave-active {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* fade-transform */
|
|
||||||
.fade-transform-leave-active,
|
|
||||||
.fade-transform-enter-active {
|
|
||||||
transition: all 0.38s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-transform-enter-from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(-30px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-transform-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(30px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* breadcrumb transition */
|
|
||||||
.breadcrumb-enter-active {
|
|
||||||
transition: all 0.4s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.breadcrumb-leave-active {
|
|
||||||
position: absolute;
|
|
||||||
transition: all 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.breadcrumb-enter-from,
|
|
||||||
.breadcrumb-leave-active {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(20px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 重置el-menu的展开收起动画时长
|
|
||||||
*/
|
|
||||||
.outer-most .el-collapse-transition-leave-active,
|
|
||||||
.outer-most .el-collapse-transition-enter-active {
|
|
||||||
transition: 0.2s all ease-in-out !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.horizontal-collapse-transition {
|
|
||||||
transition: var(--pure-transition-duration) all !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-enter-active,
|
|
||||||
.slide-leave-active {
|
|
||||||
transition: opacity 0.3s,
|
|
||||||
transform 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-enter-from,
|
|
||||||
.slide-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(-30%);
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
@use "src/element";
|
|
||||||
|
|
||||||
/* 定义滚动条高宽及背景高宽分别对应横竖滚动条的尺寸 */
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
background-color: var(--el-text-color-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 定义滚动条轨道内阴影+圆角 */
|
|
||||||
::-webkit-scrollbar-track {
|
|
||||||
background-color: #ebecef;
|
|
||||||
border-radius: 5px;
|
|
||||||
box-shadow: inset 0 0 6px #ebecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 定义滑块内阴影+圆角 */
|
|
||||||
::-webkit-scrollbar-thumb {
|
|
||||||
background-color: #d0d2d6;
|
|
||||||
border-radius: 5px;
|
|
||||||
box-shadow: inset 0 0 6px #d0d2d6;
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
// 空心的虚线圆
|
|
||||||
.dashed-circle {
|
|
||||||
width: 46px; /* 圆圈的宽度 */
|
|
||||||
height: 46px; /* 圆圈的高度 */
|
|
||||||
line-height: 40px;
|
|
||||||
font-size: 16px;
|
|
||||||
text-align: center;
|
|
||||||
border: 2px dashed; /* 边框宽度、样式和颜色 */
|
|
||||||
border-radius: 50%; /* 将元素变成圆形 */
|
|
||||||
}
|
|
||||||
|
|
||||||
.hover {
|
|
||||||
transition: all 0.3s;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--color-hover);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
@use "animations/animations";
|
|
||||||
@use "common/common";
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--colot-primary: #027AFF;
|
|
||||||
--color-primary-secondary: #00FFFF;
|
|
||||||
--color-info: #7CC1FF;
|
|
||||||
--color-hover: #1C8ADF;
|
|
||||||
--color-warning: #FFBE44;
|
|
||||||
--color-warning-secondary: #FEDB65;
|
|
||||||
--background-color: #051135;
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
@mixin view-style-default($sidebar-width,$content-width) {
|
|
||||||
&__sidebar {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
width: $sidebar-width;
|
|
||||||
height: 100%;
|
|
||||||
color: #fff;
|
|
||||||
|
|
||||||
&-item {
|
|
||||||
padding: 9px 15px;
|
|
||||||
width: 100%;
|
|
||||||
background: rgba(14, 95, 255, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
&-tag {
|
|
||||||
float: left;
|
|
||||||
margin: 0 7px 0 0;
|
|
||||||
width: 62px;
|
|
||||||
height: 26px;
|
|
||||||
line-height: 26px;
|
|
||||||
font-size: 14px;
|
|
||||||
text-align: center;
|
|
||||||
background: rgba(24, 69, 135, 0.55);
|
|
||||||
color: #fff;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-title {
|
|
||||||
width: 172px;
|
|
||||||
height: 42px;
|
|
||||||
font-size: 22px;
|
|
||||||
color: #fff;
|
|
||||||
background: url('@/assets/images/business-supervision/bg/sidebar/bg-frame-4.png') no-repeat;
|
|
||||||
background-size: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-title-describe {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--color-info-secondary-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__content {
|
|
||||||
width: $content-width;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
<script lang="ts" setup>
|
|
||||||
import { NButton, NButtonGroup } from 'naive-ui';
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
// 数据源(必须包含唯一标识字段,默认是 `id`)
|
|
||||||
data: {
|
|
||||||
type: Array,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
// 当前选中的数据
|
|
||||||
selected: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
// 唯一标识字段名(默认 `id`)
|
|
||||||
idKey: {
|
|
||||||
type: String,
|
|
||||||
default: 'name',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const emit = defineEmits(['update:selected']);
|
|
||||||
|
|
||||||
/* 全选 */
|
|
||||||
const selectAll = () => {
|
|
||||||
const allNames = props.data.map((item) => item[props.idKey]);
|
|
||||||
emit('update:selected', [...allNames]);
|
|
||||||
};
|
|
||||||
|
|
||||||
/* 反选 */
|
|
||||||
const selectInvert = () => {
|
|
||||||
const currentSelected = props.selected;
|
|
||||||
const allNames = props.data.map((item) => item[props.idKey]);
|
|
||||||
const inverted = allNames.filter((name) => !currentSelected.includes(name));
|
|
||||||
emit('update:selected', inverted);
|
|
||||||
};
|
|
||||||
|
|
||||||
/* 全不选 */
|
|
||||||
const selectCancel = () => {
|
|
||||||
emit('update:selected', []);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<n-button-group size="small">
|
|
||||||
<n-button round type="primary" @click="selectAll">全选</n-button>
|
|
||||||
<n-button type="warning" @click="selectInvert">反选</n-button>
|
|
||||||
<n-button round type="error" @click="selectCancel">全不选</n-button>
|
|
||||||
</n-button-group>
|
|
||||||
</template>
|
|
|
@ -1,4 +0,0 @@
|
||||||
/**
|
|
||||||
* 令牌缓存Key
|
|
||||||
*/
|
|
||||||
export const TOKEN_KEY = 'accessToken';
|
|
|
@ -1,9 +0,0 @@
|
||||||
/** 响应码枚举 */
|
|
||||||
export const enum ResultEnum {
|
|
||||||
// 成功
|
|
||||||
SUCCESS = '200',
|
|
||||||
// 错误
|
|
||||||
ERROR = 'B0001',
|
|
||||||
// 令牌无效或过期
|
|
||||||
TOKEN_INVALID = '209',
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
<script lang="ts" setup></script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="container m-auto">
|
|
||||||
<h1 class="mt-4 text-center font-bold font-size-[22px] c-primary">代码生成器</h1>
|
|
||||||
|
|
||||||
<main class="container mx-auto">
|
|
||||||
<router-view />
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer class="my-4 text-center">
|
|
||||||
<p>© 2025 Bunny.保留所有权利.</p>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
|
@ -1,15 +0,0 @@
|
||||||
import 'animate.css';
|
|
||||||
import '@unocss/reset/tailwind-compat.css';
|
|
||||||
import 'uno.css';
|
|
||||||
import 'virtual:unocss-devtools';
|
|
||||||
import '@/assets/styles/global.scss';
|
|
||||||
|
|
||||||
import { createApp } from 'vue';
|
|
||||||
|
|
||||||
import plugins from '@/plugins';
|
|
||||||
|
|
||||||
import App from './App.vue';
|
|
||||||
|
|
||||||
const app = createApp(App);
|
|
||||||
|
|
||||||
app.use(plugins).mount('#app');
|
|
|
@ -1,9 +0,0 @@
|
||||||
import 'highlight.js/styles/atom-one-dark.css';
|
|
||||||
import 'highlight.js/lib/common';
|
|
||||||
|
|
||||||
import highlightJSPlugin from '@highlightjs/vue-plugin';
|
|
||||||
import type { App } from 'vue';
|
|
||||||
|
|
||||||
export const setupHighLight = (app: App<Element>) => {
|
|
||||||
app.use(highlightJSPlugin);
|
|
||||||
};
|
|
|
@ -1,16 +0,0 @@
|
||||||
import type { App } from 'vue';
|
|
||||||
|
|
||||||
import { setupHighLight } from '@/plugins/highLight';
|
|
||||||
import { setupRouter } from '@/router';
|
|
||||||
import { setupStore } from '@/store';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
install(app: App<Element>) {
|
|
||||||
// 设置路由
|
|
||||||
setupRouter(app);
|
|
||||||
// 设置状态管理
|
|
||||||
setupStore(app);
|
|
||||||
// 设置代码高亮
|
|
||||||
setupHighLight(app);
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,26 +0,0 @@
|
||||||
import type { App } from 'vue';
|
|
||||||
import { createRouter, createWebHashHistory, type RouteRecordRaw } from 'vue-router';
|
|
||||||
|
|
||||||
import error from '@/router/modules/error';
|
|
||||||
import home from '@/router/modules/home';
|
|
||||||
import remaining from '@/router/modules/remaining';
|
|
||||||
|
|
||||||
// 静态路由
|
|
||||||
const routes: RouteRecordRaw[] = [...remaining, ...home, ...error] as RouteRecordRaw[];
|
|
||||||
const router = createRouter({
|
|
||||||
history: createWebHashHistory(),
|
|
||||||
routes,
|
|
||||||
scrollBehavior: () => ({ left: 0, top: 0, behavior: 'smooth' }),
|
|
||||||
});
|
|
||||||
|
|
||||||
/** 全局注册 router */
|
|
||||||
export const setupRouter = (app: App<Element>) => {
|
|
||||||
app.use(router);
|
|
||||||
};
|
|
||||||
|
|
||||||
/** 重置路由 */
|
|
||||||
export const resetRouter = () => {
|
|
||||||
router.replace({ path: '/' }).then();
|
|
||||||
};
|
|
||||||
|
|
||||||
export default router;
|
|
|
@ -1,13 +0,0 @@
|
||||||
import type { RouteRecordRaw } from 'vue-router';
|
|
||||||
|
|
||||||
import type { RouteConfigsTable } from '@/types/router/Route';
|
|
||||||
|
|
||||||
const routes: RouteRecordRaw[] | RouteConfigsTable[] = [
|
|
||||||
{
|
|
||||||
path: '/error',
|
|
||||||
component: () => import('@/views/error-page/404.vue'),
|
|
||||||
meta: { hidden: true },
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default routes;
|
|
|
@ -1,24 +0,0 @@
|
||||||
import type { RouteRecordRaw } from 'vue-router';
|
|
||||||
|
|
||||||
import Layout from '@/layout/index.vue';
|
|
||||||
import type { RouteConfigsTable } from '@/types/router/Route';
|
|
||||||
|
|
||||||
const routes: RouteRecordRaw[] | RouteConfigsTable[] = [
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
name: '/',
|
|
||||||
component: Layout,
|
|
||||||
redirect: '/home',
|
|
||||||
meta: { transition: 'fade' },
|
|
||||||
children: [
|
|
||||||
{ path: '/home', name: 'home', component: () => import('@/views/home/index.vue') },
|
|
||||||
{
|
|
||||||
path: '/generator-code',
|
|
||||||
name: 'generatorCode',
|
|
||||||
component: () => import('@/views/generator-code/index.vue'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default routes;
|
|
|
@ -1,20 +0,0 @@
|
||||||
import type { RouteRecordRaw } from 'vue-router';
|
|
||||||
|
|
||||||
import Layout from '@/layout/index.vue';
|
|
||||||
import type { RouteConfigsTable } from '@/types/router/Route';
|
|
||||||
|
|
||||||
const routes: RouteRecordRaw[] | RouteConfigsTable[] = [
|
|
||||||
{
|
|
||||||
path: '/redirect',
|
|
||||||
component: Layout,
|
|
||||||
meta: { hidden: true },
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: '/redirect/:path(.*)',
|
|
||||||
component: () => import('@/views/redirect/index.vue'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default routes;
|
|
|
@ -1,27 +0,0 @@
|
||||||
import { defineStore } from 'pinia';
|
|
||||||
|
|
||||||
import { isCSSColor, isPath } from '@/utils/regexp/regexpBackground';
|
|
||||||
|
|
||||||
const useAppStore = defineStore('appStore', {
|
|
||||||
state() {
|
|
||||||
return {
|
|
||||||
background: '',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getters: {},
|
|
||||||
actions: {
|
|
||||||
setBackground(background: string) {
|
|
||||||
if (isCSSColor(background)) {
|
|
||||||
this.background = background;
|
|
||||||
} else if (isPath(background)) {
|
|
||||||
const href = new URL(background, import.meta.url).href;
|
|
||||||
this.background = `url(${href})`;
|
|
||||||
} else {
|
|
||||||
const href = new URL('@/assets/images/common/bg/bg-layout.png', import.meta.url).href;
|
|
||||||
this.background = `url(${href})`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export { useAppStore };
|
|
|
@ -1,11 +0,0 @@
|
||||||
import { createPinia } from 'pinia';
|
|
||||||
import piniaPluginPersistedState from 'pinia-plugin-persistedstate';
|
|
||||||
import type { App } from 'vue';
|
|
||||||
|
|
||||||
const pinia = createPinia();
|
|
||||||
|
|
||||||
// 全局注册 store
|
|
||||||
export function setupStore(app: App<Element>) {
|
|
||||||
pinia.use(piniaPluginPersistedState);
|
|
||||||
app.use(pinia);
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
import { defineStore } from 'pinia';
|
|
||||||
|
|
||||||
import {
|
|
||||||
getDatabaseInfoMetaData,
|
|
||||||
getDatabaseTableList,
|
|
||||||
getTableColumnInfo,
|
|
||||||
getTableMetaData,
|
|
||||||
} from '@/api/table';
|
|
||||||
|
|
||||||
export const useTableStore = defineStore('tableStore', {
|
|
||||||
state: () => ({
|
|
||||||
databaseInfoMeta: undefined,
|
|
||||||
// 数据库所有的表
|
|
||||||
tableList: [],
|
|
||||||
tableListLoading: false,
|
|
||||||
}),
|
|
||||||
getters: {},
|
|
||||||
actions: {
|
|
||||||
/* 当前数据库信息 */
|
|
||||||
async loadDatabaseInfoMeta() {
|
|
||||||
const result = await getDatabaseInfoMetaData();
|
|
||||||
if (result.code === 200) {
|
|
||||||
this.databaseInfoMeta = result.data;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/* 数据库所有的表 */
|
|
||||||
async loadDatabaseTableList() {
|
|
||||||
this.tableListLoading = true;
|
|
||||||
const dbName = this.databaseInfoMeta?.currentDatabase;
|
|
||||||
const result = await getDatabaseTableList({ dbName });
|
|
||||||
|
|
||||||
this.tableList = result.data;
|
|
||||||
this.tableListLoading = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
/* 获取表属性 */
|
|
||||||
async getTableMetaData(tableName: string) {
|
|
||||||
const result = await getTableMetaData({ tableName });
|
|
||||||
if (result.code !== 200) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.data;
|
|
||||||
},
|
|
||||||
|
|
||||||
/* 获取表属性 */
|
|
||||||
async getTableColumnInfo(tableName: string) {
|
|
||||||
const result = await getTableColumnInfo({ tableName });
|
|
||||||
if (result.code !== 200) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.data;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,62 +0,0 @@
|
||||||
import { defineStore } from 'pinia';
|
|
||||||
|
|
||||||
import { generator, getVmsResourcePathList } from '@/api/vms';
|
|
||||||
|
|
||||||
export const useVmsStore = defineStore('vmsStore', {
|
|
||||||
// 开启持久化
|
|
||||||
// persist: true,
|
|
||||||
// persist: {
|
|
||||||
// paths: ['formValue', 'formOption'],
|
|
||||||
// },
|
|
||||||
state: () => ({
|
|
||||||
generators: [],
|
|
||||||
|
|
||||||
// 生成服务端内容
|
|
||||||
serverOptions: [],
|
|
||||||
|
|
||||||
// 生成前端内容
|
|
||||||
webOptions: [],
|
|
||||||
|
|
||||||
// 查询的表单
|
|
||||||
formValue: {
|
|
||||||
author: 'Bunny',
|
|
||||||
packageName: 'cn.bunny.services',
|
|
||||||
requestMapping: '/api',
|
|
||||||
className: '',
|
|
||||||
tableName: '',
|
|
||||||
simpleDateFormat: 'yyyy-MM-dd HH:mm:ss',
|
|
||||||
tablePrefixes: 't_,sys_,qrtz_,log_',
|
|
||||||
comment: '',
|
|
||||||
path: [],
|
|
||||||
},
|
|
||||||
|
|
||||||
// 表单选择内容
|
|
||||||
formOption: {
|
|
||||||
generatorServer: [],
|
|
||||||
generatorWeb: [],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
getters: {},
|
|
||||||
actions: {
|
|
||||||
/* 获取所有数据表 */
|
|
||||||
async generator(data: any) {
|
|
||||||
const result = await generator(data);
|
|
||||||
// 需要确保已经在 setup 中执行了 window.$message = message
|
|
||||||
|
|
||||||
this.generators = result.data.map((i: any) => ({ ...i, path: i.path.replace('.vm', '') }));
|
|
||||||
(window as any).$message.success(`生成成功,共 ${this.generators.length} 数据`);
|
|
||||||
},
|
|
||||||
|
|
||||||
/* 获取vms文件路径 */
|
|
||||||
async getVmsResourcePathList() {
|
|
||||||
const result = await getVmsResourcePathList();
|
|
||||||
// 需要确保已经在 setup 中执行了 window.$message = message
|
|
||||||
if (result.code !== 200) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
this.webOptions = result.data.web;
|
|
||||||
this.serverOptions = result.data.server;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,16 +0,0 @@
|
||||||
declare global {
|
|
||||||
/* 环境便配置 */
|
|
||||||
declare interface ViteEnv {
|
|
||||||
VITE_APP_TITLE: string;
|
|
||||||
VITE_PORT: number;
|
|
||||||
VITE_PUBLIC_PATH: string;
|
|
||||||
VITE_APP_URL: string;
|
|
||||||
VITE_APP_BASE_API: string;
|
|
||||||
VITE_STRICT_PORT: boolean;
|
|
||||||
VITE_POST_CSS_PX_TO_VIEWPORT8_PLUGIN: boolean;
|
|
||||||
VITE_MOCK_DEV_SERVER: boolean;
|
|
||||||
VITE_MOCK_BASE_API: string;
|
|
||||||
VITE_CDN: boolean;
|
|
||||||
VITE_COMPRESSION: string;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
type Recordable<T = any> = Record<string, T>;
|
|
|
@ -1,6 +0,0 @@
|
||||||
// 基础后端返回内容
|
|
||||||
export interface BaseResult<T> {
|
|
||||||
code: number;
|
|
||||||
data: T;
|
|
||||||
message: string;
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
import type { RouteComponent } from 'vue-router';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 完整子路由的`meta`配置表
|
|
||||||
*/
|
|
||||||
interface CustomizeRouteMeta {
|
|
||||||
title: string;
|
|
||||||
subtitle: string;
|
|
||||||
transition: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 整体路由配置表(包括完整子路由)
|
|
||||||
*/
|
|
||||||
export interface RouteConfigsTable {
|
|
||||||
/** 路由地址 `必填` */
|
|
||||||
path: string;
|
|
||||||
/** 路由名字(保持唯一)`可选` */
|
|
||||||
name?: string;
|
|
||||||
/** `Layout`组件 `可选` */
|
|
||||||
component?: RouteComponent;
|
|
||||||
/** 路由重定向 `可选` */
|
|
||||||
redirect?: string;
|
|
||||||
meta?: CustomizeRouteMeta;
|
|
||||||
/** 子路由配置项 */
|
|
||||||
children?: Array<RouteConfigsTable>;
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
/// <reference types="vite/client" />
|
|
|
@ -1,24 +0,0 @@
|
||||||
/**
|
|
||||||
* 复制到剪切板
|
|
||||||
* @param text 文本内容
|
|
||||||
*/
|
|
||||||
export const copy = (text: string) => {
|
|
||||||
const textarea = document.createElement('textarea');
|
|
||||||
textarea.value = text;
|
|
||||||
textarea.style.position = 'fixed';
|
|
||||||
document.body.appendChild(textarea);
|
|
||||||
textarea.select();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const success = document.execCommand('copy');
|
|
||||||
if (success) {
|
|
||||||
(window as any as any).$message.success('复制成功!');
|
|
||||||
} else {
|
|
||||||
(window as any).$message.success('复制失败!');
|
|
||||||
}
|
|
||||||
} catch (err: any) {
|
|
||||||
(window as any).$message.success('复制失败!请手动复制');
|
|
||||||
} finally {
|
|
||||||
document.body.removeChild(textarea);
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,45 +0,0 @@
|
||||||
export function downloadTextAsFile(text: string, filename: string) {
|
|
||||||
// 直接创建 File 对象(比 Blob 更高级)
|
|
||||||
const file = new File([text], filename, { type: 'text/plain' });
|
|
||||||
|
|
||||||
// 创建下载链接
|
|
||||||
const url = URL.createObjectURL(file);
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.href = url;
|
|
||||||
a.download = filename;
|
|
||||||
|
|
||||||
// 触发下载
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
|
|
||||||
// 清理
|
|
||||||
requestIdleCallback(() => {
|
|
||||||
document.body.removeChild(a);
|
|
||||||
URL.revokeObjectURL(a.href);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export const downloadBlob = (response: any) => {
|
|
||||||
try {
|
|
||||||
// 从响应头获取文件名
|
|
||||||
const contentDisposition = response.headers['content-disposition'];
|
|
||||||
let fileName = 'download.zip';
|
|
||||||
if (contentDisposition) {
|
|
||||||
const fileNameMatch = contentDisposition.match(/filename="?(.+)"/);
|
|
||||||
if (fileNameMatch && fileNameMatch[1]) {
|
|
||||||
fileName = fileNameMatch[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = window.URL.createObjectURL(new Blob([response.data]));
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.href = url;
|
|
||||||
link.setAttribute('download', fileName);
|
|
||||||
document.body.appendChild(link);
|
|
||||||
link.click();
|
|
||||||
link.remove();
|
|
||||||
window.URL.revokeObjectURL(url);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,19 +0,0 @@
|
||||||
import 'nprogress/nprogress.css';
|
|
||||||
|
|
||||||
import NProgress from 'nprogress';
|
|
||||||
|
|
||||||
// 进度条
|
|
||||||
NProgress.configure({
|
|
||||||
// 动画方式
|
|
||||||
easing: 'ease',
|
|
||||||
// 递增进度条的速度
|
|
||||||
speed: 500,
|
|
||||||
// 是否显示加载ico
|
|
||||||
showSpinner: false,
|
|
||||||
// 自动递增间隔
|
|
||||||
trickleSpeed: 200,
|
|
||||||
// 初始化时的最小百分比
|
|
||||||
minimum: 0.3,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default NProgress;
|
|
|
@ -1,29 +0,0 @@
|
||||||
/** 判断是否是CSS颜色 */
|
|
||||||
function isCSSColor(str: string) {
|
|
||||||
// 匹配十六进制颜色(如 #fff, #ffffff)
|
|
||||||
const hexColor = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
|
|
||||||
|
|
||||||
// 匹配RGB/RGBA颜色(如 rgb(255, 255, 255), rgba(255, 255, 255, 0.5))
|
|
||||||
const rgbColor = /^rgba?\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*(,\s*[\d\.]+)?\s*\)$/;
|
|
||||||
|
|
||||||
// 匹配HSL/HSLA颜色(如 hsl(120, 100%, 50%), hsla(120, 100%, 50%, 0.5))
|
|
||||||
const hslColor = /^hsla?\(\s*\d{1,3}\s*,\s*\d{1,3}%\s*,\s*\d{1,3}%\s*(,\s*[\d\.]+)?\s*\)$/;
|
|
||||||
|
|
||||||
// 匹配预定义颜色名称(如 red, blue, green)
|
|
||||||
const namedColor = /^[a-zA-Z]+$/;
|
|
||||||
|
|
||||||
return hexColor.test(str) || rgbColor.test(str) || hslColor.test(str) || namedColor.test(str);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 判断是否是相对路径或绝对路径 */
|
|
||||||
function isPath(str: string) {
|
|
||||||
// 匹配相对路径(如 ./path, ../path, path/to/file)
|
|
||||||
const relativePath = /^\.{0,2}\/[^\/].*$/;
|
|
||||||
|
|
||||||
// 匹配绝对路径(如 /path/to/file, C:\path\to\file)
|
|
||||||
const absolutePath = /^(?:\/|[A-Za-z]:\\).*$/;
|
|
||||||
|
|
||||||
return relativePath.test(str) || absolutePath.test(str);
|
|
||||||
}
|
|
||||||
|
|
||||||
export { isCSSColor, isPath };
|
|
|
@ -1,13 +0,0 @@
|
||||||
<template> </template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { useMessage } from 'naive-ui';
|
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
|
|
||||||
// naive ui 要求这样写的 https://www.naiveui.com/zh-CN/light/components/message
|
|
||||||
export default defineComponent({
|
|
||||||
setup() {
|
|
||||||
window.$message = useMessage();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -1,237 +0,0 @@
|
||||||
<script lang="ts" setup>
|
|
||||||
import { useRouter } from 'vue-router';
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="page-container">
|
|
||||||
<div class="pic-404">
|
|
||||||
<img alt="404" class="pic-404__parent" src="../../assets/images/error/404.png" />
|
|
||||||
<img alt="404" class="pic-404__child left" src="../../assets/images/error/404_cloud.png" />
|
|
||||||
<img alt="404" class="pic-404__child mid" src="../../assets/images/error/404_cloud.png" />
|
|
||||||
<img alt="404" class="pic-404__child right" src="../../assets/images/error/404_cloud.png" />
|
|
||||||
</div>
|
|
||||||
<div class="bullshit">
|
|
||||||
<div class="bullshit__oops">OOPS!</div>
|
|
||||||
<div class="bullshit__info">
|
|
||||||
All rights reserved
|
|
||||||
<a href="https://wallstreetcn.com" style="color: #20a0ff" target="_blank">wallstreetcn</a>
|
|
||||||
</div>
|
|
||||||
<div class="bullshit__headline">The webmaster said that you can not enter this page...</div>
|
|
||||||
<div class="bullshit__info">
|
|
||||||
Please check that the URL you entered is correct, or click the button below to return to the
|
|
||||||
homepage.
|
|
||||||
</div>
|
|
||||||
<a class="bullshit__return-home" href="/" @click.prevent="router.replace('/')">
|
|
||||||
Back to home
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.page-container {
|
|
||||||
display: flex;
|
|
||||||
padding: 100px;
|
|
||||||
|
|
||||||
.pic-404 {
|
|
||||||
width: 600px;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
&__parent {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__child {
|
|
||||||
&.left {
|
|
||||||
top: 17px;
|
|
||||||
left: 220px;
|
|
||||||
width: 80px;
|
|
||||||
opacity: 0;
|
|
||||||
animation-name: cloudLeft;
|
|
||||||
animation-duration: 2s;
|
|
||||||
animation-timing-function: linear;
|
|
||||||
animation-delay: 1s;
|
|
||||||
animation-fill-mode: forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.mid {
|
|
||||||
top: 10px;
|
|
||||||
left: 420px;
|
|
||||||
width: 46px;
|
|
||||||
opacity: 0;
|
|
||||||
animation-name: cloudMid;
|
|
||||||
animation-duration: 2s;
|
|
||||||
animation-timing-function: linear;
|
|
||||||
animation-delay: 1.2s;
|
|
||||||
animation-fill-mode: forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.right {
|
|
||||||
top: 100px;
|
|
||||||
left: 500px;
|
|
||||||
width: 62px;
|
|
||||||
opacity: 0;
|
|
||||||
animation-name: cloudRight;
|
|
||||||
animation-duration: 2s;
|
|
||||||
animation-timing-function: linear;
|
|
||||||
animation-delay: 1s;
|
|
||||||
animation-fill-mode: forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes cloudLeft {
|
|
||||||
0% {
|
|
||||||
top: 17px;
|
|
||||||
left: 220px;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
20% {
|
|
||||||
top: 33px;
|
|
||||||
left: 188px;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
80% {
|
|
||||||
top: 81px;
|
|
||||||
left: 92px;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
top: 97px;
|
|
||||||
left: 60px;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes cloudMid {
|
|
||||||
0% {
|
|
||||||
top: 10px;
|
|
||||||
left: 420px;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
20% {
|
|
||||||
top: 40px;
|
|
||||||
left: 360px;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
70% {
|
|
||||||
top: 130px;
|
|
||||||
left: 180px;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
top: 160px;
|
|
||||||
left: 120px;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes cloudRight {
|
|
||||||
0% {
|
|
||||||
top: 100px;
|
|
||||||
left: 500px;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
20% {
|
|
||||||
top: 120px;
|
|
||||||
left: 460px;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
80% {
|
|
||||||
top: 180px;
|
|
||||||
left: 340px;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
top: 200px;
|
|
||||||
left: 300px;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bullshit {
|
|
||||||
width: 300px;
|
|
||||||
padding: 30px 0;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
&__oops {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
font-size: 32px;
|
|
||||||
font-weight: bold;
|
|
||||||
line-height: 40px;
|
|
||||||
color: #1482f0;
|
|
||||||
opacity: 0;
|
|
||||||
animation-name: slideUp;
|
|
||||||
animation-duration: 0.5s;
|
|
||||||
animation-fill-mode: forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__headline {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: bold;
|
|
||||||
line-height: 24px;
|
|
||||||
color: #222;
|
|
||||||
opacity: 0;
|
|
||||||
animation-name: slideUp;
|
|
||||||
animation-duration: 0.5s;
|
|
||||||
animation-delay: 0.1s;
|
|
||||||
animation-fill-mode: forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__info {
|
|
||||||
margin-bottom: 30px;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 21px;
|
|
||||||
color: grey;
|
|
||||||
opacity: 0;
|
|
||||||
animation-name: slideUp;
|
|
||||||
animation-duration: 0.5s;
|
|
||||||
animation-delay: 0.2s;
|
|
||||||
animation-fill-mode: forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__return-home {
|
|
||||||
display: block;
|
|
||||||
float: left;
|
|
||||||
width: 110px;
|
|
||||||
height: 36px;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 36px;
|
|
||||||
color: #fff;
|
|
||||||
text-align: center;
|
|
||||||
cursor: pointer;
|
|
||||||
background: #1482f0;
|
|
||||||
border-radius: 100px;
|
|
||||||
opacity: 0;
|
|
||||||
animation-name: slideUp;
|
|
||||||
animation-duration: 0.5s;
|
|
||||||
animation-delay: 0.3s;
|
|
||||||
animation-fill-mode: forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes slideUp {
|
|
||||||
0% {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(60px);
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,68 +0,0 @@
|
||||||
import { NTag } from 'naive-ui';
|
|
||||||
import type { JSX } from 'vue/jsx-runtime';
|
|
||||||
|
|
||||||
export const columns: any = [
|
|
||||||
{
|
|
||||||
title: '序号',
|
|
||||||
key: 'no',
|
|
||||||
titleAlign: 'center',
|
|
||||||
align: 'center',
|
|
||||||
render(row: any, index: number) {
|
|
||||||
return index + 1;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '列名称',
|
|
||||||
key: 'columnName',
|
|
||||||
titleAlign: 'center',
|
|
||||||
align: 'center',
|
|
||||||
render(row: any): JSX.Element {
|
|
||||||
return <NTag type="primary">{row.columnName}</NTag>;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '字段名称',
|
|
||||||
key: 'lowercaseName',
|
|
||||||
titleAlign: 'center',
|
|
||||||
align: 'center',
|
|
||||||
render(row: any): JSX.Element {
|
|
||||||
return <NTag>{row.lowercaseName}</NTag>;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '数据库字段类型',
|
|
||||||
key: 'jdbcType',
|
|
||||||
titleAlign: 'center',
|
|
||||||
align: 'center',
|
|
||||||
render(row: any): JSX.Element {
|
|
||||||
return <NTag>{row.jdbcType}</NTag>;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Java类型',
|
|
||||||
key: 'javaType',
|
|
||||||
titleAlign: 'center',
|
|
||||||
align: 'center',
|
|
||||||
render(row: any): JSX.Element {
|
|
||||||
return <NTag type="warning">{row.javaType}</NTag>;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '是否为主键',
|
|
||||||
key: 'isPrimaryKey',
|
|
||||||
titleAlign: 'center',
|
|
||||||
align: 'center',
|
|
||||||
render(row: any): JSX.Element {
|
|
||||||
return row.isPrimaryKey ? <NTag type="error">是</NTag> : <NTag type="success">否</NTag>;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '字段注释',
|
|
||||||
key: 'comment',
|
|
||||||
titleAlign: 'center',
|
|
||||||
align: 'center',
|
|
||||||
render(row: any): JSX.Element {
|
|
||||||
return <NTag type="info">{row.comment}</NTag>;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
|
@ -1,35 +0,0 @@
|
||||||
<script lang="ts" setup>
|
|
||||||
import { NDataTable } from 'naive-ui';
|
|
||||||
import { onMounted, ref } from 'vue';
|
|
||||||
import { useRoute } from 'vue-router';
|
|
||||||
|
|
||||||
import { useTableStore } from '@/store/modules/table';
|
|
||||||
import { columns } from '@/views/generator-code/column-field/columns';
|
|
||||||
|
|
||||||
const route = useRoute();
|
|
||||||
const tableStore = useTableStore();
|
|
||||||
|
|
||||||
// 数据库中当前表的列信息
|
|
||||||
const datalist = ref([]);
|
|
||||||
|
|
||||||
const loading = ref(false);
|
|
||||||
|
|
||||||
/* 数据库列信息 */
|
|
||||||
const getColumnInfo = async () => {
|
|
||||||
loading.value = true;
|
|
||||||
|
|
||||||
const tableName: any = route.query.tableName;
|
|
||||||
datalist.value = await tableStore.getTableColumnInfo(tableName);
|
|
||||||
|
|
||||||
loading.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
getColumnInfo();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<!-- 当前表的列字段 -->
|
|
||||||
<n-data-table :bordered="true" :columns="columns" :data="datalist" :loading="loading" />
|
|
||||||
</template>
|
|
|
@ -1,54 +0,0 @@
|
||||||
<script lang="ts" setup>
|
|
||||||
import { NGradientText } from 'naive-ui';
|
|
||||||
import { storeToRefs } from 'pinia';
|
|
||||||
import { onMounted } from 'vue';
|
|
||||||
|
|
||||||
import { useTableStore } from '@/store/modules/table';
|
|
||||||
|
|
||||||
const tableStore = useTableStore();
|
|
||||||
const { databaseInfoMeta } = storeToRefs(tableStore);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
tableStore.loadDatabaseInfoMeta();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="database-info">
|
|
||||||
<p>
|
|
||||||
数据库产品名称:
|
|
||||||
<n-gradient-text type="primary">{{ databaseInfoMeta?.databaseProductName }}</n-gradient-text>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
数据库产品版本:
|
|
||||||
<n-gradient-text type="primary">
|
|
||||||
{{ databaseInfoMeta?.databaseProductVersion }}
|
|
||||||
</n-gradient-text>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
驱动名称:
|
|
||||||
<n-gradient-text type="primary">{{ databaseInfoMeta?.driverName }}</n-gradient-text>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
数据库驱动版本:
|
|
||||||
<n-gradient-text type="primary">{{ databaseInfoMeta?.driverVersion }}</n-gradient-text>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
数据库用户:
|
|
||||||
<n-gradient-text type="primary">{{ databaseInfoMeta?.username }}</n-gradient-text>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.database-info {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 5px 0 0 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,255 +0,0 @@
|
||||||
<script lang="ts" setup>
|
|
||||||
import {
|
|
||||||
NButton,
|
|
||||||
NButtonGroup,
|
|
||||||
NCheckbox,
|
|
||||||
NCheckboxGroup,
|
|
||||||
NForm,
|
|
||||||
NFormItemGi,
|
|
||||||
NGrid,
|
|
||||||
NGridItem,
|
|
||||||
NInput,
|
|
||||||
NSpace,
|
|
||||||
useMessage,
|
|
||||||
} from 'naive-ui';
|
|
||||||
import { storeToRefs } from 'pinia';
|
|
||||||
import { onMounted, ref } from 'vue';
|
|
||||||
import { computed } from 'vue-demi';
|
|
||||||
import { useRoute } from 'vue-router';
|
|
||||||
|
|
||||||
import { fetchTableInfo } from '@/api/sqlParser';
|
|
||||||
import { downloadByZip } from '@/api/vms';
|
|
||||||
import SelectButtonGroup from '@/components/select-button-group.vue';
|
|
||||||
import { useVmsStore } from '@/store/modules/vms';
|
|
||||||
import { downloadBlob, downloadTextAsFile } from '@/utils/file';
|
|
||||||
import {
|
|
||||||
formValueInit,
|
|
||||||
selectAll,
|
|
||||||
selectAllInvert,
|
|
||||||
selectCancelAll,
|
|
||||||
validateFormValue,
|
|
||||||
} from '@/views/generator-code/generator/hook';
|
|
||||||
import { rules } from '@/views/generator-code/generator/option';
|
|
||||||
|
|
||||||
const route = useRoute();
|
|
||||||
|
|
||||||
const vmsStore = useVmsStore();
|
|
||||||
const { formValue, formOption } = storeToRefs(vmsStore);
|
|
||||||
|
|
||||||
const message = useMessage();
|
|
||||||
const formRef = ref();
|
|
||||||
const hasDownloadZip = computed(
|
|
||||||
() => !(formOption.value.generatorWeb.length > 0 || formOption.value.generatorServer.length > 0)
|
|
||||||
);
|
|
||||||
|
|
||||||
// 解析 Sql 语句
|
|
||||||
const sql = ref();
|
|
||||||
|
|
||||||
/* 提交表单 */
|
|
||||||
const onSubmit = (e: MouseEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
formRef.value?.validate(async (errors: any) => {
|
|
||||||
if (!errors) {
|
|
||||||
validateFormValue();
|
|
||||||
|
|
||||||
// 生成代码
|
|
||||||
await vmsStore.generator(formValue.value);
|
|
||||||
} else {
|
|
||||||
errors.forEach((error: any) => {
|
|
||||||
error.forEach((err: any) => {
|
|
||||||
message.error(`${err.message}->${err.field}`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/* 清空已经生成的代码 */
|
|
||||||
const clearGeneratorCode = () => {
|
|
||||||
vmsStore.generators = [];
|
|
||||||
};
|
|
||||||
|
|
||||||
/* 下载全部 */
|
|
||||||
const downloadAll = () => {
|
|
||||||
vmsStore.generators.forEach((vms) => {
|
|
||||||
const code = vms.code;
|
|
||||||
const path = vms.path.split('/')[1];
|
|
||||||
|
|
||||||
downloadTextAsFile(code, path);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/* 下载zip文件 */
|
|
||||||
const downloadZipFile = async () => {
|
|
||||||
validateFormValue();
|
|
||||||
|
|
||||||
const result = await downloadByZip(formValue.value);
|
|
||||||
downloadBlob(result);
|
|
||||||
};
|
|
||||||
|
|
||||||
/* 解析 SQL 语句信息 */
|
|
||||||
const sqlParser = async () => {
|
|
||||||
if (!sql.value) {
|
|
||||||
message.warning('SQL 为空');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = { sql: sql.value };
|
|
||||||
const { data } = await fetchTableInfo(params);
|
|
||||||
|
|
||||||
// 设置要生成的sql表中的内容
|
|
||||||
formValue.value.comment = data.comment;
|
|
||||||
formValue.value.tableName = data.tableName;
|
|
||||||
formValueInit(data.tableName);
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
// 初始化表名称
|
|
||||||
const tableName: any = route.query.tableName;
|
|
||||||
formValueInit(tableName);
|
|
||||||
|
|
||||||
vmsStore.getVmsResourcePathList();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<n-form ref="formRef" :label-width="80" :model="formValue" :rules="rules">
|
|
||||||
<n-grid cols="24" item-responsive responsive="screen">
|
|
||||||
<n-form-item-gi
|
|
||||||
label="如果有sql会生成sql中的信息,点击【解析SQL】会替换【表名称】和【注释名称】"
|
|
||||||
path="sql"
|
|
||||||
span="24"
|
|
||||||
>
|
|
||||||
<div class="flex flex-wrap flex-col w-full">
|
|
||||||
<n-input
|
|
||||||
v-model:value="sql"
|
|
||||||
:autosize="{ minRows: 3 }"
|
|
||||||
class="w-full"
|
|
||||||
placeholder="SQL语句"
|
|
||||||
type="textarea"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<n-button-group class="mt-2">
|
|
||||||
<n-button type="primary" @click="sqlParser">解析SQL</n-button>
|
|
||||||
<n-button type="error" @click="sql = null">清空输入框</n-button>
|
|
||||||
</n-button-group>
|
|
||||||
</div>
|
|
||||||
</n-form-item-gi>
|
|
||||||
</n-grid>
|
|
||||||
|
|
||||||
<!-- 需要提交的生成表单 -->
|
|
||||||
<n-grid :cols="24" :x-gap="5" item-responsive responsive="screen">
|
|
||||||
<n-form-item-gi label="作者名称" path="author" span="12 m:8 l:6">
|
|
||||||
<n-input v-model:value="formValue.author" placeholder="作者名称" />
|
|
||||||
</n-form-item-gi>
|
|
||||||
|
|
||||||
<n-form-item-gi label="requestMapping名称" path="requestMapping" span="12 m:8 l:6">
|
|
||||||
<n-input v-model:value="formValue.requestMapping" placeholder="requestMapping名称" />
|
|
||||||
</n-form-item-gi>
|
|
||||||
|
|
||||||
<n-form-item-gi label="表名称" path="tableName" span="24 m:8 l:6">
|
|
||||||
<n-input v-model:value="formValue.tableName" placeholder="表名称" />
|
|
||||||
</n-form-item-gi>
|
|
||||||
|
|
||||||
<n-form-item-gi label="类名称" path="className" span="24 m:8 l:6">
|
|
||||||
<n-input v-model:value="formValue.className" placeholder="类名称" />
|
|
||||||
</n-form-item-gi>
|
|
||||||
|
|
||||||
<n-form-item-gi label="包名称" path="packageName" span="24 m:8 l:6">
|
|
||||||
<n-input v-model:value="formValue.packageName" placeholder="包名称" />
|
|
||||||
</n-form-item-gi>
|
|
||||||
|
|
||||||
<n-form-item-gi label="时间格式" path="simpleDateFormat" span="24 m:8 l:6">
|
|
||||||
<n-input v-model:value="formValue.simpleDateFormat" placeholder="时间格式" />
|
|
||||||
</n-form-item-gi>
|
|
||||||
|
|
||||||
<n-form-item-gi label="去除开头前缀" path="tablePrefixes" span="12 m:8 l:6">
|
|
||||||
<n-input v-model:value="formValue.tablePrefixes" placeholder="去除开头前缀" />
|
|
||||||
</n-form-item-gi>
|
|
||||||
|
|
||||||
<n-form-item-gi label="注释名称" path="comment" span="12 m:8 l:6">
|
|
||||||
<n-input v-model:value="formValue.comment" placeholder="修改注释名称" />
|
|
||||||
</n-form-item-gi>
|
|
||||||
</n-grid>
|
|
||||||
|
|
||||||
<!-- 需要生成的模板 -->
|
|
||||||
<n-grid :cols="24" :x-gap="5" item-responsive responsive="screen">
|
|
||||||
<n-form-item-gi label="生成后端" path="generatorServer" span="24 m:24 l:12">
|
|
||||||
<n-checkbox-group v-model:value="formOption.generatorServer">
|
|
||||||
<n-space>
|
|
||||||
<n-checkbox
|
|
||||||
v-for="(item, index) in vmsStore.serverOptions"
|
|
||||||
:key="index"
|
|
||||||
:value="item.name"
|
|
||||||
>
|
|
||||||
{{ item.label }}
|
|
||||||
</n-checkbox>
|
|
||||||
|
|
||||||
<!-- 选择按钮 -->
|
|
||||||
<select-button-group
|
|
||||||
v-model:selected="formOption.generatorServer"
|
|
||||||
:data="vmsStore.serverOptions"
|
|
||||||
id-key="name"
|
|
||||||
/>
|
|
||||||
</n-space>
|
|
||||||
</n-checkbox-group>
|
|
||||||
</n-form-item-gi>
|
|
||||||
|
|
||||||
<n-form-item-gi label="生成前端" path="generatorWeb" span="24 m:24 l:12">
|
|
||||||
<n-checkbox-group v-model:value="formOption.generatorWeb">
|
|
||||||
<n-space>
|
|
||||||
<n-checkbox
|
|
||||||
v-for="(item, index) in vmsStore.webOptions"
|
|
||||||
:key="index"
|
|
||||||
v-model:value="item.name"
|
|
||||||
>
|
|
||||||
{{ item.label }}
|
|
||||||
</n-checkbox>
|
|
||||||
|
|
||||||
<!-- 选择按钮 -->
|
|
||||||
<select-button-group
|
|
||||||
v-model:selected="formOption.generatorWeb"
|
|
||||||
:data="vmsStore.webOptions"
|
|
||||||
id-key="name"
|
|
||||||
/>
|
|
||||||
</n-space>
|
|
||||||
</n-checkbox-group>
|
|
||||||
</n-form-item-gi>
|
|
||||||
</n-grid>
|
|
||||||
|
|
||||||
<!-- 操作选项按钮 -->
|
|
||||||
<n-grid cols="24" item-responsive responsive="screen">
|
|
||||||
<n-grid-item class="mt-2" span="24 m:12 l:8">
|
|
||||||
<n-button attr-type="button" type="success" @click="selectAll">全部选择</n-button>
|
|
||||||
<n-button attr-type="button" type="warning" @click="selectAllInvert">全部反选</n-button>
|
|
||||||
<n-button attr-type="button" type="error" @click="selectCancelAll">全选取消</n-button>
|
|
||||||
</n-grid-item>
|
|
||||||
|
|
||||||
<n-grid-item class="mt-2" span="24 m:12 l:8">
|
|
||||||
<n-button attr-type="button" type="success" @click="onSubmit">开始生成</n-button>
|
|
||||||
<n-button attr-type="button" type="error" @click="clearGeneratorCode">清空已生成</n-button>
|
|
||||||
<n-button
|
|
||||||
:disabled="!(vmsStore.generators.length > 0)"
|
|
||||||
attr-type="button"
|
|
||||||
type="primary"
|
|
||||||
@click="downloadAll"
|
|
||||||
>
|
|
||||||
下载全部 {{ vmsStore.generators.length }}
|
|
||||||
</n-button>
|
|
||||||
</n-grid-item>
|
|
||||||
|
|
||||||
<n-grid-item class="mt-2" span="24 m:12 l:8">
|
|
||||||
<n-button
|
|
||||||
:disabled="hasDownloadZip"
|
|
||||||
attr-type="button"
|
|
||||||
class="w-full"
|
|
||||||
type="success"
|
|
||||||
@click="downloadZipFile"
|
|
||||||
>
|
|
||||||
下载zip
|
|
||||||
</n-button>
|
|
||||||
</n-grid-item>
|
|
||||||
</n-grid>
|
|
||||||
</n-form>
|
|
||||||
</template>
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue