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