|
|
@@ -1,23 +1,25 @@
|
|
|
#!/usr/bin/env groovy
|
|
|
/**
|
|
|
- * 强验证发布 SpringBoot fat-jar
|
|
|
- * - 按端口杀进程
|
|
|
- * - 按 profile 做所有进程级校验
|
|
|
+ * 强验证发布 SpringBoot fat-jar
|
|
|
+ * - 按端口杀进程
|
|
|
+ * - 按 profile 做所有进程级校验
|
|
|
*/
|
|
|
def call(Map args = [:]) {
|
|
|
- String jarName = args.jarName ?: env.JAR_NAME
|
|
|
- String jarPath = args.jarPath ?: env.JAR_PATH
|
|
|
- String remoteHost = args.remoteHost ?: env.REMOTE_HOST
|
|
|
- String remotePort = args.remotePort ?: env.REMOTE_PORT
|
|
|
- String remotePath = args.remotePath ?: env.REMOTE_PATH
|
|
|
- String profile = args.profile ?: env.PROFILE
|
|
|
- String jvmArgs = args.jvmArgs ?: env.JVM_ARGS ?: ''
|
|
|
+ String jarName = args.jarName ?: env.JAR_NAME
|
|
|
+ String jarPath = args.jarPath ?: env.JAR_PATH
|
|
|
+ String remoteHost = args.remoteHost ?: env.REMOTE_HOST
|
|
|
+ String remotePort = args.remotePort ?: env.REMOTE_PORT
|
|
|
+ String remotePath = args.remotePath ?: env.REMOTE_PATH
|
|
|
+ String profile = args.profile ?: env.PROFILE
|
|
|
+ String jvmArgs = args.jvmArgs ?: env.JVM_ARGS ?: ''
|
|
|
String remoteJar = "${remotePath}/${jarName}"
|
|
|
|
|
|
PrintMes("开始发布 ${jarName} [profile=${profile}] 到 ${remoteHost}:${remotePort}", 'yellow')
|
|
|
|
|
|
try {
|
|
|
+
|
|
|
/* 1. 按端口杀旧进程,并确认端口已释放 */
|
|
|
+ PrintMes("→ 1. 开始清理旧进程,确保端口 ${remotePort} 释放", 'yellow')
|
|
|
sh """
|
|
|
ssh -o StrictHostKeyChecking=no ${remoteHost} '
|
|
|
OLD_PID=\$(lsof -ti:${remotePort} || true)
|
|
|
@@ -32,55 +34,99 @@ def call(Map args = [:]) {
|
|
|
fi
|
|
|
'
|
|
|
"""
|
|
|
+ PrintMes("√ 1. 旧进程清理完成,端口 ${remotePort} 已释放", 'yellow')
|
|
|
+
|
|
|
|
|
|
/* 2. 备份旧包 */
|
|
|
+ PrintMes("→ 2. 开始备份旧 JAR 包 ${remoteJar}", 'yellow')
|
|
|
sh """
|
|
|
ssh -o StrictHostKeyChecking=no ${remoteHost} '
|
|
|
test -f ${remoteJar} && mv ${remoteJar} ${remoteJar}-\$(date +%Y%m%d%H%M) || true
|
|
|
'
|
|
|
"""
|
|
|
+ PrintMes("√ 2. 旧 JAR 包备份完成", 'yellow')
|
|
|
+
|
|
|
|
|
|
/* 3. 传包 + MD5 强校验 */
|
|
|
+ PrintMes("→ 3. 开始传输 JAR 包并进行 MD5 强校验", 'yellow')
|
|
|
+
|
|
|
// 1. 在本地计算 MD5
|
|
|
String localMd5 = sh(script: "md5sum ${jarPath} | awk '{print \$1}'", returnStdout: true).trim()
|
|
|
+ PrintMes(" 本地 MD5: ${localMd5}", 'yellow')
|
|
|
|
|
|
// 2. 传输 JAR 包
|
|
|
sh "scp -o StrictHostKeyChecking=no ${jarPath} ${remoteHost}:${remoteJar}"
|
|
|
|
|
|
- // 3. 远程计算 MD5 并返回(Jenkins Pipeline 会自动修剪输出)
|
|
|
+ // 3. 远程计算 MD5 并返回
|
|
|
String remoteMd5Output = sh(
|
|
|
- script: "ssh -o StrictHostKeyChecking=no ${remoteHost} 'md5sum ${remoteJar}'",
|
|
|
- returnStdout: true
|
|
|
+ script: "ssh -o StrictHostKeyChecking=no ${remoteHost} 'md5sum ${remoteJar}'",
|
|
|
+ returnStdout: true
|
|
|
).trim()
|
|
|
|
|
|
// 4. 在 Jenkins Groovy 侧处理输出,提取 MD5 值
|
|
|
- // 输出格式通常是 "MD5_SUM FILENAME"。我们只需要第一个字段。
|
|
|
String remoteMd5 = remoteMd5Output.split('\\s+')[0]
|
|
|
+ PrintMes(" 远程 MD5: ${remoteMd5}", 'yellow')
|
|
|
|
|
|
if (localMd5 != remoteMd5) {
|
|
|
+ PrintMes("× 3. MD5 校验失败:本地 ${localMd5} != 远程 ${remoteMd5}", 'red')
|
|
|
error("MD5 校验失败:本地 ${localMd5} != 远程 ${remoteMd5}")
|
|
|
}
|
|
|
+ PrintMes("√ 3. JAR 包传输和 MD5 校验成功", 'yellow')
|
|
|
|
|
|
+
|
|
|
/* 4. 启动新进程 */
|
|
|
+ PrintMes("→ 4. 启动新进程 (profile=${profile})", 'yellow')
|
|
|
sh """ssh -o StrictHostKeyChecking=no ${remoteHost} "sh -c 'cd ${remotePath} && nohup java ${jvmArgs} -jar ${jarName} --spring.profiles.active=${profile} > server.log 2>&1 &'" """
|
|
|
+ PrintMes("√ 4. 新进程启动命令已发送", 'yellow')
|
|
|
+
|
|
|
|
|
|
/* 5. 按 profile 精确校验:有且仅有 1 个进程 */
|
|
|
+ PrintMes("→ 5. 校验进程数量 (期望 1 个 ${profile} 进程)", 'yellow')
|
|
|
String procCount = sh(
|
|
|
script: "ssh -o StrictHostKeyChecking=no ${remoteHost} 'ps -ef | grep \"java.*${jarName}.*--spring.profiles.active=${profile}\" | grep -v grep | wc -l'",
|
|
|
returnStdout: true
|
|
|
).trim()
|
|
|
+ PrintMes(" 实际找到 ${procCount} 个进程", 'yellow')
|
|
|
+
|
|
|
if (procCount != "1") {
|
|
|
+ PrintMes("× 5. 启动校验失败:期望 1 个 ${profile} 进程,实际 ${procCount} 个", 'red')
|
|
|
error("启动校验失败:期望 1 个 ${profile} 进程,实际 ${procCount} 个")
|
|
|
}
|
|
|
+ PrintMes("√ 5. 进程数量校验成功", 'yellow')
|
|
|
+
|
|
|
|
|
|
- /* 6. 端口已被新进程占用 */
|
|
|
- String newPid = sh(script: "ssh -o StrictHostKeyChecking=no ${remoteHost} 'lsof -ti:${remotePort}'", returnStdout: true).trim()
|
|
|
+ /* 6. 端口已被新进程占用 (加入等待重试机制) */
|
|
|
+ def maxRetries = 12 // 最多重试 12 次
|
|
|
+ def waitTimeSec = 5 // 每次间隔 5 秒
|
|
|
+ String newPid = ""
|
|
|
+
|
|
|
+ PrintMes("→ 6. 校验端口 ${remotePort} 是否已被新进程监听 (最大等待 ${maxRetries * waitTimeSec} 秒)", 'yellow')
|
|
|
+
|
|
|
+ for (int i = 0; i < maxRetries; i++) {
|
|
|
+ newPid = sh(script: "ssh -o StrictHostKeyChecking=no ${remoteHost} 'lsof -ti:${remotePort} || true'", returnStdout: true).trim()
|
|
|
+
|
|
|
+ if (newPid) {
|
|
|
+ PrintMes(" 端口已监听,PID: ${newPid}", 'yellow')
|
|
|
+ break // 找到 PID,跳出循环
|
|
|
+ }
|
|
|
+
|
|
|
+ if (i < maxRetries - 1) {
|
|
|
+ PrintMes(" 未监听,等待 ${waitTimeSec} 秒后重试... (${i + 1}/${maxRetries})", 'yellow')
|
|
|
+ sleep(waitTimeSec) // Jenkins Pipeline 内置步骤
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
if (!newPid) {
|
|
|
- error("端口 ${remotePort} 未被新进程监听,启动可能失败")
|
|
|
+ PrintMes("× 6. 端口 ${remotePort} 在 ${maxRetries * waitTimeSec} 秒内未被新进程监听,启动可能失败", 'red')
|
|
|
+ error("端口 ${remotePort} 未被新进程监听,启动超时失败")
|
|
|
}
|
|
|
+ PrintMes("√ 6. 端口监听校验成功", 'yellow')
|
|
|
+
|
|
|
|
|
|
+ // 最终成功提示
|
|
|
PrintMes("发布成功 ${jarName} [profile=${profile}, pid=${newPid}]", 'green')
|
|
|
} catch (Exception e) {
|
|
|
+ // 捕获异常,打印红色失败信息
|
|
|
PrintMes("发布失败 ${jarName} : ${e.message}", 'red')
|
|
|
error('deploySpringBoot failed')
|
|
|
}
|