From cbb99616271102e25d8455d6054fa4cfc88863c3 Mon Sep 17 00:00:00 2001 From: jianghaifeng Date: Fri, 16 Oct 2026 13:17:29 +0800 Subject: [PATCH] =?UTF-8?q?fix(deploy):=20=E5=86=92=E7=83=9F=E8=84=9A?= =?UTF-8?q?=E6=9C=AC=E4=B8=8E=20prod=20Swagger=20=E5=BC=80=E5=85=B3?= =?UTF-8?q?=E8=A7=A3=E8=80=A6=EF=BC=8C=E9=BB=98=E8=AE=A4=E6=94=B9=E8=81=94?= =?UTF-8?q?=E8=B0=83=E5=8F=A3=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为什么:prod profile 下 SWAGGER_ENABLED 默认 false 会把 /doc.html 与 /v3/api-docs 一起关掉, 而 smoke.sh 硬期望这两者 200 —— 照文档默认配置部署完,冒烟必然假失败,会被误判成"部署挂了"; 且联调(10-19)需要 /doc.html 在线可查。 - smoke.sh 自适应:显式 SWAGGER_ENABLED 优先;未设置则自动探测(200+openapi=开、统一信封 code=404=关、其它响应仍 FAIL);关闭时这三项打印 SKIP 而不是 FAIL,开启时照旧断言 200(断言未删) - 新增 SWAGGER_REQUIRED 硬开关:联调环境可要求文档必须在线,不满足直接失败 - .env.example 默认改 SWAGGER_ENABLED=true,并写明该开关一次管两个端点 - DEPLOY.md 同步口径与冒烟说明 - 实测:prod+false 时 SKIP + 其余全 PASS + 退出码 0;prod+true 时两端口 /v3/api-docs 与 /doc.html 均 200; mvn test 83 条全过、两入口 package 通过 --- .env.example | 6 +++-- DEPLOY.md | 8 +++++-- tools/smoke.sh | 60 +++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 65 insertions(+), 9 deletions(-) diff --git a/.env.example b/.env.example index ff469b7..8b262ac 100644 --- a/.env.example +++ b/.env.example @@ -25,8 +25,10 @@ MEDIA_SIGNATURE_REQUIRED=true CORS_ALLOWED_ORIGINS=https://admin.example.com # 媒资根目录:容器里是挂载点 MEDIA_ROOT_DIR=/data/uploads -# 是否开放 Swagger UI(生产默认 false,只留 /v3/api-docs) -SWAGGER_ENABLED=false +# 是否开放在线接口文档(**/doc.html 与 /v3/api-docs 一起开关**,没有"只留一个"的中间态) +# true = 前端联调能看到 /doc.html(测试/联调环境建议 true,10-19 前端联调需要它) +# false = 两个都关闭(生产若不希望暴露 UI 就设 false,机器可读契约也一并关闭) +SWAGGER_ENABLED=true # ── 可选:Google Play 商店票据校验(不配则保持 disabled)── STORE_VERIFIER=disabled diff --git a/DEPLOY.md b/DEPLOY.md index b410eed..8e2f831 100644 --- a/DEPLOY.md +++ b/DEPLOY.md @@ -30,7 +30,7 @@ | `CORS_ALLOWED_ORIGINS` | 🔴 | prod 下为空则拒绝启动 | 逗号分隔的正式来源,**生产不许用 `*`** | | `MEDIA_ROOT_DIR` | 建议 | 默认 `./data/uploads`,容器里必须为 `/data/uploads` | 媒资根目录(挂载卷) | | `SERVER_PORT` | Compose 已设 | 默认 8081/8080 | app-api 8081 / admin-api 8080 | -| `SWAGGER_ENABLED` | 可选 | 默认 false | 生产是否开放 `/doc.html` 与 `/v3/api-docs` | +| `SWAGGER_ENABLED` | 可选 | 默认 false(**测试/联调请在 `.env` 里设 `true`**,前端要看 `/doc.html`) | **一次开关两个**:`true` = `/doc.html` + `/v3/api-docs` 都开;`false` = 两个都关(生产不想暴露 UI 就设 false,机器可读契约也一并关闭) | | `STORE_VERIFIER` / `GOOGLE_PLAY_*` | 可选 | 默认 `disabled` | 商店票据校验,没凭据就保持 disabled(对外返回"尚未配置") | **密钥怎么生成**:`openssl rand -base64 48`(每个密钥单独生成,别复用)。 @@ -124,9 +124,13 @@ exit code = **1**(部署脚本可以直接判失败)。**没有 prod profile 1. `docker compose ps`:三个应用容器 `Up (healthy)`(worker 是 `Up`)。 2. `tools/smoke.sh`:全 PASS、退出码 0。它检查 `/healthz`、`/app/categories`、`/app/home/feed`、 两个 `/v3/api-docs`、`/doc.html`,以及未授权路径是否按统一信封拒绝。 + **关于在线文档那三项**:脚本会按 `SWAGGER_ENABLED` 自适应 —— `true` 时断言 200; + `false` 时打印 **SKIP**(不算失败),因为 `/doc.html` 与 `/v3/api-docs` 都被配置关掉了。 + 没设这个变量时脚本自动探测("200 且含 openapi"算开,"统一信封 code=404"算关,其它响应→FAIL)。 + 联调环境想强制要求文档在线:`SWAGGER_REQUIRED=true tools/smoke.sh`(不满足直接失败)。 3. `curl -s http://127.0.0.1:8081/healthz`:(从容器里或宿主机)应返回 `{"code":0,...,"status":"UP"}`。 4. 业务抽查:`curl -s http://127.0.0.1:8081/app/home/feed | head -c 200`(有内容); - 后台 `http://127.0.0.1:8080/doc.html`(若 `SWAGGER_ENABLED=true`)。 + 后台 `http://127.0.0.1:8080/doc.html`(**需要 `.env` 里 `SWAGGER_ENABLED=true`**,联调环境请打开)。 5. 登录一个后台账号,确认能进后台首页(连通数据库与权限体系)。 ## 七、回滚 diff --git a/tools/smoke.sh b/tools/smoke.sh index 7d4fe0c..d4dc728 100755 --- a/tools/smoke.sh +++ b/tools/smoke.sh @@ -17,6 +17,37 @@ FAILED=0 pass() { printf ' \033[32mPASS\033[0m %s\n' "$1"; } fail() { printf ' \033[31mFAIL\033[0m %s\n' "$1"; FAILED=1; } +skip() { printf ' \033[33mSKIP\033[0m %s\n' "$1"; } + +# 在线接口文档是否开启: +# 显式 SWAGGER_ENABLED=true/false 时按它; +# 未设置则自动探测 —— 只有"200 且含 openapi"才算开启,"统一信封 code=404"才算关闭, +# 其它响应(500/挂住/异常)一律算 broken(由调用方判 FAIL),避免"探测不出来就跳过"掩盖问题。 +swagger_state() { + local explicit="${SWAGGER_ENABLED:-auto}" out code + case "$explicit" in + true) echo enabled; return ;; + false) echo disabled; return ;; + esac + code="$(http_code "$APP_BASE/v3/api-docs")" + out="$(body "$APP_BASE/v3/api-docs")" + if [ "$code" = "200" ] && printf '%s' "$out" | grep -q '"openapi"'; then + echo enabled + elif printf '%s' "$out" | grep -q '"code":404'; then + echo disabled + else + echo broken + fi +} + +# 关闭原因:区分"显式配置关闭"与"自动探测到未开启",避免 SKIP 文案误导 +swagger_disabled_reason() { + if [ "${SWAGGER_ENABLED:-auto}" = "false" ]; then + echo "SWAGGER_ENABLED=false,接口文档按配置关闭" + else + echo "自动探测:/v3/api-docs 返回 404 信封,说明接口文档未开启" + fi +} # http_code [跟随重定向]:只取状态码,连不上返回 000 http_code() { @@ -82,11 +113,30 @@ expect_code 200 "$APP_BASE/app/home/feed" "GET /app/home/feed" expect_body "$APP_BASE/app/categories" '"code":0' "GET /app/categories 返回 code=0" printf '\n[3/5] 接口文档在线\n' -expect_code 200 "$APP_BASE/v3/api-docs" "app-api /v3/api-docs" -expect_code 200 "$ADMIN_BASE/v3/api-docs" "admin-api /v3/api-docs" -expect_body "$APP_BASE/v3/api-docs" '"openapi"' "app-api /v3/api-docs 是 OpenAPI 文档" -# /doc.html 是 springdoc 的 302 跳转,跟随重定向后应落到 Swagger UI(200) -expect_code 200 "$APP_BASE/doc.html" "app-api /doc.html(跟随 302)" follow +case "$(swagger_state)" in + enabled) + # SWAGGER_ENABLED=true:/doc.html 与 /v3/api-docs 都必须在线(前端联调要看) + expect_code 200 "$APP_BASE/v3/api-docs" "app-api /v3/api-docs" + expect_code 200 "$ADMIN_BASE/v3/api-docs" "admin-api /v3/api-docs" + expect_body "$APP_BASE/v3/api-docs" '"openapi"' "app-api /v3/api-docs 是 OpenAPI 文档" + # /doc.html 是 springdoc 的 302 跳转,跟随重定向后应落到 Swagger UI(200) + expect_code 200 "$APP_BASE/doc.html" "app-api /doc.html(跟随 302)" follow + ;; + disabled) + # SWAGGER_ENABLED=false(prod 允许的配置):文档按配置关闭,不是故障 → SKIP,不能算失败 + skip "app-api /v3/api-docs —— $(swagger_disabled_reason)" + skip "admin-api /v3/api-docs —— 同上" + skip "app-api /doc.html —— 同上(联调环境请在 .env 里设 SWAGGER_ENABLED=true)" + ;; + *) + fail "接口文档状态异常:既不是 200 的 OpenAPI 文档,也不是【按配置关闭】的统一信封 —— 请人工确认" + ;; +esac + +if [ "${SWAGGER_REQUIRED:-false}" = "true" ] && [ "$(swagger_state)" != "enabled" ]; then + # 联调/演示环境可以强制要求文档在线:给流水线一个硬开关 + fail "SWAGGER_REQUIRED=true 但接口文档不可用(请检查 SWAGGER_ENABLED 与 springdoc 配置)" +fi printf '\n[4/5] 未授权路径按统一信封拒绝(不挂住)\n' expect_envelope "$APP_BASE/" "app-api /"