208 lines
7.1 KiB
Go
208 lines
7.1 KiB
Go
package invite
|
|
|
|
import (
|
|
"chatapp3-golang/internal/model"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
// loadBundle 加载某个系统下的邀请活动主配置和启用规则。
|
|
func (s *InviteService) loadBundle(ctx context.Context, sysOrigin string) (*campaignBundle, error) {
|
|
sysOrigin = normalizeSysOrigin(sysOrigin)
|
|
cacheKey := "invite:campaign:config:" + sysOrigin
|
|
if raw, err := s.repo.Redis.Get(ctx, cacheKey).Result(); err == nil && raw != "" {
|
|
var bundle campaignBundle
|
|
if jsonErr := json.Unmarshal([]byte(raw), &bundle); jsonErr == nil {
|
|
return &bundle, nil
|
|
}
|
|
}
|
|
|
|
bundle := &campaignBundle{
|
|
Config: model.InviteCampaignConfig{
|
|
SysOrigin: sysOrigin,
|
|
Enabled: false,
|
|
Timezone: defaultTimezone,
|
|
AntiCheatSameIPOnce: true,
|
|
AntiCheatSameDeviceOnce: true,
|
|
},
|
|
}
|
|
var configEntity model.InviteCampaignConfig
|
|
err := s.repo.DB.WithContext(ctx).
|
|
Where("sys_origin = ?", sysOrigin).
|
|
First(&configEntity).Error
|
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, err
|
|
}
|
|
if err == nil {
|
|
bundle.Config = configEntity
|
|
if strings.TrimSpace(bundle.Config.Timezone) == "" {
|
|
bundle.Config.Timezone = defaultTimezone
|
|
}
|
|
}
|
|
if err := s.repo.DB.WithContext(ctx).
|
|
Where("sys_origin = ?", sysOrigin).
|
|
Order("sort asc, threshold_value asc, id asc").
|
|
Find(&bundle.Rules).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
payload, _ := json.Marshal(bundle)
|
|
s.repo.Redis.Set(ctx, cacheKey, payload, 0)
|
|
return bundle, nil
|
|
}
|
|
|
|
// ensureInviteCode 确保用户拥有邀请码;没有则创建一个新的。
|
|
func (s *InviteService) ensureInviteCode(ctx context.Context, user AuthUser) (string, error) {
|
|
if mapping, err := s.findInviteCodeMappingByUserID(ctx, user.UserID); err == nil {
|
|
return mapping.InviteCode, nil
|
|
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return "", err
|
|
}
|
|
if user.Authorization == "" {
|
|
return "", NewAppError(401, "missing_authorization", "authorization is required to generate invite code")
|
|
}
|
|
resp, err := s.java.EnsureInviteCode(ctx, user.Authorization)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
info, infoErr := s.findUserBaseInfo(ctx, user.UserID)
|
|
if infoErr != nil {
|
|
return "", infoErr
|
|
}
|
|
mapping := model.InviteCodeMapping{
|
|
UserID: user.UserID,
|
|
InviteCode: resp.InviteCode,
|
|
Account: info.Account,
|
|
}
|
|
if err := s.repo.DB.WithContext(ctx).
|
|
Clauses(clause.OnConflict{
|
|
Columns: []clause.Column{{Name: "user_id"}},
|
|
DoUpdates: clause.AssignmentColumns([]string{"invite_code", "account"}),
|
|
}).
|
|
Create(&mapping).Error; err != nil {
|
|
return "", err
|
|
}
|
|
return resp.InviteCode, nil
|
|
}
|
|
|
|
// findInviteCodeMappingByUserID 按用户 ID 查询邀请码映射。
|
|
func (s *InviteService) findInviteCodeMappingByUserID(ctx context.Context, userID int64) (*model.InviteCodeMapping, error) {
|
|
var mapping model.InviteCodeMapping
|
|
err := s.repo.DB.WithContext(ctx).
|
|
Where("user_id = ?", userID).
|
|
First(&mapping).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &mapping, nil
|
|
}
|
|
|
|
// findInviteCodeMappingByCode 按邀请码查询映射记录。
|
|
func (s *InviteService) findInviteCodeMappingByCode(ctx context.Context, inviteCode string) (*model.InviteCodeMapping, error) {
|
|
var mapping model.InviteCodeMapping
|
|
err := s.repo.DB.WithContext(ctx).
|
|
Where("invite_code = ?", inviteCode).
|
|
First(&mapping).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &mapping, nil
|
|
}
|
|
|
|
// findUserBaseInfo 读取用户基础资料。
|
|
func (s *InviteService) findUserBaseInfo(ctx context.Context, userID int64) (*model.UserBaseInfo, error) {
|
|
var info model.UserBaseInfo
|
|
err := s.repo.DB.WithContext(ctx).
|
|
Where("id = ?", userID).
|
|
First(&info).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &info, nil
|
|
}
|
|
|
|
// findRelationByInvitee 查询某个被邀请人的已绑定关系。
|
|
func (s *InviteService) findRelationByInvitee(ctx context.Context, sysOrigin string, inviteeUserID int64) (*model.InviteCampaignRelation, error) {
|
|
var relation model.InviteCampaignRelation
|
|
err := s.repo.DB.WithContext(ctx).
|
|
Where("sys_origin = ? AND invitee_user_id = ?", sysOrigin, inviteeUserID).
|
|
First(&relation).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &relation, nil
|
|
}
|
|
|
|
// findMonthlyProgress 查询邀请人的当前任务周期进度;底层字段沿用 month_key 以兼容历史表结构。
|
|
func (s *InviteService) findMonthlyProgress(ctx context.Context, sysOrigin string, inviterUserID int64, monthKey string) (*model.InviteCampaignMonthlyProgress, error) {
|
|
var progress model.InviteCampaignMonthlyProgress
|
|
err := s.repo.DB.WithContext(ctx).
|
|
Where("sys_origin = ? AND inviter_user_id = ? AND month_key = ?", sysOrigin, inviterUserID, monthKey).
|
|
First(&progress).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return &model.InviteCampaignMonthlyProgress{
|
|
SysOrigin: sysOrigin,
|
|
InviterUserID: inviterUserID,
|
|
MonthKey: monthKey,
|
|
}, nil
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &progress, nil
|
|
}
|
|
|
|
// loadStats 汇总邀请人的累计邀请统计。
|
|
func (s *InviteService) loadStats(ctx context.Context, sysOrigin string, inviterUserID int64) (InviteStats, error) {
|
|
stats := InviteStats{}
|
|
if err := s.repo.DB.WithContext(ctx).
|
|
Model(&model.InviteCampaignRelation{}).
|
|
Where("sys_origin = ? AND inviter_user_id = ? AND eligible_status = ?", sysOrigin, inviterUserID, relationStatusEligible).
|
|
Count(&stats.TotalInvitedUsers).Error; err != nil {
|
|
return stats, err
|
|
}
|
|
if err := s.repo.DB.WithContext(ctx).Raw(`
|
|
SELECT COALESCE(COUNT(1), 0)
|
|
FROM invite_campaign_invitee_progress ip
|
|
INNER JOIN invite_campaign_relation r ON r.id = ip.relation_id
|
|
WHERE r.sys_origin = ? AND r.inviter_user_id = ? AND r.eligible_status = ? AND ip.valid_user = 1
|
|
`, sysOrigin, inviterUserID, relationStatusEligible).Scan(&stats.TotalValidUsers).Error; err != nil {
|
|
return stats, err
|
|
}
|
|
if err := s.repo.DB.WithContext(ctx).Raw(`
|
|
SELECT COALESCE(SUM(ip.total_recharge_coins), 0)
|
|
FROM invite_campaign_invitee_progress ip
|
|
INNER JOIN invite_campaign_relation r ON r.id = ip.relation_id
|
|
WHERE r.sys_origin = ? AND r.inviter_user_id = ? AND r.eligible_status = ?
|
|
`, sysOrigin, inviterUserID, relationStatusEligible).Scan(&stats.TotalRechargeCoins).Error; err != nil {
|
|
return stats, err
|
|
}
|
|
if err := s.repo.DB.WithContext(ctx).Raw(`
|
|
SELECT COALESCE(SUM(gold_amount), 0)
|
|
FROM invite_campaign_reward_log
|
|
WHERE sys_origin = ? AND inviter_user_id = ? AND target_user_id = ? AND status = ?
|
|
`, sysOrigin, inviterUserID, inviterUserID, rewardLogStatusSuccess).Scan(&stats.RewardGoldCoins).Error; err != nil {
|
|
return stats, err
|
|
}
|
|
return stats, nil
|
|
}
|
|
|
|
// loadClaimedRuleIDs 读取某个任务周期已领取的任务规则集合。
|
|
func (s *InviteService) loadClaimedRuleIDs(ctx context.Context, sysOrigin string, inviterUserID int64, monthKey string) (map[int64]bool, error) {
|
|
var claims []model.InviteCampaignTaskClaim
|
|
if err := s.repo.DB.WithContext(ctx).
|
|
Where("sys_origin = ? AND inviter_user_id = ? AND month_key = ?", sysOrigin, inviterUserID, monthKey).
|
|
Find(&claims).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
result := make(map[int64]bool, len(claims))
|
|
for _, item := range claims {
|
|
result[item.RuleID] = true
|
|
}
|
|
return result, nil
|
|
}
|