#!/usr/bin/env bash # Author: fruit.com.cn # # 发布后冒烟:确认两个入口"活着 + 文档在线 + 未授权路径按统一信封拒绝"。 # 任一检查失败 → 非 0 退出(可直接用在部署流水线里)。 # # 用法: # tools/smoke.sh # 默认 127.0.0.1:8081 / 127.0.0.1:8080 # APP_BASE=http://10.0.0.5:8081 ADMIN_BASE=http://10.0.0.5:8080 tools/smoke.sh set -u -o pipefail APP_BASE="${APP_BASE:-http://127.0.0.1:8081}" ADMIN_BASE="${ADMIN_BASE:-http://127.0.0.1:8080}" TIMEOUT="${SMOKE_TIMEOUT:-8}" FAILED=0 pass() { printf ' \033[32mPASS\033[0m %s\n' "$1"; } fail() { printf ' \033[31mFAIL\033[0m %s\n' "$1"; FAILED=1; } # http_code [跟随重定向]:只取状态码,连不上返回 000 http_code() { local follow="${2:-}" if [ "$follow" = "follow" ]; then curl -sL -o /dev/null -m "$TIMEOUT" -w '%{http_code}' "$1" 2>/dev/null || echo 000 else curl -s -o /dev/null -m "$TIMEOUT" -w '%{http_code}' "$1" 2>/dev/null || echo 000 fi } # body :取响应体(用于断言统一信封) body() { curl -s -m "$TIMEOUT" "$1" 2>/dev/null || true } # expect_code <期望> <说明> [follow] expect_code() { local want="$1" url="$2" label="$3" follow="${4:-}" got got="$(http_code "$url" "$follow")" if [ "$got" = "$want" ]; then pass "${label}(${got})" else fail "${label}:期望 ${want},实际 ${got} —— ${url}" fi } # expect_body <必须包含的片段> <说明> # 只看 HTTP 200 是不够的:本项目未授权时也是 HTTP 200 + 信封 code=401, # 所以健康检查/文档必须断言响应体的关键字段。 expect_body() { local url="$1" needle="$2" label="$3" out out="$(body "$url")" if printf '%s' "$out" | grep -q "$needle"; then pass "${label}" else fail "${label}:期望包含 ${needle},实际:$(printf '%s' "$out" | head -c 120)" fi } # expect_envelope <说明>:未授权路径必须返回统一响应体(code 字段),而不是挂住/空响应 expect_envelope() { local url="$1" label="$2" out out="$(body "$url")" if printf '%s' "$out" | grep -q '"code"'; then pass "${label}($(printf '%s' "$out" | head -c 80))" else fail "$label:未返回统一响应体 —— $url,实际:$(printf '%s' "$out" | head -c 120)" fi } printf '冒烟开始:APP=%s ADMIN=%s\n' "$APP_BASE" "$ADMIN_BASE" printf '\n[1/5] 健康检查 /healthz\n' expect_code 200 "$APP_BASE/healthz" "app-api /healthz 可访问" expect_body "$APP_BASE/healthz" '"status":"UP"' "app-api /healthz 返回 status=UP" expect_code 200 "$ADMIN_BASE/healthz" "admin-api /healthz 可访问" expect_body "$ADMIN_BASE/healthz" '"status":"UP"' "admin-api /healthz 返回 status=UP" printf '\n[2/5] 客户端目录接口\n' expect_code 200 "$APP_BASE/app/categories" "GET /app/categories" 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 printf '\n[4/5] 未授权路径按统一信封拒绝(不挂住)\n' expect_envelope "$APP_BASE/" "app-api /" expect_envelope "$ADMIN_BASE/admin/auth/profile" "admin-api 未带令牌访问受保护接口" printf '\n[5/5] 响应体完整性(/app/categories 必须是 {code,data})\n' expect_body "$APP_BASE/app/categories" '"data"' "GET /app/categories 带 data 字段" # 用错误的 HTTP 方法访问 POST-only 接口:只要是"统一信封"就算合格(当前实现返回 code=500 的系统异常信封) expect_envelope "$ADMIN_BASE/admin/auth/login" "admin-api 用 GET 访问 POST-only 接口仍返回统一信封" if [ "$FAILED" -eq 0 ]; then printf '\n\033[32m冒烟通过\033[0m:全部检查通过\n' exit 0 fi printf '\n\033[31m冒烟失败\033[0m:存在未通过项,请先看上面的 FAIL 行\n' exit 1