From 8fcfec9b654a10eb275fd836d1fb3969d0454351 Mon Sep 17 00:00:00 2001 From: hailin Date: Tue, 3 Mar 2026 23:16:47 -0800 Subject: [PATCH] =?UTF-8?q?fix(contribution):=20backfill=20=E5=BC=BA?= =?UTF-8?q?=E5=88=B6=E9=87=8D=E7=AE=97=20unlock=20status=EF=BC=8C=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E9=A2=84=E7=A7=8D=E7=94=A8=E6=88=B7=E5=B1=82=E7=BA=A7?= =?UTF-8?q?=E5=8D=A1=E5=9C=A85=E7=BA=A7=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题根因:直推用户先买预种导致 directReferralAdoptedCount 已累加到正确值(如5), 但 markAsAdopted() 随后被调用时硬编码 level=5/bonus=1,覆盖了正确的解锁状态。 之后 backfill 因 count 未变(5>5=false)永远不触发重算,level 永久卡死。 修复:updateAccountUnlockStatus 改用 setDirectReferralAdoptedCount() 替代 incrementDirectReferralAdoptedCount 循环,无论 count 是否变化都强制调用 updateUnlockStatus() 重算 unlockedLevelDepth 和 unlockedBonusTiers。 同时为 getDirectReferralAdoptedCount 补充注释,说明常规认种和预种均按人头计。 Co-Authored-By: Claude Sonnet 4.6 --- .../services/bonus-claim.service.ts | 18 +++++++++++------- .../repositories/synced-data.repository.ts | 7 +++++-- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/backend/services/contribution-service/src/application/services/bonus-claim.service.ts b/backend/services/contribution-service/src/application/services/bonus-claim.service.ts index 47f8048c..0b432f0e 100644 --- a/backend/services/contribution-service/src/application/services/bonus-claim.service.ts +++ b/backend/services/contribution-service/src/application/services/bonus-claim.service.ts @@ -591,13 +591,17 @@ export class BonusClaimService { expectedLevelDepth: number, expectedBonusTiers: number, ): Promise { - // 增量更新直推认种数 - const previousCount = account.directReferralAdoptedCount; - if (newDirectReferralAdoptedCount > previousCount) { - for (let i = previousCount; i < newDirectReferralAdoptedCount; i++) { - account.incrementDirectReferralAdoptedCount(); - } - } + // 使用 setDirectReferralAdoptedCount 无条件重算解锁状态。 + // + // 不能用「if (newCount > previousCount) increment」的方式,原因: + // 当直推已全部认种(DB count 已正确)但 markAsAdopted() 在此之后才 + // 被调用时,markAsAdopted 会硬编码 level=5/bonus=1,导致 level 错误, + // 但 count 不变,条件判断永远为 false,backfill 无法修复。 + // + // setDirectReferralAdoptedCount 内部调用 updateUnlockStatus(), + // 无论 count 是否变化都会根据 hasAdopted + count 重新计算正确的 + // unlockedLevelDepth 和 unlockedBonusTiers。 + account.setDirectReferralAdoptedCount(newDirectReferralAdoptedCount); await this.contributionAccountRepository.save(account); diff --git a/backend/services/contribution-service/src/infrastructure/persistence/repositories/synced-data.repository.ts b/backend/services/contribution-service/src/infrastructure/persistence/repositories/synced-data.repository.ts index 3fa94be3..e46d4f4d 100644 --- a/backend/services/contribution-service/src/infrastructure/persistence/repositories/synced-data.repository.ts +++ b/backend/services/contribution-service/src/infrastructure/persistence/repositories/synced-data.repository.ts @@ -291,11 +291,14 @@ export class SyncedDataRepository implements ISyncedDataRepository { const accountSequences = directReferrals.map((r) => r.accountSequence); - // 只统计有 MINING_ENABLED 状态认种记录的直推用户数 + // 解锁阈值按「人头」计:常规认种和预种均以「该直推用户是否有任意一条 + // MINING_ENABLED 记录」为准,每人算 1,不论认种了几棵树或几份预种。 + // 预种 handler 会在用户首次付款时向 synced_adoptions 插入一条 + // treeCount=0、status=MINING_ENABLED 的 marker,确保预种用户也能被计入。 const adoptedCount = await this.client.syncedAdoption.findMany({ where: { accountSequence: { in: accountSequences }, - status: 'MINING_ENABLED', // 只统计最终成功的认种订单 + status: 'MINING_ENABLED', }, distinct: ['accountSequence'], });