feat: 支持x-login-as三参数认证、auth_url跟随base_url

- 认证请求增加x-login-as header支持连接账户
- auth_url根据base_url动态构建,不再硬编码生产环境
- 默认Base URL改为demo环境
- 设置页面新增Account ID字段,带tooltip说明
This commit is contained in:
zqq61
2026-03-16 00:44:27 +08:00
parent 01773500af
commit 81d9c3a7e1
3 changed files with 45 additions and 15 deletions

View File

@@ -2,16 +2,20 @@
import logging
import httpx
from datetime import datetime, timezone, timedelta
from typing import Optional
from airwallex.client import AirwallexClient
logger = logging.getLogger(__name__)
class ProxiedAirwallexClient(AirwallexClient):
"""AirwallexClient that routes requests through an HTTP or SOCKS5 proxy."""
"""AirwallexClient that routes requests through an HTTP or SOCKS5 proxy.
Also supports x-login-as header for connected accounts.
"""
def __init__(self, proxy_url: str | None = None, **kwargs):
def __init__(self, proxy_url: str | None = None, login_as: str | None = None, **kwargs):
self._proxy_url = proxy_url
self._login_as = login_as
super().__init__(**kwargs)
# Replace the default httpx client with a proxied one
if proxy_url:
@@ -27,26 +31,30 @@ class ProxiedAirwallexClient(AirwallexClient):
logger.info("Airwallex client initialized without proxy")
def authenticate(self) -> None:
"""Override authenticate to use proxy for auth requests too."""
"""Override authenticate to use proxy and x-login-as for auth requests."""
if self._token and self._token_expiry and datetime.now(timezone.utc) < self._token_expiry:
return
logger.info("Authenticating with Airwallex API at %s (proxy: %s)",
self.auth_url, bool(self._proxy_url))
logger.info("Authenticating with Airwallex API at %s (proxy: %s, login_as: %s)",
self.auth_url, bool(self._proxy_url), self._login_as or "none")
auth_kwargs: dict = {"timeout": self.request_timeout}
if self._proxy_url:
auth_kwargs["proxy"] = self._proxy_url
auth_headers = {
"Content-Type": "application/json",
"x-client-id": self.client_id,
"x-api-key": self.api_key,
}
if self._login_as:
auth_headers["x-login-as"] = self._login_as
auth_client = httpx.Client(**auth_kwargs)
try:
response = auth_client.post(
self.auth_url,
headers={
"Content-Type": "application/json",
"x-client-id": self.client_id,
"x-api-key": self.api_key,
},
headers=auth_headers,
content="{}",
)
@@ -61,3 +69,11 @@ class ProxiedAirwallexClient(AirwallexClient):
logger.info("Authentication successful, token expires in 28 minutes")
finally:
auth_client.close()
@property
def headers(self):
"""Add x-login-as to all API request headers."""
h = super().headers
if self._login_as:
h["x-login-as"] = self._login_as
return h

View File

@@ -43,13 +43,14 @@ def get_client(db: Session) -> ProxiedAirwallexClient:
client_id = _get_setting(db, "airwallex_client_id")
api_key = _get_setting(db, "airwallex_api_key")
base_url = _get_setting(db, "airwallex_base_url", "https://api.airwallex.com/")
base_url = _get_setting(db, "airwallex_base_url", "https://api-demo.airwallex.com/")
login_as = _get_setting(db, "airwallex_login_as")
proxy_url = _build_proxy_url(db)
if not client_id or not api_key:
raise HTTPException(status_code=400, detail="Airwallex 凭证未配置,请在系统设置中填写 Client ID 和 API Key")
config_hash = f"{client_id}:{api_key}:{base_url}:{proxy_url}"
config_hash = f"{client_id}:{api_key}:{base_url}:{login_as}:{proxy_url}"
if _client_instance and _client_config_hash == config_hash:
return _client_instance
@@ -60,11 +61,16 @@ def get_client(db: Session) -> ProxiedAirwallexClient:
except Exception:
pass
# auth_url must match base_url domain
auth_url = base_url.rstrip("/") + "/api/v1/authentication/login"
_client_instance = ProxiedAirwallexClient(
proxy_url=proxy_url,
login_as=login_as or None,
client_id=client_id,
api_key=api_key,
base_url=base_url,
auth_url=auth_url,
)
_client_config_hash = config_hash
return _client_instance