diff --git a/common/time.go b/common/time.go index e54bfaa..907796b 100644 --- a/common/time.go +++ b/common/time.go @@ -204,3 +204,8 @@ func StrTimeToTime(s string) time.Time { t, _ := time.ParseInLocation("2006-01-02 15:04:05", s, time.Local) return t } + +func StrRFC3339TimeToTime(s string) time.Time { + t, _ := time.Parse(time.RFC3339, s) + return t +} diff --git a/gamerule/thirteen/logic.go b/gamerule/thirteen/logic.go index 9a72eaa..d6f922f 100644 --- a/gamerule/thirteen/logic.go +++ b/gamerule/thirteen/logic.go @@ -105,7 +105,7 @@ type Group struct { Head [3]int Mid [5]int End [5]int - PokerType int + PokerType int // -1 无牌数据,0 有牌数据, 1-13 特殊牌型 } func (p *Group) String() string { diff --git a/gamesrv/base/divisionsystem.go b/gamesrv/base/divisionsystem.go deleted file mode 100644 index f63a62c..0000000 --- a/gamesrv/base/divisionsystem.go +++ /dev/null @@ -1,6 +0,0 @@ -package base - -// 提供税收和流水,根据代理需求后台进行分账 -func ProfitDistribution(p *Player, tax, taxex, validFlow int64) { - //LogChannelSingleton.WriteMQData(model.GenerateTaxDivide(p.SnId, p.Platform, p.Channel, p.BeUnderAgentCode, p.PackageID, tax, taxex, validFlow, p.scene.GameId, p.scene.GameMode, p.scene.GetDBGameFree().GetId(), p.PromoterTree)) -} diff --git a/gamesrv/base/gamedetail.go b/gamesrv/base/gamedetail.go new file mode 100644 index 0000000..9b43209 --- /dev/null +++ b/gamesrv/base/gamedetail.go @@ -0,0 +1,255 @@ +package base + +import ( + "time" + + "github.com/globalsign/mgo/bson" + + "mongo.games.com/game/model" + "mongo.games.com/game/mq" +) + +/* + 记录游戏对局记录 +*/ + +type SaveGameDetailedParam struct { + LogId string // 日志id + Detail string // 游戏详细信息 + GameTime int64 // 游戏时长 + Trend20Lately string // 最近20局开奖结果 + CtrlType int // 调控类型 1控赢 2控输 + PlayerPool map[int]int // 个人水池分 + OnlyLog bool // 只返回日志,不保存 +} + +// SaveGameDetailedLog 保存游戏详细记录 +func (this *Scene) SaveGameDetailedLog(param *SaveGameDetailedParam) *SaveGameDetailedCopy { + if this == nil || param == nil { + return nil + } + + if param.GameTime <= 0 { + param.GameTime = int64(time.Now().Sub(this.GameNowTime).Seconds()) + } + + if param.GameTime < 0 { + param.GameTime = 0 + } + + now := time.Now() + + var ret SaveGameDetailedCopy + ret.Param = param + f := func(plt string) { + log := &model.GameDetailedLog{ + Id: bson.NewObjectId(), + LogId: param.LogId, + GameId: this.GameId, + Platform: plt, + MatchId: this.GetMatch().GetMatchSortId(), + SceneId: this.SceneId, + GameMode: this.GameMode, + GameFreeid: this.GetGameFreeId(), + PlayerCount: int32(len(this.Players)), + GameTiming: int32(param.GameTime), + GameBaseBet: this.GetBaseScore(), + GameDetailedNote: param.Detail, + GameDetailVer: GameDetailedVer[int(this.GameId)], + CpCtx: this.CpCtx, + Time: now, + Trend20Lately: param.Trend20Lately, + Ts: now.Unix(), + CtrlType: param.CtrlType, + PlayerPool: make(map[int]int), + CycleId: this.CycleID, + } + for k, v := range param.PlayerPool { + log.PlayerPool[k] = v + } + if param.OnlyLog { + ret.Log = append(ret.Log, log) + } else { + mq.Write(log) + } + } + + switch { + case this.IsCoinScene(): + mapPlatform := make(map[string]bool) + for _, v := range this.Players { + if v == nil { + continue + } + if _, ok := mapPlatform[v.Platform]; ok { + continue + } + mapPlatform[v.Platform] = true + f(v.Platform) + } + default: + f(this.Platform) + } + return &ret +} + +type SaveGamePlayerListLogParam struct { + LogId string // 详情日志id + Platform string // 平台 + Snid int32 // 玩家id + PlayerName string // 玩家名字 + Channel string // 渠道 + ChannelId string // 推广渠道 + TotalIn int64 // 总投入 + TotalOut int64 // 总产出(税前) + TaxCoin int64 // 总税收 + BetAmount int64 // 下注量 + WinAmountNoAnyTax int64 // 税后赢取额(净利润,正负值) + IsFirstGame bool // 是否第一次游戏 + IsFree bool // 拉霸专用 是否免费 + WinSmallGame int64 // 拉霸专用 小游戏奖励 + WinTotal int64 // 拉霸专用 本局输赢 + GameTime int64 // 游戏时长 + OnlyLog bool // 只返回日志,不保存 +} + +// SaveGamePlayerListLog 保存玩家对局记录 +func (this *Scene) SaveGamePlayerListLog(param *SaveGamePlayerListLogParam) *SaveGamePlayerListLogCopy { + if this == nil { + return nil + } + if param == nil { + return nil + } + + p := this.GetPlayer(param.Snid) + if p == nil { + return nil + } + + if param.PlayerName == "" { + param.PlayerName = p.Name + } + + var ret SaveGamePlayerListLogCopy + ret.Param = param + + baseScore := this.GetBaseScore() + + // 上报玩家游戏记录 + if !p.IsRob && (param.IsFree || param.TotalIn != 0 || param.TotalOut != 0) { + e := p.ReportGameEvent(&ReportGameEventParam{ + Tax: param.TaxCoin, + Change: param.WinAmountNoAnyTax, + In: param.TotalIn, + Out: param.TotalOut, + GameTime: param.GameTime, + OnlyLog: param.OnlyLog, + }) + if e != nil { + ret.UpLog = e + } + } + + // 保存玩家游戏日志 + now := time.Now() + log := &model.GamePlayerListLog{ + LogId: bson.NewObjectId(), + SnId: p.SnId, + Name: param.PlayerName, + GameId: this.GameId, + BaseScore: baseScore, + TaxCoin: param.TaxCoin, + Platform: param.Platform, + Channel: param.Channel, + SceneId: this.SceneId, + GameMode: this.GameMode, + GameFreeid: this.GetGameFreeId(), + GameDetailedLogId: param.LogId, + IsFirstGame: param.IsFirstGame, + BetAmount: param.BetAmount, + WinAmountNoAnyTax: param.WinAmountNoAnyTax, + TotalIn: param.TotalIn, + TotalOut: param.TotalOut, + Time: now, + RoomType: this.SceneMode, + GameDif: this.GetDBGameFree().GetGameDif(), + GameClass: this.GetDBGameFree().GetGameClass(), + MatchId: this.GetMatch().GetMatchSortId(), + MatchType: int64(this.GetMatch().GetMatchType()), + Ts: now.Unix(), + IsFree: param.IsFree, + WinSmallGame: param.WinSmallGame, + WinTotal: param.WinTotal, + CycleId: this.CycleID, + } + if param.OnlyLog { + ret.Log = append(ret.Log, log) + } else { + mq.Write(log) + } + return &ret +} + +// SaveGamePlayerListLogCopy 临时记录 +// 为了拉霸统计游戏时长,需要临时缓存游戏记录 +type SaveGamePlayerListLogCopy struct { + Param *SaveGamePlayerListLogParam + Log []*model.GamePlayerListLog + UpLog *ReportGameEventOnly // mq上报数据 +} + +func (s *SaveGamePlayerListLogCopy) Save() { + for _, v := range s.Log { + mq.Write(v) + } + if s.UpLog != nil { + for _, v := range s.UpLog.Log { + mq.Write(v, mq.BackGameRecord) + } + } +} + +// SaveGameDetailedCopy 临时记录 +// 为了拉霸统计游戏时长,需要临时缓存游戏记录 +type SaveGameDetailedCopy struct { + Param *SaveGameDetailedParam + Log []*model.GameDetailedLog +} + +func (s *SaveGameDetailedCopy) Save() { + for _, v := range s.Log { + mq.Write(v) + } +} + +// LabaLog 拉霸缓存游戏记录 +type LabaLog struct { + PlayerListLog *SaveGamePlayerListLogCopy + GameDetailLog *SaveGameDetailedCopy +} + +// Cache 临时缓存 +func (l *LabaLog) Cache(s *Scene, detailLog *SaveGameDetailedParam, playerListLog *SaveGamePlayerListLogParam) { + if s == nil { + return + } + detailLog.OnlyLog = true + playerListLog.OnlyLog = true + l.GameDetailLog = s.SaveGameDetailedLog(detailLog) + l.PlayerListLog = s.SaveGamePlayerListLog(playerListLog) +} + +// Save 保存 +func (l *LabaLog) Save(f func(log *LabaLog)) { + f(l) + l.PlayerListLog.Save() + l.GameDetailLog.Save() + l.Clear() +} + +// Clear 清空 +func (l *LabaLog) Clear() { + l.PlayerListLog = nil + l.GameDetailLog = nil +} diff --git a/gamesrv/base/gamewarning.go b/gamesrv/base/gamewarning.go index 4a0ddbe..a4e0021 100644 --- a/gamesrv/base/gamewarning.go +++ b/gamesrv/base/gamewarning.go @@ -68,27 +68,6 @@ func NewGameWarning(param string) { }), nil, "NewGameWarning").Start() } -// func WarningLoseCoin(gameFreeId int32, snid int32, loseCoin int64) { -// if model.GameParamData.WarningLoseLimit == 0 { -// return -// } -// if loseCoin < model.GameParamData.WarningLoseLimit { -// return -// } -// NewGameWarning(fmt.Sprintf(`{"WarningType":%v,"WarningGame":%v,"WarningSnid":%v,"LoseCoin":%v}`, -// Warning_LoseCoinLimit, gameFreeId, snid, loseCoin)) -// - -//func WarningBetCoinCheck(sceneId, gameFreeId int32, snid int32, betCoin int64) { -// if model.GameParamData.WarningBetMax == 0 { -// return -// } -// if betCoin > model.GameParamData.WarningBetMax { -// NewGameWarning(fmt.Sprintf(`{"WarningType":%v,"WarningSnid":%v,"WarningGame":%v,"WarningScene":%v}`, -// Warning_BetCoinMax, snid, gameFreeId, sceneId)) -// } -//} - func WarningCoinPool(warnType int, gameFreeId int32) { NewGameWarning(fmt.Sprintf(`{"WarningType":%v,"WarningGame":%v}`, warnType, gameFreeId)) @@ -97,24 +76,3 @@ func WarningBlackPlayer(snid, gameFreeId int32) { NewGameWarning(fmt.Sprintf(`{"WarningType":%v,"WarningSnid":%v,"WarningGame":%v}`, Warning_BlackPlayer, snid, gameFreeId)) } - -//func WarningWinnerRate(snid int32, winCoin, loseCoin int64) { -// if model.GameParamData.WarningWinRate == 0 { -// return -// } -// if (winCoin+1)/(loseCoin+1) < model.GameParamData.WarningWinRate { -// return -// } -// NewGameWarning(fmt.Sprintf(`{"WarningType":%v,"WarningSnid":%v,"WarningRate":%v,"WinCoin":%v,"LoseCoin":%v}`, -// Warning_WinRate, snid, (winCoin+1)/(loseCoin+1), winCoin, loseCoin)) -//} -//func WarningWinnerCoin(snid int32, winCoin, loseCoin int64) { -// if model.GameParamData.WarningWinMoney == 0 { -// return -// } -// if (winCoin - loseCoin) < model.GameParamData.WarningWinMoney { -// return -// } -// NewGameWarning(fmt.Sprintf(`{"WarningType":%v,"WarningSnid":%v,"WarningCoin":%v,"WinCoin":%v,"LoseCoin":%v}`, -// Warning_WinCoin, snid, (winCoin - loseCoin), winCoin, loseCoin)) -//} diff --git a/gamesrv/base/handredJacklistmgr.go b/gamesrv/base/handredJacklistmgr.go deleted file mode 100644 index e444859..0000000 --- a/gamesrv/base/handredJacklistmgr.go +++ /dev/null @@ -1,300 +0,0 @@ -package base - -import ( - "fmt" - "strconv" - "strings" - "time" - - "mongo.games.com/game/model" - - "github.com/globalsign/mgo/bson" - "mongo.games.com/goserver/core/basic" - "mongo.games.com/goserver/core/logger" - "mongo.games.com/goserver/core/module" - "mongo.games.com/goserver/core/task" -) - -// HundredJackListManager 排行榜 key: platform+gamefreeId -type HundredJackListManager struct { - HundredJackTsList map[string][]*HundredJackInfo - HundredJackSortList map[string][]*HundredJackInfo -} - -// HundredJackListMgr 实例化 -var HundredJackListMgr = &HundredJackListManager{ - HundredJackTsList: make(map[string][]*HundredJackInfo), - HundredJackSortList: make(map[string][]*HundredJackInfo), -} - -// HundredJackInfo 数据结构 -type HundredJackInfo struct { - model.HundredjackpotLog - linkSnids []int32 //点赞人数 -} - -// ModuleName . -func (hm *HundredJackListManager) ModuleName() string { - return "HundredJackListManager" -} - -// Init . -func (hm *HundredJackListManager) Init() { - //data := model. -} - -// Update . -func (hm *HundredJackListManager) Update() { -} - -// Shutdown . -func (hm *HundredJackListManager) Shutdown() { - module.UnregisteModule(hm) -} - -// ISInitJackInfo 仅初始化一次 -var ISInitJackInfo bool - -// InitTsJackInfo 初始化TsJackInfo -func (hm *HundredJackListManager) InitTsJackInfo(platform string, freeID int32) { - key := fmt.Sprintf("%v-%v", platform, freeID) - task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} { - datas, err := model.GetHundredjackpotLogTsByPlatformAndGameFreeID(platform, freeID) - if err != nil { - logger.Logger.Error("HundredJackListManager DelOneJackInfo ", err) - return nil - } - return datas - }), task.CompleteNotifyWrapper(func(data interface{}, tt task.Task) { - datas := data.([]model.HundredjackpotLog) - if data != nil && datas != nil { - for i := range datas { - if i == model.HundredjackpotLogMaxLimitPerQuery { - break - } - data := &HundredJackInfo{ - HundredjackpotLog: datas[i], - } - strlikeSnids := strings.Split(datas[i].LinkeSnids, "|") - for _, v := range strlikeSnids { - if v == "" { - break - } - snid, err := strconv.Atoi(v) - if err == nil { - data.linkSnids = append(data.linkSnids, int32(snid)) - } - } - hm.HundredJackTsList[key] = append(hm.HundredJackTsList[key], data) - } - // logger.Logger.Warnf("InitTsJackInfo data:%v", datas) - } else { - hm.HundredJackTsList[key] = []*HundredJackInfo{} - } - return - }), "InitTsJackInfo").Start() -} - -// InitSortJackInfo 初始化SortJackInfo -func (hm *HundredJackListManager) InitSortJackInfo(platform string, freeID int32) { - - key := fmt.Sprintf("%v-%v", platform, freeID) - task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} { - datas, err := model.GetHundredjackpotLogCoinByPlatformAndGameFreeID(platform, freeID) - if err != nil { - logger.Logger.Error("HundredJackListManager DelOneJackInfo ", err) - return nil - } - return datas - }), task.CompleteNotifyWrapper(func(data interface{}, tt task.Task) { - datas := data.([]model.HundredjackpotLog) - if data != nil && datas != nil { - for i := range datas { - if i == model.HundredjackpotLogMaxLimitPerQuery { - break - } - data := &HundredJackInfo{ - HundredjackpotLog: datas[i], - } - strlikeSnids := strings.Split(datas[i].LinkeSnids, "|") - for _, v := range strlikeSnids { - snid, err := strconv.Atoi(v) - if err == nil { - data.linkSnids = append(data.linkSnids, int32(snid)) - } - } - hm.HundredJackSortList[key] = append(hm.HundredJackSortList[key], data) - } - // logger.Logger.Warnf("InitSortJackInfo data:%v", datas) - } else { - hm.HundredJackSortList[key] = []*HundredJackInfo{} - } - return - }), "InitSortJackInfo").Start() -} - -// InitHundredJackListInfo 初始化 HundredJackListInfo -func (hm *HundredJackListManager) InitHundredJackListInfo(platform string, freeID int32) { - if ISInitJackInfo { - return - } - key := fmt.Sprintf("%v-%v", platform, freeID) - if _, exist := hm.HundredJackTsList[key]; !exist { - hm.InitTsJackInfo(platform, freeID) - } - if _, exist := hm.HundredJackSortList[key]; !exist { - hm.InitSortJackInfo(platform, freeID) - } - ISInitJackInfo = true - return -} - -// GetJackTsInfo 返回TsInfo -func (hm *HundredJackListManager) GetJackTsInfo(platform string, freeID int32) []*HundredJackInfo { - key := fmt.Sprintf("%v-%v", platform, freeID) - if _, exist := hm.HundredJackTsList[key]; !exist { // 玩家进入scene 已经初始化 - hm.InitTsJackInfo(platform, freeID) - } - return hm.HundredJackTsList[key] -} - -// GetJackSortInfo 返回SortInfo -func (hm *HundredJackListManager) GetJackSortInfo(platform string, freeID int32) []*HundredJackInfo { - key := fmt.Sprintf("%v-%v", platform, freeID) - if _, exist := hm.HundredJackSortList[key]; !exist { - hm.InitSortJackInfo(platform, freeID) - } - return hm.HundredJackSortList[key] -} - -// Insert 插入 -func (hm *HundredJackListManager) Insert(coin, turncoin int64, snid, roomid, jackType, inGame, vip int32, platform, channel, name string, gamedata []string) { - key := fmt.Sprintf("%v-%v", platform, roomid) - log := model.NewHundredjackpotLogEx(snid, coin, turncoin, roomid, jackType, inGame, vip, platform, channel, name, gamedata) - ///////////////////实际不走这里 - if _, exist := hm.HundredJackTsList[key]; !exist { - hm.InitTsJackInfo(platform, roomid) - } - if _, exist := hm.HundredJackSortList[key]; !exist { - hm.InitSortJackInfo(platform, roomid) - } - ///////////////////// - hm.InsertLog(log) - data := &HundredJackInfo{ - HundredjackpotLog: *log, - } - /*logger.Logger.Trace("HundredJackListManager log 1 ", log.SnID, log.LogID, data.GameData) - for _, v := range hm.HundredJackTsList[key] { - logger.Logger.Trace("HundredJackListManager log 2 ", v.SnID, v.LogID, v.GameData) - }*/ - for i, v := range hm.HundredJackSortList[key] { // 插入 - if v.Coin < log.Coin { - d1 := append([]*HundredJackInfo{}, hm.HundredJackSortList[key][i:]...) - hm.HundredJackSortList[key] = append(hm.HundredJackSortList[key][:i], data) - hm.HundredJackSortList[key] = append(hm.HundredJackSortList[key], d1...) - goto Exit - } - } - if len(hm.HundredJackSortList[key]) < model.HundredjackpotLogMaxLimitPerQuery { - hm.HundredJackSortList[key] = append(hm.HundredJackSortList[key], data) - } -Exit: - d1 := append([]*HundredJackInfo{}, hm.HundredJackTsList[key][0:]...) - hm.HundredJackTsList[key] = append(hm.HundredJackTsList[key][:0], data) - hm.HundredJackTsList[key] = append(hm.HundredJackTsList[key], d1...) - var delList []*HundredJackInfo - if len(hm.HundredJackTsList[key]) > model.HundredjackpotLogMaxLimitPerQuery { - delList = append(delList, hm.HundredJackTsList[key][model.HundredjackpotLogMaxLimitPerQuery:]...) - hm.HundredJackTsList[key] = hm.HundredJackTsList[key][:model.HundredjackpotLogMaxLimitPerQuery] - } - if len(hm.HundredJackSortList[key]) > model.HundredjackpotLogMaxLimitPerQuery { - delList = append(delList, hm.HundredJackSortList[key][model.HundredjackpotLogMaxLimitPerQuery:]...) - hm.HundredJackSortList[key] = hm.HundredJackSortList[key][:model.HundredjackpotLogMaxLimitPerQuery] - } - /*for _, v := range hm.HundredJackTsList[key] { - logger.Logger.Trace("HundredJackListManager log 3 ", v.SnID, v.LogID, v.GameData) - }*/ - for _, v := range delList { - if hm.IsCanDel(v, hm.HundredJackTsList[key], hm.HundredJackSortList[key]) { // 两个排行帮都不包含 - logger.Logger.Info("HundredJackListManager DelOneJackInfo ", v.LogID) - hm.DelOneJackInfo(v.Platform, v.LogID) - } - } -} - -// IsCanDel 能否删除 -func (hm *HundredJackListManager) IsCanDel(deldata *HundredJackInfo, tsList, sortList []*HundredJackInfo) bool { - for _, v := range tsList { - if v.LogID == deldata.LogID { - return false - } - } - for _, v := range sortList { - if v.LogID == deldata.LogID { - return false - } - } - return true -} - -// InsertLog insert db -func (hm *HundredJackListManager) InsertLog(log *model.HundredjackpotLog) { - task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} { - err := model.InsertHundredjackpotLog(log) - if err != nil { - logger.Logger.Error("HundredJackListManager Insert ", err) - } - return err - }), nil, "InsertHundredJack").Start() -} - -// UpdateLikeNum updata likenum -func (hm *HundredJackListManager) UpdateLikeNum(plt string, gid bson.ObjectId, like int32, likesnids string) { - task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} { - err := model.UpdateLikeNum(plt, gid, like, likesnids) - if err != nil { - logger.Logger.Error("HundredJackListManager UpdateHundredLikeNum ", err) - } - return err - }), nil, "UpdateHundredLikeNum").Start() -} - -// UpdatePlayBlackNum updata playblacknum -func (hm *HundredJackListManager) UpdatePlayBlackNum(plt string, gid bson.ObjectId, playblack int32) []string { - var ret []string - task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} { - data, err := model.UpdatePlayBlackNum(plt, gid, playblack) - if err != nil { - logger.Logger.Error("HundredJackListManager DelOneJackInfo ", err) - return nil - } - return data - }), task.CompleteNotifyWrapper(func(data interface{}, tt task.Task) { - if data != nil { - ret = data.([]string) - logger.Logger.Warnf("UpdatePlayBlackNum data:%v", ret) - } - return - }), "UpdatePlayBlackNum").Start() - - logger.Logger.Error("HundredJackListManager UpdatePlayBlackNum ", ret) - if len(ret) == 0 { - return ret - } - return nil -} - -// DelOneJackInfo del -func (hm *HundredJackListManager) DelOneJackInfo(plt string, gid bson.ObjectId) { - task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} { - err := model.RemoveHundredjackpotLogOne(plt, gid) - if err != nil { - logger.Logger.Error("HundredJackListManager DelOneJackInfo ", err) - } - return err - }), nil, "DelOneJackInfo").Start() -} - -func init() { - module.RegisteModule(HundredJackListMgr, time.Hour, 0) -} diff --git a/gamesrv/base/player.go b/gamesrv/base/player.go index 5f47a55..1bcdf6e 100644 --- a/gamesrv/base/player.go +++ b/gamesrv/base/player.go @@ -627,9 +627,15 @@ type ReportGameEventParam struct { Change int64 // 净输赢,正负值,不带税收 In, Out int64 // 投入,产出(税前) GameTime int64 // 游戏时长,秒 + OnlyLog bool // 只返回数据,不上报 } -func (this *Player) ReportGameEvent(param *ReportGameEventParam) { +type ReportGameEventOnly struct { + Param *ReportGameEventParam + Log []*model.PlayerGameRecEvent +} + +func (this *Player) ReportGameEvent(param *ReportGameEventParam) *ReportGameEventOnly { // 记录玩家 首次参与该场次的游戏时间 游戏次数 var gameFirstTime, gameFreeFirstTime time.Time var gameTimes, gameFreeTimes int64 @@ -659,6 +665,8 @@ func (this *Player) ReportGameEvent(param *ReportGameEventParam) { param.GameTime = 0 } + var ret ReportGameEventOnly + ret.Param = param log := &model.PlayerGameRecEvent{ Platform: this.Platform, RecordId: this.scene.GetRecordId(), @@ -685,7 +693,12 @@ func (this *Player) ReportGameEvent(param *ReportGameEventParam) { LastLoginTime: this.LastLoginTime.Unix(), DeviceId: this.DeviceId, } - mq.Write(log, mq.BackGameRecord) + if param.OnlyLog { + ret.Log = append(ret.Log, log) + } else { + mq.Write(log, mq.BackGameRecord) + } + return &ret } // 汇总玩家该次游戏总产生的税收 diff --git a/gamesrv/base/scene.go b/gamesrv/base/scene.go index 7dfe670..4d6a2e4 100644 --- a/gamesrv/base/scene.go +++ b/gamesrv/base/scene.go @@ -2,7 +2,6 @@ package base import ( "fmt" - "github.com/globalsign/mgo/bson" "math" "math/rand" "strconv" @@ -1457,162 +1456,6 @@ func (this *Scene) SaveFriendRecord(snid int32, isWin int32, billCoin int64, bas } } -type SaveGameDetailedParam struct { - LogId string // 日志id - Detail string // 游戏详细信息 - GameTime int64 // 游戏时长 - Trend20Lately string // 最近20局开奖结果 - CtrlType int // 调控类型 1控赢 2控输 - PlayerPool map[int]int // 个人水池分 -} - -func (this *Scene) SaveGameDetailedLog(param *SaveGameDetailedParam) { - if this == nil || param == nil { - return - } - - if param.GameTime <= 0 { - param.GameTime = int64(time.Now().Sub(this.GameNowTime).Seconds()) - } - - if param.GameTime < 0 { - param.GameTime = 0 - } - - now := time.Now() - - f := func(plt string) { - log := &model.GameDetailedLog{ - Id: bson.NewObjectId(), - LogId: param.LogId, - GameId: this.GameId, - Platform: plt, - MatchId: this.GetMatch().GetMatchSortId(), - SceneId: this.SceneId, - GameMode: this.GameMode, - GameFreeid: this.GetGameFreeId(), - PlayerCount: int32(len(this.Players)), - GameTiming: int32(param.GameTime), - GameBaseBet: this.GetBaseScore(), - GameDetailedNote: param.Detail, - GameDetailVer: GameDetailedVer[int(this.GameId)], - CpCtx: this.CpCtx, - Time: now, - Trend20Lately: param.Trend20Lately, - Ts: now.Unix(), - CtrlType: param.CtrlType, - PlayerPool: make(map[int]int), - CycleId: this.CycleID, - } - for k, v := range param.PlayerPool { - log.PlayerPool[k] = v - } - mq.Write(log) - } - - switch { - case this.IsCoinScene(): - mapPlatform := make(map[string]bool) - for _, v := range this.Players { - if v == nil { - continue - } - if _, ok := mapPlatform[v.Platform]; ok { - continue - } - mapPlatform[v.Platform] = true - f(v.Platform) - } - default: - f(this.Platform) - } -} - -type SaveGamePlayerListLogParam struct { - LogId string // 详情日志id - Platform string // 平台 - Snid int32 // 玩家id - PlayerName string // 玩家名字 - Channel string // 渠道 - ChannelId string // 推广渠道 - TotalIn int64 // 总投入 - TotalOut int64 // 总产出(税前) - TaxCoin int64 // 总税收 - BetAmount int64 // 下注量 - WinAmountNoAnyTax int64 // 税后赢取额(净利润,正负值) - IsFirstGame bool // 是否第一次游戏 - IsFree bool // 拉霸专用 是否免费 - WinSmallGame int64 // 拉霸专用 小游戏奖励 - WinTotal int64 // 拉霸专用 本局输赢 - GameTime int64 // 游戏时长 -} - -// SaveGamePlayerListLog 保存玩家对局记录 -func (this *Scene) SaveGamePlayerListLog(param *SaveGamePlayerListLogParam) { - if this == nil { - return - } - if param == nil { - return - } - - p := this.GetPlayer(param.Snid) - if p == nil { - return - } - - if param.PlayerName == "" { - param.PlayerName = p.Name - } - - baseScore := this.GetBaseScore() - - // 上报玩家游戏记录 - if !p.IsRob && (param.IsFree || param.TotalIn != 0 || param.TotalOut != 0) { - p.ReportGameEvent(&ReportGameEventParam{ - Tax: param.TaxCoin, - Change: param.WinAmountNoAnyTax, - In: param.TotalIn, - Out: param.TotalOut, - GameTime: param.GameTime, - }) - } - - // 保存玩家游戏日志 - now := time.Now() - log := &model.GamePlayerListLog{ - LogId: bson.NewObjectId(), - SnId: p.SnId, - Name: param.PlayerName, - GameId: this.GameId, - BaseScore: baseScore, - TaxCoin: param.TaxCoin, - Platform: param.Platform, - Channel: param.Channel, - SceneId: this.SceneId, - GameMode: this.GameMode, - GameFreeid: this.GetGameFreeId(), - GameDetailedLogId: param.LogId, - IsFirstGame: param.IsFirstGame, - BetAmount: param.BetAmount, - WinAmountNoAnyTax: param.WinAmountNoAnyTax, - TotalIn: param.TotalIn, - TotalOut: param.TotalOut, - Time: now, - RoomType: this.SceneMode, - GameDif: this.GetDBGameFree().GetGameDif(), - GameClass: this.GetDBGameFree().GetGameClass(), - MatchId: this.GetMatch().GetMatchSortId(), - MatchType: int64(this.GetMatch().GetMatchType()), - Ts: now.Unix(), - IsFree: param.IsFree, - WinSmallGame: param.WinSmallGame, - WinTotal: param.WinTotal, - CycleId: this.CycleID, - } - mq.Write(log) -} - func (this *Scene) IsPlayerFirst(p *Player) bool { if p == nil { return false diff --git a/gamesrv/thirteen/scene.go b/gamesrv/thirteen/scene.go index 77b3318..bd04c0f 100644 --- a/gamesrv/thirteen/scene.go +++ b/gamesrv/thirteen/scene.go @@ -487,6 +487,7 @@ func (this *SceneEx) GetScore(player *PlayerEx) { player.winAllPlayers[p.Pos] += rate p.winAllPlayers[player.Pos] -= rate player.tableScore[3] += rate - 1 + p.tableScore[3] -= rate - 1 } //中墩 rate = int64(1) @@ -509,6 +510,7 @@ func (this *SceneEx) GetScore(player *PlayerEx) { player.winAllPlayers[p.Pos] += rate p.winAllPlayers[player.Pos] -= rate player.tableScore[4] += rate - 1 + p.tableScore[4] -= rate - 1 } //尾墩 rate = int64(1) @@ -529,6 +531,7 @@ func (this *SceneEx) GetScore(player *PlayerEx) { player.winAllPlayers[p.Pos] += rate p.winAllPlayers[player.Pos] -= rate player.tableScore[5] += rate - 1 + p.tableScore[5] -= rate - 1 } if s == 3 { player.winThreePos[p.Pos] = score diff --git a/gamesrv/thirteen/scenepolicy.go b/gamesrv/thirteen/scenepolicy.go index 05d9b36..2bb9a47 100644 --- a/gamesrv/thirteen/scenepolicy.go +++ b/gamesrv/thirteen/scenepolicy.go @@ -866,6 +866,10 @@ func (this *StateOp) OnPlayerOp(s *base.Scene, p *base.Player, opcode int, param copy(playerEx.cardsO.Mid[:], common.Int64Toint(params[3:8])) copy(playerEx.cardsO.End[:], common.Int64Toint(params[8:])) playerEx.cardsO.PokerType = 0 + tp := sceneEx.logic.GetSpecialType(playerEx.cards) + if tp > 0 { + playerEx.cardsO.PokerType = tp + } sceneEx.SendSelectCards(playerEx, 0, int64(opcode)) } else { sceneEx.SendSelectCards(playerEx, int(params[0]), int64(opcode)) @@ -912,6 +916,10 @@ func (this *StateOp) OnPlayerOp(s *base.Scene, p *base.Player, opcode int, param copy(playerEx.preCardsO.Mid[:], common.Int64Toint(params[3:8])) copy(playerEx.preCardsO.End[:], common.Int64Toint(params[8:])) playerEx.preCardsO.PokerType = 0 + tp := sceneEx.logic.GetSpecialType(playerEx.cards) + if tp > 0 { + playerEx.preCardsO.PokerType = tp + } } playerEx.SendToClient(int(thirteen.TWMmoPacketID_PACKET_SCThirteenPlayerOp), pack) @@ -948,7 +956,7 @@ func (this *StateOp) OnLeave(s *base.Scene) { } // 判断是否倒水 if player.cardsO != nil && player.cardsO.PokerType != -1 { - if player.cardsO.PokerType < 1000000 { + if player.cardsO.PokerType == 0 { player.isDP = sceneEx.logic.IsDP(player.cardsO.Head, player.cardsO.Mid, player.cardsO.End) } continue diff --git a/go.mod b/go.mod index 1ae0dd7..0eee4a1 100644 --- a/go.mod +++ b/go.mod @@ -26,13 +26,13 @@ require ( github.com/tomas-qstarrs/boost v1.0.3 github.com/tomas-qstarrs/excel-converter v1.0.2 github.com/wendal/errors v0.0.0-20181209125328-7f31f4b264ec + github.com/xuri/excelize/v2 v2.9.0 github.com/zegoim/zego_server_assistant/token/go/src v0.0.0-20231013093807-4e80bab42ec3 github.com/zeromicro/go-zero v1.7.3 go.etcd.io/etcd/client/v3 v3.5.16 go.mongodb.org/mongo-driver v1.17.1 golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c google.golang.org/protobuf v1.35.1 - gorm.io/driver/mysql v1.5.7 gorm.io/gorm v1.25.12 mongo.games.com/goserver v0.0.0-00010101000000-000000000000 ) @@ -70,7 +70,7 @@ require ( github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/richardlehane/mscfb v1.0.4 // indirect - github.com/richardlehane/msoleps v1.0.3 // indirect + github.com/richardlehane/msoleps v1.0.4 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect @@ -92,7 +92,8 @@ require ( github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/xtaci/kcp-go v5.4.20+incompatible // indirect - github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 // indirect + github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d // indirect + github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect go.etcd.io/etcd/api/v3 v3.5.16 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.16 // indirect @@ -103,7 +104,7 @@ require ( go.uber.org/multierr v1.9.0 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/crypto v0.28.0 // indirect - golang.org/x/image v0.13.0 // indirect + golang.org/x/image v0.18.0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.26.0 // indirect @@ -115,4 +116,5 @@ require ( gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/driver/mysql v1.5.7 // indirect ) diff --git a/go.sum b/go.sum index 79422ec..00dcca3 100644 --- a/go.sum +++ b/go.sum @@ -279,8 +279,8 @@ github.com/richardlehane/mscfb v1.0.3/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7 github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM= github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= -github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM= -github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= +github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00= +github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= @@ -386,8 +386,12 @@ github.com/xtaci/kcp-go v5.4.20+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45 github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E= github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37/go.mod h1:HpMP7DB2CyokmAh4lp0EQnnWhmycP/TvwBGzvuie+H0= github.com/xuri/efp v0.0.0-20200605144744-ba689101faaf/go.mod h1:uBiSUepVYMhGTfDeBKKasV4GpgBlzJ46gXUBAqV8qLk= -github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 h1:6932x8ltq1w4utjmfMPVj09jdMlkY0aiA6+Skbtl3/c= -github.com/xuri/efp v0.0.0-20220603152613-6918739fd470/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= +github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d h1:llb0neMWDQe87IzJLS4Ci7psK/lVsjIS2otl+1WyRyY= +github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= +github.com/xuri/excelize/v2 v2.9.0 h1:1tgOaEq92IOEumR1/JfYS/eR0KHOCsRv/rYXXh6YJQE= +github.com/xuri/excelize/v2 v2.9.0/go.mod h1:uqey4QBZ9gdMeWApPLdhm9x+9o2lq4iVmjiLfBS5hdE= +github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 h1:hPVCafDV85blFTabnqKgNhDCkJX25eik94Si9cTER4A= +github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -441,8 +445,9 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= golang.org/x/image v0.0.0-20200922025426-e59bae62ef32/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg= golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk= +golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= +golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= diff --git a/statistics/.gitignore b/statistics/.gitignore new file mode 100644 index 0000000..3c58f2a --- /dev/null +++ b/statistics/.gitignore @@ -0,0 +1,29 @@ +# ---> Go +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +*.xlsx + +# Test binary, built with `go test -c` +*.test +local_test.go + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work + +.idea +.vscode +log + diff --git a/statistics/README.md b/statistics/README.md new file mode 100644 index 0000000..71f24d5 --- /dev/null +++ b/statistics/README.md @@ -0,0 +1,7 @@ +# statistics + +数据分析服务 + * mongodb同步到mysql + * 接收消息队列数据 + * 历史数据查询 + * 数据统计 \ No newline at end of file diff --git a/statistics/build_linux.bat b/statistics/build_linux.bat new file mode 100644 index 0000000..7962122 --- /dev/null +++ b/statistics/build_linux.bat @@ -0,0 +1,8 @@ +set GOPATH=D:\godev +go env -w GO111MODULE=on + +set CGO_ENABLED=0 +set GOOS=linux +set GOARCH=amd64 +go build +pause \ No newline at end of file diff --git a/statistics/constant/constant.go b/statistics/constant/constant.go new file mode 100644 index 0000000..78818e0 --- /dev/null +++ b/statistics/constant/constant.go @@ -0,0 +1,8 @@ +package constant + +const ( + InviteScoreTypeBind = 1 // 绑定邀请码 + InviteScoreTypePay = 2 // 充值返佣 + InviteScoreTypeRecharge = 3 // 充值完成 + InviteScoreTypePayMe = 4 // 充值(自己) +) diff --git a/statistics/etc/config.yaml b/statistics/etc/config.yaml new file mode 100644 index 0000000..ba82d5c --- /dev/null +++ b/statistics/etc/config.yaml @@ -0,0 +1,27 @@ +# 平台id +platforms: + - 1 + +# 几秒同步一次数据 +# 注册表,登录日志表 +update_second: 60 +# 注册表每次同步多少条数据 +update_account_num: 100 +# 登录日志每次同步多少条数据 +update_login_num: 100 +# 几秒读取一次玩家id列表 +update_second_snid: 30 +# 最多触发几个玩家数据更新 +update_snid_num: 100 + +# 邀请数据统计 +# 几秒读取一次邀请记录 +update_second_invite: 10 +# 一次最多读取多少条邀请记录 +update_invite_num: 30 + +# 道具获得数量统计 +# 几秒读取一次道具日志 +update_second_item: 10 +# 一次最多读取多少道具日志 +update_item_num: 100 \ No newline at end of file diff --git a/statistics/etc/mongo.yaml b/statistics/etc/mongo.yaml new file mode 100644 index 0000000..7f9e42d --- /dev/null +++ b/statistics/etc/mongo.yaml @@ -0,0 +1,53 @@ +global: + user: + HostName: 127.0.0.1 + HostPort: 27017 + Database: win88_global + Username: + Password: + Options: + log: + HostName: 127.0.0.1 + HostPort: 27017 + Database: win88_log + Username: + Password: + Options: + monitor: + HostName: 127.0.0.1 + HostPort: 27017 + Database: win88_monitor + Username: + Password: + Options: +platforms: + 0: + user: + HostName: 127.0.0.1 + HostPort: 27017 + Database: win88_user_plt_000 + Username: + Password: + Options: + log: + HostName: 127.0.0.1 + HostPort: 27017 + Database: win88_log_plt_000 + Username: + Password: + Options: + 1: + user: + HostName: 127.0.0.1 + HostPort: 27017 + Database: win88_user_plt_001 + Username: + Password: + Options: + log: + HostName: 127.0.0.1 + HostPort: 27017 + Database: win88_log_plt_001 + Username: + Password: + Options: \ No newline at end of file diff --git a/statistics/etc/mysql.yaml b/statistics/etc/mysql.yaml new file mode 100644 index 0000000..82d1fe3 --- /dev/null +++ b/statistics/etc/mysql.yaml @@ -0,0 +1,38 @@ +platforms: + global: + HostName: 127.0.0.1 + HostPort: 3306 + Database: win88_user + Username: root + Password: 123456 + Options: charset=utf8mb4&parseTime=True&loc=Local + 0: + HostName: 127.0.0.1 + HostPort: 3306 + Database: win88_plt_000 + Username: root + Password: 123456 + Options: charset=utf8mb4&parseTime=True&loc=Local + 1: + HostName: 127.0.0.1 + HostPort: 3306 + Database: win88_plt_001 + Username: root + Password: 123456 + Options: charset=utf8mb4&parseTime=True&loc=Local + count: # 破产日志库 + HostName: 127.0.0.1 + HostPort: 3306 + Database: dbmis_count + Username: root + Password: 123456 + Options: charset=utf8mb4&parseTime=True&loc=Local + +# 最大空闲连接数 +MaxIdleConns: 10 +# 最大连接数 +MaxOpenConns: 100 +# 连接可复用的最大时间 +ConnMaxLifetime: 3600 +# 连接最大空闲时间 +ConnMaxIdletime: 0 \ No newline at end of file diff --git a/statistics/logger.xml b/statistics/logger.xml new file mode 100644 index 0000000..f6eb37b --- /dev/null +++ b/statistics/logger.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/statistics/main.go b/statistics/main.go new file mode 100644 index 0000000..23e6b9f --- /dev/null +++ b/statistics/main.go @@ -0,0 +1,212 @@ +package main + +import ( + "context" + "fmt" + "os" + "os/signal" + "sync" + "time" + + "github.com/spf13/viper" + "mongo.games.com/goserver/core/logger" + "mongo.games.com/goserver/core/mongox" + "mongo.games.com/goserver/core/mysqlx" + "mongo.games.com/goserver/core/utils" + "mongo.games.com/goserver/core/viperx" + + mongomodel "mongo.games.com/game/statistics/modelmongo" + mysqlmodel "mongo.games.com/game/statistics/modelmysql" + "mongo.games.com/game/statistics/static" + "mongo.games.com/game/statistics/syn" +) + +var VP *viper.Viper + +// DoTick 定时执行 +func DoTick(ctx context.Context, wg *sync.WaitGroup, duration time.Duration, fu func(ctx context.Context)) { + wg.Add(1) + go func() { + defer wg.Done() + for { + select { + case <-ctx.Done(): + return + case <-time.After(duration): + utils.RecoverPanicFunc() // 捕获异常 + fu(ctx) + } + } + }() +} + +// DoTickPlatform 定时执行,根据platform执行 +func DoTickPlatform(ctx context.Context, wg *sync.WaitGroup, duration time.Duration, batchSize int, + fu func(ctx context.Context, platform string, batchSize int)) { + wg.Add(1) + go func() { + defer wg.Done() + for { + select { + case <-ctx.Done(): + return + case <-time.After(duration): + utils.RecoverPanicFunc() // 捕获异常 + wg := new(sync.WaitGroup) + for _, v := range VP.GetStringSlice("platforms") { + platform := v + wg.Add(1) + go func() { + defer wg.Done() + fu(ctx, platform, batchSize) + }() + } + wg.Wait() + } + } + }() +} + +func main() { + VP = viperx.GetViper("config", "yaml") + // mongo + vp := viperx.GetViper("mongo", "yaml") + // mongo初始化 + conf := &mongox.Config{} + err := vp.Unmarshal(conf) + if err != nil { + panic(fmt.Errorf("mongo config error: %v", err)) + } + mongox.Init(conf) + defer mongox.Close() + + // mysql + vp = viperx.GetViper("mysql", "yaml") + myConf := &mysqlx.Config{} + err = vp.Unmarshal(myConf) + if err != nil { + panic(fmt.Errorf("mysql config error: %v", err)) + } + mysqlx.Init(myConf) + defer mysqlx.Close() + + mysqlx.SetAutoMigrateTables(mysqlmodel.Tables) + + wg := &sync.WaitGroup{} + ctx, cancel := context.WithCancel(context.Background()) + + DoTick(ctx, wg, time.Duration(VP.GetInt64("update_second"))*time.Second, SyncSnId) + + DoTick(ctx, wg, time.Duration(VP.GetInt64("update_second_snid"))*time.Second, func(ctx context.Context) { + wg := new(sync.WaitGroup) + for _, v := range VP.GetStringSlice("platforms") { + platform := v + wg.Add(1) + go func() { + defer wg.Done() + Static(platform) + }() + } + wg.Wait() + }) + + DoTick(ctx, wg, time.Duration(VP.GetInt64("update_second_invite"))*time.Second, SyncInvite) + + DoTickPlatform(ctx, wg, time.Duration(VP.GetInt64("update_second_item"))*time.Second, VP.GetInt("update_item_num"), + func(ctx context.Context, platform string, batchSize int) { + err := syn.ItemGainDone(&syn.Data[mongomodel.ItemLog]{ + Platform: platform, + BatchSize: batchSize, + }) + if err != nil { + logger.Logger.Errorf("SyncItem error:%v", err) + } + }) + + logger.Logger.Info("start") + + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, os.Kill) + sig := <-c + logger.Logger.Infof("closing down (signal: %v)", sig) + + // release + cancel() + wg.Wait() + + logger.Logger.Info("closed") +} + +// SyncSnId 同步注册和登录日志 +func SyncSnId(ctx context.Context) { + wg := new(sync.WaitGroup) + for _, v := range VP.GetStringSlice("platforms") { + platform := v + wg.Add(1) + go func() { + defer wg.Done() + _, err := syn.UserAccount(platform, VP.GetInt("update_account_num")) + if err != nil { + logger.Logger.Errorf("SyncUserAccount error: %v", err) + return + } + + _, err = syn.LogLogin(platform, VP.GetInt("update_login_num")) + if err != nil { + logger.Logger.Errorf("SyncLogLogin error: %v", err) + return + } + }() + } + wg.Wait() +} + +// Static 玩家id触发数据统计 +func Static(platform string) { + // 查询需要更新的玩家id + var ids []*mysqlmodel.UserID + db, err := mysqlx.GetDatabase(platform) + if err != nil { + logger.Logger.Errorf("GetDatabase error: %v", err) + return + } + if err := db.Limit(VP.GetInt("update_snid_num")).Find(&ids).Error; err != nil { + logger.Logger.Warnf("Get UserID error: %v", err) + return + } + + if len(ids) == 0 { + logger.Logger.Tracef("Static: no need to update") + return + } + + // 统计玩家跳出记录 + if err := static.UserLogin(platform, ids); err != nil { + logger.Logger.Errorf("StaticUserLogin error: %v", err) + return + } + + // 删除更新过的玩家id + if err := db.Delete(ids).Error; err != nil { + logger.Logger.Errorf("Delete error: %v", err) + return + } +} + +// SyncInvite 同步邀请数据 +func SyncInvite(ctx context.Context) { + wg := new(sync.WaitGroup) + for _, v := range VP.GetStringSlice("platforms") { + platform := v + wg.Add(1) + go func() { + defer wg.Done() + err := syn.SyncInviteScore(platform, VP.GetInt("update_invite_num")) + if err != nil { + logger.Logger.Errorf("SyncInviteScore error: %v", err) + return + } + }() + } + wg.Wait() +} diff --git a/statistics/modelmongo/log_gameplayerlistlog.go b/statistics/modelmongo/log_gameplayerlistlog.go new file mode 100644 index 0000000..a2a2917 --- /dev/null +++ b/statistics/modelmongo/log_gameplayerlistlog.go @@ -0,0 +1,47 @@ +package modelmongo + +import ( + "time" + + "go.mongodb.org/mongo-driver/bson/primitive" +) + +const LogGamePlayerListLog = "log_gameplayerlistlog" + +type GamePlayerListLog struct { + LogId primitive.ObjectID `bson:"_id"` + SnId int32 //用户Id + Name string //名称 + GameId int32 //游戏id + BaseScore int32 //游戏底注 + ClubId int32 //俱乐部Id + ClubRoom string //俱乐部包间 + TaxCoin int64 //税收 + ClubPumpCoin int64 //俱乐部额外抽水 + Platform string //平台id + Channel string //渠道 + Promoter string //推广员 + PackageTag string //包标识 + SceneId int32 //场景ID + GameMode int32 //游戏类型 + GameFreeid int32 //游戏类型房间号 + GameDetailedLogId string //游戏记录Id + IsFirstGame bool //是否第一次游戏 + //对于拉霸类:BetAmount=100 WinAmountNoAnyTax=0 (表示投入多少、收益多少,值>=0) + //拉霸类小游戏会是:BetAmount=0 WinAmountNoAnyTax=100 (投入0、收益多少,值>=0) + //对战场:BetAmount=0 WinAmountNoAnyTax=100 (投入会有是0、收益有正负,WinAmountNoAnyTax=100则盈利,WinAmountNoAnyTax=-100则输100) + BetAmount int64 //下注金额 + WinAmountNoAnyTax int64 //盈利金额,不包含任何税 + TotalIn int64 //本局投入 + TotalOut int64 //本局产出 + Time time.Time //记录时间 + RoomType int32 //房间类型 + GameDif string //游戏标识 + GameClass int32 //游戏类型 1棋牌 2电子 3百人 4捕鱼 5视讯 6彩票 7体育 + MatchId int32 + MatchType int32 //0.普通场 1.锦标赛 2.冠军赛 3.vip专属 + Ts int32 + IsFree bool //拉霸专用 是否免费 + WinSmallGame int64 //拉霸专用 小游戏奖励 + WinTotal int64 //拉霸专用 输赢 +} diff --git a/statistics/modelmongo/log_invitescore.go b/statistics/modelmongo/log_invitescore.go new file mode 100644 index 0000000..4a0b636 --- /dev/null +++ b/statistics/modelmongo/log_invitescore.go @@ -0,0 +1,19 @@ +package modelmongo + +import ( + "go.mongodb.org/mongo-driver/bson/primitive" +) + +const LogInviteScore = "log_invitescore" + +type InviteScore struct { + Id primitive.ObjectID `bson:"_id"` + UpSnid int // 上级代理 + DownSnid int // 下级代理 + Level int // 代理层级 例如 1:DownSnid 是 UpSnid 的 1 级代理; 2: DownSnid 是 UpSnid 的 2 级代理 + Tp int // 返佣类型 + Rate int // 返佣比例 + Score int // 积分 + Money int // 充值金额 + Ts int // 时间戳 +} diff --git a/statistics/modelmongo/log_item.go b/statistics/modelmongo/log_item.go new file mode 100644 index 0000000..4d7221f --- /dev/null +++ b/statistics/modelmongo/log_item.go @@ -0,0 +1,27 @@ +package modelmongo + +import "go.mongodb.org/mongo-driver/bson/primitive" + +const LogItem = "log_itemlog" + +type ItemInfo struct { + ItemId int32 + ItemNum int64 +} + +type ItemLog struct { + LogId primitive.ObjectID `bson:"_id"` + Platform string //平台 + SnId int32 //玩家id + LogType int32 //记录类型 0.获取 1.消耗 + ItemId int32 //道具id + ItemName string //道具名称 + Count int64 //个数 + CreateTs int64 //记录时间 + Remark string //备注 + TypeId int32 // 变化类型 + GameId int32 // 游戏id,游戏中获得时有值 + GameFreeId int32 // 场次id,游戏中获得时有值 + Cost []*ItemInfo // 消耗的道具 + Id string // 撤销的id,兑换失败 +} diff --git a/statistics/modelmongo/log_login.go b/statistics/modelmongo/log_login.go new file mode 100644 index 0000000..5145ffe --- /dev/null +++ b/statistics/modelmongo/log_login.go @@ -0,0 +1,33 @@ +package modelmongo + +import ( + "time" + + "go.mongodb.org/mongo-driver/bson/primitive" +) + +const LogLogin = "log_login" + +const ( + LogTypeLogin int32 = iota // 登录 + LogTypeLogout // 登出 + LogTypeRehold // 重连 + LogTypeDrop // 掉线 +) + +type LoginLog struct { + LogId primitive.ObjectID `bson:"_id"` + Platform string //平台id + SnId int32 + LogType int32 + Ts int64 + Time time.Time + GameId int // 玩家掉线时所在游戏id + LastGameID int // 玩家最后所在游戏id + ChannelId string // 推广渠道 + + DeviceName string + AppVersion string + BuildVersion string + AppChannel string +} diff --git a/statistics/modelmongo/user_account.go b/statistics/modelmongo/user_account.go new file mode 100644 index 0000000..89979bf --- /dev/null +++ b/statistics/modelmongo/user_account.go @@ -0,0 +1,24 @@ +package modelmongo + +import ( + "time" + + "go.mongodb.org/mongo-driver/bson/primitive" +) + +const UserAccount = "user_account" + +type Account struct { + AccountId primitive.ObjectID `bson:"_id"` + SnId int32 // 玩家账号直接在这里生成 + Platform string // 平台 + RegisterTs int64 // 注册时间戳 + RegisteTime time.Time + ChannelId string // 推广渠道 + + Tel string `gorm:"index"` + DeviceName string `gorm:"index"` + AppVersion string `gorm:"index"` + BuildVersion string `gorm:"index"` + AppChannel string `gorm:"index"` +} diff --git a/statistics/modelmysql/bankrupt.go b/statistics/modelmysql/bankrupt.go new file mode 100644 index 0000000..1ade115 --- /dev/null +++ b/statistics/modelmysql/bankrupt.go @@ -0,0 +1,12 @@ +package modelmysql + +type Bankrupt struct { + Id int `json:"id" gorm:"column:id"` + Platform int `json:"platform" gorm:"column:platform"` + Snid int `json:"snid" gorm:"column:snid"` + RegTs int `json:"register_time" gorm:"column:register_time"` + GameId int `json:"game_id" gorm:"column:game_id"` + GameFreeId int `json:"game_free_id" gorm:"column:game_free_id"` + Coin int `json:"use_coin" gorm:"column:use_coin"` + Ts int `json:"bankrupt_time" gorm:"column:bankrupt_time"` +} diff --git a/statistics/modelmysql/log_invitescore.go b/statistics/modelmysql/log_invitescore.go new file mode 100644 index 0000000..b5ffdf4 --- /dev/null +++ b/statistics/modelmysql/log_invitescore.go @@ -0,0 +1,26 @@ +package modelmysql + +type LogInviteScoreMid struct { + ID uint `gorm:"primaryKey"` + MID string +} + +type LogInviteScore struct { + ID uint `gorm:"primaryKey"` + UpSnid int `gorm:"index"` // 上级代理 + DownSnid int `gorm:"index"` // 下级代理 + Level int `gorm:"index"` // 代理层级 例如 1:DownSnid 是 UpSnid 的 1 级代理; 2: DownSnid 是 UpSnid 的 2 级代理 + Tp int `gorm:"index"` // 返佣类型 + Rate int `gorm:"index"` // 返佣比例 + Score int `gorm:"index"` // 积分 + Money int `gorm:"index"` // 充值金额 + Ts int `gorm:"index"` // 时间戳 +} + +type LogInviteUser struct { + ID uint `gorm:"primaryKey"` + Psnid int `gorm:"index"` // 当前玩家 + Snid int `gorm:"index"` // 一级代理 + Level int `gorm:"index"` // 代理层级 例如 1:DownSnid 是 UpSnid 的 1 级代理; 2: DownSnid 是 UpSnid 的 2 级代理 + Ts int `gorm:"index"` // 绑定时间 +} diff --git a/statistics/modelmysql/log_itemgain.go b/statistics/modelmysql/log_itemgain.go new file mode 100644 index 0000000..a672611 --- /dev/null +++ b/statistics/modelmysql/log_itemgain.go @@ -0,0 +1,16 @@ +package modelmysql + +// ItemGain 道具获得数量,以小时,道具id,做主键 +type ItemGain struct { + ID uint `gorm:"primaryKey"` + Hour int64 `gorm:"index:idx_item"` // 小时时间戳,每小时统计一次 + ItemId int32 `gorm:"index:idx_item"` // 道具id + ItemNum int64 // 道具数量 +} + +// ItemTotalGain 道具获得总数 +type ItemTotalGain struct { + ID uint `gorm:"primaryKey"` + ItemId int32 `gorm:"index"` // 道具id + ItemNum int64 // 道具数量 +} diff --git a/statistics/modelmysql/log_login.go b/statistics/modelmysql/log_login.go new file mode 100644 index 0000000..912d748 --- /dev/null +++ b/statistics/modelmysql/log_login.go @@ -0,0 +1,26 @@ +package modelmysql + +import "time" + +const ( + LogTypeLogin = 1 // 登录 + LogTypeRehold = 2 // 重连 + LogTypeOffline = 3 // 离线 +) + +type LogLogin struct { + ID uint `gorm:"primaryKey"` + Snid int `gorm:"index"` + OnlineType int `gorm:"index"` + //OnlineTs int `gorm:"index"` + OnlineTime time.Time `gorm:"index"` + OfflineType int `gorm:"index"` + //OfflineTs int `gorm:"index"` + OfflineTime time.Time `gorm:"index"` + ChannelId string `gorm:"index"` // 推广渠道 + + DeviceName string `gorm:"index"` + AppVersion string `gorm:"index"` + BuildVersion string `gorm:"index"` + AppChannel string `gorm:"index"` +} diff --git a/statistics/modelmysql/log_login_mid.go b/statistics/modelmysql/log_login_mid.go new file mode 100644 index 0000000..1b1385e --- /dev/null +++ b/statistics/modelmysql/log_login_mid.go @@ -0,0 +1,6 @@ +package modelmysql + +type LogLoginMid struct { + ID uint `gorm:"primaryKey"` + MID string +} diff --git a/statistics/modelmysql/log_mid.go b/statistics/modelmysql/log_mid.go new file mode 100644 index 0000000..ebca072 --- /dev/null +++ b/statistics/modelmysql/log_mid.go @@ -0,0 +1,11 @@ +package modelmysql + +const ( + MidTypeItem = 1 // 道具记录 +) + +type LogMid struct { + ID uint `gorm:"primaryKey"` + Tp int `gorm:"index"` // 类型 + MID string +} diff --git a/statistics/modelmysql/tables.go b/statistics/modelmysql/tables.go new file mode 100644 index 0000000..8f9bd15 --- /dev/null +++ b/statistics/modelmysql/tables.go @@ -0,0 +1,17 @@ +package modelmysql + +// 需要自动迁移的表添加在这里 Tables + +var Tables = []interface{}{ + &LogLogin{}, + &LogLoginMid{}, + &UserAccount{}, + &UserLogin{}, + &UserID{}, + &LogInviteScoreMid{}, + &LogInviteScore{}, + &LogInviteUser{}, + &LogMid{}, + &ItemGain{}, + &ItemTotalGain{}, +} diff --git a/statistics/modelmysql/user_account.go b/statistics/modelmysql/user_account.go new file mode 100644 index 0000000..0d56bbf --- /dev/null +++ b/statistics/modelmysql/user_account.go @@ -0,0 +1,18 @@ +package modelmysql + +import "time" + +type UserAccount struct { + ID uint `gorm:"primaryKey"` + MID string + Snid int `gorm:"index"` + //RegisterTs int `gorm:"index"` + RegisterTime time.Time `gorm:"index"` + ChannelId string `gorm:"index"` // 推广渠道 + + DeviceName string `gorm:"index"` + AppVersion string `gorm:"index"` + BuildVersion string `gorm:"index"` + AppChannel string `gorm:"index"` + Tel string `gorm:"index"` +} diff --git a/statistics/modelmysql/user_id.go b/statistics/modelmysql/user_id.go new file mode 100644 index 0000000..5a3ce33 --- /dev/null +++ b/statistics/modelmysql/user_id.go @@ -0,0 +1,9 @@ +package modelmysql + +/* + 服务定期查询注册和登录信息,然后获取玩家id,保存到这张表中;用于后续触发和玩家相关的数据统计 +*/ + +type UserID struct { + Snid int `gorm:"primaryKey"` +} diff --git a/statistics/modelmysql/user_login.go b/statistics/modelmysql/user_login.go new file mode 100644 index 0000000..4ed6e93 --- /dev/null +++ b/statistics/modelmysql/user_login.go @@ -0,0 +1,29 @@ +package modelmysql + +import "time" + +const ( + OutTypRegister = 1 // 注册 + OutTypeLogin = 2 // 登录 + OutTypeGaming = 3 // 游戏中 + OutTypeGameOver = 4 // 游戏结束 +) + +type UserLogin struct { + ID uint `gorm:"primaryKey"` + Snid int `gorm:"uniqueIndex"` + //OnlineTs int `gorm:"index"` + OnlineTime time.Time `gorm:"index"` + //OfflineTs int `gorm:"index"` + OfflineTime time.Time `gorm:"index"` + OutType int `gorm:"index"` // 跳出类型 + GameID int `gorm:"index"` // 游戏id + Age int + Sex int + DeviceName string `gorm:"index"` + AppVersion string `gorm:"index"` + BuildVersion string `gorm:"index"` + AppChannel string `gorm:"index"` + Tel string `gorm:"index"` + ChannelId string `gorm:"index"` // 推广渠道 +} diff --git a/statistics/mq/handler.go b/statistics/mq/handler.go new file mode 100644 index 0000000..71893fd --- /dev/null +++ b/statistics/mq/handler.go @@ -0,0 +1 @@ +package mq diff --git a/statistics/mq/readme b/statistics/mq/readme new file mode 100644 index 0000000..9c843ee --- /dev/null +++ b/statistics/mq/readme @@ -0,0 +1 @@ +接收消息队列 \ No newline at end of file diff --git a/statistics/static/readme b/statistics/static/readme new file mode 100644 index 0000000..b98bf32 --- /dev/null +++ b/statistics/static/readme @@ -0,0 +1 @@ +业务统计 \ No newline at end of file diff --git a/statistics/static/user_login.go b/statistics/static/user_login.go new file mode 100644 index 0000000..d217e2c --- /dev/null +++ b/statistics/static/user_login.go @@ -0,0 +1,371 @@ +package static + +import ( + "context" + "errors" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + "mongo.games.com/goserver/core/logger" + + mymongo "mongo.games.com/goserver/core/mongox" + mymysql "mongo.games.com/goserver/core/mysqlx" + + mongomodel "mongo.games.com/game/statistics/modelmongo" + mysqlmodel "mongo.games.com/game/statistics/modelmysql" +) + +func getAccountTel(platform string, id int) (string, error) { + acc := &mongomodel.Account{} + cc, err := mymongo.GetUserCollection(platform, mongomodel.UserAccount) + if err != nil { + logger.Logger.Errorf("get collection %s error %v", mongomodel.UserAccount, err) + return "", err + } + dd := cc.FindOne(context.TODO(), bson.M{"snid": id}, options.FindOne().SetProjection(bson.M{"tel": 1})) + err = dd.Err() + if err != nil { + if errors.Is(err, mongo.ErrNoDocuments) { + logger.Logger.Tracef("getAccountTel %v not found in user_account", id) + return "", nil + } + logger.Logger.Errorf("getAccountTel %v get user_account err: %v", id, err) + return "", err + } + if err := dd.Decode(acc); err != nil { + logger.Logger.Errorf("getAccountTel %v decode user_account err: %v", id, err) + return "", err + } + return acc.Tel, nil +} + +// 游戏结束离开 +func checkGameOver(db *mymysql.Database, login *mysqlmodel.UserLogin, platform string, id int) (bool, error) { + // 最早的一条掉线记录并且是游戏结束离开 + a := &mongomodel.LoginLog{} + c, err := mymongo.GetLogCollection(platform, mongomodel.LogLogin) + if err != nil { + logger.Logger.Errorf("get collection %s error %v", mongomodel.LogLogin, err) + return false, err + } + d := c.FindOne(context.TODO(), bson.M{"snid": id, "logtype": mongomodel.LogTypeDrop, "gameid": 0, "lastgameid": bson.D{{"$gt", 0}}}, + options.FindOne().SetSort(bson.D{{"time", 1}})) + err = d.Err() + if err != nil { + if errors.Is(err, mongo.ErrNoDocuments) { + logger.Logger.Tracef("checkGameOver %v not found in log_login", id) + return false, nil + } + logger.Logger.Errorf("checkGameOver %v get log_login err: %v", id, err) + return false, err + } + if err := d.Decode(a); err != nil { + logger.Logger.Errorf("checkGameOver %v decode log_login err: %v", id, err) + return false, err + } + + // account tel + tel, err := getAccountTel(platform, id) + if err != nil { + logger.Logger.Warnf("get account tel %v err: %v", id, err) + } + + update := &mysqlmodel.UserLogin{ + //OfflineTs: int(a.Ts), + OfflineTime: a.Time, + OutType: mysqlmodel.OutTypeGameOver, + GameID: a.LastGameID, + Tel: tel, + DeviceName: a.DeviceName, + AppVersion: a.AppVersion, + BuildVersion: a.BuildVersion, + AppChannel: a.AppChannel, + ChannelId: a.ChannelId, + } + + if err := db.Model(login).Select( + "OfflineTime", "OutType", "GameID", "DeviceName", "AppVersion", "BuildVersion", "AppChannel", "Tel", + ).Updates(update).Error; err != nil { + logger.Logger.Errorf("checkLogin %v update user_login err: %v", id, err) + return false, err + } + + return true, nil +} + +// 游戏中离开 +func checkGaming(db *mymysql.Database, login *mysqlmodel.UserLogin, platform string, id int) (bool, error) { + // 最早的一条掉线记录并且是游戏中掉线 + a := &mongomodel.LoginLog{} + c, err := mymongo.GetLogCollection(platform, mongomodel.LogLogin) + if err != nil { + logger.Logger.Errorf("get collection %s error %v", mongomodel.LogLogin, err) + return false, err + } + d := c.FindOne(context.TODO(), bson.M{"snid": id, "logtype": mongomodel.LogTypeDrop, "gameid": bson.D{{"$gt", 0}}}, + options.FindOne().SetSort(bson.D{{"time", 1}})) + err = d.Err() + if err != nil { + if errors.Is(err, mongo.ErrNoDocuments) { + logger.Logger.Tracef("checkGaming %v not found in log_login", id) + return false, nil + } + logger.Logger.Errorf("checkGaming %v get log_login err: %v", id, err) + return false, err + } + if err := d.Decode(a); err != nil { + logger.Logger.Errorf("checkGaming %v decode log_login err: %v", id, err) + return false, err + } + + // account tel + tel, err := getAccountTel(platform, id) + if err != nil { + logger.Logger.Warnf("get account tel %v err: %v", id, err) + } + + update := &mysqlmodel.UserLogin{ + //OfflineTs: int(a.Ts), + OfflineTime: a.Time, + OutType: mysqlmodel.OutTypeGaming, + GameID: a.GameId, + Tel: tel, + DeviceName: a.DeviceName, + AppVersion: a.AppVersion, + BuildVersion: a.BuildVersion, + AppChannel: a.AppChannel, + ChannelId: a.ChannelId, + } + + if err := db.Model(login).Select( + "OfflineTime", "OutType", "GameID", "DeviceName", "AppVersion", "BuildVersion", "AppChannel", "Tel", + ).Updates(update).Error; err != nil { + logger.Logger.Errorf("checkLogin %v update user_login err: %v", id, err) + return false, err + } + + return true, nil +} + +// 登录后离开 +func checkLogin(db *mymysql.Database, login *mysqlmodel.UserLogin, platform string, id int) (bool, error) { + // 最早的一条掉线记录 + a := &mongomodel.LoginLog{} + c, err := mymongo.GetLogCollection(platform, mongomodel.LogLogin) + if err != nil { + logger.Logger.Errorf("get collection %s error %v", mongomodel.LogLogin, err) + return false, err + } + d := c.FindOne(context.TODO(), bson.M{"snid": id, "logtype": mongomodel.LogTypeDrop}, options.FindOne().SetSort(bson.D{{"time", 1}})) + err = d.Err() + if err != nil { + if errors.Is(err, mongo.ErrNoDocuments) { + logger.Logger.Tracef("checkLogin %v not found in log_login", id) + return false, nil + } + logger.Logger.Errorf("checkLogin %v get log_login err: %v", id, err) + return false, err + } + if err := d.Decode(a); err != nil { + logger.Logger.Errorf("checkLogin %v decode log_login err: %v", id, err) + return false, err + } + + // account tel + tel, err := getAccountTel(platform, id) + if err != nil { + logger.Logger.Warnf("get account tel %v err: %v", id, err) + } + + update := &mysqlmodel.UserLogin{ + //OfflineTs: int(a.Ts), + OfflineTime: a.Time, + OutType: mysqlmodel.OutTypeLogin, + Tel: tel, + DeviceName: a.DeviceName, + AppVersion: a.AppVersion, + BuildVersion: a.BuildVersion, + AppChannel: a.AppChannel, + ChannelId: a.ChannelId, + } + + if err := db.Model(login).Select( + "OfflineTime", "OutType", "DeviceName", "AppVersion", "BuildVersion", "AppChannel", "Tel", + ).Updates(update).Error; err != nil { + logger.Logger.Errorf("checkLogin %v update user_login err: %v", id, err) + return false, err + } + + return true, nil +} + +// 注册后离开 +func checkRegister(db *mymysql.Database, login *mysqlmodel.UserLogin, platform string, id int) (bool, error) { + a := &mongomodel.Account{} + c, err := mymongo.GetUserCollection(platform, mongomodel.UserAccount) + if err != nil { + logger.Logger.Errorf("get collection %s error %v", mongomodel.UserAccount, err) + return false, err + } + d := c.FindOne(context.TODO(), bson.M{"snid": id}) + err = d.Err() + if err != nil { + if errors.Is(err, mongo.ErrNoDocuments) { + logger.Logger.Warnf("checkRegister %v not found in user_account", id) + return false, nil + } + logger.Logger.Errorf("checkRegister %v get user_account err: %v", id, err) + return false, err + } + if err := d.Decode(a); err != nil { + logger.Logger.Errorf("checkRegister %v decode user_account err: %v", id, err) + return false, err + } + + // account tel + tel, err := getAccountTel(platform, id) + if err != nil { + logger.Logger.Warnf("get account tel %v err: %v", id, err) + } + + login.Snid = id + //login.OnlineTs = int(a.RegisterTs) + login.OnlineTime = a.RegisteTime + //login.OfflineTs = int(a.RegisterTs) + login.OfflineTime = a.RegisteTime + login.OutType = mysqlmodel.OutTypRegister + login.Tel = tel + login.DeviceName = a.DeviceName + login.AppVersion = a.AppVersion + login.BuildVersion = a.BuildVersion + login.AppChannel = a.AppChannel + login.ChannelId = a.ChannelId + + if err := db.Create(login).Error; err != nil { + logger.Logger.Errorf("checkRegister create err: %v", err) + return false, err + } + return true, nil +} + +// UserLogin 玩家跳出统计 +func UserLogin(platform string, ids []*mysqlmodel.UserID) error { + f := func(id int) error { + // 玩家是否已经统计结束,已经是游戏结束状态 + login := &mysqlmodel.UserLogin{} + db, err := mymysql.GetDatabase(platform) + if err != nil { + logger.Logger.Errorf("UserLogin get db err: %v", err) + return err + } + if err = db.Where("snid = ?", id).Find(login).Error; err != nil { + logger.Logger.Errorf("UserLogin find %v err: %v", id, err) + return err + } + + switch login.OutType { + case mysqlmodel.OutTypeGameOver: + return nil + + case mysqlmodel.OutTypeGaming: + _, err := checkGameOver(db, login, platform, id) + if err != nil { + logger.Logger.Errorf("UserLogin checkGameOver %v err: %v", id, err) + return err + } + return nil + + case mysqlmodel.OutTypeLogin: + ret, err := checkGameOver(db, login, platform, id) + if err != nil { + logger.Logger.Errorf("UserLogin checkGameOver %v err: %v", id, err) + return err + } + if ret { + return nil + } + ret, err = checkGaming(db, login, platform, id) + if err != nil { + logger.Logger.Errorf("UserLogin checkGaming %v err: %v", id, err) + return err + } + if ret { + return nil + } + + case mysqlmodel.OutTypRegister: + ret, err := checkGameOver(db, login, platform, id) + if err != nil { + logger.Logger.Errorf("UserLogin checkGameOver %v err: %v", id, err) + return err + } + if ret { + return nil + } + ret, err = checkGaming(db, login, platform, id) + if err != nil { + logger.Logger.Errorf("UserLogin checkGaming %v err: %v", id, err) + return err + } + if ret { + return nil + } + ret, err = checkLogin(db, login, platform, id) + if err != nil { + logger.Logger.Errorf("UserLogin checkLogin %v err: %v", id, err) + return err + } + if ret { + return nil + } + + default: + ret, err := checkRegister(db, login, platform, id) + if err != nil { + logger.Logger.Errorf("UserLogin checkRegister %v err: %v", id, err) + return err + } + if !ret { + logger.Logger.Warnf("UserLogin not found user_account checkRegister %v err: %v", id, err) + return nil + } + + ret, err = checkGameOver(db, login, platform, id) + if err != nil { + logger.Logger.Errorf("UserLogin checkGameOver %v err: %v", id, err) + return err + } + if ret { + return nil + } + ret, err = checkGaming(db, login, platform, id) + if err != nil { + logger.Logger.Errorf("UserLogin checkGaming %v err: %v", id, err) + return err + } + if ret { + return nil + } + ret, err = checkLogin(db, login, platform, id) + if err != nil { + logger.Logger.Errorf("UserLogin checkLogin %v err: %v", id, err) + return err + } + if ret { + return nil + } + return nil + } + + return nil + } + + for _, v := range ids { + if err := f(v.Snid); err != nil { + return err + } + } + + return nil +} diff --git a/statistics/syn/datasync.go b/statistics/syn/datasync.go new file mode 100644 index 0000000..8f94fc7 --- /dev/null +++ b/statistics/syn/datasync.go @@ -0,0 +1,102 @@ +package syn + +import ( + "context" + "errors" + + "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" + "gorm.io/gorm" + "mongo.games.com/goserver/core/logger" + + mysqlmodel "mongo.games.com/game/statistics/modelmysql" + mymongo "mongo.games.com/goserver/core/mongox" + mymysql "mongo.games.com/goserver/core/mysqlx" +) + +// Data 数据同步方法 +// T mongodb数据表结构 +// F mongodb中的每条数据的处理操作,自行实现 +type Data[T any] struct { + Platform string // 平台 + MidType int // 数据类型 例如 modelmysql.MidTypeItem + Database string // 库名称 + CollectionName string // 集合名称 + BatchSize int // 一次读取数量 + // F 自定义数据处理方法 + // data: mongodb中的一条日志 + F func(data *T, db *gorm.DB) (string, error) +} + +// CommonDone 数据获取方式,根据mongodb集合主键按时间顺序批量读取 +func (d *Data[T]) CommonDone() error { + db, err := mymysql.GetDatabase(d.Platform) + if err != nil { + logger.Logger.Errorf("mysql: failed to get database: %v", err) + return err + } + loginMID := &mysqlmodel.LogMid{Tp: d.MidType} + var n int64 + err = db.Model(&mysqlmodel.LogMid{}).Find(loginMID).Count(&n).Error + if err != nil { + logger.Logger.Errorf("mysql: failed to get log_mid: %v", err) + return err + } + if n == 0 { + if err = db.Create(loginMID).Error; err != nil { + logger.Logger.Errorf("mysql: failed to create log_mid: %v", err) + return err + } + } + + logger.Logger.Tracef("start log_mid tp:%v _id:%v", loginMID.Tp, loginMID.MID) + + _id, _ := primitive.ObjectIDFromHex(loginMID.MID) + filter := bson.M{"_id": bson.M{"$gt": _id}} + c, err := mymongo.GetCollection(d.Platform, mymongo.DatabaseType(d.Database), d.CollectionName) + if err != nil { + logger.Logger.Errorf("get collection %s %s error %v", d.Database, d.CollectionName, err) + return err + } + l, err := c.Find(context.TODO(), filter, + options.Find().SetSort(bson.D{primitive.E{Key: "_id", Value: 1}}), options.Find().SetLimit(int64(d.BatchSize))) + if err != nil && !errors.Is(err, mongo.ErrNoDocuments) { + logger.Logger.Errorf("mongo: failed to get %v: %v", d.CollectionName, err) + return err + } + + var logs []*T + if err = l.All(context.TODO(), &logs); err != nil { + l.Close(context.TODO()) + if errors.Is(err, mongo.ErrNoDocuments) { + return nil + } + + logger.Logger.Errorf("mongo: failed to get %v: %v", d.CollectionName, err) + return err + } + l.Close(context.TODO()) + if len(logs) == 0 { + logger.Logger.Infof("sync %v finished", d.CollectionName) + return nil + } + + err = db.Transaction(func(tx *gorm.DB) error { + for _, v := range logs { + loginMID.MID, err = d.F(v, tx) + if err != nil { + logger.Logger.Errorf("Process %v error:%v", d.CollectionName, err) + return err + } + if err = tx.Model(loginMID).Updates(loginMID).Error; err != nil { + logger.Logger.Errorf("mysql: failed to update %v log_mid: %v", d.CollectionName, err) + return err + } + } + return nil + }) + + return err +} diff --git a/statistics/syn/log_invitescore.go b/statistics/syn/log_invitescore.go new file mode 100644 index 0000000..1531469 --- /dev/null +++ b/statistics/syn/log_invitescore.go @@ -0,0 +1,291 @@ +package syn + +import ( + "context" + "errors" + + "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" + "gorm.io/gorm" + + "mongo.games.com/game/statistics/constant" + mongomodel "mongo.games.com/game/statistics/modelmongo" + mysqlmodel "mongo.games.com/game/statistics/modelmysql" + "mongo.games.com/goserver/core/logger" + mymongo "mongo.games.com/goserver/core/mongox" + mymysql "mongo.games.com/goserver/core/mysqlx" +) + +func SyncInviteScore(platform string, batchSize int) error { + db, err := mymysql.GetDatabase(platform) + if err != nil { + logger.Logger.Errorf("mysql: SyncInviteScore failed to get database: %v", err) + return err + } + inviteMID := &mysqlmodel.LogInviteScoreMid{ID: 1} + var n int64 + err = db.Model(&mysqlmodel.LogInviteScoreMid{}).Find(inviteMID).Count(&n).Error + if err != nil { + logger.Logger.Errorf("mysql: SyncInviteScore failed to get log_invitescore_mid: %v", err) + return err + } + if n == 0 { + if err = db.Create(inviteMID).Error; err != nil { + logger.Logger.Errorf("mysql: SyncInviteScore failed to create log_invitescore_mid: %v", err) + return err + } + } + + logger.Logger.Tracef("start SyncInviteScore log_invitescore _id:%v", inviteMID.MID) + + _id, _ := primitive.ObjectIDFromHex(inviteMID.MID) + filter := bson.M{"_id": bson.M{"$gt": _id}} + c, err := mymongo.GetLogCollection(platform, mongomodel.LogInviteScore) + if err != nil { + logger.Logger.Errorf("get collection %s error %v", mongomodel.LogInviteScore, err) + return err + } + l, err := c.Find(context.TODO(), filter, + options.Find().SetSort(bson.D{primitive.E{Key: "_id", Value: 1}}), options.Find().SetLimit(int64(batchSize))) + if err != nil && !errors.Is(err, mongo.ErrNoDocuments) { + logger.Logger.Errorf("mongo: SyncInviteScore failed to get log_invitescore: %v", err) + return err + } + + var logs []*mongomodel.InviteScore + if err = l.All(context.TODO(), &logs); err != nil { + l.Close(context.TODO()) + if errors.Is(err, mongo.ErrNoDocuments) { + return err + } + + logger.Logger.Errorf("mongo: SyncInviteScore failed to get log_invitescore: %v", err) + return err + } + l.Close(context.TODO()) + + getPSnId := func(tx *gorm.DB, snid int) (int, error) { + if snid <= 0 { + return 0, nil + } + ret := new(mysqlmodel.LogInviteUser) + if err = tx.First(ret, "snid = ? and level = 1", snid).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + logger.Logger.Errorf("mysql: SyncInviteScore failed to getPSnId: %v", err) + return 0, err + } + return ret.Psnid, nil + } + + getDownSnId := func(tx *gorm.DB, snid []int) ([]int, error) { + if len(snid) == 0 { + return nil, nil + } + var ret []int + var us []*mysqlmodel.LogInviteUser + if err = tx.Select("snid").Where("psnid IN ? AND level = 1", snid).Find(&us).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + logger.Logger.Errorf("mysql: SyncInviteScore failed to getDownSnId: %v", err) + return ret, err + } + for _, v := range us { + ret = append(ret, v.Snid) + } + return ret, nil + } + + bind := func(tx *gorm.DB, psnid, snid int, ts int) ([]*mysqlmodel.LogInviteUser, error) { + var lu []*mysqlmodel.LogInviteUser + var a1, a2, a3, a4, b1 int + var b2, b3, b4 []int + a4 = psnid + a3, err = getPSnId(tx, a4) + if err != nil { + return nil, err + } + a2, err = getPSnId(tx, a3) + if err != nil { + return nil, err + } + a1, err = getPSnId(tx, a2) + if err != nil { + return nil, err + } + b1 = snid + b2, err = getDownSnId(tx, []int{b1}) + if err != nil { + return nil, err + } + b3, err = getDownSnId(tx, b2) + if err != nil { + return nil, err + } + b4, err = getDownSnId(tx, b3) + if err != nil { + return nil, err + } + logger.Logger.Tracef("a1:%d, a2:%d, a3:%d, a4:%d, b1:%d, b2:%v, b3:%v, b4:%v", a1, a2, a3, a4, b1, b2, b3, b4) + if a1 > 0 { + if b1 > 0 { + lu = append(lu, &mysqlmodel.LogInviteUser{ + Psnid: a1, + Snid: b1, + Level: 4, + Ts: ts, + }) + logger.Logger.Tracef("a1: %v %v %v", b1, 4, ts) + } + } + if a2 > 0 { + if b1 > 0 { + lu = append(lu, &mysqlmodel.LogInviteUser{ + Psnid: a2, + Snid: b1, + Level: 3, + Ts: ts, + }) + logger.Logger.Tracef("a2: %v %v %v", b1, 3, ts) + } + for _, v := range b2 { + if v <= 0 { + continue + } + lu = append(lu, &mysqlmodel.LogInviteUser{ + Psnid: a2, + Snid: v, + Level: 4, + Ts: ts, + }) + logger.Logger.Tracef("a2: %v %v %v", v, 4, ts) + } + } + if a3 > 0 { + if b1 > 0 { + lu = append(lu, &mysqlmodel.LogInviteUser{ + Psnid: a3, + Snid: b1, + Level: 2, + Ts: ts, + }) + logger.Logger.Tracef("a3: %v %v %v", b1, 2, ts) + } + for _, v := range b2 { + if v <= 0 { + continue + } + lu = append(lu, &mysqlmodel.LogInviteUser{ + Psnid: a3, + Snid: v, + Level: 3, + Ts: ts, + }) + logger.Logger.Tracef("a3: %v %v %v", v, 3, ts) + } + for _, v := range b3 { + if v <= 0 { + continue + } + lu = append(lu, &mysqlmodel.LogInviteUser{ + Psnid: a3, + Snid: v, + Level: 4, + Ts: ts, + }) + logger.Logger.Tracef("a3: %v %v %v", v, 4, ts) + } + } + if a4 > 0 { + if b1 > 0 { + lu = append(lu, &mysqlmodel.LogInviteUser{ + Psnid: a4, + Snid: b1, + Level: 1, + Ts: ts, + }) + logger.Logger.Tracef("a4: %v %v %v", b1, 1, ts) + } + for _, v := range b2 { + if v <= 0 { + continue + } + lu = append(lu, &mysqlmodel.LogInviteUser{ + Psnid: a4, + Snid: v, + Level: 2, + Ts: ts, + }) + logger.Logger.Tracef("a4: %v %v %v", v, 2, ts) + } + for _, v := range b3 { + if v <= 0 { + continue + } + lu = append(lu, &mysqlmodel.LogInviteUser{ + Psnid: a4, + Snid: v, + Level: 3, + Ts: ts, + }) + logger.Logger.Tracef("a4: %v %v %v", v, 3, ts) + } + for _, v := range b4 { + if v <= 0 { + continue + } + lu = append(lu, &mysqlmodel.LogInviteUser{ + Psnid: a4, + Snid: v, + Level: 4, + Ts: ts, + }) + logger.Logger.Tracef("a4: %v %v %v", v, 4, ts) + } + } + return lu, nil + } + + for _, v := range logs { + err = db.Transaction(func(tx *gorm.DB) error { + inviteMID.MID = v.Id.Hex() + if err = tx.Model(inviteMID).Updates(inviteMID).Error; err != nil { + logger.Logger.Errorf("mysql: SyncInviteScore failed to update log_invitescore_mid: %v", err) + return err + } + + err = tx.Save(&mysqlmodel.LogInviteScore{ + UpSnid: v.UpSnid, + DownSnid: v.DownSnid, + Level: v.Level, + Tp: v.Tp, + Rate: v.Rate, + Score: v.Score, + Money: v.Money, + Ts: v.Ts, + }).Error + if err != nil { + logger.Logger.Errorf("mysql: SyncInviteScore failed to insert: %v", err) + return err + } + + if v.Tp == constant.InviteScoreTypeBind && v.Level == 0 { + // 绑定关系 + lu, err := bind(tx, v.UpSnid, v.DownSnid, v.Ts) + if err != nil { + logger.Logger.Errorf("mysql: SyncInviteScore failed to bind: %v", err) + return err + } + if err = tx.CreateInBatches(lu, len(lu)).Error; err != nil { + logger.Logger.Errorf("mysql: SyncInviteScore failed to create log_invite_user: %v", err) + return err + } + } + + return nil + }) + if err != nil { + logger.Logger.Errorf("mysql: SyncInviteScore failed to transaction: %v", err) + return err + } + } + return nil +} diff --git a/statistics/syn/log_itemgain.go b/statistics/syn/log_itemgain.go new file mode 100644 index 0000000..4751255 --- /dev/null +++ b/statistics/syn/log_itemgain.go @@ -0,0 +1,61 @@ +package syn + +import ( + "errors" + "time" + + "gorm.io/gorm" + "mongo.games.com/goserver/core/mongox" + + mongomodel "mongo.games.com/game/statistics/modelmongo" + mysqlmodel "mongo.games.com/game/statistics/modelmysql" +) + +func ItemGainDone(data *Data[mongomodel.ItemLog]) error { + data.MidType = mysqlmodel.MidTypeItem + data.Database = string(mongox.DatabaseLog) + data.CollectionName = mongomodel.LogItem + data.F = func(data *mongomodel.ItemLog, db *gorm.DB) (string, error) { + if data == nil || data.LogId.Hex() == "" { + return "", errors.New("null") + } + if data.LogType != 0 || data.Id != "" { + return data.LogId.Hex(), nil + } + + hourTime := time.Unix(data.CreateTs, 0).Local() + hourTs := time.Date(hourTime.Year(), hourTime.Month(), hourTime.Day(), hourTime.Hour(), 0, 0, 0, time.Local).Unix() + + item := &mysqlmodel.ItemGain{} + err := db.Model(item).Where("hour = ? and item_id = ?", hourTs, data.ItemId).First(item).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return "", err + } + item.Hour = hourTs + item.ItemId = data.ItemId + item.ItemNum += data.Count + if item.ID == 0 { + err = db.Create(item).Error + } else { + err = db.Model(item).Updates(item).Error + } + if err != nil { + return "", err + } + + itemTotal := &mysqlmodel.ItemTotalGain{} + err = db.Model(itemTotal).Where("item_id = ?", data.ItemId).First(itemTotal).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return "", err + } + itemTotal.ItemId = data.ItemId + itemTotal.ItemNum += data.Count + if itemTotal.ID == 0 { + err = db.Create(itemTotal).Error + } else { + err = db.Model(itemTotal).Updates(itemTotal).Error + } + return data.LogId.Hex(), err + } + return data.CommonDone() +} diff --git a/statistics/syn/log_login.go b/statistics/syn/log_login.go new file mode 100644 index 0000000..7f338e1 --- /dev/null +++ b/statistics/syn/log_login.go @@ -0,0 +1,181 @@ +package syn + +import ( + "context" + "errors" + + "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" + "gorm.io/gorm" + + mongomodel "mongo.games.com/game/statistics/modelmongo" + mysqlmodel "mongo.games.com/game/statistics/modelmysql" + "mongo.games.com/goserver/core/logger" + mymongo "mongo.games.com/goserver/core/mongox" + mymysql "mongo.games.com/goserver/core/mysqlx" +) + +/* + 登录日志同步使用了mongo的_id,从小到大每次同步n个 +*/ + +// LogLogin 同步登录日志 +func LogLogin(platform string, batchSize int) ([]*mysqlmodel.LogLogin, error) { + db, err := mymysql.GetDatabase(platform) + if err != nil { + logger.Logger.Errorf("mysql: SyncLogLogin failed to get database: %v", err) + return nil, err + } + loginMID := &mysqlmodel.LogLoginMid{ID: 1} + var n int64 + err = db.Model(&mysqlmodel.LogLoginMid{}).Find(loginMID).Count(&n).Error + if err != nil { + logger.Logger.Errorf("mysql: SyncLogLogin failed to get log_login_mid: %v", err) + return nil, err + } + if n == 0 { + if err = db.Create(loginMID).Error; err != nil { + logger.Logger.Errorf("mysql: SyncLogLogin failed to create log_login_mid: %v", err) + return nil, err + } + } + + logger.Logger.Tracef("start SyncLogLogin log_login _id:%v", loginMID.MID) + + _id, _ := primitive.ObjectIDFromHex(loginMID.MID) + filter := bson.M{"_id": bson.M{"$gt": _id}} + c, err := mymongo.GetLogCollection(platform, mongomodel.LogLogin) + if err != nil { + logger.Logger.Errorf("get collection %s error %v", mongomodel.LogLogin, err) + return nil, err + } + l, err := c.Find(context.TODO(), filter, + options.Find().SetSort(bson.D{primitive.E{Key: "_id", Value: 1}}), options.Find().SetLimit(int64(batchSize))) + if err != nil && !errors.Is(err, mongo.ErrNoDocuments) { + logger.Logger.Errorf("mongo: SyncLogLogin failed to get log_login: %v", err) + return nil, err + } + + var logs []*mongomodel.LoginLog + if err = l.All(context.TODO(), &logs); err != nil { + l.Close(context.TODO()) + if errors.Is(err, mongo.ErrNoDocuments) { + return nil, nil + } + + logger.Logger.Errorf("mongo: SyncLogLogin failed to get loginlog: %v", err) + return nil, err + } + l.Close(context.TODO()) + + var ls []*mysqlmodel.LogLogin + for _, v := range logs { + logger.Logger.Tracef("mongo SyncLogLogin log_login: %+v", *v) + var e *mysqlmodel.LogLogin + switch v.LogType { + case mongomodel.LogTypeLogin, mongomodel.LogTypeRehold: + onlineType := mysqlmodel.LogTypeLogin + if v.LogType == mongomodel.LogTypeRehold { + onlineType = mysqlmodel.LogTypeRehold + } + + // 创建数据 + var n int64 + if err = db.Model(&mysqlmodel.LogLogin{}).Where("snid = ? AND online_type = ? AND online_time = ?", + v.SnId, onlineType, v.Time).Count(&n).Error; err != nil { + logger.Logger.Errorf("mysql: SyncLogLogin failed to get log_login count: %v", err) + return ls, err + } + + if n == 0 { + e = &mysqlmodel.LogLogin{ + Snid: int(v.SnId), + OnlineType: onlineType, + //OnlineTs: int(v.Ts), + OnlineTime: v.Time, + OfflineType: 0, + //OfflineTs: 0, + OfflineTime: v.Time, + DeviceName: v.DeviceName, + AppVersion: v.AppVersion, + BuildVersion: v.BuildVersion, + AppChannel: v.AppChannel, + ChannelId: v.ChannelId, + } + if err = db.Create(e).Error; err != nil { + logger.Logger.Errorf("mysql: SyncLogLogin failed to create log_login: %v", err) + return ls, err + } + } else { + continue + } + + case mongomodel.LogTypeLogout, mongomodel.LogTypeDrop: + // 修改数据 + e = &mysqlmodel.LogLogin{} + err = db.Model(&mysqlmodel.LogLogin{}).Where("snid = ?", v.SnId).Order("online_time DESC").First(e).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + logger.Logger.Errorf("mysql: SyncLogLogin failed to find log_login: %v", err) + return ls, err + } + + if errors.Is(err, gorm.ErrRecordNotFound) { + logger.Logger.Warnf("mysql: SyncLogLogin not found log_login: %v", v) + continue + } + + if e.OfflineType != 0 { + logger.Logger.Tracef("mysql: SyncLogLogin already offline: %+v", *e) + continue + } + + e.OfflineType = mysqlmodel.LogTypeOffline + //e.OfflineTs = int(v.Ts) + e.OfflineTime = v.Time + if err = db.Model(e).Select("OfflineType", "OfflineTime").Updates(e).Error; err != nil { + logger.Logger.Errorf("mysql: SyncLogLogin failed to update log_login: %v", err) + return ls, err + } + default: + continue + } + + if e != nil { + ls = append(ls, e) + } + } + + if len(logs) > 0 { + err = db.Transaction(func(tx *gorm.DB) error { + loginMID.MID = logs[len(logs)-1].LogId.Hex() + if err = tx.Model(loginMID).Updates(loginMID).Error; err != nil { + logger.Logger.Errorf("mysql: SyncLogLogin failed to update log_login_mid: %v", err) + return err + } + + for _, v := range ls { + if err = tx.First(&mysqlmodel.UserID{}, "snid = ?", v.Snid).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + logger.Logger.Errorf("mysql: SyncLogLogin failed to find user_id: %v", err) + return err + } + + if errors.Is(err, gorm.ErrRecordNotFound) { + if err = tx.Create(&mysqlmodel.UserID{Snid: v.Snid}).Error; err != nil { + logger.Logger.Errorf("mysql: SyncLogLogin failed to create user_id: %v", err) + return err + } + } + } + + return nil + }) + if err != nil { + logger.Logger.Errorf("mysql: SyncLogLogin failed to transaction: %v", err) + return nil, err + } + } + + return ls, nil +} diff --git a/statistics/syn/readme b/statistics/syn/readme new file mode 100644 index 0000000..37acc1c --- /dev/null +++ b/statistics/syn/readme @@ -0,0 +1 @@ +游戏服mongodb同步到mysql \ No newline at end of file diff --git a/statistics/syn/user_account.go b/statistics/syn/user_account.go new file mode 100644 index 0000000..72b1c52 --- /dev/null +++ b/statistics/syn/user_account.go @@ -0,0 +1,105 @@ +package syn + +import ( + "context" + "errors" + + "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" + "gorm.io/gorm" + + mongomodel "mongo.games.com/game/statistics/modelmongo" + mysqlmodel "mongo.games.com/game/statistics/modelmysql" + "mongo.games.com/goserver/core/logger" + mymongo "mongo.games.com/goserver/core/mongox" + mymysql "mongo.games.com/goserver/core/mysqlx" +) + +/* + 注册信息同步,使用mongo的_id,从小到大每次同步n个 +*/ + +// UserAccount 同步注册表 +func UserAccount(platform string, batchSize int) ([]*mysqlmodel.UserAccount, error) { + db, err := mymysql.GetDatabase(platform) + if err != nil { + logger.Logger.Errorf("mysql: UserAccount failed to get database: %v", err) + return nil, err + } + account := &mysqlmodel.UserAccount{} + err = db.Model(&mysqlmodel.UserAccount{}).Last(account).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + logger.Logger.Errorf("mysql: UserAccount failed to get account: %v", err) + return nil, err + } + + logger.Logger.Tracef("start UserAccount account _id:%v", account.MID) + + _id, _ := primitive.ObjectIDFromHex(account.MID) + filter := bson.M{"_id": bson.M{"$gt": _id}} + c, err := mymongo.GetUserCollection(platform, mongomodel.UserAccount) + if err != nil { + logger.Logger.Errorf("get collection %s error %v", mongomodel.UserAccount, err) + return nil, err + } + l, err := c.Find(context.TODO(), filter, + options.Find().SetSort(bson.D{primitive.E{Key: "_id", Value: 1}}), options.Find().SetLimit(int64(batchSize))) + if err != nil && !errors.Is(err, mongo.ErrNoDocuments) { + logger.Logger.Errorf("mongo: UserAccount failed to get account: %v", err) + return nil, err + } + + var accounts []*mongomodel.Account + if err = l.All(context.TODO(), &accounts); err != nil { + l.Close(context.TODO()) + if errors.Is(err, mongo.ErrNoDocuments) { + return nil, nil + } + + logger.Logger.Errorf("mongo: UserAccount failed to get account: %v", err) + return nil, err + } + l.Close(context.TODO()) + + var as []*mysqlmodel.UserAccount + err = db.Transaction(func(tx *gorm.DB) error { + for _, v := range accounts { + logger.Logger.Tracef("mongo account: %+v", *v) + a := &mysqlmodel.UserAccount{ + MID: v.AccountId.Hex(), + Snid: int(v.SnId), + //RegisterTs: int(v.RegisterTs), + RegisterTime: v.RegisteTime, + Tel: v.Tel, + ChannelId: v.ChannelId, + } + + if err = tx.Create(a).Error; err != nil { + logger.Logger.Errorf("mysql: UserAccount failed to create account: %v", err) + return err + } + + if err = tx.First(&mysqlmodel.UserID{}, "snid = ?", v.SnId).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + logger.Logger.Errorf("mysql: UserAccount failed to find user_id: %v", err) + return err + } + + if errors.Is(err, gorm.ErrRecordNotFound) { + if err = tx.Create(&mysqlmodel.UserID{Snid: int(v.SnId)}).Error; err != nil { + logger.Logger.Errorf("mysql: UserAccount failed to create user_id: %v", err) + return err + } + } + + as = append(as, a) + } + return nil + }) + if err != nil { + logger.Logger.Errorf("mysql: UserAccount failed to transaction: %v", err) + return as, err + } + return as, nil +} diff --git a/statistics/task/build_linux.bat b/statistics/task/build_linux.bat new file mode 100644 index 0000000..0cc7a25 --- /dev/null +++ b/statistics/task/build_linux.bat @@ -0,0 +1,5 @@ +set CGO_ENABLED=0 +set GOOS=linux +set GOARCH=amd64 +go build -o ./task2 +pause \ No newline at end of file diff --git a/statistics/task/etc/DB_GameFree.dat b/statistics/task/etc/DB_GameFree.dat new file mode 100644 index 0000000..9ed5700 Binary files /dev/null and b/statistics/task/etc/DB_GameFree.dat differ diff --git a/statistics/task/etc/config.yaml b/statistics/task/etc/config.yaml new file mode 100644 index 0000000..dc134c6 --- /dev/null +++ b/statistics/task/etc/config.yaml @@ -0,0 +1,221 @@ +StartTime: "2024-11-26T00:00:00+08:00" +EndTime: "2024-11-27T00:00:00+08:00" + +Switch: # 1: open, 0: close + - 0 # 新用户游戏破产率 + - 0 # 新用户平均游戏时长 + - 0 # 新用户平均局数 + - 0 # 平均倍数 + - 1 # 活跃破产率 + - 1 # 控输赢胜率 + - 1 # 机器人胜率 + - 1 # 人均获得金币 + - 1 # 破产后离线 + - 1 # 充值玩家金币余额 + +Gamefreeids: + - 2070001 + - 2070002 + - 2070003 + - 2080001 + - 2080002 + - 2080003 + - 2090001 + - 2090002 + - 2090003 + - 2100001 + - 2100002 + - 2100003 + - 2400001 + - 2400002 + - 2400003 + - 2400004 + - 2400005 + - 2400006 + - 2440001 + - 2440002 + - 2440003 + - 2440004 + - 2440005 + - 2440006 + - 2410001 + - 2410002 + - 2410003 + - 2410004 + - 2410005 + - 2410006 + - 2450001 + - 2450002 + - 2450003 + - 2450004 + - 2450005 + - 2450006 + - 2420001 + - 2420002 + - 2420003 + - 2420004 + - 2420005 + - 2420006 + - 2460001 + - 2460002 + - 2460003 + - 2460004 + - 2460005 + - 2460006 + - 2430001 + - 2430002 + - 2430003 + - 2430004 + - 2430005 + - 2430006 + - 2470001 + - 2470002 + - 2470003 + - 2470004 + - 2470005 + - 2470006 + - 2150001 + - 2160001 + - 2170001 + - 2180001 + - 5210001 + - 5210002 + - 5210003 + - 5210004 + - 5210005 + - 2110001 + - 2110002 + - 2110003 + - 2110004 + - 2110005 + - 2110006 + - 2120001 + - 2120002 + - 2120003 + - 2120004 + - 2120005 + - 2120006 + - 2130001 + - 2130002 + - 2130003 + - 2140001 + - 2140002 + - 2140003 + - 6070001 + - 3010001 + - 3010002 + - 3010003 + - 3010004 + - 3020001 + - 3020002 + - 3020003 + - 3020004 + - 3030001 + - 3030002 + - 3030003 + - 3030004 + - 3040001 + - 3040002 + - 3040003 + - 3050001 + - 3050002 + - 3050003 + - 3060001 + - 3060002 + - 3060003 + - 3060004 + - 3070001 + - 3070002 + - 3070003 + - 3070004 + - 3080001 + - 3090001 + - 3100001 + - 3110001 + - 3120001 + +Tienlen: + - 2070001 + - 2070002 + - 2070003 + - 2080001 + - 2080002 + - 2080003 + - 2090001 + - 2090002 + - 2090003 + - 2100001 + - 2100002 + - 2100003 + - 2400001 + - 2400002 + - 2400003 + - 2400004 + - 2400005 + - 2400006 + - 2440001 + - 2440002 + - 2440003 + - 2440004 + - 2440005 + - 2440006 + - 2410001 + - 2410002 + - 2410003 + - 2410004 + - 2410005 + - 2410006 + - 2450001 + - 2450002 + - 2450003 + - 2450004 + - 2450005 + - 2450006 + - 2420001 + - 2420002 + - 2420003 + - 2420004 + - 2420005 + - 2420006 + - 2460001 + - 2460002 + - 2460003 + - 2460004 + - 2460005 + - 2460006 + - 2430001 + - 2430002 + - 2430003 + - 2430004 + - 2430005 + - 2430006 + - 2470001 + - 2470002 + - 2470003 + - 2470004 + - 2470005 + - 2470006 + + +Thirteen: + - 2110001 + - 2110002 + - 2110003 + - 2110004 + - 2110005 + - 2110006 + - 2120001 + - 2120002 + - 2120003 + - 2120004 + - 2120005 + - 2120006 + - 2130001 + - 2130002 + - 2130003 + - 2140001 + - 2140002 + - 2140003 + + + diff --git a/statistics/task/etc/mongo.yaml b/statistics/task/etc/mongo.yaml new file mode 100644 index 0000000..7f9e42d --- /dev/null +++ b/statistics/task/etc/mongo.yaml @@ -0,0 +1,53 @@ +global: + user: + HostName: 127.0.0.1 + HostPort: 27017 + Database: win88_global + Username: + Password: + Options: + log: + HostName: 127.0.0.1 + HostPort: 27017 + Database: win88_log + Username: + Password: + Options: + monitor: + HostName: 127.0.0.1 + HostPort: 27017 + Database: win88_monitor + Username: + Password: + Options: +platforms: + 0: + user: + HostName: 127.0.0.1 + HostPort: 27017 + Database: win88_user_plt_000 + Username: + Password: + Options: + log: + HostName: 127.0.0.1 + HostPort: 27017 + Database: win88_log_plt_000 + Username: + Password: + Options: + 1: + user: + HostName: 127.0.0.1 + HostPort: 27017 + Database: win88_user_plt_001 + Username: + Password: + Options: + log: + HostName: 127.0.0.1 + HostPort: 27017 + Database: win88_log_plt_001 + Username: + Password: + Options: \ No newline at end of file diff --git a/statistics/task/etc/mysql.yaml b/statistics/task/etc/mysql.yaml new file mode 100644 index 0000000..82d1fe3 --- /dev/null +++ b/statistics/task/etc/mysql.yaml @@ -0,0 +1,38 @@ +platforms: + global: + HostName: 127.0.0.1 + HostPort: 3306 + Database: win88_user + Username: root + Password: 123456 + Options: charset=utf8mb4&parseTime=True&loc=Local + 0: + HostName: 127.0.0.1 + HostPort: 3306 + Database: win88_plt_000 + Username: root + Password: 123456 + Options: charset=utf8mb4&parseTime=True&loc=Local + 1: + HostName: 127.0.0.1 + HostPort: 3306 + Database: win88_plt_001 + Username: root + Password: 123456 + Options: charset=utf8mb4&parseTime=True&loc=Local + count: # 破产日志库 + HostName: 127.0.0.1 + HostPort: 3306 + Database: dbmis_count + Username: root + Password: 123456 + Options: charset=utf8mb4&parseTime=True&loc=Local + +# 最大空闲连接数 +MaxIdleConns: 10 +# 最大连接数 +MaxOpenConns: 100 +# 连接可复用的最大时间 +ConnMaxLifetime: 3600 +# 连接最大空闲时间 +ConnMaxIdletime: 0 \ No newline at end of file diff --git a/statistics/task/excelmgr.go b/statistics/task/excelmgr.go new file mode 100644 index 0000000..f324bb5 --- /dev/null +++ b/statistics/task/excelmgr.go @@ -0,0 +1,88 @@ +package main + +import ( + "fmt" + "github.com/xuri/excelize/v2" +) + +type ExcelData struct { + *excelize.File + Head []string + Index int + IndexCell int +} + +func (e *ExcelData) SetHead(head []string) *ExcelData { + if e == nil { + return nil + } + e.Index = 1 + e.SetSheetRow("Sheet1", "A1", &head) + return e +} + +func (e *ExcelData) SetRow(row []string) *ExcelData { + if e == nil { + return nil + } + e.Index++ + e.SetSheetRow("Sheet1", "A"+fmt.Sprintf("%d", e.Index), &row) + return e +} + +func (e *ExcelData) SetCell(val interface{}) *ExcelData { + if e == nil { + return nil + } + e.IndexCell++ + cell := fmt.Sprintf("%c%d", 'A'+e.IndexCell-1, e.Index) + e.SetCellValue("Sheet1", cell, val) + return e +} + +func (e *ExcelData) NewLine() *ExcelData { + if e == nil { + return nil + } + e.Index++ + e.IndexCell = 0 + return e +} + +type ExcelMgr struct { + List map[int]*ExcelData +} + +func NewExcelMgr() *ExcelMgr { + return &ExcelMgr{ + List: make(map[int]*ExcelData), + } +} + +func (e *ExcelMgr) Register(id int, head []string) *ExcelData { + e.List[id] = &ExcelData{ + File: excelize.NewFile(), + Head: head, + Index: 0, + } + + e.List[id].NewSheet("Sheet1") + + if len(head) > 0 { + e.List[id].SetHead(head) + } + + return e.List[id] +} + +func (e *ExcelMgr) Get(id int) *ExcelData { + return e.List[id] +} + +func (e *ExcelMgr) Save(id int, fileName string) error { + d := e.List[id] + if d == nil { + return nil + } + return d.SaveAs(fileName) +} diff --git a/statistics/task/gamefree/db_gamefree.go b/statistics/task/gamefree/db_gamefree.go new file mode 100644 index 0000000..81d8b55 --- /dev/null +++ b/statistics/task/gamefree/db_gamefree.go @@ -0,0 +1,77 @@ +package gamefree + +import ( + "google.golang.org/protobuf/proto" + "mongo.games.com/game/protocol/server" + "os" +) + +func init() { + buf, err := os.ReadFile("./etc/DB_GameFree.dat") + if err != nil { + panic(err) + } + err = PBDB_GameFreeMgr.unmarshal(buf) + if err != nil { + panic(err) + } +} + +var PBDB_GameFreeMgr = &DB_GameFreeMgr{ + Datas: &server.DB_GameFreeArray{}, + pool: make(map[int32]*server.DB_GameFree), +} + +type DB_GameFreeMgr struct { + Datas *server.DB_GameFreeArray + pool map[int32]*server.DB_GameFree +} + +func (this *DB_GameFreeMgr) unmarshal(data []byte) error { + err := proto.Unmarshal(data, this.Datas) + if err == nil { + this.arrangeData() + } + return err +} + +func (this *DB_GameFreeMgr) reunmarshal(data []byte) error { + newDatas := &server.DB_GameFreeArray{} + err := proto.Unmarshal(data, newDatas) + if err == nil { + for _, item := range newDatas.Arr { + existItem := this.GetData(item.GetId()) + if existItem == nil { + this.pool[item.GetId()] = item + this.Datas.Arr = append(this.Datas.Arr, item) + + } else { + *existItem = *item + } + } + } + return err +} + +func (this *DB_GameFreeMgr) arrangeData() { + if this.Datas == nil { + return + } + + dataArr := this.Datas.GetArr() + if dataArr == nil { + return + } + + for _, data := range dataArr { + this.pool[data.GetId()] = data + + } +} + +func (this *DB_GameFreeMgr) GetData(id int32) *server.DB_GameFree { + if data, ok := this.pool[id]; ok { + return data + } + return nil +} diff --git a/statistics/task/logger.xml b/statistics/task/logger.xml new file mode 100644 index 0000000..f6eb37b --- /dev/null +++ b/statistics/task/logger.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/statistics/task/main.go b/statistics/task/main.go new file mode 100644 index 0000000..a582273 --- /dev/null +++ b/statistics/task/main.go @@ -0,0 +1,427 @@ +package main + +import ( + "fmt" + "sort" + "time" + + "github.com/spf13/viper" + "mongo.games.com/goserver/core/logger" + "mongo.games.com/goserver/core/mongox" + "mongo.games.com/goserver/core/mysqlx" + "mongo.games.com/goserver/core/viperx" + + "mongo.games.com/game/statistics/task/gamefree" + "mongo.games.com/game/statistics/task/task" +) + +const ( + ExcelTypeNewPlayerBankrupt = iota // 新用户游戏破产率 + ExcelTypeGameTimeAvg // 新用户平均游戏时长 + ExcelTypeGameCountAvg // 新用户平均局数 + ExcelTypeGameRate // 平均倍数 + ExcelTypeActiveRate // 活跃破产率 + ExcelTypeCtrlWinRate // 控输赢胜率 + ExcelTypeRobotWinRate // 机器人胜率 + ExcelTypeCoinAvg // 人均获得金币 + ExcelTypeBankruptOffline // 破产后离线 + ExcelTypeOfflineCoin // 充值当天最后金币 +) + +var VP *viper.Viper + +func main() { + defer func() { + logger.Logger.Flush() + logger.Logger.Close() + }() + VP = viperx.GetViper("config", "yaml") + // mongo + vp := viperx.GetViper("mongo", "yaml") + // mongo初始化 + conf := &mongox.Config{} + err := vp.Unmarshal(conf) + if err != nil { + panic(fmt.Errorf("mongo config error: %v", err)) + } + mongox.Init(conf) + defer mongox.Close() + + // mysql + vp = viperx.GetViper("mysql", "yaml") + myConf := &mysqlx.Config{} + err = vp.Unmarshal(myConf) + if err != nil { + panic(fmt.Errorf("mysql config error: %v", err)) + } + mysqlx.Init(myConf) + defer mysqlx.Close() + + startTime, err := time.Parse(time.RFC3339, VP.GetString("StartTime")) + if err != nil { + panic(fmt.Sprintf("time.Parse err: %v", err)) + return + } + endTime, err := time.Parse(time.RFC3339, VP.GetString("EndTime")) + if err != nil { + panic(fmt.Sprintf("time.Parse err: %v", err)) + return + } + + mgr := NewExcelMgr() + mgr.Register(ExcelTypeNewPlayerBankrupt, []string{"日期", "场次id", "破产人数", "参与人数", "破产率"}) + mgr.Register(ExcelTypeGameTimeAvg, []string{"日期", "场次id", "参与人数", "平均游戏时长"}) + mgr.Register(ExcelTypeGameCountAvg, []string{"日期", "场次id", "参与人数", "平均局数"}) + mgr.Register(ExcelTypeGameRate, []string{"日期", "场次id", "总局数", "平均倍数", "有炸弹分局数", "炸弹分平均倍数", "2留在手里的局数"}) + mgr.Register(ExcelTypeActiveRate, []string{"日期", "场次id", "破产人数", "参与人数", "破产率"}) + mgr.Register(ExcelTypeCtrlWinRate, []string{"日期", "场次id", "总局数", "平均倍数", "有炸弹分局数", "炸弹分平均倍数", "2留在手里的局数"}) + mgr.Register(ExcelTypeCtrlWinRate*10, []string{"日期", "场次id", "总局数", "平均倍数", "有炸弹分局数", "炸弹分平均倍数", "2留在手里的局数"}) + mgr.Register(ExcelTypeRobotWinRate, []string{"日期", "场次id", "总局数", "平均倍数", "有炸弹分局数", "炸弹分平均倍数", "2留在手里的局数"}) + mgr.Register(ExcelTypeCoinAvg, []string{"日期", "场次id", "参与人数", "人均获得金币"}) + mgr.Register(ExcelTypeBankruptOffline, []string{"日期", "场次id", "破产人数", "参与人数", "破产率"}) + mgr.Register(ExcelTypeOfflineCoin, []string{"日期", "场次id", "破产人数", "参与人数", "破产率"}) + switchArr := VP.GetIntSlice("Switch") + + for { + if startTime.Unix() >= endTime.Unix() { + break + } + startTimeStr := startTime.Format(time.RFC3339) + et := startTime.AddDate(0, 0, 1) + endTimeStr := et.Format(time.RFC3339) + logger.Logger.Infof("startTime: %v endTime: %v", startTimeStr, endTimeStr) + + if switchArr[ExcelTypeNewPlayerBankrupt] == 1 { + // 新用户游戏破产率 + mgr.GenNewPlayerBankruptRateExcel("1", startTimeStr, endTimeStr) + } + if switchArr[ExcelTypeGameTimeAvg] == 1 { + // 新用户平均游戏时长 + mgr.GenNewPlayerGameTimeAvgExcel("1", startTimeStr, endTimeStr) + } + if switchArr[ExcelTypeGameCountAvg] == 1 { + // 新用户平均局数 + mgr.GenGameCountExcel("1", startTimeStr, endTimeStr) + } + if switchArr[ExcelTypeGameRate] == 1 { + // 平均倍数 + mgr.GenGameRateExcel("1", startTimeStr, endTimeStr) + } + if switchArr[ExcelTypeActiveRate] == 1 { + // 活跃破产率 + mgr.GenActiveBankruptRateExcel("1", startTimeStr, endTimeStr) + } + if switchArr[ExcelTypeCtrlWinRate] == 1 { + // 控赢胜率 + mgr.GenCtrlWinRateExcel("1", startTimeStr, endTimeStr) + } + if switchArr[ExcelTypeRobotWinRate] == 1 { + // 机器人胜率 + mgr.GenRobotWinRateExcel("1", startTimeStr, endTimeStr) + } + if switchArr[ExcelTypeCoinAvg] == 1 { + // 人均获得金币 + mgr.GenCoinAvgExcel("1", startTimeStr, endTimeStr) + } + if switchArr[ExcelTypeBankruptOffline] == 1 { + // 破产后离线 + mgr.GenBankruptOfflineExcel("1", startTimeStr, endTimeStr) + } + if switchArr[ExcelTypeOfflineCoin] == 1 { + // 离线金币 + mgr.GenOfflineCoinExcel("1", startTimeStr, endTimeStr) + } + + startTime = et + } + + mgr.SaveAll(VP.GetString("StartTime")[:10], endTime.AddDate(0, 0, -1).Format(time.DateOnly)) +} + +func (e *ExcelMgr) SaveAll(startTime, endTime string) { + switchArr := VP.GetIntSlice("Switch") + if switchArr[ExcelTypeNewPlayerBankrupt] == 1 { + e.Save(ExcelTypeNewPlayerBankrupt, fmt.Sprintf("新用户破产率_%s_%s.xlsx", startTime, endTime)) + } + if switchArr[ExcelTypeGameTimeAvg] == 1 { + e.Save(ExcelTypeGameTimeAvg, fmt.Sprintf("新用户平局游戏时长_%s_%s.xlsx", startTime, endTime)) + } + if switchArr[ExcelTypeGameCountAvg] == 1 { + e.Save(ExcelTypeGameCountAvg, fmt.Sprintf("新用户平均局数_%s_%s.xlsx", startTime, endTime)) + } + if switchArr[ExcelTypeGameRate] == 1 { + e.Save(ExcelTypeGameRate, fmt.Sprintf("平均倍数_%s_%s.xlsx", startTime, endTime)) + } + if switchArr[ExcelTypeActiveRate] == 1 { + e.Save(ExcelTypeActiveRate, fmt.Sprintf("活跃破产率_%s_%s.xlsx", startTime, endTime)) + } + if switchArr[ExcelTypeCtrlWinRate] == 1 { + e.Save(ExcelTypeCtrlWinRate, fmt.Sprintf("控赢胜率_%s_%s.xlsx", startTime, endTime)) + e.Save(ExcelTypeCtrlWinRate*10, fmt.Sprintf("控输胜率_%s_%s.xlsx", startTime, endTime)) + } + if switchArr[ExcelTypeRobotWinRate] == 1 { + e.Save(ExcelTypeRobotWinRate, fmt.Sprintf("机器人输率_%s_%s.xlsx", startTime, endTime)) + } + if switchArr[ExcelTypeCoinAvg] == 1 { + e.Save(ExcelTypeCoinAvg, fmt.Sprintf("人均获得金币_%s_%s.xlsx", startTime, endTime)) + } + if switchArr[ExcelTypeBankruptOffline] == 1 { + e.Save(ExcelTypeBankruptOffline, fmt.Sprintf("破产后离线_%s_%s.xlsx", startTime, endTime)) + } + if switchArr[ExcelTypeOfflineCoin] == 1 { + e.Save(ExcelTypeOfflineCoin, fmt.Sprintf("离线金币_%s_%s.xlsx", startTime, endTime)) + } +} + +func GetGameFreeName(id int) string { + d := gamefree.PBDB_GameFreeMgr.GetData(int32(id)) + return fmt.Sprintf("%s_%s", d.Name, d.Title) +} + +func (e *ExcelMgr) GenNewPlayerBankruptRateExcel(plt string, startTime, endTime string) { + for _, v := range VP.GetIntSlice("Gamefreeids") { + _, a, b, err := task.NewPlayerBankruptRate(plt, startTime, endTime, v) + if err != nil { + logger.Logger.Errorf("NewPlayerBankruptRate get StartTime:%v EndTime:%v GameFreeId:%v err: %v", startTime, endTime, v, err) + continue + } + ex := e.Get(ExcelTypeNewPlayerBankrupt) + ex.NewLine() + ex.SetCell(startTime[:10]) + ex.SetCell(GetGameFreeName(v)) + ex.SetCell(a) + ex.SetCell(b) + if b > 0 { + ex.SetCell(float64(a) / float64(b)) + } else { + ex.SetCell(0) + } + logger.Logger.Tracef("NewPlayerBankruptRate GameFreeId: %v rate: %v", v, float64(a)/float64(b)) + } +} + +func (e *ExcelMgr) GenNewPlayerGameTimeAvgExcel(plt string, startTime, endTime string) { + for _, v := range VP.GetIntSlice("Gamefreeids") { + a, b, err := task.NewPlayerGameTimeAvg(plt, startTime, endTime, v) + if err != nil { + logger.Logger.Errorf("NewPlayerGameTimeAvg get StartTime:%v EndTime:%v GameFreeId:%v err: %v", startTime, endTime, v, err) + continue + } + ex := e.Get(ExcelTypeGameTimeAvg) + ex.NewLine() + ex.SetCell(startTime[:10]) + ex.SetCell(GetGameFreeName(v)) + ex.SetCell(a) + if a > 0 { + avg := float64(b) / float64(a) + show := fmt.Sprintf("%v", time.Second*time.Duration(avg)) + ex.SetCell(show) + } else { + ex.SetCell(0) + } + logger.Logger.Tracef("NewPlayerGameTimeAvg GameFreeId: %v avg: %v", v, float64(b)/float64(a)) + } +} + +func (e *ExcelMgr) GenGameCountExcel(plt string, startTime, endTime string) { + for _, v := range VP.GetIntSlice("Gamefreeids") { + a, b, err := task.NewPlayerGameCountAvg(plt, startTime, endTime, v) + if err != nil { + logger.Logger.Errorf("NewPlayerGameCountAvg get StartTime:%v EndTime:%v GameFreeId:%v err: %v", startTime, endTime, v, err) + continue + } + ex := e.Get(ExcelTypeGameCountAvg) + ex.NewLine() + ex.SetCell(startTime[:10]) + ex.SetCell(GetGameFreeName(v)) + ex.SetCell(a) + if a > 0 { + ex.SetCell(float64(b) / float64(a)) + } else { + ex.SetCell(0) + } + logger.Logger.Tracef("NewPlayerGameCountAvg GameFreeId: %v avg: %v", v, float64(b)/float64(a)) + } +} + +func (e *ExcelMgr) GenGameRateExcel(plt string, startTime, endTime string) { + for _, v := range VP.GetIntSlice("Gamefreeids") { + total, bombTotal, remain2Total, rateAvg, bombRateAvg, err := task.PlayerGameRate(plt, startTime, endTime, v) + if err != nil { + logger.Logger.Errorf("PlayerGameRate get StartTime:%v EndTime:%v GameFreeId:%v err: %v", startTime, endTime, v, err) + continue + } + ex := e.Get(ExcelTypeGameRate) + ex.NewLine() + ex.SetCell(startTime[:10]) + ex.SetCell(GetGameFreeName(v)) + ex.SetCell(total) + ex.SetCell(rateAvg) + ex.SetCell(bombTotal) + ex.SetCell(bombRateAvg) + ex.SetCell(remain2Total) + logger.Logger.Tracef("PlayerGameRate GameFreeId: %v total: %v rateAvg: %v bombTotal: %v bombRateAvg: %v remain2Total: %v", + v, total, rateAvg, bombTotal, bombRateAvg, remain2Total) + } +} + +func (e *ExcelMgr) GenActiveBankruptRateExcel(plt string, startTime, endTime string) { + for _, v := range VP.GetIntSlice("Gamefreeids") { + b, t, err := task.ActivePlayerBankruptRate(plt, startTime, endTime, v) + if err != nil { + logger.Logger.Errorf("ActivePlayerBankruptRate get StartTime:%v EndTime:%v GameFreeId:%v err: %v", startTime, endTime, v, err) + continue + } + + ex := e.Get(ExcelTypeActiveRate) + ex.NewLine() + ex.SetCell(startTime[:10]) + ex.SetCell(GetGameFreeName(v)) + ex.SetCell(b) + ex.SetCell(t) + if t > 0 { + if b > t { + b = t + } + ex.SetCell(float64(b) / float64(t)) + } else { + ex.SetCell(0) + } + logger.Logger.Tracef("ActivePlayerBankruptRate GameFreeId: %v rate: %v", v, float64(b)/float64(t)) + } +} + +func (e *ExcelMgr) GenCtrlWinRateExcel(plt string, startTime, endTime string) { + for _, v := range append(VP.GetIntSlice("Tienlen")) { + ret, err := task.GameDetailWinRate(plt, startTime, endTime, v) + if err != nil { + logger.Logger.Errorf("GameDetailWinRate get StartTime:%v EndTime:%v GameFreeId:%v err: %v", startTime, endTime, v, err) + continue + } + if len(ret) > 0 { + ex := e.Get(ExcelTypeCtrlWinRate) + ex.NewLine() + ex.SetCell(startTime[:10]) + ex.SetCell(GetGameFreeName(v)) + ex.SetCell(ret[0].First) + ex.SetCell(ret[0].Second) + ex.SetCell(ret[0].Third) + ex.SetCell(ret[0].Total) + if ret[0].Total > 0 { + ex.SetCell(float64(ret[0].First+ret[0].Second) / float64(ret[0].Total)) + } else { + ex.SetCell(0) + } + logger.Logger.Tracef("GameDetailWinRate GameFreeId:%v %+v", v, ret[0]) + } + if len(ret) > 1 { + ex := e.Get(ExcelTypeCtrlWinRate * 10) + ex.NewLine() + ex.SetCell(startTime[:10]) + ex.SetCell(GetGameFreeName(v)) + ex.SetCell(ret[1].First) + ex.SetCell(ret[1].Second) + ex.SetCell(ret[1].Third) + ex.SetCell(ret[1].Total) + if ret[1].Total > 0 { + ex.SetCell(float64(ret[1].First+ret[1].Second) / float64(ret[1].Total)) + } else { + ex.SetCell(0) + } + logger.Logger.Tracef("GameDetailWinRate GameFreeId:%v %+v", v, ret[1]) + } + } +} + +func (e *ExcelMgr) GenRobotWinRateExcel(plt string, startTime, endTime string) { + for _, v := range append(VP.GetIntSlice("Tienlen"), VP.GetIntSlice("Thirteen")...) { + a, b, err := task.RobotWinRate(plt, startTime, endTime, v) + if err != nil { + logger.Logger.Errorf("RobotWinRate get StartTime:%v EndTime:%v GameFreeId:%v err: %v", startTime, endTime, v, err) + continue + } + + ex := e.Get(ExcelTypeRobotWinRate) + ex.NewLine() + ex.SetCell(startTime[:10]) + ex.SetCell(GetGameFreeName(v)) + ex.SetCell(a) + ex.SetCell(b) + if b > 0 { + ex.SetCell(float64(b-a) / float64(b)) + } else { + ex.SetCell(0) + } + logger.Logger.Tracef("RobotWinRate GameFreeId: %v rate: %v", v, float64(a)/float64(b)) + } +} + +func (e *ExcelMgr) GenCoinAvgExcel(plt string, startTime, endTime string) { + type KV struct { + K int + V string + } + + var list []KV + for k, v := range task.CoinName { + list = append(list, KV{K: k, V: v}) + } + + sort.Slice(list, func(i, j int) bool { + return list[i].K < list[j].K + }) + + for _, item := range list { + k, v := item.K, item.V + a, b, err := task.CoinAvg(plt, startTime, endTime, k) + if err != nil { + logger.Logger.Errorf("CoinAvg get StartTime:%v EndTime:%v tp:%v err: %v", startTime, endTime, k, err) + continue + } + ex := e.Get(ExcelTypeCoinAvg) + ex.NewLine() + ex.SetCell(startTime[:10]) + ex.SetCell(v) + ex.SetCell(a) + ex.SetCell(b) + if a > 0 { + ex.SetCell(float64(b) / float64(a)) + } else { + ex.SetCell(0) + } + logger.Logger.Tracef("CoinAvg tp: %v rate: %v", k, float64(b)/float64(a)) + } +} + +func (e *ExcelMgr) GenBankruptOfflineExcel(plt string, startTime, endTime string) { + res, err := task.BankruptOffline(plt, startTime, endTime) + if err != nil { + logger.Logger.Errorf("BankruptOffline get StartTime:%v EndTime:%v err: %v", startTime, endTime, err) + return + } + ex := e.Get(ExcelTypeBankruptOffline) + ex.NewLine() + ex.SetCell(startTime[:10]) + ex.SetCell(res.One) + ex.SetCell(res.Two) + ex.SetCell(res.Three) + ex.SetCell(res.Recharge) + ex.SetCell(fmt.Sprintf("%v", res.D)) + logger.Logger.Tracef("BankruptOffline %+v", res) +} + +func (e *ExcelMgr) GenOfflineCoinExcel(plt string, startTime, endTime string) { + res, err := task.OfflineCoin(plt, startTime, endTime) + if err != nil { + logger.Logger.Errorf("OfflineCoin get StartTime:%v EndTime:%v err: %v", startTime, endTime, err) + return + } + for _, v := range res { + ex := e.Get(ExcelTypeOfflineCoin) + ex.NewLine() + ex.SetCell(startTime[:10]) + ex.SetCell(v.Snid) + ex.SetCell(v.Coin) + } + logger.Logger.Tracef("OfflineCoin %+v", res) +} diff --git a/statistics/task/readme b/statistics/task/readme new file mode 100644 index 0000000..5fdb673 --- /dev/null +++ b/statistics/task/readme @@ -0,0 +1 @@ +查询历史数据 \ No newline at end of file diff --git a/statistics/task/task/bankruptcy.go b/statistics/task/task/bankruptcy.go new file mode 100644 index 0000000..ea4212d --- /dev/null +++ b/statistics/task/task/bankruptcy.go @@ -0,0 +1,189 @@ +package task + +import ( + "context" + "errors" + "fmt" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + "mongo.games.com/goserver/core/logger" + mymongo "mongo.games.com/goserver/core/mongox" + mymysql "mongo.games.com/goserver/core/mysqlx" + + "mongo.games.com/game/common" + mongomodel "mongo.games.com/game/statistics/modelmongo" +) + +// 新用户id +func GetNewPayerIds(plt string, startTime, endTime string) ([]int, error) { + s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime) + c, err := mymongo.GetUserCollection(plt, mongomodel.UserAccount) + if err != nil { + return nil, err + } + + var res []struct{ Snid int } + dd, err := c.Find(context.TODO(), bson.M{"registetime": bson.M{"$gte": s, "$lt": e}}, options.Find().SetProjection(bson.M{"snid": 1})) + if err != nil { + if errors.Is(err, mongo.ErrNoDocuments) { + return nil, nil + } + logger.Logger.Errorf("find new player snid get err: %v", err) + return nil, err + } + if err := dd.All(context.TODO(), &res); err != nil { + logger.Logger.Errorf("find new player snid decode err: %v", err) + return nil, err + } + var ret []int + for _, v := range res { + ret = append(ret, v.Snid) + } + logger.Logger.Tracef("find new player snid: %v", ret) + return ret, nil +} + +// 场次破产总人数 +func GameFreeIdBankruptPlayerCount(plt string, ids []int, startTime, endTime string, gamefreeid int) (int, error) { + s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime) + db, err := mymysql.GetDatabase("count") + if err != nil { + return 0, err + } + var ret int + for _, v := range ids { + var n int64 + if err = db.Table("bankrupt_log").Where("snid = ? AND bankrupt_time >= ? AND bankrupt_time < ? AND game_free_id = ?", + v, s.Unix(), e.Unix(), gamefreeid).Count(&n).Error; err != nil { + logger.Logger.Errorf("find bankrupt player count get err: %v", err) + return 0, err + } + if n > 0 { + ret++ + } + } + return ret, nil +} + +// 场次参与总人数 +func PlayingGameCount(plt string, ids []int, startTime, endTime string, gamefreeid int) (int, error) { + s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime) + c, err := mymongo.GetLogCollection(plt, mongomodel.LogGamePlayerListLog) + if err != nil { + return 0, err + } + + var ret int + for _, v := range ids { + // 参与过游戏 + n, err := c.CountDocuments(context.TODO(), bson.M{"snid": v, "time": bson.M{"$gte": s, "$lt": e}, "gamefreeid": gamefreeid}) + if err != nil { + logger.Logger.Errorf("find playing game count get err: %v", err) + return 0, err + } + if n > 0 { + ret++ + } + } + return ret, nil +} + +// NewPlayerBankruptRate 新用户破产率 +// 新用户游戏破产率 = 新用户游戏破产玩家数量/新用户游戏参与总人数;破产数量,每个玩家每个游戏破产只统计一次;参与人数,每个玩家每个游戏只统计一次; +// 返回 新用户数量,破产人数,参与人数 +func NewPlayerBankruptRate(plt string, startTime, endTime string, gamefreeid int) (n, a, b int, err error) { + s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime) + if s.IsZero() || e.IsZero() { + return 0, 0, 0, fmt.Errorf("time format error") + } + ids, err := GetNewPayerIds(plt, startTime, endTime) + if err != nil { + return 0, 0, 0, err + } + + a, err = GameFreeIdBankruptPlayerCount(plt, ids, startTime, endTime, gamefreeid) + if err != nil { + return 0, 0, 0, err + } + b, err = PlayingGameCount(plt, ids, startTime, endTime, gamefreeid) + if err != nil { + return 0, 0, 0, err + } + if b == 0 { + return 0, 0, 0, nil + } + return +} + +// ActivePlayerCount 活跃玩家游戏总人数 +func ActivePlayerCount(plt string, startTime, endTime string, gamefreeid int) (int, error) { + s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime) + c, err := mymongo.GetLogCollection(plt, mongomodel.LogGamePlayerListLog) + if err != nil { + return 0, err + } + + var count []struct { + Count int + } + cur, err := c.Aggregate(context.TODO(), bson.A{ + bson.M{"$match": bson.M{"time": bson.M{"$gte": s, "$lt": e}, "gamefreeid": gamefreeid}}, + bson.M{"$group": bson.M{"_id": "$snid"}}, + bson.M{"$count": "count"}, + }) + if err != nil { + logger.Logger.Errorf("find active player count get err: %v", err) + return 0, err + } + if err := cur.All(context.TODO(), &count); err != nil { + logger.Logger.Errorf("find active player count decode err: %v", err) + return 0, err + } + + if len(count) == 0 { + return 0, nil + } + + return count[0].Count, nil +} + +// ActivePlayerBankruptCount 活跃玩家破产总人数 +func ActivePlayerBankruptCount(plt string, startTime, endTime string, gamefreeid int) (int, error) { + s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime) + db, err := mymysql.GetDatabase("count") + if err != nil { + return 0, err + } + var n int64 + if err = db.Table("bankrupt_log").Where("bankrupt_time >= ? AND bankrupt_time < ? AND game_free_id = ?", + s.Unix(), e.Unix(), gamefreeid).Group("snid").Count(&n).Error; err != nil { + logger.Logger.Errorf("find bankrupt count get err: %v", err) + return 0, err + } + return int(n), nil +} + +// ActivePlayerBankruptRate 活跃玩家破产率 +// 活跃玩家游戏破产率 = 活跃玩家游戏破产玩家数量/活跃玩家游戏总人数;破产数量,每个玩家每个游戏破产只统计一次;参与人数,每个玩家每个游戏只统计一次; +// 返回 破产人数,参与人数 +func ActivePlayerBankruptRate(plt string, startTime, endTime string, gamefreeid int) (a, b int, err error) { + s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime) + if s.IsZero() || e.IsZero() { + return 0, 0, fmt.Errorf("time format error") + } + + a, err = ActivePlayerBankruptCount(plt, startTime, endTime, gamefreeid) + if err != nil { + return 0, 0, err + } + b, err = ActivePlayerCount(plt, startTime, endTime, gamefreeid) + if err != nil { + return 0, 0, err + } + if b == 0 { + return 0, 0, nil + } + return +} diff --git a/statistics/task/task/bankruptoffline.go b/statistics/task/task/bankruptoffline.go new file mode 100644 index 0000000..c6f71c1 --- /dev/null +++ b/statistics/task/task/bankruptoffline.go @@ -0,0 +1,153 @@ +package task + +import ( + "context" + "fmt" + "strconv" + "strings" + "time" + + "go.mongodb.org/mongo-driver/bson" + "mongo.games.com/game/common" + mymongo "mongo.games.com/goserver/core/mongox" + mymysql "mongo.games.com/goserver/core/mysqlx" +) + +// 破产后离线;破产后5分钟内有离线行为 + +type BankruptOfflineData struct { + One int + Two int + Three int + Recharge int + D time.Duration +} + +func BankruptOffline(plt string, startTime, endTime string) (ret *BankruptOfflineData, err error) { + s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime) + if s.IsZero() || e.IsZero() { + return nil, fmt.Errorf("time format error") + } + db, err := mymysql.GetDatabase("count") + if err != nil { + return nil, err + } + + /* + SELECT + snid, + GROUP_CONCAT(bankrupt_time ORDER BY bankrupt_time ASC LIMIT 3) AS top_3_bankrupt_times, + COUNT(*) AS record_count + FROM + bankrupt_log + GROUP BY + snid; + */ + + type BankruptLog struct { + Snid int `gorm:"column:snid"` + MaxBankruptTime string `gorm:"column:top_3_bankrupt_times"` + Times []int64 `gorm:"-"` + } + + var logs []*BankruptLog + tx := db.Raw(` + WITH RankedData AS ( + SELECT + snid, + bankrupt_time, + ROW_NUMBER() OVER (PARTITION BY snid ORDER BY bankrupt_time ASC) AS ranks + FROM bankrupt_log + WHERE bankrupt_time >= ? AND bankrupt_time < ? + ) + SELECT + snid, + GROUP_CONCAT(bankrupt_time ORDER BY bankrupt_time ASC) AS top_3_bankrupt_times + FROM RankedData + WHERE ranks <= 3 + GROUP BY snid +`, s.Unix(), e.Unix()).Scan(&logs) + + if tx.Error != nil { + return nil, tx.Error + } + + var timeSecond int + var total int + + ret = &BankruptOfflineData{} + for _, v := range logs { + if v == nil || len(v.MaxBankruptTime) == 0 { + continue + } + for _, vv := range strings.Split(v.MaxBankruptTime, ",") { + n, err := strconv.Atoi(vv) + if err != nil { + return nil, err + } + v.Times = append(v.Times, int64(n)) + } + + // 破产后5分钟内有离线行为 + db, err = mymysql.GetDatabase(plt) + if err != nil { + return nil, err + } + + var isOffline bool + for k, vv := range v.Times { + var n int64 + stime, etime := time.Unix(vv, 0), time.Unix(vv, 0).Add(5*time.Minute) + if err = db.Table("user_logins").Where("snid = ? AND offline_time >= ? AND offline_time < ?", + v.Snid, stime, etime).Count(&n).Error; err != nil { + return nil, err + } + switch k { + case 0: + if n > 0 { + ret.One++ + isOffline = true + } + case 1: + if n > 0 { + ret.Two++ + isOffline = true + } + case 2: + if n > 0 { + ret.Three++ + isOffline = true + } + } + } + + if isOffline { + // 充值 + c, err := mymongo.GetLogCollection(plt, "log_dbshop") + if err != nil { + return nil, err + } + count, err := c.CountDocuments(context.TODO(), bson.M{"snid": v.Snid, "ts": bson.M{"$gte": s.Unix(), "$lt": e.Unix()}, "consume": 3, "state": 1}) + if err != nil { + return nil, err + } + if count > 0 { + ret.Recharge++ + } + + // 平均对局时长 + times, to, err := NewPlayerGameTime(plt, []int{v.Snid}, startTime, endTime, 0) + if err != nil { + return nil, err + } + timeSecond += times + total += to + } + } + + if total > 0 { + ret.D = time.Second * time.Duration(timeSecond/total) + } + + return ret, nil +} diff --git a/statistics/task/task/coin.go b/statistics/task/task/coin.go new file mode 100644 index 0000000..10747e6 --- /dev/null +++ b/statistics/task/task/coin.go @@ -0,0 +1,77 @@ +package task + +import ( + "context" + "fmt" + "go.mongodb.org/mongo-driver/bson" + "mongo.games.com/game/common" + "mongo.games.com/goserver/core/mongox" +) + +var CoinName = map[int]string{ + 44: "转盘", + 57: "轮盘视频", + 43: "签到", + 58: "签到视频", + 83: "累计签到", + 91: "累计签到进阶奖励", + 47: "vip每日礼包", + 105: "vip等级礼包", + 74: "集卡活动", + 67: "金币存钱罐", + 94: "赛季通行证等级奖励", + 95: "赛季通行证排行奖励", + 52: "排位赛段位奖励", + 65: "周卡奖励", + 5: "兑换", + 49: "礼包码兑换", + 38: "救济金", + 56: "救济金视频", +} + +func CoinHistory(plt string, startTime, endTime string, tp int, f func(data bson.M) error) error { + s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime) + if s.IsZero() || e.IsZero() { + return fmt.Errorf("time format error") + } + c, err := mongox.GetLogCollection(plt, "log_coinex") + if err != nil { + return err + } + + cur, err := c.Find(context.TODO(), bson.M{"time": bson.M{"$gte": s, "$lt": e}, "logtype": tp, "cointype": 0}) + if err != nil { + return err + } + defer cur.Close(context.Background()) + + for cur.TryNext(context.Background()) { + data := bson.M{} + if err := cur.Decode(data); err != nil { + return err + } + if err := f(data); err != nil { + return err + } + } + + return nil +} + +func CoinAvg(plt string, startTime, endTime string, tp int) (playerNumber int, total int, err error) { + player := make(map[int32]struct{}) + err = CoinHistory(plt, startTime, endTime, tp, func(data bson.M) error { + snid := data["snid"].(int32) + count := int(data["count"].(int64)) + player[snid] = struct{}{} + if count > 0 { + total += count + } + return nil + }) + if err != nil { + return + } + playerNumber = len(player) + return +} diff --git a/statistics/task/task/ctrlrate.go b/statistics/task/task/ctrlrate.go new file mode 100644 index 0000000..25eaf26 --- /dev/null +++ b/statistics/task/task/ctrlrate.go @@ -0,0 +1,214 @@ +package task + +import ( + "context" + "encoding/json" + "fmt" + "sort" + + "go.mongodb.org/mongo-driver/bson" + "mongo.games.com/goserver/core/logger" + "mongo.games.com/goserver/core/mongox" + + "mongo.games.com/game/common" +) + +func GameDetailFunc(plt string, startTime, endTime string, gamefreeid int, f func(data bson.M) error) error { + s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime) + if s.IsZero() || e.IsZero() { + return fmt.Errorf("time format error") + } + + c, err := mongox.GetLogCollection(plt, "log_gamedetailed") + if err != nil { + return err + } + + cur, err := c.Find(context.TODO(), bson.M{"time": bson.M{"$gte": s, "$lt": e}, "gamefreeid": gamefreeid}) + if err != nil { + return err + } + defer cur.Close(context.Background()) + + for cur.TryNext(context.Background()) { + data := bson.M{} + if err := cur.Decode(data); err != nil { + return err + } + if err := f(data); err != nil { + return err + } + } + + return nil +} + +type GameDetailWinRateRet struct { + First int + Second int + Third int + Total int + IsWin bool +} + +func GameDetailWinRate(plt string, startTime, endTime string, gamefreeid int) (ret []GameDetailWinRateRet, err error) { + ret = make([]GameDetailWinRateRet, 2) + err = GameDetailFunc(plt, startTime, endTime, gamefreeid, func(data bson.M) error { + ff, ss, tt, to, ct := GameDetailCount(data) + if ct == 1 && to > 0 { + ret[0].First += ff + ret[0].Second += ss + ret[0].Third += tt + ret[0].Total += to + } + if ct == 2 && to > 0 { + ret[1].First += ff + ret[1].Second += ss + ret[1].Third += tt + ret[1].Total += to + } + return nil + }) + return +} + +type RabbitMQDataRaw struct { + Source int32 + Data interface{} +} + +func GameDetailCount(data bson.M) (first, second, third, to int, ctrlType int) { + if data == nil { + return + } + gameid := data["gameid"].(int32) + gamefreeid := data["gamefreeid"].(int32) + ctrlType = int(data["ctrltype"].(int32)) + logger.Logger.Tracef("GameDetail gameid:%d, gamefreeid:%d", gameid, gamefreeid) + + if ctrlType != 1 && ctrlType != 2 { + return + } + + detail := data["gamedetailednote"] + if detail == nil { + return + } + + raw := new(RabbitMQDataRaw) + if err := json.Unmarshal([]byte(detail.(string)), raw); err != nil { + logger.Logger.Errorf("GameDetailCount Unmarshal 1 error:%v %v", err, gameid) + return + } + + switch gameid { + case 207, 208, 209, 210, 240, 241, 242, 243, 244, 245, 246, 247: // tienlen + data := new(TienLenType) + b, err := json.Marshal(raw.Data) + if err != nil { + logger.Logger.Errorf("GameDetailCount Marshal error:%v %v", err, gameid) + return + } + if err := json.Unmarshal(b, data); err != nil { + logger.Logger.Errorf("GameDetailCount Unmarshal 2 error:%v %v", err, gameid) + return + } + + var has, hasPlayer bool + for _, v := range data.PlayerData { + if v.IsRob { + has = true + } + if !v.IsRob { + hasPlayer = true + } + } + if !has || !hasPlayer { + // 没有机器人 + return 0, 0, 0, 0, ctrlType + } + + sort.Slice(data.PlayerData, func(i, j int) bool { + a, b := data.PlayerData[i].BillCoin+data.PlayerData[i].BillTaxCoin, data.PlayerData[j].BillCoin+data.PlayerData[j].BillTaxCoin + if a != b { + return a > b + } + if data.PlayerData[i].IsRob != data.PlayerData[j].IsRob { + return !data.PlayerData[i].IsRob + } + return data.PlayerData[i].UserId < data.PlayerData[j].UserId + }) + + for k, v := range data.PlayerData { + if !v.IsRob && v.BillCoin > 0 && k <= 1 { + if k == 0 { + first = 1 + } + if k == 1 { + second = 1 + } + break + } + } + if len(data.PlayerData) > 2 && first+second == 0 && !data.PlayerData[2].IsRob { + third = 1 + } + to = 1 + + case 211, 212, 213, 214: + data := new(ThirteenWaterType) + b, err := json.Marshal(raw.Data) + if err != nil { + logger.Logger.Errorf("GameDetailCount Marshal error:%v %v", err, gameid) + return + } + if err := json.Unmarshal(b, data); err != nil { + logger.Logger.Errorf("GameDetailCount Unmarshal 2 error:%v %v", err, gameid) + return + } + + var has, hasPlayer bool + for _, v := range data.PlayerData { + if v.IsRob { + has = true + } + if !v.IsRob { + hasPlayer = true + } + } + if !has || !hasPlayer { + // 没有机器人 + return 0, 0, 0, 0, ctrlType + } + + sort.Slice(data.PlayerData, func(i, j int) bool { + a, b := data.PlayerData[i].AllScore, data.PlayerData[j].AllScore + if a != b { + return a > b + } + if data.PlayerData[i].IsRob != data.PlayerData[j].IsRob { + return !data.PlayerData[i].IsRob + } + return data.PlayerData[i].UserId < data.PlayerData[j].UserId + }) + + for k, v := range data.PlayerData { + if !v.IsRob && v.AllScore > 0 && k <= 1 { + if k == 0 { + first = 1 + } + if k == 1 { + second = 1 + } + break + } + } + + if len(data.PlayerData) > 2 && first+second == 0 && !data.PlayerData[2].IsRob { + third = 1 + } + + to = 1 + } + return +} diff --git a/statistics/task/task/gamebankruptcy.go b/statistics/task/task/gamebankruptcy.go new file mode 100644 index 0000000..21bbbcf --- /dev/null +++ b/statistics/task/task/gamebankruptcy.go @@ -0,0 +1,69 @@ +package task + +import ( + "context" + "fmt" + "go.mongodb.org/mongo-driver/bson" + "mongo.games.com/game/common" + "mongo.games.com/goserver/core/logger" + mymongo "mongo.games.com/goserver/core/mongox" + mymysql "mongo.games.com/goserver/core/mysqlx" +) + +// 场次破产率 + +// GameFreeIdBankruptcyTimes 场次破产次数 +func GameFreeIdBankruptcyTimes(plt string, startTime, endTime string, gamefreeid int) (int, error) { + s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime) + db, err := mymysql.GetDatabase("count") + if err != nil { + return 0, err + } + + var n int64 + if err = db.Table("bankrupt_log").Where("bankrupt_time >= ? AND bankrupt_time < ? AND game_free_id = ?", + s.Unix(), e.Unix(), gamefreeid).Count(&n).Error; err != nil { + logger.Logger.Errorf("find bankrupt count get err: %v", err) + return 0, err + } + + return int(n), nil +} + +// GameFreeIdTotalTimes 场次总局数 +func GameFreeIdTotalTimes(plt string, startTime, endTime string, gamefreeid int) (int, error) { + s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime) + c, err := mymongo.GetLogCollection(plt, "log_gamedetailed") + if err != nil { + return 0, err + } + + n, err := c.CountDocuments(context.TODO(), bson.M{"time": bson.M{"$gte": s, "$lt": e}, "gamefreeid": gamefreeid}) + if err != nil { + logger.Logger.Errorf("find game total times get err: %v", err) + return 0, err + } + + return int(n), nil +} + +// GameBankruptcyRate 场次破产率 +// 返回 破产局数, 总局数, 错误 +func GameBankruptcyRate(plt string, startTime, endTime string, gamefreeid int) (int, int, error) { + s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime) + if s.IsZero() || e.IsZero() { + return 0, 0, fmt.Errorf("time format error") + } + + b, err := GameFreeIdBankruptcyTimes(plt, startTime, endTime, gamefreeid) + if err != nil { + return 0, 0, err + } + + t, err := GameFreeIdTotalTimes(plt, startTime, endTime, gamefreeid) + if err != nil { + return 0, 0, err + } + + return b, t, nil +} diff --git a/statistics/task/task/gamecount.go b/statistics/task/task/gamecount.go new file mode 100644 index 0000000..911c202 --- /dev/null +++ b/statistics/task/task/gamecount.go @@ -0,0 +1,60 @@ +package task + +import ( + "context" + "fmt" + "go.mongodb.org/mongo-driver/bson" + "mongo.games.com/game/common" + mongomodel "mongo.games.com/game/statistics/modelmongo" + "mongo.games.com/goserver/core/logger" + mymongo "mongo.games.com/goserver/core/mongox" +) + +// 新用户平均局数 + +func NewPlayerGameCount(plt string, ids []int, startTime, endTime string, gamefreeid int) (int, error) { + s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime) + c, err := mymongo.GetLogCollection(plt, mongomodel.LogGamePlayerListLog) + if err != nil { + return 0, err + } + + var ret int + for _, v := range ids { + n, err := c.CountDocuments(context.TODO(), bson.M{"snid": v, "gamefreeid": gamefreeid, "time": bson.M{"$gte": s, "$lt": e}}) + if err != nil { + logger.Logger.Errorf("find player gamedetailedlogid get err: %v", err) + return 0, err + } + ret += int(n) + } + + return ret, nil +} + +// NewPlayerGameCountAvg 新用户平均局数 +// 返回 参与人数,总局数 +func NewPlayerGameCountAvg(plt string, startTime, endTime string, gamefreeid int) (int, int, error) { + s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime) + if s.IsZero() || e.IsZero() { + return 0, 0, fmt.Errorf("time format error") + } + ids, err := GetNewPayerIds(plt, startTime, endTime) + if err != nil { + return 0, 0, err + } + n, err := NewPlayerGameCount(plt, ids, startTime, endTime, gamefreeid) + if err != nil { + return 0, 0, err + } + if len(ids) == 0 { + return 0, 0, nil + } + + b, err := PlayingGameCount(plt, ids, startTime, endTime, gamefreeid) + if err != nil { + return 0, 0, err + } + + return b, n, nil +} diff --git a/statistics/task/task/gamelogtype.go b/statistics/task/task/gamelogtype.go new file mode 100644 index 0000000..74cbf60 --- /dev/null +++ b/statistics/task/task/gamelogtype.go @@ -0,0 +1,1625 @@ +package task + +// 赢三张 +type WinThreeType struct { + RoomId int32 //房间ID + RoomRounds int32 //建房局数 + RoomType int32 //房间类型 + NowRound int32 //当前局数 + PlayerCount int //玩家数量 + BaseScore int32 //底分 + PlayerData []WinThreePerson //玩家信息 + Chip []PlayerChip //出牌详情 + ClubRate int32 //俱乐部抽水比例 + IsSmartOperation bool // 是否启用智能化运营 + GamePreUseSmartState int //-1:正常 + GameLastUseSmartState int //-1:正常 + RobotUseSmartState int64 // Robot使用智能化运营状况 +} +type PlayerChip struct { + UserId int32 //玩家ID + BetTotal int64 //玩家总投注 + Chip int64 //玩家得分 + StartCoin int64 //玩家开始前金币 + BetAfterCoin int64 //玩家投注后金币,也就是客户端应该显示的金币 + IsCheck bool //是否看牌 + Round int32 //当前轮次 + Op int32 //操作 +} + +type WinThreePerson struct { + UserId int32 //玩家ID + UserIcon int32 //玩家头像 + ChangeCoin int64 //玩家得分 + Cardinfo []int32 //牌值 + KindOfCard int32 //牌型 + IsWin int32 //输赢 + IsRob bool //是否是机器人 + Tax int64 //税,不一定有值,只是作为一个临时变量使用 + ClubPump int64 //俱乐部额外抽水 + Flag int //标识 + Pos int32 //位置 + StartCoin int64 //开始金币 + BetCoin int64 //押注额度 + RoundCheck int32 //轮次,看牌的 + IsFirst bool //是否第一次 + IsLeave bool + Platform string `json:"-"` + Channel string `json:"-"` + Promoter string `json:"-"` + PackageTag string `json:"-"` + InviterId int32 `json:"-"` + WBLevel int32 //黑白名单等级 + SingleFlag int32 //单控标记 +} + +// 经典牛牛 抢庄牛牛 +type BullFightType struct { + RoomId int32 //房间ID + RoomRounds int32 //建房局数 + RoomType int32 //房间类型 + NowRound int32 //当前局数 + BankId int32 //庄家ID + PlayerCount int //玩家数量 + BaseScore int32 //底分 + PlayerData []BullFightPerson //玩家信息 + ClubRate int32 //俱乐部抽水比例 + IsSmartOperation bool // 是否启用智能化运营 +} +type BullFightPerson struct { + UserId int32 //玩家ID + UserIcon int32 //玩家头像 + ChangeCoin int64 //玩家得分 + Cardinfo []int32 //牌值 + CardBakinfo []int32 //牌值 + IsWin int32 //输赢 + Tax int64 //税,不一定有值,只是作为一个临时变量使用 + TaxLottery int64 //彩金池,增加值 + ClubPump int64 //俱乐部额外抽水 + IsRob bool //是否是机器人 + Flag int //标识 + IsFirst bool + StartCoin int64 //开始金币 + Platform string `json:"-"` + Channel string `json:"-"` + Promoter string `json:"-"` + PackageTag string `json:"-"` + InviterId int32 `json:"-"` + Rate int64 //斗牛倍率 + WBLevel int32 //黑白名单等级 + RobBankRate int64 //抢庄倍率 + SingleAdjust int32 // 单控输赢 1赢 2输 +} + +// 斗地主、跑得快 +type DoudizhuType struct { + RoomId int //房间Id + BasicScore int //基本分 + Spring int //春天 1代表春天 + BombScore int //炸弹分 + BaseScore int32 //底分 + PlayerCount int32 //玩家数量 + BaseCards []int //底牌 + BankerId int32 //斗地主地主Id + PlayerData []DoudizhuPerson //玩家信息 + ClubRate int32 //俱乐部抽水比例 + IsSmartOperation bool // 是否启用智能化运营 +} +type DoudizhuPerson struct { + UserId int32 //玩家ID + UserIcon int32 //玩家头像 + ChangeCoin int64 //玩家得分 + OutCards [][]int64 //出的牌 + SurplusCards []int32 //剩下的牌 + IsWin int64 //输赢 + IsRob bool //是否是机器人 + IsFirst bool + Tax int64 //税,不一定有值,只是作为一个临时变量使用 + ClubPump int64 //俱乐部额外抽水 + StartCoin int64 //开始的金币数量 + Platform string `json:"-"` + Channel string `json:"-"` + Promoter string `json:"-"` + PackageTag string `json:"-"` + InviterId int32 `json:"-"` + WBLevel int32 //黑白名单等级 +} + +// 推饼 +type TuibingType struct { + RoomId int32 //房间ID + RoomRounds int32 //建房局数 + RoomType int32 //房间类型 + NowRound int32 //当前局数 + BankId int32 //庄家ID + PlayerCount int //玩家数量 + BaseScore int32 //底分 + PlayerData []TuibingPerson //玩家信息 + ClubRate int32 //俱乐部抽水比例 + IsSmartOperation bool // 是否启用智能化运营 +} +type TuibingPerson struct { + UserId int32 //玩家ID + UserIcon int32 //玩家头像 + ChangeCoin int64 //玩家得分 + Cardinfo []int32 //牌值 + IsWin int32 //输赢 + IsRob bool //是否是机器人 + IsFirst bool + Tax int64 //税,不一定有值,只是作为一个临时变量使用 + ClubPump int64 //俱乐部额外抽水 + Flag int //标识 + StartCoin int64 //开始金币 + Platform string `json:"-"` + Channel string `json:"-"` + Promoter string `json:"-"` + PackageTag string `json:"-"` + InviterId int32 `json:"-"` + Rate int64 //倍率 + WBLevel int32 //黑白名单等级 +} + +// 十三水 +// 十三水牌值 +type ThirteenWaterType struct { + BaseScore int32 //底分 + PlayerData []ThirteenWaterPerson //玩家信息 +} + +type ThirteenWaterPoker struct { + Head [3]int + Mid [5]int + End [5]int + PokerType int +} + +type ThirteenWaterPerson struct { + UserId int32 //玩家ID + IsRob bool // 是否是机器人 + AllScore int // 总得分 +} + +// 二人麻将 +type MahjongType struct { + RoomId int32 //房间ID + RoomRounds int32 //建房局数 + RoomType int32 //房间类型 + NowRound int32 //当前局数 + BankId int32 //庄家ID + PlayerCount int32 //玩家数量 + BaseScore int32 //底分 + HuType []int64 //本局胡牌牌型 + PlayerData []MahjongPerson //玩家信息 +} +type MahjongPerson struct { + UserId int32 //玩家ID + UserIcon int32 //玩家头像 + ChangeCoin int64 //玩家得分 + SurplusCards []int64 //剩下的牌 + CardsChow [][]int64 //吃的牌 + CardsPong []int64 //碰的牌 + CardsMingKong []int64 //明杠的牌 + CardsAnKong []int64 //暗杠的牌 + IsWin int32 //输赢 + StartCoin int64 //开始金币 + Tax int64 //税,不一定有值,只是作为一个临时变量使用 + ClubPump int64 //俱乐部额外抽水 + IsRob bool //是否是机器人 + IsFirst bool + Platform string `json:"-"` + Channel string `json:"-"` + Promoter string `json:"-"` + PackageTag string `json:"-"` + InviterId int32 `json:"-"` + WBLevel int32 //黑白名单等级 +} + +// 百人牛牛 龙虎斗 百家乐 红黑大战 +type HundredType struct { + RegionId int32 //边池ID 庄家 天 地 玄 黄 + IsWin int //边池输赢 + Rate int //倍数 + CardsInfo []int32 //扑克牌值 + PlayerData []HundredPerson //玩家属性 + CardsKind int32 //牌类型 + CardPoint int32 //点数 + IsSmartOperation bool // 是否启用智能化运营 +} +type HundredPerson struct { + UserId int32 //用户Id + UserBetTotal int64 //用户下注 + BeforeCoin int64 //下注前金额 + AfterCoin int64 //下注后金额 + ChangeCoin int64 //金额变化 + IsRob bool //是否是机器人 + IsFirst bool + WBLevel int32 //黑白名单等级 + Result int32 //单控结果 + UserBetTotalDetail []int64 //用户下注明细 +} + +// 碰撞 +type CrashType struct { + RegionId int32 //边池ID 庄家 天 地 玄 黄 + IsWin int //边池输赢 + Rate int //倍数 + CardsInfo []int32 //扑克牌值 + PlayerData []CrashPerson //玩家属性 + CardsKind int32 //牌类型 + CardPoint int32 //点数 + IsSmartOperation bool // 是否启用智能化运营 + Hash string //Hash + Period int //当前多少期 + Wheel int //第几轮 +} + +// 碰撞 +type CrashPerson struct { + UserId int32 //用户Id + UserBetTotal int64 //用户下注 + UserMultiple int32 //下注倍数 + BeforeCoin int64 //下注前金额 + AfterCoin int64 //下注后金额 + ChangeCoin int64 //金额变化 + IsRob bool //是否是机器人 + IsFirst bool + WBLevel int32 //黑白名单等级 + Result int32 //单控结果 + UserBetTotalDetail []int64 //用户下注明细 + Tax int64 //税收 +} + +// 十点半 +type TenHalfType struct { + RoomId int32 //房间ID + RoomRounds int32 //建房局数 + RoomType int32 //房间类型 + NowRound int32 //当前局数 + BankId int32 //庄家ID + PlayerCount int //玩家数量 + BaseScore int32 //底分 + PlayerData []TenHalfPerson //玩家信息 + ClubRate int32 //俱乐部抽水比例 + IsSmartOperation bool // 是否启用智能化运营 +} + +type TenHalfPerson struct { + UserId int32 //玩家ID + UserIcon int32 //玩家头像 + ChangeCoin int64 //玩家得分 + Cardinfo []int32 //牌值 + IsWin int32 //输赢 + Tax int64 //税,不一定有值,只是作为一个临时变量使用 + ClubPump int64 //俱乐部额外抽水 + IsRob bool //是否是机器人 + Flag int //标识 + IsFirst bool + StartCoin int64 //开始金币 + Platform string `json:"-"` + Channel string `json:"-"` + Promoter string `json:"-"` + PackageTag string `json:"-"` + InviterId int32 `json:"-"` + WBLevel int32 //黑白名单等级 +} + +type GanDengYanType struct { + RoomId int // 房间Id + RoomRounds int32 // 建房局数 + NowRound int32 // 当前局数 + BombScore int // 炸弹倍率 + BaseScore int32 // 底分 + PlayerCount int32 // 玩家数量 + BankerId int32 // 庄家id + PlayerData []GanDengYanPlayer // 玩家信息 + ClubRate int32 // 俱乐部抽水比例 +} + +type GanDengYanHandCards struct { + Cards []int64 + Index int32 +} + +type GanDengYanPlayer struct { + UserId int32 // 玩家ID + UserIcon int32 // 玩家头像 + ChangeCoin int64 // 玩家得分 + OutCards []*GanDengYanHandCards // 出的牌 + SurplusCards []int32 // 剩下的牌 + IsWin int64 // 输赢 + IsRob bool // 是否是机器人 + IsFirst bool + Tax int64 // 税,不一定有值,只是作为一个临时变量使用 + ClubPump int64 // 俱乐部额外抽水 + StartCoin int64 // 开始的金币数量 + Platform string `json:"-"` + Channel string `json:"-"` + Promoter string `json:"-"` + PackageTag string `json:"-"` + InviterId int32 `json:"-"` + WBLevel int32 //黑白名单等级 + NumMagic int32 // 癞子或2倍数 + NumCards int32 // 剩余牌数 + NumHand int32 // 手牌倍数 + Num int32 // 总倍数 +} + +// 红包扫雷 +type RedPackGameType struct { + BankerId int32 //庄家id + BankerBet int32 //庄家下注金额 + BankerWin int32 //庄家赢取 + BankerRest int32 //庄家退回钱数 + Rate int32 //倍数 + BombNum int32 //雷号 + HitBombCnt int32 //中雷的人数 + PlayerData []*RedPackPerson //玩家属性 +} +type RedPackPerson struct { + UserId int32 //用户Id + IsFirst bool + BeforeCoin int64 //抢包前金额 + AfterCoin int64 //抢包后金额 + ChangeCoin int64 //金额变化 + GrabCoin int32 //抢到红包的数量 + IsHit bool //是否中雷 + IsRob bool //是否是机器人 +} + +// 鱼场 +type BulletLevelTimes struct { + Level int32 //等级 + Times int32 //次数 +} +type FishCoinNum struct { + ID int32 // 鱼id + Num int32 //打死数量 + Power int32 //子弹价值 + Coin int32 //金币(总金币) + HitNum int32 //击中次数 +} + +type FishPlayerData struct { + UserId int32 //玩家ID + UserIcon int32 //玩家头像 + TotalIn int64 //玩家该阶段总投入 + TotalOut int64 //玩家该阶段总产出 + CurrCoin int64 //记录时玩家当前金币量 +} + +type FishDetiel struct { + BulletInfo *[]BulletLevelTimes //子弹统计 + HitInfo *[]FishCoinNum // + PlayData *FishPlayerData //统计 +} + +// 拉霸 +// 绝地求生记录详情 +type IslandSurvivalGameType struct { + //all + RoomId int32 //房间Id + BasicScore int32 //基本分 + PlayerSnid int32 //玩家id + BeforeCoin int64 //下注前金额 + AfterCoin int64 //下注后金额 + BetCoin int64 //下注金额 + WinCoin int64 //下注赢取金额 + IsFirst bool + Smallgamewinscore int64 //吃鸡游戏赢取的分数 + ChangeCoin int64 //本局游戏金额总变化 + FreeTimes int32 //免费转动次数 + AllWinNum int32 //中奖的线数 + LeftEnemy int32 //剩余敌人,需保存 + Killed int32 //总击杀敌人,需保存 + HitPoolIdx int //下注索引 + Cards []int //15张牌 +} + +const ( + WaterMarginSmallGame_Unop int32 = iota + WaterMarginSmallGame_AddOp + WaterMarginSmallGame_SubOp +) + +// 水浒传小游戏数据 +type WaterMarginSmallGameInfo struct { + AddOrSub int32 //加减操作 0:表示未操作 1:加 2:减 + Score int64 //本局小游戏参与的分数 + Multiple int32 //倍数 0:表示本局输了 >1:表示猜对小游戏 + Dice1 int32 //骰子1的点数 + Dice2 int32 //骰子2的点数 +} + +type WaterMarginType struct { + RoomId int32 //房间Id + BasicScore int32 //基本分 + PlayerSnid int32 //玩家id + BeforeCoin int64 //下注前金额 + AfterCoin int64 //下注后金额 + IsFirst bool //是否一次游戏 + ChangeCoin int64 //金额变化 + Score int32 //总押注数 + AllWinNum int32 //中奖的线数 + FreeTimes int32 //免费转动次数 + WinScore int32 //中奖的倍率 + AllLine int32 //线路数 + JackpotNowCoin int64 //爆奖金额 + Cards []int //15张牌 + HitPoolIdx int //压分的索引 + JackpotHitFlag int //如果jackpotnowcoin>0;该值标识中了哪些奖池,二进制字段 1:小奖(第0位) 2:中奖(第1位) 4:大奖(第2位) + TrigFree bool //是否触发免费转动 + SMGame []*WaterMarginSmallGameInfo //小游戏数据 +} + +type FootBallHeroesType struct { + RoomId int32 //房间Id + BasicScore int32 //基本分 + PlayerSnid int32 //玩家id + BeforeCoin int64 //下注前金额 + AfterCoin int64 //下注后金额 + ChangeCoin int64 //金额变化 + IsFirst bool + Score int32 //总押注数 + FreeTimes int32 //免费转动次数 + WinScore int32 //中奖的倍率 + Cards []int //15张牌 + CoinReward []int64 //礼物奖励 + Smallgamescore int64 //小游戏分数 + Smallgamewinscore int64 //小游戏赢取的分数 + SmallgameList []int32 //小游戏记录 + HitPoolIdx int //当前命中的奖池 +} +type FruitMachineType struct { + RoomId int32 //房间Id + BasicScore int32 //基本分 + PlayerSnid int32 //玩家id + BeforeCoin int64 //下注前金额 + AfterCoin int64 //下注后金额 + IsFirst bool + ChangeCoin int64 //金额变化 + Score int32 //总押注数 + AllWinNum int32 //中奖的线数 + FreeTimes int32 //免费转动次数 + WinScore int32 //中奖的倍率 + AllLine int32 //线路数 + JackpotNowCoin int64 //爆奖金额 + Cards []int //15张牌 + Smallgamescore int64 //小游戏分数 + HitPoolIdx int //当前命中的奖池 +} +type GoddessType struct { + RoomId int32 //房间Id + BasicScore int32 //基本分 + PlayerSnid int32 //玩家id + BeforeCoin int64 //下注前金额 + AfterCoin int64 //下注后金额 + ChangeCoin int64 //金额变化 + Score int32 //总押注数 + IsFirst bool + Smallgamescore int64 //小游戏分数 + Smallgamewinscore int64 //小游戏赢取的分数 + SmallgameCard int32 //小游戏卡牌 + CardsGoddess [3]int32 //3张牌 + BetMultiple int32 //押倍倍数 + SmallgameList []int32 //小游戏记录 + HitPoolIdx int //当前命中的奖池 +} +type RollLineType struct { + RoomId int32 //房间Id + BasicScore int32 //基本分 + PlayerSnid int32 //玩家id + BeforeCoin int64 //下注前金额 + AfterCoin int64 //下注后金额 + ChangeCoin int64 //金额变化 + Score int32 //总押注数 + IsFirst bool + AllWinNum int32 //中奖的线数 + FreeTimes int32 //免费转动次数 + WinScore int32 //中奖的倍率 + AllLine int32 //线路数 + RoseCount int32 //玫瑰数量 + Cards []int //15张牌 + CoinReward []int64 //礼物奖励 + HitPoolIdx int //当前命中的奖池 +} +type IceAgeType struct { + RoomId int32 //房间Id + BasicScore int32 //基本分 + PlayerSnid int32 //玩家id + BeforeCoin int64 //下注前金额 + AfterCoin int64 //下注后金额 + ChangeCoin int64 //金额变化 + Score int32 //总押注数 + Tax int64 //暗税 + IsFirst bool + AllWinNum int32 //中奖的线数 + FreeTimes int32 //免费转动次数 + WinScore int32 //中奖的倍率 + AllLine int32 //线路数 + Cards [][]int32 // 消除前后的牌(消除前15张,消除后15张...) + BetLines []int64 //下注的线 + UserName string // 昵称 + TotalPriceValue int64 // 总赢分 + IsFree bool // 是否免费 + TotalBonusValue int64 // 总bonus数 + WBLevel int32 //黑白名单等级 + WinLines [][]int // 赢分的线 + WinJackpot int64 // 赢奖池分数 + WinBonus int64 // 赢小游戏分数 +} +type TamQuocType struct { + RoomId int32 //房间Id + BasicScore int32 //基本分 + PlayerSnid int32 //玩家id + BeforeCoin int64 //下注前金额 + AfterCoin int64 //下注后金额 + ChangeCoin int64 //金额变化 + Score int32 //总押注数 + Tax int64 //暗税 + IsFirst bool + AllWinNum int32 //中奖的线数 + FreeTimes int32 //免费转动次数 + WinScore int32 //中奖的倍率 + AllLine int32 //线路数 + Cards []int32 //15张牌 + BetLines []int64 //下注的线 + WBLevel int32 //黑白名单等级 + UserName string // 昵称 + TotalPriceValue int64 // 总赢分 + IsFree bool // 是否免费 + TotalBonusValue int64 // 总bonus数 + WinLines []int // 赢分的线 + WinJackpot int64 // 赢奖池分数 + WinBonus int64 // 赢小游戏分数 +} +type CaiShenType struct { + RoomId int32 //房间Id + BasicScore int32 //基本分 + PlayerSnid int32 //玩家id + BeforeCoin int64 //下注前金额 + AfterCoin int64 //下注后金额 + ChangeCoin int64 //金额变化 + Score int32 //总押注数 + Tax int64 //暗税 + IsFirst bool + AllWinNum int32 //中奖的线数 + FreeTimes int32 //免费转动次数 + WinScore int32 //中奖的倍率 + AllLine int32 //线路数 + Cards []int32 //15张牌 + WBLevel int32 //黑白名单等级 + BetLines []int64 //下注的线 + UserName string // 昵称 + TotalPriceValue int64 // 总赢分 + IsFree bool // 是否免费 + TotalBonusValue int64 // 总bonus数 + WinLines []int // 赢分的线 + WinJackpot int64 // 赢奖池分数 + WinBonus int64 // 赢小游戏分数 +} +type AvengersType struct { + RoomId int32 //房间Id + BasicScore int32 //基本分 + PlayerSnid int32 //玩家id + BeforeCoin int64 //下注前金额 + AfterCoin int64 //下注后金额 + ChangeCoin int64 //金额变化 + Score int32 //总押注数 + Tax int64 //暗税 + IsFirst bool + AllWinNum int32 //中奖的线数 + FreeTimes int32 //免费转动次数 + WinScore int32 //中奖的倍率 + AllLine int32 //线路数 + Cards []int32 //15张牌 + WBLevel int32 //黑白名单等级 + BetLines []int64 //下注的线 + UserName string // 昵称 + TotalPriceValue int64 // 总赢分 + IsFree bool // 是否免费 + TotalBonusValue int64 // 总bonus数 + WinLines []int // 赢分的线 + WinJackpot int64 // 赢奖池分数 + WinBonus int64 // 赢小游戏分数 +} +type EasterIslandType struct { + RoomId int32 //房间Id + BasicScore int32 //基本分 + PlayerSnid int32 //玩家id + BeforeCoin int64 //下注前金额 + AfterCoin int64 //下注后金额 + ChangeCoin int64 //金额变化 + Score int32 //总押注数 + Tax int64 //暗税 + IsFirst bool + AllWinNum int32 //中奖的线数 + FreeTimes int32 //免费转动次数 + WinScore int32 //中奖的倍率 + AllLine int32 //线路数 + Cards []int32 //15张牌 + WBLevel int32 //黑白名单等级 + BetLines []int64 //下注的线 + UserName string // 昵称 + TotalPriceValue int64 // 总赢分 + IsFree bool // 是否免费 + TotalBonusValue int64 // 总bonus数 + WinLines []int // 赢分的线 + WinJackpot int64 // 赢奖池分数 + WinBonus int64 // 赢小游戏分数 +} +type RollTeamType struct { + RoomId int32 //房间Id + BasicScore int32 //基本分 + PlayerSnid int32 //玩家id + BeforeCoin int64 //下注前金额 + AfterCoin int64 //下注后金额 + ChangeCoin int64 //金额变化 + Score int32 //总押注数 + AllWinNum int32 //中奖的线数 + IsFirst bool + FreeTimes int32 //免费转动次数 + WinScore int32 //中奖的倍率 + AllLine int32 //线路数 + PokerBaseCoin int32 //扑克游戏获得的金币 + PokerRate int32 //游戏的翻倍值 + GameCount int32 //游戏次数 + HitPoolIdx int //当前命中的奖池 + Cards []int //15张牌 +} +type RollPerson struct { + UserId int32 //用户Id + UserBetTotal int64 //用户下注 + BeforeCoin int64 //下注前金额 + AfterCoin int64 //下注后金额 + ChangeCoin int64 //金额变化 + IsFirst bool + IsRob bool //是否是机器人 + WBLevel int32 //黑白名单等级 +} +type RollHundredType struct { + RegionId int32 //边池ID -1.庄家 0.大众 1.雷克萨斯 2.宝马 3.奔驰 4.保时捷 5.玛莎拉蒂 6.兰博基尼 7.法拉利 + IsWin int //边池输赢 1.赢 0.平 -1.输 + Rate int //倍数 + SType int32 //特殊牌型 临时使用 + RollPerson []RollPerson +} + +// 梭哈 +type FiveCardType struct { + RoomId int32 //房间ID + RoomRounds int32 //建房局数 + RoomType int32 //房间类型 + PlayerCount int //玩家数量 + BaseScore int32 //底分 + PlayerData []FiveCardPerson //玩家信息 + ClubRate int32 //俱乐部抽水比例 +} + +// 梭哈 +type FiveCardPerson struct { + UserId int32 //玩家ID + UserIcon int32 //玩家头像 + ChangeCoin int64 //玩家得分 + Cardinfo []int32 //牌值 + IsWin int32 //输赢 + Tax int64 //税,不一定有值,只是作为一个临时变量使用 + ClubPump int64 //俱乐部额外抽水 + IsRob bool //是否是机器人 + IsFirst bool //是否第一次 + IsLeave bool //中途离开 + Platform string `json:"-"` + Channel string `json:"-"` + Promoter string `json:"-"` + PackageTag string `json:"-"` + InviterId int32 `json:"-"` + BetTotal int64 //用户当局总下注 + IsAllIn bool //是否全下 + WBLevel int32 //黑白名单等级 +} + +// 骰子 +type RollPointPerson struct { + UserId int32 //用户Id + UserBetTotal int64 //用户下注 + BeforeCoin int64 //下注前金额 + AfterCoin int64 //下注后金额 + ChangeCoin int64 //金额变化 + IsFirst bool + IsRob bool //是否是机器人 + WBLevel int32 //黑白名单等级 + BetCoin []int32 //押注金币 +} +type RollPointType struct { + RoomId int32 + Point []int32 + Score []int32 + BetCoin int64 + WinCoin int64 + Person []RollPointPerson +} + +// 轮盘 +type RouletteType struct { + BankerInfo RouletteBanker //庄家信息 + Person []RoulettePerson //下注玩家列表 + RouletteRegion []RouletteRegion //下区域 +} +type RouletteBanker struct { + Point int //当局开的点数 + TotalBetCoin int64 //总下注金额 + TotalWinCoin int64 //总输赢金额 +} +type RouletteRegion struct { + Id int //0-156 下注位置编号 + IsWin int //是否中奖 + BetCoin int64 //当前区域总下注金额 + WinCoin int64 //当前区域总输赢金额 + Player []RoulettePlayer //当前区域下注玩家列表 +} +type RoulettePlayer struct { + UserId int32 //用户Id + BetCoin int64 //当局下注额 +} +type RoulettePerson struct { + UserId int32 //用户Id + BeforeCoin int64 //下注前金额 + AfterCoin int64 //下注后金额 + UserBetTotal int64 //用户总下注 + UserWinCoin int64 //用户输赢 + IsRob bool //是否是机器人 + WBLevel int32 //黑白名单等级 + BetCoin map[int]int64 //下注区域对应金额 +} + +// 九线拉王 +type NineLineKingType struct { + RoomId int32 //房间Id + BasicScore int32 //基本分 + PlayerSnid int32 //玩家id + BeforeCoin int64 //下注前金额 + AfterCoin int64 //下注后金额 + IsFirst bool + ChangeCoin int64 //金额变化 + Score int32 //总押注数 + AllWinNum int32 //中奖的线数 + FreeTimes int32 //免费转动次数 + WinScore int32 //中奖的倍率 + AllLine int32 //线路数 + JackpotNowCoin int64 //爆奖金额 + Cards []int //15张牌 + HitPoolIdx int //当前命中的奖池 + CommPool int64 //公共奖池 + PersonPool int64 //私人奖池 +} + +// 飞禽走兽 +type RollAnimalsType struct { + BetTotal int64 //总下注 + WinCoin int64 //用户输赢 + WinFlag []int64 //中奖元素 + RollLog []RollHundredType //每个区域下注信息 +} + +// 血战 +type BloodMahjongType struct { + RoomId int32 //房间ID + RoomRounds int32 //建房局数 + RoomType int32 //房间类型 + NowRound int32 //当前局数 + BankId int32 //庄家ID + PlayerCount int32 //玩家数量 + BaseScore int32 //底分 + PlayerData []BloodMahjongPerson //玩家信息 +} + +// 碰杠牌 +type BloodMahjongCardsLog struct { + Card int64 //牌 + Pos []int //0.东 1.南 2.西 3.北 + Flag int //1.碰 2.明杠 3.暗杠 4.补杠 +} + +// 分数类型 +type BloodMahjongScoreTiles struct { + LogType int //0.胡 1.刮风 2.下雨 3.退税 4.查花猪 5.查大叫 6.被抢杠 补杠退钱 7.呼叫转移 + OtherPos []int //源自哪个位置的玩家 + Coin int64 //理论进账 + ActualCoin int64 //实际进账 + Rate int32 //倍率 + Params []int64 //【Honor】//胡牌类型 +} +type BloodMahjongPerson struct { + UserId int32 //玩家ID + UserIcon int32 //玩家头像 + ChangeCoin int64 //玩家得分 + Pos int32 //玩家位置 0.东 1.南 2.西 3.北 + IsLeave bool //是否离场 + Bankruptcy bool //是否破产 + HuNumber int32 //第几胡 1 2 3 + LackColor int64 //定缺花色 + Hands []int64 //手牌 + HuCards []int64 //胡牌 + CardsLog []BloodMahjongCardsLog //碰杠牌 + ScoreTiles []BloodMahjongScoreTiles //分数 + IsWin int32 //输赢 + Tax int64 //税,不一定有值,只是作为一个临时变量使用 + StartCoin int64 //开始金币 + ClubPump int64 //俱乐部额外抽水 + IsRob bool //是否是机器人 + Platform string `json:"-"` + Channel string `json:"-"` + Promoter string `json:"-"` + PackageTag string `json:"-"` + InviterId int32 `json:"-"` + WBLevel int32 //黑白名单等级 +} + +type PCProp struct { + Id uint32 + TypeName string // 金币类型 + AreaType int32 // 所在区域 0平台上 1有效区 2无效区 3小车内 + CoinVal int64 // 金币面值 + X float32 + Y float32 + Z float32 + RotX float32 + RotY float32 + RotZ float32 +} + +// 推币机 +type PushingCoinRecord struct { + RoomId int // 房间id + RoomType int // 房间类型 + GameMode int // 游戏模式 + BaseScore int64 // 底分 + ShakeTimes int32 // 震动次数 + WallUpTimes int32 // 升墙次数 + EventTimes []int32 // 事件次数 + Props []*PCProp // 所有金币 +} + +type HuntingRecord struct { + RoomId int // 房间ID + BaseScore int // 底分 + SnId int32 // 玩家ID + StartCoin int64 // 下注前金额 + Coin int64 // 下注后金额 + ChangeCoin int64 // 金币变化 + CoinPool int64 // 爆奖金额 + Point int64 // 单线点数 + LineNum int64 // 线数 + BetCoin int64 // 下注金额 + WinCoin int64 // 产出金额 + Level int // 当前关卡 + Gain int64 // 翻牌奖励 +} + +// 对战三公 +type SanGongPVPType struct { + RoomId int32 //房间ID + RoomRounds int32 //建房局数 + RoomType int32 //房间类型 + NowRound int32 //当前局数 + BankId int32 //庄家ID + PlayerCount int //玩家数量 + BaseScore int32 //底分 + PlayerData []SanGongPVPPerson //玩家信息 + ClubRate int32 //俱乐部抽水比例 + IsSmartOperation bool //是否启用智能化运营 +} +type SanGongPVPPerson struct { + UserId int32 //玩家ID + UserIcon int32 //玩家头像 + ChangeCoin int64 //玩家得分 + Cardinfo []int32 //牌值 + IsWin int32 //输赢 + Tax int64 //税,不一定有值,只是作为一个临时变量使用 + ClubPump int64 //俱乐部额外抽水 + IsRob bool //是否是机器人 + Flag int //标识 + IsFirst bool + StartCoin int64 //开始金币 + Platform string `json:"-"` + Channel string `json:"-"` + Promoter string `json:"-"` + PackageTag string `json:"-"` + InviterId int32 `json:"-"` + WBLevel int32 //黑白名单等级 +} + +// 德州牛仔 +type DZNZCardInfo struct { + CardId int32 //手牌ID 牛仔 公共牌 公牛 + CardsInfo []int32 //扑克牌值 + CardsKind int32 //牌类型 +} +type DZNZZoneInfo struct { + RegionId int32 //13个下注区域 + IsWin int //边池输赢 + Rate float32 //倍数 + PlayerData []HundredPerson //玩家属性 + IsSmartOperation bool //是否启用智能化运营 +} +type DZNZHundredInfo struct { + CardData []DZNZCardInfo //发牌信息 + ZoneInfo []DZNZZoneInfo //每个下注区域信息 +} + +// 财运之神 +type FortuneZhiShenType struct { + //基本信息 + RoomId int //房间Id + BasicScore int32 //单注 + PlayerSnId int32 //玩家id + BeforeCoin int64 //下注前金额 + AfterCoin int64 //下注后金额 + ChangeCoin int64 //金额变化 + TotalBetCoin int64 //总押注 + TotalLine int32 //总线数(固定) + TotalWinCoin int64 //总派彩 + NowGameState int //当前游戏模式(0,1,2,3)普通/免费/停留旋转/停留旋转2 + NowNRound int //第几轮 + IsOffline int //0,1 正常(不显示)/掉线(显示) + FirstFreeTimes int //免费游戏剩余次数 + SecondFreeTimes int //停留旋转游戏剩余次数 + //中奖统计 + HitPrizePool []int64 //命中奖池(小奖|中奖|大奖|巨奖) + WinLineNum int //中奖线个数 + WinLineRate int64 //中奖线总倍率 + WinLineCoin int64 //中奖线派彩 + GemstoneNum int //宝石数量 + GemstoneWinCoin int64 //宝石派彩 + //详情 + Cards []int32 //元素顺序 横向 + GemstoneRateCoin []int64 //宝石金额 横向 + //中奖线详情 + WinLine []FortuneZhiShenWinLine + WBLevel int32 //黑白名单等级 + TaxCoin int64 //税收 +} +type FortuneZhiShenWinLine struct { + Id int //线号 + EleValue int32 //元素值 + Num int //数量 + Rate int64 //倍率 + WinCoin int64 //单线派彩 + WinFreeGame int //(0,1,2)旋转并停留*3/免费游戏*6/免费游戏*3 +} + +// 金鼓齐鸣记录详情 +type GoldDrumWinLineInfo struct { + EleValue int //元素值 + Rate int64 //倍率 + WinCoin int64 //单线派彩 + GameType int //(0,1,2)无/免费游戏/聚宝盆游戏 +} +type GoldDrumGameType struct { + //all + RoomId int32 //房间Id + BasicScore int32 //单注分 + PlayerSnid int32 //玩家id + BeforeCoin int64 //下注前金额 + AfterCoin int64 //下注后金额 + BetCoin int64 //下注金额 + WinCoin int64 //下注赢取金额总金额 + //Smallgamewinscore int64 //小游戏赢取的分数 + ChangeCoin int64 //本局游戏金额总变化 + FreeTimes int32 //免费转动次数 + AllWinNum int32 //中奖的线数 + HitPoolIdx int //下注索引 + Cards []int //15张牌 + + NowGameState int //当前游戏模式(0,1)普通/免费 + HitPrizePool []int64 //命中奖池(多喜小奖|多寿中奖|多禄大奖|多福巨奖) + WinLineRate int64 //中奖线总倍率 + WinLineCoin int64 //中奖线派彩 + WinLineInfo []GoldDrumWinLineInfo //中奖线详情 + + NowFreeGameTime int32 //当前免费游戏第几次 + CornucopiaCards []int32 //聚宝盆游戏数据 -1 未开启 0小将 1中奖 2大奖 3巨奖 + IsOffline bool //玩家是否掉线 true 掉线 + WBLevel int32 //黑白名单等级 + TaxCoin int64 //税收 +} + +// 金福报喜记录详情 +type CopperInfo struct { + Pos int32 //铜钱元素索引,从0开始 + Coin int64 //铜钱奖励金币 +} +type GoldBlessWinLineInfo struct { + EleValue int //元素值 + Rate int64 //倍率 + WinCoin int64 //单线派彩 + GameType int //(0,1,2,3)无/免费游戏/聚宝盆游戏/招福纳财游戏 +} +type GoldBlessGameType struct { + //all + RoomId int32 //房间Id + BasicScore int32 //单注分 + PlayerSnid int32 //玩家id + BeforeCoin int64 //下注前金额 + AfterCoin int64 //下注后金额 + BetCoin int64 //下注金额 + WinCoin int64 //本局总赢取金额 + //Smallgamewinscore int64 //小游戏赢取的分数 + ChangeCoin int64 //本局游戏金额总变化 + FreeTimes int32 //免费转动次数 + AllWinNum int32 //中奖的线数 + HitPoolIdx int //下注索引 + Cards []int //15张牌 + + NowGameState int //当前游戏模式(0,1,2)普通/免费/招福纳财 + HitPrizePool []int64 //命中奖池(多喜小奖|多寿中奖|多禄大奖|多福巨奖) + WinLineRate int64 //中奖线总倍率 + WinLineCoin int64 //中奖线派彩 + WinLineInfo []GoldBlessWinLineInfo //中奖线详情 + CopperNum int32 //本局铜钱数量 + CopperCoin int64 //本局铜钱金额 + CoppersInfo []CopperInfo //铜钱结构 + + NowFreeGameTime int32 //当前免费游戏第几次 + CornucopiaCards []int32 //聚宝盆游戏数据 -1 未开启 0小将 1中奖 2大奖 3巨奖 + IsOffline bool //玩家是否掉线 true 掉线 + WBLevel int32 //黑白名单等级 + TaxCoin int64 //税收 +} + +// 发发发 +type Classic888Type struct { + //基本信息 + RoomId int //房间Id + BasicScore int32 //单注 + PlayerSnId int32 //玩家id + BeforeCoin int64 //下注前金额 + AfterCoin int64 //下注后金额 + ChangeCoin int64 //金额变化 + TotalBetCoin int64 //总押注 + TotalLine int32 //总线数(固定) + TotalWinCoin int64 //总派彩 + NowGameState int //当前游戏模式(0,1,2)普通/免费/停留旋转 + NowNRound int //第几轮 + IsOffline int //0,1 正常(不显示)/掉线(显示) + FirstFreeTimes int //免费游戏剩余次数 + SecondFreeTimes int //停留旋转游戏剩余次数 + //中奖统计 + HitPrizePool []int64 //命中奖池(小奖|中奖|大奖|巨奖) + WinLineNum int //中奖线个数 + WinLineRate int64 //中奖线总倍率 + WinLineCoin int64 //中奖线派彩 + LanternNum int //灯笼数量 + LanternWinCoin int64 //灯笼派彩 + //详情 + Cards []int32 //元素顺序 横向 + LanternRateCoin []int64 //灯笼金额 横向 + //中奖线详情 + WinLine []Classic888WinLine + WBLevel int32 //黑白名单等级 + TaxCoin int64 //税收 +} +type Classic888WinLine struct { + Id int //线号 + EleValue int32 //元素值 + Num int //数量 + Rate int64 //倍率 + WinCoin int64 //单线派彩 + WinFreeGame int //(0,1,2,3)旋转并停留*3/免费游戏*6/免费游戏*9/免费游戏*15 +} + +// 多福 +type RichBlessedType struct { + //基本信息 + RoomId int //房间Id + BasicScore int32 //单注 + PlayerSnId int32 //玩家id + BeforeCoin int64 //下注前金额 + AfterCoin int64 //下注后金额 + ChangeCoin int64 //金额变化 + TotalBetCoin int64 //总押注 + TotalLine int32 //总线数(固定) + TotalWinCoin int64 //总派彩 + NowGameState int //当前游戏模式(0,1,2)普通/免费/jack小游戏 + NowNRound int //第几轮 + IsOffline int //0,1 正常(不显示)/掉线(显示) + FirstFreeTimes int //免费游戏剩余次数 + //中奖统计 + WinLineNum int //中奖线个数 + WinLineRate int64 //中奖线总倍率 + WinLineCoin int64 //中奖线派彩 + //详情 + Cards []int32 //普通游戏/免费游戏 + //jack游戏 + JackEleValue int32 //元素值 + JackMidCards []int64 //掀开位置 + //中奖线详情 + WinLine []RichBlessedWinLine + WBLevel int32 //黑白名单等级 + TaxCoin int64 //税收 + RealCtrl bool //人工调控 + WBState int32 //调控等级 + WeightKey int32 //当前使用权重 +} +type RichBlessedWinLine struct { + Id int //线号 + EleValue int32 //元素值 + Num int //数量 + Rate int64 //倍率 + WinCoin int64 //单线派彩 +} + +// 经典777 +type FruitsType struct { + //基本信息 + RoomId int //房间Id + BasicScore int32 //单注 + PlayerSnId int32 //玩家id + BeforeCoin int64 //下注前金额 + AfterCoin int64 //下注后金额 + ChangeCoin int64 //金额变化 + TotalBetCoin int64 //总押注 + TotalLine int32 //总线数(固定) + TotalWinCoin int64 //总派彩 + NowGameState int //当前游戏模式(0,1,2)普通/免费/小玛丽 + NowNRound int //第几轮 + IsOffline int //0,1 正常(不显示)/掉线(显示) + FirstFreeTimes int //免费游戏剩余次数 + MaryFreeTimes int //停留旋转游戏剩余次数 + //中奖统计 + HitPrizePool int64 //命中奖池金额 + WinLineNum int //中奖线个数 + WinLineRate int64 //中奖线总倍率 + WinLineCoin int64 //中奖线派彩 + JackPotNum int //777数量 + JackPotWinCoin int64 //777派彩 + //详情 + Cards []int32 //普通游戏/免费游戏 + //玛丽游戏 + MaryOutSide int32 //外圈 + MaryMidCards []int32 //内圈 + //中奖线详情 + WinLine []FruitsWinLine + WBLevel int32 //黑白名单等级 + TaxCoin int64 //税收 + RealCtrl bool //人工调控 + WBState int32 //调控等级 + WeightKey int32 //当前使用权重 +} +type FruitsWinLine struct { + Id int //线号 + EleValue int32 //元素值 + Num int //数量 + Rate int64 //倍率 + WinCoin int64 //单线派彩 + WinFreeGame int //(0,1,2,3,4,5)小玛丽1/小玛丽2/小玛丽3/免费5/免费8/免费10 +} + +// 无尽宝藏记录详情 +type EndlessTreasureWinLineInfo struct { + EleValue int //元素值 + Rate int64 //倍率 + WinCoin int64 //单线派彩 + GameType int //(0,1,2,3)无/免费游戏/聚宝盆游戏/节节高游戏 +} +type EndlessTreasureGameType struct { + //all + RoomId int32 //房间Id + TotalBetScore int32 //总押注 + BasicScore float32 //单注分 + PlayerSnid int32 //玩家id + BeforeCoin int64 //下注前金额 + AfterCoin int64 //下注后金额 + BetCoin int64 //下注金额 + WinCoin int64 //本局总赢取金额 + ChangeCoin int64 //本局游戏金额总变化 + FreeTimes int32 //免费转动次数 + AllWinNum int32 //中奖的线数 + Cards []int //15张牌 + + NowGameState int //当前游戏模式(0,1,2)普通/免费/节节高 + HitPrizePool []int64 //命中奖池(小奖|中奖|大奖|巨奖) + WinLineRate int64 //中奖线总倍率 + WinLineCoin int64 //中奖线派彩 + WinLineInfo []EndlessTreasureWinLineInfo //中奖线详情 + CopperNum int32 //本局铜钱数量 + CopperCoin int64 //本局铜钱金额 + CoppersInfo []CopperInfo //铜钱结构 + + NowFreeGameTime int32 //当前免费游戏第几次 + IsOffline bool //玩家是否掉线 true 掉线 + AddFreeTimes int32 //本局新增免费转动次数 + WBLevel int32 //黑白名单等级 + TaxCoin int64 //税收 +} + +// 拉霸类游戏 基础牌局记录 +type SlotBaseResultType struct { + RoomId int32 //房间Id + BasicBet int32 //基本分(单注金额) + PlayerSnid int32 //玩家id + BeforeCoin int64 //下注前金额 + AfterCoin int64 //下注后金额 + ChangeCoin int64 //金额变化 + IsFirst bool //是否第一次玩游戏 + IsFree bool //是否免费 + TotalBet int32 //总押注金额 + WinRate int32 //中奖的倍率 + FreeTimes int32 //免费转动次数 + AllWinNum int32 //中奖的总线数 + Tax int64 //暗税 + WBLevel int32 //黑白名单等级 + SingleFlag int32 //0不控1单控赢2单控输 + WinLineScore int64 //中奖线赢取分数 + WinJackpot int64 //奖池赢取分数 + WinSmallGame int64 //小游戏赢取分数 + WinTotal int64 //本次游戏总赢取(本次游戏赢取总金额=中奖线赢取+奖池赢取+小游戏赢取) + Cards []int32 //15张牌 + IsFoolPlayer bool //是否是新手 +} + +// 小火箭游戏 每局记录 +type SmallRocketBaseResultType struct { + //all + RoomId int32 //房间Id + TotalBetVal int64 //总押注 + PlayerSnid int32 //玩家id + BeforeCoin int64 //下注前金额 + AfterCoin int64 //下注后金额 + WinCoin int64 //本局总赢取金额 + + BetCoin1 int64 //下注金额1 + BetMul1 float64 //下注倍数1 + IsAutoBetAndTake1 bool //自动领取1 + TakeBetMul1 float64 //提款倍数1 + BetCoin2 int64 //下注金额2 + BetMul2 float64 //下注倍数2 + IsAutoBetAndTake2 bool //自动领取2 + TakeBetMul2 float64 //提款倍数2 + + TaxCoin int64 //税收 +} + +type GameResultLog struct { + BaseResult *SlotBaseResultType + AllLine int32 //线路数 + UserName string //昵称 + WinLines []int //赢分的线 + BetLines []int64 //下注的线 +} + +// 幸运骰子 +type LuckyDiceType struct { + RoomId int32 //房间Id + RoundId int32 //局数编号 + BaseScore int32 //底分 + PlayerSnid int32 //玩家id + UserName string //昵称 + BeforeCoin int64 //本局前金额 + AfterCoin int64 //本局后金额 + ChangeCoin int64 //金额变化 + Bet int64 //总押注数 + Refund int64 //返还押注数 + Award int64 //获奖金额 + BetSide int32 //压大压小 0大 1小 + Dices []int32 //3个骰子值 + Tax int64 //赢家税收 + WBLevel int32 //黑白名单等级 +} + +type CandyType struct { + RoomId int32 //房间Id + SpinID int32 //局数编号 + BasicScore int32 //基本分 + PlayerSnid int32 //玩家id + BeforeCoin int64 //下注前金额 + AfterCoin int64 //下注后金额 + ChangeCoin int64 //金额变化 + Score int32 //总押注数 + Tax int64 //暗税 + IsFirst bool + AllWinNum int32 //中奖的线数 + WinScore int32 //中奖的倍率 + AllLine int32 //线路数 + Cards []int32 //9张牌 + BetLines []int64 //下注的线 + WBLevel int32 //黑白名单等级 + UserName string // 昵称 + TotalPriceValue int64 // 总赢分 + WinLines []int // 赢分的线 + WinJackpot int64 // 赢奖池分数 +} +type MiniPokerType struct { + RoomId int32 //房间Id + SpinID int32 //局数编号 + BasicScore int32 //基本分 + PlayerSnid int32 //玩家id + BeforeCoin int64 //下注前金额 + AfterCoin int64 //下注后金额 + ChangeCoin int64 //金额变化 + Score int32 //总押注数 + Tax int64 //暗税 + IsFirst bool + WinScore int32 //中奖的倍率 + Cards []int32 //5张牌 + WBLevel int32 //黑白名单等级 + UserName string // 昵称 + TotalPriceValue int64 // 总赢分 + WinJackpot int64 // 赢奖池分数 +} +type CaoThapType struct { + RoomId int32 //房间Id + BasicScore int32 //基本分 + PlayerSnid int32 //玩家id + BeforeCoin int64 //下注前金额 + AfterCoin int64 //下注后金额 + ChangeCoin int64 //金额变化 + Score int32 //总押注数 + Tax int64 //暗税 + IsFirst bool + Cards []int32 //翻的牌 + WBLevel int32 //黑白名单等级 + UserName string // 昵称 + TotalPriceValue int64 // 总赢分 + WinJackpot int64 // 赢奖池分数 + BetInfo []CaoThapBetInfo // 每次下注信息 +} +type CaoThapBetInfo struct { + TurnID int32 // 操作ID + TurnTime int64 // 操作时间 + BetValue int64 // 下注金额 + Card int32 // 牌值 + PrizeValue int64 // 赢分 +} + +// 21点 +type BlackJackType struct { + RoomId int32 //房间ID + RoomType int32 //房间类型 + NumOfGames int //当前局数 + PlayerCount int //玩家数量 + PlayerData []*BlackJackPlayer //玩家信息 + BankerCards []int32 //庄家牌 + BankerCardType int32 //牌型 1:黑杰克 2:五小龙 3:其它点数 4:爆牌 + BankerCardPoint []int32 //点数 + BetCoin int64 //总下注 + GainCoinTax int64 //总输赢分(税前) +} + +type BlackJackCardInfo struct { + Cards []int32 //闲家牌 + CardType int32 //牌型 1:黑杰克 2:五小龙 3:其它点数 4:爆牌 + CardPoint []int32 //点数 + BetCoin int64 //下注 + GainCoinNoTax int64 //总输赢分(税后) + IsWin int32 //输赢 1赢 0平 -1输 +} + +type BlackJackPlayer struct { + UserId int32 //玩家ID + UserIcon int32 //玩家头像 + Platform string `json:"-"` + Channel string `json:"-"` + Promoter string `json:"-"` + PackageTag string `json:"-"` + InviterId int32 `json:"-"` + WBLevel int32 //黑白名单等级 + IsRob bool //是否是机器人 + Flag int //标识 + IsFirst bool //是否第一次 + Hands []BlackJackCardInfo //牌值 + IsWin int32 //输赢 + GainCoinNoTax int64 //总输赢分(税后) + Tax int64 //税,不一定有值,只是作为一个临时变量使用 + BaoCoin int64 //保险金 + BaoChange int64 //保险金输赢分 + BetCoin int64 //下注额 + BetChange int64 //下注输赢分 + Seat int //座位号 +} + +type DezhouPots struct { + BetTotal int64 //边池下注 + Player []DezhouPotPlayer //边池的玩家 +} +type DezhouPotPlayer struct { + Snid int32 //玩家ID + IsWin int32 //边池输赢 +} + +// 德州牌局记录 +type DeZhouUserOp struct { + Snid int32 // 操作人 + Op int32 // 操作类型 见 dezhoupoker.proto + Stage int // 所处牌局阶段 见 constants.go + Chip int64 // 操作筹码, (不下注为0) + ChipOnTable int64 // 操作后桌子上筹码 + Round int32 // 轮数 + Sec float64 // 操作时距离本局开始时的秒数 + TargetId int32 // 操作对象ID(没其他玩家为对象为0) +} + +// 德州 +type DezhouType struct { + RoomId int32 //房间ID + RoomType int32 //房间类型 + NumOfGames int32 //当前局数 + BankId int32 //庄家ID + PlayerCount int //玩家数量 + BaseScore int32 //底分 + BaseCards []int32 //公牌 只限于德州用 + PlayerData []DezhouPerson //玩家信息 + Pots []DezhouPots //边池情况 + Actions string //牌局记录 + UserOps []*DeZhouUserOp //牌局记录new +} +type DezhouPerson struct { + UserId int32 //玩家ID + UserIcon int32 //玩家头像 + Platform string `json:"-"` + Channel string `json:"-"` + Promoter string `json:"-"` + PackageTag string `json:"-"` + InviterId int32 `json:"-"` + WBLevel int32 //黑白名单等级 + IsRob bool //是否是机器人 + IsFirst bool //是否第一次 + IsLeave bool //中途离开 + InitCard []int32 //初始牌值 + Cardinfo []int32 //牌值 + IsWin int32 //输赢 + GainCoinNoTax int64 //总输赢分(税后) + Tax int64 //税,不一定有值,只是作为一个临时变量使用 + BetTotal int64 //用户当局总下注 + IsAllIn bool //是否全下 + RoundFold int32 //第几轮弃牌 + CardInfoEnd []int32 //结算时的牌型 + Seat int //座位号 +} + +// tienlen +type TienLenType struct { + GameId int //游戏id + BaseScore int32 //底分 + PlayerData []TienLenPerson //玩家信息 +} + +type TienLenPerson struct { + UserId int32 //玩家ID + IsRob bool //是否是机器人 + BillCoin int64 //最终得分(税后) + BillTaxCoin int64 //最终税收 + + BombCoin int64 //炸弹输赢分(税后) + BombTaxCoin int64 //炸弹税收 + CardInfoEnd []int32 //结算时的牌型 +} + +// chesstitians +type ChesstitiansType struct { + GameId int //游戏id + RoomId int32 //房间ID + RoomType int32 //房间类型 + NumOfGames int32 //当前局数 + BankId int32 //房主ID + PlayerCount int //玩家数量 + BaseScore int32 //底分 + TaxRate int32 //税率(万分比) + PlayerData []*ChesstitiansPerson //玩家信息 + RoomMode int +} +type ChesstitiansPerson struct { + UserId int32 //玩家ID + UserIcon int32 //玩家头像 + Platform string `json:"-"` + Channel string `json:"-"` + Promoter string `json:"-"` + PackageTag string `json:"-"` + InviterId int32 `json:"-"` + WBLevel int32 //黑白名单等级 + IsRob bool //是否是机器人 + IsFirst bool //是否第一次 + IsLeave bool //中途离开 + IsWin int32 //输赢 + Seat int //座位号 + GainCoin int64 //手牌输赢分(税后) + GainTaxCoin int64 //手牌税收 +} + +// tala +type TaLaType struct { + GameId int //游戏id + RoomId int32 //房间ID + RoomType int32 //房间类型 + NumOfGames int32 //当前局数 + BankId int32 //房主ID + PlayerCount int //玩家数量 + BaseScore int32 //底分 + TaxRate int32 //税率(万分比) + RoomMode int + PlayerData []TaLaPerson //玩家信息 +} +type TaLaPerson struct { + UserId int32 //玩家ID + UserIcon int32 //玩家头像 + Platform string `json:"-"` + Channel string `json:"-"` + Promoter string `json:"-"` + PackageTag string `json:"-"` + InviterId int32 `json:"-"` + WBLevel int32 //黑白名单等级 + IsRob bool //是否是机器人 + IsFirst bool //是否第一次 + IsLeave bool //中途离开 + Seat int //座位号 + ChiCoin int64 //吃输赢分(税后) + BillCoin int64 //最终得分(税后) + ChiTaxCoin int64 //吃税收 + BillTaxCoin int64 //最终税收 + IsHu bool //胡 + IsWin int32 //胜负 0平 1胜 2负 + IsLoseHu bool //包赔 + Phoms [][]int32 //phom + IsNoPhom bool //瘪 + Cards []int32 //手牌 + CardsValue int32 //点数 + OpPhom []int32 //寄 + TaLaPersonOp []TaLaPersonOp //操作信息 +} +type TaLaPersonOp struct { + Round int32 + MoCard int32 + ChuCard int32 + ChiCard int32 + Cards []int32 +} + +// samloc +type SamLocType struct { + GameId int //游戏id + RoomId int32 //房间ID + RoomType int32 //房间类型 + NumOfGames int32 //当前局数 + BankId int32 //房主ID + PlayerCount int //玩家数量 + BaseScore int32 //底分 + TaxRate int32 //税率(万分比) + PlayerData []SamLocPerson //玩家信息 + RoomMode int +} +type SamLocPerson struct { + UserId int32 //玩家ID + UserIcon int32 //玩家头像 + Platform string `json:"-"` + Channel string `json:"-"` + Promoter string `json:"-"` + PackageTag string `json:"-"` + InviterId int32 `json:"-"` + WBLevel int32 //黑白名单等级 + IsRob bool //是否是机器人 + IsFirst bool //是否第一次 + IsLeave bool //中途离开 + IsWin int32 //输赢 + Seat int //座位号 + GainCoin int64 //手牌输赢分(税后) + BombCoin int64 //炸弹输赢分(税后) + BillCoin int64 //最终得分(税后) + GainTaxCoin int64 //手牌税收 + BombTaxCoin int64 //炸弹税收 + BillTaxCoin int64 //最终税收 + DelOrderCards map[int][]int32 //已出牌 + CardInfoEnd []int32 //结算时的牌型 + IsTianHu bool //是否天胡 +} + +// 娃娃机 每局记录 +type ClawdollResultType struct { + //all + RoomId int32 //房间Id + MachineId int32 //娃娃机Id + PlayerSnid int32 //玩家id + BeforeClawdollItemNum int64 //变化前娃娃币 + AfterClawdollItemNum int64 //变化后娃娃币 + IsWin bool //是否成功 + Channel string //渠道 + Name string //场次名字 +} diff --git a/statistics/task/task/gamerate.go b/statistics/task/task/gamerate.go new file mode 100644 index 0000000..1fbf2de --- /dev/null +++ b/statistics/task/task/gamerate.go @@ -0,0 +1,110 @@ +package task + +import ( + "encoding/json" + "go.mongodb.org/mongo-driver/bson" + "mongo.games.com/goserver/core/logger" + "slices" +) + +// 场次平均倍数 + +func PlayerGameRate(plt string, startTime, endTime string, gamefreeid int) (total, bombTotal, remain2Total int, rateAvg, bombRateAvg float64, err error) { + var totalRate, totalBombRate float64 + err = GameDetailFunc(plt, startTime, endTime, gamefreeid, func(data bson.M) error { + rate, isBomb, bombRate, remain2 := GameDetailRate(data) + total++ + if isBomb { + bombTotal++ + } + totalRate += rate + totalBombRate += bombRate + if remain2 { + remain2Total++ + } + return nil + }) + if total > 0 { + rateAvg = totalRate / float64(total) + } + if bombTotal > 0 { + bombRateAvg = totalBombRate / float64(bombTotal) + } + return +} + +// rate 赢分/底分 +// isBomb 是否有炸弹 +// bombRate 炸弹倍数,炸弹赢分/底分 +// remain2 是否有剩余2 +func GameDetailRate(data bson.M) (rate float64, isBomb bool, bombRate float64, remain2 bool) { + if data == nil { + return + } + gameid := data["gameid"].(int32) + gamefreeid := data["gamefreeid"].(int32) + logger.Logger.Tracef("GameDetail gameid:%d, gamefreeid:%d", gameid, gamefreeid) + + detail := data["gamedetailednote"] + if detail == nil { + return + } + + raw := new(RabbitMQDataRaw) + if err := json.Unmarshal([]byte(detail.(string)), raw); err != nil { + logger.Logger.Errorf("GameDetailCount Unmarshal 1 error:%v %v", err, gameid) + return + } + + switch gameid { + case 207, 208, 209, 210, 240, 241, 242, 243, 244, 245, 246, 247: // tienlen + d := new(TienLenType) + b, err := json.Marshal(raw.Data) + if err != nil { + logger.Logger.Errorf("GameDetailCount Marshal error:%v %v", err, gameid) + return + } + if err := json.Unmarshal(b, d); err != nil { + logger.Logger.Errorf("GameDetailCount Unmarshal 2 error:%v %v", err, gameid) + return + } + + for _, v := range d.PlayerData { + if v.BillCoin > 0 { + rate = float64(v.BillCoin+v.BillTaxCoin) / float64(d.BaseScore) + } + if v.BombCoin > 0 { + isBomb = true + bombRate = float64(v.BombCoin+v.BombTaxCoin) / float64(d.BaseScore) + } + if slices.ContainsFunc(v.CardInfoEnd, func(i int32) bool { + switch i { + case 51, 38, 25, 12: + return true + } + return false + }) { + remain2 = true + } + } + + case 211, 212, 213, 214: + d := new(ThirteenWaterType) + b, err := json.Marshal(raw.Data) + if err != nil { + logger.Logger.Errorf("GameDetailCount Marshal error:%v %v", err, gameid) + return + } + if err := json.Unmarshal(b, d); err != nil { + logger.Logger.Errorf("GameDetailCount Unmarshal 2 error:%v %v", err, gameid) + return + } + + for _, v := range d.PlayerData { + if v.AllScore > 0 { + rate = float64(v.AllScore) / float64(d.BaseScore) + } + } + } + return +} diff --git a/statistics/task/task/gametime.go b/statistics/task/task/gametime.go new file mode 100644 index 0000000..3c184c7 --- /dev/null +++ b/statistics/task/task/gametime.go @@ -0,0 +1,104 @@ +package task + +import ( + "context" + "errors" + "fmt" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + "mongo.games.com/game/common" + mongomodel "mongo.games.com/game/statistics/modelmongo" + "mongo.games.com/goserver/core/logger" + mymongo "mongo.games.com/goserver/core/mongox" +) + +// 新用户平均游戏时长 + +// 返回 总游戏时长,总局数,错误 +func NewPlayerGameTime(plt string, ids []int, startTime, endTime string, gamefreeid int) (int, int, error) { + s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime) + c, err := mymongo.GetLogCollection(plt, mongomodel.LogGamePlayerListLog) + if err != nil { + return 0, 0, err + } + + c2, err := mymongo.GetLogCollection(plt, "log_gamedetailed") + if err != nil { + return 0, 0, err + } + + var ret int + var total int + for _, v := range ids { + // 查询玩家游戏时长 + where := bson.M{"snid": v, "time": bson.M{"$gte": s, "$lt": e}} + if gamefreeid > 0 { + where["gamefreeid"] = gamefreeid + } + cur, err := c.Find(context.TODO(), where, options.Find().SetProjection(bson.M{"gamedetailedlogid": 1})) + if err != nil { + logger.Logger.Errorf("find player gamedetailedlogid get err: %v", err) + return 0, 0, err + } + for cur.TryNext(context.TODO()) { + var vv struct{ Gamedetailedlogid string } + if err = cur.Decode(&vv); err != nil { + logger.Logger.Errorf("find player gamedetailedlogid decode err: %v", err) + cur.Close(context.Background()) + return 0, 0, err + } + // 查询游戏时长 + var res2 struct{ Gametiming int } + r := c2.FindOne(context.TODO(), bson.M{"logid": vv.Gamedetailedlogid}, options.FindOne().SetProjection(bson.M{"gametiming": 1})) + if r.Err() != nil && !errors.Is(r.Err(), mongo.ErrNoDocuments) { + logger.Logger.Errorf("find game time get err: %v", err) + cur.Close(context.Background()) + return 0, 0, err + } + if err := r.Decode(&res2); err != nil { + logger.Logger.Errorf("find game time decode err: %v", err) + cur.Close(context.Background()) + return 0, 0, err + } + ret += res2.Gametiming + total++ + } + cur.Close(context.Background()) + } + + return ret, total, nil +} + +// NewPlayerGameTimeAvg 新用户平均游戏时长 +// 新用户平均游戏时长(不算大厅时间):当天注册的玩家在房间中的总时长/当天注册总人数 +// 返回 参与人数,游戏时长 +func NewPlayerGameTimeAvg(plt string, startTime, endTime string, gamefreeid int) (int, int, error) { + s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime) + if s.IsZero() || e.IsZero() { + return 0, 0, fmt.Errorf("time format error") + } + ids, err := GetNewPayerIds(plt, startTime, endTime) + if err != nil { + return 0, 0, err + } + if len(ids) == 0 { + return 0, 0, nil + } + a, _, err := NewPlayerGameTime(plt, ids, startTime, endTime, gamefreeid) + if err != nil { + return 0, 0, err + } + if len(ids) == 0 { + return 0, 0, nil + } + + b, err := PlayingGameCount(plt, ids, startTime, endTime, gamefreeid) + if err != nil { + return 0, 0, err + } + if b == 0 { + return 0, 0, nil + } + return b, a, err +} diff --git a/statistics/task/task/rechargeoffline.go b/statistics/task/task/rechargeoffline.go new file mode 100644 index 0000000..fe31c2d --- /dev/null +++ b/statistics/task/task/rechargeoffline.go @@ -0,0 +1,62 @@ +package task + +import ( + "context" + "fmt" + "go.mongodb.org/mongo-driver/mongo/options" + + "go.mongodb.org/mongo-driver/bson" + "mongo.games.com/game/common" + mymongo "mongo.games.com/goserver/core/mongox" +) + +type RechargeOfflineData struct { + Snid int32 + Coin int64 +} + +func OfflineCoin(plt string, startTime, endTime string) (res []*RechargeOfflineData, err error) { + s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime) + if s.IsZero() || e.IsZero() { + return nil, fmt.Errorf("time format error") + } + c, err := mymongo.GetLogCollection(plt, "log_dbshop") + if err != nil { + return nil, err + } + cur, err := c.Find(context.TODO(), bson.M{"ts": bson.M{"$gte": s.Unix(), "$lt": e.Unix()}, "consume": 3, "state": 1}) + if err != nil { + return nil, err + } + defer cur.Close(context.Background()) + + var list []struct{ Snid int32 } + if err = cur.All(context.Background(), &list); err != nil { + return nil, err + } + + snids := map[int32]struct{}{} + + for _, v := range list { + if _, ok := snids[v.Snid]; ok { + continue + } + snids[v.Snid] = struct{}{} + + // 最后金币数量 + c, err := mymongo.GetLogCollection(plt, "log_coinex") + if err != nil { + return nil, err + } + one := c.FindOne(context.TODO(), bson.M{"snid": v.Snid, "cointype": 0, "ts": bson.M{"$lt": e.Unix()}}, options.FindOne().SetSort(bson.M{"ts": -1})) + if one.Err() != nil { + return nil, one.Err() + } + var data struct{ Restcount int64 } + if err = one.Decode(&data); err != nil { + return nil, err + } + res = append(res, &RechargeOfflineData{Snid: v.Snid, Coin: data.Restcount}) + } + return +} diff --git a/statistics/task/task/robotwin.go b/statistics/task/task/robotwin.go new file mode 100644 index 0000000..37fa8cf --- /dev/null +++ b/statistics/task/task/robotwin.go @@ -0,0 +1,138 @@ +package task + +import ( + "encoding/json" + "go.mongodb.org/mongo-driver/bson" + "mongo.games.com/goserver/core/logger" + "sort" +) + +// RobotWinRate 机器人胜利 +// 返回 玩家胜利局数,总局数 +func RobotWinRate(plt string, startTime, endTime string, gamefreeid int) (a, b int, err error) { + err = GameDetailFunc(plt, startTime, endTime, gamefreeid, func(data bson.M) error { + isPlayerWin, to := GameDetailRobot(data) + if isPlayerWin { + a++ + } + b += to + return nil + }) + return +} + +func GameDetailRobot(data bson.M) (isPlayerWin bool, to int) { + if data == nil { + return + } + gameid := data["gameid"].(int32) + gamefreeid := data["gamefreeid"].(int32) + logger.Logger.Tracef("GameDetailRobot gameid:%d, gamefreeid:%d", gameid, gamefreeid) + + detail := data["gamedetailednote"] + if detail == nil { + return + } + + raw := new(RabbitMQDataRaw) + if err := json.Unmarshal([]byte(detail.(string)), raw); err != nil { + logger.Logger.Errorf("GameDetailRobot Unmarshal 1 error:%v %v", err, gameid) + return + } + + switch gameid { + case 207, 208, 209, 210, 240, 241, 242, 243, 244, 245, 246, 247: // tienlen + data := new(TienLenType) + b, err := json.Marshal(raw.Data) + if err != nil { + logger.Logger.Errorf("GameDetailCount Marshal error:%v %v", err, gameid) + return + } + if err := json.Unmarshal(b, data); err != nil { + logger.Logger.Errorf("GameDetailCount Unmarshal 2 error:%v %v", err, gameid) + return + } + + var has, hasPlayer bool + for _, v := range data.PlayerData { + if v.IsRob { + has = true + } + if !v.IsRob { + hasPlayer = true + } + } + if !has || !hasPlayer { + // 没有机器人 + return + } + + sort.Slice(data.PlayerData, func(i, j int) bool { + a, b := data.PlayerData[i].BillCoin+data.PlayerData[i].BillTaxCoin, data.PlayerData[j].BillCoin+data.PlayerData[j].BillTaxCoin + if a != b { + return a > b + } + if data.PlayerData[i].IsRob != data.PlayerData[j].IsRob { + return !data.PlayerData[i].IsRob + } + return data.PlayerData[i].UserId < data.PlayerData[j].UserId + }) + + for k, v := range data.PlayerData { + if !v.IsRob && v.BillCoin > 0 && k <= 1 { + isPlayerWin = true + break + } + } + + to = 1 + + case 211, 212, 213, 214: + data := new(ThirteenWaterType) + b, err := json.Marshal(raw.Data) + if err != nil { + logger.Logger.Errorf("GameDetailCount Marshal error:%v %v", err, gameid) + return + } + if err := json.Unmarshal(b, data); err != nil { + logger.Logger.Errorf("GameDetailCount Unmarshal 2 error:%v %v", err, gameid) + return + } + + var has, hasPlayer bool + for _, v := range data.PlayerData { + if v.IsRob { + has = true + } + if !v.IsRob { + hasPlayer = true + } + } + if !has || !hasPlayer { + // 没有机器人 + return + } + + sort.Slice(data.PlayerData, func(i, j int) bool { + a, b := data.PlayerData[i].AllScore, data.PlayerData[j].AllScore + if a != b { + return a > b + } + if data.PlayerData[i].IsRob != data.PlayerData[j].IsRob { + return !data.PlayerData[i].IsRob + } + return data.PlayerData[i].UserId < data.PlayerData[j].UserId + }) + + for k, v := range data.PlayerData { + if !v.IsRob && v.AllScore > 0 && k <= 1 { + isPlayerWin = true + break + } + } + + to = 1 + } + + return +}