From ceda79f55c3e367f0b49b6db57cbf740f2227825 Mon Sep 17 00:00:00 2001 From: hujiebin Date: Thu, 31 Aug 2023 16:27:17 +0800 Subject: [PATCH] Feature/room visitor up --- cron/cron.go | 2 + cron/user_cron/svip_vip_noble.go | 20 ++++ domain/cache/user_c/svip_vip_noble.go | 95 ++++++++++++++++ domain/model/user_m/user.go | 21 ++++ route/group_r/group_list.go | 158 ++++++++++++++++++++++++++ route/router.go | 1 + 6 files changed, 297 insertions(+) create mode 100644 cron/user_cron/svip_vip_noble.go create mode 100644 domain/cache/user_c/svip_vip_noble.go diff --git a/cron/cron.go b/cron/cron.go index 985e303..e966efc 100644 --- a/cron/cron.go +++ b/cron/cron.go @@ -5,9 +5,11 @@ import ( "hilo-group/cron/gift_cron" "hilo-group/cron/group_cron" "hilo-group/cron/mic_cron" + "hilo-group/cron/user_cron" ) func Init() { + user_cron.LoadSvipVipNoble() // 加载用户特权 if !config.IsMaster() { return } diff --git a/cron/user_cron/svip_vip_noble.go b/cron/user_cron/svip_vip_noble.go new file mode 100644 index 0000000..3d0be99 --- /dev/null +++ b/cron/user_cron/svip_vip_noble.go @@ -0,0 +1,20 @@ +package user_cron + +import ( + "git.hilo.cn/hilo-common/domain" + "github.com/robfig/cron" + "hilo-group/domain/cache/user_c" +) + +// 加载数据到lru +func LoadSvipVipNoble() { + go user_c.LoadAllSvipVipNoble(domain.CreateModelNil()) // 启动先执行一次 + + c := cron.New() + spec := "0 */30 * * * ?" + _ = c.AddFunc(spec, func() { + model := domain.CreateModelNil() + user_c.LoadAllSvipVipNoble(model) + }) + c.Start() +} diff --git a/domain/cache/user_c/svip_vip_noble.go b/domain/cache/user_c/svip_vip_noble.go new file mode 100644 index 0000000..58bf95f --- /dev/null +++ b/domain/cache/user_c/svip_vip_noble.go @@ -0,0 +1,95 @@ +package user_c + +import ( + "git.hilo.cn/hilo-common/domain" + "git.hilo.cn/hilo-common/resource/mysql" + "sync" + "time" +) + +type UserLevel struct { + UserId mysql.ID + Level int +} + +var svipVipNobleLock sync.RWMutex + +var svipLevel = make(map[mysql.ID]int) // userId->svipLevel +var nobleLevel = make(map[mysql.ID]uint16) // userId->nobleLevel +var userVips = make(map[mysql.ID]bool) // userId->isVip + +// 加载数据到lru +func LoadAllSvipVipNoble(model *domain.Model) { + start := time.Now() + var svips []UserLevel + svipMap := make(map[mysql.ID]int) + if err := model.DB().Table("user_svip").Select("user_id,level").Where("level > 0").Find(&svips).Error; err != nil { + model.Log.Errorf("LoadAllSvip fail:%v", err) + } else { + for _, v := range svips { + svipMap[v.UserId] = v.Level + } + } + var nobles []UserLevel + nobleMap := make(map[mysql.ID]uint16) + if err := model.DB().Table("user_noble").Select("user_id,MAX(level) level"). + Where("level > 0 AND end_time > ?", time.Now()).Find(&nobles).Error; err != nil { + model.Log.Errorf("LoadAllNoble fail:%v", err) + } else { + for _, v := range nobles { + nobleMap[v.UserId] = uint16(v.Level) + } + } + + var vips []UserLevel + vipMap := make(map[mysql.ID]bool) + if err := model.DB().Table("user_vip").Select("user_id"). + Where("expire_at > ?", time.Now()).Find(&vips).Error; err != nil { + model.Log.Errorf("LoadAllVip fail:%v", err) + } else { + for _, v := range vips { + vipMap[v.UserId] = true + } + } + + // 上锁赋值 + svipVipNobleLock.Lock() + svipLevel = svipMap + nobleLevel = nobleMap + userVips = vipMap + svipVipNobleLock.Unlock() + model.Log.Infof("LoadAllSvipVipNoble svip:%v,vip:%v,noble:%v,cost:%vs", len(svipMap), len(vipMap), len(nobleMap), time.Now().Sub(start).Seconds()) +} + +func BatchGetNobleLevel(model *domain.Model, userIds []uint64) map[uint64]uint16 { + res := make(map[uint64]uint16) + svipVipNobleLock.RLock() + defer svipVipNobleLock.RUnlock() + for _, userId := range userIds { + res[userId] = nobleLevel[userId] + } + return res +} + +func MGetUserSvipLevel(model *domain.Model, userIds []uint64) map[uint64]int { + res := make(map[uint64]int) + svipVipNobleLock.RLock() + defer svipVipNobleLock.RUnlock() + for _, userId := range userIds { + res[userId] = svipLevel[userId] + } + return res +} + +func BatchGetVips(model *domain.Model, userIds []uint64) map[uint64]*int64 { + res := make(map[uint64]*int64) + svipVipNobleLock.RLock() + defer svipVipNobleLock.RUnlock() + for _, userId := range userIds { + vip := int64(1) + if userVips[userId] { + res[userId] = &vip + } + } + return res +} diff --git a/domain/model/user_m/user.go b/domain/model/user_m/user.go index 40287a1..a51520e 100644 --- a/domain/model/user_m/user.go +++ b/domain/model/user_m/user.go @@ -144,3 +144,24 @@ func GetUsers(model *domain.Model, ids []mysql.ID) ([]*User, error) { } return res, nil } + +// 获取用户externalIds +// return []externalIds map[userId]extId map[userId]code +func GetUserExternalIds(model *domain.Model, userIds []mysql.ID) ([]string, map[uint64]string, map[uint64]string) { + var res []string + var m = make(map[uint64]string) + var c = make(map[uint64]string) + if len(userIds) <= 0 { + return res, m, c + } + var users []User + if err := model.DB().Model(User{}).Where("id in (?)", userIds).Select("id,external_id").Find(&users).Error; err != nil { + model.Log.Errorf("GetUserExternalIds fail:%v", err) + } + for _, user := range users { + res = append(res, user.ExternalId) + m[user.ID] = user.ExternalId + c[user.ID] = user.Code + } + return res, m, c +} diff --git a/route/group_r/group_list.go b/route/group_r/group_list.go index 95722fa..ca16770 100644 --- a/route/group_r/group_list.go +++ b/route/group_r/group_list.go @@ -20,6 +20,7 @@ import ( "hilo-group/cv/user_cv" "hilo-group/domain/cache/group_c" "hilo-group/domain/cache/res_c" + "hilo-group/domain/cache/user_c" "hilo-group/domain/model/game_m" "hilo-group/domain/model/group_m" "hilo-group/domain/model/noble_m" @@ -988,6 +989,163 @@ func GetGroupVisitors(c *gin.Context) (*mycontext.MyContext, error) { return myContext, nil } +// @Tags 群组 +// @Summary 最近访问列表 +// @Accept application/x-www-form-urlencoded +// @Param token header string true "token" +// @Param nonce header string true "随机数字" +// @Param groupId path string true "群ID" +// @Param pageSize query int false "分页大小 默认:10" default(10) +// @Param pageIndex query int false "第几个分页,从1开始 默认:1" default(1) +// @Success 200 {object} GetGroupVisitorsRsp +// @Router /v1/imGroup/visitors/{groupId} [get] +func GetGroupVisitorsV2(c *gin.Context) (*mycontext.MyContext, error) { + myContext := mycontext.CreateMyContext(c.Keys) + + groupId := c.Param("groupId") + if len(groupId) <= 0 { + return myContext, bizerr.ParaMissing + } + + pageSize, err := strconv.Atoi(c.Query("pageSize")) + if err != nil || pageSize <= 0 { + pageSize = 10 + } + pageIndex, err := strconv.Atoi(c.Query("pageIndex")) + if err != nil || pageIndex <= 0 { + pageIndex = 1 + } + + userId, _, err := req.GetUserIdAndExtId(c, myContext) + if err != nil { + return myContext, err + } + + model := domain.CreateModelContext(myContext) + + groupId, err = group_m.ToImGroupId(model, groupId) + if err != nil { + return myContext, err + } + // 获取最近进房用户 + rows := group_c.GetLastRoomVisitors(model, groupId) + + userIds := make([]uint64, 0) + for _, u := range rows { + userIds = append(userIds, u) + } + result := GetGroupVisitorsRsp{Total: uint(len(userIds))} + + beginPos := pageSize * (pageIndex - 1) + if uint(beginPos) < result.Total { + // 取在线状态 + extIds, userExtIdMap, userCodeMap := user_m.GetUserExternalIds(model, userIds) + statusMap, err := tim_m.GetOnlineStatus(model, extIds) + if err != nil { + return myContext, err + } + result.Online = 0 + for _, v := range statusMap { + if v == online_e.IM_STATUS_ON_LINE { + result.Online++ + } + } + // 获取群组角色 + roles, _, err := group_m.GetRolesInGroup(model, groupId) + if err != nil { + return myContext, err + } + // 获取贵族 + nobleLevels := user_c.BatchGetNobleLevel(model, userIds) + // 获取svip + svipLevels := user_c.MGetUserSvipLevel(model, userIds) + // 获取svip + vips := user_c.BatchGetVips(model, userIds) + + roomUsers, err := group_m.RoomLivingExistsUserId(groupId) + if err != nil { + return myContext, err + } + + roomUserMap := utils.SliceToMapUInt64(roomUsers) + + // 排序规则 :在房间的优先,其次是在线,再次看角色,最后看贵族 + sort.Slice(userIds, func(i, j int) bool { + ui := userIds[i] + uj := userIds[j] + + _, ok1 := roomUserMap[ui] + _, ok2 := roomUserMap[uj] + if ok1 && !ok2 { + return true + } else if ok1 == ok2 { + ei := userExtIdMap[ui] + ej := userExtIdMap[uj] + if statusMap[ei] > statusMap[ej] { + return true + } + if statusMap[ei] == statusMap[ej] { + if roles[ui] > roles[uj] { + return true + } + if roles[ui] == roles[uj] { + // SVIP>贵族5>贵族4>贵族3>贵族2>VIP + if svipLevels[ui] > svipLevels[uj] { + return true + } else if svipLevels[ui] == svipLevels[uj] { + if nobleLevels[ui] > nobleLevels[uj] && nobleLevels[ui] >= 2 { + return true + } + if nobleLevels[ui] == nobleLevels[uj] || nobleLevels[ui] < 2 && nobleLevels[uj] < 2 { + if vips[ui] != nil { + if vips[uj] == nil { + return true + } else { + return userCodeMap[ui] < userCodeMap[uj] + } + } else if vips[uj] == nil { + return userCodeMap[ui] < userCodeMap[uj] + } + } + } + } + } + } + return false + }) + + endPos := pageSize * pageIndex + if endPos > len(userIds) { + endPos = len(userIds) + } + + userIds = userIds[beginPos:endPos] + userExtends, err := user_cv.BatchGetUserExtend(model, userIds, userId) // 这里只是取出pageSize=10条数据 + if err != nil { + return myContext, err + } + + for _, u := range userIds { + inRoom := false + if _, ok := roomUserMap[u]; ok { + inRoom = true + } + result.Members = append(result.Members, group_cv.GroupVisitorsDetail{ + CvGroupMember: user_cv.CvUserExtendToCvGroupMember(userExtends[u]), + Role: roles[u], + OnlineStatus: statusMap[userExtIdMap[u]], + InRoom: inRoom, + }) + } + } + resp.ResponseOk(c, result) + // 清理15天之前的访客,定期 + if time.Now().Second() == 0 { + group_c.RemoveRoomVisitors(model, groupId) + } + return myContext, nil +} + type OwnPublicGroupRsp struct { Total uint `json:"total"` MyGroups []group_cv.GroupDetail `json:"myGroups"` diff --git a/route/router.go b/route/router.go index c16fb82..24c2454 100644 --- a/route/router.go +++ b/route/router.go @@ -43,6 +43,7 @@ func InitRouter() *gin.Engine { imGroup.GET("/myPermanent", wrapper(group_r.GetMyGroup)) // imGroup.GET("/visitors/:groupId", wrapper(group_r.GetGroupVisitors)) + imGroup.GET("/visitors2/:groupId", wrapper(group_r.GetGroupVisitorsV2)) imGroup.GET("/ownPublicGroup/:userExternalId", wrapper(group_r.GetOwnPublicGroup)) //// 2.19的新接口 imGroup.GET("/ownGroup", wrapper(group_r.GetOwnGroup)) -- 2.22.0