From 801200f4bd011954da473b581886d53f050d4f2e Mon Sep 17 00:00:00 2001 From: sk <123456@qq.com> Date: Tue, 3 Dec 2024 13:13:24 +0800 Subject: [PATCH] add statistics --- common/time.go | 5 + go.mod | 10 +- go.sum | 15 +- statistics/.gitignore | 29 + statistics/README.md | 7 + statistics/build_linux.bat | 8 + statistics/constant/constant.go | 8 + statistics/etc/config.yaml | 27 + statistics/etc/mongo.yaml | 53 + statistics/etc/mysql.yaml | 38 + statistics/logger.xml | 22 + statistics/main.go | 212 +++ .../modelmongo/log_gameplayerlistlog.go | 47 + statistics/modelmongo/log_invitescore.go | 19 + statistics/modelmongo/log_item.go | 27 + statistics/modelmongo/log_login.go | 33 + statistics/modelmongo/user_account.go | 24 + statistics/modelmysql/bankrupt.go | 12 + statistics/modelmysql/log_invitescore.go | 26 + statistics/modelmysql/log_itemgain.go | 16 + statistics/modelmysql/log_login.go | 26 + statistics/modelmysql/log_login_mid.go | 6 + statistics/modelmysql/log_mid.go | 11 + statistics/modelmysql/tables.go | 17 + statistics/modelmysql/user_account.go | 18 + statistics/modelmysql/user_id.go | 9 + statistics/modelmysql/user_login.go | 29 + statistics/mq/handler.go | 1 + statistics/mq/readme | 1 + statistics/static/readme | 1 + statistics/static/user_login.go | 371 ++++ statistics/syn/datasync.go | 102 ++ statistics/syn/log_invitescore.go | 291 +++ statistics/syn/log_itemgain.go | 61 + statistics/syn/log_login.go | 181 ++ statistics/syn/readme | 1 + statistics/syn/user_account.go | 105 ++ statistics/task/build_linux.bat | 5 + statistics/task/etc/DB_GameFree.dat | Bin 0 -> 24144 bytes statistics/task/etc/config.yaml | 221 +++ statistics/task/etc/mongo.yaml | 53 + statistics/task/etc/mysql.yaml | 38 + statistics/task/excelmgr.go | 88 + statistics/task/gamefree/db_gamefree.go | 77 + statistics/task/logger.xml | 22 + statistics/task/main.go | 427 +++++ statistics/task/readme | 1 + statistics/task/task/bankruptcy.go | 189 ++ statistics/task/task/bankruptoffline.go | 153 ++ statistics/task/task/coin.go | 77 + statistics/task/task/ctrlrate.go | 214 +++ statistics/task/task/gamebankruptcy.go | 69 + statistics/task/task/gamecount.go | 60 + statistics/task/task/gamelogtype.go | 1625 +++++++++++++++++ statistics/task/task/gamerate.go | 110 ++ statistics/task/task/gametime.go | 104 ++ statistics/task/task/rechargeoffline.go | 62 + statistics/task/task/robotwin.go | 138 ++ 58 files changed, 5593 insertions(+), 9 deletions(-) create mode 100644 statistics/.gitignore create mode 100644 statistics/README.md create mode 100644 statistics/build_linux.bat create mode 100644 statistics/constant/constant.go create mode 100644 statistics/etc/config.yaml create mode 100644 statistics/etc/mongo.yaml create mode 100644 statistics/etc/mysql.yaml create mode 100644 statistics/logger.xml create mode 100644 statistics/main.go create mode 100644 statistics/modelmongo/log_gameplayerlistlog.go create mode 100644 statistics/modelmongo/log_invitescore.go create mode 100644 statistics/modelmongo/log_item.go create mode 100644 statistics/modelmongo/log_login.go create mode 100644 statistics/modelmongo/user_account.go create mode 100644 statistics/modelmysql/bankrupt.go create mode 100644 statistics/modelmysql/log_invitescore.go create mode 100644 statistics/modelmysql/log_itemgain.go create mode 100644 statistics/modelmysql/log_login.go create mode 100644 statistics/modelmysql/log_login_mid.go create mode 100644 statistics/modelmysql/log_mid.go create mode 100644 statistics/modelmysql/tables.go create mode 100644 statistics/modelmysql/user_account.go create mode 100644 statistics/modelmysql/user_id.go create mode 100644 statistics/modelmysql/user_login.go create mode 100644 statistics/mq/handler.go create mode 100644 statistics/mq/readme create mode 100644 statistics/static/readme create mode 100644 statistics/static/user_login.go create mode 100644 statistics/syn/datasync.go create mode 100644 statistics/syn/log_invitescore.go create mode 100644 statistics/syn/log_itemgain.go create mode 100644 statistics/syn/log_login.go create mode 100644 statistics/syn/readme create mode 100644 statistics/syn/user_account.go create mode 100644 statistics/task/build_linux.bat create mode 100644 statistics/task/etc/DB_GameFree.dat create mode 100644 statistics/task/etc/config.yaml create mode 100644 statistics/task/etc/mongo.yaml create mode 100644 statistics/task/etc/mysql.yaml create mode 100644 statistics/task/excelmgr.go create mode 100644 statistics/task/gamefree/db_gamefree.go create mode 100644 statistics/task/logger.xml create mode 100644 statistics/task/main.go create mode 100644 statistics/task/readme create mode 100644 statistics/task/task/bankruptcy.go create mode 100644 statistics/task/task/bankruptoffline.go create mode 100644 statistics/task/task/coin.go create mode 100644 statistics/task/task/ctrlrate.go create mode 100644 statistics/task/task/gamebankruptcy.go create mode 100644 statistics/task/task/gamecount.go create mode 100644 statistics/task/task/gamelogtype.go create mode 100644 statistics/task/task/gamerate.go create mode 100644 statistics/task/task/gametime.go create mode 100644 statistics/task/task/rechargeoffline.go create mode 100644 statistics/task/task/robotwin.go 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/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 0000000000000000000000000000000000000000..9ed570013bf427b6cf7d22d1081b9237d012c275 GIT binary patch literal 24144 zcmdU1du$ZP9cE_F=kyiq!sB1wq?L4YUbW zt4eLxV23lo#TWy|IEf7=zCeI87&y$!F@(C0BnI**Bx+6KJD*7!zz`BeP1A2?cV~9j z`!YB_2Z4QSvpe56^UXKE`OVD6`iOdI`yb=8?tgI6;$JRWoL{kRxT!1GzH0d3>TGT1 zjvwFutyv|x_32#QW7*c8(ice?Nxa@p=F{(z1*Gg3N|Ky=l{S#xmgy<>B~2Rc%aHUi z?IT6yW%$!i$SFdmohQ+@diM7N5+QA^WEmyXR#HlpMjFL!y*2l=&|lA>3X zG$nmhsVKXegnH>1@xs z3y12gMs|{Etz*krtrgbDUL?x zpEf+tJyg4hvGkyu%$f-Tt+3IWf`HB~Sc~?)^ALMXaI9u?>!TP+y`W1`3FuBq0XpK9 z@dRo1Skn>?^rp5(_`}A}V7V6qsUgAOx)mg&%})Ff7Z#<74tHOtDS`GLhybnE06&W_$*h64SgKWIitULiDs| z)mj-nx#uD-VRm>Z$g~2}5ff}#h@Kj~pi8F`(0!eXPY8oD5EE`xV6fJUf%GTAz|)_2 zRx3o#VdCux(NmKb1L;bFfu}2BI52WqlN&v)^8zoeNPzdWB0YNI^7pbFOhciI=)~muFV%DoHf=hA}aj+49;EF~MQkDWbj+ zl8&@9K_}48WQwbZ51Aq{6#Y0eTp;ykNK(?*1Swx%lesSc_3MfEwHJsw6q1xQI6=zS z;4De6Nt|C|6)_ub1;{YglJ6C?r+t}vM~aM|O}Mf#fH;_GXL%aI{o>-Og&?M!g- zwX=d`%Mh>a^`{|COwjN(u|nm|5U=g?XCl2zFbVRmf@RQ7V$13v;o6LT%_EJ>ucT~C3H7{gAy zt;9cNlY#IoRo4t&A@S+CXEx&mL9Tw?NY|lR)Aa)? zxlB{;@inDultjk?IzPfss3ggmJp|`d*lH_2^${Ys8Qj`%9~QVVt;aq!R2eo)UECbe zE=3xS7NPDed%slNZfyP1w}G+kU+67&m6i8X#pQg8-edl$G~73+;8A&H;YDTP`^pDo zS!4w%yQ8w~_Od&x=2e#8fdv$NoI9_)to#n_gTUvz=T+cyCI6h5SBcNavfN%iuL_?M z?9-i<^Ah;XmR@jCgvNX3>-dTZ1827Qus$?YA9Qi|A zB$ke(OEQtRXzbrqZFH&J{D#%posSQ9HfJ|4mzB%b?~KaT$0=v+&erue*csRMeg>gO z?~7j?eEloh`Lee6=Au3=)my?Iz_-+it#$po+P)N~f>Fkxf~S2#YIuaIYh7x1DAStT zyMN@!#_Z1RvWD4;qFKYp`Ht1^mzgZqJ%bHX zu2AI!P(Zb9L1?%yg{}Mgc}%y7Y-At3S~5!bH!E6?`}0R={3d%h`_YmG zOP0=x4rN+~4sR@78A;@w9nGj$GXx^%S*N<*p7oLW$2gD6qR7qO@NkGXyJfqCXX#%K zc=SB)N@F@y6cHPuits(k1o)AOupMjT-U#(}mk2H0%7IWhulJv^-6)DKKdu%YgRv89 z-kunzqZaekO-`hi9_2t90aB0dGZ9F)dLzwlIy|zm)``^8ksL^ifYhVcTsKnqe%BDr z?9Is5R}W?CvWMETn>G#g^vojVrKhpTG4@nmL;H+o()Raem=ivxNpg-c0gJGxT38dA zX7+A&pdJ5nVC(mAOt)cMoqn@CV^khALiH1{r z-|nL}u>L2a!lHc|ix=)_DKLT0=tL9;73^+!uM%l3MRj&aDpu=L_hQ$eYtanMQ zmYxKxICKrH_=?fV^MHq~T2=PHdv`xSkDYGLw)~dQ9xdl=!h+p_B?x!0RT8YFGd1wz zP39pdxw2i%CE!)F!DpMo;@ypf26w!3C0_dmHg=>6fY~=3YVtp;Vd-Tteup;tSPJhb zh-pI`2I71|a}AXYTX=SNwapf4w;Q(b5)LnIm5CnXbr*%Sg+6zv6{P~|RI-I*?r19| z+Nom;$KA2sC9zH+TX@bLY?TB%wQS*pJKnhx?^Lq|927dKEhF*Ih-ks_avbZt_GMCr zJ^D|%ImI=F4lL&2V`OmXV0{^m5j`Bl&inxA+4Smc&we($u|Rh^hjG$++@XY-D+yu7 zc`Dz4W1z<&q0>R=Tc0$}DO=DN$Vu{4zLCUW0O_22!9zM5pUCcLuuA6@CY@%>x{MrT z3L~920-#IjSfEcC>AV?)UP#A+e(FeP8KiUJ62%HfO-D{^WD}>=O|wc+=ebNKmt0-? zuSg_+lP1V z0D{_Qvb9~g*80+aM-r`PmH9*@pQB+VlcJ{v+lDQe`uMk0L#Zxbt(brea? zsaUuYuM$Hd7K<$Xl)6-iQT(6OQ_kYiEJKGjF!vP2^9~@2=Tn9O%ozFP29N-t$^qcE zZ-fSLdJF*Zq6PtgYghol+#`kQsxf2H0aR33RDv3qT@ez1hLc221ajS9AmHJViGWRe zQY3W@Q9T+OvwWit>=16gEhL2H=oe3CSdiXPKXhBgZY)qvb#4~#j%SfUVq!_ z6Z!X@;=oV*#Zl=i4p{f73k0bQ0l`#;4#BPr9fXfE0zq7M10iG~K$x=7LD*$s)P%Pz z{6V0zUU2~-H6cKln&2RunlP)v(=z-~KoZp_4lqePn$GRsGn83B+_TLpi8M|0{>Lhb z0jKx-^_10f_K zK$w!yL0~(RWeDLQ#&*yd(!c{ztZC{&YYcBZl1nz{lFos=izYhWv9?Ag>1+-B#Mv6f z$vO9~C$o#hFjD2)ATaPo7ZkEV1PZf3bQHXMhz8Md*cwE~@w-?^95BwcZa74n2pndc za2$FM5yLvi;b;?;S4cOR9TExD(f$T&mt49#_e5*%vAU7g48pzSn zs%Zn(7WMdwE!k@C;gy_x$<`&n(nGvT7IgZ=ST$HQazPm2gP|1X3*@TOmXdF*yU zA>|=ZnDXE#Z1V7cVV8$L49H`T3k)d_0mGCBhhdk82M)VD{Bb}Y-7Yw!JOmC?9vp{L z9v(>S^6*FUEA`p`F?HO#ME50Yk+_mY;=-rMuLTr&uH~>S@<$IW zSn%LdR^&dKXdbl|i7QzoF1(8@FY_((A@x!oMV1&WfABk2+MO~Q=I=4 z_g8B5{v)y(zrW-+k1S#(W@uvYvXvmNWQn-&DbYV(U#eEGy38^Rspa^aYwekZ$h}j) zV)N?3NJ4wfnq 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 +}