From d8f57d9c241170631b69b3dcf2093214ad22fa60 Mon Sep 17 00:00:00 2001 From: hujiebin Date: Mon, 6 Mar 2023 15:36:41 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E8=BF=98=E6=9C=89=E4=B8=80=E5=8D=8A?= =?UTF-8?q?=E7=9A=84group=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- _const/enum/group_e/enum.go | 16 + _const/enum/tim_e/tim.go | 12 + cv/group_cv/group.go | 16 + cv/user_cv/user.go | 160 ++++ domain/cache/group_c/group.go | 41 +- domain/event/group_ev/group_support.go | 58 ++ domain/model/diamond_m/diamond.go | 207 +++++ domain/model/game_m/game.go | 20 + domain/model/group_m/groupInvite.go | 68 ++ domain/model/mgr_m/banner.go | 182 +++++ domain/model/res_m/code.go | 20 + domain/model/tim_m/user.go | 396 ++++++++++ domain/model/user_m/country.go | 20 + domain/model/user_m/imei.go | 114 +++ domain/model/user_m/ip.go | 100 +++ domain/model/user_m/user.go | 12 + domain/model/version_m/version_control.go | 25 + domain/service/event_s/event_init.go | 73 ++ domain/service/group_power_s/group_power.go | 4 +- domain/service/group_s/group.go | 34 + domain/service/group_s/group_op.go | 73 ++ domain/service/group_s/group_support.go | 175 +++++ domain/service/signal_s/signal.go | 12 + go.mod | 1 + go.sum | 2 + myerr/bizerr/bizCode.go | 31 +- req/request.go | 47 ++ route/group_r/group_info.go | 652 ++++++++++++++++ route/group_r/group_list.go | 520 +++++++++++++ route/group_r/group_op.go | 657 ++++++++++++++++ route/group_r/group_setting.go | 806 ++++++++++++++++++++ route/group_r/group_support.go | 401 ++++++++++ route/router.go | 56 +- 33 files changed, 4974 insertions(+), 37 deletions(-) create mode 100644 _const/enum/tim_e/tim.go create mode 100644 domain/event/group_ev/group_support.go create mode 100644 domain/model/group_m/groupInvite.go create mode 100644 domain/model/mgr_m/banner.go create mode 100644 domain/model/tim_m/user.go create mode 100644 domain/model/user_m/country.go create mode 100644 domain/model/user_m/imei.go create mode 100644 domain/model/user_m/ip.go create mode 100644 domain/model/version_m/version_control.go create mode 100644 domain/service/group_s/group_support.go create mode 100644 route/group_r/group_info.go create mode 100644 route/group_r/group_setting.go create mode 100644 route/group_r/group_support.go diff --git a/_const/enum/group_e/enum.go b/_const/enum/group_e/enum.go index 3331bf6..99a7574 100644 --- a/_const/enum/group_e/enum.go +++ b/_const/enum/group_e/enum.go @@ -139,3 +139,19 @@ var GROUP_INTRODUCTION_LENGTH_LIMIT = 500 const ROOM_PASSWORD_LENGTH = 4 const SETTING_CUSTOMIZED = 2 const SETTING_OFFICIAL = 1 + +type AddGroupResultType = uint + +const ( + ADD_GROUP_DONE AddGroupResultType = 1 + ADD_GROUP_DUPLICATE AddGroupResultType = 2 + ADD_GROUP_PENDING AddGroupResultType = 3 + ADD_GROUP_FAILED AddGroupResultType = 0 +) + +var GROUP_DEFAULT_JOIN_LIMIT uint = 200 +var GROUP_VIP_JOIN_LIMIT uint = 500 +var GROUP_NOBLE4_JOIN_LIMIT uint = 1000 +var GROUP_ROLE_PERSONAL_VIEW_LIMIT = 5 +var GROUP_MANAGER_LIMIT uint = 30 +var GROUP_ADMIN_LIMIT uint = 50 diff --git a/_const/enum/tim_e/tim.go b/_const/enum/tim_e/tim.go new file mode 100644 index 0000000..f37c9e7 --- /dev/null +++ b/_const/enum/tim_e/tim.go @@ -0,0 +1,12 @@ +package tim_e + +// TIM相关常量 +const ( + SYNC_TO_SENDER = 1 + NOSYNC_TO_SENDER = 2 + + TAG_PROFILE_IM_LEVEL = "Tag_Profile_IM_Level" + TAG_PROFILE_IM_NICK = "Tag_Profile_IM_Nick" + TAG_PROFILE_IM_IMAGE = "Tag_Profile_IM_Image" + TAG_PROFILE_IM_HILO = "Tag_Profile_Custom_Hilo" +) diff --git a/cv/group_cv/group.go b/cv/group_cv/group.go index 1618a9f..d0da564 100644 --- a/cv/group_cv/group.go +++ b/cv/group_cv/group.go @@ -17,6 +17,7 @@ import ( "hilo-group/domain/model/res_m" "hilo-group/domain/model/rocket_m" "hilo-group/domain/service/group_s" + "hilo-group/myerr" "sort" ) @@ -379,3 +380,18 @@ func BuildJoinedGroupInfo(myService *domain.Service, myUserId uint64, groupIds [ } return result, len(groupInfo), nil } + +//检查群组中是否有有人在麦上,返回存在的Set[imGroupId] +func CheckMicHasUserByGroup(groupIds []string) (map[string]struct{}, error) { + groupMap := map[string]struct{}{} + for i := 0; i < len(groupIds); i++ { + flag, err := group_m.CheckGroupMicHasUser(groupIds[i]) + if err != nil { + return nil, myerr.WrapErr(err) + } + if flag { + groupMap[groupIds[i]] = struct{}{} + } + } + return groupMap, nil +} diff --git a/cv/user_cv/user.go b/cv/user_cv/user.go index 4a6525d..89d2c24 100644 --- a/cv/user_cv/user.go +++ b/cv/user_cv/user.go @@ -1033,3 +1033,163 @@ func GetUserBases(userIds []mysql.ID, myUserId mysql.ID) ([]*CvUserBase, error) } return cvUserBases, nil } + +func BatchGetUserExtend(model *domain.Model, userIds []mysql.ID, myUserId mysql.ID) (map[mysql.ID]CvUserExtend, error) { + ub, err := GetUserBases(userIds, myUserId) + if err != nil { + return nil, err + } + wg, err := BatchGetWealthGrade(userIds) + if err != nil { + return nil, err + } + cg, err := BatchGetCharmGrade(userIds) + if err != nil { + return nil, err + } + ag, err := BatchGetActiveGrade(userIds) + if err != nil { + return nil, err + } + result := make(map[mysql.ID]CvUserExtend, 0) + for _, i := range ub { + result[*i.Id] = CvUserExtend{ + CvUserBase: *i, + WealthGrade: wg[*i.Id], + CharmGrade: cg[*i.Id], + ActiveGrade: ag[*i.Id], + } + } + return result, nil +} + +func GetUserBase(userId mysql.ID, myUserId mysql.ID) (*CvUserBase, error) { + var user user_m.User + if err := mysql.Db.First(&user, userId).Error; err != nil { + return nil, myerr.WrapErr(err) + } + + isVip, expireTime, err := user_m.IsVip(userId) + if err != nil { + return nil, myerr.WrapErr(err) + } + + cvHeadwear, err := headwear_cv.GetCvHeadwear(userId) + if err != nil { + return nil, err + } + + logger := mylogrus.MyLog.WithField("func", "GetUserBase") + medals, err := user_m.GetUserMedalMerge(logger, mysql.Db, userId) + if err != nil { + return nil, err + } + + medals, medalInfo, err := getMedalInfo(mysql.Db, medals) + if err != nil { + return nil, err + } + + up := user_m.UserProperty{} + rides, err := up.BatchGet(mysql.Db, []uint64{userId}) + if err != nil { + return nil, err + } + + rp := res_m.ResProperty{} + properties, err := rp.GetAll(mysql.Db) + if err != nil { + return nil, err + } + + superManagerMap, err := user_m.GetSuperManagerMap([]uint64{userId}) + if err != nil { + return nil, err + } + + cvUserBase := CvUserBase{ + Id: &user.ID, + Avatar: StrNil(IfLogoutStr(IfLogout(user.LogoutTime), "", user.Avatar)), + DefaultAvatar: &user.DefaultAvatar, + ExternalId: StrToString(&user.ExternalId), + Nick: StrNil(IfLogoutNick(IfLogout(user.LogoutTime), user.Code, user.Nick)), + Description: StrNil(IfLogoutStr(IfLogout(user.LogoutTime), "", user.Description)), + Sex: TypeToUint8((*mysql.Type)(&user.Sex)), + Country: StrNil(user.Country), + CountryIcon: StrNil(user.CountryIcon), + Code: StrToString(&user.Code), + IsPrettyCode: user.IsPrettyCode(), + IsVip: isVip, + IsOfficialStaff: superManagerMap[user.ID], + Medals: IfLogoutMedals(IfLogout(user.LogoutTime), []uint32{}, medals), + MedalInfo: IfLogoutMedalInfo(IfLogout(user.LogoutTime), []medal_cv.CvMedal{}, medalInfo), + Headwear: IfLogoutHeadwear(IfLogout(user.LogoutTime), nil, cvHeadwear), + Ride: IfLogoutRide(IfLogout(user.LogoutTime), property_cv.CvProperty{}, property_cv.CvProperty{ + Id: rides[user.ID], + PicUrl: properties[rides[user.ID]].PicUrl, + EffectUrl: properties[rides[user.ID]].EffectUrl, + }), + } + + noble, err := noble_m.FindActiveNoble(mysql.Db, userId) + if err != nil { + return nil, err + } + + if noble != nil { + cvUserBase.Noble = noble_cv.CvNoble{ + Level: noble.Level, + EndTime: noble.EndTime.Unix(), + } + } + //本人 + if userId == myUserId { + cvUserBase.VipExpireTime = expireTime + cvUserBase.IsShowAge = TypeToUint8((*mysql.Type)(&user.IsShowAge)) + cvUserBase.Birthday = BirthdayToUint64(&user.Birthday) + isAgent := user_m.IsAgent(myUserId) + cvUserBase.IsAgentMgr = &isAgent + } else if user.IsShowAge == mysql.OPEN { + cvUserBase.Birthday = BirthdayToUint64(&user.Birthday) + } + + //判断是不是工会 + userTradeUnion, err := user_m.GetUserTradeUnion(myUserId) + if err != nil { + return nil, err + } + if userTradeUnion == nil { + isTradeUnionFlag := false + cvUserBase.IsTradeUnion = &isTradeUnionFlag + cvUserBase.IsTradeUnionMatchNotification = nil + } else { + isTradeUnionFlag := true + cvUserBase.IsTradeUnion = &isTradeUnionFlag + isTradeUnionMatchNotificationFlag := userTradeUnion.MatchNotification == mysql.OPEN + cvUserBase.IsTradeUnionMatchNotification = &isTradeUnionMatchNotificationFlag + } + + return &cvUserBase, nil +} + +func getMedalInfo(db *gorm.DB, medals []uint32) ([]uint32, []medal_cv.CvMedal, error) { + resMedals, err := res_m.MedalGetAllMap(db) + if err != nil { + return nil, nil, err + } + + // 只选择合法的勋章 + validMedals := make([]uint32, 0) + medalInfo := make([]medal_cv.CvMedal, 0) + for _, i := range medals { + if e, ok := resMedals[i]; ok { + validMedals = append(validMedals, i) + medalInfo = append(medalInfo, medal_cv.CvMedal{ + Id: i, + PicUrl: e.PicUrl, + EffectUrl: e.SvgaUrl, + }) + } + } + return validMedals, medalInfo, nil +} diff --git a/domain/cache/group_c/group.go b/domain/cache/group_c/group.go index 8c73595..1dd9d5c 100644 --- a/domain/cache/group_c/group.go +++ b/domain/cache/group_c/group.go @@ -57,11 +57,27 @@ func SetExists(groupId string) (int64, error) { return redisCli.RedisClient.Exists(context.Background(), key).Result() } -func AddGroupMember(groupId string, extIds []string) (int64, error) { +func addGroupMember(groupId string, extIds []string) (int64, error) { key := getGroupMemberKey(groupId) return redisCli.RedisClient.SAdd(context.Background(), key, extIds).Result() } +func AddGroupMember(model *domain.Model, groupId string, extId string) error { + key := getGroupMemberKey(groupId) + ret, err := redisCli.RedisClient.Exists(context.Background(), key).Result() + if err != nil { + model.Log.Infof("AddGroupMember %s, skip because set does not exist", groupId) + return err + } + if ret == 0 { + // 集合不存在,不要加进去! + return nil + } + ret, err = addGroupMember(groupId, []string{extId}) + model.Log.Infof("AddGroupMember %s, %s ret = %d, err: %v", groupId, extId, ret, err) + return err +} + func RemoveGroupMember(groupId string, externalId string) (int64, error) { key := getGroupMemberKey(groupId) return redisCli.RedisClient.SRem(context.Background(), key, externalId).Result() @@ -139,3 +155,26 @@ func SetGroupInfoCache(model *domain.Model, info *group_m.GroupInfo) error { } return nil } + +// 增加领取群组扶持ip次数 +func IncrGroupSupportAwardIp(model *domain.Model, ip mysql.Str) (times int64, err error) { + key := group_k.GetGroupSupportAwardIpKey(ip) + times, err = model.Redis.Incr(model, key).Result() + if err != nil { + model.Log.Errorf("IncrGroupSupportAwardIp fail,ip:%v,err:%v", ip, err) + } else { + model.Redis.Expire(model, key, time.Hour*24*6) // 1周1次,ttl=6天吧 + } + return +} + +// 获取领取群组扶持ip次数 +func GetGroupSupportAwardIpTimes(model *domain.Model, ip mysql.Str) (times int64, err error) { + key := group_k.GetGroupSupportAwardIpKey(ip) + times, err = model.Redis.Get(model, key).Int64() + if err != nil && err != redis.Nil { + model.Log.Errorf("GetGroupSupportAwardIpTimes fail,ip:%v,err:%v", ip, err) + return + } + return times, nil +} diff --git a/domain/event/group_ev/group_support.go b/domain/event/group_ev/group_support.go new file mode 100644 index 0000000..6052247 --- /dev/null +++ b/domain/event/group_ev/group_support.go @@ -0,0 +1,58 @@ +package group_ev + +import ( + "git.hilo.cn/hilo-common/domain" + "git.hilo.cn/hilo-common/resource/mysql" +) + +type GroupSupportEvent struct { + GroupSupportAwardAdmin GroupSupportItem + GroupCode string + GroupSupportAwardMgrs []GroupSupportItem +} + +type GroupSupportItem struct { + GroupSupportAwardId mysql.ID + UserId mysql.ID + DiamondNum uint32 +} + +func InitGroupSupportEvent(size int, groupCode string) *GroupSupportEvent { + return &GroupSupportEvent{GroupSupportAwardMgrs: make([]GroupSupportItem, 0, size), GroupCode: groupCode} +} + +func (groupSupportEvent *GroupSupportEvent) AddAdmin(groupSupportAwardId mysql.ID, userId mysql.ID, diamondNum uint32) *GroupSupportEvent { + groupSupportEvent.GroupSupportAwardAdmin = GroupSupportItem{ + GroupSupportAwardId: groupSupportAwardId, + UserId: userId, + DiamondNum: diamondNum, + } + return groupSupportEvent +} + +func (groupSupportEvent *GroupSupportEvent) AddMgr(groupSupportAwardId mysql.ID, userId mysql.ID, diamondNum uint32) *GroupSupportEvent { + groupSupportEvent.GroupSupportAwardMgrs = append(groupSupportEvent.GroupSupportAwardMgrs, GroupSupportItem{ + GroupSupportAwardId: groupSupportAwardId, + UserId: userId, + DiamondNum: diamondNum, + }) + return groupSupportEvent +} + +//注册监听 +var groupSupportListen = new(domain.EventBase) + +//添加领域事件,在每个领域模型中init中添加,因为这是静态业务,非动态的。 +func AddGroupSupportSync(callback func(model *domain.Model, event interface{}) error) { + domain.AddEventSync(groupSupportListen, callback) +} + +//加入到异步操作中 +func AddGroupSupportAsync(callback func(model *domain.Model, event interface{}) error) { + domain.AddEventAsync(groupSupportListen, callback) +} + +//领域事件发布 +func PublishGroupSupport(model *domain.Model, event interface{}) error { + return domain.PublishEvent(groupSupportListen, model, event) +} diff --git a/domain/model/diamond_m/diamond.go b/domain/model/diamond_m/diamond.go index a8bb4b0..501d9bd 100644 --- a/domain/model/diamond_m/diamond.go +++ b/domain/model/diamond_m/diamond.go @@ -2,9 +2,16 @@ package diamond_m import ( "git.hilo.cn/hilo-common/domain" + "git.hilo.cn/hilo-common/mylogrus" "git.hilo.cn/hilo-common/resource/mysql" + "git.hilo.cn/hilo-common/utils" + "gorm.io/gorm" "hilo-group/_const/enum/diamond_e" + "hilo-group/domain/model" "hilo-group/myerr" + "hilo-group/myerr/bizerr" + "strconv" + "time" ) type DiamondAccount struct { @@ -61,3 +68,203 @@ func GetDiamondAccountByUserId(model *domain.Model, userId mysql.ID) (*DiamondAc diamondAccount.Model = model return &diamondAccount, nil } + +//账号操作配置 +type DiamondOperateSet struct { + mysql.Entity + *domain.Model `gorm:"-"` + DiamondNum mysql.NumAll + FrequencyNum mysql.NumAll + FrequencyDay mysql.NumAll + DiamondMaxNum mysql.NumAll + AddReduce mysql.AddReduce + Type diamond_e.OperateType + Name mysql.Str + Status mysql.UserYesNo + DiamondType diamond_e.OperateType +} + +//匹配条件扣费 +func (diamondAccount *DiamondAccount) ChangeDiamondAccountDetail(operateType diamond_e.OperateType, originId mysql.ID, diamondNum mysql.Num) (*DiamondAccountDetail, error) { + return diamondAccount.addDiamondAccountDetail(operateType, originId, diamondNum) +} + +//钻石操作记录, +func (diamondAccount *DiamondAccount) addDiamondAccountDetail(operateType diamond_e.OperateType, originId mysql.ID, diamondNum mysql.Num) (*DiamondAccountDetail, error) { + var diamondOperateSet DiamondOperateSet + var err error + if err = diamondAccount.Db.Where(&DiamondOperateSet{ + Type: operateType, + Status: mysql.USER, + DiamondType: 1, // 1:黄钻 2:粉钻 + }).First(&diamondOperateSet).Error; err != nil { + return nil, myerr.WrapErr(err) + } + + //判断是增加,账号是否被冻结 + if diamondAccount.Status == diamond_e.Frozen && diamondOperateSet.AddReduce == mysql.REDUCE { + return nil, bizerr.DiamondAccountFrozen + } + + //无限,检查次数 + var count int64 + if diamondOperateSet.FrequencyDay == -1 { + if diamondOperateSet.FrequencyNum != -1 { + diamondAccount.Db.Model(&DiamondAccountDetail{}).Where(&DiamondAccountDetail{ + UserId: diamondAccount.UserId, + OperateType: operateType, + }).Count(&count) + if count >= int64(diamondOperateSet.FrequencyNum) { + return nil, bizerr.DiamondFrequency + //return nil, myerr.NewSysError("钻石操作次数多大, userId:" + mysql.IdToStr(diamondAccount.UserId) + " diamondOperateSetId" + mysql.IdToStr(diamondOperateSet.ID)) + } + } + } else if diamondOperateSet.FrequencyDay == 1 { + beginTime, err := time.ParseInLocation("2006-01-02", time.Now().Format("2006-01-02"), time.Local) + if err != nil { + return nil, myerr.WrapErr(err) + } + //一天的次数 + diamondAccount.Db.Model(&DiamondAccountDetail{}).Where(&DiamondAccountDetail{ + UserId: diamondAccount.UserId, + OperateType: operateType, + }).Where("created_time >= ? ", beginTime).Count(&count) + if count >= int64(diamondOperateSet.FrequencyNum) { + return nil, bizerr.DiamondFrequency + } + //终极拦截,利用 + diamondAccount.SetCheckUpdateCondition(" EXISTS (SELECT * from (SELECT COUNT(1) as n from diamond_account_detail d where d.user_id = " + strconv.FormatUint(diamondAccount.UserId, 10) + " and d.operate_type = " + strconv.FormatUint(uint64(operateType), 10) + " and d.created_time >= from_unixtime(" + strconv.FormatInt(utils.GetZeroTime(time.Now()).Unix(), 10) + ")) t where t.n < " + strconv.Itoa(diamondOperateSet.FrequencyNum) + " )") + } + + //-1,代表值无效,由参数给与 + var upateDiamondNum mysql.Num + if diamondOperateSet.DiamondNum == -1 { + upateDiamondNum = diamondNum + } else { + upateDiamondNum = mysql.Num(diamondOperateSet.DiamondNum) + } + + var afterNum mysql.Num + if diamondOperateSet.AddReduce == mysql.ADD { + afterNum = diamondAccount.DiamondNum + upateDiamondNum + } else if diamondOperateSet.AddReduce == mysql.REDUCE { + if diamondAccount.DiamondNum < upateDiamondNum { + return nil, bizerr.DiamondNoEnough + } + afterNum = diamondAccount.DiamondNum - upateDiamondNum + } else { + return nil, myerr.NewSysError("AddReduce 值错误:" + mysql.TypeToString(mysql.Type(diamondOperateSet.AddReduce))) + } + + diamondAccountDetail := &DiamondAccountDetail{ + Model: diamondAccount.Model, + UserId: diamondAccount.UserId, + DiamondAccountId: diamondAccount.ID, + OperateId: diamondOperateSet.ID, + OperateType: diamondOperateSet.Type, + OriginId: originId, + AddReduce: diamondOperateSet.AddReduce, + Num: upateDiamondNum, + Remark: diamondOperateSet.Name, + BefNum: diamondAccount.DiamondNum, + AftNum: afterNum, + diamondAccount: diamondAccount, + } + return diamondAccountDetail, err +} + +func (diamondAccountDetail *DiamondAccountDetail) PersistentNoInTransactional() error { + //fixme: 这里有点奇怪, diamondAccount持久化动作在diamondAccountDetail持久化之后,RowsAffected 就一定是0 + txDiamondAccount := diamondAccountDetail.Db.Model(diamondAccountDetail.diamondAccount) + if diamondAccountDetail.diamondAccount.CheckUpdateCondition() { + txDiamondAccount = txDiamondAccount.Where(diamondAccountDetail.diamondAccount.GetUpdateCondition()) + } + if diamondAccountDetail.AddReduce == mysql.ADD { + //增加 + txDiamondAccount.UpdateColumn("diamond_num", gorm.Expr("diamond_num + ?", diamondAccountDetail.Num)) + } else if diamondAccountDetail.AddReduce == mysql.REDUCE { + //减少,保证不能扣成负数 + txDiamondAccount.Where("diamond_num >= ?", diamondAccountDetail.Num).UpdateColumn("diamond_num", gorm.Expr("diamond_num - ?", diamondAccountDetail.Num)) + } else { + myerr.NewSysError("addReduce 枚举错误 value:" + mysql.TypeToString(mysql.Type(diamondAccountDetail.AddReduce))) + } + if err := txDiamondAccount.Error; err != nil { + return myerr.WrapErr(err) + } + if txDiamondAccount.RowsAffected == 0 { + diamondAccountDetail.Log.Errorf("gorm condition update.RowsAffected = 0,AddReduce:%v", diamondAccountDetail.AddReduce) + return myerr.NewWaring("gorm condition update.RowsAffected = 0") + } + + //持久化diamondAccountDetail + if err := model.Persistent(diamondAccountDetail.Db, diamondAccountDetail); err != nil { + return myerr.WrapErr(err) + } + //改变diamondAccount值 + if diamondAccountDetail.diamondAccount == nil { + return myerr.NewSysError("持久化错误, 模型:DiamondAccountDetail 中没有diamondAccount, DiamondAccountDetail.Id =" + strconv.Itoa(int(diamondAccountDetail.ID))) + } + + var newDiamondAccount DiamondAccount + if err := diamondAccountDetail.Db.First(&newDiamondAccount, diamondAccountDetail.diamondAccount.ID).Error; err != nil { + return myerr.WrapErr(err) + } + + if newDiamondAccount.DiamondNum < 0 { + return myerr.NewSysError("diamond_account表中,diamond_num 不能小于0, diamondAccount.id = " + strconv.Itoa(int(newDiamondAccount.ID))) + } + return nil +} + +//群组支持 +func (diamondAccount *DiamondAccount) GroupSupportAdmin(groupSupportAwardId mysql.ID, diamondNum uint32) (*DiamondAccountDetail, error) { + return diamondAccount.addDiamondAccountDetail(diamond_e.GroupSupportAdmin, groupSupportAwardId, diamondNum) +} + +func (diamondAccountDetail *DiamondAccountDetail) Persistent() error { + //fixme: 这里有点奇怪, diamondAccount持久化动作在diamondAccountDetail持久化之后,RowsAffected 就一定是0 + txDiamondAccount := diamondAccountDetail.Db.Model(diamondAccountDetail.diamondAccount) + if diamondAccountDetail.diamondAccount.CheckUpdateCondition() { + txDiamondAccount = txDiamondAccount.Where(diamondAccountDetail.diamondAccount.GetUpdateCondition()) + } + if diamondAccountDetail.AddReduce == mysql.ADD { + //增加 + txDiamondAccount.UpdateColumn("diamond_num", gorm.Expr("diamond_num + ?", diamondAccountDetail.Num)) + } else if diamondAccountDetail.AddReduce == mysql.REDUCE { + //减少,保证不能扣成负数 + txDiamondAccount.Where("diamond_num >= ?", diamondAccountDetail.Num).UpdateColumn("diamond_num", gorm.Expr("diamond_num - ?", diamondAccountDetail.Num)) + } else { + myerr.NewSysError("addReduce 枚举错误 value:" + mysql.TypeToString(mysql.Type(diamondAccountDetail.AddReduce))) + } + if err := txDiamondAccount.Error; err != nil { + return myerr.WrapErr(err) + } + if txDiamondAccount.RowsAffected == 0 { + mylogrus.MyLog.Errorf("gorm condition update.RowsAffected = 0,AddReduce:%v", diamondAccountDetail.AddReduce) + return myerr.NewWaring("gorm condition update.RowsAffected = 0") + } + + //持久化diamondAccountDetail + if err := model.Persistent(diamondAccountDetail.Db, diamondAccountDetail); err != nil { + return myerr.WrapErr(err) + } + //改变diamondAccount值 + if diamondAccountDetail.diamondAccount == nil { + return myerr.NewSysError("持久化错误, 模型:DiamondAccountDetail 中没有diamondAccount, DiamondAccountDetail.Id =" + strconv.Itoa(int(diamondAccountDetail.ID))) + } + + var newDiamondAccount DiamondAccount + if err := diamondAccountDetail.Db.First(&newDiamondAccount, diamondAccountDetail.diamondAccount.ID).Error; err != nil { + return myerr.WrapErr(err) + } + + if newDiamondAccount.DiamondNum < 0 { + return myerr.NewSysError("diamond_account表中,diamond_num 不能小于0, diamondAccount.id = " + strconv.Itoa(int(newDiamondAccount.ID))) + } + return nil +} + +//群组支持 +func (diamondAccount *DiamondAccount) GroupSupportMgr(groupSupportAwardId mysql.ID, diamondNum uint32) (*DiamondAccountDetail, error) { + return diamondAccount.addDiamondAccountDetail(diamond_e.GroupSupportMgr, groupSupportAwardId, diamondNum) +} diff --git a/domain/model/game_m/game.go b/domain/model/game_m/game.go index 0443b93..7e0afb7 100644 --- a/domain/model/game_m/game.go +++ b/domain/model/game_m/game.go @@ -4,6 +4,7 @@ import ( "git.hilo.cn/hilo-common/domain" "gorm.io/gorm" "hilo-group/_const/enum/game_e" + "hilo-group/myerr" "time" ) @@ -78,3 +79,22 @@ func GetNotEndGamesMap(model *domain.Model) map[string][]game_e.GameType { } return res } + +func IsGaming(model *domain.Model, userId uint64, txGroupId string) (bool, error) { + ids := make([]uint64, 0) + err := model.DB().Table("game_info").Where("tx_group_id = ? and battle_end_at = 0 and status in (0, 1)", txGroupId).Limit(1).Pluck("id", &ids).Error + if err != nil { + return false, myerr.WrapErr(err) + } + if len(ids) > 0 { + userIds := make([]uint64, 0) + err = model.DB().Table("game_player").Where("game_id = ? and user_id = ? and status in (0,1)", ids[0], userId).Limit(1).Pluck("id", &userIds).Error + if err != nil { + return false, myerr.WrapErr(err) + } + if len(userIds) > 0 { + return true, nil + } + } + return false, nil +} diff --git a/domain/model/group_m/groupInvite.go b/domain/model/group_m/groupInvite.go new file mode 100644 index 0000000..ca9e7f8 --- /dev/null +++ b/domain/model/group_m/groupInvite.go @@ -0,0 +1,68 @@ +package group_m + +import ( + "git.hilo.cn/hilo-common/domain" + "gorm.io/gorm" + "hilo-group/myerr" + "hilo-group/myerr/bizerr" + "time" +) + +type GroupInviteJoin struct { + Id uint64 `json:"id"` + UserId uint64 `json:"user_id"` + ImGroupId string `json:"im_group_id"` + CreatedTime time.Time `json:"created_time"` + UpdatedTime time.Time `json:"updated_time"` + InviteUserId uint64 `json:"invite_user_id"` + IsAccept int8 `json:"is_accept"` +} + +func GetByImGroupId(model *domain.Model, imGroupId string) (*GroupInfo, error) { + if len(imGroupId) <= 0 { + return nil, myerr.WrapErr(bizerr.GroupNotFound) + } + + res := new(GroupInfo) + err := model.DB().Where(&GroupInfo{ImGroupId: imGroupId}).First(&res).Error + if err != nil { + if err == gorm.ErrRecordNotFound { + return nil, nil + } else { + return nil, myerr.WrapErr(err) + } + } + return res, nil +} + +func AcceptGroupInviteJoin(model *domain.Model, userId uint64, imGroupId string) error { + err := model.DB().Exec("update group_invite_join set is_accept = 1 where user_id=? and im_group_id=?", userId, imGroupId).Error + if err != nil { + return myerr.WrapErr(err) + } + return nil +} + +func GetGroupInviteJoin(model *domain.Model, userId uint64, imGroupId string) (*GroupInviteJoin, error) { + res := new(GroupInviteJoin) + err := model.DB().Where(&GroupInviteJoin{UserId: userId, ImGroupId: imGroupId}).Where("is_accept=0").First(&res).Error + if err != nil { + if err == gorm.ErrRecordNotFound { + return nil, nil + } else { + return nil, myerr.WrapErr(err) + } + } + return res, nil +} + +func InsertGroupInviteJoin(model *domain.Model, userId, inviteUserId uint64, imGroupId string) error { + nowTime := time.Now() + sql := "insert into group_invite_join(user_id,invite_user_id,im_group_id,is_accept,created_time,updated_time) " + + "value(?,?,?,?,?,?) on duplicate key update is_accept=?,created_time=?,updated_time=?" + err := model.DB().Exec(sql, userId, inviteUserId, imGroupId, 0, nowTime, nowTime, 0, nowTime, nowTime).Error + if err != nil { + return myerr.WrapErr(bizerr.GroupNotFound) + } + return nil +} diff --git a/domain/model/mgr_m/banner.go b/domain/model/mgr_m/banner.go new file mode 100644 index 0000000..c8c8841 --- /dev/null +++ b/domain/model/mgr_m/banner.go @@ -0,0 +1,182 @@ +package mgr_m + +import ( + "database/sql/driver" + "git.hilo.cn/hilo-common/domain" + "git.hilo.cn/hilo-common/resource/mysql" + "gorm.io/gorm" + "hilo-group/myerr" + "strings" + "time" +) + +type Strs []string + +func (this *Strs) Scan(val interface{}) error { + s := val.([]uint8) + ss := strings.Split(string(s), ",") + *this = ss + return nil +} + +func (this Strs) Value() (driver.Value, error) { + str := strings.Join(this, ",") + return str, nil +} + +//群组Banner +type GroupBanner struct { + mysql.Entity + *domain.Model `gorm:"-"` + Title mysql.Str + Image mysql.Str + Url mysql.Str + GroupCode mysql.Str //群组code + N mysql.Num + Status mysql.UserYesNo + Area int // 上线区域:0全区;1阿语区;2非阿语区;3指定国家 + CountryList Strs // 指定区域的国家列表 + StartAt int64 // 上架时间戳 + EndAt int64 // 下架时间戳 +} + +func GetGroupBanner(model *domain.Model, id mysql.ID) (*GroupBanner, error) { + groupBanner := GroupBanner{} + if err := model.Db.Model(&GroupBanner{}).First(&groupBanner, id).Error; err != nil { + return &groupBanner, myerr.WrapErr(err) + } + groupBanner.Model = model + return &groupBanner, nil +} + +//加入 +func AddGroupBanner(model *domain.Model, title mysql.Str, image mysql.Str, url mysql.Str, groupCode mysql.Str, n mysql.Num) *GroupBanner { + return &GroupBanner{ + Model: model, + Title: title, + Image: image, + Url: url, + GroupCode: groupCode, + N: n, + Status: mysql.USER, + } +} + +//更新 +func (groupBanner *GroupBanner) Update(title mysql.Str, image mysql.Str, url mysql.Str, groupCode mysql.Str, n mysql.Num) *GroupBanner { + groupBanner.Title = title + groupBanner.Image = image + groupBanner.Url = url + groupBanner.GroupCode = groupCode + groupBanner.N = n + return groupBanner +} + +//上架 +func (groupBanner *GroupBanner) User() *GroupBanner { + groupBanner.Status = mysql.USER + return groupBanner +} + +//下架 +func (groupBanner *GroupBanner) NoUser() *GroupBanner { + groupBanner.Status = mysql.NOUSER + return groupBanner +} + +// 房间Banner +type RoomBanner struct { + mysql.Entity + Title mysql.Str + Image mysql.Str + Url mysql.Str + N mysql.Num + Status mysql.UserYesNo + Area int // 上线区域:0全区;1阿语区;2非阿语区;3指定国家 + CountryList Strs // 指定区域的国家列表 +} + +func (rb *RoomBanner) GetRoomBanners(db *gorm.DB, vcAllow bool) ([]RoomBanner, error) { + rows := make([]RoomBanner, 0) + if err := db.Model(&RoomBanner{}).Where(rb).Order("n DESC").Find(&rows).Error; err != nil { + return nil, err + } + res := make([]RoomBanner, 0, len(rows)) + for _, v := range rows { + if !vcAllow && v.ID == 241 { + continue + } + res = append(res, v) + } + return res, nil +} + +func (rb *RoomBanner) Create(db *gorm.DB) error { + return db.Save(rb).Error +} + +func (rb *RoomBanner) Save(db *gorm.DB) error { + return db.Omit("status").Save(rb).Error +} + +func (rb *RoomBanner) SetStatus(db *gorm.DB, status uint8) error { + return db.Model(&RoomBanner{}).Where(rb).Update("status", status).Error +} + +type DiscoveryBanner struct { + mysql.Entity + *domain.Model `gorm:"-"` + Title mysql.Str + Image mysql.Str + Url mysql.Str + GroupCode mysql.Str + N mysql.Num + BeginTime time.Time + EndTime time.Time + Area int // 上线区域:0全区;1阿语区;2非阿语区;3指定国家 + CountryList Strs // 指定区域的国家列表 +} + +func GetDiscoveryBannerOrErr(model *domain.Model, id mysql.ID) (*DiscoveryBanner, error) { + discoveryBanner := DiscoveryBanner{} + if err := model.Db.Model(&DiscoveryBanner{}).First(&discoveryBanner, id).Error; err != nil { + return &discoveryBanner, myerr.WrapErr(err) + } + discoveryBanner.Model = model + return &discoveryBanner, nil +} + +func InitDiscoveryBanner(model *domain.Model, title mysql.Str, image mysql.Str, url mysql.Str, groupCode mysql.Str, n mysql.Num, beginTime time.Time, endTime time.Time) *DiscoveryBanner { + return &DiscoveryBanner{ + Model: model, + Title: title, + Image: image, + Url: url, + GroupCode: groupCode, + N: n, + BeginTime: beginTime, + EndTime: endTime, + } +} + +//更新 +func (discoveryBanner *DiscoveryBanner) Update(title mysql.Str, image mysql.Str, url mysql.Str, groupCode mysql.Str, n mysql.Num, beginTime time.Time, endTime time.Time) *DiscoveryBanner { + discoveryBanner.Title = title + discoveryBanner.Image = image + discoveryBanner.Url = url + discoveryBanner.GroupCode = groupCode + discoveryBanner.N = n + discoveryBanner.BeginTime = beginTime + discoveryBanner.EndTime = endTime + return discoveryBanner +} + +func (discoveryBanner *DiscoveryBanner) EditEndTime(time time.Time) *DiscoveryBanner { + discoveryBanner.EndTime = time + return discoveryBanner +} + +func (discoveryBanner *DiscoveryBanner) EditBeginTime(time time.Time) *DiscoveryBanner { + discoveryBanner.BeginTime = time + return discoveryBanner +} diff --git a/domain/model/res_m/code.go b/domain/model/res_m/code.go index 1ef06fd..c3e2a57 100644 --- a/domain/model/res_m/code.go +++ b/domain/model/res_m/code.go @@ -1,8 +1,11 @@ package res_m import ( + "fmt" "git.hilo.cn/hilo-common/domain" "git.hilo.cn/hilo-common/resource/mysql" + "gorm.io/gorm" + "hilo-group/_const/enum/msg_e" "hilo-group/myerr" ) @@ -28,3 +31,20 @@ func CheckCode(model *domain.Model, code string) (bool, error) { return false, nil } } + +func GetErrByLanguage(db *gorm.DB, msgId msg_e.MsgIdType, lang string, myErr *myerr.BusinessError, args ...interface{}) *myerr.BusinessError { + var msg string + if len(args) > 0 { + msg = fmt.Sprintf(myErr.GetMsg(), args...) + } else { + msg = myErr.GetMsg() + } + if resMul, _ := GetResMultiTextBy(db, msgId, lang); resMul != nil { + if len(args) > 0 { + msg = fmt.Sprintf(resMul.Content, args...) + } else { + msg = resMul.Content + } + } + return myerr.NewBusinessCodeNoCheck(myErr.GetCode(), msg, myerr.BusinessData{}) +} diff --git a/domain/model/tim_m/user.go b/domain/model/tim_m/user.go new file mode 100644 index 0000000..70d27ff --- /dev/null +++ b/domain/model/tim_m/user.go @@ -0,0 +1,396 @@ +package tim_m + +import ( + "context" + "encoding/json" + "git.hilo.cn/hilo-common/domain" + "git.hilo.cn/hilo-common/mylogrus" + "git.hilo.cn/hilo-common/resource/redisCli" + "git.hilo.cn/hilo-common/sdk/tencentyun" + "hilo-group/_const/enum/msg_e" + "hilo-group/_const/enum/online_e" + "hilo-group/_const/enum/tim_e" + "hilo-group/_const/redis_key" + "hilo-group/domain/model/groupPower_m" + "hilo-group/domain/model/res_m" + "hilo-group/domain/model/user_m" + "runtime/debug" + "strconv" + "time" +) + +func FlushGrades(userExtId string, wealthGrade uint32, charmGrade uint32) error { + level := (charmGrade & 0x000000FF << 8) | wealthGrade&0x000000FF + + return tencentyun.SetUserLevel(userExtId, level) +} + +func SyncWealthGrade(userExtId string, wealthGrade uint32) error { + level, err := tencentyun.GetUserLevel(userExtId) + if err != nil { + return err + } + mylogrus.MyLog.Infof("SyncWealthGrade, user %s, level before = %x, wealthGrade = %d", userExtId, level, wealthGrade) + + level = level&0xFFFFFF00 | wealthGrade&0x000000FF + + return tencentyun.SetUserLevel(userExtId, level) +} + +func SyncCharmGrade(userExtId string, charmGrade uint32) error { + level, err := tencentyun.GetUserLevel(userExtId) + if err != nil { + return err + } + mylogrus.MyLog.Infof("SyncCharmGrade, user %s, level before = %x, charmGrade = %d", userExtId, level, charmGrade) + + level = level&0xFFFF00FF | (charmGrade & 0x000000FF << 8) + + return tencentyun.SetUserLevel(userExtId, level) +} + +type TimHiloInfo struct { + IsVip bool `json:"isVip"` + IsPretty bool `json:"isPretty"` + Medals []uint32 `json:"medals"` + PowerName string `json:"powerName"` // 用户加入的国家势力的绑定群组的名称 + NobleLevel uint16 `json:"nobleLevel"` +} + +func GetHiloInfo(extId string) (string, error) { + result, err := tencentyun.GetUserHiloInfo(extId) + if err != nil { + return "", err + } + return result, nil +} + +func ResetHiloInfo(extId string) error { + info := TimHiloInfo{} + buf, err := json.Marshal(info) + if err != nil { + return err + } + return tencentyun.SetUserHiloInfo(extId, string(buf)) +} + +func SyncIsVip(model *domain.Model, userId uint64, externalId string) error { + //detail, err := cv.GetUserVip(model, userId) + //if err != nil { + // return err + //} + + isVip, _, err := user_m.IsVip(userId) + if err != nil { + return err + } + //isVip := false + //if detail != nil { + // isVip = true + //} + if err = SaveIsVip(externalId, isVip); err != nil { + model.Log.Info("Sync TIM hilo info failed: ", err) + } + return nil +} + +func SaveIsVip(extId string, isVip bool) error { + value, err := tencentyun.GetUserHiloInfo(extId) + if err != nil { + return err + } + mylogrus.MyLog.Info("TIM hilo info: ", value) + + info := TimHiloInfo{} + if len(value) > 0 { + if err = json.Unmarshal([]byte(value), &info); err != nil { + return err + } + } + + info.IsVip = isVip + buf, err := json.Marshal(info) + if err != nil { + return err + } + if err = tencentyun.SetUserHiloInfo(extId, string(buf)); err != nil { + return err + } + return nil +} + +func SyncIsPretty(extId string, isPretty bool) error { + value, err := tencentyun.GetUserHiloInfo(extId) + if err != nil { + return err + } + mylogrus.MyLog.Info("TIM hilo info: ", value) + + info := TimHiloInfo{} + if len(value) > 0 { + if err = json.Unmarshal([]byte(value), &info); err != nil { + return err + } + } + + info.IsPretty = isPretty + buf, err := json.Marshal(info) + if err != nil { + return err + } + if err = tencentyun.SetUserHiloInfo(extId, string(buf)); err != nil { + return err + } + return nil +} + +func SyncMedals(model *domain.Model, userId uint64, externalId string) error { + // 同步勋章 + medals, err := user_m.GetUserMedalMerge(model.Log, model.Db, userId) + if err != nil { + return nil + } + + if err = SaveMedals(externalId, medals); err != nil { + mylogrus.MyLog.Info("Sync TIM hilo info failed: ", err) + } + return nil +} + +// fixme:同步贵族信息,购买、续费贵族时需要 +func SyncNobleLevel(extId string, nobleLevel uint16) error { + value, err := tencentyun.GetUserHiloInfo(extId) + if err != nil { + return err + } + mylogrus.MyLog.Info("TIM hilo info: ", value) + + info := TimHiloInfo{} + if len(value) > 0 { + if err = json.Unmarshal([]byte(value), &info); err != nil { + return err + } + } + + info.NobleLevel = nobleLevel + buf, err := json.Marshal(info) + if err != nil { + return err + } + if err = tencentyun.SetUserHiloInfo(extId, string(buf)); err != nil { + return err + } + return nil +} + +func SaveMedals(extId string, medals []uint32) error { + value, err := tencentyun.GetUserHiloInfo(extId) + if err != nil { + return err + } + mylogrus.MyLog.Info("TIM hilo info: ", value) + + info := TimHiloInfo{} + if len(value) > 0 { + if err = json.Unmarshal([]byte(value), &info); err != nil { + return err + } + } + + info.Medals = medals + buf, err := json.Marshal(info) + if err != nil { + return err + } + + if err = tencentyun.SetUserHiloInfo(extId, string(buf)); err != nil { + return err + } + return nil +} + +// fixme: 以下时机要同步:用户加入/退出势力;势力被取消;势力改绑定群组;群组改名称,欢迎补充 +func SyncPowerName(model *domain.Model, userId uint64, externalId string) error { + _, powerName, err := groupPower_m.GetUserGroupPower(model, userId) + if err != nil { + return err + } + + if err = SavePowerName(externalId, powerName); err != nil { + mylogrus.MyLog.Info("Sync TIM hilo info failed: ", err) + } + return nil +} + +func SavePowerName(extId string, powerName string) error { + value, err := tencentyun.GetUserHiloInfo(extId) + if err != nil { + return err + } + mylogrus.MyLog.Info("TIM hilo info: ", value) + + info := TimHiloInfo{} + if len(value) > 0 { + if err = json.Unmarshal([]byte(value), &info); err != nil { + return err + } + } + + info.PowerName = powerName + buf, err := json.Marshal(info) + if err != nil { + return err + } + + if err = tencentyun.SetUserHiloInfo(extId, string(buf)); err != nil { + return err + } + return nil +} + +func SaveNobleLevel(extId string, nobleLevel uint16) error { + value, err := tencentyun.GetUserHiloInfo(extId) + if err != nil { + return err + } + mylogrus.MyLog.Info("TIM hilo info: ", value) + + info := TimHiloInfo{} + if len(value) > 0 { + if err = json.Unmarshal([]byte(value), &info); err != nil { + return err + } + } + + info.NobleLevel = nobleLevel + buf, err := json.Marshal(info) + if err != nil { + return err + } + + if err = tencentyun.SetUserHiloInfo(extId, string(buf)); err != nil { + return err + } + return nil +} + +func FlushHiloInfo(extId string, isVip bool, isPrettyCode bool, medals []uint32, groupPowerName string, nobleLevel uint16) error { + info := TimHiloInfo{IsVip: isVip, IsPretty: isPrettyCode, Medals: medals, PowerName: groupPowerName, NobleLevel: nobleLevel} + buf, err := json.Marshal(info) + if err != nil { + return err + } + + if err = tencentyun.SetUserHiloInfo(extId, string(buf)); err != nil { + return err + } + return nil +} + +func SendGroupInvitationShare(model *domain.Model, fromAccount string, toAccounts []string, txGroupId, faceUrl string, lang string) error { + type TIMInviteEnterRoomMessage struct { + Identifier string `json:"identifier"` + GroupFaceUrl string `json:"groupFaceUrl"` + GroupId string `json:"groupId"` + } + msg := TIMInviteEnterRoomMessage{ + Identifier: "TIMGroupInviteMessage", + GroupFaceUrl: faceUrl, + GroupId: txGroupId, + } + buf, err := json.Marshal(msg) + if err != nil { + return err + } + msgBody := string(buf) + + /* mt := res_m.ResMultiText{MsgId: common.MSG_ID_GROUP_INVITE, Language: lang} + if err = mt.Get(model.Db); err != nil { + return err + }*/ + mt, err := res_m.GetResMultiTextBy(model.Db, msg_e.MSG_ID_GROUP_INVITE, lang) + if err != nil { + return err + } + + return tencentyun.BatchSendCustomMsg(model, tim_e.SYNC_TO_SENDER, fromAccount, toAccounts, msgBody, mt.Content) +} + +func GetOnlineStatus(model *domain.Model, extIds []string) (map[string]uint, error) { + left := make([]string, 0) + result := make(map[string]uint, 0) + + r, err := getOnlineStatus(extIds) + if err != nil { + model.Log.Warnf("getOnlineStatus redis failed") + left = extIds + } else { + model.Log.Infof("getOnlineStatus redis return size = %d: %v", len(r), r) + + if len(r) >= len(extIds) { + for i, e := range extIds { + if r[i] == nil { + left = append(left, e) + } else { + switch r[i].(type) { + case string: + s, err := strconv.Atoi(r[i].(string)) + if err == nil { + result[e] = uint(s) + } else { + left = append(left, e) + } + default: + model.Log.Infof("getOnlineStatus return unknown type %v for %s", r[i], e) + left = append(left, e) + } + } + } + } else { + left = extIds + } + } + model.Log.Debug("getOnlineStatus cache: ", result) + + if len(left) > 0 { + for _, i := range left { + result[i] = online_e.IM_STATUS_OFF_LINE + } + + // 异步去取TIM取数据及更新。查不到的就当离线! + go func(userIds []string) { + defer func() { + if r := recover(); r != nil { + mylogrus.MyLog.Errorf("SetOnlineStatus SYSTEM ACTION PANIC: %v, stack: %v", r, string(debug.Stack())) + } + }() + + st, err := tencentyun.BatchQueryState(model, userIds) + if err != nil { + return + } + mylogrus.MyLog.Info("getOnlineStatus db: ", st) + for k, v := range st { + if err := setOnlineStatus(k, v, time.Hour*12); err != nil { + model.Log.Warn("SetOnlineStatus failed for ", k) + } else { + model.Log.Infof("SetOnlineStatus done %s - %d", k, v) + } + } + }(left) + } + return result, nil +} + +func getOnlineStatus(extIds []string) ([]interface{}, error) { + keys := make([]string, 0) + for _, e := range extIds { + keys = append(keys, redis_key.GetOnLineStatusKey(e)) + } + return redisCli.RedisClient.MGet(context.Background(), keys...).Result() +} + +func setOnlineStatus(extId string, status uint, ttl time.Duration) error { + key := redis_key.GetOnLineStatusKey(extId) + return redisCli.RedisClient.Set(context.Background(), key, status, ttl).Err() +} diff --git a/domain/model/user_m/country.go b/domain/model/user_m/country.go new file mode 100644 index 0000000..721d1e9 --- /dev/null +++ b/domain/model/user_m/country.go @@ -0,0 +1,20 @@ +package user_m + +import ( + "git.hilo.cn/hilo-common/domain" + "git.hilo.cn/hilo-common/resource/mysql" + "hilo-group/myerr" +) + +//获取用户的国家,和所属的区域(是否阿语区) +func GetUserCountryArea(model *domain.Model, id mysql.ID) (string, int, error) { + type info struct { + Name string + Area int + } + res := new(info) + if err := model.DB().Raw("select name, area from res_country where name = (select country from user where id = ?)", id).First(&res).Error; err != nil { + return "", 0, myerr.WrapErr(err) + } + return res.Name, res.Area, nil +} diff --git a/domain/model/user_m/imei.go b/domain/model/user_m/imei.go new file mode 100644 index 0000000..fa4f0b0 --- /dev/null +++ b/domain/model/user_m/imei.go @@ -0,0 +1,114 @@ +package user_m + +import ( + "git.hilo.cn/hilo-common/domain" + "git.hilo.cn/hilo-common/resource/mysql" + "gorm.io/gorm" + "hilo-group/myerr" +) + +//用户设备 +type UserImei struct { + mysql.Entity + *domain.Model `gorm:"-"` + UserId mysql.ID + Imei mysql.Str +} + +func getUserImeiOrCreate(model *domain.Model, userId mysql.ID) (*UserImei, error) { + var userImei UserImei + if err := model.Db.Where(&UserImei{ + UserId: userId, + }).First(&userImei).Error; err != nil { + if err == gorm.ErrRecordNotFound { + userImei.UserId = userId + //userImei.Imei = imei + } else { + return nil, myerr.WrapErr(err) + } + } + /* else { + if userImei.Imei == imei { + userImei.SetLasyLoad() + } else { + userImei.Imei = imei + } + }*/ + userImei.Model = model + return &userImei, nil +} + +func GetUserImeiStr(model *domain.Model, userId mysql.ID) (string, error) { + var userImei UserImei + if err := model.Db.Where(&UserImei{ + UserId: userId, + }).First(&userImei).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return "", nil + } else { + return "", myerr.WrapErr(err) + } + } + return userImei.Imei, nil +} + +func (userImei *UserImei) updateImei(imei mysql.Str) { + if userImei.Imei == imei { + userImei.SetLasyLoad() + } else { + userImei.Imei = imei + } +} + +//统计imei数量 +func countImei(model *domain.Model, imei string) (int64, error) { + var c int64 = 0 + if err := model.Db.Model(&UserImei{}).Where(&UserImei{ + Imei: imei, + }).Count(&c).Error; err != nil { + return 0, myerr.WrapErr(err) + } + return c, nil +} + +func ImeiUserIds(model *domain.Model, imei string) ([]uint64, error) { + rows := []UserImei{} + if err := model.Db.Model(&UserImei{}).Where(&UserImei{ + Imei: imei, + }).Find(&rows).Error; err != nil { + return nil, myerr.WrapErr(err) + } + userIds := make([]uint64, 0, len(rows)) + for _, i := range rows { + userIds = append(userIds, i.UserId) + } + return userIds, nil +} + +func GetSameImeiUsers(model *domain.Model, userId uint64) ([]uint64, error) { + userImei, err := getUserImeiOrCreate(model, userId) + if err != nil { + return nil, err + } + + if len(userImei.Imei) <= 0 { + return nil, nil + } + userIds, err := ImeiUserIds(model, userImei.Imei) + if err != nil { + return nil, err + } + return userIds, nil +} + +func GetSameImeiMap(model *domain.Model, userId uint64) (map[uint64]uint64, error) { + rows, err := GetSameImeiUsers(model, userId) + if err != nil { + return nil, err + } + result := make(map[uint64]uint64, 0) + for _, i := range rows { + result[i] = userId + } + return result, nil +} diff --git a/domain/model/user_m/ip.go b/domain/model/user_m/ip.go new file mode 100644 index 0000000..741d95d --- /dev/null +++ b/domain/model/user_m/ip.go @@ -0,0 +1,100 @@ +package user_m + +import ( + "git.hilo.cn/hilo-common/domain" + "git.hilo.cn/hilo-common/resource/mysql" + "gorm.io/gorm" + "hilo-group/myerr" + "time" +) + +//用户IP +type UserIp struct { + mysql.Entity + *domain.Model `gorm:"-"` + UserId mysql.ID + Ip mysql.Str +} + +func GetUserIpOrInit(model *domain.Model, userId mysql.ID) (*UserIp, error) { + var userIp UserIp + if err := model.Db.Where(&UserImei{ + UserId: userId, + }).First(&userIp).Error; err != nil { + if err == gorm.ErrRecordNotFound { + userIp.UserId = userId + } else { + return nil, myerr.WrapErr(err) + } + } + userIp.Model = model + return &userIp, nil +} + +func GetUserIpMap(db *gorm.DB, userIds []uint64) (map[uint64]string, error) { + rows := make([]UserIp, 0) + if err := db.Model(&UserIp{}).Where("user_id IN ?", userIds).Find(&rows).Error; err != nil { + return nil, myerr.WrapErr(err) + } + // + userIpMap := map[uint64]string{} + for _, i := range rows { + userIpMap[i.UserId] = i.Ip + } + return userIpMap, nil +} + +//更新Ip +func (userIp *UserIp) UpdateIp(ip string) *UserIp { + userIp.Ip = ip + return userIp +} + +//统计imei数量 +func countIp(model *domain.Model, ip string, beginTime time.Time) (int64, error) { + var c int64 = 0 + if err := model.Db.Model(&UserIp{}).Where(&UserIp{ + Ip: ip, + }).Where("created_time >= ?", beginTime).Count(&c).Error; err != nil { + return 0, myerr.WrapErr(err) + } + return c, nil +} + +func IpUserIds(model *domain.Model, ip string) ([]uint64, error) { + rows := []UserIp{} + if err := model.Db.Model(&UserIp{}).Where(&UserIp{ + Ip: ip, + }).Find(&rows).Error; err != nil { + return nil, myerr.WrapErr(err) + } + userIds := make([]uint64, 0, len(rows)) + for _, i := range rows { + userIds = append(userIds, i.UserId) + } + return userIds, nil +} + +func GetSameIpUsers(model *domain.Model, userId uint64) ([]uint64, error) { + userIp, err := GetUserIpOrInit(model, userId) + if err != nil { + return nil, err + } + userIds, err := IpUserIds(model, userIp.Ip) + if err != nil { + return nil, err + } + return userIds, nil +} + +func GetSameIpUsersMap(model *domain.Model, userId uint64) (map[uint64]struct{}, error) { + rec, err := GetSameIpUsers(model, userId) + if err != nil { + return nil, err + } + result := make(map[uint64]struct{}, 0) + for _, i := range rec { + result[i] = struct{}{} + } + return result, nil +} diff --git a/domain/model/user_m/user.go b/domain/model/user_m/user.go index 9b0891d..f6c1960 100644 --- a/domain/model/user_m/user.go +++ b/domain/model/user_m/user.go @@ -106,3 +106,15 @@ func GetUserByExtId(model *domain.Model, externalId string) (*User, error) { user.Model = model return &user, nil } + +// 通过externalId批量获取用户 +func BatchGetUserByExtIds(model *domain.Model, extIds []string) ([]User, error) { + users := make([]User, 0) + if err := model.Db.Where("external_id IN ?", extIds).Find(&users).Error; err != nil { + return nil, myerr.WrapErr(err) + } + for _, i := range users { + i.Model = model + } + return users, nil +} diff --git a/domain/model/version_m/version_control.go b/domain/model/version_m/version_control.go new file mode 100644 index 0000000..c65de80 --- /dev/null +++ b/domain/model/version_m/version_control.go @@ -0,0 +1,25 @@ +package version_m + +import ( + "git.hilo.cn/hilo-common/domain" + "git.hilo.cn/hilo-common/resource/mysql" +) + +type VersionControl struct { + mysql.Entity + Platform mysql.Str + AuditVersion mysql.Str + Status mysql.YesNo +} + +// 获取所有版本控制 +func GetVersionControlByPlatform(model *domain.Model, platform string) *VersionControl { + res := new(VersionControl) + if err := model.Db.Model(VersionControl{}).Where("platform = ?", platform).First(&res).Error; err != nil { + model.Log.Errorf("GetVersionControlByPlatform fail:%v", err) + } + if res.Status != mysql.YES { + res.AuditVersion = "999.999.999" // 不生效设置为无限大 + } + return res +} diff --git a/domain/service/event_s/event_init.go b/domain/service/event_s/event_init.go index 4e90774..0b1a374 100644 --- a/domain/service/event_s/event_init.go +++ b/domain/service/event_s/event_init.go @@ -3,14 +3,87 @@ package event_s import ( "git.hilo.cn/hilo-common/domain" "hilo-group/_const/enum/msg_e" + "hilo-group/domain/event/group_ev" "hilo-group/domain/event/group_power_ev" + "hilo-group/domain/model/diamond_m" "hilo-group/domain/model/groupPower_m" "hilo-group/domain/model/msg_m" "hilo-group/domain/model/user_m" + "strconv" ) func EventInit() { GroupPowerEvents() + GroupSupportEvents() +} + +func GroupSupportEvents() { + //群组支持 + group_ev.AddGroupSupportSync(func(model *domain.Model, e interface{}) error { + event, ok := e.(*group_ev.GroupSupportEvent) + if !ok { + model.Log.Errorf("AddGroupSupportSync fail data") + return nil + } + model.Log.Infof("diamond AddGroupSupport admin:%v mgrs:%v", event.GroupSupportAwardAdmin, event.GroupSupportAwardMgrs) + diamondAccountAdmin, err := diamond_m.GetDiamondAccountByUserId(model, event.GroupSupportAwardAdmin.UserId) + if err != nil { + return err + } + diamondAccountDetailAdmin, err := diamondAccountAdmin.GroupSupportAdmin(event.GroupSupportAwardAdmin.GroupSupportAwardId, event.GroupSupportAwardAdmin.DiamondNum) + if err != nil { + return err + } + if err := diamondAccountDetailAdmin.Persistent(); err != nil { + return err + } + for i, _ := range event.GroupSupportAwardMgrs { + diamondAccount, err := diamond_m.GetDiamondAccountByUserId(model, event.GroupSupportAwardMgrs[i].UserId) + if err != nil { + return err + } + diamondAccountDetail, err := diamondAccount.GroupSupportMgr(event.GroupSupportAwardMgrs[i].GroupSupportAwardId, event.GroupSupportAwardMgrs[i].DiamondNum) + if err != nil { + return err + } + if err := diamondAccountDetail.Persistent(); err != nil { + return err + } + } + return nil + }) + + //群组支持奖励 + group_ev.AddGroupSupportAsync(func(model *domain.Model, e interface{}) error { + event, ok := e.(*group_ev.GroupSupportEvent) + if !ok { + model.Log.Errorf("AddGroupSupportSync fail data") + return nil + } + model.Log.Infof("msg AddGroupSupportAsync admin:%v, mgr:%v", event.GroupSupportAwardAdmin, event.GroupSupportAwardMgrs) + for i, _ := range event.GroupSupportAwardMgrs { + user, err := user_m.GetUser(model, event.GroupSupportAwardMgrs[i].UserId) + if err != nil { + model.Log.Errorf("msg AddGroupSupportAsync GetUser userId:%v, err:%v", event.GroupSupportAwardMgrs[i].UserId, err) + } + if err := msg_m.NewUserRecord(model, user.ID, msg_e.GroupSupport, user.Nick, user.ID, strconv.FormatUint(uint64(event.GroupSupportAwardMgrs[i].DiamondNum), 10), "", "", "", event.GroupCode).Persistent(); err != nil { + model.Log.Errorf("msg AddGroupSupportAsync NewUserRecord err:%v", err) + break + } + msg_m.SendEmasMsgAssistant(model, user.ExternalId, user.DeviceType) + } + user, err := user_m.GetUser(model, event.GroupSupportAwardAdmin.UserId) + if err != nil { + model.Log.Errorf("msg AddGroupSupportAsync GetUser userId:%v, err:%v", event.GroupSupportAwardAdmin.UserId, err) + } + if err := msg_m.NewUserRecord(model, user.ID, msg_e.GroupSupport, user.Nick, user.ID, strconv.FormatUint(uint64(event.GroupSupportAwardAdmin.DiamondNum), 10), "", "", "", event.GroupCode).Persistent(); err != nil { + model.Log.Errorf("msg AddGroupSupportAsync NewUserRecord err:%v", err) + return nil + } + msg_m.SendEmasMsgAssistant(model, user.ExternalId, user.DeviceType) + return nil + }) + } func GroupPowerEvents() { diff --git a/domain/service/group_power_s/group_power.go b/domain/service/group_power_s/group_power.go index 63800af..cef2684 100644 --- a/domain/service/group_power_s/group_power.go +++ b/domain/service/group_power_s/group_power.go @@ -20,7 +20,7 @@ func NewGroupPowerService(myContext *mycontext.MyContext) *GroupPowerService { //用户加入国家势力 func (s *GroupPowerService) GroupPowerUserJoin(groupPowerId mysql.ID, userId mysql.ID) error { return s.svc.Transactional(func() error { - model := domain.CreateModelContext(s.svc.MyContext) + model := domain.CreateModel(s.svc.CtxAndDb) groupPower, err := groupPower_m.GetGroupPower(model, groupPowerId) if err != nil { return err @@ -43,7 +43,7 @@ func (s *GroupPowerService) GroupPowerUserJoin(groupPowerId mysql.ID, userId mys func (s *GroupPowerService) GroupPowerUserLeave(groupPowerId mysql.ID, userId mysql.ID) (int, error) { remainSeconds := 0 err := s.svc.Transactional(func() error { - model := domain.CreateModelContext(s.svc.MyContext) + model := domain.CreateModel(s.svc.CtxAndDb) groupPower, err := groupPower_m.GetGroupPower(model, groupPowerId) if err != nil { return err diff --git a/domain/service/group_s/group.go b/domain/service/group_s/group.go index ef34c75..ed37fd2 100644 --- a/domain/service/group_s/group.go +++ b/domain/service/group_s/group.go @@ -4,9 +4,13 @@ import ( "git.hilo.cn/hilo-common/domain" "git.hilo.cn/hilo-common/mycontext" "git.hilo.cn/hilo-common/resource/config" + "git.hilo.cn/hilo-common/resource/mysql" + "gorm.io/gorm" "hilo-group/_const/enum/group_e" "hilo-group/_const/redis_key" "hilo-group/domain/model/group_m" + "hilo-group/domain/model/noble_m" + "hilo-group/domain/model/user_m" "strconv" "time" ) @@ -82,3 +86,33 @@ func GetAllSupportLevel(model *domain.Model, date string) (map[string]string, er key := redis_key.GetPrefixSupportLevel(date) return model.Redis.HGetAll(model, key).Result() } + +func (s *GroupService) GetJoinGroupLimit(userId mysql.ID) (uint, error) { + model := domain.CreateModelContext(s.svc.MyContext) + + // 获取群用户上限 + maxJoin := group_e.GROUP_DEFAULT_JOIN_LIMIT + isVip, _, err := user_m.IsVip(userId) + if err != nil { + return 0, err + } + if isVip { + maxJoin = group_e.GROUP_VIP_JOIN_LIMIT + } + isNoble4, err := noble_m.CheckNobleLevel(model.Db, userId, 4) + if err != nil { + return 0, err + } + if isNoble4 { + maxJoin = group_e.GROUP_NOBLE4_JOIN_LIMIT + } + + guLimit := group_m.GroupUserLimits{UserId: userId} + if err = guLimit.Get(model); err != nil && err != gorm.ErrRecordNotFound { + return 0, err + } + if err != gorm.ErrRecordNotFound { + maxJoin = guLimit.MaxJoin + } + return maxJoin, nil +} diff --git a/domain/service/group_s/group_op.go b/domain/service/group_s/group_op.go index ff0ad4b..d0c9a25 100644 --- a/domain/service/group_s/group_op.go +++ b/domain/service/group_s/group_op.go @@ -2,7 +2,9 @@ package group_s import ( "git.hilo.cn/hilo-common/domain" + "git.hilo.cn/hilo-common/mylogrus" "hilo-group/_const/enum/group_e" + "hilo-group/domain/cache/group_c" "hilo-group/domain/model/group_m" ) @@ -19,3 +21,74 @@ func (s *GroupService) CreateGroup(userId uint64, g *group_m.GroupInfo) error { return nil }) } + +// 退群的一系列操作 +func (s *GroupService) LeaveGroup(model *domain.Model, groupId string, userId uint64, externalId string) error { + //获取用户是否在麦上, 让用户离开麦 + micUser, err := group_m.GetMicUserByExternalId(model, externalId) + if err != nil { + return err + } + if micUser != nil { + if err = micUser.LeaveByUser(userId, externalId); err != nil { + return err + } + } + + // 退群后删除管理角色 + if err = group_m.RemoveGroupRole(model, groupId, userId); err != nil { + mylogrus.MyLog.Warnf("Can't remove group %s user %d's role.", groupId, userId) + } + + // 退群后删除它(可能)作为经理设置的欢迎语 + gwt := group_m.GroupWelcomeText{} + if err = gwt.Remove(model.Db, groupId, userId); err != nil { + mylogrus.MyLog.Warnf("Can't remove group %s user %d's welcome text.", groupId, userId) + } + + // 退群后删除小闹钟 + groupUser := group_m.GroupUser{Model: model, GroupId: groupId, UserId: userId} + if err = groupUser.Delete(); err != nil { + mylogrus.MyLog.Warnf("Can't remove group %s user %d's option.", groupId, userId) + } + + // 清理相关缓存 + group_c.ClearGroupMemberCount(groupId) + group_c.RemoveGroupMember(groupId, externalId) + + return nil +} + +// 退出永久会员的一系列操作 +func (s *GroupService) LeaveGroupMember(model *domain.Model, groupId string, userId uint64, externalId string) error { + gm := group_m.GroupMember{ + GroupId: groupId, + UserId: userId, + } + if err := gm.Remove(model.Db); err != nil { + return err + } + + // 退群后删除管理角色 + if err := group_m.RemoveGroupRole(model, groupId, userId); err != nil { + mylogrus.MyLog.Warnf("Can't remove group %s user %d's role.", groupId, userId) + } + + // 退群后删除它(可能)作为经理设置的欢迎语 + gwt := group_m.GroupWelcomeText{} + if err := gwt.Remove(model.Db, groupId, userId); err != nil { + mylogrus.MyLog.Warnf("Can't remove group %s user %d's welcome text.", groupId, userId) + } + + // 退群后删除小闹钟 + groupUser := group_m.GroupUser{Model: model, GroupId: groupId, UserId: userId} + if err := groupUser.Delete(); err != nil { + mylogrus.MyLog.Warnf("Can't remove group %s user %d's option.", groupId, userId) + } + + // 清理相关缓存 + group_c.ClearGroupMemberCount(groupId) + group_c.RemoveGroupMember(groupId, externalId) + + return nil +} diff --git a/domain/service/group_s/group_support.go b/domain/service/group_s/group_support.go new file mode 100644 index 0000000..fe63bd1 --- /dev/null +++ b/domain/service/group_s/group_support.go @@ -0,0 +1,175 @@ +package group_s + +import ( + "git.hilo.cn/hilo-common/domain" + "hilo-group/_const/enum/gift_e" + "hilo-group/domain/event/group_ev" + "hilo-group/domain/model/gift_m" + "hilo-group/domain/model/group_m" + "hilo-group/domain/model/res_m" + "hilo-group/domain/model/user_m" + "hilo-group/myerr/bizerr" + "time" +) + +// 群组支持名单过滤 +func (s *GroupService) GroupSupportList(groupId string, uids []uint64) ([]uint64, []uint64, error) { + if len(uids) <= 0 { + return uids, nil, nil + } + + result := make([]uint64, 0) + out := make([]uint64, 0) + + err := s.svc.Transactional(func() error { + model := domain.CreateModel(s.svc.CtxAndDb) + + // 1. 去掉非群管理者 + roles, _, err := group_m.GetRolesInGroup(model, groupId) + if err != nil { + return err + } + userIds := make([]uint64, 0) + for _, i := range uids { + if _, ok := roles[i]; ok { + userIds = append(userIds, i) + } else { + out = append(out, i) + model.Log.Infof("GroupSupportList: rule out %d, no role", i) + } + } + + // TODO: 去掉非群成员 + + //(4)1个账户只能做1个群组的管理员(5)1个设备下只允许领取1个管理奖励 + _, _, period := group_m.GetLastSupportPeriod(time.Now()) + gsa := group_m.GroupSupportAwardMgr{Period: period} + rows, err := gsa.Get(model.Db) + if err != nil { + return err + } + awards := make(map[uint64]struct{}, 0) + for _, i := range rows { + awards[i.UserId] = struct{}{} + } + + uids = userIds + userIds = make([]uint64, 0) + m := make(map[uint64]uint64) + for _, u := range uids { + m, err := user_m.GetSameImeiMap(model, u) + if err != nil { + return err + } + + passed := true + for _, i := range m { + if _, ok := awards[i]; ok { + if i == u { + passed = false + model.Log.Infof("GroupSupportList: rule out %d, already awarded", i) + } else { + passed = false + model.Log.Infof("GroupSupportList: rule out %d, imei awarded", i) + } + } + } + if passed == true { + userIds = append(userIds, u) + } else { + out = append(out, u) + } + } + model.Log.Infof("GroupSupportList: uids %v, map %v", uids, m) + + _, supportLevel, err := s.GetSupportLevel(groupId) + if err != nil { + return err + } + + if uint32(len(userIds)) > supportLevel { + model.Log.Infof("GroupSupportList: rule out %v, limit exeeded", userIds[supportLevel:]) + out = append(out, userIds[supportLevel:]...) + userIds = userIds[0:supportLevel] + } + result = userIds + return nil + }) + + if err == nil { + return result, out, nil + } else { + return nil, nil, err + } +} + +func (s *GroupService) GetSupportLevel(groupId string) (uint64, uint32, error) { + model := domain.CreateModel(s.svc.CtxAndDb) + + beginTime, endTime, _ := group_m.GetLastSupportPeriod(time.Now()) + + g := gift_m.GiftOperate{SceneType: gift_e.GroupSceneType, SceneUid: groupId, Model: model} + count, consume, err := g.GetConsumeByRange(beginTime, endTime) + if err != nil { + return 0, 0, err + } + + rec, err := res_m.GetResGroupSupportBy(model, count, consume) + if err != nil { + return 0, 0, err + } + if rec != nil { + return rec.ID, rec.MgrNum, nil + } + return 0, 0, nil +} + +//群组支持奖励 +func (s *GroupService) GroupSupportAward(groupId string, profitAllocator uint64, userIds []uint64, resId uint64, period string) error { + return s.svc.Transactional(func() error { + model := domain.CreateModel(s.svc.CtxAndDb) + // + groupInfo, err := group_m.GetGroupInfo(model, groupId) + if groupInfo == nil { + return bizerr.GroupNotFound + } + //发放奖励 + groupSupportAwardAdmin, groupSupportAwardMgrs, err := group_m.AddGroupSupportAward(model, groupId, profitAllocator, resId, userIds, period) + if err != nil { + return err + } + + if err := groupSupportAwardAdmin.Persistent(); err != nil { + return err + } + + groupSupportEvent := group_ev.InitGroupSupportEvent(len(groupSupportAwardMgrs), groupInfo.Code) + //数据持久化 + groupSupportEvent.AddAdmin(groupSupportAwardAdmin.ID, groupSupportAwardAdmin.UserId, groupSupportAwardAdmin.DiamondNum) + for i, _ := range groupSupportAwardMgrs { + if err := groupSupportAwardMgrs[i].Persistent(); err != nil { + return err + } + groupSupportEvent.AddMgr(groupSupportAwardMgrs[i].ID, groupSupportAwardMgrs[i].UserId, groupSupportAwardMgrs[i].DiamondNum) + } + return group_ev.PublishGroupSupport(model, groupSupportEvent) + }) +} + +func (s *GroupService) RenewGroupSupporter(groupId string, userIds []uint64) error { + return s.svc.Transactional(func() error { + model := domain.CreateModel(s.svc.CtxAndDb) + gs := group_m.GroupSupporter{GroupId: groupId} + if err := gs.Delete(model.Db); err != nil { + return err + } + + if len(userIds) > 0 { + gs = group_m.GroupSupporter{GroupId: groupId} + if err := gs.BatchSave(model.Db, userIds); err != nil { + return err + } + } + return nil + }) +} diff --git a/domain/service/signal_s/signal.go b/domain/service/signal_s/signal.go index 8f25191..9c01184 100644 --- a/domain/service/signal_s/signal.go +++ b/domain/service/signal_s/signal.go @@ -46,3 +46,15 @@ func SendSignalMsg(model *domain.Model, groupId string, msg group_m.GroupSystemM model.Log.Errorf("Marshall failure, msgId = %d : %s", msg.MsgId, err.Error()) } } + +func SendCustomMsg(model *domain.Model, groupId string, fromAccount *string, content string) (uint, error) { + groupId, err := group_m.ToTxGroupId(model, groupId) + if err != nil { + return 0, err + } + hiloUserInfo := "" + if fromAccount != nil { + hiloUserInfo = group_m.GetHiloUserInfo(model, *fromAccount) + } + return tencentyun.SendCustomMsg(model.Log, groupId, fromAccount, content, hiloUserInfo) +} diff --git a/go.mod b/go.mod index a352b38..d352b81 100644 --- a/go.mod +++ b/go.mod @@ -83,6 +83,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 // indirect + github.com/spf13/cast v1.5.0 // indirect github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.479 // indirect github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ims v1.0.479 // indirect github.com/tencentyun/tls-sig-api-v2-golang v1.0.0 // indirect diff --git a/go.sum b/go.sum index bb01aee..b5205eb 100644 --- a/go.sum +++ b/go.sum @@ -294,6 +294,8 @@ github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= diff --git a/myerr/bizerr/bizCode.go b/myerr/bizerr/bizCode.go index 1f5dde9..023ac05 100644 --- a/myerr/bizerr/bizCode.go +++ b/myerr/bizerr/bizCode.go @@ -10,8 +10,12 @@ var ( ExternalIdNoExist = myerr.NewBusinessCode(1003, "externalId no exist", myerr.BusinessData{}) CodeNoExist = myerr.NewBusinessCode(1005, "code no exist", myerr.BusinessData{}) ParaMissing = myerr.NewBusinessCode(1006, "parameter missing", myerr.BusinessData{}) + EmptyContent = myerr.NewBusinessCode(1007, "Content is empty", myerr.BusinessData{}) + ContentTooLong = myerr.NewBusinessCode(1008, "Content is too long", myerr.BusinessData{}) InvalidParameter = myerr.NewBusinessCode(1009, "Invalid parameter", myerr.BusinessData{}) + NotQualified = myerr.NewBusinessCode(1010, "Not qualified", myerr.BusinessData{}) IncorrectState = myerr.NewBusinessCode(1013, "Incorrect state", myerr.BusinessData{}) + UpgradeRequired = myerr.NewBusinessCode(1015, "Version too old. Upgrade is required", myerr.BusinessData{}) TransactionFailed = myerr.NewBusinessCode(1014, "Transaction failed", myerr.BusinessData{}) ImagePolicyViolation = myerr.NewBusinessCode(1017, "image policy violation", myerr.BusinessData{}) ReqTooFrequent = myerr.NewBusinessCode(1018, "Requests are too frequent", myerr.BusinessData{}) @@ -41,12 +45,24 @@ var ( GroupInfoMicClosed = myerr.NewBusinessCode(12009, "The Group does not open the mic positions", myerr.BusinessData{}) // 群组 - GroupNotFound = myerr.NewBusinessCode(14001, "Group not found", myerr.BusinessData{}) // 找不到该群 - NoPrivileges = myerr.NewBusinessCode(14004, "Not enough permission", myerr.BusinessData{}) // 操作权限不够 - WrongPasswordLength = myerr.NewBusinessCode(14010, "Incorrect password length", myerr.BusinessData{}) // 密码长度错误 - GroupIsBanned = myerr.NewBusinessCode(14011, "group is banned by ", myerr.BusinessData{}) // 群已经被管理员封禁 - GroupCreateLimitReached = myerr.NewBusinessCode(14014, "Create group limit reached", myerr.BusinessData{}) // 你创建的群组数已达上限 - GroupCustomThemeLimit = myerr.NewBusinessCode(14020, "Group Custom Theme Limit", myerr.BusinessData{}) //群主题定制数量只能是5个 + GroupNotFound = myerr.NewBusinessCode(14001, "Group not found", myerr.BusinessData{}) // 找不到该群 + NotGroupMember = myerr.NewBusinessCode(14002, "Not a group member", myerr.BusinessData{}) // 不是群成员 + NoPrivileges = myerr.NewBusinessCode(14004, "Not enough permission", myerr.BusinessData{}) // 操作权限不够 + InBlacklist = myerr.NewBusinessCode(14005, "Can not join the group due to blacklist", myerr.BusinessData{}) // 在群黑名单中,不能进群 + OwnerCannotLeave = myerr.NewBusinessCode(14008, "Owner can not leave the group", myerr.BusinessData{}) // 群主不能退群 + WrongPasswordLength = myerr.NewBusinessCode(14010, "Incorrect password length", myerr.BusinessData{}) // 密码长度错误 + GroupIsBanned = myerr.NewBusinessCode(14011, "group is banned by ", myerr.BusinessData{}) // 群已经被管理员封禁 + GroupLimitReached = myerr.NewBusinessCode(14012, "Join group limit reached", myerr.BusinessData{}) // 所在群已达上限 + RoleLimitReached = myerr.NewBusinessCode(14013, "Role limit reached", myerr.BusinessData{}) // 所任角色已达上限 + GroupCreateLimitReached = myerr.NewBusinessCode(14014, "Create group limit reached", myerr.BusinessData{}) // 你创建的群组数已达上限 + GroupLimitReachedVip = myerr.NewBusinessCode(14015, "Join group limit reached. Please purchase VIP", myerr.BusinessData{}) // 所在群已达上限,请购买VIP + GroupAlreadyAwarded = myerr.NewBusinessCode(14016, "Group support awarded", myerr.BusinessData{}) // 群支持奖励已经发放过了 + NotManagerOrAdmin = myerr.NewBusinessCode(14017, "Not manager or group admin", myerr.BusinessData{}) // 不是经理或管理员 + UserAlreadyAwarded = myerr.NewBusinessCode(14018, "User already awarded", myerr.BusinessData{}) // 用户的群支持奖励已经发放过了 + ImeiAlreadyAwarded = myerr.NewBusinessCode(14019, "Imei support awarded", myerr.BusinessData{}) // 设备的群支持奖励已经发放过了 + GroupCustomThemeLimit = myerr.NewBusinessCode(14020, "Group Custom Theme Limit", myerr.BusinessData{}) //群主题定制数量只能是5个 + GroupSupportIpLimit = myerr.NewBusinessCode(14024, "ID:%v is a duplicate account, please select another user to receive the reward", myerr.BusinessData{}) // 群组扶持ip限制 + GroupNoPowerToSvip6 = myerr.NewBusinessCode(14026, "Cannot use this permission for SVIP6", myerr.BusinessData{}) // 踢出,无法对SVIP6使用此权限 // 家族 GroupPowerHasJoinOther = myerr.NewBusinessCode(15001, "You already have joined power, please exit first", myerr.BusinessData{}) // 已经加入了其它国家势力 @@ -61,4 +77,7 @@ var ( // 超级管理人 OfficialStaffLimit = myerr.NewBusinessCode(22001, "Operation failed", myerr.BusinessData{}) + + GamingCannotKick = myerr.NewBusinessCode(27050, "The game has already started and the user cannot be kicked out", myerr.BusinessData{}) // 游戏已经开始,不能踢出用户 + ) diff --git a/req/request.go b/req/request.go index cf186ab..39ae35f 100644 --- a/req/request.go +++ b/req/request.go @@ -7,7 +7,10 @@ import ( "github.com/gin-gonic/gin" "hilo-group/domain/cache/user_c" "hilo-group/domain/model/res_m" + "hilo-group/myerr" "hilo-group/myerr/bizerr" + "strconv" + "strings" ) func GetUserId(c *gin.Context) (mysql.ID, error) { @@ -107,3 +110,47 @@ func GetUserEx(c *gin.Context, myContext *mycontext.MyContext) (mysql.ID, string } return 0, "", "", "", "", bizerr.ParaMissing } + +func GetAppImei(c *gin.Context) (string, error) { + imei := c.GetHeader(mycontext.IMEI) + if len(imei) <= 0 { + return "", myerr.WrapErr(bizerr.ParaMissing) + } + return imei, nil +} + +func GetRequestIP(c *gin.Context) string { + reqIP := c.ClientIP() + if reqIP == "::1" { + reqIP = "127.0.0.1" + } + return reqIP +} + +func GetAppVersion(c *gin.Context) (string, int, int, int, error) { + deviceType := c.GetHeader(mycontext.DEVICETYPE) + if len(deviceType) <= 0 { + return "", 0, 0, 0, bizerr.ParaMissing + } + appVerStr := c.GetHeader(mycontext.APP_VERSION) + if len(appVerStr) <= 0 { + return "", 0, 0, 0, bizerr.ParaMissing + } + s := strings.Split(appVerStr, ".") + if len(s) < 3 { + return "", 0, 0, 0, bizerr.InvalidParameter + } + major, err := strconv.Atoi(s[0]) + if err != nil || major < 0 { + return "", 0, 0, 0, bizerr.InvalidParameter + } + minor, err := strconv.Atoi(s[1]) + if err != nil || minor < 0 { + return "", 0, 0, 0, bizerr.InvalidParameter + } + min, err := strconv.Atoi(s[2]) + if err != nil || min < 0 { + return "", 0, 0, 0, bizerr.InvalidParameter + } + return deviceType, major, minor, min, nil +} diff --git a/route/group_r/group_info.go b/route/group_r/group_info.go new file mode 100644 index 0000000..3f2af92 --- /dev/null +++ b/route/group_r/group_info.go @@ -0,0 +1,652 @@ +package group_r + +import ( + "fmt" + "git.hilo.cn/hilo-common/domain" + "git.hilo.cn/hilo-common/mycontext" + "git.hilo.cn/hilo-common/resource/mysql" + "git.hilo.cn/hilo-common/utils" + "github.com/gin-gonic/gin" + "github.com/spf13/cast" + "gorm.io/gorm" + "hilo-group/_const/enum/game_e" + "hilo-group/_const/enum/group_e" + "hilo-group/cv/group_cv" + "hilo-group/cv/group_power_cv" + "hilo-group/cv/medal_cv" + "hilo-group/cv/user_cv" + "hilo-group/domain/cache/res_c" + "hilo-group/domain/cache/room_c" + "hilo-group/domain/model/game_m" + "hilo-group/domain/model/group_m" + "hilo-group/domain/model/mgr_m" + "hilo-group/domain/model/res_m" + "hilo-group/domain/model/rocket_m" + "hilo-group/domain/model/user_m" + "hilo-group/domain/model/version_m" + "hilo-group/domain/service/group_s" + "hilo-group/myerr" + "hilo-group/myerr/bizerr" + "hilo-group/req" + "hilo-group/resp" + "sort" + "strings" +) + +// @Tags 群组 +// @Summary 查询我创建的群组 +// @Accept application/x-www-form-urlencoded +// @Param token header string true "token" +// @Param nonce header string true "随机数字" +// @Success 200 {object} group_cv.JoinedGroupInfo +// @Router /v1/imGroup/ownGroup [get] +func GetOwnGroup(c *gin.Context) (*mycontext.MyContext, error) { + myContext := mycontext.CreateMyContext(c.Keys) + + userId, err := req.GetUserId(c) + if err != nil { + return myContext, err + } + + model := domain.CreateModelContext(myContext) + + groupIds, err := group_m.GetGroups(model.Db, userId, group_e.GROUP_OWNER) + if err != nil { + return myContext, err + } + if len(groupIds) <= 0 { + resp.ResponseOk(c, []group_cv.JoinedGroupInfo{}) + return myContext, nil + } + + myService := domain.CreateService(myContext) + result, _, err := group_cv.BuildJoinedGroupInfo(myService, userId, groupIds, 1, 1) + if err != nil { + return myContext, err + } + + 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 userExternalId path string true "用户ExternalId" +// @Success 200 {object} group_cv.TheirGroupsInfo +// @Router /v1/imGroup/theirGroup/{userExternalId} [get] +func GetTheirGroups(c *gin.Context) (*mycontext.MyContext, error) { + myContext := mycontext.CreateMyContext(c.Keys) + + externalId := c.Param("userExternalId") + if len(externalId) <= 0 { + return myContext, myerr.NewSysError("userExternalId 为必填项") + } + + model := domain.CreateModelContext(myContext) + + user, err := user_m.GetUserByExtId(model, externalId) + if err != nil { + return myContext, err + } + userId := user.ID + if userId == 0 { + return myContext, bizerr.InvalidParameter + } + + data, err := group_m.GetJoinedGroups(model.Db, userId) + if err != nil { + return myContext, err + } + + result := group_cv.TheirGroupsInfo{Total: 0} + + groupIds := make([]string, 0) + for _, i := range data { + groupIds = append(groupIds, i.GroupId) + } + groupInfo, err := group_m.BatchGetGroupInfo(model, groupIds) + if err != nil { + return myContext, err + } + owners := make([]uint64, 0) + for _, i := range groupInfo { + owners = append(owners, i.Owner) + } + + theirRoles, err := group_m.GetRolesByUser(model, userId) + if err != nil { + return myContext, err + } + model.Log.Info("GetTheirGroups: theirRoles - ", theirRoles) + + powerIds, powerNames, err := group_power_cv.BatchGetGroupPower(model.Db, owners) + if err != nil { + return myContext, err + } + groupMedals, err := group_m.BatchGetMedals(model.Db, groupIds) + if err != nil { + return myContext, err + } + resMedal, err := res_m.MedalGetAllMap(model.Db) + if err != nil { + return myContext, err + } + + roomMicUserMap, err := group_m.BatchGetAllMicUser(model, groupIds) + if err != nil { + return myContext, err + } + model.Log.Infof("GetTheirGroups: groupIds %v, roomMicUserMap %v", groupIds, roomMicUserMap) + + uids := owners + micUsersMap := make(map[string][]uint64, 0) + for _, i := range groupIds { + micUsersMap[i] = make([]uint64, 0) + + u := roomMicUserMap[i] + if len(u) >= 4 { + micUsersMap[i] = u[0:4] + } else { + micUsersMap[i] = u + } + uids = append(uids, micUsersMap[i]...) + } + + uids = utils.UniqueSliceUInt64(uids) + userTiny, err := user_cv.GetUserTinyMap(uids) + if err != nil { + return myContext, err + } + + model.Log.Infof("GetTheirGroups: userTinyMap: %v", userTiny) + + roomCount, err := group_m.BatchGetRoomCount(model, groupIds) + if err != nil { + return nil, err + } + + onMic, err := group_cv.CheckMicHasUserByGroup(groupIds) + if err != nil { + return myContext, err + } + model.Log.Info("GetTheirGroups: onMic - ", onMic) + + countryInfo, err := res_c.GetCountryIconMap(model) + if err != nil { + return myContext, err + } + + supportLevels, err := group_s.NewGroupService(myContext).GetWeekMaxSupportLevelMap() + if err != nil { + return myContext, err + } + + rr := rocket_m.RocketResult{} + maxStageMap, err := rr.GetMaxStage(mysql.Db, groupIds) + if err != nil { + return myContext, err + } + + visitCount, err := group_m.BatchGetRoomVisitCount(model.Log, groupIds) + if err != nil { + return myContext, err + } + + roomEnterTime, err := room_c.GetUserRoomVisit(userId) + if err != nil { + return myContext, err + } + model.Log.Infof("BuildJoinedGroupInfo, roomEnterTime: %v", roomEnterTime) + // 正在进行的游戏 + games := game_m.GetNotEndGamesMap(model) + + for _, i := range data { + gi := groupInfo[i.GroupId] + // 只要公开的群 + if len(gi.Password) == 0 { + result.Total++ + + role := group_e.GROUP_MEMBER + r, exists := theirRoles[i.GroupId] + if exists { + role = r + } + + micUsers := make([]user_cv.CvUserTiny, 0) + for _, j := range micUsersMap[i.GroupId] { + micUsers = append(micUsers, userTiny[j]) + } + + var maxStage *uint16 = nil + if s, ok := maxStageMap[i.GroupId]; ok { + maxStage = &s + } + + medals := make([]medal_cv.PicElement, 0) + if m, ok := groupMedals[i.GroupId]; ok { + for _, j := range m { + mId := uint32(j) + if e, ok := resMedal[mId]; ok { + medals = append(medals, medal_cv.PicElement{ + PicUrl: e.PicUrl, + }) + } + } + } + // 补上房间流水勋章 + var pe *medal_cv.PicElement + _, pe, err = medal_cv.GetGroupConsumeMedal(model, i.GroupId) + if err != nil { + model.Log.Infof("GetTheirGroups: GetGroupConsumeMedal: %s", err.Error()) + } else if pe != nil { + medals = append(medals, medal_cv.PicElement{PicUrl: pe.PicUrl}) + } + + elem := group_cv.JoinedGroupInfo{ + PopularGroupInfo: group_cv.PopularGroupInfo{ + GroupInfo: group_cv.GroupInfo{ + GroupBasicInfo: group_cv.GroupBasicInfo{ + GroupId: gi.TxGroupId, + Name: gi.Name, + Introduction: gi.Introduction, + Notification: gi.Notification, + FaceUrl: gi.FaceUrl, + Code: gi.Code, + CountryIcon: countryInfo[gi.Country], + SupportLevel: supportLevels[i.GroupId], + GroupInUserDuration: visitCount[i.GroupId], + MicNumType: int(gi.MicNumType), + GroupMedals: medals, + }, + //HasOnMic: onMicScore[i.GroupId] != 0, + GroupPowerId: powerIds[gi.Owner], + GroupPowerName: powerNames[gi.Owner], + }, + MicUsers: micUsers, + RoomUserCount: uint(roomCount[i.GroupId]), + MaxStage: maxStage, + GameTypes: games[gi.TxGroupId], + }, + LastEnterTime: roomEnterTime[i.GroupId], + } + + if role == group_e.GROUP_OWNER { + result.OwnGroups = append(result.OwnGroups, elem) + } else { + result.Groups = append(result.Groups, elem) + } + } + } + + // fixme: 按最近进入时间? + sort.Slice(result.OwnGroups, func(i, j int) bool { + return result.OwnGroups[i].LastEnterTime > result.OwnGroups[j].LastEnterTime || + result.OwnGroups[i].LastEnterTime == result.OwnGroups[j].LastEnterTime && result.OwnGroups[i].GroupId < result.OwnGroups[j].GroupId + }) + // fixme:按角色排? + sort.Slice(result.Groups, func(i, j int) bool { + return theirRoles[result.Groups[i].GroupId] > theirRoles[result.Groups[j].GroupId] || + theirRoles[result.Groups[i].GroupId] == theirRoles[result.Groups[j].GroupId] && result.Groups[i].GroupId < result.Groups[j].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" +// @Success 200 +// @Router /v1/imGroup/pluginReady/{groupId} [put] +func PluginReady(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.Param("groupId") + if len(groupId) <= 0 { + return myContext, myerr.NewSysError("groupId 为必填项") + } + + model := domain.CreateModelContext(myContext) + + groupId, err = group_m.ToImGroupId(model, groupId) + if err != nil { + return myContext, err + } + + model.Log.Infof("PluginReady user %d, %s", userId, externalId) + + /** fixme:这协议没用了? + + go func(gi *group_m.GroupInfo) { + defer func() { + if r := recover(); r != nil { + //打印错误堆栈信息 + mylogrus.MyLog.Errorf("PluginReady SYSTEM ACTION PANIC: %v, stack: %v", r, string(debug.Stack())) + } + }() + + if err := myService.OnJoinGroup(externalId, groupId); err != nil { + model.Log.Infof("PluginReady, OnJoinGroup err %v", err) + } + }(groupInfo)*/ + + 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" +// @Success 200 {object} cv.RoomInfo +// @Router /v1/imGroup/roomInfo/{groupId} [get] +func GetRoomInfo(c *gin.Context) (*mycontext.MyContext, error) { + myContext := mycontext.CreateMyContext(c.Keys) + + groupId := c.Param("groupId") + if len(groupId) <= 0 { + return myContext, myerr.NewSysError("groupId 为必填项") + } + + 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 + } + + // 版本控制 + _deviceType, _ := c.Get(mycontext.DEVICETYPE) + _appVersion, _ := c.Get(mycontext.APP_VERSION) + deviceType, appVersion := cast.ToString(_deviceType), cast.ToString(_appVersion) + vc := version_m.GetVersionControlByPlatform(model, deviceType) + allow, err := utils.CompareVersion(appVersion, fmt.Sprintf("<= %s", vc.AuditVersion)) + model.Log.Infof("GetRoomInfo CompareVersion appVersion:%v,auditVersion:%v,allow:%v,err:%v", appVersion, vc.AuditVersion, allow, 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.RoomInfo{ + 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, + GameConfig: &game_m.GameConfig{Ludo: &game_m.GameConfigDiamond{Diamond: game_e.GameLudoDiamondList}, Uno: &game_m.GameConfigDiamond{Diamond: game_e.GameLudoDiamondList}}, + } + + 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 + } + + 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 + } + } + } + + roles, orderList, err := group_m.GetRolesInGroup(model, groupId) + if err != nil { + return myContext, err + } + + users, err := user_cv.GetUserTinyMap(orderList) + if err != nil { + return myContext, err + } + + for _, i := range orderList { + result.RoleMembers = append(result.RoleMembers, group_cv.SimpleRoleInfo{ + ExternalId: users[i].ExternalId, + Role: roles[i], + }) + } + + // ---- 以下部分相对没那么重要,失败也不返回 ---- + + supportLevels, err := group_s.NewGroupService(myContext).GetWeekMaxSupportLevelMap() + if err != nil { + model.Log.Infof("GetRoomInfo: GetWeekMaxSupportLevelMap: %s", err.Error()) + } + result.SupportLevel = supportLevels[groupId] + + result.DiceNum = group_e.GROUP_DICE_NUM_DEFAULT + result.DiceType = 1 // 1: [0-9] 2:[1-6] + 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 { + model.Log.Infof("GetRoomInfo: GroupSetting: %s", err.Error()) + } + + result.WelcomeText, _, _, err = group_s.NewGroupService(myContext).GetWelcomeText(groupInfo) + if err != nil { + return myContext, err + } + result.WelcomeText = strings.ReplaceAll(result.WelcomeText, "@%s", "") + + // 补上房间流水勋章 + var pe *medal_cv.PicElement + result.TotalConsume, pe, err = medal_cv.GetGroupConsumeMedal(model, groupId) + if err != nil { + model.Log.Infof("GetRoomInfo: GetGroupConsumeMedal: %s", err.Error()) + } else if pe != nil { + result.GroupMedals = append(result.GroupMedals, medal_cv.PicElement{PicUrl: pe.PicUrl}) + } + + resMedals := []res_m.ResMedal{} + err = mysql.Db.Model(&res_m.ResMedal{}). + Joins("INNER JOIN group_medal m ON m.res_medal_id = res_medal.id"). + Where("m.im_group_id = ?", groupId). + Order("m.id desc"). + Find(&resMedals).Error + if err == nil { + for _, r := range resMedals { + result.GroupMedals = append(result.GroupMedals, medal_cv.PicElement{ + PicUrl: r.PicUrl, + SvgaUrl: r.SvgaUrl, + }) + } + } else { + model.Log.Infof("GetRoomInfo: group_medal: %s", err.Error()) + } + // 获取国家信息 + country, area, err := user_m.GetUserCountryArea(model, userId) + if err != nil { + model.Log.Errorf("GetRoomInfo 获取国家资源错误 userId:%d, err:%v", userId, err) + return myContext, err + } + + rb := mgr_m.RoomBanner{Status: mysql.YES} + rows, err := rb.GetRoomBanners(model.Db, allow) + if err == nil { + for _, i := range rows { + // 是否在banner设定区域内 + if (i.Area == 1 || i.Area == 2) && area != i.Area { // 不在区域内 + continue + } + if i.Area == 3 && !utils.IsInStringList(country, i.CountryList) { + continue + } + + result.Banners = append(result.Banners, group_cv.BannerElement{ + H5Url: i.Url, + BannerUrl: i.Image, + }) + } + } else { + model.Log.Infof("GetRoomInfo: GetRoomBanners: %s", err.Error()) + } + result.LuckyWheel, err = group_cv.GetLuckWheelState(model, groupId) + if err != nil { + model.Log.Infof("GetRoomInfo: GetLuckWheelState: %s", err.Error()) + } + // 群主的详情 + result.Owner, err = user_cv.GetUserDetail(model, groupInfo.Owner, userId) + if err != nil { + model.Log.Errorf("GetRoomInfo: GetUserBase: %s", err.Error()) + } + + 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.MemberDetail +// @Router /v1/imGroup/admin/{groupId} [get] +func GetGroupAdmin(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) + + groupId, err = group_m.ToImGroupId(model, groupId) + if err != nil { + return myContext, err + } + + roles, orderList, err := group_m.GetRolesInGroup(model, groupId) + if err != nil { + return myContext, err + } + + userIds := make([]uint64, 0) + for _, i := range orderList { + if roles[i] == group_e.GROUP_MANAGER || roles[i] == group_e.GROUP_ADMIN { + userIds = append(userIds, i) + } + } + + result := make([]group_cv.MemberDetail, 0) + if len(userIds) > 0 { + users, err := user_cv.BatchGetUserExtend(model, userIds, myUserId) + if err != nil { + return myContext, err + } + + for _, i := range userIds { + result = append(result, group_cv.MemberDetail{ + CvUserExtend: users[i], + Role: roles[i], + }) + } + } + + resp.ResponseOk(c, result) + return myContext, nil +} diff --git a/route/group_r/group_list.go b/route/group_r/group_list.go index a013831..d1bfade 100644 --- a/route/group_r/group_list.go +++ b/route/group_r/group_list.go @@ -4,9 +4,12 @@ import ( "git.hilo.cn/hilo-common/domain" "git.hilo.cn/hilo-common/mycontext" "git.hilo.cn/hilo-common/resource/mysql" + "git.hilo.cn/hilo-common/rpc" + "git.hilo.cn/hilo-common/utils" "github.com/gin-gonic/gin" "hilo-group/_const/enum/gift_e" "hilo-group/_const/enum/group_e" + "hilo-group/_const/enum/online_e" "hilo-group/cv/gift_cv" "hilo-group/cv/group_cv" "hilo-group/cv/group_power_cv" @@ -15,9 +18,14 @@ import ( "hilo-group/domain/cache/res_c" "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/rocket_m" + "hilo-group/domain/model/tim_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" @@ -621,3 +629,515 @@ func GetRecommendGroup(c *gin.Context) (*mycontext.MyContext, error) { 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 "随机数字" +// @Success 200 {object} group_cv.JoinedGroupInfo +// @Router /v1/imGroup/myRecent [get] +func GetRecentGroup(c *gin.Context) (*mycontext.MyContext, error) { + myContext := mycontext.CreateMyContext(c.Keys) + + userId, _, err := req.GetUserIdAndExtId(c, myContext) + if err != nil { + return myContext, err + } + + model := domain.CreateModelContext(myContext) + + uer := group_m.UserEnterRoom{UserId: userId} + rec, err := uer.Find(model.Db) + if err != nil { + return myContext, err + } + + myGroups, err := group_m.FindGroupMapByOwner(model, userId) + if err != nil { + return myContext, err + } + + // 剔除自己创建的群 + groupIds := make([]string, 0) + for _, i := range rec { + if _, ok := myGroups[i.GroupId]; !ok { + groupIds = append(groupIds, i.GroupId) + } + } + + myService := domain.CreateService(myContext) + result, _, err := group_cv.BuildJoinedGroupInfo(myService, userId, groupIds, 30, 1) + if err != nil { + return myContext, err + } + + 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 pageSize query int false "分页大小 默认:10" default(10) +// @Param pageIndex query int false "第几个分页,从1开始 默认:1" default(1) +// @Success 200 {object} group_cv.JoinedGroupInfo +// @Router /v1/imGroup/myPermanent [get] +func GetMyGroup(c *gin.Context) (*mycontext.MyContext, error) { + myContext := mycontext.CreateMyContext(c.Keys) + + 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) + + rec, err := group_m.GetJoinedGroups(model.Db, userId) + if err != nil { + return myContext, err + } + + myGroups, err := group_m.FindGroupMapByOwner(model, userId) + if err != nil { + return myContext, err + } + + // 剔除自己创建的群 + groupIds := make([]string, 0) + for _, i := range rec { + if _, ok := myGroups[i.GroupId]; !ok { + groupIds = append(groupIds, i.GroupId) + } + } + + myService := domain.CreateService(myContext) + result, _, err := group_cv.BuildJoinedGroupInfo(myService, userId, groupIds, pageSize, pageIndex) + if err != nil { + return myContext, err + } + + 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 pageSize query int false "分页大小 默认:10" default(10) +// @Param pageIndex query int false "第几个分页,从1开始 默认:1" default(1) +// @Success 200 {object} GroupMembersRsp +// @Router /v1/imGroup/visitors/{groupId} [get] +func GetGroupVisitors(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 + } + + uer := group_m.UserEnterRoom{GroupId: groupId} + rows, err := uer.Find(model.Db) + if err != nil { + return myContext, err + } + + now := time.Now() + t := now.AddDate(0, 0, -15) // 只要15天内进入房间的人 + userIds := make([]uint64, 0) + for _, i := range rows { + if i.EnterTime.After(t) { + userIds = append(userIds, i.UserId) + } + } + users, err := user_m.GetUserMapByIds(model, userIds) + if err != nil { + return myContext, err + } + model.Log.Infof("GetGroupVisitors %s: memberNum = %d, user size = %d", groupId, len(rows), len(userIds)) + + result := GroupMembersRsp{Total: uint(len(userIds))} + + 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("GetGroupVisitors %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 + } + svipLevels, err := rpc.MGetUserSvipLevel(model, userIds) + if err != nil { + return myContext, err + } + + vips, err := user_m.BatchGetVips(userIds) + if err != nil { + return myContext, err + } + + model.Log.Infof("GetGroupVisitors %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] { + // SVIP>贵族5>贵族4>贵族3>贵族2>VIP + if svipLevels[ui] > svipLevels[uj] { + return true + } else if svipLevels[ui] == svipLevels[uj] { + if nobleLevels[ui] > nobleLevels[uj] && nobleLevels[ui] >= 2 { + return true + } + if nobleLevels[ui] == nobleLevels[uj] || nobleLevels[ui] < 2 && nobleLevels[uj] < 2 { + if vips[ui] != nil { + if vips[uj] == nil { + return true + } else { + return users[ui].Code < users[uj].Code + } + } else if vips[uj] == nil { + return users[ui].Code < users[uj].Code + } + } + } + } + } + } + return false + }) + + model.Log.Infof("GetGroupVisitors %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 +} + +type OwnPublicGroupRsp struct { + Total uint `json:"total"` + MyGroups []group_cv.GroupDetail `json:"myGroups"` +} + +// @Tags 群组 +// @Summary 查询某人创建的(公开)群组 +// @Accept application/x-www-form-urlencoded +// @Param token header string true "token" +// @Param nonce header string true "随机数字" +// @Param userExternalId path string true "用户ExternalID" +// @Success 200 {object} OwnPublicGroupRsp +// @Router /v1/imGroup/ownPublicGroup/{userExternalId} [get] +func GetOwnPublicGroup(c *gin.Context) (*mycontext.MyContext, error) { + myContext := mycontext.CreateMyContext(c.Keys) + + externalId := c.Param("userExternalId") + if len(externalId) <= 0 { + return myContext, myerr.NewSysError("userExternalId 为必填项") + } + + model := domain.CreateModelContext(myContext) + + user, err := user_m.GetUserByExtId(model, externalId) + if err != nil { + return myContext, err + } + + if utils.IfLogout(user.LogoutTime) { + resp.ResponseOk(c, OwnPublicGroupRsp{Total: 0}) + return myContext, nil + } + + theirRoles, err := group_m.GetRolesByUser(model, user.ID) + if err != nil { + return myContext, err + } + model.Log.Info("GetOwnPublicGroup: theirRoles - ", theirRoles) + + countryInfo, err := res_c.GetCountryIconMap(model) + if err != nil { + return myContext, err + } + + permanentGroups, err := group_m.GetJoinedGroups(model.Db, user.ID) + if err != nil { + return myContext, err + } + + groups, err := group_m.FindGroupMapByOwner(model, user.ID) + if err != nil { + return myContext, err + } + + result := OwnPublicGroupRsp{Total: uint(len(permanentGroups) + len(groups))} + + groupId := "" + for i, j := range groups { + // 只要公开的群 + if len(j.Password) == 0 { + if r, ok := theirRoles[j.ImGroupId]; ok && r == group_e.GROUP_OWNER { + // fixme:如果有多个群则展示群人数最多的群组 + groupId = i + break + } + } + } + + if len(groupId) > 0 { + groupInfo := groups[groupId] + + roleMembers, myRole, err := buildRoleMembers(model, groupId, user.ID) + if err != nil { + return myContext, err + } + + // 截取前N个 + endPos := group_e.GROUP_ROLE_PERSONAL_VIEW_LIMIT + if endPos > len(roleMembers) { + endPos = len(roleMembers) + } + + // 找不到的,默认为空(可能被后台删了) + 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 + } + } + + supportLevels, err := group_s.NewGroupService(myContext).GetWeekMaxSupportLevelMap() + if err != nil { + return myContext, err + } + + mem, err := group_m.GetMembers(model.Db, groupId) + if err != nil { + return myContext, err + } + + result.MyGroups = append(result.MyGroups, group_cv.GroupDetail{ + GroupBasicInfo: group_cv.GroupBasicInfo{ + GroupId: groupInfo.TxGroupId, + Name: groupInfo.Name, + Introduction: groupInfo.Introduction, + Notification: groupInfo.Notification, + FaceUrl: groupInfo.FaceUrl, + Code: groupInfo.Code, + MemberNum: uint(len(mem)), + CountryIcon: countryInfo[groupInfo.Country], + SupportLevel: supportLevels[groupId], + MicNumType: int(groupInfo.MicNumType), + }, + MicOn: groupInfo.MicOn, + LoadHistory: groupInfo.LoadHistory, + ThemeId: themeId, + ThemeUrl: themeUrl, + ThemeType: themeType, + Role: myRole, + RoleMembers: roleMembers[0:endPos], + }) + } + + resp.ResponseOk(c, result) + return myContext, nil +} + +func buildRoleMembers(model *domain.Model, groupId string, userId uint64) ([]group_cv.RoleMemberInfo, group_e.GroupRoleType, error) { + myRole := group_e.GROUP_VISITOR + roles, _, err := group_m.GetRolesInGroup(model, groupId) + if err != nil { + return nil, myRole, err + } + + model.Log.Infof("buildRoleMembers: roles - %s, %v", groupId, roles) + + userIds := make([]uint64, 0) + for u, r := range roles { + userIds = append(userIds, u) + + if u == userId { + myRole = r + } + } + + users, err := user_cv.GetUserTinyMap(userIds) + if err != nil { + return nil, myRole, err + } + + vips, err := user_m.BatchGetVips(userIds) + if err != nil { + return nil, myRole, err + } + + extIds := make([]string, 0) + for _, i := range users { + extIds = append(extIds, i.ExternalId) + } + status, err := tim_m.GetOnlineStatus(model, extIds) + if err != nil { + return nil, myRole, err + } + + superManagerMap, err := user_m.GetSuperManagerMap(userIds) + if err != nil { + return nil, myRole, err + } + + roleMembers := make([]group_cv.RoleMemberInfo, 0) + for u, _ := range users { + m := users[u] + roleMembers = append(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(roleMembers, func(i, j int) bool { + if roleMembers[i].Role > roleMembers[j].Role { + return true + } else if roleMembers[i].Role == roleMembers[j].Role { + if roleMembers[i].OnlineStatus > roleMembers[j].OnlineStatus { + return true + } else if roleMembers[i].OnlineStatus == roleMembers[j].OnlineStatus && *roleMembers[i].Code > *roleMembers[j].Code { + return true + } + } + return false + }) + return roleMembers, myRole, nil +} diff --git a/route/group_r/group_op.go b/route/group_r/group_op.go index a343fc2..54e5dae 100644 --- a/route/group_r/group_op.go +++ b/route/group_r/group_op.go @@ -4,7 +4,9 @@ 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" @@ -16,6 +18,8 @@ import ( "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" @@ -25,7 +29,9 @@ import ( "hilo-group/domain/cache/res_c" "hilo-group/domain/model/diamond_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" @@ -33,6 +39,8 @@ import ( "hilo-group/myerr/bizerr" "hilo-group/req" "hilo-group/resp" + "math" + "runtime/debug" "sort" "strconv" "strings" @@ -825,3 +833,652 @@ func ModifyGroupInfo(c *gin.Context) (*mycontext.MyContext, 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.IsSuperManager(model, myUserId); !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) + group_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"` +} + +// @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 +} diff --git a/route/group_r/group_setting.go b/route/group_r/group_setting.go new file mode 100644 index 0000000..0108baa --- /dev/null +++ b/route/group_r/group_setting.go @@ -0,0 +1,806 @@ +package group_r + +import ( + "encoding/json" + "git.hilo.cn/hilo-common/domain" + "git.hilo.cn/hilo-common/mycontext" + "git.hilo.cn/hilo-common/resource/mysql" + "git.hilo.cn/hilo-common/rpc" + "github.com/gin-gonic/gin" + "gorm.io/gorm" + "hilo-group/_const/enum/group_e" + "hilo-group/_const/enum/msg_e" + "hilo-group/cv/user_cv" + "hilo-group/domain/cache/group_c" + "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/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" + "strconv" +) + +// @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} bool +// @Router /v1/imGroup/password/{groupId} [get] +func GetGroupPassword(c *gin.Context) (*mycontext.MyContext, error) { + myContext := mycontext.CreateMyContext(c.Keys) + + userId, err := req.GetUserId(c) + if err != nil { + return myContext, err + } + + groupId := c.Param("groupId") + if len(groupId) <= 0 { + return myContext, myerr.NewSysError("groupId 为必填项") + } + + 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 + + result := false + if gi != nil && len(gi.Password) > 0 && userId != gi.Owner { + result = true + } + + resp.ResponseOk(c, result) + return myContext, nil +} + +type SimpleRoleInfo struct { + ExternalId string `json:"externalId"` + Role group_e.GroupRoleType `json:"role"` +} + +// @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 role query int false "指定的角色,不填的话代表全选" +// @Success 200 {object} []SimpleRoleInfo +// @Router /v1/imGroup/role/{groupId} [get] +func GetGroupRole(c *gin.Context) (*mycontext.MyContext, error) { + myContext := mycontext.CreateMyContext(c.Keys) + + groupId := c.Param("groupId") + if len(groupId) <= 0 { + return myContext, myerr.NewSysError("groupId 为必填项") + } + var role group_e.GroupRoleType = 0 + r, err := strconv.Atoi(c.Query("role")) + if err == nil { + role = group_e.GroupRoleType(r) + } + + userId, err := req.GetUserId(c) + if err != nil { + return myContext, err + } + + model := domain.CreateModelContext(myContext) + + groupId, err = group_m.ToImGroupId(model, groupId) + if err != nil { + return myContext, err + } + + roles, orderList, err := group_m.GetRolesInGroup(model, groupId) + if err != nil { + return myContext, err + } + result := make([]SimpleRoleInfo, 0) + + userBases, err := user_cv.GetUserBaseMap(orderList, userId) + if err != nil { + return myContext, err + } + + for _, i := range orderList { + if role == 0 || role == roles[i] { + result = append(result, SimpleRoleInfo{ + ExternalId: *userBases[i].ExternalId, + Role: roles[i], + }) + } + } + + 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 role formData int true "要赋予的角色" +// @Param externalId formData string true "用户的externalId" +// @Success 200 +// @Router /v1/imGroup/role/{groupId} [put] +func SetGroupRole(c *gin.Context) (*mycontext.MyContext, error) { + myContext := mycontext.CreateMyContext(c.Keys) + + groupId := c.Param("groupId") + if len(groupId) <= 0 { + return myContext, myerr.NewSysError("groupId 为必填项") + } + + r, err := strconv.Atoi(c.PostForm("role")) + if err != nil { + return myContext, err + } + role := group_e.GroupRoleType(r) + if role != group_e.GROUP_MANAGER && role != group_e.GROUP_ADMIN && role != group_e.GROUP_MEMBER && role != group_e.GROUP_VISITOR { + return myContext, myerr.NewSysError("Invalid role") + } + + model := domain.CreateModelContext(myContext) + + groupId, err = group_m.ToImGroupId(model, groupId) + if err != nil { + return myContext, err + } + + // 删除角色时没必要检查上限 + if role > group_e.GROUP_MEMBER { + // 检查有没有超过上限 TODO: 如何防止并发? + count, err := group_m.GetRoleCountInGroup(model, groupId, role) + if err != nil { + return myContext, err + } + + if role == group_e.GROUP_MANAGER && count >= group_e.GROUP_MANAGER_LIMIT { + return myContext, bizerr.RoleLimitReached + } + if role == group_e.GROUP_ADMIN && count >= group_e.GROUP_ADMIN_LIMIT { + return myContext, bizerr.RoleLimitReached + } + } + + userId, myExtId, _, nick, avatar, err := req.GetUserEx(c, myContext) + if err != nil { + return myContext, err + } + + // 检查用户是否有权限做操作 + myRole, err := group_m.GetRoleInGroup(model, userId, groupId) + if err != nil { + return myContext, err + } + + // 管理员以下不能改变任何角色 + if myRole <= group_e.GROUP_ADMIN { + return myContext, bizerr.NoPrivileges + } + if role == group_e.GROUP_MANAGER && myRole != group_e.GROUP_OWNER { + return myContext, bizerr.NoPrivileges + } + if role == group_e.GROUP_ADMIN && myRole != group_e.GROUP_OWNER && myRole != group_e.GROUP_MANAGER { + return myContext, bizerr.NoPrivileges + } + if (role == group_e.GROUP_VISITOR || role == group_e.GROUP_MEMBER) && myRole != group_e.GROUP_OWNER && myRole != group_e.GROUP_MANAGER { + return myContext, bizerr.NoPrivileges + } + + externalId := c.PostForm("externalId") + model.Log.Info("externalId: ", externalId) + + // + targetUser, err := user_m.GetUserByExtId(model, externalId) + if err != nil { + return myContext, err + } + + isGroupMember, err := group_m.IsGroupMember(model.Db, groupId, targetUser.ID) + if err != nil && err != gorm.ErrRecordNotFound { + return myContext, err + } + + // 邀请用户加入群组会员、把用户设置成游客 + if role == group_e.GROUP_VISITOR || (role == group_e.GROUP_MEMBER && !isGroupMember) { + err = SetGroupMemberTourist(model, role, isGroupMember, targetUser.ID, userId, externalId, myExtId, groupId) + if err != nil { + return myContext, err + } + resp.ResponseOk(c, nil) + return myContext, nil + } + + if !isGroupMember { + return myContext, bizerr.NotGroupMember + } + + // 先读取旧角色 + oldRole, err := group_m.GetRoleInGroup(model, targetUser.ID, groupId) + if err != nil { + return myContext, err + } + + if oldRole >= myRole { + // 不能调整更高级者的角色! + return myContext, bizerr.NoPrivileges + } + + if role <= group_e.GROUP_MEMBER { + // 如果已经没有角色,则不需要取消 + if oldRole != role { + err = RemoveGroupRole(model, groupId, myExtId, externalId, nick, avatar, targetUser.Nick, targetUser.Avatar, targetUser.ID, oldRole) + if err != nil { + return myContext, err + } + } + } else if err = group_m.CreateGroupRole(model, groupId, targetUser.ID, role); err == nil { + // 发送信令 + systemMsg := group_m.GroupSystemMsg{MsgId: group_e.GroupRoleChangeSignal, Source: myExtId, Target: externalId, Content: "add"} + signal_s.SendSignalMsg(model, groupId, systemMsg, false) + + nobleLevel, err := noble_m.GetNobleLevel(model.Db, targetUser.ID) + if err != nil { + return myContext, err + } + + // 发公屏消息 + msg := group_m.CommonPublicMsg{Type: group_e.RoleAssignedPublicScreenMsg, + OperatorExternalId: myExtId, OperatorNick: nick, OperatorAvatar: avatar, + ExternalId: targetUser.ExternalId, Nick: targetUser.Nick, Avatar: targetUser.Avatar, NobleLevel: nobleLevel, + Role: role} + buf, err := json.Marshal(msg) + if err == nil { + signal_s.SendCustomMsg(model, groupId, nil, string(buf)) + } + } else { + return myContext, err + } + // 发送腾讯云信令 + con := &GroupRoleChangeMsg{Role: int(role)} + msgContent, _ := json.Marshal(con) + signal_s.SendSignalMsg(model, groupId, group_m.GroupSystemMsg{ + MsgId: group_e.GroupRoleChange, + Source: myExtId, + Target: externalId, + Content: string(msgContent), + }, false) + + // 新角色不是经理角度时,将其(可能)设置的欢迎语清除 + if role < group_e.GROUP_MANAGER { + gwt := group_m.GroupWelcomeText{} + if err = gwt.Remove(model.Db, groupId, targetUser.ID); err != nil { + model.Log.Warnf("Can't remove group %s user %d's welcome text.", groupId, userId) + } + } + + resp.ResponseOk(c, nil) + return myContext, nil +} + +func RemoveGroupRole(model *domain.Model, groupId, myExtId, externalId, nick, avatar, targetNick, targetAvatar string, + targetUserId uint64, oldRole group_e.GroupRoleType) error { + if err := group_m.RemoveGroupRole(model, groupId, targetUserId); err == nil { + // 发送信令 + systemMsg := group_m.GroupSystemMsg{MsgId: group_e.GroupRoleChangeSignal, Source: myExtId, Target: externalId, Content: "remove"} + signal_s.SendSignalMsg(model, groupId, systemMsg, false) + + nobleLevel, err := noble_m.GetNobleLevel(model.Db, targetUserId) + if err != nil { + return err + } + + // 发公屏消息 + msg := group_m.CommonPublicMsg{Type: group_e.RoleRemovedPublicScreenMsg, + OperatorExternalId: myExtId, OperatorNick: nick, OperatorAvatar: avatar, + ExternalId: externalId, Nick: targetNick, Avatar: targetAvatar, NobleLevel: nobleLevel, + Role: oldRole} + buf, err := json.Marshal(msg) + if err != nil { + return err + } + signal_s.SendCustomMsg(model, groupId, nil, string(buf)) + } + return nil +} + +func SetGroupMemberTourist(model *domain.Model, role group_e.GroupRoleType, isGroupMember bool, userId, inviteUserId uint64, + externalId, myExtId, imGroupId string) (err error) { + var msgId group_e.TypeSignalMsg + var msgContent string + switch role { + case group_e.GROUP_MEMBER: + if isGroupMember { // 已经是成员,直接返回 + return nil + } + // 邀请对方成为成员 + // 插入邀请表 + err = group_m.InsertGroupInviteJoin(model, userId, inviteUserId, imGroupId) + if err != nil { + return err + } + msgId = group_e.GroupMemberInvite + case group_e.GROUP_VISITOR: + if !isGroupMember { // 不是成员,直接返回 + return nil + } + // 移除成员 + if err = group_s.NewGroupService(nil).LeaveGroupMember(model, imGroupId, userId, externalId); err != nil { + return err + } + msgId = group_e.GroupRoleChange + con := &GroupRoleChangeMsg{Role: int(group_e.GROUP_VISITOR)} + jData, _ := json.Marshal(con) + msgContent = string(jData) + } + if msgId > 0 { + // 发送腾讯云信令 + signal_s.SendSignalMsg(model, imGroupId, group_m.GroupSystemMsg{ + MsgId: msgId, + Source: myExtId, + Target: externalId, + Content: msgContent, + }, false) + } + return nil +} + +type GroupRoleChangeMsg struct { + Role int `json:"role"` +} + +// @Tags 群组 +// @Summary 接受邀请-成为永久成员 +// @Param groupId formData string true "群ID" +// @Success 200 +// @Router /v1/imGroup/role/accept [post] +func AcceptMemberInvite(c *gin.Context) (*mycontext.MyContext, error) { + myContext := mycontext.CreateMyContext(c.Keys) + + groupId := c.Param("groupId") + if len(groupId) <= 0 { + return myContext, myerr.NewSysError("groupId 为必填项") + } + + resp.ResponseOk(c, group_e.ADD_GROUP_DONE) + 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 externalId formData string true "用户的externalId" +// @Param removeHistory formData bool true "是否删除聊天记录" +// @Param blackImei formData bool false "SVIP是否封禁设备和ip" +// @Success 200 +// @Router /v1/imGroup/blacklist/{groupId} [put] +func AddGroupBlacklist(c *gin.Context) (*mycontext.MyContext, error) { + myContext := mycontext.CreateMyContext(c.Keys) + + groupId := c.Param("groupId") + if len(groupId) <= 0 { + return myContext, myerr.NewSysError("groupId 为必填项") + } + + removeHistory, err := strconv.ParseBool(c.PostForm("removeHistory")) + if err != nil { + return myContext, myerr.NewSysError("removeHistory 为必填项") + } + + blackImei, err := strconv.ParseBool(c.PostForm("blackImei")) + if err != nil { + blackImei = false + } + + userId, myExtId, _, nick, avatar, err := req.GetUserEx(c, myContext) + if err != nil { + return myContext, err + } + + _, 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 + } + + myRole, err := group_m.GetRoleInGroup(model, userId, groupId) + if err != nil { + return myContext, err + } + + if myRole < group_e.GROUP_ADMIN { + return myContext, bizerr.NoPrivileges + } + + externalId := c.PostForm("externalId") + user, err := user_m.GetUserByExtId(model, externalId) + if err != nil { + return myContext, err + } + + //超级管理人 + if flag, err := user_m.IsSuperManager(domain.CreateModelContext(myContext), user.ID); err != nil { + return myContext, err + } else if flag { + return myContext, bizerr.OfficialStaffLimit + } + + role, err := group_m.GetRoleInGroup(model, user.ID, groupId) + if err != nil { + return myContext, err + } + isGaming, err := game_m.IsGaming(model, user.ID, txGroupId) + if err != nil { + return myContext, err + } + if isGaming { + return myContext, bizerr.GamingCannotKick + } + + if err = CheckOptToSvip6(model, userId, user.ID, lang, 10); err != nil { + return myContext, err + } + + // svip + svipMap, err := rpc.MGetUserSvip(model, []mysql.ID{userId, user.ID}) + if err != nil { + return myContext, err + } + + if myRole > role { + // 是否svip封禁设备 + var userImei, userIp string + if blackImei && role != group_e.GROUP_OWNER && role != group_e.GROUP_MANAGER { + if v, ok := svipMap[userId]; ok && v.SvipLevel > 0 { + userImei, err = user_m.GetUserImeiStr(model, user.ID) + if err != nil { + return myContext, err + } + userIpInfo, err := user_m.GetUserIpOrInit(model, user.ID) + if err != nil { + return myContext, err + } + userIp = userIpInfo.Ip + } + } + // TODO: 先发信令和公屏消息,避免被封之人收不到 + // 发送信令 + type removeHistoryParam struct { + RemoveHistory bool `json:"removeHistory"` + } + r := removeHistoryParam{RemoveHistory: removeHistory} + buf, err := json.Marshal(r) + if err == nil { + systemMsg := group_m.GroupSystemMsg{MsgId: group_e.GroupMemberRemoveSignal, + Source: myExtId, Target: externalId, Content: string(buf)} + signal_s.SendSignalMsg(model, groupId, systemMsg, false) + } + + // 发公屏消息 + msg := group_m.CommonPublicMsg{Type: group_e.UserBannedPublicScreenMsg, + OperatorExternalId: myExtId, OperatorNick: nick, OperatorAvatar: avatar, + ExternalId: user.ExternalId, Nick: user.Nick, Avatar: user.Avatar} + buf, err = json.Marshal(msg) + if err == nil { + signal_s.SendCustomMsg(model, groupId, nil, string(buf)) + } + + if err = group_s.NewGroupService(myContext).LeaveGroup(model, groupId, user.ID, externalId); err != nil { + return myContext, err + } + + gb := group_m.GroupBlacklist{ImGroupId: groupId, UserId: user.ID, Imei: userImei, Ip: userIp} + err = group_m.AddBlacklist(model, &gb) + if err != nil { + model.Log.Errorf("AddBlacklist failed for %s, user %d", groupId, user.ID) + } + } else { + return myContext, bizerr.NoPrivileges + } + + resp.ResponseOk(c, nil) + return myContext, nil +} + +// 检查是否可以对svip6进行操作 optUserId:被操作的用户id +func CheckOptToSvip6(model *domain.Model, userId, optUserId uint64, lang string, privilegeId int) error { + // 是否超管 + isM, err := user_m.IsSuperManager(model, userId) + if err != nil { + return err + } + // svip + svipMap, err := rpc.MGetUserSvip(model, []mysql.ID{userId, optUserId}) + if err != nil { + return err + } + if !isM { + if svip, ok := svipMap[userId]; !ok || svip.SvipLevel < 6 { // 非svip6(svip6可以对svip6进行操作) + // 是否禁止被封禁 + if svip, ok := svipMap[optUserId]; ok { + for _, v := range svip.Privileges { + if v.Type == privilegeId { + return myerr.WrapErr(res_m.GetErrByLanguage(model.Db, msg_e.MSG_ID_NO_POWER_TO_SVIP6, lang, bizerr.GroupNoPowerToSvip6)) + } + } + } + } + } + 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" +// @Param externalId query string true "用户的externalId" +// @Success 200 +// @Router /v1/imGroup/blacklist/{groupId} [delete] +func RemoveGroupBlacklist(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.GetUserId(c) + if err != nil { + return myContext, err + } + + model := domain.CreateModelContext(myContext) + + groupId, err = group_m.ToImGroupId(model, groupId) + if err != nil { + return myContext, err + } + + myRole, err := group_m.GetRoleInGroup(model, userId, groupId) + if err != nil { + return myContext, err + } + + if myRole < group_e.GROUP_ADMIN { + return myContext, bizerr.NoPrivileges + } + + externalId := c.Query("externalId") + if len(externalId) <= 0 { + return myContext, myerr.NewSysError("externalId 为必填项") + } + + user, err := user_m.GetUserByExtId(model, externalId) + if err != nil { + return myContext, err + } + err = group_m.RemoveBlacklist(model, &group_m.GroupBlacklist{ImGroupId: groupId, UserId: user.ID}) + if err != nil { + return myContext, err + } + + resp.ResponseOk(c, nil) + return myContext, nil +} + +//拉黑列表视图 +type GroupBlackList struct { + //拉黑时间 + BlockTime int64 `json:"blockTime"` + //用户基本信息 + UserBase user_cv.CvUserBase `json:"userBase"` +} + +// @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} GroupBlackList +// @Router /v1/imGroup/blacklist/{groupId} [get] +func GetGroupBlacklist(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.GetUserId(c) + if err != nil { + return myContext, err + } + + model := domain.CreateModelContext(myContext) + + groupId, err = group_m.ToImGroupId(model, groupId) + if err != nil { + return myContext, err + } + + // TODO: 非群成员不能看 + rows, err := group_m.FindGroupBlackList(model, groupId) + if err != nil { + return myContext, err + } + + userIds := make([]uint64, 0) + for _, i := range rows { + userIds = append(userIds, i.UserId) + } + users, err := user_cv.GetUserBaseMap(userIds, userId) + if err != nil { + return myContext, err + } + + result := make([]GroupBlackList, 0) + for _, i := range rows { + result = append(result, GroupBlackList{ + BlockTime: i.CreatedTime.Unix(), + UserBase: *users[i.UserId], + }) + } + + resp.ResponsePageOk(c, result, uint(len(result)), 1) + 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 externalId formData string true "用户的externalId" +// @Param removeHistory formData bool true "是否删除聊天记录" +// @Success 200 +// @Router /v1/imGroup/kick/{groupId} [put] +func KickGroupMembers(c *gin.Context) (*mycontext.MyContext, error) { + myContext := mycontext.CreateMyContext(c.Keys) + + groupId := c.Param("groupId") + if len(groupId) <= 0 { + return myContext, myerr.NewSysError("groupId 为必填项") + } + + removeHistory, err := strconv.ParseBool(c.PostForm("removeHistory")) + if err != nil { + return myContext, myerr.NewSysError("removeHistory 为必填项") + } + + userId, myExtId, _, _, _, err := req.GetUserEx(c, myContext) + if err != nil { + return myContext, err + } + + if !user_m.IsSuperUser(userId) { + return myContext, bizerr.NoPrivileges + } + + model := domain.CreateModelContext(myContext) + + groupId, err = group_m.ToImGroupId(model, groupId) + if err != nil { + return myContext, err + } + externalId := c.PostForm("externalId") + user, err := user_m.GetUserByExtId(model, externalId) + if err != nil { + return myContext, err + } + + role, err := group_m.GetRoleInGroup(model, user.ID, groupId) + if err != nil { + return myContext, err + } + + if role > group_e.GROUP_MEMBER { + // 不能踢有角色的人出去 + return myContext, bizerr.NoPrivileges + } + + // 发送信令? + type removeHistoryParam struct { + RemoveHistory bool `json:"removeHistory"` + } + r := removeHistoryParam{RemoveHistory: removeHistory} + buf, err := json.Marshal(r) + if err == nil { + systemMsg := group_m.GroupSystemMsg{MsgId: group_e.GroupMemberRemoveSignal, + Source: myExtId, Target: externalId, Content: string(buf)} + signal_s.SendSignalMsg(model, groupId, systemMsg, false) + } + + if err = group_s.NewGroupService(myContext).LeaveGroup(model, groupId, user.ID, 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 text formData string true "欢迎语" +// @Success 200 +// @Router /v1/imGroup/welcomeText/{groupId} [put] +func SetWelcomeText(c *gin.Context) (*mycontext.MyContext, error) { + myContext := mycontext.CreateMyContext(c.Keys) + + groupId := c.Param("groupId") + if len(groupId) <= 0 { + return myContext, myerr.NewSysError("groupId 为必填项") + } + + text := c.PostForm("text") + if len(text) <= 0 { + return myContext, bizerr.EmptyContent + } + if len([]rune(text)) > group_e.GROUP_INTRODUCTION_LENGTH_LIMIT { + return myContext, bizerr.ContentTooLong + } + + 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 + } + if group_c.IsEditGroupCd(model, groupId) { + return myContext, bizerr.EditCd + } + + myRole, err := group_m.GetRoleInGroup(model, userId, groupId) + if err != nil { + return myContext, err + } + if myRole != group_e.GROUP_OWNER && myRole != group_e.GROUP_MANAGER { + return myContext, bizerr.NoPrivileges + } + + gwt := group_m.GroupWelcomeText{GroupId: groupId, UserId: userId, Text: text} + if err = gwt.Save(model.Db); err != nil { + return myContext, err + } + + resp.ResponseOk(c, nil) + return myContext, nil +} diff --git a/route/group_r/group_support.go b/route/group_r/group_support.go new file mode 100644 index 0000000..57c20c9 --- /dev/null +++ b/route/group_r/group_support.go @@ -0,0 +1,401 @@ +package group_r + +import ( + "fmt" + "git.hilo.cn/hilo-common/domain" + "git.hilo.cn/hilo-common/mycontext" + "git.hilo.cn/hilo-common/resource/mysql" + "github.com/gin-gonic/gin" + "hilo-group/_const/enum/gift_e" + "hilo-group/_const/enum/group_e" + "hilo-group/_const/enum/msg_e" + "hilo-group/cv/group_cv" + "hilo-group/cv/user_cv" + "hilo-group/domain/cache/group_c" + "hilo-group/domain/model/gift_m" + "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" + "strings" + "time" +) + +// @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} bool +// @Router /v1/imGroup/support/page/{groupId} [get] +func GetSupportPage(c *gin.Context) (*mycontext.MyContext, error) { + myContext := mycontext.CreateMyContext(c.Keys) + + groupId := c.Param("groupId") + if len(groupId) <= 0 { + return myContext, bizerr.ParaMissing + } + + userId, err := req.GetUserId(c) + if err != nil { + return myContext, err + } + + model := domain.CreateModelContext(myContext) + groupId, err = group_m.ToImGroupId(model, groupId) + if err != nil { + return myContext, err + } + + pa, err := group_m.GetProfitAllocator(model, groupId) + if err != nil { + return myContext, err + } + + resp.ResponseOk(c, pa == userId) + return myContext, nil +} + +// @Tags 群组 +// @Summary 支持页详情 +// @Accept application/x-www-form-urlencoded +// @Param token header string true "token" +// @Param nonce header string true "随机数字" +// @Param groupId query string false "群ID" +// @Success 200 {object} cv.SupportPageDetail +// @Router /v1/imGroup/support/detail [get] +func GetSupportDetail(c *gin.Context) (*mycontext.MyContext, error) { + myContext := mycontext.CreateMyContext(c.Keys) + + userId, err := req.GetUserId(c) + if err != nil { + return myContext, err + } + + model := domain.CreateModelContext(myContext) + + groupId := c.Query("groupId") + txGroupId := groupId + if len(groupId) <= 0 { + // groupId 没填的话,用user自己的群 + groups, err := group_m.FindGroupByOwner(model, userId) + if err != nil { + return myContext, err + } + if len(groups) > 0 { + groupId = groups[0].ImGroupId + txGroupId = groups[0].TxGroupId + } else { + return myContext, bizerr.GroupNotFound + } + } else { + groupId, err = group_m.ToImGroupId(model, groupId) + if err != nil { + return myContext, err + } + } + + pa, err := group_m.GetProfitAllocator(model, groupId) + if err != nil { + return myContext, err + } + if userId != pa { + return myContext, bizerr.NoPrivileges + } + + result := group_cv.SupportPageDetail{GroupId: txGroupId} + + now := time.Now() + beginTime, endTime, _ := group_m.GetSupportLevelTime(now) + result.RemainSecond = endTime.Unix() - now.Unix() + + g := gift_m.GiftOperate{SceneType: gift_e.GroupSceneType, SceneUid: groupId, Model: model} + result.CurrentCount, result.CurrentConsume, err = g.GetConsumeByRange(beginTime, endTime) + if err != nil { + return myContext, err + } + + beginTime, endTime, _ = group_m.GetSupportLevelTime(now.AddDate(0, 0, -group_e.SUPPORT_LEVEL_PERIOD_DAY)) + result.LastCount, result.LastConsume, err = g.GetConsumeByRange(beginTime, endTime) + if err != nil { + return myContext, err + } + + rec, err := res_m.GetResGroupSupportBy(model, result.LastCount, result.LastConsume) + if err != nil { + return myContext, err + } + if rec != nil { + result.SupportLevel = string(64 + rec.Grade) + result.SupporterLimit = rec.MgrNum + } + + rec, err = res_m.GetResGroupSupportBy(model, result.CurrentCount, result.CurrentConsume) + if err != nil { + return myContext, err + } + if rec != nil { + result.CurrentSupportLevel = string(64 + rec.Grade) + } + + userBase, err := user_cv.GetUserBase(pa, userId) + if err != nil { + return myContext, err + } + if userBase != nil { + result.ProfitAllocator = *userBase + } + + // 判断这个周期这个群的奖金是否已经发过 + _, _, period := group_m.GetLastSupportPeriod(now) + gsaa := group_m.GroupSupportAwardAdmin{Period: period, GroupUid: groupId} + rows, err := gsaa.Get(model.Db) + if err != nil { + return myContext, err + } + + if len(rows) > 0 { + result.IsDispatched = true + } + + 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 false "群ID" +// @Param externalId query string true "用户的externalId" +// @Success 200 +// @Router /v1/imGroup/support/award/{groupId} [get] +func TryAddSupporter(c *gin.Context) (*mycontext.MyContext, error) { + myContext := mycontext.CreateMyContext(c.Keys) + + myUserId, err := req.GetUserId(c) + if err != nil { + return myContext, err + } + + groupId := c.Param("groupId") + if len(groupId) <= 0 { + return myContext, myerr.NewSysError("groupId 为必填项") + } + + externalId := c.Query("externalId") + if len(externalId) <= 0 { + return myContext, myerr.NewSysError("externalId 为必填项") + } + + model := domain.CreateModelContext(myContext) + groupId, err = group_m.ToImGroupId(model, groupId) + if err != nil { + return myContext, err + } + + pa, err := group_m.GetProfitAllocator(model, groupId) + if err != nil { + return myContext, err + } + if myUserId != pa { + return myContext, bizerr.NoPrivileges + } + + user, err := user_m.GetUserByExtId(model, externalId) + if err != nil { + return myContext, err + } + userId := user.ID + + role, err := group_m.GetRoleInGroup(model, userId, groupId) + if err != nil { + return myContext, err + } + + if role != group_e.GROUP_MANAGER && role != group_e.GROUP_ADMIN { + return myContext, bizerr.NotManagerOrAdmin + } + + // 判断用户是否作为助手领取过奖励 + sameImeiUsers, err := user_m.GetSameImeiMap(model, userId) + if err != nil { + return myContext, err + } + _, _, period := group_m.GetLastSupportPeriod(time.Now()) + gsaa := group_m.GroupSupportAwardMgr{Period: period} + awards, err := gsaa.Get(model.Db) + if err != nil { + return myContext, err + } + + for _, i := range awards { + if _, ok := sameImeiUsers[i.UserId]; ok { + if i.UserId == userId { + return myContext, bizerr.UserAlreadyAwarded + } else { + return myContext, bizerr.ImeiAlreadyAwarded + } + } + } + + 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 supporters formData string false "群支持者的externalId,用逗号分隔" +// @Success 200 {object} cv.AwardResult +// @Router /v1/imGroup/support/award/{groupId} [post] +func TakeSupportAward(c *gin.Context) (*mycontext.MyContext, error) { + myContext := mycontext.CreateMyContext(c.Keys) + + //userId, err := getUserId(c) + userId, lang, err := req.GetUserIdLang(c, myContext) + if err != nil { + return myContext, err + } + + groupId := c.Param("groupId") + if len(groupId) <= 0 { + return myContext, myerr.NewSysError("groupId 为必填项") + } + + model := domain.CreateModelContext(myContext) + groupId, err = group_m.ToImGroupId(model, groupId) + if err != nil { + return myContext, err + } + + pa, err := group_m.GetProfitAllocator(model, groupId) + if err != nil { + return myContext, err + } + if userId != pa { + return myContext, bizerr.NoPrivileges + } + + s := c.PostForm("supporters") + extIds := make([]string, 0) + extId2Uid := make(map[string]uint64, 0) + uid2extId := make(map[uint64]string, 0) + codeMap := make(map[mysql.ID]string, 0) + if len(s) > 0 { + extIds = strings.Split(s, ",") + users, err := user_m.BatchGetUserByExtIds(model, extIds) + if err != nil { + return myContext, err + } + for _, i := range users { + extId2Uid[i.ExternalId] = i.ID + uid2extId[i.ID] = i.ExternalId + codeMap[i.ID] = i.Code + } + } + + userIds := make([]uint64, 0) + for _, i := range extIds { + if _, ok := extId2Uid[i]; ok { + userIds = append(userIds, extId2Uid[i]) + } else { + // FIXME: report error + } + } + + // 检查是否已经发放了 + _, _, period := group_m.GetLastSupportPeriod(time.Now()) + gsa := group_m.GroupSupportAwardAdmin{Period: period, GroupUid: groupId} + rows, err := gsa.Get(model.Db) + if err != nil { + return myContext, err + } + + if len(rows) > 0 { + return myContext, bizerr.GroupAlreadyAwarded + } + + userIds, outUserIds, err := group_s.NewGroupService(myContext).GroupSupportList(groupId, userIds) + model.Log.Infof("TakeSupportAward: %v, %v", userIds, outUserIds) + + model = domain.CreateModelContext(myContext) + + resSupportId, _, err := group_s.NewGroupService(myContext).GetSupportLevel(groupId) + if err != nil { + return myContext, err + } + + if resSupportId <= 0 { + return myContext, bizerr.NotQualified + } + + // 检查userIds的ip限制 + userIp, err := user_m.GetUserIpMap(model.Db, userIds) + if err != nil { + return myContext, err + } + var sameIp = make(map[string]int) + var ipUser = make(map[string]mysql.ID) + for uid, ip := range userIp { + sameIp[ip]++ + ipUser[ip] = uid + if len(ip) > 0 { + // IP已经领取过6次奖励,点击领奖校验则提示“ID:%s为重复账户 + var msg = fmt.Sprintf(bizerr.GroupSupportIpLimit.GetMsg(), codeMap[uid]) + if resMul, _ := res_m.GetResMultiTextBy(model.Db, msg_e.MSG_ID_REPEAT_ACCOUNT, lang); resMul != nil { + msg = fmt.Sprintf(resMul.Content, codeMap[uid]) + } + if times, err := group_c.GetGroupSupportAwardIpTimes(model, ip); err != nil || times >= 6 { + return myContext, myerr.NewBusinessCodeNoCheck(bizerr.GroupSupportIpLimit.GetCode(), msg, myerr.BusinessData{}) + } + } + } + for ip, cnt := range sameIp { + if cnt >= 6 { + var msg = fmt.Sprintf(bizerr.GroupSupportIpLimit.GetMsg(), ipUser[ip]) + if resMul, _ := res_m.GetResMultiTextBy(model.Db, msg_e.MSG_ID_REPEAT_ACCOUNT, lang); resMul != nil { + msg = fmt.Sprintf(resMul.Content, ipUser[ip]) + } + return myContext, myerr.NewBusinessCodeNoCheck(bizerr.GroupSupportIpLimit.GetCode(), msg, myerr.BusinessData{}) + } + } + + // 真正地放奖励 + err = group_s.NewGroupService(myContext).GroupSupportAward(groupId, pa, userIds, resSupportId, period) + if err != nil { + return myContext, err + } + + // 记录ip获奖 + for _, userId := range userIds { + if ip, ok := userIp[userId]; ok && len(ip) > 0 { + _, _ = group_c.IncrGroupSupportAwardIp(model, ip) + } + } + + // 保存记录 + if err = group_s.NewGroupService(myContext).RenewGroupSupporter(groupId, userIds); err != nil { + model.Log.Warnf("TakeSupportAward, failed in saving group %s supporters", groupId) + } + + result := group_cv.AwardResult{} + for _, i := range userIds { + result.Success = append(result.Success, uid2extId[i]) + } + for _, i := range outUserIds { + result.Failed = append(result.Failed, uid2extId[i]) + } + + resp.ResponseOk(c, result) + return myContext, nil +} diff --git a/route/router.go b/route/router.go index 4edecf9..aad0cde 100644 --- a/route/router.go +++ b/route/router.go @@ -28,40 +28,40 @@ func InitRouter() *gin.Engine { imGroup.GET("/group/:code", wrapper(group_r.GetGroupInfo)) imGroup.GET("/detail/:groupId", wrapper(group_r.GetGroupDetail)) imGroup.PUT("/group/:groupId", wrapper(group_r.ModifyGroupInfo)) - //imGroup.GET("/search/:code", wrapper(SearchGroup)) - //imGroup.DELETE("/member/:groupId", wrapper(LeaveGroup)) - //imGroup.PUT("/permanent/:groupId", wrapper(AddPermanentMember)) - //imGroup.DELETE("/permanent/:groupId", wrapper(RemovePermanentMember)) - //imGroup.GET("/permanent/:groupId", wrapper(GetPermanentMember)) - //imGroup.GET("/myRecent", wrapper(GetRecentGroup)) - //imGroup.GET("/myPermanent", wrapper(GetMyGroup)) + imGroup.GET("/search/:code", wrapper(group_r.SearchGroup)) + imGroup.DELETE("/member/:groupId", wrapper(group_r.LeaveGroup)) + imGroup.PUT("/permanent/:groupId", wrapper(group_r.AddPermanentMember)) + imGroup.DELETE("/permanent/:groupId", wrapper(group_r.RemovePermanentMember)) + imGroup.GET("/permanent/:groupId", wrapper(group_r.GetPermanentMember)) + imGroup.GET("/myRecent", wrapper(group_r.GetRecentGroup)) + imGroup.GET("/myPermanent", wrapper(group_r.GetMyGroup)) // - //imGroup.GET("/visitors/:groupId", wrapper(GetGroupVisitors)) - //imGroup.GET("/ownPublicGroup/:userExternalId", wrapper(GetOwnPublicGroup)) + imGroup.GET("/visitors/:groupId", wrapper(group_r.GetGroupVisitors)) + imGroup.GET("/ownPublicGroup/:userExternalId", wrapper(group_r.GetOwnPublicGroup)) //// 2.19的新接口 - //imGroup.GET("/ownGroup", wrapper(GetOwnGroup)) - //imGroup.GET("/theirGroup/:userExternalId", wrapper(GetTheirGroups)) - //imGroup.PUT("/pluginReady/:groupId", wrapper(PluginReady)) - //imGroup.GET("/roomInfo/:groupId", LogRequestTime, wrapper(GetRoomInfo)) + imGroup.GET("/ownGroup", wrapper(group_r.GetOwnGroup)) + imGroup.GET("/theirGroup/:userExternalId", wrapper(group_r.GetTheirGroups)) + imGroup.PUT("/pluginReady/:groupId", wrapper(group_r.PluginReady)) + imGroup.GET("/roomInfo/:groupId", wrapper(group_r.GetRoomInfo)) // - //imGroup.GET("/password/:groupId", wrapper(GetGroupPassword)) - //imGroup.GET("/role/:groupId", wrapper(GetGroupRole)) - //imGroup.PUT("/role/:groupId", wrapper(SetGroupRole)) - //imGroup.POST("/role/accept", wrapper(AcceptMemberInvite)) - //imGroup.GET("/admin/:groupId", wrapper(GetGroupAdmin)) + imGroup.GET("/password/:groupId", wrapper(group_r.GetGroupPassword)) + imGroup.GET("/role/:groupId", wrapper(group_r.GetGroupRole)) + imGroup.PUT("/role/:groupId", wrapper(group_r.SetGroupRole)) + imGroup.POST("/role/accept", wrapper(group_r.AcceptMemberInvite)) + imGroup.GET("/admin/:groupId", wrapper(group_r.GetGroupAdmin)) // - //imGroup.PUT("/blacklist/:groupId", wrapper(AddGroupBlacklist)) - //imGroup.DELETE("/blacklist/:groupId", wrapper(RemoveGroupBlacklist)) - //imGroup.GET("/blacklist/:groupId", wrapper(GetGroupBlacklist)) - //imGroup.PUT("/kick/:groupId", wrapper(KickGroupMembers)) + imGroup.PUT("/blacklist/:groupId", wrapper(group_r.AddGroupBlacklist)) + imGroup.DELETE("/blacklist/:groupId", wrapper(group_r.RemoveGroupBlacklist)) + imGroup.GET("/blacklist/:groupId", wrapper(group_r.GetGroupBlacklist)) + imGroup.PUT("/kick/:groupId", wrapper(group_r.KickGroupMembers)) // - //imGroup.PUT("/allGroupMsg", wrapper(SendTextMsg)) + imGroup.PUT("/allGroupMsg", wrapper(group_r.SendTextMsg)) // - //imGroup.PUT("/welcomeText/:groupId", wrapper(SetWelcomeText)) - //imGroup.GET("/support/page/:groupId", wrapper(GetSupportPage)) - //imGroup.GET("/support/detail", wrapper(GetSupportDetail)) - //imGroup.GET("/support/award/:groupId", wrapper(TryAddSupporter)) - //imGroup.POST("/support/award/:groupId", wrapper(TakeSupportAward)) + imGroup.PUT("/welcomeText/:groupId", wrapper(group_r.SetWelcomeText)) + imGroup.GET("/support/page/:groupId", wrapper(group_r.GetSupportPage)) + imGroup.GET("/support/detail", wrapper(group_r.GetSupportDetail)) + imGroup.GET("/support/award/:groupId", wrapper(group_r.TryAddSupporter)) + imGroup.POST("/support/award/:groupId", wrapper(group_r.TakeSupportAward)) // //// 操作类,普通用户不用 //imGroup.PUT("/memberLimit", wrapper(SetGroupMemberLimit)) -- 2.22.0