package service import ( "encoding/json" "fmt" "gadmin/config" "gadmin/internal/admin/consts" "gadmin/internal/admin/forms" "gadmin/internal/elastic/eapi" "gadmin/internal/gorm/model" "gadmin/internal/gorm/query" "gadmin/package/gmdata" "gadmin/utility" "gadmin/utility/player" "gadmin/utility/serializer" "math" "os" "sort" "strings" "sync" "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" ) // Chapter 章节服务 var Chapter = new(sChapter) type sChapter struct{} func (s *sChapter) OrdinaryList(ctx *gin.Context, req forms.ChapterOrdinaryListReq) (resp serializer.Response) { type passData struct { Id int32 `json:"id"` Name string `json:"name"` RoomCount int32 `json:"roomCount"` Count int64 `json:"count"` Difficulty int64 `json:"difficulty"` Pass bool `json:"pass"` IsActivity bool `json:"isActivity"` } DB, err := player.GetDBByUserId(req.PlayerId) if err != nil { return serializer.Err(consts.CodeParamErr, "GetDBByUserId err", err) } var ( p = query.Use(config.GDBGroup[DB]).PlayerMaterial passDatas []passData models forms.ListRes ) material, err := p.WithContext(ctx).Where(p.Playerid.Eq(req.PlayerId)).First() if err != nil { return serializer.Err(consts.CodeParamErr, "查询出错 Find", err) } if material == nil || material.ID == 0 || material.PassRoom == "null" { return serializer.Suc(models) } if err := json.Unmarshal([]byte(material.PassRoom), &passDatas); err != nil { return serializer.Err(consts.CodeParamErr, "Unmarshal err", err) } tmp := make([]passData, 0) for k, v := range passDatas { if v.Id == 0 || v.Id > 99 { continue } data := gmdata.GetChapterById(v.Id) if data == nil { data = &gmdata.Chapter{ Name: `未知`, } } passDatas[k].Name = data.Name passDatas[k].RoomCount = data.RoomCount tmp = append(tmp, passDatas[k]) } // 使用自定义的 Less 函数来指定排序规则 sort.SliceStable(tmp, func(i, j int) bool { if tmp[i].Id != tmp[j].Id { return tmp[i].Id < tmp[j].Id // 根据 Id 升序排列 } return tmp[i].Difficulty < tmp[j].Difficulty // 如果 Id 相同,则根据 Difficulty 升序排列 }) models.List = tmp return serializer.Suc(models) } func (s *sChapter) ActivityList(ctx *gin.Context, req forms.ChapterActivityListReq) (resp serializer.Response) { type passData struct { Id int32 `json:"id"` Difficulty int32 `json:"difficulty"` DifficultyName string `json:"difficultyName"` Name string `json:"name"` Degree int32 `json:"degree"` RewardMulti float64 `json:"rewardMulti"` RewardEffectTime int64 `json:"rewardEffectTime"` ChallengeCount int64 `json:"challenge_count"` ClearanceCount int64 `json:"clearance_count"` FailCount int64 `json:"fail_count"` } DB, err := player.GetDBByUserId(req.PlayerId) if err != nil { return serializer.Err(consts.CodeParamErr, "GetDBByUserId err", err) } var ( c = query.Use(config.DB).Chapter passDatas []passData models forms.ListRes z = query.Use(config.GDBGroup[DB]).ZoneActivity ) chapters, err := c.WithContext(ctx).Where(c.PlayerID.Eq(req.PlayerId)).Find() if err != nil { return serializer.Err(consts.CodeParamErr, "查询出错 Find", err) } if len(chapters) == 0 { return serializer.Suc(models) } zone, err := z.WithContext(ctx).Where(z.Playerid.Eq(req.PlayerId)).First() if err != nil { return serializer.Err(consts.CodeParamErr, "查询出错 Find", err) } // BonusProperties 加成属性 type BonusProperties struct { DifScale int `json:"difScale"` // 难度系数 RewardMulti float64 `json:"rewardMulti"` // 奖励倍数 RewardEffectTime int64 `json:"rewardEffectTime"` // 奖励倍数生效时间 } bonusProperties := make(map[string]*BonusProperties) //difficulty := make(map[string]int32) if zone != nil { if err = json.Unmarshal([]byte(zone.BonusProperties), &bonusProperties); err != nil { return serializer.Err(consts.CodeParamErr, "Unmarshal err", err) } } logrus.Warnf("bonusProperties:%+v", bonusProperties) //chapterIds := []int64{100, 101, 102, 103} //var difficultys = []int32{0, 1, 2} for _, v := range chapters { var data = gmdata.GetChapterById(v.ChapterID) if data == nil { data = &gmdata.Chapter{ Name: `未知`, } } //var ds = []int32{0, 1, 2} //if v.ChapterID == 103 { // ds = []int32{0} //} // //for _, difficulty := range ds { // // //} bs, ok := bonusProperties[fmt.Sprintf("%v_%v", v.ChapterID, v.Difficulty)] if ok { passDatas = append(passDatas, passData{ Id: v.ChapterID, Difficulty: v.Difficulty, DifficultyName: gmdata.GetDifficultName(int64(v.Difficulty)), Name: data.Name, Degree: int32(bs.DifScale), RewardMulti: bs.RewardMulti, RewardEffectTime: bs.RewardEffectTime, ChallengeCount: int64(v.ClearanceCount + v.FailCount), ClearanceCount: int64(v.ClearanceCount), FailCount: int64(v.FailCount), }) } else { // 没有数据视为没有参与过,直接不显示 //passDatas = append(passDatas, passData{ // Id: v.ChapterID, // Difficulty: difficulty, // Name: data.Name, // Degree: 100, // 这里写死了100,其实有的初始值并不是100,需要知道 // RewardMulti: 1, // RewardEffectTime: 0, // ChallengeCount: int64(v.ClearanceCount + v.FailCount), // ClearanceCount: int64(v.ClearanceCount), // FailCount: int64(v.FailCount), //}) } } // 远征 var ( cf = query.Use(config.GDBGroup[DB]).Climbfloor ) cfd, _ := cf.WithContext(ctx).Where(cf.Playerid.Eq(req.PlayerId)).First() //if err != nil { // return serializer.Err(consts.CodeParamErr, "查询出错 Climbfloor Find", err) //} if cfd == nil { passDatas = append(passDatas, passData{ Id: 105, Name: "远征", Degree: 0, DifficultyName: gmdata.GetDifficultName(int64(0)), //ChallengeCount: int64(v.ClearanceCount + v.FailCount), //ClearanceCount: int64(v.ClearanceCount), //FailCount: int64(v.FailCount), }) } else { passDatas = append(passDatas, passData{ Id: 105, Name: "远征", Degree: cfd.MaxFloor, DifficultyName: gmdata.GetDifficultName(int64(0)), //ChallengeCount: int64(v.ClearanceCount + v.FailCount), //ClearanceCount: int64(v.ClearanceCount), //FailCount: int64(v.FailCount), }) } models.List = passDatas return serializer.Suc(models) } func (s *sChapter) doReconnectFromDB(ctx *gin.Context, params forms.ChapterReconnectReq, chapterId, difficulty int32, isNew bool) (count, userCount int64, err error) { type Result struct { TotalCount int64 `gorm:"total_count"` UserCount int64 `gorm:"user_count"` } begin, end, _ := utility.GetBeginAndEndOfDay2(params.Day, params.EndDay) end = end + 1 table := model.GetChapterTable2(chapterId) var ( q = query.Use(config.DB).ChapterLog.Table(table) db = q.UnderlyingDB() ) db = db.Where("event_id = 2 and extra like '%0%' and difficulty = ? and event_at >= ? and event_at <= ?", difficulty, begin, end) if params.ServerId > 0 { db = db.Where("server_id = ?", params.ServerId) } switch params.ChannelId { case consts.ChannelIdNone: // 不选择渠道 case consts.ChannelIdAllAdv, consts.ChannelIdAllWx, consts.ChannelIdAllTT: // 所有广告渠道 db = db.Where("channel_id in (?)", Channel.GetIdsByType(params.ChannelId)) default: // 选择指定渠道 db = db.Where("channel_id = ?", params.ChannelId) } res := make([]*Result, 0) if isNew { err = db.Select("count(*) AS total_count, count( DISTINCT user_id ) AS user_count, DATE (FROM_UNIXTIME( user_created_at )) AS d").Where("user_created_at >= ? and user_created_at <= ?", begin, end).Group("d").Find(&res).Error } else { err = db.Select("count(*) AS total_count, count( DISTINCT user_id ) AS user_count").Find(&res).Error } if err != nil { logrus.Errorf("查询出错:%+v", err) if strings.Contains(err.Error(), "1146") { createSql := "CREATE TABLE IF NOT EXISTS `%s` (`id` bigint unsigned NOT NULL AUTO_INCREMENT,`channel_id` varchar(128) COLLATE utf8mb4_general_ci DEFAULT '0' COMMENT '渠道ID',`flag` int DEFAULT '0',`user_id` bigint unsigned NOT NULL COMMENT '用户id',`server_id` int NOT NULL DEFAULT '1' COMMENT '服务器ID',`event_id` tinyint unsigned NOT NULL COMMENT '埋点id',`chapter_id` smallint unsigned NOT NULL COMMENT '关卡id',`difficulty` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '困难度',`room_id` bigint NOT NULL COMMENT '房间id',`user_created_at` int unsigned NOT NULL COMMENT '用户注册时间',`event_at` int unsigned NOT NULL COMMENT '埋点事件触发的时间',`event_at_ns` bigint unsigned NOT NULL,`token` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '登陆的token',`extra` text COLLATE utf8mb4_general_ci COMMENT '其他参数',PRIMARY KEY (`id`),UNIQUE KEY `user_event_ns` (`user_id`,`event_at_ns`),KEY `created_chapter_event_event_at_idx` (`user_created_at`,`chapter_id`,`event_id`,`event_at`),KEY `event_id_event_at_idx` (`event_id`,`event_at`),KEY `server_id` (`server_id`),KEY `channel_id` (`channel_id`),KEY `idx_event_id` (`event_id`) USING BTREE,KEY `server_id_difficulty_user_id_event_id_extra_index` (`server_id`,`difficulty`,`user_id`,`event_id`),KEY `chapter_logs_100_event_at_index` (`event_at`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='关卡信息埋点日志';" if err := q.UnderlyingDB().Exec(fmt.Sprintf(createSql, table)).Error; err != nil { logrus.Errorf("创建表%s错误:%+v", table, err) } } return 0, 0, err } for _, v := range res { count += v.TotalCount userCount += v.UserCount } return } func (s *sChapter) doReconnect(ctx *gin.Context, params forms.ChapterReconnectReq, chapterId, difficulty int32, isNew bool) (count, userCount int64, err error) { begin, end, _ := utility.GetBeginAndEndOfDay2(params.Day, params.EndDay) end = end + 1 index := fmt.Sprintf("%s%s", os.Getenv("ELASTIC_TOPIC"), model.GetChapterTable2(chapterId)) var ( mustNot []eapi.M where eapi.M ) must := []eapi.M{ { "term": eapi.M{ "event_id": "2", }, }, { "match": eapi.M{ "extra": "0", }, }, { "term": eapi.M{ "chapter_id": chapterId, }, }, { "term": eapi.M{ "difficulty": difficulty, }, }, { "range": eapi.M{ "event_at": eapi.M{ "gte": begin, "lte": end, }, }, }, } if params.ServerId > 0 { must = append(must, eapi.M{ "term": eapi.M{ "server_id": params.ServerId, }, }) } switch params.ChannelId { case consts.ChannelIdNone: // 不选择渠道 case consts.ChannelIdAllAdv, consts.ChannelIdAllWx, consts.ChannelIdAllTT: // 所有广告渠道 must = append(must, eapi.M{ "terms": eapi.M{ "channel_id": Channel.GetIdsByType(params.ChannelId), }, }) default: // 选择指定渠道 must = append(must, eapi.M{ "term": eapi.M{ "channel_id": params.ChannelId, }, }) } //if params.ChannelId != "" { // if params.ChannelId == "1" { // mustNot = append(mustNot, eapi.M{ // "term": eapi.M{ // "channel_id": "0", // }, // }) // } else { // must = append(must, eapi.M{ // "term": eapi.M{ // "channel_id": params.ChannelId, // }, // }) // } // //} // 新用户 if isNew { // 同一天 if begin+86400 == end { must = append(must, eapi.M{ "range": eapi.M{ "user_created_at": eapi.M{ "gte": begin, "lte": end, }, }, }) } else { // 多天的,分析每天的新用户 for i := begin; i < end; i += 86400 { var newMust = must newMust = append(newMust, eapi.M{ "range": eapi.M{ "user_created_at": eapi.M{ "gte": i, "lte": i + 86400, }, }, }) if len(mustNot) > 0 { where = eapi.M{ "query": eapi.M{ "bool": eapi.M{ "must": newMust, "must_not": mustNot, }, }, } } else { where = eapi.M{ "query": eapi.M{ "bool": eapi.M{ "must": newMust, }, }, } } res, err := eapi.Count(ctx, index, where) if err != nil { return 0, 0, err } if res > 0 { count += res } res2, err := eapi.Cardinality(ctx, index, where, "user_id") if err != nil { return 0, 0, err } if res2 > 0 { userCount += res2 } } return } } if len(mustNot) > 0 { where = eapi.M{ "query": eapi.M{ "bool": eapi.M{ "must": must, "must_not": mustNot, }, }, } } else { where = eapi.M{ "query": eapi.M{ "bool": eapi.M{ "must": must, }, }, } } //logrus.Warnf("doReconnect index:%v\n where:%v\n", index, utility.DumpToJSON(where)) res, err := eapi.Count(ctx, index, where) if err != nil { return 0, 0, err } res2, err := eapi.Cardinality(ctx, index, where, "user_id") if err != nil { return 0, 0, err } return res, res2, nil } func (s *sChapter) Reconnect(ctx *gin.Context, params forms.ChapterReconnectReq) (resp serializer.Response) { chapterMap := gmdata.GetChaptersMap() var ( wg sync.WaitGroup errCh = make(chan error, len(chapterMap)) ) list := make([]forms.ChapterReconnectItem, 0) for _, v := range chapterMap { wg.Add(1) go func(v *gmdata.Chapter) { defer wg.Done() //count, users, err := s.doReconnect(ctx, params, int32(v.ID), int32(v.Difficulty), false) count, users, err := s.doReconnectFromDB(ctx, params, int32(v.ID), int32(v.Difficulty), false) if err != nil { errCh <- err return } //newCount, newUsers, err := s.doReconnect(ctx, params, int32(v.ID), int32(v.Difficulty), true) newCount, newUsers, err := s.doReconnectFromDB(ctx, params, int32(v.ID), int32(v.Difficulty), true) if err != nil { errCh <- err return } list = append(list, forms.ChapterReconnectItem{ Id: len(list), ChapterId: int(v.ID), Chapter: Dash.GetChapterName(v.ID, v.Difficulty), DifficultyIndex: int(v.Difficulty), Difficulty: gmdata.GetDifficultName(v.Difficulty), DieCount: count, DieUserCount: users, NewDieCount: newCount, NewDieUserCount: newUsers, }) }(v) } wg.Wait() select { case err1 := <-errCh: return serializer.Err(consts.CodeParamErr, err1.Error(), err1) default: } for k, v := range list { list[k].Index = v.ChapterId*1000 + v.DifficultyIndex } sort.Sort(forms.ChapterReconnectItemSlice(list)) pageCount := int64(len(list)) models := new(forms.ChapterReconnectRespData) models.Page = params.Page models.PerPage = params.PerPage models.PageCount = int64(math.Ceil(float64(pageCount) / float64(params.PerPage))) startInx := (models.Page - 1) * models.PerPage endInx := (models.Page) * models.PerPage if startInx < 0 { startInx = 0 } if endInx > pageCount { endInx = pageCount } subList := list[startInx:endInx] models.List = subList return serializer.Suc(models) }