package group_m import ( "context" "encoding/json" "git.hilo.cn/hilo-common/domain" "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/sdk/tencentyun" "github.com/bluele/gcache" redis2 "github.com/go-redis/redis/v8" "gorm.io/gorm" "hilo-group/_const/enum/group_e" "hilo-group/_const/redis_key" "hilo-group/common" "hilo-group/domain/model/noble_m" "hilo-group/domain/model/res_m" "hilo-group/domain/model/user_m" "hilo-group/myerr" "hilo-group/myerr/bizerr" "runtime/debug" "strconv" "strings" "time" ) /** 1: 主要关注点:分布式事务一致性,高效。 2:存储介质:redis,理由:不重要,高频数据请求,持久化过程注意点:利用redis单次操作作为原子性,利用原子性的操作结果(判断更新数量,成败)来保证整个麦位的原子性操作。 3:分布式事务一致性(声网,服务器)。以服务端数据为准。客户端保持同声网数据一致。5分钟C端请求服务的信息,进行同步,纠正声网数据不一致问题。 4: 存储结构: ps: micUser持久话动作,没有归属于repo,因为需要处理并发下一致性问题。因此,在方法中,进行持久化, */ /** * redis记录。 micHasGroup -> set, 定于:可能麦上有人的群组(确保:麦上有人一定在该集合中), 增加时机:加入麦位之前, 减少时机:利用lua表达式控制顺序,没有人在麦上,则清理数据。(lua防止并发,麦上有人后,被移除出数据) micNumType -> groupUuid string, 有生命周期,缓存不存在的时候,就找数据库,更新:先删除缓存,再修改数据库。 核心:mic -> groupUuid Map(key:i, value:Mic) 为了减少redis的数据,当mic的值为默认值时,执行的HDel micUser -> groupUuid Map(key:i value:MicUser) userInMic -> map(key:extendId, value:MicUser) PS:userInMic同micUser 具有一致性。(原子操作)作用:用于查询某个用户在哪个麦上 */ //麦位数量类型 type MicNumType struct { model *domain.Model GroupUuid string T group_e.GroupMicNumType } const MaxMicNum = 20 //清理缓存 func (micNumType *MicNumType) ClearCache() { micNumType.model.Log.Infof("group_m MicNumType ClearCache groupUuid:%v", micNumType.GroupUuid) _, err := redisCli.GetRedis().Del(context.Background(), redis_key.GetPrefixGroupMicNumType(micNumType.GroupUuid)).Result() if err != nil { micNumType.model.Log.Errorf("group_m MicNumType ClearCache groupUuid:%v err:%v", micNumType.GroupUuid, err) } } //同步到缓存,用set,强制修改值 func (micNumType *MicNumType) AddCache() error { micNumType.model.Log.Infof("group_m MicNumType AddCache groupUuid:%v, groupMicNumType:%v", micNumType.GroupUuid, micNumType.T) _, err := redisCli.GetRedis().Set(context.Background(), redis_key.GetPrefixGroupMicNumType(micNumType.GroupUuid), strconv.Itoa(int(micNumType.T)), micExpire).Result() if err != nil { micNumType.model.Log.Errorf("MicNumType add redis set err:%v", err) return err } return nil } //获取数据,先从缓存中获取,没有则从数据库中获取,如果不存在,则同步到redis, 必须用setnx,避免同修改的时候,并发让缓存错误。 func GetMicNumType(model *domain.Model, groupUuid string) (group_e.GroupMicNumType, error) { t, err := redisCli.GetRedis().Get(context.Background(), redis_key.GetPrefixGroupMicNumType(groupUuid)).Result() if err != nil { if err != redis2.Nil { return 0, myerr.WrapErr(err) } else { //获取数据库的值,同步到缓存 groupInfo, _ := GetGroupInfo(model, groupUuid) if groupInfo == nil { return 0, bizerr.GroupNotFound } flag, err := redisCli.GetRedis().SetNX(context.Background(), redis_key.GetPrefixGroupMicNumType(groupUuid), strconv.Itoa(int(groupInfo.MicNumType)), micExpire).Result() model.Log.Infof("GetMicNumType redis SetNX groupUuid:%v, flag:%v, err:%v", groupUuid, flag, err) return groupInfo.MicNumType, nil } } else { groupMicNumType, err := strconv.ParseUint(t, 10, 8) if err != nil { return 0, nil } return group_e.GroupMicNumType(groupMicNumType), nil } } // 3天 const expireMinute = 60 * 60 * 24 * 3 const micExpire = expireMinute * time.Second //麦位基本信息。 type Mic struct { model *domain.Model //群组uuid GroupUuid string //麦位 I int //锁,是否有锁 true:锁了, false:没锁 Lock bool //麦位静音 MicForbid bool } //发言,注意(发言是在麦位上) type MicUser struct { model *domain.Model //群组uuid GroupUuid string //麦位 I int //麦中的人 ExternalId string //用户id UserId uint64 //用户cpId CpUserId uint64 //静音 true:静音,false:没有静音 Forbid bool //上麦的的时间戳 Timestamp int64 } //记录麦位上有谁。用于 type UserInMic struct { //群组uuid GroupUuid string //麦位 I int //userId UserId uint64 } //60*5 //const micInExpireScript = "local flag = redis.call('expire', '{prefixGroupMicUser}', '300') if flag == 1 then return redis.call('expire', '{prefixGroupUserMic}', '300') end return 0 " //先让user在那个麦上续时间,再让麦上有谁续时间? 目前日志统计中,并没有发现只执行一半的,全部执行成功。 const micInExpireScript = "local flag = redis.call('expire', '{prefixGroupMicUser}', '{micExpire}') if flag == 1 then return redis.call('expire', '{prefixGroupUserMic}', '{micExpire}') end return 0 " func UpdateMicExpire(model *domain.Model, groupUuid string, externalId string) error { micUser, err := GetMicUserByExternalId(model, externalId) if err != nil { return err } // if micUser != nil { if micUser.GroupUuid == groupUuid { //使用lua表达式 script := strings.Replace( strings.Replace( strings.Replace(micInExpireScript, "{micExpire}", strconv.Itoa(expireMinute), -1), "{prefixGroupMicUser}", redis_key.GetPrefixGroupMicUser(micUser.GroupUuid, micUser.I), -1), "{prefixGroupUserMic}", redis_key.GetPrefixGroupUserInMic(externalId), -1) //redisCli.GetRedis().ScriptFlush(context.Background()) n, err := redis2.NewScript(script).Run(context.Background(), redisCli.GetRedis(), []string{}).Result() if n == 0 { mylogrus.MyLog.Infof("UpdateMicExpire script result:n=%v", n) } if err != nil { return myerr.WrapErr(err) } //存在才更新。 /* if flag, err := redisCli.GetRedis().Expire(context.Background(), redis.GetPrefixGroupUserInMic(externalId), micExpire).Result(); err == nil { if flag { redisCli.GetRedis().Expire(context.Background(), redis.GetPrefixGroupMicUser(micUser.GroupUuid, micUser.I), micExpire) //基本一致,接受容错,如果担心错误,可以反向验证。目前不考虑。因为这个接口频率太高了。 } }*/ } } return nil } //12个小时 //2022-07-20 升级,判断是自己是否已经在别的麦上了 //const micInScript = "local flag = redis.call('SET', '{prefixGroupMicUser}', '{micUserStr}', 'ex', '{micExpire}', 'nx') if flag ~= false then redis.call('SETEX', '{prefixGroupUserMic}', '{micExpire}', '{groupUserStr}') return 1 end return 2 " const micInScript = "local flag = redis.call('EXISTS', '{prefixGroupUserMic}') if flag == 0 then local flag1 = redis.call('SET', '{prefixGroupMicUser}', '{micUserStr}', 'ex', '{micExpire}', 'nx') if flag1 ~= false then redis.call('SETEX', '{prefixGroupUserMic}', '{micExpire}', '{groupUserStr}') return 1 end return 2 end return 3" const micUpdateScript = "local flag = redis.call('EXISTS', '{prefixGroupUserMic}') if flag == 1 then local flag1 = redis.call('SET', '{prefixGroupMicUser}', '{micUserStr}', 'ex', '{micExpire}', 'xx') if flag1 ~= false then redis.call('SETEX', '{prefixGroupUserMic}', '{micExpire}', '{groupUserStr}') return 1 end return 2 end return 3" // //上麦(自己), //规则:1:加锁了不能上麦 2:麦上有人,不能上麦 //cpUserId如果有 func (mic *Mic) In(userId uint64, externalId string) error { // 群是否被封禁, 呃,,,呃,,,呃,,, banned := GroupBanned{ImGroupId: mic.GroupUuid} if err := banned.Get(mic.model); err != gorm.ErrRecordNotFound { return bizerr.GroupIsBanned } //判断群组设置上的麦 是否被关闭 groupInfo, err := GetGroupInfo(mic.model, mic.GroupUuid) if err != nil { return err } if groupInfo.MicOn == false { return bizerr.GroupInfoMicClosed } //麦被加锁了 if mic.Lock { return bizerr.GroupMicLock } //设置值到redis micUserStr, err := micUserToStr(MicUser{ GroupUuid: mic.GroupUuid, I: mic.I, ExternalId: externalId, UserId: userId, Forbid: false, Timestamp: time.Now().Unix(), }) if err != nil { return err } //加入到麦上可能有人的集合中。 groupMicHasIn(mic.model, mic.GroupUuid, userId) //lua上麦,让麦上的人同人在麦上,保持原子性操作 //清理脚本,不然redis占用内存越来越高,并且不会释放 groupUserStr, err := userInMicToStr(mic.GroupUuid, mic.I, userId) if err != nil { return err } script := strings.Replace(strings.Replace( strings.Replace( strings.Replace( strings.Replace(micInScript, "{micExpire}", strconv.Itoa(expireMinute), -1), "{prefixGroupMicUser}", redis_key.GetPrefixGroupMicUser(mic.GroupUuid, mic.I), -1), "{micUserStr}", micUserStr, -1), "{prefixGroupUserMic}", redis_key.GetPrefixGroupUserInMic(externalId), -1), "{groupUserStr}", groupUserStr, -1) r, err := redis2.NewScript(script).Run(context.Background(), redisCli.GetRedis(), []string{}).Result() mic.model.Log.Infof("micUser In micInScript:%v, result:%v", script, r) d := r.(int64) if err != nil { return myerr.WrapErr(err) } if d == int64(2) { return bizerr.GroupMicHasUser } if d == int64(3) { return bizerr.GroupMicUserHasIn } //离开动作已结束,增加到队列中 MicChangeRPush(mic.model, mic.GroupUuid, mic.I) // 发信令,让前端重新拉取,接受容错, sendSignalMsg(mic.model, mic.GroupUuid, GroupSystemMsg{ MsgId: group_e.GroupMicInSignal, Source: externalId, }, false) return nil } //上麦(自己), //规则:1:加锁了不能上麦 2:麦上有人,不能上麦 //cpUserId如果有 func (mic *Mic) Update(userId uint64, externalId string, cpUserId uint64, forbid bool) (err error) { defer func() { if err != nil { mic.model.Log.Errorf("MicUpdate fail,userId:%v,cpUserId:%v,err:%v", userId, cpUserId, err) } }() // 群是否被封禁, 呃,,,呃,,,呃,,, banned := GroupBanned{ImGroupId: mic.GroupUuid} if err := banned.Get(mic.model); err != gorm.ErrRecordNotFound { return bizerr.GroupIsBanned } //判断群组设置上的麦 是否被关闭 groupInfo, err := GetGroupInfo(mic.model, mic.GroupUuid) if err != nil { return err } if groupInfo.MicOn == false { return bizerr.GroupInfoMicClosed } //麦被加锁了 if mic.Lock { return bizerr.GroupMicLock } //设置值到redis micUserStr, err := micUserToStr(MicUser{ GroupUuid: mic.GroupUuid, I: mic.I, ExternalId: externalId, UserId: userId, CpUserId: cpUserId, Forbid: forbid, Timestamp: time.Now().Unix(), }) if err != nil { return err } //加入到麦上可能有人的集合中。 groupMicHasIn(mic.model, mic.GroupUuid, userId) //lua上麦,让麦上的人同人在麦上,保持原子性操作 //清理脚本,不然redis占用内存越来越高,并且不会释放 groupUserStr, err := userInMicToStr(mic.GroupUuid, mic.I, userId) if err != nil { return err } script := strings.Replace(strings.Replace( strings.Replace( strings.Replace( strings.Replace(micUpdateScript, "{micExpire}", strconv.Itoa(expireMinute), -1), "{prefixGroupMicUser}", redis_key.GetPrefixGroupMicUser(mic.GroupUuid, mic.I), -1), "{micUserStr}", micUserStr, -1), "{prefixGroupUserMic}", redis_key.GetPrefixGroupUserInMic(externalId), -1), "{groupUserStr}", groupUserStr, -1) r, err := redis2.NewScript(script).Run(context.Background(), redisCli.GetRedis(), []string{}).Result() mic.model.Log.Infof("micUser In micInScript:%v, result:%v", script, r) d := r.(int64) if err != nil { return myerr.WrapErr(err) } if d == int64(2) { return bizerr.GroupMicHasUser } if d == int64(3) { return bizerr.GroupMicUserHasIn } //离开动作已结束,增加到队列中 MicChangeRPush(mic.model, mic.GroupUuid, mic.I) // 发信令,让前端重新拉取,接受容错, sendSignalMsg(mic.model, mic.GroupUuid, GroupSystemMsg{ MsgId: group_e.GroupMicInSignal, Source: externalId, }, false) return nil } const micLeaveScript = "local flag = redis.call('DEL', '{prefixGroupMicUser}') if flag == 1 then return redis.call('Del', '{prefixGroupUserMic}') end return 2 " //离开麦(自己) //规则:1:自己的麦 func (micUser *MicUser) leave(operateUserId uint64, operateExternalId string) error { // if micUser == nil { return bizerr.GroupMicNoUser } // externalId := micUser.ExternalId // 超管处理 if flag, err := user_m.IsSuperManagerV2(micUser.model, operateUserId, micUser.UserId); err != nil { return err } else if !flag { if micUser.ExternalId != operateExternalId { //检查权限,管理人权限, 不过不拥有管理人权限,则抛出错误 if err := MgrPermission(micUser.model, micUser.GroupUuid, operateUserId, micUser.UserId); err != nil { return err } } } //设置值到redis //redisCli.GetRedis().ScriptFlush(context.Background()) script := strings.Replace(strings.Replace( micLeaveScript, "{prefixGroupMicUser}", redis_key.GetPrefixGroupMicUser(micUser.GroupUuid, micUser.I), -1), "{prefixGroupUserMic}", redis_key.GetPrefixGroupUserInMic(externalId), -1) r, err := redis2.NewScript(script).Run(context.Background(), redisCli.GetRedis(), []string{}).Result() d := r.(int64) micUser.model.Log.Infof("micUser leave micLeaveScript:%v, result:%v", script, d) if err != nil { return err } if d == 2 { //不在该麦位上,可能被redis,过期移除了。 //return bizerr.GroupMicErr } //离开动作已结束,增加到队列中 MicChangeRPush(micUser.model, micUser.GroupUuid, micUser.I) return nil } //邀请的翻译 var inviteMicMsgTranslate = map[string]string{} //邀请上麦 func InviteMicIn(model *domain.Model, groupUuid string, operateUserId uint64, beInvitedExternalId string) error { model.Log.Infof("mic InviteMicIn operateUserId:%d, beInvitedExternalId:%s", operateUserId, beInvitedExternalId) if err := CheckPermission(model, groupUuid, operateUserId); err != nil { return err } user, err := user_m.GetUser(model, operateUserId) if err != nil { return err } beInvitedUser, err := user_m.GetUserByExtId(model, beInvitedExternalId) if err != nil { return err } context, ok := inviteMicMsgTranslate[beInvitedUser.Language] if ok == false { context = inviteMicMsgTranslate["en"] } context = strings.Replace(context, "{nick}", user.Nick, -1) sendSignalMsg(model, groupUuid, GroupSystemMsg{ MsgId: group_e.GroupInviteMicInSignal, Source: user.ExternalId, Target: beInvitedExternalId, Content: context, }, false) return nil } // 用户操作下麦(自己或者管理人)fixme: 参数往往是错的 func (micUser *MicUser) LeaveByUser(operateUserId uint64, operateExternalId string) error { micUser.model.Log.Infof("mic LeaveByUser userId:%d", operateUserId) if err := micUser.leave(operateUserId, operateExternalId); err != nil { return err } // 发信令,让前端重新拉取,接受容错, sendSignalMsg(micUser.model, micUser.GroupUuid, GroupSystemMsg{ MsgId: group_e.GroupMicOutSignal, Source: operateExternalId, }, false) return nil } /*func (micUser *MicUser) LeaveBySocket(operateUserId uint64, operateExternalId string) error { micUser.model.Log.Infof("mic leaveBySocket userId:%d", operateUserId) if err := micUser.leave(operateUserId, operateExternalId); err != nil { return err } // 发信令,让前端重新拉取,接受容错, go func(groupId, externalId string) { defer func() { if r := recover(); r != nil { //打印错误堆栈信息 mylogrus.MyLog.Errorf("SendCustomMsg: LeaveBySocket SYSTEM ACTION PANIC: %v, stack: %v", r, string(debug.Stack())) } }() sendSignalMsg(groupId, GroupSystemMsg{ MsgId: group_e.GroupSocketMicOutSignal, Source: externalId, }) }(micUser.GroupUuid, operateExternalId) return nil }*/ //锁。(管理人加锁) //规则:1:麦上有人,不能锁 2:必须是管理人以上岗位 //Multi EXEC, 保证了加锁的时候,没有人 func (mic *Mic) MgrLock(userId uint64, externalId string) error { //获取麦上的人 micUser, err := GetMicUser(mic.model, mic.GroupUuid, mic.I) if err != nil { return err } // if micUser != nil { return bizerr.GroupMicHasUser } //判断权限 if err := CheckPermission(mic.model, mic.GroupUuid, userId); err != nil { return err } mic.Lock = true //设置值到redis micStr, err := micToStr(*mic) if err != nil { return err } if _, err = redisCli.GetRedis().HSet(context.Background(), redis_key.GetPrefixGroupMic(mic.GroupUuid), strconv.Itoa(mic.I), micStr).Result(); err != nil { return myerr.WrapErr(err) } MicChangeRPush(mic.model, mic.GroupUuid, mic.I) // 发信令,让前端重新拉取,接受容错, sendSignalMsg(mic.model, mic.GroupUuid, GroupSystemMsg{ MsgId: group_e.GroupMicLockSignal, Source: externalId, }, false) return nil } //去除锁。(管理人解锁) //规则,必须是管理人才能解锁 func (mic *Mic) MgrUnLock(userId uint64, externalId string) error { //判断权限 if err := CheckPermission(mic.model, mic.GroupUuid, userId); err != nil { return err } //判断权限 mic.Lock = false //移除 if _, err := redisCli.GetRedis().HDel(context.Background(), redis_key.GetPrefixGroupMic(mic.GroupUuid), strconv.Itoa(mic.I)).Result(); err != nil { return myerr.WrapErr(err) } // 发信令,让前端重新拉取,接受容错, sendSignalMsg(mic.model, mic.GroupUuid, GroupSystemMsg{ MsgId: group_e.GroupMicUnLockSignal, Source: externalId, }, false) MicChangeRPush(mic.model, mic.GroupUuid, mic.I) return nil //设置值到redis /* micStr, err := micToStr(*mic) if err != nil { return err } if _, err = redisCli.GetRedis().HSet(context.Background(), redis.GetPrefixGroupMic(mic.GroupUuid), strconv.Itoa(mic.I), micStr).Result(); err != nil { return myerr.WrapErr(err) } return nil*/ } // 麦位静音 func (mic *Mic) MgrMute(userId uint64, externalId string) error { //判断权限 if err := CheckPermission(mic.model, mic.GroupUuid, userId); err != nil { return err } mic.MicForbid = true //设置值到redis micStr, err := micToStr(*mic) if err != nil { return err } if _, err = redisCli.GetRedis().HSet(context.Background(), redis_key.GetPrefixGroupMic(mic.GroupUuid), strconv.Itoa(mic.I), micStr).Result(); err != nil { return myerr.WrapErr(err) } MicChangeRPush(mic.model, mic.GroupUuid, mic.I) // 发信令,让前端重新拉取,接受容错, sendSignalMsg(mic.model, mic.GroupUuid, GroupSystemMsg{ MsgId: group_e.GroupMicLockSignal, Source: externalId, }, false) return nil } // 麦位解除静音 func (mic *Mic) MgrUnMute(userId uint64, externalId string) error { //判断权限 if err := CheckPermission(mic.model, mic.GroupUuid, userId); err != nil { return err } // 移除 if _, err := redisCli.GetRedis().HDel(context.Background(), redis_key.GetPrefixGroupMic(mic.GroupUuid), strconv.Itoa(mic.I)).Result(); err != nil { return myerr.WrapErr(err) } // 发信令,让前端重新拉取,接受容错, sendSignalMsg(mic.model, mic.GroupUuid, GroupSystemMsg{ MsgId: group_e.GroupMicUnLockSignal, Source: externalId, }, false) MicChangeRPush(mic.model, mic.GroupUuid, mic.I) return nil //设置值到redis /* micStr, err := micToStr(*mic) if err != nil { return err } if _, err = redisCli.GetRedis().HSet(context.Background(), redis.GetPrefixGroupMic(mic.GroupUuid), strconv.Itoa(mic.I), micStr).Result(); err != nil { return myerr.WrapErr(err) } return nil*/ } //开麦, 管理人 同 自己能开麦 //规则:1:自己开麦 2:管理人开麦 func (micUser *MicUser) SpeechOpen(userId uint64, externalId string) error { if micUser == nil { return bizerr.GroupMicNoUser } else { if micUser.ExternalId == externalId { micUser.Forbid = false } else { //检查是不是管理人 if err := CheckPermission(micUser.model, micUser.GroupUuid, userId); err != nil { return err } micUser.Forbid = false } } //设置值到redis micUserStr, err := micUserToStr(*micUser) if err != nil { return err } if _, err = redisCli.GetRedis().Set(context.Background(), redis_key.GetPrefixGroupMicUser(micUser.GroupUuid, micUser.I), micUserStr, micExpire).Result(); err != nil { return myerr.WrapErr(err) } else { redisCli.GetRedis().Expire(context.Background(), redis_key.GetPrefixGroupUserInMic(externalId), micExpire) } MicChangeRPush(micUser.model, micUser.GroupUuid, micUser.I) // 发信令,让前端重新拉取,接受容错, sendSignalMsg(micUser.model, micUser.GroupUuid, GroupSystemMsg{ MsgId: group_e.GroupMicSpeechOpenSignal, Source: externalId, }, false) return nil } //禁麦, 管理人 同 自己能禁麦(特别注意:产品说,无论是否式管理人开启禁麦,自己同管理人都能关闭禁麦) //规则:1:自己禁麦 2:管理人禁麦 func (micUser *MicUser) SpeechClose(userId uint64, externalId, lang string) error { if micUser == nil { return bizerr.GroupMicNoUser } else { //自己 if micUser.ExternalId == externalId { micUser.Forbid = true } else if flag, err := user_m.IsSuperManager(micUser.model, micUser.UserId); err != nil { return err } else if flag { //不能让超级管理人移除 return bizerr.OfficialStaffLimit } else if flag, err := user_m.IsSuperManagerV2(micUser.model, userId, micUser.UserId); err != nil { return err } else if flag { //超级管理人,无敌状态 micUser.Forbid = true } else { //检查是不是管理人 if err := CheckPermission(micUser.model, micUser.GroupUuid, userId); err != nil { return err } //检查是否是贵族 if flag, err := noble_m.CheckNobleLevel(micUser.model.Db, micUser.UserId, 5); err != nil { return err } else if flag { return myerr.WrapErr(res_m.GetErrByLanguage(micUser.model.Db, common.MSG_ID_BAN_MIC_NOBLE_5, lang, bizerr.NobleNoMicSpeechCloseOverLevel5)) //return bizerr.NobleNoMicSpeechCloseLevel5 } micUser.Forbid = true } } //设置值到redis micUserStr, err := micUserToStr(*micUser) if err != nil { return err } if _, err := redisCli.GetRedis().Set(context.Background(), redis_key.GetPrefixGroupMicUser(micUser.GroupUuid, micUser.I), micUserStr, micExpire).Result(); err != nil { return myerr.WrapErr(err) } else { redisCli.GetRedis().Expire(context.Background(), redis_key.GetPrefixGroupUserInMic(externalId), micExpire) } MicChangeRPush(micUser.model, micUser.GroupUuid, micUser.I) // 发信令,让前端重新拉取,接受容错, sendSignalMsg(micUser.model, micUser.GroupUuid, GroupSystemMsg{ MsgId: group_e.GroupMicSpeechCloseSignal, Source: externalId, }, false) return nil } //群发消息 func (micUser *MicUser) ImMass(externalId string) error { if micUser == nil || micUser.ExternalId != externalId { return bizerr.GroupMicNoYou } //检查权限 if err := CheckPermission(micUser.model, micUser.GroupUuid, micUser.UserId); err != nil { return err } return nil } //检查权限,管理人权限, 不过不拥有管理人权限,则抛出错误 func MgrPermission(model *domain.Model, groupUuid string, userId1 uint64, userId2 uint64) error { if flag, err := IsRoleGreater(model, groupUuid, userId1, userId2); err != nil { return err } else { if flag == false { return bizerr.GroupMicNoPermission } } return nil } //检查权限 func CheckPermission(model *domain.Model, groupUuid string, userId uint64) error { role, err := GetRoleInGroup(model, userId, groupUuid) if err != nil { return err } if role == group_e.GROUP_VISITOR || role == group_e.GROUP_MEMBER { return bizerr.NoPrivileges } return nil } // 入参是内部使用的imGroupId,先进行转化 func sendSignalMsg(model *domain.Model, groupId string, msg GroupSystemMsg, isSyn bool) { groupId, err := ToTxGroupId(model, groupId) if err != nil { return } if isSyn { sendSignalMsgOnly(groupId, msg) } else { go func() { defer func() { if r := recover(); r != nil { //打印错误堆栈信息 mylogrus.MyLog.Errorf("sendSignalMsg SYSTEM ACTION PANIC: %v, stack: %v", r, string(debug.Stack())) } }() sendSignalMsgOnly(groupId, msg) }() } } //发送tengxunyun的系统消息 func sendSignalMsgOnly(groupId string, msg GroupSystemMsg) { buffer, err := json.Marshal(msg) if err == nil { if err = tencentyun.SendSystemMsg(mylogrus.MyLog.WithField("msgId", msg.MsgId), groupId, []string{}, string(buffer)); err != nil { mylogrus.MyLog.Warnf("SendSystemMsg failed for %s, msgId = %d", groupId, msg.MsgId) } } else { mylogrus.MyLog.Errorf("Marshall failure, msgId = %d : %s", msg.MsgId, err.Error()) } } //清理所有麦上的人,强制清理,没有抛出错误,用户接到封禁信令之后,退出麦(必须,用于容错redis清理错误),退出房间 func ClearMic(groupId string) { //清理10个,麦位从5个变成10 20211025, 改成20 20210628 for i := 1; i <= MaxMicNum; i++ { // micUserStr, err := redisCli.GetRedis().Get(context.Background(), redis_key.GetPrefixGroupMicUser(groupId, i)).Result() if err != nil { if err == redis2.Nil { mylogrus.MyLog.Infof("clearMic noUser groupId:%v, i:%v", groupId, i) } else { mylogrus.MyLog.Errorf("clearMic err groupId:%v, i:%v, err:%v", groupId, i, err) continue } } else { var micUser MicUser if err = json.Unmarshal([]byte(micUserStr), &micUser); err != nil { mylogrus.MyLog.Errorf("clearMic groupId:%v, i:%v, err:%v", groupId, i, err) continue } //删除 n, err := redisCli.GetRedis().Del(context.Background(), redis_key.GetPrefixGroupMicUser(groupId, i), redis_key.GetPrefixGroupUserInMic(micUser.ExternalId)).Result() mylogrus.MyLog.Infof("clearMic del groupId:%v, i:%v, result:%v, err:%v", groupId, i, n, err) //增加到队列中 MicEmptyRPush(domain.CreateModelNil(), groupId, i) } } } //检查改群组是否麦上有人. true:存在。 func CheckGroupMicHasUser(groupId string) (bool, error) { //麦位从5个变成10, 20211025 keys := make([]string, 0, 10) for i := 1; i <= MaxMicNum; i++ { keys = append(keys, redis_key.GetPrefixGroupMicUser(groupId, i)) /* n, err := redisCli.GetRedis().Exists(context.Background(), redis.GetPrefixGroupMicUser(groupId, i)).Result() if err != nil { return false, myerr.WrapErr(err) } if n > 0 { return true, nil }*/ } // if n, err := redisCli.GetRedis().Exists(context.Background(), keys...).Result(); err != nil { return false, myerr.WrapErr(err) } else { if n > 0 { return true, nil } else { return false, nil } } } //麦上进入了人,不是核心业务,为了辅助过滤。 func groupMicHasIn(model *domain.Model, groupId string, userId mysql.ID) { model.Log.Infof("groupMicHasIn groupId:%v", groupId) if n, err := redisCli.GetRedis().SAdd(context.Background(), redis_key.GetPrefixGroupMicHasIn(), groupId).Result(); err != nil { model.Log.Errorf("groupMicHasIn groupId:%v err:%v", groupId, err) } else { println(n) } // 下面的是只写,不查的。 todo 直接干掉??? // 只是知道用户在哪个时间点上了哪个群的麦,但是不知道上了哪个麦位置, 而且micUser已经有统计这些信息了 //if _, err := redisCli.GetRedis().ZAdd(context.Background(), redis_key.GetPrefixGroupMicHasInUserTime(), &redis2.Z{ // Score: float64(time.Now().Unix()), // Member: getMemberStr(groupId, userId), //}).Result(); err != nil { // model.Log.Errorf("groupMicHasIn redis:GetPrefixGroupMicHasInTime groupId:%v err:%v", groupId, err) //} } const micHasInScript = "local flag = redis.call('EXISTS', '{key1}', '{key2}', '{key3}', '{key4}', '{key5}', '{key6}', '{key7}', '{key8}', '{key9}', '{key10}', '{key11}', '{key12}', '{key13}', '{key14}', '{key15}', '{key16}', '{key17}', '{key18}', '{key19}', '{key20}') if flag == 0 then redis.call('Srem', '{key}', '{remKey}') end return flag " //获取麦上有人的群组 func GetMicHasInGroups() ([]string, error) { //清理lua缓存 //redisCli.GetRedis().ScriptFlush(context.Background()) //获取所有可能存在的人 groupUuids, err := redisCli.GetRedis().SMembers(context.Background(), redis_key.GetPrefixGroupMicHasIn()).Result() if err != nil { return nil, myerr.WrapErr(err) } return groupUuids, nil // 不需要下面的逐个麦位的判断了 // resultGroupUuids := make([]string, 0, len(groupUuids)) //循环lua判断是否, 最后的保证,(猜想:真正麦上有人的群没有很多) for n, r := range groupUuids { s := strings.Replace(micHasInScript, "{key}", redis_key.GetPrefixGroupMicHasIn(), -1) s = strings.Replace(s, "{remKey}", r, -1) for i := 1; i <= MaxMicNum; i++ { s = strings.Replace(s, "{key"+strconv.Itoa(i)+"}", redis_key.GetPrefixGroupMicUser(r, i), -1) } r, err := redis2.NewScript(s).Run(context.Background(), redisCli.GetRedis(), []string{}).Result() if err != nil { return nil, myerr.WrapErr(err) } d := r.(int64) if d > 0 { resultGroupUuids = append(resultGroupUuids, groupUuids[n]) } } return resultGroupUuids, nil } type micGroupNumKeyS struct{} // mic位数量缓存 var micGroupNumKey = micGroupNumKeyS{} var micGroupNumCache = gcache.New(10000).LRU().Build() // 获取麦上有人的群组&&麦上的人数 // 带lru缓存,1min func GetMicHasInGroupNum(model *domain.Model) (map[string]int64, error) { // get from cache if data, err := micGroupNumCache.Get(micGroupNumKey); err == nil { //model.Log.Infof("GetMicHasInGroupNum cache hit:%v", data) // 正服才缓存 if config.AppIsRelease() { return data.(map[string]int64), nil } } //清理lua缓存 //redisCli.GetRedis().ScriptFlush(context.Background()) //获取所有可能存在的人 groupUuids, err := redisCli.GetRedis().SMembers(context.Background(), redis_key.GetPrefixGroupMicHasIn()).Result() if err != nil { return nil, myerr.WrapErr(err) } // resultGroupUuids := make(map[string]int64, len(groupUuids)) //循环lua判断是否, 最后的保证,(猜想:真正麦上有人的群没有很多) for n, r := range groupUuids { s := strings.Replace(micHasInScript, "{key}", redis_key.GetPrefixGroupMicHasIn(), -1) s = strings.Replace(s, "{remKey}", r, -1) for i := 1; i <= MaxMicNum; i++ { s = strings.Replace(s, "{key"+strconv.Itoa(i)+"}", redis_key.GetPrefixGroupMicUser(r, i), -1) } //r, err := redis2.NewScript(s).Run(context.Background(), redisCli.GetRedis(), []string{}).Result() sha1, err := model.Redis.ScriptLoad(model, s).Result() if err != nil { return nil, myerr.WrapErr(err) } micNum, err := model.Redis.EvalSha(model, sha1, nil, nil).Int64() if err != nil { return nil, myerr.WrapErr(err) } //d := r.(int64) if micNum > 0 { resultGroupUuids[groupUuids[n]] = micNum } } // cache 1min _ = micGroupNumCache.SetWithExpire(micGroupNumKey, resultGroupUuids, time.Minute*15) //model.Log.Infof("GetMicHasInGroupNum cache miss:%v", resultGroupUuids) return resultGroupUuids, nil } //获取麦上有人的群组&&麦上的数(有时间性,目前是24小时) func GetMicHasInPeriodGroupUser() (map[string][]uint64, error) { //清理超过12小时的 if _, err := redisCli.GetRedis().ZRemRangeByScore(context.Background(), redis_key.GetPrefixGroupMicHasInUserTime(), "0", strconv.FormatUint(uint64(time.Now().Unix()-24*60*60), 10)).Result(); err != nil { return nil, myerr.WrapErr(err) } groupUserIdstrs, err := redisCli.GetRedis().ZRange(context.Background(), redis_key.GetPrefixGroupMicHasInUserTime(), 0, -1).Result() if err != nil { return nil, myerr.WrapErr(err) } result := map[string][]uint64{} for i, _ := range groupUserIdstrs { tempGroupUid, tempUserId := analysisMemberStr(groupUserIdstrs[i]) if _, flag := result[tempGroupUid]; flag { result[tempGroupUid] = append(result[tempGroupUid], tempUserId) } else { result[tempGroupUid] = []uint64{tempUserId} } } return result, nil } //获取麦上有人的群组 func GetMicUserNum(groupUuid string) (uint64, error) { //清理lua缓存 //redisCli.GetRedis().ScriptFlush(context.Background()) //循环lua判断是否 s := strings.Replace(micHasInScript, "{key}", redis_key.GetPrefixGroupMicHasIn(), -1) s = strings.Replace(s, "{remKey}", groupUuid, -1) for i := 1; i <= MaxMicNum; i++ { s = strings.Replace(s, "{key"+strconv.Itoa(i)+"}", redis_key.GetPrefixGroupMicUser(groupUuid, i), -1) } r, err := redis2.NewScript(s).Run(context.Background(), redisCli.GetRedis(), []string{}).Result() if err != nil { return 0, myerr.WrapErr(err) } d := r.(uint64) return d, nil } func GetMicNum(micNumType group_e.GroupMicNumType) int { var micNum int = 0 if micNumType == group_e.OneMicNumType { micNum = 1 } else if micNumType == group_e.TwoMicNumType { micNum = 2 } else if micNumType == group_e.ThreeMicNumType { micNum = 3 } else if micNumType == group_e.FourMicNumType { micNum = 4 } else if micNumType == group_e.FiveMicNumType { micNum = 5 } else if micNumType == group_e.SixMicNumType { micNum = 6 } else if micNumType == group_e.SevenMicNumType { micNum = 7 } else if micNumType == group_e.EightMicNumType { micNum = 8 } else if micNumType == group_e.NineMicNumType { micNum = 9 } else if micNumType == group_e.TenMicNumType { micNum = 10 } else if micNumType == group_e.ElevenMicNumType { micNum = 11 } else if micNumType == group_e.TwelveMicNumType { micNum = 12 } else if micNumType == group_e.ThirteenMicNumType { micNum = 13 } else if micNumType == group_e.FourteenMicNumType { micNum = 14 } else if micNumType == group_e.FifteenMicNumType { micNum = 15 } else if micNumType == group_e.SixteenMicNumType { micNum = 16 } else if micNumType == group_e.SeventeenMicNumType { micNum = 17 } else if micNumType == group_e.EighteenMicNumType { micNum = 18 } else if micNumType == group_e.NineteenMicNumType { micNum = 19 } else if micNumType == group_e.TwentyMicNumType { micNum = 20 } return micNum } func init() { //初始化翻译 { inviteMicMsgTranslate["zh"] = "{nick} 邀请你上麦" inviteMicMsgTranslate["en"] = "{nick} invite you to take mic" inviteMicMsgTranslate["ar"] = "يدعوك٪ {nick} إلى أخذ الميكروفون" inviteMicMsgTranslate["tr"] = "{nick}, sizi mikrofonda konuşmaya davet ediyor" inviteMicMsgTranslate["id"] = "{nick} mengundangmu menggunakan mic" inviteMicMsgTranslate["ru"] = "{nick} пригласить тебя появиться у микрофона." inviteMicMsgTranslate["ko"] = "{nick} 당신에게 마이크 요청합니다" inviteMicMsgTranslate["pt"] = "{nick} convida você para pegar o mic" inviteMicMsgTranslate["th"] = "{nick}เชิญคุณเข้าร่วมไมค์" inviteMicMsgTranslate["ca"] = "{nick} te invito al micrófono" inviteMicMsgTranslate["hi"] = "{nick} ने आपको माइक लेने के लिए आमंत्रित करता है" inviteMicMsgTranslate["vi"] = "{nick} mời bạn lên Micrô" inviteMicMsgTranslate["ur"] = "{nick} کیطرف سے آپ کو مائیک کی دعوت دی گئی ہے" } }