445 行
16 KiB
Bash
445 行
16 KiB
Bash
|
|
#!/usr/bin/env bash
|
|||
|
|
# verify.sh — 私有化部署一键验证脚本
|
|||
|
|
#
|
|||
|
|
# 用途:
|
|||
|
|
# 部署完成后,全面验证所有服务是否正常运行,输出通过/失败清单。
|
|||
|
|
# 可独立运行,也可作为 deploy-szyx.sh 和 migrate-tenant.sh 的最终步骤。
|
|||
|
|
#
|
|||
|
|
# 使用方法:
|
|||
|
|
# # 从部署目录读取配置自动推断
|
|||
|
|
# ./scripts/verify.sh
|
|||
|
|
#
|
|||
|
|
# # 指定部署地址(可覆盖 .env 中的值)
|
|||
|
|
# DEPLOY_HOST=192.168.1.100 ./scripts/verify.sh
|
|||
|
|
#
|
|||
|
|
# # 指定完整 URL
|
|||
|
|
# BASE_URL=http://192.168.1.100 ./scripts/verify.sh
|
|||
|
|
#
|
|||
|
|
# 验证项目:
|
|||
|
|
# 1. Docker 容器运行状态
|
|||
|
|
# 2. 数据库和 Redis 连通性
|
|||
|
|
# 3. tenant-service 健康(actuator/health)
|
|||
|
|
# 4. PRIVATE 模式激活
|
|||
|
|
# 5. 注册接口已阻断
|
|||
|
|
# 6. 前端页面可访问
|
|||
|
|
# 7. SDK 配置接口
|
|||
|
|
# 8. IM 服务(如已启动)
|
|||
|
|
# 9. 版本管理服务(如已启动)
|
|||
|
|
# 10. 文档站(如已启动)
|
|||
|
|
# 11. 租户登录和中文字符集
|
|||
|
|
#
|
|||
|
|
# 退出码:
|
|||
|
|
# 0 = 全部通过
|
|||
|
|
# 1 = 有项目失败
|
|||
|
|
|
|||
|
|
set -euo pipefail
|
|||
|
|
|
|||
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|||
|
|
cd "$ROOT_DIR"
|
|||
|
|
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
# 配置读取
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
# 优先使用环境变量,否则从 .env 读取
|
|||
|
|
if [ -f "$ROOT_DIR/.env" ]; then
|
|||
|
|
# shellcheck disable=SC1090
|
|||
|
|
set -a; . "$ROOT_DIR/.env" 2>/dev/null; set +a
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
DEPLOY_HOST="${DEPLOY_HOST:-127.0.0.1}"
|
|||
|
|
BASE_URL="${BASE_URL:-http://${DEPLOY_HOST}}"
|
|||
|
|
# 去掉末尾斜杠
|
|||
|
|
BASE_URL="${BASE_URL%/}"
|
|||
|
|
|
|||
|
|
MYSQL_PASSWORD="${MYSQL_PASSWORD:-}"
|
|||
|
|
REDIS_PASSWORD="${REDIS_PASSWORD:-}"
|
|||
|
|
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
# 颜色和工具函数
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
RED='\033[1;31m'; GREEN='\033[1;32m'; YELLOW='\033[1;33m'
|
|||
|
|
CYAN='\033[1;36m'; BOLD='\033[1m'; RESET='\033[0m'
|
|||
|
|
|
|||
|
|
PASS=0; FAIL=0; WARN=0
|
|||
|
|
RESULTS=() # 收集结果用于最终报告
|
|||
|
|
|
|||
|
|
pass() {
|
|||
|
|
local msg="$1"
|
|||
|
|
printf " ${GREEN}✓${RESET} PASS %s\n" "$msg"
|
|||
|
|
PASS=$((PASS+1))
|
|||
|
|
RESULTS+=("PASS|$msg")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fail() {
|
|||
|
|
local msg="$1" detail="${2:-}"
|
|||
|
|
printf " ${RED}✗${RESET} FAIL %s" "$msg"
|
|||
|
|
[ -n "$detail" ] && printf " ${YELLOW}(%s)${RESET}" "$detail"
|
|||
|
|
printf "\n"
|
|||
|
|
FAIL=$((FAIL+1))
|
|||
|
|
RESULTS+=("FAIL|$msg")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
warn() {
|
|||
|
|
local msg="$1" detail="${2:-}"
|
|||
|
|
printf " ${YELLOW}⚠${RESET} WARN %s" "$msg"
|
|||
|
|
[ -n "$detail" ] && printf " (%s)" "$detail"
|
|||
|
|
printf "\n"
|
|||
|
|
WARN=$((WARN+1))
|
|||
|
|
RESULTS+=("WARN|$msg")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
section() {
|
|||
|
|
printf "\n${CYAN}── %s ${RESET}\n" "$1"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# HTTP 请求(自动跳过代理,直连内网)
|
|||
|
|
http_get() {
|
|||
|
|
local url="$1" timeout="${2:-8}"
|
|||
|
|
curl -sL --noproxy '*' -o /dev/null -w '%{http_code}' --max-time "$timeout" "$url" 2>/dev/null || echo 000
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
http_body() {
|
|||
|
|
local url="$1" timeout="${2:-8}"
|
|||
|
|
curl -sL --noproxy '*' --max-time "$timeout" "$url" 2>/dev/null || true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
http_body_auth() {
|
|||
|
|
local url="$1" token="$2" timeout="${3:-8}"
|
|||
|
|
curl -sL --noproxy '*' --max-time "$timeout" \
|
|||
|
|
-H "Authorization: Bearer $token" "$url" 2>/dev/null || true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
http_code_auth() {
|
|||
|
|
local url="$1" token="$2" timeout="${3:-8}"
|
|||
|
|
curl -sL --noproxy '*' -o /dev/null -w '%{http_code}' --max-time "$timeout" \
|
|||
|
|
-H "Authorization: Bearer $token" "$url" 2>/dev/null || echo 000
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
# 开始验证
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
printf "\n${BOLD}════════════════════════════════════════════════════${RESET}\n"
|
|||
|
|
printf "${BOLD} XuqmGroup 私有化部署验证${RESET}\n"
|
|||
|
|
printf "${BOLD}════════════════════════════════════════════════════${RESET}\n"
|
|||
|
|
printf " 部署地址: ${BOLD}%s${RESET}\n" "$BASE_URL"
|
|||
|
|
printf " 部署目录: %s\n" "$ROOT_DIR"
|
|||
|
|
printf " 验证时间: %s\n" "$(date '+%Y-%m-%d %H:%M:%S')"
|
|||
|
|
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
# 1. Docker 容器状态
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
section "1. 容器状态"
|
|||
|
|
|
|||
|
|
REQUIRED_CONTAINERS="tenant-service nginx tenant-web ops-web"
|
|||
|
|
for CTR_SUFFIX in $REQUIRED_CONTAINERS; do
|
|||
|
|
CTR_NAME=$(docker ps --filter "name=$CTR_SUFFIX" --filter "status=running" --format '{{.Names}}' 2>/dev/null | head -1)
|
|||
|
|
if [ -n "$CTR_NAME" ]; then
|
|||
|
|
pass "容器运行中: $CTR_NAME"
|
|||
|
|
else
|
|||
|
|
fail "容器未运行: *-${CTR_SUFFIX}-*"
|
|||
|
|
fi
|
|||
|
|
done
|
|||
|
|
|
|||
|
|
OPTIONAL_CONTAINERS="file-service im-service push-service update-service license-service docs-site mysql redis"
|
|||
|
|
for CTR_SUFFIX in $OPTIONAL_CONTAINERS; do
|
|||
|
|
CTR_NAME=$(docker ps --filter "name=$CTR_SUFFIX" --filter "status=running" --format '{{.Names}}' 2>/dev/null | head -1)
|
|||
|
|
if [ -n "$CTR_NAME" ]; then
|
|||
|
|
pass "容器运行中: $CTR_NAME"
|
|||
|
|
else
|
|||
|
|
warn "容器未运行(可选): *-${CTR_SUFFIX}-*"
|
|||
|
|
fi
|
|||
|
|
done
|
|||
|
|
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
# 2. 数据库和 Redis
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
section "2. 数据库和缓存"
|
|||
|
|
|
|||
|
|
# MySQL
|
|||
|
|
MYSQL_CTR=$(docker ps --filter "name=mysql" --filter "status=running" --format '{{.Names}}' 2>/dev/null | head -1)
|
|||
|
|
if [ -n "$MYSQL_CTR" ]; then
|
|||
|
|
if docker exec "$MYSQL_CTR" mysqladmin -u root -p"${MYSQL_ROOT_PASSWORD:-}" ping --silent 2>/dev/null; then
|
|||
|
|
pass "MySQL 响应 PONG"
|
|||
|
|
else
|
|||
|
|
fail "MySQL ping 失败"
|
|||
|
|
fi
|
|||
|
|
else
|
|||
|
|
warn "MySQL 容器未运行(external 模式跳过此项)"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# Redis
|
|||
|
|
REDIS_CTR=$(docker ps --filter "name=redis" --filter "status=running" --format '{{.Names}}' 2>/dev/null | head -1)
|
|||
|
|
if [ -n "$REDIS_CTR" ]; then
|
|||
|
|
PING=$(docker exec "$REDIS_CTR" redis-cli -a "${REDIS_PASSWORD:-}" --no-auth-warning PING 2>/dev/null | tr -d '\r')
|
|||
|
|
if [ "$PING" = "PONG" ]; then
|
|||
|
|
pass "Redis 响应 PONG"
|
|||
|
|
else
|
|||
|
|
fail "Redis ping 失败 (got: $PING)"
|
|||
|
|
fi
|
|||
|
|
else
|
|||
|
|
warn "Redis 容器未运行(external 模式跳过此项)"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
# 3. 核心服务健康
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
section "3. 核心服务"
|
|||
|
|
|
|||
|
|
# actuator/health
|
|||
|
|
HEALTH_BODY=$(http_body "$BASE_URL/actuator/health")
|
|||
|
|
HEALTH_STATUS=$(echo "$HEALTH_BODY" | grep -o '"status":"[^"]*"' | head -1)
|
|||
|
|
if [ "$HEALTH_STATUS" = '"status":"UP"' ]; then
|
|||
|
|
pass "tenant-service actuator/health = UP"
|
|||
|
|
else
|
|||
|
|
fail "tenant-service 未就绪" "$(echo "$HEALTH_BODY" | head -c 100)"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# 私有化模式
|
|||
|
|
DEPLOY_STATUS=$(http_body "$BASE_URL/api/private/deployment/status")
|
|||
|
|
if echo "$DEPLOY_STATUS" | grep -q '"mode":"PRIVATE"'; then
|
|||
|
|
pass "PRIVATE 模式已激活"
|
|||
|
|
else
|
|||
|
|
fail "PRIVATE 模式未激活" "$(echo "$DEPLOY_STATUS" | head -c 100)"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
if echo "$DEPLOY_STATUS" | grep -q '"tenantRegisterEnabled":false'; then
|
|||
|
|
pass "租户注册已禁用"
|
|||
|
|
else
|
|||
|
|
fail "租户注册未禁用(安全风险)"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# 注册接口阻断
|
|||
|
|
REG_CODE=$(curl -sL --noproxy '*' -o /dev/null -w '%{http_code}' --max-time 8 \
|
|||
|
|
-X POST "$BASE_URL/api/auth/register" \
|
|||
|
|
-H 'Content-Type: application/json' \
|
|||
|
|
-d '{"email":"verify-test@block.test","username":"verifytest","password":"Test@123456"}' 2>/dev/null || echo 000)
|
|||
|
|
if [ "$REG_CODE" != "200" ] && [ "$REG_CODE" != "000" ]; then
|
|||
|
|
pass "注册接口已阻断 (HTTP $REG_CODE)"
|
|||
|
|
else
|
|||
|
|
fail "注册接口未阻断 (HTTP $REG_CODE)"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
# 4. 前端页面
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
section "4. 前端页面"
|
|||
|
|
|
|||
|
|
WEB_CODE=$(http_get "$BASE_URL/")
|
|||
|
|
if [ "$WEB_CODE" = "200" ]; then
|
|||
|
|
pass "控制台前端可访问 (HTTP 200)"
|
|||
|
|
else
|
|||
|
|
fail "控制台前端不可访问 (HTTP $WEB_CODE)"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
OPS_CODE=$(http_get "$BASE_URL/ops")
|
|||
|
|
if echo "$OPS_CODE" | grep -qE '^(200|301|302)$'; then
|
|||
|
|
pass "运营后台可访问 (HTTP $OPS_CODE)"
|
|||
|
|
else
|
|||
|
|
fail "运营后台不可访问 (HTTP $OPS_CODE)"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
DOCS_CODE=$(http_get "$BASE_URL/docs/")
|
|||
|
|
if echo "$DOCS_CODE" | grep -qE '^(200|301|302)$'; then
|
|||
|
|
pass "文档站可访问 (HTTP $DOCS_CODE)"
|
|||
|
|
else
|
|||
|
|
warn "文档站不可访问 (HTTP $DOCS_CODE)(可选服务)"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
# 5. 租户登录和中文字符集
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
section "5. 租户登录"
|
|||
|
|
|
|||
|
|
# 获取验证码
|
|||
|
|
CAPTCHA_RESP=$(http_body "$BASE_URL/api/auth/captcha")
|
|||
|
|
CAPTCHA_KEY=$(echo "$CAPTCHA_RESP" | grep -o '"key":"[^"]*"' | head -1 | cut -d'"' -f4)
|
|||
|
|
|
|||
|
|
LOGIN_OK=false
|
|||
|
|
TOKEN=""
|
|||
|
|
NICK=""
|
|||
|
|
|
|||
|
|
if [ -n "$CAPTCHA_KEY" ]; then
|
|||
|
|
# 从 Redis 读取验证码
|
|||
|
|
REDIS_CTR=$(docker ps --filter "name=redis" --filter "status=running" --format '{{.Names}}' 2>/dev/null | head -1)
|
|||
|
|
if [ -n "$REDIS_CTR" ]; then
|
|||
|
|
CAPTCHA_CODE=$(docker exec "$REDIS_CTR" redis-cli -a "${REDIS_PASSWORD:-}" --no-auth-warning \
|
|||
|
|
GET "captcha:${CAPTCHA_KEY}" 2>/dev/null | tr -d '\r')
|
|||
|
|
|
|||
|
|
if [ -n "$CAPTCHA_CODE" ]; then
|
|||
|
|
# 尝试登录(用 bootstrap 邮箱)
|
|||
|
|
BOOTSTRAP_EMAIL="${TENANT_BOOTSTRAP_EMAIL:-admin@company.com}"
|
|||
|
|
BOOTSTRAP_PASS="${TENANT_BOOTSTRAP_PASSWORD:-}"
|
|||
|
|
|
|||
|
|
LOGIN_RESP=$(curl -sL --noproxy '*' --max-time 10 \
|
|||
|
|
-X POST "$BASE_URL/api/auth/login" \
|
|||
|
|
-H 'Content-Type: application/json' \
|
|||
|
|
-d "{\"account\":\"${BOOTSTRAP_EMAIL}\",\"password\":\"${BOOTSTRAP_PASS}\",\"captchaKey\":\"${CAPTCHA_KEY}\",\"captchaCode\":\"${CAPTCHA_CODE}\"}" 2>/dev/null || true)
|
|||
|
|
|
|||
|
|
LOGIN_CODE=$(echo "$LOGIN_RESP" | grep -o '"code":[0-9]*' | head -1 | grep -o '[0-9]*')
|
|||
|
|
if [ "$LOGIN_CODE" = "200" ]; then
|
|||
|
|
TOKEN=$(echo "$LOGIN_RESP" | grep -o '"token":"[^"]*"' | cut -d'"' -f4)
|
|||
|
|
LOGIN_OK=true
|
|||
|
|
pass "租户登录成功"
|
|||
|
|
elif [ "$BOOTSTRAP_PASS" = "change-me-on-first-login" ] || [ -z "$BOOTSTRAP_PASS" ]; then
|
|||
|
|
# bootstrap 密码是占位符,说明已迁移生产数据,用生产密码登录,此处跳过
|
|||
|
|
warn "租户登录跳过(bootstrap 密码为占位符,迁移后请用生产密码验证)"
|
|||
|
|
|
|||
|
|
# 解码 JWT 检查中文字符集
|
|||
|
|
if command -v python3 >/dev/null 2>&1 && [ -n "$TOKEN" ]; then
|
|||
|
|
NICK=$(python3 -c "
|
|||
|
|
import base64, json, sys
|
|||
|
|
try:
|
|||
|
|
payload = '${TOKEN}'.split('.')[1]
|
|||
|
|
payload += '=' * (4 - len(payload) % 4)
|
|||
|
|
d = json.loads(base64.urlsafe_b64decode(payload).decode('utf-8'))
|
|||
|
|
print(d.get('nickname',''))
|
|||
|
|
except:
|
|||
|
|
print('')
|
|||
|
|
" 2>/dev/null)
|
|||
|
|
if [ -n "$NICK" ] && echo "$NICK" | python3 -c "import sys; s=sys.stdin.read().strip(); sys.exit(0 if s == s.encode('utf-8').decode('utf-8') and any(ord(c) > 0x7F for c in s) else 0)" 2>/dev/null; then
|
|||
|
|
pass "JWT 中文字段正常: $NICK"
|
|||
|
|
elif [ -n "$NICK" ]; then
|
|||
|
|
pass "JWT 昵称字段: $NICK"
|
|||
|
|
else
|
|||
|
|
warn "JWT 昵称字段为空(bootstrap 租户可能未迁移)"
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
else
|
|||
|
|
fail "租户登录失败" "code=$LOGIN_CODE (resp: $(echo "$LOGIN_RESP" | head -c 80))"
|
|||
|
|
fi
|
|||
|
|
else
|
|||
|
|
warn "无法从 Redis 读取验证码(跳过登录测试)"
|
|||
|
|
fi
|
|||
|
|
else
|
|||
|
|
warn "Redis 容器未运行(跳过登录测试)"
|
|||
|
|
fi
|
|||
|
|
else
|
|||
|
|
fail "无法获取验证码(captcha API 异常)"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
# 6. SDK 配置接口
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
section "6. SDK 配置"
|
|||
|
|
|
|||
|
|
if [ -f "$ROOT_DIR/config/sdk/xuqm-private-sdk.json" ]; then
|
|||
|
|
DEFAULT_APP_KEY=$(python3 -c "
|
|||
|
|
import json
|
|||
|
|
try:
|
|||
|
|
d = json.load(open('$ROOT_DIR/config/sdk/xuqm-private-sdk.json'))
|
|||
|
|
print(d.get('appKey',''))
|
|||
|
|
except:
|
|||
|
|
print('')
|
|||
|
|
" 2>/dev/null)
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# 从数据库查询已有 App
|
|||
|
|
MYSQL_CTR=$(docker ps --filter "name=mysql" --filter "status=running" --format '{{.Names}}' 2>/dev/null | head -1)
|
|||
|
|
APP_KEYS=""
|
|||
|
|
if [ -n "$MYSQL_CTR" ]; then
|
|||
|
|
APP_KEYS=$(docker exec "$MYSQL_CTR" sh -c \
|
|||
|
|
"mysql -u${MYSQL_USERNAME:-xuqm} -p${MYSQL_PASSWORD:-} ${MYSQL_DATABASE:-xuqm_private} -N \
|
|||
|
|
-e 'SELECT app_key FROM t_app LIMIT 5;'" 2>/dev/null | tr '\n' ' ')
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
if [ -z "$APP_KEYS" ] && [ -n "${DEFAULT_APP_KEY:-}" ]; then
|
|||
|
|
APP_KEYS="$DEFAULT_APP_KEY"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
if [ -n "$APP_KEYS" ]; then
|
|||
|
|
for APP_KEY in $APP_KEYS; do
|
|||
|
|
SDK_CODE=$(http_get "$BASE_URL/api/sdk/config?appKey=${APP_KEY}&platform=ANDROID")
|
|||
|
|
SDK_BODY=$(http_body "$BASE_URL/api/sdk/config?appKey=${APP_KEY}&platform=ANDROID")
|
|||
|
|
if [ "$SDK_CODE" = "200" ] && echo "$SDK_BODY" | grep -q '"code":200'; then
|
|||
|
|
pass "SDK config: $APP_KEY"
|
|||
|
|
else
|
|||
|
|
fail "SDK config: $APP_KEY (HTTP $SDK_CODE)"
|
|||
|
|
fi
|
|||
|
|
done
|
|||
|
|
else
|
|||
|
|
warn "未找到 App Key(数据库可能为空或尚未迁移)"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
# 7. 可选服务
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
section "7. 可选服务(需认证,401 = 正常)"
|
|||
|
|
|
|||
|
|
check_optional_service() {
|
|||
|
|
local label="$1" url="$2" token="$3"
|
|||
|
|
local code
|
|||
|
|
if [ -n "$token" ]; then
|
|||
|
|
code=$(http_code_auth "$url" "$token")
|
|||
|
|
else
|
|||
|
|
code=$(http_get "$url")
|
|||
|
|
fi
|
|||
|
|
if echo "$code" | grep -qE '^(200|201|400|401|403|404)$'; then
|
|||
|
|
pass "$label 响应正常 (HTTP $code)"
|
|||
|
|
elif [ "$code" = "000" ]; then
|
|||
|
|
warn "$label 无法连接(未部署或未启动)"
|
|||
|
|
else
|
|||
|
|
fail "$label 返回错误 (HTTP $code)"
|
|||
|
|
fi
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
check_optional_service "IM 服务" \
|
|||
|
|
"$BASE_URL/api/im/platform-events/token?appKey=test" "$TOKEN"
|
|||
|
|
|
|||
|
|
check_optional_service "版本管理 (update)" \
|
|||
|
|
"$BASE_URL/api/v1/updates/app/list?appKey=test&platform=ANDROID" "$TOKEN"
|
|||
|
|
|
|||
|
|
check_optional_service "RN 热更新 (rn)" \
|
|||
|
|
"$BASE_URL/api/v1/rn/list?appKey=test" "$TOKEN"
|
|||
|
|
|
|||
|
|
# 文件服务
|
|||
|
|
FILE_CODE=$(http_get "$BASE_URL/file/")
|
|||
|
|
if echo "$FILE_CODE" | grep -qE '^(200|400|401|403|404)$'; then
|
|||
|
|
pass "文件服务响应正常 (HTTP $FILE_CODE)"
|
|||
|
|
else
|
|||
|
|
warn "文件服务响应: HTTP $FILE_CODE"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
# 最终汇总
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
printf "\n${BOLD}════════════════════════════════════════════════════${RESET}\n"
|
|||
|
|
printf "${BOLD} 验证结果${RESET}\n"
|
|||
|
|
printf "${BOLD}════════════════════════════════════════════════════${RESET}\n"
|
|||
|
|
printf " ${GREEN}PASS${RESET}: %d 项\n" "$PASS"
|
|||
|
|
printf " ${YELLOW}WARN${RESET}: %d 项(可选服务或预期降级)\n" "$WARN"
|
|||
|
|
printf " ${RED}FAIL${RESET}: %d 项\n" "$FAIL"
|
|||
|
|
|
|||
|
|
if [ "$FAIL" -eq 0 ]; then
|
|||
|
|
printf "\n ${GREEN}${BOLD}✓ 验证通过!所有必选服务运行正常。${RESET}\n"
|
|||
|
|
[ "$WARN" -gt 0 ] && printf " ${YELLOW}(%d 个可选服务未启动,属于预期范围)${RESET}\n" "$WARN"
|
|||
|
|
else
|
|||
|
|
printf "\n ${RED}${BOLD}✗ %d 项验证失败,请检查容器日志:${RESET}\n" "$FAIL"
|
|||
|
|
printf " docker compose logs --tail 50 tenant-service\n"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
printf "\n 访问地址:${BOLD}%s${RESET}\n" "$BASE_URL"
|
|||
|
|
printf " 运营后台:${BOLD}%s/ops${RESET}\n" "$BASE_URL"
|
|||
|
|
printf " 文档站 :${BOLD}%s/docs/${RESET}\n" "$BASE_URL"
|
|||
|
|
printf "\n"
|
|||
|
|
|
|||
|
|
# 输出结果到 JSON
|
|||
|
|
RESULT_JSON="$ROOT_DIR/.deploy-state/last-verify.json"
|
|||
|
|
{
|
|||
|
|
printf '{"timestamp":"%s","base_url":"%s","pass":%d,"warn":%d,"fail":%d,"items":[' \
|
|||
|
|
"$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$BASE_URL" "$PASS" "$WARN" "$FAIL"
|
|||
|
|
first=true
|
|||
|
|
for r in "${RESULTS[@]}"; do
|
|||
|
|
STATUS="${r%%|*}"; MSG="${r##*|}"
|
|||
|
|
$first || printf ','
|
|||
|
|
printf '{"status":"%s","message":"%s"}' "$STATUS" "$MSG"
|
|||
|
|
first=false
|
|||
|
|
done
|
|||
|
|
printf ']}\n'
|
|||
|
|
} > "$RESULT_JSON" 2>/dev/null || true
|
|||
|
|
|
|||
|
|
[ "$FAIL" -eq 0 ]
|