🎨 重构后端生成内容

This commit is contained in:
bunny 2025-06-29 09:30:24 +08:00
parent d26208635f
commit 1f2896b863
10 changed files with 194 additions and 139 deletions

View File

@ -12,7 +12,7 @@ public class VmsHolder {
@PostConstruct
public void init() {
Properties prop = new Properties();
prop.put("resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
prop.put("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
Velocity.init(prop);
}
}

View File

@ -32,8 +32,8 @@ public class VmsController {
@Operation(summary = "生成代码", description = "生成代码")
@PostMapping("generator")
public Result<List<GeneratorVo>> generator(@Valid @RequestBody VmsArgumentDto dto) {
List<GeneratorVo> list = vmsService.generator(dto);
public Result<Map<String, List<GeneratorVo>>> generator(@Valid @RequestBody VmsArgumentDto dto) {
Map<String, List<GeneratorVo>> list = vmsService.generator(dto);
return Result.success(list);
}

View File

@ -18,14 +18,17 @@ public class VmsArgumentDtoBaseVmsGeneratorTemplate extends AbstractVmsGenerator
private final VmsArgumentDto dto;
private final String path;
private final String tableName;
/**
* @param dto 类名称可以自定义格式为 xxx_xxx
* @param path 当前路径
* @param tableName 表名称
*/
public VmsArgumentDtoBaseVmsGeneratorTemplate(VmsArgumentDto dto, String path) {
public VmsArgumentDtoBaseVmsGeneratorTemplate(VmsArgumentDto dto, String path, String tableName) {
this.dto = dto;
this.path = path;
this.tableName = tableName;
}
/**
@ -51,22 +54,12 @@ public class VmsArgumentDtoBaseVmsGeneratorTemplate extends AbstractVmsGenerator
// 设置包名称
context.put("package", dto.getPackageName());
// 类名称如果是小驼峰需要 [手写] [下划线] 之后由 [代码 -> 小驼峰/大驼峰]
String className = dto.getClassName();
// 去除表开头前缀
String tablePrefixes = dto.getTablePrefixes();
// 表前缀 转成数组
String replaceTableName = "";
for (String prefix : tablePrefixes.split("[,]")) {
replaceTableName = className.replace(prefix, "");
}
// 将类名称转成小驼峰
String toCamelCase = TypeConvertUtil.convertToCamelCase(replaceTableName);
String toCamelCase = TypeConvertUtil.convertToCamelCase(tableName);
context.put("classLowercaseName", toCamelCase);
// 将类名称转成大驼峰
String convertToCamelCase = TypeConvertUtil.convertToCamelCase(replaceTableName, true);
String convertToCamelCase = TypeConvertUtil.convertToCamelCase(tableName, true);
context.put("classUppercaseName", convertToCamelCase);
}
@ -78,7 +71,6 @@ public class VmsArgumentDtoBaseVmsGeneratorTemplate extends AbstractVmsGenerator
*/
@Override
void templateMerge(VelocityContext context, StringWriter writer) {
// Velocity 生成模板
Template servicePathTemplate = Velocity.getTemplate("vms/" + path, "UTF-8");
servicePathTemplate.merge(context, writer);
}

View File

@ -1,9 +1,8 @@
package cn.bunny.domain.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@ -19,31 +18,23 @@ import java.util.List;
public class VmsArgumentDto {
@Schema(name = "author", description = "作者名称")
String author = "Bunny";
String author;
@Schema(name = "packageName", description = "包名称")
String packageName = "cn.bunny.services";
String packageName;
@Schema(name = "requestMapping", description = "requestMapping 名称")
String requestMapping = "/api";
String requestMapping;
@NotBlank(message = "类名称不能为空")
@NotNull(message = "类名称不能为空")
@Pattern(regexp = "^(?:[a-z][a-z0-9_]*|[_a-z][a-z0-9_]*)$", message = "类名称不合法")
@Schema(name = "className", description = "类名称格式为xxx xxx_xxx")
private String className;
@NotBlank(message = "表名称不能为空")
@NotNull(message = "表名称不能为空")
@Pattern(regexp = "^(?:[a-z][a-z0-9_]*|[_a-z][a-z0-9_]*)$", message = "表名称不合法")
@Schema(name = "tableName", description = "表名称")
private String tableName;
@NotEmpty(message = "表名称不能为空")
private List<String> tableNames;
@Schema(name = "simpleDateFormat", description = "时间格式")
private String simpleDateFormat = "yyyy-MM-dd HH:mm:ss";
private String simpleDateFormat;
@Schema(name = "tablePrefixes", description = "去除表前缀")
private String tablePrefixes = "t_,sys_,qrtz_,log_";
private String tablePrefixes;
@Schema(name = "comment", description = "注释内容")
private String comment;

View File

@ -75,6 +75,7 @@ public class GlobalExceptionHandler {
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);
}

View File

@ -16,7 +16,7 @@ public interface VmsService {
* @param dto VmsArgumentDto
* @return 生成内容
*/
List<GeneratorVo> generator(VmsArgumentDto dto);
Map<String, List<GeneratorVo>> generator(VmsArgumentDto dto);
/**
* 获取vms文件路径

View File

@ -1,14 +1,11 @@
package cn.bunny.service.impl;
import cn.bunny.core.factory.ConcreteDatabaseInfo;
import cn.bunny.core.factory.ConcreteSqlParserDatabaseInfo;
import cn.bunny.core.template.VmsArgumentDtoBaseVmsGeneratorTemplate;
import cn.bunny.domain.dto.VmsArgumentDto;
import cn.bunny.domain.entity.ColumnMetaData;
import cn.bunny.domain.entity.TableMetaData;
import cn.bunny.domain.vo.GeneratorVo;
import cn.bunny.domain.vo.VmsPathVo;
import cn.bunny.service.VmsService;
import cn.bunny.service.impl.vms.VmsCodeGeneratorService;
import cn.bunny.service.impl.vms.VmsZipService;
import cn.bunny.utils.ResourceFileUtil;
import cn.bunny.utils.VmsUtil;
import cn.hutool.crypto.digest.MD5;
@ -18,83 +15,38 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* VMS服务主实现类负责协调各子服务完成代码生成资源管理和打包下载功能
*/
@Service
@RequiredArgsConstructor
public class VmsServiceImpl implements VmsService {
private final ConcreteDatabaseInfo databaseInfoCore;
private final ConcreteSqlParserDatabaseInfo sqlParserDatabaseInfo;
private final VmsCodeGeneratorService codeGeneratorService;
private final VmsZipService zipService;
/**
* 生成服务端代码
*
* @param dto VmsArgumentDto
* @return 生成内容
*/
@Override
public List<GeneratorVo> generator(VmsArgumentDto dto) {
String tableName = dto.getTableName();
String sql = dto.getSql();
// 表格属性名 列信息
TableMetaData tableMetaData = StringUtils.hasText(dto.getSql())
? sqlParserDatabaseInfo.getTableMetadata(dto.getSql())
: databaseInfoCore.getTableMetadata(dto.getTableName());
List<ColumnMetaData> columnInfoList = StringUtils.hasText(sql)
? sqlParserDatabaseInfo.tableColumnInfo(sql)
: databaseInfoCore.tableColumnInfo(tableName).stream().distinct().toList();
return dto.getPath().stream().map(path -> {
// 生成模板
VmsArgumentDtoBaseVmsGeneratorTemplate vmsArgumentDtoBaseVmsGenerator = new VmsArgumentDtoBaseVmsGeneratorTemplate(dto, path);
StringWriter writer = vmsArgumentDtoBaseVmsGenerator.generatorCodeTemplate(tableMetaData, columnInfoList);
// 处理 vm 文件名
path = VmsUtil.handleVmFilename(path, dto.getClassName());
return GeneratorVo.builder()
.code(writer.toString())
.comment(tableMetaData.getComment())
.tableName(tableMetaData.getTableName())
.path(path)
.build();
}).toList();
public Map<String, List<GeneratorVo>> generator(VmsArgumentDto dto) {
return codeGeneratorService.generateCode(dto);
}
/**
* 获取vms文件路径
*
* @return vms下的文件路径
*/
@SneakyThrows
@Override
public Map<String, List<VmsPathVo>> vmsResourcePathList() {
// 读取当前项目中所有的 vm 模板发给前端
List<String> vmsRelativeFiles = ResourceFileUtil.getRelativeFiles("vms");
return vmsRelativeFiles.stream().map(vmFile -> {
return vmsRelativeFiles.stream()
.map(vmFile -> {
String[] filepathList = vmFile.split("/");
String filename = filepathList[filepathList.length - 1].replace(".vm", "");
// 生成标签上的id
String id = VmsUtil.generateDivId();
return VmsPathVo.builder()
.id(id)
.id(VmsUtil.generateDivId())
.name(vmFile)
.label(filename)
.type(filepathList[0])
@ -103,56 +55,22 @@ public class VmsServiceImpl implements VmsService {
.collect(Collectors.groupingBy(VmsPathVo::getType));
}
/**
* 打包成zip下载
*
* @param dto VmsArgumentDto
* @return zip 文件
*/
@Override
public ResponseEntity<byte[]> downloadByZip(VmsArgumentDto dto) {
// 需要下载的数据
List<GeneratorVo> generatorVoList = generator(dto);
byte[] zipBytes = zipService.createZipFile(dto);
// 1. 创建临时ZIP文件
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try (ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
// 2. 遍历并创建
generatorVoList.forEach(generatorVo -> {
// zip中的路径
String path = generatorVo.getPath().replace(".vm", "");
// zip中的文件
String code = generatorVo.getCode();
ZipEntry zipEntry = new ZipEntry(path);
try {
// 如果有 / 会转成文件夹
zipOutputStream.putNextEntry(zipEntry);
// 写入文件
zipOutputStream.write(code.getBytes(StandardCharsets.UTF_8));
zipOutputStream.closeEntry();
} catch (IOException e) {
throw new RuntimeException(e);
}
});
} catch (IOException e) {
throw new RuntimeException(e);
}
// 2.1 文件不重名
// 下载文件名称
long currentTimeMillis = System.currentTimeMillis();
String digestHex = MD5.create().digestHex(currentTimeMillis + "");
String generateZipFilename = digestHex + ".zip";
// 3. 准备响应
// 设置响应头
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition", "attachment; filename=" + "vms-" + digestHex + ".zip");
headers.add("Content-Disposition", "attachment; filename=" + generateZipFilename);
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
headers.add("Pragma", "no-cache");
headers.add("Expires", "0");
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
return new ResponseEntity<>(byteArrayInputStream.readAllBytes(), headers, HttpStatus.OK);
return new ResponseEntity<>(zipBytes, headers, HttpStatus.OK);
}
}

View File

@ -0,0 +1,91 @@
package cn.bunny.service.impl.vms;
import cn.bunny.core.factory.ConcreteDatabaseInfo;
import cn.bunny.core.factory.ConcreteSqlParserDatabaseInfo;
import cn.bunny.core.template.VmsArgumentDtoBaseVmsGeneratorTemplate;
import cn.bunny.domain.dto.VmsArgumentDto;
import cn.bunny.domain.entity.ColumnMetaData;
import cn.bunny.domain.entity.TableMetaData;
import cn.bunny.domain.vo.GeneratorVo;
import cn.bunny.utils.VmsUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.io.StringWriter;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* 负责处理代码生成逻辑的服务类
*/
@Service
@RequiredArgsConstructor
public class VmsCodeGeneratorService {
private final ConcreteDatabaseInfo databaseInfoCore;
private final ConcreteSqlParserDatabaseInfo sqlParserDatabaseInfo;
/**
* 根据DTO生成代码模板
*
* @param dto 包含生成参数的数据传输对象
* @return 生成的代码模板列表
*/
public Map<String, List<GeneratorVo>> generateCode(VmsArgumentDto dto) {
String sql = dto.getSql();
return dto.getTableNames().stream()
.map(tableName -> {
TableMetaData tableMetaData = getTableMetadata(dto, tableName);
List<ColumnMetaData> columnInfoList = getColumnInfoList(sql, tableName);
return dto.getPath().stream()
.map(path -> {
VmsArgumentDtoBaseVmsGeneratorTemplate generator = new VmsArgumentDtoBaseVmsGeneratorTemplate(dto, path, tableName);
StringWriter writer = generator.generatorCodeTemplate(tableMetaData, columnInfoList);
String processedPath = VmsUtil.handleVmFilename(path, tableMetaData.getTableName());
return GeneratorVo.builder()
.id(UUID.randomUUID().toString())
.code(writer.toString())
.comment(tableMetaData.getComment())
.tableName(tableMetaData.getTableName())
.path(processedPath)
.build();
})
.toList();
})
.flatMap(List::stream)
.collect(Collectors.groupingBy(GeneratorVo::getTableName));
}
/**
* 获取表元数据
*
* @param dto 生成参数
* @param tableName 表名
* @return 表元数据对象
*/
private TableMetaData getTableMetadata(VmsArgumentDto dto, String tableName) {
return StringUtils.hasText(dto.getSql())
? sqlParserDatabaseInfo.getTableMetadata(dto.getSql())
: databaseInfoCore.getTableMetadata(tableName);
}
/**
* 获取列信息列表
*
* @param sql SQL语句
* @param tableName 表名
* @return 列元数据列表
*/
private List<ColumnMetaData> getColumnInfoList(String sql, String tableName) {
return StringUtils.hasText(sql)
? sqlParserDatabaseInfo.tableColumnInfo(sql)
: databaseInfoCore.tableColumnInfo(tableName).stream().distinct().toList();
}
}

View File

@ -0,0 +1,62 @@
package cn.bunny.service.impl.vms;
import cn.bunny.domain.dto.VmsArgumentDto;
import cn.bunny.domain.vo.GeneratorVo;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* 负责处理ZIP打包和下载逻辑的服务类
*/
@Service
@RequiredArgsConstructor
public class VmsZipService {
private final VmsCodeGeneratorService codeGeneratorService;
/**
* 创建ZIP文件字节数组
*
* @param dto 生成参数
* @return ZIP文件字节数组
*/
public byte[] createZipFile(VmsArgumentDto dto) {
List<GeneratorVo> generatorVoList = codeGeneratorService.generateCode(dto).values().stream().flatMap(Collection::stream).toList();
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
generatorVoList.forEach(generatorVo -> addToZip(zipOutputStream, generatorVo));
return byteArrayOutputStream.toByteArray();
} catch (IOException e) {
throw new RuntimeException("Failed to create ZIP file", e);
}
}
/**
* 将单个生成结果添加到ZIP文件
*
* @param zipOutputStream ZIP输出流
* @param generatorVo 生成结果对象
*/
private void addToZip(ZipOutputStream zipOutputStream, GeneratorVo generatorVo) {
try {
String path = generatorVo.getPath().replace(".vm", "");
ZipEntry zipEntry = new ZipEntry(path);
zipOutputStream.putNextEntry(zipEntry);
zipOutputStream.write(generatorVo.getCode().getBytes(StandardCharsets.UTF_8));
zipOutputStream.closeEntry();
} catch (IOException e) {
throw new RuntimeException("Failed to add file to ZIP: " + generatorVo.getPath(), e);
}
}
}