From 00b7ee27db14e1c8817018b012035ac0b9a4bca3 Mon Sep 17 00:00:00 2001 From: eric Date: Thu, 26 Mar 2026 03:15:23 -0500 Subject: [PATCH] 's' --- app/routers/nomadvip.py | 113 ++++++++++++++++++++++++++-------------- 1 file changed, 75 insertions(+), 38 deletions(-) diff --git a/app/routers/nomadvip.py b/app/routers/nomadvip.py index 406b3d8..449235b 100644 --- a/app/routers/nomadvip.py +++ b/app/routers/nomadvip.py @@ -3,11 +3,13 @@ import logging import random import string +import threading import time import requests from fastapi import APIRouter, HTTPException, Request from fastapi.responses import JSONResponse, RedirectResponse +from requests.adapters import HTTPAdapter from ..config import BASE_URL, PB_ADMIN_EMAIL, PB_ADMIN_PASSWORD, PB_URL from ..payment import ( @@ -21,6 +23,17 @@ from ..services import handle_payment_success, processed_orders router = APIRouter(prefix="/nomadvip", tags=["nomadvip"]) +DEFAULT_HTTP_TIMEOUT = 10 +PB_ADMIN_TOKEN_TTL_SECONDS = 60 + +_http_session = requests.Session() +_http_session.mount("http://", HTTPAdapter(pool_connections=32, pool_maxsize=64)) +_http_session.mount("https://", HTTPAdapter(pool_connections=32, pool_maxsize=64)) + +_admin_token_cache_lock = threading.Lock() +_admin_token_cache_value: str | None = None +_admin_token_cache_expire_at = 0.0 + def _get_notify_path(provider_name: str) -> str: if provider_name == "xorpay": @@ -301,6 +314,13 @@ async def nomadvip_check_vip(user_id: str = ""): def _get_pb_admin_token() -> str | None: """获取 PocketBase 管理员 token。""" + global _admin_token_cache_value, _admin_token_cache_expire_at + + now = time.time() + with _admin_token_cache_lock: + if _admin_token_cache_value and now < _admin_token_cache_expire_at: + return _admin_token_cache_value + if not PB_ADMIN_EMAIL or not PB_ADMIN_PASSWORD: return None @@ -310,13 +330,20 @@ def _get_pb_admin_token() -> str | None: "/api/admins/auth-with-password", ]: try: - response = requests.post( + response = _http_session.post( f"{base}{path}", json={"identity": PB_ADMIN_EMAIL, "password": PB_ADMIN_PASSWORD}, - timeout=10, + timeout=DEFAULT_HTTP_TIMEOUT, ) if response.status_code == 200: - return response.json().get("token") + token = response.json().get("token") + if token: + with _admin_token_cache_lock: + _admin_token_cache_value = token + _admin_token_cache_expire_at = ( + time.time() + PB_ADMIN_TOKEN_TTL_SECONDS + ) + return token except Exception: pass return None @@ -330,13 +357,13 @@ def _pb_list_email_links(admin_token: str, email: str) -> list[dict]: base = (PB_URL or "").rstrip("/") headers = {"Authorization": f"Bearer {admin_token}"} - for candidate in [email, email.lower()]: + for candidate in {email, email.lower()}: safe_email = _pb_escape(candidate) - response = requests.get( + response = _http_session.get( f"{base}/api/collections/vip_email_link/records", params={"filter": f'email = "{safe_email}"', "perPage": 1}, headers=headers, - timeout=10, + timeout=DEFAULT_HTTP_TIMEOUT, ) if response.status_code != 200: continue @@ -367,19 +394,19 @@ def _pb_upsert_email_link( if items: record_id = items[0].get("id") if record_id: - requests.patch( + _http_session.patch( f"{base}/api/collections/vip_email_link/records/{record_id}", json=payload, headers=headers, - timeout=10, + timeout=DEFAULT_HTTP_TIMEOUT, ) return - requests.post( + _http_session.post( f"{base}/api/collections/vip_email_link/records", json=payload, headers=headers, - timeout=10, + timeout=DEFAULT_HTTP_TIMEOUT, ) @@ -395,14 +422,14 @@ def _pb_move_site_vip( base = (PB_URL or "").rstrip("/") safe_from_user_id = _pb_escape(from_user_id) safe_site_id = _pb_escape(site_id) - response = requests.get( + response = _http_session.get( f"{base}/api/collections/site_vip/records", params={ "filter": f'user_id = "{safe_from_user_id}" && site_id = "{safe_site_id}"', "perPage": 500, }, headers={"Authorization": f"Bearer {admin_token}"}, - timeout=10, + timeout=DEFAULT_HTTP_TIMEOUT, ) if response.status_code != 200: return @@ -416,18 +443,18 @@ def _pb_move_site_vip( record_id = item.get("id") if not record_id: continue - requests.patch( + _http_session.patch( f"{base}/api/collections/site_vip/records/{record_id}", json={"user_id": to_user_id}, headers=headers, - timeout=10, + timeout=DEFAULT_HTTP_TIMEOUT, ) def _pb_latest_vip_payment(admin_token: str, user_id: str) -> dict | None: base = (PB_URL or "").rstrip("/") safe_user_id = _pb_escape(user_id) - response = requests.get( + response = _http_session.get( f"{base}/api/collections/payments/records", params={ "filter": f'user_id = "{safe_user_id}" && type = "vip"', @@ -435,7 +462,7 @@ def _pb_latest_vip_payment(admin_token: str, user_id: str) -> dict | None: "sort": "-created", }, headers={"Authorization": f"Bearer {admin_token}"}, - timeout=10, + timeout=DEFAULT_HTTP_TIMEOUT, ) if response.status_code != 200: return None @@ -452,7 +479,7 @@ def _pb_list_site_vip_by_order( base = (PB_URL or "").rstrip("/") safe_order_id = _pb_escape(order_id) safe_site_id = _pb_escape(site_id) - response = requests.get( + response = _http_session.get( f"{base}/api/collections/site_vip/records", params={ "filter": f'order_id = "{safe_order_id}" && site_id = "{safe_site_id}"', @@ -460,7 +487,7 @@ def _pb_list_site_vip_by_order( "sort": "-created", }, headers={"Authorization": f"Bearer {admin_token}"}, - timeout=10, + timeout=DEFAULT_HTTP_TIMEOUT, ) if response.status_code != 200: return [] @@ -470,10 +497,10 @@ def _pb_list_site_vip_by_order( def _pb_get_user_email(admin_token: str, user_id: str) -> str | None: base = (PB_URL or "").rstrip("/") - response = requests.get( + response = _http_session.get( f"{base}/api/collections/users/records/{user_id}", headers={"Authorization": f"Bearer {admin_token}"}, - timeout=10, + timeout=DEFAULT_HTTP_TIMEOUT, ) if response.status_code != 200: return None @@ -488,7 +515,7 @@ def _pb_find_linked_member_from_order( ) -> tuple[str, str] | None: base = (PB_URL or "").rstrip("/") safe_user_id = _pb_escape(anonymous_user_id) - response = requests.get( + response = _http_session.get( f"{base}/api/collections/payments/records", params={ "filter": f'user_id = "{safe_user_id}" && type = "vip"', @@ -496,7 +523,7 @@ def _pb_find_linked_member_from_order( "sort": "-created", }, headers={"Authorization": f"Bearer {admin_token}"}, - timeout=10, + timeout=DEFAULT_HTTP_TIMEOUT, ) if response.status_code != 200: return None @@ -538,7 +565,7 @@ def _pb_ensure_site_vip( safe_from_user_id = _pb_escape(from_user_id) safe_to_user_id = _pb_escape(to_user_id) safe_site_id = _pb_escape(site_id) - source_records = requests.get( + source_records = _http_session.get( f"{base}/api/collections/site_vip/records", params={ "filter": f'user_id = "{safe_from_user_id}" && site_id = "{safe_site_id}"', @@ -546,9 +573,9 @@ def _pb_ensure_site_vip( "sort": "-created", }, headers={"Authorization": f"Bearer {admin_token}"}, - timeout=10, + timeout=DEFAULT_HTTP_TIMEOUT, ) - to_records = requests.get( + to_records = _http_session.get( f"{base}/api/collections/site_vip/records", params={ "filter": f'user_id = "{safe_to_user_id}" && site_id = "{safe_site_id}"', @@ -556,7 +583,7 @@ def _pb_ensure_site_vip( "sort": "-created", }, headers={"Authorization": f"Bearer {admin_token}"}, - timeout=10, + timeout=DEFAULT_HTTP_TIMEOUT, ) payment = _pb_latest_vip_payment(admin_token, from_user_id) order_id = (payment or {}).get("order_id") or "" @@ -578,11 +605,11 @@ def _pb_ensure_site_vip( next_order_id = order_id or (canonical.get("order_id") or "").strip() if next_order_id: patch_body["order_id"] = next_order_id - requests.patch( + _http_session.patch( f"{base}/api/collections/site_vip/records/{canonical['id']}", json=patch_body, headers=headers, - timeout=10, + timeout=DEFAULT_HTTP_TIMEOUT, ) duplicate_ids = set() @@ -591,21 +618,21 @@ def _pb_ensure_site_vip( if record_id and record_id != canonical["id"]: duplicate_ids.add(record_id) for record_id in duplicate_ids: - requests.delete( + _http_session.delete( f"{base}/api/collections/site_vip/records/{record_id}", headers={"Authorization": f"Bearer {admin_token}"}, - timeout=10, + timeout=DEFAULT_HTTP_TIMEOUT, ) return if not order_id: return - requests.post( + _http_session.post( f"{base}/api/collections/site_vip/records", json={"user_id": to_user_id, "site_id": site_id, "order_id": order_id}, headers=headers, - timeout=10, + timeout=DEFAULT_HTTP_TIMEOUT, ) @@ -627,10 +654,10 @@ async def nomadvip_check_vip_by_email(item: dict): return {"vip": False, "error": "missing email or password"} base = (PB_URL or "").rstrip("/") - login_res = requests.post( + login_res = _http_session.post( f"{base}/api/collections/users/auth-with-password", json={"identity": email, "password": password}, - timeout=10, + timeout=DEFAULT_HTTP_TIMEOUT, ) if login_res.status_code != 200: try: @@ -663,6 +690,7 @@ async def nomadvip_check_vip_by_email(item: dict): pb = get_pb_client() matched_user_id = None + vip_check_cache: dict[str, bool] = {} recovered_anonymous_user_id = ( anonymous_user_id if anonymous_user_id.startswith("user") and anonymous_user_id[4:].isdigit() @@ -670,7 +698,16 @@ async def nomadvip_check_vip_by_email(item: dict): ) recovery_source = None - if recovered_anonymous_user_id and _uid_has_vip(pb, recovered_anonymous_user_id): + def has_vip_cached(uid: str) -> bool: + if not uid: + return False + if uid in vip_check_cache: + return vip_check_cache[uid] + result = _uid_has_vip(pb, uid) + vip_check_cache[uid] = result + return result + + if recovered_anonymous_user_id and has_vip_cached(recovered_anonymous_user_id): matched_user_id = recovered_anonymous_user_id recovery_source = "request_user_id" else: @@ -691,8 +728,8 @@ async def nomadvip_check_vip_by_email(item: dict): if linked_anonymous_user_id and linked_anonymous_user_id not in linked_ids: linked_ids.append(linked_anonymous_user_id) - for linked_uid in linked_ids: - if not _uid_has_vip(pb, linked_uid): + for linked_uid in dict.fromkeys(linked_ids): + if not has_vip_cached(linked_uid): continue matched_user_id = linked_uid if linked_anonymous_user_id.startswith("user") and linked_anonymous_user_id[4:].isdigit(): @@ -705,7 +742,7 @@ async def nomadvip_check_vip_by_email(item: dict): if matched_user_id: break - if not matched_user_id and _uid_has_vip(pb, pb_user_id): + if not matched_user_id and has_vip_cached(pb_user_id): matched_user_id = pb_user_id recovered_anonymous_user_id = None recovery_source = "pb_user_id"