Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eb7b0edc96 | |||
| 6d64a9bfbf | |||
| fc15a34a77 | |||
| 7d0595105f |
@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.1.11] - 2026-08-26
|
||||
|
||||
### 修复
|
||||
- 修复 Windows 版因构建流程缺陷未包含「扫码出库」功能的问题;升级后 Windows 用户即可使用扫码枪扫码出库(macOS / 网页 / 安卓版此前已正常)。
|
||||
|
||||
## [1.1.10] - 2026-08-25
|
||||
|
||||
### 新功能
|
||||
|
||||
+81
-10
@@ -29,17 +29,69 @@ 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
|
||||
|
||||
setup_ssh
|
||||
echo "==> deploy-client: uploading files to ${TARGET_HOST}"
|
||||
${SCP} "$VERSION_YAML" "${TARGET_USER}@${TARGET_HOST}:/tmp/version.yaml"
|
||||
rsync -avz --delete -e "ssh -i ~/.ssh/ec2_deploy.pem -o StrictHostKeyChecking=no" \
|
||||
/tmp/jiu-web-new/ "${TARGET_USER}@${TARGET_HOST}:/tmp/jiu-web-new/"
|
||||
# --- 版本一致性校验(发版前,本地产物)--------------------------------------
|
||||
# 部署错版事故的通用防线:确认将要上线的产物版本 == 本次发版 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
|
||||
|
||||
# Desktop / mobile installers (served from /downloads/ by nginx). Guarded —
|
||||
# may be absent in a partial manual deploy.
|
||||
[ -f dist/jiu-windows-x64-setup.exe ] && ${SCP} dist/jiu-windows-x64-setup.exe "${TARGET_USER}@${TARGET_HOST}:/tmp/jiu-windows-x64-setup.exe" || true
|
||||
[ -f dist/jiu-macos-x64.zip ] && ${SCP} dist/jiu-macos-x64.zip "${TARGET_USER}@${TARGET_HOST}:/tmp/jiu-macos-x64.zip" || true
|
||||
[ -f dist/jiu-android.apk ] && ${SCP} dist/jiu-android.apk "${TARGET_USER}@${TARGET_HOST}:/tmp/jiu-android.apk" || true
|
||||
setup_ssh
|
||||
RSYNC_SSH="ssh -i ~/.ssh/ec2_deploy.pem -o StrictHostKeyChecking=no"
|
||||
|
||||
# 经隧道推文件到 ali 偶发中断,大安装包尤甚(见 client-v1.1.10 run #362 的
|
||||
# Deploy → Ali 失败:android 83M scp 传到一半连接 reset,整个 job 挂 14min)。
|
||||
# 用 rsync --partial(断点续传)+ 重试替代 scp:断了从断点续传、不整段重来。
|
||||
# 单文件推送,重试 5 次;安装包已压缩(apk/zip/exe)故不加 -z。
|
||||
push_file() { # push_file <local-file> <remote-abs-path>
|
||||
local src="$1" dst="$2" i
|
||||
for i in 1 2 3 4 5; do
|
||||
if rsync -av --partial --timeout=120 -e "$RSYNC_SSH" \
|
||||
"$src" "${TARGET_USER}@${TARGET_HOST}:${dst}"; then
|
||||
return 0
|
||||
fi
|
||||
echo "!! push_file: ${src} 传输中断(第 ${i}/5 次),5s 后 --partial 续传重试…" >&2
|
||||
sleep 5
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
echo "==> deploy-client: uploading files to ${TARGET_HOST}"
|
||||
# version.yaml 承载版本号/更新日志,必达;5 次仍失败则 set -e 中止发版。
|
||||
push_file "$VERSION_YAML" /tmp/version.yaml
|
||||
|
||||
# Flutter web 目录同步(--delete 保持一致;--partial 续传;重试 3 次)。web 是
|
||||
# 发版核心产物,3 次仍失败即中止(避免把不完整目录切上线)。
|
||||
web_ok=0
|
||||
for i in 1 2 3; do
|
||||
if rsync -avz --delete --partial --timeout=120 -e "$RSYNC_SSH" \
|
||||
/tmp/jiu-web-new/ "${TARGET_USER}@${TARGET_HOST}:/tmp/jiu-web-new/"; then
|
||||
web_ok=1; break
|
||||
fi
|
||||
echo "!! web 同步中断(第 ${i}/3 次),5s 后 --partial 续传重试…" >&2; sleep 5
|
||||
done
|
||||
[ "$web_ok" = 1 ] || { echo "ERROR: web 同步 3 次均失败,中止发版。" >&2; exit 1; }
|
||||
|
||||
# Desktop / mobile installers(nginx 从 /downloads/ 提供)。文件缺失可跳过
|
||||
# (部分手动发版);传输 5 次仍失败仅告警不阻断 web/version 切换(下次发版补)。
|
||||
[ -f dist/jiu-windows-x64-setup.exe ] && { push_file dist/jiu-windows-x64-setup.exe /tmp/jiu-windows-x64-setup.exe || echo "!! windows 安装包 5 次仍失败,跳过。" >&2; } || true
|
||||
[ -f dist/jiu-macos-x64.zip ] && { push_file dist/jiu-macos-x64.zip /tmp/jiu-macos-x64.zip || echo "!! macos 安装包 5 次仍失败,跳过。" >&2; } || true
|
||||
[ -f dist/jiu-android.apk ] && { push_file dist/jiu-android.apk /tmp/jiu-android.apk || echo "!! android 安装包 5 次仍失败,跳过。" >&2; } || true
|
||||
|
||||
${SSH} "${TARGET_USER}@${TARGET_HOST}" << 'ENDSSH'
|
||||
set -e
|
||||
@@ -64,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"
|
||||
|
||||
@@ -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 <<MSG
|
||||
ERROR: ${STAGE_TAG} 的 Windows 包构建自**错误的提交**,client/ 子树与本次发版不一致:
|
||||
发版提交(${TAG}) client/ tree = ${REL_TREE}
|
||||
winstage 提交 client/ tree = ${WIN_TREE}
|
||||
→ winstage-v${VER} 是在别的提交上打的 tag,发出去会是「版本号对但代码错版」的包。
|
||||
修复:在 GitHub 用**本次发版的提交**重建 Windows 包,再重跑发版:
|
||||
gh workflow run windows.yml -f ver=${VER} (仓库 ${GH_REPO},确保其 main 已含本次发版提交)
|
||||
MSG
|
||||
exit 1
|
||||
fi
|
||||
echo "==> 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
|
||||
|
||||
@@ -44,8 +44,13 @@ for line in raw.splitlines(keepends=True):
|
||||
if line.startswith('version:'):
|
||||
out.append('version: "%s"\n' % ver)
|
||||
elif line.startswith('build_number:'):
|
||||
# build_number 由版本号公式派生(major*10000+minor*100+patch),与 pubspec
|
||||
# 的 CFBundleVersion 同源、天然单调。旧法「读仓库 version.yaml +1」有 bug:
|
||||
# CI/本地发版都不回写仓库那份,它永远冻结 → 每次都算出同一个值(见 1.1.9/1.1.10
|
||||
# 均为 5 的历史)。改公式后无状态依赖,CI 与 local-release 结果一致。
|
||||
try:
|
||||
b = int(line.split(':', 1)[1].strip()) + 1
|
||||
_mj, _mn, _pt = (int(x) for x in ver.split('.')[:3])
|
||||
b = _mj * 10000 + _mn * 100 + _pt
|
||||
except Exception:
|
||||
b = 1
|
||||
out.append('build_number: %d\n' % b)
|
||||
|
||||
@@ -381,8 +381,12 @@ for line in raw.splitlines(keepends=True):
|
||||
if line.startswith('version:'):
|
||||
out.append('version: "%s"\n' % ver)
|
||||
elif line.startswith('build_number:'):
|
||||
# build_number 由版本号公式派生(major*10000+minor*100+patch),与 pubspec
|
||||
# 的 CFBundleVersion(BUILD)同源、天然单调。旧法「读仓库 version.yaml +1」有 bug:
|
||||
# 仓库那份从不回写、永远冻结 → 每次都算出同一个值(1.1.9/1.1.10 均为 5)。
|
||||
try:
|
||||
b = int(line.split(':', 1)[1].strip()) + 1
|
||||
_mj, _mn, _pt = (int(x) for x in ver.split('.')[:3])
|
||||
b = _mj * 10000 + _mn * 100 + _pt
|
||||
except Exception:
|
||||
b = 1
|
||||
out.append('build_number: %d\n' % b)
|
||||
|
||||
Reference in New Issue
Block a user