2026-04-27 19:12:00 +08:00

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
}