Files
LoveACE-EndF/provider/aufe/aac/__init__.py
2025-08-03 16:50:56 +08:00

288 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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("系统错误,已进行多次重试")