package group_s import ( "context" "encoding/json" "fmt" "git.hilo.cn/hilo-common/domain" "git.hilo.cn/hilo-common/mycontext" "git.hilo.cn/hilo-common/mylogrus" "git.hilo.cn/hilo-common/resource/config" "git.hilo.cn/hilo-common/resource/mysql" "git.hilo.cn/hilo-common/resource/redisCli" "git.hilo.cn/hilo-common/rpc" "git.hilo.cn/hilo-common/sdk/agora" "git.hilo.cn/hilo-common/sdk/tencentyun" "git.hilo.cn/hilo-common/sdk/trtc" "git.hilo.cn/hilo-common/utils" "gorm.io/gorm" "hilo-group/_const/enum/group_e" "hilo-group/_const/enum/msg_e" "hilo-group/_const/redis_key" "hilo-group/_const/redis_key/group_k" "hilo-group/common" "hilo-group/cv/property_cv" "hilo-group/domain/cache/group_c" "hilo-group/domain/cache/tim_c" "hilo-group/domain/event/group_ev" "hilo-group/domain/model/group_m" "hilo-group/domain/model/noble_m" "hilo-group/domain/model/res_m" "hilo-group/domain/model/user_m" "hilo-group/domain/service/group_mic_s" "hilo-group/domain/service/signal_s" "hilo-group/myerr" "hilo-group/myerr/bizerr" "strconv" "strings" "time" ) // 创建群组 func (s *GroupService) CreateGroup(userId uint64, g *group_m.GroupInfo) error { return s.svc.Transactional(func() error { model := domain.CreateModel(s.svc.CtxAndDb) if err := group_m.CreateGroup(model, g); err != nil { return err } if err := group_m.CreateGroupRole(model, g.ImGroupId, userId, group_e.GROUP_OWNER); err != nil { return err } //// 新房间标记成trtc房间 //if err := group_m.InitTRTC(model, g.ImGroupId); err != nil { // model.Log.Errorf("CreateGroup g:%+v, err:%v", g, err) //} return nil }) } // 退群的一系列操作 func (s *GroupService) LeaveGroup(model *domain.Model, groupId string, userId uint64, externalId string) error { //获取用户是否在麦上, 让用户离开麦 micUser, err := group_m.GetMicUserByExternalId(model, externalId) if err != nil { return err } if micUser != nil { if err = micUser.LeaveByUser(userId, externalId); err != nil { return err } } // 退群后删除管理角色 if err = group_m.RemoveGroupRole(model, groupId, userId); err != nil { mylogrus.MyLog.Warnf("Can't remove group %s user %d's role.", groupId, userId) } // 退群后删除它(可能)作为经理设置的欢迎语 gwt := group_m.GroupWelcomeText{} if err = gwt.Remove(model.Db, groupId, userId); err != nil { mylogrus.MyLog.Warnf("Can't remove group %s user %d's welcome text.", groupId, userId) } // 退群后删除小闹钟 groupUser := group_m.GroupUser{Model: model, GroupId: groupId, UserId: userId} if err = groupUser.Delete(); err != nil { mylogrus.MyLog.Warnf("Can't remove group %s user %d's option.", groupId, userId) } // 清理相关缓存 group_c.ClearGroupMemberCount(groupId) group_c.RemoveGroupMember(groupId, externalId) return nil } // 退出永久会员的一系列操作 func (s *GroupService) LeaveGroupMember(model *domain.Model, groupId string, userId uint64, externalId string) error { gm := group_m.GroupMember{ GroupId: groupId, UserId: userId, } if err := gm.Remove(model.Db); err != nil { return err } // 退群后删除管理角色 if err := group_m.RemoveGroupRole(model, groupId, userId); err != nil { mylogrus.MyLog.Warnf("Can't remove group %s user %d's role.", groupId, userId) } // 退群后删除它(可能)作为经理设置的欢迎语 gwt := group_m.GroupWelcomeText{} if err := gwt.Remove(model.Db, groupId, userId); err != nil { mylogrus.MyLog.Warnf("Can't remove group %s user %d's welcome text.", groupId, userId) } // 退群后删除小闹钟 groupUser := group_m.GroupUser{Model: model, GroupId: groupId, UserId: userId} if err := groupUser.Delete(); err != nil { mylogrus.MyLog.Warnf("Can't remove group %s user %d's option.", groupId, userId) } // 清理相关缓存 group_c.ClearGroupMemberCount(groupId) group_c.RemoveGroupMember(groupId, externalId) return nil } //进入房间, 返回channelId, err func (s *GroupService) GroupIn(userId uint64, externalId string, groupUuid string, password, imei, ip string, provider group_e.GroupProvider, roomId int64) (string, string, error) { var channelId string var token string var rideId uint64 err := s.svc.Transactional(func() error { //检查群组是否存在, 没有真正的domel,直接service上怼 model := domain.CreateModel(s.svc.CtxAndDb) // 群是否被封禁 banned := group_m.GroupBanned{ImGroupId: groupUuid} if err := banned.Get(model); err != gorm.ErrRecordNotFound { return bizerr.GroupIsBanned } var groupInfo group_m.GroupInfo if err := model.Db.Where(&group_m.GroupInfo{ ImGroupId: groupUuid, }).First(&groupInfo).Error; err != nil { return myerr.WrapErr(err) } if userId != groupInfo.Owner && len(groupInfo.Password) > 0 && password != groupInfo.Password { return bizerr.IncorrectPassword } //检查群组中是否有拉黑名单 //var n int64 //if err := model.Db.Model(&group_m.GroupBlacklist{}).Where(&group_m.GroupBlacklist{ // ImGroupId: groupUuid, // UserId: userId, //}).Count(&n).Error; err != nil { // return nil, nil, myerr.WrapErr(err) //} //if n != 0 { // return nil, nil, bizerr.InBlacklist //} svip, _ := rpc.GetUserSvip(model, userId) if userId != groupInfo.Owner { // 是否超管 isM, err := user_m.IsSuperManager(model, userId) if err != nil { model.Log.Errorf("GroupIn err:%v", err) return myerr.NewSysError("not super manager") } // 不是超管 且 用户是否在群的黑名单中 if !isM && group_m.InGroupBlackList(model, groupUuid, imei, ip, userId) { // 特殊处理的拉黑列表 blackMap := map[string]uint64{"HTGS#a17058241": 3058361} if bUid, ok := blackMap[groupUuid]; ok && bUid == userId { return bizerr.InBlacklist } if svip.SvipLevel < 6 { // svip6暂时不判断GroupBlackList return bizerr.InBlacklist } } } //是否被踢出 if i, err := redisCli.GetRedis().Exists(context.Background(), redis_key.GetGroupKickGroupUuidUserId(groupUuid, userId)).Result(); err != nil { return myerr.WrapErr(err) } else if i == 0 { user, err := user_m.GetUser(model, userId) if err != nil { return err } if provider == group_e.GroupProvider_TRTC { channelId = groupInfo.ChannelId token = trtc.CreateGroupTRTCUserSig(userId, config.GetTRTCConfig()) model.Log.Infof("enter trtc group userId:%v, groupId:%v", userId, groupUuid) } else { channelId, token, err = agora.CreateGroupAgora(groupInfo.ChannelId, uint32(userId)) } if err != nil { return err } else { //加入房间 group_m.RoomLivingIn(model, groupUuid, userId, externalId, false) groupUser, err := group_m.GetGroupUserOrInit(model, groupUuid, userId) if err != nil { model.Log.Errorf("GroupIn GetGroupUserOrInit err:%v, groupId:%v, userId:%v", err, groupUuid, userId) } if groupUser != nil { if err := groupUser.SetRoomInTime().Persistent(); err != nil { model.Log.Errorf("GroupIn groupUser Persistent err:%v, groupId:%v, userId:%v", err, groupUuid, userId) } } // 发送进群TIM信令。fixme: 应该发在event处理 isVip, err := user.CheckVip() if err != nil { return err } nobleLevel, err := noble_m.GetNobleLevel(mysql.Db, userId) if err != nil { return err } type UserParam struct { Nick string `json:"nick"` UserAvatar string `json:"userAvatar"` IsVip bool `json:"isVip"` RideId uint64 `json:"rideId"` RideUrl string `json:"rideUrl"` RideEffectUrl string `json:"rideEffectUrl"` RidSenderAvatar string `json:"ridSenderAvatar"` RidReceiverAvatar string `json:"ridReceiverAvatar"` NobleLevel uint16 `json:"nobleLevel"` SvipLevel int `json:"svipLevel"` Svip rpc.CvSvip `json:"svip"` CpLevel int `json:"cpLevel"` // cp等级 CpUserAvatar string `json:"cpUserAvatar"` // cp用户头像 EntryEffectType int `json:"entryEffectType"` // 进场特效类型 1: CP 2:神秘人 3:贵族 4:vip } up := user_m.UserProperty{} rides, err := up.BatchGet(mysql.Db, []uint64{userId}) if err != nil { return err } //rp := res_m.ResProperty{} //properties, err := rp.GetAll(mysql.Db) properties, err := property_cv.GetExtendedProperty(mysql.Db) if err != nil { return err } var cpLevel int var cpUserAvatar string var cpEntryEffect bool if cp, _ := rpc.GetUserCpEntryEffect(model, userId); cp != nil { cpLevel = cp.CpLevel cpUserAvatar = cp.CpUserAvatar cpEntryEffect = cp.CpEntryEffect } r := UserParam{ Nick: user.Nick, UserAvatar: user.Avatar, IsVip: isVip, RideId: rides[userId], RideUrl: properties[rides[userId]].PicUrl, RideEffectUrl: properties[rides[userId]].EffectUrl, // todo replace ui svga RidSenderAvatar: properties[rides[userId]].SenderAvatar, RidReceiverAvatar: properties[rides[userId]].ReceiverAvatar, NobleLevel: nobleLevel, Svip: rpc.CopySimpleSvip(svip), CpLevel: cpLevel, CpUserAvatar: cpUserAvatar, } rideId = r.RideId // 进场特效类型 var entryEffectType int // 进场特效类型 1: CP 2:神秘人 3:贵族 4:vip ,顺序从小到大 if r.IsVip { entryEffectType = 4 } if r.NobleLevel > 0 { entryEffectType = 3 } for _, v := range r.Svip.Privileges { if len(v.MysteryCode) > 0 { entryEffectType = 2 } } if cpEntryEffect { entryEffectType = 1 } r.EntryEffectType = entryEffectType buf, err := json.Marshal(r) if err == nil { //发送腾讯云IM系统消息 signal_s.SendSignalMsg(model, groupUuid, group_m.GroupSystemMsg{ MsgId: group_e.GroupInSignal, Source: user.ExternalId, Content: string(buf), }, true) } isMember, _ := group_m.IsGroupMember(model.Db, groupUuid, userId) return group_ev.PublishGroupIn(model, &group_ev.GroupInEvent{ GroupId: groupUuid, UserId: userId, ExternalId: user.ExternalId, Nick: user.Nick, Avatar: user.Avatar, IsMember: isMember, IsVip: isVip, NobleLevel: nobleLevel, }) } } else { return bizerr.GroupInKick } }) if err != nil { return "", "", err } else { //go dealActDataAfterEnterRoom(s.svc.MyContext, userId, rideId, roomId) return channelId, token, nil } } func (s *GroupService) JoinGroup(userId uint64, externalId string, groupId string) error { model := domain.CreateModel(s.svc.CtxAndDb) model.Log.Infof("Async: user %d(%s) is joining group %s", userId, externalId, groupId) for i := 1; i < 3; i++ { info, err, errCode := tencentyun.AddGroup(model, groupId, []string{externalId}) if err == nil && info != nil { for i, r := range info { if i == externalId { if r == group_e.ADD_GROUP_DONE || r == group_e.ADD_GROUP_DUPLICATE { // 加群成功后,马上发送通知给客户端,让他们拉取历史消息 err = rpc.SendJoinGroup(userId, externalId, groupId) model.Log.Infof("joinGroup: SendJoinGroup err = %v", err) } if r == group_e.ADD_GROUP_DONE { group_c.ClearGroupMemberCount(groupId) tim_c.AddGroupMember(model, groupId, externalId) } } return nil } } else if errCode == 10014 || errCode == 10038 { // 如果群成员达到上限,删除一个(10014 达到群上限;10038 达到APP上限) extId, err := s.RemoveZombie(model, groupId) if err != nil { return err } model.Log.Infof("JoinGroup %s limit reached, errCode = %d, %s kicked", groupId, errCode, extId) } } return fmt.Errorf("failed 3 times") } // 清除TIM群的僵尸 func (s *GroupService) RemoveZombie(model *domain.Model, groupId string) (string, error) { rows, err := group_m.GetMembers(model.Db, groupId) if err != nil { return "", err } userIds := make([]uint64, 0) for _, i := range rows { userIds = append(userIds, i.UserId) } model.Log.Infof("JoinGroup %s: members: %v", groupId, userIds) roomUids, err := group_m.RoomLivingExistsUserId(groupId) if err != nil { return "", err } model.Log.Infof("JoinGroup %s: roomUids: %v", groupId, roomUids) userIds = append(userIds, roomUids...) userIds = utils.UniqueSliceUInt64(userIds) m, err := user_m.GetUserMapByIds(model, userIds) if err != nil { return "", err } extIdsMap := make(map[string]uint64) for _, i := range m { extIdsMap[i.ExternalId] = i.ID } model.Log.Infof("JoinGroup %s: extIdsMap: %v", groupId, extIdsMap) _, gm, err := tencentyun.GetGroupMemberInfo(model, groupId) if err != nil { return "", err } zombie := "" for _, i := range gm { if _, ok := extIdsMap[i.Member_Account]; !ok { model.Log.Infof("JoinGroup %s: to kick %s", groupId, i.Member_Account) tencentyun.LeaveGroup(groupId, []string{i.Member_Account}) zombie = i.Member_Account break } } return zombie, nil } //离开房间 func (s *GroupService) GroupLeave(userId uint64, externalId string, groupId string) error { model := domain.CreateModelContext(s.svc.MyContext) // check cp麦位置 group_mic_s.NewGroupPowerService(s.svc.MyContext).CheckCpLeaveMic(groupId, userId) _, err := group_m.RoomLivingLeave(model, userId, externalId, groupId) return err } //踢人 func (s *GroupService) GroupKick(groupUuid string, userId uint64, userExternalId string, userNick string, avatar string, beKickUserId uint64, beKickExternalId string, beKickUserNick string, beKickUserAvatar, lang string) error { return s.svc.Transactional(func() error { model := domain.CreateModel(s.svc.CtxAndDb) //木有model层给我,直接server怼了 //被踢的人不能是超级管理人 if flag, err := user_m.IsSuperManager(model, beKickUserId); err != nil { return err } else if flag { return bizerr.OfficialStaffLimit } //超级管理人,无敌状态 if flag, err := user_m.IsSuperManagerV2(model, userId, beKickUserId); err != nil { return err } else if !flag { //判断权限 if err := group_m.MgrPermission(model, groupUuid, userId, beKickUserId); err != nil { return err } //检查是否是贵族 if flag, err := noble_m.CheckNobleLevel(model.Db, beKickUserId, 5); err != nil { return err } else if flag { return myerr.WrapErr(res_m.GetErrByLanguage(model.Db, common.MSG_ID_KICK_NOBLE_5, lang, bizerr.NobleNoKickOverLevel5)) //return bizerr.NobleNoKickLevel5 } } //踢人10分钟 _, err := redisCli.GetRedis().Set(context.Background(), redis_key.GetGroupKickGroupUuidUserId(groupUuid, beKickUserId), strconv.Itoa(int(beKickUserId)), time.Minute*10).Result() if err != nil { return myerr.WrapErr(err) } //删除房间中redis //group_m.RoomLivingLeave(model, groupUuid, beKickUserId) group_m.RoomLivingLeaveByKick(model, groupUuid, beKickUserId, beKickExternalId, userExternalId) // 发信令,让前端重新拉取,接受容错, /* SendSignalMsg(groupUuid, group_m.GroupSystemMsg{ MsgId: group_m2.GroupKickOut, Source: userExternalId, Target: beKickExternalId, })*/ //记录踢人(非事务性, 错误不进行处理) if err := group_m.AddGroupKickRecord(model, groupUuid, userId, beKickUserId); err != nil { model.Log.Errorln(err) } /* //获取用户是否在麦上, 让用户离开麦 micUser, err := group_m.GetMicUserByExternalId(model, beKickExternalId) if err != nil { return nil, nil, err } if micUser != nil { micUser.LeaveByUser(userId, userExternalId) }*/ return group_ev.PublishGroupKickOut(model, &group_ev.GroupKickOutEvent{ GroupId: groupUuid, OperatorExternalId: userExternalId, OperatorName: userNick, OperatorFaceUrl: avatar, MemberExternalId: beKickExternalId, MemberName: beKickUserNick, MemberAvatar: beKickUserAvatar, }) }) } // 群管理人清理公屏 func (s *GroupService) GroupClearScreenByMgr(groupId string, userId uint64) error { model := domain.CreateModelContext(s.svc.MyContext) //检查权限 if err := group_m.CheckPermission(model, groupId, userId); err != nil { return err } systemMsg := group_m.GroupSystemMsg{MsgId: group_e.GroupClearScreen, Source: "", Content: ""} signal_s.SendSignalMsg(model, groupId, systemMsg, false) return nil } func dealActDataAfterEnterRoom(myContext *mycontext.MyContext, userId, rideId uint64, roomId int64) { defer utils.CheckGoPanic() if rideId == 1261 { // 处理活动数据 go rpc.AddActPoint(domain.CreateModelContext(myContext), userId, 5, roomId) } } // 创建房间,慎用 func (s *GroupService) CreateGroupMulByUid(userId uint64, num int, micNumType group_e.GroupMicNumType) error { model := domain.CreateModel(s.svc.CtxAndDb) if lock := redisCli.Lock(group_k.GetGroupLockKey(userId), time.Second*5); !lock { return bizerr.ReqTooFrequent } user, err := user_m.GetUser(model, userId) if err != nil { model.Log.Errorf("CreateGroupMulByUid err:%v", err) return err } // 强行修正为英语,以保证建群成功 user.Language = utils.DEFAULT_LANG // 群组的头像默认为用户头像,群组的名称默认为用户昵称,群简介默认为“s%的群组”,群公告默认为“欢迎来到Hilo” name := "" text := res_m.ResMultiText{MsgId: msg_e.MSG_ID_GROUP_NAME, Language: user.Language} if text.Get(model.Db) == nil { name = strings.ReplaceAll(text.Content, "{nick}", user.Nick) } introduction := "" text2 := res_m.ResMultiText{MsgId: msg_e.MSG_ID_GROUP_INTRODUCTION, Language: user.Language} if text2.Get(model.Db) == nil { introduction = strings.ReplaceAll(text2.Content, "{nick}", user.Nick) } notification := "" text3 := res_m.ResMultiText{MsgId: msg_e.MSG_ID_GROUP_NOTIFICATION, Language: user.Language} if text3.Get(model.Db) == nil { notification = strings.ReplaceAll(text3.Content, "{nick}", user.Nick) } faceUrl := user.Avatar for idx := 0; idx < num; idx++ { code := group_m.GenerateGroupCode(group_e.GROUP_DEFAULT_CODE_LENGTH) i := 0 for ; i < group_e.CREATE_GROUP_MAX_ATTEMPT; i++ { success, err := res_m.CheckCode(model, code) if err != nil || !success { break } r, err := group_m.FindGroupByCode(model, code) if err == nil && r == nil { break } code = group_m.GenerateGroupCode(group_e.GROUP_DEFAULT_CODE_LENGTH) } if i >= group_e.CREATE_GROUP_MAX_ATTEMPT { return myerr.NewSysError("Failed in generating groupId") } groupId := group_e.OverseaGroupNamePrefix + code groupId, err = tencentyun.CreateGroup(name, groupId) if err != nil { model.Log.Errorf("CreateGroupMulByUid err:%v", err) return err } channelId := utils.GetUUID() _, _, err = agora.CreateGroupAgora(channelId, uint32(userId)) if err != nil { // 回滚,删除刚刚建立的TX群组 tencentyun.DestroyGroup(groupId, false) model.Log.Errorf("CreateGroupMulByUid err:%v", err) return err } roomType := group_e.OverseaRoom g := group_m.GroupInfo{ ImGroupId: groupId, TxGroupId: groupId, Type: uint16(roomType), Code: code, OriginCode: code, Owner: userId, Name: name, Introduction: introduction, Notification: notification, FaceUrl: faceUrl, Country: user.Country, ChannelId: channelId, MicOn: true, LoadHistory: false, MicNumType: micNumType, TouristMic: 1, TouristSendMsg: 1, TouristSendPic: 1, IsGameRoom: 1, } if err := s.CreateGroup(userId, &g); err != nil { // 回滚,删除刚刚建立的TX群组 tencentyun.DestroyGroup(groupId, false) model.Log.Errorf("CreateGroupMulByUid err:%v", err) return err } gm := group_m.GroupMember{ GroupId: groupId, UserId: userId, } if err = gm.Create(model.Db); err != nil { model.Log.Warnf("Failed to set GroupMember +%v", gm) } time.Sleep(time.Millisecond * 30) } return nil }