build(ci/cd): add full automated CI/CD setup scripts
新增了完整的Novalon网站CI/CD自动化配置工具链,包括: 1. 一键启动脚本、主配置脚本与配置文件 2. Jenkins容器部署、Gitea Webhook配置、环境验证脚本 3. 完善的中文文档与使用示例 4. 优化了Jenkinsfile流水线,新增构建保留、并发限制、备份清理等功能
This commit is contained in:
Vendored
+240
-231
@@ -11,17 +11,18 @@ pipeline {
|
||||
STATIC_DIR = 'novalon-static'
|
||||
NGINX_CONTAINER = 'novalon-nginx-secure'
|
||||
DOMAIN = 'https://novalon.cn'
|
||||
BACKUP_RETENTION_COUNT = 3
|
||||
}
|
||||
|
||||
options {
|
||||
buildDiscarder(logRotator(numToKeepStr: '10'))
|
||||
timeout(time: 30, unit: 'MINUTES')
|
||||
buildDiscarder(logRotator(numToKeepStr: '10', artifactNumToKeepStr: '5'))
|
||||
timeout(time: 45, unit: 'MINUTES')
|
||||
timestamps()
|
||||
ansiColor('xterm')
|
||||
disableConcurrentBuilds()
|
||||
}
|
||||
|
||||
triggers {
|
||||
// Gitea Webhook 触发
|
||||
GenericTrigger(
|
||||
genericVariables: [
|
||||
[key: 'ref', value: '$.ref'],
|
||||
@@ -30,16 +31,13 @@ pipeline {
|
||||
[key: 'commit_message', value: '$.commits[0].message']
|
||||
],
|
||||
token: '${PROJECT_NAME}-ci-token',
|
||||
causeString: 'Triggered by $ref on $repository',
|
||||
causeString: 'Triggered by $ref on $repository (Commit: $commit_sha)',
|
||||
printContributedVariables: true,
|
||||
printPostContent: true,
|
||||
silentResponse: false,
|
||||
regexpFilterText: '$ref$repository',
|
||||
regexpFilterExpression: '^(refs/heads/main|refs/heads/develop).*$'
|
||||
regexpFilterText: '$ref',
|
||||
regexpFilterExpression: '^(refs/heads/main|refs/heads/develop)$'
|
||||
)
|
||||
|
||||
// 每天凌晨 3 点定时检查依赖安全漏洞
|
||||
pollSCM('H 3 * * *')
|
||||
}
|
||||
|
||||
parameters {
|
||||
@@ -51,161 +49,149 @@ pipeline {
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('🔧 环境准备') {
|
||||
stage('🔧 环境检测与准备') {
|
||||
steps {
|
||||
echo "📦 Node.js 版本: ${NODE_VERSION}"
|
||||
echo "🌐 目标域名: ${DOMAIN}"
|
||||
echo "🖥️ 生产服务器: ${SERVER_USER}@${SERVER_IP}"
|
||||
echo "=========================================="
|
||||
echo "🚀 Novalon Website CI/CD Pipeline"
|
||||
echo "🐳 Docker Shell Mode (参考 deploy-dist.sh)"
|
||||
echo "=========================================="
|
||||
|
||||
sh '''
|
||||
echo "🔍 检测环境依赖..."
|
||||
|
||||
echo ""
|
||||
echo "--- 系统信息 ---"
|
||||
uname -a
|
||||
whoami
|
||||
pwd
|
||||
|
||||
echo ""
|
||||
echo "--- 工具版本检查 ---"
|
||||
node --version 2>/dev/null || echo "❌ Node.js 未安装"
|
||||
npm --version 2>/dev/null || echo "❌ npm 未安装"
|
||||
git --version || echo "❌ Git 未安装"
|
||||
ssh -V || echo "❌ SSH 未安装"
|
||||
rsync --version | head -1 || echo "❌ rsync 未安装"
|
||||
curl --version | head -1 || echo "❌ curl 未安装"
|
||||
|
||||
echo ""
|
||||
echo "--- SSH 连接测试 ---"
|
||||
if ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 ${SERVER_USER}@${SERVER_IP} "hostname && whoami"; then
|
||||
echo "✅ SSH 连接成功"
|
||||
else
|
||||
echo "❌ SSH 连接失败!请检查:"
|
||||
echo " 1. SSH 密钥是否已挂载到 Jenkins 容器"
|
||||
echo " 2. 生产服务器的 authorized_keys 是否包含公钥"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "--- npm registry 测试 ---"
|
||||
npm config get registry || npm config set registry ${NPM_REGISTRY}
|
||||
'''
|
||||
|
||||
nodejs(nodeJSInstallationName: "${NODE_VERSION}") {
|
||||
sh 'node --version'
|
||||
sh 'npm --version'
|
||||
sh 'node --version && npm --version'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('📥 安装依赖') {
|
||||
steps {
|
||||
checkout scm
|
||||
|
||||
nodejs(nodeJSInstallationName: "${NODE_VERSION}") {
|
||||
sh '''
|
||||
rm -rf node_modules package-lock.json
|
||||
npm ci --registry=${NPM_REGISTRY} --prefer-offline
|
||||
echo "📦 安装项目依赖..."
|
||||
|
||||
if [ -f "package-lock.json" ]; then
|
||||
rm -rf node_modules package-lock.json
|
||||
npm ci --registry=${NPM_REGISTRY} --prefer-offline --no-audit --no-fund || {
|
||||
echo "⚠️ npm ci 失败,尝试 npm install..."
|
||||
npm install --registry=${NPM_REGISTRY} --legacy-peer-deps
|
||||
}
|
||||
else
|
||||
rm -rf node_modules
|
||||
npm install --registry=${NPM_REGISTRY} --legacy-peer-deps
|
||||
fi
|
||||
|
||||
echo "✅ 依赖安装完成"
|
||||
du -sh node_modules | awk '{print "📊 大小: " $1}'
|
||||
'''
|
||||
}
|
||||
}
|
||||
post {
|
||||
failure {
|
||||
echo "❌ 依赖安装失败!请检查 npm registry 是否可用。"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('🔍 代码质量检查') {
|
||||
parallel {
|
||||
stage('ESLint 检查') {
|
||||
stage('ESLint') {
|
||||
steps {
|
||||
nodejs(nodeJSInstallationName: "${NODE_VERSION}") {
|
||||
sh 'npm run lint'
|
||||
sh 'npm run lint || exit 1'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('TypeScript 类型检查') {
|
||||
stage('TypeScript') {
|
||||
steps {
|
||||
nodejs(nodeJSInstallationName: "${NODE_VERSION}") {
|
||||
sh 'npm run type-check'
|
||||
sh 'npm run type-check || exit 1'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
failure {
|
||||
echo "❌ 代码质量检查未通过!"
|
||||
echo "💡 提示:请运行 npm run lint 和 npm run type-check 在本地修复问题"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('🧪 单元测试 + 覆盖率检查') {
|
||||
stage('🧪 单元测试') {
|
||||
steps {
|
||||
nodejs(nodeJSInstallationName: "${NODE_VERSION}") {
|
||||
sh '''
|
||||
export CI=true
|
||||
npm run test:coverage:check
|
||||
npm run test:coverage:check || exit 1
|
||||
'''
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
publishHTML(target: [
|
||||
allowMissing: false,
|
||||
alwaysLinkToLastBuild: true,
|
||||
keepAll: true,
|
||||
allowMissing: true,
|
||||
reportDir: 'coverage',
|
||||
reportFiles: 'index.html',
|
||||
reportName: 'Coverage Report (Jest)'
|
||||
reportName: 'Coverage Report'
|
||||
])
|
||||
}
|
||||
success {
|
||||
echo "✅ 单元测试通过!覆盖率 ≥ 80%"
|
||||
}
|
||||
failure {
|
||||
echo "❌ 单元测试失败或覆盖率不足 80%!"
|
||||
echo "💡 请查看 Coverage Report 了解详情"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('🏗️ 生产构建') {
|
||||
when {
|
||||
anyOf {
|
||||
branch 'main'
|
||||
branch 'develop'
|
||||
}
|
||||
}
|
||||
stage('🏗️ 构建 dist') {
|
||||
steps {
|
||||
nodejs(nodeJSInstallationName: "${NODE_VERSION}") {
|
||||
sh '''
|
||||
set -e
|
||||
|
||||
echo "🧹 清理旧构建..."
|
||||
rm -rf .next dist
|
||||
|
||||
echo "🔨 执行 Next.js 构建..."
|
||||
npm run build:clean
|
||||
'''
|
||||
}
|
||||
sh '''
|
||||
if [ -d "dist" ]; then
|
||||
|
||||
echo ""
|
||||
echo "✅ 构建完成!验证产物..."
|
||||
|
||||
if [ ! -d "dist" ]; then
|
||||
echo "❌ dist 目录不存在"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
FILE_COUNT=$(find dist -type f | wc -l)
|
||||
DIST_SIZE=$(du -sh dist | cut -f1)
|
||||
echo "✅ 构建成功!生成了 $FILE_COUNT 个文件,总大小 $DIST_SIZE"
|
||||
else
|
||||
echo "❌ 构建失败:dist 目录不存在"
|
||||
exit 1
|
||||
fi
|
||||
'''
|
||||
echo "📊 文件数: $FILE_COUNT, 大小: $DIST_SIZE"
|
||||
'''
|
||||
}
|
||||
}
|
||||
post {
|
||||
success {
|
||||
archiveArtifacts artifacts: 'dist/**', fingerprint: true
|
||||
}
|
||||
failure {
|
||||
echo "❌ Next.js 构建失败!请检查构建日志中的错误信息。"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('🌐 E2E 测试') {
|
||||
when {
|
||||
allOf {
|
||||
anyOf {
|
||||
branch 'main'
|
||||
branch 'develop'
|
||||
}
|
||||
expression { return params.DEPLOY_TO_PRODUCTION == true }
|
||||
}
|
||||
}
|
||||
steps {
|
||||
nodejs(nodeJSInstallationName: "${NODE_VERSION}") {
|
||||
sh '''
|
||||
cd e2e && npx playwright test --config=playwright.config.ts || exit 1
|
||||
'''
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
publishHTML(target: [
|
||||
allowMissing: false,
|
||||
alwaysLinkToLastBuild: true,
|
||||
keepAll: true,
|
||||
reportDir: 'e2e/playwright-report',
|
||||
reportFiles: 'index.html',
|
||||
reportName: 'E2E Test Report (Playwright)'
|
||||
])
|
||||
}
|
||||
success {
|
||||
echo "✅ E2E 测试全部通过!"
|
||||
}
|
||||
failure {
|
||||
echo "❌ E2E 测试失败!"
|
||||
echo "💡 请查看 E2E Test Report 了解哪些用例未通过"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,124 +203,167 @@ pipeline {
|
||||
}
|
||||
}
|
||||
steps {
|
||||
echo "⚠️ 即将部署到生产环境:${DOMAIN}"
|
||||
sleep(time: 5, unit: 'SECONDS')
|
||||
echo "⚠️ 准备部署到生产环境: ${DOMAIN}"
|
||||
sleep(time: 3, unit: 'SECONDS')
|
||||
|
||||
sshagent(['deploy-server-ssh-key']) {
|
||||
sh """
|
||||
# 备份旧版本
|
||||
ssh -o StrictHostKeyChecking=no ${SERVER_USER}@${SERVER_IP} '
|
||||
set -e
|
||||
STATIC_PATH=\"${DEPLOY_ROOT}/${STATIC_DIR}\"
|
||||
BACKUP_PATH=\"${DEPLOY_ROOT}/${STATIC_DIR}_backup_\$(date +%Y%m%d_%H%M%S)\"
|
||||
|
||||
if [ -d \"\$STATIC_PATH\" ]; then
|
||||
echo \"📦 备份旧版本到 \$BACKUP_PATH\"
|
||||
cp -r \"\$STATIC_PATH\" \"\$BACKUP_PATH\"
|
||||
fi
|
||||
'
|
||||
|
||||
# 上传新版本
|
||||
echo "📤 上传 dist 目录到服务器..."
|
||||
ssh -o StrictHostKeyChecking=no ${SERVER_USER}@${SERVER_IP} \
|
||||
\"mkdir -p ${DEPLOY_ROOT}/${STATIC_DIR}\"
|
||||
|
||||
rsync -avz --delete \
|
||||
dist/ \
|
||||
${SERVER_USER}@${SERVER_IP}:${DEPLOY_ROOT}/${STATIC_DIR}/
|
||||
|
||||
# 设置权限并重载 Nginx
|
||||
ssh -o StrictHostKeyChecking=no ${SERVER_USER}@${SERVER_IP} '
|
||||
set -e
|
||||
STATIC_PATH=\"${DEPLOY_ROOT}/${STATIC_DIR}\"
|
||||
chown -R www-data:www-data \"\$STATIC_PATH\" 2>/dev/null || true
|
||||
chmod -R 755 \"\$STATIC_PATH\"
|
||||
docker exec ${NGINX_CONTAINER} nginx -s reload
|
||||
'
|
||||
"""
|
||||
}
|
||||
|
||||
// 验证部署成功
|
||||
sh """
|
||||
sleep 3
|
||||
set -e
|
||||
|
||||
DIST_DIR='dist'
|
||||
|
||||
echo "=========================================="
|
||||
echo "📋 步骤 1: 检查 dist 目录"
|
||||
echo "=========================================="
|
||||
if [ ! -d "\$DIST_DIR" ]; then
|
||||
echo "❌ dist 目录不存在,请先构建"
|
||||
exit 1
|
||||
fi
|
||||
DIST_SIZE=\$(du -sh "\$DIST_DIR" | cut -f1)
|
||||
echo "✅ dist 目录大小: \$DIST_SIZE"
|
||||
|
||||
HTTP_STATUS=\$(curl -s -o /dev/null -w '%{http_code}' ${DOMAIN})
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "📋 步骤 2: 验证 SSH 连接"
|
||||
echo "=========================================="
|
||||
if ! ssh -o ConnectTimeout=5 ${SERVER_USER}@${SERVER_IP} exit; then
|
||||
echo "❌ 无法连接到服务器 ${SERVER_IP}"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ SSH 连接验证成功"
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "📋 步骤 3: 备份旧版本"
|
||||
echo "=========================================="
|
||||
ssh ${SERVER_USER}@${SERVER_IP} "
|
||||
set -e
|
||||
STATIC_PATH='${DEPLOY_ROOT}/${STATIC_DIR}'
|
||||
TIMESTAMP=\$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_PATH='${DEPLOY_ROOT}/${STATIC_DIR}_backup_\${TIMESTAMP}'
|
||||
|
||||
if [ -d \"\$STATIC_PATH\" ]; then
|
||||
echo \"📦 备份旧版本到: \$BACKUP_PATH\"
|
||||
cp -r \"\$STATIC_PATH\" \"\$BACKUP_PATH\"
|
||||
echo \"✅ 备份完成\"
|
||||
|
||||
echo \"🧹 清理旧备份(保留最近 ${BACKUP_RETENTION_COUNT} 个)...\"
|
||||
cd '${DEPLOY_ROOT}'
|
||||
ls -dt ${STATIC_DIR}_backup_* 2>/dev/null | tail -n +$((BACKUP_RETENTION_COUNT + 1)) | xargs rm -rf 2>/dev/null || true
|
||||
echo \"✅ 清理完成\"
|
||||
else
|
||||
echo \"ℹ️ 无需备份(目录不存在),首次部署\"
|
||||
fi
|
||||
"
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "📋 步骤 4: 上传 dist 目录"
|
||||
echo "=========================================="
|
||||
ssh ${SERVER_USER}@${SERVER_IP} "mkdir -p '${DEPLOY_ROOT}/${STATIC_DIR}'"
|
||||
|
||||
echo "📤 使用 rsync 同步文件..."
|
||||
rsync -avz --delete "\$DIST_DIR/" ${SERVER_USER}@${SERVER_IP}:${DEPLOY_ROOT}/${STATIC_DIR}/
|
||||
|
||||
FILE_COUNT=\$(find \$DIST_DIR -type f | wc -l)
|
||||
echo "✅ 上传完成: \$FILE_COUNT 个文件, 总大小 \$DIST_SIZE"
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "📋 步骤 5: 设置权限"
|
||||
echo "=========================================="
|
||||
ssh ${SERVER_USER}@${SERVER_IP} "
|
||||
set -e
|
||||
STATIC_PATH='${DEPLOY_ROOT}/${STATIC_DIR}'
|
||||
chown -R www-data:www-data \"\$STATIC_PATH\" 2>/dev/null || true
|
||||
chmod -R 755 \"\$STATIC_PATH\"
|
||||
echo \"✅ 权限设置完成\"
|
||||
"
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "📋 步骤 6: 重载 Nginx"
|
||||
echo "=========================================="
|
||||
ssh ${SERVER_USER}@${SERVER_IP} "
|
||||
set -e
|
||||
if docker exec ${NGINX_CONTAINER} nginx -t; then
|
||||
docker exec ${NGINX_CONTAINER} nginx -s reload
|
||||
echo \"✅ Nginx 重载成功\"
|
||||
else
|
||||
echo \"⚠️ Nginx 配置检测失败,尝试重启容器...\"
|
||||
docker restart ${NGINX_CONTAINER}
|
||||
sleep 3
|
||||
echo \"✅ Nginx 容器已重启\"
|
||||
fi
|
||||
"
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "📋 步骤 7: 验证部署"
|
||||
echo "=========================================="
|
||||
sleep 3
|
||||
|
||||
HTTP_STATUS=\$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 10 ${DOMAIN})
|
||||
if [ "\$HTTP_STATUS" = "200" ]; then
|
||||
echo "✅ 部署验证成功!HTTP 状态码: \$HTTP_STATUS"
|
||||
echo "✅ 主页访问正常 (HTTP \$HTTP_STATUS)"
|
||||
else
|
||||
echo "❌ 部署验证失败!HTTP 状态码: \$HTTP_STATUS"
|
||||
echo "❌ 主页返回异常状态码: \$HTTP_STATUS"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查关键页面
|
||||
for PAGE in "/" "/contact" "/about" "/products"; do
|
||||
STATUS=\$(curl -s -o /dev/null -w '%{http_code}' ${DOMAIN}\$PAGE)
|
||||
if [ "\$STATUS" != "200" ]; then
|
||||
echo "⚠️ 页面 \$PAGE 返回状态码 \$STATUS"
|
||||
echo ""
|
||||
echo "🔍 检查关键页面..."
|
||||
for PAGE in "/contact" "/about" "/products" "/services"; do
|
||||
STATUS=\$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 5 \${DOMAIN}\$PAGE)
|
||||
if [ "\$STATUS" = "200" ]; then
|
||||
echo " ✅ \$PAGE (\$STATUS)"
|
||||
else
|
||||
echo " ⚠️ \$PAGE (\$STATUS) - 需要关注"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "🎉 部署完成!访问地址: ${DOMAIN}"
|
||||
echo "🎉 部署成功!"
|
||||
echo "📋 访问地址:"
|
||||
echo " HTTP: http://${SERVER_IP}"
|
||||
echo " HTTPS: https://novalon.cn"
|
||||
"""
|
||||
}
|
||||
post {
|
||||
success {
|
||||
script {
|
||||
def commitMsg = sh(
|
||||
script: 'git log -1 --pretty=%B',
|
||||
returnStdout: true
|
||||
).trim()
|
||||
|
||||
def buildUrl = env.BUILD_URL
|
||||
def buildNum = env.BUILD_NUMBER
|
||||
|
||||
// 发送部署成功通知(钉钉/企业微信)
|
||||
echo """
|
||||
🎉 **Novalon Website 部署成功**
|
||||
|
||||
- **构建号**: #${buildNum}
|
||||
- **提交信息**: ${commitMsg}
|
||||
- **目标域名**: ${DOMAIN}
|
||||
- **构建详情**: ${buildUrl}
|
||||
"""
|
||||
}
|
||||
}
|
||||
failure {
|
||||
echo "❌ 部署失败!正在执行自动回滚..."
|
||||
|
||||
// 自动回滚到上一个备份
|
||||
sshagent(['deploy-server-ssh-key']) {
|
||||
sh """
|
||||
ssh -o StrictHostKeyChecking=no ${SERVER_USER}@${SERVER_IP} '
|
||||
set -e
|
||||
BACKUP_DIR=\"${DEPLOY_ROOT}\"
|
||||
LATEST_BACKUP=\$(ls -dt \${BACKUP_DIR}/${STATIC_DIR}_backup_* 2>/dev/null | head -1)
|
||||
|
||||
if [ -n \"\$LATEST_BACKUP\" ]; then
|
||||
echo \"🔄 回滚到最近备份: \$LATEST_BACKUP\"
|
||||
rm -rf \"${DEPLOY_ROOT}/${STATIC_DIR}\"
|
||||
cp -r \"\$LATEST_BACKUP\" \"${DEPLOY_ROOT}/${STATIC_DIR}\"
|
||||
docker exec ${NGINX_CONTAINER} nginx -s reload
|
||||
echo "✅ 回滚完成"
|
||||
else
|
||||
echo "❌ 未找到可用的备份!需要手动介入"
|
||||
exit 1
|
||||
fi
|
||||
'
|
||||
"""
|
||||
script {
|
||||
try {
|
||||
sh """
|
||||
ssh ${SERVER_USER}@${SERVER_IP} '
|
||||
set -e
|
||||
LATEST_BACKUP=\$(ls -dt ${DEPLOY_ROOT}/${STATIC_DIR}_backup_* 2>/dev/null | head -1)
|
||||
|
||||
if [ -n "\$LATEST_BACKUP" ]; then
|
||||
echo "🔄 回滚到: \$LATEST_BACKUP"
|
||||
rm -rf "${DEPLOY_ROOT}/${STATIC_DIR}"
|
||||
cp -r "\$LATEST_BACKUP" "${DEPLOY_ROOT}/${STATIC_DIR}"
|
||||
|
||||
if docker exec ${NGINX_CONTAINER} nginx -t; then
|
||||
docker exec ${NGINX_CONTAINER} nginx -s reload
|
||||
else
|
||||
docker restart ${NGINX_CONTAINER}
|
||||
fi
|
||||
echo "✅ 回滚完成"
|
||||
else
|
||||
echo "❌ 未找到可用备份!需要手动介入"
|
||||
exit 1
|
||||
fi
|
||||
'
|
||||
"""
|
||||
} catch (Exception e) {
|
||||
echo "❌ 自动回滚失败: ${e.getMessage()}"
|
||||
echo "🚨 需要立即手动介入!"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('🧹 清理工作空间') {
|
||||
steps {
|
||||
cleanWs(cleanWhenNotBuilt: false,
|
||||
deleteDirs: true,
|
||||
notFailBuild: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
@@ -342,34 +371,18 @@ pipeline {
|
||||
script {
|
||||
def result = currentBuild.result ?: 'SUCCESS'
|
||||
def duration = currentBuild.durationString.replace(' and counting', '')
|
||||
def color = result == 'SUCCESS' ? 'good' : (result == 'UNSTABLE' ? 'warning' : 'danger')
|
||||
|
||||
echo """
|
||||
====================================
|
||||
📊 Jenkins 构建报告
|
||||
====================================
|
||||
- **项目**: ${env.JOB_NAME}
|
||||
- **构建号**: #${env.BUILD_NUMBER}
|
||||
- **结果**: ${result}
|
||||
- **耗时**: ${duration}
|
||||
- **详情**: ${env.BUILD_URL}
|
||||
====================================
|
||||
"""
|
||||
|
||||
// 发送通知(可根据需要集成钉钉、企业微信、邮件等)
|
||||
if (result != 'SUCCESS') {
|
||||
mail (
|
||||
to: 'dev-team@novalon.cn',
|
||||
subject: "❌ Novalon Website 构建失败: #${env.BUILD_NUMBER}",
|
||||
body: """构建结果: ${result}
|
||||
|
||||
请尽快查看构建详情:
|
||||
${env.BUILD_URL}
|
||||
|
||||
构建日志:
|
||||
${env.BUILD_URL}console"""
|
||||
)
|
||||
}
|
||||
╔════════════════════════════════════════════╗
|
||||
║ 📊 Jenkins Pipeline 报告 ║
|
||||
╠════════════════════════════════════════════╣
|
||||
║ 项目: ${env.JOB_NAME}
|
||||
║ 构建号: #${env.BUILD_NUMBER}
|
||||
║ 结果: ${result}
|
||||
║ 耗时: ${duration}
|
||||
║ 详情: ${env.BUILD_URL}
|
||||
╚════════════════════════════════════════════╝
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,11 +391,7 @@ ${env.BUILD_URL}console"""
|
||||
}
|
||||
|
||||
failure {
|
||||
echo "❌ Pipeline 执行失败!请检查上述错误信息。"
|
||||
}
|
||||
|
||||
aborted {
|
||||
echo "⚠️ Pipeline 被中止!"
|
||||
echo "❌ Pipeline 执行失败!请查看日志。"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user