🎉 init-代码生成器

This commit is contained in:
bunny 2025-07-17 22:21:21 +08:00
commit ae769e4cb1
111 changed files with 26143 additions and 0 deletions

View File

@ -0,0 +1,279 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<artifactId>module-generator-code</artifactId>
<packaging>jar</packaging>
<name>module-generator-code</name>
<description>代码生成器</description>
<url>https://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<java.version>17</java.version>
<junit.version>3.8.1</junit.version>
<mybatis-plus.version>3.5.6</mybatis-plus.version>
<mysql.version>9.2.0</mysql.version>
<knife4j.version>4.5.0</knife4j.version>
<fastjson2.version>2.0.47</fastjson2.version>
<minio.version>8.5.17</minio.version>
<lombok.version>1.18.32</lombok.version>
<jwt.version>0.12.6</jwt.version>
<easyexcel.version>4.0.2</easyexcel.version>
<jodatime.version>2.10.1</jodatime.version>
<aspectj>1.9.21</aspectj>
<pagehelper.version>6.1.0</pagehelper.version>
<velocity.version>2.2</velocity.version>
<velocity-tools.version>3.1</velocity-tools.version>
<HikariCP.version>6.2.1</HikariCP.version>
<dynamic.datasource.version>4.3.1</dynamic.datasource.version>
<jackson-dataType.version>2.12.3</jackson-dataType.version>
<quartz-scheduler.version>2.3.2</quartz-scheduler.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<!-- velocity -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>${velocity.version}</version>
</dependency>
<dependency>
<groupId>org.apache.velocity.tools</groupId>
<artifactId>velocity-tools-generic</artifactId>
<version>${velocity-tools.version}</version>
</dependency>
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- mysql连接池 -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>${HikariCP.version}</version>
</dependency>
<!-- 多数据库源插件 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
<version>${dynamic.datasource.version}</version>
</dependency>
<!-- knife 4 j-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
<!-- fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson2.version}</version>
</dependency>
<!-- minio -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>${minio.version}</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!-- hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.27</version>
</dependency>
<!--jjwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jwt.version}</version>
</dependency>
<!-- Excel表操作 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>${easyexcel.version}</version>
</dependency>
<!-- aspectj -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj}</version>
</dependency>
<!-- aspectj -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj}</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>${jodatime.version}</version>
</dependency>
<!-- fasterxml -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson-dataType.version}</version>
</dependency>
<!-- quartz定时任务 -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>${quartz-scheduler.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>5.1</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- knife4j -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.8.6</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.6.14</version>
</dependency>
<!-- fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
</dependency>
<!-- 小驼峰 和 大驼峰之间互转 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.4.7-jre</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
</dependencies>
<!-- <profiles> -->
<!-- &lt;!&ndash;dev环境&ndash;&gt; -->
<!-- <profile> -->
<!-- <id>dev</id> -->
<!-- <properties> -->
<!-- <profiles.active>dev</profiles.active> -->
<!-- </properties> -->
<!-- <activation> -->
<!-- <activeByDefault>true</activeByDefault> -->
<!-- </activation> -->
<!-- </profile> -->
<!-- &lt;!&ndash;test环境&ndash;&gt; -->
<!-- <profile> -->
<!-- <id>test</id> -->
<!-- <properties> -->
<!-- <profiles.active>test</profiles.active> -->
<!-- </properties> -->
<!-- </profile> -->
<!-- &lt;!&ndash;prod环境&ndash;&gt; -->
<!-- <profile> -->
<!-- <id>prod</id> -->
<!-- <properties> -->
<!-- <profiles.active>prod</profiles.active> -->
<!-- </properties> -->
<!-- </profile> -->
<!-- </profiles> -->
</project>

View File

@ -0,0 +1,11 @@
package com.auth.module.generator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GeneratorCodeMainApplication {
public static void main(String[] args) {
SpringApplication.run(GeneratorCodeMainApplication.class, args);
}
}

View File

@ -0,0 +1,50 @@
package com.auth.module.generator.config;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer;
import org.springframework.beans.propertyeditors.StringTrimmerEditor;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;
import java.io.IOException;
/**
* 去除前端传递的空格
*/
@ControllerAdvice
public class ControllerStringParamTrimConfig {
/**
* 创建 String trim 编辑器
* 构造方法中 boolean 参数含义为如果是空白字符串,是否转换为null
* 即如果为true,那么 " " 会被转换为 null,否者为 ""
*/
@InitBinder
public void initBinder(WebDataBinder binder) {
StringTrimmerEditor propertyEditor = new StringTrimmerEditor(false);
// String 类对象注册编辑器
binder.registerCustomEditor(String.class, propertyEditor);
}
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return jacksonObjectMapperBuilder -> {
// String 类型自定义反序列化操作
jacksonObjectMapperBuilder
.deserializerByType(String.class, new StdScalarDeserializer<String>(String.class) {
@Override
public String deserialize(JsonParser jsonParser, DeserializationContext ctx) throws IOException {
// // 去除全部空格
// return StringUtils.trimAllWhitespace(jsonParser.getValueAsString());
// 仅去除前后空格
return jsonParser.getValueAsString().trim();
}
});
};
}
}

View File

@ -0,0 +1,19 @@
package com.auth.module.generator.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 所有接口
.allowedOrigins("*") // 允许所有来源
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允许方法
.allowedHeaders("*") // 允许所有头
// .allowCredentials(true) // 允许凭证
.maxAge(3600); // 预检请求缓存时间
}
}

View File

@ -0,0 +1,44 @@
package com.auth.module.generator.config;
import io.swagger.v3.oas.models.ExternalDocumentation;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
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(url);
// 使用协议
License license = new License().name("MIT").url("https://mit-license.org");
// 相关信息
Info info = new Info().title("Bunny-Admin")
.contact(contact).license(license)
.description("Bunny代码生成器")
.summary("Bunny的代码生成器")
.termsOfService(url)
.version("v1.0.0");
return new OpenAPI().info(info).externalDocs(new ExternalDocumentation());
}
@Bean
public GroupedOpenApi all() {
return GroupedOpenApi.builder().group("全部请求接口").pathsToMatch("/api/**").build();
}
}

View File

@ -0,0 +1,18 @@
package com.auth.module.generator.config;
import jakarta.annotation.PostConstruct;
import org.apache.velocity.app.Velocity;
import org.springframework.stereotype.Component;
import java.util.Properties;
@Component
public class VmsHolder {
@PostConstruct
public void init() {
Properties prop = new Properties();
prop.put("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
Velocity.init(prop);
}
}

View File

@ -0,0 +1,55 @@
package com.auth.module.generator.controller;
import com.auth.module.generator.domain.dto.VmsArgumentDto;
import com.auth.module.generator.domain.result.Result;
import com.auth.module.generator.domain.vo.GeneratorVo;
import com.auth.module.generator.service.GeneratorService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.apache.logging.log4j.util.Strings;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
/**
* 代码生成器控制器
* 提供代码生成和打包下载功能
*/
@Tag(name = "生成模板", description = "生成模板接口")
@RestController
@RequestMapping("/api/generator")
@RequiredArgsConstructor
public class GeneratorController {
private final GeneratorService generatorService;
@Operation(summary = "生成代码", description = "根据SQL或数据库表生成代码")
@PostMapping
public Result<Map<String, List<GeneratorVo>>> generator(@Valid @RequestBody VmsArgumentDto dto) {
// 判断当前是使用 SQL语句 生成还是 数据库生成
String sql = dto.getSql();
Map<String, List<GeneratorVo>> result = Strings.isEmpty(sql)
? generatorService.generateCodeByDatabase(dto)
: generatorService.generateCodeBySql(dto);
return Result.success(result);
}
@Operation(summary = "打包下载", description = "将生成的代码打包为ZIP文件下载")
@PostMapping("downloadByZip")
public ResponseEntity<byte[]> downloadByZip(@Valid @RequestBody VmsArgumentDto dto) {
// 判断当前是使用 SQL语句 生成还是 数据库生成
String sql = dto.getSql();
return Strings.isEmpty(sql)
? generatorService.downloadByZipByDatabase(dto)
: generatorService.downloadByZipBySqL(dto);
}
}

View File

@ -0,0 +1,51 @@
package com.auth.module.generator.controller;
import com.auth.module.generator.core.provider.SqlMetadataProvider;
import com.auth.module.generator.domain.entity.ColumnMetaData;
import com.auth.module.generator.domain.entity.TableMetaData;
import com.auth.module.generator.domain.result.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* SQL解析控制器
* 提供SQL语句解析功能提取表和列元数据
*/
@Tag(name = "解析SQL", description = "解析SQL接口")
@RestController
@RequestMapping("/api/sqlParser")
@RequiredArgsConstructor
public class SqlParserController {
private final SqlMetadataProvider sqlMetadataProvider;
/**
* 解析SQL获取表信息
*
* @param sql SQL语句
* @return 表元数据
*/
@Operation(summary = "解析SQL表信息", description = "解析SQL语句提取表结构信息")
@PostMapping("tableInfo")
public Result<TableMetaData> tableInfo(String sql) {
return Result.success(sqlMetadataProvider.getTableMetadata(sql));
}
/**
* 解析SQL获取列信息
*
* @param sql SQL语句
* @return 列元数据列表
*/
@Operation(summary = "解析SQL列数据", description = "解析SQL语句提取列结构信息")
@PostMapping("columnMetaData")
public Result<List<ColumnMetaData>> columnMetaData(String sql) {
return Result.success(sqlMetadataProvider.getColumnInfoList(sql));
}
}

View File

@ -0,0 +1,56 @@
package com.auth.module.generator.controller;
import com.auth.module.generator.core.provider.DatabaseMetadataProvider;
import com.auth.module.generator.domain.entity.ColumnMetaData;
import com.auth.module.generator.domain.entity.DatabaseInfoMetaData;
import com.auth.module.generator.domain.entity.TableMetaData;
import com.auth.module.generator.domain.result.Result;
import com.auth.module.generator.domain.result.ResultCodeEnum;
import com.auth.module.generator.service.TableService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
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")
@RequiredArgsConstructor
public class TableController {
private final TableService tableService;
private final DatabaseMetadataProvider databaseMetadataProvider;
@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 = databaseMetadataProvider.getTableMetadataBatch(dbName);
return Result.success(list);
}
@Operation(summary = "表属性", description = "获取当前查询表属性")
@GetMapping("tableMetaData")
public Result<TableMetaData> tableMetaData(String tableName) {
TableMetaData tableMetaData = databaseMetadataProvider.getTableMetadata(tableName);
return Result.success(tableMetaData);
}
@Operation(summary = "表的列属性", description = "获取当前查询表中列属性")
@GetMapping("tableColumnInfo")
public Result<List<ColumnMetaData>> tableColumnInfo(String tableName) {
List<ColumnMetaData> columnInfo = databaseMetadataProvider.getColumnInfoList(tableName);
return Result.success(columnInfo, ResultCodeEnum.LOAD_FINISHED);
}
}

View File

@ -0,0 +1,31 @@
package com.auth.module.generator.controller;
import com.auth.module.generator.domain.result.Result;
import com.auth.module.generator.domain.vo.VmsPathVo;
import com.auth.module.generator.service.VmsService;
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;
import java.util.Map;
@Tag(name = "生成器", description = "代码生成器接口")
@RestController
@RequestMapping("/api/vms")
public class VmsController {
@Resource
private VmsService vmsService;
@Operation(summary = "获取vms文件路径", description = "获取所有vms下的文件路径")
@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,24 @@
package com.auth.module.generator.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class WebController {
@GetMapping("/")
public String indexPage() {
return "redirect:/database";
}
@GetMapping("database")
public String databasePage() {
return "database";
}
@GetMapping("/sql")
public String sqlPage() {
return "sql";
}
}

View File

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

View File

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

View File

@ -0,0 +1,235 @@
package com.auth.module.generator.core.provider;
import com.auth.module.generator.domain.entity.ColumnMetaData;
import com.auth.module.generator.domain.entity.DatabaseInfoMetaData;
import com.auth.module.generator.domain.entity.TableMetaData;
import com.auth.module.generator.exception.GeneratorCodeException;
import com.auth.module.generator.exception.MetadataNotFoundException;
import com.auth.module.generator.exception.MetadataProviderException;
import com.auth.module.generator.utils.MysqlTypeConvertUtil;
import com.zaxxer.hikari.HikariDataSource;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.stream.Collectors;
/**
* 数据库元数据提供者
* 提供从数据库获取表结构信息的实现
*/
@Component
@RequiredArgsConstructor
public class DatabaseMetadataProvider implements IMetadataProvider {
private final HikariDataSource dataSource;
@Value("${bunny.master.database}")
private String currentDatabase;
/**
* 根据表名标识符获取单个表的元数据信息
*
* @param tableName 要查询的表名大小写敏感度取决于数据库实现
* @return TableMetaData 包含表元数据的对象包括
* - 表名
* - 表注释/备注
* - 表所属目录通常是数据库名
* - 表类型通常为"TABLE"
* @throws MetadataNotFoundException 当指定的表不存在时抛出
* @throws GeneratorCodeException 当查询过程中发生其他异常时抛出
*/
@Override
public TableMetaData getTableMetadata(String tableName) {
try (Connection connection = dataSource.getConnection()) {
DatabaseMetaData metaData = connection.getMetaData();
ResultSet tables = metaData.getTables(null, null, tableName, new String[]{"TABLE"});
if (tables.next()) {
return TableMetaData.builder()
.tableName(tableName)
.comment(tables.getString("REMARKS"))
.tableCat(tables.getString("TABLE_CAT"))
.tableType(tables.getString("TABLE_TYPE"))
.build();
}
throw new MetadataNotFoundException("Table not found: " + tableName);
} catch (SQLException e) {
throw new GeneratorCodeException("Failed to get metadata for table: " + tableName, e);
}
}
/**
* 获取指定表的所有列信息列表
*
* @param tableName 要查询的表名大小写敏感度取决于数据库实现
* @return 包含所有列元数据的列表每个列的信息封装在ColumnMetaData对象中
* @throws MetadataProviderException 当获取列元数据过程中发生异常时抛出
*/
@Override
public List<ColumnMetaData> getColumnInfoList(String tableName) {
try (Connection connection = dataSource.getConnection()) {
Set<String> primaryKeys = getPrimaryKeys(tableName);
DatabaseMetaData metaData = connection.getMetaData();
return getColumnMetaData(metaData, tableName, primaryKeys);
} catch (SQLException e) {
throw new GeneratorCodeException("Failed to get column info for table: " + tableName, e);
}
}
/**
* 获取表的所有主键列名 获取表的主键列名集合
*
* @param tableName 表名
* @return 主键列名的集合
*/
private Set<String> getPrimaryKeys(String tableName) {
Set<String> primaryKeys = new HashSet<>();
try (Connection connection = dataSource.getConnection()) {
ResultSet pkResultSet = connection.getMetaData().getPrimaryKeys(null, null, tableName);
while (pkResultSet.next()) {
primaryKeys.add(pkResultSet.getString("COLUMN_NAME").toLowerCase());
}
} catch (SQLException e) {
throw new GeneratorCodeException("Get primary key error: " + e.getMessage());
}
return primaryKeys;
}
/**
* 获取列元数据列表
*/
private List<ColumnMetaData> getColumnMetaData(DatabaseMetaData metaData, String tableName, Set<String> primaryKeys) throws SQLException {
Map<String, ColumnMetaData> columnMap = new LinkedHashMap<>();
try (ResultSet columnsRs = metaData.getColumns(null, null, tableName, null)) {
while (columnsRs.next()) {
ColumnMetaData column = buildColumnMetaData(columnsRs, primaryKeys);
columnMap.putIfAbsent(column.getColumnName(), column);
}
}
return columnMap.values().stream().distinct().collect(Collectors.toList());
}
/**
* 构建列元数据对象
*/
private ColumnMetaData buildColumnMetaData(ResultSet columnsRs, Set<String> primaryKeys) throws SQLException {
String columnName = columnsRs.getString("COLUMN_NAME");
String typeName = columnsRs.getString("TYPE_NAME");
ColumnMetaData column = new ColumnMetaData();
column.setColumnName(columnName);
column.setLowercaseName(MysqlTypeConvertUtil.convertToCamelCase(columnName, false));
column.setUppercaseName(MysqlTypeConvertUtil.convertToCamelCase(columnName, true));
column.setJdbcType(typeName);
column.setJavaType(MysqlTypeConvertUtil.convertToJavaType(typeName));
column.setJavascriptType(StringUtils.uncapitalize(column.getJavaType()));
column.setComment(columnsRs.getString("REMARKS"));
column.setIsPrimaryKey(primaryKeys.contains(columnName));
return column;
}
/**
* 获取数据库的元数据信息
*
* @return DatabaseInfoMetaData 包含数据库基本信息的对象包括
* - 数据库产品名称和版本
* - JDBC驱动名称和版本
* - 连接URL
* - 用户名
* - 当前数据库名称
* @throws GeneratorCodeException 如果获取数据库信息时发生SQL异常
*/
public DatabaseInfoMetaData databaseInfoMetaData() {
// 使用try-with-resources确保Connection自动关闭
try (Connection connection = dataSource.getConnection()) {
// 获取数据库的元数据对象
DatabaseMetaData metaData = connection.getMetaData();
// 使用Builder模式构建数据库信息对象
return DatabaseInfoMetaData.builder()
// 数据库产品名称(如MySQL, Oracle等)
.databaseProductName(metaData.getDatabaseProductName())
// 数据库产品版本号
.databaseProductVersion(metaData.getDatabaseProductVersion())
// JDBC驱动名称
.driverName(metaData.getDriverName())
// JDBC驱动版本
.driverVersion(metaData.getDriverVersion())
// 数据库连接URL
.url(metaData.getURL())
// 连接使用的用户名
.username(metaData.getUserName())
// 当前使用的数据库名称(由类成员变量提供)
.currentDatabase(currentDatabase)
.build();
} catch (SQLException e) {
// 将SQL异常转换为自定义的业务异常
throw new GeneratorCodeException("Get database info error:" + e.getMessage());
}
}
/**
* 批量获取指定数据库中所有表的元数据信息
* 获取指定数据库中的所有表信息
*
* @param dbName 数据库名称
* @return 包含所有表元数据的列表每个表的信息封装在TableMetaData对象中
* @throws MetadataProviderException 如果获取元数据过程中发生异常
*/
public List<TableMetaData> getTableMetadataBatch(String dbName) {
// 初始化返回结果列表
List<TableMetaData> allTableInfo = new ArrayList<>();
// 使用try-with-resources确保Connection资源自动关闭
try (Connection conn = dataSource.getConnection()) {
// 获取数据库的元数据对象
DatabaseMetaData metaData = conn.getMetaData();
/*
参数说明
1. dbName - 数据库/目录名称null表示忽略
2. null - 模式/模式名称null表示忽略
3. "%" - 表名称模式"%"表示所有表
4. new String[]{"TABLE"} - 类型数组这里只查询普通表
*/
ResultSet tables = metaData.getTables(dbName, null, "%", new String[]{"TABLE"});
// 遍历查询结果集中的所有表
while (tables.next()) {
// 从结果集中获取表的各种属性
String tableName = tables.getString("TABLE_NAME"); // 表名
String remarks = tables.getString("REMARKS"); // 表备注/注释
String tableCat = tables.getString("TABLE_CAT"); // 表所属的目录(通常是数据库名)
String tableType = tables.getString("TABLE_TYPE"); // 表类型(这里应该是"TABLE")
// 使用Builder模式创建TableMetaData对象并设置属性
TableMetaData tableMetaData = TableMetaData.builder()
.tableName(tableName) // 设置表名
.comment(remarks) // 设置表注释
.tableCat(tableCat) // 设置表所属目录
.tableType(tableType) // 设置表类型
.build();
// 将表元数据对象添加到结果列表
allTableInfo.add(tableMetaData);
}
} catch (Exception e) {
// 捕获任何异常并转换为自定义异常抛出
throw new MetadataProviderException("Failed to get batch table metadata", e);
}
// 返回包含所有表元数据的列表
return allTableInfo;
}
}

View File

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

View File

@ -0,0 +1,121 @@
package com.auth.module.generator.core.provider;
import com.auth.module.generator.core.dialect.DatabaseDialect;
import com.auth.module.generator.domain.entity.ColumnMetaData;
import com.auth.module.generator.domain.entity.TableMetaData;
import com.auth.module.generator.exception.GeneratorCodeException;
import com.auth.module.generator.exception.SqlParseException;
import com.auth.module.generator.utils.MysqlTypeConvertUtil;
import lombok.RequiredArgsConstructor;
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.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.List;
@Component
@RequiredArgsConstructor
public class SqlMetadataProvider implements IMetadataProvider {
private final DatabaseDialect dialect;
/**
* 解析 sql 表信息
* 先解析SQL语句解析列字段信息
*
* @param sqlStatement sql语句
* @return 表西悉尼
* @see CCJSqlParserUtil 使用这个工具进行SQL的解析
*/
@Override
public TableMetaData getTableMetadata(String sqlStatement) {
TableMetaData tableInfo = new TableMetaData();
// 解析sql
Statement statement;
try {
statement = CCJSqlParserUtil.parse(sqlStatement);
} catch (JSQLParserException e) {
throw new GeneratorCodeException("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());
// 注释信息
String comment = dialect.extractTableComment(tableOptionsStrings);
tableInfo.setComment(comment);
return tableInfo;
}
/**
* 获取当前表的列属性
*
* @param sqlStatement sql语句
* @return 当前表所有的列内容
*/
@Override
public List<ColumnMetaData> getColumnInfoList(String sqlStatement) {
// 解析sql
Statement statement;
try {
statement = CCJSqlParserUtil.parse(sqlStatement);
} catch (JSQLParserException e) {
throw new SqlParseException("Fail parse sql", e.getCause());
}
if (!(statement instanceof CreateTable createTable)) {
throw new IllegalArgumentException("Lack of Sql Statement");
}
return createTable.getColumnDefinitions()
.stream().map(column -> {
// 列信息
ColumnMetaData columnInfo = new ColumnMetaData();
// 列名称
String columnName = column.getColumnName().replaceAll("`", "");
columnInfo.setColumnName(columnName);
// 设置 JDBC 类型
String dataType = column.getColDataType().getDataType();
columnInfo.setJdbcType(dataType);
// 设置 Java 类型
String javaType = MysqlTypeConvertUtil.convertToJavaType(dataType.contains("varchar") ? "varchar" : dataType);
columnInfo.setJavaType(javaType);
// 设置 JavaScript 类型
columnInfo.setJavascriptType(StringUtils.uncapitalize(javaType));
// 列字段转成 下划线 -> 小驼峰
String lowercaseName = MysqlTypeConvertUtil.convertToCamelCase(columnName, false);
columnInfo.setLowercaseName(lowercaseName);
// 列字段转成 下划线 -> 大驼峰名称
String uppercaseName = MysqlTypeConvertUtil.convertToCamelCase(columnName, true);
columnInfo.setUppercaseName(uppercaseName);
// 解析注释
List<String> columnSpecs = column.getColumnSpecs();
// 设置列属性信息
String comment = dialect.extractColumnComment(columnSpecs);
columnInfo.setComment(comment);
return columnInfo;
}).toList();
}
}

View File

@ -0,0 +1,70 @@
package com.auth.module.generator.core.template;
import com.auth.module.generator.domain.entity.ColumnMetaData;
import com.auth.module.generator.domain.entity.TableMetaData;
import org.apache.velocity.VelocityContext;
import java.io.StringWriter;
import java.util.List;
import java.util.stream.Collectors;
/**
* 模板生成抽象基类
* 定义代码生成的模板方法流程
*/
public abstract class AbstractTemplateGenerator {
/**
* 生成代码模板
*
* @param tableMeta 表元数据
* @param columns 列信息列表
* @return 生成的代码内容
*/
public StringWriter generateCode(TableMetaData tableMeta, List<ColumnMetaData> columns) {
VelocityContext context = new VelocityContext();
prepareVelocityContext(context, tableMeta, columns);
return mergeTemplate(context);
}
/**
* 准备Velocity上下文数据
*/
private void prepareVelocityContext(VelocityContext context, TableMetaData tableMeta, List<ColumnMetaData> columns) {
context.put("leftBrace", "{");
context.put("tableName", tableMeta.getTableName());
context.put("columnInfoList", columns);
context.put("baseColumnList", getDistinctColumnNames(columns));
addContext(context); // 子类可扩展
}
/**
* 获取去重的列名列表
*/
private String getDistinctColumnNames(List<ColumnMetaData> columns) {
return columns.stream()
.map(ColumnMetaData::getColumnName)
.distinct()
.collect(Collectors.joining(","));
}
/**
* 合并Velocity模板
*/
private StringWriter mergeTemplate(VelocityContext context) {
StringWriter writer = new StringWriter();
templateMerge(context, writer);
return writer;
}
/**
* 添加生成内容由子类实现
*/
protected abstract void addContext(VelocityContext context);
/**
* 模板合并由子类实现
*/
protected abstract void templateMerge(VelocityContext context, StringWriter writer);
}

View File

@ -0,0 +1,104 @@
package com.auth.module.generator.core.template;
import com.auth.module.generator.domain.dto.VmsArgumentDto;
import com.auth.module.generator.domain.entity.TableMetaData;
import com.auth.module.generator.utils.MysqlTypeConvertUtil;
import com.google.common.base.CaseFormat;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.springframework.util.StringUtils;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 使用模板方法方便扩展
* 如果需要继承 AbstractVmsGenerator
*/
public class VmsTBaseTemplateGenerator extends AbstractTemplateGenerator {
private final VmsArgumentDto dto;
private final String path;
private final TableMetaData tableMetaData;
/**
* @param dto 类名称可以自定义格式为 xxx_xxx
* @param path 当前路径
* @param tableMetaData 表名称
*/
public VmsTBaseTemplateGenerator(VmsArgumentDto dto, String path, TableMetaData tableMetaData) {
// 处理表名称替换前缀
String tableName = tableMetaData.getTableName();
String[] prefixes = dto.getTablePrefixes().split("[,]");
tableMetaData.setCleanTableName(tableName);
for (String prefix : prefixes) {
if (tableName.startsWith(prefix)) {
String handlerTableName = tableName.replace(prefix, "");
tableMetaData.setCleanTableName(handlerTableName);
}
}
this.dto = dto;
this.path = path;
this.tableMetaData = tableMetaData;
}
/**
* 添加生成内容
*
* @param context VelocityContext
*/
@Override
public void addContext(VelocityContext context) {
// 当前的表名
String cleanTableName = tableMetaData.getCleanTableName();
cleanTableName = StringUtils.hasText(cleanTableName) ? cleanTableName : tableMetaData.getTableName();
// 表的注释内容
String comment = tableMetaData.getComment();
// 当前日期
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", comment);
// 设置包名称
context.put("package", dto.getPackageName());
// 将类名称转成小驼峰
String lowerCamelCase = MysqlTypeConvertUtil.convertToCamelCase(cleanTableName, false);
context.put("classLowercaseName", lowerCamelCase);
// 将类名称转成大驼峰
String upperCameCase = MysqlTypeConvertUtil.convertToCamelCase(cleanTableName, true);
context.put("classUppercaseName", upperCameCase);
// 添加中划线
String lowerHyphenName = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, lowerCamelCase);
context.put("lowerHyphenName", lowerHyphenName);
}
/**
* Velocity 生成模板
*
* @param context VelocityContext
* @param writer StringWriter 写入
*/
@Override
public void templateMerge(VelocityContext context, StringWriter writer) {
Template servicePathTemplate = Velocity.getTemplate("vms/" + path, "UTF-8");
servicePathTemplate.merge(context, writer);
}
}

View File

@ -0,0 +1,41 @@
package com.auth.module.generator.domain.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.util.List;
@Data
@Schema(name = "VmsArgumentDto", description = "生成代码请求参数")
public class VmsArgumentDto {
@Schema(name = "author", description = "作者名称")
String author = "";
@Schema(name = "packageName", description = "包名称")
@NotBlank(message = "包名不能为空")
String packageName;
@Schema(name = "requestMapping", description = "requestMapping 名称")
String requestMapping = "";
@Schema(name = "tableNames", description = "表名列表")
private List<String> tableNames;
@Schema(name = "simpleDateFormat", description = "时间格式")
private String simpleDateFormat = "yyyy-MM-dd HH:mm:ss";
@Schema(name = "tablePrefixes", description = "去除表前缀")
private String tablePrefixes = "";
@Schema(name = "path", description = "路径")
@NotEmpty(message = "表名称不能为空")
private List<String> path;
@Schema(name = "sql", description = "SQL 语句")
private String sql;
}

View File

@ -0,0 +1,38 @@
package com.auth.module.generator.domain.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ColumnMetaData {
/* 列名称 */
private String columnName;
/* 字段名称,小驼峰名称 */
private String lowercaseName;
/* 大驼峰名称 */
private String uppercaseName;
/* 数据库字段类型 */
private String jdbcType;
/* Java类型 */
private String javaType;
/* Javascript类型 */
private String javascriptType;
/* 是否为主键 */
private Boolean isPrimaryKey;
/* 字段注释 */
private String comment;
}

View File

@ -0,0 +1,42 @@
package com.auth.module.generator.domain.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Schema(name = "DatabaseInfoMetaData", description = "数据库信息")
public class DatabaseInfoMetaData {
@Schema(name = "databaseList", description = "数据库所有的数据库")
List<TableMetaData> databaseList;
@Schema(name = "databaseProductName", description = "数据库产品名称")
private String databaseProductName;
@Schema(name = "databaseProductVersion", description = "数据库产品版本")
private String databaseProductVersion;
@Schema(name = "driverName", description = "驱动名称")
private String driverName;
@Schema(name = "driverVersion", description = "数据库驱动版本")
private String driverVersion;
@Schema(name = "url", description = "数据链接url")
private String url;
@Schema(name = "username", description = "数据库用户")
private String username;
@Schema(name = "currentDatabase", description = "当前数据库名称")
private String currentDatabase;
}

View File

@ -0,0 +1,41 @@
package com.auth.module.generator.domain.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "TableMetaData", description = "表信息数据")
public class TableMetaData implements Serializable {
@Schema(name = "tableName", description = "表名")
private String tableName;
@Schema(name = "handlerTableName", description = "处理后的表名称")
private String cleanTableName;
@Schema(name = "comment", description = "注释内容")
private String comment;
@Schema(name = "tableCats", description = "表目录")
private String tableCat;
@Schema(name = "tableType", description = "表类型(通常是\"TABLE\"")
private String tableType;
@Schema(name = "className", description = "类名")
private String className;
@Schema(name = "columns", description = "列名称")
private List<ColumnMetaData> columns = new ArrayList<>();
}

View File

@ -0,0 +1,34 @@
package com.auth.module.generator.domain.result;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
/**
* 封装分页查询结果
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "PageResult 对象", title = "分页返回结果", description = "分页返回结果")
public class PageResult<T> implements Serializable {
@Schema(name = "pageNo", title = "当前页")
private Long pageNo;
@Schema(name = "pageSize", title = "每页记录数")
private Long pageSize;
@Schema(name = "total", title = "总记录数")
private Long total;
@Schema(name = "list", title = "当前页数据集合")
private List<T> list;
}

View File

@ -0,0 +1,173 @@
package com.auth.module.generator.domain.result;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> {
// 状态码
private Integer code;
// 返回消息
private String message;
// 返回数据
private T data;
/**
* * 自定义返回体
*
* @param data 返回体
* @return Result<T>
*/
protected static <T> Result<T> build(T data) {
Result<T> result = new Result<>();
result.setData(data);
return result;
}
/**
* * 自定义返回体使用ResultCodeEnum构建
*
* @param body 返回体
* @param codeEnum 返回状态码
* @return Result<T>
*/
public static <T> Result<T> build(T body, ResultCodeEnum codeEnum) {
Result<T> result = build(body);
result.setCode(codeEnum.getCode());
result.setMessage(codeEnum.getMessage());
return result;
}
/**
* * 自定义返回体
*
* @param body 返回体
* @param code 返回状态码
* @param message 返回消息
* @return Result<T>
*/
public static <T> Result<T> build(T body, Integer code, String message) {
Result<T> result = build(body);
result.setCode(code);
result.setMessage(message);
result.setData(null);
return result;
}
/**
* * 操作成功
*
* @return Result<T>
*/
public static <T> Result<T> success() {
return success(null, ResultCodeEnum.SUCCESS);
}
/**
* * 操作成功
*
* @param data baseCategory1List
*/
public static <T> Result<T> success(T data) {
return build(data, ResultCodeEnum.SUCCESS);
}
/**
* * 操作成功-状态码
*
* @param codeEnum 状态码
*/
public static <T> Result<T> success(ResultCodeEnum codeEnum) {
return success(null, codeEnum);
}
/**
* * 操作成功-自定义返回数据和状态码
*
* @param data 返回体
* @param codeEnum 状态码
*/
public static <T> Result<T> success(T data, ResultCodeEnum codeEnum) {
return build(data, codeEnum);
}
/**
* * 操作失败-自定义返回数据和状态码
*
* @param data 返回体
* @param message 错误信息
*/
public static <T> Result<T> success(T data, String message) {
return build(data, 200, message);
}
/**
* * 操作失败-自定义返回数据和状态码
*
* @param data 返回体
* @param code 状态码
* @param message 错误信息
*/
public static <T> Result<T> success(T data, Integer code, String message) {
return build(data, code, message);
}
/**
* * 操作失败
*/
public static <T> Result<T> error() {
return Result.build(null);
}
/**
* * 操作失败-自定义返回数据
*
* @param data 返回体
*/
public static <T> Result<T> error(T data) {
return build(data, ResultCodeEnum.FAIL);
}
/**
* * 操作失败-状态码
*
* @param codeEnum 状态码
*/
public static <T> Result<T> error(ResultCodeEnum codeEnum) {
return build(null, codeEnum);
}
/**
* * 操作失败-自定义返回数据和状态码
*
* @param data 返回体
* @param codeEnum 状态码
*/
public static <T> Result<T> error(T data, ResultCodeEnum codeEnum) {
return build(data, codeEnum);
}
/**
* * 操作失败-自定义返回数据和状态码
*
* @param data 返回体
* @param code 状态码
* @param message 错误信息
*/
public static <T> Result<T> error(T data, Integer code, String message) {
return build(data, code, message);
}
/**
* * 操作失败-自定义返回数据和状态码
*
* @param data 返回体
* @param message 错误信息
*/
public static <T> Result<T> error(T data, String message) {
return build(null, 500, message);
}
}

View File

@ -0,0 +1,90 @@
package com.auth.module.generator.domain.result;
import lombok.Getter;
/**
* 统一返回结果状态信息类
*/
@Getter
public enum ResultCodeEnum {
// 成功操作 200
SUCCESS(200, "操作成功"),
LOAD_FINISHED(200, "加载完成"),
ADD_SUCCESS(200, "添加成功"),
UPDATE_SUCCESS(200, "修改成功"),
DELETE_SUCCESS(200, "删除成功"),
SORT_SUCCESS(200, "排序成功"),
SUCCESS_UPLOAD(200, "上传成功"),
SUCCESS_LOGOUT(200, "退出成功"),
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

@ -0,0 +1,31 @@
package com.auth.module.generator.domain.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Schema(name = "GeneratorVo", description = "生成前端/后端返回参数")
public class GeneratorVo {
@Schema(name = "id", description = "生成的唯一值")
private String id;
@Schema(name = "code", description = "生成的代码")
private String code;
@Schema(name = "tableName", description = "表名")
private String tableName;
@Schema(name = "comment", description = "注释内容")
private String comment;
@Schema(name = "path", description = "生成类型路径")
private String path;
}

View File

@ -0,0 +1,28 @@
package com.auth.module.generator.domain.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Schema(name = "GeneratorVo", description = "生成前端/后端路径返回参数")
public class VmsPathVo {
@Schema(name = "id", description = "生成的唯一值")
private String id;
@Schema(name = "name", description = "路径名称")
private String name;
@Schema(name = "label", description = "显示的label")
private String label;
@Schema(name = "type", description = "文件夹最上级目录名称")
private String type;
}

View File

@ -0,0 +1,46 @@
package com.auth.module.generator.exception;
import com.auth.module.generator.domain.result.ResultCodeEnum;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
@NoArgsConstructor
@Getter
@ToString
@Slf4j
public class GeneratorCodeException extends RuntimeException {
// 状态码
Integer code;
// 描述信息
String message = "服务异常";
// 返回结果状态
ResultCodeEnum resultCodeEnum;
public GeneratorCodeException(Integer code, String message) {
super(message);
this.code = code;
this.message = message;
}
public GeneratorCodeException(String message) {
super(message);
this.message = message;
}
public GeneratorCodeException(ResultCodeEnum codeEnum) {
super(codeEnum.getMessage());
this.code = codeEnum.getCode();
this.message = codeEnum.getMessage();
this.resultCodeEnum = codeEnum;
}
public GeneratorCodeException(String message, Exception exception) {
super(message);
this.message = message;
log.error(message, exception);
}
}

View File

@ -0,0 +1,105 @@
package com.auth.module.generator.exception;
import com.auth.module.generator.domain.result.Result;
import com.auth.module.generator.domain.result.ResultCodeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* 全局异常拦截器
*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
// 自定义异常信息
@ExceptionHandler(GeneratorCodeException.class)
@ResponseBody
public Result<Object> exceptionHandler(GeneratorCodeException exception) {
Integer code = exception.getCode() != null ? exception.getCode() : 500;
return Result.error(null, code, exception.getMessage());
}
// 运行时异常信息
@ExceptionHandler(RuntimeException.class)
@ResponseBody
public Result<Object> exceptionHandler(RuntimeException exception) {
String message = exception.getMessage();
message = StringUtils.hasText(message) ? message : "服务器异常";
exception.printStackTrace();
// 解析异常
String jsonParseError = "JSON parse error (.*)";
Matcher jsonParseErrorMatcher = Pattern.compile(jsonParseError).matcher(message);
if (jsonParseErrorMatcher.find()) {
return Result.error(null, 500, "JSON解析异常 " + jsonParseErrorMatcher.group(1));
}
// 数据过大
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) + " 字段数据过大");
}
// 主键冲突
String primaryKeyError = "Duplicate entry '(.*?)' for key .*";
Matcher primaryKeyErrorMatcher = Pattern.compile(primaryKeyError).matcher(message);
if (primaryKeyErrorMatcher.find()) {
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) + " 不合法");
}
log.error("GlobalExceptionHandler===>运行时异常信息:{}", message);
return Result.error(null, 500, message);
}
// 表单验证字段
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<String> handleValidationExceptions(MethodArgumentNotValidException ex) {
String errorMessage = ex.getBindingResult().getFieldErrors().stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.distinct()
.collect(Collectors.joining(", "));
return Result.error(null, 201, errorMessage);
}
// 特定异常处理
@ExceptionHandler(ArithmeticException.class)
@ResponseBody
public Result<Object> error(ArithmeticException exception) {
log.error("GlobalExceptionHandler===>特定异常信息:{}", exception.getMessage());
return Result.error(null, 500, exception.getMessage());
}
// 处理SQL异常
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
@ResponseBody
public Result<String> exceptionHandler(SQLIntegrityConstraintViolationException exception) {
log.error("GlobalExceptionHandler===>处理SQL异常:{}", exception.getMessage());
String message = exception.getMessage();
if (message.contains("Duplicate entry")) {
// 错误信息
return Result.error(ResultCodeEnum.USER_IS_EMPTY);
} else {
return Result.error(ResultCodeEnum.UNKNOWN_EXCEPTION);
}
}
}

View File

@ -0,0 +1,7 @@
package com.auth.module.generator.exception;
public class MetadataNotFoundException extends RuntimeException {
public MetadataNotFoundException(String message) {
super(message);
}
}

View File

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

View File

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

View File

@ -0,0 +1,49 @@
package com.auth.module.generator.service;
import com.auth.module.generator.domain.dto.VmsArgumentDto;
import com.auth.module.generator.domain.vo.GeneratorVo;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import java.util.List;
import java.util.Map;
/**
* 代码生成服务接口
* 提供基于数据库和SQL的代码生成功能
*/
public interface GeneratorService {
/**
* 根据数据库表生成代码
*
* @param dto 生成参数
* @return 生成的代码按表名分组
*/
Map<String, List<GeneratorVo>> generateCodeByDatabase(VmsArgumentDto dto);
/**
* 根据SQL语句生成代码
*
* @param dto 生成参数
* @return 生成的代码按表名分组
*/
Map<String, List<GeneratorVo>> generateCodeBySql(VmsArgumentDto dto);
/**
* 打包数据库生成的代码为ZIP下载
*
* @param dto 生成参数
* @return ZIP文件响应实体
*/
ResponseEntity<byte[]> downloadByZipByDatabase(@Valid VmsArgumentDto dto);
/**
* 打包SQL生成的代码为ZIP下载
*
* @param dto 生成参数
* @return ZIP文件响应实体
*/
ResponseEntity<byte[]> downloadByZipBySqL(@Valid VmsArgumentDto dto);
}

View File

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

View File

@ -0,0 +1,17 @@
package com.auth.module.generator.service;
import com.auth.module.generator.domain.vo.VmsPathVo;
import java.util.List;
import java.util.Map;
public interface VmsService {
/**
* 获取vms文件路径
*
* @return vms下的文件路径
*/
Map<String, List<VmsPathVo>> vmsResourcePathList();
}

View File

@ -0,0 +1,74 @@
package com.auth.module.generator.service.helper;
import com.auth.module.generator.utils.MysqlTypeConvertUtil;
import com.google.common.base.CaseFormat;
import java.util.Map;
/**
* 代码生成工具类
*/
public class VmsGeneratorPathHelper {
private static final Map<String, String> FILE_TYPE_SUFFIXES = Map.of(
"controller", "Controller",
"service", "Service",
"serviceImpl", "ServiceImpl",
"mapper", "Mapper",
"resourceMapper", "Mapper",
"dto", "Dto",
"entity", "Entity",
"vo", "Vo"
);
/**
* 处理模板文件路径和命名
*
* @param path 原始模板路径
* @param tableName 数据库表名
* @return 处理后的文件路径
*/
public static String processVmPath(String path, String tableName) {
String lowerCamelCase = MysqlTypeConvertUtil.convertToCamelCase(tableName, false);
if (lowerCamelCase != null) {
String[] pathParts = path.replace("$className", lowerCamelCase).split("/");
// 处理文件名
pathParts[pathParts.length - 1] = processFilename(
pathParts[pathParts.length - 1],
lowerCamelCase
);
return String.join("/", pathParts);
}
return path;
}
/**
* 处理文件名生成
*/
private static String processFilename(String filename, String tableName) {
filename = filename.replace(".vm", "");
String[] parts = filename.split("\\.");
String baseName = parts[0];
String extension = parts.length > 1 ? parts[1] : "";
String upperCamelCase = MysqlTypeConvertUtil.convertToCamelCase(tableName, true);
String lowerCamelCase = MysqlTypeConvertUtil.convertToCamelCase(tableName, false);
// 如果包含Java和xml需要进行处理
if (filename.contains("java") || filename.contains("xml")) {
return upperCamelCase + FILE_TYPE_SUFFIXES.getOrDefault(baseName, "") + "." + extension;
}
if (filename.equals("api.ts") || filename.equals("store.ts")) {
return lowerCamelCase + ".ts";
}
if (filename.equals("dialog.vue")) {
return CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, lowerCamelCase) + "-dialog.vue";
}
return filename;
}
}

View File

@ -0,0 +1,153 @@
package com.auth.module.generator.service.impl;
import com.auth.module.generator.core.provider.IMetadataProvider;
import com.auth.module.generator.core.template.VmsTBaseTemplateGenerator;
import com.auth.module.generator.domain.dto.VmsArgumentDto;
import com.auth.module.generator.domain.entity.ColumnMetaData;
import com.auth.module.generator.domain.entity.TableMetaData;
import com.auth.module.generator.domain.vo.GeneratorVo;
import com.auth.module.generator.service.GeneratorService;
import com.auth.module.generator.service.helper.VmsGeneratorPathHelper;
import com.auth.module.generator.utils.ZipFileUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 代码生成服务实现类
* 实现基于数据库和SQL的代码生成逻辑
*/
@Service
@RequiredArgsConstructor
public class GeneratorServiceImpl implements GeneratorService {
private final IMetadataProvider databaseMetadataProvider;
private final IMetadataProvider sqlMetadataProvider;
/**
* 代码生成方法---数据库生成
*
* @param dto 生成参数
* @return 生成的代码按表名分组
*/
@Override
public Map<String, List<GeneratorVo>> generateCodeByDatabase(VmsArgumentDto dto) {
return dto.getTableNames().parallelStream()
.flatMap(tableName -> {
TableMetaData tableMeta = databaseMetadataProvider.getTableMetadata(tableName);
List<ColumnMetaData> columns = databaseMetadataProvider.getColumnInfoList(tableName);
return getGeneratorStream(dto, tableMeta, columns);
})
.collect(Collectors.groupingBy(GeneratorVo::getTableName));
}
/**
* 代码生成方法---Sql语句生成
*
* @param dto 生成参数
* @return 生成的代码按表名分组
*/
@Override
public Map<String, List<GeneratorVo>> generateCodeBySql(VmsArgumentDto dto) {
// 根据Sql语句进行分析表的属性和表列字段
String sql = dto.getSql();
TableMetaData tableMeta = sqlMetadataProvider.getTableMetadata(sql);
List<ColumnMetaData> columns = sqlMetadataProvider.getColumnInfoList(sql);
// 生成代码
List<GeneratorVo> generatorVoList = getGeneratorStream(dto, tableMeta, columns).toList();
Map<String, List<GeneratorVo>> map = new HashMap<>();
map.put(tableMeta.getTableName(), generatorVoList);
return map;
}
/**
* 根据数据库进行生成
*
* @param dto 生成参数
* @return 生成的ZIP文件
*/
@Override
public ResponseEntity<byte[]> downloadByZipByDatabase(VmsArgumentDto dto) {
return downloadByZip(dto, this::generateCodeByDatabase);
}
/**
* 根据Sql语句及逆行生成
*
* @param dto 生成参数
* @return 生成的ZIP文件
*/
@Override
public ResponseEntity<byte[]> downloadByZipBySqL(VmsArgumentDto dto) {
return downloadByZip(dto, this::generateCodeBySql);
}
/**
* 通用ZIP打包下载方法
*
* @param dto 生成参数
* @param generator 代码生成函数
* @return ZIP文件响应实体
*/
private ResponseEntity<byte[]> downloadByZip(VmsArgumentDto dto,
Function<VmsArgumentDto, Map<String, List<GeneratorVo>>> generator) {
// 调用生成函数
List<GeneratorVo> generatorVoList = generator.apply(dto)
.values().stream()
.flatMap(Collection::stream)
.toList();
// 创建Zip文件
byte[] zipBytes = ZipFileUtil.createZipFile(generatorVoList);
// 设置返回给前端的文件名
String zipFilename = "code-" + UUID.randomUUID().toString().split("-")[0] + ".zip";
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition", "attachment; filename=" + zipFilename);
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
headers.add("Pragma", "no-cache");
headers.add("Expires", "0");
return new ResponseEntity<>(zipBytes, headers, HttpStatus.OK);
}
/**
* 获取生成器流
*
* @param dto 生成参数
* @param tableMeta 表元数据
* @param columns 列信息
* @return 生成器流
*/
public Stream<GeneratorVo> getGeneratorStream(VmsArgumentDto dto, TableMetaData tableMeta, List<ColumnMetaData> columns) {
// 因为这里使用到了并行流 tableMeta 操作正常是会拿到修改后的值但是在并行流会有线程安全问题所以直接要手动实现深拷贝
return dto.getPath().parallelStream().map(path -> {
// 创建生成模板
VmsTBaseTemplateGenerator generator = new VmsTBaseTemplateGenerator(dto, path, tableMeta);
// 生成好的模板
String code = generator.generateCode(tableMeta, columns).toString();
String processVmPath = VmsGeneratorPathHelper.processVmPath(path, tableMeta.getCleanTableName());
return GeneratorVo.builder()
.id(UUID.randomUUID().toString())
.code(code)
.comment(tableMeta.getComment())
.tableName(tableMeta.getTableName())
.path(processVmPath)
.build();
});
}
}

View File

@ -0,0 +1,44 @@
package com.auth.module.generator.service.impl;
import com.auth.module.generator.core.provider.DatabaseMetadataProvider;
import com.auth.module.generator.domain.entity.DatabaseInfoMetaData;
import com.auth.module.generator.domain.entity.TableMetaData;
import com.auth.module.generator.service.TableService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class TableServiceImpl implements TableService {
private final DatabaseMetadataProvider databaseMetadataProvider;
/**
* 数据库所有的信息
*
* @return 当前连接的数据库信息属性
*/
@Override
public DatabaseInfoMetaData databaseInfoMetaData() {
List<TableMetaData> databaseTableList = databaseMetadataProvider.getTableMetadataBatch(null);
// 将当前数据库表分组以数据库名称为key
List<TableMetaData> databaseList = databaseTableList.stream()
.collect(Collectors.groupingBy(TableMetaData::getTableCat))
.values().stream()
.map(tableInfoVos -> {
TableMetaData tableInfoVo = tableInfoVos.get(0);
tableInfoVo.setTableName(null);
return tableInfoVo;
})
.toList();
DatabaseInfoMetaData databaseInfoMetaData = databaseMetadataProvider.databaseInfoMetaData();
databaseInfoMetaData.setDatabaseList(databaseList);
return databaseInfoMetaData;
}
}

View File

@ -0,0 +1,65 @@
package com.auth.module.generator.service.impl;
import com.auth.module.generator.domain.vo.VmsPathVo;
import com.auth.module.generator.service.VmsService;
import com.auth.module.generator.utils.ResourceFileUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* VMS服务主实现类负责协调各子服务完成代码生成资源管理和打包下载功能
*/
@Service
@RequiredArgsConstructor
public class VmsServiceImpl implements VmsService {
/**
* 获取VMS资源文件路径列表并按类型分组
*
* @return 按类型分组的VMS路径Mapkey为类型value为对应类型的VMS路径列表
* @throws RuntimeException 当获取资源路径失败时抛出
*/
@Override
public Map<String, List<VmsPathVo>> vmsResourcePathList() {
try {
// 1. 获取vms目录下所有相对路径文件列表
List<String> vmsRelativeFiles = ResourceFileUtil.getRelativeFiles("vms");
// 2. 处理文件路径并分组将文件路径字符串转换为VmsPathVo对象
return vmsRelativeFiles.parallelStream()
.map(vmFile -> {
// 分割文件路径
String[] filepathList = vmFile.split("/");
// 获取文件名不含扩展名
String filename = filepathList[filepathList.length - 1].replace(".vm", "");
/*
生成前端可用的唯一DOM元素ID
格式: "id-" + 无横线的UUID (例如: "id-550e8400e29b41d4a716446655440000")
用途:
1. 用于关联label标签和input元素的for属性
2. 确保列表项在前端有唯一标识
*/
String id = "id-" + UUID.randomUUID().toString().replace("-", "");
return VmsPathVo.builder()
.id(id)
.name(vmFile)
.label(filename)
.type(filepathList[0]) // 使用路径的第一部分作为类型
.build();
})
// 转换为VO对象
.collect(Collectors.groupingBy(VmsPathVo::getType)); // 按类型分组
} catch (Exception e) {
throw new RuntimeException("Failed to get VMS resource paths: " + e.getMessage(), e);
}
}
}

View File

@ -0,0 +1,52 @@
package com.auth.module.generator.utils;
import com.google.common.base.CaseFormat;
import org.assertj.core.util.introspection.CaseFormatUtils;
/* 类型转换数据库转Java类型等 */
public class MysqlTypeConvertUtil {
/**
* 将数据库类型转换为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";
};
}
/**
* 下划线命名转驼峰命名
*
* @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,134 @@
package com.auth.module.generator.utils;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Stream;
/* 当前生产/开发下的资源文件 */
public class ResourceFileUtil {
/**
* 获取目标文件夹下所有文件完整路径
*
* @param dirname 文件夹名称
* @return 目标文件完整路径
* @throws IOException IOException
*/
public static List<String> getAbsoluteFiles(String dirname) throws IOException {
List<String> fileNames = new ArrayList<>();
// 加载当前类
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Enumeration<URL> urls = classLoader.getResources(dirname);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
if (url.getProtocol().equals("file")) {
// 文件系统
File file = new File(url.getFile());
if (file.isDirectory()) {
addFullFilesFromDirectory(file, fileNames);
}
} else if (url.getProtocol().equals("jar")) {
// JAR文件
String jarPath = url.getPath().substring(5, url.getPath().indexOf("!"));
try (JarFile jar = new JarFile(jarPath)) {
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
String name = entries.nextElement().getName();
if (name.startsWith(dirname + "/") && !name.endsWith("/")) {
fileNames.add(name);
}
}
}
}
}
return fileNames;
}
/**
* 添加文件
* 获取目标文件夹下所有文件完整路径
*
* @param directory 文件夹
* @param fileNames 文件名称
*/
private static void addFullFilesFromDirectory(File directory, List<String> fileNames) {
File[] files = directory.listFiles();
if (files != null) {
for (File file : files) {
if (file.isFile()) {
fileNames.add(file.getPath());
} else if (file.isDirectory()) {
addFullFilesFromDirectory(file, fileNames);
}
}
}
}
/**
* 获取相对文件夹路径
*
* @return 相对当前的文件夹路径
* @throws IOException IOException
* @throws URISyntaxException URISyntaxException
*/
public static List<String> getRelativeFiles(String dirname) throws IOException, URISyntaxException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
URL resource = classLoader.getResource(dirname);
if (resource == null) return Collections.emptyList();
// 处理JAR包内的情况
if (resource.getProtocol().equals("jar")) {
return getFilesFromJar(resource, dirname);
} else {
// 处理文件系统情况
return getFilesFromFileSystem(resource);
}
}
private static List<String> getFilesFromJar(URL jarUrl, String dirname) throws IOException {
List<String> fileNames = new ArrayList<>();
String jarPath = jarUrl.getPath().substring(5, jarUrl.getPath().indexOf("!"));
try (JarFile jar = new JarFile(jarPath)) {
jar.entries().asIterator()
.forEachRemaining(entry -> {
String name = entry.getName();
String prefix = dirname + "/";
if (name.startsWith(prefix) && !name.endsWith("/")) {
fileNames.add(name.substring(prefix.length()));
}
});
}
return fileNames;
}
private static List<String> getFilesFromFileSystem(URL resource) throws IOException, URISyntaxException {
Path filepath = Paths.get(resource.toURI());
try (Stream<Path> paths = Files.walk(filepath)) {
return paths.filter(Files::isRegularFile)
.map(filepath::relativize)
.map(Path::toString)
// 统一使用/作为分隔符
.map(s -> s.replace('\\', '/'))
.toList();
}
}
}

View File

@ -0,0 +1,58 @@
package com.auth.module.generator.utils;
import com.auth.module.generator.domain.vo.GeneratorVo;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* VMS代码生成系统的ZIP打包服务
* <p>
* 提供将生成的代码模板打包为ZIP文件并支持下载的功能
*/
public class ZipFileUtil {
private static final String FILE_EXTENSION = ".vm";
private static final String UTF_8 = StandardCharsets.UTF_8.name();
/**
* 创建ZIP文件
*
* @param generatorVoList 生成的代码列表
* @return ZIP文件字节数组
* @throws RuntimeException 打包失败时抛出
*/
public static byte[] createZipFile(List<GeneratorVo> generatorVoList) {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream, StandardCharsets.UTF_8)) {
generatorVoList.forEach(vo -> addToZip(zipOutputStream, vo));
return byteArrayOutputStream.toByteArray();
} catch (IOException e) {
throw new RuntimeException("Failed to create ZIP file", e);
}
}
/**
* 添加文件到ZIP 将单个代码文件添加到ZIP输出流
*
* @param zipOutputStream ZIP文件输出流
* @param generatorVo 代码生成结果对象包含文件路径和内容
* @throws RuntimeException 当文件添加失败时抛出包含失败文件路径信息
*/
private static void addToZip(ZipOutputStream zipOutputStream, GeneratorVo generatorVo) {
try {
String entryPath = generatorVo.getPath().replace(FILE_EXTENSION, "");
zipOutputStream.putNextEntry(new ZipEntry(entryPath));
zipOutputStream.write(generatorVo.getCode().getBytes(UTF_8));
zipOutputStream.closeEntry();
} catch (IOException e) {
throw new RuntimeException("Failed to add file to ZIP: " + generatorVo.getPath(), e);
}
}
}

View File

@ -0,0 +1,7 @@
bunny:
master:
host: rm-bp12z6hlv46vi6g8mro.mysql.rds.aliyuncs.com
port: 3306
database: test_auth
username: bunny_test
password: Test1234

View File

@ -0,0 +1,32 @@
server:
port: 8800
spring:
profiles:
active: dev
application:
name: generator-code
devtools:
livereload:
enabled: true
port: 8880
thymeleaf:
check-template-location: false
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://${bunny.master.host}:${bunny.master.port}/${bunny.master.database}?serverTimezone=GMT%2B8&useSSL=false&characterEncoding=utf-8&allowPublicKeyRetrieval=true
username: ${bunny.master.username}
password: ${bunny.master.password}
hikari:
maximum-pool-size: 20
connection-timeout: 30000
logging:
file:
path: "logs/${spring.application.name}"
level:
root: info
com.auth: debug

View File

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

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<contextName>logback</contextName>
<!-- 格式化 年--日 输出 -->
<timestamp key="datetime" datePattern="yyyy-MM-dd"/>
<!-- 引入Spring属性 -->
<springProperty name="APP_NAME" source="spring.application.name" defaultValue="application"/>
<!--编码-->
<property name="ENCODING" value="UTF-8"/>
<!-- 控制台日志 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!-- 临界值过滤器 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
<encoder>
<pattern>
%cyan([%thread %d{yyyy-MM-dd HH:mm:ss}]) %yellow(%-5level) %green(%logger{100}).%boldRed(%method)-%boldMagenta(%line)-%blue(%msg%n)
</pattern>
<charset>${ENCODING}</charset>
</encoder>
</appender>
<!-- 文件日志 -->
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>logs/${APP_NAME}/${datetime}.log</file>
<append>true</append>
<encoder>
<pattern>%date{yyyy-MM-dd HH:mm:ss} [%-5level] %thread %file:%line %logger %msg%n</pattern>
<charset>${ENCODING}</charset>
</encoder>
</appender>
<!-- 让SpringBoot内部日志ERROR级别 减少日志输出 -->
<logger name="org.springframework" level="ERROR" additivity="false">
<appender-ref ref="STOUT"/>
</logger>
<!-- 让mybatis整合包日志ERROR 减少日志输出 -->
<logger name="org.mybatis" level="ERROR" additivity="false">
<appender-ref ref="STOUT"/>
</logger>
<!-- 让ibatis 日志ERROR 减少日志输出 -->
<logger name="org.apache.ibatis" level="ERROR" additivity="false">
<appender-ref ref="STOUT"/>
</logger>
<!-- 让 tomcat包打印日志 日志ERROR 减少日志输出 -->
<logger name="org.apache" level="ERROR" additivity="false">
<appender-ref ref="STOUT"/>
</logger>
<!-- 我们自己开发的程序为DEBUG -->
<logger name="cn.bunny" level="DEBUG" additivity="false">
<appender-ref ref="STOUT"/>
</logger>
<logger name="com.baomidou" level="ERROR" additivity="false">
<appender-ref ref="STOUT"/>
</logger>
<!-- 根日志记录器INFO级别 -->
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
</configuration>

View File

@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>400 错误 - generator-code</title>
<meta content="" name="keywords">
<meta content="" name="description">
<meta content="webkit" name="renderer">
<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible">
<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<meta content="black" name="apple-mobile-web-app-status-bar-style">
<meta content="yes" name="apple-mobile-web-app-capable">
<meta content="telephone=no" name="format-detection">
<meta CONTENT="no-cache" HTTP-EQUIV="pragma">
<meta CONTENT="no-store, must-revalidate" HTTP-EQUIV="Cache-Control">
<meta CONTENT="Wed, 26 Feb 1997 08:21:57 GMT" HTTP-EQUIV="expires">
<meta CONTENT="0" HTTP-EQUIV="expires">
<style>
body {
font: 16px arial, 'Microsoft Yahei', 'Hiragino Sans GB', sans-serif;
}
h1 {
margin: 0;
color: #3a87ad;
font-size: 26px;
}
.content {
width: 45%;
margin: 0 auto;
}
.content > div {
margin-top: 200px;
padding: 20px;
background: #d9edf7;
border-radius: 12px;
}
.content dl {
color: #2d6a88;
line-height: 40px;
}
.content div div {
padding-bottom: 20px;
text-align: center;
}
</style>
</head>
<body>
<div class="content">
<div>
<h1>HTTP 400 - Bad Request</h1>
<dl>
<dt>错误说明:因为错误的语法导致服务器无法理解请求信息。</dt>
<dt>原因1客户端发起的请求不符合服务器对请求的某些限制或者请求本身存在一定的错误。</dt>
<dd>解决办法:</dd>
<dd>链接中有特殊字符或者链接长度过长导致,请对应修改.</dd>
<dt>原因2request header 或者 cookie 过大所引起</dt>
<dd>解决办法:</dd>
<dd>crtl+shift+delete 快捷键清除cookie.</dd>
</dl>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>403 错误 - generator-code</title>
<meta content="" name="keywords">
<meta content="" name="description">
<meta content="webkit" name="renderer">
<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible">
<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<meta content="black" name="apple-mobile-web-app-status-bar-style">
<meta content="yes" name="apple-mobile-web-app-capable">
<meta content="telephone=no" name="format-detection">
<meta CONTENT="no-cache" HTTP-EQUIV="pragma">
<meta CONTENT="no-store, must-revalidate" HTTP-EQUIV="Cache-Control">
<meta CONTENT="Wed, 26 Feb 1997 08:21:57 GMT" HTTP-EQUIV="expires">
<meta CONTENT="0" HTTP-EQUIV="expires">
<style>
body {
font: 16px arial, 'Microsoft Yahei', 'Hiragino Sans GB', sans-serif;
}
h1 {
margin: 0;
color: #3a87ad;
font-size: 26px;
}
.content {
width: 45%;
margin: 0 auto;
}
.content > div {
margin-top: 200px;
padding: 20px;
background: #d9edf7;
border-radius: 12px;
}
.content dl {
color: #2d6a88;
line-height: 40px;
}
.content div div {
padding-bottom: 20px;
text-align: center;
}
</style>
</head>
<body>
<div class="content">
<div>
<h1>403 - Forbidden 禁止访问: 访问被拒绝</h1>
<dl>
<dt>错误说明:禁止访问,服务器拒绝访问</dt>
<dt>原因1未找到默认的索引文件</dt>
<dd>解决办法:</dd>
<dd>IIS中【启用默认内容文档】选项中将默认打开文档修改为程序首页文件格式index.html或者index.php</dd>
<dt>原因2文件夹安全权限导致</dt>
<dd>解决办法:</dd>
<dd>程序文件-右击-属性-安全-Users-修改为读取和执行权限</dd>
</dl>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,78 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>404 错误 - generator-code</title>
<meta content="" name="keywords">
<meta content="" name="description">
<meta content="webkit" name="renderer">
<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible">
<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<meta content="black" name="apple-mobile-web-app-status-bar-style">
<meta content="yes" name="apple-mobile-web-app-capable">
<meta content="telephone=no" name="format-detection">
<meta CONTENT="no-cache" HTTP-EQUIV="pragma">
<meta CONTENT="no-store, must-revalidate" HTTP-EQUIV="Cache-Control">
<meta CONTENT="Wed, 26 Feb 1997 08:21:57 GMT" HTTP-EQUIV="expires">
<meta CONTENT="0" HTTP-EQUIV="expires">
<style>
body {
font: 16px arial, 'Microsoft Yahei', 'Hiragino Sans GB', sans-serif;
}
h1 {
margin: 0;
color: #3a87ad;
font-size: 26px;
}
.content {
width: 45%;
margin: 0 auto;
}
.content > div {
margin-top: 50px;
padding: 20px;
background: #d9edf7;
border-radius: 12px;
}
.content dl {
color: #2d6a88;
line-height: 40px;
}
.content div div {
padding-bottom: 20px;
text-align: center;
}
</style>
</head>
<body>
<div class="content">
<div>
<h1>404 - Page Not Found 未找到</h1>
<dl>
<dt>错误说明:请求的页面不存在</dt>
<dt>原因1访问的文档权限不够</dt>
<dd>解决办法:</dd>
<dd>修改文件权限为755windos系统修改目录权限为可写可读。</dd>
<dt>原因2防火墙的原因</dt>
<dd>解决办法:</dd>
<dd>先关闭让防火墙通过WWW服务。</dd>
<dt>原因3站点根目录无默认访问文件</dt>
<dd>解决办法:</dd>
<dd>在根目录中创建index.html或者创建index.php。</dd>
<dt>原因4站点配置目录不正确</dt>
<dd>解决办法:</dd>
<dd>将网站应用程序复制到站点目录中,或者修改站点配置目录指定到应用程序目录中。</dd>
<dt>原因5站点使用了伪静态</dt>
<dd>解决办法:</dd>
<dd>将伪静态规则删除,或者重新编写正确的伪静态规则,或关闭伪静态配置。</dd>
</dl>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,64 @@
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="UTF-8">
<title>500 - 服务器错误</title>
<meta content="width=device-width, maximum-scale=1, initial-scale=1" name="viewport"/>
<style>
html, body {
height: 100%;
}
body {
color: #333;
margin: auto;
padding: 1em;
display: table;
user-select: none;
box-sizing: border-box;
font: lighter 20px "微软雅黑";
}
a {
color: #3498db;
text-decoration: none;
}
h1 {
margin-top: 0;
font-size: 3.5em;
}
main {
margin: 0 auto;
text-align: center;
display: table-cell;
vertical-align: middle;
}
.btn {
color: #fff;
padding: .75em 1em;
background: #3498db;
border-radius: 1.5em;
display: inline-block;
transition: opacity .3s, transform .3s;
}
.btn:hover {
transform: scale(1.1);
}
.btn:active {
opacity: .7;
}
</style>
</head>
<body>
<main>
<h1>:'(</h1>
<p>服务器开小差啦!管理员正在修理中...</p>
<p>还请阁下静候站点恢复~</p>
</main>
</body>
</html>

View File

@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>501 错误 - generator-code</title>
<meta content="" name="keywords">
<meta content="" name="description">
<meta content="webkit" name="renderer">
<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible">
<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<meta content="black" name="apple-mobile-web-app-status-bar-style">
<meta content="yes" name="apple-mobile-web-app-capable">
<meta content="telephone=no" name="format-detection">
<meta CONTENT="no-cache" HTTP-EQUIV="pragma">
<meta CONTENT="no-store, must-revalidate" HTTP-EQUIV="Cache-Control">
<meta CONTENT="Wed, 26 Feb 1997 08:21:57 GMT" HTTP-EQUIV="expires">
<meta CONTENT="0" HTTP-EQUIV="expires">
<style>
body {
font: 16px arial, 'Microsoft Yahei', 'Hiragino Sans GB', sans-serif;
}
h1 {
margin: 0;
color: #3a87ad;
font-size: 26px;
}
.content {
width: 45%;
margin: 0 auto;
}
.content > div {
margin-top: 200px;
padding: 20px;
background: #d9edf7;
border-radius: 12px;
}
.content dl {
color: #2d6a88;
line-height: 40px;
}
.content div div {
padding-bottom: 20px;
text-align: center;
}
</style>
</head>
<body>
<div class="content">
<div>
<h1>HTTP 501 - Not Implemented</h1>
<dl>
<dt>错误说明:服务器没有相应的执行动作来完成当前请求。</dt>
<dt>原因1Web 服务器不支持实现此请求所需的功能</dt>
<dd>解决办法:</dd>
<dd>可以用来HttpWebRequest指定一个UserAgent来试试的有时候你可以换电脑来测试一下的。</dd>
</dl>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,80 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>502 错误 - generator-code</title>
<meta content="" name="keywords">
<meta content="" name="description">
<meta content="webkit" name="renderer">
<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible">
<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<meta content="black" name="apple-mobile-web-app-status-bar-style">
<meta content="yes" name="apple-mobile-web-app-capable">
<meta content="telephone=no" name="format-detection">
<meta CONTENT="no-cache" HTTP-EQUIV="pragma">
<meta CONTENT="no-store, must-revalidate" HTTP-EQUIV="Cache-Control">
<meta CONTENT="Wed, 26 Feb 1997 08:21:57 GMT" HTTP-EQUIV="expires">
<meta CONTENT="0" HTTP-EQUIV="expires">
<style>
body {
font: 16px arial, 'Microsoft Yahei', 'Hiragino Sans GB', sans-serif;
}
h1 {
margin: 0;
color: #3a87ad;
font-size: 26px;
}
.content {
width: 45%;
margin: 0 auto;
}
.content > div {
margin-top: 50px;
padding: 20px;
background: #d9edf7;
border-radius: 12px;
}
.content dl {
color: #2d6a88;
line-height: 40px;
}
.content div div {
padding-bottom: 20px;
text-align: center;
}
</style>
</head>
<body>
<div class="content">
<div>
<h1>HTTP 502 - Bad Gateway 没有响应</h1>
<dl>
<dt>错误说明坏的网关http向后端节点请求没有响应</dt>
<dt>原因1DNS 缓冲</dt>
<dd>解决办法:</dd>
<dd>在dos窗口运行 ipconfig /flushdns该命令会刷新DNS缓冲。</dd>
<dt>原因2浏览器代理</dt>
<dd>解决办法:</dd>
<dd>关掉代理。</dd>
<dt>原因3dns 被劫持了即使使用国外的dns也会被劫持</dt>
<dd>解决办法:</dd>
<dd>
去掉VPN服务器的DNS。切换另外的dns。在windows系统中可以在本地网络连接的属性中去掉默认的dns选用国外的dns比如google的或opendns。
</dd>
<dt>原因4php执行超时</dt>
<dd>解决办法:</dd>
<dd>修改/usr/local/php/etc/php.ini 将max_execution_time 改为300。</dd>
<dt>原因5nginx等待时间超时</dt>
<dd>解决办法:</dd>
<dd>适当增加nginx.conf配置文件中FastCGI的timeout时间。</dd>
</dl>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>503 错误 - generator-code</title>
<meta content="" name="keywords">
<meta content="" name="description">
<meta content="webkit" name="renderer">
<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible">
<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<meta content="black" name="apple-mobile-web-app-status-bar-style">
<meta content="yes" name="apple-mobile-web-app-capable">
<meta content="telephone=no" name="format-detection">
<meta CONTENT="no-cache" HTTP-EQUIV="pragma">
<meta CONTENT="no-store, must-revalidate" HTTP-EQUIV="Cache-Control">
<meta CONTENT="Wed, 26 Feb 1997 08:21:57 GMT" HTTP-EQUIV="expires">
<meta CONTENT="0" HTTP-EQUIV="expires">
<style>
body {
font: 16px arial, 'Microsoft Yahei', 'Hiragino Sans GB', sans-serif;
}
h1 {
margin: 0;
color: #3a87ad;
font-size: 26px;
}
.content {
width: 45%;
margin: 0 auto;
}
.content > div {
margin-top: 200px;
padding: 20px;
background: #d9edf7;
border-radius: 12px;
}
.content dl {
color: #2d6a88;
line-height: 40px;
}
.content div div {
padding-bottom: 20px;
text-align: center;
}
</style>
</head>
<body>
<div class="content">
<div>
<h1>HTTP 503 - Service Unavailable 服务不可用</h1>
<dl>
<dt>错误说明:服务当前不可用</dt>
<dt>原因1服务不可用状态</dt>
<dd>解决办法:</dd>
<dd>服务器或许就是正在维护或者暂停了,你可以联系一下服务器空间商。</dd>
<dt>原因2程序占用资源太多</dt>
<dd>解决办法:</dd>
<dd>通过设置应用程序池把账户改为NetworkService即可解决。</dd>
</dl>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,81 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>504 错误 - generator-code</title>
<meta content="" name="keywords">
<meta content="" name="description">
<meta content="webkit" name="renderer">
<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible">
<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<meta content="black" name="apple-mobile-web-app-status-bar-style">
<meta content="yes" name="apple-mobile-web-app-capable">
<meta content="telephone=no" name="format-detection">
<meta CONTENT="no-cache" HTTP-EQUIV="pragma">
<meta CONTENT="no-store, must-revalidate" HTTP-EQUIV="Cache-Control">
<meta CONTENT="Wed, 26 Feb 1997 08:21:57 GMT" HTTP-EQUIV="expires">
<meta CONTENT="0" HTTP-EQUIV="expires">
<style>
body {
font: 16px arial, 'Microsoft Yahei', 'Hiragino Sans GB', sans-serif;
}
h1 {
margin: 0;
color: #3a87ad;
font-size: 26px;
}
.content {
width: 45%;
margin: 0 auto;
}
.content > div {
margin-top: 50px;
padding: 20px;
background: #d9edf7;
border-radius: 12px;
}
.content dl {
color: #2d6a88;
line-height: 40px;
}
.content div div {
padding-bottom: 20px;
text-align: center;
}
</style>
</head>
<body>
<div class="content">
<div>
<h1>HTTP 504 - Gateway Timeout 网关超时</h1>
<dl>
<dt>错误说明:网关超时,服务器响应时间,达到超出设定的范围</dt>
<dt>原因1后端电脑之间 IP 通讯缓慢而产生</dt>
<dd>解决办法:</dd>
<dd>如果您的 Web 服务器由某一网站托管, 只有负责那个网站设置的人员才能解决这个问题。</dd>
<dt>原因2由于nginx默认的fastcgi进程响应的缓冲区太小造成的错误</dt>
<dd>解决办法:</dd>
<dd>一般默认的fastcgi进程响应的缓冲区是8K这时可以设置大一点在nginx.conf里加入fastcgi_buffers 8
128k这表示设置fastcgi缓冲区为8块128k大小的空间。当然如果在进行某一项即时的操作, 可能需要nginx的超时参数调大点,
例如设置成60秒:send_timeout 60;经过这两个参数的调整一般不会再提示“504 Gateway Time-out”错误问题基本解决。
</dd>
<dt>原因3PHP环境的配置问题</dt>
<dd>解决办法:</dd>
<dd>更改php-fpm的几处配置 把max_children由之前的10改为现在的30这样就可以保证有充足的php-cgi进程可以被使用
把request_terminate_timeout由之前的0s改为60s这样php-cgi进程 处理脚本的超时时间就是60秒可以防止进程都被挂起提高利用效率。
接着再更改nginx的几个配置项减少FastCGI的请求次数尽量维持buffers不变 fastcgi_buffers由 4 64k 改为 2
256k fastcgi_buffer_size 由 64k 改为 128K fastcgi_busy_buffers_size 由 128K 改为 256K
fastcgi_temp_file_write_size 由 128K 改为 256K。 重新加载php-fpm和nginx的配置再次测试如果没有出现“504
Gateway Time-out”错误问题解决。
</dd>
</dl>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,72 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>505 错误 - generator-code</title>
<meta content="" name="keywords">
<meta content="" name="description">
<meta content="webkit" name="renderer">
<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible">
<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<meta content="black" name="apple-mobile-web-app-status-bar-style">
<meta content="yes" name="apple-mobile-web-app-capable">
<meta content="telephone=no" name="format-detection">
<meta CONTENT="no-cache" HTTP-EQUIV="pragma">
<meta CONTENT="no-store, must-revalidate" HTTP-EQUIV="Cache-Control">
<meta CONTENT="Wed, 26 Feb 1997 08:21:57 GMT" HTTP-EQUIV="expires">
<meta CONTENT="0" HTTP-EQUIV="expires">
<style>
body {
font: 16px arial, 'Microsoft Yahei', 'Hiragino Sans GB', sans-serif;
}
h1 {
margin: 0;
color: #3a87ad;
font-size: 26px;
}
.content {
width: 45%;
margin: 0 auto;
}
.content > div {
margin-top: 200px;
padding: 20px;
background: #d9edf7;
border-radius: 12px;
}
.content dl {
color: #2d6a88;
line-height: 40px;
}
.content div div {
padding-bottom: 20px;
text-align: center;
}
</style>
</head>
<body>
<div class="content">
<div>
<h1>HTTP 505 - HTTP Version Not Supported</h1>
<dl>
<dt>错误说明HTTP 版本不受支持。</dt>
<dt>原因1您的 Web 服务器不支持,或拒绝支持客户端(如您的浏览器)在发送给服务器的 HTTP 请求数据流中指定的 HTTP
协议版本
</dt>
<dd>解决办法:</dd>
<dd>升级您的 Web 服务器软件。</dd>
<dt>原因2http请求格式的错误</dt>
<dd>解决办法:</dd>
<dd>对照一下自己的代码从打印的信息中终于找到问题所在。可能在请求后面多加了一个空格。http协议真是很严格了。
</dd>
</dl>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>506 错误 - generator-code</title>
<meta content="" name="keywords">
<meta content="" name="description">
<meta content="webkit" name="renderer">
<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible">
<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<meta content="black" name="apple-mobile-web-app-status-bar-style">
<meta content="yes" name="apple-mobile-web-app-capable">
<meta content="telephone=no" name="format-detection">
<meta CONTENT="no-cache" HTTP-EQUIV="pragma">
<meta CONTENT="no-store, must-revalidate" HTTP-EQUIV="Cache-Control">
<meta CONTENT="Wed, 26 Feb 1997 08:21:57 GMT" HTTP-EQUIV="expires">
<meta CONTENT="0" HTTP-EQUIV="expires">
<style>
body {
font: 16px arial, 'Microsoft Yahei', 'Hiragino Sans GB', sans-serif;
}
h1 {
margin: 0;
color: #3a87ad;
font-size: 26px;
}
.content {
width: 45%;
margin: 0 auto;
}
.content > div {
margin-top: 200px;
padding: 20px;
background: #d9edf7;
border-radius: 12px;
}
.content dl {
color: #2d6a88;
line-height: 40px;
}
.content div div {
padding-bottom: 20px;
text-align: center;
}
</style>
</head>
<body>
<div class="content">
<div>
<h1>HTTP 506 - Variant Also Negotiates</h1>
<dl>
<dt>错误说明:</dt>
<dt>原因1服务器存在内部配置错误</dt>
<dd>解决办法:</dd>
<dd>被请求的协商变元资源被配置为在透明内容协商中使用自己,因此在一个协商处理中不是一个合适的重点。</dd>
</dl>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>507 错误 - generator-code</title>
<meta content="" name="keywords">
<meta content="" name="description">
<meta content="webkit" name="renderer">
<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible">
<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<meta content="black" name="apple-mobile-web-app-status-bar-style">
<meta content="yes" name="apple-mobile-web-app-capable">
<meta content="telephone=no" name="format-detection">
<meta CONTENT="no-cache" HTTP-EQUIV="pragma">
<meta CONTENT="no-store, must-revalidate" HTTP-EQUIV="Cache-Control">
<meta CONTENT="Wed, 26 Feb 1997 08:21:57 GMT" HTTP-EQUIV="expires">
<meta CONTENT="0" HTTP-EQUIV="expires">
<style>
body {
font: 16px arial, 'Microsoft Yahei', 'Hiragino Sans GB', sans-serif;
}
h1 {
margin: 0;
color: #3a87ad;
font-size: 26px;
}
.content {
width: 45%;
margin: 0 auto;
}
.content > div {
margin-top: 200px;
padding: 20px;
background: #d9edf7;
border-radius: 12px;
}
.content dl {
color: #2d6a88;
line-height: 40px;
}
.content div div {
padding-bottom: 20px;
text-align: center;
}
</style>
</head>
<body>
<div class="content">
<div>
<h1>HTTP 507 - Insufficient Storage</h1>
<dl>
<dt>错误说明:</dt>
<dt>原因1服务器无法存储完成请求所必须的内容</dt>
<dd>解决办法:</dd>
<dd>这个状况被认为是临时的。WebDAV (RFC 4918)。</dd>
</dl>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>509 错误 - generator-code</title>
<meta content="" name="keywords">
<meta content="" name="description">
<meta content="webkit" name="renderer">
<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible">
<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<meta content="black" name="apple-mobile-web-app-status-bar-style">
<meta content="yes" name="apple-mobile-web-app-capable">
<meta content="telephone=no" name="format-detection">
<meta CONTENT="no-cache" HTTP-EQUIV="pragma">
<meta CONTENT="no-store, must-revalidate" HTTP-EQUIV="Cache-Control">
<meta CONTENT="Wed, 26 Feb 1997 08:21:57 GMT" HTTP-EQUIV="expires">
<meta CONTENT="0" HTTP-EQUIV="expires">
<style>
body {
font: 16px arial, 'Microsoft Yahei', 'Hiragino Sans GB', sans-serif;
}
h1 {
margin: 0;
color: #3a87ad;
font-size: 26px;
}
.content {
width: 45%;
margin: 0 auto;
}
.content > div {
margin-top: 200px;
padding: 20px;
background: #d9edf7;
border-radius: 12px;
}
.content dl {
color: #2d6a88;
line-height: 40px;
}
.content div div {
padding-bottom: 20px;
text-align: center;
}
</style>
</head>
<body>
<div class="content">
<div>
<h1>HTTP 509 - Bandwidth Limit Exceeded</h1>
<dl>
<dt>错误说明:</dt>
<dt>原因1网站流量已经超出您所购买的方案限制即服务器达到带宽限制</dt>
<dd>解决办法:</dd>
<dd>1.升级方案 2.等到下个月后流量重新计算,网站即可正常浏览。</dd>
</dl>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>510 错误 - generator-code</title>
<meta content="" name="keywords">
<meta content="" name="description">
<meta content="webkit" name="renderer">
<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible">
<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<meta content="black" name="apple-mobile-web-app-status-bar-style">
<meta content="yes" name="apple-mobile-web-app-capable">
<meta content="telephone=no" name="format-detection">
<meta CONTENT="no-cache" HTTP-EQUIV="pragma">
<meta CONTENT="no-store, must-revalidate" HTTP-EQUIV="Cache-Control">
<meta CONTENT="Wed, 26 Feb 1997 08:21:57 GMT" HTTP-EQUIV="expires">
<meta CONTENT="0" HTTP-EQUIV="expires">
<style>
body {
font: 16px arial, 'Microsoft Yahei', 'Hiragino Sans GB', sans-serif;
}
h1 {
margin: 0;
color: #3a87ad;
font-size: 26px;
}
.content {
width: 45%;
margin: 0 auto;
}
.content > div {
margin-top: 200px;
padding: 20px;
background: #d9edf7;
border-radius: 12px;
}
.content dl {
color: #2d6a88;
line-height: 40px;
}
.content div div {
padding-bottom: 20px;
text-align: center;
}
</style>
</head>
<body>
<div class="content">
<div>
<h1>HTTP 510 - Not Extended</h1>
<dl>
<dt>错误说明:</dt>
<dt>原因1获取资源所需要的策略并没有被满足</dt>
<dd>解决办法:</dd>
<dd>需要请求有额外的扩展内容,服务器才能处理请求。</dd>
</dl>
</div>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@ -0,0 +1,179 @@
const AppGeneratorPage = defineComponent({
name: "MainGeneratorPage",
template: `
<div class="offcanvas offcanvas-start" data-bs-scroll="false" tabindex="-1" ref="offcanvasElementRef">
<!-- 侧边栏头部 -->
<div class="offcanvas-header bg-primary text-white">
<div>
<h5 class="offcanvas-title mb-1">
<i class="bi bi-file-earmark-code me-2"></i>
</h5>
<p class="small mb-0 opacity-75">已生成的文件列表</p>
</div>
<button @click="onGeneratorPageFlagClick" aria-label="Close" class="btn-close btn-close-white" type="button"></button>
</div>
<!-- 侧边栏内容区域 -->
<div class="offcanvas-body p-0">
<ol class="list-group list-group-numbered rounded-0">
<li class="list-group-item border-0 p-3" v-for="[table, value] in Object.entries(generatorData)" :key="table">
<h6 class="mb-0 fw-bold text-primary d-inline-block"><i class="bi bi-table me-2"></i>{{table}}</h6>
<span class="badge bg-primary rounded-pill float-end">{{value.length}} 模板</span>
<!-- 生成的文件列表 -->
<ul class="list-group list-group-flush">
<!-- 单个文件卡片 -->
<li class="list-group-item p-0 mb-2 border-0" v-for="(item, index) in value" :key="item.id">
<div class="card shadow-sm border-0">
<!-- 文件标题 - 可折叠 -->
<div class="card-header bg-light d-flex justify-content-between align-items-center p-3"
role="button" data-bs-toggle="collapse" :data-bs-target="'#collapse-' + item.id" aria-expanded="false">
<div class="d-flex align-items-center">
<i class="bi bi-bi-file-earmark-code me-2 text-primary fs-5"></i>
<span class="text-truncate" style="max-width: 99%" :title="item.path">
{{item.comment}}{{item.path}}
</span>
</div>
<i class="bi bi-chevron-down transition-all"></i>
</div>
<!-- 文件内容 -->
<div class="collapse" :id="'collapse-' + item.id">
<div class="card-body p-0 bg-dark">
<div class="position-relative">
<!-- 使用条件渲染显示不同图标 -->
<i v-if="!copied"
class="bi bi-clipboard position-absolute text-white fs-4"
role="button" style="top: 10px; right: 10px" @click="onCopyToClipboard(item.code)"></i>
<i v-else
class="bi bi-clipboard-check position-absolute text-success fs-4"
role="button" style="top: 10px; right: 10px"></i>
</div>
<pre>
<code class="language-javascript hljs">
{{item.code}}
</code>
</pre>
</div>
</div>
</div>
</li>
</ul>
</li>
</ol>
</div>
<!-- 侧边栏底部操作按钮 -->
<div class="offcanvas-footer p-3 bg-light border-top">
<div class="d-grid gap-2">
<button class="btn btn-primary" @click="onDownloadAllFile">
<i class="bi bi-download me-2"></i>
</button>
</div>
</div>
</div>
`,
props: {
// 是否显示生成页面
generatorPageFlag: {type: Boolean, default: true},
// 生成模板数据
generatorData: {type: Object},
},
data() {
return {
// 控制图标状态的响应式变量
copied: ref(false),
// 存储 offcanvas 实例
offcanvas: ref(null)
}
},
methods: {
/**
* 点击复制图表
* 几秒后恢复原状
*/
async onCopyToClipboard(code) {
this.copied = true;
try {
await navigator.clipboard.writeText(code);
antd.notification.open({
type: 'success',
message: '复制成功',
description: '已将内容复制至剪切板',
duration: 3,
});
} catch (err) {
antd.notification.open({
type: 'error',
message: '复制失败',
description: err.message``,
duration: 3,
});
console.error('复制失败:', err);
// 回退到传统方法
const textarea = document.createElement('textarea');
textarea.value = code;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
}
// 显示已复制图标时长
if (this.copied) {
setTimeout(() => {
this.copied = false;
}, 2000)
}
},
/* 下载全部文件 */
onDownloadAllFile() {
// 遍历所有文件并下载
Object.values(this.generatorData).flat().forEach(item => {
const blob = new Blob([item.code], {type: 'text/plain'});
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
// 从路径中提取文件名
const fileName = item.path.split('/').pop();
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
// 清理
setTimeout(() => {
document.body.removeChild(link);
URL.revokeObjectURL(url);
}, 100);
});
},
/* 关闭窗口 */
onGeneratorPageFlagClick() {
this.$emit("update:generatorPageFlag", !this.generatorPageFlag);
},
},
mounted() {
// 初始化 OffCanvas
this.offcanvas = new bootstrap.Offcanvas(this.$refs.offcanvasElementRef);
// 监听隐藏事件
this.$refs.offcanvasElementRef.addEventListener('hidden.bs.offcanvas', () => {
this.$emit("update:generatorPageFlag", false);
});
},
watch: {
generatorPageFlag(newVal) {
if (newVal) {
this.offcanvas.show();
} else {
this.offcanvas.hide();
}
},
}
});

View File

@ -0,0 +1,54 @@
// 如果一开始定义过了 defineComponent 就不要在下 script 标签中再写了
const {defineComponent} = Vue;
// 定义 Header 组件
const AppHeader = defineComponent({
name: "AppHeader",
template: `
<header>
<!-- 头部 -->
<div class="header-content text-center mb-2 p-2 bg-light rounded shadow-sm">
<h2 class="text-primary fw-bold mb-3">
<i class="bi bi-code-square me-2"></i>
Bunny{{ title || '代码生成器' }}
</h2>
<p class="text-muted mb-0">
快速生成数据库表对应的代码这里可以跳转到
<a href="/database" class="text-decoration-none"><i class="bi bi-database-fill"></i></a>
<a href="/sql" class="text-decoration-none"><i class="bi bi-filetype-sql"></i>SQL</a>
</p>
<!-- 代码仓库链接区域包含GitHub和Gitee仓库链接 -->
<div class="d-flex justify-content-center align-items-center mt-2 gap-3">
<!-- GitHub 仓库链接 -->
<a class="btn btn-outline-dark d-flex align-items-center gap-2"
href="https://github.com/BunnyMaster/generator-code-server" target="_blank">
<svg class="feather feather-github" fill="none" height="20" stroke="currentColor"
stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24"
width="20"
xmlns="http://www.w3.org/2000/svg">
<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path>
</svg>
GitHub 仓库
</a>
<!-- Gitee 仓库链接 -->
<a class="btn btn-outline-danger d-flex align-items-center gap-2"
href="https://gitee.com/BunnyBoss/generator-code-server" target="_blank">
<svg height="20" viewBox="0 0 24 24" width="20" xmlns="http://www.w3.org/2000/svg">
<path d="M11.984 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12a12 12 0 0 0 12-12A12 12 0 0 0 12 0zm6.09 5.333c.328 0 .593.266.592.593v1.482a.594.594 0 0 1-.593.592H9.777c-.982 0-1.778.796-1.778 1.778v5.63c0 .327.266.592.593.592h5.63c.982 0 1.778-.796 1.778-1.778v-.296a.593.593 0 0 0-.592-.593h-4.15a.59.59 0 0 1-.592-.592v-1.482a.593.593 0 0 1 .593-.592h6.815c.327 0 .593.265.593.592v3.408a4 4 0 0 1-4 4H5.926a.593.593 0 0 1-.593-.593V9.778a4.444 4.444 0 0 1 4.445-4.444h8.296Z"
fill="currentColor"/>
</svg>
Gitee 仓库
</a>
</div>
</div>
</header>
`,
data() {
return {
title: document.title,
}
}
});

View File

@ -0,0 +1,42 @@
// axios 配置
const axiosInstance = axios.create({
baseURL: 'http://localhost:8800/api',
timeout: 16000,
headers: {'Content-Type': 'application/json;charset=utf-8'},
});
// 响应拦截器
axiosInstance.interceptors.response.use(
(response) => {
// 检查配置的响应类型是否为二进制类型('blob' 或 'arraybuffer', 如果是,直接返回响应对象
if (response.config.responseType === 'blob' || response.config.responseType === 'arraybuffer') {
return response;
}
if (response.status === 200) {
const {code, message} = response.data;
if (code !== 200) {
antd.message.error(message);
}
return response.data;
}
// 系统出错
return Promise.reject(response.data.message || 'Error');
},
(error) => {
// 异常处理
if (error.response.data) {
const {code, message} = error.response.data;
if (code === 500) {
antd.message.error(message);
} else {
antd.message.error(message || '系统出错');
}
return error.response.data;
}
return Promise.reject(error.message);
}
);

View File

@ -0,0 +1,5 @@
document.addEventListener('DOMContentLoaded', (event) => {
document.querySelectorAll('pre code').forEach((block) => {
hljs.highlightElement(block);
});
});

View File

@ -0,0 +1,5 @@
document.addEventListener('DOMContentLoaded', function () {
// 初始化所有tooltip
const tooltips = document.querySelectorAll('[data-bs-toggle="tooltip"]');
[...tooltips].forEach(t => new bootstrap.Tooltip(t));
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#abb2bf;background:#282c34}.hljs-comment,.hljs-quote{color:#5c6370;font-style:italic}.hljs-doctag,.hljs-formula,.hljs-keyword{color:#c678dd}.hljs-deletion,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-subst{color:#e06c75}.hljs-literal{color:#56b6c2}.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#98c379}.hljs-attr,.hljs-number,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-type,.hljs-variable{color:#d19a66}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-symbol,.hljs-title{color:#61aeee}.hljs-built_in,.hljs-class .hljs-title,.hljs-title.class_{color:#e6c07b}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}

View File

@ -0,0 +1,7 @@
/* 添加 bootstrap 样式 */
@import url("bootstrap/bootstrap.min.css");
@import url("bootstrap/bootstrap-icons.min.css");
/* 引入Highlight.js的CSS */
@import url("highlight/atom-one-dark.min.css");
/* 添加自定义样式 */
@import url("./style/style.css");

View File

@ -0,0 +1,63 @@
/* 提高 Antd Message 的 z-index */
.ant-message {
z-index: 1100 !important;
}
/* 响应式 OffCanvas 宽度 */
.offcanvas.offcanvas-start {
width: 50%;
}
@media (max-width: 991.98px) {
.offcanvas.offcanvas-start {
width: 75%;
}
}
@media (max-width: 767.98px) {
.offcanvas.offcanvas-start {
width: 100%;
}
}
/* 添加自定义样式 */
.database-info-card {
border-left: 4px solid #0d6efd;
margin-top: 20px;
}
.table-info-section {
background-color: #f8f9fa;
border-radius: 5px;
padding: 15px;
margin-bottom: 20px;
}
.column-list {
max-height: 500px;
overflow-y: auto;
}
.column-item {
border-left: 3px solid #6c757d;
margin-bottom: 10px;
transition: all 0.3s;
}
.column-item:hover {
border-left-color: #0d6efd;
background-color: #f8f9fa;
}
.badge-java {
background-color: #5382a1;
}
.badge-jdbc {
background-color: #4479a1;
}
.badge-js {
background-color: #f7df1e;
color: #000;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).dayjs_plugin_advancedFormat=t()}(this,(function(){"use strict";return function(e,t){var r=t.prototype,n=r.format;r.format=function(e){var t=this,r=this.$locale();if(!this.isValid())return n.bind(this)(e);var s=this.$utils(),a=(e||"YYYY-MM-DDTHH:mm:ssZ").replace(/\[([^\]]+)]|Q|wo|ww|w|WW|W|zzz|z|gggg|GGGG|Do|X|x|k{1,2}|S/g,(function(e){switch(e){case"Q":return Math.ceil((t.$M+1)/3);case"Do":return r.ordinal(t.$D);case"gggg":return t.weekYear();case"GGGG":return t.isoWeekYear();case"wo":return r.ordinal(t.week(),"W");case"w":case"ww":return s.s(t.week(),"w"===e?1:2,"0");case"W":case"WW":return s.s(t.isoWeek(),"W"===e?1:2,"0");case"k":case"kk":return s.s(String(0===t.$H?24:t.$H),"k"===e?1:2,"0");case"X":return Math.floor(t.$d.getTime()/1e3);case"x":return t.$d.getTime();case"z":return"["+t.offsetName()+"]";case"zzz":return"["+t.offsetName("long")+"]";default:return e}}));return n.bind(this)(a)}}}));

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).dayjs_plugin_customParseFormat=t()}(this,(function(){"use strict";var e={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},t=/(\[[^[]*\])|([-_:/.,()\s]+)|(A|a|Q|YYYY|YY?|ww?|MM?M?M?|Do|DD?|hh?|HH?|mm?|ss?|S{1,3}|z|ZZ?)/g,n=/\d/,r=/\d\d/,i=/\d\d?/,o=/\d*[^-_:/,()\s\d]+/,s={},a=function(e){return(e=+e)+(e>68?1900:2e3)};var f=function(e){return function(t){this[e]=+t}},h=[/[+-]\d\d:?(\d\d)?|Z/,function(e){(this.zone||(this.zone={})).offset=function(e){if(!e)return 0;if("Z"===e)return 0;var t=e.match(/([+-]|\d\d)/g),n=60*t[1]+(+t[2]||0);return 0===n?0:"+"===t[0]?-n:n}(e)}],u=function(e){var t=s[e];return t&&(t.indexOf?t:t.s.concat(t.f))},d=function(e,t){var n,r=s.meridiem;if(r){for(var i=1;i<=24;i+=1)if(e.indexOf(r(i,0,t))>-1){n=i>12;break}}else n=e===(t?"pm":"PM");return n},c={A:[o,function(e){this.afternoon=d(e,!1)}],a:[o,function(e){this.afternoon=d(e,!0)}],Q:[n,function(e){this.month=3*(e-1)+1}],S:[n,function(e){this.milliseconds=100*+e}],SS:[r,function(e){this.milliseconds=10*+e}],SSS:[/\d{3}/,function(e){this.milliseconds=+e}],s:[i,f("seconds")],ss:[i,f("seconds")],m:[i,f("minutes")],mm:[i,f("minutes")],H:[i,f("hours")],h:[i,f("hours")],HH:[i,f("hours")],hh:[i,f("hours")],D:[i,f("day")],DD:[r,f("day")],Do:[o,function(e){var t=s.ordinal,n=e.match(/\d+/);if(this.day=n[0],t)for(var r=1;r<=31;r+=1)t(r).replace(/\[|\]/g,"")===e&&(this.day=r)}],w:[i,f("week")],ww:[r,f("week")],M:[i,f("month")],MM:[r,f("month")],MMM:[o,function(e){var t=u("months"),n=(u("monthsShort")||t.map((function(e){return e.slice(0,3)}))).indexOf(e)+1;if(n<1)throw new Error;this.month=n%12||n}],MMMM:[o,function(e){var t=u("months").indexOf(e)+1;if(t<1)throw new Error;this.month=t%12||t}],Y:[/[+-]?\d+/,f("year")],YY:[r,function(e){this.year=a(e)}],YYYY:[/\d{4}/,f("year")],Z:h,ZZ:h};function l(n){var r,i;r=n,i=s&&s.formats;for(var o=(n=r.replace(/(\[[^\]]+])|(LTS?|l{1,4}|L{1,4})/g,(function(t,n,r){var o=r&&r.toUpperCase();return n||i[r]||e[r]||i[o].replace(/(\[[^\]]+])|(MMMM|MM|DD|dddd)/g,(function(e,t,n){return t||n.slice(1)}))}))).match(t),a=o.length,f=0;f<a;f+=1){var h=o[f],u=c[h],d=u&&u[0],l=u&&u[1];o[f]=l?{regex:d,parser:l}:h.replace(/^\[|\]$/g,"")}return function(e){for(var t={},n=0,r=0;n<a;n+=1){var i=o[n];if("string"==typeof i)r+=i.length;else{var s=i.regex,f=i.parser,h=e.slice(r),u=s.exec(h)[0];f.call(t,u),e=e.replace(u,"")}}return function(e){var t=e.afternoon;if(void 0!==t){var n=e.hours;t?n<12&&(e.hours+=12):12===n&&(e.hours=0),delete e.afternoon}}(t),t}}return function(e,t,n){n.p.customParseFormat=!0,e&&e.parseTwoDigitYear&&(a=e.parseTwoDigitYear);var r=t.prototype,i=r.parse;r.parse=function(e){var t=e.date,r=e.utc,o=e.args;this.$u=r;var a=o[1];if("string"==typeof a){var f=!0===o[2],h=!0===o[3],u=f||h,d=o[2];h&&(d=o[2]),s=this.$locale(),!f&&d&&(s=n.Ls[d]),this.$d=function(e,t,n,r){try{if(["x","X"].indexOf(t)>-1)return new Date(("X"===t?1e3:1)*e);var i=l(t)(e),o=i.year,s=i.month,a=i.day,f=i.hours,h=i.minutes,u=i.seconds,d=i.milliseconds,c=i.zone,m=i.week,M=new Date,Y=a||(o||s?1:M.getDate()),p=o||M.getFullYear(),v=0;o&&!s||(v=s>0?s-1:M.getMonth());var D,w=f||0,g=h||0,y=u||0,L=d||0;return c?new Date(Date.UTC(p,v,Y,w,g,y,L+60*c.offset*1e3)):n?new Date(Date.UTC(p,v,Y,w,g,y,L)):(D=new Date(p,v,Y,w,g,y,L),m&&(D=r(D).week(m).toDate()),D)}catch(e){return new Date("")}}(t,a,r,n),this.init(),d&&!0!==d&&(this.$L=this.locale(d).$L),u&&t!=this.format(a)&&(this.$d=new Date("")),s={}}else if(a instanceof Array)for(var c=a.length,m=1;m<=c;m+=1){o[1]=a[m-1];var M=n.apply(this,o);if(M.isValid()){this.$d=M.$d,this.$L=M.$L,this.init();break}m===c&&(this.$d=new Date(""))}else i.call(this,e)}}}));

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
!function(n,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(n="undefined"!=typeof globalThis?globalThis:n||self).dayjs_plugin_localeData=e()}(this,(function(){"use strict";return function(n,e,t){var r=e.prototype,o=function(n){return n&&(n.indexOf?n:n.s)},u=function(n,e,t,r,u){var i=n.name?n:n.$locale(),a=o(i[e]),s=o(i[t]),f=a||s.map((function(n){return n.slice(0,r)}));if(!u)return f;var d=i.weekStart;return f.map((function(n,e){return f[(e+(d||0))%7]}))},i=function(){return t.Ls[t.locale()]},a=function(n,e){return n.formats[e]||function(n){return n.replace(/(\[[^\]]+])|(MMMM|MM|DD|dddd)/g,(function(n,e,t){return e||t.slice(1)}))}(n.formats[e.toUpperCase()])},s=function(){var n=this;return{months:function(e){return e?e.format("MMMM"):u(n,"months")},monthsShort:function(e){return e?e.format("MMM"):u(n,"monthsShort","months",3)},firstDayOfWeek:function(){return n.$locale().weekStart||0},weekdays:function(e){return e?e.format("dddd"):u(n,"weekdays")},weekdaysMin:function(e){return e?e.format("dd"):u(n,"weekdaysMin","weekdays",2)},weekdaysShort:function(e){return e?e.format("ddd"):u(n,"weekdaysShort","weekdays",3)},longDateFormat:function(e){return a(n.$locale(),e)},meridiem:this.$locale().meridiem,ordinal:this.$locale().ordinal}};r.localeData=function(){return s.bind(this)()},t.localeData=function(){var n=i();return{firstDayOfWeek:function(){return n.weekStart||0},weekdays:function(){return t.weekdays()},weekdaysShort:function(){return t.weekdaysShort()},weekdaysMin:function(){return t.weekdaysMin()},months:function(){return t.months()},monthsShort:function(){return t.monthsShort()},longDateFormat:function(e){return a(n,e)},meridiem:n.meridiem,ordinal:n.ordinal}},t.months=function(){return u(i(),"months")},t.monthsShort=function(){return u(i(),"monthsShort","months",3)},t.weekdays=function(n){return u(i(),"weekdays",null,null,n)},t.weekdaysShort=function(n){return u(i(),"weekdaysShort","weekdays",3,n)},t.weekdaysMin=function(n){return u(i(),"weekdaysMin","weekdays",2,n)}}}));

View File

@ -0,0 +1 @@
!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(t="undefined"!=typeof globalThis?globalThis:t||self).dayjs_plugin_quarterOfYear=n()}(this,(function(){"use strict";var t="month",n="quarter";return function(e,i){var r=i.prototype;r.quarter=function(t){return this.$utils().u(t)?Math.ceil((this.month()+1)/3):this.month(this.month()%3+3*(t-1))};var s=r.add;r.add=function(e,i){return e=Number(e),this.$utils().p(i)===n?this.add(3*e,t):s.bind(this)(e,i)};var u=r.startOf;r.startOf=function(e,i){var r=this.$utils(),s=!!r.u(i)||i;if(r.p(e)===n){var o=this.quarter()-1;return s?this.month(3*o).startOf(t).startOf("day"):this.month(3*o+2).endOf(t).endOf("day")}return u.bind(this)(e,i)}}}));

View File

@ -0,0 +1 @@
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).dayjs_plugin_weekOfYear=t()}(this,(function(){"use strict";var e="week",t="year";return function(i,n,r){var f=n.prototype;f.week=function(i){if(void 0===i&&(i=null),null!==i)return this.add(7*(i-this.week()),"day");var n=this.$locale().yearStart||1;if(11===this.month()&&this.date()>25){var f=r(this).startOf(t).add(1,t).date(n),s=r(this).endOf(e);if(f.isBefore(s))return 1}var a=r(this).startOf(t).date(n).startOf(e).subtract(1,"millisecond"),o=this.diff(a,e,!0);return o<0?r(this).startOf("week").week():Math.ceil(o)},f.weeks=function(e){return void 0===e&&(e=null),this.week(e)}}}));

View File

@ -0,0 +1 @@
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).dayjs_plugin_weekYear=t()}(this,(function(){"use strict";return function(e,t){t.prototype.weekYear=function(){var e=this.month(),t=this.week(),n=this.year();return 1===t&&11===e?n+1:0===e&&t>=52?n-1:n}}}));

View File

@ -0,0 +1 @@
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).dayjs_plugin_weekday=t()}(this,(function(){"use strict";return function(e,t){t.prototype.weekday=function(e){var t=this.$locale().weekStart||0,i=this.$W,n=(i<t?i+7:i)-t;return this.$utils().u(e)?n:this.subtract(n,"day").add(e,"day")}}}));

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,78 @@
/*! `javascript` grammar compiled for Highlight.js 11.7.0 */
(()=>{var e=(()=>{"use strict"
;const e="[A-Za-z$_][0-9A-Za-z$_]*",n=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],a=["true","false","null","undefined","NaN","Infinity"],t=["Object","Function","Boolean","Symbol","Math","Date","Number","BigInt","String","RegExp","Array","Float32Array","Float64Array","Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Int32Array","Uint16Array","Uint32Array","BigInt64Array","BigUint64Array","Set","Map","WeakSet","WeakMap","ArrayBuffer","SharedArrayBuffer","Atomics","DataView","JSON","Promise","Generator","GeneratorFunction","AsyncFunction","Reflect","Proxy","Intl","WebAssembly"],s=["Error","EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"],r=["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],c=["arguments","this","super","console","window","document","localStorage","module","global"],i=[].concat(r,t,s)
;return o=>{const l=o.regex,b=e,d={begin:/<[A-Za-z0-9\\._:-]+/,
end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,n)=>{
const a=e[0].length+e.index,t=e.input[a]
;if("<"===t||","===t)return void n.ignoreMatch();let s
;">"===t&&(((e,{after:n})=>{const a="</"+e[0].slice(1)
;return-1!==e.input.indexOf(a,n)})(e,{after:a})||n.ignoreMatch())
;const r=e.input.substring(a)
;((s=r.match(/^\s*=/))||(s=r.match(/^\s+extends\s+/))&&0===s.index)&&n.ignoreMatch()
}},g={$pattern:e,keyword:n,literal:a,built_in:i,"variable.language":c
},u="\\.([0-9](_?[0-9])*)",m="0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*",E={
className:"number",variants:[{
begin:`(\\b(${m})((${u})|\\.)?|(${u}))[eE][+-]?([0-9](_?[0-9])*)\\b`},{
begin:`\\b(${m})\\b((${u})\\b|\\.)?|(${u})\\b`},{
begin:"\\b(0|[1-9](_?[0-9])*)n\\b"},{
begin:"\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\b"},{
begin:"\\b0[bB][0-1](_?[0-1])*n?\\b"},{begin:"\\b0[oO][0-7](_?[0-7])*n?\\b"},{
begin:"\\b0[0-7]+n?\\b"}],relevance:0},A={className:"subst",begin:"\\$\\{",
end:"\\}",keywords:g,contains:[]},y={begin:"html`",end:"",starts:{end:"`",
returnEnd:!1,contains:[o.BACKSLASH_ESCAPE,A],subLanguage:"xml"}},N={
begin:"css`",end:"",starts:{end:"`",returnEnd:!1,
contains:[o.BACKSLASH_ESCAPE,A],subLanguage:"css"}},_={className:"string",
begin:"`",end:"`",contains:[o.BACKSLASH_ESCAPE,A]},h={className:"comment",
variants:[o.COMMENT(/\/\*\*(?!\/)/,"\\*/",{relevance:0,contains:[{
begin:"(?=@[A-Za-z]+)",relevance:0,contains:[{className:"doctag",
begin:"@[A-Za-z]+"},{className:"type",begin:"\\{",end:"\\}",excludeEnd:!0,
excludeBegin:!0,relevance:0},{className:"variable",begin:b+"(?=\\s*(-)|$)",
endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]
}),o.C_BLOCK_COMMENT_MODE,o.C_LINE_COMMENT_MODE]
},f=[o.APOS_STRING_MODE,o.QUOTE_STRING_MODE,y,N,_,{match:/\$\d+/},E]
;A.contains=f.concat({begin:/\{/,end:/\}/,keywords:g,contains:["self"].concat(f)
});const v=[].concat(h,A.contains),p=v.concat([{begin:/\(/,end:/\)/,keywords:g,
contains:["self"].concat(v)}]),S={className:"params",begin:/\(/,end:/\)/,
excludeBegin:!0,excludeEnd:!0,keywords:g,contains:p},w={variants:[{
match:[/class/,/\s+/,b,/\s+/,/extends/,/\s+/,l.concat(b,"(",l.concat(/\./,b),")*")],
scope:{1:"keyword",3:"title.class",5:"keyword",7:"title.class.inherited"}},{
match:[/class/,/\s+/,b],scope:{1:"keyword",3:"title.class"}}]},R={relevance:0,
match:l.either(/\bJSON/,/\b[A-Z][a-z]+([A-Z][a-z]*|\d)*/,/\b[A-Z]{2,}([A-Z][a-z]+|\d)+([A-Z][a-z]*)*/,/\b[A-Z]{2,}[a-z]+([A-Z][a-z]+|\d)*([A-Z][a-z]*)*/),
className:"title.class",keywords:{_:[...t,...s]}},O={variants:[{
match:[/function/,/\s+/,b,/(?=\s*\()/]},{match:[/function/,/\s*(?=\()/]}],
className:{1:"keyword",3:"title.function"},label:"func.def",contains:[S],
illegal:/%/},k={
match:l.concat(/\b/,(I=[...r,"super","import"],l.concat("(?!",I.join("|"),")")),b,l.lookahead(/\(/)),
className:"title.function",relevance:0};var I;const x={
begin:l.concat(/\./,l.lookahead(l.concat(b,/(?![0-9A-Za-z$_(])/))),end:b,
excludeBegin:!0,keywords:"prototype",className:"property",relevance:0},T={
match:[/get|set/,/\s+/,b,/(?=\()/],className:{1:"keyword",3:"title.function"},
contains:[{begin:/\(\)/},S]
},C="(\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)|"+o.UNDERSCORE_IDENT_RE+")\\s*=>",M={
match:[/const|var|let/,/\s+/,b,/\s*/,/=\s*/,/(async\s*)?/,l.lookahead(C)],
keywords:"async",className:{1:"keyword",3:"title.function"},contains:[S]}
;return{name:"Javascript",aliases:["js","jsx","mjs","cjs"],keywords:g,exports:{
PARAMS_CONTAINS:p,CLASS_REFERENCE:R},illegal:/#(?![$_A-z])/,
contains:[o.SHEBANG({label:"shebang",binary:"node",relevance:5}),{
label:"use_strict",className:"meta",relevance:10,
begin:/^\s*['"]use (strict|asm)['"]/
},o.APOS_STRING_MODE,o.QUOTE_STRING_MODE,y,N,_,h,{match:/\$\d+/},E,R,{
className:"attr",begin:b+l.lookahead(":"),relevance:0},M,{
begin:"("+o.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",
keywords:"return throw case",relevance:0,contains:[h,o.REGEXP_MODE,{
className:"function",begin:C,returnBegin:!0,end:"\\s*=>",contains:[{
className:"params",variants:[{begin:o.UNDERSCORE_IDENT_RE,relevance:0},{
className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,
excludeEnd:!0,keywords:g,contains:p}]}]},{begin:/,/,relevance:0},{match:/\s+/,
relevance:0},{variants:[{begin:"<>",end:"</>"},{
match:/<[A-Za-z0-9\\._:-]+\s*\/>/},{begin:d.begin,
"on:begin":d.isTrulyOpeningTag,end:d.end}],subLanguage:"xml",contains:[{
begin:d.begin,end:d.end,skip:!0,contains:["self"]}]}]},O,{
beginKeywords:"while if switch catch for"},{
begin:"\\b(?!function)"+o.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{",
returnBegin:!0,label:"func.def",contains:[S,o.inherit(o.TITLE_MODE,{begin:b,
className:"title.function"})]},{match:/\.\.\./,relevance:0},x,{match:"\\$"+b,
relevance:0},{match:[/\bconstructor(?=\s*\()/],className:{1:"title.function"},
contains:[S]},k,{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/,
className:"variable.constant"},w,T,{match:/\$[(.]/}]}}})()
;hljs.registerLanguage("javascript",e)})();

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,189 @@
const DatabaseCard = defineComponent({
name: "MainCard",
template: `
<div class="card shadow-sm position-relative">
<!-- 使用提示区域包含标题数据库统计信息和操作指引 -->
<div class="card-header bg-primary bg-opacity-10 border-bottom">
<div class="d-flex justify-content-between align-items-center">
<div>
<h5 class="card-title mb-0">
<i class="bi bi-info-circle-fill text-primary me-2"></i>使
</h5>
</div>
<div>
<span class="badge bg-primary rounded-pill me-2">
<i class="bi bi-database me-1"></i>
数据库: <span class="fw-normal">{{databaseList.length}}</span>
</span>
<span class="badge bg-primary rounded-pill">
<i class="bi bi-table me-1"></i>
数据库表: <span class="fw-normal">{{rawTableList.length}}</span>
</span>
</div>
</div>
<p class="card-subtitle mt-2 text-muted">
<i class="bi bi-mouse me-1"></i>
点击 <code class="bg-primary bg-opacity-10">复选框</code>
<code class="bg-primary bg-opacity-10">选择按钮</code>
进行选择之后点击 <code class="bg-primary bg-opacity-10">生成选中表</code>
</p>
</div>
<!-- 数据库操作区域包含数据库选择表选择和查询功能 -->
<div class="card-body bg-light">
<div class="row g-3 align-items-end">
<!-- 数据库选择下拉框 -->
<div class="col-md-5">
<label class="form-label fw-semibold" for="databaseSelect">
<i class="bi bi-database me-1"></i>
</label>
<select class="form-select shadow-sm" id="databaseSelect" v-model="dbName">
<option disabled selected>请选择数据库...</option>
<option :key="index" :title="db.comment" :value="db.tableCat"
v-for="(db,index) in databaseList">
{{db.tableCat}}
</option>
</select>
</div>
<!-- 数据库表搜索输入框 -->
<div class="col-md-5">
<label class="form-label fw-semibold" for="tableSelect">
<i class="bi bi-table me-1"></i>
数据库表选择
</label>
<input class="form-control shadow-sm" id="tableSelect" placeholder="输入表名或表注释"
v-model="tableName"/>
</div>
<!-- 查询按钮 -->
<div class="col-md-2 d-grid">
<button class="btn btn-primary shadow-sm" disabled type="button"
v-if="dbLoading">
<span aria-hidden="true" class="spinner-grow spinner-grow-sm"></span>
<span role="status">Loading...</span>
</button>
<button @click="onRefresh" class="btn btn-primary shadow-sm" v-else>
<i class="bi bi-search me-1"></i>
查询
</button>
</div>
</div>
<!-- 数据库连接详情折叠面板 -->
<div class="pt-1 bg-light">
<a class="d-flex align-items-center text-decoration-none" data-bs-toggle="collapse"
href="#dbInfoCollapse">
<i class="bi bi-database me-2"></i>
<span>数据库连接详情</span>
<i class="bi bi-chevron-down ms-auto"></i>
</a>
<div class="collapse mt-2" id="dbInfoCollapse">
<div class="card card-body bg-white">
<ul class="list-unstyled mb-0">
<li class="mb-2">
<strong>数据库:</strong> {{databaseInfo.databaseProductName}}
{{databaseInfo.databaseProductVersion}}
</li>
<li class="mb-2">
<strong>驱动:</strong> {{databaseInfo.driverName}}
({{databaseInfo.driverVersion}})
</li>
<li class="mb-2"><strong>URL:</strong>
<code class="d-block text-break">{{databaseInfo.url}}</code>
</li>
<li class="mb-2"><strong>用户:</strong> {{databaseInfo.username}}</li>
<li><strong>当前库:</strong> {{dbName}}</li>
</ul>
</div>
</div>
</div>
</div>
</div>
`,
props: {
/* 原始表列表数据,由父组件传入 */
rawTableList: {type: Array,},
},
data() {
return {
// 当前选中的数据库名称
dbName: ref(""),
// 输入搜索的表名或表注释
tableName: ref(""),
// 数据库加载状态标志
dbLoading: ref(false),
// 数据库连接信息对象
databaseInfo: ref({}),
// 所有数据库列表
databaseList: ref([]),
}
},
methods: {
/**
* 获取当前连接的数据库元数据信息
* 包括数据库信息数据库列表和当前数据库名称
* 此方法会触发数据库表列表的更新
* @async
* @returns {Promise<void>}
*/
async getDatabaseInfoMetaData() {
this.dbLoading = true;
try {
const response = await axiosInstance.get("/table/databaseInfoMetaData");
const {data, code, message} = response;
if (code !== 200) {
antd.message.error(message);
return
}
// 设置数据库连接信息
this.databaseInfo = data;
// 设置所有数据库列表
this.databaseList = data.databaseList;
// 如果当前未选择数据库,则使用默认数据库
if (!this.dbName) {
this.dbName = data.currentDatabase;
}
} finally {
this.dbLoading = false;
}
},
/**
* 刷新查询操作
* 重新获取数据库信息并触发父组件更新表列表
* @async
* @returns {Promise<void>}
*/
async onRefresh() {
await this.getDatabaseInfoMetaData();
// 通知父组件更新表列表
this.$emit("getDatabaseTableList");
}
},
async mounted() {
// 组件挂载时初始化数据库信息
await this.getDatabaseInfoMetaData();
},
watch: {
/* 监听数据库名称变化并通知父组件 */
dbName: {
handler(val) {
this.$emit("update:dbSelect", val);
},
},
/* 监听表名输入变化并通知父组件 */
tableName: {
handler(val) {
this.$emit("update:tableSelect", val);
}
}
}
});

View File

@ -0,0 +1,385 @@
const DatabaseForm = {
name: "MainForm",
template: `
<div class="card shadow-sm mt-2 bg-body-secondary">
<!-- 表单标题和折叠控制 -->
<div class="card-header p-3">
<a aria-controls="generatorFormCollapse" aria-expanded="false"
class="d-flex align-items-center text-decoration-none" data-bs-toggle="collapse"
href="#generatorFormCollapse">
<i class="bi bi-pencil me-2"></i>
<span class="fw-semibold">填写生成表单信息</span>
<i class="bi bi-chevron-down ms-auto transition-transform rotate-180"></i>
</a>
</div>
<!-- 表单内容区域 -->
<div class="collapse" :class="{ 'show': defaultCollapse }" id="generatorFormCollapse">
<form class="card-body row" @submit.prevent="handleSubmit" novalidate>
<!-- 基本信息输入区域 -->
<div class="col-md-4 mb-3 has-validation">
<label class="form-label fw-medium" for="author">作者名称</label>
<input class="form-control border-secondary" :class="{ 'is-invalid': errors.author }"
id="author" placeholder="输入作者名称" v-model="form.author" type="text"
@input="validateField('author')">
<div class="invalid-feedback">
{{ errors.author || '请输入作者名称' }}
</div>
</div>
<div class="col-md-4 mb-3 has-validation">
<label class="form-label fw-medium" for="requestMapping">requestMapping名称</label>
<input class="form-control border-secondary" :class="{ 'is-invalid': errors.requestMapping }"
id="requestMapping" placeholder="输入requestMapping名称" v-model="form.requestMapping" type="text"
@input="validateField('requestMapping')">
<div class="invalid-feedback">
{{ errors.requestMapping || '请输入requestMapping名称' }}
</div>
</div>
<div class="col-md-4 mb-3 has-validation">
<label class="form-label fw-medium" for="packageName">包名称</label>
<input class="form-control border-secondary" :class="{ 'is-invalid': errors.packageName }"
id="packageName" placeholder="输入包名称" v-model="form.packageName" type="text"
@input="validateField('packageName')">
<div class="invalid-feedback">
{{ errors.packageName || '请输入包名称' }}
</div>
</div>
<div class="col-md-4 mb-3 has-validation">
<label class="form-label fw-medium" for="simpleDateFormat">时间格式</label>
<input class="form-control border-secondary" :class="{ 'is-invalid': errors.simpleDateFormat }"
id="simpleDateFormat" placeholder="输入时间格式" v-model="form.simpleDateFormat" type="text"
@input="validateField('simpleDateFormat')">
<div class="invalid-feedback">
{{ errors.simpleDateFormat || '请输入时间格式' }}
</div>
</div>
<div class="col-md-4 mb-3 has-validation">
<label class="form-label fw-medium" for="tablePrefixes">去除开头前缀</label>
<input class="form-control border-secondary" :class="{ 'is-invalid': errors.tablePrefixes }"
id="tablePrefixes" placeholder="去除开头前缀" v-model="form.tablePrefixes" type="text"
@input="validateField('tablePrefixes')">
<div class="invalid-feedback">
{{ errors.tablePrefixes || '请输入去除开头前缀' }}
</div>
</div>
<!-- 已选择的表 -->
<div class="col-md-12" v-show="form.tableNames.length > 0">
<label class="form-label fw-medium" for="tableNames">已选择的要生成表({{form.tableNames.length}})</label>
<span class="badge rounded-pill text-bg-dark me-1" v-for="(item,index) in form.tableNames" :ket="index">{{item}}</span>
</div>
<!-- 前端模板选择区域 -->
<div class="col-12 mt-3 mb-3 p-3 bg-light rounded">
<label class="form-check-inline col-form-label fw-medium">生成前端模板</label>
<div class="form-check form-check-inline" v-for="(web,index) in webList" :key="index">
<input class="form-check-input border-secondary" :class="{ 'is-invalid': errors.webTemplates }"
:id="web.id" type="checkbox" v-model="web.checked"
@change="validateTemplates">
<label class="form-check-label" :for="web.id" :title="web.name">{{web.label}}</label>
</div>
<div class="form-check form-check-inline btn-group ms-2">
<button class="btn btn-outline-primary btn-sm" type="button"
@click="onTableSelectAll(webList)">全选</button>
<button class="btn btn-outline-secondary btn-sm" type="button"
@click="onTableInvertSelection(webList)">反选</button>
<button class="btn btn-outline-danger btn-sm" type="button"
@click="onTableClearAll(webList)">全不选</button>
</div>
<div v-if="errors.webTemplates" class="invalid-feedback d-block">
{{ errors.webTemplates }}
</div>
</div>
<!-- 后端模板选择区域 -->
<div class="col-12 mt-2 mb-3 p-3 bg-light rounded">
<label class="form-check-inline col-form-label fw-medium">生成后端模板</label>
<div class="form-check form-check-inline" v-for="(server,index) in serverList" :key="index">
<input class="form-check-input border-secondary"
:class="{ 'is-invalid': errors.serverTemplates }" :id="server.id" type="checkbox"
v-model="server.checked" @change="validateTemplates">
<label class="form-check-label" :title="server.name" :for="server.id">{{server.label}}</label>
</div>
<div class="form-check form-check-inline btn-group ms-2">
<button class="btn btn-outline-primary btn-sm" type="button"
@click="onTableSelectAll(serverList)">全选</button>
<button class="btn btn-outline-secondary btn-sm" type="button"
@click="onTableInvertSelection(serverList)">反选</button>
<button class="btn btn-outline-danger btn-sm" type="button"
@click="onTableClearAll(serverList)">全不选</button>
</div>
<div v-if="errors.serverTemplates" class="invalid-feedback d-block">
{{ errors.serverTemplates }}
</div>
</div>
<!-- 操作按钮区域 -->
<div class="row mt-4">
<div class="col-md-4 btn-group">
<button class="btn btn-outline-primary" data-bs-title="选择数据表中所有的内容" data-bs-toggle="tooltip"
type="button" @click="onSelectAll">
全部选择
</button>
<button class="btn btn-outline-secondary" data-bs-title="将选择的表格内容反向选择" data-bs-toggle="tooltip"
type="button" @click="onInvertSelection">
全部反选
</button>
<button class="btn btn-outline-danger" data-bs-title="取消全部已经选择的数据表" data-bs-toggle="tooltip"
type="button" @click="onClearAll">
全部取消
</button>
</div>
<div class="col-md-4 btn-group">
<button class="btn btn-primary shadow-sm" disabled type="button"
v-if="generatorCodeLoading">
<span aria-hidden="true" class="spinner-grow spinner-grow-sm"></span>
<span role="status">生成选中表...</span>
</button>
<button v-else class="btn btn-primary" type="submit">
生成选中表
</button>
<button class="btn btn-warning" type="button" @click="onClearGeneratorData">清空生成记录</button>
</div>
<div class="col-md-4 d-grid gap-2">
<button class="btn btn-primary shadow-sm" disabled type="button"
v-if="downloadLoading">
<span aria-hidden="true" class="spinner-grow spinner-grow-sm"></span>
<span role="status">下载ZIP...</span>
</button>
<button v-else class="btn btn-primary text-white" type="button" @click="onDownloadZip">下载ZIP</button>
</div>
</div>
</form>
</div>
</div>
`,
props: {
// 表单数据对象,包含生成代码所需的各种参数
form: {type: Object, required: true},
// 生成代码的回调函数
onGeneratorCode: {type: Function, required: true},
// 清空生成记录
onClearGeneratorData: {type: Function, required: true},
// 生成代码加载
generatorCodeLoading: {type: Boolean, required: true},
},
data() {
return {
// 控制表单默认是否展开
defaultCollapse: true,
// 后端模板选项列表
serverList: ref([]),
// 前端模板选项列表
webList: ref([]),
// 错误信息对象
errors: {
author: '',
requestMapping: '',
packageName: '',
simpleDateFormat: '',
tablePrefixes: '',
webTemplates: '',
serverTemplates: ''
},
downloadLoading: ref(false),
}
},
methods: {
/**
* 验证表单字段
* @param {string} field - 字段名
*/
validateField(field) {
if (!this.form[field] || this.form[field].trim() === '') {
this.errors[field] = '此字段为必填项';
return false;
}
this.errors[field] = '';
return true;
},
/* 验证模板选择 */
validateTemplates() {
// 检查列表是否有一个选中的
const hasWebSelected = this.webList.some(item => item.checked);
const hasServerSelected = this.serverList.some(item => item.checked);
// 列表都没有选中
if (!hasWebSelected && !hasServerSelected) {
this.errors.webTemplates = '请至少选择一个前端或后端模板';
this.errors.serverTemplates = '请至少选择一个前端或后端模板';
return false;
}
// 列表选中让错误提示小时
this.errors.webTemplates = '';
this.errors.serverTemplates = '';
// 发生选择变化时,同时父级更新表单
this.updateForm();
return true;
},
/* 验证整个表单 */
validateForm() {
let isValid = true;
// 验证文本字段
const textFields = ['author', 'requestMapping', 'packageName', 'simpleDateFormat', 'tablePrefixes'];
textFields.forEach(field => {
if (!this.validateField(field)) {
isValid = false;
}
});
// 验证模板选择
if (!this.validateTemplates()) {
isValid = false;
}
return isValid;
},
/* 更新父级表单 */
updateForm() {
const webList = this.webList.filter(item => item.checked).map(item => item.name);
const serverList = this.serverList.filter(item => item.checked).map(item => item.name);
const newForm = {...this.form, path: [...webList, ...serverList,]};
this.$emit("update:form", newForm);
},
/* 处理表单提交 */
handleSubmit() {
if (this.validateForm()) {
this.updateForm();
// 如果验证通过,调用父组件提供的生成代码方法
this.onGeneratorCode();
} else {
// 验证失败,可以在这里添加额外的处理逻辑
antd.message.error("表单验证失败")
}
},
/**
* 获取VMS资源路径列表
* 从服务器获取前端和后端模板的路径列表
* @async
* @returns {Promise<void>}
*/
async getVmsResourcePathList() {
const response = await axiosInstance.get("/vms/vmsResourcePathList");
const {data, code, message} = response;
if (code !== 200) {
antd.message.error(message);
return;
}
// 初始化模板选择状态
this.serverList = data.server.map(item => ({...item, checked: false}));
this.webList = data.web.map(item => ({...item, checked: false}));
},
/**
* 下载Zip文件
* @returns {Promise<void>}
*/
async onDownloadZip() {
this.downloadLoading = true;
try {
// 重要指定响应类型为blob
const response = await axiosInstance({
url: "/generator/downloadByZip",
method: "POST",
data: this.form,
responseType: 'blob'
});
// 从响应头中获取文件名
const contentDisposition = response.headers['content-disposition'];
let fileName = 'download.zip';
if (contentDisposition) {
const fileNameMatch = contentDisposition.match(/filename=(.+)/);
if (fileNameMatch && fileNameMatch[1]) {
fileName = fileNameMatch[1];
// 处理可能的编码文件名如UTF-8编码
if (fileName.startsWith("UTF-8''")) {
fileName = decodeURIComponent(fileName.replace("UTF-8''", ''));
}
}
}
// 创建Blob对象
const blob = new Blob([response.data]);
// 创建下载链接
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = fileName;
document.body.appendChild(link);
// 触发点击下载
link.click();
// 清理
window.URL.revokeObjectURL(downloadUrl);
document.body.removeChild(link);
} catch (error) {
console.error('下载失败:', error);
}
this.downloadLoading = false;
},
/**
* 全选指定列表
* @param {Array} list - 要处理的列表
*/
onTableSelectAll(list) {
list.forEach(item => item.checked = true);
this.validateTemplates();
},
/**
* 反选指定列表
* @param {Array} list - 要处理的列表
*/
onTableInvertSelection(list) {
list.forEach(item => item.checked = !item.checked);
this.validateTemplates();
},
/**
* 清空指定列表的选择
* @param {Array} list - 要处理的列表
*/
onTableClearAll(list) {
list.forEach(item => item.checked = false);
this.validateTemplates();
},
/* 全选所有模板 */
onSelectAll() {
this.onTableSelectAll(this.webList);
this.onTableSelectAll(this.serverList);
},
/* 反选所有模板 */
onInvertSelection() {
this.onTableInvertSelection(this.webList);
this.onTableInvertSelection(this.serverList);
},
/* 清空所有模板的选择 */
onClearAll() {
this.onTableClearAll(this.webList);
this.onTableClearAll(this.serverList);
}
},
async mounted() {
// 组件挂载时获取模板列表
await this.getVmsResourcePathList();
}
}

View File

@ -0,0 +1,383 @@
const DatabaseTable = defineComponent({
name: "MainTable",
template: `
<div class="card mt-2 shadow-sm">
<!-- 表格标题和选择选项 -->
<div class="d-flex justify-content-between card-header bg-primary bg-opacity-10">
<h5 class="card-title mb-0">
<i class="bi bi-table me-2"></i>
</h5>
<div>
<div class="form-check form-check-inline">
<input class="form-check-input" name="TableSelectRadios" type="radio" id="radioSelectAll" value="all"
v-model="selectedOption">
<label class="form-check-label" for="radioSelectAll">选择全部</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" name="TableSelectRadios" type="radio" id="radioSelectCurrentPage"
value="current" v-model="selectedOption">
<label class="form-check-label" for="radioSelectCurrentPage">选择当前页</label>
</div>
</div>
</div>
<!-- 表格内容区域 -->
<div class="card-body p-0">
<div class="table-responsive">
<!-- 加载状态 -->
<div class="p-5 text-center" v-if="loading">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
<!-- 空状态提示 -->
<div class="p-5 text-center" v-else-if="tableList.length === 0">
<i class="bi bi-exclamation-circle fs-1 text-muted"></i>
<p class="mt-2 text-muted">没有找到数据表</p>
</div>
<!-- 数据表格 -->
<table class="table table-striped table-bordered table-hover mb-0" v-else>
<thead class="table-light">
<tr>
<th scope="col" width="2%">
<input class="form-check-input border-secondary" type="checkbox"
v-model="tableSelectAllChecked" @change="onSelectAllTable">
</th>
<th scope="col" width="3%">#</th>
<th scope="col" width="25%">表名</th>
<th scope="col" width="25%">注释</th>
<th scope="col" width="15%">所属数据库</th>
<th class="text-center" scope="col" width="15%">操作</th>
</tr>
</thead>
<tbody>
<template v-for="(table, index) in paginatedTableList" :key="table.tableName">
<tr>
<td>
<input class="form-check-input border-secondary" type="checkbox" v-model="table.checked"
@change="onSelectTable($event.target.checked, table)">
</td>
<td>{{ (currentPage - 1) * itemsPerPage + index + 1 }}</td>
<td>
<a class="text-decoration-none" href="#" @click.prevent="toggleTableDetails(table)">
{{ table.tableName }}
<i class="bi"
:class="{'bi-chevron-down': !table.showDetails, 'bi-chevron-up': table.showDetails}"></i>
</a>
</td>
<td>{{ table.comment || '无注释' }}</td>
<td>{{ table.tableCat }}</td>
<td class="d-flex justify-content-around">
<button class="btn btn-sm btn-outline-primary"
@click="onSelectTable(!table.checked, table)">
<i class="bi bi-database-check"></i>
</button>
<button class="btn btn-sm btn-outline-primary" @click="toggleTableDetails(table)">
<i class="bi bi-gear"></i>
</button>
</td>
</tr>
<!-- 表详情展开行 -->
<tr v-if="table.showDetails">
<td colspan="6" class="p-0">
<div class="p-3 bg-light">
<h6 class="mb-3">表信息</h6>
<div class="row mb-4">
<div class="col-md-3">
<p class="mb-1"><strong>表名:</strong> {{table.tableName}}</p>
</div>
<div class="col-md-3">
<p class="mb-1"><strong>类型:</strong> {{table.tableType}}</p>
</div>
<div class="col-md-6">
<p class="mb-1"><strong>注释:</strong> {{table.comment || ''}}</p>
</div>
</div>
<h6 class="mb-3">列信息</h6>
<div class="table-responsive">
<table class="table table-sm table-bordered">
<thead>
<tr>
<th>列名</th>
<th>数据库类型</th>
<th>Java类型</th>
<th>JS类型</th>
<th>主键</th>
<th>注释</th>
</tr>
</thead>
<tbody>
<tr v-for="column in table.columns" :key="column.columnName">
<td>{{column.columnName}}</td>
<td>{{column.jdbcType}}</td>
<td>{{column.javaType}}</td>
<td>{{column.javascriptType}}</td>
<td>
<span class="badge bg-success"
v-if="column.isPrimaryKey"></span>
<span class="badge bg-secondary" v-else></span>
</td>
<td>{{column.comment || '无'}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
<!-- 分页控件 -->
<div class="card-footer bg-light" v-if="tableList.length > 0">
<div class="d-flex justify-content-between align-items-center">
<div class="form-text">
显示 {{ (currentPage - 1) * itemsPerPage + 1 }}~{{ Math.min(currentPage * itemsPerPage,
rawTableList.length) }} {{ rawTableList.length }}
</div>
<nav aria-label="Page navigation">
<ul class="pagination mb-0">
<li :class="{ disabled: currentPage === 1 }" class="page-item">
<a @click.prevent="goToPage(1)" class="page-link" href="#">
<i class="bi bi-chevron-double-left"></i>
</a>
</li>
<li :class="{ disabled: currentPage === 1 }" class="page-item">
<a @click.prevent="goToPage(currentPage - 1)" class="page-link" href="#">
<i class="bi bi-chevron-left"></i>
</a>
</li>
<!-- 显示页码 -->
<li v-for="page in visiblePages" :key="page" :class="{ active: currentPage === page }"
class="page-item">
<a @click.prevent="goToPage(page)" class="page-link" href="#">{{ page }}</a>
</li>
<li :class="{ disabled: currentPage === totalPages }" class="page-item">
<a @click.prevent="goToPage(currentPage + 1)" class="page-link" href="#">
<i class="bi bi-chevron-right"></i>
</a>
</li>
<li :class="{ disabled: currentPage === totalPages }" class="page-item">
<a @click.prevent="goToPage(totalPages)" class="page-link" href="#">
<i class="bi bi-chevron-double-right"></i>
</a>
</li>
</ul>
</nav>
<div class="dropdown">
<button aria-expanded="false" class="btn btn-outline-secondary dropdown-toggle"
data-bs-toggle="dropdown" id="itemsPerPageDropdown" type="button">
每页 {{ itemsPerPage }}
</button>
<ul aria-labelledby="itemsPerPageDropdown" class="dropdown-menu">
<li v-for="option in tablePageOptions" :key="option">
<a @click.prevent="changeItemsPerPage(option)" class="dropdown-item" href="#">
{{ option }} /
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
`,
props: {
// 加载状态
loading: {type: Boolean, default: false},
// 处理后的表格数据包含checked状态
tableList: {type: Array, required: true, default: () => []},
// 原始表格数据不包含checked状态
rawTableList: {type: Array, required: true, default: () => []},
// 生成代码的回调函数
onGeneratorCode: {type: Function, required: true},
// 表单数据
form: {type: Object, required: true},
// 数据库选择发生变化
dbSelect: {type: String, required: true},
},
data() {
return {
// 当前页码
currentPage: 1,
// 每页显示条数
itemsPerPage: 30,
// 每页条数选项
tablePageOptions: [5, 10, 15, 30, 50, 100, 150, 200],
// 选择模式all-全选 current-当前页
selectedOption: "current",
// 表头全选状态
tableSelectAllChecked: false
}
},
computed: {
/**
* 计算总页数
* @returns {number} 总页数
*/
totalPages() {
return Math.ceil(this.rawTableList.length / this.itemsPerPage);
},
/**
* 当前页的数据
* @returns {Array} 分页后的数据
*/
paginatedTableList() {
const start = (this.currentPage - 1) * this.itemsPerPage;
const end = start + this.itemsPerPage;
return this.tableList.slice(start, end);
},
/**
* 当前选中的表名数组
* @returns {Array} 选中的表名
*/
selectedTableNames() {
return this.tableList.filter(table => table.checked).map(table => table.tableName);
}
},
methods: {
/**
* 跳转到指定页码
* @param {number} page - 目标页码
*/
goToPage(page) {
if (page >= 1 && page <= this.totalPages) {
this.currentPage = page;
}
},
/**
* 更改每页显示条数
* @param {number} size - 每页条数
*/
changeItemsPerPage(size) {
this.itemsPerPage = size;
// 重置到第一页
this.currentPage = 1;
},
/**
* 计算可见的页码范围
* @returns {Array} 可见页码数组
*/
visiblePages() {
// 显示当前页前后各2页
const range = 2;
const start = Math.max(1, this.currentPage - range);
const end = Math.min(this.totalPages, this.currentPage + range);
const pages = [];
for (let i = start; i <= end; i++) {
pages.push(i);
}
return pages;
},
/**
* 表头全选/取消全选
* 当前选择的是所有书库操作列表为 this.tableList
* 当前选择的是当前页的数据库表操作列表为 this.paginatedTableList
*/
onSelectAllTable() {
const checked = this.tableSelectAllChecked;
// 选择是否是所有的数据库
const tablesToUpdate = this.selectedOption === "all"
? this.tableList
: this.paginatedTableList;
// 将数据库变为当前选中的状态
tablesToUpdate.forEach(table => table.checked = checked);
// 更新父级列表状态
this.updateFormSelectedTables();
},
/**
* 选择单个表
* @param {Boolean} checked - 事件对象
* @param {Object} table - 表数据对象
*/
onSelectTable(checked, table) {
table.checked = checked;
this.updateFormSelectedTables();
// 更新表头全选状态
if (!table.checked) {
this.tableSelectAllChecked = false;
} else {
this.tableSelectAllChecked = this.paginatedTableList.every(t => t.checked);
}
},
/**
* 切换表格详情展开/收起状态
* 如果是首次展开且没有列数据则请求获取列信息
* @param {Object} table - 表格对象
* @returns {Promise<void>}
*/
async toggleTableDetails(table) {
// 如果是展开操作且没有列数据,则请求获取列信息
if (!table.showDetails) {
// 发送请求获取列信息
const {data, code, message} = await axiosInstance({
url: "/table/tableColumnInfo",
params: {tableName: table.tableName}
});
// 请求成功时保存列数据
if (code === 200) {
table.columns = data;
antd.message.success(message);
}
}
// 切换展开/收起状态
table.showDetails = !table.showDetails;
},
/* 更新表单中选中的表名 */
updateFormSelectedTables() {
const payload = {
...this.form,
tableNames: this.selectedTableNames
}
// 更新父级 form 的内容
this.$emit("update:form", payload);
},
/**
* 重置父级表单
* 如果要同时更新会变得很复杂所以在这里讲逻辑定义为
* 选型变化时直接取消全部之后重新选择
*/
resetForm() {
this.tableSelectAllChecked = false;
this.tableList.forEach(table => table.checked = false);
this.updateFormSelectedTables();
}
},
watch: {
/* 监听选择模式变化 */
selectedOption: 'resetForm',
/* 监听当前页变化 */
currentPage: 'resetForm',
/* 监听每页条数变化 */
itemsPerPage: 'resetForm',
/* 数据库选择发生变化也重置表单 */
dbSelect: 'resetForm',
}
});

View File

@ -0,0 +1,413 @@
const SqlForm = {
name: "MainForm",
template: `
<div class="card shadow-sm mt-2 bg-body-secondary">
<!-- 表单标题和折叠控制 -->
<div class="card-header p-3">
<a aria-controls="generatorFormCollapse" aria-expanded="false"
class="d-flex align-items-center text-decoration-none" data-bs-toggle="collapse"
href="#generatorFormCollapse">
<i class="bi bi-pencil me-2"></i>
<span class="fw-semibold">填写生成表单信息</span>
<i class="bi bi-chevron-down ms-auto transition-transform rotate-180"></i>
</a>
</div>
<!-- 表单内容区域 -->
<div class="collapse" :class="{ 'show': defaultCollapse }" id="generatorFormCollapse">
<form class="card-body row" @submit.prevent="handleSubmit" novalidate>
<!-- 基本信息输入区域 -->
<div class="col-md-4 mb-3 has-validation">
<label class="form-label fw-medium" for="author">作者名称</label>
<input class="form-control border-secondary" :class="{ 'is-invalid': errors.author }"
id="author" placeholder="输入作者名称" v-model="form.author" type="text"
@input="validateField('author')">
<div class="invalid-feedback">
{{ errors.author || '请输入作者名称' }}
</div>
</div>
<div class="col-md-4 mb-3 has-validation">
<label class="form-label fw-medium" for="requestMapping">requestMapping名称</label>
<input class="form-control border-secondary" :class="{ 'is-invalid': errors.requestMapping }"
id="requestMapping" placeholder="输入requestMapping名称" v-model="form.requestMapping" type="text"
@input="validateField('requestMapping')">
<div class="invalid-feedback">
{{ errors.requestMapping || '请输入requestMapping名称' }}
</div>
</div>
<div class="col-md-4 mb-3 has-validation">
<label class="form-label fw-medium" for="packageName">包名称</label>
<input class="form-control border-secondary" :class="{ 'is-invalid': errors.packageName }"
id="packageName" placeholder="输入包名称" v-model="form.packageName" type="text"
@input="validateField('packageName')">
<div class="invalid-feedback">
{{ errors.packageName || '请输入包名称' }}
</div>
</div>
<div class="col-md-4 mb-3 has-validation">
<label class="form-label fw-medium" for="simpleDateFormat">时间格式</label>
<input class="form-control border-secondary" :class="{ 'is-invalid': errors.simpleDateFormat }"
id="simpleDateFormat" placeholder="输入时间格式" v-model="form.simpleDateFormat" type="text"
@input="validateField('simpleDateFormat')">
<div class="invalid-feedback">
{{ errors.simpleDateFormat || '请输入时间格式' }}
</div>
</div>
<div class="col-md-4 mb-3 has-validation">
<label class="form-label fw-medium" for="tablePrefixes">去除开头前缀</label>
<input class="form-control border-secondary" :class="{ 'is-invalid': errors.tablePrefixes }"
id="tablePrefixes" placeholder="去除开头前缀" v-model="form.tablePrefixes" type="text"
@input="validateField('tablePrefixes')">
<div class="invalid-feedback">
{{ errors.tablePrefixes || '请输入去除开头前缀' }}
</div>
</div>
<div class="col-md-12 mb-3 has-validation">
<label class="form-label fw-medium" for="sql">Sql语句</label>
<textarea class="form-control border-secondary" style="height: 150px;" :class="{ 'is-invalid': errors.sql }"
id="sql" placeholder="请输入Sql语句" v-model="form.sql" @input="validateField('sql')"/>
<div class="invalid-feedback">
{{ errors.sql || '请输入Sql语句' }}
</div>
</div>
<!-- 前端模板选择区域 -->
<div class="col-12 mt-3 mb-3 p-3 bg-light rounded">
<label class="form-check-inline col-form-label fw-medium">生成前端模板</label>
<div class="form-check form-check-inline" v-for="(web,index) in webList" :key="index">
<input class="form-check-input border-secondary" :class="{ 'is-invalid': errors.webTemplates }"
:id="web.id" type="checkbox" v-model="web.checked"
@change="validateTemplates">
<label class="form-check-label" :for="web.id" :title="web.name">{{web.label}}</label>
</div>
<div class="form-check form-check-inline btn-group ms-2">
<button class="btn btn-outline-primary btn-sm" type="button"
@click="onTableSelectAll(webList)">全选</button>
<button class="btn btn-outline-secondary btn-sm" type="button"
@click="onTableInvertSelection(webList)">反选</button>
<button class="btn btn-outline-danger btn-sm" type="button"
@click="onTableClearAll(webList)">全不选</button>
</div>
<div v-if="errors.webTemplates" class="invalid-feedback d-block">
{{ errors.webTemplates }}
</div>
</div>
<!-- 后端模板选择区域 -->
<div class="col-12 mt-2 mb-3 p-3 bg-light rounded">
<label class="form-check-inline col-form-label fw-medium">生成后端模板</label>
<div class="form-check form-check-inline" v-for="(server,index) in serverList" :key="index">
<input class="form-check-input border-secondary"
:class="{ 'is-invalid': errors.serverTemplates }" :id="server.id" type="checkbox"
v-model="server.checked" @change="validateTemplates">
<label class="form-check-label" :title="server.name" :for="server.id">{{server.label}}</label>
</div>
<div class="form-check form-check-inline btn-group ms-2">
<button class="btn btn-outline-primary btn-sm" type="button"
@click="onTableSelectAll(serverList)">全选</button>
<button class="btn btn-outline-secondary btn-sm" type="button"
@click="onTableInvertSelection(serverList)">反选</button>
<button class="btn btn-outline-danger btn-sm" type="button"
@click="onTableClearAll(serverList)">全不选</button>
</div>
<div v-if="errors.serverTemplates" class="invalid-feedback d-block">
{{ errors.serverTemplates }}
</div>
</div>
<!-- 操作按钮区域 -->
<div class="row mt-4">
<div class="col-md-4 btn-group">
<button class="btn btn-outline-primary" data-bs-title="选择数据表中所有的内容" data-bs-toggle="tooltip"
type="button" @click="onSelectAll">
全部选择
</button>
<button class="btn btn-outline-secondary" data-bs-title="将选择的表格内容反向选择" data-bs-toggle="tooltip"
type="button" @click="onInvertSelection">
全部反选
</button>
<button class="btn btn-outline-danger" data-bs-title="取消全部已经选择的数据表" data-bs-toggle="tooltip"
type="button" @click="onClearAll">
全部取消
</button>
</div>
<div class="col-md-4 btn-group">
<button class="btn btn-primary shadow-sm" disabled type="button"
v-if="generatorCodeLoading">
<span aria-hidden="true" class="spinner-grow spinner-grow-sm"></span>
<span role="status">生成中...</span>
</button>
<button v-else class="btn btn-primary" type="submit">
开始生成
</button>
<button class="btn btn-info" type="button" @click="onGetSqlParserInfo">获取Sql内容信息</button>
<button class="btn btn-warning" type="button" @click="onClearGeneratorData">清空生成记录</button>
</div>
<div class="col-md-4 d-grid gap-2">
<button class="btn btn-primary shadow-sm" disabled type="button"
v-if="downloadLoading">
<span aria-hidden="true" class="spinner-grow spinner-grow-sm"></span>
<span role="status">下载ZIP...</span>
</button>
<button v-else class="btn btn-primary text-white" type="button" @click="onDownloadZip">下载ZIP</button>
</div>
</div>
</form>
</div>
</div>
`,
props: {
// 表单数据对象,包含生成代码所需的各种参数
form: {type: Object, required: true},
// 生成代码的回调函数
onGeneratorCode: {type: Function, required: true},
// 清空生成记录
onClearGeneratorData: {type: Function, required: true},
// 生成代码加载
generatorCodeLoading: {type: Boolean, required: true},
// sql语句中表信息
tableInfo: {type: Object, required: true},
// sql语句中列信息
columnMetaList: {type: Object, required: true},
},
data() {
return {
// 控制表单默认是否展开
defaultCollapse: true,
// 后端模板选项列表
serverList: ref([]),
// 前端模板选项列表
webList: ref([]),
// 错误信息对象
errors: {
author: '',
requestMapping: '',
packageName: '',
simpleDateFormat: '',
tablePrefixes: '',
webTemplates: '',
serverTemplates: '',
sql: ''
},
downloadLoading: ref(false),
};
},
methods: {
/**
* 验证表单字段
* @param {string} field - 字段名
*/
validateField(field) {
if (!this.form[field] || this.form[field].trim() === '') {
this.errors[field] = '此字段为必填项';
return false;
}
this.errors[field] = '';
return true;
},
/* 验证模板选择 */
validateTemplates() {
// 检查列表是否有一个选中的
const hasWebSelected = this.webList.some(item => item.checked);
const hasServerSelected = this.serverList.some(item => item.checked);
// 列表都没有选中
if (!hasWebSelected && !hasServerSelected) {
this.errors.webTemplates = '请至少选择一个前端或后端模板';
this.errors.serverTemplates = '请至少选择一个前端或后端模板';
return false;
}
// 列表选中让错误提示小时
this.errors.webTemplates = '';
this.errors.serverTemplates = '';
// 发生选择变化时,同时父级更新表单
this.updateForm();
return true;
},
/* 验证整个表单 */
validateForm() {
let isValid = true;
// 验证文本字段
const textFields = ['author', 'requestMapping', 'packageName', 'simpleDateFormat', 'tablePrefixes', "sql"];
textFields.forEach(field => {
if (!this.validateField(field)) {
isValid = false;
}
});
// 验证模板选择
if (!this.validateTemplates()) {
isValid = false;
}
return isValid;
},
/* 更新父级表单 */
updateForm() {
const webList = this.webList.filter(item => item.checked).map(item => item.name);
const serverList = this.serverList.filter(item => item.checked).map(item => item.name);
const newForm = {...this.form, path: [...webList, ...serverList,]};
this.$emit("update:form", newForm);
},
/* 获取Sql内容信息 */
async onGetSqlParserInfo() {
const validate = this.validateForm();
if (!validate) return;
// 设置请求参数
const data = {sql: this.form.sql};
// 获取表信息
const tableInfoResponse = await axiosInstance.post("/sqlParser/tableInfo", data, {headers: {'Content-Type': 'multipart/form-data'}});
if (tableInfoResponse.code === 200) {
this.$emit("update:tableInfo", tableInfoResponse.data)
}
// 获取sql中的列信息
const columnMetaDataResponse = await axiosInstance.post("/sqlParser/columnMetaData", data, {headers: {'Content-Type': 'multipart/form-data'}});
if (columnMetaDataResponse.code === 200) {
this.$emit("update:columnMetaList", columnMetaDataResponse.data)
}
},
/* 处理表单提交 */
handleSubmit() {
if (this.validateForm()) {
this.updateForm();
// 如果验证通过,调用父组件提供的生成代码方法
this.onGeneratorCode();
} else {
// 验证失败,可以在这里添加额外的处理逻辑
antd.message.error("表单验证失败")
}
},
/**
* 获取VMS资源路径列表
* 从服务器获取前端和后端模板的路径列表
* @async
* @returns {Promise<void>}
*/
async getVmsResourcePathList() {
const response = await axiosInstance.get("/vms/vmsResourcePathList");
const {data, code, message} = response;
if (code !== 200) {
antd.message.error(message);
return;
}
// 初始化模板选择状态
this.serverList = data.server.map(item => ({...item, checked: false}));
this.webList = data.web.map(item => ({...item, checked: false}));
},
/**
* 下载Zip文件
* @returns {Promise<void>}
*/
async onDownloadZip() {
this.downloadLoading = true;
try {
const response = await axiosInstance({
url: "/generator/downloadByZip",
method: "POST",
data: this.form,
responseType: 'blob' // 重要指定响应类型为blob
});
// 从响应头中获取文件名
const contentDisposition = response.headers['content-disposition'];
let fileName = 'download.zip';
if (contentDisposition) {
const fileNameMatch = contentDisposition.match(/filename=(.+)/);
if (fileNameMatch && fileNameMatch[1]) {
fileName = fileNameMatch[1];
// 处理可能的编码文件名如UTF-8编码
if (fileName.startsWith("UTF-8''")) {
fileName = decodeURIComponent(fileName.replace("UTF-8''", ''));
}
}
}
// 创建Blob对象
const blob = new Blob([response.data]);
// 创建下载链接
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = fileName;
document.body.appendChild(link);
// 触发点击下载
link.click();
// 清理
window.URL.revokeObjectURL(downloadUrl);
document.body.removeChild(link);
} catch (error) {
console.error('下载失败:', error);
}
this.downloadLoading = false;
},
/**
* 全选指定列表
* @param {Array} list - 要处理的列表
*/
onTableSelectAll(list) {
list.forEach(item => item.checked = true);
this.validateTemplates();
},
/**
* 反选指定列表
* @param {Array} list - 要处理的列表
*/
onTableInvertSelection(list) {
list.forEach(item => item.checked = !item.checked);
this.validateTemplates();
},
/**
* 清空指定列表的选择
* @param {Array} list - 要处理的列表
*/
onTableClearAll(list) {
list.forEach(item => item.checked = false);
this.validateTemplates();
},
/* 全选所有模板 */
onSelectAll() {
this.onTableSelectAll(this.webList);
this.onTableSelectAll(this.serverList);
},
/* 反选所有模板 */
onInvertSelection() {
this.onTableInvertSelection(this.webList);
this.onTableInvertSelection(this.serverList);
},
/* 清空所有模板的选择 */
onClearAll() {
this.onTableClearAll(this.webList);
this.onTableClearAll(this.serverList);
}
},
async mounted() {
// 组件挂载时获取模板列表
await this.getVmsResourcePathList();
},
}

View File

@ -0,0 +1,112 @@
const SqlParserInfo = {
name: "SqlParserInfo",
template: `
<div class="card database-info-card">
<!-- 数据库信息折叠面板 -->
<div class="card-header bg-light d-flex justify-content-between align-items-center" data-bs-toggle="collapse"
:data-bs-target="'#databaseInfoCollapse'" aria-expanded="true" aria-controls="databaseInfoCollapse"
style="cursor: pointer">
<h4 class="mb-0"><i class="fas fa-database me-2"></i></h4>
<i class="fas fa-chevron-down transition-all"></i>
</div>
<div id="databaseInfoCollapse" class="collapse show">
<div class="card-body">
<!-- 表信息折叠面板 -->
<div class="table-info-section mb-3">
<div class="d-flex justify-content-between align-items-center mb-2"
data-bs-toggle="collapse"
:data-bs-target="'#tableInfoCollapse'"
aria-expanded="true"
aria-controls="tableInfoCollapse"
style="cursor: pointer">
<h5 class="mb-0"><i class="fas fa-table me-2"></i></h5>
<i class="fas fa-chevron-down transition-all"></i>
</div>
<div id="tableInfoCollapse" class="collapse show">
<div class="row">
<div class="col-md-6">
<ul class="list-group list-group-flush">
<li class="list-group-item">
<strong>表名:</strong> {{ tableInfo.tableName }}
</li>
<li class="list-group-item">
<strong>注释:</strong> {{ tableInfo.comment || '' }}
</li>
</ul>
</div>
<div class="col-md-6">
<ul class="list-group list-group-flush">
<li class="list-group-item">
<strong>表类型:</strong> {{ tableInfo.tableType || '' }}
</li>
<li class="list-group-item">
<strong>表目录:</strong> {{ tableInfo.tableCat || '' }}
</li>
</ul>
</div>
</div>
</div>
</div>
<!-- 列信息折叠面板 -->
<div>
<div class="d-flex justify-content-between align-items-center mb-2"
data-bs-toggle="collapse"
:data-bs-target="'#columnInfoCollapse'"
aria-expanded="true"
aria-controls="columnInfoCollapse"
style="cursor: pointer">
<h5 class="mb-0"><i class="fas fa-columns me-2"></i> ({{ columnMetaList.length }})</h5>
<i class="fas fa-chevron-down transition-all"></i>
</div>
<div id="columnInfoCollapse" class="collapse show">
<div class="column-list">
<div :key="index" class="card column-item mb-2" v-for="(column, index) in columnMetaList">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start">
<p class="card-text text-muted small mb-2">
{{column.columnName.replace(/\`/g, '')}} {{ column.comment || '无注释' }}
</p>
<span class="badge bg-secondary" v-if="column.isPrimaryKey">主键</span>
</div>
<div class="row">
<div class="col-md-4">
<small class="text-muted">Java类型:</small>
<span class="badge badge-java ms-2">{{ column.javaType }}</span>
</div>
<div class="col-md-4">
<small class="text-muted">JDBC类型:</small>
<span class="badge badge-jdbc ms-2">{{ column.jdbcType }}</span>
</div>
<div class="col-md-4">
<small class="text-muted">JS类型:</small>
<span class="badge badge-js ms-2">{{ column.javascriptType }}</span>
</div>
</div>
<div class="mt-2">
<small class="text-muted">字段名:</small>
<code class="ms-2">{{ column.lowercaseName.replace(/\`/g, '') }}</code>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`,
props: {
// sql语句中表信息
tableInfo: {type: Object, required: true},
// sql语句中列信息
columnMetaList: {type: Object, required: true},
},
data() {
return {};
},
};

View File

@ -0,0 +1,210 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>代码生成器-数据库生成</title>
<!-- 本地引入 Bootstrap CSS -->
<link rel="stylesheet" th:href="@{/src/lib/css/index.css}">
<!-- 本地引入 Vue.js -->
<script th:src="@{/src/lib/js/vue/vue.global.js}"></script>
<!-- 本地引入 Bootstrap JS -->
<script th:src="@{/src/lib/js/boostrap/bootstrap.bundle.min.js}"></script>
<!-- 本地引入 popper JS -->
<script th:src="@{/src/lib/js/boostrap/popper.min.js}"></script>
<!-- 本地引入 axios JS -->
<script th:src="@{/src/lib/js/axios/axios.min.js}"></script>
<!-- 引入dayjs -->
<script th:src="@{/src/lib/js/dayjs/dayjs.min.js}"></script>
<script th:src="@{/src/lib/js/dayjs/customParseFormat.js}"></script>
<script th:src="@{/src/lib/js/dayjs/weekday.js}"></script>
<script th:src="@{/src/lib/js/dayjs/localeData.js}"></script>
<script th:src="@{/src/lib/js/dayjs/weekOfYear.js}"></script>
<script th:src="@{/src/lib/js/dayjs/weekYear.js}"></script>
<script th:src="@{/src/lib/js/dayjs/advancedFormat.js}"></script>
<script th:src="@{/src/lib/js/dayjs/quarterOfYear.js}"></script>
<!-- 引入 antd JS -->
<script th:src="@{/src/lib/js/dayjs/antd.min.js}"></script>
</head>
<body>
<div id="app">
<div class="container-fluid my-4">
<!-- 主标题 -->
<app-header></app-header>
<!-- 主卡片 -->
<database-card :raw-table-list="rawTableList" @get-database-table-list="getDatabaseTableList"
v-model:db-select="dbSelect" v-model:table-select="tableSelect"></database-card>
<!-- 填写生成表单 -->
<database-form :generator-code-loading="generatorCodeLoading" :on-clear-generator-data="onClearGeneratorData"
:on-generator-code="onGeneratorCode" ref="mainFormRef" v-model:form="form"></database-form>
<!-- 表格显示 -->
<database-table :db-select="dbSelect" :loading="loading" :on-generator-code="onGeneratorCode"
:raw-table-list="rawTableList" :table-list="tableList" v-model:form="form"></database-table>
<!-- 模板生成页面 -->
<app-generator-page :generator-data="generatorData"
v-model:generator-page-flag="generatorPageFlag"></app-generator-page>
</div>
</div>
</body>
<!-- 引入Highlight.js -->
<script th:src="@{/src/lib/js/highlightjs/highlight.min.js}"></script>
<script th:src="@{/src/lib/js/highlightjs/javascript.min.js}"></script>
<!-- 初始化 Highlight.js -->
<script th:src="@{/src/config/highlight-config.js}"></script>
<!-- 设置 popper 提示框 -->
<script th:src="@{/src/config/popper-config.js}"></script>
<!-- 加载全局axios配置 -->
<script th:src="@{/src/config/axios-config.js}"></script>
<!-- 引入组件 -->
<script th:src="@{/src/components/AppHeader.js}"></script>
<script th:src="@{/src/views/database/DatabaseCard.js}"></script>
<script th:src="@{/src/views/database/DatabaseForm.js}"></script>
<script th:src="@{/src/views/database/DatabaseTable.js}"></script>
<script th:src="@{/src/components/AppGeneratorPage.js}"></script>
<script>
const {createApp, ref} = Vue
const app = createApp({
setup() {
return {
// 数据库选择
dbSelect: ref(""),
// 数据库表过滤
tableSelect: ref(""),
// 数据库表列表
tableList: ref([]),
// 原始数据库列表
rawTableList: ref([]),
// 是否加载
loading: ref(false),
// 是否正在生成
generatorCodeLoading: ref(false),
// 提交的表单
form: ref({
// 作者名称
author: "Bunny",
// requestMapping名称
requestMapping: "/api",
// 表名称
tableNames: [],
// 包名称
packageName: "cn.bunny",
// 时间格式
simpleDateFormat: "yyyy-MM-dd HH:mm:ss",
// 去除开头前缀
tablePrefixes: "t_,sys_,qrtz_,log_",
// 生成代码路径
path: [],
}),
// 是否显示生成页面
generatorPageFlag: ref(false),
// 生成的数据
generatorData: ref({}),
};
},
methods: {
/* 获取[当前/所有]数据库表 */
async getDatabaseTableList() {
this.loading = true;
// 查询数据库表
const response = await axiosInstance.get("/table/databaseTableList", {params: {dbName: this.dbSelect}});
const {data, code, message} = response;
if (code !== 200) {
antd.message.error(message);
return;
}
// 设置数据库列表
this.tableList = data;
this.rawTableList = data;
// 重置到第一页
this.currentPage = 1;
this.loading = false;
},
/**
* 生成代码
* @returns {Promise<void>}
*/
async onGeneratorCode() {
this.generatorCodeLoading = true;
// 验证表单是否通过
const isValidate = this.$refs.mainFormRef.validateForm();
if (!isValidate) return;
// 请求生成模板
const {data, code, message} = await axiosInstance.post("/generator", this.form);
// 判断是否请求成功
if (code !== 200) {
this.generatorCodeLoading = false;
return;
} else antd.message.success(message);
// 显示生成的页面
this.generatorData = data;
this.generatorPageFlag = true;
this.generatorCodeLoading = false;
// 等待 DOM 更新,之后手动更新代码高亮
await this.$nextTick();
hljs.highlightAll();
},
/* 清空已经生成的内容 */
onClearGeneratorData() {
this.generatorData = {};
antd.message.success("清除完成");
},
},
watch: {
/* 数据表选择 */
dbSelect: "getDatabaseTableList",
/* 过滤数据表 */
tableSelect(val) {
this.tableList = this.rawTableList;
// 根据表名进行过滤筛选或者根据注释内容进行筛选
this.tableList = this.tableList.filter(table => table.tableName.includes(val) || table.comment.includes(val));
},
/**
* 监听form表单放到 localStorage
* 不要使用 immediate 否则初始话加载的时候会将 localStorage 改成 原始表单
*/
form: {
deep: true,
handler(val) {
localStorage.setItem("form", JSON.stringify(val));
},
}
},
mounted() {
const form = localStorage.getItem("form");
if (form !== null) {
this.form = JSON.parse(form);
}
}
});
// 注册组件
app.component('AppHeader', AppHeader);
app.component('DatabaseCard', DatabaseCard);
app.component('DatabaseForm', DatabaseForm);
app.component('DatabaseTable', DatabaseTable);
app.component('AppGeneratorPage', AppGeneratorPage);
app.mount('#app');
</script>
</html>

View File

@ -0,0 +1,146 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>代码生成器-SQL语句生成</title>
<!-- 本地引入 Bootstrap CSS -->
<link rel="stylesheet" th:href="@{/src/lib/css/index.css}">
<!-- 本地引入 Vue.js -->
<script th:src="@{/src/lib/js/vue/vue.global.js}"></script>
<!-- 本地引入 Bootstrap JS -->
<script th:src="@{/src/lib/js/boostrap/bootstrap.bundle.min.js}"></script>
<!-- 本地引入 popper JS -->
<script th:src="@{/src/lib/js/boostrap/popper.min.js}"></script>
<!-- 本地引入 axios JS -->
<script th:src="@{/src/lib/js/axios/axios.min.js}"></script>
<!-- 引入dayjs -->
<script th:src="@{/src/lib/js/dayjs/dayjs.min.js}"></script>
<script th:src="@{/src/lib/js/dayjs/customParseFormat.js}"></script>
<script th:src="@{/src/lib/js/dayjs/weekday.js}"></script>
<script th:src="@{/src/lib/js/dayjs/localeData.js}"></script>
<script th:src="@{/src/lib/js/dayjs/weekOfYear.js}"></script>
<script th:src="@{/src/lib/js/dayjs/weekYear.js}"></script>
<script th:src="@{/src/lib/js/dayjs/advancedFormat.js}"></script>
<script th:src="@{/src/lib/js/dayjs/quarterOfYear.js}"></script>
<!-- 引入 antd JS -->
<script th:src="@{/src/lib/js/dayjs/antd.min.js}"></script>
</head>
<body>
<div id="app">
<div class="container-fluid my-4">
<!-- 主标题 -->
<app-header></app-header>
<sql-form :generator-code-loading="generatorCodeLoading" :on-clear-generator-data="onClearGeneratorData"
:on-generator-code="onGeneratorCode" ref="mainFormRef" v-model:column-meta-list="columnMetaList"
v-model:form="form" v-model:table-info="tableInfo"></sql-form>
<!-- 数据库信息展示区域 -->
<sql-parser-info :column-meta-list="columnMetaList" :table-info="tableInfo"></sql-parser-info>
<!-- 模板生成页面 -->
<app-generator-page :generator-data="generatorData"
v-model:generator-page-flag="generatorPageFlag"></app-generator-page>
</div>
</div>
</body>
<!-- 引入Highlight.js -->
<script th:src="@{/src/lib/js/highlightjs/highlight.min.js}"></script>
<script th:src="@{/src/lib/js/highlightjs/javascript.min.js}"></script>
<!-- 初始化 Highlight.js -->
<script th:src="@{/src/config/highlight-config.js}"></script>
<!-- 设置 popper 提示框 -->
<script th:src="@{/src/config/popper-config.js}"></script>
<!-- 加载全局axios配置 -->
<script th:src="@{/src/config/axios-config.js}"></script>
<!-- 引入组件 -->
<script th:src="@{/src/components/AppHeader.js}"></script>
<script th:src="@{/src/views/sql/SqlForm.js}"></script>
<script th:src="@{/src/views/sql/SqlParserInfo.js}"></script>
<script th:src="@{/src/components/AppGeneratorPage.js}"></script>
<script>
const {createApp, ref} = Vue
const app = createApp({
setup() {
return {
// 是否正在生成
generatorCodeLoading: ref(false),
// 提交的表单
form: ref({
// 作者名称
author: "Bunny",
// requestMapping名称
requestMapping: "/api",
// 包名称
packageName: "cn.bunny",
// 时间格式
simpleDateFormat: "yyyy-MM-dd HH:mm:ss",
// 去除开头前缀
tablePrefixes: "t_,sys_,qrtz_,log_",
// 生成代码路径
path: [],
sql: "",
}),
// 是否显示生成页面
generatorPageFlag: ref(false),
// 生成的数据
generatorData: ref({}),
// sql语句中表信息
tableInfo: ref({}),
// sql语句中列信息
columnMetaList: ref([]),
};
},
methods: {
/**
* 生成代码
* @returns {Promise<void>}
*/
async onGeneratorCode() {
this.generatorCodeLoading = true;
// 验证表单是否通过
const isValidate = this.$refs.mainFormRef.validateForm();
if (!isValidate) return;
// 请求生成模板
const {data, code, message} = await axiosInstance.post("/generator", this.form);
// 判断是否请求成功
if (code !== 200) {
this.generatorCodeLoading = false;
return;
} else antd.message.success(message);
// 显示生成的页面
this.generatorData = data;
this.generatorPageFlag = true;
this.generatorCodeLoading = false;
// 等待 DOM 更新,之后手动更新代码高亮
await this.$nextTick();
hljs.highlightAll();
},
/* 清空已经生成的内容 */
onClearGeneratorData() {
this.generatorData = {};
antd.message.success("清除完成")
},
},
});
// 注册组件
app.component('AppHeader', AppHeader);
app.component('SqlForm', SqlForm);
app.component('SqlParserInfo', SqlParserInfo);
app.component('AppGeneratorPage', AppGeneratorPage);
app.mount('#app');
</script>
</html>

View File

@ -0,0 +1,69 @@
package ${package}.controller;
import ${package}.domain.dto.${classUppercaseName}Dto;
import ${package}.domain.entity.${classUppercaseName}Entity;
import ${package}.domain.vo.${classUppercaseName}Vo;
import io.swagger.v3.oas.annotations.Parameter;
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 com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import ${package}.service.${classUppercaseName}Service;
import ${package}.domain.vo.result.PageResult;
import ${package}.domain.vo.result.Result;
import ${package}.domain.vo.result.ResultCodeEnum;
import java.util.List;
/**
* <p>
* ${comment} 前端控制器
* </p>
*
* @author ${author}
* @since ${date}
*/
@Tag(name = "${comment}", description = "${comment}相关接口")
@RestController
@RequestMapping("${requestMapping}/${lowerHyphenName}")
@RequiredArgsConstructor
public class ${classUppercaseName}Controller {
private final ${classUppercaseName}Service ${classLowercaseName}Service;
@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)
@PathVariable("limit") Integer limit,
${classUppercaseName}Dto dto) {
Page<${classUppercaseName}Entity> pageParams = new Page<>(page, limit);
PageResult<${classUppercaseName}Vo> pageResult = ${classLowercaseName}Service.get${classUppercaseName}Page(pageParams, dto);
return Result.success(pageResult);
}
@Operation(summary = "添加${comment}", description = "添加${comment}")
@PostMapping()
public Result<String> add${classUppercaseName}(@Valid @RequestBody ${classUppercaseName}Dto dto) {
${classLowercaseName}Service.add${classUppercaseName}(dto);
return Result.success(ResultCodeEnum.ADD_SUCCESS);
}
@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()
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,37 @@
package ${package}.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
import ${package}.domain.dto.${classUppercaseName}Dto;
import ${package}.domain.entity.${classUppercaseName}Entity;
import ${package}.domain.vo.${classUppercaseName}Vo;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.apache.ibatis.annotations.Param;
import ${package}.domain.vo.result.PageResult;
import ${package}.domain.vo.result.Result;
import ${package}.domain.vo.result.ResultCodeEnum;
import java.util.List;
/**
* <p>
* ${comment} Mapper 接口
* </p>
*
* @author ${author}
* @since ${date}
*/
@Mapper
public interface ${classUppercaseName}Mapper extends BaseMapper<${classUppercaseName}Entity> {
/**
* * 分页查询${comment}内容
*
* @param pageParams ${comment}分页参数
* @param dto ${comment}查询表单
* @return ${comment}分页结果
*/
IPage<${classUppercaseName}Vo> selectListByPage(@Param("page") Page<${classUppercaseName}Entity> pageParams, @Param("dto") ${classUppercaseName}Dto dto);
}

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="${package}.mapper.${classUppercaseName}Mapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="${package}.domain.entity.${classUppercaseName}Entity">
#foreach($field in ${columnInfoList})
<id column="${field.columnName}" property="${field.lowercaseName}"/>
#end
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
${baseColumnList}
</sql>
<!-- 分页查询${comment}内容 -->
<select id="selectListByPage" resultType="${package}.domain.vo.${classUppercaseName}Vo">
select
base.*,
create_user.username as create_username,
update_user.username as update_username
from $tableName base
left join sys_user create_user on create_user.id = base.create_user
left join sys_user update_user on update_user.id = base.update_user
<where>
base.is_deleted = 0
#foreach($field in $columnInfoList)
<if test="dto.${field.lowercaseName} != null and dto.${field.lowercaseName} != ''">
and base.${field.columnName} like CONCAT('%',#{dto.${field.lowercaseName}},'%')
</if>
#end
</where>
</select>
</mapper>

View File

@ -0,0 +1,25 @@
package ${package}.domain.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.math.BigDecimal;
import java.util.Date;
@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
}

Some files were not shown because too many files have changed in this diff Show More