service/app/mix/service/utilservice.go

2397 lines
74 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"encoding/hex"
"encoding/json"
"fmt"
"service/api/consts"
"service/api/errcode"
"service/api/interfaces"
"service/api/message/request"
accountproto "service/api/proto/account/proto"
accountrelationproto "service/api/proto/accountrelation/proto"
contact_customer_service_sessionproto "service/api/proto/contact_customer_service_session/proto"
imageauditproto "service/api/proto/imageaudit/proto"
imageaudittaskproto "service/api/proto/imageaudittask/proto"
loginproto "service/api/proto/login/proto"
momentproto "service/api/proto/moment/proto"
streamerproto "service/api/proto/streamer/proto"
streamerlinkproto "service/api/proto/streamerlink/proto"
textauditproto "service/api/proto/textaudit/proto"
textaudittaskproto "service/api/proto/textaudittask/proto"
thumbsupproto "service/api/proto/thumbsup/proto"
videomoderationproto "service/api/proto/video_moderation/proto"
videomoderationtaskproto "service/api/proto/video_moderation_task/proto"
zoneproto "service/api/proto/zone/proto"
zone_collaborator_proto "service/api/proto/zone_collaborator/proto"
zone_third_partner_proto "service/api/proto/zone_third_partner/proto"
zonemomentproto "service/api/proto/zonemoment/proto"
zonemomentthumbsupproto "service/api/proto/zonemomentthumbsup/proto"
"service/apollostruct"
"service/bizcommon/util"
"service/dbstruct"
"service/library/apollo"
"service/library/logger"
"service/library/mediafiller"
"service/library/mycrypto"
"service/library/redis"
"service/library/validator"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/qiniu/qmgo"
"go.mongodb.org/mongo-driver/mongo"
goproto "google.golang.org/protobuf/proto"
)
// 不向外暴露的辅助公共函数
// 注册账户
func (s *Service) utilRegisterUser(ctx *gin.Context, req *loginproto.MobilePhoneInfoComponent, inviter *int64, devType int32) (login *dbstruct.Login, account *dbstruct.Account, ec errcode.ErrCode) {
var err error
inviterUserId := int64(0)
ec = errcode.ErrCodeLoginSrvOk
//查询是否已经创建账号
list, err := _DefaultAccount.OpListByPhoneHash(ctx, req.PhoneHash)
if err != nil {
logger.Error("_DefaultAccount OpListByPhoneHashfail, err: %v", err)
ec = errcode.ErrCodeAccountSrvFail
return
}
if len(list) > 0 {
logger.Error("Account OpCreate failed, err: %v", err)
ec = errcode.ErrCodeAccountSrvFail
return
}
//判断邀请人是否是主播
if inviter != nil {
account, err := _DefaultAccount.OpListByUserId(ctx, &accountproto.OpListByUserIdReq{
UserId: inviter,
})
if err != nil || account == nil {
logger.Error("inviter does not exist: err:%v", err)
} else if util.DerefInt64(account.Role) != consts.Streamer {
logger.Error("inviter is not a streamer")
} else {
inviterUserId = util.DerefInt64(inviter)
}
}
// 使用发号器发放一个user_id
userIdSeq, err := _DefaultUserId.OpGetNextNormalUserId(ctx)
if err != nil {
logger.Error("Generate user id fail, err: %v", err)
ec = errcode.ErrCodeAccountSrvFail
return
}
// 获取初始账户信息,开始填充
account, err = _DefaultAccount.GenerateOriginalAccount()
if err != nil {
logger.Error("GenerateOriginalAccount failed, err: %v", err)
ec = errcode.ErrCodeLoginSrvFail
return
}
account.UserId = goproto.Int64(userIdSeq.UserId)
account.UserIdString = goproto.String(fmt.Sprint(userIdSeq.UserId))
account.Name = goproto.String(fmt.Sprintf("用户%v", userIdSeq.UserId))
account.MobilePhone = goproto.String(req.MobilePhone)
account.RegionCode = goproto.String(req.RegionCode)
account.PhoneHash = goproto.String(req.PhoneHash)
account.IsAMember = goproto.Int64(0)
account.DevType = goproto.Int32(devType)
if inviterUserId != 0 {
account.Inviter = goproto.Int64(inviterUserId)
}
// 创建账户生成mid
err = _DefaultAccount.OpCreate(ctx, &accountproto.OpCreateReq{
Account: account,
})
if err != nil {
logger.Error("Account OpCreate failed, err: %v", err)
ec = errcode.ErrCodeAccountSrvFail
return
}
// 获取初始登录信息,开始填充
login = _DefaultLogin.GenerateOriginalLogin()
login.Mid = account.Mid
login.PhoneHash = account.PhoneHash
login.RegionCode = account.RegionCode
// 创建登录信息
err = _DefaultLogin.OpCreate(ctx, &loginproto.OpCreateReq{
Login: login,
})
if err != nil {
logger.Error("Login OpCreate failed, err: %v", err)
ec = errcode.ErrCodeLoginSrvFail
return
}
return
}
// 提供session_id返回session_id -> Session的Map
func (s *Service) utilGetContactCustomerServiceSessionMap(ctx *gin.Context, sessionIds []int64) (_map map[int64]*dbstruct.ContactCustomerServiceSession, err error) {
list, err := _DefaultContactCustomerServiceSession.OpListBySessionIds(ctx, &contact_customer_service_sessionproto.OpListBySessionIdsReq{
SessionIds: sessionIds,
})
if err != nil {
logger.Error("OpListBySessionIds fail: %v", err)
return
}
_map = make(map[int64]*dbstruct.ContactCustomerServiceSession)
for _, session := range list {
_map[util.DerefInt64(session.Id)] = session
}
return
}
func (s *Service) utilThumbsUpMoment(ctx *gin.Context, req *momentproto.OpThumbsUpReq) (ec errcode.ErrCode) {
ec = errcode.ErrCodeMomentSrvOk
thumbsup, err := _DefaultThumbsUp.OpListBySentence(ctx, &thumbsupproto.OpListBySentenceReq{
MomentId: req.MomentId,
Mid: req.Mid,
})
if err != nil {
logger.Error("_DefaultZoneMomentThumbsUp OpListBySentence fail, req: %v, err: %v", util.ToJson(req), err)
ec = errcode.ErrCodeZoneMomentThumbsUpSrvFail
return
}
if thumbsup != nil {
ec = errcode.ErrCodeZoneMomentThumbsUpDuplicateKey
return
}
//先写入点赞表
err = _DefaultThumbsUp.OpCreate(ctx, &thumbsupproto.OpCreateReq{
ThumbsUp: &dbstruct.ThumbsUp{
MomentId: req.MomentId,
Mid: req.Mid,
},
})
if mongo.IsDuplicateKeyError(err) {
logger.Error("_DefaultThumbsUp OpCreate duplicate key found, req: %v, err: %v", util.ToJson(req), err)
ec = errcode.ErrCodeThumbsUpDuplicateKey
return
}
if err != nil {
logger.Error("_DefaultThumbsUp OpCreate fail, req: %v, err: %v", util.ToJson(req), err)
ec = errcode.ErrCodeThumbsUpSrvFail
return
}
//再自增点赞数
if err := _DefaultMoment.OpThumbsUp(ctx, req); err != nil {
logger.Error("_DefaultMoment OpThumbsUp fail, req: %v, err: %v", util.ToJson(req), err)
ec = errcode.ErrCodeMomentSrvFail
//删除点赞表内容
if err := _DefaultThumbsUp.OpDelete(ctx, &thumbsupproto.OpDeleteReq{
MomentId: req.MomentId,
Mid: req.Mid,
}); err != nil {
logger.Error("_DefaultThumbsUp OpDelete fail, req: %v, err: %v", util.ToJson(req), err)
ec = errcode.ErrCodeThumbsUpSrvFail
return
}
}
return
}
func (s *Service) utilUnThumbsUpMoment(ctx *gin.Context, req *momentproto.OpThumbsUpReq) (ec errcode.ErrCode) {
ec = errcode.ErrCodeMomentSrvOk
//先删除点赞表
if err := _DefaultThumbsUp.OpDelete(ctx, &thumbsupproto.OpDeleteReq{
MomentId: req.MomentId,
Mid: req.Mid,
}); err != nil {
logger.Error("_DefaultThumbsUp OpDelete fail, req: %v, err: %v", util.ToJson(req), err)
ec = errcode.ErrCodeThumbsUpSrvFail
return
}
//再自减点赞数
if err := _DefaultMoment.OpThumbsUp(ctx, req); err != nil {
logger.Error("_DefaultMoment OpThumbsUp fail, req: %v, err: %v", util.ToJson(req), err)
ec = errcode.ErrCodeMomentSrvFail
//恢复点赞表内容
if err := _DefaultThumbsUp.OpCreate(ctx, &thumbsupproto.OpCreateReq{
ThumbsUp: &dbstruct.ThumbsUp{
MomentId: req.MomentId,
Mid: req.Mid,
},
}); err != nil {
logger.Error("_DefaultThumbsUp OpCreate fail, req: %v, err: %v", util.ToJson(req), err)
ec = errcode.ErrCodeThumbsUpSrvFail
return
}
return
}
return
}
func (s *Service) utilFillMomentsWithApiVOInfo(ctx *gin.Context, list []*dbstruct.Moment, visitorMid int64) (volist []*momentproto.ApiMomentVO, err error) {
// 1.初始化返回的volist获取mids并将动态基底填充进去
volist = make([]*momentproto.ApiMomentVO, 0)
midSet := make(map[int64]*dbstruct.Moment)
mids := make([]int64, 0)
momentIds := make([]string, 0)
for _, moment := range list {
vo := &momentproto.ApiMomentVO{
Moment: moment,
}
volist = append(volist, vo)
mid := moment.GetMid()
if midSet[mid] == nil {
midSet[mid] = &dbstruct.Moment{}
mids = append(mids, mid)
}
momentIds = append(momentIds, fmt.Sprint(moment.GetId()))
}
// 2.通过mids获取主播信息map
streamerExtMap, err := s.utilGetStreamerExtMapByMids(ctx, mids, consts.InterfaceType_Api)
if err != nil {
logger.Error("utilGetStreamerExtMapByMids fail, err: %v", err)
return
}
// 3.获取访问者的关注列表
followMap, err := s.utilGetFollowMap(ctx, visitorMid)
if err != nil {
logger.Error("utilGetFollowMap fail")
return
}
// 4.获取审核信息
tasks, err := _DefaultMomentAuditTask.GetByMomentIds(ctx, momentIds)
if err != nil {
logger.Error("GetByMomentIds fail")
return
}
taskMp := make(map[string]*dbstruct.MomentAuditTask)
for _, task := range tasks {
taskMp[task.GetAssociativeTableId()] = task
}
// 5.填充所有信息
for _, vo := range volist {
// 填充主播信息
vo.CopyStreamerExt(streamerExtMap[vo.GetMid()])
// 填充是否关注
s.utilFillIsFollowedFillable(ctx, followMap, vo)
// 填充是否点赞
if err = s.utilFillIsThumbedUpFillable(ctx, visitorMid, vo); err != nil {
logger.Error("utilFillIsThumbedUpFillable fail")
return
}
// 仅创建者才填充信息
if visitorMid == vo.Moment.GetMid() {
// 填充审核信息
vo.ImageAuditOpinion = taskMp[fmt.Sprint(vo.Moment.GetId())].GetImageAuditOpinion()
vo.TextAuditOpinion = taskMp[fmt.Sprint(vo.Moment.GetId())].GetTextAuditOpinion()
vo.ManuallyReviewOpinion = taskMp[fmt.Sprint(vo.Moment.GetId())].GetManuallyReviewOpinion()
}
}
return
}
func (s *Service) utilFillMomentsWithOpVOInfo(ctx *gin.Context, list []*dbstruct.Moment, visitorMid int64) (volist []*momentproto.OpMomentVO, err error) {
// 1.初始化返回的volist获取mids并将动态基底填充进去
volist = make([]*momentproto.OpMomentVO, 0)
midSet := make(map[int64]*dbstruct.Moment)
mids := make([]int64, 0)
momentIds := make([]string, 0)
for _, moment := range list {
vo := &momentproto.OpMomentVO{
Moment: moment,
}
volist = append(volist, vo)
mid := moment.GetMid()
if midSet[mid] == nil {
midSet[mid] = &dbstruct.Moment{}
mids = append(mids, mid)
}
momentIds = append(momentIds, fmt.Sprint(moment.GetId()))
}
// 2.通过mids获取主播信息map
streamerExtMap, err := s.utilGetStreamerExtMapByMids(ctx, mids, consts.InterfaceType_Op)
if err != nil {
logger.Error("utilGetStreamerExtMapByMids fail, err: %v", err)
return
}
// 3.获取审核信息
tasks, err := _DefaultMomentAuditTask.GetByMomentIds(ctx, momentIds)
if err != nil {
logger.Error("GetByMomentIds fail")
return
}
taskMp := make(map[string]*dbstruct.MomentAuditTask)
for _, task := range tasks {
taskMp[task.GetAssociativeTableId()] = task
}
// 4.填充所有信息
for _, vo := range volist {
// 填充主播信息
vo.CopyStreamerExt(streamerExtMap[vo.GetMid()])
// 填充审核信息
vo.ImageAuditOpinion = taskMp[fmt.Sprint(vo.Moment.GetId())].GetImageAuditOpinion()
vo.TextAuditOpinion = taskMp[fmt.Sprint(vo.Moment.GetId())].GetTextAuditOpinion()
vo.ManuallyReviewOpinion = taskMp[fmt.Sprint(vo.Moment.GetId())].GetManuallyReviewOpinion()
}
return
}
func (s *Service) utilGetFollowMap(ctx *gin.Context, visitorMid int64) (_map map[int64]*dbstruct.AccountRelation, err error) {
// 查找访问人的关注列表
accountrelations, err := _DefaultAccountRelation.OpListBySubMidAndPredicate(ctx, &accountrelationproto.OpListBySubMidAndPredicateReq{
SubMid: goproto.Int64(visitorMid),
Predicate: goproto.Int64(consts.Follow),
})
if err != nil {
logger.Error("_DefaultAccountRelation OpListBySubMidAndPredicate fail, err: %v", err)
return nil, err
}
// 取出关注列表中的obj_mid
_map = make(map[int64]*dbstruct.AccountRelation)
for _, accountrelation := range accountrelations {
_map[util.DerefInt64(accountrelation.ObjMid)] = accountrelation
}
return
}
func (s *Service) utilFillIsFollowedFillable(ctx *gin.Context, visitorFollowMap map[int64]*dbstruct.AccountRelation, isFollowedFillable interfaces.IsFollowedFillable) {
if visitorFollowMap[isFollowedFillable.GetMid()] != nil {
isFollowedFillable.SetIsFollowed(consts.IsFollowed_Yes)
} else {
isFollowedFillable.SetIsFollowed(consts.IsFollowed_No)
}
return
}
func (s *Service) utilFillIsThumbedUpFillable(ctx *gin.Context, visitorMid int64, isThumbedUp interfaces.IsThumbedUpFillable) error {
thumbsup, err := _DefaultThumbsUp.OpListBySentence(ctx, &thumbsupproto.OpListBySentenceReq{
MomentId: goproto.Int64(isThumbedUp.GetMomentId()),
Mid: goproto.Int64(visitorMid),
})
if err != nil {
logger.Error("_DefaultThumbsUp OpListBySentence fail, err: %v", err)
return err
}
if thumbsup != nil {
isThumbedUp.SetIsThumbedUp(consts.IsThumbedUp_Yes)
} else {
isThumbedUp.SetIsThumbedUp(consts.IsThumbedUp_No)
}
return nil
}
func (s *Service) utilFillImageAuditTaskVO(ctx *gin.Context, vo *imageaudittaskproto.ImageAuditTaskVO) error {
if vo == nil || vo.ImageAuditTask == nil {
return nil
}
task := vo.ImageAuditTask
if util.DerefInt64(task.IsFragmented) == 1 {
imageaudits, err := _DefaultImageAudit.GetListByIds(ctx, util.DerefStringSlice(task.ImageAuditFragmentIds))
if err != nil {
logger.Error("GetListByIds fail, err: %v", err)
return err
}
vo.CopyImageAudits(imageaudits)
} else {
imageaudit, err := _DefaultImageAudit.OpList(ctx, &imageauditproto.OpListReq{
Id: task.ImageAuditId,
})
if err != nil {
logger.Error("OpList fail, err: %v", err)
return err
}
vo.CopyImageAudits([]*dbstruct.ImageAudit{imageaudit})
}
return nil
}
func (s *Service) utilFillTextAuditTaskVO(ctx *gin.Context, vo *textaudittaskproto.TextAuditTaskVO) error {
if vo == nil || vo.TextAuditTask == nil {
return nil
}
task := vo.TextAuditTask
textaudit, err := _DefaultTextAudit.OpList(ctx, &textauditproto.OpListReq{
Id: task.TextAuditId,
})
if err != nil {
logger.Error("OpList fail, err: %v", err)
return err
}
vo.CopyTextAudit(textaudit)
return nil
}
func (s *Service) utilFillVideoModerationTaskVO(ctx *gin.Context, vo *videomoderationtaskproto.VideoModerationTaskVO) error {
if vo == nil || vo.VideoModerationTask == nil {
return nil
}
task := vo.VideoModerationTask
if util.DerefInt64(task.IsFragmented) == 1 {
videomoderations, err := _DefaultVideoModeration.GetListByIds(ctx, util.DerefStringSlice(task.VideoModerationFragmentIds))
if err != nil {
logger.Error("GetListByIds fail, err: %v", err)
return err
}
vo.CopyVideoModerations(videomoderations)
} else {
videomoderation, err := _DefaultVideoModeration.OpList(ctx, &videomoderationproto.OpListReq{
Id: task.VideoModerationId,
})
if err != nil {
logger.Error("OpList fail, err: %v", err)
return err
}
vo.CopyVideoModerations([]*dbstruct.VideoModeration{videomoderation})
}
return nil
}
// 将联系客服消息转化为Text
func (s *Service) utilStringifyContactCustomerServices(ctx *gin.Context, contactCustomerServices []*dbstruct.ContactCustomerService) (msg string, err error) {
if len(contactCustomerServices) == 0 {
return
}
_map := make(map[int64][]*dbstruct.ContactCustomerService)
sessionIds := make([]int64, len(contactCustomerServices))
for i, contact_customer_service := range contactCustomerServices {
sessionId := util.DerefInt64(contact_customer_service.SessionId)
contents := _map[sessionId]
_map[sessionId] = append(contents, contact_customer_service)
sessionIds[i] = util.DerefInt64(contact_customer_service.SessionId)
}
sessionMap, err := s.utilGetContactCustomerServiceSessionMap(ctx, sessionIds)
if err != nil {
logger.Error("utilGetContactCustomerServiceSessionMap fail, err: %v", err)
return
}
countUnread, err := _DefaultContactCustomerService.OpCountUnread(ctx)
if err != nil {
logger.Error("OpCountUnread fail, err: %v", err)
return
}
msgBuilder := &strings.Builder{}
msgBuilder.WriteString(fmt.Sprintf("上分钟收到的联系客服消息:%v\n", len(contactCustomerServices)))
msgBuilder.WriteString(fmt.Sprintf("当前总未读消息数:%v\n\n", countUnread))
msgBuilder.WriteString("新增未读消息:\n")
for i, contactCustomerService := range contactCustomerServices {
isRead := util.DerefInt64(contactCustomerService.IsRead)
if isRead == consts.ContactCustomerService_Read {
continue
}
sessionId := util.DerefInt64(contactCustomerService.SessionId)
account, err1 := _DefaultAccount.OpListByMid(ctx, &accountproto.OpListByMidReq{
Mid: sessionMap[sessionId].SubMid,
})
if err1 != nil {
logger.Error("OpListByMid fail, err: %v", err1)
err = err1
return
}
ct := util.DerefInt64(contactCustomerService.Ct)
createtime := time.Unix(ct, 0).Format(time.DateTime)
msgBuilder.WriteString(fmt.Sprintf("%v\n", i+1))
msgBuilder.WriteString(fmt.Sprintf("用户id %v\n", util.DerefInt64(account.UserId)))
msgBuilder.WriteString(fmt.Sprintf("用户mid %v\n", util.DerefInt64(account.Mid)))
msgBuilder.WriteString(fmt.Sprintf("发送内容: %v\n", util.DerefString(contactCustomerService.Message)))
msgBuilder.WriteString(fmt.Sprintf("发送时间: %v\n\n", createtime))
}
msg = msgBuilder.String()
return
}
func (s *Service) utilCancelAccountByMids(ctx *gin.Context, midList []int64) error {
if len(midList) == 0 {
return nil
}
// 执行下线操作
if err := _DefaultToken.OpDeleteByMids(ctx, midList); err != nil {
logger.Error("_DefaultToken OpDeleteByMids fail, err: %v", err)
return err
}
// 查询相应的login和account信息
logins, err := _DefaultLogin.OpListByMids(ctx, &loginproto.OpListByMidsReq{
Mids: midList,
})
if err != nil {
logger.Error("_DefaultLogin OpListByMids fail, err: %v", err)
return err
}
if len(logins) == 0 {
logger.Error("No login entity was found")
return qmgo.ErrNoSuchDocuments
}
accounts, err := _DefaultAccount.OpListByMids(ctx, &accountproto.OpListByMidsReq{
Mids: midList,
})
if err != nil {
logger.Error("_DefaultAccount OpListByMids fail, err: %v", err)
return err
}
if len(accounts) == 0 {
logger.Error("No account entity was found")
return qmgo.ErrNoSuchDocuments
}
// 存入历史表
err = _DefaultLogin.OpCreateHis(ctx, logins)
if err != nil {
logger.Error("_DefaultLogin OpCreateHis fail, err: %v", err)
return err
}
err = _DefaultAccount.OpCreateHis(ctx, accounts)
if err != nil {
logger.Error("_DefaultAccount OpCreateHis fail, err: %v", err)
return err
}
// 准备信息
key := "cancelled_account"
cfg := apollostruct.CancelledAccountCfg{}
err = apollo.GetJson(key, &cfg, apollo.ApolloOpts().SetNamespace("account_init"))
if err != nil {
logger.Error("Apollo read failed : %v", err)
return err
}
// 更新信息
for _, login := range logins {
phoneHash := strings.ToLower(util.DerefString(login.PhoneHash))
err = _DefaultLogin.OpUpdate(ctx, &loginproto.OpUpdateReq{
Login: &dbstruct.Login{
Id: login.Id,
Password: goproto.String(cfg.Password),
PhoneHash: goproto.String(phoneHash),
},
})
if qmgo.IsDup(err) {
err = _DefaultLogin.OpUpdate(ctx, &loginproto.OpUpdateReq{
Login: &dbstruct.Login{
Id: login.Id,
Password: goproto.String(cfg.Password),
PhoneHash: goproto.String(phoneHash + qmgo.NewObjectID().Hex()),
},
})
}
if err != nil {
logger.Error("_DefaultLogin OpUpdate fail, err: %v", err)
return err
}
}
imageIds := make([]int64, 0)
imageIds = append(imageIds, cfg.AvatarImageId)
for _, account := range accounts {
phoneHash := strings.ToLower(util.DerefString(account.PhoneHash))
updateReq := &accountproto.OpUpdateReq{
Account: &dbstruct.Account{
Mid: account.Mid,
PhoneHash: goproto.String(phoneHash),
MobilePhone: goproto.String(cfg.MobilePhone),
Status: goproto.Int64(consts.AccountStatus_Cancelled),
},
}
// 非主播才修改名字和头像
if account.GetRole() != consts.Streamer {
updateReq.Account.Name = goproto.String(cfg.Name)
updateReq.Account.Avatar = &dbstruct.MediaComponent{
ImageIds: &imageIds,
}
}
err = _DefaultAccount.OpUpdate(ctx, updateReq)
if qmgo.IsDup(err) {
updateReq := &accountproto.OpUpdateReq{
Account: &dbstruct.Account{
Mid: account.Mid,
PhoneHash: goproto.String(phoneHash + qmgo.NewObjectID().Hex()),
MobilePhone: goproto.String(cfg.MobilePhone),
Status: goproto.Int64(consts.AccountStatus_Cancelled),
},
}
if account.GetRole() != consts.Streamer {
updateReq.Account.Name = goproto.String(cfg.Name)
updateReq.Account.Avatar = &dbstruct.MediaComponent{
ImageIds: &imageIds,
}
}
err = _DefaultAccount.OpUpdate(ctx, updateReq)
}
if err != nil {
logger.Error("_DefaultAccount OpUpdate fail, err: %v", err)
return err
}
}
// 针对主播账户隐藏streamer并从es表中删除
streamerMids := make([]int64, 0)
for _, acct := range accounts {
if acct.GetRole() == consts.Streamer {
streamerMids = append(streamerMids, acct.GetMid())
}
}
if len(streamerMids) > 0 {
err = _DefaultStreamer.OpUpdateByMids(ctx, &dbstruct.Streamer{
IsHided: goproto.Int64(consts.IsHided_Yes),
}, streamerMids)
if err != nil {
logger.Error("_DefaultStreamer OpUpdateByMids fail, err: %v", err)
return err
}
err = _DefaultStreamerAcct.OpUpdateSelectivelyByMids(ctx, &dbstruct.EsStreamerAcctUpdater{
DelFlag: goproto.Int64(consts.Deleted),
}, streamerMids)
if err != nil {
logger.Error("_DefaultStreamerAcct OpUpdateSelectivelyByMids fail, err: %v", err)
return err
}
}
return nil
}
func (s *Service) utilGetStreamerLinkVOListMapByMids(ctx *gin.Context, mids []int64) (mp map[int64][]*streamerlinkproto.StreamerLinkVO, err error) {
// 获取streamerlinkMap
streamerlinkMap, err := _DefaultStreamerLink.GetStreamerLinkMapByMids(ctx, mids)
if err != nil {
logger.Error("_DefaultAccount GetStreamerLinkMapByMids fail, err: %v", err)
return
}
// 获取streamerlinkVO
mp = make(map[int64][]*streamerlinkproto.StreamerLinkVO)
platformCfgMap := make(map[int64]*apollostruct.PlatformCfg)
for mid, streamerlinklist := range streamerlinkMap {
streamerlinkvolist := make([]*streamerlinkproto.StreamerLinkVO, 0)
for _, streamerlink := range streamerlinklist {
linkNo := util.DerefInt64(streamerlink.LinkNo)
if platformCfgMap[linkNo] == nil {
platformCfgMap[linkNo] = &apollostruct.PlatformCfg{}
err = apollo.GetJson(fmt.Sprintf("%d", linkNo), platformCfgMap[linkNo], apollo.ApolloOpts().SetNamespace("platform"))
if err != nil {
logger.Error("Apollo read failed : %v", err)
return
}
}
vo := streamerlinkproto.NewStreamerLinkVO().CopyStreamerLink(streamerlink).CopyPlatformCfg(platformCfgMap[linkNo])
streamerlinkvolist = append(streamerlinkvolist, vo)
}
mp[mid] = streamerlinkvolist
}
return
}
// 获取主播信息
func (s *Service) utilGetStreamerExtMapByMids(ctx *gin.Context, mids []int64, option int, ignoreOpt ...map[string]bool) (streamerExtMap map[int64]streamerproto.StreamerExtVO, err error) {
if len(mids) == 0 {
return
}
ignoreMap := make(map[string]bool)
if len(ignoreOpt) > 0 {
ignoreMap = ignoreOpt[0]
}
// 获取accountMap
accountMap := make(map[int64]*dbstruct.Account)
if !ignoreMap["account"] {
accountMap, err = _DefaultAccount.GetAccountMapByMids(ctx, mids)
if err != nil {
logger.Error("_DefaultAccount GetAccountMapByMids fail, err: %v", err)
return
}
}
// 获取streamerMap
streamerMap := make(map[int64]*dbstruct.Streamer)
if !ignoreMap["streamer"] {
streamerMap, err = _DefaultStreamer.GetStreamerMapByMids(ctx, mids)
if err != nil {
logger.Error("_DefaultAccount GetStreamerMapByMids fail, err: %v", err)
return
}
}
// 获取streamerlinkVOListMap
streamerlinkvolistMap := make(map[int64][]*streamerlinkproto.StreamerLinkVO)
if !ignoreMap["streamer_links"] {
streamerlinkvolistMap, err = s.utilGetStreamerLinkVOListMapByMids(ctx, mids)
if err != nil {
logger.Error("utilGetStreamerLinkVOListMapByMids fail, err: %v", err)
return
}
}
// 获取zoneMap
zonesMap := make(map[int64][]*dbstruct.Zone, 0)
if option == consts.InterfaceType_Api && !ignoreMap["zones"] {
zonesMap, err = _DefaultZone.GetZoneMapByMids(ctx, mids)
if err != nil {
logger.Error("_DefaultZone GetZoneMapByMids fail, err: %v", err)
return
}
}
streamerExtMap = make(map[int64]streamerproto.StreamerExtVO, 0)
for _, mid := range mids {
var streamerExt streamerproto.StreamerExtVO
if option == consts.InterfaceType_Api {
streamerExt = &streamerproto.ApiListExtVO{}
} else if option == consts.InterfaceType_Op {
streamerExt = &streamerproto.OpListExtVO{}
}
// 主播vas信息
userVas, _ := _DefaultVas.GetUserVasInfo(ctx, mid)
// 组装返回内容
streamerExt.CopyAccount(accountMap[mid])
streamerExt.CopyStreamer(streamerMap[mid])
streamerlinkvolist := streamerlinkvolistMap[mid]
streamerExt.CopyPlatforms(&streamerlinkvolist)
streamerExt.CopyUserVas(userVas)
streamerExt.CopyZones(zonesMap[mid])
// 计算空间更新时间距离现在时间跨度
nowTime := time.Now()
lastZoneMomentCt := int64(0)
for _, zone := range zonesMap[mid] {
if util.DerefInt64(zone.LastZoneMomentCt) > lastZoneMomentCt {
lastZoneMomentCt = util.DerefInt64(zone.LastZoneMomentCt)
}
}
if lastZoneMomentCt == 0 {
streamerExt.SetIsActiveWithinAWeek(consts.ZoneIsActiveWithinAWeek_No)
streamerExt.SetDaysElapsedSinceTheLastZonesUpdate(consts.DaysElapsedSinceTheLastZonesUpdate_Never)
} else {
lastZoneMomentCreateDay := util.GetDayStartTimeStamp(time.Unix(lastZoneMomentCt, 0))
today := util.GetDayStartTimeStamp(nowTime)
daysElapsedSinceTheLastZonesUpdate := (today - lastZoneMomentCreateDay) / int64(86400) // 24 * 60 * 60 = 86400秒
streamerExt.SetDaysElapsedSinceTheLastZonesUpdate(daysElapsedSinceTheLastZonesUpdate)
if daysElapsedSinceTheLastZonesUpdate <= 7 {
streamerExt.SetIsActiveWithinAWeek(consts.ZoneIsActiveWithinAWeek_Yes)
} else {
streamerExt.SetIsActiveWithinAWeek(consts.ZoneIsActiveWithinAWeek_No)
}
}
streamerExtMap[mid] = streamerExt
}
return
}
func (s *Service) utilFillIsZoneMomentThumbedUpFillable(ctx *gin.Context, visitorMid int64, isZoneMomentThumbedUp interfaces.IsZoneMomentThumbedUpFillable) error {
thumbsup, err := _DefaultZoneMomentThumbsUp.OpListBySentence(ctx, &zonemomentthumbsupproto.OpListBySentenceReq{
ZoneMomentId: goproto.Int64(isZoneMomentThumbedUp.GetZoneMomentId()),
Mid: goproto.Int64(visitorMid),
})
if err != nil {
logger.Error("_DefaultThumbsUp OpListBySentence fail, err: %v", err)
return err
}
if thumbsup != nil {
isZoneMomentThumbedUp.SetIsZoneMomentThumbedUp(consts.IsThumbedUp_Yes)
} else {
isZoneMomentThumbedUp.SetIsZoneMomentThumbedUp(consts.IsThumbedUp_No)
}
return nil
}
func (s *Service) utilThumbsUpZoneMoment(ctx *gin.Context, req *zonemomentproto.OpZoneMomentThumbsUpReq) (ec errcode.ErrCode) {
ec = errcode.ErrCodeZoneMomentSrvOk
thumbsup, err := _DefaultZoneMomentThumbsUp.OpListBySentence(ctx, &zonemomentthumbsupproto.OpListBySentenceReq{
ZoneMomentId: req.ZoneMomentId,
Mid: req.Mid,
})
if err != nil {
logger.Error("_DefaultZoneMomentThumbsUp OpListBySentence fail, req: %v, err: %v", util.ToJson(req), err)
ec = errcode.ErrCodeZoneMomentThumbsUpSrvFail
return
}
if thumbsup != nil {
ec = errcode.ErrCodeZoneMomentThumbsUpDuplicateKey
return
}
//先写入点赞表
err = _DefaultZoneMomentThumbsUp.OpCreate(ctx, &zonemomentthumbsupproto.OpCreateReq{
ZoneMomentThumbsUp: &dbstruct.ZoneMomentThumbsUp{
ZoneMomentId: req.ZoneMomentId,
Mid: req.Mid,
},
})
if mongo.IsDuplicateKeyError(err) {
logger.Error("_DefaultZoneMomentThumbsUp OpCreate duplicate key found, req: %v, err: %v", util.ToJson(req), err)
ec = errcode.ErrCodeZoneMomentThumbsUpDuplicateKey
return
}
if err != nil {
logger.Error("_DefaultZoneMomentThumbsUp OpCreate fail, req: %v, err: %v", util.ToJson(req), err)
ec = errcode.ErrCodeZoneMomentThumbsUpSrvFail
return
}
//再自增点赞数
if err := _DefaultZoneMoment.OpZoneMomentThumbsUp(ctx, req); err != nil {
logger.Error("_DefaultMoment OpZoneMomentThumbsUp fail, req: %v, err: %v", util.ToJson(req), err)
ec = errcode.ErrCodeMomentSrvFail
//删除点赞表内容
if err := _DefaultZoneMomentThumbsUp.OpDelete(ctx, &zonemomentthumbsupproto.OpDeleteReq{
ZoneMomentId: req.ZoneMomentId,
Mid: req.Mid,
}); err != nil {
logger.Error("_DefaultZoneMomentThumbsUp OpDelete fail, req: %v, err: %v", util.ToJson(req), err)
ec = errcode.ErrCodeZoneMomentThumbsUpSrvFail
return
}
}
return
}
func (s *Service) utilUnThumbsZoneMoment(ctx *gin.Context, req *zonemomentproto.OpZoneMomentThumbsUpReq) (ec errcode.ErrCode) {
ec = errcode.ErrCodeZoneMomentSrvOk
//先删除点赞表
if err := _DefaultZoneMomentThumbsUp.OpDelete(ctx, &zonemomentthumbsupproto.OpDeleteReq{
ZoneMomentId: req.ZoneMomentId,
Mid: req.Mid,
}); err != nil {
logger.Error("_DefaultZoneMomentThumbsUp OpDelete fail, req: %v, err: %v", util.ToJson(req), err)
ec = errcode.ErrCodeZoneMomentThumbsUpSrvFail
return
}
//再自减点赞数
if err := _DefaultZoneMoment.OpZoneMomentThumbsUp(ctx, req); err != nil {
logger.Error("_DefaultMoment OpZoneMomentThumbsUp fail, req: %v, err: %v", util.ToJson(req), err)
ec = errcode.ErrCodeZoneMomentSrvFail
//恢复点赞表内容
if err := _DefaultZoneMomentThumbsUp.OpCreate(ctx, &zonemomentthumbsupproto.OpCreateReq{
ZoneMomentThumbsUp: &dbstruct.ZoneMomentThumbsUp{
ZoneMomentId: req.ZoneMomentId,
Mid: req.Mid,
},
}); err != nil {
logger.Error("_DefaultZoneMomentThumbsUp OpCreate fail, req: %v, err: %v", util.ToJson(req), err)
ec = errcode.ErrCodeZoneMomentThumbsUpSrvFail
return
}
return
}
return
}
// 加密未解锁身份的动态
func (s *Service) utilEncryptInaccessibleZoneMoment(vo *zonemomentproto.ApiZoneMomentVO) {
if vo.GetIsCreatingPaidText() == consts.IsCreatingPaidText_Yes {
vo.Text = goproto.String(vo.GetText()[:len(vo.GetText())-len(vo.GetPaidText())])
vo.PaidText = nil
}
if vo.GetMType() == consts.MediaTypeImg {
imageIds := vo.MediaComp.GetImageIds()
mediaVisibleRange := vo.GetMediaVisibleRange()
// 为0也至少下发一张图片
if mediaVisibleRange == 0 {
mediaVisibleRange = 1
}
if len(imageIds) <= int(mediaVisibleRange) {
return
}
imageIds = imageIds[:mediaVisibleRange]
vo.MediaComp.ImageIds = util.Int64Slice(imageIds)
}
}
// 填充动态是否解锁
func (s *Service) utilFillIsZoneMomentUnlocked(vo *zonemomentproto.ApiZoneMomentVO, zoneZidMap map[int64]*dbstruct.Zone, momentIdZmuMap map[int64]*dbstruct.ZoneMomentUnlock, visitorMid int64) {
if IsZoneVIP(visitorMid) {
vo.SetIsZoneMomentUnlocked(consts.IsZoneMomentUnlocked_Yes)
logger.Info("_ZoneVIP mlist mid: %v", visitorMid)
return
}
// 若动态在本人创建的空间内,则必然解锁
if zoneZidMap[util.DerefInt64(vo.ZoneMoment.Zid)] != nil {
vo.SetIsZoneMomentUnlocked(consts.IsZoneMomentUnlocked_Yes)
return
}
switch util.DerefInt64(vo.ZoneMoment.CType) {
case consts.ZoneMomentCType_Free:
vo.SetIsZoneMomentUnlocked(consts.IsZoneMomentUnlocked_Yes)
case consts.ZoneMomentCType_Paid:
if vo.IsSuperfanshipUnlocked == consts.IsSuperfanshipUnlocked_Yes { // 超粉解锁
vo.SetIsZoneMomentUnlocked(consts.IsZoneMomentUnlocked_Yes)
} else if vo.GetIsIronfanVisible() == consts.IsIronfanVisible_Yes && vo.IsIronfanshipUnlocked == consts.IsIronfanshipUnlocked_Yes { // 铁粉可见
vo.SetIsZoneMomentUnlocked(consts.IsZoneMomentUnlocked_Yes)
} else if momentIdZmuMap[vo.ZoneMoment.GetId()].IsUnlock() { // 动态已购买
vo.SetIsZoneMomentUnlocked(consts.IsZoneMomentUnlocked_Yes)
} else { // 未解锁,上锁
vo.SetIsZoneMomentUnlocked(consts.IsZoneMomentUnlocked_No)
s.utilEncryptInaccessibleZoneMoment(vo)
}
}
}
func (s *Service) utilGetStreamerRecommList(ctx *gin.Context) (recommlist []int64, err error) {
// 1.从redis中获取数据
err = redis.GetRedisClient().GetObject(consts.RedisStreamerPrefix+"recomm_list", &recommlist)
if err != nil {
logger.Error("Redis read failed : %v", err)
return
}
// 2.若redis命中失败再从数据库查
if len(recommlist) == 0 {
logger.Error("Redis hit failed, reading recommendation list from mongo...")
list, err := _DefaultStreamer.OpList(ctx, &streamerproto.OpListReq{
Sort: []string{"-fans"},
})
if err != nil {
logger.Error("OpList fail, err: %v", err)
return nil, err
}
recommlist = make([]int64, len(list))
for i, streamer := range list {
recommlist[i] = util.DerefInt64(streamer.Mid)
}
//若数据库命中成功则立即加载进redis
if len(recommlist) != 0 {
err := redis.GetRedisClient().Set(consts.RedisStreamerPrefix+"recomm_list", recommlist, 0)
if err != nil {
logger.Error("Redis cache fail, err: %v", err)
}
}
}
return
}
func (s *Service) utilGetInitUserVisitOffset(ctx *gin.Context, mid int64, recommlistLength int64) (err error) {
uservisitoffset, err := _DefaultUserVisitOffset.OpGetUserVisitOffset(ctx, mid)
if err != nil {
logger.Error("OpGetUserVisitOffset fail, err: %v", err)
return
}
var execFunc func(*gin.Context, *dbstruct.UserVisitOffset) error
if uservisitoffset == nil {
execFunc = _DefaultUserVisitOffset.OpCreate
} else {
execFunc = _DefaultUserVisitOffset.OpUpdate
}
// 吞吐量大于等于推荐数组长度,则这次获取后已经触底
if consts.StreamerRecommThroughput >= recommlistLength {
err = execFunc(ctx, &dbstruct.UserVisitOffset{
Id: mid,
StreamerRecommOffset: 0,
BottomFlag: goproto.Int64(1),
Ver: 0,
})
if err != nil {
logger.Error("_DefaultUserVisitOffset OpCreate fail, err: %v", err)
return
}
} else {
err = execFunc(ctx, &dbstruct.UserVisitOffset{
Id: mid,
StreamerRecommOffset: consts.StreamerRecommThroughput,
BottomFlag: goproto.Int64(0),
Ver: 0,
})
if err != nil {
logger.Error("_DefaultUserVisitOffset OpCreate fail, err: %v", err)
return
}
}
return nil
}
func (s *Service) utilGetUpUserVisitOffset(ctx *gin.Context, mid int64, recommlistLength int64) (uservisitoffset *dbstruct.UserVisitOffset, err error) {
uservisitoffset, err = _DefaultUserVisitOffset.OpGetUserVisitOffset(ctx, mid)
if err != nil {
logger.Error("OpGetUserVisitOffset fail, err: %v", err)
return
}
if uservisitoffset == nil {
// 默认用户刚进推荐页面的第一次操作一定是冷启动操作,游标也由冷启动创建
return nil, qmgo.ErrNoSuchDocuments
} else {
nowoffset := (uservisitoffset.StreamerRecommOffset + consts.StreamerRecommThroughput) % recommlistLength
// 向上操作固定清掉触底标志
var bottomFlagPtr *int64 = nil
if uservisitoffset.GetBottomFlag() == 1 {
bottomFlagPtr = goproto.Int64(0)
}
err = _DefaultUserVisitOffset.OpUpdate(ctx, &dbstruct.UserVisitOffset{
Id: uservisitoffset.Id,
StreamerRecommOffset: nowoffset,
BottomFlag: bottomFlagPtr,
Ver: uservisitoffset.Ver,
})
if err != nil {
logger.Error("_DefaultUserVisitOffset OpUpdate fail, err: %v", err)
return
}
}
return
}
func (s *Service) utilGetDownUserVisitOffset(ctx *gin.Context, mid int64, recommlistLength int64) (uservisitoffset *dbstruct.UserVisitOffset, err error) {
uservisitoffset, err = _DefaultUserVisitOffset.OpGetUserVisitOffset(ctx, mid)
if err != nil {
logger.Error("OpGetUserVisitOffset fail, err: %v", err)
return
}
if uservisitoffset == nil {
// 默认用户刚进推荐页面的第一次操作一定是冷启动操作,游标也由冷启动创建
return nil, qmgo.ErrNoSuchDocuments
} else {
if uservisitoffset.GetBottomFlag() == 1 {
return
}
offset := uservisitoffset.StreamerRecommOffset
if offset+consts.StreamerRecommThroughput >= recommlistLength {
err = _DefaultUserVisitOffset.OpUpdate(ctx, &dbstruct.UserVisitOffset{
Id: uservisitoffset.Id,
StreamerRecommOffset: 0,
BottomFlag: goproto.Int64(1),
Ver: uservisitoffset.Ver,
})
if err != nil {
logger.Error("_DefaultUserVisitOffset OpUpdate fail, err: %v", err)
return
}
} else {
err = _DefaultUserVisitOffset.OpUpdate(ctx, &dbstruct.UserVisitOffset{
Id: uservisitoffset.Id,
StreamerRecommOffset: offset + consts.StreamerRecommThroughput,
Ver: uservisitoffset.Ver,
})
if err != nil {
logger.Error("_DefaultUserVisitOffset OpUpdate fail, err: %v", err)
return
}
}
}
return
}
// 获取该用户当前在主播推荐数组中的游标和是否已经触底标志
func (s *Service) utilGetStreamerRecommlistOffsetAndBottomeFlag(ctx *gin.Context, recommListLength int64, mid int64, opType int64) (offset int64, bottomFlag int64, err error) {
offset = int64(0)
var uservisitoffset *dbstruct.UserVisitOffset
// 初始化操作
switch opType {
case consts.Recomm_Init:
err = s.utilGetInitUserVisitOffset(ctx, mid, int64(recommListLength))
if err != nil {
logger.Error("utilGetInitUserVisitOffset fail, err: %v", err)
return
}
// 向上滚动操作
case consts.Recomm_Up:
// 若吞吐量比推荐数组长度小则正常操作否则认为游标永远是0
if consts.StreamerRecommThroughput < recommListLength {
uservisitoffset, err = s.utilGetUpUserVisitOffset(ctx, mid, int64(recommListLength))
if err != nil {
logger.Error("utilGetUpUserVisitOffset fail, err: %v", err)
return
}
offset = uservisitoffset.StreamerRecommOffset
}
// 向下滚动操作
case consts.Recomm_Down:
// 若吞吐量比推荐数组长度小,则正常操作,否则认为直接触底
if consts.StreamerRecommThroughput < recommListLength {
uservisitoffset, err = s.utilGetDownUserVisitOffset(ctx, mid, int64(recommListLength))
if err != nil {
logger.Error("utilGetUpUserVisitOffset fail, err: %v", err)
return
}
if uservisitoffset.GetBottomFlag() == 1 {
return 0, 1, nil
}
offset = uservisitoffset.StreamerRecommOffset
} else {
return 0, 1, nil
}
}
return
}
func (s *Service) utilGetStreamerRecommListVO(ctx *gin.Context, recommlist []int64, mid int64, opType int64) (recommStreamerList []*streamerproto.ApiListExtVO, err error) {
// 获取用户游标
offset := int64(0)
recommListLength := int64(len(recommlist))
offset, bottomFlag, err := s.utilGetStreamerRecommlistOffsetAndBottomeFlag(ctx, recommListLength, mid, opType)
if err != nil {
logger.Error("utilGetStreamerRecommlistOffsetAndBottomeFlag fail, err: %v", err)
return
}
if bottomFlag == 1 { // 已触底
return make([]*streamerproto.ApiListExtVO, 0), nil
}
// 向下操作,去尾
upperBound := int64(consts.StreamerRecommThroughput)
if len(recommlist) < consts.StreamerRecommThroughput {
upperBound = int64(len(recommlist))
}
if opType == consts.Recomm_Down {
surplusVolume := recommListLength - offset
if surplusVolume < consts.StreamerRecommThroughput {
upperBound = surplusVolume
}
}
// 根据用户游标查询得到结果
midList := make([]int64, 0)
for i := int64(0); i < upperBound; i++ {
index := (offset + int64(i)) % int64(recommListLength)
midList = append(midList, recommlist[index])
}
mp, err := s.utilGetStreamerExtMapByMids(ctx, midList, consts.InterfaceType_Api)
if err != nil {
logger.Error("utilGetStreamerExtMapByMids fail, err: %v", err)
return
}
recommStreamerList = make([]*streamerproto.ApiListExtVO, 0)
for _, mid := range midList {
vo, _ := mp[mid].(*streamerproto.ApiListExtVO)
recommStreamerList = append(recommStreamerList, vo)
}
return
}
func (s *Service) utilGetInitUserMomentVisitOffset(ctx *gin.Context, mid int64, recommlistLength int64, throughput int64) (err error) {
uservisitoffset, err := _DefaultUserMomentVisitOffset.OpGetUserMomentVisitOffset(ctx, mid)
if err != nil {
logger.Error("OpGetUserMomentVisitOffset fail, err: %v", err)
return
}
var execFunc func(*gin.Context, *dbstruct.UserMomentVisitOffset) error
if uservisitoffset == nil {
execFunc = _DefaultUserMomentVisitOffset.OpCreate
} else {
execFunc = _DefaultUserMomentVisitOffset.OpUpdate
}
// 吞吐量大于等于推荐数组长度,则这次获取后已经触底
if throughput >= recommlistLength {
err = execFunc(ctx, &dbstruct.UserMomentVisitOffset{
Id: mid,
MomentRecommOffset: 0,
BottomFlag: goproto.Int64(1),
Ver: 0,
})
if err != nil {
logger.Error("_DefaultUserMomentVisitOffset OpCreate fail, err: %v", err)
return
}
} else {
err = execFunc(ctx, &dbstruct.UserMomentVisitOffset{
Id: mid,
MomentRecommOffset: consts.MomentRecommThroughput,
BottomFlag: goproto.Int64(0),
Ver: 0,
})
if err != nil {
logger.Error("_DefaultUserMomentVisitOffset OpCreate fail, err: %v", err)
return
}
}
return nil
}
func (s *Service) utilGetUpUserMomentVisitOffset(ctx *gin.Context, mid int64, recommlistLength int64, throughput int64) (uservisitoffset *dbstruct.UserMomentVisitOffset, err error) {
uservisitoffset, err = _DefaultUserMomentVisitOffset.OpGetUserMomentVisitOffset(ctx, mid)
if err != nil {
logger.Error("OpGetUserMomentVisitOffset fail, err: %v", err)
return
}
if uservisitoffset == nil {
// 默认用户刚进推荐页面的第一次操作一定是冷启动操作,游标也由冷启动创建
return nil, qmgo.ErrNoSuchDocuments
} else {
nowoffset := (uservisitoffset.MomentRecommOffset + throughput) % recommlistLength
// 向上操作固定清掉触底标志
var bottomFlagPtr *int64 = nil
if uservisitoffset.GetBottomFlag() == 1 {
bottomFlagPtr = goproto.Int64(0)
}
err = _DefaultUserMomentVisitOffset.OpUpdate(ctx, &dbstruct.UserMomentVisitOffset{
Id: uservisitoffset.Id,
MomentRecommOffset: nowoffset,
BottomFlag: bottomFlagPtr,
Ver: uservisitoffset.Ver,
})
if err != nil {
logger.Error("_DefaultUserMomentVisitOffset OpUpdate fail, err: %v", err)
return
}
}
return
}
func (s *Service) utilGetDownUserMomentVisitOffset(ctx *gin.Context, mid int64, recommlistLength int64, throughput int64) (uservisitoffset *dbstruct.UserMomentVisitOffset, err error) {
uservisitoffset, err = _DefaultUserMomentVisitOffset.OpGetUserMomentVisitOffset(ctx, mid)
if err != nil {
logger.Error("OpGetUserMomentVisitOffset fail, err: %v", err)
return
}
if uservisitoffset == nil {
// 默认用户刚进推荐页面的第一次操作一定是冷启动操作,游标也由冷启动创建
return nil, qmgo.ErrNoSuchDocuments
} else {
if uservisitoffset.GetBottomFlag() == 1 {
return
}
offset := uservisitoffset.MomentRecommOffset
if offset+throughput >= recommlistLength {
err = _DefaultUserMomentVisitOffset.OpUpdate(ctx, &dbstruct.UserMomentVisitOffset{
Id: uservisitoffset.Id,
MomentRecommOffset: 0,
BottomFlag: goproto.Int64(1),
Ver: uservisitoffset.Ver,
})
if err != nil {
logger.Error("_DefaultUserMomentVisitOffset OpUpdate fail, err: %v", err)
return
}
} else {
err = _DefaultUserMomentVisitOffset.OpUpdate(ctx, &dbstruct.UserMomentVisitOffset{
Id: uservisitoffset.Id,
MomentRecommOffset: offset + throughput,
Ver: uservisitoffset.Ver,
})
if err != nil {
logger.Error("_DefaultUserMomentVisitOffset OpUpdate fail, err: %v", err)
return
}
}
}
return
}
// 获取该用户当前在主播推荐数组中的游标和是否已经触底标志
func (s *Service) utilGetMomentRecommlistOffsetAndBottomeFlag(ctx *gin.Context, mid int64, opType int64, recommListLength int64, throughput int64) (offset int64, isAtBottom int64, err error) {
offset = int64(0)
isRecommListSuffi := throughput <= recommListLength
var uservisitoffset *dbstruct.UserMomentVisitOffset
// 初始化操作
switch opType {
case consts.Recomm_Init:
err = s.utilGetInitUserMomentVisitOffset(ctx, mid, recommListLength, throughput)
if err != nil {
logger.Error("utilGetInitUserMomentVisitOffset fail, err: %v", err)
return
}
// 向上滚动操作
case consts.Recomm_Up:
// 若吞吐量比推荐数组长度小,则正常操作,否则认为直接触顶(游标永远是0)
if !isRecommListSuffi {
return
}
uservisitoffset, err = s.utilGetUpUserMomentVisitOffset(ctx, mid, recommListLength, throughput)
if err != nil {
logger.Error("utilGetUpUserMomentVisitOffset fail, err: %v", err)
return
}
offset = uservisitoffset.MomentRecommOffset
// 向下滚动操作
case consts.Recomm_Down:
// 若吞吐量比推荐数组长度小,则正常操作,否则认为直接触底
if !isRecommListSuffi {
return 0, 1, nil
}
uservisitoffset, err = s.utilGetDownUserMomentVisitOffset(ctx, mid, recommListLength, throughput)
if err != nil {
logger.Error("utilGetUpUserMomentVisitOffset fail, err: %v", err)
return
}
if uservisitoffset.GetBottomFlag() == 1 {
return 0, 1, nil
}
offset = uservisitoffset.MomentRecommOffset
}
return
}
// 根据提供的用户mid和操作类型从最近推荐列表中获取吞吐量大小的列表并更新用户游标
func (s *Service) utilGetMomentRecentListIds(ctx *gin.Context, mid int64, opType int64, throughput int64) (ids []int64, err error) {
if opType != consts.Recomm_Up {
return
}
// 从redis中获取动态列表长度
recentListLength, err := redis.GetRedisClient().LLen(consts.RedisMomentPrefix + "recent_list")
if err != nil {
logger.Error("Redis read failed : %v", err)
return
}
if recentListLength == 0 {
return
}
// 获取用户游标
offset, err := redis.GetRedisClient().HGetInt64(consts.RedisMomentPrefix+"recent_list_offset", fmt.Sprint(mid))
if err != nil && !redis.GetRedisClient().IsErrNil(err) {
logger.Error("Redis read failed : %v", err)
return
}
// 检验游标是否还可用
ids = make([]int64, 0)
incr := 0
for ; incr < int(throughput) && offset < recentListLength; incr++ {
id, err := redis.GetRedisClient().LIndexInt64(consts.RedisMomentPrefix+"recent_list", int(offset))
if err != nil {
logger.Error("Redis read failed : %v", err)
return make([]int64, 0), err
}
ids = append(ids, id)
offset++
}
_, err = redis.GetRedisClient().HIncrby(consts.RedisMomentPrefix+"recent_list_offset", fmt.Sprint(mid), incr)
if err != nil && !redis.GetRedisClient().IsErrNil(err) {
logger.Error("Redis cache failed : %v", err)
return
}
return
}
// 根据提供的用户mid和操作类型从推荐列表中获取吞吐量大小的列表并更新用户游标
func (s *Service) utilGetMomentRecommListIds(ctx *gin.Context, mid int64, opType int64, throughput int64) (ids []int64, err error) {
// 从redis中获取动态列表长度
recommListLength, err := redis.GetRedisClient().LLen(consts.RedisMomentPrefix + "recomm_list")
if err != nil {
logger.Error("Redis read failed : %v", err)
return
}
if recommListLength == 0 {
return
}
// 获取用户游标
offset := int64(0)
// 获取游标和是否已触底标志
offset, bottomFlag, err := s.utilGetMomentRecommlistOffsetAndBottomeFlag(ctx, mid, opType, recommListLength, throughput)
if err != nil {
logger.Error("utilGetMomentRecommlistOffsetAndBottomeFlag fail, err: %v", err)
return
}
if bottomFlag == 1 { // 已触底
return make([]int64, 0), nil
}
// 向下操作,去尾
upperBound := throughput
if opType == consts.Recomm_Down {
surplusVolume := recommListLength - offset
if surplusVolume < throughput {
upperBound = surplusVolume
}
}
// 根据用户游标得到待查询ids
ids = make([]int64, 0)
for i := int64(0); i < upperBound; i++ {
index := (offset + i) % recommListLength
id, err := redis.GetRedisClient().LIndexInt64(consts.RedisMomentPrefix+"recomm_list", int(index))
if err != nil {
logger.Error("Redis read failed : %v", err)
return make([]int64, 0), err
}
ids = append(ids, id)
}
return
}
// 根据提供的用户mid和操作类型从推荐列表中获取吞吐量大小的列表并更新用户游标
func (s *Service) utilGetRestrictedMomentRecommListIds(ctx *gin.Context, mid int64, opType int64, throughput int64) (ids []int64, err error) {
// 从redis中获取动态列表长度
recommListLength, err := redis.GetRedisClient().LLen(consts.RedisMomentPrefix + "restricted_recomm_list")
if err != nil {
logger.Error("Redis read failed : %v", err)
return
}
if recommListLength == 0 {
return
}
// 获取用户游标
offset := int64(0)
// 获取游标和是否已触底标志
offset, bottomFlag, err := s.utilGetMomentRecommlistOffsetAndBottomeFlag(ctx, mid, opType, recommListLength, throughput)
if err != nil {
logger.Error("utilGetMomentRecommlistOffsetAndBottomeFlag fail, err: %v", err)
return
}
if bottomFlag == 1 { // 已触底
return make([]int64, 0), nil
}
// 向下操作,去尾
upperBound := throughput
if opType == consts.Recomm_Down {
surplusVolume := recommListLength - offset
if surplusVolume < throughput {
upperBound = surplusVolume
}
}
// 根据用户游标得到待查询ids
ids = make([]int64, 0)
for i := int64(0); i < upperBound; i++ {
index := (offset + i) % recommListLength
id, err := redis.GetRedisClient().LIndexInt64(consts.RedisMomentPrefix+"restricted_recomm_list", int(index))
if err != nil {
logger.Error("Redis read failed : %v", err)
return make([]int64, 0), err
}
ids = append(ids, id)
}
return
}
// 根据提供的ids查询出动态并填充主播、是否关注、是否点赞并保证按照提供的顺序返回ApiMomentVO的list
func (s *Service) utilGetApiMomentVOListByIds(ctx *gin.Context, visitorMid int64, ids []int64) (volist []*momentproto.ApiMomentVO, err error) {
if len(ids) == 0 {
return make([]*momentproto.ApiMomentVO, 0), nil
}
rlist, err := _DefaultMoment.OpListByIds(ctx, &momentproto.OpListByIdsReq{
Ids: ids,
})
if err != nil {
logger.Error("_DefaultMoment OpListByIds fail, , err: %v", err)
return
}
// 重新按照ids的顺序排列list
momentMap := make(map[int64]*dbstruct.Moment)
for _, moment := range rlist {
momentMap[util.DerefInt64(moment.Id)] = moment
}
list := make([]*dbstruct.Moment, 0)
for _, id := range ids {
if momentMap[id] != nil {
list = append(list, momentMap[id])
}
}
// 2.填充业务层信息
volist, err = s.utilFillMomentsWithApiVOInfo(ctx, list, visitorMid)
if err != nil {
logger.Error("utilFillMomentsWithApiVOInfo fail, req: %v, err: %v", err)
return
}
return
}
var ZoneVIPMidMap = map[int64]bool{
161: true,
1: true,
170: true,
165: true,
168: true,
167: true,
169: true,
2: true,
606403: true,
}
func IsZoneVIP(mid int64) bool {
//cfg := &apollostruct.ZoneVIPConfig{}
//_ = apollo.GetJson(consts.ZoneVIPConfigKey, cfg, apollo.ApolloOpts().SetNamespace("zone"))
//if util.Int64Contains(cfg.WhiteMids, mid) {
// if mid == 161 {
// logger.Info("_ZoneVip Apollo, mids: %v", cfg.WhiteMids)
// }
// return true
//}
return ZoneVIPMidMap[mid]
}
func (s *Service) utilFillZonesWithApiVOInfo(ctx *gin.Context, list []*dbstruct.Zone, visitorMid int64, zidZuMap map[int64]*dbstruct.ZoneUnlock) (volist []*zoneproto.ApiZoneVO, err error) {
// 1.获取midszids并填充空间基底
volist = make([]*zoneproto.ApiZoneVO, 0)
mids := make([]int64, 0)
zids := make([]int64, 0)
midSet := make(map[int64]*dbstruct.Zone)
zidSet := make(map[int64]*dbstruct.Zone)
for _, zone := range list {
mid := zone.GetMid()
zid := zone.GetId()
if midSet[mid] == nil {
midSet[mid] = &dbstruct.Zone{}
mids = append(mids, mid)
}
if zidSet[zid] == nil {
zidSet[zid] = &dbstruct.Zone{}
zids = append(zids, zid)
}
vo := &zoneproto.ApiZoneVO{}
vo.CopyZone(zone)
volist = append(volist, vo)
}
// 2.获取主播信息
ignoreMap := make(map[string]bool)
ignoreMap["zones"] = true
streamerExtMap, err := s.utilGetStreamerExtMapByMids(ctx, mids, consts.InterfaceType_Api, ignoreMap)
if err != nil {
logger.Error("utilGetStreamerExtMapByMids fail, req: %v, err: %v", err)
return
}
// 3.获取访客访问这些空间的session
zonesessionMp, err := _DefaultZoneSession.GetZoneSessionMapByMidAndZids(ctx, visitorMid, zids)
if err != nil {
logger.Error("GetZoneSessionMapByMidAndZids fail, req: %v, err: %v", err)
return
}
// 4.获取访客代运营的空间
tpZoneMap, err := _DefaultZoneThirdPartner.GetZoneThirdPartnerMapByTpMid(ctx, visitorMid)
if err != nil {
logger.Error("GetZoneThirdPartnerMapByTpMid fail, req: %v, err: %v", err)
return
}
// 5.获取访客协作的空间
collabZoneMap, err := _DefaultZoneCollaborator.GetZoneCollaboratorMapByCMid(ctx, visitorMid)
if err != nil {
logger.Error("GetZoneCollaboratorMapByCMid fail, req: %v, err: %v", err)
return
}
// 6.获取这些空间的预览图
zonePreviewsMp := make(map[int64]*dbstruct.MediaComponent, 0)
// 7.获取访客创建的空间
zoneMap, err := _DefaultZone.GetZoneMapByMids(ctx, []int64{visitorMid})
if err != nil {
logger.Error("_DefaultZone GetZoneMapByMids fail, req: %v, err: %v", err)
return
}
zones := zoneMap[visitorMid]
zoneZidMap := make(map[int64]*dbstruct.Zone, 0)
for _, zone := range zones {
zoneZidMap[zone.GetId()] = zone
}
// 8.获取空间付费信息
zvMap, _ := _DefaultVas.GetZoneVasByIds(ctx, zids)
// 9.填充信息
for _, vo := range volist {
zid := vo.Zone.GetId()
// 填充主播信息
vo.CopyStreamerExt(streamerExtMap[vo.GetMid()])
// 填充是否有未读信息
zonesession := zonesessionMp[zid]
if zonesession != nil && zonesession.GetUt() < vo.Zone.GetLastZoneMomentCt() {
vo.IsUnreadZoneMomentExist = consts.IsUnreadZoneMomentExist_Yes
} else {
vo.IsUnreadZoneMomentExist = consts.IsUnreadZoneMomentExist_No
}
// 填充访客身份
vo.VisitorRole = consts.Zone_Outsider
if zidZuMap[zid].IsUnlockAdmission() && tpZoneMap[zid] == nil && collabZoneMap[zid] == nil { // 访客已解锁空间是否有该zid
vo.VisitorRole = consts.Zone_Visitor
} else if zoneZidMap[zid] != nil { // 访客创建的空间是否有该zid
vo.VisitorRole = consts.Zone_Creater
} else if tpZoneMap[zid] != nil { // 访客代运营的空间是否有该zid
vo.VisitorRole = consts.Zone_ThridPartner
} else if collabZoneMap[zid] != nil { // 访客协作的空间是否有该zid
vo.VisitorRole = consts.Zone_Collaborator
}
// 填充该空间预览图
if zonePreviewsMp[zid] != nil {
vo.Previews = zonePreviewsMp[zid]
} else {
zonePreviewsMp[zid], err = s.utilGetZonePreviews(ctx, zid)
if err != nil {
logger.Error("utilGetZonePreviews fail, req: %v, err: %v", err)
return
}
vo.Previews = zonePreviewsMp[zid]
}
// 填充该空间总消耗费用
vo.Expenditure = zidZuMap[zid].GetConsume()
// 填充铁粉解锁信息
zu, ok := zidZuMap[zid]
if ok && zu.IsUnlockIronfanship() {
vo.IsIronfanshipUnlocked = consts.IsIronfanshipUnlocked_Yes
} else {
vo.IsIronfanshipUnlocked = consts.IsIronfanshipUnlocked_No
}
// 填充超粉解锁信息
if ok && zu.IsUnlockSuperfanship() {
vo.IsSuperfanshipUnlocked = consts.IsSuperfanshipUnlocked_Yes
} else {
vo.IsSuperfanshipUnlocked = consts.IsSuperfanshipUnlocked_No
}
// 空间vip 白名单 直接给看
if IsZoneVIP(visitorMid) {
logger.Info("_ZoneVIP mid: %v, zid: %v", visitorMid, zid)
vo.VisitorRole = consts.Zone_Visitor
vo.IsIronfanshipUnlocked = consts.IsIronfanshipUnlocked_Yes
vo.IsSuperfanshipUnlocked = consts.IsSuperfanshipUnlocked_Yes
}
if zv, ok := zvMap[zid]; ok {
vo.CopyZoneVas(zv)
}
}
return
}
func (s *Service) utilGetZonePreviews(ctx *gin.Context, zid int64) (previews *dbstruct.MediaComponent, err error) {
list, err := _DefaultZoneMoment.OpListByZid(ctx, &zonemomentproto.OpListByZidReq{
Zid: goproto.Int64(zid),
MType: goproto.Int64(consts.MediaTypeImg),
CType: goproto.Int64(consts.ZoneMomentCType_Free),
Status: goproto.Int64(consts.ZoneMoment_Public),
Limit: 4,
})
if err != nil {
logger.Error("_DefaultZoneMoment OpListByZid fail, req: %v, err: %v", err)
return
}
imageIds := make([]int64, 0)
for _, zonemoment := range list {
for _, imageId := range zonemoment.MediaComp.GetImageIds() {
imageIds = append(imageIds, imageId)
if len(imageIds) == 4 {
break
}
}
}
if len(imageIds) == 0 {
vzmlist := make([]*dbstruct.ZoneMoment, 0)
vzmlist, err = _DefaultZoneMoment.OpListByZid(ctx, &zonemomentproto.OpListByZidReq{
Zid: goproto.Int64(zid),
MType: goproto.Int64(consts.MediaTypeVideo),
CType: goproto.Int64(consts.ZoneMomentCType_Free),
Status: goproto.Int64(consts.ZoneMoment_Public),
Limit: 4,
})
if err != nil {
logger.Error("_DefaultZoneMoment OpListByZid fail, req: %v, err: %v", err)
return
}
objectMediaNum := 1 // 单个空间服务总共1个媒体类
mediaFillableList := make([]mediafiller.MediaFillable, len(vzmlist)*objectMediaNum)
for i, vo := range vzmlist {
mediaFillableList[objectMediaNum*i+0] = vo.MediaComp
}
imageIds, err = mediafiller.GetCoverIds(ctx, mediaFillableList)
if err != nil {
logger.Error("GetCoverIds fail, req: %v, err: %v", err)
return
}
}
previews = &dbstruct.MediaComponent{
ImageIds: util.Int64Slice(imageIds),
}
return
}
func (s *Service) utilFillZoneMomentsWithApiVOInfo(ctx *gin.Context, list []*dbstruct.ZoneMoment, visitorMid int64, zidZuMap map[int64]*dbstruct.ZoneUnlock) (volist []*zonemomentproto.ApiZoneMomentVO, err error) {
// 1.填充信息
volist = make([]*zonemomentproto.ApiZoneMomentVO, 0)
midSet := make(map[int64]*dbstruct.Moment)
mids := make([]int64, 0)
zids := make([]int64, 0)
zidSet := make(map[int64]*dbstruct.Zone)
momentIds := make([]int64, 0)
for _, zonemoment := range list {
vo := &zonemomentproto.ApiZoneMomentVO{
ZoneMoment: zonemoment,
}
volist = append(volist, vo)
momentIds = append(momentIds, zonemoment.GetId())
mid := zonemoment.GetMid()
if midSet[mid] == nil {
midSet[mid] = &dbstruct.Moment{}
mids = append(mids, mid)
}
zid := zonemoment.GetZid()
if zidSet[zid] == nil {
zidSet[zid] = &dbstruct.Zone{}
zids = append(zids, zid)
}
}
// 2.通过mids获取主播信息map
ignoreMap := make(map[string]bool)
ignoreMap["zones"] = true
streamerExtMap, err := s.utilGetStreamerExtMapByMids(ctx, mids, consts.InterfaceType_Api, ignoreMap)
if err != nil {
logger.Error("utilGetStreamerExtMapByMids fail, err: %v", err)
return
}
// 3.获取访客创建的空间
zoneMap, err := _DefaultZone.GetZoneMapByMids(ctx, []int64{visitorMid})
if err != nil {
logger.Error("_DefaultZone GetZoneMapByMids fail, req: %v, err: %v", err)
return
}
zones := zoneMap[visitorMid]
zoneZidMap := make(map[int64]*dbstruct.Zone, 0)
for _, zone := range zones {
zoneZidMap[zone.GetId()] = zone
}
// 4.获取动态的一些静态数据信息
zmStatMap, _ := _DefaultVas.GetZoneMomentStatByIds(ctx, momentIds)
// 5.获取该mid是否已解锁这些动态
momentIdZmuMap, _ := _DefaultVas.GetZoneMomentUnlockMapByMidMomentIds(ctx, visitorMid, momentIds)
// 6.获取空间价格
zvMap, _ := _DefaultVas.GetZoneVasByIds(ctx, zids)
//logger.Info("_MomentVO zvMap: %v", util.ToJson(zvMap))
//logger.Info("_MomentVO zidZuMap: %v", util.ToJson(zidZuMap))
// 7.填充所有信息
for _, vo := range volist {
vo.ZoneMoment.CoinPrice = vo.ZoneMoment.GetCoinPrice()
zid := vo.ZoneMoment.GetZid()
// 主播信息
vo.CopyStreamerExt(streamerExtMap[vo.GetMid()])
// 是否点赞
if err = s.utilFillIsZoneMomentThumbedUpFillable(ctx, visitorMid, vo); err != nil {
logger.Error("utilFillIsZoneMomentThumbedUpFillable fail, err: %v", err)
return
}
// 填充铁粉解锁信息
zu, ok := zidZuMap[zid]
if ok && zu.IsUnlockIronfanship() {
vo.IsIronfanshipUnlocked = consts.IsIronfanshipUnlocked_Yes
} else {
vo.IsIronfanshipUnlocked = consts.IsIronfanshipUnlocked_No
}
// 填充超粉解锁信息
if ok && zu.IsUnlockSuperfanship() {
vo.IsSuperfanshipUnlocked = consts.IsSuperfanshipUnlocked_Yes
} else {
vo.IsSuperfanshipUnlocked = consts.IsSuperfanshipUnlocked_No
}
// 是否解锁动态
s.utilFillIsZoneMomentUnlocked(vo, zoneZidMap, momentIdZmuMap, visitorMid)
// 动态已解锁信息
if zmStat, ok := zmStatMap[vo.ZoneMoment.GetId()]; ok {
vo.BuyerCnt = zmStat.BuyerCnt
}
// 已消费金额
if zu, ok := zidZuMap[zid]; ok {
vo.Expenditure = zu.GetConsume()
}
// 铁粉价格
if zv, ok := zvMap[zid]; ok {
vo.IronfanshipPrice = zv.IronfanshipPrice
vo.IronfanshipCoinPrice = zv.GetIronfanshipCoinPrice()
vo.IsSuperfanshipEnabled = int64(zv.IsSuperfanshipEnabled)
}
// 非创建者,抹去审核信息
if visitorMid != vo.ZoneMoment.GetMid() {
vo.ZoneMoment.Status = nil
vo.ZoneMoment.ImageAuditStatus = nil
vo.ZoneMoment.TextAuditStatus = nil
vo.ZoneMoment.ManuallyReviewStatus = nil
vo.ZoneMoment.ImageAuditOpinion = nil
vo.ZoneMoment.TextAuditOpinion = nil
vo.ZoneMoment.ManuallyReviewOpinion = nil
vo.ZoneMoment.ManuallyReviewOperator = nil
}
}
return
}
func (s *Service) utilFillZonesWithOpVOInfo(ctx *gin.Context, list []*dbstruct.Zone) (volist []*zoneproto.OpZoneVO, err error) {
// 1.获取midszids并填充空间基底
volist = make([]*zoneproto.OpZoneVO, 0)
mids := make([]int64, 0)
zids := make([]int64, 0)
midSet := make(map[int64]*dbstruct.Zone)
zidSet := make(map[int64]*dbstruct.Zone)
for _, zone := range list {
mid := zone.GetMid()
zid := zone.GetId()
if midSet[mid] == nil {
midSet[mid] = &dbstruct.Zone{}
mids = append(mids, mid)
}
if zidSet[zid] == nil {
zidSet[zid] = &dbstruct.Zone{}
zids = append(zids, zid)
}
vo := &zoneproto.OpZoneVO{}
vo.CopyZone(zone)
volist = append(volist, vo)
}
// 2.获取主播信息
ignoreMap := make(map[string]bool)
ignoreMap["zones"] = true
streamerExtMap, err := s.utilGetStreamerExtMapByMids(ctx, mids, consts.InterfaceType_Op, ignoreMap)
if err != nil {
logger.Error("utilGetStreamerExtMapByMids fail, req: %v, err: %v", err)
return
}
// 3.获取空间代运营
ztpMp, err := _DefaultZoneThirdPartner.GetZoneThirdPartnerMapByZids(ctx, zids)
if err != nil {
logger.Error("GetZoneThirdPartnerMapByZids fail, req: %v, err: %v", err)
return
}
// 4.获取空间协作者
zcMp, err := _DefaultZoneCollaborator.GetZoneCollaboratorMapByZids(ctx, zids)
if err != nil {
logger.Error("GetZoneCollaboratorMapByZids fail, req: %v, err: %v", err)
return
}
// 5.获取账户信息
midSet = make(map[int64]*dbstruct.Zone)
mids = make([]int64, 0)
for _, ztp := range ztpMp {
mid := ztp.GetThirdPartnerMid()
if midSet[mid] == nil {
midSet[mid] = &dbstruct.Zone{}
mids = append(mids, mid)
}
}
for _, zclist := range zcMp {
for _, zc := range zclist {
mid := zc.GetCollaboratorMid()
if midSet[mid] == nil {
midSet[mid] = &dbstruct.Zone{}
mids = append(mids, mid)
}
}
}
acctMp, err := _DefaultAccount.GetAccountMapByMids(ctx, mids)
if err != nil {
logger.Error("GetAccountMapByMids fail, req: %v, err: %v", err)
return
}
// 8.获取空间付费信息
zvMap, _ := _DefaultVas.GetZoneVasByIds(ctx, zids)
// 9.获取空间人数信息
zmcountMap, _ := _DefaultVas.GetZoneMemberCountGroupByZoneMemberType(ctx, zids)
// 10.填充信息
for _, vo := range volist {
zid := vo.Zone.GetId()
// 填充主播信息
vo.CopyStreamerExt(streamerExtMap[vo.GetMid()])
// 填充代运营
ztp := ztpMp[zid]
ztpAcctVO := &accountproto.ApiListOthersVO{}
ztpAcctVO.CopyAccount(acctMp[ztp.GetThirdPartnerMid()])
vo.ZoneThirdPartner = &zone_third_partner_proto.ZoneThirdPartnerApiVO{
ZoneThirdPartner: ztp,
Account: ztpAcctVO,
}
vo.ZoneThirdPartner.IsHided = vo.Zone.GetIsZoneThirdPartnerHided()
// 填充协作者
zclist := zcMp[zid]
vo.ZoneCollaboratorList = make([]*zone_collaborator_proto.ZoneCollaboratorApiVO, 0)
for _, zc := range zclist {
zcAcctVO := &accountproto.ApiListOthersVO{}
zcAcctVO.CopyAccount(acctMp[zc.GetCollaboratorMid()])
vo.ZoneCollaboratorList = append(vo.ZoneCollaboratorList, &zone_collaborator_proto.ZoneCollaboratorApiVO{
ZoneCollaborator: zc,
Account: zcAcctVO,
})
}
if zv, ok := zvMap[zid]; ok {
vo.CopyZoneVas(zv)
}
// 填充空间总人数
zmcount := zmcountMap[zid]
vo.IronfanNum = zmcount[int64(dbstruct.ZoneMemberTypeIronfan)]
vo.SuperfanNum = zmcount[int64(dbstruct.ZoneMemberTypeSuperfan)]
vo.EntrantNum = zmcount[int64(dbstruct.ZoneMemberTypeNormal)]
}
return
}
func (s *Service) utilFillZoneMomentsWithOpVOInfo(ctx *gin.Context, list []*dbstruct.ZoneMoment) (volist []*zonemomentproto.OpZoneMomentVO, err error) {
// 1.填充信息
volist = make([]*zonemomentproto.OpZoneMomentVO, 0)
midSet := make(map[int64]*dbstruct.Moment)
mids := make([]int64, 0)
for _, zonemoment := range list {
vo := &zonemomentproto.OpZoneMomentVO{
ZoneMoment: zonemoment,
}
volist = append(volist, vo)
mid := zonemoment.GetMid()
if midSet[mid] == nil {
midSet[mid] = &dbstruct.Moment{}
mids = append(mids, mid)
}
}
// 2.通过mids获取主播信息map
ignoreMap := make(map[string]bool)
ignoreMap["zones"] = true
streamerExtMap, err := s.utilGetStreamerExtMapByMids(ctx, mids, consts.InterfaceType_Op, ignoreMap)
if err != nil {
logger.Error("utilGetStreamerExtMapByMids fail, err: %v", err)
return
}
// 7.填充所有信息
for _, vo := range volist {
// 主播信息
vo.CopyStreamerExt(streamerExtMap[vo.GetMid()])
}
return
}
func (s *Service) utilAssembleDailyStatementZoneInfo(zoneprofits, zonerefunds []*dbstruct.ZoneProfit, st, et int64) (list []*dbstruct.DailyStatementZoneInfo) {
list = make([]*dbstruct.DailyStatementZoneInfo, 0)
zidDlystmtMp := make(map[int64]*dbstruct.DailyStatementZoneInfo)
// 先写入退款相关的信息
zidZrMp := make(map[int64]*dbstruct.ZoneProfit)
for _, zonerefund := range zonerefunds {
zidZrMp[zonerefund.GetZid()] = zonerefund
dlystmt := &dbstruct.DailyStatementZoneInfo{
Zid: zonerefund.Zid,
Mid: zonerefund.Mid,
RefundAmount: zonerefund.Amount,
RefunderAmount: zonerefund.Num,
StartTime: goproto.Int64(st),
EndTime: goproto.Int64(et),
}
zidDlystmtMp[zonerefund.GetZid()] = dlystmt
list = append(list, dlystmt)
}
// 再写入收入相关的信息
for _, zoneprofit := range zoneprofits {
dlystmt, ok := zidDlystmtMp[zoneprofit.GetZid()]
if !ok {
//没有相关信息,写入一个
dlystmt = &dbstruct.DailyStatementZoneInfo{
Zid: zoneprofit.Zid,
Mid: zoneprofit.Mid,
StartTime: goproto.Int64(st),
EndTime: goproto.Int64(et),
}
zidDlystmtMp[zoneprofit.GetZid()] = dlystmt
list = append(list, dlystmt)
}
switch zoneprofit.GetProductId() {
case dbstruct.ProductIdH5ZoneMoment:
dlystmt.ZoneMomentAmount = zoneprofit.Amount
case dbstruct.ProductIdH5ZoneAdmission:
dlystmt.AdmissionAmount = zoneprofit.Amount
dlystmt.EntrantNum = goproto.Int64(dlystmt.GetEntrantNum() + zoneprofit.GetNum())
case dbstruct.ProductIdH5ZoneSuperfanship:
dlystmt.SuperfanshipAmount = zoneprofit.Amount
}
dlystmt.TotalAmount = goproto.Int64(dlystmt.GetTotalAmount() + zoneprofit.GetAmount())
}
return
}
func (s *Service) utilSignHvyogoMessage(msg interfaces.HvyogoSignable) ([]byte, error) {
// 非空校验
if notNullItem, ok := msg.(validator.NotNullItem); ok {
err := validator.GetDefaultNotNullValidator().Validate(notNullItem)
if err != nil {
return make([]byte, 0), fmt.Errorf("非空校验失败: %v", err)
}
}
// 1.RSA加密生成签名
sign, err := mycrypto.CryptoServiceInstance().HygSHA1WithRSA.Sign([]byte(util.SortParam(msg.GetArgList())))
if err != nil {
logger.Error("Sign fail, err :%v", err)
return make([]byte, 0), err
}
// 2.设置签名
msg.SetSign(sign)
// 3.AES加密转大写十六进制
bytes, err := json.Marshal(msg)
if err != nil {
logger.Error("json Marshal fail, err :%v", err)
return make([]byte, 0), err
}
aesEncryptedBytes, err := mycrypto.CryptoServiceInstance().HygAES.Encrypt(bytes)
if err != nil {
logger.Error("HygAES Encryption fail, err :%v", err)
return make([]byte, 0), err
}
businessBody := strings.ToUpper(hex.EncodeToString(aesEncryptedBytes))
// 4.生成最终报文
result := &request.HYGCommonReq{
CooperatorId: msg.GetCooperatorId(),
BusinessBody: businessBody,
}
resultBytes, err := json.Marshal(result)
if err != nil {
logger.Error("json Marshal fail, err :%v", err)
return make([]byte, 0), err
}
return resultBytes, nil
}
func (s *Service) UtilEncryptVideosForZoneMomentVOs(ctx *gin.Context, list []*zonemomentproto.ApiZoneMomentVO) {
videoIdForUploadFail, err := apollo.GetIntValue(consts.VideoIdForUploadFail, apollo.ApolloOpts().SetNamespace("application"))
if err != nil {
logger.Error("Apollo read failed : %v", err)
}
media := &dbstruct.MediaComponent{
ImageIds: util.Int64Slice(make([]int64, 0)),
VideoIds: util.Int64Slice([]int64{int64(videoIdForUploadFail)}),
}
mediafiller.FillEntity(ctx, media)
vdi := &dbstruct.ToCVideo{}
if len(media.Videos) > 0 {
vdi = media.Videos[0]
}
for _, vo := range list {
if vo.IsZoneMomentUnlocked == consts.IsZoneMomentUnlocked_No {
videoIds := vo.MediaComp.GetVideoIds()
for i := range videoIds {
videoIds[i] = int64(videoIdForUploadFail)
}
vo.MediaComp.VideoIds = util.Int64Slice(videoIds)
for _, video := range vo.MediaComp.Videos {
video.Urls = make([]string, 0)
video.Urls = append(video.Urls, vdi.Urls...)
}
}
}
}
func (s *Service) utilLogoutAll(ctx *gin.Context, mid int64) (err error) {
if err = _DefaultToken.OpDeleteByMid(ctx, mid); err != nil && err != qmgo.ErrNoSuchDocuments {
logger.Error("ApiDeleteByMid failed, err: %v", err)
return
}
// 更新登录状态
if err = _DefaultLogin.OpUpdateByMid(ctx, &loginproto.OpUpdateByMidReq{
Login: &dbstruct.Login{
IsLogined: goproto.Int64(0),
},
Mid: goproto.Int64(mid),
}); err != nil {
logger.Error("ApiUpdateByMid failed, err: %v", err)
return
}
return
}
func (s *Service) utilWriteNotifCancel(ctx *gin.Context, nidAccessor interfaces.NidAccessible) {
// 获取通知builder
notifBuilders := make([]*dbstruct.NotifBuilder, 0)
notifBuildersObj, ok := ctx.Get("notif_builders")
if ok {
notifBuilders = notifBuildersObj.([]*dbstruct.NotifBuilder)
}
notifBuilders = append(notifBuilders, &dbstruct.NotifBuilder{
TemplateId: consts.CtrlNotifTemp_CancelNotif,
GetNid: nidAccessor.GetNid,
})
ctx.Set("notif_builders", notifBuilders)
}
func (s *Service) utilWriteNotifInfo(ctx *gin.Context, notifTempId int64, objMid int64, notifTempParams ...any) {
// 获取通知builder
notifBuilders := make([]*dbstruct.NotifBuilder, 0)
notifBuildersObj, ok := ctx.Get("notif_builders")
if ok {
notifBuilders = notifBuildersObj.([]*dbstruct.NotifBuilder)
}
notifBuilders = append(notifBuilders, &dbstruct.NotifBuilder{
TemplateId: notifTempId,
ObjMids: []int64{objMid},
TemplateParams: notifTempParams,
})
ctx.Set("notif_builders", notifBuilders)
}
func (s *Service) utilWriteNotifInfoByMap(ctx *gin.Context, notifTempId int64, objMid int64, mp map[string]any) {
// 获取通知builder
notifBuilders := make([]*dbstruct.NotifBuilder, 0)
notifBuildersObj, ok := ctx.Get("notif_builders")
if ok {
notifBuilders = notifBuildersObj.([]*dbstruct.NotifBuilder)
}
notifBuilder := &dbstruct.NotifBuilder{
TemplateId: notifTempId,
ObjMids: []int64{objMid},
}
templateParamsObj, ok := mp["template_params"]
if ok {
notifBuilder.TemplateParams = templateParamsObj.([]any)
}
linkTextTemplateParamsObj, ok := mp["link_text_template_params"]
if ok {
notifBuilder.LinkTextTemplateParams = linkTextTemplateParamsObj.([]any)
}
thumbnailObj, ok := mp["thumbnail"]
if ok {
notifBuilder.Thumbnail = thumbnailObj.(*dbstruct.MediaComponent)
}
hyperlinksObj, ok := mp["hyperlinks"]
if ok {
notifBuilder.HyperLinks = hyperlinksObj.([]*dbstruct.NotifHyperlink)
}
pushTimeObj, ok := mp["push_time"]
if ok {
notifBuilder.PushTime = pushTimeObj.(int64)
}
setNidObj, ok := mp["set_nid"]
if ok {
notifBuilder.SetNid = setNidObj.(func(ctx *gin.Context, nid int64) error)
}
buildHyperLink, ok := mp["build_hyperlink"]
if ok {
notifBuilder.BuildHyperLink = buildHyperLink.(func(ctx *gin.Context, frontendRouteId int64) ([]*dbstruct.NotifHyperlink, error))
}
notifBuilders = append(notifBuilders, notifBuilder)
ctx.Set("notif_builders", notifBuilders)
}
func (s *Service) utilCalcAccountPunishmentEndNotifTime(acctpunishment *dbstruct.AccountPunishment) (int64, error) {
notifTime := acctpunishment.GetEndTime()
cfg := apollostruct.AcctPunishmentRealEndtimeCfg{}
err := apollo.GetJson(consts.AcctPunishmentRealEndTimeKey, &cfg, apollo.ApolloOpts().SetNamespace("application"))
if err != nil {
logger.Error("Apollo read failed : %v", err)
return 0, err
}
interval, ok := cfg.IntervalMap[fmt.Sprint(acctpunishment.GetType())]
if !ok {
interval = cfg.DefaultInterval
}
if notifTime%interval != 0 { // 已经是倍数则不再顺延
notifTime = (notifTime/interval + 1) * interval
}
return notifTime, nil
}
func (s *Service) utilBuildInwardHyperLink(ctx *gin.Context, frontendRouteId int64) ([]*dbstruct.NotifHyperlink, error) {
// 获取跳转路径
frontendroute, err := _DefaultFrontendRoute.GetById(ctx, frontendRouteId)
if err != nil {
logger.Error("_DefaultFrontendRoute GetById fail, err: %v", err)
return make([]*dbstruct.NotifHyperlink, 0), err
}
hyperlinks := make([]*dbstruct.NotifHyperlink, 0)
hyperlinks = append(hyperlinks, &dbstruct.NotifHyperlink{
Action: goproto.String(consts.FrontendRouteAction_Inward),
Params: frontendroute.AppRoutePath,
})
hyperlinks = append(hyperlinks, &dbstruct.NotifHyperlink{
Action: goproto.String(consts.FrontendRouteAction_Inward),
Params: frontendroute.H5RoutePath,
})
return hyperlinks, nil
}
func (s *Service) utilIncrUrc(mid, nType, incr int64) error {
_, err := redis.GetRedisClient().IncrBy(util.GetNotifUrcIdForRedis(mid, nType), incr)
if err != nil {
logger.Error("Redis IncrBy fail, err: %v", err)
return err
}
_, err = redis.GetRedisClient().IncrBy(util.GetNotifUrcTotalIdForRedis(mid), incr)
if err != nil {
logger.Error("Redis IncrBy fail, err: %v", err)
return err
}
return nil
}
func (s *Service) utilDecrUrc(mid, nType, incr int64) error {
_, err := redis.GetRedisClient().DecrBy(util.GetNotifUrcIdForRedis(mid, nType), incr)
if err != nil {
logger.Error("Redis DecrBy fail, err: %v", err)
return err
}
_, err = redis.GetRedisClient().DecrBy(util.GetNotifUrcTotalIdForRedis(mid), incr)
if err != nil {
logger.Error("Redis DecrBy fail, err: %v", err)
return err
}
return nil
}
func (s *Service) utilGetRecentReceiveNotif(ctx *gin.Context, mid, nType int64) (*dbstruct.Notification, error) {
recentReceive, err := _DefaultNotifRecentReceive.GetNotifRecentReceive(ctx, util.GetNotifRecentReceiveId(mid, nType))
if err != nil {
logger.Error("GetNotifRecentReceive failed : %v", err)
return nil, err
}
var notif *dbstruct.Notification
if recentReceive != nil {
notif, err = _DefaultNotification.GetListById(ctx, recentReceive.Nid)
if err != nil {
logger.Error("GetListById failed : %v", err)
return nil, err
}
}
return notif, nil
}