This commit is contained in:
jingna 2025-07-02 10:16:11 +08:00
commit 033e7314fc
3 changed files with 189 additions and 58 deletions

View File

@ -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<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

View File

@ -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();
}
}

View File

@ -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">
<parent>
<artifactId>sdk</artifactId>
<groupId>io.gisbi</groupId>
<version>1.0.0</version>
<artifactId>sdk</artifactId>
<version>2.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>