You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

892 lines
21 KiB
Go

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package provider
import (
"context"
"joybuywork/internal/app"
"joybuywork/internal/consts"
"joybuywork/internal/consts/ckey"
"joybuywork/internal/repository/cache"
"joybuywork/internal/repository/finder"
"joybuywork/internal/repository/model"
"joybuywork/internal/repository/mtype"
"joybuywork/internal/repository/withdefault"
"joybuywork/internal/service/provider/s2s"
"joybuywork/internal/service/queue/producer"
"joybuywork/internal/service/queue/types"
"joybuywork/internal/utils"
"joybuywork/internal/utils/lockdo"
"joybuywork/internal/utils/logic"
"joybuywork/internal/utils/merr"
"joybuywork/internal/utils/verify"
"strings"
"time"
"joybuywork/proto/go_proto/workerr"
ddevicepb "gits.branchcn.com/backend/deeplink/proto/go_proto/ddevice"
"gits.branchcn.com/backend/microcode/plugins/derivative/lock"
"gorm.io/gen/field"
)
type FissionUser struct {
logic.Context
user *model.FissionUser
cpn *model.FissionCampaign
channel *model.FissionCampaignChannel
conf *model.FissionCampaignConfig
cpnControl *CampaignTZ
upData, msUpData []field.AssignExpr
changeLangMark bool
changeLvMark bool
subUserIdLock, deviceIdLock *lock.RedisLock
}
func NewFissionUser(ctx context.Context, cpn *model.FissionCampaign, channel *model.FissionCampaignChannel, opts ...logic.CtxOption) *FissionUser {
user := &FissionUser{
cpn: cpn,
channel: channel,
}
user.BaseInit(ctx, merr.NewBE(ctx), opts...)
return user
}
func (s *FissionUser) NotifyUser(behaviorTime, noticeType int64, isInstall bool, noticeTag, userMark, uid, deviceId, lvMark, lang, platform, customParam string, webChannelId, channelId int64, expTime time.Duration, parent *model.FissionUser) (user, bindParent *model.FissionUser, newUser bool, err error) {
defer s.lockRelease()
cpn := s.cpn
uct, err := s.userCampaignType(lang, lvMark, "")
if err != nil {
return
}
// 如果没有关联的上级,尝试找一下
defer func() {
if err != nil || user == nil {
return
}
err = s._refresh(user)
if err != nil {
return
}
if (parent == nil || parent.ID != user.ParentID) && user.ParentID > 0 {
bindParent, err = finder.FissionUser(s.Ctx, cpn.ID, user.ParentID)
if err != nil {
err = s.BE().CacheError(err)
return
}
} else {
bindParent = parent
}
if newUser {
return
}
}()
user, err = s.notifyFindUser(noticeType, uct, userMark, uid, deviceId, platform, lang, lvMark, customParam, webChannelId)
if err != nil || user != nil {
return
}
if uid != "" {
lc := s.getSubUserIdLock(uid)
if lc.Error() != nil {
err = workerr.BusyError
return
}
}
if deviceId != "" {
lc := s.getDeviceIdLock(deviceId)
if lc.Error() != nil {
err = workerr.BusyError
return
}
}
// 再重新查一次
user, err = s.notifyFindUser(noticeType, uct, userMark, uid, deviceId, platform, lang, lvMark, customParam, webChannelId)
if err != nil || user != nil {
return
}
if cpn.FissionID == consts.IdKeep && user == nil && noticeType == types.NoticeTypeCustom {
return
}
lvMark, err = s._lvMark(uid, lang, lvMark, mtype.LVApiLogSourceNotifyUser)
if err != nil {
return
}
//新的活动用户
newUser = true
user = withdefault.FissionUser(&model.FissionUser{
CreatedAt: behaviorTime,
CampaignID: cpn.ID,
ChannelID: 0,
ParentID: 0,
Deep: 0,
IsNew: utils.Ternary[int8](isInstall, mtype.Yes, mtype.No),
SubUserID: uid,
DeviceID: deviceId,
UserMark: utils.Ternary[string](uid == "", deviceId, uid),
UserType: mtype.FissionUserTypePlay,
ParentIds: "",
IsView: utils.Ternary[int8](uid == consts.FissionViewer, mtype.Yes, mtype.No),
ShareLink: "",
Source: 0,
LangMark: lang,
UserLang: uct.LangMark,
LevelMark: lvMark,
UserLevelMark: uct.LevelMark,
UserLevel: uct.LevelId,
})
switch noticeType {
case types.NoticeTypeOpen:
user.Source = mtype.FissionUserSourceOpen
case types.NoticeTypeInstall:
user.Source = mtype.FissionUserSourceInstall
if noticeTag == types.NoticeTagWeb {
user.Source = mtype.FissionUserSourceWebFirstOpen
}
case types.NoticeTypeRegister:
user.Source = mtype.FissionUserSourceRegister
case types.NoticeTypeCustom:
user.Source = mtype.FissionUserSourceCustom
case types.NoticeTypePay:
user.Source = mtype.FissionUserSourcePay
}
if parent != nil {
user.ParentID = parent.ID
user.ChannelID = parent.ChannelID
user.Deep = parent.Deep + 1
user.LangMark = utils.STernary[string](lang, "", parent.LangMark)
if parent.ParentID > 0 {
user.SetParentIds(append(parent.GetParentIds(), parent.ParentID))
}
}
// 如果没绑定上级的渠道,就绑定来源渠道
if channelId > 0 && user.ChannelID == 0 {
user.ChannelID = channelId
}
err = s._create(user, uct, platform, lvMark, customParam, "", "", webChannelId)
if err != nil {
return
}
if user.SubUserID != "" {
producer.NewCampaignNotice(s.Ctx).ProjectUser(types.ProjectUser{
ProjectId: cpn.ProjectID,
UserId: user.SubUserID,
})
}
app.Redis.Cache.Set(s.Ctx, ckey.FissionUserUniqMark(cpn.ID, userMark), user.ID, expTime)
app.Redis.Cache.Set(s.Ctx, ckey.FissionUserIovdMark(user.ID), userMark, expTime)
return
}
func (s *FissionUser) VisitUser(uid, platform, lang, lvMark, customParam, ip, centerHashId, moduleId string, device *ddevicepb.FindBlackboxRes, wbu *model.FissionWebUser) (user *model.FissionUser, err error) {
defer s.lockRelease()
defer func() {
if err != nil {
return
}
err = s._refresh(user)
}()
lvMark, err = s._lvMark(uid, lang, lvMark, mtype.LVApiLogSourceVisitUser)
if err != nil {
return
}
conf, err := s.getConf()
if err != nil {
return
}
var webChancelId int64
if wbu != nil {
webChancelId = wbu.ChannelID
}
userId := utils.Ternary[string](uid == "", consts.FissionViewer, uid)
// 白名单校验
err = s._white(conf, userId, lang)
if err != nil {
return
}
countryMark := s._country(conf.RewardGroup, ip)
user, err = s._findBySubUserId(userId)
if err != nil {
return
}
uct, err := s.userCampaignType(lang, lvMark, countryMark)
if err != nil {
return
}
//有用户,或者活动结束了
if user != nil {
user = s._refreshAttr(user, uct, platform, lang, lvMark, customParam, centerHashId, moduleId, webChancelId)
return
}
var deviceId string
var parent *model.FissionUser
if userId != consts.FissionViewer {
user, parent, deviceId, err = s._deviceUser(device, uid, conf)
if err != nil || user != nil {
user = s._refreshSubUserId(user, uid)
user = s._refreshAttr(user, uct, platform, lang, lvMark, customParam, centerHashId, moduleId, webChancelId)
return
}
}
//加锁来创建
lc := s.getSubUserIdLock(userId).Lock()
if lc.Error() != nil {
err = workerr.BusyError
return
}
if deviceId != "" {
lc1 := s.getDeviceIdLock(deviceId).Lock()
if lc1.Error() != nil {
err = workerr.BusyError
return
}
}
//获取到锁再找一次
user, err = s._findBySubUserId(userId)
if err != nil {
err = s.BE().CacheError(err)
return
}
if user != nil {
user = s._refreshAttr(user, uct, platform, lang, lvMark, customParam, centerHashId, moduleId, webChancelId)
return
}
//创建用户
user = withdefault.FissionUser(&model.FissionUser{
CampaignID: s.cpn.ID,
SubUserID: userId,
DeviceID: deviceId,
UserMark: utils.Ternary[string](userId != "", userId, deviceId),
UserType: utils.Ternary[int8](s.cpn.EndTime < time.Now().Unix(), mtype.FissionUserTypeVisitor, mtype.FissionUserTypePlay),
IsView: utils.Ternary[int8](userId == consts.FissionViewer, mtype.Yes, mtype.No),
Source: mtype.FissionUserSourceVisit,
LangMark: lang,
LevelMark: lvMark,
UserLevelMark: uct.LevelMark,
UserLevel: uct.LevelId,
UserLang: uct.LangMark,
UserCountry: uct.CountryMark,
})
if s.channel != nil {
user.ChannelID = s.channel.ID
}
if parent != nil {
user.ParentID = parent.ID
user.ChannelID = parent.ChannelID
user.Deep = parent.Deep + 1
if parent.ParentID > 0 {
user.SetParentIds(append(parent.GetParentIds(), parent.ParentID))
}
}
err = s._create(user, uct, platform, lvMark, customParam, centerHashId, moduleId, webChancelId)
if err != nil {
return
}
if user.SubUserID != "" {
producer.NewCampaignNotice(s.Ctx).ProjectUser(types.ProjectUser{
ProjectId: s.cpn.ProjectID,
UserId: user.SubUserID,
})
}
return
}
func (s *FissionUser) GetChangeLangMark() bool {
return s.changeLangMark
}
func (s *FissionUser) GetChangeLvMark() bool {
return s.changeLvMark
}
func (s *FissionUser) getSubUserIdLock(userId string) *lock.RedisLock {
if s.subUserIdLock == nil {
s.subUserIdLock = app.NewRedisLock(s.Ctx, ckey.FissionUserStoreOnce(s.cpn.ID, userId))
}
return s.subUserIdLock
}
func (s *FissionUser) getDeviceIdLock(deviceId string) *lock.RedisLock {
if s.deviceIdLock == nil {
s.deviceIdLock = app.NewRedisLock(s.Ctx, ckey.FissionUserStoreDeviceOnce(s.cpn.ID, deviceId))
}
return s.deviceIdLock
}
func (s *FissionUser) lockRelease() {
if s.subUserIdLock != nil {
s.subUserIdLock.Release()
}
if s.deviceIdLock != nil {
s.deviceIdLock.Release()
}
}
func (s *FissionUser) getConf() (conf *model.FissionCampaignConfig, err error) {
if s.conf == nil {
s.conf, err = cache.NewCampaignConfig(s.Ctx).FindByCampaignId(s.cpn.ID)
if err != nil {
err = s.BE().CacheError(err)
return
}
}
return s.conf, nil
}
func (s *FissionUser) getTz(lang string) *CampaignTZ {
if s.cpnControl == nil {
s.cpnControl = NewCampaignTZ(s.Ctx, s.cpn, lang)
}
return s.cpnControl
}
func (s *FissionUser) _white(conf *model.FissionCampaignConfig, userId, lang string) (err error) {
if userId == consts.FissionViewer {
return
}
tz := s.getTz(lang)
if tz.IsWait() && conf.HasWarmUp == mtype.No {
return workerr.CNotRunError
}
if tz.IsPre() {
userIdList := strings.Split(conf.WhiteList, ";")
for _, id := range userIdList {
if userId == id {
return
}
}
return workerr.CNotRunError
}
return
}
func (s *FissionUser) _refresh(user *model.FissionUser) (err error) {
if len(s.upData) == 0 {
return
}
m := s.GetDao().FissionUser
_, err = m.WithContext(s.Ctx).Where(m.ID.Eq(user.ID)).UpdateSimple(s.upData...)
if err != nil {
err = s.BE().MysqlError(err)
return
}
ms := s.GetDao().FissionUser.SubTable(s.cpn.ID)
_, err = ms.WithContext(s.Ctx).Where(ms.ID.Eq(user.ID)).UpdateSimple(s.msUpData...)
if err != nil {
err = s.BE().MysqlError(err)
return
}
// 清除缓存
_ = cache.NewFissionUser(s.Ctx, user.CampaignID).Clear(user)
return
}
func (s *FissionUser) _country(rewardGroup int8, ip string) string {
var countryMark string
if rewardGroup == mtype.FissionRewardGroupCountry && ip != "" {
res, _ := app.NewDeepLinkDevice().FindIpAreaCode(s.Ctx, &ddevicepb.FindIpAreaCodeReq{Ip: ip})
countryMark = res.GetAreaCode()
}
return countryMark
}
func (s *FissionUser) _findBySubUserId(userId string) (user *model.FissionUser, err error) {
if userId == "" {
return
}
user, err = cache.NewFissionUser(s.Ctx, s.cpn.ID).FindBySubUserId(userId)
if err != nil {
err = s.BE().CacheError(err)
return
}
return
}
func (s *FissionUser) _findByDeviceId(deviceId string) (user *model.FissionUser, err error) {
if deviceId == "" {
return
}
user, err = cache.NewFissionUser(s.Ctx, s.cpn.ID).FindByDeviceId(deviceId)
if err != nil {
err = s.BE().CacheError(err)
return
}
return
}
func (s *FissionUser) _refreshDeviceId(user *model.FissionUser, deviceId string) *model.FissionUser {
m := s.GetDao().FissionUser
ms := s.GetDao().FissionUser.SubTable(s.cpn.ID)
if user.DeviceID == "" && deviceId != "" {
s.upData = append(s.upData, m.DeviceID.Value(deviceId))
s.msUpData = append(s.msUpData, ms.DeviceID.Value(deviceId))
user.DeviceID = deviceId
if user.SubUserID == "" && user.UserMark == "" {
s.upData = append(s.upData, m.UserMark.Value(deviceId))
s.msUpData = append(s.msUpData, ms.UserMark.Value(deviceId))
user.UserMark = deviceId
}
}
return user
}
func (s *FissionUser) _refreshSubUserId(user *model.FissionUser, subUserId string) *model.FissionUser {
m := s.GetDao().FissionUser
ms := s.GetDao().FissionUser.SubTable(s.cpn.ID)
if user.SubUserID == "" && subUserId != "" {
s.upData = append(s.upData, m.SubUserID.Value(subUserId), m.UserMark.Value(subUserId))
s.msUpData = append(s.msUpData, ms.SubUserID.Value(subUserId), m.UserMark.Value(subUserId))
user.SubUserID = subUserId
user.UserMark = subUserId
if user.SubUserID != "" {
producer.NewCampaignNotice(s.Ctx).ProjectUser(types.ProjectUser{
ProjectId: s.cpn.ProjectID,
UserId: user.SubUserID,
})
}
}
return user
}
func (s *FissionUser) _refreshAttr(user *model.FissionUser, uct consts.CampaignUserType, platform, lang, lvMark, customParam, centerHashId, moduleId string, webChannelId int64) *model.FissionUser {
attr := NewFissionUserAttr(s.Ctx, user)
m := s.GetDao().FissionUser
ms := s.GetDao().FissionUser.SubTable(s.cpn.ID)
channel := s.channel
// 如果用户没有绑定渠道,就绑定一下
if s.channel != nil && user.ChannelID == 0 {
s.upData = append(s.upData, m.ChannelID.Value(channel.ID))
s.msUpData = append(s.msUpData, ms.ChannelID.Value(channel.ID))
user.ChannelID = channel.ID
}
// 更换匹配中的语种
if user.UserLang != uct.LangMark {
s.upData = append(s.upData, m.UserLang.Value(uct.LangMark))
s.msUpData = append(s.msUpData, ms.UserLang.Value(uct.LangMark))
user.UserLang = uct.LangMark
}
// 更换匹配中的用户等级
if user.UserLevel != uct.LevelId && user.FixedUserLevel == mtype.No {
s.upData = append(s.upData, m.UserLevelMark.Value(uct.LevelMark), m.UserLevel.Value(uct.LevelId))
s.msUpData = append(s.msUpData, ms.UserLevelMark.Value(uct.LevelMark), ms.UserLevel.Value(uct.LevelId))
user.UserLevelMark = uct.LevelMark
user.UserLevel = uct.LevelId
}
// 更换匹配中的国家
if user.UserCountry != uct.CountryMark {
s.upData = append(s.upData, m.UserCountry.Value(uct.CountryMark))
s.msUpData = append(s.msUpData, ms.UserCountry.Value(uct.CountryMark))
user.UserCountry = uct.CountryMark
}
// 用户带的语言标识
if lang != "" && user.LangMark != lang {
s.upData = append(s.upData, m.LangMark.Value(lang))
s.msUpData = append(s.msUpData, ms.LangMark.Value(lang))
user.LangMark = lang
s.changeLangMark = true
_ = attr.Lang(lang)
}
if lvMark != "" {
// 用户带的用户等级标识
oldLv := strings.Split(user.LevelMark, ",")
setLv := strings.Split(lvMark, ",")
// 如果数组相等,并且也没有切换语种,那就更新了
if !verify.SliceEq(oldLv, setLv) {
s.upData = append(s.upData, m.LevelMark.Value(lvMark))
s.msUpData = append(s.msUpData, ms.LevelMark.Value(lvMark))
user.LevelMark = lvMark
_ = attr.UserLv(uct.LevelMark, setLv)
s.changeLvMark = true
}
}
_ = attr.Platform(platform)
_ = attr.Center(centerHashId)
_ = attr.CenterModule(moduleId)
_ = attr.CustomParam(customParam)
if webChannelId > 0 {
_ = attr.WebChannel(webChannelId)
}
return user
}
// 去拿deviceCode信息
func (s *FissionUser) _deviceUser(device *ddevicepb.FindBlackboxRes, uid string, conf *model.FissionCampaignConfig) (user, parent *model.FissionUser, deviceId string, err error) {
if device.GetDeviceId() == "" {
return
}
//看看能不能找到device的参与数据
user, err = s._findByDeviceId(device.GetDeviceId())
if err != nil {
err = s.BE().CacheError(err)
return
}
if user == nil {
deviceId = device.GetDeviceId()
if device.GetLastParentUserId() != "" {
parent, err = s._findBySubUserId(device.GetLastParentUserId())
if err != nil {
err = s.BE().CacheError(err)
return
}
}
return
}
// 这个deviceId被其他人绑定过的
if user.SubUserID == "" {
return
}
if conf.DeviceOnce == mtype.Yes && user.SubUserID != uid {
err = workerr.DeviceOnceError
return
}
user = nil
return
}
func (s *FissionUser) _findByUserMark(noticeType int64, userMark string) (user *model.FissionUser, err error) {
if !verify.InArray[int64](noticeType, types.NoticeTypeInstall, types.NoticeTypeOpen) || userMark == "" {
return
}
id, _ := app.Redis.Cache.Get(s.Ctx, ckey.FissionUserUniqMark(s.cpn.ID, userMark)).Int64()
if id == 0 {
return
}
user, err = cache.NewFissionUser(s.Ctx, s.cpn.ID).FindById(id)
if err != nil {
err = s.BE().CacheError(err)
return
}
return
}
func (s *FissionUser) notifyFindUser(noticeType int64, uct consts.CampaignUserType, userMark, uid, deviceId, platform, lang, lvMark, customParam string, webChannelId int64) (user *model.FissionUser, err error) {
defer func() {
if user != nil {
user = s._refreshAttr(user, uct, platform, lang, lvMark, customParam, "", "", webChannelId)
}
}()
user, err = s._findByUserMark(noticeType, userMark)
if err != nil || user != nil {
return
}
user, err = s._findBySubUserId(uid)
if err != nil || user != nil {
return
}
if user != nil {
user = s._refreshDeviceId(user, deviceId)
return
}
user, err = s._findByDeviceId(deviceId)
if err != nil || user == nil {
return
}
// 相同设备,但是不同用户id, 当新用户处理
if uid != "" && user.SubUserID != "" && user.SubUserID != uid {
user = nil
return
}
user = s._refreshSubUserId(user, uid)
return
}
func (s *FissionUser) _create(user *model.FissionUser, uct consts.CampaignUserType, platform, lvMark, customParam, centerHashId, moduleId string, webChannelId int64) (err error) {
ms := s.GetDao().FissionUser.SubTable(user.CampaignID)
if err = ms.WithContext(s.Ctx).Create(user); err != nil {
err = s.BE().MysqlError(err)
return
}
m := s.GetDao().FissionUser
if err = m.WithContext(s.Ctx).Create(user); err != nil {
err = s.BE().MysqlError(err)
return
}
err = s.GetDao().FissionUserStat.WithContext(s.Ctx).Create(withdefault.FissionUserStat(&model.FissionUserStat{
UserID: user.ID,
CampaignID: user.CampaignID,
}))
if err != nil {
err = s.BE().MysqlError(err)
return
}
attr := NewFissionUserAttr(s.Ctx, user)
err = attr.WithUser(
platform,
user.LangMark,
uct.LevelMark, lvMark,
uct.CountryMark,
centerHashId,
moduleId,
webChannelId,
)
if err != nil {
return
}
err = attr.CustomParam(customParam)
if err != nil {
return
}
return
}
// UserCampaignType 获取活动对应的用户所属类型数据
func (s *FissionUser) userCampaignType(langMark, lvMark, countryMark string) (uct consts.CampaignUserType, err error) {
cpn := s.cpn
conf, err := s.getConf()
if err != nil {
return
}
var userLang, userLevelMark, userCountry string
var userLevel int64
defer func() {
if userLevel > 0 && userLevelMark == "" {
level, _ := cache.NewFissionUserLevel(s.Ctx).FindById(userLevel)
userLevelMark = level.Mark
}
uct.LangMark, uct.LevelId, uct.LevelMark, uct.CountryMark = userLang, userLevel, userLevelMark, userCountry
}()
ful := finder.NewFuserLayer(conf)
switch conf.RewardGroup {
case mtype.FissionRewardGroupUserLevel:
var dfId int64
lvMarks := strings.Split(lvMark, ",")
for _, mark := range lvMarks {
var lvId int64
lvId, err = cache.NewFissionUserLevel(s.Ctx).FindByMarkId(cpn.ProjectID, mark)
if err != nil {
err = s.BE().CacheError(err)
return
}
if lvId == 0 {
continue
}
if dfId == 0 {
dfId = lvId
}
_, df := ful.Level().Selected(lvId)
if !df {
userLevel = lvId
userLevelMark = mark
break
}
}
if userLevel == 0 {
userLevel, _ = ful.Level().Selected(0)
}
case mtype.FissionRewardGroupLang:
userLang, _ = ful.Lang().Selected(langMark)
case mtype.FissionRewardGroupLangAndUserLevel:
userLang, _ = ful.LangLevel().GetLang(langMark)
var dfId int64
lvMarks := strings.Split(lvMark, ",")
for _, mark := range lvMarks {
var lvId int64
lvId, err = cache.NewFissionUserLevel(s.Ctx).FindByMarkId(cpn.ProjectID, mark)
if err != nil {
err = s.BE().CacheError(err)
return
}
if lvId == 0 {
continue
}
if dfId == 0 {
dfId = lvId
}
_, df := ful.LangLevel().GetLevel(userLang, lvId)
if !df {
userLevel = lvId
userLevelMark = mark
}
}
if userLevel == 0 {
userLevel, _ = ful.LangLevel().GetLevel(userLang, 0)
}
case mtype.FissionRewardGroupCountry:
userCountry, _ = ful.Country().Selected(countryMark)
}
return
}
func (s *FissionUser) _lvMark(subUserId, langMark, lvMark, source string) (lv string, err error) {
if subUserId == "" {
return
}
project, err := cache.NewProject(s.Ctx).FindById(s.cpn.ProjectID)
if err != nil {
err = s.BE().CacheError(err)
return
}
conf, err := NewFissionConfig(s.Ctx, project)
if err != nil {
return
}
url, err := conf.GetLvUrl()
if err != nil {
return
}
if url == nil {
return lvMark, nil
}
key := ckey.FissionLvApiCache(project.ID, subUserId)
err = lockdo.ExistDo(s.Ctx, key, func(exist bool) (err error) {
if exist {
lv = app.Redis.Cache.Get(s.Ctx, key).Val()
return
}
sts, err := s2s.NewLvApi(s.Ctx, conf.Config())
if err != nil {
return
}
data, err := sts.LvApi(s.cpn, subUserId, langMark, source)
if err != nil {
err = nil
app.Redis.Cache.Set(s.Ctx, key, lv, 5*time.Second)
return
}
lv = strings.Join(data.List, ",")
app.Redis.Cache.Set(s.Ctx, key, lv, 30*time.Second)
return
})
if err != nil {
err = s.BE().CacheError(err)
return
}
return
}