191 lines
6.3 KiB
Go
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
|
|
}
|