package com.example.workutils; import com.example.workutils.system.config.*; import com.example.workutils.system.plugin.Plugin; import com.example.workutils.system.plugin.PluginDescriptor; import com.example.workutils.system.plugin.PluginManager; import com.example.workutils.system.plugin.VisualPluginFactory; import com.example.workutils.system.support.JfxBootstrap; import com.example.workutils.system.support.WinConsole; import com.example.workutils.system.ui.MainWindow; import com.example.workutils.system.ui.Theme; import javafx.application.Platform; import javafx.scene.Parent; import javafx.scene.control.Alert; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; import java.util.*; import java.util.concurrent.CountDownLatch; @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) @Slf4j public class WorkUtilsApplication implements CommandLineRunner { static { // 消除 JavaFX classpath 运行时的 WARNING: Unsupported JavaFX configuration java.util.logging.Logger rootLogger = java.util.logging.Logger.getLogger(""); rootLogger.setFilter(record -> !record.getMessage().contains("Unsupported JavaFX configuration")); } private final PluginManager pluginManager; private final WorkUtilsProperties properties; private final SettingsService settingsService; /** 主窗口引用,供内嵌插件面板使用 */ private MainWindow mainWindow; /** 系统设置窗口缓存(内嵌模式复用) */ private SettingsWindow settingsWindow; public WorkUtilsApplication(PluginManager pluginManager, WorkUtilsProperties properties, SettingsService settingsService) { this.pluginManager = pluginManager; this.properties = properties; this.settingsService = settingsService; } // ==================== 启动诊断 ==================== /** * 应用就绪后验证所有 data/ 配置文件状态 */ @EventListener(ApplicationReadyEvent.class) public void onReady() { String[] files = { "project_paths.txt", "datasources.json", "damaged_repos.cache", "mybatis-generator.properties", "workutils.config" }; java.nio.file.Path dataDir = java.nio.file.Paths.get( System.getProperty("user.dir"), "data"); StringBuilder sb = new StringBuilder("data 目录文件状态:\n"); for (String f : files) { java.nio.file.Path p = dataDir.resolve(f); try { if (java.nio.file.Files.exists(p)) { long size = java.nio.file.Files.size(p); sb.append(String.format(" %-35s %s%n", f, size > 0 ? size + " bytes ✓" : "0 bytes ✗ 空文件!")); } else { sb.append(String.format(" %-35s 不存在 ✗%n", f)); } } catch (Exception e) { sb.append(String.format(" %-35s 读取失败: %s%n", f, e.getMessage())); } } log.info(sb.toString()); } public static void main(String[] args) { WinConsole.ensureUtf8(); SpringApplication.run(WorkUtilsApplication.class, args); } @Override public void run(String... args) throws Exception { if (args.length == 0) { showMainWindow(); } else if ("--console".equals(args[0])) { showInteractiveMenu(); } else { processCommand(args); } } // ==================== 主窗口入口 ==================== private void showMainWindow() throws InterruptedException { JfxBootstrap.await(); if (JfxBootstrap.isFailed()) { System.err.println("JavaFX 初始化失败,回退到控制台模式"); showInteractiveMenu(); return; } // 始终使用亮色主题 Theme.Mode mode = Theme.Mode.LIGHT; Platform.runLater(() -> Theme.applyTheme(mode)); List navItems = buildNavItems(); CountDownLatch windowClosed = new CountDownLatch(1); Platform.runLater(() -> { try { mainWindow = new MainWindow(navItems, properties); mainWindow.showAndWait(); } catch (Exception e) { log.error("主窗口创建失败", e); System.err.println("主窗口创建失败: " + e.getMessage()); } finally { windowClosed.countDown(); } }); windowClosed.await(); log.info("主窗口已关闭"); } // ==================== 导航项构建 ==================== private List buildNavItems() { List items = new ArrayList<>(); for (Plugin plugin : pluginManager.getEnabledPlugins()) { PluginDescriptor desc = plugin.getDescriptor(); VisualPluginFactory factory = plugin.getVisualFactory(); String pluginId = desc.getId(); items.add(new MainWindow.NavItem( desc.getIcon(), desc.getName(), desc.getDescription(), factory != null ? () -> openPluginEmbedded(pluginId, plugin, factory) : null, pluginId )); } // 系统设置 — 内嵌为 Tab items.add(new MainWindow.NavItem( "⚙️", "系统设置", "配置系统路径、数据库连接、项目路径、多数据源、字体大小等", this::openSettingsEmbedded, "__settings__" )); return items; } // ==================== 内嵌优先打开 ==================== /** * 优先尝试内嵌模式;若插件不支持嵌入,则退回到独立窗口。 *

* 在 JFX 线程上调用(来自导航按钮事件)。 */ private void openPluginEmbedded(String pluginId, Plugin plugin, VisualPluginFactory factory) { // 配置检查 String configError = plugin.checkConfig(); if (configError != null) { showConfigAlert(plugin.getName(), configError); return; } // 尝试获取内嵌面板 Parent embeddedPane = factory.getEmbeddedPane(); if (embeddedPane != null && mainWindow != null) { PluginDescriptor desc = plugin.getDescriptor(); mainWindow.embedPlugin(pluginId, desc.getIcon(), desc.getName(), embeddedPane); return; } // 回退:独立窗口模式 openPluginStandalone(plugin, factory); } // ==================== 独立窗口模式(弹出或回退) ==================== /** * 以独立窗口形式打开插件。用于: *

    *
  • 插件不支持内嵌时的回退
  • *
  • 用户点击「弹出窗口」按钮
  • *
*/ private void openPluginStandalone(Plugin plugin, VisualPluginFactory factory) { new Thread(() -> { try { JfxBootstrap.await(); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); return; } CountDownLatch windowClosed = new CountDownLatch(1); Platform.runLater(() -> { try { factory.open(windowClosed::countDown); } catch (Exception e) { log.error("独立窗口创建失败: {}", plugin.getName(), e); windowClosed.countDown(); } }); try { windowClosed.await(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }, plugin.getId() + "-Window").start(); } // ==================== 配置提示 ==================== private void showConfigAlert(String pluginName, String message) { Alert alert = new Alert(Alert.AlertType.WARNING); alert.setTitle("配置检查"); alert.setHeaderText(pluginName + " 配置不完整"); alert.setContentText(message); alert.showAndWait(); } // ==================== 系统设置 ==================== private void openSettingsEmbedded() { if (settingsWindow == null) { settingsWindow = settingsService.createSettingsWindow(null); } if (mainWindow != null) { Parent pane = settingsWindow.getContentPane(); mainWindow.embedPlugin("__settings__", "⚙️", "系统设置", pane); } } private void openSettingsStandalone() { if (settingsWindow == null) { settingsWindow = settingsService.createSettingsWindow(null); } javafx.stage.Stage s = settingsWindow.getStage(); if (s != null) { s.show(); } } /** @deprecated 保留兼容旧路径 */ private void openSettingsWindow() { new Thread(() -> settingsService.showSettings(null, null), "SettingsWindow-Thread").start(); } // ==================== 控制台模式(降级) ==================== private void showInteractiveMenu() { Scanner scanner = new Scanner(System.in); pluginManager.runMainMenu(scanner); } private void processCommand(String... args) throws Exception { String command = args[0].toLowerCase(); Optional toolOpt = pluginManager.getPluginByCommand(command); if (toolOpt.isPresent()) { Plugin plugin = toolOpt.get(); try { String[] toolArgs = args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]; plugin.executeCommand(toolArgs); } catch (Exception e) { log.error("命令执行失败: {} [{}]", plugin.getName(), plugin.getId(), e); System.err.println("命令执行失败: " + e.getMessage()); } } else { System.err.println("未知命令: " + command); } } }