功能已稳定

This commit is contained in:
张家训
2026-06-19 18:26:52 +08:00
parent 0939c61c1d
commit dbc9902716
8 changed files with 195 additions and 64 deletions
+1 -1
View File
@@ -24,7 +24,7 @@
<!-- Maven 编译/测试启动时的 JVM 参数,确保 IDEA 内运行不乱码 --> <!-- Maven 编译/测试启动时的 JVM 参数,确保 IDEA 内运行不乱码 -->
<argLine>-Dfile.encoding=UTF-8</argLine> <argLine>-Dfile.encoding=UTF-8</argLine>
<!-- OpenJFX 版本 --> <!-- OpenJFX 版本 -->
<javafx.version>17.0.14</javafx.version> <javafx.version>17.0.15</javafx.version>
<!-- AtlantaFX 版本 --> <!-- AtlantaFX 版本 -->
<atlantafx.version>2.0.1</atlantafx.version> <atlantafx.version>2.0.1</atlantafx.version>
<!-- 指定 JDK 17 路径(系统默认 JDK 8 时启用) --> <!-- 指定 JDK 17 路径(系统默认 JDK 8 时启用) -->
+10
View File
@@ -110,6 +110,16 @@ if %errorlevel% neq 0 (
pause pause
exit /b 1 exit /b 1
) )
::: 4.5 补充 JavaFX 原生 DLL(jlink 不会自动从 SDK bin/ 复制)
echo Copy JavaFX native DLLs...
if exist "%JFX_SDK_HOME%\bin\glass.dll" (
xcopy /y /q "%JFX_SDK_HOME%\bin\*.dll" "%TARGET%\jre\bin\" >nul
echo [OK] JavaFX DLLs copied
) else (
echo [WARN] No JavaFX DLLs found in %JFX_SDK_HOME%\bin
)
echo [OK] JRE generated : %TARGET%\jre echo [OK] JRE generated : %TARGET%\jre
::: ============================================================ ::: ============================================================
@@ -70,6 +70,8 @@ public class GitSyncPlugin implements Plugin, InitializingBean {
private List<DamagedRepo> lastDamagedRepos; private List<DamagedRepo> lastDamagedRepos;
/** 缓存上一次使用的目标分支 */ /** 缓存上一次使用的目标分支 */
private String lastBranch; private String lastBranch;
/** 同步取消标记(供 GUI 终止按钮使用) */
private volatile boolean syncCancelled = false;
// ==================== 启动时从日志恢复缓存 ==================== // ==================== 启动时从日志恢复缓存 ====================
@@ -257,11 +259,7 @@ public class GitSyncPlugin implements Plugin, InitializingBean {
Map<String, String> baseUrlMap = new LinkedHashMap<>(); Map<String, String> baseUrlMap = new LinkedHashMap<>();
for (FileOperationHelper.ProjectPathInfo proj : projects) { for (FileOperationHelper.ProjectPathInfo proj : projects) {
if (proj.getRemoteBaseUrl() != null && !proj.getRemoteBaseUrl().isEmpty()) { if (proj.getRemoteBaseUrl() != null && !proj.getRemoteBaseUrl().isEmpty()) {
String url = proj.getRemoteBaseUrl(); baseUrlMap.put(normalizePath(proj.getPath()), proj.getRemoteBaseUrl());
if (!url.endsWith("/")) {
url = url + "/";
}
baseUrlMap.put(normalizePath(proj.getPath()), url);
} }
} }
log.info("从项目路径文件加载了 {} 条远程URL映射", baseUrlMap.size()); log.info("从项目路径文件加载了 {} 条远程URL映射", baseUrlMap.size());
@@ -290,7 +288,7 @@ public class GitSyncPlugin implements Plugin, InitializingBean {
int done = completed.incrementAndGet(); int done = completed.incrementAndGet();
String tag; String tag;
switch (result) { switch (result) {
case OK: tag = "成功"; break; case OK: tag = result.cloned ? "拉取" : "成功"; break;
case DAMAGED: tag = "损坏"; break; case DAMAGED: tag = "损坏"; break;
default: tag = "失败"; break; default: tag = "失败"; break;
} }
@@ -485,11 +483,7 @@ public class GitSyncPlugin implements Plugin, InitializingBean {
List<FileOperationHelper.ProjectPathInfo> projects = fileOperationHelper.getProjectPathsWithStatus(); List<FileOperationHelper.ProjectPathInfo> projects = fileOperationHelper.getProjectPathsWithStatus();
for (FileOperationHelper.ProjectPathInfo proj : projects) { for (FileOperationHelper.ProjectPathInfo proj : projects) {
if (proj.getRemoteBaseUrl() != null && !proj.getRemoteBaseUrl().isEmpty()) { if (proj.getRemoteBaseUrl() != null && !proj.getRemoteBaseUrl().isEmpty()) {
String url = proj.getRemoteBaseUrl(); map.put(normalizePath(proj.getPath()), proj.getRemoteBaseUrl());
if (!url.endsWith("/")) {
url = url + "/";
}
map.put(normalizePath(proj.getPath()), url);
} }
} }
} catch (IOException e) { } catch (IOException e) {
@@ -498,15 +492,14 @@ public class GitSyncPlugin implements Plugin, InitializingBean {
return map; return map;
} }
/** 解析 clone URL:优先从项目路径文件的 BASE_URL 拼接,其次用缓存的 remoteUrl */ /** 解析 clone URL:BASE_URL 本身就是完整的 clone 地址,直接返回 */
private String resolveCloneUrl(DamagedRepo dr, Map<String, String> baseUrlMap) { private String resolveCloneUrl(DamagedRepo dr, Map<String, String> baseUrlMap) {
// 1. 从项目路径文件的 BASE_URL 拼接(项目路径文件为准) // 1. 从项目路径文件的 BASE_URL(已是完整 clone URL,如 https://user@host/path/repo.git)
if (baseUrlMap != null) { if (baseUrlMap != null) {
String normalizedPath = normalizePath(dr.path); String normalizedPath = normalizePath(dr.path);
String baseUrl = baseUrlMap.get(normalizedPath); String baseUrl = baseUrlMap.get(normalizedPath);
if (baseUrl != null) { if (baseUrl != null) {
String repoName = extractName(dr.path); return baseUrl;
return baseUrl + repoName;
} }
} }
// 2. 兜底:缓存的 remoteUrl // 2. 兜底:缓存的 remoteUrl
@@ -520,9 +513,31 @@ public class GitSyncPlugin implements Plugin, InitializingBean {
private SyncResult syncOneRepo(String repoPath, String branch, Map<String, String> baseUrlMap) { private SyncResult syncOneRepo(String repoPath, String branch, Map<String, String> baseUrlMap) {
File repoDir = new File(repoPath); File repoDir = new File(repoPath);
if (!repoDir.exists() || !new File(repoDir, ".git").exists()) {
// 目录不存在 或 空目录 → 自动 clone
if (!repoDir.exists() || (repoDir.isDirectory() && isEmptyDir(repoDir))) {
String reason = !repoDir.exists() ? "目录不存在" : "目录为空";
log.info("{},自动 clone: {}", reason, repoPath);
String cloneUrl = resolveSimpleCloneUrl(repoPath, baseUrlMap);
if (cloneUrl != null) {
if (doClone(repoPath, branch, cloneUrl)) {
SyncResult r = SyncResult.OK;
r.output = "auto-cloned";
r.cloned = true;
return r;
}
log.warn("自动 clone 失败: {} -> {}", repoPath, "exit code != 0");
}
SyncResult r = SyncResult.DAMAGED; SyncResult r = SyncResult.DAMAGED;
r.error = "目录不存在或无 .git"; r.error = reason + ",自动 clone 失败(远程URL: " + (cloneUrl != null ? cloneUrl : "未知") + ")";
r.remoteUrl = cloneUrl;
return r;
}
// 目录存在但无 .git → 仓库损坏
if (!new File(repoDir, ".git").exists()) {
SyncResult r = SyncResult.DAMAGED;
r.error = "目录存在但无 .git(仓库损坏)";
r.remoteUrl = getRemoteUrl(repoPath, baseUrlMap); r.remoteUrl = getRemoteUrl(repoPath, baseUrlMap);
return r; return r;
} }
@@ -583,40 +598,49 @@ public class GitSyncPlugin implements Plugin, InitializingBean {
return result; return result;
} }
// ==================== 重拉(clone)逻辑 ==================== // ==================== clone 逻辑 ====================
private boolean doReclone(DamagedRepo dr, String cloneUrl) { /** 执行 git clone(删除旧目录后克隆),供 auto-clone 和手动重拉共用 */
String repoPath = dr.path; private boolean doClone(String repoPath, String branch, String cloneUrl) {
String branch = dr.branch;
// 删除旧目录
File dir = new File(repoPath); File dir = new File(repoPath);
// 确保父目录存在
if (dir.getParentFile() != null && !dir.getParentFile().exists()) {
dir.getParentFile().mkdirs();
}
// 删除旧目录(如果存在)
if (dir.exists()) { if (dir.exists()) {
try { try {
deleteRecursive(dir.toPath()); deleteRecursive(dir.toPath());
log.info("已删除仓库: {}", repoPath); log.info("已删除仓库: {}", repoPath);
System.out.println(" 已删除旧目录");
} catch (IOException e) { } catch (IOException e) {
System.err.println(" 删除失败: " + e.getMessage()); log.error("删除失败: {}", repoPath, e);
return false; return false;
} }
} }
// clone // clone
String parentDir = dir.getParent(); String parentDir = dir.getParent() != null ? dir.getParent() : ".";
System.out.println(" clone URL: " + cloneUrl);
CmdResult cr = runGit(parentDir, CmdResult cr = runGit(parentDir,
"git clone -b " + branch + " " + cloneUrl + " \"" + repoPath + "\""); "git clone -b " + branch + " " + cloneUrl + " \"" + repoPath + "\"");
if (cr.ok) { if (cr.ok) {
System.out.println(" clone 成功");
return true; return true;
} else { } else {
System.err.println(" clone 失败: " + cr.error); log.error("clone 失败: {} -> {}", cloneUrl, cr.error);
return false; return false;
} }
} }
/** 手动重拉(供 DamagedRepo 场景使用) */
private boolean doReclone(DamagedRepo dr, String cloneUrl) {
System.out.println(" clone URL: " + cloneUrl);
boolean ok = doClone(dr.path, dr.branch, cloneUrl);
if (ok) {
System.out.println(" clone 成功");
} else {
System.err.println(" clone 失败");
}
return ok;
}
// ==================== Git 命令执行 ==================== // ==================== Git 命令执行 ====================
/** /**
@@ -721,16 +745,13 @@ public class GitSyncPlugin implements Plugin, InitializingBean {
} catch (IOException ignored) { } catch (IOException ignored) {
} }
} }
// 3. 兜底: 从项目路径文件中的 BASE_URL 拼接完整 clone URL // 3. 兜底: 项目路径文件中的 BASE_URL 即完整的 clone URL
// 格式: 项目路径文件 key=仓库路径, value=BASE_URL; clone URL = BASE_URL + cf/ + 仓库名
if (baseUrlMap != null) { if (baseUrlMap != null) {
String normalizedPath = normalizePath(repoPath); String normalizedPath = normalizePath(repoPath);
String baseUrl = baseUrlMap.get(normalizedPath); String baseUrl = baseUrlMap.get(normalizedPath);
if (baseUrl != null) { if (baseUrl != null) {
String repoName = extractName(repoPath); log.info("从项目路径文件获取远程URL: {} -> {}", repoPath, baseUrl);
String fullUrl = baseUrl + repoName; return baseUrl;
log.info("从项目路径文件拼接远程URL: {} -> {}", repoPath, fullUrl);
return fullUrl;
} }
} }
return null; return null;
@@ -766,6 +787,19 @@ public class GitSyncPlugin implements Plugin, InitializingBean {
return idx >= 0 ? path.substring(idx + 1) : path; return idx >= 0 ? path.substring(idx + 1) : path;
} }
/** 判断目录是否为空 */
private boolean isEmptyDir(File dir) {
if (!dir.isDirectory()) return false;
String[] files = dir.list();
return files != null && files.length == 0;
}
/** 从 baseUrlMap 获取 clone URL(不拼仓库名,BASE_URL 即完整 clone URL) */
private String resolveSimpleCloneUrl(String repoPath, Map<String, String> baseUrlMap) {
if (baseUrlMap == null) return null;
return baseUrlMap.get(normalizePath(repoPath));
}
/** 路径规范化:统一用 / 分隔、小写,用于映射表匹配 */ /** 路径规范化:统一用 / 分隔、小写,用于映射表匹配 */
private String normalizePath(String path) { private String normalizePath(String path) {
if (path == null || path.isEmpty()) return ""; if (path == null || path.isEmpty()) return "";
@@ -801,6 +835,7 @@ public class GitSyncPlugin implements Plugin, InitializingBean {
* 以回调方式同步所有项目(供可视窗口调用) * 以回调方式同步所有项目(供可视窗口调用)
*/ */
public void syncWithCallback(String branch, SyncCallback cb) { public void syncWithCallback(String branch, SyncCallback cb) {
syncCancelled = false;
List<FileOperationHelper.ProjectPathInfo> projects; List<FileOperationHelper.ProjectPathInfo> projects;
try { try {
projects = fileOperationHelper.getProjectPathsWithStatus(); projects = fileOperationHelper.getProjectPathsWithStatus();
@@ -814,12 +849,12 @@ public class GitSyncPlugin implements Plugin, InitializingBean {
return; return;
} }
if (syncCancelled) { cb.onLog("[INFO] 同步已取消"); return; }
Map<String, String> baseUrlMap = new LinkedHashMap<>(); Map<String, String> baseUrlMap = new LinkedHashMap<>();
for (FileOperationHelper.ProjectPathInfo proj : projects) { for (FileOperationHelper.ProjectPathInfo proj : projects) {
if (proj.getRemoteBaseUrl() != null && !proj.getRemoteBaseUrl().isEmpty()) { if (proj.getRemoteBaseUrl() != null && !proj.getRemoteBaseUrl().isEmpty()) {
String url = proj.getRemoteBaseUrl(); baseUrlMap.put(normalizePath(proj.getPath()), proj.getRemoteBaseUrl());
if (!url.endsWith("/")) url = url + "/";
baseUrlMap.put(normalizePath(proj.getPath()), url);
} }
} }
@@ -835,7 +870,13 @@ public class GitSyncPlugin implements Plugin, InitializingBean {
cb.onLog("并行线程: " + PARALLEL); cb.onLog("并行线程: " + PARALLEL);
cb.onLog("开始同步...\n"); cb.onLog("开始同步...\n");
if (syncCancelled) { cb.onLog("[INFO] 同步已取消"); pool.shutdownNow(); return; }
for (int i = 0; i < total; i++) { for (int i = 0; i < total; i++) {
if (syncCancelled) {
// 不再提交新任务,但已提交的会在 latch 中计完
break;
}
final FileOperationHelper.ProjectPathInfo proj = projects.get(i); final FileOperationHelper.ProjectPathInfo proj = projects.get(i);
final String repoPath = proj.getPath(); final String repoPath = proj.getPath();
final String repoName = extractName(repoPath); final String repoName = extractName(repoPath);
@@ -850,7 +891,7 @@ public class GitSyncPlugin implements Plugin, InitializingBean {
int done = completed.incrementAndGet(); int done = completed.incrementAndGet();
String tag; String tag;
switch (result) { switch (result) {
case OK: tag = "成功"; break; case OK: tag = result.cloned ? "拉取" : "成功"; break;
case DAMAGED: tag = "损坏"; break; case DAMAGED: tag = "损坏"; break;
default: tag = "失败"; break; default: tag = "失败"; break;
} }
@@ -869,10 +910,20 @@ public class GitSyncPlugin implements Plugin, InitializingBean {
pool.shutdown(); pool.shutdown();
try { try {
boolean allDone = latch.await(SYNC_TOTAL_TIMEOUT_MINUTES, TimeUnit.MINUTES); // 缩短每次 await 时间,以便及时响应取消
if (!allDone) { long checkIntervalMs = 500;
pool.shutdownNow(); long deadline = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(SYNC_TOTAL_TIMEOUT_MINUTES);
cb.onLog("\n[ERROR] 同步超时(超过 " + SYNC_TOTAL_TIMEOUT_MINUTES + " 分钟),强制终止"); while (!latch.await(checkIntervalMs, TimeUnit.MILLISECONDS)) {
if (syncCancelled) {
cb.onLog("\n[WARN] 用户取消了同步,正在终止...");
pool.shutdownNow();
break;
}
if (System.currentTimeMillis() > deadline) {
pool.shutdownNow();
cb.onLog("\n[ERROR] 同步超时(超过 " + SYNC_TOTAL_TIMEOUT_MINUTES + " 分钟),强制终止");
break;
}
} }
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
@@ -881,6 +932,7 @@ public class GitSyncPlugin implements Plugin, InitializingBean {
return; return;
} }
// 汇总结果
int success = 0, failed = 0; int success = 0, failed = 0;
List<DamagedRepo> damagedRepos = new ArrayList<>(); List<DamagedRepo> damagedRepos = new ArrayList<>();
for (FileOperationHelper.ProjectPathInfo proj : projects) { for (FileOperationHelper.ProjectPathInfo proj : projects) {
@@ -914,6 +966,11 @@ public class GitSyncPlugin implements Plugin, InitializingBean {
} }
} }
/** 取消当前正在执行的同步(供 GUI 终止按钮调用) */
public void cancelSync() {
this.syncCancelled = true;
}
/** /**
* 获取损坏仓库的 clone URL(供窗口使用) * 获取损坏仓库的 clone URL(供窗口使用)
*/ */
@@ -969,6 +1026,7 @@ public class GitSyncPlugin implements Plugin, InitializingBean {
String output; String output;
String error; String error;
String remoteUrl; String remoteUrl;
boolean cloned; // 标记是否是通过 auto-clone 完成的,区别于正常 fetch+checkout+pull
} }
/** 损坏仓库信息 */ /** 损坏仓库信息 */
@@ -20,7 +20,6 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
/** /**
* Git 仓库同步可视化窗口 * Git 仓库同步可视化窗口
@@ -46,7 +45,6 @@ public class GitSyncWindow {
private ProgressBar progressBar; private ProgressBar progressBar;
private Label progressLabel; private Label progressLabel;
private TextArea logArea; private TextArea logArea;
private final AtomicBoolean stopRequested = new AtomicBoolean(false);
// Tab 3: 损坏仓库处理 // Tab 3: 损坏仓库处理
private TableView<DamagedRow> damagedTable; private TableView<DamagedRow> damagedTable;
@@ -204,7 +202,11 @@ public class GitSyncWindow {
stopBtn = StyledButton.danger("终止", Theme.base(14)); stopBtn = StyledButton.danger("终止", Theme.base(14));
stopBtn.setDisable(true); stopBtn.setDisable(true);
stopBtn.setOnAction(e -> stopRequested.set(true)); stopBtn.setOnAction(e -> {
stopBtn.setDisable(true);
progressLabel.setText("正在终止...");
plugin.cancelSync();
});
branchRow.getChildren().addAll(branchLabel, branchField, syncBtn, stopBtn); branchRow.getChildren().addAll(branchLabel, branchField, syncBtn, stopBtn);
@@ -240,7 +242,6 @@ public class GitSyncWindow {
syncBtn.setDisable(true); syncBtn.setDisable(true);
stopBtn.setDisable(false); stopBtn.setDisable(false);
stopRequested.set(false);
logArea.clear(); logArea.clear();
progressBar.setProgress(0); progressBar.setProgress(0);
progressLabel.setText("正在同步..."); progressLabel.setText("正在同步...");
@@ -44,9 +44,16 @@ public class MyBatisExportWindow {
// 操作 // 操作
private Button exportBtn; private Button exportBtn;
private Button stopBtn;
private ProgressBar progressBar; private ProgressBar progressBar;
private Label progressLabel; private Label progressLabel;
// 日志
private TextArea logArea;
// 取消标记
private volatile boolean exportCancelled = false;
// 历史记录 // 历史记录
private TableView<HistoryRow> historyTable; private TableView<HistoryRow> historyTable;
private final ObservableList<HistoryRow> historyRows = FXCollections.observableArrayList(); private final ObservableList<HistoryRow> historyRows = FXCollections.observableArrayList();
@@ -131,20 +138,40 @@ public class MyBatisExportWindow {
exportBtn = StyledButton.primary("导出", Theme.bold(fsBtn())); exportBtn = StyledButton.primary("导出", Theme.bold(fsBtn()));
exportBtn.setOnAction(e -> startExport()); exportBtn.setOnAction(e -> startExport());
filterRow.getChildren().addAll(modeLabel, filterModeCmb, filterValueField, filterHint, exportBtn); stopBtn = StyledButton.danger("终止", Theme.base(fsBtn()));
stopBtn.setDisable(true);
stopBtn.setOnAction(e -> {
stopBtn.setDisable(true);
progressLabel.setText("正在终止...");
exportCancelled = true;
});
filterRow.getChildren().addAll(modeLabel, filterModeCmb, filterValueField, filterHint, exportBtn, stopBtn);
// === 进度 === // === 进度 ===
HBox progressRow = new HBox(Theme.S2); HBox progressRow = new HBox(Theme.S2);
progressRow.setAlignment(Pos.CENTER_LEFT); progressRow.setAlignment(Pos.CENTER_LEFT);
progressBar = new ProgressBar(0); progressBar = new ProgressBar(0);
progressBar.setPrefWidth(300); progressBar.setPrefWidth(400);
progressBar.setPrefHeight(16); progressBar.setPrefHeight(20);
progressBar.setVisible(false); progressBar.setVisible(false);
progressLabel = new Label(""); progressLabel = new Label("");
progressLabel.setFont(Theme.base(fsBody() - 2)); progressLabel.setFont(Theme.base(fsBody() - 2));
progressLabel.setTextFill(Theme.TEXT_SECONDARY); progressLabel.setTextFill(Theme.TEXT_SECONDARY);
progressRow.getChildren().addAll(progressBar, progressLabel); progressRow.getChildren().addAll(progressBar, progressLabel);
// === 日志区域 ===
Label logTitle = new Label("执行日志");
logTitle.setFont(Theme.heading(fsBody()));
logTitle.setTextFill(Theme.TEXT_PRIMARY);
logArea = new TextArea();
logArea.setEditable(false);
logArea.setFont(Theme.mono(12));
logArea.getStyleClass().add(Theme.CSS_LOG_AREA);
logArea.setPrefHeight(150);
logArea.setStyle("-fx-background-color: " + Theme.toWeb(Theme.BG_BODY) + ";");
VBox.setVgrow(logArea, Priority.NEVER);
// === 标题 === // === 标题 ===
Label tableTitle = new Label("导出历史记录"); Label tableTitle = new Label("导出历史记录");
tableTitle.setFont(Theme.heading(fsSection())); tableTitle.setFont(Theme.heading(fsSection()));
@@ -184,7 +211,7 @@ public class MyBatisExportWindow {
historyTable.getColumns().addAll(colAction, colIndex, colQuery, colFile, colCount, colTime); historyTable.getColumns().addAll(colAction, colIndex, colQuery, colFile, colCount, colTime);
VBox.setVgrow(historyTable, Priority.ALWAYS); VBox.setVgrow(historyTable, Priority.ALWAYS);
root.getChildren().addAll(filterRow, progressRow, tableTitle, historyTable); root.getChildren().addAll(filterRow, progressRow, logTitle, logArea, tableTitle, historyTable);
stage.setScene(new Scene(root)); stage.setScene(new Scene(root));
} }
@@ -106,18 +106,28 @@ public class MyBatisExportService {
} }
} }
/** 导出进度回调 */
public interface ExportCallback {
void onProjectStart(int current, int total, String projectName);
void onProjectDone(int current, int total, String projectName, int recordCount);
/** 是否已请求取消 */
boolean isCancelled();
}
/** /**
* 按过滤条件导出并返回结果(含文件路径,供窗口展示 + 保存 CSV) * 按过滤条件导出并返回结果(含文件路径,供窗口展示 + 保存 CSV)
*/ */
public ExportResult exportWithResult(String fileNamePrefix, String filterMode, String filterValue) throws Exception { public ExportResult exportWithResult(String fileNamePrefix, String filterMode, String filterValue) throws Exception {
SqlFilter filter = null; return exportWithCallback(fileNamePrefix, filterMode, filterValue, null);
if ("table".equals(filterMode) && filterValue != null && !filterValue.isEmpty()) { }
filter = new TableFilter(filterValue);
} else if ("keyword".equals(filterMode) && filterValue != null && !filterValue.isEmpty()) { /**
filter = new KeywordFilter(filterValue); * 带进度回调的导出(供可视化窗口使用)
} else if ("tagid".equals(filterMode) && filterValue != null && !filterValue.isEmpty()) { * @param cb 进度回调,传 null 则降级为无回调模式
filter = new TagIdFilter(filterValue); */
} public ExportResult exportWithCallback(String fileNamePrefix, String filterMode, String filterValue,
ExportCallback cb) throws Exception {
SqlFilter filter = buildFilter(filterMode, filterValue);
List<String> projectPaths = fileOperationHelper.getProjectPaths(); List<String> projectPaths = fileOperationHelper.getProjectPaths();
if (projectPaths.isEmpty()) { if (projectPaths.isEmpty()) {
@@ -125,16 +135,30 @@ public class MyBatisExportService {
return new ExportResult(new ArrayList<>(), ""); return new ExportResult(new ArrayList<>(), "");
} }
int total = projectPaths.size();
List<SqlExportRow> resultList = new ArrayList<>(); List<SqlExportRow> resultList = new ArrayList<>();
int current = 0;
for (String projectPath : projectPaths) { for (String projectPath : projectPaths) {
current++;
String projectName = fileOperationHelper.extractProjectName(projectPath);
if (cb != null) {
if (cb.isCancelled()) { break; }
cb.onProjectStart(current, total, projectName);
}
try { try {
List<String[]> projectRecords = processProject(projectPath, filter, null); List<String[]> projectRecords = processProject(projectPath, filter, null);
for (String[] r : projectRecords) { for (String[] r : projectRecords) {
resultList.add(new SqlExportRow(r[0], r[1], r[2], r[3], r[4])); resultList.add(new SqlExportRow(r[0], r[1], r[2], r[3], r[4]));
} }
if (cb != null) {
cb.onProjectDone(current, total, projectName, projectRecords.size());
}
} catch (Exception e) { } catch (Exception e) {
log.error("处理项目时出错: " + projectPath, e); log.error("处理项目时出错: " + projectPath, e);
if (cb != null) {
cb.onProjectDone(current, total, projectName + " (出错)", 0);
}
} }
} }
@@ -150,6 +174,18 @@ public class MyBatisExportService {
return new ExportResult(resultList, fileName); return new ExportResult(resultList, fileName);
} }
/** 根据过滤模式和值构建过滤器 */
private SqlFilter buildFilter(String filterMode, String filterValue) {
if ("table".equals(filterMode) && filterValue != null && !filterValue.isEmpty()) {
return new TableFilter(filterValue);
} else if ("keyword".equals(filterMode) && filterValue != null && !filterValue.isEmpty()) {
return new KeywordFilter(filterValue);
} else if ("tagid".equals(filterMode) && filterValue != null && !filterValue.isEmpty()) {
return new TagIdFilter(filterValue);
}
return null;
}
private void exportWithFilter(String fileNamePrefix, SqlFilter filter, private void exportWithFilter(String fileNamePrefix, SqlFilter filter,
ProcessStrategy processStrategy) throws Exception { ProcessStrategy processStrategy) throws Exception {
List<String> projectPaths = fileOperationHelper.getProjectPaths(); List<String> projectPaths = fileOperationHelper.getProjectPaths();
@@ -231,7 +231,7 @@ public class WorkUtilsProperties {
* 文件不存在时自动创建空文件。 * 文件不存在时自动创建空文件。
*/ */
public Path getProjectPathsFilePath() { public Path getProjectPathsFilePath() {
Path p = Paths.get(projectPathsFile != null ? projectPathsFile : "project_paths.txt"); Path p = Paths.get(projectPathsFile != null ? projectPathsFile : "data/project_paths.txt");
if (p.isAbsolute()) { if (p.isAbsolute()) {
return p; return p;
} }
@@ -171,7 +171,6 @@ public class MainWindow {
stage.setScene(scene); stage.setScene(scene);
showWelcomePage(); showWelcomePage();
if (!navItems.isEmpty()) selectNavItem(navItems.get(0));
} }
// ==================== 统一顶部栏 ==================== // ==================== 统一顶部栏 ====================