hi-server/script/family_delete_account_cleanup_check.sh

347 lines
11 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
set -euo pipefail
BASE_URL="${BASE_URL:-http://127.0.0.1:8080}"
CONFIG_PATH="${CONFIG_PATH:-etc/ppanel.yaml}"
EMAIL_A="${EMAIL_A:-family_cleanup_$(date +%s)@example.com}"
DEVICE_A="${DEVICE_A:-qa-cleanup-device-a-$(date +%s)}"
DEVICE_B="${DEVICE_B:-qa-cleanup-device-b-$(date +%s)}"
SEND_CODE_RETRY_WAIT="${SEND_CODE_RETRY_WAIT:-61}"
SEND_CODE_MAX_RETRY="${SEND_CODE_MAX_RETRY:-8}"
VERIFY_CODE_PROVIDER="${VERIFY_CODE_PROVIDER:-}"
VERIFY_CODE_OVERRIDE="${VERIFY_CODE_OVERRIDE:-}"
REGISTER_CODE_OVERRIDE="${REGISTER_CODE_OVERRIDE:-}"
SECURITY_CODE_OVERRIDE="${SECURITY_CODE_OVERRIDE:-}"
DELETE_ACCOUNT_CODE_OVERRIDE="${DELETE_ACCOUNT_CODE_OVERRIDE:-}"
ALLOW_INTERACTIVE_INPUT="${ALLOW_INTERACTIVE_INPUT:-auto}"
ENABLE_REDIS_CODE_FALLBACK="${ENABLE_REDIS_CODE_FALLBACK:-1}"
REDIS_CLI_BIN="${REDIS_CLI_BIN:-redis-cli}"
REDIS_HOST="${REDIS_HOST:-127.0.0.1}"
REDIS_PORT="${REDIS_PORT:-6379}"
REDIS_DB="${REDIS_DB:-0}"
REDIS_PASSWORD="${REDIS_PASSWORD:-}"
BIND_VERIFY_RETRY="${BIND_VERIFY_RETRY:-1}"
bold() { printf "\033[1m%s\033[0m\n" "$*"; }
info() { printf "[INFO] %s\n" "$*" >&2; }
warn() { printf "[WARN] %s\n" "$*" >&2; }
fail() { printf "[FAIL] %s\n" "$*" >&2; exit 1; }
require_cmd() {
command -v "$1" >/dev/null 2>&1 || fail "缺少命令: $1"
}
json_code() { jq -r '.code // ""' <<<"$1"; }
json_msg() { jq -r '.msg // ""' <<<"$1"; }
can_prompt_input() {
if [[ "$ALLOW_INTERACTIVE_INPUT" == "1" ]]; then
return 0
fi
if [[ "$ALLOW_INTERACTIVE_INPUT" == "0" ]]; then
return 1
fi
[[ -t 0 ]]
}
redis_get_key_raw() {
local key="$1"
command -v "$REDIS_CLI_BIN" >/dev/null 2>&1 || return 1
local -a args
args=("$REDIS_CLI_BIN" -h "$REDIS_HOST" -p "$REDIS_PORT" -n "$REDIS_DB" --raw)
if [[ -n "$REDIS_PASSWORD" ]]; then
args+=(-a "$REDIS_PASSWORD")
fi
"${args[@]}" GET "$key" 2>/dev/null || return 1
}
get_verify_code_from_redis() {
local email="$1"
local typ="$2"
local scene key raw code
if [[ "$ENABLE_REDIS_CODE_FALLBACK" != "1" ]]; then
return 1
fi
case "$typ" in
1) scene="register" ;;
2) scene="security" ;;
4) scene="delete_account" ;;
*) scene="unknown" ;;
esac
key="auth:verify:email:${scene}:${email}"
raw="$(redis_get_key_raw "$key" || true)"
[[ -n "$raw" ]] || return 1
code="$(jq -r '.code // ""' <<<"$raw" 2>/dev/null || true)"
[[ -n "$code" && "$code" != "null" ]] || return 1
printf "%s" "$code"
return 0
}
api_request() {
local method="$1"
local path="$2"
local body="${3:-}"
local token="${4:-}"
local -a args
args=(-sS -X "$method" "${BASE_URL}${path}" -H "Content-Type: application/json")
if [[ -n "$token" ]]; then
args+=(-H "Authorization: ${token}")
fi
if [[ "$method" != "GET" ]]; then
args+=(-d "$body")
fi
curl "${args[@]}"
}
assert_success() {
local resp="$1"
local step="$2"
local code
code="$(json_code "$resp")"
if [[ "$code" != "200" ]]; then
fail "${step} 失败, code=${code}, msg=$(json_msg "$resp"), resp=${resp}"
fi
}
token_or_keep() {
local resp="$1"
local fallback="$2"
local token
token="$(jq -r '.data.token // ""' <<<"$resp")"
if [[ -n "$token" && "$token" != "null" ]]; then
printf "%s" "$token"
else
printf "%s" "$fallback"
fi
}
device_login() {
local identifier="$1"
local user_agent="$2"
local payload resp token
payload="$(jq -nc --arg identifier "$identifier" --arg user_agent "$user_agent" '{identifier:$identifier,user_agent:$user_agent}')"
resp="$(api_request "POST" "/v1/auth/login/device" "$payload")"
assert_success "$resp" "设备登录(${identifier})"
token="$(jq -r '.data.token // ""' <<<"$resp")"
[[ -n "$token" && "$token" != "null" ]] || fail "设备登录(${identifier})返回空 token: ${resp}"
printf "%s" "$token"
}
send_code_with_retry() {
local email="$1"
local typ="$2"
local payload resp code retry
payload="$(jq -nc --arg email "$email" --argjson type "$typ" '{email:$email,type:$type}')"
retry=0
while true; do
resp="$(api_request "POST" "/v1/common/send_code" "$payload")"
code="$(json_code "$resp")"
if [[ "$code" == "200" ]]; then
printf "%s" "$resp"
return 0
fi
if [[ "$code" == "401" ]]; then
retry=$((retry + 1))
if (( retry > SEND_CODE_MAX_RETRY )); then
fail "发送验证码达到重试上限, email=${email}, type=${typ}, resp=${resp}"
fi
warn "验证码发送频率限制,等待 ${SEND_CODE_RETRY_WAIT}s 后重试(${retry}/${SEND_CODE_MAX_RETRY})..."
sleep "${SEND_CODE_RETRY_WAIT}"
continue
fi
fail "发送验证码失败, email=${email}, type=${typ}, code=${code}, msg=$(json_msg "$resp"), resp=${resp}"
done
}
get_verify_code() {
local email="$1"
local typ="$2"
local send_resp code
send_resp="$(send_code_with_retry "$email" "$typ")"
code="$(jq -r '.data.code // ""' <<<"$send_resp")"
if [[ -n "$code" && "$code" != "null" ]]; then
info "验证码已从接口返回(type=${typ})"
printf "%s" "$code"
return 0
fi
code="$(get_verify_code_from_redis "$email" "$typ" || true)"
if [[ -n "$code" ]]; then
info "验证码已从 Redis 回读(type=${typ})"
printf "%s" "$code"
return 0
fi
if [[ -n "$VERIFY_CODE_PROVIDER" ]]; then
code="$("$VERIFY_CODE_PROVIDER" "$email" "$typ" || true)"
if [[ -n "$code" ]]; then
info "验证码由 VERIFY_CODE_PROVIDER 提供(type=${typ})"
printf "%s" "$code"
return 0
fi
fi
if [[ "$typ" == "1" && -n "$REGISTER_CODE_OVERRIDE" ]]; then
printf "%s" "$REGISTER_CODE_OVERRIDE"
return 0
fi
if [[ "$typ" == "2" && -n "$SECURITY_CODE_OVERRIDE" ]]; then
printf "%s" "$SECURITY_CODE_OVERRIDE"
return 0
fi
if [[ "$typ" == "4" && -n "$DELETE_ACCOUNT_CODE_OVERRIDE" ]]; then
printf "%s" "$DELETE_ACCOUNT_CODE_OVERRIDE"
return 0
fi
if [[ -n "$VERIFY_CODE_OVERRIDE" ]]; then
printf "%s" "$VERIFY_CODE_OVERRIDE"
return 0
fi
if ! can_prompt_input; then
fail "验证码不可自动获取(type=${typ}, email=${email})"
fi
read -r -p "请输入邮箱 ${email} 的验证码(type=${typ})" code
[[ -n "$code" ]] || fail "验证码为空"
printf "%s" "$code"
}
bind_email_with_verification() {
local token="$1"
local email="$2"
local code="$3"
local payload
payload="$(jq -nc --arg email "$email" --arg code "$code" '{email:$email,code:$code}')"
api_request "POST" "/v1/public/user/bind_email_with_verification" "$payload" "$token"
}
bind_email_with_verification_auto() {
local token="$1"
local email="$2"
local typ="$3"
local max_retry="${4:-$BIND_VERIFY_RETRY}"
local attempt=0 code resp api_code
while true; do
code="$(get_verify_code "$email" "$typ")"
resp="$(bind_email_with_verification "$token" "$email" "$code")"
api_code="$(json_code "$resp")"
if [[ "$api_code" == "200" ]]; then
printf "%s" "$resp"
return 0
fi
if [[ "$api_code" == "70001" && "$attempt" -lt "$max_retry" ]]; then
warn "绑定邮箱验证码校验失败,重新获取验证码重试($((attempt+1))/${max_retry})..."
attempt=$((attempt + 1))
continue
fi
fail "绑定邮箱(${email}) 失败, code=${api_code}, msg=$(json_msg "$resp"), resp=${resp}"
done
}
delete_account() {
local token="$1"
local email="$2"
local code="$3"
local payload resp
payload="$(jq -nc --arg email "$email" --arg code "$code" '{email:$email,code:$code}')"
resp="$(api_request "POST" "/v1/public/user/delete_account" "$payload" "$token")"
assert_success "$resp" "注销账号(${email})"
printf "%s" "$resp"
}
db_query_json() {
local query="$1"
GOCACHE=/tmp/go-build go run ./script/db_query.go --config "$CONFIG_PATH" --query "$query"
}
assert_db_field_equals() {
local json="$1"
local field="$2"
local expected="$3"
local actual
actual="$(jq -r ".[0].${field} // \"\"" <<<"$json")"
if [[ "$actual" != "$expected" ]]; then
fail "DB校验失败: 字段 ${field} 期望=${expected}, 实际=${actual}, 数据=${json}"
fi
}
main() {
require_cmd curl
require_cmd jq
require_cmd go
bold "家庭组注销清理校验脚本"
info "BASE_URL=${BASE_URL}"
info "CONFIG_PATH=${CONFIG_PATH}"
info "EMAIL_A=${EMAIL_A}"
info "DEVICE_A=${DEVICE_A}"
info "DEVICE_B=${DEVICE_B}"
local token_a token_b
local bind_a_resp bind_b_resp del_b_resp del_a_resp
local family_id owner_user_id member_user_id
local code_delete
local q_member_before q_member_after q_owner_after q_family_after
bold "步骤1: A设备登录并绑定邮箱A"
token_a="$(device_login "$DEVICE_A" "qa-cleanup-device-a")"
bind_a_resp="$(bind_email_with_verification_auto "$token_a" "$EMAIL_A" 1)"
token_a="$(token_or_keep "$bind_a_resp" "$token_a")"
bold "步骤2: B设备登录并绑定邮箱A加入家庭"
token_b="$(device_login "$DEVICE_B" "qa-cleanup-device-b")"
bind_b_resp="$(bind_email_with_verification_auto "$token_b" "$EMAIL_A" 2)"
token_b="$(token_or_keep "$bind_b_resp" "$token_b")"
family_id="$(jq -r '.data.family_id // ""' <<<"$bind_b_resp")"
owner_user_id="$(jq -r '.data.owner_user_id // ""' <<<"$bind_b_resp")"
member_user_id="$(jq -r '.data.user_id // ""' <<<"$bind_b_resp")"
[[ -n "$family_id" && "$family_id" != "null" ]] || fail "无法获取 family_id: ${bind_b_resp}"
[[ -n "$owner_user_id" && "$owner_user_id" != "null" ]] || fail "无法获取 owner_user_id: ${bind_b_resp}"
[[ -n "$member_user_id" && "$member_user_id" != "null" ]] || fail "无法获取 member_user_id: ${bind_b_resp}"
q_member_before="$(db_query_json "SELECT status, role, family_id FROM user_family_member WHERE user_id = ${member_user_id} ORDER BY id DESC LIMIT 1")"
assert_db_field_equals "$q_member_before" "status" "1"
info "B注销前家庭状态校验通过(status=1)"
bold "步骤3: 注销设备B账号检查B离组"
code_delete="$(get_verify_code "$EMAIL_A" 4)"
del_b_resp="$(delete_account "$token_b" "$EMAIL_A" "$code_delete")"
info "B注销结果: $(jq -c '.data' <<<"$del_b_resp")"
q_member_after="$(db_query_json "SELECT status, role, family_id FROM user_family_member WHERE user_id = ${member_user_id} ORDER BY id DESC LIMIT 1")"
assert_db_field_equals "$q_member_after" "status" "3"
info "B注销后家庭状态校验通过(status=3)"
bold "步骤4: 注销设备A(家庭主)账号,检查家庭解散"
code_delete="$(get_verify_code "$EMAIL_A" 4)"
del_a_resp="$(delete_account "$token_a" "$EMAIL_A" "$code_delete")"
info "A注销结果: $(jq -c '.data' <<<"$del_a_resp")"
q_owner_after="$(db_query_json "SELECT status, role, family_id FROM user_family_member WHERE user_id = ${owner_user_id} ORDER BY id DESC LIMIT 1")"
assert_db_field_equals "$q_owner_after" "status" "3"
info "A注销后主账号家庭状态校验通过(status=3)"
q_family_after="$(db_query_json "SELECT status FROM user_family WHERE id = ${family_id} ORDER BY id DESC LIMIT 1")"
assert_db_field_equals "$q_family_after" "status" "0"
info "家庭状态校验通过(status=0)"
bold "校验通过 ✅ 注销后家庭成员与家庭状态已正确更新"
}
main "$@"