From 275ae7f24119af77d6718f203768f4aab8ad1803 Mon Sep 17 00:00:00 2001 From: yml Date: Thu, 12 Nov 2026 19:28:53 +0800 Subject: [PATCH] =?UTF-8?q?refactor(weibo):=20=E7=A7=BB=E9=99=A4=E5=85=91?= =?UTF-8?q?=E6=8D=A2=E5=BE=85=E7=A1=AE=E8=AE=A4=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_migrations.py | 63 ++++++++++++++++++- tests/test_weibo_collab.py | 7 +-- ...1112_0079_weibo_remove_exchange_pending.py | 35 +++++++++++ web/backend/services/weibo_collab_service.py | 19 ++---- web/frontend/src/api/weiboCollab.ts | 1 - web/frontend/src/pages/WeiboCollabPage.tsx | 7 +-- 6 files changed, 104 insertions(+), 28 deletions(-) create mode 100644 web/backend/migrations/versions/20261112_0079_weibo_remove_exchange_pending.py diff --git a/tests/test_migrations.py b/tests/test_migrations.py index d9cb1b3..14f4a33 100644 --- a/tests/test_migrations.py +++ b/tests/test_migrations.py @@ -24,13 +24,19 @@ from sqlalchemy.orm import Session import web.backend.database as database_module from tests.support.db import get_mysql_credentials, get_test_engine from web.backend.database import Base -from web.backend.models import AuditLog, HuyaExternalOrder, User, WeiboAccount +from web.backend.models import ( + AuditLog, + HuyaExternalOrder, + User, + WeiboAccount, + WeiboExternalOrder, +) TESTS_DIR = Path(__file__).resolve().parent PROJECT_ROOT = TESTS_DIR.parent VERSIONS_DIR = PROJECT_ROOT / "web" / "backend" / "migrations" / "versions" -HEAD_REVISION = "20261112_0078" +HEAD_REVISION = "20261112_0079" # 迁移新增、但不声明在模型里的复合查询索引。 EXTRA_INDEXES = ( @@ -408,3 +414,56 @@ class TestMigrationSmoke: mig_engine.dispose() with admin_engine.begin() as conn: conn.execute(text(f"DROP DATABASE IF EXISTS `{mig_db}`;")) + + def test_0079_moves_exchange_pending_to_recharged(self): + creds = get_mysql_credentials() + mig_db = f"douyu_login_mig_{secrets.token_hex(4)}" + mig_url = ( + f"mysql+pymysql://{quote_plus(creds['user'])}:" + f"{quote_plus(creds['password'])}@{creds['host']}:{creds['port']}/" + f"{mig_db}?charset=utf8mb4" + ) + + admin_engine = get_test_engine() + with admin_engine.begin() as conn: + conn.execute( + text( + f"CREATE DATABASE `{mig_db}` " + "CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;" + ) + ) + + original_url = database_module.DATABASE_URL + mig_engine = create_engine(mig_url) + try: + database_module.DATABASE_URL = mig_url + cfg = _migration_config() + cfg.set_main_option("sqlalchemy.url", mig_url) + command.upgrade(cfg, "20261112_0078") + + with Session(mig_engine) as session: + order = WeiboExternalOrder( + order_no="MIG-0079-PENDING", + status="exchange_pending", + exchange_started_at=datetime(2026, 11, 12, 8, 30, tzinfo=UTC), + ) + session.add(order) + session.commit() + + command.upgrade(cfg, "20261112_0079") + + with mig_engine.connect() as conn: + row = conn.execute( + text( + "SELECT status, recharged_at " + "FROM weibo_external_orders " + "WHERE order_no = 'MIG-0079-PENDING'" + ) + ).one() + assert row.status == "recharged" + assert row.recharged_at is not None + finally: + database_module.DATABASE_URL = original_url + mig_engine.dispose() + with admin_engine.begin() as conn: + conn.execute(text(f"DROP DATABASE IF EXISTS `{mig_db}`;")) diff --git a/tests/test_weibo_collab.py b/tests/test_weibo_collab.py index 20dbda4..a27b6b5 100644 --- a/tests/test_weibo_collab.py +++ b/tests/test_weibo_collab.py @@ -143,9 +143,8 @@ def test_exchange_record_unique_match_and_archive(monkeypatch, db): } monkeypatch.setattr(collab, "WeiboClient", FakeClient) - collab.check_balance_groups(db, user, [first], threshold=100) - collab.set_status(db, user, first, collab.STATUS_EXCHANGE_PENDING) - collab.set_status(db, user, second, collab.STATUS_EXCHANGE_PENDING) + collab.set_status(db, user, first, collab.STATUS_RECHARGED) + collab.set_status(db, user, second, collab.STATUS_RECHARGED) result = collab.check_exchange_records(db, user, [first, second]) @@ -155,7 +154,7 @@ def test_exchange_record_unique_match_and_archive(monkeypatch, db): db.refresh(second) assert first.status == collab.STATUS_DONE assert first.exchange_verdict == collab.VERDICT_SUCCESS - assert second.status == collab.STATUS_EXCHANGE_PENDING + assert second.status == collab.STATUS_RECHARGED assert second.exchange_verdict == collab.VERDICT_UNKNOWN archived = collab.archive_orders(db, user, [first.id], reason="客户验收") diff --git a/web/backend/migrations/versions/20261112_0079_weibo_remove_exchange_pending.py b/web/backend/migrations/versions/20261112_0079_weibo_remove_exchange_pending.py new file mode 100644 index 0000000..256997d --- /dev/null +++ b/web/backend/migrations/versions/20261112_0079_weibo_remove_exchange_pending.py @@ -0,0 +1,35 @@ +"""微博外部单协作移除「兑换待确认」状态,存量订单归入「已充值」。""" + +from __future__ import annotations + +from collections.abc import Sequence + +import sqlalchemy as sa +from alembic import op + +revision: str = "20261112_0079" +down_revision: str | None = "20261112_0078" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + +TABLE = "weibo_external_orders" + + +def upgrade() -> None: + bind = op.get_bind() + if not sa.inspect(bind).has_table(TABLE): + return + bind.execute( + sa.text( + f""" + UPDATE {TABLE} + SET status = 'recharged', + recharged_at = COALESCE(recharged_at, exchange_started_at, updated_at, created_at) + WHERE status = 'exchange_pending' + """ + ) + ) + + +def downgrade() -> None: + """旧状态没有独立业务含义,降级时保持「已充值」。""" diff --git a/web/backend/services/weibo_collab_service.py b/web/backend/services/weibo_collab_service.py index 7842d15..d4c1176 100644 --- a/web/backend/services/weibo_collab_service.py +++ b/web/backend/services/weibo_collab_service.py @@ -33,7 +33,6 @@ from .weibo_service import query_weibo_mall, refresh_weibo_account STATUS_INCOMPLETE = "incomplete" STATUS_TO_RECHARGE = "to_recharge" STATUS_RECHARGED = "recharged" -STATUS_EXCHANGE_PENDING = "exchange_pending" STATUS_DONE = "done" STATUS_WEIDOU_ERROR = "weidou_error" STATUS_FAILED = "failed" @@ -43,7 +42,6 @@ ORDER_STATUSES = ( STATUS_INCOMPLETE, STATUS_TO_RECHARGE, STATUS_RECHARGED, - STATUS_EXCHANGE_PENDING, STATUS_DONE, STATUS_WEIDOU_ERROR, STATUS_FAILED, @@ -54,7 +52,6 @@ ORDER_STATUS_LABELS = { STATUS_INCOMPLETE: "待补资料", STATUS_TO_RECHARGE: "待充值", STATUS_RECHARGED: "已充值", - STATUS_EXCHANGE_PENDING: "兑换待确认", STATUS_DONE: "兑换成功", STATUS_WEIDOU_ERROR: "微豆异常", STATUS_FAILED: "兑换失败", @@ -65,15 +62,13 @@ STATUS_FLOW = ( STATUS_INCOMPLETE, STATUS_TO_RECHARGE, STATUS_RECHARGED, - STATUS_EXCHANGE_PENDING, STATUS_DONE, ) STAGE_INDEX = {status: index for index, status in enumerate(STATUS_FLOW)} NEXT_STATUS = { STATUS_INCOMPLETE: STATUS_TO_RECHARGE, STATUS_TO_RECHARGE: STATUS_RECHARGED, - STATUS_RECHARGED: STATUS_EXCHANGE_PENDING, - STATUS_EXCHANGE_PENDING: STATUS_DONE, + STATUS_RECHARGED: STATUS_DONE, } BALANCE_GROUP_STATUSES = ( @@ -672,9 +667,9 @@ def set_status( _assert_mutable(order) if target not in ORDER_STATUSES: raise CollabError("非法状态", code="status_invalid") - if target in (STATUS_RECHARGED, STATUS_WEIDOU_ERROR) and not allow_check: + if target == STATUS_WEIDOU_ERROR and not allow_check: raise CollabError( - "「已充值 / 微豆异常」必须通过余额核账写入", + "「微豆异常」必须通过余额核账写入", code="balance_check_required", ) old = order.status @@ -725,12 +720,6 @@ def set_status( if rollback: order.exchange_started_at = None order.done_at = None - elif target == STATUS_EXCHANGE_PENDING and not order.recharged_at: - raise CollabError("请先完成余额核账", code="recharge_check_required") - elif target == STATUS_EXCHANGE_PENDING: - if order.exchange_started_at is None: - order.exchange_started_at = now - order.done_at = None elif target == STATUS_DONE: if order.exchange_started_at is None: order.exchange_started_at = now @@ -1227,7 +1216,7 @@ def check_exchange_records( order.exchange_record_snapshot = _json_safe(record) order.exchange_verdict = VERDICT_SUCCESS order.exchange_message = "兑换记录唯一命中" - if order.status == STATUS_EXCHANGE_PENDING: + if order.status == STATUS_RECHARGED: set_status( db, current, diff --git a/web/frontend/src/api/weiboCollab.ts b/web/frontend/src/api/weiboCollab.ts index ced9712..4abf7c5 100644 --- a/web/frontend/src/api/weiboCollab.ts +++ b/web/frontend/src/api/weiboCollab.ts @@ -4,7 +4,6 @@ export type WeiboCollabStatus = | 'incomplete' | 'to_recharge' | 'recharged' - | 'exchange_pending' | 'done' | 'weidou_error' | 'failed' diff --git a/web/frontend/src/pages/WeiboCollabPage.tsx b/web/frontend/src/pages/WeiboCollabPage.tsx index 431ddc5..5e875d9 100644 --- a/web/frontend/src/pages/WeiboCollabPage.tsx +++ b/web/frontend/src/pages/WeiboCollabPage.tsx @@ -70,7 +70,6 @@ const STATUS_META: Record = incomplete: { label: '待补资料', color: 'default' }, to_recharge: { label: '待充值', color: 'blue' }, recharged: { label: '已充值', color: 'cyan' }, - exchange_pending: { label: '兑换待确认', color: 'purple' }, done: { label: '兑换成功', color: 'success' }, weidou_error: { label: '微豆异常', color: 'orange' }, failed: { label: '兑换失败', color: 'error' }, @@ -87,7 +86,6 @@ const STATUS_ORDER: WeiboCollabStatus[] = [ 'incomplete', 'to_recharge', 'recharged', - 'exchange_pending', 'done', 'weidou_error', 'failed', @@ -95,7 +93,7 @@ const STATUS_ORDER: WeiboCollabStatus[] = [ ]; const MANUAL_STATUS_OPTIONS = STATUS_ORDER.filter( - (item) => item !== 'recharged' && item !== 'weidou_error', + (item) => item !== 'weidou_error', ).map((value) => ({ value, label: STATUS_META[value].label })); const VERDICT_META: Record = { @@ -1373,9 +1371,6 @@ export default function WeiboCollabPage() { - )}