package service import ( "context" "math" "time" mhayaTime "github.com/mhaya/extend/time" "github.com/mhaya/game/game_cluster/internal/code" "github.com/mhaya/game/game_cluster/internal/constant" "github.com/mhaya/game/game_cluster/internal/mdb" "github.com/mhaya/game/game_cluster/internal/mdb/models" "github.com/mhaya/game/game_cluster/nodes/webadmin/common" "github.com/mhaya/game/game_cluster/nodes/webadmin/entity" "github.com/mhaya/game/game_cluster/nodes/webadmin/model" mhayaLogger "github.com/mhaya/logger" "github.com/spf13/cast" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) type Synthesis struct { db *mongo.Database } func NewSynthesis() *Synthesis { return &Synthesis{ db: mdb.MDB, } } // 统计用户相关信息 func (s *Synthesis) UserList(req entity.UserListReq) (*entity.UserListResp, *code.Result) { page, pageSize := checkPageParam(req.Page, req.Size) // 构建查询条件 filter := bson.M{} if req.UserName != "" { filter["userName"] = req.UserName } if req.NickName != "" { filter["nickName"] = req.NickName } // 设置分页选项 findOptions := options.Find() findOptions.SetSkip(int64((page - 1) * pageSize)) findOptions.SetLimit(int64(pageSize)) findOptions.SetSort(bson.D{{"createTime", -1}}) ctx, cancel := context.WithTimeout(context.Background(), 100*time.Second) defer cancel() collection := mdb.MDB.Collection(constant.CNamePlayer) // 获取总数total count, err := collection.CountDocuments(ctx, filter) if err != nil { mhayaLogger.Warnf("UserList CountDocuments error:%v", err) return nil, common.NewResult(code.InternalError) } // TODO UserList 统计用户相关信息 // 查询数据 var results []*entity.UserListDetail cursor, err := collection.Find(ctx, filter, findOptions) if err != nil { mhayaLogger.Warnf("UserList Find error:%v", err) return nil, common.NewResult(code.InternalError) } defer cursor.Close(ctx) // 解析结果 for cursor.Next(ctx) { var result entity.UserListDetail if err := cursor.Decode(&result); err != nil { mhayaLogger.Warnf("UserList Decode error:%v", err) return nil, common.NewResult(code.InternalError) } results = append(results, &result) } if err := cursor.Err(); err != nil { mhayaLogger.Warnf("UserList cursor error:%v", err) return nil, common.NewResult(code.InternalError) } return &entity.UserListResp{ Details: results, Total: count, }, nil } func (s *Synthesis) FindMDBUserLogDaily(req entity.UserLogDailyReq) (*entity.UserLogDailyResp, *code.Result) { page, pageSize := checkPageParam(req.Page, req.Size) // 定义上下文 ctx, cancel := context.WithTimeout(context.Background(), 100*time.Second) defer cancel() // 指定集合 collection := mdb.MDB.Collection(constant.CNamePlayerDailyRecord) // 构建查询条件 - 如果查询值为空那就不添加查询条件 filter := bson.M{} if req.StartTime != 0 { filter["daily"] = bson.M{ "$gte": req.StartTime, "$lte": req.EndTime, } } if req.Platform != "" && req.Platform != "all" { filter["platform"] = req.Platform } if req.Channel != "" { filter["channel"] = req.Channel } // 计算总数 count, err := collection.CountDocuments(ctx, filter) if err != nil { mhayaLogger.Warnf("FindMDBUserLogDaily CountDocuments error:%v", err) return nil, common.NewResult(code.InternalError) } // 执行查询 opts := options.Find() opts.SetSkip(int64((page - 1) * pageSize)) opts.SetLimit(int64(pageSize)) opts.SetSort(bson.D{{"daily", -1}}) cursor, err := collection.Find(ctx, filter, opts) if err != nil { mhayaLogger.Warnf("FindMDBUserLogDaily Find error:%v", err) return nil, common.NewResult(code.InternalError) } defer cursor.Close(ctx) // 解析查询结果 var results []*entity.UserLogDailyDetail for cursor.Next(ctx) { var result *entity.UserLogDailyDetail err := cursor.Decode(&result) if err != nil { mhayaLogger.Warnf("FindMDBUserLogDaily Decode error:%v", err) return nil, common.NewResult(code.InternalError) } results = append(results, result) } // 同一天的数据全部放到platform= ALl 且数据累加 // 如果没有platform=all 的那就新增一个 同一个时间段只能有一个 platform=all // 将同一天的数据累加到platform=all的记录中 allPlatformRecordMap := make(map[int64]*entity.UserLogDailyDetail) for _, result := range results { allPlatformRecordMap[result.Timestamp] = &entity.UserLogDailyDetail{ Timestamp: result.Timestamp, Platform: "all", } } for _, v := range results { if v.Timestamp == allPlatformRecordMap[v.Timestamp].Timestamp { allPlatformRecordMap[v.Timestamp].Registered += v.Registered allPlatformRecordMap[v.Timestamp].LoggedIn += v.LoggedIn allPlatformRecordMap[v.Timestamp].NewActive += v.NewActive allPlatformRecordMap[v.Timestamp].OldActive += v.OldActive allPlatformRecordMap[v.Timestamp].TotalPoints += v.TotalPoints allPlatformRecordMap[v.Timestamp].UProduced += v.UProduced allPlatformRecordMap[v.Timestamp].UCashout += v.UCashout allPlatformRecordMap[v.Timestamp].NewLogin += v.NewLogin allPlatformRecordMap[v.Timestamp].OldLogin += v.OldLogin } } // 替换原有结果 for _, record := range allPlatformRecordMap { results = append(results, record) } return &entity.UserLogDailyResp{ Details: results, Total: count, }, nil } // FindWithdrawal 根据请求查询提现记录 func (s *Synthesis) FindWithdrawal(req entity.UserWithdrawalReq) (*entity.UserWithdrawalResp, *code.Result) { page, pageSize := checkPageParam(req.Page, req.Size) ctx, cancel := context.WithTimeout(context.Background(), 100*time.Second) defer cancel() collection := mdb.MDB.Collection(constant.CNameCashOutRecord) // 构建过滤器 filter := bson.M{} if req.UserName != "" { filter["userName"] = req.UserName } if req.NickName != "" { filter["nickName"] = req.NickName } if req.ID != "" { filter["_id"], _ = primitive.ObjectIDFromHex(req.ID) } if req.Address != "" { filter["address"] = req.Address } if req.State > 0 { filter["state"] = req.State } if req.Withdrawal > 0 { filter["withdrawal"] = req.Withdrawal } if req.StartTime > 0 && req.EndTime > 0 && req.StartTime <= req.EndTime { filter["createAt"] = bson.M{ "$gte": req.StartTime, "$lte": req.EndTime, } } if req.AmountMin > 0 && req.AmountMax > 0 && req.AmountMin <= req.AmountMax { filter["amount"] = bson.M{ "$gte": req.AmountMin, "$lte": req.AmountMax, } } if req.AfterAmountMin > 0 && req.AfterAmountMax > 0 && req.AfterAmountMin <= req.AfterAmountMax { filter["after_amount"] = bson.M{ "$gte": req.AfterAmountMin, "$lte": req.AfterAmountMax, } } // 设置分页选项 findOptions := options.Find() findOptions.SetSkip(int64((page - 1) * pageSize)) findOptions.SetLimit(int64(pageSize)) findOptions.SetSort(bson.D{{"createAt", -1}}) // 获取总数total count, err := collection.CountDocuments(ctx, filter) if err != nil { mhayaLogger.Warnf("FindWithdrawal CountDocuments error:%v", err) return nil, common.NewResult(code.InternalError) } // 查询数据 var results []*entity.UserWithdrawalDetail cursor, err := collection.Find(ctx, filter, findOptions) if err != nil { mhayaLogger.Warnf("FindWithdrawal Find error:%v", err) return nil, common.NewResult(code.InternalError) } defer cursor.Close(ctx) // 解析结果 for cursor.Next(ctx) { var result entity.UserWithdrawalDetail if err := cursor.Decode(&result); err != nil { mhayaLogger.Warnf("FindWithdrawal Decode error:%v", err) return nil, common.NewResult(code.InternalError) } results = append(results, &result) } if err := cursor.Err(); err != nil { mhayaLogger.Warnf("FindWithdrawal cursor error:%v", err) return nil, common.NewResult(code.InternalError) } return &entity.UserWithdrawalResp{ Details: results, Total: count, }, nil } // 导出提现记录 func (s *Synthesis) WithdrawalExport(req entity.UserWithdrawalExportReq) (*entity.UserWithdrawalResp, *code.Result) { ctx, cancel := context.WithTimeout(context.Background(), 100*time.Second) defer cancel() collection := mdb.MDB.Collection(constant.CNameCashOutRecord) // 构建过滤器 filter := bson.M{} if req.UserName != "" { filter["userName"] = req.UserName } if req.NickName != "" { filter["nickName"] = req.NickName } if req.ID != "" { filter["_id"], _ = primitive.ObjectIDFromHex(req.ID) } if req.Address != "" { filter["address"] = req.Address } if req.State > 0 { filter["state"] = req.State } if req.Withdrawal > 0 { filter["withdrawal"] = req.Withdrawal } if req.StartTime > 0 && req.EndTime > 0 && req.StartTime <= req.EndTime { filter["createAt"] = bson.M{ "$gte": req.StartTime, "$lte": req.EndTime, } } if req.AmountMin > 0 && req.AmountMax > 0 && req.AmountMin <= req.AmountMax { filter["amount"] = bson.M{ "$gte": req.AmountMin, "$lte": req.AmountMax, } } if req.AfterAmountMin > 0 && req.AfterAmountMax > 0 && req.AfterAmountMin <= req.AfterAmountMax { filter["after_amount"] = bson.M{ "$gte": req.AfterAmountMin, "$lte": req.AfterAmountMax, } } findOptions := options.Find() findOptions.SetSort(bson.D{{"createAt", -1}}) // 获取总数total count, err := collection.CountDocuments(ctx, filter) if err != nil { mhayaLogger.Warnf("WithdrawalExportData CountDocuments error:%v", err) return nil, common.NewResult(code.InternalError) } // 查询数据 var results []*entity.UserWithdrawalDetail cursor, err := collection.Find(ctx, filter, findOptions) if err != nil { mhayaLogger.Warnf("WithdrawalExportData Find error:%v", err) return nil, common.NewResult(code.InternalError) } defer cursor.Close(ctx) // 解析结果 for cursor.Next(ctx) { var result entity.UserWithdrawalDetail if err := cursor.Decode(&result); err != nil { mhayaLogger.Warnf("WithdrawalExportData Decode error:%v", err) return nil, common.NewResult(code.InternalError) } results = append(results, &result) } if err := cursor.Err(); err != nil { mhayaLogger.Warnf("WithdrawalExportData cursor error:%v", err) return nil, common.NewResult(code.InternalError) } return &entity.UserWithdrawalResp{ Details: results, Total: count, }, nil } // WithdrawalStatus 更新提现状态 func (s *Synthesis) WithdrawalStatus(req entity.UserWithdrawalStatus) *code.Result { // ctx, cancel := context.WithTimeout(context.Background(), 100*time.Second) // defer cancel() collection := mdb.MDB.Collection(constant.CNameCashOutRecord) // 更新条件 updateCondition := bson.M{"userName": req.UserName} // 更新内容 updateContent := bson.M{"$set": bson.M{"status": req.Status}} // 设置更新选项 updateOptions := options.Update() // 设置 upsert 选项 // 执行更新操作 _, err := collection.UpdateOne(context.TODO(), updateCondition, updateContent, updateOptions) if err != nil { mhayaLogger.Warnf("WithdrawalStatus UpdateOne error:%v", err) return common.NewResult(code.InternalError) } return nil } // WithdrawalStatusBatch 更新提现状态 func (s *Synthesis) WithdrawalStatusBatch(req entity.UserWithdrawalStatusBatch) *code.Result { collection := mdb.MDB.Collection(constant.CNameCashOutRecord) if len(req.ID) == 0 { return common.NewResult(code.ParamError) } for _, id := range req.ID { objID := primitive.ObjectID{} objID, _ = primitive.ObjectIDFromHex(id) updateCondition := bson.M{"_id": objID} updateContent := bson.M{} withdrawal := models.CashOutRecord{} err := collection.FindOne(context.TODO(), updateCondition).Decode(&withdrawal) if err != nil { mhayaLogger.Warnf("WithdrawalStatusBatch FindOne error:%v", err) continue } if req.Withdrawal != 0 { if withdrawal.Status == 1 { updateContent = bson.M{"$set": bson.M{"withdrawal": req.Withdrawal}} } else { continue } } if req.Status > 0 { if withdrawal.Status != 0 { continue } updateContent = bson.M{"$set": bson.M{"status": req.Status}} } updateOptions := options.Update().SetUpsert(true) _, err = collection.UpdateOne(context.TODO(), updateCondition, updateContent, updateOptions) if err != nil { mhayaLogger.Warnf("WithdrawalStatusBatch UpdateOne error:%v", err) continue } } return nil } // FindUserCountryCount 查询用户国家分布 // // 返回值为 UserCountryResp 的切片和错误。 func (s *Synthesis) FindUserCountryCount() (*entity.UserCountryResp, *code.Result) { // 选择数据库和集合 collection := mdb.MDB.Collection(constant.CNamePlayerCountryByIPStat) // 定义聚合管道 // 定义聚合管道 pipeline := []bson.D{ { {Key: "$project", Value: bson.D{ {Key: "playerRegisterCountry", Value: bson.D{{Key: "$objectToArray", Value: "$playerRegisterCountry"}}}, }}, }, { {Key: "$unwind", Value: "$playerRegisterCountry"}, }, { {Key: "$group", Value: bson.D{ {Key: "_id", Value: "$playerRegisterCountry.k"}, {Key: "totalValue", Value: bson.D{{Key: "$sum", Value: "$playerRegisterCountry.v"}}}, }}, }, { {Key: "$project", Value: bson.D{ {Key: "_id", Value: 0}, {Key: "countryKey", Value: "$_id"}, {Key: "totalValue", Value: 1}, }}, }, } // 执行聚合查询 cursor, err := collection.Aggregate(context.TODO(), pipeline) if err != nil { mhayaLogger.Warnf("FindUserCountryCount Aggregate error:%v", err) return nil, common.NewResult(code.InternalError) } defer cursor.Close(context.TODO()) // 遍历查询结果 var results []bson.M if err := cursor.All(context.TODO(), &results); err != nil { mhayaLogger.Warnf("FindUserCountryCount All error:%v", err) return nil, common.NewResult(code.InternalError) } var totalIPCount int64 var data []*entity.UserCountryDetail // 将结果转换为 UserCountryDetail for _, r := range results { var resp entity.UserCountryDetail resp.Country = r["countryKey"].(string) resp.IPCount = int(r["totalValue"].(int32)) totalIPCount += int64(resp.IPCount) data = append(data, &resp) } for _, v := range data { // 保留小数点后两位 v.Percentage = math.Round(float64(v.IPCount)/float64(totalIPCount)*10000) / 100 } // 根据阈值过滤结果 otherCount := 0 otherPercentage := 0.00 filteredResults := make([]*entity.UserCountryDetail, 0) threshold := 1.00 for _, r := range data { if r.Percentage >= threshold { filteredResults = append(filteredResults, r) // 保留小数点后两位 r.Percentage = math.Round(r.Percentage*100) / 100 otherPercentage += r.Percentage } else { otherCount += r.IPCount } } // 将其他国家添加到过滤后的结果中 if otherCount > 0 { p := 100.00 - math.Round(otherPercentage*100)/100 filteredResults = append(filteredResults, &entity.UserCountryDetail{ Country: "other", IPCount: otherCount, Percentage: math.Round(p*100) / 100, }) } return &entity.UserCountryResp{ Details: filteredResults, }, nil } // FindUserRetention UserRetentionResp 用户留存率 // 1. 获取指定日期范围内的注册用户数量 // 2. 获取指定日期范围内的活跃用户数量 // 3. 计算留存率 // 4. 返回结果 func (s *Synthesis) FindUserRetention(req entity.UserRetentionReq) (*entity.UserRetentionResp, *code.Result) { playerPreserve := models.GetPlayerPreserve(req.StartTime, req.EndTime) // 查询数据 var results []*entity.UserRetentionDetail for key, v := range playerPreserve { var retention entity.Retention for _, vv := range v { if vv.ID == 1 { retention.Day1 = entity.DayRetention{ LoggedIn: vv.Ratio, LoginDate: key, } } if vv.ID == 3 { retention.Day3 = entity.DayRetention{ LoggedIn: vv.Ratio, LoginDate: key, } } if vv.ID == 7 { retention.Day7 = entity.DayRetention{ LoggedIn: vv.Ratio, LoginDate: key, } } if vv.ID == 14 { retention.Day14 = entity.DayRetention{ LoggedIn: vv.Ratio, LoginDate: key, } } if vv.ID == 30 { retention.Day30 = entity.DayRetention{ LoggedIn: vv.Ratio, LoginDate: key, } } } results = append(results, &entity.UserRetentionDetail{ RegistrationDate: key, RetentionData: retention, }) } return &entity.UserRetentionResp{ Details: results, }, nil } // FindUserLevel 用户等级统计 func (s *Synthesis) FindUserLevel() (*entity.UserLevelCountResp, *code.Result) { ctx, cancel := context.WithTimeout(context.Background(), 100*time.Second) defer cancel() collection := mdb.MDB.Collection(constant.CNamePlayerLevelStat) // 查询所有文档 cursor, err := collection.Find(ctx, bson.M{}) if err != nil { mhayaLogger.Warnf("FindUserLevel Find error:%v", err) return nil, common.NewResult(code.InternalError) } defer cursor.Close(ctx) var results []*entity.UserLevelCountDetail var reData []*models.PlayerLevelStat for cursor.Next(ctx) { var result models.PlayerLevelStat if err := cursor.Decode(&result); err != nil { mhayaLogger.Warnf("FindUserLevel Decode error:%v", err) return nil, common.NewResult(code.InternalError) } reData = append(reData, &result) } if err := cursor.Err(); err != nil { mhayaLogger.Warnf("FindUserLevel cursor error:%v", err) return nil, common.NewResult(code.InternalError) } dataMap := make(map[string]int) for _, v := range reData { for key, v1 := range v.ServerLevel { dataMap[key] += v1 } } for k, v := range dataMap { results = append(results, &entity.UserLevelCountDetail{ Level: cast.ToInt(k), UserCount: v, }) } return &entity.UserLevelCountResp{ Details: results, }, nil } func (s *Synthesis) InsertRecord(param model.UserOperationLog) { mhayaLogger.Warnf("InsertRecord param:%#v", param) record := new(model.UserOperationLog) collection := mdb.MDB.Collection(record.TableName()) insertData := bson.M{} insertData["user_name"] = param.Username insertData["role_id"] = param.RoleId insertData["url"] = param.Path insertData["method"] = param.Method insertData["status_code"] = param.StatusCode insertData["dur"] = param.Dur insertData["client_ip"] = param.ClientIP insertData["error_message"] = param.ErrorMessage insertData["created_at"] = mhayaTime.Now().Unix() _, err := collection.InsertOne(context.Background(), insertData) if err != nil { mhayaLogger.Warnf("InsertRecord InsertOne error:%v", err) return } } func (s *Synthesis) RecordList(req entity.RecordListReq) (*entity.RecordListResp, *code.Result) { page, pageSize := checkPageParam(req.Page, req.Size) record := new(model.UserOperationLog) collection := mdb.MDB.Collection(record.TableName()) // 构建过滤器 filter := bson.M{} if req.UserName != "" { filter["userName"] = req.UserName } if req.RoleId != "" { filter["role_id"] = req.RoleId } if req.ID != "" { id, err := primitive.ObjectIDFromHex(req.ID) if err != nil { mhayaLogger.Warnf("RecordList ObjectIDFromHex error:%v, req.ID:%s", err, req.ID) return nil, common.NewResult(code.ParamError) } filter["id"] = id } if req.StartTime != 0 { filter["createAt"] = bson.M{ "$gte": req.StartTime, "$lte": req.EndTime, } } // 设置分页选项 findOptions := options.Find() findOptions.SetSkip(int64((page - 1) * pageSize)) findOptions.SetLimit(int64(pageSize)) findOptions.SetSort(bson.D{{"created_at", -1}}) // 获取总数total ctx, cancel := context.WithTimeout(context.Background(), 100*time.Second) defer cancel() count, err := collection.CountDocuments(ctx, filter) if err != nil { mhayaLogger.Warnf("RecordList CountDocuments error:%v", err) return nil, common.NewResult(code.InternalError) } // 查询数据 var results []*entity.RecordListDetail cursor, err := collection.Find(ctx, filter, findOptions) if err != nil { mhayaLogger.Warnf("RecordList Find error:%v", err) return nil, common.NewResult(code.InternalError) } defer cursor.Close(ctx) // 解析结果 for cursor.Next(ctx) { var result entity.RecordListDetail if err := cursor.Decode(&result); err != nil { mhayaLogger.Warnf("RecordList Decode error:%v", err) return nil, common.NewResult(code.InternalError) } results = append(results, &result) } if err := cursor.Err(); err != nil { mhayaLogger.Warnf("RecordList cursor error:%v", err) return nil, common.NewResult(code.InternalError) } return &entity.RecordListResp{ Details: results, Total: count, }, nil } // 转盘统计 func (s *Synthesis) Turntable(req entity.TurntableReq) (*entity.TurntableResp, *code.Result) { // TODO Turntable 转盘统计 return nil, nil } // 资产统计 func (s *Synthesis) Assets(req entity.AssetsReq) (*entity.AssetsResp, *code.Result) { // TODO Assets 资产统计 return nil, nil } // 邀请统计 func (s *Synthesis) Invite(req entity.InviteReq) (*entity.InviteResp, *code.Result) { // TODO Invite 邀请统计 return nil, nil } // 活跃统计 func (s *Synthesis) Active(req entity.ActiveReq) (*entity.ActiveResp, *code.Result) { // TODO Active 活跃统计 return nil, nil }