Files
homeutils/src/main/java/com/example/workutils/WorkUtilsApplication.java
T
2026-06-23 20:25:40 +08:00

288 lines
10 KiB
Java

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<MainWindow.NavItem> 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<MainWindow.NavItem> buildNavItems() {
List<MainWindow.NavItem> 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;
}
// ==================== 内嵌优先打开 ====================
/**
* 优先尝试内嵌模式;若插件不支持嵌入,则退回到独立窗口。
* <p>
* 在 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);
}
// ==================== 独立窗口模式(弹出或回退) ====================
/**
* 以独立窗口形式打开插件。用于:
* <ul>
* <li>插件不支持内嵌时的回退</li>
* <li>用户点击「弹出窗口」按钮</li>
* </ul>
*/
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<Plugin> 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);
}
}
}