From c4939a69419dc2d417af954bf006f7f74dd603ee Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Wed, 22 Jul 2026 12:08:04 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(ci):=20=E5=89=8D=E7=AB=AF=E4=BE=9D?= =?UTF-8?q?=E8=B5=96=20Docker=20Volume=20=E6=8C=81=E4=B9=85=E5=8C=96?= =?UTF-8?q?=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 按 package-lock.json md5 hash 命名 volume,自动失效 - 缓存命中时跳过 npm ci,节省 2-5 分钟安装时间 - 保留最近 5 个缓存 volume,自动清理旧缓存 - volume 操作失败时自动降级为无缓存模式 - 保持原有 3 次重试 + 国内镜像源逻辑 --- scripts/ci/step_frontend_install.sh | 97 +++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 13 deletions(-) diff --git a/scripts/ci/step_frontend_install.sh b/scripts/ci/step_frontend_install.sh index 624dd7b8c..1b29dd4c6 100755 --- a/scripts/ci/step_frontend_install.sh +++ b/scripts/ci/step_frontend_install.sh @@ -1,6 +1,7 @@ #!/bin/sh -# CI 公共步骤:前端依赖安装(在 docker node 容器中运行) -# 优化:增加国内npm镜像源,加重试间隔 +# CI 公共步骤:前端依赖安装(Docker Volume 持久化缓存方案) +# 通过 Docker named volume 缓存 node_modules,按 package-lock.json hash 命名 +# 缓存命中时跳过 npm ci,直接复用已有 volume set -eu MODE="${1:-full}" @@ -10,16 +11,86 @@ echo "=== 前端依赖安装开始 (模式: $MODE) ===" # npm国内镜像源(加速下载,减少网络失败) NPM_REGISTRY="https://registry.npmmirror.com" -for i in 1 2 3; do - echo "npm ci 尝试 $i/3 (镜像: $NPM_REGISTRY)" - docker run --rm \ - -v "$PWD:/workspace" \ - -w /workspace/apps/web \ - docker.m.daocloud.io/library/node:20 \ - sh -lc "npm config set registry $NPM_REGISTRY && npm ci --no-audit --no-fund" && break - echo "npm ci 失败,重试 $i/3..." - [ $i -eq 3 ] && exit 1 - sleep 10 -done +# 缓存配置 — 与 step_frontend_run.sh 保持一致 +LOCK_FILE="apps/web/package-lock.json" +VOLUME_PREFIX="ci-web-nm-" +KEEP_CACHE_COUNT=5 + +# 计算 package-lock.json 的 md5 hash 作为缓存 key +VOLUME_NAME="" +if [ -f "$LOCK_FILE" ]; then + LOCK_HASH=$(md5sum "$LOCK_FILE" | cut -c1-12) + VOLUME_NAME="${VOLUME_PREFIX}${LOCK_HASH}" + echo "缓存 key: $LOCK_HASH (volume: $VOLUME_NAME)" +else + echo "警告: 未找到 $LOCK_FILE,将不使用持久化缓存" +fi + +# 检查 volume 是否存在(缓存命中) +CACHE_HIT=0 +if [ -n "$VOLUME_NAME" ]; then + if docker volume inspect "$VOLUME_NAME" >/dev/null 2>&1; then + CACHE_HIT=1 + echo "缓存命中!复用 volume: $VOLUME_NAME" + else + echo "缓存未命中,创建 volume 并安装依赖..." + # 创建 volume(失败则降级为无缓存模式) + if ! docker volume create "$VOLUME_NAME" >/dev/null 2>&1; then + echo "警告: 创建 volume 失败,降级为无缓存模式" + VOLUME_NAME="" + fi + fi +fi + +# 构建 docker run 的 volume 挂载参数(空时不挂载) +VOLUME_ARGS="" +if [ -n "$VOLUME_NAME" ]; then + VOLUME_ARGS="-v ${VOLUME_NAME}:/workspace/apps/web/node_modules" +fi + +# 缓存未命中时执行 npm ci +if [ "$CACHE_HIT" -eq 0 ]; then + for i in 1 2 3; do + echo "npm ci 尝试 $i/3 (镜像: $NPM_REGISTRY)" + docker run --rm -v "$PWD:/workspace" $VOLUME_ARGS -w /workspace/apps/web docker.m.daocloud.io/library/node:20 sh -lc "npm config set registry $NPM_REGISTRY && npm ci --no-audit --no-fund" && break + echo "npm ci 失败,重试 $i/3..." + [ $i -eq 3 ] && exit 1 + sleep 10 + done +else + echo "跳过 npm ci,直接使用缓存的 node_modules" +fi + +# 清理旧缓存 volume(保留最近 N 个,防止磁盘占用无限增长) +if [ -n "$VOLUME_PREFIX" ]; then + echo "清理旧缓存 volume(保留最近 ${KEEP_CACHE_COUNT} 个)..." + ALL_VOLUMES=$(docker volume ls -q --filter "name=${VOLUME_PREFIX}" 2>/dev/null || true) + if [ -n "$ALL_VOLUMES" ]; then + TOTAL=$(echo "$ALL_VOLUMES" | wc -l) + if [ "$TOTAL" -gt "$KEEP_CACHE_COUNT" ]; then + # 按创建时间排序,保留最新的 N 个 + SORTED_VOLUMES=$(for v in $ALL_VOLUMES; do + CREATED=$(docker volume inspect --format '{{.CreatedAt}}' "$v" 2>/dev/null || echo "0") + echo "$CREATED $v" + done | sort | awk '{print $2}') + + # 删除超出保留数量的旧 volume + REMOVE_COUNT=$((TOTAL - KEEP_CACHE_COUNT)) + TO_DELETE=$(echo "$SORTED_VOLUMES" | head -n "$REMOVE_COUNT") + REMOVED=0 + for v in $TO_DELETE; do + # 跳过当前正在使用的 volume + if [ "$v" != "$VOLUME_NAME" ]; then + if docker volume rm "$v" >/dev/null 2>&1; then + REMOVED=$((REMOVED + 1)) + fi + fi + done + echo "已清理 $REMOVED 个旧缓存 volume,当前共 $((TOTAL - REMOVED)) 个" + else + echo "当前缓存 volume 数量: $TOTAL,无需清理" + fi + fi +fi echo "=== 前端依赖安装完成 ===" -- 2.54.0 From ecbc3b02905ab0634355777f5366e736dc2d5a5a Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Wed, 22 Jul 2026 12:08:15 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat(ci):=20=E5=89=8D=E7=AB=AF=E8=BF=90?= =?UTF-8?q?=E8=A1=8C=E8=84=9A=E6=9C=AC=E6=8C=82=E8=BD=BD=20Docker=20Volume?= =?UTF-8?q?=20=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 与 step_frontend_install.sh 使用相同 hash 计算逻辑 - 自动挂载对应 volume 到 node_modules - volume 不存在时优雅降级,不影响正常运行 --- scripts/ci/step_frontend_run.sh | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/scripts/ci/step_frontend_run.sh b/scripts/ci/step_frontend_run.sh index 6dcae42cc..3374b88a0 100755 --- a/scripts/ci/step_frontend_run.sh +++ b/scripts/ci/step_frontend_run.sh @@ -1,12 +1,26 @@ #!/bin/sh # CI 公共步骤:前端命令执行(在 docker node 容器中运行) # 用法:step_frontend_run.sh "要执行的命令" +# 支持 Docker Volume 持久化缓存的 node_modules set -eu CMD="${1:-echo 'no command'}" -docker run --rm \ - -v "$PWD:/workspace" \ - -w /workspace/apps/web \ - docker.m.daocloud.io/library/node:20 \ - sh -lc "$CMD" +# 缓存配置 — 与 step_frontend_install.sh 保持一致 +LOCK_FILE="apps/web/package-lock.json" +VOLUME_PREFIX="ci-web-nm-" + +# 计算 package-lock.json 的 hash,挂载对应的 volume +VOLUME_ARGS="" +if [ -f "$LOCK_FILE" ]; then + LOCK_HASH=$(md5sum "$LOCK_FILE" | cut -c1-12) + VOLUME_NAME="${VOLUME_PREFIX}${LOCK_HASH}" + if docker volume inspect "$VOLUME_NAME" >/dev/null 2>&1; then + VOLUME_ARGS="-v ${VOLUME_NAME}:/workspace/apps/web/node_modules" + echo "使用缓存 volume: $VOLUME_NAME" + else + echo "提示: 未找到缓存 volume $VOLUME_NAME,将使用源码目录 node_modules" + fi +fi + +docker run --rm -v "$PWD:/workspace" $VOLUME_ARGS -w /workspace/apps/web docker.m.daocloud.io/library/node:20 sh -lc "$CMD" -- 2.54.0