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