fix(weibo): 识别支付域登录失效
This commit is contained in:
+12
-2
@@ -15,6 +15,7 @@ import json
|
||||
import secrets
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from html import unescape
|
||||
from html.parser import HTMLParser
|
||||
from typing import Any
|
||||
from urllib.parse import parse_qs, urljoin, urlsplit
|
||||
@@ -321,6 +322,7 @@ class WeiboClient:
|
||||
data: dict[str, Any] | None = None,
|
||||
json: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
response: requests.Response | None = None
|
||||
try:
|
||||
response = self.session.request(
|
||||
method,
|
||||
@@ -333,10 +335,18 @@ class WeiboClient:
|
||||
)
|
||||
response.raise_for_status()
|
||||
payload = response.json()
|
||||
except (requests.exceptions.JSONDecodeError, ValueError) as exc:
|
||||
text = unescape(response.text if response is not None else "")
|
||||
if "retcode=6102" in text or "/sso/login.php" in text:
|
||||
raise WeiboAuthError(
|
||||
"微博支付/活动域登录态已失效(retcode=6102),"
|
||||
"请重新登录微博后重新导入 CK"
|
||||
) from exc
|
||||
path = urlsplit(response.url).path if response is not None else ""
|
||||
suffix = f": {path}" if path else ""
|
||||
raise WeiboTransportError(f"微博接口返回非 JSON{suffix}") from exc
|
||||
except requests.RequestException as exc:
|
||||
raise WeiboTransportError(f"请求微博失败: {exc}") from exc
|
||||
except ValueError as exc:
|
||||
raise WeiboTransportError("微博返回内容不是合法 JSON") from exc
|
||||
if not isinstance(payload, dict):
|
||||
raise WeiboTransportError("微博返回 JSON 结构异常")
|
||||
return payload
|
||||
|
||||
@@ -8,6 +8,7 @@ generated:
|
||||
status: stable
|
||||
verified:
|
||||
- { by: ai/codex, at: 2026-11-07T19:03:41+08:00 }
|
||||
- { by: ai/codex, at: 2026-11-08T13:45:00+08:00 }
|
||||
---
|
||||
|
||||
# 微博微豆 CK 接口与兑换链路确认
|
||||
@@ -723,6 +724,37 @@ data.totalCount
|
||||
| `400000` | `user not login`,CK 无效 |
|
||||
| 其他 | 保留服务端 `msg` 和 `trace_id`,不要盲目重试状态变更接口 |
|
||||
|
||||
### 10.1 登录页 HTML 与 `retcode=6102`
|
||||
|
||||
部分 V+ / 支付域接口在登录态失效时仍返回 HTTP 200,但 `Content-Type` 为 `text/html`,正文是“新浪通行证”跳转页,并包含:
|
||||
|
||||
```text
|
||||
retcode=6102
|
||||
```
|
||||
|
||||
2026-11-08 实测到同一账号可以正常请求:
|
||||
|
||||
```text
|
||||
GET https://vipclub.e.weibo.com/aj/mall/home
|
||||
-> code=100000
|
||||
```
|
||||
|
||||
但后续请求:
|
||||
|
||||
```text
|
||||
GET https://vipclub.weibo.com/aj/vmember/gfsupport
|
||||
GET https://vipclub.weibo.com/aj/paycomponent/userinfo
|
||||
-> text/html + retcode=6102
|
||||
```
|
||||
|
||||
这说明 CK 已经不能访问 V+ 支付/活动域,不能归类为普通“非 JSON 查询失败”。处理约定:
|
||||
|
||||
- `retcode=6102` 或跳转 `/sso/login.php` 归类为 `WeiboAuthError`。
|
||||
- 账号 `ck_check_status` 写为 `invalid`,UI 显示「失效」。
|
||||
- 提示用户重新登录微博,并在 V+ 页面可用后重新导出、导入完整 CK。
|
||||
- 不要通过反复刷新、重试支付接口来“恢复”状态。
|
||||
- 其他未知 HTML 或网关错误保留为 `error`,UI 显示「查询失败」,不要误标成 CK 失效。
|
||||
|
||||
## 11. 尚未确认
|
||||
|
||||
- 是否只需要 `SUB`,还是必须完整 Cookie Jar。
|
||||
|
||||
@@ -205,6 +205,22 @@ def test_refresh_marks_payment_access_failure(db, current_user):
|
||||
assert "触发微博账号风控" in account.ck_check_message
|
||||
|
||||
|
||||
def test_refresh_marks_payment_login_expiry_as_invalid(db, current_user):
|
||||
account_out = import_cookies(
|
||||
WeiboCookieImport(text="SCF=a; SUB=b", tag=""),
|
||||
db=db,
|
||||
current=current_user,
|
||||
).accounts[0]
|
||||
account = db.query(WeiboAccount).filter(WeiboAccount.id == account_out.id).one()
|
||||
StubWeiboClient.payment_error = WeiboAuthError("支付域 retcode=6102")
|
||||
|
||||
with pytest.raises(WeiboAuthError, match="6102"):
|
||||
weibo_service.refresh_weibo_account(db, account)
|
||||
|
||||
assert account.ck_check_status == "invalid"
|
||||
assert "6102" in account.ck_check_message
|
||||
|
||||
|
||||
def test_list_accounts_returns_parallel_page_shape(db, current_user):
|
||||
assert (
|
||||
import_cookies(
|
||||
|
||||
@@ -17,6 +17,7 @@ from core.weibo import (
|
||||
WeiboExchangeUncertainError,
|
||||
WeiboHomeSnapshot,
|
||||
WeiboRechargeError,
|
||||
WeiboTransportError,
|
||||
extract_bind_raw_url,
|
||||
normalize_weibo_cookie,
|
||||
)
|
||||
@@ -50,7 +51,7 @@ class FakeResponse:
|
||||
|
||||
def json(self):
|
||||
if not isinstance(self.payload, dict):
|
||||
raise TypeError("not json")
|
||||
raise requests.exceptions.JSONDecodeError("not json", self.text, 0)
|
||||
return self.payload
|
||||
|
||||
|
||||
@@ -155,6 +156,30 @@ def test_fetch_home_keeps_trace_id_for_other_api_error():
|
||||
client.fetch_home()
|
||||
|
||||
|
||||
def test_fetch_home_treats_sina_login_redirect_as_auth_error():
|
||||
session = FakeSession(
|
||||
[
|
||||
(
|
||||
"<html><head><meta http-equiv='refresh' "
|
||||
"content=\"0; url='https://vipclub.weibo.com/aj/vmember/"
|
||||
"gfsupport?retcode=6102'\"></head></html>"
|
||||
)
|
||||
]
|
||||
)
|
||||
client = WeiboClient("SUB=expired", session=session)
|
||||
|
||||
with pytest.raises(WeiboAuthError, match="重新登录微博"):
|
||||
client.fetch_home()
|
||||
|
||||
|
||||
def test_fetch_home_reports_non_json_endpoint_path():
|
||||
session = FakeSession(["<html><body>gateway error</body></html>"])
|
||||
client = WeiboClient("SUB=test", session=session)
|
||||
|
||||
with pytest.raises(WeiboTransportError, match="/aj/mall/home"):
|
||||
client.fetch_home()
|
||||
|
||||
|
||||
def test_generate_bind_scheme_posts_account_type_without_weibo_cookie():
|
||||
session = FakeSession(
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user