Files
hjsq_server/app/service/sys_config/paymentpopup.go
T
2026-10-08 16:50:13 +08:00

200 lines
8.9 KiB
Go

package sys_config
import (
"fmt"
"math/rand"
"strconv"
"time"
"91jd-server/app/appg"
"91jd-server/app/proto"
"91jd-server/common"
"91jd-server/common/constant/redisconst"
"91jd-server/common/log"
"91jd-server/models/v/sysconfmod"
"91jd-server/models/v/usermod"
"github.com/vmihailenco/msgpack/v5"
)
// mapUserPaymentStatusPopup 用户付费状态分层 → 需要拉取的配置项集合
var mapUserPaymentStatusPopup = map[string]map[sysconfmod.VCode]bool{
usermod.UserPaymentStatusPopupNewUnpay: {
sysconfmod.VCodeUserPaymentStatusPopupNewUserPopup: true,
sysconfmod.VCodeUserPaymentStatusPopupNewUserFloating: true,
sysconfmod.VCodeUserPaymentStatusPopupNewUserPlayPage: true,
sysconfmod.VCodeUserPaymentStatusPopupNewUserMeTab: true,
sysconfmod.VCodeUserPaymentStatusPopupNewUserVipCardID: true,
sysconfmod.VCodeUserPaymentStatusPopupNewUserCountdown: true,
},
usermod.UserPaymentStatusPopupUnder7DayUnpay: {
sysconfmod.VCodeUserPaymentStatusPopupUnpayUnder7dayPopup: true,
sysconfmod.VCodeUserPaymentStatusPopupUnpayUnder7dayFloating: true,
sysconfmod.VCodeUserPaymentStatusPopupUnpayUnder7dayPlayPage: true,
sysconfmod.VCodeUserPaymentStatusPopupUnpayUnder7dayMeTab: true,
sysconfmod.VCodeUserPaymentStatusPopupUnpayUnder7dayVipCardID: true,
sysconfmod.VCodeUserPaymentStatusPopupUnpayUnder7dayCountdown: true,
},
usermod.UserPaymentStatusPopupUnder7DayUnpayNoCountdown: {
sysconfmod.VCodeUserPaymentStatusPopupUnpayUnder7dayPopupNoCountdown: true,
sysconfmod.VCodeUserPaymentStatusPopupUnpayUnder7dayFloatingNoCountdown: true,
sysconfmod.VCodeUserPaymentStatusPopupUnpayUnder7dayPlayPageNoCountdown: true,
sysconfmod.VCodeUserPaymentStatusPopupUnpayUnder7dayMeTabNoCountdown: true,
sysconfmod.VCodeUserPaymentStatusPopupUnpayUnder7dayVipCardID: true,
sysconfmod.VCodeUserPaymentStatusPopupUnpayUnder7dayCountdown: true,
},
usermod.UserPaymentStatusPopupOver7DayUnpay: {
sysconfmod.VCodeUserPaymentStatusPopupUnpayOver7dayPopup: true,
sysconfmod.VCodeUserPaymentStatusPopupUnpayOver7dayFloating: true,
sysconfmod.VCodeUserPaymentStatusPopupUnpayOver7dayPlayPage: true,
sysconfmod.VCodeUserPaymentStatusPopupUnpayOver7dayMeTab: true,
sysconfmod.VCodeUserPaymentStatusPopupUnpayOver7dayVipCardID: true,
sysconfmod.VCodeUserPaymentStatusPopupUnpayOver7dayCountdown: true,
},
usermod.UserPaymentStatusPopupOver7DayNeedUpgrade: {
sysconfmod.VCodeUserPaymentStatusPopupPayNeedUpgradePopup: true,
sysconfmod.VCodeUserPaymentStatusPopupPayNeedUpgradeFloating: true,
sysconfmod.VCodeUserPaymentStatusPopupPayNeedUpgradePlayPage: true,
sysconfmod.VCodeUserPaymentStatusPopupPayNeedUpgradeMeTab: true,
sysconfmod.VCodeUserPaymentStatusPopupPayNeedUpgradeVipCardID: true,
sysconfmod.VCodeUserPaymentStatusPopupPayNeedUpgradeCountdown: true,
},
usermod.UserPaymentStatusPopupMaxVIPLevel: {
sysconfmod.VCodeUserPaymentStatusPopupMaxVIPLevel: true,
},
}
// SysConfUserPaymentStatusPopup 根据用户付费状态分层,返回对应的分层弹窗配置与分层标识。
// 结果按分层做 1-3 分钟随机时长的 Redis 缓存。
func SysConfUserPaymentStatusPopup(user *usermod.User) (res proto.PaymentStatusPopupConfig, userType string, err error) {
if user == nil {
return
}
userType = user.GetPaymentStatusPopup()
userCreateTime := user.CreatedAt
vCodes, ok := mapUserPaymentStatusPopup[userType]
if !ok {
return
}
// 缓存键使用原始分层标识:有倒计时/无倒计时两个分层的配置项不同,必须分开缓存,
// 否则会互相覆盖,导致下发错误的图片源。
// v2:修正倒计时开关解析后旧缓存可能把 LastDiscountTime 固化为 0,需要换 key 失效。
cacheKey := fmt.Sprintf("%s:%s:v2", redisconst.UserPaymentStatusPopupCache, userType)
// 读缓存
str, err := appg.Redis.Get(cacheKey)
if err != nil {
log.Warn(fmt.Sprintf("获取用户分层弹窗缓存数据异常:%v", err))
}
if str != nil {
if err = msgpack.Unmarshal([]byte(*str), &res); err == nil {
// 缓存按分层(userType)共享、不含 uid,图片配置可共享;但 LastDiscountTime
// 是 per-user 动态值(依赖当前用户注册时间),必须基于当前用户重算,
// 否则会串用到同分层其他用户的时刻。
res.LastDiscountTime = paymentPopupDiscountTime(userType, userCreateTime, !res.LastDiscountTime.IsZero())
userType = normalizePaymentStatusPopupType(userType)
return
}
log.Warn(fmt.Sprintf("解析用户分层弹窗缓存数据异常:%v", err))
}
vCodesKey := make([]sysconfmod.VCode, 0, len(vCodes))
for vCode := range vCodes {
vCodesKey = append(vCodesKey, vCode)
}
data, err := sysconfmod.BatchGetInfoByVCode(vCodesKey)
if err != nil {
log.Error("获取用户分层弹窗数据失败!", log.E(err))
res = proto.PaymentStatusPopupConfig{} // 兜底:返回空配置
} else { // 使用两层 else 是为了让兜底方案也能被缓存,避免故障时每次请求都访问数据库
countdownEnabled := false
for _, d := range data {
switch d.VCode {
case string(sysconfmod.VCodeUserPaymentStatusPopupNewUserPopup),
string(sysconfmod.VCodeUserPaymentStatusPopupUnpayUnder7dayPopup),
string(sysconfmod.VCodeUserPaymentStatusPopupUnpayUnder7dayPopupNoCountdown),
string(sysconfmod.VCodeUserPaymentStatusPopupUnpayOver7dayPopup),
string(sysconfmod.VCodeUserPaymentStatusPopupPayNeedUpgradePopup):
res.Homepage = d.Value
case string(sysconfmod.VCodeUserPaymentStatusPopupNewUserFloating),
string(sysconfmod.VCodeUserPaymentStatusPopupUnpayUnder7dayFloating),
string(sysconfmod.VCodeUserPaymentStatusPopupUnpayUnder7dayFloatingNoCountdown),
string(sysconfmod.VCodeUserPaymentStatusPopupUnpayOver7dayFloating),
string(sysconfmod.VCodeUserPaymentStatusPopupPayNeedUpgradeFloating):
res.HomepageFlot = d.Value
case string(sysconfmod.VCodeUserPaymentStatusPopupNewUserPlayPage),
string(sysconfmod.VCodeUserPaymentStatusPopupUnpayUnder7dayPlayPage),
string(sysconfmod.VCodeUserPaymentStatusPopupUnpayUnder7dayPlayPageNoCountdown),
string(sysconfmod.VCodeUserPaymentStatusPopupUnpayOver7dayPlayPage),
string(sysconfmod.VCodeUserPaymentStatusPopupPayNeedUpgradePlayPage):
res.PlayPage = d.Value
case string(sysconfmod.VCodeUserPaymentStatusPopupNewUserMeTab),
string(sysconfmod.VCodeUserPaymentStatusPopupUnpayUnder7dayMeTab),
string(sysconfmod.VCodeUserPaymentStatusPopupUnpayUnder7dayMeTabNoCountdown),
string(sysconfmod.VCodeUserPaymentStatusPopupUnpayOver7dayMeTab),
string(sysconfmod.VCodeUserPaymentStatusPopupPayNeedUpgradeMeTab),
string(sysconfmod.VCodeUserPaymentStatusPopupMaxVIPLevel):
res.MeTab = d.Value
case string(sysconfmod.VCodeUserPaymentStatusPopupNewUserVipCardID),
string(sysconfmod.VCodeUserPaymentStatusPopupUnpayUnder7dayVipCardID),
string(sysconfmod.VCodeUserPaymentStatusPopupUnpayOver7dayVipCardID),
string(sysconfmod.VCodeUserPaymentStatusPopupPayNeedUpgradeVipCardID):
res.VipCard = d.Value
case string(sysconfmod.VCodeUserPaymentStatusPopupNewUserCountdown),
string(sysconfmod.VCodeUserPaymentStatusPopupUnpayUnder7dayCountdown):
// 后台 bool 配置落库为 "true"/"false"(也可兼容 "1"/"0"),不能只判断 "1"。
enabled, parseErr := strconv.ParseBool(d.Value)
if parseErr != nil {
enabled = false
}
if enabled {
countdownEnabled = true
}
}
}
res.LastDiscountTime = paymentPopupDiscountTime(userType, userCreateTime, countdownEnabled)
}
common.Go(func() {
// 加入缓存,随机 60-180 秒,避免缓存集中失效
random := rand.Intn(120) + 60
b, mErr := msgpack.Marshal(res)
if mErr != nil {
return
}
if sErr := appg.Redis.Set(cacheKey, b, time.Duration(random)*time.Second); sErr != nil {
log.Warn(fmt.Sprintf("设置用户分层弹窗缓存数据异常:%v", sErr))
}
})
userType = normalizePaymentStatusPopupType(userType)
return
}
// normalizePaymentStatusPopupType 统一对外的分层标识:无倒计时仅作内部区分,对外并入 under_7_day_unpay。
// 前端通过 LastDiscountTime 是否为零值来判断是否展示倒计时。
func normalizePaymentStatusPopupType(userType string) string {
if userType == usermod.UserPaymentStatusPopupUnder7DayUnpayNoCountdown {
return usermod.UserPaymentStatusPopupUnder7DayUnpay
}
return userType
}
// paymentPopupDiscountTime 返回该分层的倒计时截止时间(UTC):
// 新用户 1 天,7天内未付款(含无倒计时变体) 2 天,其余分层或无倒计时开关时返回零值。
func paymentPopupDiscountTime(userType string, userCreateTime time.Time, countdownEnabled bool) time.Time {
if !countdownEnabled {
return time.Time{}
}
switch userType {
case usermod.UserPaymentStatusPopupNewUnpay:
return userCreateTime.AddDate(0, 0, 1).UTC()
case usermod.UserPaymentStatusPopupUnder7DayUnpay,
usermod.UserPaymentStatusPopupUnder7DayUnpayNoCountdown:
return userCreateTime.AddDate(0, 0, 2).UTC()
default:
return time.Time{}
}
}