Files
LoveACE-EndF/provider/aufe/aac/__init__.py

288 lines
10 KiB
Python
Raw Normal View History

2025-08-03 16:50:56 +08:00
from typing import Optional
from urllib.parse import unquote
from loguru import logger
from provider.aufe.aac.model import (
LoveACScoreInfo,
LoveACScoreInfoResponse,
LoveACScoreListResponse,
SimpleResponse,
ErrorLoveACScoreInfo,
ErrorLoveACScoreInfoResponse,
ErrorLoveACScoreListResponse,
ErrorLoveACScoreCategory,
)
from provider.aufe.client import (
AUFEConnection,
AUFEConfig,
activity_tracker,
retry_async,
AUFEConnectionError,
AUFELoginError,
AUFEParseError,
RetryConfig
)
class AACConfig:
"""AAC 模块配置常量"""
BASE_URL = "http://api-dekt-ac-acxk-net.vpn2.aufe.edu.cn:8118"
WEB_URL = "http://dekt-ac-acxk-net.vpn2.aufe.edu.cn:8118"
LOGIN_SERVICE_URL = "http://uaap-aufe-edu-cn.vpn2.aufe.edu.cn:8118/cas/login?service=http%3a%2f%2fapi.dekt.ac.acxk.net%2fUser%2fIndex%2fCoreLoginCallback%3fisCASGateway%3dtrue"
@retry_async()
async def get_system_token(vpn_connection: AUFEConnection) -> Optional[str]:
"""
获取系统令牌 (sys_token)
Args:
vpn_connection: VPN连接实例
Returns:
Optional[str]: 系统令牌失败时返回None
Raises:
AUFEConnectionError: 连接失败
AUFEParseError: 令牌解析失败
"""
try:
next_location = AACConfig.LOGIN_SERVICE_URL
max_redirects = 10 # 防止无限重定向
redirect_count = 0
while redirect_count < max_redirects:
response = await vpn_connection.requester().get(
next_location, follow_redirects=False
)
# 如果是重定向,继续跟踪
if response.status_code in (301, 302, 303, 307, 308):
next_location = response.headers.get("Location")
if not next_location:
raise AUFEParseError("重定向响应中缺少Location头")
logger.debug(f"重定向到: {next_location}")
redirect_count += 1
if "register?ticket=" in next_location:
logger.info(f"重定向到爱安财注册页面: {next_location}")
try:
sys_token = next_location.split("ticket=")[-1]
# URL编码转为正常字符串
sys_token = unquote(sys_token)
if sys_token:
logger.info(f"获取到系统令牌: {sys_token[:10]}...")
return sys_token
else:
raise AUFEParseError("提取的系统令牌为空")
except Exception as e:
raise AUFEParseError(f"解析系统令牌失败: {str(e)}") from e
else:
break
if redirect_count >= max_redirects:
raise AUFEConnectionError(f"重定向次数过多 ({max_redirects})")
raise AUFEParseError("未能从重定向中获取到系统令牌")
except (AUFEConnectionError, AUFEParseError):
raise
except Exception as e:
logger.error(f"获取系统令牌异常: {str(e)}")
raise AUFEConnectionError(f"获取系统令牌失败: {str(e)}") from e
class AACClient:
"""爱安财系统客户端"""
def __init__(
self,
vpn_connection: AUFEConnection,
ticket: Optional[str] = None,
retry_config: Optional[RetryConfig] = None
):
"""
初始化爱安财系统客户端
Args:
vpn_connection: VPN连接实例
ticket: 系统令牌
retry_config: 重试配置
"""
self.vpn_connection = vpn_connection
self.base_url = AACConfig.BASE_URL.rstrip("/")
self.web_url = AACConfig.WEB_URL.rstrip("/")
self.twfid = vpn_connection.get_twfid()
self.system_token: Optional[str] = ticket
self.retry_config = retry_config or RetryConfig()
logger.info(
f"爱安财系统客户端初始化: base_url={self.base_url}, web_url={self.web_url}"
)
def _get_default_headers(self) -> dict:
"""获取默认请求头"""
return {
**AUFEConfig.DEFAULT_HEADERS,
"ticket": self.system_token or "",
"sdp-app-session": self.twfid or "",
}
@activity_tracker
@retry_async()
async def validate_connection(self) -> bool:
"""
验证爱安财系统连接
Returns:
bool: 连接是否有效
Raises:
AUFEConnectionError: 连接失败
"""
try:
headers = AUFEConfig.DEFAULT_HEADERS.copy()
response = await self.vpn_connection.requester().get(
f"{self.web_url}/", headers=headers
)
is_valid = response.status_code == 200
logger.info(
f"爱安财系统连接验证结果: {'有效' if is_valid else '无效'} (HTTP状态码: {response.status_code})"
)
if not is_valid:
raise AUFEConnectionError(f"爱安财系统连接验证失败,状态码: {response.status_code}")
return is_valid
except AUFEConnectionError:
raise
except Exception as e:
logger.error(f"验证爱安财系统连接异常: {str(e)}")
raise AUFEConnectionError(f"验证连接失败: {str(e)}") from e
@activity_tracker
async def fetch_score_info(self) -> LoveACScoreInfo:
"""
获取爱安财总分信息使用重试机制
Returns:
LoveACScoreInfo: 总分信息失败时返回错误模型
"""
try:
logger.info("开始获取爱安财总分信息")
headers = self._get_default_headers()
# 使用新的重试机制
score_response = await self.vpn_connection.model_request(
model=LoveACScoreInfoResponse,
url=f"{self.base_url}/User/Center/DoGetScoreInfo?sf_request_type=ajax",
method="POST",
headers=headers,
data={}, # 空的POST请求体
follow_redirects=True,
)
if score_response and score_response.code == 0 and score_response.data:
logger.info(
f"爱安财总分信息获取成功: {score_response.data.total_score}"
)
return score_response.data
else:
error_msg = score_response.msg if score_response else '未知错误'
logger.error(f"获取爱安财总分信息失败: {error_msg}")
# 返回错误模型
return ErrorLoveACScoreInfo(
TotalScore=-1.0,
IsTypeAdopt=False,
TypeAdoptResult=f"请求失败: {error_msg}",
)
except (AUFEConnectionError, AUFEParseError) as e:
logger.error(f"获取爱安财总分信息失败: {str(e)}")
return ErrorLoveACScoreInfo(
TotalScore=-1.0,
IsTypeAdopt=False,
TypeAdoptResult=f"请求失败: {str(e)}",
)
except Exception as e:
logger.error(f"获取爱安财总分信息异常: {str(e)}")
# 返回错误模型
return ErrorLoveACScoreInfo(
TotalScore=-1.0,
IsTypeAdopt=False,
TypeAdoptResult="系统错误,请稍后重试",
)
@activity_tracker
async def fetch_score_list(
self, page_index: int = 1, page_size: int = 10
) -> LoveACScoreListResponse:
"""
获取爱安财分数列表使用重试机制
Args:
page_index: 页码默认为1
page_size: 每页大小默认为10
Returns:
LoveACScoreListResponse: 分数列表响应失败时返回错误模型
"""
def _create_error_response(error_msg: str) -> ErrorLoveACScoreListResponse:
"""创建错误响应模型"""
return ErrorLoveACScoreListResponse(
code=-1,
msg=error_msg,
data=[
ErrorLoveACScoreCategory(
ID="error",
ShowNum=-1,
TypeName="请求失败",
TotalScore=-1.0,
children=[],
)
],
)
try:
logger.info(
f"开始获取爱安财分数列表,页码: {page_index}, 每页大小: {page_size}"
)
headers = self._get_default_headers()
data = {"pageIndex": str(page_index), "pageSize": str(page_size)}
# 使用新的重试机制
score_list_response = await self.vpn_connection.model_request(
model=LoveACScoreListResponse,
url=f"{self.base_url}/User/Center/DoGetScoreList?sf_request_type=ajax",
method="POST",
headers=headers,
data=data,
follow_redirects=True,
)
if (
score_list_response
and score_list_response.code == 0
and score_list_response.data
):
logger.info(
f"爱安财分数列表获取成功,分类数量: {len(score_list_response.data)}"
)
return score_list_response
else:
error_msg = score_list_response.msg if score_list_response else '未知错误'
logger.error(f"获取爱安财分数列表失败: {error_msg}")
return _create_error_response(f"请求失败: {error_msg}")
except (AUFEConnectionError, AUFEParseError) as e:
logger.error(f"获取爱安财分数列表失败: {str(e)}")
return _create_error_response(f"请求失败: {str(e)}")
except Exception as e:
logger.error(f"获取爱安财分数列表异常: {str(e)}")
return _create_error_response("系统错误,已进行多次重试")