pipeline { agent any environment { JAVA_HOME = 'E:\\2025-2026\\GitAIOps\\jdk' ANDROID_HOME = 'D:\\development\\Android' WIX_HOME = 'C:\\Program Files (x86)\\WiX Toolset v3.11' SONAR_HOST_URL = 'http://localhost:9000' SONAR_PROJECT_KEY = 'slms' SONAR_PROJECT_NAME = 'slms' SONARQUBE_SCANNER_PARAMS = '-Dsonar.qualitygate.wait=true -Dsonar.qualitygate.timeout=300' SONARQUBE_QUALITY_GATE = 'SLMS-Quality-Gate' } tools { maven 'maven396' jdk 'jdk21' } stages { stage('1. 拉取Gitea代码') { steps { echo '========== 从 Gitea 拉取代码 ==========' // 清理可能被占用的文件 script { try { bat ''' @echo off echo 清理工作空间中可能被占用的文件... REM 只结束与当前工作空间相关的 Java 进程 for /f "tokens=2" %%i in ('tasklist /FI "IMAGENAME eq java.exe" /FO LIST ^| findstr /C:"PID:"') do ( wmic process where "ProcessId=%%i and CommandLine like '%%slms%%'" delete 2>nul ) REM 等待进程结束 timeout /t 1 /nobreak >nul echo ✓ 清理完成 ''' } catch (Exception e) { echo "清理过程中出现警告: ${e.message}" } } // 使用优化的 Git 拉取配置 checkout([ $class: 'GitSCM', branches: [[name: '*/main']], extensions: [ [$class: 'CleanBeforeCheckout'], [$class: 'CleanCheckout'] ], userRemoteConfigs: [[ url: 'http://localhost:3000/gitea/slms.git', credentialsId: 'gitea-credentials' ]] ]) echo '✓ 代码拉取成功' } } stage('2. Maven编译') { steps { echo '========== Maven 编译 SLMS 项目 ==========' bat ''' set JAVA_HOME=%JAVA_HOME% REM 强制删除 target 目录(如果存在) if exist target ( echo 清理旧的 target 目录... rmdir /S /Q target 2>nul if exist target ( echo 警告: 无法删除 target 目录,尝试使用 Maven clean... ) else ( echo ✓ target 目录已清理 ) ) REM Maven 编译 mvn clean compile test-compile -DskipTests ''' echo '✓ 项目编译成功' } } stage('3. 运行Mock测试') { steps { echo '========== 运行单元测试 ==========' script { try { bat ''' set JAVA_HOME=%JAVA_HOME% mvn test ''' echo '✓ 测试执行完成' } catch (Exception e) { echo '⚠️ 测试失败,但继续流水线' echo "错误信息: ${e.message}" currentBuild.result = 'UNSTABLE' } } } post { always { junit allowEmptyResults: true, testResults: '**/target/surefire-reports/*.xml' } } } stage('4. Sonar质检') { when { expression { currentBuild.result == null || currentBuild.result == 'SUCCESS' || currentBuild.result == 'UNSTABLE' } } steps { echo '========== 执行 SonarQube 代码质量检测 ==========' withSonarQubeEnv('SonarQube') { bat ''' set JAVA_HOME=%JAVA_HOME% mvn sonar:sonar -Dsonar.qualitygate.wait=true -Dsonar.qualitygate.timeout=300 ''' } echo '✓ SonarQube 分析完成' } } stage('5. 质量门禁') { steps { echo '========== 等待 SLMS-Quality-Gate 质量门禁结果 ==========' timeout(time: 10, unit: 'MINUTES') { script { withSonarQubeEnv('SonarQube') { def qg = waitForQualityGate() if (qg.status != 'OK') { error "SLMS-Quality-Gate 质量门禁未通过: ${qg.status}" } else { echo "✓ SLMS-Quality-Gate 质量门禁检查通过: ${qg.status}" } } } } } } stage('6. 准备打包') { when { expression { currentBuild.result == null || currentBuild.result == 'SUCCESS' || currentBuild.result == 'UNSTABLE' } } steps { echo '========== 准备并行打包 ==========' bat ''' echo 检查编译结果... if exist target\\classes ( echo ✓ 编译完成,classes 目录存在 ) else ( echo ⚠️ 警告: classes 目录不存在 ) echo. echo 准备开始四端并行打包: echo • CLI: 使用 -Pcli profile echo • GUI: 使用 -Pgui-swing profile echo • Web: 使用 -Pweb profile echo • Android: 使用 Gradle echo. echo ✓ 准备完成,开始并行打包 ''' } } stage('7. 四端并行打包') { when { expression { currentBuild.result == null || currentBuild.result == 'SUCCESS' || currentBuild.result == 'UNSTABLE' } } parallel { stage('7.1 CLI 打包 (JAR)') { steps { echo '========== 打包 CLI 应用 (JAR) ==========' bat ''' @echo off set JAVA_HOME=%JAVA_HOME% echo [CLI] 开始打包... REM 使用 CLI profile 打包 mvn package -Pcli -DskipTests -Dmaven.compiler.skip=true echo [CLI] 检查生成的文件... if exist target\\slms-1.0-SNAPSHOT-cli-shaded.jar ( echo [CLI] ✓ CLI shaded JAR 生成成功 for %%F in (target\\slms-1.0-SNAPSHOT-cli-shaded.jar) do echo [CLI] 大小: %%~zF bytes ) else ( echo [CLI] ⚠️ 错误: 找不到 CLI shaded JAR echo [CLI] 检查 target 目录: dir target\\*.jar exit /b 1 ) ''' echo '✓ CLI JAR 打包成功' } } stage('7.2 GUI 打包 (Swing JAR)') { steps { echo '========== 打包 GUI Swing 应用 (独立 JAR) ==========' bat ''' @echo off set JAVA_HOME=%JAVA_HOME% echo [GUI] 开始打包... REM 使用 gui-swing profile 生成独立 JAR mvn package -Pgui-swing -DskipTests -Dmaven.compiler.skip=true echo [GUI] 检查生成的文件... if exist target\\slms-1.0-SNAPSHOT-gui-swing.jar ( echo [GUI] ✓ GUI Swing JAR 生成成功 for %%F in (target\\slms-1.0-SNAPSHOT-gui-swing.jar) do echo [GUI] 大小: %%~zF bytes ) else ( echo [GUI] ⚠️ 错误: GUI Swing JAR 未找到 echo [GUI] 检查 target 目录内容: dir target\\*.jar exit /b 1 ) ''' echo '✓ GUI Swing JAR 打包成功' } } stage('7.3 Web 打包 (WAR)') { steps { echo '========== 打包 Web 应用 (WAR) ==========' bat ''' @echo off set JAVA_HOME=%JAVA_HOME% echo [Web] 开始打包... REM 使用 Web profile 打包 mvn package -Pweb -DskipTests -Dmaven.compiler.skip=true echo [Web] 检查生成的文件... if exist target\\slms-1.0-SNAPSHOT.war ( echo [Web] ✓ Web WAR 生成成功 for %%F in (target\\slms-1.0-SNAPSHOT.war) do echo [Web] 大小: %%~zF bytes ) else ( if exist target\\slms-1.0-SNAPSHOT.jar ( echo [Web] ✓ Web JAR 生成成功 (备用) for %%F in (target\\slms-1.0-SNAPSHOT.jar) do echo [Web] 大小: %%~zF bytes ) else ( echo [Web] ⚠️ 错误: 找不到 Web WAR/JAR echo [Web] 检查 target 目录: dir target\\*.war dir target\\*.jar exit /b 1 ) ) ''' echo '✓ Web WAR 打包成功' } } stage('7.4 Android Gradle 打包 (APK)') { steps { echo '========== 打包 Android 应用 (APK) ==========' bat ''' @echo off setlocal EnableDelayedExpansion set JAVA_HOME=%JAVA_HOME% set ANDROID_HOME=%ANDROID_HOME% echo 打包 Android 应用... REM 使用 Gradle 打包 Android APK(不操作 target 目录) call gradlew.bat :android:assembleDebug REM 先列出 APK 目录看文件名 echo 列出 APK 目录: dir android\\build\\outputs\\apk\\debug\\*.apk REM 检查并复制 APK - 如果文件名相同就跳过 if exist android\\build\\outputs\\apk\\debug\\SLMS-debug.apk ( echo ✓ 找到 SLMS-debug.apk if /I "SLMS-debug.apk" NEQ "slms-debug.apk" ( copy /Y android\\build\\outputs\\apk\\debug\\SLMS-debug.apk android\\build\\outputs\\apk\\debug\\slms-debug.apk >nul 2>&1 ) echo ✓ Android APK 打包完成: slms-debug.apk exit /b 0 ) if exist android\\build\\outputs\\apk\\debug\\android-debug.apk ( echo ✓ 找到 android-debug.apk copy /Y android\\build\\outputs\\apk\\debug\\android-debug.apk android\\build\\outputs\\apk\\debug\\slms-debug.apk >nul 2>&1 echo ✓ Android APK 打包完成: slms-debug.apk exit /b 0 ) REM 尝试复制第一个找到的 APK echo ⚠️ 警告: 找不到预期的 APK 文件,尝试复制第一个找到的 APK set APK_FOUND=0 for %%f in (android\\build\\outputs\\apk\\debug\\*.apk) do ( if !APK_FOUND! EQU 0 ( echo 找到 APK: %%f copy /Y "%%f" android\\build\\outputs\\apk\\debug\\slms-debug.apk >nul 2>&1 echo ✓ 已复制 %%f 为 slms-debug.apk set APK_FOUND=1 ) ) if !APK_FOUND! EQU 0 ( echo ✗ 错误: 完全找不到 APK 文件 exit /b 1 ) exit /b 0 ''' echo '✓ Android APK 打包成功' } } } } stage('7.5 重命名和验证制品') { when { expression { currentBuild.result == null || currentBuild.result == 'SUCCESS' || currentBuild.result == 'UNSTABLE' } } steps { echo '========== 重命名和验证制品 ==========' script { bat ''' @echo off echo ============================================ echo 文件重命名和验证阶段 echo ============================================ echo. REM ========== 1.1 CLI JAR 重命名 ========== echo [1/4] 处理 CLI JAR... if exist target\\slms-1.0-SNAPSHOT-cli-shaded.jar ( echo ✓ 找到源文件: slms-1.0-SNAPSHOT-cli-shaded.jar copy /Y target\\slms-1.0-SNAPSHOT-cli-shaded.jar target\\slms-cli.jar >nul if exist target\\slms-cli.jar ( for %%F in (target\\slms-cli.jar) do ( set SIZE=%%~zF set /a SIZE_MB=%%~zF/1048576 ) if !SIZE_MB! GTR 30 ( echo ✓ 重命名成功: slms-cli.jar ^(!SIZE_MB! MB^) ) else ( echo ⚠️ 警告: slms-cli.jar 文件大小异常 ^(!SIZE_MB! MB^),预期 ^> 30 MB ) ) else ( echo ✗ 错误: 重命名失败 ) ) else ( echo ⚠️ 警告: 找不到 CLI 源文件 ) echo. REM ========== 1.2 GUI JAR 重命名 ========== echo [2/4] 处理 GUI JAR... if exist target\\slms-1.0-SNAPSHOT-gui-swing.jar ( echo ✓ 找到源文件: slms-1.0-SNAPSHOT-gui-swing.jar copy /Y target\\slms-1.0-SNAPSHOT-gui-swing.jar target\\slms-gui.jar >nul if exist target\\slms-gui.jar ( for %%F in (target\\slms-gui.jar) do ( set SIZE=%%~zF set /a SIZE_MB=%%~zF/1048576 ) if !SIZE_MB! GTR 35 ( echo ✓ 重命名成功: slms-gui.jar ^(!SIZE_MB! MB^) ) else ( echo ⚠️ 警告: slms-gui.jar 文件大小异常 ^(!SIZE_MB! MB^),预期 ^> 35 MB ) REM 创建启动脚本 echo @echo off > target\\run-gui.bat echo chcp 65001 ^>nul >> target\\run-gui.bat echo echo ============================================ >> target\\run-gui.bat echo echo SLMS GUI Application ^(Swing Version^) >> target\\run-gui.bat echo echo ============================================ >> target\\run-gui.bat echo echo. >> target\\run-gui.bat echo echo Starting GUI application... >> target\\run-gui.bat echo java -jar slms-gui.jar >> target\\run-gui.bat echo if errorlevel 1 ^( >> target\\run-gui.bat echo echo. >> target\\run-gui.bat echo echo Error: Failed to start GUI application >> target\\run-gui.bat echo echo Please ensure Java 11+ is installed >> target\\run-gui.bat echo pause >> target\\run-gui.bat echo ^) >> target\\run-gui.bat echo ✓ 创建启动脚本: run-gui.bat REM 创建 README 文件 echo SLMS GUI Application ^(Swing Version^) > target\\README-GUI.txt echo. >> target\\README-GUI.txt echo ========================================== >> target\\README-GUI.txt echo. >> target\\README-GUI.txt echo 运行方式: >> target\\README-GUI.txt echo 1. 双击 run-gui.bat ^(推荐^) >> target\\README-GUI.txt echo 2. 命令行: java -jar slms-gui.jar >> target\\README-GUI.txt echo. >> target\\README-GUI.txt echo 特点: >> target\\README-GUI.txt echo - 单一 JAR 文件,无需额外依赖 >> target\\README-GUI.txt echo - 使用 Swing 界面,兼容性好 >> target\\README-GUI.txt echo - 包含完整功能:图书管理、借阅管理 >> target\\README-GUI.txt echo - 文件大小约 36 MB >> target\\README-GUI.txt echo. >> target\\README-GUI.txt echo 系统要求: >> target\\README-GUI.txt echo - Java 11 或更高版本 >> target\\README-GUI.txt echo - Windows / Linux / macOS >> target\\README-GUI.txt echo. >> target\\README-GUI.txt echo 注意: >> target\\README-GUI.txt echo - library.db 必须与应用文件在同一目录 >> target\\README-GUI.txt echo - 首次运行会自动初始化数据库 >> target\\README-GUI.txt echo. >> target\\README-GUI.txt echo ========================================== >> target\\README-GUI.txt echo ✓ 创建说明文档: README-GUI.txt ) else ( echo ✗ 错误: 重命名失败 ) ) else ( echo ⚠️ 警告: 找不到 GUI 源文件 ) echo. REM ========== 1.3 Web WAR 重命名 ========== echo [3/4] 处理 Web WAR... if exist target\\slms-1.0-SNAPSHOT.war ( echo ✓ 找到源文件: slms-1.0-SNAPSHOT.war copy /Y target\\slms-1.0-SNAPSHOT.war target\\slms-web.war >nul if exist target\\slms-web.war ( for %%F in (target\\slms-web.war) do ( set SIZE=%%~zF set /a SIZE_MB=%%~zF/1048576 ) if !SIZE_MB! GTR 10 ( echo ✓ 重命名成功: slms-web.war ^(!SIZE_MB! MB^) ) else ( echo ⚠️ 警告: slms-web.war 文件大小异常 ^(!SIZE_MB! MB^),预期 ^> 10 MB ) ) else ( echo ✗ 错误: 重命名失败 ) ) else ( echo ⚠️ 警告: 找不到 Web WAR 源文件 ) echo. REM ========== 1.4 复制数据库文件 ========== echo [4/4] 处理数据库文件... if exist library.db ( echo ✓ 找到数据库文件: library.db copy /Y library.db target\\library.db >nul if exist target\\library.db ( echo ✓ 复制成功: target\\library.db ) else ( echo ✗ 错误: 复制失败 ) ) else ( echo ⚠️ 警告: 找不到 library.db ) echo. REM ========== 1.5 文件验证和日志 ========== echo ============================================ echo 制品验证报告 echo ============================================ echo. echo [CLI 应用] if exist target\\slms-cli.jar ( for %%F in (target\\slms-cli.jar) do ( set /a SIZE_MB=%%~zF/1048576 echo ✓ slms-cli.jar - !SIZE_MB! MB ) ) else ( echo ✗ slms-cli.jar - 不存在 ) echo. echo [GUI 应用] if exist target\\slms-gui.jar ( for %%F in (target\\slms-gui.jar) do ( set /a SIZE_MB=%%~zF/1048576 echo ✓ slms-gui.jar - !SIZE_MB! MB ) ) else ( echo ✗ slms-gui.jar - 不存在 ) if exist target\\run-gui.bat ( echo ✓ run-gui.bat - 存在 ) else ( echo ✗ run-gui.bat - 不存在 ) if exist target\\README-GUI.txt ( echo ✓ README-GUI.txt - 存在 ) else ( echo ✗ README-GUI.txt - 不存在 ) echo. echo [Web 应用] if exist target\\slms-web.war ( for %%F in (target\\slms-web.war) do ( set /a SIZE_MB=%%~zF/1048576 echo ✓ slms-web.war - !SIZE_MB! MB ) ) else ( echo ✗ slms-web.war - 不存在 ) echo. echo [Android 应用] if exist android\\build\\outputs\\apk\\debug\\slms-debug.apk ( for %%F in (android\\build\\outputs\\apk\\debug\\slms-debug.apk) do ( set /a SIZE_MB=%%~zF/1048576 echo ✓ slms-debug.apk - !SIZE_MB! MB ) ) else ( if exist android\\build\\outputs\\apk\\debug\\SLMS-debug.apk ( for %%F in (android\\build\\outputs\\apk\\debug\\SLMS-debug.apk) do ( set /a SIZE_MB=%%~zF/1048576 echo ✓ SLMS-debug.apk - !SIZE_MB! MB ) ) else ( echo ✗ APK - 不存在 ) ) echo. echo [数据库] if exist target\\library.db ( for %%F in (target\\library.db) do ( set /a SIZE_KB=%%~zF/1024 echo ✓ library.db - !SIZE_KB! KB ) ) else ( echo ✗ library.db - 不存在 ) echo. echo ============================================ echo 重命名阶段完成 echo ============================================ REM 不因警告而失败构建 exit /b 0 ''' } echo '✓ 制品重命名和验证完成' } } stage('7.6 创建Windows安装包 (jpackage)') { when { expression { currentBuild.result == null || currentBuild.result == 'SUCCESS' || currentBuild.result == 'UNSTABLE' } } steps { echo '========== 使用 jpackage 创建 EXE 和 MSI ==========' script { try { bat ''' @echo off setlocal EnableDelayedExpansion set JAVA_HOME=%JAVA_HOME% REM 使用短路径避免 LocalSystem 权限导致的 "此时不应有" 错误 for %%P in ("C:\\Program Files (x86)\\WiX Toolset v3.11") do set "WIX_HOME=%%~sP" set "PATH=%WIX_HOME%\\bin;%PATH%" echo ============================================ echo jpackage 打包阶段 echo ============================================ echo. REM 检查 jpackage echo [1/6] 检查 jpackage 工具... jpackage --version >nul 2>&1 if errorlevel 1 ( echo ✗ jpackage 不可用 echo 需要 JDK 14+ 才能使用 jpackage exit /b 1 ) for /f "tokens=*" %%i in ('jpackage --version') do set JPACKAGE_VER=%%i echo ✓ jpackage 可用: !JPACKAGE_VER! echo. REM 检查 WiX Toolset echo [2/6] 检查 WiX Toolset... set "WIX_AVAILABLE=0" if exist "%WIX_HOME%\\bin\\candle.exe" ( echo ✓ WiX Toolset 已安装: %WIX_HOME% set "WIX_AVAILABLE=1" ) else ( echo ⚠️ WiX Toolset 未找到,跳过 EXE/MSI,改用 ZIP 绿色版 ) echo. REM 准备 jpackage 输入目录 echo [3/6] 准备 jpackage 输入目录... if exist target\\jpackage-input rmdir /S /Q target\\jpackage-input mkdir target\\jpackage-input if exist target\\slms-gui.jar ( copy /Y target\\slms-gui.jar target\\jpackage-input\\ >nul echo ✓ 复制 slms-gui.jar ) else ( echo ✗ 找不到 slms-gui.jar exit /b 1 ) if exist library.db ( copy /Y library.db target\\jpackage-input\\ >nul echo ✓ 复制 library.db ) echo. REM 创建 app-image (包含 JRE 的应用目录) echo [4/6] 创建 app-image... jpackage ^ --type app-image ^ --input target\\jpackage-input ^ --name SLMS ^ --main-jar slms-gui.jar ^ --main-class com.smartlibrary.gui.SimpleGUIApplication ^ --app-version 1.0 ^ --vendor "CHZU" ^ --description "Smart Library Management System" ^ --dest target\\jpackage-output if errorlevel 1 ( echo ✗ app-image 创建失败 exit /b 1 ) echo ✓ app-image 创建成功 echo. if "%WIX_AVAILABLE%"=="1" ( REM 创建 EXE 安装包 echo [5/6] 创建 EXE 安装包... jpackage ^ --type exe ^ --app-image target\\jpackage-output\\SLMS ^ --name SLMS ^ --app-version 1.0 ^ --vendor "CHZU" ^ --description "Smart Library Management System" ^ --win-menu ^ --win-dir-chooser ^ --win-shortcut ^ --dest target if errorlevel 1 ( echo ⚠️ EXE 安装包创建失败 ^(可能缺少 Inno Setup^) ) else ( REM 重命名生成的 EXE if exist target\\SLMS-1.0.exe ( move /Y target\\SLMS-1.0.exe target\\slms-gui.exe >nul for %%F in (target\\slms-gui.exe) do ( set /a SIZE_MB=%%~zF/1048576 echo ✓ EXE 安装包创建成功: slms-gui.exe ^(!SIZE_MB! MB^) ) ) else ( echo ⚠️ EXE 文件生成了但未找到预期文件名,列出 target 目录: dir target\\*.exe ) ) echo. REM 创建 MSI 安装包 echo [6/6] 创建 MSI 安装包... jpackage ^ --type msi ^ --app-image target\\jpackage-output\\SLMS ^ --name SLMS ^ --app-version 1.0 ^ --vendor "CHZU" ^ --description "Smart Library Management System" ^ --win-menu ^ --win-dir-chooser ^ --win-shortcut ^ --dest target if errorlevel 1 ( echo ⚠️ MSI 创建失败,将保留 EXE/ZIP ) else ( REM 重命名 MSI if exist target\\SLMS-1.0.msi ( move /Y target\\SLMS-1.0.msi target\\slms-gui-installer.msi >nul for %%F in (target\\slms-gui-installer.msi) do ( set /a SIZE_MB=%%~zF/1048576 echo ✓ MSI 创建成功: slms-gui-installer.msi ^(!SIZE_MB! MB^) ) ) else ( echo ⚠️ MSI 文件未找到,检查其他可能的文件名... dir target\\*.msi ) ) echo. ) else ( echo [5/6] WiX 不可用,跳过 EXE/MSI,创建 ZIP 绿色版... ) if %WIX_AVAILABLE%==0 ( powershell -Command "Compress-Archive -Path target\\jpackage-output\\SLMS -DestinationPath target\\slms-gui.zip -Force" if exist target\\slms-gui.zip ( echo ✓ ZIP 绿色版创建成功: slms-gui.zip ) else ( echo ✗ ZIP 创建失败,请检查 jpackage 输出 ) ) echo. REM 创建 README 文件 echo 创建 EXE 和 MSI 说明文档... echo SLMS GUI Application - EXE Version > target\\README-GUI-EXE.txt echo. >> target\\README-GUI-EXE.txt echo 运行方式: >> target\\README-GUI-EXE.txt echo 双击 slms-gui.exe 即可运行 >> target\\README-GUI-EXE.txt echo. >> target\\README-GUI-EXE.txt echo 特点: >> target\\README-GUI-EXE.txt echo - 包含 Java 运行时,无需安装 Java >> target\\README-GUI-EXE.txt echo - Windows 原生可执行文件 >> target\\README-GUI-EXE.txt echo - 文件大小约 50-80 MB >> target\\README-GUI-EXE.txt echo. >> target\\README-GUI-EXE.txt echo 系统要求: >> target\\README-GUI-EXE.txt echo - Windows 10 或更高版本 >> target\\README-GUI-EXE.txt echo. >> target\\README-GUI-EXE.txt echo SLMS GUI Application - MSI Installer > target\\README-GUI-MSI.txt echo. >> target\\README-GUI-MSI.txt echo 安装方式: >> target\\README-GUI-MSI.txt echo 1. 双击 slms-gui-installer.msi >> target\\README-GUI-MSI.txt echo 2. 按照安装向导完成安装 >> target\\README-GUI-MSI.txt echo 3. 从开始菜单或桌面快捷方式启动 >> target\\README-GUI-MSI.txt echo. >> target\\README-GUI-MSI.txt echo 特点: >> target\\README-GUI-MSI.txt echo - 标准 Windows 安装程序 >> target\\README-GUI-MSI.txt echo - 自动创建开始菜单快捷方式 >> target\\README-GUI-MSI.txt echo - 可选择安装目录 >> target\\README-GUI-MSI.txt echo - 支持卸载 >> target\\README-GUI-MSI.txt echo. >> target\\README-GUI-MSI.txt echo 系统要求: >> target\\README-GUI-MSI.txt echo - Windows 10 或更高版本 >> target\\README-GUI-MSI.txt echo. >> target\\README-GUI-MSI.txt echo ✓ 说明文档创建完成 echo. echo ============================================ echo jpackage 打包完成 echo ============================================ echo. echo 生成的文件: if exist target\\slms-gui.exe echo ✓ slms-gui.exe if exist target\\slms-gui-installer.msi echo ✓ slms-gui-installer.msi if exist target\\README-GUI-EXE.txt echo ✓ README-GUI-EXE.txt if exist target\\README-GUI-MSI.txt echo ✓ README-GUI-MSI.txt echo. ''' echo '✓ Windows 安装包创建成功' } catch (Exception e) { echo '⚠️ jpackage 打包失败,但继续构建' echo "错误信息: ${e.message}" currentBuild.result = 'UNSTABLE' } } } } stage('8. 归档制品') { when { expression { currentBuild.result == null || currentBuild.result == 'SUCCESS' || currentBuild.result == 'UNSTABLE' } } steps { echo '========== 归档构建制品 ==========' script { // 显示当前工作目录 bat ''' @echo off echo 当前工作目录: cd echo. echo 列出 target 目录: dir target\\*.jar 2>nul || echo 没有找到 JAR 文件 echo. echo 列出 android/build/outputs/apk/debug 目录: dir android\\build\\outputs\\apk\\debug\\*.apk 2>nul || echo 没有找到 APK 文件 echo. ''' // 检查文件是否存在 bat ''' @echo off echo 检查制品文件... echo. echo [CLI 应用] if exist target\\slms-cli.jar ( echo ✓ 找到 slms-cli.jar for %%F in (target\\slms-cli.jar) do echo 大小: %%~zF bytes ) else ( echo ✗ 未找到 slms-cli.jar ) echo. echo [GUI 应用 - Swing 版本] if exist target\\slms-gui.jar ( echo ✓ 找到 slms-gui.jar for %%F in (target\\slms-gui.jar) do echo 大小: %%~zF bytes ) else ( echo ✗ 未找到 slms-gui.jar ) if exist target\\run-gui.bat ( echo ✓ 找到 run-gui.bat ) else ( echo ⚠️ 未找到 run-gui.bat ) if exist target\\README-GUI.txt ( echo ✓ 找到 README-GUI.txt ) else ( echo ⚠️ 未找到 README-GUI.txt ) echo. echo [Web 应用] if exist target\\slms-web.war ( echo ✓ 找到 slms-web.war for %%F in (target\\slms-web.war) do echo 大小: %%~zF bytes ) else ( if exist target\\slms-web.jar ( echo ✓ 找到 slms-web.jar for %%F in (target\\slms-web.jar) do echo 大小: %%~zF bytes ) else ( echo ⚠️ 警告: 找不到 slms-web.war 或 slms-web.jar ) ) echo. echo [Android 应用] if exist android\\build\\outputs\\apk\\debug\\slms-debug.apk ( echo ✓ 找到 slms-debug.apk for %%F in (android\\build\\outputs\\apk\\debug\\slms-debug.apk) do echo 大小: %%~zF bytes ) else ( if exist android\\build\\outputs\\apk\\debug\\SLMS-debug.apk ( echo ✓ 找到 SLMS-debug.apk for %%F in (android\\build\\outputs\\apk\\debug\\SLMS-debug.apk) do echo 大小: %%~zF bytes ) else ( echo ⚠️ 警告: 找不到 slms-debug.apk ) ) echo. echo [数据库文件] if exist target\\library.db ( echo ✓ 找到 library.db ) else ( echo ⚠️ 未找到 library.db ) exit /b 0 ''' // 归档制品 - 使用 try-catch 确保即使部分文件缺失也能继续 try { echo '开始归档制品...' // 归档所有制品: JAR, WAR, EXE, MSI, ZIP, APK archiveArtifacts artifacts: 'target/slms-*.jar,target/slms-*.war,target/slms-*.exe,target/slms-*.msi,target/slms-*.zip,target/*.bat,target/README-*.txt,target/library.db,android/build/outputs/apk/debug/*.apk', fingerprint: true, allowEmptyArchive: true, onlyIfSuccessful: false echo '✓ 制品归档完成' // Stash 制品供后续阶段使用 echo '保存制品到 stash...' stash includes: 'target/slms-*.jar,target/slms-*.war,target/slms-*.exe,target/slms-*.msi,target/slms-*.zip,target/*.bat,target/README-*.txt,target/library.db,android/build/outputs/apk/debug/*.apk', name: 'build-artifacts', allowEmpty: true echo '✓ 制品已保存到 stash' } catch (Exception e) { echo "⚠️ 制品归档时出现问题: ${e.message}" echo "继续执行流水线..." } } } } stage('9. 推送头歌') { when { expression { currentBuild.result == null || currentBuild.result == 'SUCCESS' || currentBuild.result == 'UNSTABLE' } } parallel { stage('9.1 推送源代码到 main') { steps { echo '========== 推送源代码到头歌 main 分支 ==========' script { try { withCredentials([usernamePassword( credentialsId: 'educoder-credentials', usernameVariable: 'EDUCODER_USER', passwordVariable: 'EDUCODER_PASS' )]) { bat ''' @echo off setlocal EnableExtensions EnableDelayedExpansion REM URL编码用户名和密码 for /f %%i in ('powershell -NoLogo -NoProfile -Command "[Console]::Out.Write([uri]::EscapeDataString($env:EDUCODER_USER))"') do set USER_ENC=%%i for /f %%i in ('powershell -NoLogo -NoProfile -Command "[Console]::Out.Write([uri]::EscapeDataString($env:EDUCODER_PASS))"') do set PASS_ENC=%%i git config user.name "Jenkins CI" git config user.email "ldl@chzu.edu.cn" git remote remove educoder 2>nul || echo Remote educoder does not exist git remote add educoder https://bdgit.educoder.net/pu6zrsfoy/slms.git git fetch --unshallow 2>nul || echo Already a complete repository git push https://%USER_ENC%:%PASS_ENC%@bdgit.educoder.net/pu6zrsfoy/slms.git HEAD:refs/heads/main --force ''' } echo '✓ 源代码推送到 main 成功' } catch (Exception e) { echo '⚠ 推送代码到 main 失败' echo "错误信息: ${e.message}" currentBuild.result = 'UNSTABLE' } } } } stage('9.2 推送制品到 release') { steps { echo '========== 推送构建制品到头歌 release 分支 ==========' script { try { // Unstash 制品 echo '恢复制品从 stash...' unstash 'build-artifacts' echo '✓ 制品已恢复' withCredentials([usernamePassword( credentialsId: 'educoder-credentials', usernameVariable: 'EDUCODER_USER', passwordVariable: 'EDUCODER_PASS' )]) { bat ''' @echo off setlocal EnableExtensions EnableDelayedExpansion REM URL编码用户名和密码 for /f %%i in ('powershell -NoLogo -NoProfile -Command "[Console]::Out.Write([uri]::EscapeDataString($env:EDUCODER_USER))"') do set USER_ENC=%%i for /f %%i in ('powershell -NoLogo -NoProfile -Command "[Console]::Out.Write([uri]::EscapeDataString($env:EDUCODER_PASS))"') do set PASS_ENC=%%i set "VERSION_TAG=1-0-0-%BUILD_NUMBER%" REM 创建 artifacts 目录 if not exist artifacts mkdir artifacts echo 复制制品到 artifacts 目录... if exist target\slms-cli.jar copy /Y target\slms-cli.jar artifacts\slms-cli-!VERSION_TAG!.jar >nul if exist target\slms-gui.jar copy /Y target\slms-gui.jar artifacts\slms-gui-!VERSION_TAG!.jar >nul if exist target\slms-gui.exe copy /Y target\slms-gui.exe artifacts\slms-gui-!VERSION_TAG!.exe >nul if exist target\slms-gui-installer.msi copy /Y target\slms-gui-installer.msi artifacts\slms-gui-installer-!VERSION_TAG!.msi >nul if exist target\slms-gui.zip copy /Y target\slms-gui.zip artifacts\slms-gui-!VERSION_TAG!.zip >nul if exist target\slms-web.war copy /Y target\slms-web.war artifacts\slms-web-!VERSION_TAG!.war >nul if exist android\build\outputs\apk\debug\slms-debug.apk copy /Y android\build\outputs\apk\debug\slms-debug.apk artifacts\slms-debug-!VERSION_TAG!.apk >nul if exist target\library.db copy /Y target\library.db artifacts\library-!VERSION_TAG!.db >nul if exist target\run-gui.bat copy /Y target\run-gui.bat artifacts\run-gui-!VERSION_TAG!.bat >nul if exist target\README-GUI.txt copy /Y target\README-GUI.txt artifacts\README-GUI.txt >nul if exist target\README-GUI-EXE.txt copy /Y target\README-GUI-EXE.txt artifacts\README-GUI-EXE.txt >nul if exist target\README-GUI-MSI.txt copy /Y target\README-GUI-MSI.txt artifacts\README-GUI-MSI.txt >nul if exist target\slms-gui.exe copy /Y target\slms-gui.exe artifacts\slms-gui.exe >nul if exist target\slms-gui-installer.msi copy /Y target\slms-gui-installer.msi artifacts\slms-gui-installer.msi >nul if exist target\SLMS-v1.0.0.zip copy /Y target\SLMS-v1.0.0.zip artifacts\SLMS-v1.0.0.zip >nul REM 创建 orphan 分支,只推送制品 git checkout --orphan release-%BUILD_NUMBER% git rm -rf . --ignore-unmatch >nul 2>&1 git add artifacts/ git commit -m "release: 构建制品 Build #%BUILD_NUMBER%" echo 推送制品到头歌 release 分支... git push https://%USER_ENC%:%PASS_ENC%@bdgit.educoder.net/pu6zrsfoy/slms.git HEAD:refs/heads/release --force REM 切回 main 分支 git checkout main echo ✓ release 分支已更新制品 ''' } echo '✓ 制品推送到 release 分支成功' } catch (Exception e) { echo '⚠ 推送制品到 release 失败' echo "错误信息: ${e.message}" currentBuild.result = 'UNSTABLE' } } } } } } } post { always { script { echo '========== 流水线执行完成 ==========' echo "当前构建结果: ${currentBuild.result}" echo "当前构建状态: ${currentBuild.currentResult}" // 清理残留的 Java 进程 echo '========== 清理残留进程 ==========' try { bat ''' @echo off echo 清理与 slms 相关的残留进程... for /f "tokens=2" %%i in ('tasklist /FI "IMAGENAME eq java.exe" /FO LIST ^| findstr /C:"PID:"') do ( wmic process where "ProcessId=%%i and CommandLine like '%%slms%%'" delete 2>nul ) echo ✓ 进程清理完成 ''' } catch (Exception e) { echo "⚠️ 清理进程时出错: ${e.message}" } // 检查制品是否存在 echo '========== 检查制品文件 ==========' try { bat ''' @echo off if exist target\\slms-cli.jar (echo ✓ slms-cli.jar 存在) else (echo ✗ slms-cli.jar 不存在) if exist target\\slms-gui.jar (echo ✓ slms-gui.jar 存在) else (echo ✗ slms-gui.jar 不存在) if exist target\\slms-web.war ( echo ✓ slms-web.war 存在 ) else ( if exist target\\slms-web.jar ( echo ✓ slms-web.jar 存在 ) else ( echo ✗ slms-web.war/jar 不存在 ) ) if exist android\\build\\outputs\\apk\\debug\\slms-debug.apk (echo ✓ slms-debug.apk 存在) else (echo ✗ slms-debug.apk 不存在) exit /b 0 ''' } catch (Exception e) { echo "⚠️ 检查制品文件时出错: ${e.message}" } // 发送邮件 - 使用最简单的方式 echo '========== 发送邮件通知 ==========' def finalResult = currentBuild.result ?: 'SUCCESS' def emailSubject = "" def emailStatus = "" if (finalResult == 'SUCCESS') { emailSubject = "✅ SLMS 构建成功 - Build #${BUILD_NUMBER}" emailStatus = "成功" } else if (finalResult == 'FAILURE') { emailSubject = "❌ SLMS 构建失败 - Build #${BUILD_NUMBER}" emailStatus = "失败" } else if (finalResult == 'UNSTABLE') { emailSubject = "⚠️ SLMS 构建不稳定 - Build #${BUILD_NUMBER}" emailStatus = "不稳定" } else { emailSubject = "ℹ️ SLMS 构建完成 - Build #${BUILD_NUMBER}" emailStatus = "完成" } echo "准备发送邮件: ${emailSubject}" echo "收件人: 602924803@qq.com" try { mail to: '602924803@qq.com', subject: emailSubject, body: """SLMS 项目构建${emailStatus} 构建编号: #${BUILD_NUMBER} 构建状态: ${emailStatus} 构建时间: ${new Date(currentBuild.startTimeInMillis)} Git 提交: ${env.GIT_COMMIT} 查看构建详情: ${BUILD_URL} 查看构建日志: ${BUILD_URL}console Jenkins 制品列表: ${BUILD_URL}artifact/ SonarQube 报告: http://localhost:9000/dashboard?id=slms:slms Release 制品: https://bdgit.educoder.net/pu6zrsfoy/slms/tree/release/SLMS 此邮件由 Jenkins 自动发送 | Build #${BUILD_NUMBER} """ echo "✓ 邮件已发送到: 602924803@qq.com" echo "✓ 邮件主题: ${emailSubject}" } catch (Exception e) { echo "❌ 邮件发送失败!" echo "错误信息: ${e.message}" echo "错误类型: ${e.class.name}" echo "错误详情:" e.printStackTrace() } } } } }