130 lines
4.0 KiB
Go

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()
}