feat: 实现清理失效账号安全方案(包含预览、SSE测试、安全删除以及本地标记清空)

This commit is contained in:
liumangmang
2026-07-02 00:19:26 +08:00
parent 1ef6188c02
commit 536c0aa3d4
6 changed files with 798 additions and 3 deletions
+77 -1
View File
@@ -345,7 +345,7 @@ class Sub2ApiWebsiteClient:
def account_exists(self, account_id: str, endpoint: str = "/accounts") -> bool | None:
"""检查目标账号是否存在。
优先拉取账号列表判断:
- 列表成功取到 → return account_id in idsTrue=存在,False=已删除)
- 列表取不到(None)→ return None(校验失败,不清本地)
@@ -357,6 +357,82 @@ class Sub2ApiWebsiteClient:
return None
return account_id in ids
def test_account(self, account_id: str, endpoint: str = "/accounts") -> dict[str, Any]:
"""测试远端账号可用性。利用 SSE 监听 test_complete 事件的 success 状态。
若 404,返回 {"status": "404", "cleanup_allowed": True, "message": "账号不存在"}
若 timeout / 无法建立连接,返回 {"status": "timeout"/"error", "cleanup_allowed": False}
"""
import json
url = self._url(f"{endpoint}/{account_id}/test")
headers = self._headers()
# Accept text/event-stream since it's an SSE stream
headers["Accept"] = "text/event-stream"
try:
with self._client.stream("POST", url, headers=headers) as response:
if response.status_code == 404:
return {"status": "404", "cleanup_allowed": True, "message": "账号在远端不存在 (404)"}
if response.status_code >= 400:
return {"status": "http_error", "cleanup_allowed": False, "message": f"测试接口返回 HTTP {response.status_code}"}
# Read SSE stream line by line
current_event = None
success_value = None
has_error_event = False
for line in response.iter_lines():
if not line:
continue
if line.startswith("event:"):
current_event = line[6:].strip()
elif line.startswith("data:"):
data_str = line[5:].strip()
if current_event == "error":
has_error_event = True
if current_event == "test_complete" or not current_event:
try:
parsed = json.loads(data_str)
if isinstance(parsed, dict):
if "success" in parsed:
success_value = parsed["success"]
elif parsed.get("event") == "test_complete" and "success" in parsed:
success_value = parsed["success"]
except Exception:
pass
if success_value is True:
return {"status": "success", "cleanup_allowed": False, "message": "测试成功,账号有效"}
elif success_value is False:
return {"status": "failed", "cleanup_allowed": True, "message": "测试失败,账号失效"}
elif has_error_event:
return {"status": "error", "cleanup_allowed": False, "message": "测试接口返回了错误事件"}
else:
return {"status": "invalid_sse", "cleanup_allowed": False, "message": "未收到有效的测试完成事件"}
except httpx.TimeoutException as exc:
return {"status": "timeout", "cleanup_allowed": False, "message": f"连接超时: {exc}"}
except Exception as exc:
return {"status": "error", "cleanup_allowed": False, "message": f"请求异常: {exc}"}
def delete_account(self, account_id: str, endpoint: str = "/accounts") -> dict[str, Any]:
"""删除目标账号。
若 404 视为 already_deleted。
"""
url = self._url(f"{endpoint}/{account_id}")
headers = self._headers()
try:
resp = self._client.request("DELETE", url, headers=headers)
if resp.status_code == 404:
return {"status": "already_deleted", "message": "账号不存在或已在远端被删除"}
resp.raise_for_status()
return {"status": "deleted", "message": "账号已删除"}
except httpx.HTTPStatusError as exc:
if exc.response.status_code == 404:
return {"status": "already_deleted", "message": "账号不存在或已在远端被删除"}
raise WebsiteError(_friendly_http_error(exc)) from exc
except Exception as exc:
raise WebsiteError(f"删除账号失败: {exc}") from exc
@staticmethod
def extract_id(value: Any) -> str:
return _extract_id(value)