fix(release): support scoped backend preflight
This commit is contained in:
@@ -45,7 +45,7 @@
|
||||
- 线上移动端 H5:`https://peilian.njzhmj.top/h5/`,Caddy 服务 `/opt/wygj/www/h5`。
|
||||
- 只发布移动端静态资源时,用 `npm --prefix mobile-uni run build:h5` 后备份远端 `/opt/wygj/www/h5`,再 `rsync -az --delete mobile-uni/dist/build/h5/ YCWY:/opt/wygj/www/h5/`;前端静态修复不需要重启后端。
|
||||
- 原生 App 不复用 H5 静态发布流程;`npm --prefix mobile-uni run build:app` 仅生成 HBuilderX 出包资源,未提供法务 HTTPS 链接、签名或真机验收前不得称为已出包或上架。
|
||||
- 完整发布收口须按 `docs/DEV_SETUP.md` 运行同时启用远端静态、后端和 schema 的 `release-preflight`;只有三项都通过,才可称当前本地构建已与线上完整包匹配。单纯 H5 资源匹配不等同于完整发布验证。
|
||||
- 定向后端发布只启用远端后端 + schema 的 `release-preflight`,不得因本轮未发布的管理端/H5 不匹配而制造假失败,也不得把结果称为完整包匹配;完整发布收口须同时启用远端静态、后端和 schema,只有三项都通过才可称当前本地构建已与线上完整包匹配。
|
||||
- `aihr.practice.runtime-schema-bootstrap` / `AIHR_PRACTICE_RUNTIME_SCHEMA_BOOTSTRAP` 仅限本地开发逃生开关,Spring 默认关闭;生产请求只能校验表、列和关键索引,任何 DDL 都必须由正式 SQL 迁移完成,完整预检会拒绝开启该开关的服务。
|
||||
|
||||
## 当前业务边界
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
## Unreleased
|
||||
|
||||
- **2026-07-28 至 2026-07-29,8 月 1 日 Android 受控内测候选包与法务链路,当前总体 No-Go**:补齐登录页协议默认不勾选与法务地址失败关闭、App-Plus 原生隐私配置生成/校验、APK 包名/版本/API/签名/哈希/包内资源一致性/敏感值门禁、DCloud 云证书自定义基座和独立测试包流程;《服务协议》《隐私政策》静态页已部署到 `/legal/`,发布前整站备份为 `/opt/wygj/backups/www-20260728224710`。客户端法务校验已移除 App-Plus 不保证支持的浏览器 `URL` 构造器,测试验证码也不再以源码常量进入安装包;五个文件选择服务与九个消费页面进一步统一“用户取消/平台失败”分流,取消后不再残留上传或转写错误;直通车首次弱网加载失败后可原位重试,失败时清空旧通道并禁止提交。当前独立 APK `com.yincheng.wygj 0.1.10 (110)` 的 `targetSdkVersion=35`,SHA-256 为 `20CB16B4E9D024DBF5EEEF577BFBA3B4DF5E5E78B597BABB99CB7450C7AE5034`;Android 原生网络安全配置经 APK 反编译确认 `usesCleartextTraffic=false`、`networkSecurityConfig` 引用和系统证书信任配置均已进入安装包。本机 `Medium_Phone` Android 15/API 35 虚拟机已完成从 `0.1.9` 保留数据覆盖升级和冷启动,真实显示原生隐私弹窗,四项敏感权限保持未授权且未见 FATAL/ANR;`0.1.6` 已在 vivo V2443A(Android 15)完成覆盖安装和冷启动。代码候选提交 `dc20b920` 已在干净工作区通过统一 RC 门禁,`releaseEligible=true`:移动端 198 项测试、类型检查、H5/App 构建、管理端构建、`ruoyi-aihr` 698 项全量测试、后端打包及 APK 强化门禁全部通过;最小交付包 SHA-256 为 `4BAA79B2CA2C4D486B6194288D90022D5F994A4D8C8E05D7C2DDD87F9BF5B3B7`,解压后 9 个文件、其中 8 个载荷逐文件复验通过。只读生产预检确认根站、租户接口、移动首页和 64/64 schema 门禁通过,但生产后端模块、管理端和移动 H5 与本地候选仍不一致;用户隐私选择、权限反例、认证态核心业务和正式内容也未完成,故 Go/No-Go 为 **No-Go**。本次没有新增生产发布、服务重启、数据库或静态资源变更。
|
||||
- **2026-07-29,发布预检支持定向后端/schema 范围,未发布**:`release-preflight.sh` 不再把远端后端或 schema 核验强制绑定到管理端/H5 静态匹配;定向后端发布可只启用 `RELEASE_VERIFY_REMOTE_BACKEND=true RELEASE_VERIFY_REMOTE_SCHEMA=true`,且只要求本地后端 JAR 新鲜。完整包口径仍必须同时启用远端静态、后端和 schema 三项。新增范围回归脚本,校验缺少 `RELEASE_REMOTE_URL` 时失败关闭、定向模式不回退为静态耦合、完整模式命令仍保留;本项只修改本地工具与文档,没有连接或变更生产。
|
||||
- **2026-07-29,8 月 1 日 APK 生产接口兼容性只读核验,未发布**:线上与候选 `ruoyi-aihr` 均含 819 个非目录/非 Manifest 条目,0 个增删、775 个逐字节相同、44 个不同;短信登录、移动首页、查/练/问、直通车和 ASR/TTS 对应的 9 个关键控制器 class 哈希逐一相同。未登录 GET 探针得到公开首页业务码 `200`、5 个鉴权入口 `401`、13 个仅 POST 入口 `405`,没有发送业务 POST 或写入数据。44 个差异仅来自 `AihrModelSeedService`、`AihrPracticeSeedService`、`AihrSopSeedService` 及内部类,内容是禁止兜底文案冒充已创建/派发工单、阻止含占位内容或不完整五维评分的场景发布;因此现网接口壳可承接 APK,但候选业务语义红线仍需授权后定向发布后端。管理端/H5 与 schema 不属于该定向后端发布;本次没有执行生产同步、重启或数据库变更。
|
||||
- **2026-07-26,阿里云百炼 ASR 生产热修复**:`POST /api/ai/asr` 新增 `dashscope/qianwen + qwen3-asr-flash` 的 `/compatible-mode/v1/chat/completions` + Base64 `input_audio` 分支,原 SiliconFlow `/audio/transcriptions` multipart 路径继续兼容,ASR 超时放宽至 30 秒。定向 Maven 测试 10 项通过;后端 JAR(SHA-256 `46c99cff75d21f8536a71c3c058b6437e99dbf9ff4bae3542b44471f3f34ad84`)已发布,生产 ASR 已切换至 `dashscope/qwen3-asr-flash`,旧 `siliconflow/TeleAI/TeleSpeechASR` 已停用。生产接口获得正确转写与受控 `ossId`,Android HBuilderX 标准基座“按住说话”请求成功,线上日志无 403/ASR 异常。回滚目录为 `/opt/wygj/backups/asr-hotfix-20260726210122`;本次未发布静态资源、未变更 schema,也不代表完整包一致性或原生 App 全项验收。
|
||||
- **2026-07-26,移动端编译链升级与 uni-ui 解析修复,未发布**:本机 HBuilderX GUI 已升级为 `5.22.2026071707`,CLI 为 `5.22.2026072503-alpha`;`mobile-uni` 的 App、App-Plus、H5 与 Vite 编译包统一到对应的 `3.0.0-alpha-5020220260725001` 批次,消除 5.15/5.21 混用产生的两套 `uni-cli-shared` 状态隔离,使登录页 `<uni-forms>`、`<uni-easyinput>` 等组件重新由 `pages.json` easycom 规则正常解析。H5 与 App 均以 `Compiler version: 5.22(vue3)` 构建通过;浏览器和 vivo V2443A(Android 15)真机均确认手机号、验证码标签及输入框正常。HBuilder 5.22 标准基座 `versionName=15.22` 已自动更新并成功运行,测试手机号配合固定验证码登录成功,可进入员工首页并切换“问”页,未出现编译器/SDK 版本不匹配弹窗或组件解析告警。升级后仍须重建并安装 5.22 自定义运行基座完成原生权限全清单;`verify:app-release` 当前因缺少含正式法务 HTTPS 链接的 `androidPrivacy.json` 按预期阻断。本项未发布生产 H5,也未云打包 APK/IPA,手机上的 `com.yincheng.wygj` 正式包未被覆盖。
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
## 发布前置条件
|
||||
|
||||
1. 确认发布窗口、DB 备份和回滚负责人;迁移脚本包含 `ALTER TABLE`,不能在无备份状态执行。
|
||||
2. 发布前先执行 `scripts/release-preflight.sh` 的本地只读检查。完整包发布后,必须按 `docs/DEV_SETUP.md` 使用 `RELEASE_VERIFY_REMOTE_MATCH=true RELEASE_VERIFY_REMOTE_BACKEND=true RELEASE_VERIFY_REMOTE_SCHEMA=true ./scripts/release-preflight.sh` 核对线上静态资源、AIHR 模块和必需表;schema 检查只读、不执行迁移。只校验 H5 资源不能替代完整包复核。
|
||||
2. 发布前先执行 `scripts/release-preflight.sh` 的本地只读检查。定向后端发布后使用 `RELEASE_VERIFY_REMOTE_BACKEND=true RELEASE_VERIFY_REMOTE_SCHEMA=true ./scripts/release-preflight.sh`,只核对 AIHR 模块、必需表和生产运行时 schema 保护,不因本轮未发布的静态资源产生假失败;该结果必须明确记录为“后端定向发布”。完整包发布后,必须按 `docs/DEV_SETUP.md` 再启用 `RELEASE_VERIFY_REMOTE_MATCH=true`,同时核对线上静态资源、AIHR 模块和必需表;schema 检查只读、不执行迁移。定向后端或单项 H5 校验不能替代完整包复核。
|
||||
3. 确认迁移目标库、租户和字符集为 MySQL 8 / `utf8mb4`;脚本只允许在目标业务库执行。
|
||||
4. 生产组织同步凭据、AI 供应商留存/费用口径和正式试点窗口仍未闭合时,不要把迁移完成宣称为 BRD 验收完成。
|
||||
|
||||
|
||||
+3
-3
@@ -308,13 +308,13 @@ 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;比较 ruoyi-aihr 模块内容,避免 ZIP 打包时间戳造成误报
|
||||
RELEASE_REMOTE_URL=https://peilian.njzhmj.top RELEASE_VERIFY_REMOTE_MATCH=true RELEASE_VERIFY_REMOTE_BACKEND=true ./scripts/release-preflight.sh
|
||||
# 定向后端发布后只核对后端与 schema,不要求本轮未发布的管理端/H5 与本地一致
|
||||
RELEASE_REMOTE_URL=https://peilian.njzhmj.top RELEASE_VERIFY_REMOTE_BACKEND=true RELEASE_VERIFY_REMOTE_SCHEMA=true ./scripts/release-preflight.sh
|
||||
# 发布后同时核对线上静态资源、后端包和必需表(schema 检查为只读)
|
||||
RELEASE_REMOTE_URL=https://peilian.njzhmj.top RELEASE_VERIFY_REMOTE_MATCH=true RELEASE_VERIFY_REMOTE_BACKEND=true RELEASE_VERIFY_REMOTE_SCHEMA=true ./scripts/release-preflight.sh
|
||||
```
|
||||
|
||||
`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` 覆盖;路径必须是安全的绝对路径。开启远端后端或 schema 核验时,预检还会只读检查 `wygj-aihr.service` 及其 `EnvironmentFile` 的有效配置,`AIHR_PRACTICE_RUNTIME_SCHEMA_BOOTSTRAP` 必须为 `false` 或未设置;无法读取引用的环境文件会失败关闭,而不会把本机环境变量当作线上证据。
|
||||
三个远端核验开关可以按实际发布单元独立组合,但只要启用任一开关就必须提供 `RELEASE_REMOTE_URL`。定向模式只要求对应的本地产物保持新鲜:静态核验要求管理端/H5,后端核验要求后端 JAR,单独 schema 核验不要求无关构建产物。`RELEASE_VERIFY_REMOTE_BACKEND=true` 会打印整包 jar SHA-256 和 `ruoyi-aihr` 模块内容 SHA-256,实际匹配以模块内容 hash 为准;它不再隐式要求管理端/H5 静态资源匹配。只有同时启用 `RELEASE_VERIFY_REMOTE_MATCH=true`、`RELEASE_VERIFY_REMOTE_BACKEND=true` 和 `RELEASE_VERIFY_REMOTE_SCHEMA=true`,才能称为完整包匹配。远端后端默认核对 `/opt/wygj/app/ruoyi-admin.jar`,如发布路径不同可通过 `RELEASE_REMOTE_BACKEND_PATH` 覆盖;路径必须是安全的绝对路径。开启远端后端或 schema 核验时,预检还会只读检查 `wygj-aihr.service` 及其 `EnvironmentFile` 的有效配置,`AIHR_PRACTICE_RUNTIME_SCHEMA_BOOTSTRAP` 必须为 `false` 或未设置;无法读取引用的环境文件会失败关闭,而不会把本机环境变量当作线上证据。
|
||||
|
||||
带 `RELEASE_REMOTE_URL` 时,预检同时校验租户接口 JSON 的业务 `code=200`;HTTP 200 但业务返回 401/405 会判定失败。
|
||||
|
||||
|
||||
+118
-79
@@ -43,34 +43,50 @@ normalized_jar_content_sha256() {
|
||||
rm -f "$manifest_path"
|
||||
}
|
||||
|
||||
verify_remote_match="${RELEASE_VERIFY_REMOTE_MATCH:-false}"
|
||||
verify_remote_backend="${RELEASE_VERIFY_REMOTE_BACKEND:-false}"
|
||||
verify_remote_schema="${RELEASE_VERIFY_REMOTE_SCHEMA:-false}"
|
||||
remote_verification_requested="false"
|
||||
if [[ "$verify_remote_match" == "true" || "$verify_remote_backend" == "true" || "$verify_remote_schema" == "true" ]]; then
|
||||
remote_verification_requested="true"
|
||||
fi
|
||||
if [[ "$remote_verification_requested" == "true" && -z "${RELEASE_REMOTE_URL:-}" ]]; then
|
||||
fail "remote verification flags require RELEASE_REMOTE_URL"
|
||||
fi
|
||||
|
||||
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)"
|
||||
[[ -n "$frontend_asset" ]] || fail "frontend index does not reference a JavaScript entry"
|
||||
[[ -n "$mobile_asset" ]] || fail "mobile H5 index does not reference a JavaScript entry"
|
||||
|
||||
frontend_asset_path="frontend/dist/${frontend_asset#/}"
|
||||
mobile_asset_rel="${mobile_asset#/}"
|
||||
mobile_asset_rel="${mobile_asset_rel#h5/}"
|
||||
mobile_asset_path="mobile-uni/dist/build/h5/$mobile_asset_rel"
|
||||
require_file "$frontend_asset_path"
|
||||
require_file "$mobile_asset_path"
|
||||
grep -q '/h5/' "$mobile_index" || fail "mobile H5 index does not contain the /h5/ base path"
|
||||
|
||||
if [[ "${RELEASE_VERIFY_REMOTE_BACKEND:-false}" == "true" && "${RELEASE_VERIFY_REMOTE_MATCH:-false}" != "true" ]]; then
|
||||
fail "RELEASE_VERIFY_REMOTE_BACKEND=true requires RELEASE_VERIFY_REMOTE_MATCH=true"
|
||||
require_static_artifacts="true"
|
||||
require_backend_artifact="true"
|
||||
if [[ "$remote_verification_requested" == "true" ]]; then
|
||||
require_static_artifacts="$verify_remote_match"
|
||||
require_backend_artifact="$verify_remote_backend"
|
||||
fi
|
||||
if [[ "${RELEASE_VERIFY_REMOTE_SCHEMA:-false}" == "true" && "${RELEASE_VERIFY_REMOTE_MATCH:-false}" != "true" ]]; then
|
||||
fail "RELEASE_VERIFY_REMOTE_SCHEMA=true requires RELEASE_VERIFY_REMOTE_MATCH=true"
|
||||
|
||||
if [[ "$require_static_artifacts" == "true" ]]; then
|
||||
require_file "$frontend_index"
|
||||
require_file "$mobile_index"
|
||||
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)"
|
||||
[[ -n "$frontend_asset" ]] || fail "frontend index does not reference a JavaScript entry"
|
||||
[[ -n "$mobile_asset" ]] || fail "mobile H5 index does not reference a JavaScript entry"
|
||||
|
||||
frontend_asset_path="frontend/dist/${frontend_asset#/}"
|
||||
mobile_asset_rel="${mobile_asset#/}"
|
||||
mobile_asset_rel="${mobile_asset_rel#h5/}"
|
||||
mobile_asset_path="mobile-uni/dist/build/h5/$mobile_asset_rel"
|
||||
require_file "$frontend_asset_path"
|
||||
require_file "$mobile_asset_path"
|
||||
grep -q '/h5/' "$mobile_index" || fail "mobile H5 index does not contain the /h5/ base path"
|
||||
fi
|
||||
|
||||
if [[ "$require_backend_artifact" == "true" ]]; then
|
||||
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"
|
||||
fi
|
||||
|
||||
if [[ "${AIHR_PRACTICE_RUNTIME_SCHEMA_BOOTSTRAP:-false}" == "true" ]]; then
|
||||
fail "AIHR_PRACTICE_RUNTIME_SCHEMA_BOOTSTRAP must stay false for a release; apply the formal SQL migration instead"
|
||||
fi
|
||||
@@ -92,11 +108,15 @@ require_fresh_artifact() {
|
||||
[[ "$artifact_epoch" -ge "$head_epoch" ]] || fail "artifact is older than HEAD; rebuild before release: $artifact"
|
||||
}
|
||||
|
||||
require_fresh_artifact "$frontend_index"
|
||||
require_fresh_artifact "$frontend_asset_path"
|
||||
require_fresh_artifact "$mobile_index"
|
||||
require_fresh_artifact "$mobile_asset_path"
|
||||
require_fresh_artifact "$backend_jar"
|
||||
if [[ "$require_static_artifacts" == "true" ]]; then
|
||||
require_fresh_artifact "$frontend_index"
|
||||
require_fresh_artifact "$frontend_asset_path"
|
||||
require_fresh_artifact "$mobile_index"
|
||||
require_fresh_artifact "$mobile_asset_path"
|
||||
fi
|
||||
if [[ "$require_backend_artifact" == "true" ]]; then
|
||||
require_fresh_artifact "$backend_jar"
|
||||
fi
|
||||
|
||||
require_remote_business_success() {
|
||||
local label="$1"
|
||||
@@ -759,65 +779,28 @@ REMOTE
|
||||
echo "remote_personal_oss_configuration=true $remote_ssh:$remote_db"
|
||||
}
|
||||
|
||||
changed_files="$(git status --porcelain)"
|
||||
[[ -z "$changed_files" ]] || fail "worktree has uncommitted changes; commit the release batch before publishing"
|
||||
|
||||
echo "commit=$(git rev-parse HEAD)"
|
||||
echo "head_epoch=$head_epoch"
|
||||
echo "worktree=clean"
|
||||
echo "frontend_index_sha256=$(sha256 "$frontend_index")"
|
||||
echo "frontend_asset=$frontend_asset"
|
||||
echo "frontend_asset_sha256=$(sha256 "$frontend_asset_path")"
|
||||
echo "mobile_index_sha256=$(sha256 "$mobile_index")"
|
||||
echo "mobile_asset=$mobile_asset"
|
||||
echo "mobile_asset_sha256=$(sha256 "$mobile_asset_path")"
|
||||
echo "backend_jar_sha256=$(sha256 "$backend_jar")"
|
||||
|
||||
if [[ -n "${RELEASE_REMOTE_URL:-}" ]]; then
|
||||
remote="${RELEASE_REMOTE_URL%/}"
|
||||
curl -fsS --max-time 15 "$remote/" >/dev/null || fail "remote root check failed: $remote/"
|
||||
echo "remote_root=200 $remote/"
|
||||
require_remote_business_success "remote_tenant_list" "$remote/prod-api/auth/tenant/list"
|
||||
require_remote_business_success "remote_mobile_home" "$remote/prod-api/api/aihr/mobile/home/user"
|
||||
|
||||
if [[ "${RELEASE_VERIFY_REMOTE_SCHEMA:-false}" == "true" ]]; then
|
||||
require_remote_schema
|
||||
fi
|
||||
|
||||
if [[ "${RELEASE_VERIFY_REMOTE_SCHEMA:-false}" == "true" || "${RELEASE_VERIFY_REMOTE_BACKEND:-false}" == "true" ]]; then
|
||||
require_remote_practice_schema_guard
|
||||
fi
|
||||
|
||||
if [[ "${RELEASE_VERIFY_REMOTE_MATCH:-false}" == "true" ]]; then
|
||||
remote_frontend_asset="$(curl -fsS --max-time 15 "$remote/" | sed -nE 's/.*src="(\/assets\/[^" ]+\.js)".*/\1/p' | head -n 1)"
|
||||
remote_mobile_asset="$(curl -fsS --max-time 15 "$remote/h5/" | sed -nE 's/.*src="(\/h5\/assets\/[^" ]+\.js)".*/\1/p' | head -n 1)"
|
||||
[[ "$remote_frontend_asset" == /assets/*.js ]] || fail "remote frontend entry asset not found"
|
||||
[[ "$remote_mobile_asset" == /h5/assets/*.js ]] || fail "remote mobile H5 entry asset not found"
|
||||
|
||||
remote_frontend_sha256="$(curl -fsS --max-time 15 "$remote$remote_frontend_asset" | sha256_stream)"
|
||||
remote_mobile_sha256="$(curl -fsS --max-time 15 "$remote$remote_mobile_asset" | sha256_stream)"
|
||||
local_frontend_sha256="$(sha256 "$frontend_asset_path")"
|
||||
local_mobile_sha256="$(sha256 "$mobile_asset_path")"
|
||||
echo "remote_frontend_asset=$remote_frontend_asset"
|
||||
echo "remote_frontend_asset_sha256=$remote_frontend_sha256"
|
||||
echo "remote_mobile_asset=$remote_mobile_asset"
|
||||
echo "remote_mobile_asset_sha256=$remote_mobile_sha256"
|
||||
[[ "$remote_frontend_sha256" == "$local_frontend_sha256" ]] || fail "remote frontend asset does not match local build"
|
||||
[[ "$remote_mobile_sha256" == "$local_mobile_sha256" ]] || fail "remote mobile H5 asset does not match local build"
|
||||
echo "remote_asset_match=true"
|
||||
|
||||
if [[ "${RELEASE_VERIFY_REMOTE_BACKEND:-false}" == "true" ]]; then
|
||||
require_remote_backend_match() {
|
||||
command -v ssh >/dev/null 2>&1 || fail "ssh is required for remote backend verification"
|
||||
remote_ssh="${RELEASE_REMOTE_SSH:-YCWY}"
|
||||
remote_backend_path="${RELEASE_REMOTE_BACKEND_PATH:-/opt/wygj/app/ruoyi-admin.jar}"
|
||||
local remote_ssh="${RELEASE_REMOTE_SSH:-YCWY}"
|
||||
local 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_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"
|
||||
|
||||
local remote_backend_jar_sha256
|
||||
local local_backend_jar_sha256
|
||||
local local_backend_module_jar
|
||||
local local_backend_module_sha256
|
||||
local remote_backend_module_sha_file
|
||||
local remote_backend_module_sha256
|
||||
|
||||
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_sha_file="$(mktemp)"
|
||||
ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_backend_path" "$backend_module_jar_name" > "$remote_backend_module_sha_file" <<'REMOTE'
|
||||
set -euo pipefail
|
||||
@@ -842,6 +825,7 @@ REMOTE
|
||||
rm -f "$remote_backend_module_sha_file"
|
||||
[[ -n "$remote_backend_module_sha256" ]] || fail "remote backend module hash check returned no value: $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"
|
||||
@@ -849,7 +833,62 @@ REMOTE
|
||||
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"
|
||||
}
|
||||
|
||||
changed_files="$(git status --porcelain)"
|
||||
[[ -z "$changed_files" ]] || fail "worktree has uncommitted changes; commit the release batch before publishing"
|
||||
|
||||
echo "commit=$(git rev-parse HEAD)"
|
||||
echo "head_epoch=$head_epoch"
|
||||
echo "worktree=clean"
|
||||
if [[ "$require_static_artifacts" == "true" ]]; then
|
||||
echo "frontend_index_sha256=$(sha256 "$frontend_index")"
|
||||
echo "frontend_asset=$frontend_asset"
|
||||
echo "frontend_asset_sha256=$(sha256 "$frontend_asset_path")"
|
||||
echo "mobile_index_sha256=$(sha256 "$mobile_index")"
|
||||
echo "mobile_asset=$mobile_asset"
|
||||
echo "mobile_asset_sha256=$(sha256 "$mobile_asset_path")"
|
||||
fi
|
||||
if [[ "$require_backend_artifact" == "true" ]]; then
|
||||
echo "backend_jar_sha256=$(sha256 "$backend_jar")"
|
||||
fi
|
||||
|
||||
if [[ -n "${RELEASE_REMOTE_URL:-}" ]]; then
|
||||
remote="${RELEASE_REMOTE_URL%/}"
|
||||
curl -fsS --max-time 15 "$remote/" >/dev/null || fail "remote root check failed: $remote/"
|
||||
echo "remote_root=200 $remote/"
|
||||
require_remote_business_success "remote_tenant_list" "$remote/prod-api/auth/tenant/list"
|
||||
require_remote_business_success "remote_mobile_home" "$remote/prod-api/api/aihr/mobile/home/user"
|
||||
|
||||
if [[ "$verify_remote_schema" == "true" ]]; then
|
||||
require_remote_schema
|
||||
fi
|
||||
|
||||
if [[ "$verify_remote_schema" == "true" || "$verify_remote_backend" == "true" ]]; then
|
||||
require_remote_practice_schema_guard
|
||||
fi
|
||||
|
||||
if [[ "$verify_remote_backend" == "true" ]]; then
|
||||
require_remote_backend_match
|
||||
fi
|
||||
|
||||
if [[ "$verify_remote_match" == "true" ]]; then
|
||||
remote_frontend_asset="$(curl -fsS --max-time 15 "$remote/" | sed -nE 's/.*src="(\/assets\/[^" ]+\.js)".*/\1/p' | head -n 1)"
|
||||
remote_mobile_asset="$(curl -fsS --max-time 15 "$remote/h5/" | sed -nE 's/.*src="(\/h5\/assets\/[^" ]+\.js)".*/\1/p' | head -n 1)"
|
||||
[[ "$remote_frontend_asset" == /assets/*.js ]] || fail "remote frontend entry asset not found"
|
||||
[[ "$remote_mobile_asset" == /h5/assets/*.js ]] || fail "remote mobile H5 entry asset not found"
|
||||
|
||||
remote_frontend_sha256="$(curl -fsS --max-time 15 "$remote$remote_frontend_asset" | sha256_stream)"
|
||||
remote_mobile_sha256="$(curl -fsS --max-time 15 "$remote$remote_mobile_asset" | sha256_stream)"
|
||||
local_frontend_sha256="$(sha256 "$frontend_asset_path")"
|
||||
local_mobile_sha256="$(sha256 "$mobile_asset_path")"
|
||||
echo "remote_frontend_asset=$remote_frontend_asset"
|
||||
echo "remote_frontend_asset_sha256=$remote_frontend_sha256"
|
||||
echo "remote_mobile_asset=$remote_mobile_asset"
|
||||
echo "remote_mobile_asset_sha256=$remote_mobile_sha256"
|
||||
[[ "$remote_frontend_sha256" == "$local_frontend_sha256" ]] || fail "remote frontend asset does not match local build"
|
||||
[[ "$remote_mobile_sha256" == "$local_mobile_sha256" ]] || fail "remote mobile H5 asset does not match local build"
|
||||
echo "remote_asset_match=true"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
SCRIPT="$ROOT_DIR/scripts/release-preflight.sh"
|
||||
DEV_SETUP="$ROOT_DIR/docs/DEV_SETUP.md"
|
||||
|
||||
bash -n "$SCRIPT"
|
||||
|
||||
backend_without_url="$(
|
||||
RELEASE_VERIFY_REMOTE_BACKEND=true bash "$SCRIPT" 2>&1 || true
|
||||
)"
|
||||
grep -Fq 'remote verification flags require RELEASE_REMOTE_URL' <<<"$backend_without_url"
|
||||
|
||||
schema_without_url="$(
|
||||
RELEASE_VERIFY_REMOTE_SCHEMA=true bash "$SCRIPT" 2>&1 || true
|
||||
)"
|
||||
grep -Fq 'remote verification flags require RELEASE_REMOTE_URL' <<<"$schema_without_url"
|
||||
|
||||
if grep -Fq 'RELEASE_VERIFY_REMOTE_BACKEND=true requires RELEASE_VERIFY_REMOTE_MATCH=true' "$SCRIPT"; then
|
||||
echo 'FAIL: backend-only verification is still coupled to static resource matching' >&2
|
||||
exit 1
|
||||
fi
|
||||
if grep -Fq 'RELEASE_VERIFY_REMOTE_SCHEMA=true requires RELEASE_VERIFY_REMOTE_MATCH=true' "$SCRIPT"; then
|
||||
echo 'FAIL: schema-only verification is still coupled to static resource matching' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
backend_call_line="$(grep -n '^[[:space:]]*require_remote_backend_match$' "$SCRIPT" | cut -d: -f1)"
|
||||
static_scope_line="$(grep -n '^[[:space:]]*if \[\[ "\$verify_remote_match" == "true" \]\]; then$' "$SCRIPT" | cut -d: -f1)"
|
||||
[[ -n "$backend_call_line" && -n "$static_scope_line" && "$backend_call_line" -lt "$static_scope_line" ]]
|
||||
|
||||
grep -Fq 'require_static_artifacts="$verify_remote_match"' "$SCRIPT"
|
||||
grep -Fq 'require_backend_artifact="$verify_remote_backend"' "$SCRIPT"
|
||||
grep -Fq 'RELEASE_VERIFY_REMOTE_BACKEND=true RELEASE_VERIFY_REMOTE_SCHEMA=true ./scripts/release-preflight.sh' "$DEV_SETUP"
|
||||
grep -Fq 'RELEASE_VERIFY_REMOTE_MATCH=true RELEASE_VERIFY_REMOTE_BACKEND=true RELEASE_VERIFY_REMOTE_SCHEMA=true ./scripts/release-preflight.sh' "$DEV_SETUP"
|
||||
|
||||
echo 'PASS: release preflight supports scoped backend/schema verification and preserves the full-package mode'
|
||||
Reference in New Issue
Block a user