功能已稳定
This commit is contained in:
@@ -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 时启用) -->
|
||||||
|
|||||||
@@ -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;
|
||||||
|
long deadline = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(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();
|
pool.shutdownNow();
|
||||||
cb.onLog("\n[ERROR] 同步超时(超过 " + SYNC_TOTAL_TIMEOUT_MINUTES + " 分钟),强制终止");
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+43
-7
@@ -106,35 +106,59 @@ 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()) {
|
|
||||||
filter = new TagIdFilter(filterValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 带进度回调的导出(供可视化窗口使用)
|
||||||
|
* @param cb 进度回调,传 null 则降级为无回调模式
|
||||||
|
*/
|
||||||
|
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()) {
|
||||||
log.warn("未找到项目路径");
|
log.warn("未找到项目路径");
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 统一顶部栏 ====================
|
// ==================== 统一顶部栏 ====================
|
||||||
|
|||||||
Reference in New Issue
Block a user