兼容性优化

This commit is contained in:
张家训
2026-06-23 20:25:40 +08:00
parent c89fcba998
commit 443a5e9cd9
7 changed files with 277 additions and 72 deletions
-1
View File
@@ -269,7 +269,6 @@
<directory>${basedir}/scripts</directory>
<includes>
<include>start.bat</include>
<include>start-jar.bat</include>
</includes>
</resource>
</resources>
+96 -45
View File
@@ -1,6 +1,6 @@
@echo off
chcp 65001 >nul
setlocal
setlocal enabledelayedexpansion
:: ============================================
:: WorkUtils 生产打包脚本(免安装版)
@@ -8,7 +8,7 @@ setlocal
:: (含 exe + 内嵌JRE + jar + 启动脚本)
:: ============================================
::: 切换到项目根目录
:: 切换到项目根目录
cd /d "%~dp0.."
set "PROJECT_DIR=%CD%"
set "TARGET=%PROJECT_DIR%\target"
@@ -20,26 +20,55 @@ echo ========== WorkUtils 生产构建 ==========
echo 项目目录: %PROJECT_DIR%
echo.
::: ============================================================
::: 步骤 1:清理残留进程
::: ============================================================
echo [1/5] Clean stale processes...
:: ============================================================
:: 步骤 1:清理残留进程 & 解锁删除旧产物
:: ============================================================
echo [1/5] Clean stale processes and files...
taskkill /f /im java.exe 2>nul
taskkill /f /im workutils.exe 2>nul
:: 等进程完全退出,文件锁释放
echo 等待进程退出...
ping 127.0.0.1 -n 4 >nul
::: ============================================================
::: 步骤 2:Maven 打包
::: ============================================================
echo.
echo [2/5] Maven build...
:: --- 清理 target/dist ---
if exist "%TARGET%" (
echo Cleaning old target...
:: 策略1:直接 rd(文件锁已释放时应成功)
rd /s /q "%TARGET%" 2>nul
:: 策略2:如果 rd 失败,改名后重建
if exist "%TARGET%" (
echo [WARN] Cannot delete target, retrying with Maven clean...
call "%PROJECT_DIR%\mvnw.cmd" clean -q 2>nul
echo [WARN] 直接删除失败,尝试改名清理...
:: 先删 dist/ 子目录的文件(规避 JRE 文件锁)
if exist "%DIST%" (
del /f /s /q "%DIST%\*" 2>nul
rd /s /q "%DIST%" 2>nul
)
:: 再删 jre/
if exist "%TARGET%\jre" (
del /f /s /q "%TARGET%\jre\*" 2>nul
rd /s /q "%TARGET%\jre" 2>nul
)
:: 删 target/ 下其余文件
del /f /s /q "%TARGET%\*" 2>nul
for /d %%d in ("%TARGET%\*") do rd /s /q "%%d" 2>nul
rd /s /q "%TARGET%" 2>nul
)
:: 策略3:改名旧目录,让后续步骤创建新的
if exist "%TARGET%" (
for /f "tokens=2 delims==" %%I in ('wmic os get localdatetime /value') do set "TS=%%I"
set "OLD_TARGET=%PROJECT_DIR%\target_old_!TS:~0,14!"
echo [WARN] 无法删除,重命名为 !OLD_TARGET! ...
move "%TARGET%" "!OLD_TARGET!" >nul 2>&1
if exist "!OLD_TARGET!" (
echo [INFO] 旧产物已移至 !OLD_TARGET!,稍后请手动删除
)
)
)
:: ============================================================
:: 步骤 2:Maven 打包
:: ============================================================
echo.
echo [2/5] Maven build...
call "%PROJECT_DIR%\mvnw.cmd" package -DskipTests -q
if %errorlevel% neq 0 (
echo [ERROR] Maven build failed!
@@ -47,22 +76,22 @@ if %errorlevel% neq 0 (
exit /b 1
)
::: ============================================================
::: 步骤 3:提取版本号(PowerShell 解析 pom.xml)
::: ============================================================
:: ============================================================
:: 步骤 3:提取版本号
:: ============================================================
echo.
echo [3/5] Read version from pom.xml...
for /f "usebackq delims=" %%a in (`powershell -NoProfile -NonInteractive -Command "([xml](Get-Content -Encoding UTF8 '%PROJECT_DIR%\pom.xml')).project.version"`) do set "VERSION=%%a"
if "%VERSION%"=="" set "VERSION=0.0.1-SNAPSHOT"
echo 版本: %VERSION%
::: ============================================================
::: 步骤 4:生成精简 JRE(jlink 内嵌 Java 17 + JavaFX)
::: ============================================================
:: ============================================================
:: 步骤 4:生成精简 JRE(jlink 内嵌 Java 17 + JavaFX)
:: ============================================================
echo.
echo [4/5] Generate bundled JRE...
::: 4.1 查找 JDK 17(环境变量 JDK17_HOME > 默认路径)
:: 4.1 查找 JDK 17
if not defined JDK17_HOME (
for %%d in (
"C:\Program Files\Java\jdk-17"
@@ -81,7 +110,7 @@ if not defined JDK17_HOME (
)
:jdk17_found
::: 4.2 查找 JavaFX SDK(环境变量 JFX_SDK_HOME > 默认路径)
:: 4.2 查找 JavaFX SDK
if not defined JFX_SDK_HOME (
for %%d in (
"C:\Program Files\Java\javafx-sdk-17.0.15"
@@ -102,10 +131,10 @@ if not defined JFX_SDK_HOME (
echo JDK : %JDK17_HOME%
echo JavaFX: %JFX_SDK_HOME%
::: 4.3 清理旧 JRE
if exist "%TARGET%\jre" rd /s /q "%TARGET%\jre"
:: 4.3 清理旧 JRE
if exist "%TARGET%\jre" rd /s /q "%TARGET%\jre" 2>nul
::: 4.4 执行 jlink
:: 4.4 执行 jlink
echo Running jlink, please wait...
"%JDK17_HOME%\bin\jlink.exe" ^
--module-path "%JFX_SDK_HOME%\lib;%JDK17_HOME%\jmods" ^
@@ -119,7 +148,7 @@ if %errorlevel% neq 0 (
exit /b 1
)
::: 4.5 补充 JavaFX 原生 DLL(jlink 不会自动从 SDK bin/ 复制)
:: 4.5 补充 JavaFX 原生 DLL
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
@@ -130,17 +159,21 @@ if exist "%JFX_SDK_HOME%\bin\glass.dll" (
echo [OK] JRE generated : %TARGET%\jre
::: ============================================================
::: 步骤 5:组装发布目录 & 打包 ZIP
::: ============================================================
:: ============================================================
:: 步骤 5:组装发布目录 & 打包 ZIP
:: ============================================================
echo.
echo [5/5] Assemble distribution...
::: 清理旧发布目录
if exist "%DIST%" rd /s /q "%DIST%"
:: 清理旧发布目录
if exist "%DIST%" (
del /f /s /q "%DIST%\*" 2>nul
for /d %%d in ("%DIST%\*") do rd /s /q "%%d" 2>nul
rd /s /q "%DIST%" 2>nul
)
mkdir "%DIST%\workutils" 2>nul
::: 复制 EXE
:: 复制 EXE
if exist "%TARGET%\workutils.exe" (
copy /y "%TARGET%\workutils.exe" "%DIST%\workutils\" >nul
echo [OK] workutils.exe
@@ -148,44 +181,61 @@ if exist "%TARGET%\workutils.exe" (
echo [WARN] workutils.exe not found!
)
::: 复制 JAR
:: 复制 JAR
for %%j in ("%TARGET%\workutils-*.jar") do (
copy /y "%%j" "%DIST%\workutils\" >nul
echo [OK] %%~nxj
)
::: 复制内嵌 JRE
:: 复制内嵌 JRE
if exist "%TARGET%\jre\bin\java.exe" (
echo 复制内嵌 JRE...
xcopy /e /i /q "%TARGET%\jre" "%DIST%\workutils\jre\" >nul
echo [OK] jre/ ^(bundled Java 17 + JavaFX^)
) else (
echo [WARN] jre not found in target!
)
::: ============================================================
::: 生成 start.bat(备选启动方式)
::: ============================================================
:: ============================================================
:: 生成 start.bat
:: ============================================================
(
echo @echo off
echo chcp 65001 ^>nul
echo title WorkUtils
echo java -jar "%%~dp0workutils-%VERSION%.jar" %%*
echo pause
echo.
echo :: 优先用内嵌 JRE,否则查系统 Java 17+
echo set "JAVA_BIN=%%~dp0jre\bin\java.exe"
echo if exist "%%JAVA_BIN%%" goto :run
echo.
echo set "JAVA_BIN=java"
echo for /f "tokens=3" %%%%v in ^('"%%JAVA_BIN%%" -version 2^>^&1 ^| findstr /i "version"'^) do set "JAVA_VER=%%%%~v"
echo for /f "tokens=1 delims=." %%%%m in ^("%%JAVA_VER%%"^) do set "MAJOR=%%%%m"
echo if "%%MAJOR%%"=="1" for /f "tokens=2 delims=." %%%%m in ^("%%JAVA_VER%%"^) do set "MAJOR=%%%%m"
echo if %%MAJOR%% LSS 17 ^(
echo echo [ERROR] Java %%JAVA_VER%% found. WorkUtils requires Java 17+.
echo echo Download JDK 17+ from https://adoptium.net/
echo pause ^& exit /b 1
echo ^)
echo.
echo :run
echo "%%JAVA_BIN%%" -jar "%%~dp0workutils-%VERSION%.jar" %%*
echo if %%errorlevel%% neq 0 pause
) > "%DIST%\workutils\start.bat"
echo [OK] start.bat
::: ============================================================
::: 打包 ZIP
::: ============================================================
:: ============================================================
:: 打包 ZIP
:: ============================================================
set "ZIPNAME=workutils-v%VERSION%"
echo 正在压缩 %ZIPNAME%.zip ...
powershell -Command ^
"Compress-Archive -Path '%DIST%\workutils' -DestinationPath '%DIST%\%ZIPNAME%.zip' -Force"
echo [OK] %ZIPNAME%.zip
::: ============================================================
::: 完成
::: ============================================================
:: ============================================================
:: 完成
:: ============================================================
echo.
echo ============================================
echo Build Complete ^! v%VERSION%
@@ -196,3 +246,4 @@ echo %DIST%\%ZIPNAME%.zip
echo ============================================
echo.
pause
exit /b 0
-7
View File
@@ -1,7 +0,0 @@
@echo off
chcp 65001 >nul
title WorkUtils
java -Dfile.encoding=UTF-8 -jar "%~dp0target\workutils-0.0.2-SNAPSHOT.jar" %*
pause
+52 -6
View File
@@ -1,10 +1,56 @@
@echo off
chcp 65001 > nul
title workutils
chcp 65001 >nul
title WorkUtils
:: 切到脚本所在目录的父目录(即项目根目录)
:: 当脚本在 scripts/ 时 → 项目根;在 target/ 时 → 项目根
cd /d "%~dp0.."
:: 从 pom.xml 读取版本号
set "VERSION="
for /f "usebackq delims=" %%a in (`powershell -NoProfile -NonInteractive -Command "([xml](Get-Content -Encoding UTF8 '%CD%\pom.xml')).project.version"`) do set "VERSION=%%a"
if "%VERSION%"=="" set "VERSION=0.0.1-SNAPSHOT"
:: 优先匹配版本号精确的 JAR,找不到回退通配符
set "JAR="
if exist "target\workutils-%VERSION%.jar" set "JAR=target\workutils-%VERSION%.jar"
if "%JAR%"=="" for %%j in ("target\workutils-*.jar") do set "JAR=%%j"
if "%JAR%"=="" (
echo [ERROR] JAR not found in target\ Run build_release.bat first.
pause & exit /b 1
)
:: ======== 查找可用的 Java 17+ ========
set "JAVA_EXE="
:: 1) 优先用 jlink 生成的内嵌 JRE(运行过 build_release.bat 后产生)
if exist "target\jre\bin\java.exe" set "JAVA_EXE=target\jre\bin\java.exe"
:: 2) 检查 JDK17_HOME 环境变量
if "%JAVA_EXE%"=="" if defined JDK17_HOME if exist "%JDK17_HOME%\bin\java.exe" set "JAVA_EXE=%JDK17_HOME%\bin\java.exe"
:: 3) 检查 JAVA_HOME(可能是 JDK 17)
if "%JAVA_EXE%"=="" if defined JAVA_HOME if exist "%JAVA_HOME%\bin\java.exe" set "JAVA_EXE=%JAVA_HOME%\bin\java.exe"
:: 4) 回退到 PATH 上的 java
if "%JAVA_EXE%"=="" set "JAVA_EXE=java"
:: 检查 Java 版本是否 ≥ 17
for /f "tokens=3" %%v in ('"%JAVA_EXE%" -version 2^>^&1 ^| findstr /i "version"') do set "JAVA_VER=%%~v"
for /f "tokens=1 delims=." %%m in ("%JAVA_VER%") do set "MAJOR=%%m"
if "%MAJOR%"=="1" for /f "tokens=2 delims=." %%m in ("%JAVA_VER%") do set "MAJOR=%%m"
if %MAJOR% LSS 17 (
echo [ERROR] Java %JAVA_VER% found. WorkUtils requires Java 17+.
echo Path: %JAVA_EXE%
echo Download JDK 17+ from https://adoptium.net/
pause & exit /b 1
)
echo ====================================
echo workutils 系统工具集
echo 控制台编码: UTF-8 (65001)
echo WorkUtils v%VERSION%
echo Java: %JAVA_VER% ^( %JAVA_EXE% ^)
echo ====================================
echo.
cd /d "%~dp0"
java -jar target\workutils-0.0.2-SNAPSHOT.jar
"%JAVA_EXE%" -Dfile.encoding=UTF-8 -jar "%JAR%" %*
if %errorlevel% neq 0 pause
@@ -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;
Files.createDirectories(target.getParent());
ClassPathResource cpr = new ClassPathResource(classpathResource);
if (cpr.exists()) {
try (InputStream in = cpr.getInputStream()) {
Files.copy(in, target, StandardCopyOption.REPLACE_EXISTING);
// 文件存在且非空 → 跳过;不存在或为空(0字节)→ 重新复制模板
if (Files.exists(target) && Files.size(target) > 0) {
log.debug("{} 已存在且非空,跳过复制: {}", label, target.toAbsolutePath());
return;
}
Files.createDirectories(target.getParent());
// 多 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;
}
}
/**