|
|
/**
|
|
|
* 智能图书管理系统 (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}"
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
} |