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

191 lines
6.3 KiB
Go

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
}
// 首页需要同时返回当前 30 天周期进度、累计统计、邀请码和任务配置。
now := time.Now()
timezone := normalizeTimezone(bundle.Config.Timezone)
periodKey, periodEndAt, secondsToReset := thirtyDayPeriodInfoAt(now, timezone, bundle.Config.CreateTime)
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: inviteTaskPeriodDays,
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
}