Compare commits

...

19 Commits

Author SHA1 Message Date
bunny 42f1a7edfe 🐛 build文件确实 2025-06-04 13:29:15 +08:00
bunny f95dc4d947 🐛 生成字段丢失 2025-06-01 19:42:07 +08:00
bunny 11b20ba6f5 🎨 feat: 生成器代码格式 2025-05-11 15:49:26 +08:00
bunny 9356d39347 feat: 打包经过 2025-05-01 16:17:16 +08:00
bunny 8f3ea1e87f fix: 修复转大驼峰命名失败 2025-04-23 12:37:16 +08:00
bunny 16a8170e86 build: 打包 2025-04-23 11:28:18 +08:00
bunny d38844eea5 feat: 调整目录结构 2025-04-23 11:22:46 +08:00
bunny e832b5ffab docs: 更新文档 2025-04-22 20:30:20 +08:00
bunny fd722d2515 build: 构建前端 2025-04-22 20:21:58 +08:00
bunny b6e263d114 build: 构建前端 2025-04-22 20:20:18 +08:00
bunny ff2f3144dc feat: 新增解析SQL 2025-04-22 19:17:25 +08:00
bunny 4f0007ee01 feat: 添加当前连接数据库信息 2025-04-21 13:18:09 +08:00
bunny faaca67cc6 style: 修改代码样式 2025-04-21 11:24:30 +08:00
bunny 3d3540e3e9 feat: 优化代码 2025-04-20 22:28:45 +08:00
bunny 2325754ede docs: 更新文档 2025-04-19 13:37:20 +08:00
bunny 617ff87736 feat: 更新前端文件 2025-04-15 20:26:45 +08:00
bunny 474f3ab89b fix: 修复生成bug和缺陷 2025-04-15 19:11:38 +08:00
bunny e2b6d49518 🎉 feat: 前端页面更新 2025-04-11 22:10:24 +08:00
bunny 5c241728f0 feat: 生成文件名优化;大小写驼峰名转换优化 2025-04-11 22:06:47 +08:00
187 changed files with 10165 additions and 816 deletions

41
.gitignore vendored
View File

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

View File

@ -2,12 +2,52 @@
## 功能展示
点击 表名 或 注释内容 跳转到另一个页面
点击 `表名``注释内容` 跳转到另一个页面
![屏幕截图_6-4-2025_1486_localhost](./images/屏幕截图_6-4-2025_1486_localhost.jpeg)
![image-20250419132154669](./images/image-20250419132154669.png)
![image-20250406140934864](./images/image-20250406140934864.png)
![image-20250422202525702](./images/image-20250422202525702.png)
![屏幕截图_6-4-2025_1486_localhost](./images/屏幕截图_6-4-2025_1486_localhost-1743920303637-1.jpeg)
![image-20250422202618670](./images/image-20250422202618670.png)
<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);
```
![wx+alipay](images/wx_alipay.png)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
package cn.bunny.dao.entity;
package cn.bunny.domain.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;

View File

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

View File

@ -1,4 +1,4 @@
package cn.bunny.dao.result;
package cn.bunny.domain.result;
import lombok.AllArgsConstructor;
import lombok.Data;

View File

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

View File

@ -1,4 +1,4 @@
package cn.bunny.dao.vo;
package cn.bunny.domain.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;

View File

@ -1,4 +1,4 @@
package cn.bunny.dao.vo;
package cn.bunny.domain.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,6 +15,7 @@ import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Stream;
/* 当前生产/开发下的资源文件 */
public class ResourceFileUtil {
/**

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
server:
port: 9999
port: 8800
spring:
profiles:
@ -8,7 +8,6 @@ spring:
name: generator-code
thymeleaf:
check-template-location: false
prefix: classpath:/static/
datasource:
type: com.zaxxer.hikari.HikariDataSource

View File

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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列表
*/

View File

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

View File

@ -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
},
// 分页查询结果

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

29
generator-code-web/.env Normal file
View File

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

View File

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

View File

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

View File

@ -0,0 +1,11 @@
dist
node_modules
public
.husky
.vscode
.idea
*.sh
*.md
src/assets
stats.html

View File

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

View File

@ -0,0 +1,11 @@
dist
node_modules
public
.husky
.vscode
.idea
*.sh
*.md
src/assets
stats.html

View File

@ -0,0 +1,8 @@
{
"hash": "727e0168",
"configHash": "10198a12",
"lockfileHash": "530f8857",
"browserHash": "41b39cb9",
"optimized": {},
"chunks": {}
}

View File

@ -0,0 +1,3 @@
{
"type": "module"
}

View File

@ -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.logterser打包慢但能去除 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`;
}
},
},
},
};
};

View File

@ -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
* cdnhttps://www.bootcdn.cn当然你也可以选择 https://unpkg.com 或者 https://www.jsdelivr.com
* 使jscss文件cdn
*/
export const cdn = importToCDN({
//prodUrl解释 name: 对应下面modules的nameversion: 自动读取本地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;
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,9 @@
import { pathResolve } from './utils';
export const resolve = () => {
return {
alias: {
'@': pathResolve('../src'),
},
};
};

View File

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

View File

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

View File

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

View File

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

View File

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