package group_r import ( "encoding/json" "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/sdk/agora" "git.hilo.cn/hilo-common/sdk/aws" "git.hilo.cn/hilo-common/sdk/tencentyun" "git.hilo.cn/hilo-common/utils" "github.com/gin-gonic/gin" uuid "github.com/satori/go.uuid" "github.com/spf13/cast" "gorm.io/gorm" "hilo-group/_const/enum/diamond_e" "hilo-group/_const/enum/group_e" "hilo-group/_const/enum/msg_e" "hilo-group/_const/enum/online_e" "hilo-group/_const/enum/user_e" "hilo-group/_const/redis_key/group_k" "hilo-group/cv/billboard_cv" "hilo-group/cv/gift_cv" "hilo-group/cv/group_cv" "hilo-group/cv/user_cv" "hilo-group/domain/cache/game_c" "hilo-group/domain/cache/group_c" "hilo-group/domain/cache/res_c" "hilo-group/domain/cache/room_c" "hilo-group/domain/cache/tim_c" "hilo-group/domain/cache/user_c" "hilo-group/domain/model/diamond_m" "hilo-group/domain/model/game_m" "hilo-group/domain/model/group_m" "hilo-group/domain/model/noble_m" "hilo-group/domain/model/res_m" "hilo-group/domain/model/tim_m" "hilo-group/domain/model/user_m" "hilo-group/domain/service/group_s" "hilo-group/domain/service/signal_s" "hilo-group/myerr" "hilo-group/myerr/bizerr" "hilo-group/req" "hilo-group/resp" "math" "runtime/debug" "sort" "strconv" "strings" "time" ) type CreateGroupRsp struct { ImGroupId string `json:"imGroupId"` Code string `json:"code"` } // @Tags 群组 // @Summary 建群 // @Accept application/x-www-form-urlencoded // @Param token header string true "token" // @Param nonce header string true "随机数字" // @Param name formData string false "群名" // @Param introduction formData string false "群简介" // @Param notification formData string false "群公告" // @Param faceUrl formData string false "群头像 URL" // @Success 200 {object} CreateGroupRsp // @Router /v1/imGroup/group [post] func CreateGroup(c *gin.Context) (*mycontext.MyContext, error) { myContext := mycontext.CreateMyContext(c.Keys) userId, err := req.GetUserId(c) if err != nil { return myContext, err } if lock := redisCli.Lock(group_k.GetGroupLockKey(userId), time.Second*5); !lock { return myContext, bizerr.ReqTooFrequent } model := domain.CreateModelContext(myContext) user, err := user_m.GetUser(model, userId) if err != nil { return myContext, err } // Kludge: 不支持的语言,强行修正为英语,以保证建群成功 if c, err := res_m.GetCountryByLanguage(model, user.Language); err != nil || c == nil { user.Language = utils.DEFAULT_LANG } // 群组的头像默认为用户头像,群组的名称默认为用户昵称,群简介默认为“s%的群组”,群公告默认为“欢迎来到Hilo” name := c.PostForm("name") if len(name) <= 0 { 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 := c.PostForm("introduction") if len(introduction) <= 0 { text := res_m.ResMultiText{MsgId: msg_e.MSG_ID_GROUP_INTRODUCTION, Language: user.Language} if text.Get(model.Db) == nil { introduction = strings.ReplaceAll(text.Content, "{nick}", user.Nick) } } notification := c.PostForm("notification") if len(notification) <= 0 { text := res_m.ResMultiText{MsgId: msg_e.MSG_ID_GROUP_NOTIFICATION, Language: user.Language} if text.Get(model.Db) == nil { notification = strings.ReplaceAll(text.Content, "{nick}", user.Nick) } } faceUrl := c.PostForm("faceUrl") if len(faceUrl) <= 0 { faceUrl = user.Avatar } myGroups, err := group_m.FindGroupByOwner(model, userId) if err != nil { return nil, err } // Kludge: 对超级号放开建群限制 if !user_m.IsSuperUser(userId) && len(myGroups) >= group_e.GROUP_CREATE_LIMIT { return myContext, bizerr.GroupCreateLimitReached } 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 myContext, myerr.NewSysError("Failed in generating groupId") } groupId := group_e.OverseaGroupNamePrefix + code groupId, err = tencentyun.CreateGroup(name, groupId) if err != nil { return myContext, err } channelId := getUUID() _, _, err = agora.CreateGroupAgora(channelId, uint32(userId)) if err != nil { // 回滚,删除刚刚建立的TX群组 tencentyun.DestroyGroup(groupId, false) return myContext, 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: group_e.TenMicNumType, TouristMic: 1, TouristSendMsg: 1, TouristSendPic: 1, } if err := group_s.NewGroupService(myContext).CreateGroup(userId, &g); err != nil { // 回滚,删除刚刚建立的TX群组 tencentyun.DestroyGroup(groupId, false) return myContext, 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) } resp.ResponseOk(c, CreateGroupRsp{ImGroupId: groupId, Code: code}) return myContext, nil } func getUUID() string { return strings.Replace(uuid.NewV4().String(), "-", "", -1) } // @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" // @Success 200 // @Router /v1/imGroup/group/{groupId} [delete] func DestroyGroup(c *gin.Context) (*mycontext.MyContext, error) { myContext := mycontext.CreateMyContext(c.Keys) groupId := c.Param("groupId") if len(groupId) <= 0 { return myContext, myerr.NewSysError("groupId 为必填项") } userId, _, err := req.GetUserIdAndExtId(c, myContext) if err != nil { return myContext, err } if !user_m.IsSuperUser(userId) { return myContext, bizerr.NoPrivileges } // 参数已经是TxGroupId,不需要再转换了 err = tencentyun.DestroyGroup(groupId, false) if err != nil { return myContext, err } resp.ResponseOk(c, groupId) return myContext, nil } // @Tags 群组 // @Summary 查询群信息(运营用) // @Accept application/x-www-form-urlencoded // @Param token header string true "token" // @Param nonce header string true "随机数字" // @Param code path string true "群CODE" // @Success 200 {object} group_cv.GroupInfo // @Router /v1/imGroup/group/{code} [get] func GetGroupInfo(c *gin.Context) (*mycontext.MyContext, error) { myContext := mycontext.CreateMyContext(c.Keys) code := c.Param("code") if len(code) <= 0 { return myContext, myerr.NewSysError("code 为必填项") } model := domain.CreateModelContext(myContext) r, err := group_m.FindGroupByCode(model, code) if r == nil { return myContext, bizerr.GroupNotFound } groupInfo, err := tencentyun.GetGroupInfo(model, []string{r.ImGroupId}, true) if err != nil { return myContext, err } if len(groupInfo) < 1 { return myContext, myerr.NewSysError("ImGroupId does not exists.") } info := &groupInfo[0] countryInfo, err := res_c.GetCountryIconMap(model) if err != nil { return myContext, err } result := group_cv.GroupInfo{ GroupBasicInfo: group_cv.GroupBasicInfo{ GroupId: r.TxGroupId, Name: r.Name, Introduction: r.Introduction, Notification: r.Notification, FaceUrl: r.FaceUrl, Owner_Account: info.Owner_Account, CreateTime: info.CreateTime, LastMsgTime: info.LastMsgTime, NextMsgSeq: info.NextMsgSeq, MemberNum: info.MemberNum, MaxMemberNum: info.MaxMemberNum, ShutUpAllMember: info.ShutUpAllMember, Code: r.Code, CountryIcon: countryInfo[r.Country], MicNumType: int(r.MicNumType), }, } for _, i := range info.MemberList { result.MemberList = append(result.MemberList, group_cv.MemberListInfo{ Member_Account: i.Member_Account, JoinTime: i.JoinTime, LastSendMsgTime: i.LastSendMsgTime, }) } resp.ResponseOk(c, result) 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" // @Success 200 {object} group_cv.GroupDetail // @Router /v1/imGroup/detail/{groupId} [get] func GetGroupDetail(c *gin.Context) (*mycontext.MyContext, error) { myContext := mycontext.CreateMyContext(c.Keys) groupId := c.Param("groupId") if len(groupId) <= 0 { return myContext, myerr.NewSysError("groupId 为必填项") } myUserId, err := req.GetUserId(c) if err != nil { return myContext, err } model := domain.CreateModelContext(myContext) groupInfo, err := group_m.GetInfoByTxGroupId(model, groupId) if err != nil { return myContext, err } if groupInfo == nil { return myContext, bizerr.GroupNotFound } groupId = groupInfo.ImGroupId memberCount, err := group_m.GetMemberCount(model.Db, groupId) if err != nil { return myContext, err } countryInfo, err := res_c.GetCountryIconMap(model) if err != nil { return myContext, err } themeUrl := "" var themeId uint64 = 0 var themeType uint8 = 1 if groupInfo.ThemeId > 0 { //官方主题 // 找不到的,默认为空(可能被后台删了) themeType = 1 themes, _ := res_m.GroupThemeGetAll(model.Db) for _, i := range themes { if i.ID == uint64(groupInfo.ThemeId) { themeUrl = i.Url themeId = i.ID break } } } else { //可能是自定义主题 themeId, themeUrl, err = group_m.GetShowCustomTheme(model, groupInfo.ImGroupId) if err != nil { return myContext, err } if themeId != 0 { themeType = 2 } } result := group_cv.GroupDetail{ GroupBasicInfo: group_cv.GroupBasicInfo{ GroupId: groupInfo.TxGroupId, Name: groupInfo.Name, Introduction: groupInfo.Introduction, Notification: groupInfo.Notification, FaceUrl: groupInfo.FaceUrl, MemberNum: memberCount, Code: groupInfo.Code, CountryIcon: countryInfo[groupInfo.Country], MicNumType: int(groupInfo.MicNumType), TouristMic: groupInfo.TouristMic, TouristSendMsg: groupInfo.TouristSendMsg, TouristSendPic: groupInfo.TouristSendPic, MemberFee: groupInfo.MemberFee, }, MicOn: groupInfo.MicOn, LoadHistory: groupInfo.LoadHistory, ThemeId: themeId, ThemeUrl: themeUrl, ThemeType: themeType, Role: group_e.GROUP_VISITOR, } result.TotalConsume, err = gift_cv.GetGroupConsumeTotal(model, groupId) if err != nil { return myContext, err } model.Log.Infof("GetGroupDetail, before GetGroupTop3Consume: +%v", result) result.TopConsumers, err = billboard_cv.GetGroupTop3Consume(model, groupId, myUserId) if err != nil { return myContext, err } model.Log.Infof("GetGroupDetail, after GetGroupTop3Consume: +%v", result) result.WelcomeText, _, _, err = group_s.NewGroupService(myContext).GetWelcomeText(groupInfo) if err != nil { return myContext, err } result.WelcomeText = strings.ReplaceAll(result.WelcomeText, "@%s", "") result.DiceNum = group_e.GROUP_DICE_NUM_DEFAULT result.DiceType = 1 gs := group_m.GroupSetting{GroupId: groupId} err = gs.Get(model.Db) if err == nil { result.DiceNum = gs.DiceNum result.DiceType = gs.DiceType } else if err != gorm.ErrRecordNotFound { return myContext, err } roles, _, err := group_m.GetRolesInGroup(model, groupInfo.ImGroupId) if err != nil { return myContext, err } userId, err := req.GetUserId(c) if err != nil { return myContext, err } isGroupMember, err := group_m.IsGroupMember(model.Db, groupId, userId) if err != nil { return myContext, err } if isGroupMember { result.Role = group_e.GROUP_MEMBER } groupUser := group_m.GroupUser{Model: model, UserId: userId, GroupId: groupId} msgStatus, err := groupUser.Get() if err != nil { return myContext, err } // 默认为免打扰 ok := false if result.MsgStatus, ok = msgStatus[groupId]; !ok { //result.MsgStatus = group_enum.DoNotDisturbMsgStatusGroupUser result.MsgStatus = group_e.NormalMsgStatusGroupUser } emptyStr := "" if len(groupInfo.Password) > 0 { // 代表有密码 result.Password = &emptyStr } userIds := make([]uint64, 0) for u, r := range roles { userIds = append(userIds, u) if u == userId { if r == group_e.GROUP_OWNER || r == group_e.GROUP_MANAGER { // 如果用户是OW或经理,可以看到密码,同时设置角色 result.Role = r if len(groupInfo.Password) > 0 { result.Password = &groupInfo.Password } } else if r == group_e.GROUP_ADMIN { // 如果用户是管理员,仅设置角色 result.Role = r } } } userIds = append(userIds, groupInfo.Owner) users, err := user_cv.GetUserTinyMap(userIds) if err != nil { return myContext, err } result.Owner, err = user_cv.GetUserDetail(model, groupInfo.Owner, userId) if err != nil { return myContext, err } vips, err := user_m.BatchGetVips(userIds) if err != nil { return myContext, myerr.WrapErr(err) } //extIds := make([]string, 0) //for _, i := range users { // extIds = append(extIds, i.ExternalId) //} /* fixme:为了性能,直接不查IM在线状态 status, err := tim_m.GetOnlineStatus(model, extIds) if err != nil { return myContext, err } */ superManagerMap, err := user_m.GetSuperManagerMap(userIds) if err != nil { return myContext, err } status := make(map[string]uint, 0) for u, _ := range users { m := users[u] result.RoleMembers = append(result.RoleMembers, group_cv.RoleMemberInfo{ CvUserBase: user_cv.CvUserBase{ Avatar: &m.Avatar, ExternalId: &m.ExternalId, Nick: &m.Nick, Sex: &m.Sex, Country: &m.CountryIcon, CountryIcon: &m.CountryIcon, Code: &m.Code, IsPrettyCode: m.IsPrettyCode, IsVip: vips[m.Id] != nil, IsOfficialStaff: superManagerMap[m.Id], VipExpireTime: vips[m.Id], }, Role: roles[u], OnlineStatus: status[m.ExternalId], }) } sort.SliceStable(result.RoleMembers, func(i, j int) bool { if result.RoleMembers[i].Role > result.RoleMembers[j].Role { return true } else if result.RoleMembers[i].Role == result.RoleMembers[j].Role { if result.RoleMembers[i].OnlineStatus > result.RoleMembers[j].OnlineStatus { return true } else if result.RoleMembers[i].OnlineStatus == result.RoleMembers[j].OnlineStatus && *result.RoleMembers[i].Code > *result.RoleMembers[j].Code { return true } } return false }) // 截取前N个 endPos := group_e.GROUP_ROLE_VIEW_LIMIT if endPos > len(result.RoleMembers) { endPos = len(result.RoleMembers) } result.RoleMembers = result.RoleMembers[0:endPos] // todo: 直接用全量接口,只为方便 supportLevels, err := group_s.NewGroupService(myContext).GetWeekMaxSupportLevelMap() if err != nil { return myContext, err } result.SupportLevel = supportLevels[groupId] resp.ResponseOk(c, result) 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 name formData string false "群名" // @Param introduction formData string false "群简介" // @Param notification formData string false "群公告" // @Param faceUrl formData string false "群头像 URL" // @Param password formData string false "入群密码" // @Param micOn formData bool false "是否打开麦位" // @Param loadHistory formData bool false "入群后是否加载历史" // @Param themeId formData int false "群组背景ID" // @Param welcomeText formData string false "入群欢迎语" // @Param diceNum formData uint16 false "骰子数量" // @Param diceType formData int false "骰子类型 1:数字0-9 2:数字1-6" // @Param touristMic formData int false "游客是否能上麦(1是2否,不传则不改变)" // @Param touristSendMsg formData int false "游客是否是否能发言(1是2否,不传则不改变)" // @Param touristSendPic formData int false "游客是否能发图片(1是2否,不传则不改变)" // @Param isChangeFee formData int false "是否更改加入群组需要的黄钻数,0否1是(兼容旧版本)" // @Param memberFee formData int false "加入群组需要的黄钻数" // @Success 200 // @Router /v1/imGroup/group/{groupId} [put] func ModifyGroupInfo(c *gin.Context) (*mycontext.MyContext, error) { myContext := mycontext.CreateMyContext(c.Keys) imGroupId := c.Param("groupId") if len(imGroupId) <= 0 { return myContext, myerr.NewSysError("groupId为必填项") } userId, externalId, err := req.GetUserIdAndExtId(c, myContext) if err != nil { return myContext, err } model := domain.CreateModelContext(myContext) imGroupId, err = group_m.ToImGroupId(model, imGroupId) if err != nil { return myContext, err } // 判断有没有权限修改资料 role, err := group_m.GetRoleInGroup(model, userId, imGroupId) if err != nil { return myContext, err } if role != group_e.GROUP_OWNER && role != group_e.GROUP_MANAGER { return myContext, bizerr.NoPrivileges } groupInfo, err := group_m.GetGroupInfo(model, imGroupId) if err != nil { return myContext, err } diamondAccount, err := diamond_m.GetDiamondAccountByUserId(model, groupInfo.Owner) if err != nil { return myContext, err } if diamondAccount.Status == diamond_e.Frozen { // 如果已经冻结,则不能操作 return myContext, bizerr.NoPrivileges } type signalMsg struct { Name *string `json:"name"` Introduction *string `json:"introduction"` Notification *string `json:"notification"` FaceUrl *string `json:"faceUrl"` MicOn *bool `json:"micOn"` MicNumType *group_e.GroupMicNumType ThemeId *int16 `json:"themeId"` ThemeUrl *string `json:"themeUrl"` //1: 官方 2:自定义 ThemeType *uint8 `json:"themeType"` TouristMic *uint8 `json:"touristMic"` // 游客是否能上麦1是2否 TouristSendMsg *uint8 `json:"touristSendMsg"` // 游客是否能发消息1是2否 TouristSendPic *uint8 `json:"touristSendPic"` // 游客是否能发图片1是2否 MemberFee *uint64 `json:"memberFee"` // 加入会员需要黄钻数 } signal := signalMsg{} fields := make([]string, 0) name, exists := c.GetPostForm("name") if exists { fields = append(fields, "name") signal.Name = &name // 2022-04-06 官方群组不能通过客户端修改名称和头像 if group_m.IsOfficialGroup(imGroupId) { return myContext, bizerr.NoPrivileges } } introduction, exists := c.GetPostForm("introduction") if exists { fields = append(fields, "introduction") signal.Introduction = &introduction } notification, exists := c.GetPostForm("notification") if exists { fields = append(fields, "notification") signal.Notification = ¬ification } g := group_m.GroupInfo{ Name: name, Introduction: introduction, Notification: notification, } faceUrl, exists := c.GetPostForm("faceUrl") welcomeText := c.PostForm("welcomeText") // 编辑cd if len(name) > 0 || len(introduction) > 0 || len(notification) > 0 || len(faceUrl) > 0 || len(welcomeText) > 0 { if group_c.IsEditGroupCd(model, imGroupId) { return myContext, bizerr.EditCd } } if exists { // 2022-04-06 官方群组不能通过客户端修改名称和头像 if group_m.IsOfficialGroup(imGroupId) { return myContext, bizerr.NoPrivileges } // 2022-06-20 HL限制1115,1116群组改群头像 /* if imGroupId == "@TGS#3LXQ2TWH3" || imGroupId == "@TGS#3LICQDVHM" { return myContext, bizerr.NoPrivileges } */ if len(faceUrl) > 0 { g.FaceUrl = utils.MakeFullUrl(faceUrl) switch config.GetConfigApp().MODERATE { case "AWS": passed, err := aws.ModerateLabels(model.Log, userId, faceUrl) if err == nil { if !passed { return myContext, bizerr.ImagePolicyViolation } } else { model.Log.Warnf("ModerateLabels err:%v", err) } case "TENCENT": label, err := tencentyun.ModerateImage(model, userId, "", faceUrl, g.FaceUrl) if err == nil && label != "Pass" { return myContext, bizerr.ImagePolicyViolation } } } fields = append(fields, "face_url") signal.FaceUrl = &g.FaceUrl } password, exists := c.GetPostForm("password") if exists { if len(password) > 0 && len(password) != group_e.ROOM_PASSWORD_LENGTH { return myContext, bizerr.WrongPasswordLength } g.Password = password fields = append(fields, "password") } if micOn, err := strconv.ParseBool(c.PostForm("micOn")); err == nil { g.MicOn = micOn fields = append(fields, "mic_on") signal.MicOn = &g.MicOn } if loadHistory, err := strconv.ParseBool(c.PostForm("loadHistory")); err == nil { g.LoadHistory = loadHistory fields = append(fields, "load_history") } if touristMic, err := strconv.ParseInt(c.PostForm("touristMic"), 10, 16); err == nil && touristMic > 0 && touristMic < 3 { g.TouristMic = uint8(touristMic) fields = append(fields, "tourist_mic") signal.TouristMic = &g.TouristMic } if touristSendMsg, err := strconv.ParseInt(c.PostForm("touristSendMsg"), 10, 16); err == nil && touristSendMsg > 0 && touristSendMsg < 3 { g.TouristSendMsg = uint8(touristSendMsg) fields = append(fields, "tourist_send_msg") signal.TouristSendMsg = &g.TouristSendMsg } if touristSendPic, err := strconv.ParseInt(c.PostForm("touristSendPic"), 10, 16); err == nil && touristSendPic > 0 && touristSendPic < 3 { g.TouristSendPic = uint8(touristSendPic) fields = append(fields, "tourist_send_pic") signal.TouristSendPic = &g.TouristSendPic } if isChangeFee, err := strconv.ParseInt(c.PostForm("isChangeFee"), 10, 16); err == nil && isChangeFee == 1 { if memberFee, err := strconv.ParseUint(c.PostForm("memberFee"), 10, 16); err == nil { g.MemberFee = memberFee fields = append(fields, "member_fee") signal.MemberFee = &g.MemberFee } } if themeId, err := strconv.ParseInt(c.PostForm("themeId"), 10, 16); err == nil { if themeId == 0 { g.ThemeId = int16(themeId) fields = append(fields, "theme_id") signal.ThemeId = &g.ThemeId var t uint8 = group_e.SETTING_CUSTOMIZED signal.ThemeType = &t } else if rows, err := res_m.GroupThemeGetAllInUse(model.Db); err == nil { for _, i := range rows { if i.ID == uint64(themeId) { g.ThemeId = int16(themeId) fields = append(fields, "theme_id") signal.ThemeId = &g.ThemeId signal.ThemeUrl = &i.Url var t uint8 = group_e.SETTING_OFFICIAL signal.ThemeType = &t break } } } } if len(welcomeText) > 0 { if len([]rune(welcomeText)) > group_e.GROUP_INTRODUCTION_LENGTH_LIMIT { model.Log.Warnf("ModifyGroupInfo %s - text too long", imGroupId) } else { gwt := group_m.GroupWelcomeText{GroupId: imGroupId, UserId: userId, Text: welcomeText} if err = gwt.Save(model.Db); err != nil { model.Log.Warnf("ModifyGroupInfo %s - Save WelcomeText FAILED:%v", imGroupId, err) } } } // 没必要就不调用 if len(fields) > 0 { db := g.Update(model, imGroupId, fields) if db.Error != nil { return myContext, db.Error } buf, err := json.Marshal(signal) if err == nil { systemMsg := group_m.GroupSystemMsg{MsgId: group_e.GroupEditProfileSignal, Source: externalId, Content: string(buf)} signal_s.SendSignalMsg(model, imGroupId, systemMsg, false) } } if diceNum, err := strconv.Atoi(c.PostForm("diceNum")); err == nil { if diceNum <= 0 || diceNum > group_e.GROUP_DICE_NUM_MAX { model.Log.Warnf("ModifyGroupInfo %s: Invalid dice number %d", imGroupId, diceNum) } else { gs := group_m.GroupSetting{GroupId: imGroupId, DiceNum: uint16(diceNum)} if err = gs.SetDiceNum(model.Db); err != nil { model.Log.Warnf("ModifyGroupInfo %s: set failed: %s", imGroupId, err.Error()) } } } if diceType, err := strconv.Atoi(c.PostForm("diceType")); err == nil { if diceType <= 0 || diceType > 2 { model.Log.Warnf("ModifyGroupInfo %s: Invalid dice diceType %d", imGroupId, diceType) } else { gs := group_m.GroupSetting{GroupId: imGroupId, DiceType: uint16(diceType)} if err = gs.SetDiceType(model.Db); err != nil { model.Log.Warnf("ModifyGroupInfo %s: set failed: %s", imGroupId, err.Error()) } } } resp.ResponseOk(c, nil) return myContext, nil } // @Tags 群组 // @Summary 搜索群 // @Accept application/x-www-form-urlencoded // @Param token header string true "token" // @Param nonce header string true "随机数字" // @Param code path string true "群CODE" // @Success 200 {object} []group_cv.GroupInfo // @Router /v1/imGroup/search/{code} [get] func SearchGroup(c *gin.Context) (*mycontext.MyContext, error) { myContext := mycontext.CreateMyContext(c.Keys) myUserId, err := req.GetUserId(c) if err != nil { return myContext, err } code := c.Param("code") if len(code) <= 0 { return myContext, myerr.NewSysError("code 为必填项") } model := domain.CreateModelContext(myContext) result := make([]group_cv.GroupInfo, 0) total := 0 g, err := group_m.GetGroupByCode(model, code) if err != nil { return myContext, err } if g != nil { owner, err := user_m.GetUser(model, g.Owner) if err != nil { return myContext, err } if owner.Status == user_e.UserStatusFrozen { if flag, _ := user_m.IsSuperManagerV2(model, myUserId, owner.ID); !flag { // 被封锁的用户,除了超管账户,其它用户搜索他的群组和个人ID,搜索结果为空 resp.ResponsePageOk(c, result, uint(total), 1) return myContext, nil } } if group_m.IsHiddenGroupBy(model, g.ImGroupId) { if flag, _ := user_m.IsSuperManagerV2(model, myUserId, g.Owner); !flag { // 被隐藏的用户,除了超管账户,其它用户搜索他的群组和个人ID,搜索结果为空 resp.ResponsePageOk(c, result, uint(total), 1) return myContext, nil } } supportLevels, err := group_s.NewGroupService(myContext).GetWeekMaxSupportLevelMap() if err != nil { return myContext, err } // 允许多个结果,虽然目前只有一个群 groupIds := []string{g.ImGroupId} groupInfo := map[string]group_m.GroupInfo{g.ImGroupId: *g} if len(groupIds) > 0 { countryInfo, err := res_c.GetCountryIconMap(model) if err != nil { return myContext, err } for _, i := range groupIds { var password *string = nil if len(groupInfo[i].Password) > 0 && groupInfo[i].Owner != myUserId { emptyStr := "" password = &emptyStr } memberCount, err := group_m.GetMemberCount(model.Db, i) if err != nil { return myContext, err } result = append(result, group_cv.GroupInfo{ GroupBasicInfo: group_cv.GroupBasicInfo{ GroupId: groupInfo[i].TxGroupId, Name: groupInfo[i].Name, Introduction: groupInfo[i].Introduction, Notification: groupInfo[i].Notification, FaceUrl: groupInfo[i].FaceUrl, //Owner_Account: i.Owner_Account, MemberNum: memberCount, Code: groupInfo[i].Code, CountryIcon: countryInfo[groupInfo[i].Country], Password: password, SupportLevel: supportLevels[i], MicNumType: int(groupInfo[i].MicNumType), }, }) } } } resp.ResponsePageOk(c, result, uint(total), 1) return myContext, nil } // @Tags 群组 // @Summary 离开群组(obsolete) // @Accept application/x-www-form-urlencoded // @Param token header string true "token" // @Param nonce header string true "随机数字" // @Param groupId path string true "群ID" // @Success 200 {object} uint // @Router /v1/imGroup/member/{groupId} [delete] func LeaveGroup(c *gin.Context) (*mycontext.MyContext, error) { myContext := mycontext.CreateMyContext(c.Keys) groupId := c.Param("groupId") if len(groupId) <= 0 { return myContext, myerr.NewSysError("groupId 为必填项") } userId, externalId, err := req.GetUserIdAndExtId(c, myContext) if err != nil { return myContext, err } model := domain.CreateModelContext(myContext) groupInfo, err := group_m.GetInfoByTxGroupId(model, groupId) if groupInfo == nil { return myContext, bizerr.GroupNotFound } if userId == groupInfo.Owner { return myContext, bizerr.OwnerCannotLeave } if err = group_s.NewGroupService(myContext).LeaveGroup(model, groupId, userId, externalId); err != nil { return myContext, err } resp.ResponseOk(c, nil) 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 isInvite formData int false "是否群邀请0否1是" // @Success 200 {object} uint "结果:1 为新加入成功;2 为已经是群成员" // @Router /v1/imGroup/permanent/{groupId} [put] func AddPermanentMember(c *gin.Context) (*mycontext.MyContext, error) { myContext := mycontext.CreateMyContext(c.Keys) groupId := c.Param("groupId") if len(groupId) <= 0 { return myContext, myerr.NewSysError("groupId 为必填项") } isInvite, err := strconv.Atoi(c.PostForm("isInvite")) if err != nil { isInvite = 0 } userId, externalId, nick, avatar, _, err := req.GetUserEx(c, myContext) if err != nil { return myContext, err } model := domain.CreateModelContext(myContext) ip := req.GetRequestIP(c) imei, err := req.GetAppImei(c) if err != nil { imei, err = user_m.GetUserImeiStr(model, userId) if err != nil { return myContext, err } } groupId, err = group_m.ToImGroupId(model, groupId) if err != nil { return myContext, err } // 群是否被封禁 banned := group_m.GroupBanned{ImGroupId: groupId} if err = banned.Get(model); err != gorm.ErrRecordNotFound { return myContext, bizerr.GroupIsBanned } // 已经成员的,直接返回 rec, err := group_m.GetJoinedGroups(model.Db, userId) if err != nil { return myContext, err } for _, i := range rec { if i.GroupId == groupId { resp.ResponseOk(c, group_e.ADD_GROUP_DUPLICATE) return myContext, nil } } // 用户是否在群的黑名单中 if group_m.InGroupBlackList(model, groupId, imei, ip, userId) { return myContext, bizerr.InBlacklist } // 检查所在群数是不是已达上限 maxJoin, err := group_s.NewGroupService(myContext).GetJoinGroupLimit(userId) if err != nil { return myContext, err } isVip, _, err := user_m.IsVip(userId) if err != nil { return myContext, err } if uint(len(rec)) >= maxJoin { if isVip { return myContext, bizerr.GroupLimitReached } else { return myContext, bizerr.GroupLimitReachedVip } } // 不在房间内的不能成为永久成员 userIds, err := group_m.RoomLivingExistsUserId(groupId) if err != nil { return myContext, err } inRoom := false for _, u := range userIds { if u == userId { inRoom = true break } } if !inRoom { if !config.AppIsLocal() { return myContext, bizerr.IncorrectState } } // 加入群组是否需要收费 needCost, err := AddPermanentMemberCost(c, myContext, isInvite, userId, groupId) if err != nil { return myContext, err } gm := group_m.GroupMember{ GroupId: groupId, UserId: userId, } if err = gm.Create(model.Db); err != nil { return myContext, err } // 新进群的公屏消息 if nobleLevel, err := noble_m.GetNobleLevel(model.Db, userId); err == nil { msg := group_m.CommonPublicMsg{Type: group_e.UserJoinPublicScreenMsg, ExternalId: externalId, Nick: nick, Avatar: avatar, IsVip: isVip, NobleLevel: nobleLevel} buf, err := json.Marshal(msg) if err == nil { go func(groupId, text string) { defer func() { if r := recover(); r != nil { //打印错误堆栈信息 mylogrus.MyLog.Errorf("SendCustomMsg SYSTEM ACTION PANIC: %v, stack: %v", r, string(debug.Stack())) } }() signal_s.SendCustomMsg(domain.CreateModelContext(model.MyContext), groupId, nil, text) }(groupId, string(buf)) } } // fixme: 这些缓存还需要吗? group_c.ClearGroupMemberCount(groupId) tim_c.AddGroupMember(model, groupId, externalId) if isInvite == 1 && !needCost { // 已经接受了进群邀请 group_m.AcceptGroupInviteJoin(model, userId, groupId) } resp.ResponseOk(c, group_e.ADD_GROUP_DONE) return myContext, nil } // 成员永久会议,检查是否需要收费 func AddPermanentMemberCost(c *gin.Context, myContext *mycontext.MyContext, isInvite int, userId uint64, groupId string) (bool, error) { needCost := true // 加入群组是否需要收费 var model = domain.CreateModelContext(myContext) info, err := group_m.GetByImGroupId(model, groupId) if err != nil { return needCost, err } if info.MemberFee > 0 { // 需要收费 // 旧版本(2.32.0以下),提示升级 _, major, minor, _, err := req.GetAppVersion(c) if err != nil { return needCost, err } if major <= 2 && minor < 32 { return needCost, bizerr.UpgradeRequired } // 验证是否邀请 if isInvite == 1 { inviteInfo, err := group_m.GetGroupInviteJoin(model, userId, groupId) if err != nil { return needCost, err } if inviteInfo != nil && time.Now().Unix()-inviteInfo.CreatedTime.Unix() < 86400 { // 一天内的邀请才有效 needCost = false } } if needCost { // 需要扣黄钻 err = ChangeDiamondAccountDetail(myContext, diamond_e.JoinGroupCost, mysql.ID(info.Id), userId, mysql.Num(info.MemberFee)) if err != nil { return needCost, err } // 给一半群主 err = ChangeDiamondAccountDetail(myContext, diamond_e.JoinGroupAdd, mysql.ID(info.Id), info.Owner, mysql.Num(math.Floor(float64(info.MemberFee)/2))) if err != nil { return needCost, err } } } return needCost, nil } func ChangeDiamondAccountDetail(myContext *mycontext.MyContext, operateType diamond_e.OperateType, originId mysql.ID, userId mysql.ID, diamondNum mysql.Num) error { db := mysql.Db.Begin() model := domain.CreateModelContext(myContext) diamondAccount, err := diamond_m.GetDiamondAccountByUserId(model, userId) if err != nil { return err } diamondAccountDetail, err := diamondAccount.ChangeDiamondAccountDetail(operateType, originId, diamondNum) if err != nil { db.Rollback() return err } if err := diamondAccountDetail.PersistentNoInTransactional(); err != nil { db.Rollback() return err } db.Commit() return 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" // @Success 200 {object} uint // @Router /v1/imGroup/permanent/{groupId} [delete] func RemovePermanentMember(c *gin.Context) (*mycontext.MyContext, error) { myContext := mycontext.CreateMyContext(c.Keys) groupId := c.Param("groupId") if len(groupId) <= 0 { return myContext, myerr.NewSysError("groupId 为必填项") } userId, externalId, err := req.GetUserIdAndExtId(c, myContext) if err != nil { return myContext, err } model := domain.CreateModelContext(myContext) groupInfo, err := group_m.GetInfoByTxGroupId(model, groupId) if err != nil { return myContext, err } if groupInfo == nil { return myContext, bizerr.GroupNotFound } groupId = groupInfo.ImGroupId if userId == groupInfo.Owner { return myContext, bizerr.OwnerCannotLeave } if err = group_s.NewGroupService(myContext).LeaveGroupMember(model, groupId, userId, externalId); err != nil { return myContext, err } resp.ResponseOk(c, nil) return myContext, nil } type GroupMembersRsp struct { Members []group_cv.MemberDetail `json:"members"` Online uint `json:"online"` // 在线人数 Total uint `json:"total"` } type GetGroupVisitorsRsp struct { Members []group_cv.GroupVisitorsDetail `json:"members"` Online uint `json:"online"` // 在线人数 Total uint `json:"total"` } // @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} GroupMembersRsp // @Router /v1/imGroup/permanent/{groupId} [get] func GetPermanentMember(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, err := group_m.GetMembers(model.Db, groupId) if err != nil { return myContext, err } userIds := make([]uint64, 0) for _, i := range rows { userIds = append(userIds, i.UserId) } users, err := user_m.GetUserMapByIds(model, userIds) if err != nil { return myContext, err } model.Log.Infof("GetGroupMembers %s: memberNum = %d, user size = %d", groupId, len(rows), len(userIds)) result := GroupMembersRsp{Total: uint(len(rows))} beginPos := pageSize * (pageIndex - 1) if uint(beginPos) < result.Total { // 取在线状态 extIds := make([]string, 0) for _, i := range users { extIds = append(extIds, i.ExternalId) } 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++ } } model.Log.Infof("GetGroupMembers %s: statusMap size = %d, onLine = %d", groupId, len(statusMap), result.Online) roles, _, err := group_m.GetRolesInGroup(model, groupId) if err != nil { return myContext, err } nobleLevels, err := noble_m.BatchGetNobleLevel(model.Db, userIds) if err != nil { return myContext, err } vips, err := user_m.BatchGetVips(userIds) if err != nil { return myContext, err } model.Log.Infof("GetGroupMembers %s, users %v, roles: %v, nobles: %v, vips: %v", groupId, userIds, roles, nobleLevels, vips) roomUsers, err := group_m.RoomLivingExistsUserId(groupId) if err != nil { return myContext, err } roomUserMap := utils.SliceToMapUInt64(roomUsers) //model.Log.Infof("GetGroupMembers %s, roomStatusMap %v", groupId, roomStatusMap) // 排序规则 :在房间的优先,其次是在线,再次看角色,最后看贵族 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 := users[ui].ExternalId ej := users[uj].ExternalId if statusMap[ei] > statusMap[ej] { return true } if statusMap[ei] == statusMap[ej] { if roles[ui] > roles[uj] { return true } if roles[ui] == roles[uj] { // 贵族5>贵族4>贵族3>贵族2>VIP 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 users[ui].Code < users[uj].Code } } else if vips[uj] == nil { return users[ui].Code < users[uj].Code } } } } } return false }) model.Log.Infof("GetGroupMembers %s, sorted users: %v", groupId, userIds) endPos := pageSize * pageIndex if endPos > len(userIds) { endPos = len(userIds) } userIds = userIds[beginPos:endPos] userExtends, err := user_cv.BatchGetUserExtend(model, userIds, userId) 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.MemberDetail{ CvUserExtend: userExtends[u], Role: roles[u], OnlineStatus: statusMap[users[u].ExternalId], InRoom: inRoom, }) } } resp.ResponseOk(c, result) return myContext, nil } // @Tags 群组 // @Summary 全服广播群消息(运营用,慎用) // @Accept application/x-www-form-urlencoded // @Param token header string true "token" // @Param nonce header string true "随机数字" // @Param contentType formData int true "消息类型:1 跳转url;2 跳转房间" // @Param content formData string true "消息内容" // @Param h5url formData string false "要转跳的url" // @Param groupId formData string false "群ID" // @Success 200 {object} uint // @Router /v1/imGroup/allGroupMsg [put] func SendTextMsg(c *gin.Context) (*mycontext.MyContext, error) { myContext := mycontext.CreateMyContext(c.Keys) userId, err := req.GetUserId(c) if err != nil { return myContext, err } if !user_m.IsSuperUser(userId) { return myContext, bizerr.NoPrivileges } contentType, err := strconv.Atoi(c.PostForm("contentType")) if err != nil { return myContext, bizerr.ParaMissing } content := c.PostForm("content") if len(content) <= 0 { return myContext, bizerr.ParaMissing } h5url := "" groupId := "" if contentType == 1 { h5url = c.PostForm("h5url") if len(h5url) == 0 { return myContext, bizerr.InvalidParameter } } else if contentType == 2 { groupId = c.PostForm("groupId") if len(groupId) == 0 { return myContext, bizerr.InvalidParameter } } else { return myContext, bizerr.InvalidParameter } model := domain.CreateModelContext(myContext) txGroupId := groupId groupId, err = group_m.ToImGroupId(model, groupId) if err != nil { return myContext, err } gsm := group_m.GroupCustomMsg{ CommonPublicMsg: group_m.CommonPublicMsg{Type: group_e.JumpMessage}, ContentType: contentType, Content: content, H5: h5url, GroupId: txGroupId, } buf, err := json.Marshal(gsm) var failedCount uint = 0 if err == nil { groupIds, err := group_m.GetAllGroupIds(model.Db) if err != nil { return myContext, err } for i, g := range groupIds { seq, err := signal_s.SendCustomMsg(model, g, nil, string(buf)) if err == nil { model.Log.Infof("Succeeded in sending GroupCustomMsg to group %s, seq = %d", g, seq) } else { model.Log.Infof("Failed in sending GroupCustomMsg to group %s", g) failedCount++ } if i%100 == 0 { time.Sleep(time.Millisecond * 500) } } } resp.ResponseOk(c, failedCount) return myContext, nil } // @Tags 群组 // @Summary 复制群到海外版 // @Accept application/x-www-form-urlencoded // @Param token header string true "token" // @Param nonce header string true "随机数字" // @Param groupId formData string false "群ID" // @Param size formData uint false "批量单位" // @Param limit formData uint false "批量数" // @Success 200 {object} []string // @Router /v1/imGroup/upgrade [put] func UpgradeGroup(c *gin.Context) (*mycontext.MyContext, error) { myContext := mycontext.CreateMyContext(c.Keys) userId, err := req.GetUserId(c) if err != nil { return myContext, err } if !user_m.IsSuperUser(userId) { return myContext, bizerr.NoPrivileges } model := domain.CreateModelContext(myContext) failed := make([]string, 0) groupId := c.PostForm("groupId") if len(groupId) > 0 { gi, err := group_m.GetGroupInfo(model, groupId) if err != nil { return myContext, err } if gi == nil { return myContext, bizerr.GroupNotFound } // fixme: if gi.Type == group_e.OverseaRoom { model.Log.Infof("UpgradeGroup, no need to upgrade %s", groupId) } else { if err = upgradeRoom(model, gi); err != nil { failed = append(failed, gi.ImGroupId) } } } else { size, err := strconv.Atoi(c.PostForm("size")) if err != nil || size == 0 { return myContext, bizerr.InvalidParameter } else { limit, _ := strconv.Atoi(c.PostForm("limit")) g := group_m.GroupInfo{Type: group_e.LocalRoom} rec, err := g.FindAllGroups(model.Db) if err != nil { return myContext, err } index := 0 for start := 0; start < len(rec) && index < limit; start += size { end := start + size if end >= len(rec) { end = len(rec) } go func(index int, groups []group_m.GroupInfo) { model.Log.Infof("Rountine %d process %d", index, len(groups)) for i, g := range groups { model.Log.Infof("Routine %d upgrading %s", index, g.ImGroupId) if err = upgradeRoom(model, &g); err != nil { model.Log.Errorf("Upgrade %s failed", g.ImGroupId) } if i > 0 && i%50 == 0 { time.Sleep(time.Second) } } }(index, rec[start:end]) index++ } } } resp.ResponseOk(c, failed) return myContext, nil } func upgradeRoom(model *domain.Model, gi *group_m.GroupInfo) error { if strings.HasPrefix(gi.TxGroupId, group_e.OverseaGroupNamePrefix) { // 已经是新版,无需处理 return nil } if strings.HasPrefix(gi.TxGroupId, group_e.OldGroupNamePrefix) { // 升级群号,然后两边都升级 newGroupId := group_e.NewGroupNamePrefix + gi.TxGroupId[len(group_e.OldGroupNamePrefix):] // 避免TX报错 if len(gi.Name) <= 0 { gi.Name = "empty" } txGroupId, err := tencentyun.CreateGroup(gi.Name, newGroupId) if err != nil { return err } g := group_m.GroupInfo{ TxGroupId: txGroupId, Type: group_e.OverseaRoom, } db := g.Update(model, gi.ImGroupId, []string{"tx_group_id", "type"}) if db.Error != nil { return err } tencentyun.DestroyGroup(gi.TxGroupId, true) } else { // 在海外建一个同样的群即可 // 避免TX报错 if len(gi.Name) <= 0 { gi.Name = "empty" } _, err := tencentyun.CreateGroup(gi.Name, gi.TxGroupId) if err != nil { return err } // 更新type即可 g := group_m.GroupInfo{ Type: group_e.OverseaRoom, } db := g.Update(model, gi.ImGroupId, []string{"type"}) if db.Error != nil { return err } } // 删除旧群 return nil } // @Tags 群组 // @Summary 降级直播间为会议模式 // @Accept application/x-www-form-urlencoded // @Param token header string true "token" // @Param nonce header string true "随机数字" // @Param groupId formData string false "群ID" // @Success 200 // @Router /v1/imGroup/downgrade [put] func DowngradeGroup(c *gin.Context) (*mycontext.MyContext, error) { myContext := mycontext.CreateMyContext(c.Keys) userId, err := req.GetUserId(c) if err != nil { return myContext, err } if !user_m.IsSuperUser(userId) { return myContext, bizerr.NoPrivileges } model := domain.CreateModelContext(myContext) groupId := c.PostForm("groupId") if len(groupId) <= 0 { return myContext, bizerr.InvalidParameter } gi, err := group_m.GetGroupInfo(model, groupId) if err != nil { return myContext, err } if gi == nil { return myContext, bizerr.GroupNotFound } if gi.Type == 2 { model.Log.Infof("DowngradeGroup, no need to downgrade %s", groupId) } else { if err = downgradeRoom(myContext, gi); err != nil { return myContext, err } } resp.ResponseOk(c, nil) return myContext, nil } func downgradeRoom(myContext *mycontext.MyContext, gi *group_m.GroupInfo) error { // 删除旧直播间 err := tencentyun.DestroyGroup(gi.TxGroupId, false) if err != nil { return err } // 恢复会议群 groupId, err := tencentyun.CreateGroup(gi.Name, "") if err != nil { return err } g := group_m.GroupInfo{ ImGroupId: groupId, TxGroupId: groupId, Type: group_e.LocalRoom, Code: gi.Code + "#", OriginCode: gi.OriginCode, Owner: gi.Owner, Name: gi.Name, Introduction: gi.Introduction, Notification: gi.Notification, FaceUrl: gi.FaceUrl, Country: gi.Country, ChannelId: gi.ChannelId, MicOn: gi.MicOn, LoadHistory: gi.LoadHistory, MicNumType: gi.MicNumType, TouristMic: 1, TouristSendMsg: 1, TouristSendPic: 1, } if err := group_s.NewGroupService(myContext).CreateGroup(g.Owner, &g); err != nil { // 回滚,删除刚刚建立的TX群组 tencentyun.DestroyGroup(groupId, false) return err } return nil } // @Tags 群组 // @Summary 进入房间 // @Accept application/x-www-form-urlencoded // @Param token header string true "token" // @Param nonce header string true "随机数字" // @Param groupId formData string true "群ID" // @Param password formData string false "房间密码" // @Param enterType formData int false "进房类型:1.ludo游戏快速匹配进房 2:uno" // @Param gameCode formData string false "gameCode" // @Success 200 {object} group_cv.GroupChannelId // @Router /v1/imGroup/in [put] func GroupIn(c *gin.Context) (*mycontext.MyContext, error) { myContext := mycontext.CreateMyContext(c.Keys) userId, externalId, err := req.GetUserIdAndExtId(c, myContext) if err != nil { return myContext, err } groupId := c.PostForm("groupId") password := c.PostForm("password") enterType := c.PostForm("enterType") gameCode := c.PostForm("gameCode") // 把id:9 添加进房间:5030的黑名单 if (userId == 2087771 || userId == 1763211) && groupId == "HTGS#a46766257" { return myContext, bizerr.NoPrivileges } // 把Cartier和1980加入到房间id:VIP、房间id:305、房间id:7的房间黑名单 if (userId == 2129451 || userId == 2809891) && groupId == "HTGS#a25015185" { return myContext, bizerr.NoPrivileges } if (userId == 2129451 || userId == 2809891) && groupId == "HTGS#a48723458" { return myContext, bizerr.NoPrivileges } if (userId == 2129451 || userId == 2809891) && groupId == "HTGS#a69660046" { return myContext, bizerr.NoPrivileges } model := domain.CreateModelContext(myContext) gi, err := group_m.GetInfoByTxGroupId(model, groupId) if err != nil { return myContext, err } if gi == nil { return myContext, bizerr.GroupNotFound } groupId = gi.ImGroupId ip := req.GetRequestIP(c) imei, err := req.GetAppImei(c) if err != nil { imei, err = user_m.GetUserImeiStr(model, userId) if err != nil { return myContext, err } } model.Log.Infof("GroupIn ip userId:%v,imGroupId:%v,ip:%v,imei:%v", userId, groupId, ip, imei) provider := group_e.GroupProvider_SW if group_m.IsUseTRTC(model, groupId) { provider = group_e.GroupProvider_TRTC //total := int64(11000) //if !config.AppIsRelease() { // total = 30 //} //if group_m.CountTRTC(model) > total { // roomUser, err := group_m.GetRoomOnlineUser(myContext, groupId) // if err == nil && roomUser.Total == 0 { // if err := group_m.DeleteTRTC(model, groupId); err == nil { // provider = group_e.GroupProvider_SW // } // } //} } else { // 增加trtc房间 //dayMaxCovertNum := int64(600) //numKey := rediskey.GetConvertToTRTCNum() //covertNum, err := redisCli.GetCacheInt64(numKey) //if time.Now().Unix() > 1684810309 && time.Now().Unix() < 1684857600 && err == nil && covertNum <= dayMaxCovertNum { // roomUser, err := group_m.GetRoomOnlineUser(myContext, groupId) // if err == nil && roomUser.Total == 0 { // newNum, err := redisCli.IncrNumExpire(numKey, 1, time.Hour*24) // if err == nil && newNum <= dayMaxCovertNum { // if err := group_m.InitTRTC(model, groupId); err != nil { // return myContext, err // } // provider = group_e.GroupProvider_TRTC // model.Log.Infof("auto shift trtc room groupId:%v,%+v-%v", groupId, roomUser, err) // } // } //} } // 进房时判断要用哪个im targetProvider := group_m.GetRandomImProvider(model) if provider != targetProvider { roomUser, err := group_m.GetRoomOnlineUser(myContext, groupId) if err == nil && roomUser.Total == 0 { // 房间没人才做切换 switch provider { case group_e.GroupProvider_SW: // 旧的是声网,切换成trtc if err := group_m.InitTRTC(model, groupId); err != nil { return myContext, err } case group_e.GroupProvider_TRTC: // 旧的是trtc,切换成声网 if err := group_m.DeleteTRTC(model, groupId); err != nil { return myContext, err } } // 切换 provider = targetProvider // log err = group_m.CreateGroupImLog(model, groupId, provider) if err != nil { model.Log.Errorf("CreateGroupImLog err:%v, imGroupId:%s, provider:%v", err, groupId, provider) } } } if provider == group_e.GroupProvider_TRTC { // 版本升级提示,旧版本(3.6.0以下),提示升级 _, major, minor, _, err := req.GetAppVersion(c) if err != nil { return myContext, err } if (major < 3) || (major == 3 && minor < 6) { return myContext, bizerr.UpgradeRequired } } // 指定房间号写死trtc if groupId == "HTGS#a93989299" { provider = group_e.GroupProvider_TRTC } // 指定房间号写死sw if groupId == "HTGS#a25015185" { provider = group_e.GroupProvider_SW } if channelId, token, err := group_s.NewGroupService(myContext).GroupIn(userId, externalId, groupId, password, imei, ip, provider, gi.Id); err != nil { return myContext, err } else { // 加入房间缓存 if err = room_c.ProcessRoomVisit(groupId, userId); err != nil { myContext.Log.Infof("GroupIn, ProcessRoomVisit err: %s", err.Error()) } // 更新用户进入房间缓存记录 if err = room_c.ProcessUserRoomVisit(userId, groupId); err != nil { myContext.Log.Infof("GroupIn, ProcessUserRoomVisit err: %s", err.Error()) } resp.ResponseOk(c, group_cv.GroupChannelId{ ChannelId: channelId, Token: token, AgoraId: uint32(userId), MicNumType: gi.MicNumType, Provider: provider, }) // v2.26及以后,客户端自己加TIM群,不再由服务器代加 _, major, minor, _, err := req.GetAppVersion(c) if err != nil || major < 2 || major == 2 && minor < 26 { go func() { defer func() { if r := recover(); r != nil { //打印错误堆栈信息 mylogrus.MyLog.Errorf("GroupIn - JoinGroup, SYSTEM ACTION PANIC: %v, stack: %v", r, string(debug.Stack())) } }() err := group_s.NewGroupService(myContext).JoinGroup(userId, externalId, gi.TxGroupId) mylogrus.MyLog.Infof("myService.JoinGroup %s, user %d, err:%v", groupId, userId, err) }() } // 判断是否需要执行游戏逻辑 if enterType != "" && gameCode != "" { traceId, _ := c.Get(mycontext.TRACEID) token := c.Writer.Header().Get(mycontext.TOKEN) err := game_c.SetAutoMathEnterRoom(userId, gi.ImGroupId, cast.ToString(traceId), token, enterType, gameCode) if err != nil { model.Log.Errorf("GroupIn cache.SetAutoMathEnterRoom userId:%v, imGroupId:%v, err:%v", userId, gi.ImGroupId, err) } //go proxy.GameAfterEnterRoom(model, userId, externalId, traceId, token, enterType, gameCode, gi) } //// 临时 //go func() { // time.Sleep(time.Second * 2) // //发送全麦信息 // myContext.Log.Infof("imCallBack CallbackAfterNewMemberJoin MicAllRPush begin MemberAccount:%v, gi:%v", externalId, gi) // if err := group_m.MicAllRPush(domain.CreateModelContext(myContext), groupId, externalId); err != nil { // myContext.Log.Errorf("imCallBack CallbackAfterNewMemberJoin MicAllRPush err MemberAccount:%v, gi:%v,err:%v", externalId, gi, err) // } else { // myContext.Log.Infof("imCallBack CallbackAfterNewMemberJoin MicAllRPush success MemberAccount:%v, gi:%v,err:%v", externalId, gi, err) // } // //加入在线列表 // user, err := user_m.GetUserByExtId(domain.CreateModelContext(myContext), externalId) // if err != nil { // myContext.Log.Errorf("imCallBack CallbackAfterNewMemberJoin RoomLivingIn GetUserByExtId err:%+v, MemberAccount:%v", err, externalId) // } // //添加用户在线列表 // err = group_m.RoomLivingIn(domain.CreateModelContext(myContext), groupId, user.ID, user.ExternalId, false) // if err != nil { // myContext.Log.Errorf("imCallBack CallbackAfterNewMemberJoin err:%+v, userId:%v", err, user.ID) // } else { // myContext.Log.Infof("imCallBack CallbackAfterNewMemberJoin RoomLivingIn success MemberAccount:%v, gi:%v", externalId, gi) // } //}() return myContext, nil } } // @Tags 群组 // @Summary 离开房间 // @Accept application/x-www-form-urlencoded // @Param token header string true "token" // @Param nonce header string true "随机数字" // @Param groupId formData string true "群ID" // @Success 200 // @Router /v1/imGroup/leave [post] func GroupLeave(c *gin.Context) (*mycontext.MyContext, error) { myContext := mycontext.CreateMyContext(c.Keys) userId, exteranlId, err := req.GetUserIdAndExtId(c, myContext) if err != nil { return myContext, err } groupId := c.PostForm("groupId") if len(groupId) <= 0 { return myContext, bizerr.InvalidParameter } model := domain.CreateModelContext(myContext) groupId, err = group_m.ToImGroupId(model, groupId) if err != nil { return myContext, err } if err := group_s.NewGroupService(myContext).GroupLeave(userId, exteranlId, groupId); err != nil { myContext.Log.Errorf("GroupLeave GroupLeave err:%v", err) return myContext, err } else { /* roomOnlineUser, err := cv.GetGroupInUser(domain.CreateModelContext(myContext), groupId) if err != nil { myContext.Log.Errorf("cron socketStatus cv.GetGroupInUser err:%v", err) } buf, err := json.Marshal(roomOnlineUser) if err != nil { myContext.Log.Errorf("cron socketStatus json.Marshal err:%v", err) } service.SendSignalMsg(groupId, group_m.GroupSystemMsg{ MsgId: group_enum.GroupOnlineUser, Content: string(buf), } , true)*/ } resp.ResponseOk(c, nil) return myContext, nil } // @Tags 群组 // @Summary 踢人 // @Accept application/x-www-form-urlencoded // @Param token header string true "token" // @Param nonce header string true "随机数字" // @Param groupId formData string true "群ID" // @Param externalId formData string true "用户的externalId" // @Success 200 // @Router /v1/imGroup/kick [post] func GroupKick(c *gin.Context) (*mycontext.MyContext, error) { myContext := mycontext.CreateMyContext(c.Keys) userId, externalId, nick, avatar, _, err := req.GetUserEx(c, myContext) if err != nil { return myContext, err } groupId := c.PostForm("groupId") if len(groupId) <= 0 { return myContext, bizerr.InvalidParameter } _, lang, err := req.GetUserIdLang(c, myContext) if err != nil { return myContext, err } model := domain.CreateModelContext(myContext) txGroupId := groupId groupId, err = group_m.ToImGroupId(model, groupId) if err != nil { return myContext, err } beKickExternalId := c.PostForm("externalId") beKickUser, err := user_c.GetUserByExternalId(domain.CreateModelContext(myContext), beKickExternalId) if err != nil { return myContext, err } isGaming, err := game_m.IsGaming(model, beKickUser.ID, txGroupId) if err != nil { return myContext, err } if isGaming { return myContext, bizerr.GamingCannotKick } if err = CheckOptToSvip6(model, userId, beKickUser.ID, lang, 10); err != nil { return myContext, err } //beKickUserId, err := toUserId(beKickExternalId) if err := group_s.NewGroupService(myContext).GroupKick(groupId, userId, externalId, nick, avatar, beKickUser.ID, beKickUser.ExternalId, beKickUser.Nick, beKickUser.Avatar, lang); err != nil { return myContext, err } resp.ResponseOk(c, nil) return myContext, nil }