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; 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.doman.TableData;
import io.gisbi.application.appcode.service.DatabaseService; import io.gisbi.application.appcode.service.DatabaseService;
import io.gisbi.application.appcode.utils.ZipUtils; 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.beans.factory.annotation.Autowired;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import java.io.*; import java.io.*;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
@RestController @RestController
@RequestMapping("/project") @RequestMapping("/project")
public class ProjectExportController { 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 = "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; private static final String FIXED_ZIP_FULL_PATH = FIXED_ZIP_PATH + FIXED_ZIP_NAME;
// 自定义数据库服务类 // 自定义数据库服务类
@Autowired @Autowired
private DatabaseService databaseService; private DatabaseService databaseService;
@Resource
private IModuleService moduleService;
/** /**
* 接口导出项目 ZIP 插入数据库 SQL 文件后重新打包 * 接口导出项目 ZIP 插入数据库 SQL 文件后重新打包
*/ */
@ -46,9 +54,7 @@ public class ProjectExportController {
// 创建时间戳和输出 ZIP 名称 // 创建时间戳和输出 ZIP 名称
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
String outputZipName = FIXED_ZIP_NAME.replaceFirst("\\..*$", "") + String outputZipName = FIXED_ZIP_NAME.replaceFirst("\\..*$", "") + "_" + timestamp + ".zip";
"-with-sql-" + timestamp + ".zip";
// 定义解压目标路径和最终 ZIP 输出路径 // 定义解压目标路径和最终 ZIP 输出路径
// File extractDir = new File(FIXED_ZIP_PATH, "extracted-project"); // File extractDir = new File(FIXED_ZIP_PATH, "extracted-project");
String extractTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); String extractTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
@ -75,10 +81,11 @@ public class ProjectExportController {
createAndGenerateSqlFile(extractDir, id); createAndGenerateSqlFile(extractDir, id);
//TODO 4. 给后端配置文件加上必要的配置 //TODO 4. 给后端配置文件加上必要的配置
modifyBackendConfigFiles(extractDir); // modifyBackendConfigFiles(extractDir);
modifyDeepConfigWithYaml(extractDir);
//TODO 5. 生成前端vue文件 //TODO 5. 生成前端vue文件
generateFrontendVueFiles(extractDir); generateFrontendVueFiles(extractDir, id);
// 6. 重新打包整个解压后的目录内容 // 6. 重新打包整个解压后的目录内容
ZipUtils.zipDirectoryContents(extractDir, finalZip); ZipUtils.zipDirectoryContents(extractDir, finalZip);
@ -96,7 +103,7 @@ public class ProjectExportController {
* 创建 db 目录并生成 data.sql 文件 * 创建 db 目录并生成 data.sql 文件
*/ */
private void createAndGenerateSqlFile(File extractDir, String id) throws IOException { 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()) { if (!dbDir.exists() && !dbDir.mkdirs()) {
throw new IOException("无法创建 db 目录: " + dbDir.getAbsolutePath()); 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 { 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()) { if (!configPath.exists()) {
throw new FileNotFoundException("配置文件不存在: " + configPath.getAbsolutePath()); throw new FileNotFoundException("配置文件不存在: " + configPath.getAbsolutePath());
} }
@ -147,27 +218,30 @@ public class ProjectExportController {
/** /**
* 在前端目录下生成一个 Vue 文件 * 在前端目录下生成一个 Vue 文件
*/ */
private void generateFrontendVueFiles(File extractDir) throws IOException { private void generateFrontendVueFiles(File extractDir, String id) throws IOException {
File vueDir = new File(extractDir, "frontend/src/views/generated"); File vueDir = new File(extractDir, "stdproject/frontend/src/views/generated");
if (!vueDir.exists() && !vueDir.mkdirs()) { if (!vueDir.exists() && !vueDir.mkdirs()) {
throw new IOException("无法创建 Vue 文件目录: " + vueDir.getAbsolutePath()); 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"); if (ObjectUtil.isEmpty(module.get("canvas_style_data"))) {
continue;
try (BufferedWriter writer = new BufferedWriter(new FileWriter(vueFile))) { }
writer.write("<template>"); String canvasStyleData = module.get("canvas_style_data").toString();
writer.newLine(); File vueFile = new File(vueDir, fileName + i+".vue");
writer.write(" <div>这是自动生成的页面</div>"); try (BufferedWriter writer = new BufferedWriter(new FileWriter(vueFile))) {
writer.newLine(); writer.write(canvasStyleData);
writer.write("</template>"); }
writer.newLine(); i++;
writer.write("<script>");
writer.newLine();
writer.write("export default { name: 'GeneratedPage' };");
writer.newLine();
writer.write("</script>");
} }
} }
/** /**

View File

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

View File

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