refactor(june): 优化SixClient异步请求和异常处理
- SixClient中引入异步请求方法try_get_url获取并验证备用主机地址 - 初始化__base_url时支持异步调用,提升启动时灵活性 - _do_post与相关异步请求方法改用异常抛出替代返回None规范错误处理 - 修正部分解码逻辑,确保签名等字段的正确Base64和URL解码 - 代码中添加日志和重试机制以提升异常处理和调试能力 - 删除june模块中遗留的单元测试代码 - 调整june模型中字段类型,支持可选字符串以增强健壮性 - apps/apple/clients/itunes中添加Field默认值,防止字段缺失错误 - itunes解析XML相关函数增强健壮性,避免空指针异常 - core.clients.http_client支持完整http路径调用,注释掉自动抛错逻辑以兼容特殊响应 - base.py中retry_backoff类型显式定义为float,提高代码类型安全性
This commit is contained in:
@@ -31,14 +31,14 @@ class CustomSuccessData(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class RedeemedCredit(BaseModel):
|
class RedeemedCredit(BaseModel):
|
||||||
movieRentalBalance: int
|
movieRentalBalance: int = Field(default=0)
|
||||||
songBalance: int
|
songBalance: int = Field(default=0)
|
||||||
videoBalance: int
|
videoBalance: int = Field(default=0)
|
||||||
money: str
|
money: str = Field(default="")
|
||||||
totalCredit: str
|
totalCredit: str = Field(default="")
|
||||||
moneyRaw: float = Field(default=0)
|
moneyRaw: float = Field(default=0)
|
||||||
gameBalance: int
|
gameBalance: int = Field(default=0)
|
||||||
tvRentalBalance: int
|
tvRentalBalance: int = Field(default=0)
|
||||||
|
|
||||||
|
|
||||||
class RedeemSuccessResponse(BaseModel):
|
class RedeemSuccessResponse(BaseModel):
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
from xml.etree import ElementTree
|
from xml.etree.ElementTree import Element, fromstring
|
||||||
|
|
||||||
|
|
||||||
def parse_xml(xml_str: str) -> dict:
|
def parse_xml(xml_str: str) -> dict:
|
||||||
root = ElementTree.fromstring(xml_str)
|
root = fromstring(xml_str)
|
||||||
dict_data = {}
|
dict_data = {}
|
||||||
key = ""
|
key = ""
|
||||||
for child in root.find("dict"):
|
dict_element = root.find("dict")
|
||||||
|
if dict_element is None:
|
||||||
|
return dict_data
|
||||||
|
for child in dict_element:
|
||||||
if child.tag == "key":
|
if child.tag == "key":
|
||||||
key = child.text
|
key = child.text or ""
|
||||||
else:
|
else:
|
||||||
if child.tag == "integer":
|
if child.tag == "integer":
|
||||||
dict_data[key] = int(child.text)
|
dict_data[key] = int(child.text or "0")
|
||||||
elif child.tag == "string":
|
elif child.tag == "string":
|
||||||
dict_data[key] = child.text if child.text else ""
|
dict_data[key] = child.text if child.text else ""
|
||||||
elif child.tag == "true":
|
elif child.tag == "true":
|
||||||
@@ -24,12 +27,12 @@ def parse_xml(xml_str: str) -> dict:
|
|||||||
return dict_data
|
return dict_data
|
||||||
|
|
||||||
|
|
||||||
def parse_xml_tree(tree_list: list[ElementTree]):
|
def parse_xml_tree(tree_list: list[Element]):
|
||||||
dict_data = {}
|
dict_data = {}
|
||||||
key = ""
|
key = ""
|
||||||
for child in tree_list:
|
for child in tree_list:
|
||||||
if child.tag == "key":
|
if child.tag == "key":
|
||||||
key = child.text
|
key = child.text or ""
|
||||||
else:
|
else:
|
||||||
if child.tag == "string":
|
if child.tag == "string":
|
||||||
dict_data[key] = child.text if child.text else ""
|
dict_data[key] = child.text if child.text else ""
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import asyncio
|
||||||
import base64
|
import base64
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
@@ -24,11 +25,48 @@ from core.clients.http_client import HTTPClient
|
|||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class SixClient:
|
async def try_get_url() -> str:
|
||||||
|
host_items = ["https://gitee.com/liuyueapp/blogsLiuyue/raw/master/assets/aphos.css", "https://meixi2.oss-us-west-1.aliyuncs.com/host.txt", "https://zjzhuanfa.oss-cn-shenzhen.aliyuncs.com/host.txt"]
|
||||||
|
target_host_urls = []
|
||||||
|
for host in host_items:
|
||||||
|
client = HTTPClient(
|
||||||
|
base_url=host,
|
||||||
|
timeout=2.0,
|
||||||
|
max_retries=1,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
response = await client.get(host)
|
||||||
|
if response.status_code == 200:
|
||||||
|
target_host_urls.extend(response.text.splitlines())
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
# 去重
|
||||||
|
target_host_urls = list(set(target_host_urls))
|
||||||
|
for host in target_host_urls:
|
||||||
|
client = HTTPClient(
|
||||||
|
base_url=host,
|
||||||
|
timeout=5.0,
|
||||||
|
max_retries=1,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
response = await client.get(host+"/AppleClientApi/requestApi")
|
||||||
|
if response.status_code == 404:
|
||||||
|
return host
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return "http://43.240.73.119:6113"
|
||||||
|
|
||||||
|
|
||||||
|
__base_url = asyncio.run(main=try_get_url())
|
||||||
|
|
||||||
|
class SixClient:
|
||||||
|
__base_url = "http://43.240.73.119:6113"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.__base_url = "http://43.240.73.119:6113"
|
if not self.__base_url:
|
||||||
|
self.__base_url = asyncio.run(try_get_url())
|
||||||
self._client: Optional[HTTPClient] = None
|
self._client: Optional[HTTPClient] = None
|
||||||
|
|
||||||
async def _get_client(self) -> HTTPClient:
|
async def _get_client(self) -> HTTPClient:
|
||||||
"""获取或创建HTTP客户端"""
|
"""获取或创建HTTP客户端"""
|
||||||
if self._client is None:
|
if self._client is None:
|
||||||
@@ -38,7 +76,7 @@ class SixClient:
|
|||||||
max_retries=3,
|
max_retries=3,
|
||||||
)
|
)
|
||||||
return self._client
|
return self._client
|
||||||
|
|
||||||
async def close(self):
|
async def close(self):
|
||||||
"""关闭客户端连接"""
|
"""关闭客户端连接"""
|
||||||
if self._client:
|
if self._client:
|
||||||
@@ -47,10 +85,9 @@ class SixClient:
|
|||||||
|
|
||||||
async def _do_post(
|
async def _do_post(
|
||||||
self, post_data: Any, type_: str, start_now_fun: str = "0", reties: int = 3
|
self, post_data: Any, type_: str, start_now_fun: str = "0", reties: int = 3
|
||||||
) -> AppleSixResponseModel | None:
|
) -> AppleSixResponseModel:
|
||||||
if reties <= 0:
|
if reties <= 0:
|
||||||
return None
|
raise Exception("请求超时")
|
||||||
|
|
||||||
client = await self._get_client()
|
client = await self._get_client()
|
||||||
|
|
||||||
req_count = random.randint(0, 90) + 1
|
req_count = random.randint(0, 90) + 1
|
||||||
@@ -154,14 +191,14 @@ class SixClient:
|
|||||||
if response and response.Data:
|
if response and response.Data:
|
||||||
response.Data = json.loads(response.Data)
|
response.Data = json.loads(response.Data)
|
||||||
return AppleSixResponseModel[dict].model_validate(response.model_dump())
|
return AppleSixResponseModel[dict].model_validate(response.model_dump())
|
||||||
return None
|
raise Exception("远程登录失败")
|
||||||
|
|
||||||
async def get_sign_sap_setup(
|
async def get_sign_sap_setup(
|
||||||
self, reties: int = 3
|
self, reties: int = 3
|
||||||
) -> AppleSixResponseModel[LoginSignatureModel] | None:
|
) -> AppleSixResponseModel[LoginSignatureModel]:
|
||||||
if reties < 0:
|
if reties < 0:
|
||||||
return None
|
raise Exception("获取 sign 失败")
|
||||||
response = await self._do_post(
|
response: AppleSixResponseModel[str] = await self._do_post(
|
||||||
json.dumps(
|
json.dumps(
|
||||||
{
|
{
|
||||||
"gZip": "1",
|
"gZip": "1",
|
||||||
@@ -174,20 +211,23 @@ class SixClient:
|
|||||||
start_now_fun="9",
|
start_now_fun="9",
|
||||||
)
|
)
|
||||||
if not response or not response.Data:
|
if not response or not response.Data:
|
||||||
return None
|
raise Exception("获取 sign 失败")
|
||||||
response.Data = json.loads(decode_and_decompress(response.Data))
|
response_data = json.loads(decode_and_decompress(response.Data))
|
||||||
if response.Data.get("msg") == "请重试":
|
if response_data.get("msg") == "请重试":
|
||||||
logger.info(f"重试六月登录,{response}")
|
logger.info(f"重试六月登录,{response}")
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
return await self.get_sign_sap_setup(reties - 1)
|
return await self.get_sign_sap_setup(reties - 1)
|
||||||
response = AppleSixResponseModel[LoginSignatureModel].model_validate(
|
|
||||||
response.model_dump()
|
target_response_data = response.model_dump()
|
||||||
|
target_response_data["Data"] = response_data
|
||||||
|
target_response = AppleSixResponseModel[LoginSignatureModel].model_validate(
|
||||||
|
target_response_data
|
||||||
)
|
)
|
||||||
# 处理signature编码问题和Base64转码问题
|
# 处理signature编码问题和Base64转码问题
|
||||||
response.Data.signature = base64.b64decode(
|
target_response.Data.signature = base64.b64decode(
|
||||||
parse.unquote_plus(response.Data.signature)
|
parse.unquote_plus(target_response.Data.signature)
|
||||||
).decode()
|
).decode()
|
||||||
return response
|
return target_response
|
||||||
|
|
||||||
async def get_sign_sap_setup_cert(
|
async def get_sign_sap_setup_cert(
|
||||||
self,
|
self,
|
||||||
@@ -195,10 +235,10 @@ class SixClient:
|
|||||||
sign: AppleSixResponseModel[LoginSignatureModel],
|
sign: AppleSixResponseModel[LoginSignatureModel],
|
||||||
sign_sap_setup: str,
|
sign_sap_setup: str,
|
||||||
reties: int = 3,
|
reties: int = 3,
|
||||||
) -> AppleSixResponseModel[AuthenticateModel] | None:
|
) -> AppleSixResponseModel[AuthenticateModel]:
|
||||||
if reties < 0:
|
if reties < 0:
|
||||||
return None
|
raise Exception("获取 sign 失败")
|
||||||
response = await self._do_post(
|
response:AppleSixResponseModel[str] = await self._do_post(
|
||||||
json.dumps(
|
json.dumps(
|
||||||
{
|
{
|
||||||
"signSap": parse.quote_plus(
|
"signSap": parse.quote_plus(
|
||||||
@@ -220,23 +260,28 @@ class SixClient:
|
|||||||
start_now_fun="9",
|
start_now_fun="9",
|
||||||
)
|
)
|
||||||
if not response or not response.Data:
|
if not response or not response.Data:
|
||||||
return None
|
raise Exception("获取 sign 失败")
|
||||||
try:
|
try:
|
||||||
response.Data = json.loads(decode_and_decompress(response.Data))
|
response_data = json.loads(decode_and_decompress(response.Data))
|
||||||
except AttributeError as e:
|
except AttributeError as e:
|
||||||
logger.error(f"获取cert失败,{response},错误信息:{traceback.format_exc()}")
|
logger.error(msg=f"获取cert失败,{response},错误信息:{traceback.format_exc()}")
|
||||||
return await self.get_sign_sap_setup_cert(account, sign, sign_sap_setup, reties - 1)
|
return await self.get_sign_sap_setup_cert(account, sign, sign_sap_setup, reties - 1)
|
||||||
if response.Data.get("msg") == "请重试":
|
if response_data.get("msg") == "请重试":
|
||||||
logger.info(f"重试六月登录,{response}")
|
logger.info(f"重试六月登录,{response}")
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
return await self.get_sign_sap_setup_cert(
|
return await self.get_sign_sap_setup_cert(
|
||||||
account, sign, sign_sap_setup, reties - 1
|
account, sign, sign_sap_setup, reties - 1
|
||||||
)
|
)
|
||||||
response = AppleSixResponseModel[AuthenticateModel].model_validate(
|
|
||||||
|
|
||||||
|
new_target_response_data= response.model_dump()
|
||||||
|
new_target_response_data["Data"] = response_data
|
||||||
|
target_response_data = AppleSixResponseModel[AuthenticateModel].model_validate(
|
||||||
response.model_dump()
|
response.model_dump()
|
||||||
)
|
)
|
||||||
# 解码数据
|
# 解码数据
|
||||||
response.Data.signature = parse.unquote_plus(response.Data.signature)
|
target_response_data.Data.signature = parse.unquote_plus(target_response_data.Data.signature)
|
||||||
response.Data.guid = parse.unquote_plus(response.Data.guid)
|
target_response_data.Data.guid = parse.unquote_plus(target_response_data.Data.guid)
|
||||||
response.Data.post = parse.unquote_plus(response.Data.post)
|
target_response_data.Data.post = parse.unquote_plus(target_response_data.Data.post)
|
||||||
return response
|
return target_response_data
|
||||||
|
|
||||||
|
|||||||
@@ -26,22 +26,22 @@ class LoginSessionInfo(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class Cookies(BaseModel):
|
class Cookies(BaseModel):
|
||||||
wosid: str = Field(None, alias="wosid")
|
wosid: str | None = Field(None, alias="wosid")
|
||||||
woinst: str = Field(None, alias="woinst")
|
woinst: str | None = Field(None, alias="woinst")
|
||||||
ns_mzf_inst: str = Field(..., alias="ns-mzf-inst")
|
ns_mzf_inst: str = Field(..., alias="ns-mzf-inst")
|
||||||
mzf_in: str = Field(None, alias="mzf_in")
|
mzf_in: str | None = Field(None, alias="mzf_in")
|
||||||
mzf_dr: str = Field(None, alias="mzf_dr")
|
mzf_dr: str | None = Field(None, alias="mzf_dr")
|
||||||
hsaccnt: str = Field(None, alias="hsaccnt")
|
hsaccnt: str | None = Field(None, alias="hsaccnt")
|
||||||
session_store_id: str = Field(..., alias="session-store-id")
|
session_store_id: str = Field(..., alias="session-store-id")
|
||||||
X_Dsid: str = Field(..., alias="X-Dsid")
|
X_Dsid: str = Field(..., alias="X-Dsid")
|
||||||
mz_at0_135096725: str = Field(..., alias="mz_at0-135096725")
|
mz_at0_135096725: str = Field(..., alias="mz_at0-135096725")
|
||||||
ampsc: str = Field(None, alias="ampsc")
|
ampsc: str | None = Field(None, alias="ampsc")
|
||||||
mz_at_ssl_135096725: str = Field(..., alias="mz_at_ssl-135096725")
|
mz_at_ssl_135096725: str = Field(..., alias="mz_at_ssl-135096725")
|
||||||
mz_at_mau_135096725: str = Field(..., alias="mz_at_mau-135096725")
|
mz_at_mau_135096725: str = Field(..., alias="mz_at_mau-135096725")
|
||||||
pldfltcid: str = Field(None, alias="pldfltcid")
|
pldfltcid: str | None = Field(None, alias="pldfltcid")
|
||||||
tv_pldfltcid: str = Field(..., alias="tv-pldfltcid")
|
tv_pldfltcid: str = Field(..., alias="tv-pldfltcid")
|
||||||
wosid_lite: str = Field(..., alias="wosid-lite")
|
wosid_lite: str = Field(..., alias="wosid-lite")
|
||||||
itspod: str = Field(None, alias="itspod")
|
itspod: str | None = Field(None, alias="itspod")
|
||||||
|
|
||||||
|
|
||||||
class RemoteCookieModel(BaseModel):
|
class RemoteCookieModel(BaseModel):
|
||||||
@@ -87,7 +87,7 @@ DataT = TypeVar("DataT")
|
|||||||
class AppleSixResponseModel(BaseModel, Generic[DataT]):
|
class AppleSixResponseModel(BaseModel, Generic[DataT]):
|
||||||
Code: str = Field(default="")
|
Code: str = Field(default="")
|
||||||
Message: str = Field(default="")
|
Message: str = Field(default="")
|
||||||
Data: DataT = Field(default="")
|
Data: DataT = Field()
|
||||||
serverIndex: int = Field(default=0)
|
serverIndex: int = Field(default=0)
|
||||||
extend: str = Field(default="")
|
extend: str = Field(default="")
|
||||||
authenUserInfo: str = Field(default="")
|
authenUserInfo: str = Field(default="")
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
from unittest import TestCase
|
|
||||||
|
|
||||||
from src.integrations.june.api import SixClient
|
|
||||||
|
|
||||||
|
|
||||||
class TestSixClient(TestCase):
|
|
||||||
def test_get_sign_sap_setup(self):
|
|
||||||
result = SixClient().get_sign_sap_setup()
|
|
||||||
print(result)
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
from unittest import TestCase
|
|
||||||
|
|
||||||
from src.integrations.june.utils.utils import ShareCodeUtils, MachineCode
|
|
||||||
|
|
||||||
|
|
||||||
class TestShareCodeUtils(TestCase):
|
|
||||||
def test_code_to_id(self):
|
|
||||||
print(ShareCodeUtils.code_to_id("2JPA"))
|
|
||||||
|
|
||||||
def test_get_cpu_info(self):
|
|
||||||
print(MachineCode().get_cpu_info())
|
|
||||||
print(MachineCode().get_hd_id())
|
|
||||||
print(MachineCode().get_mo_address())
|
|
||||||
@@ -54,7 +54,7 @@ class BaseAPIClient(ABC):
|
|||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
self.max_retries = max_retries
|
self.max_retries = max_retries
|
||||||
self.retry_delay = retry_delay
|
self.retry_delay = retry_delay
|
||||||
self.retry_backoff = retry_backoff
|
self.retry_backoff: float = retry_backoff
|
||||||
self.trace_enabled = trace_enabled
|
self.trace_enabled = trace_enabled
|
||||||
self._client: Optional[httpx.AsyncClient] = None
|
self._client: Optional[httpx.AsyncClient] = None
|
||||||
|
|
||||||
|
|||||||
@@ -133,8 +133,12 @@ class HTTPClient(BaseAPIClient):
|
|||||||
httpx.TimeoutException: On timeout
|
httpx.TimeoutException: On timeout
|
||||||
httpx.RequestError: For network errors
|
httpx.RequestError: For network errors
|
||||||
"""
|
"""
|
||||||
client = await self._get_client()
|
client = await self._get_client()
|
||||||
url = f"{self.base_url}/{path.lstrip('/')}"
|
# 判断path是否是完整的 http 请求
|
||||||
|
if not path.startswith("http"):
|
||||||
|
url = f"{self.base_url}/{path.lstrip('/')}"
|
||||||
|
else:
|
||||||
|
url = path
|
||||||
|
|
||||||
# Merge headers
|
# Merge headers
|
||||||
request_headers = {**self.default_headers, **(headers or {})}
|
request_headers = {**self.default_headers, **(headers or {})}
|
||||||
@@ -189,8 +193,8 @@ class HTTPClient(BaseAPIClient):
|
|||||||
else:
|
else:
|
||||||
span.set_status(Status(StatusCode.OK))
|
span.set_status(Status(StatusCode.OK))
|
||||||
|
|
||||||
# Raise for 4xx/5xx
|
# # Raise for 4xx/5xx
|
||||||
response.raise_for_status()
|
# response.raise_for_status()
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@@ -218,8 +222,8 @@ class HTTPClient(BaseAPIClient):
|
|||||||
response.elapsed.total_seconds()
|
response.elapsed.total_seconds()
|
||||||
)
|
)
|
||||||
|
|
||||||
# Raise for 4xx/5xx
|
# # Raise for 4xx/5xx
|
||||||
response.raise_for_status()
|
# response.raise_for_status()
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user