Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7aebe96bb3 |
@@ -48,6 +48,103 @@ rm -rf /var/www/cma/assets/
|
||||
cp -r dist/* /var/www/cma/
|
||||
echo " ✓ 部署完成"
|
||||
|
||||
# 3b. 前端层一致性断言(T2, 2026-09-18):部署链静默替换门
|
||||
# 背景:[3] 只有 rm -rf + cp,缓存层吐旧 index、或 index 指向已被删的旧 chunk 时,现象是
|
||||
# 「首页 200、点进页面 404/白屏」,而其后的冒烟 5 项全为后端项(127.0.0.1:8010)→ 整轮部署全绿。
|
||||
# 三条断言各自独立输出,任一不一致即 exit 1。离线演练只覆盖下面三个变量,无需 build。
|
||||
# >>> cma-frontend-assert(最小可抽取块:scripts/tests 按行首标记 awk 抽取后原样执行,改结构请沿用标记)
|
||||
FRONTEND_DIST_DIR="${FRONTEND_DIST_DIR:-$SCRIPT_DIR/frontend/dist}"
|
||||
CMA_WEBROOT="${CMA_WEBROOT:-/var/www/cma}"
|
||||
SITE_URL="${SITE_URL:-https://cma.sxbh.ltd}"
|
||||
echo "[3b/5] 前端层一致性断言(dist ↔ webroot ↔ 引用链)..."
|
||||
FRONTEND_ASSERT_FAIL=0
|
||||
|
||||
# ① 逐文件 md5 对账:dist/assets 与 webroot/assets 文件集合一致(无多、无少)且同名文件 md5 相同
|
||||
DIST_ASSETS="$FRONTEND_DIST_DIR/assets"
|
||||
WEB_ASSETS="$CMA_WEBROOT/assets"
|
||||
DIST_FILES="$(ls -1 "$DIST_ASSETS" 2>/dev/null | sort)"
|
||||
WEB_FILES="$(ls -1 "$WEB_ASSETS" 2>/dev/null | sort)"
|
||||
MISSING_FILES="$(comm -23 <(printf '%s\n' "$DIST_FILES") <(printf '%s\n' "$WEB_FILES"))"
|
||||
EXTRA_FILES="$(comm -13 <(printf '%s\n' "$DIST_FILES") <(printf '%s\n' "$WEB_FILES"))"
|
||||
MD5_DIFF=""
|
||||
while IFS= read -r F; do
|
||||
[ -n "$F" ] || continue
|
||||
D_MD5="$(md5sum "$DIST_ASSETS/$F" 2>/dev/null | awk '{print $1}')"
|
||||
W_MD5="$(md5sum "$WEB_ASSETS/$F" 2>/dev/null | awk '{print $1}')"
|
||||
[ "$D_MD5" = "$W_MD5" ] || MD5_DIFF="${MD5_DIFF}${MD5_DIFF:+, }$F(dist=$D_MD5 web=$W_MD5)"
|
||||
done <<< "$DIST_FILES"
|
||||
if [ -n "$MISSING_FILES" ] || [ -n "$EXTRA_FILES" ] || [ -n "$MD5_DIFF" ]; then
|
||||
echo " ❌ 断言① 逐文件 md5 对账失败(dist=$DIST_ASSETS)"
|
||||
[ -z "$MISSING_FILES" ] || echo " webroot 缺失: $(printf '%s' "$MISSING_FILES" | tr '\n' ' ')"
|
||||
[ -z "$EXTRA_FILES" ] || echo " webroot 多出: $(printf '%s' "$EXTRA_FILES" | tr '\n' ' ')"
|
||||
[ -z "$MD5_DIFF" ] || echo " md5 不一致: $MD5_DIFF"
|
||||
FRONTEND_ASSERT_FAIL=1
|
||||
else
|
||||
echo " ✅ 断言① 逐文件 md5 对账通过($(printf '%s\n' "$DIST_FILES" | awk 'NF{n++} END{print n+0}') 个文件全部一致)"
|
||||
fi
|
||||
|
||||
# ② 主包一致:webroot/index.html 解析出的入口 chunk 必须在 dist 存在且 md5 相同
|
||||
INDEX_HTML="$CMA_WEBROOT/index.html"
|
||||
MAIN_BUNDLE="$(grep -o '/assets/index-[A-Za-z0-9._-]*\.js' "$INDEX_HTML" 2>/dev/null | head -n 1)"
|
||||
if [ -z "$MAIN_BUNDLE" ]; then
|
||||
echo " ❌ 断言② 主包一致失败: 未能从 $INDEX_HTML 解析出入口 chunk(/assets/index-*.js)"
|
||||
FRONTEND_ASSERT_FAIL=1
|
||||
elif [ ! -f "$FRONTEND_DIST_DIR$MAIN_BUNDLE" ]; then
|
||||
echo " ❌ 断言② 主包一致失败: index.html 引用的 $MAIN_BUNDLE 在 dist 不存在($FRONTEND_DIST_DIR)"
|
||||
FRONTEND_ASSERT_FAIL=1
|
||||
else
|
||||
MAIN_D_MD5="$(md5sum "$FRONTEND_DIST_DIR$MAIN_BUNDLE" 2>/dev/null | awk '{print $1}')"
|
||||
MAIN_W_MD5="$(md5sum "$CMA_WEBROOT$MAIN_BUNDLE" 2>/dev/null | awk '{print $1}')"
|
||||
if [ -n "$MAIN_D_MD5" ] && [ "$MAIN_D_MD5" = "$MAIN_W_MD5" ]; then
|
||||
echo " ✅ 断言② 主包一致通过($MAIN_BUNDLE dist=web=$MAIN_D_MD5)"
|
||||
else
|
||||
echo " ❌ 断言② 主包一致失败: $MAIN_BUNDLE dist=$MAIN_D_MD5 web=$MAIN_W_MD5"
|
||||
FRONTEND_ASSERT_FAIL=1
|
||||
fi
|
||||
fi
|
||||
|
||||
# ③ 引用链可 200:主包内引用的懒加载 chunk 逐个请求 $SITE_URL,必须全部 HTTP 200(无静默跳过分支)
|
||||
# 字面量形态取并集:现网 Vite 产物实测为相对形态 from"./Login-CCqwmqG_.js",另有 "/assets/*.js" 形态;
|
||||
# 两者都归一到 /assets/<name> 再请求。解析不到任何引用 → 显式判失败(不得静默放行)。
|
||||
MAIN_JS_FILE="$CMA_WEBROOT$MAIN_BUNDLE"
|
||||
CHUNKS="$(grep -o -E '(\./|/assets/)[A-Za-z0-9._-]+\.js' "$MAIN_JS_FILE" 2>/dev/null | sed -E 's#^\./#/assets/#' | sort -u)"
|
||||
CHUNK_TOTAL="$(printf '%s\n' "$CHUNKS" | awk 'NF{n++} END{print n+0}')"
|
||||
if [ ! -f "$MAIN_JS_FILE" ]; then
|
||||
echo " ❌ 断言③ 引用链可 200 失败: 主包在 webroot 不存在($MAIN_JS_FILE)"
|
||||
FRONTEND_ASSERT_FAIL=1
|
||||
elif [ "$CHUNK_TOTAL" -eq 0 ]; then
|
||||
echo " ❌ 断言③ 引用链可 200 失败: 主包 $MAIN_BUNDLE 内未解析到任何 chunk 引用(./*.js 或 /assets/*.js)"
|
||||
FRONTEND_ASSERT_FAIL=1
|
||||
else
|
||||
CHUNK_OK=0
|
||||
CHUNK_BAD=""
|
||||
while IFS= read -r C; do
|
||||
[ -n "$C" ] || continue
|
||||
C_CODE="$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 "$SITE_URL$C" 2>/dev/null || echo 000)"
|
||||
if [ "$C_CODE" = "200" ]; then
|
||||
CHUNK_OK=$((CHUNK_OK + 1))
|
||||
else
|
||||
CHUNK_BAD="${CHUNK_BAD}${CHUNK_BAD:+, }$C(HTTP=$C_CODE)"
|
||||
fi
|
||||
done <<< "$CHUNKS"
|
||||
if [ -n "$CHUNK_BAD" ]; then
|
||||
echo " ❌ 断言③ 引用链可 200 失败($SITE_URL): $CHUNK_BAD"
|
||||
FRONTEND_ASSERT_FAIL=1
|
||||
else
|
||||
echo " ✅ 断言③ 引用链可 200 通过($CHUNK_OK/$CHUNK_TOTAL chunk @ $SITE_URL)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 三条均为硬门:任一不一致即中止部署(nginx 直接服务静态文件,无需 reload)
|
||||
if [ "$FRONTEND_ASSERT_FAIL" -ne 0 ]; then
|
||||
echo ""
|
||||
echo "🚨 前端层一致性断言未通过 → 中止部署(webroot 可能停留在旧包 / 半包)"
|
||||
echo " dist=$FRONTEND_DIST_DIR webroot=$CMA_WEBROOT site=$SITE_URL"
|
||||
exit 1
|
||||
fi
|
||||
echo " ✓ 前端层一致性断言通过(① 逐文件 md5 ② 主包 ③ 引用链 200)"
|
||||
# <<< cma-frontend-assert
|
||||
|
||||
# 4. 更新后端依赖(失败不阻断 — 依赖已在系统Python中,避免PEP668中断部署)
|
||||
echo "[4/5] 更新后端依赖..."
|
||||
cd "$SCRIPT_DIR/backend"
|
||||
|
||||
Reference in New Issue
Block a user