feat(admin): 添加用户管理相关文件

添加用户管理视图、API和状态管理文件
This commit is contained in:
张翔
2026-03-28 14:37:29 +08:00
commit 08ea5fbe98
1643 changed files with 255646 additions and 0 deletions
@@ -0,0 +1,4 @@
from .api_client import APIClient
from .auth_manager import AuthManager
__all__ = ["APIClient", "AuthManager"]
@@ -0,0 +1,306 @@
import time
from typing import Optional, Dict, Any, Union
from datetime import datetime
import requests
from apitest.models.test_models import HTTPMethod, PerformanceMetrics
from apitest.models.exceptions import RequestException
class APIClient:
"""API客户端"""
def __init__(self, base_url: str, timeout: int = 5000, max_retries: int = 3, logger=None):
"""初始化API客户端
Args:
base_url: 基础URL
timeout: 超时时间(毫秒)
max_retries: 最大重试次数
logger: 日志记录器
"""
self.base_url = base_url.rstrip("/")
self.timeout = timeout / 1000
self.max_retries = max_retries
self.logger = logger
self._session = requests.Session()
self._default_headers = {
"Content-Type": "application/json",
"Accept": "application/json"
}
def set_default_headers(self, headers: Dict[str, str]):
"""设置默认请求头
Args:
headers: 请求头字典
"""
self._default_headers.update(headers)
def set_auth_token(self, token: str):
"""设置认证token
Args:
token: 认证token
"""
self._default_headers["Authorization"] = f"Bearer {token}"
def _build_url(self, endpoint: str) -> str:
"""构建完整URL
Args:
endpoint: API端点
Returns:
完整URL
"""
endpoint = endpoint.lstrip("/")
return f"{self.base_url}/{endpoint}"
def _merge_headers(self, headers: Optional[Dict[str, str]]) -> Dict[str, str]:
"""合并请求头
Args:
headers: 请求头字典
Returns:
合并后的请求头
"""
merged = self._default_headers.copy()
if headers:
merged.update(headers)
return merged
def _calculate_metrics(
self,
start_time: float,
request_data: Union[Dict, str, None],
response_data: Any
) -> PerformanceMetrics:
"""计算性能指标
Args:
start_time: 请求开始时间
request_data: 请求数据
response_data: 响应数据
Returns:
性能指标
"""
end_time = time.time()
response_time = int((end_time - start_time) * 1000)
request_size = 0
if request_data:
if isinstance(request_data, dict):
request_size = len(str(request_data))
elif isinstance(request_data, str):
request_size = len(request_data)
response_size = 0
if response_data:
response_size = len(str(response_data))
return PerformanceMetrics(
response_time=response_time,
request_size=request_size,
response_size=response_size,
timestamp=datetime.now()
)
def _execute_request(
self,
method: HTTPMethod,
url: str,
headers: Dict[str, str],
params: Optional[Dict[str, Any]] = None,
body: Optional[Dict[str, Any]] = None
) -> requests.Response:
"""执行HTTP请求
Args:
method: HTTP方法
url: 请求URL
headers: 请求头
params: URL参数
body: 请求体
Returns:
响应对象
Raises:
RequestException: 请求失败时抛出
"""
try:
if method == HTTPMethod.GET:
return self._session.get(url, headers=headers, params=params, timeout=self.timeout)
elif method == HTTPMethod.POST:
return self._session.post(url, headers=headers, params=params, json=body, timeout=self.timeout)
elif method == HTTPMethod.PUT:
return self._session.put(url, headers=headers, params=params, json=body, timeout=self.timeout)
elif method == HTTPMethod.DELETE:
return self._session.delete(url, headers=headers, params=params, timeout=self.timeout)
elif method == HTTPMethod.PATCH:
return self._session.patch(url, headers=headers, params=params, json=body, timeout=self.timeout)
elif method == HTTPMethod.HEAD:
return self._session.head(url, headers=headers, params=params, timeout=self.timeout)
elif method == HTTPMethod.OPTIONS:
return self._session.options(url, headers=headers, params=params, timeout=self.timeout)
else:
raise RequestException(f"不支持的HTTP方法: {method}")
except requests.Timeout:
raise RequestException(f"请求超时: {url}")
except requests.ConnectionError:
raise RequestException(f"连接失败: {url}")
except requests.RequestException as e:
raise RequestException(f"请求异常: {e}")
def request(
self,
method: HTTPMethod,
endpoint: str,
headers: Optional[Dict[str, str]] = None,
params: Optional[Dict[str, Any]] = None,
body: Optional[Dict[str, Any]] = None,
retry_count: int = 0
) -> Dict[str, Any]:
"""发送HTTP请求
Args:
method: HTTP方法
endpoint: API端点
headers: 请求头
params: URL参数
body: 请求体
retry_count: 当前重试次数
Returns:
包含响应数据和性能指标的字典
Raises:
RequestException: 请求失败且重试次数用尽时抛出
"""
url = self._build_url(endpoint)
merged_headers = self._merge_headers(headers)
if self.logger:
self.logger.debug(f"发送{method.value}请求: {url}")
start_time = time.time()
try:
response = self._execute_request(method, url, merged_headers, params, body)
try:
response_body = response.json()
except ValueError:
response_body = response.text
performance = self._calculate_metrics(start_time, body, response_body)
if self.logger:
self.logger.debug(
f"响应: HTTP {response.status_code}, "
f"耗时: {performance.response_time}ms, "
f"大小: {performance.response_size}字节"
)
return {
"status_code": response.status_code,
"response_body": response_body,
"response_headers": dict(response.headers),
"performance": performance
}
except RequestException as e:
if retry_count < self.max_retries:
if self.logger:
self.logger.warning(f"请求失败,正在重试 ({retry_count + 1}/{self.max_retries}): {e}")
time.sleep(1 * (retry_count + 1))
return self.request(method, endpoint, headers, params, body, retry_count + 1)
else:
if self.logger:
self.logger.error(f"请求失败,重试次数用尽: {e}")
raise
def get(
self,
endpoint: str,
headers: Optional[Dict[str, str]] = None,
params: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""发送GET请求
Args:
endpoint: API端点
headers: 请求头
params: URL参数
Returns:
响应数据
"""
return self.request(HTTPMethod.GET, endpoint, headers, params)
def post(
self,
endpoint: str,
body: Optional[Dict[str, Any]] = None,
headers: Optional[Dict[str, str]] = None,
params: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""发送POST请求
Args:
endpoint: API端点
body: 请求体
headers: 请求头
params: URL参数
Returns:
响应数据
"""
return self.request(HTTPMethod.POST, endpoint, headers, params, body)
def put(
self,
endpoint: str,
body: Optional[Dict[str, Any]] = None,
headers: Optional[Dict[str, str]] = None,
params: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""发送PUT请求
Args:
endpoint: API端点
body: 请求体
headers: 请求头
params: URL参数
Returns:
响应数据
"""
return self.request(HTTPMethod.PUT, endpoint, headers, params, body)
def delete(
self,
endpoint: str,
headers: Optional[Dict[str, str]] = None,
params: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""发送DELETE请求
Args:
endpoint: API端点
headers: 请求头
params: URL参数
Returns:
响应数据
"""
return self.request(HTTPMethod.DELETE, endpoint, headers, params)
def close(self):
"""关闭会话"""
self._session.close()
if self.logger:
self.logger.debug("API客户端会话已关闭")
@@ -0,0 +1,201 @@
import json
import time
from typing import Optional, Dict, Any
from datetime import datetime, timedelta
from apitest.models.exceptions import AuthException
class AuthManager:
"""认证管理器"""
def __init__(self, base_url: str, credentials: Dict[str, str], logger):
"""初始化认证管理器
Args:
base_url: 基础URL
credentials: 认证凭据
logger: 日志记录器
"""
self.base_url = base_url.rstrip("/")
self.credentials = credentials
self.logger = logger
self._token: Optional[str] = None
self._token_expiry: Optional[datetime] = None
self._refresh_token: Optional[str] = None
self._login_endpoint: str = "/sys/auth/login"
def set_login_endpoint(self, endpoint: str):
"""设置登录端点
Args:
endpoint: 登录端点
"""
self._login_endpoint = endpoint
def login(self, login_endpoint: Optional[str] = None) -> Dict[str, Any]:
"""执行登录操作
Args:
login_endpoint: 登录端点,默认使用配置的端点
Returns:
登录响应数据
Raises:
AuthException: 登录失败时抛出
"""
endpoint = login_endpoint or self._login_endpoint
url = f"{self.base_url}{endpoint}"
self.logger.info(f"尝试登录: {url}")
try:
import requests
response = requests.post(
url,
json={
"username": self.credentials.get("username", ""),
"password": self.credentials.get("password", "")
},
timeout=10
)
if response.status_code == 200:
data = response.json()
if "data" in data and "token" in data["data"]:
self._token = data["data"]["token"]
self._refresh_token = data["data"].get("refreshToken")
expiry_seconds = data["data"].get("expiresIn", 3600)
self._token_expiry = datetime.now() + timedelta(seconds=expiry_seconds)
self.logger.info("登录成功")
return data
else:
raise AuthException("登录响应中未找到token")
else:
raise AuthException(f"登录失败: HTTP {response.status_code}")
except requests.RequestException as e:
raise AuthException(f"登录请求失败: {e}")
def get_token(self) -> Optional[str]:
"""获取当前token
Returns:
当前token,如果未登录则返回None
"""
return self._token
def set_token(self, token: str, expiry_seconds: int = 3600):
"""设置token
Args:
token: 认证令牌
expiry_seconds: 过期时间(秒),默认3600秒
"""
self._token = token
self._token_expiry = datetime.now() + timedelta(seconds=expiry_seconds)
self.logger.info("Token已设置")
def is_token_valid(self) -> bool:
"""检查token是否有效
Returns:
token是否有效
"""
if not self._token or not self._token_expiry:
return False
return datetime.now() < self._token_expiry
def refresh_token(self) -> bool:
"""刷新token
Returns:
刷新是否成功
"""
if not self._refresh_token:
self.logger.warning("没有可用的refresh token")
return False
try:
import requests
url = f"{self.base_url}/sys/auth/refresh"
response = requests.post(
url,
json={"refreshToken": self._refresh_token},
timeout=10
)
if response.status_code == 200:
data = response.json()
if "data" in data and "token" in data["data"]:
self._token = data["data"]["token"]
self._refresh_token = data["data"].get("refreshToken")
expiry_seconds = data["data"].get("expiresIn", 3600)
self._token_expiry = datetime.now() + timedelta(seconds=expiry_seconds)
self.logger.info("Token刷新成功")
return True
self.logger.warning("Token刷新失败,尝试重新登录")
return False
except Exception as e:
self.logger.error(f"Token刷新异常: {e}")
return False
def ensure_authenticated(self) -> str:
"""确保已认证,如果token过期则自动刷新或重新登录
Returns:
有效的token
Raises:
AuthException: 认证失败时抛出
"""
if not self._token:
self.login()
elif not self.is_token_valid():
if not self.refresh_token():
self.login()
if not self._token:
raise AuthException("无法获取有效的认证token")
return self._token
def logout(self):
"""登出"""
self._token = None
self._refresh_token = None
self._token_expiry = None
self.logger.info("已登出")
def get_auth_headers(self) -> Dict[str, str]:
"""获取认证请求头
Returns:
包含认证信息的请求头字典
"""
token = self.ensure_authenticated()
return {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
def set_credentials(self, username: str, password: str):
"""设置认证凭据
Args:
username: 用户名
password: 密码
"""
self.credentials = {"username": username, "password": password}
self.logger.info("认证凭据已更新")