168 lines
5.2 KiB
Go
168 lines
5.2 KiB
Go
package invite
|
|
|
|
import (
|
|
"chatapp3-golang/internal/model"
|
|
"chatapp3-golang/internal/utils"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
// ClaimTask 处理邀请任务奖励领取请求。
|
|
func (s *InviteService) ClaimTask(ctx context.Context, user AuthUser, req TaskClaimRequest) (*TaskClaimResponse, error) {
|
|
if req.RuleID == 0 {
|
|
return nil, NewAppError(400, "invalid_rule_id", "ruleId is required")
|
|
}
|
|
bundle, err := s.loadBundle(ctx, user.SysOrigin)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rule := findRuleByID(bundle.Rules, req.RuleID)
|
|
if rule == nil || !rule.Enabled {
|
|
return nil, NewAppError(404, "rule_not_found", "rule not found")
|
|
}
|
|
if rule.RuleType != ruleTypeInviterValidCount && rule.RuleType != ruleTypeInviterTotalRecharge {
|
|
return nil, NewAppError(400, "invalid_rule_type", "rule cannot be claimed manually")
|
|
}
|
|
|
|
monthKey := monthKeyAt(time.Now(), normalizeTimezone(bundle.Config.Timezone))
|
|
claimed, err := s.hasTaskClaim(ctx, user.SysOrigin, user.UserID, monthKey, rule.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
monthly, err := s.findMonthlyProgress(ctx, user.SysOrigin, user.UserID, monthKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
progressValue := monthly.TotalRecharge
|
|
if rule.RuleType == ruleTypeInviterValidCount {
|
|
progressValue = monthly.InviteCount
|
|
}
|
|
if claimed {
|
|
return &TaskClaimResponse{
|
|
Claimed: true,
|
|
AlreadyClaimed: true,
|
|
RuleID: rule.ID,
|
|
RuleType: rule.RuleType,
|
|
Progress: progressValue,
|
|
Threshold: rule.ThresholdValue,
|
|
Reward: rewardFromRule(rule),
|
|
}, nil
|
|
}
|
|
if progressValue < rule.ThresholdValue {
|
|
return nil, NewAppError(400, "task_not_reached", "task threshold not reached")
|
|
}
|
|
|
|
// 先发奖,再落领取记录;发奖侧通过 businessKey 兜底幂等。
|
|
_, err = s.dispatchReward(ctx, rewardDispatchInput{
|
|
SysOrigin: user.SysOrigin,
|
|
MonthKey: monthKey,
|
|
InviterUserID: refInt64(user.UserID),
|
|
TargetUserID: user.UserID,
|
|
Reward: rewardFromRule(rule),
|
|
Scene: rule.RuleType,
|
|
RuleID: refInt64(rule.ID),
|
|
BusinessKey: fmt.Sprintf("claim:%d:%s:%d", user.UserID, monthKey, rule.ID),
|
|
GoldOrigin: mapGoldOrigin(rule.RuleType),
|
|
PropsOrigin: mapPropsOrigin(rule.RuleType),
|
|
Remark: "resident invite monthly task reward",
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
now := time.Now()
|
|
err = s.repo.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
if err := s.createTaskClaimIfAbsentTx(tx, user.SysOrigin, user.UserID, monthKey, rule.ID, now); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &TaskClaimResponse{
|
|
Claimed: true,
|
|
AlreadyClaimed: false,
|
|
RuleID: rule.ID,
|
|
RuleType: rule.RuleType,
|
|
Progress: progressValue,
|
|
Threshold: rule.ThresholdValue,
|
|
Reward: rewardFromRule(rule),
|
|
}, nil
|
|
}
|
|
|
|
// hasTaskClaim 判断任务是否已经被领取。
|
|
func (s *InviteService) hasTaskClaim(ctx context.Context, sysOrigin string, inviterUserID int64, monthKey string, ruleID int64) (bool, error) {
|
|
var count int64
|
|
err := s.repo.DB.WithContext(ctx).
|
|
Model(&model.InviteCampaignTaskClaim{}).
|
|
Where("sys_origin = ? AND inviter_user_id = ? AND month_key = ? AND rule_id = ?", sysOrigin, inviterUserID, monthKey, ruleID).
|
|
Count(&count).Error
|
|
return count > 0, err
|
|
}
|
|
|
|
// incrementMonthlyProgressTx 在事务里更新月度进度。
|
|
func (s *InviteService) incrementMonthlyProgressTx(tx *gorm.DB, sysOrigin string, inviterUserID int64, monthKey string, mutate func(item *model.InviteCampaignMonthlyProgress)) error {
|
|
var progress model.InviteCampaignMonthlyProgress
|
|
err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
|
|
Where("sys_origin = ? AND inviter_user_id = ? AND month_key = ?", sysOrigin, inviterUserID, monthKey).
|
|
First(&progress).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
id, idErr := utils.NextID()
|
|
if idErr != nil {
|
|
return idErr
|
|
}
|
|
now := time.Now()
|
|
progress = model.InviteCampaignMonthlyProgress{
|
|
ID: id,
|
|
SysOrigin: sysOrigin,
|
|
InviterUserID: inviterUserID,
|
|
MonthKey: monthKey,
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
}
|
|
mutate(&progress)
|
|
return tx.Create(&progress).Error
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mutate(&progress)
|
|
progress.UpdateTime = time.Now()
|
|
return tx.Save(&progress).Error
|
|
}
|
|
|
|
// createTaskClaimIfAbsentTx 在事务里创建领取记录,避免重复领取。
|
|
func (s *InviteService) createTaskClaimIfAbsentTx(tx *gorm.DB, sysOrigin string, inviterUserID int64, monthKey string, ruleID int64, claimTime time.Time) error {
|
|
var existing model.InviteCampaignTaskClaim
|
|
err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
|
|
Where("sys_origin = ? AND inviter_user_id = ? AND month_key = ? AND rule_id = ?", sysOrigin, inviterUserID, monthKey, ruleID).
|
|
First(&existing).Error
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return err
|
|
}
|
|
id, idErr := utils.NextID()
|
|
if idErr != nil {
|
|
return idErr
|
|
}
|
|
return tx.Create(&model.InviteCampaignTaskClaim{
|
|
ID: id,
|
|
SysOrigin: sysOrigin,
|
|
InviterUserID: inviterUserID,
|
|
MonthKey: monthKey,
|
|
RuleID: ruleID,
|
|
ClaimTime: claimTime,
|
|
CreateTime: claimTime,
|
|
UpdateTime: claimTime,
|
|
}).Error
|
|
}
|