From 28dc46ad95fc9c046c6e1d7fb91c500f5c228582 Mon Sep 17 00:00:00 2001 From: Milky0217 Date: Thu, 7 May 2026 17:20:43 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20expire-paid-user.s?= =?UTF-8?q?h=20=E8=84=9A=E6=9C=AC=EF=BC=8C=E4=BD=BF=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E4=BB=98=E8=B4=B9=E6=97=B6=E9=97=B4=E8=BF=87=E6=9C=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/expire-paid-user.sh | 214 ++++++++++++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100755 scripts/expire-paid-user.sh diff --git a/scripts/expire-paid-user.sh b/scripts/expire-paid-user.sh new file mode 100755 index 0000000..3ba816f --- /dev/null +++ b/scripts/expire-paid-user.sh @@ -0,0 +1,214 @@ +#!/bin/bash +# scripts/expire-paid-user.sh +# 通过 SSH 在服务器上执行 SQL,使用户的付费时间过期(仅修改 paid_expires_at) +# 与 set-paid-user.sh --expires 的区别:此脚本专用于"过期"场景, +# 保持 is_paid=true(用户仍标记为付费),仅将 paid_expires_at 设到过去。 +# +# 使用 config/development.toml 中的数据库配置 +# 密码由服务器的 1panel/环境变量提供,无需手动传入 +# +# 用法: +# ./scripts/expire-paid-user.sh +# ./scripts/expire-paid-user.sh --date YYYY-MM-DD +# +# 示例: +# ./scripts/expire-paid-user.sh 1 # 设为昨天到期(立即过期) +# ./scripts/expire-paid-user.sh 1 --date 2025-01-01 # 设为指定日期到期(已过期) + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +CONFIG_DIR="$PROJECT_ROOT/config" +ENV_FILE="$PROJECT_ROOT/.env" + +get_ssh_host() { + grep '^SSH_SERVER=' "$ENV_FILE" 2>/dev/null | head -1 | cut -d'=' -f2- | tr -d '"' | tr -d "'" | xargs +} + +usage() { + echo "用法: $0 [--date YYYY-MM-DD]" + echo "" + echo " user_id 用户 ID(必填)" + echo " --date DATE 指定过期日期(默认: 昨天)" + echo "" + echo "说明:" + echo " 将用户 paid_expires_at 设到过去日期(is_paid 保持 true)" + echo " 用户的 is_paid_active 会变为 false" + echo "" + echo "数据源:" + echo " SSH 服务器: .env 中 SSH_SERVER" + echo " 数据库配置: config/development.toml (host/port/dbname)" + echo " 密码: 服务器上的环境变量/1panel 自动提供" + echo "" + echo "示例:" + echo " $0 1 # 设为昨天到期" + echo " $0 1 --date 2025-01-01 # 设为 2025-01-01" + exit 0 +} + +validate_date() { + local date="$1" + if [[ ! "$date" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then + echo "错误: 日期格式无效,请使用 YYYY-MM-DD" + exit 1 + fi +} + +# 解析 toml 文件中的 database_url +parse_toml_db_url() { + local file="$1" + grep '^database_url' "$file" 2>/dev/null | head -1 | sed 's/^database_url *= *["'"'"']//' | sed 's/["'"'"']*$//' | tr -d ' ' +} + +# 解析 postgres:// URL +parse_db_url() { + local db_url="$1" + local rest="${db_url#postgres://}" + DB_USER="${rest%%:*}" + local host_port="${rest##*@}" + host_port="${host_port%%/*}" + if [[ "$host_port" == *:* ]]; then + DB_HOST="${host_port%%:*}" + DB_PORT="${host_port##*:}" + else + DB_HOST="$host_port" + DB_PORT="5432" + fi + DB_NAME="${rest##*/}" + DB_NAME="${DB_NAME%%\?*}" +} + +info() { echo "[INFO] $1"; } +warn() { echo "[WARN] $1"; } +error() { echo "[ERROR] $1" >&2; } +step() { echo ""; echo ">>> 第 $1 步: $2"; echo ""; } + +# 在服务器上通过 docker exec 执行 SQL +exec_sql() { + local sql="$1" + ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o ConnectTimeout=5 "$SSH_HOST" \ + "cd /root/rust/rust_backend_dev 2>/dev/null || true; docker exec 1Panel-postgresql-FtMo psql -U ${DB_USER} -d ${DB_NAME} -c \"${sql}\"" 2>&1 +} + +main() { + if [[ $# -lt 1 ]]; then + usage + fi + + USER_ID="$1" + shift + + if [[ ! "$USER_ID" =~ ^[0-9]+$ ]]; then + error "user_id 必须是数字" + exit 1 + fi + + EXPIRES_DATE="" + + while [[ $# -gt 0 ]]; do + case "$1" in + --date) + EXPIRES_DATE="$2" + validate_date "$EXPIRES_DATE" + shift 2 + ;; + -h|--help) usage ;; + *) error "未知参数: $1"; usage ;; + esac + done + + # 默认过期日期:昨天 + if [[ -z "$EXPIRES_DATE" ]]; then + if date --version 2>/dev/null | grep -q GNU; then + EXPIRES_DATE=$(date -d "yesterday" "+%Y-%m-%d") + else + EXPIRES_DATE=$(date -v -1d "+%Y-%m-%d") + fi + fi + + # ===== Step 1: 读取配置 ===== + step 1 "读取配置" + + SSH_HOST="$(get_ssh_host)" + if [[ -z "$SSH_HOST" ]]; then + error "未找到 SSH_SERVER 配置,请检查 .env 文件" + exit 1 + fi + info "SSH 服务器: $SSH_HOST" + + CONFIG_FILE="$CONFIG_DIR/development.toml" + if [[ ! -f "$CONFIG_FILE" ]]; then + error "配置文件不存在: $CONFIG_FILE" + exit 1 + fi + + TOML_DB_URL="$(parse_toml_db_url "$CONFIG_FILE")" + if [[ -z "$TOML_DB_URL" ]]; then + error "development.toml 中未找到 database_url" + exit 1 + fi + + parse_db_url "$TOML_DB_URL" + info "数据库: ${DB_USER}@${DB_HOST}:${DB_PORT}/${DB_NAME}" + + # ===== Step 2: 构造 SQL ===== + step 2 "构造 SQL 语句" + + SQL="UPDATE users SET is_paid = true, paid_expires_at = '${EXPIRES_DATE}'::date WHERE id = ${USER_ID};" + info "设置过期日期: $EXPIRES_DATE" + echo " SQL: $SQL" + + # ===== Step 3: 执行 SQL ===== + step 3 "通过 SSH 在服务器上执行 SQL" + + info "连接 $SSH_HOST (超时 15s) ..." + + SQL_RAW=$(exec_sql "$SQL") + SQL_EXIT=$? + + if [[ $SQL_EXIT -ne 0 ]]; then + error "执行失败(退出码: $SQL_EXIT)" + echo "$SQL_RAW" | sed 's/^/ /' >&2 + exit 1 + fi + + echo "$SQL_RAW" + if echo "$SQL_RAW" | grep -q "UPDATE [01]"; then + ROWS=$(echo "$SQL_RAW" | grep "UPDATE" | grep -oP '\d+' | head -1) + if [[ "${ROWS:-0}" == "0" ]]; then + warn "未更新任何行(用户 ID $USER_ID 可能不存在)" + else + info "成功更新 ${ROWS} 行" + fi + else + warn "无法确认更新结果" + fi + + # ===== Step 4: 验证结果 ===== + step 4 "查询用户信息确认" + + QUERY_SQL="SELECT id, name, phone, type, is_paid, is_admin, paid_expires_at FROM users WHERE id = ${USER_ID};" + info "SQL: $QUERY_SQL" + QUERY_RAW=$(exec_sql "$QUERY_SQL") + QUERY_EXIT=$? + + if [[ $QUERY_EXIT -ne 0 ]]; then + warn "查询失败(退出码: $QUERY_EXIT),但数据更新已执行" + echo "$QUERY_RAW" | sed 's/^/ /' + else + echo "$QUERY_RAW" + fi + + # ===== 总结 ===== + echo "" + echo "============================================" + echo " 执行完成" + echo "============================================" + echo " 用户 ID: $USER_ID" + echo " 过期日期: $EXPIRES_DATE" + echo " 服务器: $SSH_HOST" + echo "============================================" + echo "" + echo "提示: is_paid 保持 true,is_paid_active 会变为 false(付费已过期)" +} + +main "$@"