From fdbcca4d955ef736276199ed1ffad5d74059b5cb Mon Sep 17 00:00:00 2001 From: SmartUp Developer Date: Thu, 2 Jul 2026 21:27:52 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20delay=20binding.percent=20update=20until?= =?UTF-8?q?=20remote=20sync=20succeeds=20and=20update=20UI=20labels=20to?= =?UTF-8?q?=20=E8=87=AA=E5=8A=A8=E4=BF=9D=E5=BA=95=E7=9B=88=E5=88=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/services/website_sync.py | 14 ++++--- backend/test_group_binding_create_sync.py | 46 +++++++++++++++++++++++ frontend/src/views/Websites.vue | 4 +- 3 files changed, 57 insertions(+), 7 deletions(-) diff --git a/backend/app/services/website_sync.py b/backend/app/services/website_sync.py index ae68a0d..7efd992 100644 --- a/backend/app/services/website_sync.py +++ b/backend/app/services/website_sync.py @@ -113,6 +113,7 @@ def _log( message: str, old_rate: Any = None, new_rate: Any = None, + effective_percent: str = None, ) -> WebsiteSyncLog: row = WebsiteSyncLog( website_id=website.id, @@ -120,7 +121,7 @@ def _log( target_group_id=binding.target_group_id, target_group_name=binding.target_group_name, algorithm=binding.algorithm, - percent=binding.percent, + percent=effective_percent if effective_percent is not None else binding.percent, source_rates_json=json.dumps(source_rates, ensure_ascii=False), old_rate=fixed_decimal_string(old_rate, 2) if old_rate not in (None, "") else None, new_rate=fixed_decimal_string(new_rate, 2) if new_rate not in (None, "") else None, @@ -168,6 +169,7 @@ def sync_binding(db: Session, binding: WebsiteGroupBinding, write: bool = True) }) try: target_rate = calculate_target_rate([item.get("rate") for item in source_rates], binding.percent, binding.algorithm) + auto_percent_str = None if binding.algorithm == "priority_weighted_plus_percent": rates = [rate for rate in (parse_positive_decimal(item.get("rate")) for item in source_rates) if rate is not None] if rates: @@ -179,10 +181,10 @@ def sync_binding(db: Session, binding: WebsiteGroupBinding, write: bool = True) w1 = Decimal("0.85") w_others = Decimal("0.15") / Decimal(N - 1) base = rates_sorted[0] * w1 + sum(rates_sorted[1:], Decimal("0")) * w_others - + if base > 0: effective_percent = (target_rate / base - Decimal("1")) * Decimal("100") - binding.percent = fixed_decimal_string(effective_percent.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP), 2) + auto_percent_str = fixed_decimal_string(effective_percent.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP), 2) except Exception as exc: return _log(db, binding, website, source_rates, "failed", str(exc)) @@ -198,15 +200,17 @@ def sync_binding(db: Session, binding: WebsiteGroupBinding, write: bool = True) binding.target_group_id, target_rate, ) + if auto_percent_str is not None: + binding.percent = auto_percent_str website.last_status = "healthy" website.last_error = None except Exception as exc: website.last_status = "unhealthy" website.last_error = str(exc) db.commit() - return _log(db, binding, website, source_rates, "failed", f"写回失败:{exc}", old_rate, target_rate) + return _log(db, binding, website, source_rates, "failed", f"写回失败:{exc}", old_rate, target_rate, auto_percent_str) db.commit() - log = _log(db, binding, website, source_rates, "success", "同步成功", old_rate, target_rate) + log = _log(db, binding, website, source_rates, "success", "同步成功", old_rate, target_rate, auto_percent_str) old_rate_str = fixed_decimal_string(old_rate, 2) if old_rate not in (None, "") else None new_rate_str = fixed_decimal_string(target_rate, 2) if old_rate_str != new_rate_str: diff --git a/backend/test_group_binding_create_sync.py b/backend/test_group_binding_create_sync.py index d18d14d..64c16bf 100644 --- a/backend/test_group_binding_create_sync.py +++ b/backend/test_group_binding_create_sync.py @@ -433,3 +433,49 @@ def test_sync_binding_auto_percent_writeback(monkeypatch, db_session): log = db_session.query(WebsiteSyncLog).filter(WebsiteSyncLog.binding_id == binding.id).one() assert log.percent == "50.00" assert log.new_rate == "3.00" + + +def test_sync_binding_auto_percent_failure_does_not_save(monkeypatch, db_session): + website, upstream = seed_rows(db_session) + binding = WebsiteGroupBinding( + website_id=website.id, + target_group_id="target", + target_group_name="Target group", + source_groups_json=json.dumps([{ + "upstream_id": upstream.id, + "upstream_name": "Upstream", + "group_id": "source", + "group_name": "Source", + }], ensure_ascii=False), + percent="10.00", + algorithm="priority_weighted_plus_percent", + enabled=True, + ) + db_session.add(binding) + db_session.commit() + db_session.refresh(binding) + + class FakeClient: + def __init__(self, **kwargs): + pass + def __enter__(self): + return self + def __exit__(self, *args): + pass + def get_groups(self, endpoint): + return [{"id": "target", "name": "Target group", "rate_multiplier": "2.0"}] + def update_group_rate(self, endpoint, group_id, rate): + raise Exception("connection timeout") + + monkeypatch.setattr(websites_router, "Sub2ApiWebsiteClient", FakeClient) + monkeypatch.setattr("app.services.website_sync.Sub2ApiWebsiteClient", FakeClient) + + from app.services.website_sync import sync_binding + sync_binding(db_session, binding, write=True) + + db_session.refresh(binding) + assert binding.percent == "10.00" + + log = db_session.query(WebsiteSyncLog).filter(WebsiteSyncLog.binding_id == binding.id).one() + assert log.status == "failed" + assert log.percent == "50.00" diff --git a/frontend/src/views/Websites.vue b/frontend/src/views/Websites.vue index 03a4f67..0722164 100644 --- a/frontend/src/views/Websites.vue +++ b/frontend/src/views/Websites.vue @@ -287,7 +287,7 @@
- + @@ -1094,7 +1094,7 @@ const syncingImportStatus = ref(false) const reorderingPriorities = ref(false) const statusLabel = (s: string) => ({ healthy: '健康', unhealthy: '异常', unknown: '未知' }[s] || s) -const algorithmLabel = (s: string) => ({ max_plus_percent: '最高倍率', average_plus_percent: '平均倍率', min_plus_percent: '最低倍率', priority_weighted_plus_percent: '优先级期望成本' }[s] || s) +const algorithmLabel = (s: string) => ({ max_plus_percent: '最高倍率', average_plus_percent: '平均倍率', min_plus_percent: '最低倍率', priority_weighted_plus_percent: '自动保底盈利' }[s] || s) const toUTC = (t: string) => /[Z+\-]\d*$/.test(t.trim()) ? t : t + 'Z' const fmtTime = (t: string) => dayjs(toUTC(t)).format('MM-DD HH:mm:ss') const sourceGroupId = (group: any) => String(group?.group_id || group?.id || group?.name || '')