fix(authorization): fix race condition in reward distribution calculation

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 <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-11 19:01:11 -08:00
parent fc55afb7b9
commit f94e7283d5
1 changed files with 24 additions and 4 deletions

View File

@ -859,12 +859,20 @@ export class AuthorizationApplicationService {
const communityStats = await this.statsRepository.findByAccountSequence( const communityStats = await this.statsRepository.findByAccountSequence(
nearestCommunity.userId.accountSequence, 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棵 const initialTarget = nearestCommunity.getInitialTarget() // 社区初始考核目标10棵
this.logger.debug( this.logger.debug(
`[getCommunityRewardDistribution] Community ${nearestCommunity.userId.accountSequence} ` + `[getCommunityRewardDistribution] Community ${nearestCommunity.userId.accountSequence} ` +
`benefitActive=false, currentTeamCount=${currentTeamCount}, initialTarget=${initialTarget}`, `benefitActive=false, rawTeamCount=${rawTeamCount}, treeCount=${treeCount}, ` +
`currentTeamCount(before)=${currentTeamCount}, initialTarget=${initialTarget}`,
) )
// 6. 查找上级社区(用于接收考核前的权益) // 6. 查找上级社区(用于接收考核前的权益)
@ -1023,9 +1031,15 @@ export class AuthorizationApplicationService {
// 5. 权益未激活,计算考核分配 // 5. 权益未激活,计算考核分配
const stats = await this.statsRepository.findByAccountSequence(nearestAuthProvince.userId.accountSequence) 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棵 const initialTarget = nearestAuthProvince.getInitialTarget() // 500棵
this.logger.debug(
`[getProvinceTeamRewardDistribution] rawTeamCount=${rawTeamCount}, treeCount=${treeCount}, currentTeamCount(before)=${currentTeamCount}`,
)
// 6. 查找上级(用于接收考核前的权益) // 6. 查找上级(用于接收考核前的权益)
let parentAccountSequence: number = HEADQUARTERS_ACCOUNT_SEQUENCE let parentAccountSequence: number = HEADQUARTERS_ACCOUNT_SEQUENCE
let parentReason = '上级为总部社区' let parentReason = '上级为总部社区'
@ -1227,9 +1241,15 @@ export class AuthorizationApplicationService {
// 5. 权益未激活,计算考核分配 // 5. 权益未激活,计算考核分配
const stats = await this.statsRepository.findByAccountSequence(nearestAuthCity.userId.accountSequence) 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棵 const initialTarget = nearestAuthCity.getInitialTarget() // 100棵
this.logger.debug(
`[getCityTeamRewardDistribution] rawTeamCount=${rawTeamCount}, treeCount=${treeCount}, currentTeamCount(before)=${currentTeamCount}`,
)
// 6. 查找上级 // 6. 查找上级
let parentAccountSequence: number = HEADQUARTERS_ACCOUNT_SEQUENCE let parentAccountSequence: number = HEADQUARTERS_ACCOUNT_SEQUENCE
let parentReason = '上级为总部社区' let parentReason = '上级为总部社区'