package group_r import ( "git.hilo.cn/hilo-common/domain" "git.hilo.cn/hilo-common/mycontext" "git.hilo.cn/hilo-common/resource/redisCli" "git.hilo.cn/hilo-common/sdk/agora" "git.hilo.cn/hilo-common/sdk/tencentyun" "git.hilo.cn/hilo-common/utils" "github.com/gin-gonic/gin" uuid "github.com/satori/go.uuid" "gorm.io/gorm" "hilo-group/_const/enum/group_e" "hilo-group/_const/enum/msg_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/res_c" "hilo-group/domain/model/group_m" "hilo-group/domain/model/res_m" "hilo-group/domain/model/user_m" "hilo-group/domain/service/group_s" "hilo-group/myerr" "hilo-group/myerr/bizerr" "hilo-group/req" "hilo-group/resp" "sort" "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} 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} 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} 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 }