From 6d64a9bfbf743d8fb9f8293bc302f73e4872481b Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Wed, 26 Aug 2026 16:14:07 +0800 Subject: [PATCH] =?UTF-8?q?fix(devops):=20=E5=8F=91=E7=89=88=E5=8A=A0?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E4=B8=80=E8=87=B4=E6=80=A7=E7=A1=AC=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C=EF=BC=8C=E6=9D=9C=E7=BB=9D=E9=94=99=E7=89=88=20Window?= =?UTF-8?q?s=20=E5=8C=85/=E9=94=99=E7=89=88=E9=83=A8=E7=BD=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因:github 模式的 windows 构建按版本名(winstage-vX)拉包,不校验提交, winstage tag 可能打在错误提交上 → 发出「版本号对但代码错版」的包 (client-v1.1.10 缺扫码功能事故)。 - fetch-windows-staged.sh:比对发版提交与 winstage 提交的 client/ 子树 SHA, 不一致硬失败并提示从发版提交重建 winstage。 - deploy-client.sh:发版前校验 version.yaml + web version.json 版本==tag, 发版后实测线上 /version==tag(重试5次),任一不符即部署失败。 Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Bupi8Kdqkfx2N5acFsHTx5 --- scripts/ci/deploy-client.sh | 41 ++++++++++++++++++++++++++++++ scripts/ci/fetch-windows-staged.sh | 26 +++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/scripts/ci/deploy-client.sh b/scripts/ci/deploy-client.sh index bbb5ebc..d511ed5 100644 --- a/scripts/ci/deploy-client.sh +++ b/scripts/ci/deploy-client.sh @@ -29,6 +29,28 @@ rm -rf /tmp/jiu-web-new mkdir -p /tmp/jiu-web-new tar -xzf dist/web.tar.gz -C /tmp/jiu-web-new --strip-components=1 +# --- 版本一致性校验(发版前,本地产物)-------------------------------------- +# 部署错版事故的通用防线:确认将要上线的产物版本 == 本次发版 tag 版本。 +# ① version.yaml 的 version:(后端 /version 与官网下载页实时读它) +# ② Flutter web 产物 version.json 的 version(自动更新客户端据此判断新版) +# 任一不符即中止发版(exit 1),绝不把错版切上线。 +TAG_VER="$(ver_from_tag "$TAG")" +# 解析器均把路径经 argv 传入、用 chr(34)/chr(39) 规避引号字面量,免嵌套引号踩坑。 +YAML_VER="$(python3 -c 'import sys +for line in open(sys.argv[1], encoding="utf-8"): + if line.startswith("version:"): + print(line.split(":", 1)[1].strip().strip(chr(34)).strip(chr(39))); break' "$VERSION_YAML")" +WEB_VER="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1], encoding="utf-8")).get("version", ""))' /tmp/jiu-web-new/version.json 2>/dev/null || echo '')" +echo "==> deploy-client: 版本校验 tag=${TAG_VER} version.yaml=${YAML_VER} web=${WEB_VER}" +if [ "$YAML_VER" != "$TAG_VER" ]; then + echo "ERROR: version.yaml 版本(${YAML_VER}) != 发版 tag(${TAG_VER}),中止发版。" >&2 + exit 1 +fi +if [ -n "$WEB_VER" ] && [ "$WEB_VER" != "$TAG_VER" ]; then + echo "ERROR: Flutter web 产物版本(${WEB_VER}) != 发版 tag(${TAG_VER}),中止发版。" >&2 + exit 1 +fi + setup_ssh RSYNC_SSH="ssh -i ~/.ssh/ec2_deploy.pem -o StrictHostKeyChecking=no" @@ -94,6 +116,25 @@ rm -f /opt/jiu/downloads/jiu-windows-* /opt/jiu/downloads/jiu-macos-* /opt/jiu/d echo "Client deploy complete!" ENDSSH +# --- 版本一致性校验(发版后,线上实测)-------------------------------------- +# 切换完成后实测线上 /version(后端每请求实时读 version.yaml),确认真正上线的 +# 版本 == 发版 tag。不符即判定部署失败(exit 1)。域名 fronts 当前 active 机器。 +VERIFY_URL="${VERIFY_URL:-https://jiu.51yanmei.com}" +LIVE_VER="" +for i in 1 2 3 4 5; do + LIVE_VER="$(curl -fsS --max-time 15 "${VERIFY_URL}/version" 2>/dev/null \ + | python3 -c "import json,sys; print(json.load(sys.stdin).get('version',''))" 2>/dev/null || echo '')" + [ "$LIVE_VER" = "$TAG_VER" ] && break + echo "!! 线上 /version=${LIVE_VER:-<空>} 尚未更新到 ${TAG_VER}(第 ${i}/5 次),3s 后重试…" >&2 + sleep 3 +done +if [ "$LIVE_VER" != "$TAG_VER" ]; then + echo "ERROR: 部署后线上 ${VERIFY_URL}/version 版本(${LIVE_VER:-<空>}) != 发版 tag(${TAG_VER}),部署失败。" >&2 + teardown_ssh + exit 1 +fi +echo "==> deploy-client: 线上版本校验通过 /version=${LIVE_VER}" + teardown_ssh rm -rf /tmp/jiu-web-new echo "==> deploy-client: done" diff --git a/scripts/ci/fetch-windows-staged.sh b/scripts/ci/fetch-windows-staged.sh index e7b4ae9..9d6da01 100755 --- a/scripts/ci/fetch-windows-staged.sh +++ b/scripts/ci/fetch-windows-staged.sh @@ -35,6 +35,32 @@ MSG exit 1 fi +# --- client/ 子树 SHA 强校验(根因防线)--------------------------------------- +# winstage 的 GitHub Release 是**构建当时** github HEAD 上打的轻量 tag,只按版本名 +# 匹配无法保证它构建自本次发版的同一提交。历史事故(client-v1.1.10):winstage-v1.1.10 +# 打在了扫码提交的父提交上 → 发出「版本号对但缺扫码功能」的错版 Windows 包。 +# 这里比对**发版提交**与**winstage 提交**的 `client/` 子树 SHA(git 内容寻址,同源提交 +# 必然同 tree),不一致直接硬失败,逼迫先从发版提交重建 winstage,绝不发出错版。 +REL_TREE=$(git rev-parse HEAD:client) +git fetch --depth=1 "https://x-access-token:${GH_TOKEN}@github.com/${GH_REPO}.git" \ + "refs/tags/${STAGE_TAG}" >/dev/null 2>&1 || { + echo "ERROR: 无法从 GitHub 拉取 ${STAGE_TAG} 提交以校验 client/ 子树。" >&2 + exit 1 +} +WIN_TREE=$(git rev-parse FETCH_HEAD:client) +if [ "$REL_TREE" != "$WIN_TREE" ]; then + cat >&2 < fetch-windows-staged: client/ 子树校验通过(tree ${REL_TREE}),winstage 与发版同源。" + # 取同名资产的 GitHub API asset url(用 Accept: octet-stream 拉二进制,302 跳 codeload,走 github.com 不过 frps) aid=$(printf '%s' "$info" | python3 -c "import sys,json; print(next((a['id'] for a in json.load(sys.stdin).get('assets',[]) if a['name']=='${NAME}'), ''))") if [ -z "$aid" ]; then