fix: 优化导出功能

优化项目导出逻辑
This commit is contained in:
weitang 2025-07-03 08:50:19 +08:00
parent 8cfe070b9a
commit 18a5ab367b
3 changed files with 109 additions and 31 deletions

View File

@ -1,38 +1,46 @@
package io.gisbi.application.appcode.controller;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import io.gisbi.application.appcode.doman.TableData;
import io.gisbi.application.appcode.service.DatabaseService;
import io.gisbi.application.appcode.utils.ZipUtils;
import io.gisbi.application.module.domain.Module;
import io.gisbi.application.module.service.IModuleService;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/project")
public class ProjectExportController {
// 模板 ZIP 文件路径
private static final String FIXED_ZIP_PATH = "E:/opt/gisbi2.0/";
private static final String FIXED_ZIP_NAME = "backend.zip";
private static final String FIXED_ZIP_NAME = "stdproject.zip";
private static final String FIXED_ZIP_FULL_PATH = FIXED_ZIP_PATH + FIXED_ZIP_NAME;
// 自定义数据库服务类
@Autowired
private DatabaseService databaseService;
@Resource
private IModuleService moduleService;
/**
* 接口导出项目 ZIP 插入数据库 SQL 文件后重新打包
*/
@ -46,9 +54,7 @@ public class ProjectExportController {
// 创建时间戳和输出 ZIP 名称
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
String outputZipName = FIXED_ZIP_NAME.replaceFirst("\\..*$", "") +
"-with-sql-" + timestamp + ".zip";
String outputZipName = FIXED_ZIP_NAME.replaceFirst("\\..*$", "") + "_" + timestamp + ".zip";
// 定义解压目标路径和最终 ZIP 输出路径
// File extractDir = new File(FIXED_ZIP_PATH, "extracted-project");
String extractTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
@ -75,10 +81,11 @@ public class ProjectExportController {
createAndGenerateSqlFile(extractDir, id);
//TODO 4. 给后端配置文件加上必要的配置
modifyBackendConfigFiles(extractDir);
// modifyBackendConfigFiles(extractDir);
modifyDeepConfigWithYaml(extractDir);
//TODO 5. 生成前端vue文件
generateFrontendVueFiles(extractDir);
generateFrontendVueFiles(extractDir, id);
// 6. 重新打包整个解压后的目录内容
ZipUtils.zipDirectoryContents(extractDir, finalZip);
@ -96,7 +103,7 @@ public class ProjectExportController {
* 创建 db 目录并生成 data.sql 文件
*/
private void createAndGenerateSqlFile(File extractDir, String id) throws IOException {
File dbDir = new File(extractDir, "backend/db");
File dbDir = new File(extractDir, "stdproject/backend/db");
if (!dbDir.exists() && !dbDir.mkdirs()) {
throw new IOException("无法创建 db 目录: " + dbDir.getAbsolutePath());
}
@ -105,10 +112,74 @@ public class ProjectExportController {
}
/**
* 修改后端配置文件支持嵌套 YAML 结构
* 修改后端配置文件支持嵌套 YAML 结构 通过yaml格式修改
*/
private static void modifyDeepConfigWithYaml(File extractDir) throws IOException {
File configPath = new File(extractDir, "stdproject/backend/src/main/resources/application.yml");
if (!configPath.exists()) {
throw new FileNotFoundException("配置文件不存在: " + configPath.getAbsolutePath());
}
Yaml yaml = new Yaml();
List<Object> allDocuments = new ArrayList<>();
// 读取所有文档
try (InputStream in = new FileInputStream(configPath)) {
for (Object doc : yaml.loadAll(in)) {
allDocuments.add(doc);
}
}
if (allDocuments.isEmpty()) {
throw new IOException("配置文件内容为空");
}
// 获取第一个文档作为主配置
Object firstDoc = allDocuments.getFirst();
if (!(firstDoc instanceof Map)) {
throw new IOException("第一个文档不是 Map 类型,格式错误");
}
Map<String, Object> root = (Map<String, Object>) firstDoc;
// 修改 spring.datasource.url 字段
if (root.containsKey("spring")) {
Map<String, Object> spring = (Map<String, Object>) root.get("spring");
if (spring.containsKey("datasource")) {
Map<String, Object> datasource = (Map<String, Object>) spring.get("datasource");
datasource.put("url", "jdbc:mysql://localhost:3306/newdb");
datasource.put("username", "root");
datasource.put("password", "123456");
}
}
// 修改 server.port 字段
if (root.containsKey("server")) {
Map<String, Object> server = (Map<String, Object>) root.get("server");
server.put("port", 8081);
}
// 更新第一个文档
allDocuments.set(0, root);
// 写回 YAML 文件
DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
// 美化输出格式
options.setPrettyFlow(true);
// 缩进两个空格
options.setIndent(2);
Yaml newYaml = new Yaml(options);
try (Writer writer = new FileWriter(configPath)) {
newYaml.dump(root, writer);
}
}
/**
* 修改后端配置文件支持嵌套 YAML 结构直接根据字符串修改
*/
private void modifyBackendConfigFiles(File extractDir) throws IOException {
File configPath = new File(extractDir, "backend/src/main/resources/application.yml");
File configPath = new File(extractDir, "stdproject/backend/src/main/resources/application.yml");
if (!configPath.exists()) {
throw new FileNotFoundException("配置文件不存在: " + configPath.getAbsolutePath());
}
@ -147,27 +218,30 @@ public class ProjectExportController {
/**
* 在前端目录下生成一个 Vue 文件
*/
private void generateFrontendVueFiles(File extractDir) throws IOException {
File vueDir = new File(extractDir, "frontend/src/views/generated");
private void generateFrontendVueFiles(File extractDir, String id) throws IOException {
File vueDir = new File(extractDir, "stdproject/frontend/src/views/generated");
if (!vueDir.exists() && !vueDir.mkdirs()) {
throw new IOException("无法创建 Vue 文件目录: " + vueDir.getAbsolutePath());
}
List<Map<String, Object>> modules =
moduleService.listMaps(new LambdaQueryWrapper<Module>().eq(Module::getAppId, id).in(Module::getType,
"01", "02").isNotNull(Module::getCanvasStyleData).select(Module::getCanvasStyleData,
Module::getName));
String fileName = "GeneratedPage";
int i = 1;
for (Map<String, Object> module : modules) {
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>");
if (ObjectUtil.isEmpty(module.get("canvas_style_data"))) {
continue;
}
String canvasStyleData = module.get("canvas_style_data").toString();
File vueFile = new File(vueDir, fileName + i+".vue");
try (BufferedWriter writer = new BufferedWriter(new FileWriter(vueFile))) {
writer.write(canvasStyleData);
}
i++;
}
}
/**

View File

@ -298,6 +298,9 @@ public class DatabaseService {
// 对字符串进行转义
values.append(escapeSql(s));
case null -> values.append("NULL");
case Boolean b ->
// 将布尔值转换为 0 1
values.append(b ? 1 : 0);
case Number ignored -> values.append(value); // 数字不需要加引号
default ->
// 其他类型统一转为字符串处理

View File

@ -2,12 +2,13 @@ package io.gisbi.application.module.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
import lombok.Getter;
import lombok.Setter;
import org.apache.ibatis.type.JdbcType;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 应用_系统模块