fix: delay binding.percent update until remote sync succeeds and update UI labels to 自动保底盈利

This commit is contained in:
SmartUp Developer
2026-07-02 21:27:52 +08:00
parent 0e77de31ae
commit fdbcca4d95
3 changed files with 57 additions and 7 deletions
+8 -4
View File
@@ -113,6 +113,7 @@ def _log(
message: str, message: str,
old_rate: Any = None, old_rate: Any = None,
new_rate: Any = None, new_rate: Any = None,
effective_percent: str = None,
) -> WebsiteSyncLog: ) -> WebsiteSyncLog:
row = WebsiteSyncLog( row = WebsiteSyncLog(
website_id=website.id, website_id=website.id,
@@ -120,7 +121,7 @@ def _log(
target_group_id=binding.target_group_id, target_group_id=binding.target_group_id,
target_group_name=binding.target_group_name, target_group_name=binding.target_group_name,
algorithm=binding.algorithm, 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), 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, 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, 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: try:
target_rate = calculate_target_rate([item.get("rate") for item in source_rates], binding.percent, binding.algorithm) 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": 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] rates = [rate for rate in (parse_positive_decimal(item.get("rate")) for item in source_rates) if rate is not None]
if rates: if rates:
@@ -182,7 +184,7 @@ def sync_binding(db: Session, binding: WebsiteGroupBinding, write: bool = True)
if base > 0: if base > 0:
effective_percent = (target_rate / base - Decimal("1")) * Decimal("100") 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: except Exception as exc:
return _log(db, binding, website, source_rates, "failed", str(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, binding.target_group_id,
target_rate, target_rate,
) )
if auto_percent_str is not None:
binding.percent = auto_percent_str
website.last_status = "healthy" website.last_status = "healthy"
website.last_error = None website.last_error = None
except Exception as exc: except Exception as exc:
website.last_status = "unhealthy" website.last_status = "unhealthy"
website.last_error = str(exc) website.last_error = str(exc)
db.commit() 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() 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 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) new_rate_str = fixed_decimal_string(target_rate, 2)
if old_rate_str != new_rate_str: if old_rate_str != new_rate_str:
+46
View File
@@ -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() log = db_session.query(WebsiteSyncLog).filter(WebsiteSyncLog.binding_id == binding.id).one()
assert log.percent == "50.00" assert log.percent == "50.00"
assert log.new_rate == "3.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"
+2 -2
View File
@@ -287,7 +287,7 @@
<el-form-item label="算法"> <el-form-item label="算法">
<div class="custom-select-wrapper"> <div class="custom-select-wrapper">
<el-select v-model="bindingForm.algorithm" style="width:100%" class="custom-theme-select"> <el-select v-model="bindingForm.algorithm" style="width:100%" class="custom-theme-select">
<el-option label="优先级期望成本 + 百分比" value="priority_weighted_plus_percent" /> <el-option label="自动保底盈利" value="priority_weighted_plus_percent" />
<el-option label="最高倍率 + 百分比" value="max_plus_percent" /> <el-option label="最高倍率 + 百分比" value="max_plus_percent" />
<el-option label="平均倍率 + 百分比" value="average_plus_percent" /> <el-option label="平均倍率 + 百分比" value="average_plus_percent" />
<el-option label="最低倍率 + 百分比" value="min_plus_percent" /> <el-option label="最低倍率 + 百分比" value="min_plus_percent" />
@@ -1094,7 +1094,7 @@ const syncingImportStatus = ref(false)
const reorderingPriorities = ref(false) const reorderingPriorities = ref(false)
const statusLabel = (s: string) => ({ healthy: '健康', unhealthy: '异常', unknown: '未知' }[s] || s) 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 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 fmtTime = (t: string) => dayjs(toUTC(t)).format('MM-DD HH:mm:ss')
const sourceGroupId = (group: any) => String(group?.group_id || group?.id || group?.name || '') const sourceGroupId = (group: any) => String(group?.group_id || group?.id || group?.name || '')