fix: 优化导出功能

优化导出项目框架
This commit is contained in:
weitang 2025-07-02 09:31:53 +08:00
parent 3a4bc50d2b
commit da89e012d4
3 changed files with 189 additions and 58 deletions

View File

@ -25,73 +25,150 @@ public class ProjectExportController {
// 模板 ZIP 文件路径 // 模板 ZIP 文件路径
private static final String FIXED_ZIP_PATH = "E:/opt/gisbi2.0/"; private static final String FIXED_ZIP_PATH = "E:/opt/gisbi2.0/";
private static final String FIXED_ZIP_NAME = "a.zip"; private static final String FIXED_ZIP_NAME = "backend.zip";
private static final String FIXED_ZIP_FULL_PATH= FIXED_ZIP_PATH + FIXED_ZIP_NAME;
private static final String FIXED_ZIP_FULL_PATH = FIXED_ZIP_PATH + FIXED_ZIP_NAME;
// 自定义数据库服务类 // 自定义数据库服务类
@Autowired @Autowired
private DatabaseService databaseService; private DatabaseService databaseService;
/** /**
* 接口上传项目 ZIP 解压 导出数据库部分数据 插入 SQL 文件 重新打包并下载 * 接口导出项目 ZIP 插入数据库 SQL 文件后重新打包
*/ */
@PostMapping("/export") @PostMapping("/export")
public void exportProjectWithDbData( public void exportProjectWithDbData(@RequestParam("id") String id) throws Exception {
@RequestParam("id") String id) throws Exception { // 检查原始 ZIP 是否存在
File fixedZipFile = new File(FIXED_ZIP_FULL_PATH);
String fixedZipPath = FIXED_ZIP_FULL_PATH;
File fixedZipFile = new File(fixedZipPath);
if (!fixedZipFile.exists()) { if (!fixedZipFile.exists()) {
throw new FileNotFoundException("指定的 ZIP 文件不存在: " + fixedZipPath); throw new FileNotFoundException("指定的 ZIP 文件不存在: " + FIXED_ZIP_FULL_PATH);
} }
// 创建时间戳和临时目录路径 // 创建时间戳和输出 ZIP 名称
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
Path tempExtractDir = Paths.get(FIXED_ZIP_PATH, "temp", "project-" + timestamp); String outputZipName = FIXED_ZIP_NAME.replaceFirst("\\..*$", "") +
"-with-sql-" + timestamp + ".zip";
// 定义解压目标路径和最终 ZIP 输出路径
// File extractDir = new File(FIXED_ZIP_PATH, "extracted-project");
String extractTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
File extractDir = new File(FIXED_ZIP_PATH, "extracted-project-" + extractTime);
File finalZip = new File(FIXED_ZIP_PATH + "export/", outputZipName);
try { try {
Files.createDirectories(tempExtractDir); // 1. 清空已存在的解压目录避免旧数据干扰
if (extractDir.exists()) {
// 1. 解压模板 ZIP 到临时目录 try {
File uploadedZip = new File(tempExtractDir + ".zip"); deleteDirectory(extractDir);
FileCopyUtils.copy(new FileInputStream(fixedZipFile), new FileOutputStream(uploadedZip)); } catch (Exception e) {
ZipUtils.unzip(uploadedZip, tempExtractDir.toFile()); throw new IOException("无法清理旧的解压目录: " + extractDir.getAbsolutePath(), e);
// 2. 获取解压后的根目录自动识别第一个子目录
File[] extractedRoots = tempExtractDir.toFile().listFiles(File::isDirectory);
if (extractedRoots == null || extractedRoots.length == 0) {
throw new IOException("ZIP 解压后未找到项目根目录");
}
File projectRoot = extractedRoots[0];
// 3. 获取 db 目录
File dbDir = new File(projectRoot, "db");
if (!dbDir.exists()) {
boolean created = dbDir.mkdirs();
if (!created) {
throw new IOException("无法创建 db 目录: " + dbDir.getAbsolutePath());
} }
} }
if (!extractDir.mkdirs()) {
throw new IOException("无法创建解压目录: " + extractDir.getAbsolutePath());
}
// 4. 查询数据库并生成 SQL 文件保存到 db 目录中 // 2. 直接解压 ZIP 到目标目录
generateSqlFile(id, new File(dbDir, "data.sql").getAbsolutePath()); ZipUtils.unzip(fixedZipFile, extractDir);
// 5. 重新打包整个项目根目录为新的 ZIP 文件 //TODO 3. 查找 db 目录并生成 SQL 文件
String outputZipName = FIXED_ZIP_NAME.replaceFirst("\\..*$", "") + createAndGenerateSqlFile(extractDir, id);
"-with-sql-" + timestamp + ".zip";
File finalZip = new File(FIXED_ZIP_PATH + "export/", outputZipName); //TODO 4. 给后端配置文件加上必要的配置
ZipUtils.zipFolder(projectRoot, finalZip); modifyBackendConfigFiles(extractDir);
}catch (Exception e){
//TODO 5. 生成前端vue文件
generateFrontendVueFiles(extractDir);
// 6. 重新打包整个解压后的目录内容
ZipUtils.zipDirectoryContents(extractDir, finalZip);
} catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
}finally { throw new RuntimeException("导出项目出错");
// 7. 清理临时文件可选 } finally {
deleteDirectory(tempExtractDir.toFile()); // 7.删除解压目录
deleteDirectory(extractDir);
} }
} }
/**
* 创建 db 目录并生成 data.sql 文件
*/
private void createAndGenerateSqlFile(File extractDir, String id) throws IOException {
File dbDir = new File(extractDir, "backend/db");
if (!dbDir.exists() && !dbDir.mkdirs()) {
throw new IOException("无法创建 db 目录: " + dbDir.getAbsolutePath());
}
String sqlFilePath = new File(dbDir, "data.sql").getAbsolutePath();
generateSqlFile(id, sqlFilePath);
}
/**
* 修改后端配置文件支持嵌套 YAML 结构
*/
private void modifyBackendConfigFiles(File extractDir) throws IOException {
File configPath = new File(extractDir, "backend/src/main/resources/application.yml");
if (!configPath.exists()) {
throw new FileNotFoundException("配置文件不存在: " + configPath.getAbsolutePath());
}
List<String> lines = Files.readAllLines(configPath.toPath());
boolean inServerBlock = false;
for (int i = 0; i < lines.size(); i++) {
String line = lines.get(i);
// 判断是否进入 server
if (line.trim().startsWith("server:")) {
inServerBlock = true;
continue;
}
// 如果在 server 块中并且遇到下一个顶级 key则退出 block
if (inServerBlock && line.trim().contains(":") && !line.startsWith(" ") && !line.startsWith("\t")) {
break;
}
// 修改 port 字段
if (inServerBlock && line.trim().startsWith("port:")) {
// 获取原缩进
int indent = line.indexOf("port:");
String newLine = " ".repeat(indent) + "port: 8081";
lines.set(i, newLine);
break; // 找到并修改后退出
}
}
// 写回文件
Files.write(configPath.toPath(), lines);
}
/**
* 在前端目录下生成一个 Vue 文件
*/
private void generateFrontendVueFiles(File extractDir) throws IOException {
File vueDir = new File(extractDir, "frontend/src/views/generated");
if (!vueDir.exists() && !vueDir.mkdirs()) {
throw new IOException("无法创建 Vue 文件目录: " + vueDir.getAbsolutePath());
}
File vueFile = new File(vueDir, "GeneratedPage.vue");
try (BufferedWriter writer = new BufferedWriter(new FileWriter(vueFile))) {
writer.write("<template>");
writer.newLine();
writer.write(" <div>这是自动生成的页面</div>");
writer.newLine();
writer.write("</template>");
writer.newLine();
writer.write("<script>");
writer.newLine();
writer.write("export default { name: 'GeneratedPage' };");
writer.newLine();
writer.write("</script>");
}
}
/** /**
* 根据 id 查询数据库并生成 SQL 文件CREATE TABLE + INSERT INTO * 根据 id 查询数据库并生成 SQL 文件CREATE TABLE + INSERT INTO

View File

@ -1,43 +1,84 @@
package io.gisbi.application.appcode.utils; package io.gisbi.application.appcode.utils;
import org.springframework.util.FileCopyUtils;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.util.Objects;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
public class ZipUtils { public class ZipUtils {
public static void unzip(File zipFile, File outputFolder) throws IOException {
public static void unzip(File zipFile, File destDir) throws IOException {
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile))) { try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile))) {
ZipEntry entry; ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) { while ((entry = zis.getNextEntry()) != null) {
File file = new File(outputFolder, entry.getName()); File entryDestination = new File(destDir, entry.getName());
if (entry.isDirectory()) { if (entry.isDirectory()) {
Files.createDirectories(file.toPath()); entryDestination.mkdirs();
} else { } else {
Files.createDirectories(file.getParentFile().toPath()); entryDestination.getParentFile().mkdirs();
FileCopyUtils.copy(zis, new FileOutputStream(file)); try (FileOutputStream fos = new FileOutputStream(entryDestination)) {
byte[] buffer = new byte[8192];
int len;
while ((len = zis.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
}
} }
zis.closeEntry(); zis.closeEntry();
} }
} }
} }
private static String sanitizePath(String path) {
// 替换非法字符
return path.replaceAll("[\0<>:\"/\\|?*]", "_");
}
public static void zipFolder(File folder, File zipFile) throws IOException { public static void zipFolder(File folder, File zipFile) throws IOException {
try ( try (
FileOutputStream fos = new FileOutputStream(zipFile); FileOutputStream fos = new FileOutputStream(zipFile);
ZipOutputStream zos = new ZipOutputStream(fos) ZipOutputStream zos = new ZipOutputStream(fos)
) { ) {
zipFile(folder, folder.getName(), zos); zipFile(folder, folder.getName(), zos);
} }
} }
public static void zipDirectoryContents(File sourceDir, File outputZip) throws IOException {
try (FileOutputStream fos = new FileOutputStream(outputZip);
ZipOutputStream zos = new ZipOutputStream(fos)) {
for (File file : Objects.requireNonNull(sourceDir.listFiles())) {
addToZip(file, sourceDir, zos, "");
}
}
}
private static void addToZip(File file, File baseDir, ZipOutputStream zos, String parent) throws IOException {
String entryName = parent + file.getName();
if (file.isDirectory()) {
for (File child : file.listFiles()) {
addToZip(child, baseDir, zos, entryName + "/");
}
} else {
try (FileInputStream fis = new FileInputStream(file)) {
ZipEntry entry = new ZipEntry(entryName);
zos.putNextEntry(entry);
byte[] buffer = new byte[8192];
int len;
while ((len = fis.read(buffer)) > 0) {
zos.write(buffer, 0, len);
}
zos.closeEntry();
}
}
}
private static void zipFile(File fileToZip, String fileName, ZipOutputStream zos) throws IOException { private static void zipFile(File fileToZip, String fileName, ZipOutputStream zos) throws IOException {
if (fileToZip.isDirectory()) { if (fileToZip.isDirectory()) {
if (fileName.endsWith("/")) { if (fileName.endsWith("/")) {
@ -67,4 +108,17 @@ public class ZipUtils {
zos.closeEntry(); zos.closeEntry();
} }
} }
private void deleteDirectory(File file) {
if (file.isDirectory()) {
File[] files = file.listFiles();
if (files != null) {
for (File f : files) {
deleteDirectory(f);
}
}
}
file.delete();
}
} }

View File

@ -3,9 +3,9 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent> <parent>
<artifactId>sdk</artifactId>
<groupId>io.gisbi</groupId> <groupId>io.gisbi</groupId>
<version>1.0.0</version> <artifactId>sdk</artifactId>
<version>2.0.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>