fix: 优化清理失效账号执行响应的 success 标志,对 test_account/delete_account 引入外部 API 日志审计,并对账号 ID 进行安全转义

This commit is contained in:
liumangmang
2026-07-02 00:25:24 +08:00
parent 536c0aa3d4
commit 3b23f21ef3
3 changed files with 134 additions and 63 deletions
+84 -53
View File
@@ -363,75 +363,106 @@ class Sub2ApiWebsiteClient:
若 timeout / 无法建立连接,返回 {"status": "timeout"/"error", "cleanup_allowed": False}
"""
import json
url = self._url(f"{endpoint}/{account_id}/test")
quoted_id = quote(account_id, safe="")
path = f"{endpoint}/{quoted_id}/test"
url = self._url(path)
headers = self._headers()
# Accept text/event-stream since it's an SSE stream
headers["Accept"] = "text/event-stream"
started = time.monotonic()
status_code: int | None = None
error_type: str | None = None
error_msg: str | None = None
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}"}
try:
with self._client.stream("POST", url, headers=headers) as response:
status_code = response.status_code
if response.status_code == 404:
return {"status": "404", "cleanup_allowed": True, "message": "账号在远端不存在 (404)"}
if response.status_code >= 400:
response.raise_for_status()
# Read SSE stream line by line
current_event = None
success_value = None
has_error_event = False
# 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
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}"}
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:
error_type = "WebsiteError"
error_msg = "测试接口返回了错误事件"
return {"status": "error", "cleanup_allowed": False, "message": "测试接口返回了错误事件"}
else:
error_type = "WebsiteError"
error_msg = "未收到有效的测试完成事件"
return {"status": "invalid_sse", "cleanup_allowed": False, "message": "未收到有效的测试完成事件"}
except httpx.TimeoutException as exc:
error_type, error_msg = type(exc).__name__, str(exc)[:500]
return {"status": "timeout", "cleanup_allowed": False, "message": f"连接超时: {exc}"}
except httpx.HTTPStatusError as exc:
error_type, error_msg = type(exc).__name__, str(exc)[:500]
status_code = exc.response.status_code
return {"status": "http_error", "cleanup_allowed": False, "message": f"测试接口返回 HTTP {status_code}"}
except Exception as exc:
error_type, error_msg = type(exc).__name__, str(exc)[:500]
return {"status": "error", "cleanup_allowed": False, "message": f"请求异常: {exc}"}
finally:
elapsed = int((time.monotonic() - started) * 1000)
parsed = urlparse(url)
log_external_api_call(
direction="website",
target_type="website",
method="POST",
path=parsed.path or path,
url_host=parsed.hostname or "",
duration_ms=elapsed,
status_code=status_code,
success=error_type is None,
target_id=self.target_id,
target_name=self.target_name,
error_type=error_type,
error_message=error_msg,
)
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()
quoted_id = quote(account_id, safe="")
path = f"{endpoint}/{quoted_id}"
try:
resp = self._client.request("DELETE", url, headers=headers)
if resp.status_code == 404:
return {"status": "already_deleted", "message": "账号不存在或已在远端被删除"}
resp.raise_for_status()
self._request("DELETE", path)
return {"status": "deleted", "message": "账号已删除"}
except httpx.HTTPStatusError as exc:
if exc.response.status_code == 404:
except WebsiteError as exc:
cause = exc.__cause__
if isinstance(cause, httpx.HTTPStatusError) and cause.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
raise
@staticmethod
def extract_id(value: Any) -> str: