diff --git a/tests/test_account_overview.py b/tests/test_account_overview.py index 90131b9..d399efb 100644 --- a/tests/test_account_overview.py +++ b/tests/test_account_overview.py @@ -575,3 +575,109 @@ class TestSnapshotExposedOnDetailOnly: assert "overview_snapshot" not in item, ( "列表接口不该返回完整快照(每个账号一份 JSON,体积爆炸)" ) + + +class TestOverviewRefreshesAccountFields: + """查询的职责是**刷新账号字段**,不是另开一份副本。 + + 用户反馈很直接:「一键查询这也太重复了,直接更新账号信息不行吗? + 那建立账号信息表有啥用」。 + + 对 —— 鱼翅 / 积分 / 交易余额 / 开通状态账号表本来就有对应列, + 查询把新值写进去、由已有界面展示即可。这里钉住写回行为, + 尤其是**积分到期时间**:它有 `accounts.points_expire_at` 列, + 但之前忘了写(也没在界面上显示)。 + """ + + def setup_method(self): + self.engine = get_test_engine() + self.session = make_test_session(self.engine) + self.user = User(username="rf-admin", password_hash="h", role="super_admin") + self.session.add(self.user) + self.session.flush() + self.account = Account( + username="rf-acc", password="p", email="r@e.com", email_password="ep" + ) + self.session.add(self.account) + self.session.commit() + + def teardown_method(self): + self.session.close() + + def _run(self, *, expire_at: str = "1795967999"): + from web.backend.services.douyu_runner_overview import OverviewMixin + + class _H(OverviewMixin): + def _client(self, cookie): + return _FakeClient() + + def _mark_task(self, *a, **k): + pass + + def _push_log(self, *a, **k): + pass + + def _refresh_account_gold_balance(self, c, a): + a.gold_balance = 88 + a.exchange_balance = 9 + return {"gold_balance": 88, "exchange_balance": 9} + + def _refresh_account_points(self, c, a, cookie, *, ctn=None): + a.points = 777 + return { + "points": 777, + "points_query": {"raw": {"data": {"willExpireTime": expire_at}}}, + } + + def _refresh_elite_handbook(self, c, a, *, manual_id, rid=""): + a.elite_opened = True + return { + "elite_opened": True, + "elite_expire_time": 0, + "elite_manual_score": 5, + } + + def _refresh_esports_handbook(self, c, a, *, manual_id): + a.esports_opened = True + return {"esports_manual_type": 1, "esports_manual_score": 3} + + task = DouyuTask( + batch_id="rf", + account_id=self.account.id, + task_type="query_account_overview", + handbook_scope="elite", + status="running", + message="", + created_by=self.user.id, + ) + self.session.add(task) + self.session.commit() + _H()._execute_query_account_overview( + self.session, + task, + self.account, + "c", + {"manual_id": "M", "esports_manual_id": "E", "rid": "R"}, + ) + self.session.commit() + self.session.refresh(self.account) + + def test_assets_are_written_to_account_columns(self): + self._run() + assert self.account.points == 777 + assert self.account.gold_balance == 88 + assert self.account.exchange_balance == 9 + assert self.account.elite_opened is True + assert self.account.esports_opened is True + + def test_points_expire_at_is_written(self): + """积分到期时间有专门的列,查询必须写它(之前漏了)。""" + self._run(expire_at="1795967999") + assert self.account.points_expire_at is not None + assert self.account.points_expire_at.year != 1970 + + def test_no_expiry_clears_the_column_not_zero(self): + """斗鱼用 0 表示没有到期时间 —— 不能存成 0(会显示 1970/1/1)。""" + self._run(expire_at="0") + assert self.account.points_expire_at is None + assert self.account.overview_snapshot["assets"]["points_expire_at"] is None diff --git a/web/backend/routers/accounts.py b/web/backend/routers/accounts.py index dd90412..6074c74 100644 --- a/web/backend/routers/accounts.py +++ b/web/backend/routers/accounts.py @@ -999,6 +999,8 @@ def account_overview( "game_channel": acc.game_channel, **bind_payload(acc), # 最近一次「一键查询」快照(列表接口刻意**不**返回,避免体积爆炸) + # 积分到期时间:账号自己的字段,账号详情里展示(一键查询会刷新它) + "points_expire_at": acc.points_expire_at, "overview_snapshot": acc.overview_snapshot, "overview_queried_at": acc.overview_queried_at, "elite_opened": acc.elite_opened, diff --git a/web/backend/services/douyu_runner_overview.py b/web/backend/services/douyu_runner_overview.py index 0d3abda..7f06216 100644 --- a/web/backend/services/douyu_runner_overview.py +++ b/web/backend/services/douyu_runner_overview.py @@ -33,7 +33,7 @@ from sqlalchemy.orm import Session from core.douyu import DouyuActivityClient from ..models import Account, DouyuTask -from .douyu_runner_donate import _points_expire_at +from .douyu_runner_donate import _parse_expire_at, _points_expire_at #: 默认关注的高价值商品。按**商品名包含**匹配 —— 官方列表里的名字带前缀 #: (实际是「套装-兔兔白日梦」),所以配置里写核心词即可。 @@ -246,7 +246,12 @@ class OverviewMixin: overview["assets"]["points"] = points.get("points") # `_refresh_account_points` 只回 {"points", "points_query"}, # 到期时间得从 points_query 里取 —— 复用已有具名函数,不自己扒结构。 - overview["assets"]["points_expire_at"] = _ts(_points_expire_at(points)) + expire_raw = _ts(_points_expire_at(points)) + overview["assets"]["points_expire_at"] = expire_raw + # **也写回账号**:积分到期时间是账号的信息,本来就该由账号表承载并 + # 在账号详情里展示 —— 查询只是一次「刷新」,不是另开一份副本。 + expire_dt = _parse_expire_at(expire_raw) + account.points_expire_at = expire_dt except Exception as exc: # noqa: BLE001 errors["points"] = _err(exc) diff --git a/web/frontend/src/api/types.ts b/web/frontend/src/api/types.ts index a0efe78..ace8ecb 100644 --- a/web/frontend/src/api/types.ts +++ b/web/frontend/src/api/types.ts @@ -155,6 +155,8 @@ export interface AccountOverview { points: number | null; gold_balance: number | null; exchange_balance: number | null; + /** 积分到期时间(一键查询会刷新它) */ + points_expire_at: string | null; game_name: string; game_channel: string; /** 三列各管一个作用域:unknown / unbound / pending / bound */ diff --git a/web/frontend/src/pages/AccountDetailPage.tsx b/web/frontend/src/pages/AccountDetailPage.tsx index 9e819b5..2c9d54f 100644 --- a/web/frontend/src/pages/AccountDetailPage.tsx +++ b/web/frontend/src/pages/AccountDetailPage.tsx @@ -380,7 +380,7 @@ export default function AccountDetailPage() { title={ - 一键查询(斗鱼实时) + 高价值套装与兑换记录(一键查询) } extra={ @@ -406,8 +406,9 @@ export default function AccountDetailPage() { /> ) : ( - 实时向斗鱼查询:鱼翅 / 积分(含到期时间)/ 精英宝典 / 电竞手册 / 官方兑换记录, - 并重点标出「兔兔白日梦」「狂战萌兔」是否已获得。结果会**存在这个账号上**。 + 实时向斗鱼查一次这个账号:鱼翅 / 积分 / 开通状态会**就地刷新到下面的账号信息**, + 这里只单独展示账号表里没有的两项 —— 「兔兔白日梦」「狂战萌兔」是否已获得, + 以及官方兑换记录。 )} @@ -574,6 +575,12 @@ export default function AccountDetailPage() {
+ ; - const assets = result.assets || {}; const errors = result.errors || {}; const goods = result.high_value_goods || []; const records = result.exchange_records || []; @@ -195,42 +199,6 @@ export default function AccountOverviewPanel({ /> )} - - - - {assets.gold_balance ?? '-'} - - - {assets.exchange_balance ?? '-'} - - {assets.points ?? '-'} - - {formatTime(assets.points_expire_at)} - - - - - - - - - {yesNo(result.elite?.opened)} - - - - {formatTime(result.elite?.expire_time)} - - - - {yesNo(result.esports?.opened)} - - - - {result.esports?.points ?? '-'} - - - - {goods.length === 0 ? ( 没有配置关注的高价值套装,或本次没取到