增加生成后端服务代码。

This commit is contained in:
asp_ly 2024-12-28 19:47:15 +08:00
parent 01f57e22a0
commit cfeafb4b36
13 changed files with 541 additions and 12 deletions

View File

@ -235,6 +235,12 @@
<artifactId>json-schema-validator</artifactId>
<version>1.0.86</version>
</dependency>
<!-- Freemarker模板引擎 -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.32</version>
</dependency>
</dependencies>
<build>

View File

@ -1,20 +1,11 @@
package com.qqchen.deploy.backend.deploy.entity;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import com.qqchen.deploy.backend.deploy.enums.ProjectGroupTypeEnum;
import com.qqchen.deploy.backend.framework.annotation.LogicDelete;
import com.qqchen.deploy.backend.framework.domain.Entity;
import com.qqchen.deploy.backend.system.entity.User;
import jakarta.persistence.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 项目组实体
@ -25,6 +16,7 @@ import java.util.Set;
@Table(name = "deploy_project_group")
//@SQLDelete(sql = "UPDATE deploy_project_group SET deleted = TRUE WHERE id = ?; DELETE FROM deploy_project_group_environment WHERE project_group_id = ?")
//@Where(clause = "deleted = false")
@LogicDelete
public class ProjectGroup extends Entity<Long> {
/**

View File

@ -0,0 +1,106 @@
package com.qqchen.deploy.backend.framework.generator;
import com.qqchen.deploy.backend.framework.utils.CodeGeneratorUtils;
import com.qqchen.deploy.backend.framework.utils.CodeGeneratorUtils.GeneratorConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.util.Scanner;
/**
* 代码生成器主入口
*/
@Slf4j
public class CodeGenerator {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
try {
// 1. 输入模块信息
System.out.println("请输入模块名称(中文,如:用户):");
String moduleName = scanner.nextLine();
System.out.println("请输入类名英文User");
String className = scanner.nextLine();
System.out.println("请输入基础包路径com.qqchen.deploy.backend.system");
String basePackage = scanner.nextLine();
System.out.println("请输入输出路径src/main/java");
String outputPath = scanner.nextLine();
System.out.println("请输入表名sys_user");
String tableName = scanner.nextLine();
System.out.println("请输入URL路径user");
String urlPath = scanner.nextLine();
// 2. 输入建表SQL
System.out.println("请输入CREATE TABLE SQL语句以分号结束");
StringBuilder sqlBuilder = new StringBuilder();
String line;
while (!(line = scanner.nextLine()).contains(";")) {
sqlBuilder.append(line).append("\n");
}
sqlBuilder.append(line);
String createTableSql = sqlBuilder.toString();
// 3. 验证输入
if (StringUtils.isAnyBlank(moduleName, className, basePackage, outputPath, tableName, urlPath, createTableSql)) {
throw new IllegalArgumentException("所有输入项都不能为空");
}
// 4. 配置生成器
GeneratorConfig config = new GeneratorConfig();
config.setModuleName(moduleName);
config.setClassName(className);
config.setBasePackage(basePackage);
config.setOutputPath(outputPath);
config.setTableName(tableName);
config.setUrlPath(urlPath);
// 5. 生成代码
CodeGeneratorUtils.generateCode(createTableSql, config);
System.out.println("代码生成成功!");
System.out.println("生成的文件路径:" + outputPath);
} catch (Exception e) {
log.error("代码生成失败", e);
System.err.println("代码生成失败:" + e.getMessage());
} finally {
scanner.close();
}
}
/**
* 通过API方式生成代码
*/
public static void generate(String moduleName, String className, String basePackage,
String outputPath, String tableName, String urlPath, String createTableSql) {
try {
// 1. 验证输入
if (StringUtils.isAnyBlank(moduleName, className, basePackage, outputPath, tableName, urlPath, createTableSql)) {
throw new IllegalArgumentException("所有输入项都不能为空");
}
// 2. 配置生成器
GeneratorConfig config = new GeneratorConfig();
config.setModuleName(moduleName);
config.setClassName(className);
config.setBasePackage(basePackage);
config.setOutputPath(outputPath);
config.setTableName(tableName);
config.setUrlPath(urlPath);
// 3. 生成代码
CodeGeneratorUtils.generateCode(createTableSql, config);
log.info("代码生成成功!输出路径:{}", outputPath);
} catch (Exception e) {
log.error("代码生成失败", e);
throw new RuntimeException("代码生成失败:" + e.getMessage(), e);
}
}
}

View File

@ -0,0 +1,257 @@
package com.qqchen.deploy.backend.framework.utils;
import com.qqchen.deploy.backend.framework.generator.CodeGenerator;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 代码生成器工具类
*/
@Slf4j
public class CodeGeneratorUtils {
@Data
public static class FieldInfo {
private String fieldName; // 字段名
private String fieldType; // 字段类型
private String columnName; // 数据库列名
private String comment; // 注释
private boolean nullable; // 是否可空
private boolean isPrimaryKey; // 是否主键
}
@Data
public static class GeneratorConfig {
private String moduleName; // 模块名称中文
private String className; // 类名
private String basePackage; // 基础包路径
private String outputPath; // 输出路径
private String tableName; // 表名
private String urlPath; // URL路径
private List<FieldInfo> fields; // 字段信息
}
private static final Configuration configuration;
static {
configuration = new Configuration(Configuration.VERSION_2_3_32);
configuration.setClassLoaderForTemplateLoading(CodeGeneratorUtils.class.getClassLoader(), "templates");
configuration.setDefaultEncoding("UTF-8");
}
/**
* 生成所有代码文件
*
* @param createTableSql CREATE TABLE SQL语句
* @param config 生成器配置
*/
public static void generateCode(String createTableSql, GeneratorConfig config) {
try {
// 解析SQL获取字段信息
config.setFields(parseSqlFields(createTableSql));
// 生成各种类型的文件
generateFile("entity.ftl", config, "/entity/" + config.getClassName() + ".java");
generateFile("dto.ftl", config, "/dto/" + config.getClassName() + "DTO.java");
generateFile("query.ftl", config, "/query/" + config.getClassName() + "Query.java");
generateFile("repository.ftl", config, "/repository/I" + config.getClassName() + "Repository.java");
generateFile("service.ftl", config, "/service/I" + config.getClassName() + "Service.java");
generateFile("serviceImpl.ftl", config, "/service/impl/" + config.getClassName() + "ServiceImpl.java");
generateFile("controller.ftl", config, "/controller/" + config.getClassName() + "ApiController.java");
generateFile("converter.ftl", config, "/converter/" + config.getClassName() + "Converter.java");
log.info("代码生成完成,输出路径: {}", config.getOutputPath());
} catch (Exception e) {
log.error("代码生成失败", e);
throw new RuntimeException("代码生成失败", e);
}
}
/**
* 解析CREATE TABLE SQL获取字段信息
*/
private static List<FieldInfo> parseSqlFields(String createTableSql) {
List<FieldInfo> fields = new ArrayList<>();
// 移除多余的空白字符和换行
createTableSql = createTableSql.replaceAll("\\s+", " ").trim();
// 提取表定义部分
int startIndex = createTableSql.indexOf("(");
int endIndex = createTableSql.lastIndexOf(")");
if (startIndex == -1 || endIndex == -1) {
throw new IllegalArgumentException("无效的CREATE TABLE语句");
}
// 获取字段定义部分
String fieldDefinitions = createTableSql.substring(startIndex + 1, endIndex);
// 按逗号分割字段定义
String[] fieldDefs = fieldDefinitions.split(",");
for (String fieldDef : fieldDefs) {
fieldDef = fieldDef.trim();
// 跳过主键定义等非字段定义行
if (fieldDef.toLowerCase().startsWith("primary key") ||
fieldDef.toLowerCase().startsWith("key") ||
fieldDef.toLowerCase().startsWith("constraint") ||
fieldDef.toLowerCase().startsWith("index") ||
fieldDef.isEmpty()) {
continue;
}
// 解析字段定义
String[] parts = fieldDef.split("\\s+", 3);
if (parts.length < 2) {
continue;
}
FieldInfo field = new FieldInfo();
// 设置字段名移除反引号
field.setColumnName(parts[0].replace("`", ""));
// 设置字段类型
String typeStr = parts[1].toUpperCase();
if (typeStr.contains("(")) {
typeStr = typeStr.substring(0, typeStr.indexOf("("));
}
field.setFieldType(sqlTypeToJavaType(typeStr));
// 设置字段名驼峰命名
field.setFieldName(columnToField(field.getColumnName()));
// 设置是否可空
field.setNullable(!fieldDef.toLowerCase().contains("not null"));
// 设置注释
int commentIndex = fieldDef.toLowerCase().indexOf("comment");
if (commentIndex != -1) {
String comment = fieldDef.substring(commentIndex + 7).trim();
// 提取引号中的注释内容
if (comment.startsWith("'") && comment.endsWith("'")) {
field.setComment(comment.substring(1, comment.length() - 1));
}
}
// 设置是否为主键
field.setPrimaryKey(fieldDef.toLowerCase().contains("primary key"));
// 基础字段不需要生成
if (!isBaseField(field.getColumnName())) {
fields.add(field);
}
}
return fields;
}
/**
* 判断是否为基础字段这些字段由父类Entity提供
*/
private static boolean isBaseField(String columnName) {
return columnName.equals("id") ||
columnName.equals("create_by") ||
columnName.equals("create_time") ||
columnName.equals("update_by") ||
columnName.equals("update_time") ||
columnName.equals("version") ||
columnName.equals("deleted");
}
/**
* 生成单个文件
*/
private static void generateFile(String templateName, GeneratorConfig config, String subPath) throws IOException, TemplateException {
Template template = configuration.getTemplate(templateName);
String outputPath = config.getOutputPath() + subPath;
File outputFile = new File(outputPath);
// 确保目录存在
outputFile.getParentFile().mkdirs();
// 准备数据模型
Map<String, Object> dataModel = new HashMap<>();
dataModel.put("basePackage", config.getBasePackage());
dataModel.put("moduleName", config.getModuleName());
dataModel.put("className", config.getClassName());
dataModel.put("tableName", config.getTableName());
dataModel.put("fields", config.getFields());
dataModel.put("urlPath", config.getUrlPath());
// 生成文件
try (Writer writer = new FileWriter(outputFile)) {
template.process(dataModel, writer);
}
log.info("生成文件: {}", outputPath);
}
/**
* 数据库列名转Java字段名
*/
private static String columnToField(String columnName) {
String[] parts = columnName.toLowerCase().split("_");
StringBuilder result = new StringBuilder(parts[0]);
for (int i = 1; i < parts.length; i++) {
result.append(StringUtils.capitalize(parts[i]));
}
return result.toString();
}
/**
* SQL类型转Java类型
*/
private static String sqlTypeToJavaType(String sqlType) {
sqlType = sqlType.toUpperCase();
switch (sqlType) {
case "VARCHAR":
case "CHAR":
case "TEXT":
return "String";
case "INT":
case "INTEGER":
return "Integer";
case "BIGINT":
return "Long";
case "DECIMAL":
return "BigDecimal";
case "DATETIME":
case "TIMESTAMP":
return "LocalDateTime";
case "DATE":
return "LocalDate";
case "BIT":
case "TINYINT":
return "Boolean";
default:
return "String";
}
}
}

View File

@ -79,9 +79,11 @@ VALUES
(203, '环境管理', '/deploy/environments', '/src/pages/Deploy/Environment/List/index', 'CloudOutlined', 2, 200, 3, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE),
(204, '部署配置管理', '/deploy/deployment', '/src/pages/Deploy/Deployment/List/index', 'CloudOutlined', 2, 200, 3, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE),
(204, '部署配置管理', '/deploy/deployment', '/src/pages/Deploy/Deployment/List/index', 'CloudOutlined', 2, 200, 4, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE),
(205, 'Jenkins管理', '/deploy/jenkins', '/src/pages/Deploy/Jenkins/List/index', 'CloudOutlined', 2, 200, 5, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE),
-- 三方系统
(205, '三方系统管理', '/deploy/external', '/src/pages/Deploy/external/index', 'ApiOutlined', 2, 200, 70, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE);
(206, '三方系统管理', '/deploy/external', '/src/pages/Deploy/external/index', 'ApiOutlined', 2, 200, 6, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE);
-- 初始化角色数据
INSERT INTO sys_role (id, create_time, code, name, type, description, sort)

View File

@ -0,0 +1,28 @@
package ${basePackage}.controller;
import com.qqchen.deploy.backend.framework.controller.BaseController;
import ${basePackage}.entity.${className};
import ${basePackage}.dto.${className}DTO;
import ${basePackage}.query.${className}Query;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
/**
* ${moduleName} Controller
*/
@Slf4j
@RestController
@RequestMapping("/api/v1/${urlPath}")
@Tag(name = "${moduleName}管理", description = "${moduleName}管理相关接口")
public class ${className}ApiController extends BaseController<${className}, ${className}DTO, Long, ${className}Query> {
@Override
protected void exportData(HttpServletResponse response, List<${className}DTO> data) {
// TODO: 实现导出逻辑
log.info("导出${moduleName}数据,数据量:{}", data.size());
}
}

View File

@ -0,0 +1,13 @@
package ${basePackage}.converter;
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
import ${basePackage}.entity.${className};
import ${basePackage}.dto.${className}DTO;
import org.mapstruct.Mapper;
/**
* ${moduleName} Converter
*/
@Mapper(config = BaseConverter.class)
public interface ${className}Converter extends BaseConverter<${className}, ${className}DTO> {
}

View File

@ -0,0 +1,24 @@
package ${basePackage}.dto;
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
* ${moduleName} DTO
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class ${className}DTO extends BaseDTO {
<#list fields as field>
<#if field.comment??>
/**
* ${field.comment}
*/
</#if>
private ${field.fieldType} ${field.fieldName};
</#list>
}

View File

@ -0,0 +1,28 @@
package ${basePackage}.entity;
import com.qqchen.deploy.backend.framework.domain.Entity;
import jakarta.persistence.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
* ${moduleName}实体
*/
@Data
@EqualsAndHashCode(callSuper = true)
@jakarta.persistence.Entity
@Table(name = "${tableName}")
public class ${className} extends Entity<Long> {
<#list fields as field>
<#if field.comment??>
/**
* ${field.comment}
*/
</#if>
@Column(name = "${field.columnName}"<#if !field.nullable>, nullable = false</#if>)
private ${field.fieldType} ${field.fieldName};
</#list>
}

View File

@ -0,0 +1,31 @@
package ${basePackage}.query;
import com.qqchen.deploy.backend.framework.annotation.QueryField;
import com.qqchen.deploy.backend.framework.enums.QueryType;
import com.qqchen.deploy.backend.framework.query.BaseQuery;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
* ${moduleName}查询对象
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class ${className}Query extends BaseQuery {
<#list fields as field>
<#if field.comment??>
/**
* ${field.comment}
*/
</#if>
<#if field.fieldType == "String">
@QueryField(field = "${field.columnName}", type = QueryType.LIKE)
<#else>
@QueryField(field = "${field.columnName}")
</#if>
private ${field.fieldType} ${field.fieldName};
</#list>
}

View File

@ -0,0 +1,12 @@
package ${basePackage}.repository;
import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
import ${basePackage}.entity.${className};
import org.springframework.stereotype.Repository;
/**
* ${moduleName} Repository
*/
@Repository
public interface I${className}Repository extends IBaseRepository<${className}, Long> {
}

View File

@ -0,0 +1,12 @@
package ${basePackage}.service;
import com.qqchen.deploy.backend.framework.service.IBaseService;
import ${basePackage}.entity.${className};
import ${basePackage}.dto.${className}DTO;
import ${basePackage}.query.${className}Query;
/**
* ${moduleName} Service接口
*/
public interface I${className}Service extends IBaseService<${className}, ${className}DTO, Long, ${className}Query> {
}

View File

@ -0,0 +1,18 @@
package ${basePackage}.service.impl;
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
import ${basePackage}.entity.${className};
import ${basePackage}.dto.${className}DTO;
import ${basePackage}.query.${className}Query;
import ${basePackage}.service.I${className}Service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* ${moduleName} Service实现
*/
@Slf4j
@Service
public class ${className}ServiceImpl extends BaseServiceImpl<${className}, ${className}DTO, Long, ${className}Query>
implements I${className}Service {
}