1.Jenkins结合Jacoco插件实现覆盖率展示

可以参考另一篇文章:Jenkins集成Jacoco实现覆盖率测试报告-CSDN博客

2.针对增量代码获取覆盖率

查询了很多资料,可以下载Jenkins的新版报告插件“coverage”,内置的好像有diff-report

下面是针对Jenkins版本不够,自定义脚本实现的方案

2.1 原任务(任务创建完整步骤参考上面文章),新增"Execute shell"生成added-files.txt

基于git仓库,获取当前提交与上一次提交的差异文件

#!/bin/bash
# 脚本功能:仅基于本地.git仓库,获取当前提交与上一次提交的Java文件差异
# 输出:added-files.txt(增量Java文件清单) + 打印差异信息

# ==================== 第一步:环境初始化 & 容错 ====================
# 强制UTF-8编码(解决中文文件名/日志乱码)
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8

# 进入Jenkins工作空间(脚本执行目录)
WORKSPACE=$(pwd)
cd "${WORKSPACE}" || {
    echo "ERROR: 无法进入工作空间 ${WORKSPACE}"
    exit 1
}

# 检查本地.git目录是否存在
if [ ! -d ".git" ]; then
    echo "ERROR: 本地.git目录不存在!"
    exit 1
fi

# ==================== 第二步:获取本地提交信息(无分支/远程依赖) ====================
# 获取当前提交的完整Commit ID(本地HEAD)
CURRENT_COMMIT=$(git rev-parse HEAD 2>/dev/null)
if [ -z "${CURRENT_COMMIT}" ]; then
    echo "ERROR: 无法获取本地HEAD的Commit ID!"
    exit 1
fi
SHORT_CURRENT=${CURRENT_COMMIT:0:8}

# 获取上一次提交的Commit ID(本地HEAD^)
PREV_COMMIT=$(git rev-parse HEAD^ 2>/dev/null)
if [ -z "${PREV_COMMIT}" ]; then
    echo "WARNING: 无上次提交记录(当前是首次提交),生成空差异清单"
    touch "${WORKSPACE}/added-files.txt"
    exit 0
fi
SHORT_PREV=${PREV_COMMIT:0:8}

# ==================== 第三步:获取两次提交的Java文件差异 ====================
# 仅筛选Java文件的差异(新增/修改),输出到added-files.txt
git diff --name-only --diff-filter=AM "${PREV_COMMIT}" "${CURRENT_COMMIT}" -- '*.java' > "${WORKSPACE}/added-files.txt" 2>/dev/null

# 统计差异文件数
DIFF_COUNT=$(wc -l < "${WORKSPACE}/added-files.txt")
# 去除空行(避免统计异常)
sed -i '/^$/d' "${WORKSPACE}/added-files.txt"
REAL_COUNT=$(wc -l < "${WORKSPACE}/added-files.txt")

# ==================== 第四步:输出日志(供Jenkins调用) ====================
echo "✅ 本地Git差异获取完成:"
echo "  当前提交:${SHORT_CURRENT}(完整:${CURRENT_COMMIT})"
echo "  上次提交:${SHORT_PREV}(完整:${PREV_COMMIT})"
echo "  增量Java文件数:${REAL_COUNT}"

if [ ${REAL_COUNT} -gt 0 ]; then
    echo "  增量文件清单:"
    cat "${WORKSPACE}/added-files.txt" | while read FILE; do
        [ -n "${FILE}" ] && echo "    - ${FILE}"
    done
fi

exit 0

可以执行测试,成功打印差异文件,工作空间成功生成即可

基于git仓库,获取当前最新提交与dev最新提交的差异文件

#!/bin/bash
# 脚本功能:
# 1. 自动识别Jenkins当前构建的分支(适配Branches to build配置的任意分支)
# 2. 对比远程origin/dev与当前构建分支的Java文件差异
# 3. 兼容detached HEAD状态,输出added-files.txt(增量/全量Java文件清单)
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8

# ==================== 基础配置 & 容错 ====================
WORKSPACE=$(pwd)
cd "${WORKSPACE}" || {
    echo "ERROR: 无法进入工作空间 ${WORKSPACE}"
    exit 1
}

if [ ! -d ".git" ]; then
    echo "ERROR: 本地.git目录不存在!"
    exit 1
fi

# 远程仓库名称(固定为origin,匹配Jenkins拉取的远程)
REMOTE_NAME="origin"
# 基准对比分支(固定为远程dev)
BASE_BRANCH="${REMOTE_NAME}/dev"

# ==================== 核心:自动获取Jenkins当前构建分支 ====================
# 方案1:优先读取Jenkins内置环境变量(最准确)
TARGET_BRANCH=""
if [ -n "${GIT_BRANCH}" ]; then
    # Jenkins Git插件自动设置的环境变量,格式如:origin/ticket/13
    TARGET_BRANCH="${GIT_BRANCH}"
    echo "INFO: 从Jenkins环境变量获取当前构建分支:${TARGET_BRANCH}"
else
    # 方案2:环境变量不存在时,从Git信息中推导(兼容detached HEAD)
    echo "INFO: 未检测到Jenkins环境变量,从Git推导当前分支..."
    # 获取当前checkout的引用(如refs/remotes/origin/ticket/13)
    CURRENT_REF=$(git symbolic-ref -q HEAD 2>/dev/null || git rev-parse --abbrev-ref HEAD 2>/dev/null)
    if [ "${CURRENT_REF}" = "HEAD" ] || [ -z "${CURRENT_REF}" ]; then
        # detached HEAD状态:从reflog找最新的远程分支引用
        CURRENT_REF=$(git reflog show -1 | awk '{print $3}' | sed 's/^origin\///' 2>/dev/null)
        if [ -n "${CURRENT_REF}" ]; then
            TARGET_BRANCH="${REMOTE_NAME}/${CURRENT_REF}"
        else
            # 终极兜底:用HEAD代表当前代码
            TARGET_BRANCH="HEAD"
        fi
    else
        # 正常分支状态:拼接远程分支名
        TARGET_BRANCH="${REMOTE_NAME}/${CURRENT_REF}"
    fi
fi

# 最终校验:确保目标分支格式正确(origin/xxx)
if [[ ! "${TARGET_BRANCH}" =~ ^${REMOTE_NAME}/ ]]; then
    TARGET_BRANCH="${REMOTE_NAME}/${TARGET_BRANCH}"
fi
echo "INFO: 最终确定对比的目标分支:${TARGET_BRANCH}"

# ==================== 拉取远程最新引用(复用Jenkins已拉取的缓存) ====================
# 注释冗余的git fetch(Jenkins已提前拉取,避免权限问题)
# echo "INFO: 拉取远程仓库(${REMOTE_NAME})最新分支信息..."
# git fetch --quiet --prune "${REMOTE_NAME}" 2>/dev/null || {
#     echo "WARNING: 拉取远程引用失败,使用本地缓存(仍尝试对比)"
# }

# ==================== 验证远程分支是否存在 ====================
# 检查远程dev分支是否存在
if ! git show-ref --verify --quiet "refs/remotes/${BASE_BRANCH}" 2>/dev/null; then
    echo "ERROR: 远程仓库无${BASE_BRANCH}分支!"
    touch "${WORKSPACE}/added-files.txt"
    exit 0
fi

# 检查目标分支是否存在(兼容HEAD)
if [ "${TARGET_BRANCH}" != "HEAD" ] && ! git show-ref --verify --quiet "refs/remotes/${TARGET_BRANCH}" 2>/dev/null; then
    echo "WARNING: 远程仓库无${TARGET_BRANCH}分支,将使用当前HEAD对比"
    TARGET_BRANCH="HEAD"
fi

# ==================== 获取两个分支的最新Commit ====================
# 远程dev分支最新Commit
DEV_COMMIT=$(git rev-parse "${BASE_BRANCH}" 2>/dev/null)
SHORT_DEV=${DEV_COMMIT:0:8}

# 当前目标分支最新Commit
TARGET_COMMIT=$(git rev-parse "${TARGET_BRANCH}" 2>/dev/null)
SHORT_TARGET=${TARGET_COMMIT:0:8}

if [ -z "${DEV_COMMIT}" ] || [ -z "${TARGET_COMMIT}" ]; then
    echo "WARNING: 无法获取分支Commit ID,输出全量Java文件"
    GET_FULL_LIST=1
else
    GET_FULL_LIST=0
    echo "INFO: 对比基准 - ${BASE_BRANCH} (${SHORT_DEV}) vs ${TARGET_BRANCH} (${SHORT_TARGET})"
fi

# ==================== 生成差异文件(核心) ====================
if [ ${GET_FULL_LIST} -eq 0 ]; then
    # 对比远程dev和当前构建分支的Java文件差异(新增/修改)
    git diff --name-only --diff-filter=AM "${DEV_COMMIT}" "${TARGET_COMMIT}" -- '*.java' > "${WORKSPACE}/added-files.txt" 2>/dev/null
else
    # 容错:对比失败时输出全量Java文件
    find "${WORKSPACE}" -path "${WORKSPACE}/.git" -prune -o -name "*.java" -type f | sed "s|^${WORKSPACE}/||" > "${WORKSPACE}/added-files.txt" 2>/dev/null
fi

# ==================== 清理 & 统计 & 日志 ====================
sed -i '/^$/d' "${WORKSPACE}/added-files.txt"
REAL_COUNT=$(wc -l < "${WORKSPACE}/added-files.txt")

echo -e "\n✅ 差异文件生成完成:"
echo "  对比分支:${BASE_BRANCH} ↔ ${TARGET_BRANCH}"
echo "  增量Java文件数:${REAL_COUNT}"

if [ ${REAL_COUNT} -gt 0 ] && [ ${REAL_COUNT} -le 50 ]; then
    echo "  增量文件清单:"
    cat "${WORKSPACE}/added-files.txt" | while read FILE; do
        [ -n "${FILE}" ] && echo "    - ${FILE}"
    done
elif [ ${REAL_COUNT} -gt 50 ]; then
    echo "  增量文件清单:共${REAL_COUNT}个文件(数量过多,不展示)"
else
    echo "  提示:${TARGET_BRANCH}与${BASE_BRANCH}无新增/修改的Java文件"
fi

exit 0

执行成功的打印

2.2 根据差异的文件的class文件生成差异报告

添加获取差异class文件的命令

# ---------------------- 3. Jenkins 端:生成报告(核心修复) ----------------------
log "INFO" "开始 Jenkins 端生成报告..."

JENKINS_SCRIPT=$(cat <<'EOF'
    # 基础环境配置
    export LANG=en_US.UTF-8
    export LC_ALL=en_US.UTF-8
    unset PYENV_ROOT
    export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

    # 核心路径(关键修复:移除末尾/)
    WORKSPACE="/var/lib/jenkins/workspace/gf-busniess-test-report"
    JACOCO_CLI="/opt/jacoco/lib/jacococli.jar"
    DIFF_FILE="${WORKSPACE}/added-files.txt"
    COV_FILE="${WORKSPACE}/coverage.exec"
    REPORT_DIR="${WORKSPACE}/diff-report"
    TEMP_FILTER_DIR="${WORKSPACE}/temp-incremental-classes"

    # 日志函数
    jenkins_log() {
        local LEVEL=$1
        local MESSAGE=$2
        echo "[$(date +'%Y-%m-%d %H:%M:%S')] [Jenkins] [${LEVEL}] ${MESSAGE}"
    }

    # ===================== 前置校验 =====================
    jenkins_log "INFO" "启动增量覆盖率报告生成流程"
    if [ ! -d "${WORKSPACE}" ]; then
        jenkins_log "ERROR" "工作空间不存在:${WORKSPACE}"
        exit 1
    fi
    cd "${WORKSPACE}" || { jenkins_log "ERROR" "无法进入工作空间"; exit 1; }

    # ===================== 校验diff文件 =====================
    FILE_COUNT=0
    if [ -f "${DIFF_FILE}" ]; then
        sed -i '/^$/d; /^#/d' "${DIFF_FILE}" 2>/dev/null
        FILE_COUNT=$(wc -l < "${DIFF_FILE}")
        jenkins_log "INFO" "加载增量文件完成,有效文件数:${FILE_COUNT}"
    else
        jenkins_log "WARN" "增量文件不存在,创建空文件"
        touch "${DIFF_FILE}"
    fi

    # ===================== 清理旧文件 =====================
    rm -rf "${REPORT_DIR}" "${TEMP_FILTER_DIR}"
    mkdir -p "${REPORT_DIR}" || { jenkins_log "ERROR" "创建报告目录失败"; exit 1; }

    # ===================== 生成报告 =====================
    if [ ${FILE_COUNT} -eq 0 ]; then
        # 无增量:友好报告
        jenkins_log "INFO" "无增量文件,生成友好提示报告"
        cat > "${REPORT_DIR}/index.html" <<HTML
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>增量代码覆盖率报告</title>
    <style>
        body {font-family: Arial;margin:40px auto;text-align:center;max-width:800px;}
        .box {padding:20px;border:1px solid #eee;border-radius:8px;}
        h1 {color:#666;}
        .time {color:#999;font-size:14px;margin-top:15px;}
    </style>
</head>
<body>
    <div class="box">
        <h1>当前构建无增量 Java 代码</h1>
        <p>本次无需要统计覆盖率的变更文件</p>
        <p class="time">生成时间:$(date +'%Y-%m-%d %H:%M:%S')</p>
    </div>
</body>
</html>
HTML
        jenkins_log "INFO" "✅ 无增量报告生成完成"
    else
        # 有增量:过滤class + 生成报告(核心修复路径转换)
        jenkins_log "INFO" "开始过滤增量class文件"
        mkdir -p "${TEMP_FILTER_DIR}" || { jenkins_log "ERROR" "创建临时目录失败"; exit 1; }

        # 遍历拷贝class(关键修复:路径转换逻辑)
        COPY_COUNT=0
        while IFS= read -r JAVA_FILE; do
            [ -z "${JAVA_FILE}" ] && continue
            
            # 核心修复:分步转换路径,避免拼接错误
            # 步骤1:去掉src/main/java/前缀
            RELATIVE_JAVA=${JAVA_FILE#src/main/java/}
            # 步骤2:替换后缀为.class
            RELATIVE_CLASS=${RELATIVE_JAVA/.java/.class}
            # 步骤3:拼接完整路径(无//)
            FULL_CLASS="${WORKSPACE}/target/classes/${RELATIVE_CLASS}"

            if [ -f "${FULL_CLASS}" ]; then
                # 创建目标目录+拷贝(保留属性)
                TARGET_DIR="${TEMP_FILTER_DIR}/$(dirname "${RELATIVE_CLASS}")"
                mkdir -p "${TARGET_DIR}" && cp -p "${FULL_CLASS}" "${TARGET_DIR}/"
                COPY_COUNT=$((COPY_COUNT + 1))
                jenkins_log "INFO" "✅ 拷贝class:${RELATIVE_CLASS}"
            else
                jenkins_log "WARN" "❌ class不存在:${FULL_CLASS}"
                # 兜底:打印Jenkins端实际的class文件路径,便于排查
                jenkins_log "INFO" "    Jenkins端target/classes目录文件:$(ls ${WORKSPACE}/target/classes/com/cffex/business/service/ 2>/dev/null | head -5)"
            fi
        done < "${DIFF_FILE}"

        # 校验拷贝结果
        if [ ${COPY_COUNT} -eq 0 ]; then
            jenkins_log "ERROR" "❌ 无有效class文件,生成报告失败"
            # 兜底:打印Jenkins端class目录结构
            jenkins_log "INFO" "Jenkins端target/classes完整目录:$(ls -R ${WORKSPACE}/target/classes/ 2>/dev/null | head -20)"
            exit 1
        fi

        # 核心:官方标准report命令(无--diff)
        jenkins_log "INFO" "执行Jacoco report命令(官方0.8.14)"
        java -jar "${JACOCO_CLI}" report \
            "${COV_FILE}" \
            --classfiles "${TEMP_FILTER_DIR}" \
            --sourcefiles "${WORKSPACE}/src/main/java" \
            --html "${REPORT_DIR}" \
            --quiet 2>/tmp/jacoco_error.log

        # 结果校验
        if [ $? -eq 0 ] && [ -f "${REPORT_DIR}/index.html" ]; then
            jenkins_log "INFO" "✅ 增量覆盖率报告生成成功"
            jenkins_log "INFO" "📄 报告路径:${REPORT_DIR}/index.html"
        else
            jenkins_log "ERROR" "❌ 报告生成失败,错误日志:$(cat /tmp/jacoco_error.log 2>/dev/null)"
            exit 1
        fi
    fi

    # ===================== 权限修复 =====================
    chown -R jenkins:jenkins "${REPORT_DIR}" "${DIFF_FILE}" "${COV_FILE}" "${WORKSPACE}/target"
    chmod -R 755 "${REPORT_DIR}"
    rm -rf "${TEMP_FILTER_DIR}"  # 清理临时文件

    jenkins_log "INFO" "🎉 Jenkins端全流程执行完成"
    exit 0
EOF
)

执行成功会生成diff-report文件

完整的dump脚本

#!/bin/bash

# ===================== 自定义配置区(仅需维护这里) ======================
# JaCoCo基础配置(应用服务器)
JACOCO_CLI_JAR="/opt/jacoco/lib/jacococli.jar"
COV_FILE="/root/app-service/gf-business/coverage.exec"
JACOCO_PORT=6300
JAVA_HOME="/usr/local/jdk-8"
APP_NAME="gf-business.jar"

# Jenkins配置
JENKINS_SERVER_IP="你的ip"
JENKINS_USER="root"
JENKINS_WORKSPACE="/var/lib/jenkins/workspace/gf-busniess-test-report"
JENKINS_JACOCO_CLI_JAR="/opt/jacoco/lib/jacococli.jar"

# 应用Jar包配置
APP_JAR_PATH="/root/app-service/gf-business/gf-business.jar"
TEMP_CLASS_DIR="/tmp/gf-business-classes"

# ==============================================================================

# ---------------------- 工具函数 ----------------------
log() {
    local LEVEL=$1
    local MESSAGE=$2
    echo "[$(date +'%Y-%m-%d %H:%M:%S')] [$LEVEL] $MESSAGE"
}

error_exit() {
    log "ERROR" "$1"
    [ -d "$TEMP_CLASS_DIR" ] && rm -rf "$TEMP_CLASS_DIR"
    [ -f "$COV_FILE" ] && rm -f "$COV_FILE"
    exit 1
}

print_sep() {
    log "INFO" "======================================================"
}

# ---------------------- 初始化 ----------------------
print_sep
log "INFO" "开始执行 JaCoCo 覆盖率采集 + 增量报告流程"
print_sep

export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8
export PATH=$JAVA_HOME/bin:$PATH

# ---------------------- 前置检查 ----------------------
log "INFO" "前置检查..."
! command -v java &>/dev/null && error_exit "Java 未找到"
[ ! -f "$JACOCO_CLI_JAR" ] && error_exit "JaCoCo CLI 不存在"
[ ! -f "$APP_JAR_PATH" ] && error_exit "应用jar包不存在"
! command -v scp &>/dev/null || ! command -v ssh &>/dev/null && error_exit "请安装 openssh-clients"
APP_PID=$(ps -ef | grep -E "[j]ava.*$APP_NAME" | awk '{print $2}')
[ -z "$APP_PID" ] && error_exit "未找到应用进程 $APP_NAME"

log "INFO" "✅ 前置检查通过"
print_sep

# ---------------------- 1. 生成 coverage.exec ----------------------
log "INFO" "执行 JaCoCo dump..."
[ -f "$COV_FILE" ] && rm -f "$COV_FILE"

java -jar "$JACOCO_CLI_JAR" dump \
  --address localhost \
  --port "$JACOCO_PORT" \
  --destfile "$COV_FILE" \
  --quiet || error_exit "dump 失败"

[ ! -s "$COV_FILE" ] && error_exit "coverage.exec 为空"
log "INFO" "✅ coverage.exec 生成成功"
print_sep

# ---------------------- 2. 推送到 Jenkins ----------------------
log "INFO" "推送 coverage.exec 到 Jenkins..."
scp -q -o StrictHostKeyChecking=no -o ConnectTimeout=10 \
  "$COV_FILE" "$JENKINS_USER@$JENKINS_SERVER_IP:$JENKINS_WORKSPACE/" \
  || error_exit "推送 exec 失败"

log "INFO" "解压 jar 并推送 class..."
rm -rf "$TEMP_CLASS_DIR" && mkdir -p "$TEMP_CLASS_DIR"
# 关键修复:解压时保留完整目录结构,且只解压class文件
unzip -q -o "$APP_JAR_PATH" "BOOT-INF/classes/**/*.class" -d "$TEMP_CLASS_DIR" || error_exit "解压失败"
CLASS_DIR="$TEMP_CLASS_DIR/BOOT-INF/classes"
[ ! -d "$CLASS_DIR" ] && error_exit "无 BOOT-INF/classes 目录"

# 关键修复:推送class文件到Jenkins的target/classes(和路径转换逻辑匹配)
ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 \
  "$JENKINS_USER@$JENKINS_SERVER_IP" \
  "mkdir -p '$JENKINS_WORKSPACE/target/classes' && chown -R jenkins:jenkins '$JENKINS_WORKSPACE/target'"

# 关键修复:递归推送所有class文件(保留目录结构)
scp -r -q -o StrictHostKeyChecking=no "$CLASS_DIR/"* \
  "$JENKINS_USER@$JENKINS_SERVER_IP:$JENKINS_WORKSPACE/target/classes/" \
  || error_exit "推送 class 失败"

log "INFO" "✅ 文件推送完成(exec + class)"
print_sep

# ---------------------- 3. Jenkins 端:生成报告(核心修复) ----------------------
log "INFO" "开始 Jenkins 端生成报告..."

JENKINS_SCRIPT=$(cat <<'EOF'
    # 基础环境配置
    export LANG=en_US.UTF-8
    export LC_ALL=en_US.UTF-8
    unset PYENV_ROOT
    export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

    WORKSPACE="/var/lib/jenkins/workspace/gf-busniess-test-report"
    JACOCO_CLI="/opt/jacoco/lib/jacococli.jar"
    DIFF_FILE="${WORKSPACE}/added-files.txt"
    COV_FILE="${WORKSPACE}/coverage.exec"
    REPORT_DIR="${WORKSPACE}/diff-report"
    TEMP_FILTER_DIR="${WORKSPACE}/temp-incremental-classes"

    # 日志函数
    jenkins_log() {
        local LEVEL=$1
        local MESSAGE=$2
        echo "[$(date +'%Y-%m-%d %H:%M:%S')] [Jenkins] [${LEVEL}] ${MESSAGE}"
    }

    # ===================== 前置校验 =====================
    jenkins_log "INFO" "启动增量覆盖率报告生成流程"
    if [ ! -d "${WORKSPACE}" ]; then
        jenkins_log "ERROR" "工作空间不存在:${WORKSPACE}"
        exit 1
    fi
    cd "${WORKSPACE}" || { jenkins_log "ERROR" "无法进入工作空间"; exit 1; }

    # ===================== 校验diff文件 =====================
    FILE_COUNT=0
    if [ -f "${DIFF_FILE}" ]; then
        sed -i '/^$/d; /^#/d' "${DIFF_FILE}" 2>/dev/null
        FILE_COUNT=$(wc -l < "${DIFF_FILE}")
        jenkins_log "INFO" "加载增量文件完成,有效文件数:${FILE_COUNT}"
    else
        jenkins_log "WARN" "增量文件不存在,创建空文件"
        touch "${DIFF_FILE}"
    fi

    # ===================== 清理旧文件 =====================
    rm -rf "${REPORT_DIR}" "${TEMP_FILTER_DIR}"
    mkdir -p "${REPORT_DIR}" || { jenkins_log "ERROR" "创建报告目录失败"; exit 1; }

    # ===================== 生成报告 =====================
    if [ ${FILE_COUNT} -eq 0 ]; then
        # 无增量:友好报告
        jenkins_log "INFO" "无增量文件,生成友好提示报告"
        cat > "${REPORT_DIR}/index.html" <<HTML
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>增量代码覆盖率报告</title>
    <style>
        body {font-family: Arial;margin:40px auto;text-align:center;max-width:800px;}
        .box {padding:20px;border:1px solid #eee;border-radius:8px;}
        h1 {color:#666;}
        .time {color:#999;font-size:14px;margin-top:15px;}
    </style>
</head>
<body>
    <div class="box">
        <h1>当前构建无增量 Java 代码</h1>
        <p>本次无需要统计覆盖率的变更文件</p>
        <p class="time">生成时间:$(date +'%Y-%m-%d %H:%M:%S')</p>
    </div>
</body>
</html>
HTML
        jenkins_log "INFO" "✅ 无增量报告生成完成"
    else
        # 有增量:过滤class + 生成报告(核心修复路径转换)
        jenkins_log "INFO" "开始过滤增量class文件"
        mkdir -p "${TEMP_FILTER_DIR}" || { jenkins_log "ERROR" "创建临时目录失败"; exit 1; }

        # 遍历拷贝class(关键修复:路径转换逻辑)
        COPY_COUNT=0
        while IFS= read -r JAVA_FILE; do
            [ -z "${JAVA_FILE}" ] && continue
            
            # 核心修复:分步转换路径,避免拼接错误
            # 步骤1:去掉src/main/java/前缀
            RELATIVE_JAVA=${JAVA_FILE#src/main/java/}
            # 步骤2:替换后缀为.class
            RELATIVE_CLASS=${RELATIVE_JAVA/.java/.class}
            # 步骤3:拼接完整路径(无//)
            FULL_CLASS="${WORKSPACE}/target/classes/${RELATIVE_CLASS}"

            if [ -f "${FULL_CLASS}" ]; then
                # 创建目标目录+拷贝(保留属性)
                TARGET_DIR="${TEMP_FILTER_DIR}/$(dirname "${RELATIVE_CLASS}")"
                mkdir -p "${TARGET_DIR}" && cp -p "${FULL_CLASS}" "${TARGET_DIR}/"
                COPY_COUNT=$((COPY_COUNT + 1))
                jenkins_log "INFO" "✅ 拷贝class:${RELATIVE_CLASS}"
            else
                jenkins_log "WARN" "❌ class不存在:${FULL_CLASS}"
                # 兜底:打印Jenkins端实际的class文件路径,便于排查
                jenkins_log "INFO" "    Jenkins端target/classes目录文件:$(ls ${WORKSPACE}/target/classes/com/cffex/business/service/ 2>/dev/null | head -5)"
            fi
        done < "${DIFF_FILE}"

        # 校验拷贝结果
        if [ ${COPY_COUNT} -eq 0 ]; then
            jenkins_log "ERROR" "❌ 无有效class文件,生成报告失败"
            # 兜底:打印Jenkins端class目录结构
            jenkins_log "INFO" "Jenkins端target/classes完整目录:$(ls -R ${WORKSPACE}/target/classes/ 2>/dev/null | head -20)"
            exit 1
        fi

        # 核心:官方标准report命令(无--diff)
        jenkins_log "INFO" "执行Jacoco report命令(官方0.8.14)"
        java -jar "${JACOCO_CLI}" report \
            "${COV_FILE}" \
            --classfiles "${TEMP_FILTER_DIR}" \
            --sourcefiles "${WORKSPACE}/src/main/java" \
            --html "${REPORT_DIR}" \
            --quiet 2>/tmp/jacoco_error.log

        # 结果校验
        if [ $? -eq 0 ] && [ -f "${REPORT_DIR}/index.html" ]; then
            jenkins_log "INFO" "✅ 增量覆盖率报告生成成功"
            jenkins_log "INFO" "📄 报告路径:${REPORT_DIR}/index.html"
        else
            jenkins_log "ERROR" "❌ 报告生成失败,错误日志:$(cat /tmp/jacoco_error.log 2>/dev/null)"
            exit 1
        fi
    fi

    # ===================== 权限修复 =====================
    chown -R jenkins:jenkins "${REPORT_DIR}" "${DIFF_FILE}" "${COV_FILE}" "${WORKSPACE}/target"
    chmod -R 755 "${REPORT_DIR}"
    rm -rf "${TEMP_FILTER_DIR}"  # 清理临时文件

    jenkins_log "INFO" "🎉 Jenkins端全流程执行完成"
    exit 0
EOF
)

# 执行远程脚本
ssh -o StrictHostKeyChecking=no -o LogLevel=ERROR -o ConnectTimeout=20 \
  "$JENKINS_USER@$JENKINS_SERVER_IP" "$JENKINS_SCRIPT" | while read line; do
    log "INFO" "$line"
done

JENKINS_RET=${PIPESTATUS[0]}
[ $JENKINS_RET -ne 0 ] && error_exit "Jenkins 执行失败"

print_sep
log "INFO" "清理临时文件..."
rm -rf "$TEMP_CLASS_DIR"
log "INFO" "🎉 全流程执行完成!报告路径:${JENKINS_WORKSPACE}/diff-report/index.html"
print_sep

exit 0

生成的报告没有高亮提示

2.3 仅生成增量的jacoco报告

dump脚本仅需要生成exec文件,以及筛选出来增量的class文件,推到工作台

#!/bin/bash

# ===================== 自定义配置区 ======================
# JaCoCo基础配置
COV_FILE="/root/app-service/gf-business/coverage.exec"
JACOCO_CLI_JAR="/opt/jacoco/lib/jacococli.jar"
JACOCO_PORT=6300
JAVA_HOME="/usr/local/jdk-8"
APP_NAME="gf-business.jar"

# Jenkins配置
JENKINS_SERVER_IP="你的ip"
JENKINS_USER="root"
JENKINS_WORKSPACE="/var/lib/jenkins/workspace/gf-busniess-test-report"
JENKINS_CLASS_DIR="${JENKINS_WORKSPACE}/incremental-classes"  # 统一用这个目录(增量/全量都放这)
JENKINS_RUN_USER="jenkins"
DIFF_FILE_PATH="${JENKINS_WORKSPACE}/added-files.txt"

# 应用Jar包配置
APP_JAR_PATH="/root/app-service/gf-business/gf-business.jar"
TEMP_CLASS_DIR="/tmp/gf-business-classes"
TEMP_INCREMENTAL_CLASS_DIR="/tmp/gf-incremental-classes"

# ==============================================================================

# 工具函数
log() {
    echo "[$(date +'%Y-%m-%d %H:%M:%S')] [$1] $2"
}

error_exit() {
    log "ERROR" "$1"
    [ -d "$TEMP_CLASS_DIR" ] && rm -rf "$TEMP_CLASS_DIR"
    [ -d "$TEMP_INCREMENTAL_CLASS_DIR" ] && rm -rf "$TEMP_INCREMENTAL_CLASS_DIR"
    [ -f "$COV_FILE" ] && rm -f "$COV_FILE"
    [ -f "/tmp/added-files.txt" ] && rm -f "/tmp/added-files.txt"
    exit 1
}

# 初始化
log "INFO" "===== 开始执行 JaCoCo 数据采集(适配Jenkins插件) ====="
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8
export PATH=$JAVA_HOME/bin:$PATH

# 前置检查
log "INFO" "前置检查..."
! command -v java &>/dev/null && error_exit "Java 未找到"
[ ! -f "$JACOCO_CLI_JAR" ] && error_exit "JaCoCo CLI 不存在"
[ ! -f "$APP_JAR_PATH" ] && error_exit "应用jar包不存在"
! command -v scp &>/dev/null || ! command -v ssh &>/dev/null && error_exit "请安装 openssh-clients"
APP_PID=$(ps -ef | grep -E "[j]ava.*$APP_NAME" | awk '{print $2}')
[ -z "$APP_PID" ] && error_exit "未找到应用进程 $APP_NAME"
log "INFO" "✅ 前置检查通过"

# 1. 生成 coverage.exec
log "INFO" "执行 JaCoCo dump..."
[ -f "$COV_FILE" ] && rm -f "$COV_FILE"
java -jar "$JACOCO_CLI_JAR" dump --address localhost --port "$JACOCO_PORT" --destfile "$COV_FILE" --quiet || error_exit "dump 失败"
[ ! -s "$COV_FILE" ] && error_exit "coverage.exec 为空"
log "INFO" "✅ coverage.exec 生成成功"

# 2. 解压应用Jar(先拿到全量class)
log "INFO" "解压应用Jar包(全量class)..."
rm -rf "$TEMP_CLASS_DIR" && mkdir -p "$TEMP_CLASS_DIR"
unzip -q -o "$APP_JAR_PATH" "BOOT-INF/classes/**/*.class" -d "$TEMP_CLASS_DIR" || error_exit "解压失败"
FULL_CLASS_DIR="$TEMP_CLASS_DIR/BOOT-INF/classes"
[ ! -d "$FULL_CLASS_DIR" ] && error_exit "无 BOOT-INF/classes 目录"

# 3. 拉取增量文件列表(解决pyenv报错+文件不存在)
log "INFO" "拉取增量文件列表..."
ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 \
    "$JENKINS_USER@$JENKINS_SERVER_IP" \
    "unset PYENV_ROOT && source /dev/null && \
     if [ -f '${DIFF_FILE_PATH}' ]; then
         cat '${DIFF_FILE_PATH}' | sed '/^$/d; /^#/d'
     else
         touch '${DIFF_FILE_PATH}'
         echo ''
     fi" > /tmp/added-files.txt || {
         log "WARN" "拉取增量文件列表失败,将推送全量class"
         > /tmp/added-files.txt
     }

# 4. 筛选class(增量/全量逻辑)
log "INFO" "筛选class文件..."
rm -rf "$TEMP_INCREMENTAL_CLASS_DIR" && mkdir -p "$TEMP_INCREMENTAL_CLASS_DIR"
COPY_COUNT=0

# 先尝试读取增量文件
while IFS= read -r JAVA_FILE; do
    [ -z "${JAVA_FILE}" ] && continue
    RELATIVE_JAVA=${JAVA_FILE#src/main/java/}
    RELATIVE_CLASS=${RELATIVE_JAVA/.java/.class}
    FULL_CLASS="${FULL_CLASS_DIR}/${RELATIVE_CLASS}"
    
    if [ -f "${FULL_CLASS}" ]; then
        TARGET_DIR="${TEMP_INCREMENTAL_CLASS_DIR}/$(dirname "${RELATIVE_CLASS}")"
        mkdir -p "${TARGET_DIR}" && cp -p "${FULL_CLASS}" "${TARGET_DIR}/"
        COPY_COUNT=$((COPY_COUNT + 1))
    fi
done < /tmp/added-files.txt

# 无增量时推送全量class
if [ ${COPY_COUNT} -eq 0 ]; then
    log "INFO" "未找到增量文件,推送全量class文件"
    # 拷贝全量class到临时目录
    cp -rp "${FULL_CLASS_DIR}/"* "${TEMP_INCREMENTAL_CLASS_DIR}/"
    COPY_COUNT=$(find "${TEMP_INCREMENTAL_CLASS_DIR}" -name "*.class" | wc -l)
    log "INFO" "✅ 全量class文件数:${COPY_COUNT}"
else
    log "INFO" "✅ 筛选出增量class文件数:${COPY_COUNT}"
fi

# 5. 推送数据到Jenkins
log "INFO" "推送 coverage.exec 到 Jenkins..."
scp -q -o StrictHostKeyChecking=no -o ConnectTimeout=10 "$COV_FILE" "$JENKINS_USER@$JENKINS_SERVER_IP:$JENKINS_WORKSPACE/" || error_exit "推送exec失败"

log "INFO" "推送class文件到 Jenkins..."
# 清理旧目录+重建
ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 "$JENKINS_USER@$JENKINS_SERVER_IP" "rm -rf ${JENKINS_CLASS_DIR} && mkdir -p ${JENKINS_CLASS_DIR}" || error_exit "初始化class目录失败"
# 推送筛选后的class(增量/全量)
scp -r -q -o StrictHostKeyChecking=no "$TEMP_INCREMENTAL_CLASS_DIR/"* "$JENKINS_USER@$JENKINS_SERVER_IP:${JENKINS_CLASS_DIR}/" || error_exit "推送class失败"

# 6. 统一赋权
log "INFO" "配置Jenkins工作空间权限..."
ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 "$JENKINS_USER@$JENKINS_SERVER_IP" \
    "chown -R ${JENKINS_RUN_USER} ${JENKINS_WORKSPACE} && \
     chmod -R 755 ${JENKINS_WORKSPACE}" || error_exit "赋权失败"

# 7. 收尾清理
log "INFO" "清理本地临时文件..."
rm -rf "$TEMP_CLASS_DIR" "$TEMP_INCREMENTAL_CLASS_DIR" /tmp/added-files.txt

log "INFO" "===== 执行完成! ====="
log "INFO" "  - 覆盖率数据:${JENKINS_WORKSPACE}/coverage.exec"
log "INFO" "  - Class文件目录:${JENKINS_CLASS_DIR}(${COPY_COUNT}个文件)"
exit 0

构建后配置:扫描增量的class文件

3.构建后生成增量代码报告

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐