diff --git a/core/core-backend/src/main/java/io/gisbi/application/appcode/controller/ProjectExportController.java b/core/core-backend/src/main/java/io/gisbi/application/appcode/controller/ProjectExportController.java index cef0b6f..0d27d59 100644 --- a/core/core-backend/src/main/java/io/gisbi/application/appcode/controller/ProjectExportController.java +++ b/core/core-backend/src/main/java/io/gisbi/application/appcode/controller/ProjectExportController.java @@ -25,73 +25,150 @@ public class ProjectExportController { // 模板 ZIP 文件路径 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_FULL_PATH= FIXED_ZIP_PATH + FIXED_ZIP_NAME; - + private static final String FIXED_ZIP_NAME = "backend.zip"; + private static final String FIXED_ZIP_FULL_PATH = FIXED_ZIP_PATH + FIXED_ZIP_NAME; // 自定义数据库服务类 @Autowired private DatabaseService databaseService; /** - * 接口:上传项目 ZIP 包,解压 → 导出数据库部分数据 → 插入 SQL 文件 → 重新打包并下载 + * 接口:导出项目 ZIP 包,插入数据库 SQL 文件后重新打包 */ @PostMapping("/export") - public void exportProjectWithDbData( - @RequestParam("id") String id) throws Exception { - - String fixedZipPath = FIXED_ZIP_FULL_PATH; - File fixedZipFile = new File(fixedZipPath); + public void exportProjectWithDbData(@RequestParam("id") String id) throws Exception { + // 检查原始 ZIP 是否存在 + File fixedZipFile = new File(FIXED_ZIP_FULL_PATH); 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")); - 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 { - Files.createDirectories(tempExtractDir); - - // 1. 解压模板 ZIP 到临时目录 - File uploadedZip = new File(tempExtractDir + ".zip"); - FileCopyUtils.copy(new FileInputStream(fixedZipFile), new FileOutputStream(uploadedZip)); - ZipUtils.unzip(uploadedZip, tempExtractDir.toFile()); - - // 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()); + // 1. 清空已存在的解压目录(避免旧数据干扰) + if (extractDir.exists()) { + try { + deleteDirectory(extractDir); + } catch (Exception e) { + throw new IOException("无法清理旧的解压目录: " + extractDir.getAbsolutePath(), e); } } + if (!extractDir.mkdirs()) { + throw new IOException("无法创建解压目录: " + extractDir.getAbsolutePath()); + } - // 4. 查询数据库并生成 SQL 文件,保存到 db 目录中 - generateSqlFile(id, new File(dbDir, "data.sql").getAbsolutePath()); + // 2. 直接解压 ZIP 到目标目录 + ZipUtils.unzip(fixedZipFile, extractDir); - // 5. 重新打包整个项目根目录为新的 ZIP 文件 - String outputZipName = FIXED_ZIP_NAME.replaceFirst("\\..*$", "") + - "-with-sql-" + timestamp + ".zip"; - File finalZip = new File(FIXED_ZIP_PATH + "export/", outputZipName); - ZipUtils.zipFolder(projectRoot, finalZip); - }catch (Exception e){ + //TODO 3. 查找 db 目录并生成 SQL 文件 + createAndGenerateSqlFile(extractDir, id); + + //TODO 4. 给后端配置文件加上必要的配置 + modifyBackendConfigFiles(extractDir); + + //TODO 5. 生成前端vue文件 + generateFrontendVueFiles(extractDir); + + // 6. 重新打包整个解压后的目录内容 + ZipUtils.zipDirectoryContents(extractDir, finalZip); + + } catch (Exception e) { e.printStackTrace(); - }finally { - // 7. 清理临时文件(可选) - deleteDirectory(tempExtractDir.toFile()); + throw new RuntimeException("导出项目出错"); + } finally { + // 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 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(""); + writer.newLine(); + writer.write(""); + } + } /** * 根据 id 查询数据库,并生成 SQL 文件(CREATE TABLE + INSERT INTO) diff --git a/core/core-backend/src/main/java/io/gisbi/application/appcode/utils/ZipUtils.java b/core/core-backend/src/main/java/io/gisbi/application/appcode/utils/ZipUtils.java index 7e4fd98..19ffc5c 100644 --- a/core/core-backend/src/main/java/io/gisbi/application/appcode/utils/ZipUtils.java +++ b/core/core-backend/src/main/java/io/gisbi/application/appcode/utils/ZipUtils.java @@ -1,43 +1,84 @@ package io.gisbi.application.appcode.utils; -import org.springframework.util.FileCopyUtils; - import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; -import java.nio.file.Files; +import java.util.Objects; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; 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))) { ZipEntry entry; while ((entry = zis.getNextEntry()) != null) { - File file = new File(outputFolder, entry.getName()); + File entryDestination = new File(destDir, entry.getName()); if (entry.isDirectory()) { - Files.createDirectories(file.toPath()); + entryDestination.mkdirs(); } else { - Files.createDirectories(file.getParentFile().toPath()); - FileCopyUtils.copy(zis, new FileOutputStream(file)); + entryDestination.getParentFile().mkdirs(); + 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(); } } } + private static String sanitizePath(String path) { + // 替换非法字符 + return path.replaceAll("[\0<>:\"/\\|?*]", "_"); + } + public static void zipFolder(File folder, File zipFile) throws IOException { try ( - FileOutputStream fos = new FileOutputStream(zipFile); - ZipOutputStream zos = new ZipOutputStream(fos) + FileOutputStream fos = new FileOutputStream(zipFile); + ZipOutputStream zos = new ZipOutputStream(fos) ) { 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 { if (fileToZip.isDirectory()) { if (fileName.endsWith("/")) { @@ -67,4 +108,17 @@ public class ZipUtils { 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(); + } + } diff --git a/sdk/sdk-bundle/pom.xml b/sdk/sdk-bundle/pom.xml index 4b4ffdc..7df8a60 100644 --- a/sdk/sdk-bundle/pom.xml +++ b/sdk/sdk-bundle/pom.xml @@ -3,9 +3,9 @@ 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"> - sdk io.gisbi - 1.0.0 + sdk + 2.0.0 4.0.0 @@ -18,7 +18,7 @@ common 2.0.0 - + io.gisbi @@ -67,4 +67,4 @@ - \ No newline at end of file +