From f94e7283d589af840032610e732cf17d261a32c3 Mon Sep 17 00:00:00 2001 From: hailin Date: Thu, 11 Dec 2025 19:01:11 -0800 Subject: [PATCH] fix(authorization): fix race condition in reward distribution calculation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When calculating reward distribution for community/province/city benefits, the totalTeamPlantingCount was being read after referral-service had already updated it with the current planting. This caused incorrect distribution where benefits were given to the community holder before they completed their assessment target. Fix: Subtract treeCount from rawTeamCount to restore the "before planting" team count, ensuring correct assessment calculation. Affected methods: - getCommunityRewardDistribution - getProvinceTeamRewardDistribution - getCityTeamRewardDistribution 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../authorization-application.service.ts | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/backend/services/authorization-service/src/application/services/authorization-application.service.ts b/backend/services/authorization-service/src/application/services/authorization-application.service.ts index fda98987..bf77b6f4 100644 --- a/backend/services/authorization-service/src/application/services/authorization-application.service.ts +++ b/backend/services/authorization-service/src/application/services/authorization-application.service.ts @@ -859,12 +859,20 @@ export class AuthorizationApplicationService { const communityStats = await this.statsRepository.findByAccountSequence( nearestCommunity.userId.accountSequence, ) - const currentTeamCount = communityStats?.totalTeamPlantingCount ?? 0 + const rawTeamCount = communityStats?.totalTeamPlantingCount ?? 0 + + // 重要:由于 referral-service 和 reward-service 都消费同一个 Kafka 事件, + // 存在竞态条件,此时查询到的 totalTeamPlantingCount 可能已经包含了本次认种。 + // 因此需要减去本次认种数量来还原"认种前"的团队数。 + // 注意:如果 referral-service 还没处理完,rawTeamCount 可能还是旧值, + // 此时 currentTeamCount 可能为负数,需要取 max(0, ...) + const currentTeamCount = Math.max(0, rawTeamCount - treeCount) const initialTarget = nearestCommunity.getInitialTarget() // 社区初始考核目标:10棵 this.logger.debug( `[getCommunityRewardDistribution] Community ${nearestCommunity.userId.accountSequence} ` + - `benefitActive=false, currentTeamCount=${currentTeamCount}, initialTarget=${initialTarget}`, + `benefitActive=false, rawTeamCount=${rawTeamCount}, treeCount=${treeCount}, ` + + `currentTeamCount(before)=${currentTeamCount}, initialTarget=${initialTarget}`, ) // 6. 查找上级社区(用于接收考核前的权益) @@ -1023,9 +1031,15 @@ export class AuthorizationApplicationService { // 5. 权益未激活,计算考核分配 const stats = await this.statsRepository.findByAccountSequence(nearestAuthProvince.userId.accountSequence) - const currentTeamCount = stats?.totalTeamPlantingCount ?? 0 + const rawTeamCount = stats?.totalTeamPlantingCount ?? 0 + // 修复竞态条件:减去本次认种数量来还原"认种前"的团队数 + const currentTeamCount = Math.max(0, rawTeamCount - treeCount) const initialTarget = nearestAuthProvince.getInitialTarget() // 500棵 + this.logger.debug( + `[getProvinceTeamRewardDistribution] rawTeamCount=${rawTeamCount}, treeCount=${treeCount}, currentTeamCount(before)=${currentTeamCount}`, + ) + // 6. 查找上级(用于接收考核前的权益) let parentAccountSequence: number = HEADQUARTERS_ACCOUNT_SEQUENCE let parentReason = '上级为总部社区' @@ -1227,9 +1241,15 @@ export class AuthorizationApplicationService { // 5. 权益未激活,计算考核分配 const stats = await this.statsRepository.findByAccountSequence(nearestAuthCity.userId.accountSequence) - const currentTeamCount = stats?.totalTeamPlantingCount ?? 0 + const rawTeamCount = stats?.totalTeamPlantingCount ?? 0 + // 修复竞态条件:减去本次认种数量来还原"认种前"的团队数 + const currentTeamCount = Math.max(0, rawTeamCount - treeCount) const initialTarget = nearestAuthCity.getInitialTarget() // 100棵 + this.logger.debug( + `[getCityTeamRewardDistribution] rawTeamCount=${rawTeamCount}, treeCount=${treeCount}, currentTeamCount(before)=${currentTeamCount}`, + ) + // 6. 查找上级 let parentAccountSequence: number = HEADQUARTERS_ACCOUNT_SEQUENCE let parentReason = '上级为总部社区'