兼容性优化
This commit is contained in:
@@ -17,6 +17,8 @@ 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;
|
||||
@@ -48,6 +50,37 @@ public class WorkUtilsApplication implements CommandLineRunner {
|
||||
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);
|
||||
|
||||
@@ -3,12 +3,15 @@ package com.example.workutils.system.config;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@@ -20,6 +23,7 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
/**
|
||||
* 工具集配置属性
|
||||
@@ -301,23 +305,74 @@ public class WorkUtilsProperties {
|
||||
|
||||
private void ensureDataFile(Path target, String classpathResource, String label) {
|
||||
try {
|
||||
if (Files.exists(target)) return;
|
||||
// 文件存在且非空 → 跳过;不存在或为空(0字节)→ 重新复制模板
|
||||
if (Files.exists(target) && Files.size(target) > 0) {
|
||||
log.debug("{} 已存在且非空,跳过复制: {}", label, target.toAbsolutePath());
|
||||
return;
|
||||
}
|
||||
Files.createDirectories(target.getParent());
|
||||
ClassPathResource cpr = new ClassPathResource(classpathResource);
|
||||
if (cpr.exists()) {
|
||||
try (InputStream in = cpr.getInputStream()) {
|
||||
|
||||
// 多 ClassLoader 回退,兼容 fat JAR / thin JAR / IDE 环境
|
||||
InputStream in = openClasspathStream(classpathResource);
|
||||
if (in != null) {
|
||||
try (in) {
|
||||
Files.copy(in, target, StandardCopyOption.REPLACE_EXISTING);
|
||||
log.info("已从模板复制{}: {} ({} bytes)", label, target.toAbsolutePath(), Files.size(target));
|
||||
}
|
||||
log.info("已从模板复制{}: {}", label, target.toAbsolutePath());
|
||||
} else {
|
||||
Files.createFile(target);
|
||||
log.info("模板未找到,已创建空{}: {}", label, target.toAbsolutePath());
|
||||
// 所有 ClassLoader 都未找到模板,保留空文件防下次重试
|
||||
if (!Files.exists(target)) Files.createFile(target);
|
||||
log.warn("模板未找到[classpath:{}],已创建空{}: {}", classpathResource, label, target.toAbsolutePath());
|
||||
}
|
||||
} catch (java.io.IOException e) {
|
||||
log.warn("创建{}失败: {}", label, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 多 ClassLoader 回退打开 classpath 资源流。
|
||||
* 优先级:ContextClassLoader → 当前类的 ClassLoader → 系统 ClassLoader → 直读 JAR
|
||||
*/
|
||||
private static InputStream openClasspathStream(String path) {
|
||||
InputStream in;
|
||||
|
||||
// 1) Thread ContextClassLoader(Spring Boot fat JAR 标准)
|
||||
ClassLoader ctxCl = Thread.currentThread().getContextClassLoader();
|
||||
if (ctxCl != null) {
|
||||
in = ctxCl.getResourceAsStream(path);
|
||||
if (in != null) return in;
|
||||
}
|
||||
|
||||
// 2) 当前类的 ClassLoader
|
||||
in = WorkUtilsProperties.class.getClassLoader().getResourceAsStream(path);
|
||||
if (in != null) return in;
|
||||
|
||||
// 3) 系统 ClassLoader(兜底)
|
||||
in = ClassLoader.getSystemResourceAsStream(path);
|
||||
if (in != null) return in;
|
||||
|
||||
// 4) 直接从 JAR 文件读取(终极回退,绕过所有 ClassLoader 问题)
|
||||
try {
|
||||
URL jarUrl = WorkUtilsProperties.class.getProtectionDomain().getCodeSource().getLocation();
|
||||
if (jarUrl != null) {
|
||||
String jarPath = jarUrl.getPath();
|
||||
String entryName = "BOOT-INF/classes/" + path;
|
||||
try (JarFile jar = new JarFile(jarPath)) {
|
||||
java.util.jar.JarEntry entry = jar.getJarEntry(entryName);
|
||||
if (entry != null) {
|
||||
byte[] bytes = jar.getInputStream(entry).readAllBytes();
|
||||
log.info("从 JAR 直接读取模板: {} ({} bytes)", entryName, bytes.length);
|
||||
return new ByteArrayInputStream(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// 不是 JAR 环境或读取失败,忽略,前面 ClassLoader 方式已尽力
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Path getExportBasePath() {
|
||||
return Paths.get(exportDirectory);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,12 @@ import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Font;
|
||||
import javafx.scene.text.FontWeight;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 工集 UI 设计系统 —— AtlantaFX 主题管理 + 字体/间距 Token
|
||||
* <p>
|
||||
@@ -25,6 +31,9 @@ public final class Theme {
|
||||
private static volatile Mode currentMode = Mode.LIGHT;
|
||||
private static volatile String currentCustomCss = "css/light-theme.css";
|
||||
|
||||
/** 缓存已加载的 CSS data: URI,避免重复读取 */
|
||||
private static final Map<String, String> cssFileCache = new ConcurrentHashMap<>();
|
||||
|
||||
public static Mode getCurrentMode() { return currentMode; }
|
||||
|
||||
/**
|
||||
@@ -44,17 +53,36 @@ public final class Theme {
|
||||
}
|
||||
|
||||
/**
|
||||
* 将自定义 CSS 样式表添加到 Scene
|
||||
* 将自定义 CSS 样式表添加到 Scene。
|
||||
* <p>
|
||||
* 兼容 Spring Boot fat JAR:使用 data: URI 内联 CSS,不依赖文件系统 URL。
|
||||
*/
|
||||
public static void applyCustomCss(Scene scene) {
|
||||
var cssUrl = Theme.class.getClassLoader().getResource(currentCustomCss);
|
||||
if (cssUrl == null) {
|
||||
String dataUri = cssFileCache.computeIfAbsent(currentCustomCss, Theme::loadCssAsDataUri);
|
||||
if (dataUri == null) {
|
||||
System.err.println("[Theme] 警告: 找不到 CSS 资源 " + currentCustomCss + ",将使用默认样式");
|
||||
return;
|
||||
}
|
||||
String css = cssUrl.toExternalForm();
|
||||
scene.getStylesheets().removeIf(s -> s.contains("light-theme.css") || s.contains("dark-theme.css"));
|
||||
scene.getStylesheets().add(css);
|
||||
scene.getStylesheets().add(dataUri);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 classpath 读取 CSS 文件,转成 data: URI(Base64),JavaFX 原生支持。
|
||||
*/
|
||||
private static String loadCssAsDataUri(String classpath) {
|
||||
try (InputStream in = Theme.class.getClassLoader().getResourceAsStream(classpath)) {
|
||||
if (in == null) {
|
||||
System.err.println("[Theme] 错误: classpath 中找不到 " + classpath);
|
||||
return null;
|
||||
}
|
||||
String css = new String(in.readAllBytes(), StandardCharsets.UTF_8);
|
||||
String b64 = Base64.getEncoder().encodeToString(css.getBytes(StandardCharsets.UTF_8));
|
||||
return "data:text/css;base64," + b64;
|
||||
} catch (Exception e) {
|
||||
System.err.println("[Theme] 错误: 读取 CSS 失败 " + classpath + " - " + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user