Răsfoiți Sursa

Implement Jenkins pipeline for automated build and deployment, including environment setup, dependency installation, and Cloudflare deployment. Add logging and notification features for build status updates via Lark and Telegram. Introduce utility scripts for time management and Cloudflare synchronization.

yb 3 săptămâni în urmă
părinte
comite
eba6fa2971

+ 6 - 0
CLAUDE.md

@@ -86,6 +86,12 @@ src/
 | 摄像头 | `/api/cameras/*` | 摄像头 CRUD |
 | 摄像头 | `/api/cameras/*` | 摄像头 CRUD |
 | Stream | `/api/stream/*` | 视频/直播管理 |
 | Stream | `/api/stream/*` | 视频/直播管理 |
 
 
+## Vibe Kanban
+
+| 属性 | 值 |
+|------|-----|
+| 项目 ID | `f81aecc3-9f5b-40d1-9fd0-f0ae1fd7c791` |
+
 ## 开发命令
 ## 开发命令
 
 
 ```bash
 ```bash

+ 447 - 0
Jenkinsfile

@@ -0,0 +1,447 @@
+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 配置
+        NODE_VERSION = 'v18.20.4'
+        NODE_PATH = "/root/.nvm/versions/node/${NODE_VERSION}/bin"
+        PNPM_PATH = '/root/.nvm/versions/node/v18.20.4/bin/pnpm'
+
+        // Cloudflare 配置
+        CLOUDFLARE_PROJECT_NAME = 'tg-live-game'
+        DEPLOY_PATH = 'web'
+        DEPLOY_URL = "https://${CLOUDFLARE_PROJECT_NAME}.pwtk.cc"
+
+        // 日志配置
+        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_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-'
+    }
+
+    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 "检查当前 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
+
+                        log_message "检查并更新 Git 子模块"
+
+                        if [ -f ".gitmodules" ]; then
+                            log_message "发现 .gitmodules,开始更新子模块"
+                            git submodule update --init --recursive
+                            log_message "子模块更新完成"
+                        else
+                            log_message "没有子模块,跳过"
+                        fi
+
+                        update_progress 30 "子模块更新完成"
+                    '''
+                }
+            }
+        }
+
+        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 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 40 "依赖安装完成"
+                    '''
+                }
+            }
+        }
+
+        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 55 "配置文件检查完成"
+                    '''
+                }
+            }
+        }
+
+        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: 'CLOUDFLARE_API_TOKEN', variable: 'CLOUDFLARE_API_TOKEN'),
+                        string(credentialsId: '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}"
+                '''
+            }
+        }
+
+        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
+            }
+        }
+    }
+}

+ 1 - 1
architecture.canvas

@@ -20,4 +20,4 @@
 		{"id":"edge_api_utils","fromNode":"api","fromSide":"right","toNode":"utils","toSide":"left","label":"request"},
 		{"id":"edge_api_utils","fromNode":"api","fromSide":"right","toNode":"utils","toSide":"left","label":"request"},
 		{"id":"edge_components_utils","fromNode":"components","fromSide":"bottom","toNode":"utils","toSide":"top"}
 		{"id":"edge_components_utils","fromNode":"components","fromSide":"bottom","toNode":"utils","toSide":"top"}
 	]
 	]
-}
+}

+ 94 - 0
jenkins/lib/cloudflare_sync.sh

@@ -0,0 +1,94 @@
+#!/bin/bash
+
+# Cloudflare Pages 部署模块
+# 提供 Cloudflare Pages 部署功能
+
+# 部署到 Cloudflare Pages 的函数
+deploy_to_cloudflare() {
+    local cloudflare_project_name="$1"
+    local build_branch="$2"
+    local dist_dir="${3:-dist}"
+
+    # 验证凭证是否存在(不记录敏感值)
+    if [ -z "${CLOUDFLARE_API_TOKEN}" ]; then
+        log_message "❌ 错误: CLOUDFLARE_API_TOKEN 未设置"
+        return 1
+    fi
+   
+    log_message "✅ Cloudflare 凭证验证通过"
+    
+    log_message "========================================="
+    log_message "开始部署到 Cloudflare Pages"
+    log_message "========================================="
+    log_message "项目名称: ${cloudflare_project_name}"
+    log_message "构建目录: ${dist_dir}"
+    log_message "分支: ${build_branch}"
+    
+    # 检查构建目录是否存在
+    if [ ! -d "${dist_dir}" ]; then
+        log_message "❌ 错误: 构建目录 ${dist_dir} 不存在"
+        log_message "当前目录内容:"
+        ls -la
+        return 1
+    fi
+    
+    log_message "✅ 构建目录检查通过"
+    log_message "构建产物大小: $(du -sh ${dist_dir} | cut -f1)"
+    log_message "文件数量: $(find ${dist_dir} -type f | wc -l)"
+    
+    # 部署到 Cloudflare Pages
+    log_message "🚀 开始部署到 Cloudflare Pages..."
+    DEPLOY_START=$(date +%s)
+    
+    # 使用 wrangler pages deploy 命令 (推荐方式)
+    # --project-name: Pages 项目名称
+    # --branch: 部署分支,用于预览环境
+    # --commit-dirty: 允许未提交的更改
+    
+    # 执行部署命令并捕获输出和退出状态
+    set +e  # 临时禁用错误退出
+    npx wrangler@3.90.0 pages deploy ${dist_dir} \
+        --project-name="${cloudflare_project_name}" \
+        --branch="${build_branch}" \
+        --commit-dirty=true \
+        2>&1 | tee -a "$BUILD_LOG_FILE"
+    DEPLOY_EXIT_CODE=${PIPESTATUS[0]}
+    set -e  # 重新启用错误退出
+    
+    DEPLOY_END=$(date +%s)
+    DEPLOY_DURATION=$((DEPLOY_END - DEPLOY_START))
+    
+    # 检查部署是否成功
+    if [ ${DEPLOY_EXIT_CODE} -eq 0 ]; then
+        log_message "========================================="
+        log_message "✅ Cloudflare Pages 部署成功!"
+        log_message "========================================="
+        log_message "部署耗时: ${DEPLOY_DURATION}秒"
+        
+        # 根据分支判断访问地址
+        if [ "${build_branch}" = "main" ] || [ "${build_branch}" = "master" ]; then
+            log_message "📡 生产环境地址: https://${cloudflare_project_name}.pages.dev"
+        else
+            log_message "📡 预览环境地址: https://${build_branch}.${cloudflare_project_name}.pages.dev"
+        fi
+        
+        return 0
+    else
+        log_message "========================================="
+        log_message "❌ Cloudflare Pages 部署失败 (退出码: ${DEPLOY_EXIT_CODE})"
+        log_message "========================================="
+        log_message "部署耗时: ${DEPLOY_DURATION}秒"
+        log_message ""
+        log_message "请检查以下事项:"
+        log_message "1. Cloudflare API Token 是否有效且具有 Pages 编辑权限"
+        log_message "2. Cloudflare Account ID 是否正确"
+        log_message "3. Pages 项目 '${cloudflare_project_name}' 是否已在 Dashboard 中创建"
+        log_message "4. 访问 https://dash.cloudflare.com/profile/api-tokens 检查 Token 权限"
+        log_message ""
+        log_message "如果项目不存在,请先在 Cloudflare Dashboard 创建:"
+        log_message "   Workers & Pages → Create → Pages → 项目名: ${cloudflare_project_name}"
+        
+        return 1
+    fi
+}
+

+ 172 - 0
jenkins/lib/lark_dev.sh

@@ -0,0 +1,172 @@
+#!/bin/bash
+
+# Lark (飞书) 通知模块
+# 提供 Lark Webhook 通知功能
+#
+# ⚠️ 重要提示:
+#   所有配置项必须从 Jenkinsfile 的 environment 部分传入
+#   此脚本不包含任何硬编码配置,确保配置集中管理
+#
+# 必需的环境变量:
+#   - LARK_WEBHOOK_URL: Lark Webhook URL(必须在 Jenkinsfile 中设置)
+
+# 验证必需的环境变量
+if [ -z "${LARK_WEBHOOK_URL}" ]; then
+    echo "错误: LARK_WEBHOOK_URL 环境变量未设置" >&2
+    echo "请确保在 Jenkinsfile 的 environment 部分设置了 LARK_WEBHOOK_URL" >&2
+    exit 1
+fi
+
+# 发送 Lark 消息的函数
+send_lark_message() {
+    local title="$1"
+    local content="$2"
+
+    # 根据标题判断颜色(成功=绿色,失败=红色,其他=蓝色)
+    local color="blue"
+    if echo "$title" | grep -q "✅\|成功"; then
+        color="green"
+    elif echo "$title" | grep -q "❌\|失败"; then
+        color="red"
+    fi
+
+    # 转义 JSON 特殊字符(简化版,只处理必要的转义)
+    local escaped_title=$(echo "$title" | sed 's/"/\\"/g')
+    local escaped_content=$(echo "$content" | sed 's/"/\\"/g' | sed ':a;N;$!ba;s/\n/\\n/g')
+
+    # 构建 JSON payload(使用测试脚本中验证过的格式)
+    local payload="{
+    \"msg_type\": \"interactive\",
+    \"card\": {
+        \"header\": {
+            \"title\": {
+                \"tag\": \"plain_text\",
+                \"content\": \"${escaped_title}\"
+            },
+            \"template\": \"${color}\"
+        },
+        \"elements\": [
+            {
+                \"tag\": \"div\",
+                \"text\": {
+                    \"tag\": \"lark_md\",
+                    \"content\": \"${escaped_content}\"
+                }
+            }
+        ]
+    }
+}"
+
+    # 发送到 Lark(记录响应)
+    local response=$(curl -s -X POST "${LARK_WEBHOOK_URL}" \
+        -H "Content-Type: application/json" \
+        -d "$payload")
+
+    # 检查是否成功
+    if echo "$response" | grep -q '"code":0'; then
+        if type -t log_message >/dev/null 2>&1; then
+            log_message "✓ Lark 通知发送成功"
+        fi
+    else
+        if type -t log_message >/dev/null 2>&1; then
+            log_message "✗ Lark 通知发送失败: $response"
+        fi
+    fi
+}
+
+# 发送修复通知到 Lark 的函数
+send_lark_fix_notification() {
+    local project_name="$1"
+    local build_time="$2"
+    local commit_hash="$3"
+    local fix_count="$4"
+    local fix_list="$5"
+    local branch="$6"
+    local env="$7"
+
+    local title="🔧 ${project_name} - 发现 ${fix_count} 个修复"
+
+    # 转义 Markdown 特殊字符
+    local escaped_fixes=$(echo "$fix_list" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g')
+
+    local content="**构建时间:** ${build_time}
+**分支:** ${branch}
+**环境:** ${env}
+**提交:** ${commit_hash}
+**修复数量:** ${fix_count}
+
+---
+
+**修复内容:**
+${escaped_fixes}
+
+---
+✅ 构建成功并已部署"
+
+    log_message "发送 Lark 通知: ${title}"
+    send_lark_message "$title" "$content"
+}
+
+# 发送版本发布通知(带 Bug 链接)
+send_release_notification_with_bugs() {
+    local previous_tag="$1"
+    local current_tag="$2"
+    local bug_count="$3"
+    local bug_list="$4"     # 格式: bug_id|bug_title|zentao_url (每行一个)
+    local code_changes="$5" # 格式: type|commit_message (每行一个)
+
+    # 构建已解决 Bug 链接列表
+    local bug_section=""
+    if [ ${bug_count} -gt 0 ] && [ -n "$bug_list" ]; then
+        bug_section="\\n\\n**已解决Bug** (${bug_count}个):\\n"
+        while IFS='|' read -r bug_id bug_title bug_url; do
+            if [ -n "$bug_id" ] && [ -n "$bug_url" ]; then
+                # 使用 Markdown 链接格式
+                bug_section="${bug_section}• [#${bug_id} ${bug_title}](${bug_url})\\n"
+            fi
+        done <<<"$bug_list"
+    fi
+
+    # 构建代码更改列表
+    local changes_section=""
+    if [ -n "$code_changes" ]; then
+        changes_section="\\n\\n**代码更改:**\\n"
+        while IFS='|' read -r change_type change_message; do
+            if [ -n "$change_type" ] && [ -n "$change_message" ]; then
+                changes_section="${changes_section}• ${change_type}: ${change_message}\\n"
+            fi
+        done <<<"$code_changes"
+    fi
+
+    # 构建完整消息内容
+    local content="**版本:** ${previous_tag} → ${current_tag}${bug_section}${changes_section}"
+
+    # 发送飞书卡片通知
+    curl -s -X POST "${LARK_WEBHOOK_URL}" \
+        -H "Content-Type: application/json" \
+        -d "{
+            \"msg_type\": \"interactive\",
+            \"card\": {
+                \"header\": {
+                    \"title\": {
+                        \"tag\": \"plain_text\",
+                        \"content\": \"🚀 pwtk管端端版本发布\"
+                    },
+                    \"template\": \"blue\"
+                },
+                \"elements\": [
+                    {
+                        \"tag\": \"div\",
+                        \"text\": {
+                            \"tag\": \"lark_md\",
+                            \"content\": \"${content}\"
+                        }
+                    }
+                ]
+            }
+        }" >/dev/null
+
+    if type -t log_message >/dev/null 2>&1; then
+        log_message "✓ 版本发布通知已发送 (Bug: ${bug_count})"
+    fi
+}

+ 49 - 0
jenkins/lib/logger.sh

@@ -0,0 +1,49 @@
+#!/bin/bash
+
+# 日志记录模块
+# 提供日志记录和日志文件管理功能
+#
+# ⚠️ 重要提示:
+#   所有配置项必须从 Jenkinsfile 的 environment 部分传入
+#   此脚本不包含任何硬编码配置,确保配置集中管理
+#
+# 必需的环境变量:
+#   - BUILD_LOG_FILE: 日志文件路径(应在 Jenkinsfile 中设置)
+
+# 全局变量(从环境变量读取,如果没有设置则使用默认值作为后备)
+BUILD_LOG_FILE="${BUILD_LOG_FILE:-/tmp/build_$(date '+%Y%m%d_%H%M%S').log}"
+
+# 初始化日志文件(带 UTF-8 BOM)
+init_log_file() {
+    echo -ne '\xEF\xBB\xBF' > "$BUILD_LOG_FILE"
+}
+
+# 日志记录函数
+log_message() {
+    local message="$1"
+    echo -e "[$(date '+%Y-%m-%d %H:%M:%S')] $message" | tee -a "$BUILD_LOG_FILE"
+}
+
+# 清理旧日志文件,只保留最近5个
+cleanup_old_logs() {
+    local log_dir="${1:-/tmp}"
+    local log_prefix="${2:-build_}"
+
+    # 列出所有构建日志文件(按时间从旧到新排序),删除旧的文件,只保留最新的5个
+    find "$log_dir" -name "${log_prefix}*.log" -type f -printf '%T+ %p\n' | \
+    sort | \
+    head -n -5 | \
+    awk '{print $2}' | \
+    xargs -r rm -f
+
+    log_message "已清理旧日志文件"
+}
+
+# 统计日志中的警告和错误数量
+get_log_stats() {
+    local warning_count=$(grep -i "warning" "$BUILD_LOG_FILE" | wc -l)
+    local error_count=$(grep -i "error" "$BUILD_LOG_FILE" | wc -l)
+
+    echo "WARNING_COUNT=${warning_count}"
+    echo "ERROR_COUNT=${error_count}"
+}

+ 160 - 0
jenkins/lib/telegram.sh

@@ -0,0 +1,160 @@
+#!/bin/bash
+
+# Telegram 通知模块
+# 提供 Telegram Bot 消息发送和文件上传功能
+#
+# ⚠️ 重要提示:
+#   所有配置项必须从 Jenkinsfile 的 environment 部分传入(通过 credentials)
+#   此脚本不包含任何硬编码配置,确保配置集中管理
+#
+# 必需的环境变量(通过 Jenkins credentials 注入):
+#   - TELEGRAM_BOT_TOKEN: Telegram Bot Token
+#   - TELEGRAM_CHAT_ID: Telegram Chat ID
+
+# 验证必需的环境变量
+if [ -z "${TELEGRAM_BOT_TOKEN}" ] || [ -z "${TELEGRAM_CHAT_ID}" ]; then
+    echo "警告: Telegram 配置未设置,Telegram 通知功能将被禁用" >&2
+    echo "如需启用,请在 Jenkinsfile 中使用 withCredentials 注入 TELEGRAM_BOT_TOKEN 和 TELEGRAM_CHAT_ID" >&2
+    # 不退出,允许脚本继续运行(Telegram 是可选的)
+fi
+
+# 发送 Telegram 消息的函数
+send_telegram_message() {
+    local message="$1"
+
+    # 检查配置是否可用
+    if [ -z "${TELEGRAM_BOT_TOKEN}" ] || [ -z "${TELEGRAM_CHAT_ID}" ]; then
+        if [ "$(type -t log_message)" = "function" ]; then
+            log_message "警告: Telegram 配置未设置,跳过通知"
+        fi
+        return 0
+    fi
+
+    if [ "$(type -t log_message)" = "function" ]; then
+        log_message "Telegram通知: $message"
+    fi
+
+    curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
+        -H "Content-Type: application/json; charset=utf-8" \
+        -d "$(echo -n "{\"chat_id\":\"${TELEGRAM_CHAT_ID}\",\"text\":\"${message}\",\"parse_mode\":\"HTML\"}" | iconv -f UTF-8 -t UTF-8)"
+}
+
+# 发送文件到 Telegram 的函数
+send_telegram_file() {
+    local file="$1"
+    local caption="$2"
+
+    # 检查配置是否可用
+    if [ -z "${TELEGRAM_BOT_TOKEN}" ] || [ -z "${TELEGRAM_CHAT_ID}" ]; then
+        if [ "$(type -t log_message)" = "function" ]; then
+            log_message "警告: Telegram 配置未设置,跳过文件上传"
+        fi
+        return 0
+    fi
+
+    if [ "$(type -t log_message)" = "function" ]; then
+        log_message "发送日志文件到Telegram: $caption"
+    fi
+
+    curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendDocument" \
+        -H "Content-Type: multipart/form-data; charset=utf-8" \
+        -F "chat_id=${TELEGRAM_CHAT_ID}" \
+        -F "document=@$file" \
+        -F "caption=$caption"
+}
+
+# 更新进度条的函数
+update_progress() {
+    local percent=$1
+    local step_name="$2"
+    local progress_bar=""
+    local filled=$((percent / 10))
+    local empty=$((10 - filled))
+
+    # 构建进度条
+    for ((i=0; i<filled; i++)); do
+        progress_bar="${progress_bar}🟩"
+    done
+    for ((i=0; i<empty; i++)); do
+        progress_bar="${progress_bar}⬜"
+    done
+
+    # 如果达到100%,添加成功标记
+    if [ $percent -eq 100 ]; then
+        progress_bar="${progress_bar} ${percent}% ✅"
+    else
+        progress_bar="${progress_bar} ${percent}%"
+    fi
+
+    # 发送进度更新
+    send_telegram_message "构建进度: ${step_name}
+${progress_bar}"
+}
+
+# 发送构建开始通知
+send_build_start_notification() {
+    local project_name="$1"
+    local build_time="$2"
+    local commit_date="$3"
+    local commit_message="$4"
+    local branch_name="$5"
+    local env="$6"
+
+    send_telegram_message "🚀 构建开始
+时间: ${build_time}
+项目名称: ${project_name}
+提交代码日期: ${commit_date}
+提交信息: ${commit_message}
+分支: ${branch_name}
+环境: ${env}"
+}
+
+# 发送构建成功通知
+send_build_success_notification() {
+    local project_name="$1"
+    local build_time="$2"
+    local commit_date="$3"
+    local commit_message="$4"
+    local commit_hash="$5"
+    local branch_name="$6"
+    local env="$7"
+    local minutes="$8"
+    local seconds="$9"
+
+    send_telegram_message "✅ 构建成功
+时间: ${build_time}
+项目名称: ${project_name}
+提交代码日期: ${commit_date}
+提交信息: ${commit_message}
+commit hash: ${commit_hash}
+分支: ${branch_name}
+环境: ${env}
+耗时: ${minutes}分${seconds}秒"
+}
+
+# 发送构建失败通知
+send_build_failure_notification() {
+    local project_name="$1"
+    local fail_time="$2"
+    local commit_date="$3"
+    local commit_message="$4"
+    local commit_hash="$5"
+    local branch_name="$6"
+    local env="$7"
+    local fail_stage="$8"
+    local error_message="$9"
+    local minutes="${10}"
+    local seconds="${11}"
+
+    send_telegram_message "❌ 构建失败
+时间: ${fail_time}
+项目名称: ${project_name}
+提交代码日期: ${commit_date}
+提交信息: ${commit_message}
+commit hash: ${commit_hash}
+分支: ${branch_name}
+环境: ${env}
+失败阶段: ${fail_stage}
+错误: ${error_message}
+耗时: ${minutes}分${seconds}秒"
+}

+ 54 - 0
jenkins/lib/time-utils.sh

@@ -0,0 +1,54 @@
+#!/bin/bash
+
+# 时间工具模块
+# 提供网络时间获取和时间计算功能
+
+# 获取网络时间的函数
+get_network_time() {
+    # 使用超时控制的curl请求
+    local time_data
+    local network_time
+
+    # 尝试 WorldTimeAPI,超时5秒,忽略错误输出
+    time_data=$(curl -s -m 5 "http://worldtimeapi.org/api/timezone/Asia/Tokyo" 2>/dev/null || true)
+    if [ ! -z "$time_data" ]; then
+        network_time=$(echo "$time_data" | grep -o '"datetime":"[^"]*"' | cut -d'"' -f4 | cut -d'.' -f1 2>/dev/null || true)
+        if [ ! -z "$network_time" ]; then
+            echo "$network_time"
+            return 0
+        fi
+    fi
+
+    # 如果第一个API失败,尝试备用API,超时5秒
+    time_data=$(curl -s -m 5 "http://worldclockapi.com/api/json/utc/now" 2>/dev/null || true)
+    if [ ! -z "$time_data" ]; then
+        local utc_time=$(echo "$time_data" | grep -o '"currentDateTime":"[^"]*"' | cut -d'"' -f4 | cut -d'.' -f1 2>/dev/null || true)
+        if [ ! -z "$utc_time" ]; then
+            date -d "$utc_time UTC + 9 hours" "+%Y-%m-%d %H:%M:%S" 2>/dev/null || date '+%Y-%m-%d %H:%M:%S'
+            return 0
+        fi
+    fi
+
+    # 如果所有API都失败或超时,使用系统时间
+    echo "[WARNING] Network time API failed, using system time" >&2
+    date '+%Y-%m-%d %H:%M:%S'
+    return 0
+}
+
+# 计算两个时间戳之间的时长(返回分钟和秒)
+calculate_duration() {
+    local start_timestamp=$1
+    local end_timestamp=$2
+    local duration=$((end_timestamp - start_timestamp))
+    local minutes=$((duration / 60))
+    local seconds=$((duration % 60))
+
+    echo "MINUTES=${minutes}"
+    echo "SECONDS=${seconds}"
+}
+
+# 获取当前时间戳
+get_timestamp() {
+    local time_str="${1:-$(date '+%Y-%m-%d %H:%M:%S')}"
+    date -d "$time_str" +%s
+}

+ 772 - 0
jenkins/lib/zentao_dev.sh

@@ -0,0 +1,772 @@
+#!/bin/bash
+
+# ============================================================================
+# 禅道 Bug 追踪模块 - DEV 开发环境专用版本
+# ============================================================================
+# ⚠️ 重要提示:
+#   此文件仅供 Jenkinsfile.dev 使用
+#   Jenkinsfile.release 严禁使用此文件,必须使用 zentao_dev.sh
+#
+# 功能说明:
+#   1. 从 Git 提交历史中提取 Bug 修复信息
+#   2. 调用禅道 API 自动更新 Bug 状态为 "resolved"
+#   3. 发送飞书(Lark)通知 - 每个 Bug 单独发送绿色卡片
+#   4. 发送飞书版本发布汇总通知 - 蓝色卡片汇总所有已解决Bug
+#   5. 发送 Telegram 汇总通知
+#
+# 使用场景:
+#   - dev 环境: 每次代码推送自动执行
+#   - 分析范围: 从上次成功构建到当前 HEAD 的所有提交
+#   - 自动更新: 将 active/wait 状态的 Bug 更新为 resolved
+#   - 即时通知: 每解决一个 Bug 立即发送飞书通知
+#
+# 环境隔离:
+#   ✅ 仅用于开发环境 (Jenkinsfile.dev)
+#   ❌ 不得用于生产发布 (Jenkinsfile.release)
+# ============================================================================
+
+# ----------------------------------------------------------------------------
+# 配置部分
+# ----------------------------------------------------------------------------
+# ⚠️ 重要提示:
+#   所有配置项必须从 Jenkinsfile 的 environment 部分传入
+#   此脚本不包含任何硬编码配置,确保配置集中管理
+#
+# 必需的环境变量:
+#   - ZENTAO_PRODUCT_ID: 禅道产品 ID
+#   - ZENTAO_API_URL: 禅道 API 地址
+#   - ZENTAO_BUG_BASE_URL: 禅道 Bug 详情页基础 URL
+#   - COMMIT_BASE_URL: Git 提交详情页基础 URL
+#   - LARK_WEBHOOK_URL: Lark Webhook URL
+# ----------------------------------------------------------------------------
+
+# 验证必需的环境变量
+if [ -z "${ZENTAO_PRODUCT_ID}" ] || [ -z "${ZENTAO_API_URL}" ] ||
+    [ -z "${ZENTAO_BUG_BASE_URL}" ] || [ -z "${COMMIT_BASE_URL}" ] ||
+    [ -z "${LARK_WEBHOOK_URL}" ]; then
+    echo "错误: 缺少必需的环境变量配置" >&2
+    echo "请确保在 Jenkinsfile 的 environment 部分设置了所有必需变量" >&2
+    exit 1
+fi
+
+# ----------------------------------------------------------------------------
+# 函数: 发送单个 Bug 解决通知到飞书
+# ----------------------------------------------------------------------------
+# 参数:
+#   $1: bug_id         - Bug ID (例如: 2262)
+#   $2: bug_title      - Bug 标题 (例如: "登录页面404错误")
+#   $3: old_status     - 旧状态 (例如: "active")
+#   $4: commit_info    - 提交信息 (例如: "[abc123] 修复登录问题")
+#   $5: bug_url        - Bug 详情页 URL
+#
+# 功能:
+#   发送一个绿色的飞书卡片,显示单个 Bug 的解决信息,包含查看详情按钮
+# ----------------------------------------------------------------------------
+send_bug_resolved_notification() {
+    local bug_id="$1"
+    local bug_title="$2"
+    local old_status="$3"
+    local commit_info="$4"
+    local bug_url="$5"
+    local code_url="$6"
+    # 发送 POST 请求到飞书 Webhook
+    curl -s -X POST "${LARK_WEBHOOK_URL}" \
+        -H "Content-Type: application/json" \
+        -d "{
+            \"msg_type\": \"interactive\",
+            \"card\": {
+                \"header\": {
+                    \"title\": {
+                        \"tag\": \"plain_text\",
+                        \"content\": \"🐛 pwtk管端端Bug更新\"
+                    },
+                    \"template\": \"green\"
+                },
+                \"elements\": [
+                    {
+                        \"tag\": \"div\",
+                        \"text\": {
+                            \"tag\": \"lark_md\",
+                            \"content\": \"**Bug #${bug_id}**: ${bug_title}\"
+                        }
+                    },
+                    {
+                        \"tag\": \"action\",
+                        \"actions\": [
+                            {
+                                \"tag\": \"button\",
+                                \"text\": {
+                                    \"tag\": \"plain_text\",
+                                    \"content\": \"查看代码\"
+                                },
+                                \"type\": \"primary\",
+                                \"url\": \"${code_url}\"
+                            }
+                        ]
+                    }
+                ]
+            }
+        }" >/dev/null
+}
+
+# ----------------------------------------------------------------------------
+# 函数: 发送版本发布汇总通知到飞书
+# ----------------------------------------------------------------------------
+# 参数:
+#   $1: previous_tag   - 上一个版本标签 (例如: "v1.2.30" 或 "dev")
+#   $2: current_tag    - 当前版本标签 (例如: "v1.2.31" 或 "abc123")
+#   $3: resolved_count - 已解决的 Bug 数量
+#   $4: resolved_bugs  - 已解决的 Bug 列表 (格式: "bug_info|bug_url" 每行一个)
+#
+# 功能:
+#   发送一个蓝色的飞书卡片,汇总本次发布解决的所有 Bug
+# ----------------------------------------------------------------------------
+send_release_summary_notification() {
+    local previous_tag="$1"
+    local current_tag="$2"
+    local resolved_count="$3"
+    local resolved_bugs="$4"
+
+    # 构建已解决 Bug 链接列表
+    local resolved_links=""
+    if [ -n "$resolved_bugs" ]; then
+        # 逐行读取 Bug 信息,格式化为 Markdown 链接
+        while IFS='|' read -r bug_info bug_url; do
+            if [ -n "$bug_info" ]; then
+                resolved_links="${resolved_links}• [${bug_info}](${bug_url})\\n"
+            fi
+        done <<<"$resolved_bugs"
+    fi
+
+    # 构建完整消息内容
+    local content="**版本**: ${previous_tag} → ${current_tag}\\n\\n"
+
+    if [ ${resolved_count} -gt 0 ]; then
+        content="${content}**已解决Bug** (${resolved_count}个):\\n${resolved_links}"
+    else
+        content="${content}**本次发布未解决Bug**"
+    fi
+
+    # 发送 POST 请求到飞书 Webhook
+    curl -s -X POST "${LARK_WEBHOOK_URL}" \
+        -H "Content-Type: application/json" \
+        -d "{
+            \"msg_type\": \"interactive\",
+            \"card\": {
+                \"header\": {
+                    \"title\": {
+                        \"tag\": \"plain_text\",
+                        \"content\": \"🚀 pwtk管端端版本发布\"
+                    },
+                    \"template\": \"blue\"
+                },
+                \"elements\": [
+                    {
+                        \"tag\": \"div\",
+                        \"text\": {
+                            \"tag\": \"lark_md\",
+                            \"content\": \"${content}\"
+                        }
+                    }
+                ]
+            }
+        }" >/dev/null
+}
+
+# ----------------------------------------------------------------------------
+# 函数: 禅道 Bug 追踪主流程
+# ----------------------------------------------------------------------------
+# 参数:
+#   $1: start_ref      - 起始 Git 引用 (例如: "HEAD~10" 或 "v1.2.30")
+#   $2: end_ref        - 结束 Git 引用 (例如: "HEAD" 或 "v1.2.31")
+#   $3: version_info   - 版本信息 (例如: "dev (abc123)" 或 "v1.2.30 → v1.2.31")
+#   $4: update_status  - 是否更新禅道状态 ("true"=更新, "false"=只汇总)
+#
+# 功能:
+#   1. 从 Git 提交历史中提取符合格式的 Bug 修复 (fix:2262, fix-2262, fix: 2262 2265)
+#   2. 查询禅道获取 Bug 状态和标题
+#   3. 如果 update_status=true,调用禅道 API 更新 Bug 状态为 "resolved"
+#   4. 发送飞书通知 (每个 Bug 单独发送 + 最后发送汇总)
+#   5. 发送 Telegram 汇总通知
+#
+# 提交格式支持:
+#   - fix:2262 修复登录问题
+#   - fix-2262 修复登录问题
+#   - fix: 2262 修复登录问题
+#   - fix: 2262 2265 修复多个问题 (一次提交修复多个 Bug)
+# ----------------------------------------------------------------------------
+process_zentao_bugs() {
+    local start_ref="$1"
+    local end_ref="$2"
+    local version_info="$3"
+    local update_status="${4:-false}" # 默认为 false,只汇总不更新
+
+    # 输出开始信息
+    if type -t log_message >/dev/null 2>&1; then
+        log_message "=============== 禅道 Bug 追踪开始 ==============="
+        log_message "分析范围: ${start_ref}..${end_ref}"
+        log_message "版本信息: ${version_info}"
+        log_message "更新禅道状态: ${update_status}"
+    fi
+
+    # 初始化 bug 列表关联数组
+    # 格式: BUG_MAP[bug_id] = "commit_hash|author|message\ncommit_hash|author|message"
+    declare -A BUG_MAP
+
+    # 获取所有符合格式的提交 (保存到变量以避免管道子shell问题)
+    # 格式: commit_hash|author|subject
+    COMMITS=$(git log "${start_ref}..${end_ref}" --pretty=format:"%h|%an|%s")
+
+    # 遍历每个提交,提取 Bug ID
+    while IFS='|' read -r commit_hash author commit_message; do
+        # 检查提交信息是否包含 fix: 或 fix- 格式
+        if echo "$commit_message" | grep -qE "fix[:-]\s*[0-9]"; then
+            # 提取所有 Bug ID (支持多个 Bug ID,用空格分隔)
+            # 例如: "fix: 2262 2265" 提取出 "2262 2265"
+            # 步骤1: 提取 fix: 或 fix- 后面的所有数字
+            # 步骤2: 移除 fix: 或 fix-
+            # 步骤3: 使用 xargs 自动trim前后空格
+            bug_ids=$(echo "$commit_message" | grep -oE "fix[:-]\s*([0-9]+(\s+[0-9]+)*)" | sed -E 's/fix[:-]//' | xargs)
+
+            if [ -n "$bug_ids" ]; then
+                if type -t log_message >/dev/null 2>&1; then
+                    log_message "发现 Bug 修复: [$commit_hash] $author - $commit_message"
+                fi
+
+                # 分割多个 Bug ID,逐个处理
+                for bug_id in $bug_ids; do
+                    if [ -n "$bug_id" ]; then
+                        # 存储到关联数组 (如果已存在则追加,支持一个 Bug 多次提交修复)
+                        if [ -n "${BUG_MAP[$bug_id]}" ]; then
+                            BUG_MAP[$bug_id]="${BUG_MAP[$bug_id]}
+${commit_hash}|${author}|${commit_message}"
+                        else
+                            BUG_MAP[$bug_id]="${commit_hash}|${author}|${commit_message}"
+                        fi
+                    fi
+                done
+            fi
+        fi
+    done <<<"$COMMITS"
+
+    # 统计发现的 Bug 数量
+    BUG_COUNT=${#BUG_MAP[@]}
+
+    if type -t log_message >/dev/null 2>&1; then
+        log_message "共发现 ${BUG_COUNT} 个 Bug"
+    fi
+
+    # 如果没有发现 Bug,直接返回
+    if [ $BUG_COUNT -eq 0 ]; then
+        if type -t log_message >/dev/null 2>&1; then
+            log_message "本次构建未发现符合格式的 Bug 修复"
+            log_message "=============== 禅道 Bug 追踪结束 ==============="
+        fi
+        return 0
+    fi
+
+    # 获取禅道 API Token
+    if type -t log_message >/dev/null 2>&1; then
+        log_message "正在获取禅道 API Token..."
+    fi
+
+    # 调用禅道登录接口获取 Token
+    ZENTAO_TOKEN_RESPONSE=$(curl -s -X POST "${ZENTAO_API_URL}/tokens" \
+        -H "Content-Type: application/json" \
+        -d "{\"account\":\"${ZENTAO_USERNAME}\",\"password\":\"${ZENTAO_PASSWORD}\"}")
+
+    # 从响应中提取 Token
+    ZENTAO_TOKEN=$(echo "$ZENTAO_TOKEN_RESPONSE" | grep -o '"token":"[^"]*"' | cut -d'"' -f4)
+
+    # 检查 Token 是否获取成功
+    if [ -z "$ZENTAO_TOKEN" ]; then
+        if type -t log_message >/dev/null 2>&1; then
+            log_message "警告: 无法获取禅道 Token,仅生成链接列表"
+            log_message "响应: ${ZENTAO_TOKEN_RESPONSE}"
+        fi
+
+        # Token 获取失败,只生成链接列表不更新状态
+        BUG_LINKS=""
+        for bug_id in $(echo "${!BUG_MAP[@]}" | tr ' ' '\n' | sort -n); do
+            COMMIT_INFO=""
+            # 遍历该 Bug 的所有相关提交
+            while IFS= read -r commit_line; do
+                commit_hash=$(echo "$commit_line" | cut -d'|' -f1)
+                author=$(echo "$commit_line" | cut -d'|' -f2)
+                COMMIT_INFO="${COMMIT_INFO}  修复人: ${author} [${commit_hash}]
+"
+            done <<<"${BUG_MAP[$bug_id]}"
+
+            BUG_LINKS="${BUG_LINKS}#${bug_id}
+${COMMIT_INFO}  ${ZENTAO_BUG_BASE_URL}${bug_id}.html
+
+"
+        done
+
+        # 发送 Telegram 通知 (包含警告信息)
+        if type -t send_telegram_message >/dev/null 2>&1; then
+            send_telegram_message "🐛 Bug 修复汇总
+版本: ${version_info}
+
+${BUG_LINKS}
+📊 总计: ${BUG_COUNT} 个 Bug 已修复
+⚠️ 禅道状态未更新(Token获取失败)"
+        fi
+        return 1
+    fi
+
+    if type -t log_message >/dev/null 2>&1; then
+        log_message "成功获取禅道 Token"
+    fi
+
+    # 初始化汇总信息
+    BUG_LINKS=""          # Telegram 消息用的链接列表
+    RESOLVED_COUNT=0      # 已解决的 Bug 数量
+    ERROR_COUNT=0         # 失败的 Bug 数量
+    RESOLVED_BUGS_LIST="" # 飞书汇总用的 Bug 列表
+
+    # 处理每个 Bug (按 Bug ID 排序)
+    for bug_id in $(echo "${!BUG_MAP[@]}" | tr ' ' '\n' | sort -n); do
+        if type -t log_message >/dev/null 2>&1; then
+            log_message "----------------------------------------"
+            log_message "处理 Bug #${bug_id}"
+        fi
+
+        # 查询 Bug 状态
+        BUG_INFO=$(curl -s -X GET "${ZENTAO_API_URL}/bugs/${bug_id}" \
+            -H "Token: ${ZENTAO_TOKEN}")
+
+        # 使用 Python 解析 JSON,正确处理中文
+        BUG_STATUS=$(echo "$BUG_INFO" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data.get('status', ''))" 2>/dev/null)
+        BUG_TITLE=$(echo "$BUG_INFO" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data.get('title', ''))" 2>/dev/null)
+        BUG_OPENED_BY=$(echo "$BUG_INFO" | python3 -c "import sys, json; data=json.load(sys.stdin); openedBy=data.get('openedBy', {}); print(openedBy.get('account', '') if isinstance(openedBy, dict) else '')" 2>/dev/null)
+
+        # 检查是否成功获取 Bug 信息
+        if [ -z "$BUG_STATUS" ]; then
+            if type -t log_message >/dev/null 2>&1; then
+                log_message "警告: 无法获取 Bug #${bug_id} 的状态信息"
+            fi
+            ERROR_COUNT=$((ERROR_COUNT + 1))
+
+            # 添加到失败列表
+            COMMIT_INFO=""
+            while IFS= read -r commit_line; do
+                commit_hash=$(echo "$commit_line" | cut -d'|' -f1)
+                author=$(echo "$commit_line" | cut -d'|' -f2)
+                COMMIT_INFO="${COMMIT_INFO}  修复人: ${author} [${commit_hash}]
+"
+            done <<<"${BUG_MAP[$bug_id]}"
+
+            BUG_LINKS="${BUG_LINKS}❌ #${bug_id} (查询失败)
+${COMMIT_INFO}  ${ZENTAO_BUG_BASE_URL}${bug_id}.html
+
+"
+            continue
+        fi
+
+        if type -t log_message >/dev/null 2>&1; then
+            log_message "Bug #${bug_id} 状态: ${BUG_STATUS}"
+            log_message "Bug #${bug_id} 标题: ${BUG_TITLE}"
+            log_message "Bug #${bug_id} 创建者: ${BUG_OPENED_BY}"
+        fi
+
+        # 获取提交信息 (所有情况都需要)
+        COMMIT_INFO=""
+        while IFS= read -r commit_line; do
+            commit_hash=$(echo "$commit_line" | cut -d'|' -f1)
+            author=$(echo "$commit_line" | cut -d'|' -f2)
+            COMMIT_INFO="${COMMIT_INFO}  修复人: ${author} [${commit_hash}]
+"
+        done <<<"${BUG_MAP[$bug_id]}"
+
+        # 检查是否为未解决状态 (active 或 wait 状态才需要处理)
+        if [ "$BUG_STATUS" = "active" ] || [ "$BUG_STATUS" = "wait" ]; then
+            # 根据 update_status 参数决定是否更新禅道状态
+            if [ "$update_status" = "true" ]; then
+                if type -t log_message >/dev/null 2>&1; then
+                    log_message "Bug #${bug_id} 状态为未解决,准备更新为已解决..."
+                fi
+
+                # 准备评论内容 (包含所有相关 commit 信息)
+                COMMENT="已修复该问题
+
+相关提交:"
+                while IFS= read -r commit_info; do
+                    commit_hash=$(echo "$commit_info" | cut -d'|' -f1)
+                    author=$(echo "$commit_info" | cut -d'|' -f2)
+                    message=$(echo "$commit_info" | cut -d'|' -f3)
+                    COMMENT="${COMMENT}
+- [${commit_hash}] ${author}: ${message}"
+                done <<<"${BUG_MAP[$bug_id]}"
+
+                COMMENT="${COMMENT}
+
+代码提交: <a href=\"${COMMIT_BASE_URL}${commit_hash}\" target=\"_blank\">点击查看</a>"
+
+                # 生成解决 Bug 的 JSON 临时文件
+                RESOLVE_JSON_FILE="/tmp/zentao_resolve_${bug_id}_$$.json"
+
+                # 确定指派对象:优先使用创建者,如果获取失败则使用当前用户
+                ASSIGNEE="${BUG_OPENED_BY}"
+                if [ -z "$ASSIGNEE" ]; then
+                    if type -t log_message >/dev/null 2>&1; then
+                        log_message "警告: 无法获取 Bug #${bug_id} 创建者,将指派给当前用户 ${ZENTAO_USERNAME}"
+                    fi
+                    ASSIGNEE="${ZENTAO_USERNAME}"
+                fi
+
+                # 使用 Python 生成 JSON (确保正确处理中文和特殊字符)
+                python3 -c "import json; print(json.dumps({
+    'resolution': 'fixed',
+    'resolvedBuild': 'trunk',
+    'resolvedDate': '$(date "+%Y-%m-%d %H:%M:%S")',
+    'assignedTo': '${ASSIGNEE}',
+    'comment': '''${COMMENT}'''
+}, ensure_ascii=False, indent=2))" >"$RESOLVE_JSON_FILE"
+
+                # 调用禅道 API 解决 Bug
+                RESOLVE_RESPONSE=$(curl -s -X POST "${ZENTAO_API_URL}/bugs/${bug_id}/resolve" \
+                    -H "Token: ${ZENTAO_TOKEN}" \
+                    -H "Content-Type: application/json" \
+                    -d @"$RESOLVE_JSON_FILE")
+
+                # 清理临时文件
+                rm -f "$RESOLVE_JSON_FILE"
+
+                # 检查是否成功 (响应包含 "id" 字段表示成功)
+                if echo "$RESOLVE_RESPONSE" | grep -q '"id"'; then
+                    if type -t log_message >/dev/null 2>&1; then
+                        log_message "✅ 成功解决 Bug #${bug_id}"
+                    fi
+                    RESOLVED_COUNT=$((RESOLVED_COUNT + 1))
+                    BUG_LINKS="${BUG_LINKS}✅ #${bug_id} - ${BUG_TITLE}
+${COMMIT_INFO}  ${ZENTAO_BUG_BASE_URL}${bug_id}.html
+
+"
+                    # 添加到已解决 Bug 列表 (用于飞书汇总通知)
+                    RESOLVED_BUGS_LIST="${RESOLVED_BUGS_LIST}#${bug_id} ${BUG_TITLE}|${ZENTAO_BUG_BASE_URL}${bug_id}.html
+"
+
+                    # 发送飞书单个 Bug 通知
+                    BUG_URL="${ZENTAO_BUG_BASE_URL}${bug_id}.html"
+                    CODE_URL="${COMMIT_BASE_URL}${commit_hash}"
+                    COMMIT_SUMMARY=$(echo "${BUG_MAP[$bug_id]}" | head -1 | awk -F'|' '{print "[" $1 "] " $3}')
+                    send_bug_resolved_notification "${bug_id}" "${BUG_TITLE}" "${BUG_STATUS}" "${COMMIT_SUMMARY}" "${BUG_URL}" "${CODE_URL}"
+
+                    if type -t log_message >/dev/null 2>&1; then
+                        log_message "飞书通知已发送 (Bug #${bug_id})"
+                    fi
+                else
+                    # 更新失败
+                    if type -t log_message >/dev/null 2>&1; then
+                        log_message "❌ 解决 Bug #${bug_id} 失败: ${RESOLVE_RESPONSE}"
+                    fi
+                    ERROR_COUNT=$((ERROR_COUNT + 1))
+                    BUG_LINKS="${BUG_LINKS}❌ #${bug_id} - ${BUG_TITLE} (更新失败)
+${COMMIT_INFO}  ${ZENTAO_BUG_BASE_URL}${bug_id}.html
+
+"
+                fi
+            else
+                # update_status = false: 只汇总,不更新禅道状态
+                if type -t log_message >/dev/null 2>&1; then
+                    log_message "Bug #${bug_id} 状态为未解决,但只汇总不更新禅道状态"
+                fi
+                RESOLVED_COUNT=$((RESOLVED_COUNT + 1))
+                BUG_LINKS="${BUG_LINKS}📋 #${bug_id} - ${BUG_TITLE}
+${COMMIT_INFO}  ${ZENTAO_BUG_BASE_URL}${bug_id}.html
+
+"
+                # 添加到已解决 Bug 列表
+                RESOLVED_BUGS_LIST="${RESOLVED_BUGS_LIST}#${bug_id} ${BUG_TITLE}|${ZENTAO_BUG_BASE_URL}${bug_id}.html
+"
+            fi
+        elif [ "$BUG_STATUS" = "resolved" ] || [ "$BUG_STATUS" = "closed" ]; then
+            # Bug 已经是 resolved 或 closed 状态,跳过处理
+            if type -t log_message >/dev/null 2>&1; then
+                log_message "Bug #${bug_id} 已经是 ${BUG_STATUS} 状态,跳过更新(不显示在通知中)"
+            fi
+            # 不添加到 BUG_LINKS,不计数,不发通知
+        else
+            # 其他状态 (例如: testing),跳过处理
+            if type -t log_message >/dev/null 2>&1; then
+                log_message "Bug #${bug_id} 状态为 ${BUG_STATUS},跳过更新(不显示在通知中)"
+            fi
+            # 不添加到 BUG_LINKS,不计数,不发通知
+        fi
+    done
+
+    # 输出汇总信息
+    if type -t log_message >/dev/null 2>&1; then
+        log_message "=============== 禅道 Bug 追踪完成 ==============="
+        log_message "总计: ${BUG_COUNT} 个 Bug"
+        log_message "已解决: ${RESOLVED_COUNT} 个"
+        log_message "失败: ${ERROR_COUNT} 个"
+    fi
+
+    # 发送 Telegram 汇总通知
+    if type -t send_telegram_message >/dev/null 2>&1; then
+        send_telegram_message "🐛 Bug 修复汇总
+版本: ${version_info}
+
+${BUG_LINKS}
+📊 统计:
+- 已解决: ${RESOLVED_COUNT} 个
+- 失败: ${ERROR_COUNT} 个"
+    fi
+
+    # 发送飞书版本发布汇总通知 (仅在有解决 Bug 时发送)
+    if [ ${RESOLVED_COUNT} -gt 0 ] || [ ${ERROR_COUNT} -gt 0 ]; then
+        if type -t log_message >/dev/null 2>&1; then
+            log_message "发送飞书版本发布汇总通知..."
+        fi
+        send_release_summary_notification "${start_ref}" "${end_ref}" "${RESOLVED_COUNT}" "${RESOLVED_BUGS_LIST}"
+        if type -t log_message >/dev/null 2>&1; then
+            log_message "飞书版本发布通知已发送"
+        fi
+    else
+        if type -t log_message >/dev/null 2>&1; then
+            log_message "本次构建无Bug状态变更,跳过飞书版本汇总通知"
+        fi
+    fi
+
+    if type -t log_message >/dev/null 2>&1; then
+        log_message "=============== 禅道 Bug 追踪结束 ==============="
+    fi
+}
+
+resolve_zentao_bugs() {
+    local workspace_dir="${1:-${WORKSPACE:-.}}"
+    local last_success_file="${workspace_dir}/.last_success_commit"
+
+    if type -t log_message >/dev/null 2>&1; then
+        log_message "=============== 禅道 Bug 自动解决开始 ==============="
+    fi
+
+    # 确定分析范围
+    LAST_SUCCESS_HASH=$(cat "$last_success_file" 2>/dev/null || echo "")
+
+    if [ -z "$LAST_SUCCESS_HASH" ]; then
+        if type -t log_message >/dev/null 2>&1; then
+            log_message "首次构建,分析最近 20 个包含 fix: 的提交"
+        fi
+        # 获取最近 20 个包含 fix: 的提交
+        FIX_COMMITS=$(git log -20 --pretty=format:"%h|%an|%s" | grep -E "fix[:-]\s*[0-9]" || true)
+    else
+        if type -t log_message >/dev/null 2>&1; then
+            log_message "上次成功构建: ${LAST_SUCCESS_HASH}"
+            log_message "分析从 ${LAST_SUCCESS_HASH} 到 HEAD 的所有 fix: 提交"
+        fi
+        # 获取从上次成功构建到 HEAD 的所有包含 fix: 的提交
+        FIX_COMMITS=$(git log ${LAST_SUCCESS_HASH}..HEAD --pretty=format:"%h|%an|%s" | grep -E "fix[:-]\s*[0-9]" || true)
+    fi
+
+    if [ -z "$FIX_COMMITS" ]; then
+        if type -t log_message >/dev/null 2>&1; then
+            log_message "未发现包含 Bug 修复标记 (fix:) 的提交"
+            log_message "=============== 禅道 Bug 自动解决结束 ==============="
+        fi
+        return 0
+    fi
+
+    # 统计找到的提交数
+    COMMIT_COUNT=$(echo "$FIX_COMMITS" | wc -l | tr -d ' ')
+    if type -t log_message >/dev/null 2>&1; then
+        log_message "找到 ${COMMIT_COUNT} 个包含 fix: 的提交"
+    fi
+
+    # 初始化 bug 映射数组(bug_id => 最新的提交信息)
+    # 使用关联数组存储每个 bug 的最新提交(最上面的提交是最新的)
+    declare -A BUG_MAP
+
+    # 解析提交,提取 bug IDs(从新到旧的顺序)
+    while IFS='|' read -r commit_hash author commit_message; do
+        # 提取所有 bug IDs
+        if echo "$commit_message" | grep -qE "fix[:-]\s*[0-9]"; then
+            bug_ids=$(echo "$commit_message" | grep -oE "fix[:-]\s*([0-9]+(\s+[0-9]+)*)" | sed -E 's/fix[:-]\s*//')
+
+            for bug_id in $bug_ids; do
+                if [ -n "$bug_id" ]; then
+                    # 只在 bug 第一次出现时记录(即最新的提交)
+                    if [ -z "${BUG_MAP[$bug_id]}" ]; then
+                        BUG_MAP[$bug_id]="${commit_hash}|${author}|${commit_message}"
+                        if type -t log_message >/dev/null 2>&1; then
+                            log_message "发现 Bug #${bug_id}: [$commit_hash] $commit_message"
+                        fi
+                    fi
+                fi
+            done
+        fi
+    done <<<"$FIX_COMMITS"
+
+    # 统计发现的 bug 数量
+    BUG_COUNT=${#BUG_MAP[@]}
+
+    if type -t log_message >/dev/null 2>&1; then
+        log_message "共发现 ${BUG_COUNT} 个唯一的 Bug"
+    fi
+
+    if [ $BUG_COUNT -eq 0 ]; then
+        if type -t log_message >/dev/null 2>&1; then
+            log_message "未能提取到有效的 Bug ID"
+            log_message "=============== 禅道 Bug 自动解决结束 ==============="
+        fi
+        return 0
+    fi
+
+    # 获取禅道 Token
+    if type -t log_message >/dev/null 2>&1; then
+        log_message "正在获取禅道 API Token..."
+    fi
+
+    ZENTAO_TOKEN_RESPONSE=$(curl -s -X POST "${ZENTAO_API_URL}/tokens" \
+        -H "Content-Type: application/json" \
+        -d "{\"account\":\"${ZENTAO_USERNAME}\",\"password\":\"${ZENTAO_PASSWORD}\"}")
+
+    ZENTAO_TOKEN=$(echo "$ZENTAO_TOKEN_RESPONSE" | grep -o '"token":"[^"]*"' | cut -d'"' -f4)
+
+    if [ -z "$ZENTAO_TOKEN" ]; then
+        if type -t log_message >/dev/null 2>&1; then
+            log_message "错误: 无法获取禅道 Token"
+            log_message "响应: ${ZENTAO_TOKEN_RESPONSE}"
+            log_message "=============== 禅道 Bug 自动解决结束 ==============="
+        fi
+        return 1
+    fi
+
+    if type -t log_message >/dev/null 2>&1; then
+        log_message "成功获取禅道 Token"
+    fi
+
+    # 初始化统计计数器
+    RESOLVED_COUNT=0
+    SKIPPED_COUNT=0
+    ERROR_COUNT=0
+
+    # 处理每个 bug(按 bug ID 排序)
+    for bug_id in $(echo "${!BUG_MAP[@]}" | tr ' ' '\n' | sort -n); do
+        if type -t log_message >/dev/null 2>&1; then
+            log_message "----------------------------------------"
+            log_message "处理 Bug #${bug_id}"
+        fi
+
+        # 获取该 bug 的最新提交信息
+        commit_info="${BUG_MAP[$bug_id]}"
+        commit_hash=$(echo "$commit_info" | cut -d'|' -f1)
+        author=$(echo "$commit_info" | cut -d'|' -f2)
+        commit_message=$(echo "$commit_info" | cut -d'|' -f3)
+
+        # 查询 bug 详情
+        BUG_INFO=$(curl -s -X GET "${ZENTAO_API_URL}/bugs/${bug_id}" \
+            -H "Token: ${ZENTAO_TOKEN}")
+
+        # 使用 Python 解析 JSON
+        BUG_STATUS=$(echo "$BUG_INFO" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data.get('status', ''))" 2>/dev/null)
+        BUG_TITLE=$(echo "$BUG_INFO" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data.get('title', ''))" 2>/dev/null)
+        BUG_OPENED_BY=$(echo "$BUG_INFO" | python3 -c "import sys, json; data=json.load(sys.stdin); openedBy=data.get('openedBy', {}); print(openedBy.get('account', '') if isinstance(openedBy, dict) else '')" 2>/dev/null)
+
+        if [ -z "$BUG_STATUS" ]; then
+            if type -t log_message >/dev/null 2>&1; then
+                log_message "警告: 无法获取 Bug #${bug_id} 的状态信息"
+            fi
+            ERROR_COUNT=$((ERROR_COUNT + 1))
+            continue
+        fi
+
+        if type -t log_message >/dev/null 2>&1; then
+            log_message "Bug #${bug_id} 状态: ${BUG_STATUS}"
+            log_message "Bug #${bug_id} 标题: ${BUG_TITLE}"
+            log_message "Bug #${bug_id} 创建者: ${BUG_OPENED_BY}"
+            log_message "最新修复提交: [${commit_hash}] ${author}"
+        fi
+
+        # 检查是否为未解决状态
+        if [ "$BUG_STATUS" = "active" ] || [ "$BUG_STATUS" = "wait" ]; then
+            if type -t log_message >/dev/null 2>&1; then
+                log_message "Bug #${bug_id} 状态为未解决,准备解决..."
+            fi
+
+            # 准备 comment(只包含最新的提交)
+            COMMENT="已修复该问题
+
+相关提交:
+- [${commit_hash}] ${author}: ${commit_message}
+
+代码详情: ${COMMIT_BASE_URL}${commit_hash}"
+
+            # 生成解决 bug 的 JSON payload
+            RESOLVE_JSON_FILE="/tmp/zentao_resolve_${bug_id}_$$.json"
+
+            # 确定指派对象:优先使用创建者,如果获取失败则使用当前用户
+            ASSIGNEE="${BUG_OPENED_BY}"
+            if [ -z "$ASSIGNEE" ]; then
+                if type -t log_message >/dev/null 2>&1; then
+                    log_message "警告: 无法获取 Bug #${bug_id} 创建者,将指派给当前用户 ${ZENTAO_USERNAME}"
+                fi
+                ASSIGNEE="${ZENTAO_USERNAME}"
+            fi
+
+            python3 -c "import json; print(json.dumps({
+    'resolution': 'fixed',
+    'resolvedBuild': 'trunk',
+    'resolvedDate': '$(date "+%Y-%m-%d %H:%M:%S")',
+    'assignedTo': '${ASSIGNEE}',
+    'comment': '''${COMMENT}'''
+}, ensure_ascii=False, indent=2))" >"$RESOLVE_JSON_FILE"
+
+            # 调用 API 解决 bug
+            RESOLVE_RESPONSE=$(curl -s -X POST "${ZENTAO_API_URL}/bugs/${bug_id}/resolve" \
+                -H "Token: ${ZENTAO_TOKEN}" \
+                -H "Content-Type: application/json" \
+                -d @"$RESOLVE_JSON_FILE")
+
+            # 清理临时文件
+            rm -f "$RESOLVE_JSON_FILE"
+
+            # 检查是否成功
+            if echo "$RESOLVE_RESPONSE" | grep -q '"id"'; then
+                if type -t log_message >/dev/null 2>&1; then
+                    log_message "✅ 成功解决 Bug #${bug_id}"
+                fi
+                RESOLVED_COUNT=$((RESOLVED_COUNT + 1))
+
+                # 发送飞书通知(每个 bug 单独通知)
+                BUG_URL="${ZENTAO_BUG_BASE_URL}${bug_id}.html"
+                CODE_URL="${COMMIT_BASE_URL}${commit_hash}"
+                COMMIT_SUMMARY="[${commit_hash}] ${commit_message}"
+                send_bug_resolved_notification "${bug_id}" "${BUG_TITLE}" "${BUG_STATUS}" "${COMMIT_SUMMARY}" "${BUG_URL}" "${CODE_URL}"
+
+                if type -t log_message >/dev/null 2>&1; then
+                    log_message "飞书通知已发送 (Bug #${bug_id})"
+                fi
+            else
+                if type -t log_message >/dev/null 2>&1; then
+                    log_message "❌ 解决 Bug #${bug_id} 失败: ${RESOLVE_RESPONSE}"
+                fi
+                ERROR_COUNT=$((ERROR_COUNT + 1))
+            fi
+        elif [ "$BUG_STATUS" = "resolved" ] || [ "$BUG_STATUS" = "closed" ]; then
+            if type -t log_message >/dev/null 2>&1; then
+                log_message "Bug #${bug_id} 已经是 ${BUG_STATUS} 状态,跳过处理"
+            fi
+            SKIPPED_COUNT=$((SKIPPED_COUNT + 1))
+        else
+            if type -t log_message >/dev/null 2>&1; then
+                log_message "Bug #${bug_id} 状态为 ${BUG_STATUS},跳过处理"
+            fi
+            SKIPPED_COUNT=$((SKIPPED_COUNT + 1))
+        fi
+    done
+
+    if type -t log_message >/dev/null 2>&1; then
+        log_message "=============== 禅道 Bug 自动解决完成 ==============="
+        log_message "分析提交数: ${COMMIT_COUNT}"
+        log_message "发现 Bug 数: ${BUG_COUNT}"
+        log_message "已解决: ${RESOLVED_COUNT} 个"
+        log_message "已跳过: ${SKIPPED_COUNT} 个"
+        log_message "失败: ${ERROR_COUNT} 个"
+        log_message "=============== 禅道 Bug 自动解决结束 ==============="
+    fi
+
+    return 0
+}