package registerreward import ( "context" "strings" "time" "chatapp3-golang/internal/model" "gorm.io/gorm" "gorm.io/gorm/clause" ) // registerRewardGrantPlan 表示当前注册奖励日志最终命中的发奖决策。 type registerRewardGrantPlan struct { ClaimDate string // 领取日期,按每日限额口径记录 GrantMode string // 发奖决策模式 SkipDispatch bool // 是否跳过真实发奖动作 } // resolveGrantPlan 负责确定本次注册奖励是否还能占用当天名额。 func (s *RegisterRewardService) resolveGrantPlan( ctx context.Context, logRecord *model.ResidentRegisterRewardLog, event registerRewardEvent, resolved registerRewardResolvedConfig, ) (registerRewardGrantPlan, error) { // 已经有过发奖决策的日志,失败重试时必须沿用原始结果,避免重试把配额重新算乱。 if strings.TrimSpace(logRecord.ClaimDate) != "" && strings.TrimSpace(logRecord.GrantMode) != "" { return registerRewardGrantPlan{ ClaimDate: logRecord.ClaimDate, GrantMode: logRecord.GrantMode, SkipDispatch: strings.ToUpper(strings.TrimSpace(logRecord.GrantMode)) == registerRewardGrantModeQuotaExhausted, }, nil } claimDate := currentRegisterRewardClaimDate() if resolved.DailyLimit <= 0 { if err := updateRegisterRewardGrantDecision( s.repo.DB.WithContext(ctx), logRecord.ID, claimDate, registerRewardGrantModeIssueReward, ); err != nil { return registerRewardGrantPlan{}, err } return registerRewardGrantPlan{ ClaimDate: claimDate, GrantMode: registerRewardGrantModeIssueReward, }, nil } plan := registerRewardGrantPlan{ ClaimDate: claimDate, GrantMode: registerRewardGrantModeIssueReward, } err := s.repo.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { // 通过锁定当前系统的配置行,串行化每日限额检查,避免并发用户同时穿透当天名额。 var configRow model.ResidentRegisterRewardConfig if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}). Where("sys_origin = ?", strings.ToUpper(strings.TrimSpace(event.SysOrigin))). First(&configRow).Error; err != nil { return err } dailyLimit := configRow.DailyLimit if dailyLimit <= 0 { plan.GrantMode = registerRewardGrantModeIssueReward return updateRegisterRewardGrantDecision(tx, logRecord.ID, claimDate, plan.GrantMode) } var occupiedCount int64 if err := tx.Model(&model.ResidentRegisterRewardLog{}). Where("sys_origin = ? AND claim_date = ? AND grant_mode = ?", event.SysOrigin, claimDate, registerRewardGrantModeIssueReward). Count(&occupiedCount).Error; err != nil { return err } if occupiedCount >= dailyLimit { plan.GrantMode = registerRewardGrantModeQuotaExhausted plan.SkipDispatch = true } return updateRegisterRewardGrantDecision(tx, logRecord.ID, claimDate, plan.GrantMode) }) if err != nil { return registerRewardGrantPlan{}, err } return plan, nil } // updateRegisterRewardGrantDecision 回写日志的领取日期和发奖决策结果。 func updateRegisterRewardGrantDecision( db *gorm.DB, logID int64, claimDate string, grantMode string, ) error { return db.Model(&model.ResidentRegisterRewardLog{}). Where("id = ?", logID). Updates(map[string]any{ "claim_date": strings.TrimSpace(claimDate), "grant_mode": strings.ToUpper(strings.TrimSpace(grantMode)), "update_time": time.Now(), }).Error } // currentRegisterRewardClaimDate 返回“每日限额”口径下的当前日期。 func currentRegisterRewardClaimDate() string { location, err := time.LoadLocation(registerRewardDailyQuotaTimezone) if err != nil { return time.Now().Format("2006-01-02") } return time.Now().In(location).Format("2006-01-02") } // normalizeRegisterRewardClaimDate 规范后台查询传入的日期;未传时默认看今天。 func normalizeRegisterRewardClaimDate(raw string) string { trimmed := strings.TrimSpace(raw) if trimmed == "" { return currentRegisterRewardClaimDate() } if _, err := time.Parse("2006-01-02", trimmed); err == nil { return trimmed } return currentRegisterRewardClaimDate() }