pipeline { agent any environment { // 语言环境设置 LANG = 'en_US.UTF-8' LC_ALL = 'en_US.UTF-8' // 项目配置 VITE_NODE_ENV = 'production' PROJECT_NAME = 'tg-live-game-web' // 分支配置 BUILD_BRANCH = 'main' // Node 配置 22.21.1 也是可用的 NODE_VERSION = 'v20.19.3' NODE_PATH = "/root/.nvm/versions/node/${NODE_VERSION}/bin" PNPM_PATH = "/root/.nvm/versions/node/${NODE_VERSION}/bin/pnpm" // Cloudflare 配置 CLOUDFLARE_PROJECT_NAME = 'tg-live-game-web' DEPLOY_PATH = 'web' DEPLOY_URL = "https://${CLOUDFLARE_PROJECT_NAME}.pwtk-dev.work" // 日志配置 BUILD_LOG_FILE = "${WORKSPACE}/build_${BUILD_NUMBER}.log" // ======================================== // 第三方服务配置 // ======================================== // Lark (飞书) 配置 LARK_WEBHOOK_URL = 'https://open.larksuite.com/open-apis/bot/v2/hook/7142cabc-603b-423f-8081-315cd3c19365' // Telegram 配置 TELEGRAM_BOT_TOKEN = '8132916684:AAEd1oObTMPxxcLV6LICKIDTPf1vBSn2rGk' TELEGRAM_CHAT_ID = '7061322031' // 禅道配置 (可选,设置 ZENTAO_ENABLED=true 启用) ZENTAO_ENABLED = 'false' // 默认关闭,需要配置好凭据后开启 ZENTAO_PRODUCT_ID = '1' ZENTAO_API_URL = 'https://zentao.pwtk.cc/zentao/api.php/v1' ZENTAO_BUG_BASE_URL = 'https://zentao.pwtk.cc/zentao/bug-view-' // 禅道凭据: 在 Jenkins 中配置 ZENTAO_CREDENTIALS (username/password) // Git 提交链接配置 COMMIT_BASE_URL = 'https://github.com/pwtk/tg-live-game-web/commit/' } options { // 保留最近的构建记录 buildDiscarder(logRotator( numToKeepStr: '1', daysToKeepStr: '1' )) // 禁止并发构建 disableConcurrentBuilds() // 构建超时时间 timeout(time: 30, unit: 'MINUTES') } stages { stage('初始化') { steps { script { sh ''' echo "=============== 初始化 ===============" echo "PROJECT_NAME=${PROJECT_NAME}" echo "BUILD_BRANCH=${BUILD_BRANCH}" echo "NODE_VERSION=${NODE_VERSION}" source ${WORKSPACE}/jenkins/lib/logger.sh source ${WORKSPACE}/jenkins/lib/time-utils.sh source ${WORKSPACE}/jenkins/lib/telegram.sh source ${WORKSPACE}/jenkins/lib/lark_dev.sh source ${WORKSPACE}/jenkins/lib/cloudflare_sync.sh # 导出函数供后续使用 export -f log_message export -f send_telegram_message export -f send_telegram_file export -f send_lark_message export -f send_lark_fix_notification export -f update_progress export -f get_network_time export -f deploy_to_cloudflare # 清理旧日志 cleanup_old_logs "/tmp" "build_" # 初始化日志文件 init_log_file log_message "=============== 构建开始 ===============" log_message "构建编号: ${BUILD_NUMBER}" log_message "工作目录: ${WORKSPACE}" # 记录构建开始时间 BUILD_START_TIME=$(get_network_time) BUILD_START_TIMESTAMP=$(get_timestamp "$BUILD_START_TIME") echo "BUILD_START_TIME='${BUILD_START_TIME}'" > ${WORKSPACE}/build_vars.env echo "BUILD_START_TIMESTAMP=${BUILD_START_TIMESTAMP}" >> ${WORKSPACE}/build_vars.env log_message "构建开始时间: ${BUILD_START_TIME}" # 发送初始进度 update_progress 0 "初始化" ''' } } } stage('环境检查') { steps { script { sh ''' source ${WORKSPACE}/jenkins/lib/logger.sh source ${WORKSPACE}/jenkins/lib/telegram.sh source ${WORKSPACE}/build_vars.env log_message "=============== 环境检查 ===============" log_message "检查 Node 版本: ${NODE_VERSION}" # 检查 Node 版本目录是否存在 NODE_DIR="/root/.nvm/versions/node/${NODE_VERSION}" if [ ! -d "${NODE_DIR}" ]; then log_message "❌ 错误: Node ${NODE_VERSION} 未安装" log_message "Node 目录不存在: ${NODE_DIR}" log_message "请先在服务器上安装 Node ${NODE_VERSION}" log_message "安装命令: nvm install ${NODE_VERSION}" # 列出已安装的 Node 版本 log_message "当前已安装的 Node 版本:" ls -la /root/.nvm/versions/node/ 2>/dev/null || log_message "无法读取 nvm 版本目录" send_telegram_message "❌ 构建中止 - ${PROJECT_NAME} Node 版本 ${NODE_VERSION} 未安装 请在服务器上执行: nvm install ${NODE_VERSION}" exit 1 fi # 检查 node 可执行文件是否存在 if [ ! -x "${NODE_PATH}/node" ]; then log_message "❌ 错误: Node 可执行文件不存在" log_message "路径: ${NODE_PATH}/node" send_telegram_message "❌ 构建中止 - ${PROJECT_NAME} Node 可执行文件不存在: ${NODE_PATH}/node" exit 1 fi # 检查 pnpm 是否存在 if [ ! -x "${PNPM_PATH}" ]; then log_message "⚠️ 警告: pnpm 未安装,尝试安装..." export PATH="${NODE_PATH}:$PATH" npm install -g pnpm if [ ! -x "${PNPM_PATH}" ]; then log_message "❌ 错误: pnpm 安装失败" send_telegram_message "❌ 构建中止 - ${PROJECT_NAME} pnpm 安装失败,请手动安装: npm install -g pnpm" exit 1 fi fi # 验证 Node 版本 export PATH="${NODE_PATH}:$PATH" ACTUAL_VERSION=$(node -v) log_message "✅ Node 版本验证通过: ${ACTUAL_VERSION}" log_message "✅ pnpm 版本: $(${PNPM_PATH} -v)" update_progress 10 "环境检查完成" ''' } } } stage('代码拉取') { steps { script { sh ''' source ${WORKSPACE}/jenkins/lib/logger.sh source ${WORKSPACE}/jenkins/lib/telegram.sh source ${WORKSPACE}/build_vars.env log_message "清理 Git 工作区(保留配置文件和 jenkins 目录)" git checkout -- . || true git clean -fd -e .env* -e jenkins/ -e node_modules/ || true log_message "检查当前 Git 状态" log_message "当前分支: $(git rev-parse --abbrev-ref HEAD)" log_message "最新提交: $(git log -1 --oneline)" # 获取最新提交信息 LAST_COMMIT_DATE=$(git log -1 --format='%ci' | sed 's/ [+-][0-9]\\{4\\}$//') LAST_COMMIT_MESSAGE=$(git log -1 --pretty=format:"%s") LAST_COMMIT_HASH=$(git log -1 --pretty=format:"%H" | cut -c1-8) echo "LAST_COMMIT_DATE='${LAST_COMMIT_DATE}'" >> ${WORKSPACE}/build_vars.env echo "LAST_COMMIT_MESSAGE='${LAST_COMMIT_MESSAGE}'" >> ${WORKSPACE}/build_vars.env echo "LAST_COMMIT_HASH='${LAST_COMMIT_HASH}'" >> ${WORKSPACE}/build_vars.env log_message "提交日期: ${LAST_COMMIT_DATE}" log_message "提交信息: ${LAST_COMMIT_MESSAGE}" log_message "提交哈希: ${LAST_COMMIT_HASH}" # 提取修复信息 log_message "提取本次构建的修复信息" LAST_SUCCESS_HASH=$(cat ${WORKSPACE}/.last_success_commit 2>/dev/null || echo "") if [ -z "${LAST_SUCCESS_HASH}" ]; then log_message "首次构建,提取最近 20 个 fix 提交" FIX_COMMITS=$(git log -20 --oneline | grep -E "^[a-f0-9]+ fix[:(]" || true) else log_message "上次成功构建: ${LAST_SUCCESS_HASH}" FIX_COMMITS=$(git log ${LAST_SUCCESS_HASH}..HEAD --oneline | grep -E "^[a-f0-9]+ fix[:(]" || true) fi if [ -z "${FIX_COMMITS}" ]; then log_message "没有找到包含 fix: 的提交" echo "FIX_LIST='无修复记录'" >> ${WORKSPACE}/build_vars.env echo "FIX_COUNT=0" >> ${WORKSPACE}/build_vars.env else FIX_COUNT=$(echo "$FIX_COMMITS" | wc -l) log_message "找到 ${FIX_COUNT} 个修复" echo "FIX_COUNT=${FIX_COUNT}" >> ${WORKSPACE}/build_vars.env fi # 发送构建开始通知 send_build_start_notification \ "${PROJECT_NAME}" \ "${BUILD_START_TIME}" \ "${LAST_COMMIT_DATE}" \ "${LAST_COMMIT_MESSAGE}" \ "${BUILD_BRANCH}" \ "${VITE_NODE_ENV}" update_progress 20 "代码拉取完成" ''' } } } stage('依赖安装') { steps { script { sh ''' source ${WORKSPACE}/jenkins/lib/logger.sh source ${WORKSPACE}/jenkins/lib/telegram.sh source ${WORKSPACE}/jenkins/lib/time-utils.sh source ${WORKSPACE}/build_vars.env export PATH="${NODE_PATH}:$PATH" log_message "Node 版本: $(node -v)" log_message "pnpm 版本: $(${PNPM_PATH} -v)" log_message "清理旧的构建产物和 node_modules" rm -rf dist rm -rf node_modules log_message "开始安装依赖" INSTALL_START=$(date +%s) if [ -f pnpm-lock.yaml ]; then log_message "使用 pnpm install --frozen-lockfile" if ! ${PNPM_PATH} install --frozen-lockfile 2>&1 | tee -a "$BUILD_LOG_FILE"; then log_message "frozen-lockfile 失败,尝试普通安装" ${PNPM_PATH} install 2>&1 | tee -a "$BUILD_LOG_FILE" fi else ${PNPM_PATH} install 2>&1 | tee -a "$BUILD_LOG_FILE" fi INSTALL_END=$(date +%s) INSTALL_DURATION=$((INSTALL_END - INSTALL_START)) log_message "依赖安装完成,耗时: ${INSTALL_DURATION}秒" update_progress 35 "依赖安装完成" ''' } } } stage('生成配置文件') { steps { script { sh ''' source ${WORKSPACE}/jenkins/lib/logger.sh source ${WORKSPACE}/jenkins/lib/telegram.sh log_message "检查环境配置文件" if [ -f ".env.production" ]; then log_message "✅ 找到 .env.production" log_message "配置内容:" cat .env.production | grep -v "^#" | grep -v "^$" | tee -a "$BUILD_LOG_FILE" else log_message "⚠️ 未找到 .env.production,使用默认配置" fi if [ -f ".env" ]; then log_message "✅ 找到 .env (基础配置)" fi log_message "配置文件检查完成" update_progress 50 "配置文件检查完成" ''' } } } stage('项目构建') { steps { script { sh ''' source ${WORKSPACE}/jenkins/lib/logger.sh source ${WORKSPACE}/jenkins/lib/telegram.sh source ${WORKSPACE}/jenkins/lib/time-utils.sh source ${WORKSPACE}/build_vars.env export PATH="${NODE_PATH}:$PATH" log_message "开始执行构建" if ! ${PNPM_PATH} run build 2>&1 | tee -a "$BUILD_LOG_FILE"; then log_message "错误: build 失败" FAIL_TIME=$(get_network_time) FAIL_TIMESTAMP=$(get_timestamp "$FAIL_TIME") eval $(calculate_duration ${BUILD_START_TIMESTAMP} ${FAIL_TIMESTAMP}) send_build_failure_notification \ "${PROJECT_NAME}" \ "${FAIL_TIME}" \ "${LAST_COMMIT_DATE}" \ "${LAST_COMMIT_MESSAGE}" \ "${LAST_COMMIT_HASH}" \ "${BUILD_BRANCH}" \ "${VITE_NODE_ENV}" \ "build" \ "构建命令执行失败" \ "${MINUTES}" \ "${SECONDS}" send_telegram_file "$BUILD_LOG_FILE" "❌ 构建失败 - 完整日志" exit 1 fi update_progress 70 "构建完成" ''' } } } stage('Cloudflare 部署') { steps { script { withCredentials([ string(credentialsId: 'XD_CLOUDFLARE_API_TOKEN', variable: 'CLOUDFLARE_API_TOKEN'), string(credentialsId: 'XD_CLOUDFLARE_ACCOUNT_ID', variable: 'CLOUDFLARE_ACCOUNT_ID') ]) { sh ''' source ${WORKSPACE}/jenkins/lib/lark_dev.sh source ${WORKSPACE}/jenkins/lib/logger.sh source ${WORKSPACE}/jenkins/lib/telegram.sh source ${WORKSPACE}/jenkins/lib/time-utils.sh source ${WORKSPACE}/jenkins/lib/cloudflare_sync.sh source ${WORKSPACE}/build_vars.env export PATH="${NODE_PATH}:$PATH" export CLOUDFLARE_API_TOKEN="${CLOUDFLARE_API_TOKEN}" if ! deploy_to_cloudflare "${CLOUDFLARE_PROJECT_NAME}" "${BUILD_BRANCH}" "dist"; then log_message "❌ Cloudflare Pages 部署失败" FAIL_TIME=$(get_network_time) FAIL_TIMESTAMP=$(get_timestamp "$FAIL_TIME") eval $(calculate_duration ${BUILD_START_TIMESTAMP} ${FAIL_TIMESTAMP}) send_build_failure_notification \ "${PROJECT_NAME}" \ "${FAIL_TIME}" \ "${LAST_COMMIT_DATE}" \ "${LAST_COMMIT_MESSAGE}" \ "${LAST_COMMIT_HASH}" \ "${BUILD_BRANCH}" \ "${VITE_NODE_ENV}" \ "Cloudflare 部署" \ "Cloudflare Pages 部署失败" \ "${MINUTES}" \ "${SECONDS}" send_telegram_file "$BUILD_LOG_FILE" "❌ 构建失败 - 完整日志" exit 1 fi log_message "发送部署完成通知" PACKAGE_VERSION=$(node -p "require('./package.json').version" 2>/dev/null || echo "未知") log_message "项目版本: ${PACKAGE_VERSION}" send_lark_message \ "🌐 Cloudflare 部署完成 - ${PROJECT_NAME}" \ "**访问地址**: [${DEPLOY_URL}](${DEPLOY_URL}) **版本**: ${PACKAGE_VERSION} **分支**: ${BUILD_BRANCH} **环境**: ${VITE_NODE_ENV} **提交**: ${LAST_COMMIT_HASH} **部署时间**: $(get_network_time)" log_message "✅ 部署完成: ${DEPLOY_URL}" update_progress 100 "部署完成" ''' } } } } } post { success { script { sh ''' source ${WORKSPACE}/jenkins/lib/logger.sh source ${WORKSPACE}/jenkins/lib/telegram.sh source ${WORKSPACE}/jenkins/lib/lark_dev.sh source ${WORKSPACE}/jenkins/lib/time-utils.sh source ${WORKSPACE}/build_vars.env eval $(get_log_stats) log_message "Warning数量: ${WARNING_COUNT}" log_message "Error数量: ${ERROR_COUNT}" BUILD_END_TIME=$(get_network_time) BUILD_END_TIMESTAMP=$(get_timestamp "$BUILD_END_TIME") eval $(calculate_duration ${BUILD_START_TIMESTAMP} ${BUILD_END_TIMESTAMP}) log_message "=============== 构建完成 ===============" log_message "总耗时: ${MINUTES}分${SECONDS}秒" send_build_success_notification \ "${PROJECT_NAME}" \ "${BUILD_END_TIME}" \ "${LAST_COMMIT_DATE}" \ "${LAST_COMMIT_MESSAGE}" \ "${LAST_COMMIT_HASH}" \ "${BUILD_BRANCH}" \ "${VITE_NODE_ENV}" \ "${MINUTES}" \ "${SECONDS}" send_telegram_file "$BUILD_LOG_FILE" "✅ 构建成功 - 完整日志" if [ "${FIX_COUNT:-0}" -gt 0 ]; then log_message "发送修复通知到 Lark" FORMATTED_FIXES=$(echo -e "${FIX_LIST}") send_lark_fix_notification \ "${PROJECT_NAME}" \ "${BUILD_END_TIME}" \ "${LAST_COMMIT_HASH}" \ "${FIX_COUNT}" \ "${FORMATTED_FIXES}" \ "${BUILD_BRANCH}" \ "${VITE_NODE_ENV}" fi echo "${LAST_COMMIT_HASH}" > ${WORKSPACE}/.last_success_commit log_message "保存成功构建标记: ${LAST_COMMIT_HASH}" ''' // 禅道 Bug 自动解决 (可选功能) script { if (env.ZENTAO_ENABLED == 'true') { withCredentials([ usernamePassword(credentialsId: 'ZENTAO_CREDENTIALS', usernameVariable: 'ZENTAO_USERNAME', passwordVariable: 'ZENTAO_PASSWORD') ]) { sh ''' source ${WORKSPACE}/jenkins/lib/logger.sh source ${WORKSPACE}/jenkins/lib/telegram.sh source ${WORKSPACE}/jenkins/lib/lark_dev.sh source ${WORKSPACE}/jenkins/lib/zentao_dev.sh log_message "=============== 禅道 Bug 追踪 ===============" # 调用禅道 Bug 自动解决 resolve_zentao_bugs "${WORKSPACE}" || true log_message "=============== 禅道 Bug 追踪完成 ===============" ''' } } else { echo "禅道集成未启用 (设置 ZENTAO_ENABLED=true 启用)" } } } } failure { script { sh ''' source ${WORKSPACE}/jenkins/lib/logger.sh source ${WORKSPACE}/jenkins/lib/telegram.sh log_message "构建失败,请查看日志" if [ -f "${WORKSPACE}/build_vars.env" ]; then source ${WORKSPACE}/build_vars.env send_telegram_message "❌ 构建失败 - ${PROJECT_NAME} - 请查看 Jenkins 日志" fi ''' } } always { script { archiveArtifacts artifacts: 'build_*.log', allowEmptyArchive: true } } } }