package invite import ( "chatapp3-golang/internal/model" "context" "net/url" "sort" "strings" "time" ) // GetHome 返回邀请活动首页所需的配置、任务、当前周期进度和累计统计。 func (s *InviteService) GetHome(ctx context.Context, user AuthUser) (*HomeResponse, error) { bundle, err := s.loadBundle(ctx, user.SysOrigin) if err != nil { return nil, err } inviteCode, err := s.ensureInviteCode(ctx, user) if err != nil { return nil, err } // 首页需要同时返回当前自然月周期进度、累计统计、邀请码和任务配置。 now := time.Now() timezone := normalizeTimezone(bundle.Config.Timezone) periodKey, periodEndAt, secondsToReset, resetPeriodDays := calendarMonthPeriodInfoAt(now, timezone) monthly, err := s.findMonthlyProgress(ctx, user.SysOrigin, user.UserID, periodKey) if err != nil { return nil, err } stats, err := s.loadStats(ctx, user.SysOrigin, user.UserID) if err != nil { return nil, err } claims, err := s.loadClaimedRuleIDs(ctx, user.SysOrigin, user.UserID, periodKey) if err != nil { return nil, err } inviterBindRule := firstRule(bundle.Rules, ruleTypeInviterBind) inviteeBindRule := firstRule(bundle.Rules, ruleTypeInviteeBind) inviteeRechargeRules := rulesByType(bundle.Rules, ruleTypeInviteeRecharge) validCountRules := rulesByType(bundle.Rules, ruleTypeInviterValidCount) totalRechargeRules := rulesByType(bundle.Rules, ruleTypeInviterTotalRecharge) resp := &HomeResponse{ CampaignEnabled: bundle.Config.Enabled, SysOrigin: user.SysOrigin, InviteCode: inviteCode, InviteLink: s.buildInviteLink(inviteCode), ShareTitle: defaultIfBlank(bundle.Config.ShareTitle, defaultShareTitle), ShareDesc: defaultIfBlank(bundle.Config.ShareDesc, defaultShareDesc), DownloadURL: bundle.Config.DownloadURL, ServiceContact: bundle.Config.ServiceContact, Timezone: timezone, PeriodKey: periodKey, MonthKey: periodKey, ServerTimeMs: now.UnixMilli(), PeriodEndAtMs: periodEndAt.UnixMilli(), SecondsToReset: secondsToReset, ResetPeriodDays: resetPeriodDays, ValidUserThreshold: effectiveValidThreshold(inviteeRechargeRules), InviterBindReward: rewardFromRule(inviterBindRule), InviteeBindReward: rewardFromRule(inviteeBindRule), InviteeRecharge: rewardsFromRules(inviteeRechargeRules), Stats: stats, } resp.MonthlyProgress.InviteCount = monthly.InviteCount resp.MonthlyProgress.ValidUserCount = monthly.ValidUserCount resp.MonthlyProgress.TotalRecharge = monthly.TotalRecharge resp.MonthlyProgress.RewardGoldCoins = monthly.RewardGoldCoins resp.ValidCountTasks = buildTaskViews(validCountRules, monthly.ValidUserCount, claims) resp.TotalRechargeTasks = buildTaskViews(totalRechargeRules, monthly.TotalRecharge, claims) return resp, nil } // buildInviteLink 返回用户分享出去的 H5 落地页链接;公开 landing 接口仍只用于 H5 拉数据。 func (s *InviteService) buildInviteLink(inviteCode string) string { baseURL := strings.TrimRight(strings.TrimSpace(s.cfg.Invite.PublicBaseURL), "/") if baseURL == "" { baseURL = "http://localhost:2900" } publicH5Path := strings.TrimSpace(s.cfg.Invite.PublicH5Path) if publicH5Path == "" { publicH5Path = "/h5/app-invite/landing.html" } rawURL := baseURL + "/" + strings.TrimLeft(publicH5Path, "/") parsedURL, err := url.Parse(rawURL) if err != nil { return rawURL + "?code=" + url.QueryEscape(inviteCode) } query := parsedURL.Query() query.Set("code", inviteCode) parsedURL.RawQuery = query.Encode() return parsedURL.String() } // buildTaskViews 根据规则、进度和领取状态生成任务展示列表。 func buildTaskViews(rules []model.InviteCampaignRewardRule, progress int64, claimed map[int64]bool) []TaskView { result := make([]TaskView, 0, len(rules)) for _, rule := range rules { if !rule.Enabled { continue } result = append(result, TaskView{ RuleID: rule.ID, RuleType: rule.RuleType, Threshold: rule.ThresholdValue, Progress: progress, GoldAmount: rule.GoldAmount, RewardGroupID: rule.RewardGroupID, Achieved: progress >= rule.ThresholdValue, Claimed: claimed[rule.ID], }) } return result } // firstRule 返回某类规则里的第一条规则。 func firstRule(rules []model.InviteCampaignRewardRule, ruleType string) *model.InviteCampaignRewardRule { for _, item := range rules { if item.RuleType == ruleType && item.Enabled { rule := item return &rule } } return nil } // rulesByType 返回某类规则的有序列表。 func rulesByType(rules []model.InviteCampaignRewardRule, ruleType string) []model.InviteCampaignRewardRule { result := make([]model.InviteCampaignRewardRule, 0) for _, item := range rules { if item.RuleType == ruleType && item.Enabled { result = append(result, item) } } sort.SliceStable(result, func(i, j int) bool { if result[i].Sort == result[j].Sort { if result[i].ThresholdValue == result[j].ThresholdValue { return result[i].ID < result[j].ID } return result[i].ThresholdValue < result[j].ThresholdValue } return result[i].Sort < result[j].Sort }) return result } // findRuleByID 按规则 ID 从规则列表中查找规则。 func findRuleByID(rules []model.InviteCampaignRewardRule, ruleID int64) *model.InviteCampaignRewardRule { for _, item := range rules { if item.ID == ruleID { rule := item return &rule } } return nil } // rewardFromRule 把规则转换成对外奖励视图。 func rewardFromRule(rule *model.InviteCampaignRewardRule) RewardView { if rule == nil { return RewardView{} } return RewardView{ RuleID: rule.ID, RuleType: rule.RuleType, Threshold: rule.ThresholdValue, GoldAmount: rule.GoldAmount, RewardGroupID: rule.RewardGroupID, } } // rewardsFromRules 把规则列表转换成奖励视图列表。 func rewardsFromRules(rules []model.InviteCampaignRewardRule) []RewardView { result := make([]RewardView, 0, len(rules)) for _, item := range rules { result = append(result, rewardFromRule(&item)) } return result } // effectiveValidThreshold 返回“有效用户”判定使用的固定充值门槛。 func effectiveValidThreshold(_ []model.InviteCampaignRewardRule) int64 { return defaultValidRechargeCoins }