fix(release): normalize backend module evidence

This commit is contained in:
2026-07-14 22:38:23 +08:00
parent 2f3caa46ba
commit 48f4de0366
3 changed files with 54 additions and 8 deletions
+1
View File
@@ -9,6 +9,7 @@
- 2026-07-14 22:16 BRD 生产发布完成:基于 `HEAD=ee261568` 在隔离干净 worktree 重新构建并发布管理端、`mobile-uni` H5 和 `ruoyi-admin` 后端;远端备份为 `/opt/wygj/backups/release-20260714221321`。发布后远端资源已与本地产物一致:管理端 `assets/index-B9T505HT.js`(SHA-256 `66a827a7b3e5d794ae9da54c6e2276080995614fa52f54d49f746087663f8c94`)、H5 `assets/index-CDn6TrX8.js`(SHA-256 `eecb4820f3887aceb952efe3e6f2210bab325be8b03f2813e6ec304c68b26f08`)、后端 `/opt/wygj/app/ruoyi-admin.jar`(SHA-256 `e2941cf1e2a31e22c979cea77a12b2e6ef2e5430409398dbde0918a1ab00f437`);`wygj-aihr.service` 重启后为 `active`,根站、`/h5/`、租户列表和员工首页 API 均返回 `200`,发布后 `RELEASE_VERIFY_REMOTE_MATCH=true` 预检通过。Chrome 匿名打开 H5 后标题为“今日”、员工端四个 tab 和最新主 JS 均正常加载;生产 `13900001111 + 123456` 登录被服务端按生产验证码策略拒绝,随后验证码发送触发号码天级限流,因此本轮未完成认证态浏览器回归,也未把匿名渲染当作登录/主管权限验收。未写入业务数据;正式主管组织映射、真实试点数据和认证态回归仍未完成。
- 2026-07-14 BRD 发布预检后端证据补强:`release-preflight.sh` 新增可选 `RELEASE_VERIFY_REMOTE_BACKEND=true`,通过 SSH 比对远端 `ruoyi-admin.jar` SHA-256;该开关未同时启用远端资源比对时现在直接失败,不再静默跳过。远端只读业务检查同时覆盖租户列表和移动端员工首页;默认发布路径仍为 `/opt/wygj/app/ruoyi-admin.jar`,可用 `RELEASE_REMOTE_BACKEND_PATH` 覆盖。本轮未重新发布业务包。
- 2026-07-14 BRD 发布预检后端 hash 误报修复:外层 Spring Boot jar 的 ZIP 元数据会因构建时间变化而不同,预检现保留整包 SHA-256 作为证据,同时比较 `ruoyi-aihr` 嵌套模块的归一化内容 SHA-256;远端模块内容一致才判定后端匹配,并继续拒绝不安全的远端路径。本轮未重新发布业务包。
- 2026-07-14 BRD 验收脚本执行一致性修复:`scripts/tests/pilot-export-contract.test.mjs` 新增 Node shebang 并恢复可执行权限,直接运行与 `node` 运行结果一致,5/5 通过;同时保留 `release-preflight.sh` 的后端 hash 配置误用门禁。该批次只修工具与证据链,不改业务数据或生产包。
- 2026-07-14 22:03 BRD 发布证据复核:基于当前 `HEAD=ab10e565` 重新构建管理端、`mobile-uni` H5 和 `ruoyi-admin` 后端产物均成功;本地产物为管理端 `assets/index-B9T505HT.js`(SHA-256 `66a827a7b3e5d794ae9da54c6e2276080995614fa52f54d49f746087663f8c94`)、H5 `assets/index-CDn6TrX8.js`(SHA-256 `eecb4820f3887aceb952efe3e6f2210bab325be8b03f2813e6ec304c68b26f08`),后端 jar SHA-256 `bdbd50be0b5e6aaa05504726fee7016a712c3138494ac84c4d4dfbe669aa6379`;线上仍加载管理端 `assets/index-CJZ3Ax3Z.js`(SHA-256 `8b9278a26ccb760abce489b12a20b221748c64d4c4153613d2511777bb9677bc`)与 H5 `assets/index-D4-NrEpb.js`(SHA-256 `ab15bfd17cabe58e2f34b0ac61ee198a837128ed85dc6bb0ddf77bc3e22dcd9c`),当前分支修复尚未发布。`RELEASE_VERIFY_REMOTE_MATCH=true ./scripts/release-preflight.sh` 在工作区卫生检查阶段因两份用户未提交 Figma 文档停止;本轮未执行生产静态同步、后端重启或业务数据写入。
- 2026-07-14 21:28 BRD 发布只读复核:生产根站、`/h5/` 和 `/prod-api/auth/tenant/list` 均返回 `200`;线上管理端仍加载 `assets/index-CJZ3Ax3Z.js`(SHA-256 `8b9278a26ccb760abce489b12a20b221748c64d4c4153613d2511777bb9677bc`),H5 仍加载 `assets/index-D4-NrEpb.js`(SHA-256 `ab15bfd17cabe58e2f34b0ac61ee198a837128ed85dc6bb0ddf77bc3e22dcd9c`)。本地最新提交 `d2b0035e` 的租户隔离修复尚未发布;本轮仅做 GET/hash 核验,未执行生产静态同步、后端重启或业务数据写入。
+2 -2
View File
@@ -167,11 +167,11 @@ curl -k -s https://peilian.njzhmj.top/h5/ | sed -n '1,20p'
RELEASE_REMOTE_URL=https://peilian.njzhmj.top ./scripts/release-preflight.sh
# 发布后核验线上主资源是否与当前本地产物一致
RELEASE_REMOTE_URL=https://peilian.njzhmj.top RELEASE_VERIFY_REMOTE_MATCH=true ./scripts/release-preflight.sh
# 同时通过 SSH 校验线上后端 jar;默认使用本机 SSH alias YCWY
# 同时通过 SSH 校验线上后端 jar;默认使用本机 SSH alias YCWY;比较 ruoyi-aihr 模块内容,避免 ZIP 打包时间戳造成误报
RELEASE_REMOTE_URL=https://peilian.njzhmj.top RELEASE_VERIFY_REMOTE_MATCH=true RELEASE_VERIFY_REMOTE_BACKEND=true ./scripts/release-preflight.sh
```
`RELEASE_VERIFY_REMOTE_BACKEND=true` 必须与 `RELEASE_VERIFY_REMOTE_MATCH=true` 同时使用;远端后端默认核对 `/opt/wygj/app/ruoyi-admin.jar`,如发布路径不同可通过 `RELEASE_REMOTE_BACKEND_PATH` 覆盖。
`RELEASE_VERIFY_REMOTE_BACKEND=true` 必须与 `RELEASE_VERIFY_REMOTE_MATCH=true` 同时使用;预检会同时打印整包 jar SHA-256 和 `ruoyi-aihr` 模块内容 SHA-256,实际匹配以模块内容 hash 为准。远端后端默认核对 `/opt/wygj/app/ruoyi-admin.jar`,如发布路径不同可通过 `RELEASE_REMOTE_BACKEND_PATH` 覆盖;路径必须是安全的绝对路径。
带 `RELEASE_REMOTE_URL` 时,预检同时校验租户接口 JSON 的业务 `code=200`;HTTP 200 但业务返回 401/405 会判定失败。
+51 -6
View File
@@ -29,12 +29,28 @@ sha256_stream() {
fi
}
normalized_jar_content_sha256() {
local jar_path="$1"
local manifest_path
manifest_path="$(mktemp)"
while IFS= read -r entry; do
case "$entry" in
*/|META-INF/*.SF|META-INF/*.RSA|META-INF/*.DSA) continue ;;
esac
printf '%s %s\n' "$(unzip -p "$jar_path" "$entry" | sha256_stream)" "$entry" >> "$manifest_path"
done < <(unzip -Z1 "$jar_path" | LC_ALL=C sort)
sha256 "$manifest_path"
rm -f "$manifest_path"
}
frontend_index="frontend/dist/index.html"
mobile_index="mobile-uni/dist/build/h5/index.html"
backend_jar="backend/ruoyi-admin/target/ruoyi-admin.jar"
require_file "$frontend_index"
require_file "$mobile_index"
require_file "$backend_jar"
backend_module_jar_name="$(unzip -Z1 "$backend_jar" | sed -nE 's#BOOT-INF/lib/(ruoyi-aihr-[^/]+\.jar)#\1#p' | head -1)"
[[ -n "$backend_module_jar_name" ]] || fail "backend jar does not contain the ruoyi-aihr module"
frontend_asset="$(sed -nE 's/.*src="([^"]+\.js)".*/\1/p' "$frontend_index" | head -1)"
mobile_asset="$(sed -nE 's/.*src="([^"]+\.js)".*/\1/p' "$mobile_index" | head -1)"
@@ -131,12 +147,41 @@ if [[ -n "${RELEASE_REMOTE_URL:-}" ]]; then
remote_ssh="${RELEASE_REMOTE_SSH:-YCWY}"
remote_backend_path="${RELEASE_REMOTE_BACKEND_PATH:-/opt/wygj/app/ruoyi-admin.jar}"
[[ "$remote_backend_path" =~ ^/[A-Za-z0-9._/-]+$ ]] || fail "remote backend path must be an absolute safe path: $remote_backend_path"
remote_backend_sha256="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" "sha256sum '$remote_backend_path'" | awk '{print $1}')" || fail "remote backend hash check failed: $remote_ssh:$remote_backend_path"
[[ "$remote_backend_sha256" =~ ^[0-9a-f]{64}$ ]] || fail "remote backend hash is invalid: $remote_ssh:$remote_backend_path"
local_backend_sha256="$(sha256 "$backend_jar")"
echo "remote_backend_sha256=$remote_backend_sha256"
[[ "$remote_backend_sha256" == "$local_backend_sha256" ]] || fail "remote backend jar does not match local build"
echo "remote_backend_match=true"
remote_backend_jar_sha256="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" "sha256sum '$remote_backend_path'" | awk '{print $1}')" || fail "remote backend hash check failed: $remote_ssh:$remote_backend_path"
[[ "$remote_backend_jar_sha256" =~ ^[0-9a-f]{64}$ ]] || fail "remote backend hash is invalid: $remote_ssh:$remote_backend_path"
local_backend_jar_sha256="$(sha256 "$backend_jar")"
local_backend_module_jar="$(mktemp)"
unzip -p "$backend_jar" "BOOT-INF/lib/$backend_module_jar_name" > "$local_backend_module_jar"
local_backend_module_sha256="$(normalized_jar_content_sha256 "$local_backend_module_jar")"
rm -f "$local_backend_module_jar"
remote_backend_module_sha256="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_backend_path" "$backend_module_jar_name" <<'REMOTE'
set -euo pipefail
outer="$1"
module="$2"
nested="$(mktemp)"
manifest="$(mktemp)"
cleanup() {
rm -f "$nested" "$manifest"
}
trap cleanup EXIT
unzip -p "$outer" "BOOT-INF/lib/$module" > "$nested"
while IFS= read -r entry; do
case "$entry" in
*/|META-INF/*.SF|META-INF/*.RSA|META-INF/*.DSA) continue ;;
esac
printf '%s %s\n' "$(unzip -p "$nested" "$entry" | sha256sum | awk '{print $1}')" "$entry" >> "$manifest"
done < <(unzip -Z1 "$nested" | LC_ALL=C sort)
sha256sum "$manifest" | awk '{print $1}'
REMOTE
)" || fail "remote backend module hash check failed: $remote_ssh:$remote_backend_path"
[[ "$remote_backend_module_sha256" =~ ^[0-9a-f]{64}$ ]] || fail "remote backend module hash is invalid: $remote_ssh:$remote_backend_path"
echo "remote_backend_jar_sha256=$remote_backend_jar_sha256"
echo "local_backend_jar_sha256=$local_backend_jar_sha256"
echo "backend_module=$backend_module_jar_name"
echo "remote_backend_module_sha256=$remote_backend_module_sha256"
echo "local_backend_module_sha256=$local_backend_module_sha256"
[[ "$remote_backend_module_sha256" == "$local_backend_module_sha256" ]] || fail "remote AIHR module does not match local build"
echo "remote_backend_module_match=true"
fi
fi
fi