You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
slms/Jenkinsfile

1233 lines
67 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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'
}
options {
skipDefaultCheckout()
buildDiscarder(logRotator(numToKeepStr: '10'))
disableConcurrentBuilds()
timestamps()
}
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. 准备打包') {
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('6. 四端并行打包') {
when {
expression { currentBuild.result == null || currentBuild.result == 'SUCCESS' || currentBuild.result == 'UNSTABLE' }
}
parallel {
stage('6.1 CLI 打包 (JAR)') {
steps {
echo '========== 打包 CLI 应用 (JAR) =========='
bat '''
@echo off
set JAVA_HOME=%JAVA_HOME%
echo [CLI] 开始打包...
REM 手动清理 CLI 输出目录,避免 maven-clean-plugin 的锁冲突
if exist target\\cli rmdir /S /Q target\\cli
REM 使用 CLI profile 打包,指定独立输出目录,跳过 WAR 生成
REM 注意:移除 clean 目标,避免多进程并发清理 target 导致的冲突
mvn "-Dcustom.build.directory=target/cli" package -Pcli -DskipTests -Dmaven.war.skip=true
echo [CLI] 检查生成的文件...
echo [CLI] 列出 target\\cli 目录所有 JAR 文件:
dir target\\cli\\*.jar 2>nul || echo [CLI] 目录中没有 JAR 文件
echo.
if exist target\\cli\\slms-1.0-SNAPSHOT-cli-shaded.jar (
echo [CLI] ✓ CLI shaded JAR 生成成功
for %%F in (target\\cli\\slms-1.0-SNAPSHOT-cli-shaded.jar) do echo [CLI] 大小: %%~zF bytes
) else (
echo [CLI] ⚠️ 警告: 找不到 slms-1.0-SNAPSHOT-cli-shaded.jar
echo [CLI] 尝试查找其他 shaded JAR...
dir target\\cli\\*shaded*.jar 2>nul || echo [CLI] 没有找到 shaded JAR
)
'''
echo '✓ CLI JAR 打包成功'
}
}
stage('6.2 GUI 打包 (Swing JAR)') {
steps {
echo '========== 打包 GUI Swing 应用 (独立 JAR) =========='
bat '''
@echo off
set JAVA_HOME=%JAVA_HOME%
echo [GUI] 开始打包...
REM 手动清理 GUI 输出目录
if exist target\\gui rmdir /S /Q target\\gui
REM 使用 gui-swing profile 生成独立 JAR指定独立输出目录跳过 WAR 生成
mvn "-Dcustom.build.directory=target/gui" package -Pgui-swing -DskipTests -Dmaven.war.skip=true
echo [GUI] 检查生成的文件...
echo [GUI] 列出 target\\gui 目录所有 JAR 文件:
dir target\\gui\\*.jar 2>nul || echo [GUI] 目录中没有 JAR 文件
echo.
if exist target\\gui\\slms-1.0-SNAPSHOT-gui-swing.jar (
echo [GUI] ✓ GUI Swing JAR 生成成功
for %%F in (target\\gui\\slms-1.0-SNAPSHOT-gui-swing.jar) do echo [GUI] 大小: %%~zF bytes
) else (
echo [GUI] ⚠️ 警告: 找不到 slms-1.0-SNAPSHOT-gui-swing.jar
echo [GUI] 尝试查找其他 JAR...
dir target\\gui\\*swing*.jar 2>nul || echo [GUI] 没有找到 swing JAR
)
'''
echo '✓ GUI Swing JAR 打包成功'
}
}
stage('6.3 Web 打包 (WAR)') {
steps {
echo '========== 打包 Web 应用 (WAR) =========='
bat '''
@echo off
set JAVA_HOME=%JAVA_HOME%
echo [Web] 开始打包...
REM 手动清理 Web 输出目录
if exist target\\web rmdir /S /Q target\\web
REM 使用 Web profile 打包,指定独立输出目录
mvn "-Dcustom.build.directory=target/web" package -Pweb -DskipTests
echo [Web] 检查生成的文件...
if exist target\\web\\slms-1.0-SNAPSHOT.war (
echo [Web] ✓ Web WAR 生成成功
for %%F in (target\\web\\slms-1.0-SNAPSHOT.war) do echo [Web] 大小: %%~zF bytes
) else (
if exist target\\web\\slms-1.0-SNAPSHOT.jar (
echo [Web] ✓ Web JAR 生成成功 (备用)
for %%F in (target\\web\\slms-1.0-SNAPSHOT.jar) do echo [Web] 大小: %%~zF bytes
) else (
echo [Web] ⚠️ 错误: 找不到 Web WAR/JAR
echo [Web] 检查 target\\web 目录:
dir target\\web\\*.war
dir target\\web\\*.jar
exit /b 1
)
)
'''
echo '✓ Web WAR 打包成功'
}
}
stage('6.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('6.5 重命名和验证制品') {
when {
expression { currentBuild.result == null || currentBuild.result == 'SUCCESS' || currentBuild.result == 'UNSTABLE' }
}
steps {
echo '========== 重命名和验证制品 =========='
script {
bat '''
@echo off
setlocal EnableDelayedExpansion
echo ============================================
echo 文件重命名和验证阶段
echo ============================================
echo.
REM ========== 1.1 CLI JAR 重命名 ==========
echo [1/4] 处理 CLI JAR... (target\\cli)
echo 列出 target\\cli 目录:
dir target\\cli\\*.jar 2>nul || echo 目录为空或不存在
set CLI_JAR_FOUND=0
REM 尝试多种可能的文件名
if exist target\\cli\\slms-1.0-SNAPSHOT-cli-shaded.jar (
echo ✓ 找到: slms-1.0-SNAPSHOT-cli-shaded.jar
copy /Y target\\cli\\slms-1.0-SNAPSHOT-cli-shaded.jar target\\slms-cli.jar >nul
set CLI_JAR_FOUND=1
) else if exist target\\cli\\slms-1.0-SNAPSHOT-shaded.jar (
echo ✓ 找到备选: slms-1.0-SNAPSHOT-shaded.jar
copy /Y target\\cli\\slms-1.0-SNAPSHOT-shaded.jar target\\slms-cli.jar >nul
set CLI_JAR_FOUND=1
) else if exist target\\cli\\slms-1.0-SNAPSHOT.jar (
echo ✓ 找到默认JAR: slms-1.0-SNAPSHOT.jar
copy /Y target\\cli\\slms-1.0-SNAPSHOT.jar target\\slms-cli.jar >nul
set CLI_JAR_FOUND=1
)
if !CLI_JAR_FOUND!==1 (
if exist target\\slms-cli.jar (
for %%F in (target\\slms-cli.jar) do (
set SIZE=%%~zF
set /a SIZE_MB=%%~zF/1048576
)
echo ✓ 重命名成功: slms-cli.jar ^(!SIZE_MB! MB^)
) else (
echo ✗ 错误: 重命名失败
)
) else (
echo ⚠️ 警告: 找不到任何 CLI JAR 文件
)
echo.
REM ========== 1.2 GUI JAR 重命名 ==========
echo [2/4] 处理 GUI JAR... (target\\gui)
echo 列出 target\\gui 目录:
dir target\\gui\\*.jar 2>nul || echo 目录为空或不存在
set GUI_JAR_FOUND=0
REM 尝试多种可能的文件名
if exist target\\gui\\slms-1.0-SNAPSHOT-gui-swing.jar (
echo ✓ 找到: slms-1.0-SNAPSHOT-gui-swing.jar
copy /Y target\\gui\\slms-1.0-SNAPSHOT-gui-swing.jar target\\slms-gui.jar >nul
set GUI_JAR_FOUND=1
) else if exist target\\gui\\slms-1.0-SNAPSHOT-shaded.jar (
echo ✓ 找到备选: slms-1.0-SNAPSHOT-shaded.jar
copy /Y target\\gui\\slms-1.0-SNAPSHOT-shaded.jar target\\slms-gui.jar >nul
set GUI_JAR_FOUND=1
) else if exist target\\gui\\slms-1.0-SNAPSHOT.jar (
echo ✓ 找到默认JAR: slms-1.0-SNAPSHOT.jar
copy /Y target\\gui\\slms-1.0-SNAPSHOT.jar target\\slms-gui.jar >nul
set GUI_JAR_FOUND=1
)
if !GUI_JAR_FOUND!==1 (
if exist target\\slms-gui.jar (
for %%F in (target\\slms-gui.jar) do (
set SIZE=%%~zF
set /a SIZE_MB=%%~zF/1048576
)
echo ✓ 重命名成功: slms-gui.jar ^(!SIZE_MB! 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 JAR 文件
)
echo.
REM ========== 1.3 Web WAR 重命名 ==========
echo [3/4] 处理 Web WAR... (target\\web)
if exist target\\web\\slms-1.0-SNAPSHOT.war (
echo ✓ 找到源文件: slms-1.0-SNAPSHOT.war
copy /Y target\\web\\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 源文件 (target\\web\\slms-1.0-SNAPSHOT.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('6.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('7. 归档制品') {
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 应用]
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
)
echo.
echo [Windows 安装包]
if exist "target\\slms-gui.exe" (
echo ✓ 找到 slms-gui.exe
for %%F in (target\\slms-gui.exe) do echo 大小: %%~zF bytes
) else (
echo ✗ 未找到 slms-gui.exe
)
if exist "target\\slms-gui-installer.msi" (
echo ✓ 找到 slms-gui-installer.msi
for %%F in (target\\slms-gui-installer.msi) do echo 大小: %%~zF bytes
) else (
echo ✗ 未找到 slms-gui-installer.msi
)
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 (
echo ⚠️ 警告: 找不到 slms-web.war
)
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 文件、数据库、文档、APK
archiveArtifacts artifacts: 'target/slms-*.jar,target/slms-*.war,target/slms-gui.exe,target/slms-gui-installer.msi,target/library.db,target/README-*.txt,target/run-*.bat,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-gui.exe,target/slms-gui-installer.msi,target/library.db,target/README-*.txt,target/run-*.bat,android/build/outputs/apk/debug/*.apk', name: 'build-artifacts', allowEmpty: true
echo '✓ 制品已保存到 stash'
} catch (Exception e) {
echo "⚠️ 制品归档时出现问题: ${e.message}"
echo "继续执行流水线..."
}
}
}
}
stage('8. 部署到 Tomcat 11') {
when {
expression { currentBuild.result == null || currentBuild.result == 'SUCCESS' || currentBuild.result == 'UNSTABLE' }
}
steps {
echo '========== 部署到本地 Tomcat 11 =========='
script {
// 恢复制品(处理重启构建情况)
echo '恢复制品从 stash...'
unstash 'build-artifacts'
echo '✓ 制品已恢复'
def tomcatHome = 'E:\\2025-2026\\GitAIOps\\tomcat'
def tomcatWebapps = "${tomcatHome}\\webapps"
def tomcatBin = "${tomcatHome}\\bin"
def appName = 'slms'
try {
// 防止 Jenkins 杀死衍生进程
env.BUILD_ID = "dontKillMe"
bat """
@echo off
echo 正在检查端口 8080 占用...
for /f "tokens=5" %%a in ('netstat -ano ^| findstr :8080') do (
if not "%%a"=="0" (
echo 发现占用端口 8080 的进程 PID: %%a
taskkill /F /PID %%a 2>nul
)
)
echo 停止 Tomcat...
if exist "${tomcatBin}\\shutdown.bat" call "${tomcatBin}\\shutdown.bat" 2>nul
echo 清理旧应用...
if exist "${tomcatWebapps}\\${appName}.war" del /F /Q "${tomcatWebapps}\\${appName}.war"
if exist "${tomcatWebapps}\\${appName}" rmdir /S /Q "${tomcatWebapps}\\${appName}"
echo 部署新 WAR 包...
if exist "target\\slms-web.war" (
copy /Y "target\\slms-web.war" "${tomcatWebapps}\\${appName}.war"
echo ✓ 成功部署 WAR 包: target\\slms-web.war
) else (
echo ✗ 错误: 找不到 WAR 文件 (target\\slms-web.war)
echo 正在检查其他可能的 WAR 文件位置...
if exist "target\\web\\slms-1.0-SNAPSHOT.war" (
copy /Y "target\\web\\slms-1.0-SNAPSHOT.war" "${tomcatWebapps}\\${appName}.war"
echo ✓ 成功部署 WAR 包从 target\\web\\ 目录
) else (
echo ✗ 错误: 找不到任何 WAR 文件
exit /b 1
)
)
echo 更新数据库文件...
if exist "target\\library.db" (
copy /Y "target\\library.db" "${tomcatBin}\\library.db"
echo ✓ 成功复制数据库文件
) else (
if exist "library.db" (
copy /Y "library.db" "${tomcatBin}\\library.db"
echo ✓ 成功复制数据库文件从根目录
) else (
echo ⚠️ 警告: 找不到 library.db 文件,应用可能无法正常工作
)
)
echo 启动 Tomcat 11...
set CATALINA_HOME=${tomcatHome}
REM 检查 WAR 包内容 (调试用)
echo [Debug] 检查 WAR 包中的 WebApplication 类...
jar tf "${tomcatWebapps}\\${appName}.war" | findstr "WebApplication.class"
REM 使用 catalina.bat run 启动并重定向日志
REM 移除可能引起 CMD 解析错误的中文注释
start "Tomcat" /B "${tomcatBin}\\catalina.bat" run > "${tomcatHome}\\logs\\jenkins-tomcat.log" 2>&1
"""
echo '等待 Tomcat 启动 (60秒)...'
sleep 60
echo '验证应用访问...'
bat """
@echo off
powershell -NoLogo -NoProfile -Command "try { \$response = Invoke-WebRequest -Uri 'http://localhost:8080/${appName}/' -UseBasicParsing; if (\$response.StatusCode -eq 200) { Write-Host '✓ 部署成功,应用可访问'; exit 0 } else { Write-Host '✗ 访问失败,状态码: ' \$response.StatusCode; exit 1 } } catch { Write-Host '✗ 无法访问应用: ' \$_.Exception.Message; exit 1 }"
"""
} catch (Exception e) {
echo "⚠️ Tomcat 部署或验证失败: ${e.message}"
echo "读取 Tomcat 启动日志 (jenkins-tomcat.log)..."
bat """
@echo off
powershell -NoLogo -NoProfile -Command "if (Test-Path '${tomcatHome}\\logs\\jenkins-tomcat.log') { Get-Content '${tomcatHome}\\logs\\jenkins-tomcat.log' -Tail 500 } else { Write-Host '找不到 jenkins-tomcat.log' }"
"""
currentBuild.result = 'UNSTABLE'
}
}
}
}
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
REM 获取当前提交的 SHA
for /f %%i in ('git rev-parse HEAD') do set CURRENT_SHA=%%i
echo 当前提交 SHA: !CURRENT_SHA!
REM 使用 SHA 推送到 main 分支
git push https://%USER_ENC%:%PASS_ENC%@bdgit.educoder.net/pu6zrsfoy/slms.git !CURRENT_SHA!: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 {
def releaseVersion = "v1.0.0.${env.BUILD_NUMBER}"
env.RELEASE_ARTIFACT_VERSION = releaseVersion
echo "本次 release 制品版本: ${releaseVersion}"
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=v1.0.0.%BUILD_NUMBER%"
set "ARTIFACTS_ROOT=artifacts"
set "ARTIFACTS_DIR=%ARTIFACTS_ROOT%\\\\!VERSION_TAG!"
REM 创建 artifacts 目录
if not exist "%ARTIFACTS_ROOT%" mkdir "%ARTIFACTS_ROOT%"
if exist "!ARTIFACTS_DIR!" rmdir /S /Q "!ARTIFACTS_DIR!"
mkdir "!ARTIFACTS_DIR!"
echo 复制制品到 !ARTIFACTS_DIR! 目录...
if exist target\\\\slms-cli.war copy /Y target\\\\slms-cli.war "!ARTIFACTS_DIR!\\\\slms-cli-!VERSION_TAG!.war" >nul
if exist target\\\\slms-gui.war copy /Y target\\\\slms-gui.war "!ARTIFACTS_DIR!\\\\slms-gui-!VERSION_TAG!.war" >nul
if exist target\\\\slms-web.war copy /Y target\\\\slms-web.war "!ARTIFACTS_DIR!\\\\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_DIR!\\\\slms-debug-!VERSION_TAG!.apk" >nul
if exist target\\\\library.db copy /Y target\\\\library.db "!ARTIFACTS_DIR!\\\\library-!VERSION_TAG!.db" >nul
set "MANIFEST_FILE=!ARTIFACTS_DIR!\\\\manifest-!VERSION_TAG!.txt"
echo SLMS Release Manifest - !VERSION_TAG! > "!MANIFEST_FILE!"
echo Build ID: %BUILD_NUMBER% >> "!MANIFEST_FILE!"
echo 发布时间: %date% %time% >> "!MANIFEST_FILE!"
echo. >> "!MANIFEST_FILE!"
echo 制品列表: >> "!MANIFEST_FILE!"
for %%F in (!ARTIFACTS_DIR!\\\\*) do echo %%~nxF >> "!MANIFEST_FILE!"
echo ✓ 制品清单已生成: !MANIFEST_FILE!
echo 当前制品目录文件列表:
dir /B "!ARTIFACTS_DIR!"
REM 创建 orphan 分支,只推送制品
git checkout --orphan release-%BUILD_NUMBER%
git rm -rf . --ignore-unmatch >nul 2>&1
git add artifacts/
git commit -m "release: 发布 !VERSION_TAG! (Build #%BUILD_NUMBER%)"
echo 推送制品到头歌 release 分支: !VERSION_TAG! ...
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 分支已更新制品: !VERSION_TAG!
"""
}
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.war (echo ✓ slms-cli.war 存在) else (echo ✗ slms-cli.war 不存在)
if exist target\\slms-gui.war (echo ✓ slms-gui.war 存在) else (echo ✗ slms-gui.war 不存在)
if exist target\\slms-web.war (
echo ✓ slms-web.war 存在
) else (
echo ✗ slms-web.war 不存在
)
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 = ""
def releaseVersion = env.RELEASE_ARTIFACT_VERSION ?: "v1.0.0.${BUILD_NUMBER}"
if (finalResult == 'SUCCESS') {
emailSubject = "✅ SLMS 构建成功 - ${releaseVersion} (Build #${BUILD_NUMBER})"
emailStatus = "成功"
} else if (finalResult == 'FAILURE') {
emailSubject = "❌ SLMS 构建失败 - ${releaseVersion} (Build #${BUILD_NUMBER})"
emailStatus = "失败"
} else if (finalResult == 'UNSTABLE') {
emailSubject = "⚠️ SLMS 构建不稳定 - ${releaseVersion} (Build #${BUILD_NUMBER})"
emailStatus = "不稳定"
} else {
emailSubject = " SLMS 构建完成 - ${releaseVersion} (Build #${BUILD_NUMBER})"
emailStatus = "完成"
}
echo "准备发送邮件: ${emailSubject}"
echo "收件人: 602924803@qq.com"
try {
mail to: '602924803@qq.com',
subject: emailSubject,
body: """SLMS 项目构建${emailStatus}
构建编号: #${BUILD_NUMBER}
Release 版本: ${releaseVersion}
构建状态: ${emailStatus}
构建时间: ${new Date(currentBuild.startTimeInMillis)}
Git 提交: ${env.GIT_COMMIT}
制品列表:
- slms-cli.war (命令行版本)
- slms-gui.war (图形界面版本)
- slms-web.war (Web应用版本)
- slms-debug.apk (Android版本)
- library.db (数据库文件)
查看构建详情: ${BUILD_URL}
查看构建日志: ${BUILD_URL}console
Jenkins 制品列表: ${BUILD_URL}artifact/
SonarQube 报告: http://localhost:9000/dashboard?id=slms:slms
Release 制品: https://code.educoder.net/pu6zrsfoy/slms/tree/release/artifacts/${releaseVersion}
此邮件由 Jenkins 自动发送 | Build #${BUILD_NUMBER}
"""
echo "✓ 邮件已发送到: 602924803@qq.com"
echo "✓ 邮件主题: ${emailSubject}"
} catch (Exception e) {
echo "❌ 邮件发送失败!"
echo "错误信息: ${e.message}"
echo "错误类型: ${e.class.name}"
echo "错误详情:"
e.printStackTrace()
}
}
}
}
}