Compare commits
20 Commits
Author | SHA1 | Date |
---|---|---|
|
42f1a7edfe | |
|
f95dc4d947 | |
|
11b20ba6f5 | |
|
9356d39347 | |
|
8f3ea1e87f | |
|
16a8170e86 | |
|
d38844eea5 | |
|
e832b5ffab | |
|
fd722d2515 | |
|
b6e263d114 | |
|
ff2f3144dc | |
|
4f0007ee01 | |
|
faaca67cc6 | |
|
3d3540e3e9 | |
|
2325754ede | |
|
617ff87736 | |
|
474f3ab89b | |
|
e2b6d49518 | |
|
5c241728f0 | |
|
be1b09dc70 |
|
@ -4,6 +4,42 @@ target/
|
|||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
logs/
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
.eslintcache
|
||||
report.html
|
||||
vite.config.*.timestamp*
|
||||
|
||||
bunny-web.site.csr
|
||||
bunny-web.site.key
|
||||
bunny-web.site_bundle.crt
|
||||
bunny-web.site_bundle.pem
|
||||
|
||||
yarn.lock
|
||||
npm-debug.log*
|
||||
.pnpm-error.log*
|
||||
.pnpm-debug.log
|
||||
tests/**/coverage/
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
|
@ -15,7 +51,6 @@ logs/
|
|||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
@ -26,9 +61,11 @@ logs/
|
|||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
# Editor directories and files
|
||||
tsconfig.tsbuildinfo
|
50
README.md
50
README.md
|
@ -2,12 +2,52 @@
|
|||
|
||||
## 功能展示
|
||||
|
||||
点击 表名 或 注释内容 跳转到另一个页面
|
||||
点击 `表名` 或 `注释内容` 跳转到另一个页面
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
<video src="./images/QQ202546-141117.mp4"></video>
|
||||
## 内置字段
|
||||
|
||||
```java
|
||||
// vm 不能直接写 `{` 需要转换下
|
||||
context.put("leftBrace", "{");
|
||||
|
||||
// 当前的表名
|
||||
context.put("tableName", tableMetaData.getTableName());
|
||||
|
||||
// 当前表的列信息
|
||||
context.put("columnInfoList", columnInfoList);
|
||||
|
||||
// 数据库sql列
|
||||
context.put("baseColumnList", String.join(",", list));
|
||||
|
||||
// 当前日期
|
||||
String date = new SimpleDateFormat(dto.getSimpleDateFormat()).format(new Date());
|
||||
context.put("date", date);
|
||||
|
||||
// 作者名字
|
||||
context.put("author", dto.getAuthor());
|
||||
|
||||
// 每个 Controller 上的请求前缀
|
||||
context.put("requestMapping", dto.getRequestMapping());
|
||||
|
||||
// 表字段的注释内容
|
||||
context.put("comment", dto.getComment());
|
||||
|
||||
// 设置包名称
|
||||
context.put("package", dto.getPackageName());
|
||||
|
||||
// 将类名称转成小驼峰
|
||||
String toCamelCase = TypeConvertCore.convertToCamelCase(replaceTableName);
|
||||
context.put("classLowercaseName", toCamelCase);
|
||||
|
||||
// 将类名称转成大驼峰
|
||||
String convertToCamelCase = TypeConvertCore.convertToCamelCase(replaceTableName, true);
|
||||
context.put("classUppercaseName", convertToCamelCase);
|
||||
```
|
||||
|
||||

|
|
@ -47,11 +47,6 @@
|
|||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
<version>3.49.1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
|
@ -64,6 +59,11 @@
|
|||
<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>
|
||||
|
@ -74,6 +74,7 @@
|
|||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- knife4j -->
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
|
@ -89,17 +90,24 @@
|
|||
<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>
|
|
@ -7,16 +7,23 @@ import io.swagger.v3.oas.models.info.Info;
|
|||
import io.swagger.v3.oas.models.info.License;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springdoc.core.models.GroupedOpenApi;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@Slf4j
|
||||
public class Knife4jConfig {
|
||||
|
||||
@Value("${server.port}")
|
||||
private String port;
|
||||
|
||||
@Bean
|
||||
public OpenAPI openAPI() {
|
||||
String url = "http://localhost:" + port;
|
||||
|
||||
// 作者等信息
|
||||
Contact contact = new Contact().name("Bunny").email("1319900154@qq.com").url("http://localhost:9999");
|
||||
Contact contact = new Contact().name("Bunny").email("1319900154@qq.com").url(url);
|
||||
// 使用协议
|
||||
License license = new License().name("MIT").url("https://mit-license.org");
|
||||
// 相关信息
|
||||
|
@ -24,7 +31,7 @@ public class Knife4jConfig {
|
|||
.contact(contact).license(license)
|
||||
.description("Bunny代码生成器")
|
||||
.summary("Bunny的代码生成器")
|
||||
.termsOfService("http://localhost:9999")
|
||||
.termsOfService(url)
|
||||
.version("v1.0.0");
|
||||
|
||||
return new OpenAPI().info(info).externalDocs(new ExternalDocumentation());
|
|
@ -0,0 +1,39 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package cn.bunny.controller;
|
||||
|
||||
import cn.bunny.domain.entity.ColumnMetaData;
|
||||
import cn.bunny.domain.entity.DatabaseInfoMetaData;
|
||||
import cn.bunny.domain.entity.TableMetaData;
|
||||
import cn.bunny.domain.result.Result;
|
||||
import cn.bunny.service.TableService;
|
||||
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.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Tag(name = "数据库表控制器", description = "数据库表信息接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/table")
|
||||
public class TableController {
|
||||
|
||||
@Resource
|
||||
private TableService tableService;
|
||||
|
||||
@Operation(summary = "当前数据库信息", description = "当前连接的数据库信息")
|
||||
@GetMapping("databaseInfoMetaData")
|
||||
public Result<DatabaseInfoMetaData> databaseInfoMetaData() {
|
||||
DatabaseInfoMetaData databaseInfoMetaData = tableService.databaseInfoMetaData();
|
||||
return Result.success(databaseInfoMetaData);
|
||||
}
|
||||
|
||||
@Operation(summary = "数据库所有的表", description = "获取[当前/所有]数据库表")
|
||||
@GetMapping("databaseTableList")
|
||||
public Result<List<TableMetaData>> databaseTableList(String dbName) {
|
||||
List<TableMetaData> list = tableService.databaseTableList(dbName);
|
||||
return Result.success(list);
|
||||
}
|
||||
|
||||
@Operation(summary = "表属性", description = "获取当前查询表属性")
|
||||
@GetMapping("tableMetaData")
|
||||
public Result<TableMetaData> tableMetaData(String tableName) {
|
||||
TableMetaData tableMetaData = tableService.tableMetaData(tableName);
|
||||
return Result.success(tableMetaData);
|
||||
}
|
||||
|
||||
@Operation(summary = "表的列属性", description = "获取当前查询表中列属性")
|
||||
@GetMapping("tableColumnInfo")
|
||||
public Result<List<ColumnMetaData>> tableColumnInfo(String tableName) {
|
||||
List<ColumnMetaData> columnInfo = tableService.tableColumnInfo(tableName);
|
||||
return Result.success(columnInfo);
|
||||
}
|
||||
}
|
|
@ -1,12 +1,13 @@
|
|||
package cn.bunny.controller;
|
||||
|
||||
import cn.bunny.dao.dto.VmsArgumentDto;
|
||||
import cn.bunny.dao.result.Result;
|
||||
import cn.bunny.dao.vo.GeneratorVo;
|
||||
import cn.bunny.dao.vo.VmsPathVo;
|
||||
import cn.bunny.domain.dto.VmsArgumentDto;
|
||||
import cn.bunny.domain.result.Result;
|
||||
import cn.bunny.domain.vo.GeneratorVo;
|
||||
import cn.bunny.domain.vo.VmsPathVo;
|
||||
import cn.bunny.service.VmsService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
@ -19,16 +20,13 @@ import java.util.Map;
|
|||
@RequestMapping("/api/vms")
|
||||
public class VmsController {
|
||||
|
||||
private final VmsService vmsService;
|
||||
|
||||
public VmsController(VmsService vmsService) {
|
||||
this.vmsService = vmsService;
|
||||
}
|
||||
@Resource
|
||||
private VmsService vmsService;
|
||||
|
||||
@Operation(summary = "获取vms文件路径", description = "获取所有vms下的文件路径")
|
||||
@GetMapping("getVmsPathList")
|
||||
public Result<Map<String, List<VmsPathVo>>> getVmsPathList() {
|
||||
Map<String, List<VmsPathVo>> list = vmsService.getVmsPathList();
|
||||
@GetMapping("vmsResourcePathList")
|
||||
public Result<Map<String, List<VmsPathVo>>> vmsResourcePathList() {
|
||||
Map<String, List<VmsPathVo>> list = vmsService.vmsResourcePathList();
|
||||
return Result.success(list);
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
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,92 +1,56 @@
|
|||
package cn.bunny.utils;
|
||||
package cn.bunny.core.factory;
|
||||
|
||||
import cn.bunny.dao.entity.ColumnMetaData;
|
||||
import cn.bunny.dao.entity.TableMetaData;
|
||||
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 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.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
@Component
|
||||
public class DbInfoUtil {
|
||||
public class ConcreteDatabaseInfo extends AbstractDatabaseInfo {
|
||||
|
||||
private final DataSource dataSource;
|
||||
|
||||
public DbInfoUtil(DataSource dataSource) {
|
||||
this.dataSource = dataSource;
|
||||
}
|
||||
@Value("${bunny.master.database}")
|
||||
private String currentDatabase;
|
||||
|
||||
/**
|
||||
* 获取表的所有主键列名
|
||||
* 数据库所有的信息
|
||||
*
|
||||
* @param tableName 表名
|
||||
* @return 主键列名的集合
|
||||
* @return 当前连接的数据库信息属性
|
||||
*/
|
||||
public Set<String> getPrimaryKeyColumns(String tableName) throws SQLException {
|
||||
// 主键的key
|
||||
Set<String> primaryKeys = new HashSet<>();
|
||||
|
||||
@SneakyThrows
|
||||
public DatabaseInfoMetaData databaseInfoMetaData() {
|
||||
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;
|
||||
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 dbName 数据库名称,如果不传为数据库中所有的表
|
||||
* @return 当前/所有 的数据库表
|
||||
* @param tableName 表名称或sql
|
||||
* @return 表西悉尼
|
||||
*/
|
||||
public List<TableMetaData> getDbTableList(String dbName) throws SQLException {
|
||||
// 所有的表属性
|
||||
List<TableMetaData> list = 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 = tableInfo(dbName);
|
||||
list.add(tableMetaData);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表注释信息
|
||||
*
|
||||
* @param tableName 数据库表名
|
||||
* @return 表信息
|
||||
* @throws SQLException SQLException
|
||||
*/
|
||||
public TableMetaData tableInfo(String tableName) throws SQLException {
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public TableMetaData getTableMetadata(String tableName) {
|
||||
TableMetaData tableMetaData;
|
||||
|
||||
try (Connection connection = dataSource.getConnection()) {
|
||||
|
@ -119,18 +83,47 @@ public class DbInfoUtil {
|
|||
}
|
||||
|
||||
/**
|
||||
* 数据库表列信息
|
||||
* 获取[当前/所有]数据库表
|
||||
*
|
||||
* @param tableName 表名
|
||||
* @return 列表信息
|
||||
* @throws SQLException SQLException
|
||||
* @return 所有表信息
|
||||
*/
|
||||
public List<ColumnMetaData> columnInfo(String tableName) throws SQLException {
|
||||
@SneakyThrows
|
||||
public List<TableMetaData> databaseTableList(String dbName) {
|
||||
// 当前数据库数据库所有的表
|
||||
List<TableMetaData> allTableInfo = new ArrayList<>();
|
||||
|
||||
try (Connection connection = dataSource.getConnection()) {
|
||||
DatabaseMetaData metaData = connection.getMetaData();
|
||||
|
||||
List<ColumnMetaData> columns = new ArrayList<>();
|
||||
// 当前数据库中所有的表
|
||||
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);
|
||||
|
||||
|
@ -138,25 +131,24 @@ public class DbInfoUtil {
|
|||
try (ResultSet columnsRs = metaData.getColumns(null, null, tableName, null)) {
|
||||
while (columnsRs.next()) {
|
||||
ColumnMetaData column = new ColumnMetaData();
|
||||
|
||||
// 列字段
|
||||
String columnName = columnsRs.getString("COLUMN_NAME");
|
||||
|
||||
// 将当前表的列类型转成 Java 类型
|
||||
String javaType = ConvertUtil.convertToJavaType(column.getJdbcType());
|
||||
// 数据库类型
|
||||
String typeName = columnsRs.getString("TYPE_NAME");
|
||||
|
||||
// 设置列字段
|
||||
column.setColumnName(columnName);
|
||||
// 列字段转成 下划线 -> 小驼峰
|
||||
column.setFieldName(ConvertUtil.convertToFieldName(column.getColumnName()));
|
||||
|
||||
column.setLowercaseName(TypeConvertUtil.convertToCamelCase(column.getColumnName()));
|
||||
// 列字段转成 下划线 -> 大驼峰名称
|
||||
column.setUppercaseName(TypeConvertUtil.convertToCamelCase(column.getColumnName(), true));
|
||||
// 字段类型
|
||||
column.setJdbcType(columnsRs.getString("TYPE_NAME"));
|
||||
column.setJdbcType(typeName);
|
||||
// 字段类型转 Java 类型
|
||||
String javaType = TypeConvertUtil.convertToJavaType(typeName);
|
||||
column.setJavaType(javaType);
|
||||
// 字段类型转 JavaScript 类型
|
||||
column.setJavascriptType(StringUtils.uncapitalize(javaType));
|
||||
|
||||
// 备注信息
|
||||
column.setComment(columnsRs.getString("REMARKS"));
|
||||
|
||||
|
@ -167,29 +159,11 @@ public class DbInfoUtil {
|
|||
column.setIsPrimaryKey(isPrimaryKey);
|
||||
}
|
||||
|
||||
columns.add(column);
|
||||
map.putIfAbsent(column.getColumnName(), column);
|
||||
}
|
||||
}
|
||||
|
||||
columns.get(0).setIsPrimaryKey(true);
|
||||
|
||||
return columns;
|
||||
return new ArrayList<>(map.values());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库所有的信息
|
||||
*
|
||||
* @param tableName 表名
|
||||
* @return 表内容
|
||||
* @throws SQLException SQLException
|
||||
*/
|
||||
public TableMetaData dbInfo(String tableName) throws SQLException {
|
||||
List<ColumnMetaData> columnMetaData = columnInfo(tableName);
|
||||
TableMetaData tableMetaData = tableInfo(tableName);
|
||||
|
||||
tableMetaData.setColumns(columnMetaData);
|
||||
|
||||
return tableMetaData;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
package cn.bunny.core.factory;
|
||||
|
||||
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.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Component
|
||||
public class ConcreteSqlParserDatabaseInfo extends AbstractDatabaseInfo {
|
||||
/**
|
||||
* 解析 sql 表信息
|
||||
*
|
||||
* @param sql 表名称或sql
|
||||
* @return 表西悉尼
|
||||
*/
|
||||
@Override
|
||||
public TableMetaData getTableMetadata(String sql) {
|
||||
TableMetaData tableInfo = new TableMetaData();
|
||||
|
||||
// 解析sql
|
||||
Statement statement;
|
||||
try {
|
||||
statement = CCJSqlParserUtil.parse(sql);
|
||||
} catch (JSQLParserException e) {
|
||||
throw new RuntimeException("SQL解析失败");
|
||||
}
|
||||
if (!(statement instanceof CreateTable createTable)) {
|
||||
throw new IllegalArgumentException("缺少SQL语句");
|
||||
}
|
||||
|
||||
// 设置表基本信息
|
||||
String tableName = createTable.getTable().getName().replaceAll("`", "");
|
||||
tableInfo.setTableName(tableName);
|
||||
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));
|
||||
}
|
||||
|
||||
return tableInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前表的列属性
|
||||
*
|
||||
* @param sql 表名称或sql
|
||||
* @return 当前表所有的列内容
|
||||
*/
|
||||
@Override
|
||||
public List<ColumnMetaData> tableColumnInfo(String sql) {
|
||||
// 解析sql
|
||||
Statement statement;
|
||||
try {
|
||||
statement = CCJSqlParserUtil.parse(sql);
|
||||
} catch (JSQLParserException e) {
|
||||
throw new RuntimeException("SQL解析失败");
|
||||
}
|
||||
|
||||
if (!(statement instanceof CreateTable createTable)) {
|
||||
throw new IllegalArgumentException("缺少SQL语句");
|
||||
}
|
||||
|
||||
return 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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
package cn.bunny.core.template;
|
||||
|
||||
import cn.bunny.domain.dto.VmsArgumentDto;
|
||||
import cn.bunny.utils.TypeConvertUtil;
|
||||
import org.apache.velocity.Template;
|
||||
import org.apache.velocity.VelocityContext;
|
||||
import org.apache.velocity.app.Velocity;
|
||||
|
||||
import java.io.StringWriter;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 使用模板方法,方便扩展
|
||||
* 如果需要继承 AbstractVmsGenerator
|
||||
*/
|
||||
public class VmsArgumentDtoBaseVmsGeneratorTemplate extends AbstractVmsGeneratorTemplate {
|
||||
|
||||
private final VmsArgumentDto dto;
|
||||
private final String path;
|
||||
|
||||
/**
|
||||
* @param dto 类名称可以自定义,格式为 xxx_xxx
|
||||
* @param path 当前路径
|
||||
*/
|
||||
public VmsArgumentDtoBaseVmsGeneratorTemplate(VmsArgumentDto dto, String path) {
|
||||
this.dto = dto;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加生成内容
|
||||
*
|
||||
* @param context VelocityContext
|
||||
*/
|
||||
@Override
|
||||
void addContext(VelocityContext context) {
|
||||
// 当前日期
|
||||
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 className = dto.getClassName();
|
||||
// 去除表开头前缀
|
||||
String tablePrefixes = dto.getTablePrefixes();
|
||||
// 将 表前缀 转成数组
|
||||
String replaceTableName = "";
|
||||
for (String prefix : tablePrefixes.split("[,,]")) {
|
||||
replaceTableName = className.replace(prefix, "");
|
||||
}
|
||||
|
||||
// 将类名称转成小驼峰
|
||||
String toCamelCase = TypeConvertUtil.convertToCamelCase(replaceTableName);
|
||||
context.put("classLowercaseName", toCamelCase);
|
||||
|
||||
// 将类名称转成大驼峰
|
||||
String convertToCamelCase = TypeConvertUtil.convertToCamelCase(replaceTableName, true);
|
||||
context.put("classUppercaseName", convertToCamelCase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Velocity 生成模板
|
||||
*
|
||||
* @param context VelocityContext
|
||||
* @param writer StringWriter 写入
|
||||
*/
|
||||
@Override
|
||||
void templateMerge(VelocityContext context, StringWriter writer) {
|
||||
// Velocity 生成模板
|
||||
Template servicePathTemplate = Velocity.getTemplate("vms/" + path, "UTF-8");
|
||||
servicePathTemplate.merge(context, writer);
|
||||
}
|
||||
}
|
|
@ -1,17 +1,15 @@
|
|||
package cn.bunny.dao.dto;
|
||||
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.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class VmsArgumentDto {
|
||||
|
@ -43,6 +41,13 @@ public class VmsArgumentDto {
|
|||
/* 去除表前缀 */
|
||||
private String tablePrefixes = "t_,sys_,qrtz_,log_";
|
||||
|
||||
/* 注释内容 */
|
||||
private String comment;
|
||||
|
||||
/* 路径 */
|
||||
private List<String> path;
|
||||
|
||||
/* SQL 语句 */
|
||||
private String sql;
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package cn.bunny.dao.entity;
|
||||
package cn.bunny.domain.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
|
@ -14,8 +14,11 @@ public class ColumnMetaData {
|
|||
/* 列名称 */
|
||||
private String columnName;
|
||||
|
||||
/* 字段名称 */
|
||||
private String fieldName;
|
||||
/* 字段名称,小驼峰名称 */
|
||||
private String lowercaseName;
|
||||
|
||||
/* 大驼峰名称 */
|
||||
private String uppercaseName;
|
||||
|
||||
/* 数据库字段类型 */
|
||||
private String jdbcType;
|
|
@ -0,0 +1,40 @@
|
|||
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,4 +1,4 @@
|
|||
package cn.bunny.dao.entity;
|
||||
package cn.bunny.domain.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
|
@ -1,4 +1,4 @@
|
|||
package cn.bunny.dao.result;
|
||||
package cn.bunny.domain.result;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
@ -16,19 +16,19 @@ import java.util.List;
|
|||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
@Schema(name = "PageResult 对象" , title = "分页返回结果" , description = "分页返回结果" )
|
||||
@Schema(name = "PageResult 对象", title = "分页返回结果", description = "分页返回结果")
|
||||
public class PageResult<T> implements Serializable {
|
||||
|
||||
@Schema(name = "pageNo" , title = "当前页" )
|
||||
@Schema(name = "pageNo", title = "当前页")
|
||||
private Long pageNo;
|
||||
|
||||
@Schema(name = "pageSize" , title = "每页记录数" )
|
||||
@Schema(name = "pageSize", title = "每页记录数")
|
||||
private Long pageSize;
|
||||
|
||||
@Schema(name = "total" , title = "总记录数" )
|
||||
@Schema(name = "total", title = "总记录数")
|
||||
private Long total;
|
||||
|
||||
@Schema(name = "list" , title = "当前页数据集合" )
|
||||
@Schema(name = "list", title = "当前页数据集合")
|
||||
private List<T> list;
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package cn.bunny.dao.result;
|
||||
package cn.bunny.domain.result;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
|
@ -0,0 +1,90 @@
|
|||
package cn.bunny.domain.result;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 统一返回结果状态信息类
|
||||
*/
|
||||
@Getter
|
||||
public enum ResultCodeEnum {
|
||||
// 成功操作 200
|
||||
SUCCESS(200, "操作成功"),
|
||||
ADD_SUCCESS(200, "添加成功"),
|
||||
UPDATE_SUCCESS(200, "修改成功"),
|
||||
DELETE_SUCCESS(200, "删除成功"),
|
||||
SORT_SUCCESS(200, "排序成功"),
|
||||
SUCCESS_UPLOAD(200, "上传成功"),
|
||||
SUCCESS_LOGOUT(200, "退出成功"),
|
||||
LOGOUT_SUCCESS(200, "退出成功"),
|
||||
EMAIL_CODE_REFRESH(200, "邮箱验证码已刷新"),
|
||||
EMAIL_CODE_SEND_SUCCESS(200, "邮箱验证码已发送"),
|
||||
|
||||
// 验证错误 201
|
||||
USERNAME_OR_PASSWORD_NOT_EMPTY(201, "用户名或密码不能为空"),
|
||||
EMAIL_CODE_NOT_EMPTY(201, "邮箱验证码不能为空"),
|
||||
SEND_EMAIL_CODE_NOT_EMPTY(201, "请先发送邮箱验证码"),
|
||||
EMAIL_CODE_NOT_MATCHING(201, "邮箱验证码不匹配"),
|
||||
LOGIN_ERROR(500, "账号或密码错误"),
|
||||
LOGIN_ERROR_USERNAME_PASSWORD_NOT_EMPTY(201, "登录信息不能为空"),
|
||||
GET_BUCKET_EXCEPTION(201, "获取文件信息失败"),
|
||||
SEND_MAIL_CODE_ERROR(201, "邮件发送失败"),
|
||||
EMAIL_CODE_EMPTY(201, "邮箱验证码过期或不存在"),
|
||||
EMAIL_EXIST(201, "邮箱已存在"),
|
||||
REQUEST_IS_EMPTY(201, "请求数据为空"),
|
||||
DATA_TOO_LARGE(201, "请求数据为空"),
|
||||
UPDATE_NEW_PASSWORD_SAME_AS_OLD_PASSWORD(201, "新密码与密码相同"),
|
||||
|
||||
// 数据相关 206
|
||||
ILLEGAL_REQUEST(206, "非法请求"),
|
||||
REPEAT_SUBMIT(206, "重复提交"),
|
||||
DATA_ERROR(206, "数据异常"),
|
||||
EMAIL_USER_TEMPLATE_IS_EMPTY(206, "邮件模板为空"),
|
||||
EMAIL_TEMPLATE_IS_EMPTY(206, "邮件模板为空"),
|
||||
EMAIL_USER_IS_EMPTY(206, "关联邮件用户配置为空"),
|
||||
DATA_EXIST(206, "数据已存在"),
|
||||
DATA_NOT_EXIST(206, "数据不存在"),
|
||||
ALREADY_USER_EXCEPTION(206, "用户已存在"),
|
||||
USER_IS_EMPTY(206, "用户不存在"),
|
||||
FILE_NOT_EXIST(206, "文件不存在"),
|
||||
NEW_PASSWORD_SAME_OLD_PASSWORD(206, "新密码不能和旧密码相同"),
|
||||
MISSING_TEMPLATE_FILES(206, "缺少模板文件"),
|
||||
|
||||
// 身份过期 208
|
||||
LOGIN_AUTH(208, "请先登陆"),
|
||||
AUTHENTICATION_EXPIRED(208, "身份验证过期"),
|
||||
SESSION_EXPIRATION(208, "会话过期"),
|
||||
|
||||
// 209
|
||||
THE_SAME_USER_HAS_LOGGED_IN(209, "相同用户已登录"),
|
||||
|
||||
// 提示错误
|
||||
UPDATE_ERROR(216, "修改失败"),
|
||||
URL_ENCODE_ERROR(216, "URL编码失败"),
|
||||
ILLEGAL_CALLBACK_REQUEST_ERROR(217, "非法回调请求"),
|
||||
FETCH_USERINFO_ERROR(219, "获取用户信息失败"),
|
||||
ILLEGAL_DATA_REQUEST(219, "非法数据请求"),
|
||||
CLASS_NOT_FOUND(219, "类名不存在"),
|
||||
ADMIN_ROLE_CAN_NOT_DELETED(219, "无法删除admin角色"),
|
||||
ROUTER_RANK_NEED_LARGER_THAN_THE_PARENT(219, "设置路由等级需要大于或等于父级的路由等级"),
|
||||
|
||||
// 无权访问 403
|
||||
FAIL_NO_ACCESS_DENIED(403, "无权访问"),
|
||||
FAIL_NO_ACCESS_DENIED_USER_OFFLINE(403, "用户强制下线"),
|
||||
TOKEN_PARSING_FAILED(403, "token解析失败"),
|
||||
FAIL_NO_ACCESS_DENIED_USER_LOCKED(403, "该账户已封禁"),
|
||||
|
||||
// 系统错误 500
|
||||
UNKNOWN_EXCEPTION(500, "服务异常"),
|
||||
SERVICE_ERROR(500, "服务异常"),
|
||||
UPLOAD_ERROR(500, "上传失败"),
|
||||
FAIL(500, "失败"),
|
||||
;
|
||||
|
||||
private final Integer code;
|
||||
private final String message;
|
||||
|
||||
ResultCodeEnum(Integer code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package cn.bunny.dao.vo;
|
||||
package cn.bunny.domain.vo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
|
@ -1,4 +1,4 @@
|
|||
package cn.bunny.dao.vo;
|
||||
package cn.bunny.domain.vo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
|
@ -1,6 +1,6 @@
|
|||
package cn.bunny.exception;
|
||||
|
||||
import cn.bunny.dao.result.ResultCodeEnum;
|
||||
import cn.bunny.domain.result.ResultCodeEnum;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
@ -10,7 +10,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||
@Getter
|
||||
@ToString
|
||||
@Slf4j
|
||||
public class AuthCustomerException extends RuntimeException {
|
||||
public class GeneratorCodeException extends RuntimeException {
|
||||
// 状态码
|
||||
Integer code;
|
||||
|
||||
|
@ -20,19 +20,18 @@ public class AuthCustomerException extends RuntimeException {
|
|||
// 返回结果状态
|
||||
ResultCodeEnum resultCodeEnum;
|
||||
|
||||
|
||||
public AuthCustomerException(Integer code, String message) {
|
||||
public GeneratorCodeException(Integer code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public AuthCustomerException(String message) {
|
||||
public GeneratorCodeException(String message) {
|
||||
super(message);
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public AuthCustomerException(ResultCodeEnum codeEnum) {
|
||||
public GeneratorCodeException(ResultCodeEnum codeEnum) {
|
||||
super(codeEnum.getMessage());
|
||||
this.code = codeEnum.getCode();
|
||||
this.message = codeEnum.getMessage();
|
|
@ -1,8 +1,8 @@
|
|||
package cn.bunny.exception;
|
||||
|
||||
|
||||
import cn.bunny.dao.result.Result;
|
||||
import cn.bunny.dao.result.ResultCodeEnum;
|
||||
import cn.bunny.domain.result.Result;
|
||||
import cn.bunny.domain.result.ResultCodeEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.support.DefaultMessageSourceResolvable;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
@ -11,7 +11,6 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
|
|||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import java.nio.file.AccessDeniedException;
|
||||
import java.sql.SQLIntegrityConstraintViolationException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
@ -24,9 +23,9 @@ import java.util.stream.Collectors;
|
|||
@Slf4j
|
||||
public class GlobalExceptionHandler {
|
||||
// 自定义异常信息
|
||||
@ExceptionHandler(AuthCustomerException.class)
|
||||
@ExceptionHandler(GeneratorCodeException.class)
|
||||
@ResponseBody
|
||||
public Result<Object> exceptionHandler(AuthCustomerException exception) {
|
||||
public Result<Object> exceptionHandler(GeneratorCodeException exception) {
|
||||
Integer code = exception.getCode() != null ? exception.getCode() : 500;
|
||||
return Result.error(null, code, exception.getMessage());
|
||||
}
|
||||
|
@ -50,24 +49,24 @@ public class GlobalExceptionHandler {
|
|||
String dataTooLongError = "Data too long for column (.*?) at row 1";
|
||||
Matcher dataTooLongErrorMatcher = Pattern.compile(dataTooLongError).matcher(message);
|
||||
if (dataTooLongErrorMatcher.find()) {
|
||||
return Result.error(null, 500, dataTooLongErrorMatcher.group(1) + " 字段数据过大" );
|
||||
return Result.error(null, 500, dataTooLongErrorMatcher.group(1) + " 字段数据过大");
|
||||
}
|
||||
|
||||
// 主键冲突
|
||||
String primaryKeyError = "Duplicate entry '(.*?)' for key .*";
|
||||
Matcher primaryKeyErrorMatcher = Pattern.compile(primaryKeyError).matcher(message);
|
||||
if (primaryKeyErrorMatcher.find()) {
|
||||
return Result.error(null, 500, "[" + primaryKeyErrorMatcher.group(1) + "]已存在" );
|
||||
return Result.error(null, 500, "[" + primaryKeyErrorMatcher.group(1) + "]已存在");
|
||||
}
|
||||
|
||||
// corn表达式错误
|
||||
String cronExpression = "CronExpression '(.*?)' is invalid";
|
||||
Matcher cronExpressionMatcher = Pattern.compile(cronExpression).matcher(message);
|
||||
if (cronExpressionMatcher.find()) {
|
||||
return Result.error(null, 500, "表达式 " + cronExpressionMatcher.group(1) + " 不合法" );
|
||||
return Result.error(null, 500, "表达式 " + cronExpressionMatcher.group(1) + " 不合法");
|
||||
}
|
||||
|
||||
log.error("GlobalExceptionHandler===>运行时异常信息:{}" , message);
|
||||
log.error("GlobalExceptionHandler===>运行时异常信息:{}", message);
|
||||
return Result.error(null, 500, message);
|
||||
}
|
||||
|
||||
|
@ -76,7 +75,7 @@ public class GlobalExceptionHandler {
|
|||
public Result<String> handleValidationExceptions(MethodArgumentNotValidException ex) {
|
||||
String errorMessage = ex.getBindingResult().getFieldErrors().stream()
|
||||
.map(DefaultMessageSourceResolvable::getDefaultMessage)
|
||||
.collect(Collectors.joining(", " ));
|
||||
.collect(Collectors.joining(", "));
|
||||
return Result.error(null, 201, errorMessage);
|
||||
}
|
||||
|
||||
|
@ -84,28 +83,19 @@ public class GlobalExceptionHandler {
|
|||
@ExceptionHandler(ArithmeticException.class)
|
||||
@ResponseBody
|
||||
public Result<Object> error(ArithmeticException exception) {
|
||||
log.error("GlobalExceptionHandler===>特定异常信息:{}" , exception.getMessage());
|
||||
log.error("GlobalExceptionHandler===>特定异常信息:{}", exception.getMessage());
|
||||
|
||||
return Result.error(null, 500, exception.getMessage());
|
||||
}
|
||||
|
||||
// spring security异常
|
||||
@ExceptionHandler(AccessDeniedException.class)
|
||||
@ResponseBody
|
||||
public Result<String> error(AccessDeniedException exception) throws AccessDeniedException {
|
||||
log.error("GlobalExceptionHandler===>spring security异常:{}" , exception.getMessage());
|
||||
|
||||
return Result.error(ResultCodeEnum.SERVICE_ERROR);
|
||||
}
|
||||
|
||||
// 处理SQL异常
|
||||
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
|
||||
@ResponseBody
|
||||
public Result<String> exceptionHandler(SQLIntegrityConstraintViolationException exception) {
|
||||
log.error("GlobalExceptionHandler===>处理SQL异常:{}" , exception.getMessage());
|
||||
log.error("GlobalExceptionHandler===>处理SQL异常:{}", exception.getMessage());
|
||||
|
||||
String message = exception.getMessage();
|
||||
if (message.contains("Duplicate entry" )) {
|
||||
if (message.contains("Duplicate entry")) {
|
||||
// 错误信息
|
||||
return Result.error(ResultCodeEnum.USER_IS_EMPTY);
|
||||
} else {
|
|
@ -0,0 +1,13 @@
|
|||
package cn.bunny.service;
|
||||
|
||||
import cn.bunny.domain.entity.TableMetaData;
|
||||
|
||||
public interface SqlParserService {
|
||||
/**
|
||||
* 解析SQL内容
|
||||
*
|
||||
* @param sql Sql语句
|
||||
* @return 表信息内容
|
||||
*/
|
||||
TableMetaData tableInfo(String sql);
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
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,8 +1,8 @@
|
|||
package cn.bunny.service;
|
||||
|
||||
import cn.bunny.dao.dto.VmsArgumentDto;
|
||||
import cn.bunny.dao.vo.GeneratorVo;
|
||||
import cn.bunny.dao.vo.VmsPathVo;
|
||||
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;
|
||||
|
||||
|
@ -23,7 +23,7 @@ public interface VmsService {
|
|||
*
|
||||
* @return vms下的文件路径
|
||||
*/
|
||||
Map<String, List<VmsPathVo>> getVmsPathList();
|
||||
Map<String, List<VmsPathVo>> vmsResourcePathList();
|
||||
|
||||
/**
|
||||
* 打包成zip下载
|
||||
|
@ -32,4 +32,6 @@ public interface VmsService {
|
|||
* @return zip 文件
|
||||
*/
|
||||
ResponseEntity<byte[]> downloadByZip(@Valid VmsArgumentDto dto);
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
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,21 +1,24 @@
|
|||
package cn.bunny.service.impl;
|
||||
|
||||
import cn.bunny.dao.dto.VmsArgumentDto;
|
||||
import cn.bunny.dao.entity.ColumnMetaData;
|
||||
import cn.bunny.dao.vo.GeneratorVo;
|
||||
import cn.bunny.dao.vo.TableInfoVo;
|
||||
import cn.bunny.dao.vo.VmsPathVo;
|
||||
import cn.bunny.service.TableService;
|
||||
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.apache.velocity.VelocityContext;
|
||||
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;
|
||||
|
@ -29,13 +32,13 @@ import java.util.zip.ZipEntry;
|
|||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
@Service
|
||||
|
||||
public class VmsServiceImpl implements VmsService {
|
||||
private final TableService tableService;
|
||||
|
||||
public VmsServiceImpl(TableService tableService) {
|
||||
this.tableService = tableService;
|
||||
}
|
||||
@Resource
|
||||
ConcreteDatabaseInfo databaseInfoCore;
|
||||
|
||||
@Resource
|
||||
ConcreteSqlParserDatabaseInfo sqlParserDatabaseInfo;
|
||||
|
||||
/**
|
||||
* 生成服务端代码
|
||||
|
@ -46,35 +49,25 @@ public class VmsServiceImpl implements VmsService {
|
|||
@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 -> {
|
||||
StringWriter writer = new StringWriter();
|
||||
|
||||
// 表格属性名 和 列信息
|
||||
TableInfoVo tableMetaData = tableService.getTableMetaData(tableName);
|
||||
List<ColumnMetaData> columnInfoList = tableService.getColumnInfo(tableName);
|
||||
List<String> list = columnInfoList.stream().map(ColumnMetaData::getColumnName).toList();
|
||||
|
||||
// 添加要生成的属性
|
||||
VelocityContext context = new VelocityContext();
|
||||
|
||||
// 当前的表名
|
||||
context.put("tableName", tableMetaData.getTableName());
|
||||
|
||||
// 表字段的注释内容
|
||||
context.put("comment", tableMetaData.getComment());
|
||||
|
||||
// 设置包名称
|
||||
context.put("package", dto.getPackageName());
|
||||
|
||||
// 当前表的列信息
|
||||
context.put("columnInfoList", columnInfoList);
|
||||
|
||||
// 数据库sql列
|
||||
context.put("baseColumnList", String.join(",", list));
|
||||
|
||||
// 生成模板
|
||||
VmsUtil.commonVms(writer, context, "vms/" + path, dto);
|
||||
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())
|
||||
|
@ -92,7 +85,7 @@ public class VmsServiceImpl implements VmsService {
|
|||
*/
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public Map<String, List<VmsPathVo>> getVmsPathList() {
|
||||
public Map<String, List<VmsPathVo>> vmsResourcePathList() {
|
||||
// 读取当前项目中所有的 vm 模板发给前端
|
||||
List<String> vmsRelativeFiles = ResourceFileUtil.getRelativeFiles("vms");
|
||||
|
|
@ -15,6 +15,7 @@ import java.util.jar.JarEntry;
|
|||
import java.util.jar.JarFile;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/* 当前生产/开发下的资源文件 */
|
||||
public class ResourceFileUtil {
|
||||
|
||||
/**
|
|
@ -0,0 +1,61 @@
|
|||
package cn.bunny.utils;
|
||||
|
||||
import com.google.common.base.CaseFormat;
|
||||
import org.assertj.core.util.introspection.CaseFormatUtils;
|
||||
|
||||
/* 类型转换,数据库转Java类型等 */
|
||||
public class TypeConvertUtil {
|
||||
|
||||
/**
|
||||
* 将数据库类型转换为Java类型
|
||||
*/
|
||||
public static String convertToJavaType(String columnType) {
|
||||
if (columnType == null) return "Object";
|
||||
|
||||
columnType = columnType.toLowerCase();
|
||||
return switch (columnType) {
|
||||
case "varchar", "char", "text", "longtext", "mediumtext", "tinytext" -> "String";
|
||||
case "int", "integer", "tinyint", "smallint" -> "Integer";
|
||||
case "bigint" -> "Long";
|
||||
case "decimal", "numeric" -> "BigDecimal";
|
||||
case "float" -> "Float";
|
||||
case "double" -> "Double";
|
||||
case "boolean", "bit", "tinyint unsigned" -> "Boolean";
|
||||
case "date", "year" -> "Date";
|
||||
case "time" -> "Time";
|
||||
case "datetime", "timestamp" -> "LocalDateTime";
|
||||
case "blob", "longblob", "mediumblob", "tinyblob" -> "byte[]";
|
||||
default -> "Object";
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 下划线命名转驼峰命名
|
||||
*/
|
||||
public static String convertToCamelCase(String name) {
|
||||
return convertToCamelCase(name, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下划线命名转驼峰命名
|
||||
*
|
||||
* @param name 原始名称,传入的值可以是
|
||||
* `xxx_xxx` `CaseFormat`
|
||||
* `caseFormat`
|
||||
* @param firstLetterCapital 首字母是否大写
|
||||
*/
|
||||
public static String convertToCamelCase(String name, boolean firstLetterCapital) {
|
||||
if (name == null || name.isEmpty()) return name;
|
||||
|
||||
// 转成小驼峰
|
||||
String lowerCamelCase = CaseFormatUtils.toCamelCase(name);
|
||||
|
||||
// 首字母不大写
|
||||
if (!firstLetterCapital) {
|
||||
return lowerCamelCase;
|
||||
}
|
||||
|
||||
// 将小驼峰转成大驼峰
|
||||
return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, lowerCamelCase);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
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,7 +1,6 @@
|
|||
bunny:
|
||||
master:
|
||||
# host: 192.168.3.137
|
||||
host: localhost
|
||||
host: 192.168.3.137
|
||||
port: 3306
|
||||
database: auth_admin
|
||||
username: root
|
|
@ -5,7 +5,7 @@ bunny:
|
|||
database: auth_admin
|
||||
username: root
|
||||
password: "123456"
|
||||
connect:
|
||||
url: jdbc:sqlite::resource:database.sqlite
|
||||
username: root
|
||||
password: "123456"
|
||||
# connect:
|
||||
# url: jdbc:sqlite::resource:database.sqlite
|
||||
# username: root
|
||||
# password: "123456"
|
|
@ -1,5 +1,5 @@
|
|||
server:
|
||||
port: 9999
|
||||
port: 8800
|
||||
|
||||
spring:
|
||||
profiles:
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
|
@ -1,63 +1,63 @@
|
|||
package ${package}.controller;
|
||||
|
||||
import cn.bunny.dao.pojo.result.Result;
|
||||
import cn.bunny.dao.pojo.result.ResultCodeEnum;
|
||||
import cn.bunny.domain.pojo.result.Result;
|
||||
import cn.bunny.domain.pojo.result.ResultCodeEnum;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Mono;
|
||||
import cn.bunny.dao.pojo.result.PageResult;
|
||||
import cn.bunny.domain.pojo.result.PageResult;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* ${comment}表 前端控制器
|
||||
* ${comment} 前端控制器
|
||||
* </p>
|
||||
*
|
||||
* @author ${author}
|
||||
* @since ${date}
|
||||
*/
|
||||
@Tag(name = "${comment}", description = "${comment}相关接口" )
|
||||
@Tag(name = "${comment}" , description = "${comment}相关接口" )
|
||||
@RestController
|
||||
@RequestMapping("${requestMapping}/${classLowercaseName}" )
|
||||
public class ${classUppercaseName}Controller {
|
||||
|
||||
@Autowired
|
||||
@Resource
|
||||
private ${classUppercaseName}Service ${classLowercaseName}Service;
|
||||
|
||||
@Operation(summary = "分页查询${comment}", description = "分页查询${comment}" )
|
||||
@GetMapping("get${classUppercaseName}List/{page}/{limit}" )
|
||||
public Result<PageResult<${classUppercaseName}Vo>> get${classUppercaseName}List(
|
||||
@Parameter(name = "page", description = "当前页", required = true)
|
||||
@Operation(summary = "分页查询${comment}" , description = "分页${comment}" )
|
||||
@GetMapping("{page}/{limit}" )
|
||||
public Result<PageResult<${classUppercaseName}Vo>> get${classUppercaseName}Page(
|
||||
@Parameter(name = "page" , description = "当前页" , required = true)
|
||||
@PathVariable("page" ) Integer page,
|
||||
@Parameter(name = "limit", description = "每页记录数", required = true)
|
||||
@Parameter(name = "limit" , description = "每页记录数" , required = true)
|
||||
@PathVariable("limit" ) Integer limit,
|
||||
${classUppercaseName}Dto dto) {
|
||||
Page<${classUppercaseName}> pageParams = new Page<>(page, limit);
|
||||
PageResult<${classUppercaseName}Vo> pageResult = ${classLowercaseName}Service.get${classUppercaseName}List(pageParams, dto);
|
||||
PageResult<${classUppercaseName}Vo> pageResult = ${classLowercaseName}Service.get${classUppercaseName}Page(pageParams, dto);
|
||||
return Result.success(pageResult);
|
||||
}
|
||||
|
||||
@Operation(summary = "添加${comment}", description = "添加${comment}" )
|
||||
@PostMapping("add${classUppercaseName}" )
|
||||
public Result<String> add${classUppercaseName}(@Valid @RequestBody ${classUppercaseName}AddDto dto) {
|
||||
@Operation(summary = "添加${comment}" , description = "添加${comment}" )
|
||||
@PostMapping()
|
||||
public Result<String> add${classUppercaseName}(@Valid @RequestBody ${classUppercaseName}to dto) {
|
||||
${classLowercaseName}Service.add${classUppercaseName}(dto);
|
||||
return Result.success(ResultCodeEnum.ADD_SUCCESS);
|
||||
}
|
||||
|
||||
@Operation(summary = "更新${comment}", description = "更新${comment}" )
|
||||
@PutMapping("update${classUppercaseName}" )
|
||||
public Result<String> update${classUppercaseName}(@Valid @RequestBody ${classUppercaseName}UpdateDto dto) {
|
||||
@Operation(summary = "更新${comment}" , description = "更新${comment}" )
|
||||
@PutMapping()
|
||||
public Result<String> update${classUppercaseName}(@Valid @RequestBody ${classUppercaseName}Dto dto) {
|
||||
${classLowercaseName}Service.update${classUppercaseName}(dto);
|
||||
return Result.success(ResultCodeEnum.UPDATE_SUCCESS);
|
||||
}
|
||||
|
||||
@Operation(summary = "删除${comment}", description = "删除${comment}" )
|
||||
@DeleteMapping("delete${classUppercaseName}" )
|
||||
@Operation(summary = "删除${comment}" , description = "删除${comment}" )
|
||||
@DeleteMapping()
|
||||
public Result<String> delete${classUppercaseName}(@RequestBody List<Long> ids) {
|
||||
${classLowercaseName}Service.delete${classUppercaseName}(ids);
|
||||
return Result.success(ResultCodeEnum.DELETE_SUCCESS);
|
|
@ -0,0 +1,13 @@
|
|||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
@Schema(name = "${classUppercaseName}DTO对象", title = "${comment}", description = "${comment}的DTO对象")
|
||||
public class ${classUppercaseName}Dto {
|
||||
|
||||
#foreach($field in ${columnInfoList})
|
||||
@Schema(name = "${field.lowercaseName}", title = "${field.comment}")
|
||||
private ${field.javaType} ${field.lowercaseName};
|
||||
|
||||
#end
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
@EqualsAndHashCode(callSuper = true)
|
||||
@Getter
|
||||
@Setter
|
||||
@Accessors(chain = true)
|
||||
@TableName("${tableName}")
|
||||
@Schema(name = "${classUppercaseName}对象", title = "${comment}", description = "${comment}的实体类对象")
|
||||
public class ${classUppercaseName} extends BaseEntity {
|
||||
|
||||
#foreach($field in ${columnInfoList})
|
||||
@Schema(name = "${field.lowercaseName}", title = "${field.comment}")
|
||||
private ${field.javaType} ${field.lowercaseName};
|
||||
|
||||
#end
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(name = "${classUppercaseName}VO对象", title = "${comment}", description = "${comment}的VO对象")
|
||||
public class ${classUppercaseName}Vo {
|
||||
|
||||
#foreach($field in ${columnInfoList})
|
||||
@Schema(name = "${field.lowercaseName}", title = "${field.comment}")
|
||||
private ${field.javaType} ${field.lowercaseName};
|
||||
|
||||
#end
|
||||
}
|
||||
|
|
@ -27,10 +27,4 @@ public interface ${classUppercaseName}Mapper extends BaseMapper<${classUppercase
|
|||
*/
|
||||
IPage<${classUppercaseName}Vo> selectListByPage(@Param("page" ) Page<${classUppercaseName}> pageParams, @Param("dto" ) ${classUppercaseName}Dto dto);
|
||||
|
||||
/**
|
||||
* 物理删除${comment}
|
||||
*
|
||||
* @param ids 删除 id 列表
|
||||
*/
|
||||
void deleteBatchIdsWithPhysics(List<Long> ids);
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
<!-- 通用查询映射结果 -->
|
||||
<resultMap id="BaseResultMap" type="${classUppercaseName}">
|
||||
#foreach($field in ${columnInfoList})
|
||||
<id column="${field.column}" property="${field.fieldName}"/>
|
||||
<id column="${field.columnName}" property="${field.lowercaseName}"/>
|
||||
#end
|
||||
</resultMap>
|
||||
|
||||
|
@ -26,20 +26,11 @@
|
|||
<where>
|
||||
base.is_deleted = 0
|
||||
#foreach($field in $columnInfoList)
|
||||
<if test="dto.${field.fieldName} != null and dto.${field.fieldName} != ''">
|
||||
and base.${field.columnName} like CONCAT('%',#{dto.${field.fieldName}},'%')
|
||||
<if test="dto.${field.lowercaseName} != null and dto.${field.lowercaseName} != ''">
|
||||
and base.${field.columnName} like CONCAT('%',#{dto.${field.lowercaseName}},'%')
|
||||
</if>
|
||||
#end
|
||||
</where>
|
||||
</select>
|
||||
|
||||
<!-- 物理删除${comment} -->
|
||||
<delete id="deleteBatchIdsWithPhysics">
|
||||
delete from ${tableName}
|
||||
where id in
|
||||
<foreach collection="ids" item="id" open="(" close=")" separator=",">
|
||||
#{id}
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
</mapper>
|
|
@ -1,6 +1,6 @@
|
|||
package ${package}.service.impl;
|
||||
|
||||
import cn.bunny.dao.pojo.result.PageResult;
|
||||
import cn.bunny.domain.pojo.result.PageResult;
|
||||
import ${package}.mapper.${classUppercaseName}Mapper;
|
||||
import ${package}.service.${classUppercaseName}Service;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
|
@ -30,7 +30,7 @@ public class ${classUppercaseName}ServiceImpl extends ServiceImpl<${classUpperca
|
|||
* @return 查询分页${comment}返回对象
|
||||
*/
|
||||
@Override
|
||||
public PageResult<${classUppercaseName}Vo> get${classUppercaseName}List(Page<${classUppercaseName}> pageParams, ${classUppercaseName}Dto dto) {
|
||||
public PageResult<${classUppercaseName}Vo> get${classUppercaseName}Page(Page<${classUppercaseName}> pageParams, ${classUppercaseName}Dto dto) {
|
||||
IPage<${classUppercaseName}Vo> page = baseMapper.selectListByPage(pageParams, dto);
|
||||
|
||||
return PageResult.<${classUppercaseName}Vo>builder()
|
||||
|
@ -48,7 +48,6 @@ public class ${classUppercaseName}ServiceImpl extends ServiceImpl<${classUpperca
|
|||
*/
|
||||
@Override
|
||||
public void add${classUppercaseName}(@Valid ${classUppercaseName}AddDto dto) {
|
||||
// 保存数据
|
||||
${classUppercaseName} ${classLowercaseName} =new ${classUppercaseName}();
|
||||
BeanUtils.copyProperties(dto, ${classLowercaseName});
|
||||
save(${classLowercaseName});
|
||||
|
@ -61,7 +60,6 @@ public class ${classUppercaseName}ServiceImpl extends ServiceImpl<${classUpperca
|
|||
*/
|
||||
@Override
|
||||
public void update${classUppercaseName}(@Valid ${classUppercaseName}UpdateDto dto) {
|
||||
// 更新内容
|
||||
${classUppercaseName} ${classLowercaseName} =new ${classUppercaseName}();
|
||||
BeanUtils.copyProperties(dto, ${classLowercaseName});
|
||||
updateById(${classLowercaseName});
|
||||
|
@ -74,6 +72,6 @@ public class ${classUppercaseName}ServiceImpl extends ServiceImpl<${classUpperca
|
|||
*/
|
||||
@Override
|
||||
public void delete${classUppercaseName}(List<Long> ids) {
|
||||
baseMapper.deleteBatchIdsWithPhysics(ids);
|
||||
removeByIds(ids);
|
||||
}
|
||||
}
|
|
@ -1,12 +1,11 @@
|
|||
package ${package}.service;
|
||||
|
||||
import cn.bunny.dao.entity.system.MenuIcon;
|
||||
import cn.bunny.dao.pojo.result.PageResult;
|
||||
import cn.bunny.domain.entity.system.MenuIcon;
|
||||
import cn.bunny.domain.pojo.result.PageResult;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
@ -20,28 +19,28 @@ import java.util.List;
|
|||
public interface ${classUppercaseName}Service extends IService<${classUppercaseName}> {
|
||||
|
||||
/**
|
||||
* * 获取${comment}列表
|
||||
* 分页查询${comment}
|
||||
*
|
||||
* @return ${comment}返回列表
|
||||
* @return {@link ${classUppercaseName}Vo}
|
||||
*/
|
||||
PageResult<${classUppercaseName}Vo> get${classUppercaseName}List(Page<${classUppercaseName}> pageParams, ${classUppercaseName}Dto dto);
|
||||
PageResult<${classUppercaseName}Vo> get${classUppercaseName}Page(Page<${classUppercaseName}> pageParams, ${classUppercaseName}Dto dto);
|
||||
|
||||
/**
|
||||
* * 添加${comment}
|
||||
* 添加${comment}
|
||||
*
|
||||
* @param dto 添加表单
|
||||
*/
|
||||
void add${classUppercaseName}(@Valid ${classUppercaseName}AddDto dto);
|
||||
void add${classUppercaseName}(${classUppercaseName}AddDto dto);
|
||||
|
||||
/**
|
||||
* * 更新${comment}
|
||||
* 更新${comment}
|
||||
*
|
||||
* @param dto 更新表单
|
||||
* @param dto {@link ${classUppercaseName}UpdateDto}
|
||||
*/
|
||||
void update${classUppercaseName}(@Valid ${classUppercaseName}UpdateDto dto);
|
||||
void update${classUppercaseName}(${classUppercaseName}UpdateDto dto);
|
||||
|
||||
/**
|
||||
* * 删除|批量删除${comment}类型
|
||||
* 删除|批量删除${comment}类型
|
||||
*
|
||||
* @param ids 删除id列表
|
||||
*/
|
|
@ -23,7 +23,7 @@ export function onAdd() {
|
|||
props: {
|
||||
formInline: {
|
||||
#foreach($item in $columnInfoList)
|
||||
$!{item.fieldName}: undefined,
|
||||
$!{item.columnName}: undefined,
|
||||
#end
|
||||
},
|
||||
},
|
||||
|
@ -53,7 +53,7 @@ export function onUpdate(row: any) {
|
|||
props: {
|
||||
formInline: {
|
||||
#foreach($item in $columnInfoList)
|
||||
$!{item.fieldName}: row.$!{item.fieldName},
|
||||
$!{item.columnName}: row.$!{item.fieldName},
|
||||
#end
|
||||
}
|
||||
},
|
|
@ -16,7 +16,7 @@ export const use${classUppercaseName}Store = defineStore('${lowercaseName}Store'
|
|||
form: {
|
||||
#foreach($item in $columnInfoList)
|
||||
// $!{item.comment}
|
||||
$!{item.fieldName}: undefined,
|
||||
$!{item.columnName}: undefined,
|
||||
#end
|
||||
},
|
||||
// 分页查询结果
|
|
@ -3,9 +3,9 @@ export interface FormItemProps {
|
|||
#foreach($field in $columnInfoList)
|
||||
// $field.comment
|
||||
#if($field.javascriptType == "object")
|
||||
$field.fieldName: any
|
||||
$field.columnName: any
|
||||
#else
|
||||
$field.fieldName: $field.javascriptType
|
||||
$field.columnName: $field.javascriptType
|
||||
#end
|
||||
#end
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
package cn.bunny;
|
||||
|
||||
|
||||
import cn.bunny.dao.entity.ColumnMetaData;
|
||||
import cn.bunny.dao.entity.TableMetaData;
|
||||
import cn.bunny.utils.ConvertUtil;
|
||||
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;
|
||||
|
@ -19,9 +19,8 @@ import java.util.List;
|
|||
@SpringBootTest
|
||||
public class JDBCTest {
|
||||
|
||||
DatabaseMetaData metaData;
|
||||
|
||||
private final DataSource dataSource;
|
||||
DatabaseMetaData metaData;
|
||||
|
||||
public JDBCTest(DataSource dataSource) {
|
||||
this.dataSource = dataSource;
|
||||
|
@ -84,9 +83,9 @@ public class JDBCTest {
|
|||
while (columnsRs.next()) {
|
||||
ColumnMetaData column = new ColumnMetaData();
|
||||
column.setColumnName(columnsRs.getString("COLUMN_NAME"));
|
||||
column.setFieldName(ConvertUtil.convertToFieldName(column.getColumnName()));
|
||||
column.setLowercaseName(TypeConvertUtil.convertToCamelCase(column.getColumnName()));
|
||||
column.setJdbcType(columnsRs.getString("TYPE_NAME"));
|
||||
column.setJavaType(ConvertUtil.convertToJavaType(column.getJdbcType()));
|
||||
column.setJavaType(TypeConvertUtil.convertToJavaType(column.getJdbcType()));
|
||||
column.setComment(columnsRs.getString("REMARKS"));
|
||||
|
||||
columns.add(column);
|
|
@ -0,0 +1,105 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
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,6 +1,6 @@
|
|||
package cn.bunny.service.impl;
|
||||
|
||||
import cn.bunny.dao.vo.VmsPathVo;
|
||||
import cn.bunny.domain.vo.VmsPathVo;
|
||||
import cn.bunny.utils.ResourceFileUtil;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -15,7 +15,7 @@ class VmsServiceImplTest {
|
|||
|
||||
|
||||
@Test
|
||||
void getVmsPathList() throws IOException, URISyntaxException {
|
||||
void vmsResourcePathList() throws IOException, URISyntaxException {
|
||||
List<String> vmsFiles = ResourceFileUtil.getAbsoluteFiles("vms");
|
||||
System.out.println(vmsFiles);
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package cn.bunny.utils;
|
||||
|
||||
import cn.bunny.dao.entity.ColumnMetaData;
|
||||
import cn.bunny.dao.entity.TableMetaData;
|
||||
import cn.bunny.domain.entity.TableMetaData;
|
||||
import lombok.SneakyThrows;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
@ -11,35 +10,20 @@ 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
|
||||
class DbInfoUtilTest {
|
||||
class ConcreteDatabaseInfoCoreTest {
|
||||
|
||||
String tableName = "sys_i18n";
|
||||
|
||||
@Autowired
|
||||
private DbInfoUtil dbInfoUtil;
|
||||
|
||||
@Autowired
|
||||
private DataSource dataSource;
|
||||
|
||||
@Test
|
||||
void columnInfo() throws SQLException {
|
||||
List<ColumnMetaData> columnMetaDataList = dbInfoUtil.columnInfo(tableName);
|
||||
columnMetaDataList.forEach(System.out::println);
|
||||
}
|
||||
|
||||
@Test
|
||||
void dbInfo() throws SQLException {
|
||||
TableMetaData tableMetaData = dbInfoUtil.dbInfo(tableName);
|
||||
System.out.println(tableMetaData);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTableInfo() {
|
||||
void testTableInfoMetaData() {
|
||||
TableMetaData tableMetaData;
|
||||
|
||||
try (Connection connection = dataSource.getConnection()) {
|
||||
|
@ -50,13 +34,7 @@ class DbInfoUtilTest {
|
|||
if (tables.next()) {
|
||||
String remarks = tables.getString("REMARKS");
|
||||
String tableCat = tables.getString("TABLE_CAT");
|
||||
String tableSchem = tables.getString("TABLE_SCHEM");
|
||||
String tableType = tables.getString("TABLE_TYPE");
|
||||
String typeCat = tables.getString("TYPE_CAT");
|
||||
String typeSchem = tables.getString("TYPE_SCHEM");
|
||||
String typeName = tables.getString("TYPE_NAME");
|
||||
String selfReferencingColName = tables.getString("SELF_REFERENCING_COL_NAME");
|
||||
String refGeneration = tables.getString("REF_GENERATION");
|
||||
|
||||
tableMetaData = TableMetaData.builder()
|
||||
.tableName(tableName)
|
|
@ -0,0 +1,21 @@
|
|||
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
|
|
@ -0,0 +1,29 @@
|
|||
# 应用名称
|
||||
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
|
|
@ -0,0 +1,26 @@
|
|||
# 平台本地运行端口号
|
||||
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
|
|
@ -0,0 +1,26 @@
|
|||
# 平台本地运行端口号
|
||||
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
|
|
@ -0,0 +1,11 @@
|
|||
dist
|
||||
node_modules
|
||||
public
|
||||
.husky
|
||||
.vscode
|
||||
.idea
|
||||
*.sh
|
||||
*.md
|
||||
|
||||
src/assets
|
||||
stats.html
|
|
@ -0,0 +1,46 @@
|
|||
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",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
|
@ -0,0 +1,11 @@
|
|||
dist
|
||||
node_modules
|
||||
public
|
||||
.husky
|
||||
.vscode
|
||||
.idea
|
||||
*.sh
|
||||
*.md
|
||||
|
||||
src/assets
|
||||
stats.html
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"hash": "727e0168",
|
||||
"configHash": "10198a12",
|
||||
"lockfileHash": "530f8857",
|
||||
"browserHash": "41b39cb9",
|
||||
"optimized": {},
|
||||
"chunks": {}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"type": "module"
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
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`;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
|
@ -0,0 +1,47 @@
|
|||
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;
|
||||
};
|
|
@ -0,0 +1,12 @@
|
|||
import type { CSSOptions } from 'vite';
|
||||
|
||||
|
||||
export const css = (mode: string): CSSOptions => {
|
||||
return {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
additionalData: `@use "@/assets/styles/minix/sidebar" as *;`,
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
|
@ -0,0 +1,14 @@
|
|||
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__),
|
||||
};
|
||||
};
|
|
@ -0,0 +1,58 @@
|
|||
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
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* 此文件作用于 `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 };
|
|
@ -0,0 +1,45 @@
|
|||
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),
|
||||
];
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
import { pathResolve } from './utils';
|
||||
|
||||
export const resolve = () => {
|
||||
return {
|
||||
alias: {
|
||||
'@': pathResolve('../src'),
|
||||
},
|
||||
};
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
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;
|
||||
};
|
|
@ -0,0 +1,118 @@
|
|||
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);
|
||||
};
|
|
@ -0,0 +1,31 @@
|
|||
# 使用官方的 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;"]
|
|
@ -0,0 +1,32 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
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',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]);
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue