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.

791 lines
41 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.

/**
* 智能图书管理系统 (MCSLMS) - 单分支流水线
* 四端统一构建CLI、GUI、Web、Android
* 含单元测试、SonarQube 质量扫描、jpackage EXE、头歌平台发布
*/
pipeline {
agent any
// 触发方式Gitea Webhook 推送触发 (Generic Webhook Trigger)
// Gitea 配置:仓库 -> 设置 -> Webhooks -> http://localhost:8084/generic-webhook-trigger/invoke?token=mcslms
// Jenkins 配置Token=mcslms, Token Credential=空
options {
// 只保留最近5次构建制品也只保留5次
buildDiscarder(logRotator(numToKeepStr: '5', artifactNumToKeepStr: '3'))
timestamps()
timeout(time: 60, unit: 'MINUTES')
disableConcurrentBuilds()
}
environment {
JAVA_HOME = 'E:\\2025-2026\\GitAIOps\\jdk'
ANDROID_HOME = 'D:/development/Android'
SONAR_HOST_URL = 'http://localhost:9000'
SONAR_PROJECT_KEY = 'mcslms'
SONAR_PROJECT_NAME = 'mcslms'
RELEASE_VERSION = "v1.80.0.${BUILD_NUMBER}"
}
stages {
stage('1. 环境信息') {
steps {
echo '=========================================='
echo ' 智能图书管理系统 (MCSLMS) - 四端统一构建'
echo '=========================================='
echo "构建号: ${env.BUILD_NUMBER}"
echo "版本号: ${env.RELEASE_VERSION}"
bat 'java -version'
}
}
stage('2. 构建所有模块') {
steps {
echo '>>> 构建所有模块 (Core, CLI, GUI, Backend, Launcher)'
bat 'gradlew.bat clean build -x test --no-daemon'
}
}
stage('3. 运行单元测试') {
steps {
echo '>>> 运行单元测试并生成覆盖率报告'
script {
try {
// 运行测试并生成JaCoCo覆盖率报告测试会自动初始化数据
bat 'gradlew.bat test jacocoTestReport --no-daemon'
echo '✓ 测试执行完成,覆盖率报告已生成'
} catch (Exception e) {
echo "⚠️ 测试失败,但继续流水线: ${e.message}"
currentBuild.result = 'UNSTABLE'
}
}
}
post {
always {
junit allowEmptyResults: true, testResults: '**/build/test-results/test/*.xml'
publishHTML(target: [
allowMissing: true,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'core/build/reports/tests/test',
reportFiles: 'index.html',
reportName: 'Core 测试报告'
])
// 发布JaCoCo覆盖率报告
publishHTML(target: [
allowMissing: true,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'core/build/reports/jacoco/test/html',
reportFiles: 'index.html',
reportName: 'Core 覆盖率报告'
])
}
}
}
stage('4. SonarQube 质量扫描') {
steps {
echo '>>> 执行 SonarQube 代码质量检测'
// 确保编译输出存在(测试阶段可能清理了)
bat 'gradlew.bat :core:classes :cli:classes :gui:classes :backend:classes -x test --no-daemon'
withSonarQubeEnv('SonarQube') {
bat '''
gradlew.bat sonar ^
-Dsonar.projectKey=%SONAR_PROJECT_KEY% ^
-Dsonar.projectName=%SONAR_PROJECT_NAME% ^
-Dsonar.host.url=%SONAR_HOST_URL% ^
-Dsonar.gradle.skipCompile=true ^
--no-daemon
'''
}
echo '✓ SonarQube 分析完成'
}
}
// 添加质量门禁检查阶段(非阻塞,不影响构建状态)
stage('5. 质量门禁检查') {
steps {
echo '>>> 检查 SonarQube 质量门禁状态(非阻塞模式)'
script {
try {
timeout(time: 2, unit: 'MINUTES') {
def qg = waitForQualityGate abortPipeline: false
if (qg.status == 'OK') {
echo '✓ 质量门禁检查通过'
} else {
echo " 质量门禁状态: ${qg.status},不影响构建结果"
}
}
} catch (Exception e) {
echo " 质量门禁检查跳过: ${e.message}"
echo " SonarQube 分析结果可在 SonarQube 控制台查看"
}
}
}
}
stage('6. 清理旧制品') {
steps {
echo '>>> 清理旧版本制品,只保留当前构建'
bat '''
@echo off
echo 清理 CLI 旧制品...
del /Q cli\\build\\libs\\*.jar 2>nul
echo 清理 GUI 旧制品...
del /Q gui\\build\\libs\\*.jar 2>nul
echo 清理 Backend 旧制品...
del /Q backend\\build\\libs\\*.jar 2>nul
del /Q backend\\build\\libs\\*.war 2>nul
echo 清理 artifacts 目录...
if exist artifacts rmdir /S /Q artifacts
mkdir artifacts
echo ✓ 旧制品清理完成
'''
}
}
stage('7. 构建四端') {
parallel {
stage('CLI') {
steps {
echo '>>> [2/5] 构建 CLI 命令行端 (fatJar)'
bat 'gradlew.bat :cli:fatJar -x test -PbuildNumber=%BUILD_NUMBER% --no-daemon'
}
}
stage('GUI') {
steps {
echo '>>> [3/5] 构建 GUI 桌面端 (fatJar)'
bat 'gradlew.bat :gui:fatJar -x test -PbuildNumber=%BUILD_NUMBER% --no-daemon'
}
}
stage('Backend JAR') {
steps {
echo '>>> [4/5] 构建 Web 后端 (bootJar)'
bat 'gradlew.bat :backend:bootJar -x test -PbuildNumber=%BUILD_NUMBER% --no-daemon'
}
}
stage('Backend WAR') {
steps {
echo '>>> [4/5] 构建 Web 后端 (bootWar)'
bat 'gradlew.bat :backend:bootWar -x test -PbuildNumber=%BUILD_NUMBER% --no-daemon'
}
}
stage('Android') {
steps {
echo '>>> [5/5] 构建 Android App'
bat 'gradlew.bat :android:assembleDebug --no-daemon'
}
}
stage('Launcher') {
steps {
echo '>>> [6/6] 构建 Launcher 启动器'
bat 'gradlew.bat :launcher:build :launcher:createExe --no-daemon'
// 确保EXE文件被复制到dist目录
bat 'gradlew.bat :launcher:copyExe --no-daemon'
}
}
}
}
stage('8. 创建 GUI 安装包 (jpackage)') {
steps {
echo '>>> 使用 jpackage 创建 Windows EXE 安装包'
script {
try {
bat '''
@echo off
setlocal EnableDelayedExpansion
set JAVA_HOME=%JAVA_HOME%
echo ============================================
echo jpackage 打包 GUI 应用
echo ============================================
REM 检查 jpackage
echo [1/4] 检查 jpackage...
jpackage --version >nul 2>&1
if errorlevel 1 (
echo ✗ jpackage 不可用,需要 JDK 14+
exit /b 1
)
for /f "tokens=*" %%i in ('jpackage --version') do echo ✓ jpackage: %%i
REM 准备输入目录
echo [2/4] 准备 jpackage 输入...
if exist jpackage-input rmdir /S /Q jpackage-input
mkdir jpackage-input
REM 查找 GUI fatJar (带 -all 后缀)
set GUI_JAR=
for %%f in (gui\\build\\libs\\*-all.jar) do set GUI_JAR=%%f
if "!GUI_JAR!"=="" (
for %%f in (gui\\build\\libs\\mcslms-gui-*.jar) do set GUI_JAR=%%f
)
if "!GUI_JAR!"=="" (
echo ✗ 找不到 GUI JAR
exit /b 1
)
copy /Y "!GUI_JAR!" jpackage-input\\mcslms-gui.jar >nul
echo ✓ 复制 !GUI_JAR!
REM 创建 app-image
echo [3/4] 创建 app-image...
if exist jpackage-output rmdir /S /Q jpackage-output
jpackage ^
--type app-image ^
--input jpackage-input ^
--name MCSLMS ^
--main-jar mcslms-gui.jar ^
--main-class com.smartlibrary.gui.GUIApplication ^
--app-version 1.0.9.%BUILD_NUMBER% ^
--vendor "CHZU" ^
--description "智能图书管理系统 GUI 客户端" ^
--add-modules java.base,java.desktop,java.sql,java.logging,java.naming,java.management,java.prefs,java.xml,jdk.unsupported ^
--java-options "-Xmx512m" ^
--dest jpackage-output
if errorlevel 1 (
echo ✗ app-image 创建失败
exit /b 1
)
echo ✓ app-image 创建成功
REM 创建 EXE 安装包
echo [4/4] 创建 EXE 安装包...
jpackage ^
--type exe ^
--app-image jpackage-output\\MCSLMS ^
--name MCSLMS ^
--app-version 1.0.9.%BUILD_NUMBER% ^
--vendor "CHZU" ^
--description "智能图书管理系统 GUI 客户端" ^
--win-menu ^
--win-dir-chooser ^
--win-shortcut ^
--dest artifacts
if errorlevel 1 (
echo ⚠️ EXE 创建失败,创建 ZIP 绿色版...
powershell -Command "Compress-Archive -Path jpackage-output\\MCSLMS -DestinationPath artifacts\\mcslms-gui.zip -Force"
if exist artifacts\\mcslms-gui.zip (
echo ✓ ZIP 绿色版创建成功
)
) else (
REM 重命名 EXE
if exist artifacts\\MCSLMS-1.0.%BUILD_NUMBER%.exe (
move /Y artifacts\\MCSLMS-1.0.%BUILD_NUMBER%.exe artifacts\\mcslms-gui-v1.80.0.%BUILD_NUMBER%.exe >nul
echo ✓ EXE 创建成功: mcslms-gui-v1.80.0.%BUILD_NUMBER%.exe
)
)
echo.
echo ============================================
echo jpackage 打包完成
echo ============================================
'''
echo '✓ GUI EXE 创建成功'
} catch (Exception e) {
echo "⚠️ jpackage 打包失败: ${e.message}"
currentBuild.result = 'UNSTABLE'
}
}
}
}
stage('9. 打包完整发布包') {
steps {
echo '>>> 更新 dist 目录并打包为完整发布 ZIP'
bat '''
@echo off
setlocal EnableDelayedExpansion
set "VERSION_TAG=v1.80.0.%BUILD_NUMBER%"
echo ============================================
echo 打包 MCSLMS 完整发布包: !VERSION_TAG!
echo ============================================
REM 更新 dist 目录中的 JAR 文件为最新构建版本
echo [1/6] 更新 CLI JAR...
for %%f in (cli\\build\\libs\\mcslms-cli-*-all.jar) do (
copy /Y "%%f" "dist\\cli.jar" >nul
echo ✓ cli.jar
)
echo [2/6] 更新 GUI JAR...
for %%f in (gui\\build\\libs\\mcslms-gui-*-all.jar) do (
copy /Y "%%f" "dist\\gui.jar" >nul
echo ✓ gui.jar
)
echo [3/6] 更新 Backend JAR...
for %%f in (backend\\build\\libs\\mcslms-backend-*.jar) do (
echo %%f | findstr /V "plain" >nul && (
copy /Y "%%f" "dist\\backend.jar" >nul
echo ✓ backend.jar
)
)
echo [4/6] 更新 Android APK...
for %%f in (android\\build\\outputs\\apk\\debug\\*.apk) do (
copy /Y "%%f" "dist\\android.apk" >nul
echo ✓ android.apk
)
echo [5/6] 更新 Launcher JAR...
for %%f in (launcher\\build\\libs\\*.jar) do (
copy /Y "%%f" "dist\\launcher.jar" >nul
echo ✓ launcher.jar
)
echo [6/6] 复制缺失的文件...
REM 复制 launcher 生成的 MCSLMS.exe 文件
if exist "launcher\\build\\launch4j\\MCSLMS.exe" (
copy /Y "launcher\\build\\launch4j\\MCSLMS.exe" "dist\\MCSLMS.exe" >nul
echo ✓ MCSLMS.exe
) else (
echo ⚠️ 缺少 MCSLMS.exe 启动器
)
REM 复制数据库和配置文件 - 统一从 core/ 目录
if exist "core\\library.db" (
copy /Y "core\\library.db" "dist\\library.db" >nul
echo ✓ library.db
) else (
echo ⚠️ 缺少 core\\library.db 数据库
)
if exist "core\\datasource.properties" (
copy /Y "core\\datasource.properties" "dist\\datasource.properties" >nul
echo ✓ datasource.properties
) else (
echo ⚠️ 缺少 core\\datasource.properties 配置
)
echo.
echo dist 目录内容:
dir /B dist
REM 打包整个 dist 目录为 ZIP
echo.
echo 打包 dist 目录为 ZIP...
powershell -Command "Compress-Archive -Path 'dist\\*' -DestinationPath 'artifacts\\mcslms-release-!VERSION_TAG!.zip' -Force"
if exist "artifacts\\mcslms-release-!VERSION_TAG!.zip" (
echo.
echo ✓ 完整发布包创建成功: mcslms-release-!VERSION_TAG!.zip
for %%A in ("artifacts\\mcslms-release-!VERSION_TAG!.zip") do echo 文件大小: %%~zA bytes
echo.
echo 使用说明:
echo 1. 解压 ZIP 到任意目录
echo 2. 双击 MCSLMS.exe 即可启动
) else (
echo ✗ ZIP 打包失败
)
'''
}
}
stage('10. 归档制品') {
steps {
echo '>>> 归档构建产物(仅当前版本)'
script {
// 只归档当前构建版本的制品,避免历史版本堆积
def versionPattern = "v1.*.${BUILD_NUMBER}"
archiveArtifacts artifacts: """
cli/build/libs/mcslms-cli-*${BUILD_NUMBER}*.jar,
gui/build/libs/mcslms-gui-*${BUILD_NUMBER}*.jar,
backend/build/libs/mcslms-backend-*${BUILD_NUMBER}*.jar,
backend/build/libs/mcslms-web-*${BUILD_NUMBER}*.war,
android/build/outputs/apk/debug/*.apk,
artifacts/mcslms-gui-*${BUILD_NUMBER}*.exe,
artifacts/mcslms-gui-*${BUILD_NUMBER}*.zip,
artifacts/mcslms-release-*${BUILD_NUMBER}*.zip
""".replaceAll('\\s+', ''),
fingerprint: true,
allowEmptyArchive: true
}
}
}
stage('11. 部署到 Tomcat') {
steps {
echo '========== 部署到本地 Tomcat =========='
script {
def tomcatHome = 'E:\\2025-2026\\GitAIOps\\tomcat'
def tomcatWebapps = "${tomcatHome}\\webapps"
def tomcatBin = "${tomcatHome}\\bin"
def appName = 'mcslms'
try {
// 防止 Jenkins 杀死衍生进程
env.BUILD_ID = "dontKillMe"
bat """
@echo off
setlocal EnableDelayedExpansion
echo ============================================
echo MCSLMS Web 应用部署到 Tomcat
echo ============================================
echo.
REM 检查端口 8080 占用
echo [1/6] 检查端口 8080 占用...
for /f "tokens=5" %%a in ('netstat -ano ^| findstr :8080 ^| findstr LISTENING') do (
if not "%%a"=="0" (
echo 发现占用端口 8080 的进程 PID: %%a
taskkill /F /PID %%a 2>nul
echo ✓ 已终止进程
)
)
echo.
REM 停止 Tomcat
echo [2/6] 停止 Tomcat...
if exist "${tomcatBin}\\shutdown.bat" (
call "${tomcatBin}\\shutdown.bat" 2>nul
timeout /t 5 /nobreak >nul
echo ✓ Tomcat 已停止
)
echo.
REM 清理旧应用
echo [3/6] 清理旧应用...
if exist "${tomcatWebapps}\\${appName}.war" (
del /F /Q "${tomcatWebapps}\\${appName}.war"
echo ✓ 删除旧 WAR 包
)
if exist "${tomcatWebapps}\\${appName}" (
rmdir /S /Q "${tomcatWebapps}\\${appName}"
echo ✓ 删除旧应用目录
)
echo.
REM 查找并部署新 WAR 包
echo [4/6] 部署新 WAR 包...
set WAR_FOUND=0
for %%f in (backend\\build\\libs\\*.war) do (
if !WAR_FOUND!==0 (
copy /Y "%%f" "${tomcatWebapps}\\${appName}.war" >nul
echo ✓ 部署 WAR 包: %%f
set WAR_FOUND=1
)
)
if !WAR_FOUND!==0 (
echo ✗ 错误: 找不到 WAR 文件
exit /b 1
)
echo.
REM 复制数据库文件 - 统一使用 core/library.db
echo [5/6] 复制数据库文件...
if exist "core\\library.db" (
copy /Y "core\\library.db" "${tomcatBin}\\library.db" >nul
echo ✓ 复制数据库: core\\library.db
) else (
echo ⚠️ 警告: 未找到 core\\library.db
)
echo.
REM 启动 Tomcat
echo [6/6] 启动 Tomcat...
set CATALINA_HOME=${tomcatHome}
start "Tomcat-MCSLMS" /B "${tomcatBin}\\catalina.bat" run > "${tomcatHome}\\logs\\mcslms-tomcat.log" 2>&1
echo ✓ Tomcat 正在启动...
echo.
"""
echo '等待 Tomcat 启动 (30秒)...'
sleep 30
echo '验证应用访问...'
bat """
@echo off
echo.
echo ============================================
echo 验证 MCSLMS 应用
echo ============================================
powershell -NoLogo -NoProfile -Command "try { \$response = Invoke-WebRequest -Uri 'http://localhost:8080/${appName}/' -UseBasicParsing -TimeoutSec 10; if (\$response.StatusCode -eq 200) { Write-Host '✓ 部署成功!应用已可访问'; Write-Host ' 访问地址: http://localhost:8080/${appName}/'; exit 0 } else { Write-Host '✗ 访问失败,状态码:' \$response.StatusCode; exit 1 } } catch { Write-Host '⚠️ 无法访问应用:' \$_.Exception.Message; Write-Host ' 请检查 Tomcat 日志'; exit 0 }"
echo.
echo 部署信息:
echo - 应用名称: ${appName}
echo - 访问地址: http://localhost:8080/${appName}/
echo - Tomcat目录: ${tomcatHome}
echo - 日志文件: ${tomcatHome}\\logs\\mcslms-tomcat.log
"""
echo '✓ Tomcat 部署完成'
} catch (Exception e) {
echo "⚠️ Tomcat 部署失败: ${e.message}"
echo "读取 Tomcat 启动日志..."
bat """
@echo off
powershell -NoLogo -NoProfile -Command "if (Test-Path '${tomcatHome}\\logs\\mcslms-tomcat.log') { Get-Content '${tomcatHome}\\logs\\mcslms-tomcat.log' -Tail 100 } else { Write-Host '找不到日志文件' }"
"""
currentBuild.result = 'UNSTABLE'
}
}
}
}
stage('12. 推送到头歌平台') {
parallel {
stage('12.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 "jenkins@mcslms.local"
git remote remove educoder 2>nul || echo Remote educoder does not exist
git remote add educoder https://bdgit.educoder.net/pu6zrsfoy/mcslms.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/mcslms.git !CURRENT_SHA!:refs/heads/main --force
'''
}
echo '✓ 源代码推送到 main 成功'
} catch (Exception e) {
echo "⚠ 推送代码到 main 失败: ${e.message}"
currentBuild.result = 'UNSTABLE'
}
}
}
}
stage('12.2 推送制品到 release') {
steps {
echo '========== 推送制品到头歌 release 分支 =========='
script {
try {
withCredentials([usernamePassword(
credentialsId: 'educoder-credentials',
usernameVariable: 'EDUCODER_USER',
passwordVariable: 'EDUCODER_PASS'
)]) {
bat '''
@echo off
setlocal EnableExtensions EnableDelayedExpansion
set "VERSION_TAG=v1.80.0.%BUILD_NUMBER%"
set "ARTIFACTS_DIR=artifacts\\!VERSION_TAG!"
echo ============================================
echo 准备制品目录: !VERSION_TAG!
echo ============================================
REM 创建制品目录
if not exist artifacts mkdir artifacts
if exist "!ARTIFACTS_DIR!" rmdir /S /Q "!ARTIFACTS_DIR!"
mkdir "!ARTIFACTS_DIR!"
REM 复制制品并重命名 (使用 fatJar)
echo 复制制品...
for %%f in (cli\\build\\libs\\mcslms-cli-*-all.jar) do (
copy /Y "%%f" "!ARTIFACTS_DIR!\\mcslms-cli-!VERSION_TAG!.jar" >nul
echo ✓ CLI JAR (fatJar)
)
for %%f in (gui\\build\\libs\\mcslms-gui-*-all.jar) do (
copy /Y "%%f" "!ARTIFACTS_DIR!\\mcslms-gui-!VERSION_TAG!.jar" >nul
echo ✓ GUI JAR (fatJar)
)
for %%f in (backend\\build\\libs\\mcslms-backend-*.jar) do (
copy /Y "%%f" "!ARTIFACTS_DIR!\\mcslms-backend-!VERSION_TAG!.jar" >nul
echo ✓ Backend JAR
)
for %%f in (backend\\build\\libs\\mcslms-web-*.war) do (
copy /Y "%%f" "!ARTIFACTS_DIR!\\mcslms-web-!VERSION_TAG!.war" >nul
echo ✓ Web WAR
)
REM 查找 APK
for %%f in (android\\build\\outputs\\apk\\debug\\*.apk) do (
copy /Y "%%f" "!ARTIFACTS_DIR!\\mcslms-app-!VERSION_TAG!.apk" >nul
echo ✓ Android APK
)
REM 复制 GUI EXE/ZIP
if exist artifacts\\mcslms-gui-!VERSION_TAG!.exe (
copy /Y artifacts\\mcslms-gui-!VERSION_TAG!.exe "!ARTIFACTS_DIR!\\\\" >nul
echo ✓ GUI EXE
)
if exist artifacts\\mcslms-gui.zip (
copy /Y artifacts\\mcslms-gui.zip "!ARTIFACTS_DIR!\\mcslms-gui-!VERSION_TAG!.zip" >nul
echo ✓ GUI ZIP
)
REM 复制完整发布包 ZIP
if exist "artifacts\\mcslms-release-!VERSION_TAG!.zip" (
copy /Y "artifacts\\mcslms-release-!VERSION_TAG!.zip" "!ARTIFACTS_DIR!\\\\" >nul
echo ✓ 完整发布包 ZIP (四端+启动器)
)
REM 生成清单文件
echo MCSLMS Release Manifest - !VERSION_TAG! > "!ARTIFACTS_DIR!\\manifest.txt"
echo Build ID: %BUILD_NUMBER% >> "!ARTIFACTS_DIR!\\manifest.txt"
echo 发布时间: %date% %time% >> "!ARTIFACTS_DIR!\\manifest.txt"
echo. >> "!ARTIFACTS_DIR!\\manifest.txt"
echo 制品列表: >> "!ARTIFACTS_DIR!\\manifest.txt"
for %%F in (!ARTIFACTS_DIR!\\*) do echo %%~nxF >> "!ARTIFACTS_DIR!\\manifest.txt"
echo.
echo 制品目录内容:
dir /B "!ARTIFACTS_DIR!"
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
echo.
echo ============================================
echo 推送到头歌 release 分支
echo ============================================
git config user.name "Jenkins CI"
git config user.email "jenkins@mcslms.local"
REM 删除 artifacts 根目录下的 exe/zip 文件,只保留版本目录
del /F /Q artifacts\\*.exe 2>nul
del /F /Q artifacts\\*.zip 2>nul
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 推送到头歌平台...
git push https://%USER_ENC%:%PASS_ENC%@bdgit.educoder.net/pu6zrsfoy/mcslms.git HEAD:refs/heads/release --force
REM 恢复原分支
git checkout -f develop 2>nul || git checkout -f main 2>nul
echo ✓ 制品已推送到头歌 release 分支: !VERSION_TAG!
'''
}
echo '✓ 头歌平台发布成功'
} catch (Exception e) {
echo "⚠ 推送到头歌失败: ${e.message}"
currentBuild.result = 'UNSTABLE'
}
}
}
}
}
}
}
post {
success {
echo '=========================================='
echo " ✅ 四端构建成功!版本: ${env.RELEASE_VERSION}"
echo ' - CLI: cli/build/libs/mcslms-cli-v1.80.0.X-all.jar'
echo ' - GUI: gui/build/libs/mcslms-gui-v1.80.0.X-all.jar'
echo ' - GUI EXE: artifacts/mcslms-gui-v1.80.0.X.exe'
echo ' - Web WAR: backend/build/libs/mcslms-web-v1.80.0.X.war'
echo ' - Backend: backend/build/libs/mcslms-backend-v1.80.0.X.jar'
echo ' - Android: android/build/outputs/apk/'
echo ' - 完整包: artifacts/mcslms-release-v1.80.0.X.zip'
echo ' - Tomcat: http://localhost:8080/mcslms/'
echo ' - 测试报告: Jenkins -> Core 测试报告'
echo ' - SonarQube: http://localhost:9000/dashboard?id=mcslms'
echo ' - 头歌源码: https://bdgit.educoder.net/pu6zrsfoy/mcslms/tree/main'
echo ' - 头歌制品: https://bdgit.educoder.net/pu6zrsfoy/mcslms/tree/release'
echo '=========================================='
}
failure {
echo '❌ 构建失败,请检查日志'
}
always {
script {
echo "=== 构建完成: ${env.RELEASE_VERSION} ==="
// 清理临时目录
bat 'if exist jpackage-input rmdir /S /Q jpackage-input 2>nul'
bat 'if exist jpackage-output rmdir /S /Q jpackage-output 2>nul'
// 发送邮件通知
echo '========== 发送邮件通知 =========='
def finalResult = currentBuild.result ?: 'SUCCESS'
def emailSubject = ""
def emailStatus = ""
if (finalResult == 'SUCCESS') {
emailSubject = "✅ MCSLMS 构建成功 - ${env.RELEASE_VERSION} (Build #${BUILD_NUMBER})"
emailStatus = "成功"
} else if (finalResult == 'FAILURE') {
emailSubject = "❌ MCSLMS 构建失败 - ${env.RELEASE_VERSION} (Build #${BUILD_NUMBER})"
emailStatus = "失败"
} else if (finalResult == 'UNSTABLE') {
emailSubject = "⚠️ MCSLMS 构建不稳定 - ${env.RELEASE_VERSION} (Build #${BUILD_NUMBER})"
emailStatus = "不稳定"
} else {
emailSubject = " MCSLMS 构建完成 - ${env.RELEASE_VERSION} (Build #${BUILD_NUMBER})"
emailStatus = "完成"
}
echo "准备发送邮件: ${emailSubject}"
echo "收件人: 602924803@qq.com"
try {
mail to: '602924803@qq.com',
subject: emailSubject,
body: """MCSLMS 项目构建${emailStatus}
构建编号: #${BUILD_NUMBER}
Release 版本: ${env.RELEASE_VERSION}
构建状态: ${emailStatus}
构建时间: ${new Date(currentBuild.startTimeInMillis)}
Git 提交: ${env.GIT_COMMIT ?: 'N/A'}
制品列表:
- mcslms-cli-${env.RELEASE_VERSION}.jar (命令行版本)
- mcslms-gui-${env.RELEASE_VERSION}.jar (图形界面版本)
- mcslms-gui-${env.RELEASE_VERSION}.exe (Windows安装包)
- mcslms-web-${env.RELEASE_VERSION}.war (Web应用版本)
- mcslms-backend-${env.RELEASE_VERSION}.jar (后端服务)
- mcslms-app-${env.RELEASE_VERSION}.apk (Android版本)
- mcslms-release-${env.RELEASE_VERSION}.zip (完整发布包: 四端+启动器)
访问地址:
- Tomcat: http://localhost:8080/mcslms/
- SonarQube: http://localhost:9000/dashboard?id=mcslms
- 头歌源码: https://bdgit.educoder.net/pu6zrsfoy/mcslms/tree/main
- 头歌制品: https://bdgit.educoder.net/pu6zrsfoy/mcslms/tree/release
查看构建详情: ${BUILD_URL}
"""
echo '✓ 邮件发送成功'
} catch (Exception e) {
echo "⚠️ 邮件发送失败: ${e.message}"
}
}
}
}
}