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 0000000..9ed5700
Binary files /dev/null and b/statistics/task/etc/DB_GameFree.dat differ
diff --git a/statistics/task/etc/config.yaml b/statistics/task/etc/config.yaml
new file mode 100644
index 0000000..dc134c6
--- /dev/null
+++ b/statistics/task/etc/config.yaml
@@ -0,0 +1,221 @@
+StartTime: "2024-11-26T00:00:00+08:00"
+EndTime: "2024-11-27T00:00:00+08:00"
+
+Switch: # 1: open, 0: close
+ - 0 # 新用户游戏破产率
+ - 0 # 新用户平均游戏时长
+ - 0 # 新用户平均局数
+ - 0 # 平均倍数
+ - 1 # 活跃破产率
+ - 1 # 控输赢胜率
+ - 1 # 机器人胜率
+ - 1 # 人均获得金币
+ - 1 # 破产后离线
+ - 1 # 充值玩家金币余额
+
+Gamefreeids:
+ - 2070001
+ - 2070002
+ - 2070003
+ - 2080001
+ - 2080002
+ - 2080003
+ - 2090001
+ - 2090002
+ - 2090003
+ - 2100001
+ - 2100002
+ - 2100003
+ - 2400001
+ - 2400002
+ - 2400003
+ - 2400004
+ - 2400005
+ - 2400006
+ - 2440001
+ - 2440002
+ - 2440003
+ - 2440004
+ - 2440005
+ - 2440006
+ - 2410001
+ - 2410002
+ - 2410003
+ - 2410004
+ - 2410005
+ - 2410006
+ - 2450001
+ - 2450002
+ - 2450003
+ - 2450004
+ - 2450005
+ - 2450006
+ - 2420001
+ - 2420002
+ - 2420003
+ - 2420004
+ - 2420005
+ - 2420006
+ - 2460001
+ - 2460002
+ - 2460003
+ - 2460004
+ - 2460005
+ - 2460006
+ - 2430001
+ - 2430002
+ - 2430003
+ - 2430004
+ - 2430005
+ - 2430006
+ - 2470001
+ - 2470002
+ - 2470003
+ - 2470004
+ - 2470005
+ - 2470006
+ - 2150001
+ - 2160001
+ - 2170001
+ - 2180001
+ - 5210001
+ - 5210002
+ - 5210003
+ - 5210004
+ - 5210005
+ - 2110001
+ - 2110002
+ - 2110003
+ - 2110004
+ - 2110005
+ - 2110006
+ - 2120001
+ - 2120002
+ - 2120003
+ - 2120004
+ - 2120005
+ - 2120006
+ - 2130001
+ - 2130002
+ - 2130003
+ - 2140001
+ - 2140002
+ - 2140003
+ - 6070001
+ - 3010001
+ - 3010002
+ - 3010003
+ - 3010004
+ - 3020001
+ - 3020002
+ - 3020003
+ - 3020004
+ - 3030001
+ - 3030002
+ - 3030003
+ - 3030004
+ - 3040001
+ - 3040002
+ - 3040003
+ - 3050001
+ - 3050002
+ - 3050003
+ - 3060001
+ - 3060002
+ - 3060003
+ - 3060004
+ - 3070001
+ - 3070002
+ - 3070003
+ - 3070004
+ - 3080001
+ - 3090001
+ - 3100001
+ - 3110001
+ - 3120001
+
+Tienlen:
+ - 2070001
+ - 2070002
+ - 2070003
+ - 2080001
+ - 2080002
+ - 2080003
+ - 2090001
+ - 2090002
+ - 2090003
+ - 2100001
+ - 2100002
+ - 2100003
+ - 2400001
+ - 2400002
+ - 2400003
+ - 2400004
+ - 2400005
+ - 2400006
+ - 2440001
+ - 2440002
+ - 2440003
+ - 2440004
+ - 2440005
+ - 2440006
+ - 2410001
+ - 2410002
+ - 2410003
+ - 2410004
+ - 2410005
+ - 2410006
+ - 2450001
+ - 2450002
+ - 2450003
+ - 2450004
+ - 2450005
+ - 2450006
+ - 2420001
+ - 2420002
+ - 2420003
+ - 2420004
+ - 2420005
+ - 2420006
+ - 2460001
+ - 2460002
+ - 2460003
+ - 2460004
+ - 2460005
+ - 2460006
+ - 2430001
+ - 2430002
+ - 2430003
+ - 2430004
+ - 2430005
+ - 2430006
+ - 2470001
+ - 2470002
+ - 2470003
+ - 2470004
+ - 2470005
+ - 2470006
+
+
+Thirteen:
+ - 2110001
+ - 2110002
+ - 2110003
+ - 2110004
+ - 2110005
+ - 2110006
+ - 2120001
+ - 2120002
+ - 2120003
+ - 2120004
+ - 2120005
+ - 2120006
+ - 2130001
+ - 2130002
+ - 2130003
+ - 2140001
+ - 2140002
+ - 2140003
+
+
+
diff --git a/statistics/task/etc/mongo.yaml b/statistics/task/etc/mongo.yaml
new file mode 100644
index 0000000..7f9e42d
--- /dev/null
+++ b/statistics/task/etc/mongo.yaml
@@ -0,0 +1,53 @@
+global:
+ user:
+ HostName: 127.0.0.1
+ HostPort: 27017
+ Database: win88_global
+ Username:
+ Password:
+ Options:
+ log:
+ HostName: 127.0.0.1
+ HostPort: 27017
+ Database: win88_log
+ Username:
+ Password:
+ Options:
+ monitor:
+ HostName: 127.0.0.1
+ HostPort: 27017
+ Database: win88_monitor
+ Username:
+ Password:
+ Options:
+platforms:
+ 0:
+ user:
+ HostName: 127.0.0.1
+ HostPort: 27017
+ Database: win88_user_plt_000
+ Username:
+ Password:
+ Options:
+ log:
+ HostName: 127.0.0.1
+ HostPort: 27017
+ Database: win88_log_plt_000
+ Username:
+ Password:
+ Options:
+ 1:
+ user:
+ HostName: 127.0.0.1
+ HostPort: 27017
+ Database: win88_user_plt_001
+ Username:
+ Password:
+ Options:
+ log:
+ HostName: 127.0.0.1
+ HostPort: 27017
+ Database: win88_log_plt_001
+ Username:
+ Password:
+ Options:
\ No newline at end of file
diff --git a/statistics/task/etc/mysql.yaml b/statistics/task/etc/mysql.yaml
new file mode 100644
index 0000000..82d1fe3
--- /dev/null
+++ b/statistics/task/etc/mysql.yaml
@@ -0,0 +1,38 @@
+platforms:
+ global:
+ HostName: 127.0.0.1
+ HostPort: 3306
+ Database: win88_user
+ Username: root
+ Password: 123456
+ Options: charset=utf8mb4&parseTime=True&loc=Local
+ 0:
+ HostName: 127.0.0.1
+ HostPort: 3306
+ Database: win88_plt_000
+ Username: root
+ Password: 123456
+ Options: charset=utf8mb4&parseTime=True&loc=Local
+ 1:
+ HostName: 127.0.0.1
+ HostPort: 3306
+ Database: win88_plt_001
+ Username: root
+ Password: 123456
+ Options: charset=utf8mb4&parseTime=True&loc=Local
+ count: # 破产日志库
+ HostName: 127.0.0.1
+ HostPort: 3306
+ Database: dbmis_count
+ Username: root
+ Password: 123456
+ Options: charset=utf8mb4&parseTime=True&loc=Local
+
+# 最大空闲连接数
+MaxIdleConns: 10
+# 最大连接数
+MaxOpenConns: 100
+# 连接可复用的最大时间
+ConnMaxLifetime: 3600
+# 连接最大空闲时间
+ConnMaxIdletime: 0
\ No newline at end of file
diff --git a/statistics/task/excelmgr.go b/statistics/task/excelmgr.go
new file mode 100644
index 0000000..f324bb5
--- /dev/null
+++ b/statistics/task/excelmgr.go
@@ -0,0 +1,88 @@
+package main
+
+import (
+ "fmt"
+ "github.com/xuri/excelize/v2"
+)
+
+type ExcelData struct {
+ *excelize.File
+ Head []string
+ Index int
+ IndexCell int
+}
+
+func (e *ExcelData) SetHead(head []string) *ExcelData {
+ if e == nil {
+ return nil
+ }
+ e.Index = 1
+ e.SetSheetRow("Sheet1", "A1", &head)
+ return e
+}
+
+func (e *ExcelData) SetRow(row []string) *ExcelData {
+ if e == nil {
+ return nil
+ }
+ e.Index++
+ e.SetSheetRow("Sheet1", "A"+fmt.Sprintf("%d", e.Index), &row)
+ return e
+}
+
+func (e *ExcelData) SetCell(val interface{}) *ExcelData {
+ if e == nil {
+ return nil
+ }
+ e.IndexCell++
+ cell := fmt.Sprintf("%c%d", 'A'+e.IndexCell-1, e.Index)
+ e.SetCellValue("Sheet1", cell, val)
+ return e
+}
+
+func (e *ExcelData) NewLine() *ExcelData {
+ if e == nil {
+ return nil
+ }
+ e.Index++
+ e.IndexCell = 0
+ return e
+}
+
+type ExcelMgr struct {
+ List map[int]*ExcelData
+}
+
+func NewExcelMgr() *ExcelMgr {
+ return &ExcelMgr{
+ List: make(map[int]*ExcelData),
+ }
+}
+
+func (e *ExcelMgr) Register(id int, head []string) *ExcelData {
+ e.List[id] = &ExcelData{
+ File: excelize.NewFile(),
+ Head: head,
+ Index: 0,
+ }
+
+ e.List[id].NewSheet("Sheet1")
+
+ if len(head) > 0 {
+ e.List[id].SetHead(head)
+ }
+
+ return e.List[id]
+}
+
+func (e *ExcelMgr) Get(id int) *ExcelData {
+ return e.List[id]
+}
+
+func (e *ExcelMgr) Save(id int, fileName string) error {
+ d := e.List[id]
+ if d == nil {
+ return nil
+ }
+ return d.SaveAs(fileName)
+}
diff --git a/statistics/task/gamefree/db_gamefree.go b/statistics/task/gamefree/db_gamefree.go
new file mode 100644
index 0000000..81d8b55
--- /dev/null
+++ b/statistics/task/gamefree/db_gamefree.go
@@ -0,0 +1,77 @@
+package gamefree
+
+import (
+ "google.golang.org/protobuf/proto"
+ "mongo.games.com/game/protocol/server"
+ "os"
+)
+
+func init() {
+ buf, err := os.ReadFile("./etc/DB_GameFree.dat")
+ if err != nil {
+ panic(err)
+ }
+ err = PBDB_GameFreeMgr.unmarshal(buf)
+ if err != nil {
+ panic(err)
+ }
+}
+
+var PBDB_GameFreeMgr = &DB_GameFreeMgr{
+ Datas: &server.DB_GameFreeArray{},
+ pool: make(map[int32]*server.DB_GameFree),
+}
+
+type DB_GameFreeMgr struct {
+ Datas *server.DB_GameFreeArray
+ pool map[int32]*server.DB_GameFree
+}
+
+func (this *DB_GameFreeMgr) unmarshal(data []byte) error {
+ err := proto.Unmarshal(data, this.Datas)
+ if err == nil {
+ this.arrangeData()
+ }
+ return err
+}
+
+func (this *DB_GameFreeMgr) reunmarshal(data []byte) error {
+ newDatas := &server.DB_GameFreeArray{}
+ err := proto.Unmarshal(data, newDatas)
+ if err == nil {
+ for _, item := range newDatas.Arr {
+ existItem := this.GetData(item.GetId())
+ if existItem == nil {
+ this.pool[item.GetId()] = item
+ this.Datas.Arr = append(this.Datas.Arr, item)
+
+ } else {
+ *existItem = *item
+ }
+ }
+ }
+ return err
+}
+
+func (this *DB_GameFreeMgr) arrangeData() {
+ if this.Datas == nil {
+ return
+ }
+
+ dataArr := this.Datas.GetArr()
+ if dataArr == nil {
+ return
+ }
+
+ for _, data := range dataArr {
+ this.pool[data.GetId()] = data
+
+ }
+}
+
+func (this *DB_GameFreeMgr) GetData(id int32) *server.DB_GameFree {
+ if data, ok := this.pool[id]; ok {
+ return data
+ }
+ return nil
+}
diff --git a/statistics/task/logger.xml b/statistics/task/logger.xml
new file mode 100644
index 0000000..f6eb37b
--- /dev/null
+++ b/statistics/task/logger.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/statistics/task/main.go b/statistics/task/main.go
new file mode 100644
index 0000000..a582273
--- /dev/null
+++ b/statistics/task/main.go
@@ -0,0 +1,427 @@
+package main
+
+import (
+ "fmt"
+ "sort"
+ "time"
+
+ "github.com/spf13/viper"
+ "mongo.games.com/goserver/core/logger"
+ "mongo.games.com/goserver/core/mongox"
+ "mongo.games.com/goserver/core/mysqlx"
+ "mongo.games.com/goserver/core/viperx"
+
+ "mongo.games.com/game/statistics/task/gamefree"
+ "mongo.games.com/game/statistics/task/task"
+)
+
+const (
+ ExcelTypeNewPlayerBankrupt = iota // 新用户游戏破产率
+ ExcelTypeGameTimeAvg // 新用户平均游戏时长
+ ExcelTypeGameCountAvg // 新用户平均局数
+ ExcelTypeGameRate // 平均倍数
+ ExcelTypeActiveRate // 活跃破产率
+ ExcelTypeCtrlWinRate // 控输赢胜率
+ ExcelTypeRobotWinRate // 机器人胜率
+ ExcelTypeCoinAvg // 人均获得金币
+ ExcelTypeBankruptOffline // 破产后离线
+ ExcelTypeOfflineCoin // 充值当天最后金币
+)
+
+var VP *viper.Viper
+
+func main() {
+ defer func() {
+ logger.Logger.Flush()
+ logger.Logger.Close()
+ }()
+ VP = viperx.GetViper("config", "yaml")
+ // mongo
+ vp := viperx.GetViper("mongo", "yaml")
+ // mongo初始化
+ conf := &mongox.Config{}
+ err := vp.Unmarshal(conf)
+ if err != nil {
+ panic(fmt.Errorf("mongo config error: %v", err))
+ }
+ mongox.Init(conf)
+ defer mongox.Close()
+
+ // mysql
+ vp = viperx.GetViper("mysql", "yaml")
+ myConf := &mysqlx.Config{}
+ err = vp.Unmarshal(myConf)
+ if err != nil {
+ panic(fmt.Errorf("mysql config error: %v", err))
+ }
+ mysqlx.Init(myConf)
+ defer mysqlx.Close()
+
+ startTime, err := time.Parse(time.RFC3339, VP.GetString("StartTime"))
+ if err != nil {
+ panic(fmt.Sprintf("time.Parse err: %v", err))
+ return
+ }
+ endTime, err := time.Parse(time.RFC3339, VP.GetString("EndTime"))
+ if err != nil {
+ panic(fmt.Sprintf("time.Parse err: %v", err))
+ return
+ }
+
+ mgr := NewExcelMgr()
+ mgr.Register(ExcelTypeNewPlayerBankrupt, []string{"日期", "场次id", "破产人数", "参与人数", "破产率"})
+ mgr.Register(ExcelTypeGameTimeAvg, []string{"日期", "场次id", "参与人数", "平均游戏时长"})
+ mgr.Register(ExcelTypeGameCountAvg, []string{"日期", "场次id", "参与人数", "平均局数"})
+ mgr.Register(ExcelTypeGameRate, []string{"日期", "场次id", "总局数", "平均倍数", "有炸弹分局数", "炸弹分平均倍数", "2留在手里的局数"})
+ mgr.Register(ExcelTypeActiveRate, []string{"日期", "场次id", "破产人数", "参与人数", "破产率"})
+ mgr.Register(ExcelTypeCtrlWinRate, []string{"日期", "场次id", "总局数", "平均倍数", "有炸弹分局数", "炸弹分平均倍数", "2留在手里的局数"})
+ mgr.Register(ExcelTypeCtrlWinRate*10, []string{"日期", "场次id", "总局数", "平均倍数", "有炸弹分局数", "炸弹分平均倍数", "2留在手里的局数"})
+ mgr.Register(ExcelTypeRobotWinRate, []string{"日期", "场次id", "总局数", "平均倍数", "有炸弹分局数", "炸弹分平均倍数", "2留在手里的局数"})
+ mgr.Register(ExcelTypeCoinAvg, []string{"日期", "场次id", "参与人数", "人均获得金币"})
+ mgr.Register(ExcelTypeBankruptOffline, []string{"日期", "场次id", "破产人数", "参与人数", "破产率"})
+ mgr.Register(ExcelTypeOfflineCoin, []string{"日期", "场次id", "破产人数", "参与人数", "破产率"})
+ switchArr := VP.GetIntSlice("Switch")
+
+ for {
+ if startTime.Unix() >= endTime.Unix() {
+ break
+ }
+ startTimeStr := startTime.Format(time.RFC3339)
+ et := startTime.AddDate(0, 0, 1)
+ endTimeStr := et.Format(time.RFC3339)
+ logger.Logger.Infof("startTime: %v endTime: %v", startTimeStr, endTimeStr)
+
+ if switchArr[ExcelTypeNewPlayerBankrupt] == 1 {
+ // 新用户游戏破产率
+ mgr.GenNewPlayerBankruptRateExcel("1", startTimeStr, endTimeStr)
+ }
+ if switchArr[ExcelTypeGameTimeAvg] == 1 {
+ // 新用户平均游戏时长
+ mgr.GenNewPlayerGameTimeAvgExcel("1", startTimeStr, endTimeStr)
+ }
+ if switchArr[ExcelTypeGameCountAvg] == 1 {
+ // 新用户平均局数
+ mgr.GenGameCountExcel("1", startTimeStr, endTimeStr)
+ }
+ if switchArr[ExcelTypeGameRate] == 1 {
+ // 平均倍数
+ mgr.GenGameRateExcel("1", startTimeStr, endTimeStr)
+ }
+ if switchArr[ExcelTypeActiveRate] == 1 {
+ // 活跃破产率
+ mgr.GenActiveBankruptRateExcel("1", startTimeStr, endTimeStr)
+ }
+ if switchArr[ExcelTypeCtrlWinRate] == 1 {
+ // 控赢胜率
+ mgr.GenCtrlWinRateExcel("1", startTimeStr, endTimeStr)
+ }
+ if switchArr[ExcelTypeRobotWinRate] == 1 {
+ // 机器人胜率
+ mgr.GenRobotWinRateExcel("1", startTimeStr, endTimeStr)
+ }
+ if switchArr[ExcelTypeCoinAvg] == 1 {
+ // 人均获得金币
+ mgr.GenCoinAvgExcel("1", startTimeStr, endTimeStr)
+ }
+ if switchArr[ExcelTypeBankruptOffline] == 1 {
+ // 破产后离线
+ mgr.GenBankruptOfflineExcel("1", startTimeStr, endTimeStr)
+ }
+ if switchArr[ExcelTypeOfflineCoin] == 1 {
+ // 离线金币
+ mgr.GenOfflineCoinExcel("1", startTimeStr, endTimeStr)
+ }
+
+ startTime = et
+ }
+
+ mgr.SaveAll(VP.GetString("StartTime")[:10], endTime.AddDate(0, 0, -1).Format(time.DateOnly))
+}
+
+func (e *ExcelMgr) SaveAll(startTime, endTime string) {
+ switchArr := VP.GetIntSlice("Switch")
+ if switchArr[ExcelTypeNewPlayerBankrupt] == 1 {
+ e.Save(ExcelTypeNewPlayerBankrupt, fmt.Sprintf("新用户破产率_%s_%s.xlsx", startTime, endTime))
+ }
+ if switchArr[ExcelTypeGameTimeAvg] == 1 {
+ e.Save(ExcelTypeGameTimeAvg, fmt.Sprintf("新用户平局游戏时长_%s_%s.xlsx", startTime, endTime))
+ }
+ if switchArr[ExcelTypeGameCountAvg] == 1 {
+ e.Save(ExcelTypeGameCountAvg, fmt.Sprintf("新用户平均局数_%s_%s.xlsx", startTime, endTime))
+ }
+ if switchArr[ExcelTypeGameRate] == 1 {
+ e.Save(ExcelTypeGameRate, fmt.Sprintf("平均倍数_%s_%s.xlsx", startTime, endTime))
+ }
+ if switchArr[ExcelTypeActiveRate] == 1 {
+ e.Save(ExcelTypeActiveRate, fmt.Sprintf("活跃破产率_%s_%s.xlsx", startTime, endTime))
+ }
+ if switchArr[ExcelTypeCtrlWinRate] == 1 {
+ e.Save(ExcelTypeCtrlWinRate, fmt.Sprintf("控赢胜率_%s_%s.xlsx", startTime, endTime))
+ e.Save(ExcelTypeCtrlWinRate*10, fmt.Sprintf("控输胜率_%s_%s.xlsx", startTime, endTime))
+ }
+ if switchArr[ExcelTypeRobotWinRate] == 1 {
+ e.Save(ExcelTypeRobotWinRate, fmt.Sprintf("机器人输率_%s_%s.xlsx", startTime, endTime))
+ }
+ if switchArr[ExcelTypeCoinAvg] == 1 {
+ e.Save(ExcelTypeCoinAvg, fmt.Sprintf("人均获得金币_%s_%s.xlsx", startTime, endTime))
+ }
+ if switchArr[ExcelTypeBankruptOffline] == 1 {
+ e.Save(ExcelTypeBankruptOffline, fmt.Sprintf("破产后离线_%s_%s.xlsx", startTime, endTime))
+ }
+ if switchArr[ExcelTypeOfflineCoin] == 1 {
+ e.Save(ExcelTypeOfflineCoin, fmt.Sprintf("离线金币_%s_%s.xlsx", startTime, endTime))
+ }
+}
+
+func GetGameFreeName(id int) string {
+ d := gamefree.PBDB_GameFreeMgr.GetData(int32(id))
+ return fmt.Sprintf("%s_%s", d.Name, d.Title)
+}
+
+func (e *ExcelMgr) GenNewPlayerBankruptRateExcel(plt string, startTime, endTime string) {
+ for _, v := range VP.GetIntSlice("Gamefreeids") {
+ _, a, b, err := task.NewPlayerBankruptRate(plt, startTime, endTime, v)
+ if err != nil {
+ logger.Logger.Errorf("NewPlayerBankruptRate get StartTime:%v EndTime:%v GameFreeId:%v err: %v", startTime, endTime, v, err)
+ continue
+ }
+ ex := e.Get(ExcelTypeNewPlayerBankrupt)
+ ex.NewLine()
+ ex.SetCell(startTime[:10])
+ ex.SetCell(GetGameFreeName(v))
+ ex.SetCell(a)
+ ex.SetCell(b)
+ if b > 0 {
+ ex.SetCell(float64(a) / float64(b))
+ } else {
+ ex.SetCell(0)
+ }
+ logger.Logger.Tracef("NewPlayerBankruptRate GameFreeId: %v rate: %v", v, float64(a)/float64(b))
+ }
+}
+
+func (e *ExcelMgr) GenNewPlayerGameTimeAvgExcel(plt string, startTime, endTime string) {
+ for _, v := range VP.GetIntSlice("Gamefreeids") {
+ a, b, err := task.NewPlayerGameTimeAvg(plt, startTime, endTime, v)
+ if err != nil {
+ logger.Logger.Errorf("NewPlayerGameTimeAvg get StartTime:%v EndTime:%v GameFreeId:%v err: %v", startTime, endTime, v, err)
+ continue
+ }
+ ex := e.Get(ExcelTypeGameTimeAvg)
+ ex.NewLine()
+ ex.SetCell(startTime[:10])
+ ex.SetCell(GetGameFreeName(v))
+ ex.SetCell(a)
+ if a > 0 {
+ avg := float64(b) / float64(a)
+ show := fmt.Sprintf("%v", time.Second*time.Duration(avg))
+ ex.SetCell(show)
+ } else {
+ ex.SetCell(0)
+ }
+ logger.Logger.Tracef("NewPlayerGameTimeAvg GameFreeId: %v avg: %v", v, float64(b)/float64(a))
+ }
+}
+
+func (e *ExcelMgr) GenGameCountExcel(plt string, startTime, endTime string) {
+ for _, v := range VP.GetIntSlice("Gamefreeids") {
+ a, b, err := task.NewPlayerGameCountAvg(plt, startTime, endTime, v)
+ if err != nil {
+ logger.Logger.Errorf("NewPlayerGameCountAvg get StartTime:%v EndTime:%v GameFreeId:%v err: %v", startTime, endTime, v, err)
+ continue
+ }
+ ex := e.Get(ExcelTypeGameCountAvg)
+ ex.NewLine()
+ ex.SetCell(startTime[:10])
+ ex.SetCell(GetGameFreeName(v))
+ ex.SetCell(a)
+ if a > 0 {
+ ex.SetCell(float64(b) / float64(a))
+ } else {
+ ex.SetCell(0)
+ }
+ logger.Logger.Tracef("NewPlayerGameCountAvg GameFreeId: %v avg: %v", v, float64(b)/float64(a))
+ }
+}
+
+func (e *ExcelMgr) GenGameRateExcel(plt string, startTime, endTime string) {
+ for _, v := range VP.GetIntSlice("Gamefreeids") {
+ total, bombTotal, remain2Total, rateAvg, bombRateAvg, err := task.PlayerGameRate(plt, startTime, endTime, v)
+ if err != nil {
+ logger.Logger.Errorf("PlayerGameRate get StartTime:%v EndTime:%v GameFreeId:%v err: %v", startTime, endTime, v, err)
+ continue
+ }
+ ex := e.Get(ExcelTypeGameRate)
+ ex.NewLine()
+ ex.SetCell(startTime[:10])
+ ex.SetCell(GetGameFreeName(v))
+ ex.SetCell(total)
+ ex.SetCell(rateAvg)
+ ex.SetCell(bombTotal)
+ ex.SetCell(bombRateAvg)
+ ex.SetCell(remain2Total)
+ logger.Logger.Tracef("PlayerGameRate GameFreeId: %v total: %v rateAvg: %v bombTotal: %v bombRateAvg: %v remain2Total: %v",
+ v, total, rateAvg, bombTotal, bombRateAvg, remain2Total)
+ }
+}
+
+func (e *ExcelMgr) GenActiveBankruptRateExcel(plt string, startTime, endTime string) {
+ for _, v := range VP.GetIntSlice("Gamefreeids") {
+ b, t, err := task.ActivePlayerBankruptRate(plt, startTime, endTime, v)
+ if err != nil {
+ logger.Logger.Errorf("ActivePlayerBankruptRate get StartTime:%v EndTime:%v GameFreeId:%v err: %v", startTime, endTime, v, err)
+ continue
+ }
+
+ ex := e.Get(ExcelTypeActiveRate)
+ ex.NewLine()
+ ex.SetCell(startTime[:10])
+ ex.SetCell(GetGameFreeName(v))
+ ex.SetCell(b)
+ ex.SetCell(t)
+ if t > 0 {
+ if b > t {
+ b = t
+ }
+ ex.SetCell(float64(b) / float64(t))
+ } else {
+ ex.SetCell(0)
+ }
+ logger.Logger.Tracef("ActivePlayerBankruptRate GameFreeId: %v rate: %v", v, float64(b)/float64(t))
+ }
+}
+
+func (e *ExcelMgr) GenCtrlWinRateExcel(plt string, startTime, endTime string) {
+ for _, v := range append(VP.GetIntSlice("Tienlen")) {
+ ret, err := task.GameDetailWinRate(plt, startTime, endTime, v)
+ if err != nil {
+ logger.Logger.Errorf("GameDetailWinRate get StartTime:%v EndTime:%v GameFreeId:%v err: %v", startTime, endTime, v, err)
+ continue
+ }
+ if len(ret) > 0 {
+ ex := e.Get(ExcelTypeCtrlWinRate)
+ ex.NewLine()
+ ex.SetCell(startTime[:10])
+ ex.SetCell(GetGameFreeName(v))
+ ex.SetCell(ret[0].First)
+ ex.SetCell(ret[0].Second)
+ ex.SetCell(ret[0].Third)
+ ex.SetCell(ret[0].Total)
+ if ret[0].Total > 0 {
+ ex.SetCell(float64(ret[0].First+ret[0].Second) / float64(ret[0].Total))
+ } else {
+ ex.SetCell(0)
+ }
+ logger.Logger.Tracef("GameDetailWinRate GameFreeId:%v %+v", v, ret[0])
+ }
+ if len(ret) > 1 {
+ ex := e.Get(ExcelTypeCtrlWinRate * 10)
+ ex.NewLine()
+ ex.SetCell(startTime[:10])
+ ex.SetCell(GetGameFreeName(v))
+ ex.SetCell(ret[1].First)
+ ex.SetCell(ret[1].Second)
+ ex.SetCell(ret[1].Third)
+ ex.SetCell(ret[1].Total)
+ if ret[1].Total > 0 {
+ ex.SetCell(float64(ret[1].First+ret[1].Second) / float64(ret[1].Total))
+ } else {
+ ex.SetCell(0)
+ }
+ logger.Logger.Tracef("GameDetailWinRate GameFreeId:%v %+v", v, ret[1])
+ }
+ }
+}
+
+func (e *ExcelMgr) GenRobotWinRateExcel(plt string, startTime, endTime string) {
+ for _, v := range append(VP.GetIntSlice("Tienlen"), VP.GetIntSlice("Thirteen")...) {
+ a, b, err := task.RobotWinRate(plt, startTime, endTime, v)
+ if err != nil {
+ logger.Logger.Errorf("RobotWinRate get StartTime:%v EndTime:%v GameFreeId:%v err: %v", startTime, endTime, v, err)
+ continue
+ }
+
+ ex := e.Get(ExcelTypeRobotWinRate)
+ ex.NewLine()
+ ex.SetCell(startTime[:10])
+ ex.SetCell(GetGameFreeName(v))
+ ex.SetCell(a)
+ ex.SetCell(b)
+ if b > 0 {
+ ex.SetCell(float64(b-a) / float64(b))
+ } else {
+ ex.SetCell(0)
+ }
+ logger.Logger.Tracef("RobotWinRate GameFreeId: %v rate: %v", v, float64(a)/float64(b))
+ }
+}
+
+func (e *ExcelMgr) GenCoinAvgExcel(plt string, startTime, endTime string) {
+ type KV struct {
+ K int
+ V string
+ }
+
+ var list []KV
+ for k, v := range task.CoinName {
+ list = append(list, KV{K: k, V: v})
+ }
+
+ sort.Slice(list, func(i, j int) bool {
+ return list[i].K < list[j].K
+ })
+
+ for _, item := range list {
+ k, v := item.K, item.V
+ a, b, err := task.CoinAvg(plt, startTime, endTime, k)
+ if err != nil {
+ logger.Logger.Errorf("CoinAvg get StartTime:%v EndTime:%v tp:%v err: %v", startTime, endTime, k, err)
+ continue
+ }
+ ex := e.Get(ExcelTypeCoinAvg)
+ ex.NewLine()
+ ex.SetCell(startTime[:10])
+ ex.SetCell(v)
+ ex.SetCell(a)
+ ex.SetCell(b)
+ if a > 0 {
+ ex.SetCell(float64(b) / float64(a))
+ } else {
+ ex.SetCell(0)
+ }
+ logger.Logger.Tracef("CoinAvg tp: %v rate: %v", k, float64(b)/float64(a))
+ }
+}
+
+func (e *ExcelMgr) GenBankruptOfflineExcel(plt string, startTime, endTime string) {
+ res, err := task.BankruptOffline(plt, startTime, endTime)
+ if err != nil {
+ logger.Logger.Errorf("BankruptOffline get StartTime:%v EndTime:%v err: %v", startTime, endTime, err)
+ return
+ }
+ ex := e.Get(ExcelTypeBankruptOffline)
+ ex.NewLine()
+ ex.SetCell(startTime[:10])
+ ex.SetCell(res.One)
+ ex.SetCell(res.Two)
+ ex.SetCell(res.Three)
+ ex.SetCell(res.Recharge)
+ ex.SetCell(fmt.Sprintf("%v", res.D))
+ logger.Logger.Tracef("BankruptOffline %+v", res)
+}
+
+func (e *ExcelMgr) GenOfflineCoinExcel(plt string, startTime, endTime string) {
+ res, err := task.OfflineCoin(plt, startTime, endTime)
+ if err != nil {
+ logger.Logger.Errorf("OfflineCoin get StartTime:%v EndTime:%v err: %v", startTime, endTime, err)
+ return
+ }
+ for _, v := range res {
+ ex := e.Get(ExcelTypeOfflineCoin)
+ ex.NewLine()
+ ex.SetCell(startTime[:10])
+ ex.SetCell(v.Snid)
+ ex.SetCell(v.Coin)
+ }
+ logger.Logger.Tracef("OfflineCoin %+v", res)
+}
diff --git a/statistics/task/readme b/statistics/task/readme
new file mode 100644
index 0000000..5fdb673
--- /dev/null
+++ b/statistics/task/readme
@@ -0,0 +1 @@
+查询历史数据
\ No newline at end of file
diff --git a/statistics/task/task/bankruptcy.go b/statistics/task/task/bankruptcy.go
new file mode 100644
index 0000000..ea4212d
--- /dev/null
+++ b/statistics/task/task/bankruptcy.go
@@ -0,0 +1,189 @@
+package task
+
+import (
+ "context"
+ "errors"
+ "fmt"
+
+ "go.mongodb.org/mongo-driver/bson"
+ "go.mongodb.org/mongo-driver/mongo"
+ "go.mongodb.org/mongo-driver/mongo/options"
+ "mongo.games.com/goserver/core/logger"
+ mymongo "mongo.games.com/goserver/core/mongox"
+ mymysql "mongo.games.com/goserver/core/mysqlx"
+
+ "mongo.games.com/game/common"
+ mongomodel "mongo.games.com/game/statistics/modelmongo"
+)
+
+// 新用户id
+func GetNewPayerIds(plt string, startTime, endTime string) ([]int, error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ c, err := mymongo.GetUserCollection(plt, mongomodel.UserAccount)
+ if err != nil {
+ return nil, err
+ }
+
+ var res []struct{ Snid int }
+ dd, err := c.Find(context.TODO(), bson.M{"registetime": bson.M{"$gte": s, "$lt": e}}, options.Find().SetProjection(bson.M{"snid": 1}))
+ if err != nil {
+ if errors.Is(err, mongo.ErrNoDocuments) {
+ return nil, nil
+ }
+ logger.Logger.Errorf("find new player snid get err: %v", err)
+ return nil, err
+ }
+ if err := dd.All(context.TODO(), &res); err != nil {
+ logger.Logger.Errorf("find new player snid decode err: %v", err)
+ return nil, err
+ }
+ var ret []int
+ for _, v := range res {
+ ret = append(ret, v.Snid)
+ }
+ logger.Logger.Tracef("find new player snid: %v", ret)
+ return ret, nil
+}
+
+// 场次破产总人数
+func GameFreeIdBankruptPlayerCount(plt string, ids []int, startTime, endTime string, gamefreeid int) (int, error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ db, err := mymysql.GetDatabase("count")
+ if err != nil {
+ return 0, err
+ }
+ var ret int
+ for _, v := range ids {
+ var n int64
+ if err = db.Table("bankrupt_log").Where("snid = ? AND bankrupt_time >= ? AND bankrupt_time < ? AND game_free_id = ?",
+ v, s.Unix(), e.Unix(), gamefreeid).Count(&n).Error; err != nil {
+ logger.Logger.Errorf("find bankrupt player count get err: %v", err)
+ return 0, err
+ }
+ if n > 0 {
+ ret++
+ }
+ }
+ return ret, nil
+}
+
+// 场次参与总人数
+func PlayingGameCount(plt string, ids []int, startTime, endTime string, gamefreeid int) (int, error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ c, err := mymongo.GetLogCollection(plt, mongomodel.LogGamePlayerListLog)
+ if err != nil {
+ return 0, err
+ }
+
+ var ret int
+ for _, v := range ids {
+ // 参与过游戏
+ n, err := c.CountDocuments(context.TODO(), bson.M{"snid": v, "time": bson.M{"$gte": s, "$lt": e}, "gamefreeid": gamefreeid})
+ if err != nil {
+ logger.Logger.Errorf("find playing game count get err: %v", err)
+ return 0, err
+ }
+ if n > 0 {
+ ret++
+ }
+ }
+ return ret, nil
+}
+
+// NewPlayerBankruptRate 新用户破产率
+// 新用户游戏破产率 = 新用户游戏破产玩家数量/新用户游戏参与总人数;破产数量,每个玩家每个游戏破产只统计一次;参与人数,每个玩家每个游戏只统计一次;
+// 返回 新用户数量,破产人数,参与人数
+func NewPlayerBankruptRate(plt string, startTime, endTime string, gamefreeid int) (n, a, b int, err error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ if s.IsZero() || e.IsZero() {
+ return 0, 0, 0, fmt.Errorf("time format error")
+ }
+ ids, err := GetNewPayerIds(plt, startTime, endTime)
+ if err != nil {
+ return 0, 0, 0, err
+ }
+
+ a, err = GameFreeIdBankruptPlayerCount(plt, ids, startTime, endTime, gamefreeid)
+ if err != nil {
+ return 0, 0, 0, err
+ }
+ b, err = PlayingGameCount(plt, ids, startTime, endTime, gamefreeid)
+ if err != nil {
+ return 0, 0, 0, err
+ }
+ if b == 0 {
+ return 0, 0, 0, nil
+ }
+ return
+}
+
+// ActivePlayerCount 活跃玩家游戏总人数
+func ActivePlayerCount(plt string, startTime, endTime string, gamefreeid int) (int, error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ c, err := mymongo.GetLogCollection(plt, mongomodel.LogGamePlayerListLog)
+ if err != nil {
+ return 0, err
+ }
+
+ var count []struct {
+ Count int
+ }
+ cur, err := c.Aggregate(context.TODO(), bson.A{
+ bson.M{"$match": bson.M{"time": bson.M{"$gte": s, "$lt": e}, "gamefreeid": gamefreeid}},
+ bson.M{"$group": bson.M{"_id": "$snid"}},
+ bson.M{"$count": "count"},
+ })
+ if err != nil {
+ logger.Logger.Errorf("find active player count get err: %v", err)
+ return 0, err
+ }
+ if err := cur.All(context.TODO(), &count); err != nil {
+ logger.Logger.Errorf("find active player count decode err: %v", err)
+ return 0, err
+ }
+
+ if len(count) == 0 {
+ return 0, nil
+ }
+
+ return count[0].Count, nil
+}
+
+// ActivePlayerBankruptCount 活跃玩家破产总人数
+func ActivePlayerBankruptCount(plt string, startTime, endTime string, gamefreeid int) (int, error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ db, err := mymysql.GetDatabase("count")
+ if err != nil {
+ return 0, err
+ }
+ var n int64
+ if err = db.Table("bankrupt_log").Where("bankrupt_time >= ? AND bankrupt_time < ? AND game_free_id = ?",
+ s.Unix(), e.Unix(), gamefreeid).Group("snid").Count(&n).Error; err != nil {
+ logger.Logger.Errorf("find bankrupt count get err: %v", err)
+ return 0, err
+ }
+ return int(n), nil
+}
+
+// ActivePlayerBankruptRate 活跃玩家破产率
+// 活跃玩家游戏破产率 = 活跃玩家游戏破产玩家数量/活跃玩家游戏总人数;破产数量,每个玩家每个游戏破产只统计一次;参与人数,每个玩家每个游戏只统计一次;
+// 返回 破产人数,参与人数
+func ActivePlayerBankruptRate(plt string, startTime, endTime string, gamefreeid int) (a, b int, err error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ if s.IsZero() || e.IsZero() {
+ return 0, 0, fmt.Errorf("time format error")
+ }
+
+ a, err = ActivePlayerBankruptCount(plt, startTime, endTime, gamefreeid)
+ if err != nil {
+ return 0, 0, err
+ }
+ b, err = ActivePlayerCount(plt, startTime, endTime, gamefreeid)
+ if err != nil {
+ return 0, 0, err
+ }
+ if b == 0 {
+ return 0, 0, nil
+ }
+ return
+}
diff --git a/statistics/task/task/bankruptoffline.go b/statistics/task/task/bankruptoffline.go
new file mode 100644
index 0000000..c6f71c1
--- /dev/null
+++ b/statistics/task/task/bankruptoffline.go
@@ -0,0 +1,153 @@
+package task
+
+import (
+ "context"
+ "fmt"
+ "strconv"
+ "strings"
+ "time"
+
+ "go.mongodb.org/mongo-driver/bson"
+ "mongo.games.com/game/common"
+ mymongo "mongo.games.com/goserver/core/mongox"
+ mymysql "mongo.games.com/goserver/core/mysqlx"
+)
+
+// 破产后离线;破产后5分钟内有离线行为
+
+type BankruptOfflineData struct {
+ One int
+ Two int
+ Three int
+ Recharge int
+ D time.Duration
+}
+
+func BankruptOffline(plt string, startTime, endTime string) (ret *BankruptOfflineData, err error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ if s.IsZero() || e.IsZero() {
+ return nil, fmt.Errorf("time format error")
+ }
+ db, err := mymysql.GetDatabase("count")
+ if err != nil {
+ return nil, err
+ }
+
+ /*
+ SELECT
+ snid,
+ GROUP_CONCAT(bankrupt_time ORDER BY bankrupt_time ASC LIMIT 3) AS top_3_bankrupt_times,
+ COUNT(*) AS record_count
+ FROM
+ bankrupt_log
+ GROUP BY
+ snid;
+ */
+
+ type BankruptLog struct {
+ Snid int `gorm:"column:snid"`
+ MaxBankruptTime string `gorm:"column:top_3_bankrupt_times"`
+ Times []int64 `gorm:"-"`
+ }
+
+ var logs []*BankruptLog
+ tx := db.Raw(`
+ WITH RankedData AS (
+ SELECT
+ snid,
+ bankrupt_time,
+ ROW_NUMBER() OVER (PARTITION BY snid ORDER BY bankrupt_time ASC) AS ranks
+ FROM bankrupt_log
+ WHERE bankrupt_time >= ? AND bankrupt_time < ?
+ )
+ SELECT
+ snid,
+ GROUP_CONCAT(bankrupt_time ORDER BY bankrupt_time ASC) AS top_3_bankrupt_times
+ FROM RankedData
+ WHERE ranks <= 3
+ GROUP BY snid
+`, s.Unix(), e.Unix()).Scan(&logs)
+
+ if tx.Error != nil {
+ return nil, tx.Error
+ }
+
+ var timeSecond int
+ var total int
+
+ ret = &BankruptOfflineData{}
+ for _, v := range logs {
+ if v == nil || len(v.MaxBankruptTime) == 0 {
+ continue
+ }
+ for _, vv := range strings.Split(v.MaxBankruptTime, ",") {
+ n, err := strconv.Atoi(vv)
+ if err != nil {
+ return nil, err
+ }
+ v.Times = append(v.Times, int64(n))
+ }
+
+ // 破产后5分钟内有离线行为
+ db, err = mymysql.GetDatabase(plt)
+ if err != nil {
+ return nil, err
+ }
+
+ var isOffline bool
+ for k, vv := range v.Times {
+ var n int64
+ stime, etime := time.Unix(vv, 0), time.Unix(vv, 0).Add(5*time.Minute)
+ if err = db.Table("user_logins").Where("snid = ? AND offline_time >= ? AND offline_time < ?",
+ v.Snid, stime, etime).Count(&n).Error; err != nil {
+ return nil, err
+ }
+ switch k {
+ case 0:
+ if n > 0 {
+ ret.One++
+ isOffline = true
+ }
+ case 1:
+ if n > 0 {
+ ret.Two++
+ isOffline = true
+ }
+ case 2:
+ if n > 0 {
+ ret.Three++
+ isOffline = true
+ }
+ }
+ }
+
+ if isOffline {
+ // 充值
+ c, err := mymongo.GetLogCollection(plt, "log_dbshop")
+ if err != nil {
+ return nil, err
+ }
+ count, err := c.CountDocuments(context.TODO(), bson.M{"snid": v.Snid, "ts": bson.M{"$gte": s.Unix(), "$lt": e.Unix()}, "consume": 3, "state": 1})
+ if err != nil {
+ return nil, err
+ }
+ if count > 0 {
+ ret.Recharge++
+ }
+
+ // 平均对局时长
+ times, to, err := NewPlayerGameTime(plt, []int{v.Snid}, startTime, endTime, 0)
+ if err != nil {
+ return nil, err
+ }
+ timeSecond += times
+ total += to
+ }
+ }
+
+ if total > 0 {
+ ret.D = time.Second * time.Duration(timeSecond/total)
+ }
+
+ return ret, nil
+}
diff --git a/statistics/task/task/coin.go b/statistics/task/task/coin.go
new file mode 100644
index 0000000..10747e6
--- /dev/null
+++ b/statistics/task/task/coin.go
@@ -0,0 +1,77 @@
+package task
+
+import (
+ "context"
+ "fmt"
+ "go.mongodb.org/mongo-driver/bson"
+ "mongo.games.com/game/common"
+ "mongo.games.com/goserver/core/mongox"
+)
+
+var CoinName = map[int]string{
+ 44: "转盘",
+ 57: "轮盘视频",
+ 43: "签到",
+ 58: "签到视频",
+ 83: "累计签到",
+ 91: "累计签到进阶奖励",
+ 47: "vip每日礼包",
+ 105: "vip等级礼包",
+ 74: "集卡活动",
+ 67: "金币存钱罐",
+ 94: "赛季通行证等级奖励",
+ 95: "赛季通行证排行奖励",
+ 52: "排位赛段位奖励",
+ 65: "周卡奖励",
+ 5: "兑换",
+ 49: "礼包码兑换",
+ 38: "救济金",
+ 56: "救济金视频",
+}
+
+func CoinHistory(plt string, startTime, endTime string, tp int, f func(data bson.M) error) error {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ if s.IsZero() || e.IsZero() {
+ return fmt.Errorf("time format error")
+ }
+ c, err := mongox.GetLogCollection(plt, "log_coinex")
+ if err != nil {
+ return err
+ }
+
+ cur, err := c.Find(context.TODO(), bson.M{"time": bson.M{"$gte": s, "$lt": e}, "logtype": tp, "cointype": 0})
+ if err != nil {
+ return err
+ }
+ defer cur.Close(context.Background())
+
+ for cur.TryNext(context.Background()) {
+ data := bson.M{}
+ if err := cur.Decode(data); err != nil {
+ return err
+ }
+ if err := f(data); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func CoinAvg(plt string, startTime, endTime string, tp int) (playerNumber int, total int, err error) {
+ player := make(map[int32]struct{})
+ err = CoinHistory(plt, startTime, endTime, tp, func(data bson.M) error {
+ snid := data["snid"].(int32)
+ count := int(data["count"].(int64))
+ player[snid] = struct{}{}
+ if count > 0 {
+ total += count
+ }
+ return nil
+ })
+ if err != nil {
+ return
+ }
+ playerNumber = len(player)
+ return
+}
diff --git a/statistics/task/task/ctrlrate.go b/statistics/task/task/ctrlrate.go
new file mode 100644
index 0000000..25eaf26
--- /dev/null
+++ b/statistics/task/task/ctrlrate.go
@@ -0,0 +1,214 @@
+package task
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "sort"
+
+ "go.mongodb.org/mongo-driver/bson"
+ "mongo.games.com/goserver/core/logger"
+ "mongo.games.com/goserver/core/mongox"
+
+ "mongo.games.com/game/common"
+)
+
+func GameDetailFunc(plt string, startTime, endTime string, gamefreeid int, f func(data bson.M) error) error {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ if s.IsZero() || e.IsZero() {
+ return fmt.Errorf("time format error")
+ }
+
+ c, err := mongox.GetLogCollection(plt, "log_gamedetailed")
+ if err != nil {
+ return err
+ }
+
+ cur, err := c.Find(context.TODO(), bson.M{"time": bson.M{"$gte": s, "$lt": e}, "gamefreeid": gamefreeid})
+ if err != nil {
+ return err
+ }
+ defer cur.Close(context.Background())
+
+ for cur.TryNext(context.Background()) {
+ data := bson.M{}
+ if err := cur.Decode(data); err != nil {
+ return err
+ }
+ if err := f(data); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+type GameDetailWinRateRet struct {
+ First int
+ Second int
+ Third int
+ Total int
+ IsWin bool
+}
+
+func GameDetailWinRate(plt string, startTime, endTime string, gamefreeid int) (ret []GameDetailWinRateRet, err error) {
+ ret = make([]GameDetailWinRateRet, 2)
+ err = GameDetailFunc(plt, startTime, endTime, gamefreeid, func(data bson.M) error {
+ ff, ss, tt, to, ct := GameDetailCount(data)
+ if ct == 1 && to > 0 {
+ ret[0].First += ff
+ ret[0].Second += ss
+ ret[0].Third += tt
+ ret[0].Total += to
+ }
+ if ct == 2 && to > 0 {
+ ret[1].First += ff
+ ret[1].Second += ss
+ ret[1].Third += tt
+ ret[1].Total += to
+ }
+ return nil
+ })
+ return
+}
+
+type RabbitMQDataRaw struct {
+ Source int32
+ Data interface{}
+}
+
+func GameDetailCount(data bson.M) (first, second, third, to int, ctrlType int) {
+ if data == nil {
+ return
+ }
+ gameid := data["gameid"].(int32)
+ gamefreeid := data["gamefreeid"].(int32)
+ ctrlType = int(data["ctrltype"].(int32))
+ logger.Logger.Tracef("GameDetail gameid:%d, gamefreeid:%d", gameid, gamefreeid)
+
+ if ctrlType != 1 && ctrlType != 2 {
+ return
+ }
+
+ detail := data["gamedetailednote"]
+ if detail == nil {
+ return
+ }
+
+ raw := new(RabbitMQDataRaw)
+ if err := json.Unmarshal([]byte(detail.(string)), raw); err != nil {
+ logger.Logger.Errorf("GameDetailCount Unmarshal 1 error:%v %v", err, gameid)
+ return
+ }
+
+ switch gameid {
+ case 207, 208, 209, 210, 240, 241, 242, 243, 244, 245, 246, 247: // tienlen
+ data := new(TienLenType)
+ b, err := json.Marshal(raw.Data)
+ if err != nil {
+ logger.Logger.Errorf("GameDetailCount Marshal error:%v %v", err, gameid)
+ return
+ }
+ if err := json.Unmarshal(b, data); err != nil {
+ logger.Logger.Errorf("GameDetailCount Unmarshal 2 error:%v %v", err, gameid)
+ return
+ }
+
+ var has, hasPlayer bool
+ for _, v := range data.PlayerData {
+ if v.IsRob {
+ has = true
+ }
+ if !v.IsRob {
+ hasPlayer = true
+ }
+ }
+ if !has || !hasPlayer {
+ // 没有机器人
+ return 0, 0, 0, 0, ctrlType
+ }
+
+ sort.Slice(data.PlayerData, func(i, j int) bool {
+ a, b := data.PlayerData[i].BillCoin+data.PlayerData[i].BillTaxCoin, data.PlayerData[j].BillCoin+data.PlayerData[j].BillTaxCoin
+ if a != b {
+ return a > b
+ }
+ if data.PlayerData[i].IsRob != data.PlayerData[j].IsRob {
+ return !data.PlayerData[i].IsRob
+ }
+ return data.PlayerData[i].UserId < data.PlayerData[j].UserId
+ })
+
+ for k, v := range data.PlayerData {
+ if !v.IsRob && v.BillCoin > 0 && k <= 1 {
+ if k == 0 {
+ first = 1
+ }
+ if k == 1 {
+ second = 1
+ }
+ break
+ }
+ }
+ if len(data.PlayerData) > 2 && first+second == 0 && !data.PlayerData[2].IsRob {
+ third = 1
+ }
+ to = 1
+
+ case 211, 212, 213, 214:
+ data := new(ThirteenWaterType)
+ b, err := json.Marshal(raw.Data)
+ if err != nil {
+ logger.Logger.Errorf("GameDetailCount Marshal error:%v %v", err, gameid)
+ return
+ }
+ if err := json.Unmarshal(b, data); err != nil {
+ logger.Logger.Errorf("GameDetailCount Unmarshal 2 error:%v %v", err, gameid)
+ return
+ }
+
+ var has, hasPlayer bool
+ for _, v := range data.PlayerData {
+ if v.IsRob {
+ has = true
+ }
+ if !v.IsRob {
+ hasPlayer = true
+ }
+ }
+ if !has || !hasPlayer {
+ // 没有机器人
+ return 0, 0, 0, 0, ctrlType
+ }
+
+ sort.Slice(data.PlayerData, func(i, j int) bool {
+ a, b := data.PlayerData[i].AllScore, data.PlayerData[j].AllScore
+ if a != b {
+ return a > b
+ }
+ if data.PlayerData[i].IsRob != data.PlayerData[j].IsRob {
+ return !data.PlayerData[i].IsRob
+ }
+ return data.PlayerData[i].UserId < data.PlayerData[j].UserId
+ })
+
+ for k, v := range data.PlayerData {
+ if !v.IsRob && v.AllScore > 0 && k <= 1 {
+ if k == 0 {
+ first = 1
+ }
+ if k == 1 {
+ second = 1
+ }
+ break
+ }
+ }
+
+ if len(data.PlayerData) > 2 && first+second == 0 && !data.PlayerData[2].IsRob {
+ third = 1
+ }
+
+ to = 1
+ }
+ return
+}
diff --git a/statistics/task/task/gamebankruptcy.go b/statistics/task/task/gamebankruptcy.go
new file mode 100644
index 0000000..21bbbcf
--- /dev/null
+++ b/statistics/task/task/gamebankruptcy.go
@@ -0,0 +1,69 @@
+package task
+
+import (
+ "context"
+ "fmt"
+ "go.mongodb.org/mongo-driver/bson"
+ "mongo.games.com/game/common"
+ "mongo.games.com/goserver/core/logger"
+ mymongo "mongo.games.com/goserver/core/mongox"
+ mymysql "mongo.games.com/goserver/core/mysqlx"
+)
+
+// 场次破产率
+
+// GameFreeIdBankruptcyTimes 场次破产次数
+func GameFreeIdBankruptcyTimes(plt string, startTime, endTime string, gamefreeid int) (int, error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ db, err := mymysql.GetDatabase("count")
+ if err != nil {
+ return 0, err
+ }
+
+ var n int64
+ if err = db.Table("bankrupt_log").Where("bankrupt_time >= ? AND bankrupt_time < ? AND game_free_id = ?",
+ s.Unix(), e.Unix(), gamefreeid).Count(&n).Error; err != nil {
+ logger.Logger.Errorf("find bankrupt count get err: %v", err)
+ return 0, err
+ }
+
+ return int(n), nil
+}
+
+// GameFreeIdTotalTimes 场次总局数
+func GameFreeIdTotalTimes(plt string, startTime, endTime string, gamefreeid int) (int, error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ c, err := mymongo.GetLogCollection(plt, "log_gamedetailed")
+ if err != nil {
+ return 0, err
+ }
+
+ n, err := c.CountDocuments(context.TODO(), bson.M{"time": bson.M{"$gte": s, "$lt": e}, "gamefreeid": gamefreeid})
+ if err != nil {
+ logger.Logger.Errorf("find game total times get err: %v", err)
+ return 0, err
+ }
+
+ return int(n), nil
+}
+
+// GameBankruptcyRate 场次破产率
+// 返回 破产局数, 总局数, 错误
+func GameBankruptcyRate(plt string, startTime, endTime string, gamefreeid int) (int, int, error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ if s.IsZero() || e.IsZero() {
+ return 0, 0, fmt.Errorf("time format error")
+ }
+
+ b, err := GameFreeIdBankruptcyTimes(plt, startTime, endTime, gamefreeid)
+ if err != nil {
+ return 0, 0, err
+ }
+
+ t, err := GameFreeIdTotalTimes(plt, startTime, endTime, gamefreeid)
+ if err != nil {
+ return 0, 0, err
+ }
+
+ return b, t, nil
+}
diff --git a/statistics/task/task/gamecount.go b/statistics/task/task/gamecount.go
new file mode 100644
index 0000000..911c202
--- /dev/null
+++ b/statistics/task/task/gamecount.go
@@ -0,0 +1,60 @@
+package task
+
+import (
+ "context"
+ "fmt"
+ "go.mongodb.org/mongo-driver/bson"
+ "mongo.games.com/game/common"
+ mongomodel "mongo.games.com/game/statistics/modelmongo"
+ "mongo.games.com/goserver/core/logger"
+ mymongo "mongo.games.com/goserver/core/mongox"
+)
+
+// 新用户平均局数
+
+func NewPlayerGameCount(plt string, ids []int, startTime, endTime string, gamefreeid int) (int, error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ c, err := mymongo.GetLogCollection(plt, mongomodel.LogGamePlayerListLog)
+ if err != nil {
+ return 0, err
+ }
+
+ var ret int
+ for _, v := range ids {
+ n, err := c.CountDocuments(context.TODO(), bson.M{"snid": v, "gamefreeid": gamefreeid, "time": bson.M{"$gte": s, "$lt": e}})
+ if err != nil {
+ logger.Logger.Errorf("find player gamedetailedlogid get err: %v", err)
+ return 0, err
+ }
+ ret += int(n)
+ }
+
+ return ret, nil
+}
+
+// NewPlayerGameCountAvg 新用户平均局数
+// 返回 参与人数,总局数
+func NewPlayerGameCountAvg(plt string, startTime, endTime string, gamefreeid int) (int, int, error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ if s.IsZero() || e.IsZero() {
+ return 0, 0, fmt.Errorf("time format error")
+ }
+ ids, err := GetNewPayerIds(plt, startTime, endTime)
+ if err != nil {
+ return 0, 0, err
+ }
+ n, err := NewPlayerGameCount(plt, ids, startTime, endTime, gamefreeid)
+ if err != nil {
+ return 0, 0, err
+ }
+ if len(ids) == 0 {
+ return 0, 0, nil
+ }
+
+ b, err := PlayingGameCount(plt, ids, startTime, endTime, gamefreeid)
+ if err != nil {
+ return 0, 0, err
+ }
+
+ return b, n, nil
+}
diff --git a/statistics/task/task/gamelogtype.go b/statistics/task/task/gamelogtype.go
new file mode 100644
index 0000000..74cbf60
--- /dev/null
+++ b/statistics/task/task/gamelogtype.go
@@ -0,0 +1,1625 @@
+package task
+
+// 赢三张
+type WinThreeType struct {
+ RoomId int32 //房间ID
+ RoomRounds int32 //建房局数
+ RoomType int32 //房间类型
+ NowRound int32 //当前局数
+ PlayerCount int //玩家数量
+ BaseScore int32 //底分
+ PlayerData []WinThreePerson //玩家信息
+ Chip []PlayerChip //出牌详情
+ ClubRate int32 //俱乐部抽水比例
+ IsSmartOperation bool // 是否启用智能化运营
+ GamePreUseSmartState int //-1:正常
+ GameLastUseSmartState int //-1:正常
+ RobotUseSmartState int64 // Robot使用智能化运营状况
+}
+type PlayerChip struct {
+ UserId int32 //玩家ID
+ BetTotal int64 //玩家总投注
+ Chip int64 //玩家得分
+ StartCoin int64 //玩家开始前金币
+ BetAfterCoin int64 //玩家投注后金币,也就是客户端应该显示的金币
+ IsCheck bool //是否看牌
+ Round int32 //当前轮次
+ Op int32 //操作
+}
+
+type WinThreePerson struct {
+ UserId int32 //玩家ID
+ UserIcon int32 //玩家头像
+ ChangeCoin int64 //玩家得分
+ Cardinfo []int32 //牌值
+ KindOfCard int32 //牌型
+ IsWin int32 //输赢
+ IsRob bool //是否是机器人
+ Tax int64 //税,不一定有值,只是作为一个临时变量使用
+ ClubPump int64 //俱乐部额外抽水
+ Flag int //标识
+ Pos int32 //位置
+ StartCoin int64 //开始金币
+ BetCoin int64 //押注额度
+ RoundCheck int32 //轮次,看牌的
+ IsFirst bool //是否第一次
+ IsLeave bool
+ Platform string `json:"-"`
+ Channel string `json:"-"`
+ Promoter string `json:"-"`
+ PackageTag string `json:"-"`
+ InviterId int32 `json:"-"`
+ WBLevel int32 //黑白名单等级
+ SingleFlag int32 //单控标记
+}
+
+// 经典牛牛 抢庄牛牛
+type BullFightType struct {
+ RoomId int32 //房间ID
+ RoomRounds int32 //建房局数
+ RoomType int32 //房间类型
+ NowRound int32 //当前局数
+ BankId int32 //庄家ID
+ PlayerCount int //玩家数量
+ BaseScore int32 //底分
+ PlayerData []BullFightPerson //玩家信息
+ ClubRate int32 //俱乐部抽水比例
+ IsSmartOperation bool // 是否启用智能化运营
+}
+type BullFightPerson struct {
+ UserId int32 //玩家ID
+ UserIcon int32 //玩家头像
+ ChangeCoin int64 //玩家得分
+ Cardinfo []int32 //牌值
+ CardBakinfo []int32 //牌值
+ IsWin int32 //输赢
+ Tax int64 //税,不一定有值,只是作为一个临时变量使用
+ TaxLottery int64 //彩金池,增加值
+ ClubPump int64 //俱乐部额外抽水
+ IsRob bool //是否是机器人
+ Flag int //标识
+ IsFirst bool
+ StartCoin int64 //开始金币
+ Platform string `json:"-"`
+ Channel string `json:"-"`
+ Promoter string `json:"-"`
+ PackageTag string `json:"-"`
+ InviterId int32 `json:"-"`
+ Rate int64 //斗牛倍率
+ WBLevel int32 //黑白名单等级
+ RobBankRate int64 //抢庄倍率
+ SingleAdjust int32 // 单控输赢 1赢 2输
+}
+
+// 斗地主、跑得快
+type DoudizhuType struct {
+ RoomId int //房间Id
+ BasicScore int //基本分
+ Spring int //春天 1代表春天
+ BombScore int //炸弹分
+ BaseScore int32 //底分
+ PlayerCount int32 //玩家数量
+ BaseCards []int //底牌
+ BankerId int32 //斗地主地主Id
+ PlayerData []DoudizhuPerson //玩家信息
+ ClubRate int32 //俱乐部抽水比例
+ IsSmartOperation bool // 是否启用智能化运营
+}
+type DoudizhuPerson struct {
+ UserId int32 //玩家ID
+ UserIcon int32 //玩家头像
+ ChangeCoin int64 //玩家得分
+ OutCards [][]int64 //出的牌
+ SurplusCards []int32 //剩下的牌
+ IsWin int64 //输赢
+ IsRob bool //是否是机器人
+ IsFirst bool
+ Tax int64 //税,不一定有值,只是作为一个临时变量使用
+ ClubPump int64 //俱乐部额外抽水
+ StartCoin int64 //开始的金币数量
+ Platform string `json:"-"`
+ Channel string `json:"-"`
+ Promoter string `json:"-"`
+ PackageTag string `json:"-"`
+ InviterId int32 `json:"-"`
+ WBLevel int32 //黑白名单等级
+}
+
+// 推饼
+type TuibingType struct {
+ RoomId int32 //房间ID
+ RoomRounds int32 //建房局数
+ RoomType int32 //房间类型
+ NowRound int32 //当前局数
+ BankId int32 //庄家ID
+ PlayerCount int //玩家数量
+ BaseScore int32 //底分
+ PlayerData []TuibingPerson //玩家信息
+ ClubRate int32 //俱乐部抽水比例
+ IsSmartOperation bool // 是否启用智能化运营
+}
+type TuibingPerson struct {
+ UserId int32 //玩家ID
+ UserIcon int32 //玩家头像
+ ChangeCoin int64 //玩家得分
+ Cardinfo []int32 //牌值
+ IsWin int32 //输赢
+ IsRob bool //是否是机器人
+ IsFirst bool
+ Tax int64 //税,不一定有值,只是作为一个临时变量使用
+ ClubPump int64 //俱乐部额外抽水
+ Flag int //标识
+ StartCoin int64 //开始金币
+ Platform string `json:"-"`
+ Channel string `json:"-"`
+ Promoter string `json:"-"`
+ PackageTag string `json:"-"`
+ InviterId int32 `json:"-"`
+ Rate int64 //倍率
+ WBLevel int32 //黑白名单等级
+}
+
+// 十三水
+// 十三水牌值
+type ThirteenWaterType struct {
+ BaseScore int32 //底分
+ PlayerData []ThirteenWaterPerson //玩家信息
+}
+
+type ThirteenWaterPoker struct {
+ Head [3]int
+ Mid [5]int
+ End [5]int
+ PokerType int
+}
+
+type ThirteenWaterPerson struct {
+ UserId int32 //玩家ID
+ IsRob bool // 是否是机器人
+ AllScore int // 总得分
+}
+
+// 二人麻将
+type MahjongType struct {
+ RoomId int32 //房间ID
+ RoomRounds int32 //建房局数
+ RoomType int32 //房间类型
+ NowRound int32 //当前局数
+ BankId int32 //庄家ID
+ PlayerCount int32 //玩家数量
+ BaseScore int32 //底分
+ HuType []int64 //本局胡牌牌型
+ PlayerData []MahjongPerson //玩家信息
+}
+type MahjongPerson struct {
+ UserId int32 //玩家ID
+ UserIcon int32 //玩家头像
+ ChangeCoin int64 //玩家得分
+ SurplusCards []int64 //剩下的牌
+ CardsChow [][]int64 //吃的牌
+ CardsPong []int64 //碰的牌
+ CardsMingKong []int64 //明杠的牌
+ CardsAnKong []int64 //暗杠的牌
+ IsWin int32 //输赢
+ StartCoin int64 //开始金币
+ Tax int64 //税,不一定有值,只是作为一个临时变量使用
+ ClubPump int64 //俱乐部额外抽水
+ IsRob bool //是否是机器人
+ IsFirst bool
+ Platform string `json:"-"`
+ Channel string `json:"-"`
+ Promoter string `json:"-"`
+ PackageTag string `json:"-"`
+ InviterId int32 `json:"-"`
+ WBLevel int32 //黑白名单等级
+}
+
+// 百人牛牛 龙虎斗 百家乐 红黑大战
+type HundredType struct {
+ RegionId int32 //边池ID 庄家 天 地 玄 黄
+ IsWin int //边池输赢
+ Rate int //倍数
+ CardsInfo []int32 //扑克牌值
+ PlayerData []HundredPerson //玩家属性
+ CardsKind int32 //牌类型
+ CardPoint int32 //点数
+ IsSmartOperation bool // 是否启用智能化运营
+}
+type HundredPerson struct {
+ UserId int32 //用户Id
+ UserBetTotal int64 //用户下注
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ IsRob bool //是否是机器人
+ IsFirst bool
+ WBLevel int32 //黑白名单等级
+ Result int32 //单控结果
+ UserBetTotalDetail []int64 //用户下注明细
+}
+
+// 碰撞
+type CrashType struct {
+ RegionId int32 //边池ID 庄家 天 地 玄 黄
+ IsWin int //边池输赢
+ Rate int //倍数
+ CardsInfo []int32 //扑克牌值
+ PlayerData []CrashPerson //玩家属性
+ CardsKind int32 //牌类型
+ CardPoint int32 //点数
+ IsSmartOperation bool // 是否启用智能化运营
+ Hash string //Hash
+ Period int //当前多少期
+ Wheel int //第几轮
+}
+
+// 碰撞
+type CrashPerson struct {
+ UserId int32 //用户Id
+ UserBetTotal int64 //用户下注
+ UserMultiple int32 //下注倍数
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ IsRob bool //是否是机器人
+ IsFirst bool
+ WBLevel int32 //黑白名单等级
+ Result int32 //单控结果
+ UserBetTotalDetail []int64 //用户下注明细
+ Tax int64 //税收
+}
+
+// 十点半
+type TenHalfType struct {
+ RoomId int32 //房间ID
+ RoomRounds int32 //建房局数
+ RoomType int32 //房间类型
+ NowRound int32 //当前局数
+ BankId int32 //庄家ID
+ PlayerCount int //玩家数量
+ BaseScore int32 //底分
+ PlayerData []TenHalfPerson //玩家信息
+ ClubRate int32 //俱乐部抽水比例
+ IsSmartOperation bool // 是否启用智能化运营
+}
+
+type TenHalfPerson struct {
+ UserId int32 //玩家ID
+ UserIcon int32 //玩家头像
+ ChangeCoin int64 //玩家得分
+ Cardinfo []int32 //牌值
+ IsWin int32 //输赢
+ Tax int64 //税,不一定有值,只是作为一个临时变量使用
+ ClubPump int64 //俱乐部额外抽水
+ IsRob bool //是否是机器人
+ Flag int //标识
+ IsFirst bool
+ StartCoin int64 //开始金币
+ Platform string `json:"-"`
+ Channel string `json:"-"`
+ Promoter string `json:"-"`
+ PackageTag string `json:"-"`
+ InviterId int32 `json:"-"`
+ WBLevel int32 //黑白名单等级
+}
+
+type GanDengYanType struct {
+ RoomId int // 房间Id
+ RoomRounds int32 // 建房局数
+ NowRound int32 // 当前局数
+ BombScore int // 炸弹倍率
+ BaseScore int32 // 底分
+ PlayerCount int32 // 玩家数量
+ BankerId int32 // 庄家id
+ PlayerData []GanDengYanPlayer // 玩家信息
+ ClubRate int32 // 俱乐部抽水比例
+}
+
+type GanDengYanHandCards struct {
+ Cards []int64
+ Index int32
+}
+
+type GanDengYanPlayer struct {
+ UserId int32 // 玩家ID
+ UserIcon int32 // 玩家头像
+ ChangeCoin int64 // 玩家得分
+ OutCards []*GanDengYanHandCards // 出的牌
+ SurplusCards []int32 // 剩下的牌
+ IsWin int64 // 输赢
+ IsRob bool // 是否是机器人
+ IsFirst bool
+ Tax int64 // 税,不一定有值,只是作为一个临时变量使用
+ ClubPump int64 // 俱乐部额外抽水
+ StartCoin int64 // 开始的金币数量
+ Platform string `json:"-"`
+ Channel string `json:"-"`
+ Promoter string `json:"-"`
+ PackageTag string `json:"-"`
+ InviterId int32 `json:"-"`
+ WBLevel int32 //黑白名单等级
+ NumMagic int32 // 癞子或2倍数
+ NumCards int32 // 剩余牌数
+ NumHand int32 // 手牌倍数
+ Num int32 // 总倍数
+}
+
+// 红包扫雷
+type RedPackGameType struct {
+ BankerId int32 //庄家id
+ BankerBet int32 //庄家下注金额
+ BankerWin int32 //庄家赢取
+ BankerRest int32 //庄家退回钱数
+ Rate int32 //倍数
+ BombNum int32 //雷号
+ HitBombCnt int32 //中雷的人数
+ PlayerData []*RedPackPerson //玩家属性
+}
+type RedPackPerson struct {
+ UserId int32 //用户Id
+ IsFirst bool
+ BeforeCoin int64 //抢包前金额
+ AfterCoin int64 //抢包后金额
+ ChangeCoin int64 //金额变化
+ GrabCoin int32 //抢到红包的数量
+ IsHit bool //是否中雷
+ IsRob bool //是否是机器人
+}
+
+// 鱼场
+type BulletLevelTimes struct {
+ Level int32 //等级
+ Times int32 //次数
+}
+type FishCoinNum struct {
+ ID int32 // 鱼id
+ Num int32 //打死数量
+ Power int32 //子弹价值
+ Coin int32 //金币(总金币)
+ HitNum int32 //击中次数
+}
+
+type FishPlayerData struct {
+ UserId int32 //玩家ID
+ UserIcon int32 //玩家头像
+ TotalIn int64 //玩家该阶段总投入
+ TotalOut int64 //玩家该阶段总产出
+ CurrCoin int64 //记录时玩家当前金币量
+}
+
+type FishDetiel struct {
+ BulletInfo *[]BulletLevelTimes //子弹统计
+ HitInfo *[]FishCoinNum //
+ PlayData *FishPlayerData //统计
+}
+
+// 拉霸
+// 绝地求生记录详情
+type IslandSurvivalGameType struct {
+ //all
+ RoomId int32 //房间Id
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ BetCoin int64 //下注金额
+ WinCoin int64 //下注赢取金额
+ IsFirst bool
+ Smallgamewinscore int64 //吃鸡游戏赢取的分数
+ ChangeCoin int64 //本局游戏金额总变化
+ FreeTimes int32 //免费转动次数
+ AllWinNum int32 //中奖的线数
+ LeftEnemy int32 //剩余敌人,需保存
+ Killed int32 //总击杀敌人,需保存
+ HitPoolIdx int //下注索引
+ Cards []int //15张牌
+}
+
+const (
+ WaterMarginSmallGame_Unop int32 = iota
+ WaterMarginSmallGame_AddOp
+ WaterMarginSmallGame_SubOp
+)
+
+// 水浒传小游戏数据
+type WaterMarginSmallGameInfo struct {
+ AddOrSub int32 //加减操作 0:表示未操作 1:加 2:减
+ Score int64 //本局小游戏参与的分数
+ Multiple int32 //倍数 0:表示本局输了 >1:表示猜对小游戏
+ Dice1 int32 //骰子1的点数
+ Dice2 int32 //骰子2的点数
+}
+
+type WaterMarginType struct {
+ RoomId int32 //房间Id
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ IsFirst bool //是否一次游戏
+ ChangeCoin int64 //金额变化
+ Score int32 //总押注数
+ AllWinNum int32 //中奖的线数
+ FreeTimes int32 //免费转动次数
+ WinScore int32 //中奖的倍率
+ AllLine int32 //线路数
+ JackpotNowCoin int64 //爆奖金额
+ Cards []int //15张牌
+ HitPoolIdx int //压分的索引
+ JackpotHitFlag int //如果jackpotnowcoin>0;该值标识中了哪些奖池,二进制字段 1:小奖(第0位) 2:中奖(第1位) 4:大奖(第2位)
+ TrigFree bool //是否触发免费转动
+ SMGame []*WaterMarginSmallGameInfo //小游戏数据
+}
+
+type FootBallHeroesType struct {
+ RoomId int32 //房间Id
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ IsFirst bool
+ Score int32 //总押注数
+ FreeTimes int32 //免费转动次数
+ WinScore int32 //中奖的倍率
+ Cards []int //15张牌
+ CoinReward []int64 //礼物奖励
+ Smallgamescore int64 //小游戏分数
+ Smallgamewinscore int64 //小游戏赢取的分数
+ SmallgameList []int32 //小游戏记录
+ HitPoolIdx int //当前命中的奖池
+}
+type FruitMachineType struct {
+ RoomId int32 //房间Id
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ IsFirst bool
+ ChangeCoin int64 //金额变化
+ Score int32 //总押注数
+ AllWinNum int32 //中奖的线数
+ FreeTimes int32 //免费转动次数
+ WinScore int32 //中奖的倍率
+ AllLine int32 //线路数
+ JackpotNowCoin int64 //爆奖金额
+ Cards []int //15张牌
+ Smallgamescore int64 //小游戏分数
+ HitPoolIdx int //当前命中的奖池
+}
+type GoddessType struct {
+ RoomId int32 //房间Id
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ Score int32 //总押注数
+ IsFirst bool
+ Smallgamescore int64 //小游戏分数
+ Smallgamewinscore int64 //小游戏赢取的分数
+ SmallgameCard int32 //小游戏卡牌
+ CardsGoddess [3]int32 //3张牌
+ BetMultiple int32 //押倍倍数
+ SmallgameList []int32 //小游戏记录
+ HitPoolIdx int //当前命中的奖池
+}
+type RollLineType struct {
+ RoomId int32 //房间Id
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ Score int32 //总押注数
+ IsFirst bool
+ AllWinNum int32 //中奖的线数
+ FreeTimes int32 //免费转动次数
+ WinScore int32 //中奖的倍率
+ AllLine int32 //线路数
+ RoseCount int32 //玫瑰数量
+ Cards []int //15张牌
+ CoinReward []int64 //礼物奖励
+ HitPoolIdx int //当前命中的奖池
+}
+type IceAgeType struct {
+ RoomId int32 //房间Id
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ Score int32 //总押注数
+ Tax int64 //暗税
+ IsFirst bool
+ AllWinNum int32 //中奖的线数
+ FreeTimes int32 //免费转动次数
+ WinScore int32 //中奖的倍率
+ AllLine int32 //线路数
+ Cards [][]int32 // 消除前后的牌(消除前15张,消除后15张...)
+ BetLines []int64 //下注的线
+ UserName string // 昵称
+ TotalPriceValue int64 // 总赢分
+ IsFree bool // 是否免费
+ TotalBonusValue int64 // 总bonus数
+ WBLevel int32 //黑白名单等级
+ WinLines [][]int // 赢分的线
+ WinJackpot int64 // 赢奖池分数
+ WinBonus int64 // 赢小游戏分数
+}
+type TamQuocType struct {
+ RoomId int32 //房间Id
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ Score int32 //总押注数
+ Tax int64 //暗税
+ IsFirst bool
+ AllWinNum int32 //中奖的线数
+ FreeTimes int32 //免费转动次数
+ WinScore int32 //中奖的倍率
+ AllLine int32 //线路数
+ Cards []int32 //15张牌
+ BetLines []int64 //下注的线
+ WBLevel int32 //黑白名单等级
+ UserName string // 昵称
+ TotalPriceValue int64 // 总赢分
+ IsFree bool // 是否免费
+ TotalBonusValue int64 // 总bonus数
+ WinLines []int // 赢分的线
+ WinJackpot int64 // 赢奖池分数
+ WinBonus int64 // 赢小游戏分数
+}
+type CaiShenType struct {
+ RoomId int32 //房间Id
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ Score int32 //总押注数
+ Tax int64 //暗税
+ IsFirst bool
+ AllWinNum int32 //中奖的线数
+ FreeTimes int32 //免费转动次数
+ WinScore int32 //中奖的倍率
+ AllLine int32 //线路数
+ Cards []int32 //15张牌
+ WBLevel int32 //黑白名单等级
+ BetLines []int64 //下注的线
+ UserName string // 昵称
+ TotalPriceValue int64 // 总赢分
+ IsFree bool // 是否免费
+ TotalBonusValue int64 // 总bonus数
+ WinLines []int // 赢分的线
+ WinJackpot int64 // 赢奖池分数
+ WinBonus int64 // 赢小游戏分数
+}
+type AvengersType struct {
+ RoomId int32 //房间Id
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ Score int32 //总押注数
+ Tax int64 //暗税
+ IsFirst bool
+ AllWinNum int32 //中奖的线数
+ FreeTimes int32 //免费转动次数
+ WinScore int32 //中奖的倍率
+ AllLine int32 //线路数
+ Cards []int32 //15张牌
+ WBLevel int32 //黑白名单等级
+ BetLines []int64 //下注的线
+ UserName string // 昵称
+ TotalPriceValue int64 // 总赢分
+ IsFree bool // 是否免费
+ TotalBonusValue int64 // 总bonus数
+ WinLines []int // 赢分的线
+ WinJackpot int64 // 赢奖池分数
+ WinBonus int64 // 赢小游戏分数
+}
+type EasterIslandType struct {
+ RoomId int32 //房间Id
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ Score int32 //总押注数
+ Tax int64 //暗税
+ IsFirst bool
+ AllWinNum int32 //中奖的线数
+ FreeTimes int32 //免费转动次数
+ WinScore int32 //中奖的倍率
+ AllLine int32 //线路数
+ Cards []int32 //15张牌
+ WBLevel int32 //黑白名单等级
+ BetLines []int64 //下注的线
+ UserName string // 昵称
+ TotalPriceValue int64 // 总赢分
+ IsFree bool // 是否免费
+ TotalBonusValue int64 // 总bonus数
+ WinLines []int // 赢分的线
+ WinJackpot int64 // 赢奖池分数
+ WinBonus int64 // 赢小游戏分数
+}
+type RollTeamType struct {
+ RoomId int32 //房间Id
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ Score int32 //总押注数
+ AllWinNum int32 //中奖的线数
+ IsFirst bool
+ FreeTimes int32 //免费转动次数
+ WinScore int32 //中奖的倍率
+ AllLine int32 //线路数
+ PokerBaseCoin int32 //扑克游戏获得的金币
+ PokerRate int32 //游戏的翻倍值
+ GameCount int32 //游戏次数
+ HitPoolIdx int //当前命中的奖池
+ Cards []int //15张牌
+}
+type RollPerson struct {
+ UserId int32 //用户Id
+ UserBetTotal int64 //用户下注
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ IsFirst bool
+ IsRob bool //是否是机器人
+ WBLevel int32 //黑白名单等级
+}
+type RollHundredType struct {
+ RegionId int32 //边池ID -1.庄家 0.大众 1.雷克萨斯 2.宝马 3.奔驰 4.保时捷 5.玛莎拉蒂 6.兰博基尼 7.法拉利
+ IsWin int //边池输赢 1.赢 0.平 -1.输
+ Rate int //倍数
+ SType int32 //特殊牌型 临时使用
+ RollPerson []RollPerson
+}
+
+// 梭哈
+type FiveCardType struct {
+ RoomId int32 //房间ID
+ RoomRounds int32 //建房局数
+ RoomType int32 //房间类型
+ PlayerCount int //玩家数量
+ BaseScore int32 //底分
+ PlayerData []FiveCardPerson //玩家信息
+ ClubRate int32 //俱乐部抽水比例
+}
+
+// 梭哈
+type FiveCardPerson struct {
+ UserId int32 //玩家ID
+ UserIcon int32 //玩家头像
+ ChangeCoin int64 //玩家得分
+ Cardinfo []int32 //牌值
+ IsWin int32 //输赢
+ Tax int64 //税,不一定有值,只是作为一个临时变量使用
+ ClubPump int64 //俱乐部额外抽水
+ IsRob bool //是否是机器人
+ IsFirst bool //是否第一次
+ IsLeave bool //中途离开
+ Platform string `json:"-"`
+ Channel string `json:"-"`
+ Promoter string `json:"-"`
+ PackageTag string `json:"-"`
+ InviterId int32 `json:"-"`
+ BetTotal int64 //用户当局总下注
+ IsAllIn bool //是否全下
+ WBLevel int32 //黑白名单等级
+}
+
+// 骰子
+type RollPointPerson struct {
+ UserId int32 //用户Id
+ UserBetTotal int64 //用户下注
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ IsFirst bool
+ IsRob bool //是否是机器人
+ WBLevel int32 //黑白名单等级
+ BetCoin []int32 //押注金币
+}
+type RollPointType struct {
+ RoomId int32
+ Point []int32
+ Score []int32
+ BetCoin int64
+ WinCoin int64
+ Person []RollPointPerson
+}
+
+// 轮盘
+type RouletteType struct {
+ BankerInfo RouletteBanker //庄家信息
+ Person []RoulettePerson //下注玩家列表
+ RouletteRegion []RouletteRegion //下区域
+}
+type RouletteBanker struct {
+ Point int //当局开的点数
+ TotalBetCoin int64 //总下注金额
+ TotalWinCoin int64 //总输赢金额
+}
+type RouletteRegion struct {
+ Id int //0-156 下注位置编号
+ IsWin int //是否中奖
+ BetCoin int64 //当前区域总下注金额
+ WinCoin int64 //当前区域总输赢金额
+ Player []RoulettePlayer //当前区域下注玩家列表
+}
+type RoulettePlayer struct {
+ UserId int32 //用户Id
+ BetCoin int64 //当局下注额
+}
+type RoulettePerson struct {
+ UserId int32 //用户Id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ UserBetTotal int64 //用户总下注
+ UserWinCoin int64 //用户输赢
+ IsRob bool //是否是机器人
+ WBLevel int32 //黑白名单等级
+ BetCoin map[int]int64 //下注区域对应金额
+}
+
+// 九线拉王
+type NineLineKingType struct {
+ RoomId int32 //房间Id
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ IsFirst bool
+ ChangeCoin int64 //金额变化
+ Score int32 //总押注数
+ AllWinNum int32 //中奖的线数
+ FreeTimes int32 //免费转动次数
+ WinScore int32 //中奖的倍率
+ AllLine int32 //线路数
+ JackpotNowCoin int64 //爆奖金额
+ Cards []int //15张牌
+ HitPoolIdx int //当前命中的奖池
+ CommPool int64 //公共奖池
+ PersonPool int64 //私人奖池
+}
+
+// 飞禽走兽
+type RollAnimalsType struct {
+ BetTotal int64 //总下注
+ WinCoin int64 //用户输赢
+ WinFlag []int64 //中奖元素
+ RollLog []RollHundredType //每个区域下注信息
+}
+
+// 血战
+type BloodMahjongType struct {
+ RoomId int32 //房间ID
+ RoomRounds int32 //建房局数
+ RoomType int32 //房间类型
+ NowRound int32 //当前局数
+ BankId int32 //庄家ID
+ PlayerCount int32 //玩家数量
+ BaseScore int32 //底分
+ PlayerData []BloodMahjongPerson //玩家信息
+}
+
+// 碰杠牌
+type BloodMahjongCardsLog struct {
+ Card int64 //牌
+ Pos []int //0.东 1.南 2.西 3.北
+ Flag int //1.碰 2.明杠 3.暗杠 4.补杠
+}
+
+// 分数类型
+type BloodMahjongScoreTiles struct {
+ LogType int //0.胡 1.刮风 2.下雨 3.退税 4.查花猪 5.查大叫 6.被抢杠 补杠退钱 7.呼叫转移
+ OtherPos []int //源自哪个位置的玩家
+ Coin int64 //理论进账
+ ActualCoin int64 //实际进账
+ Rate int32 //倍率
+ Params []int64 //【Honor】//胡牌类型
+}
+type BloodMahjongPerson struct {
+ UserId int32 //玩家ID
+ UserIcon int32 //玩家头像
+ ChangeCoin int64 //玩家得分
+ Pos int32 //玩家位置 0.东 1.南 2.西 3.北
+ IsLeave bool //是否离场
+ Bankruptcy bool //是否破产
+ HuNumber int32 //第几胡 1 2 3
+ LackColor int64 //定缺花色
+ Hands []int64 //手牌
+ HuCards []int64 //胡牌
+ CardsLog []BloodMahjongCardsLog //碰杠牌
+ ScoreTiles []BloodMahjongScoreTiles //分数
+ IsWin int32 //输赢
+ Tax int64 //税,不一定有值,只是作为一个临时变量使用
+ StartCoin int64 //开始金币
+ ClubPump int64 //俱乐部额外抽水
+ IsRob bool //是否是机器人
+ Platform string `json:"-"`
+ Channel string `json:"-"`
+ Promoter string `json:"-"`
+ PackageTag string `json:"-"`
+ InviterId int32 `json:"-"`
+ WBLevel int32 //黑白名单等级
+}
+
+type PCProp struct {
+ Id uint32
+ TypeName string // 金币类型
+ AreaType int32 // 所在区域 0平台上 1有效区 2无效区 3小车内
+ CoinVal int64 // 金币面值
+ X float32
+ Y float32
+ Z float32
+ RotX float32
+ RotY float32
+ RotZ float32
+}
+
+// 推币机
+type PushingCoinRecord struct {
+ RoomId int // 房间id
+ RoomType int // 房间类型
+ GameMode int // 游戏模式
+ BaseScore int64 // 底分
+ ShakeTimes int32 // 震动次数
+ WallUpTimes int32 // 升墙次数
+ EventTimes []int32 // 事件次数
+ Props []*PCProp // 所有金币
+}
+
+type HuntingRecord struct {
+ RoomId int // 房间ID
+ BaseScore int // 底分
+ SnId int32 // 玩家ID
+ StartCoin int64 // 下注前金额
+ Coin int64 // 下注后金额
+ ChangeCoin int64 // 金币变化
+ CoinPool int64 // 爆奖金额
+ Point int64 // 单线点数
+ LineNum int64 // 线数
+ BetCoin int64 // 下注金额
+ WinCoin int64 // 产出金额
+ Level int // 当前关卡
+ Gain int64 // 翻牌奖励
+}
+
+// 对战三公
+type SanGongPVPType struct {
+ RoomId int32 //房间ID
+ RoomRounds int32 //建房局数
+ RoomType int32 //房间类型
+ NowRound int32 //当前局数
+ BankId int32 //庄家ID
+ PlayerCount int //玩家数量
+ BaseScore int32 //底分
+ PlayerData []SanGongPVPPerson //玩家信息
+ ClubRate int32 //俱乐部抽水比例
+ IsSmartOperation bool //是否启用智能化运营
+}
+type SanGongPVPPerson struct {
+ UserId int32 //玩家ID
+ UserIcon int32 //玩家头像
+ ChangeCoin int64 //玩家得分
+ Cardinfo []int32 //牌值
+ IsWin int32 //输赢
+ Tax int64 //税,不一定有值,只是作为一个临时变量使用
+ ClubPump int64 //俱乐部额外抽水
+ IsRob bool //是否是机器人
+ Flag int //标识
+ IsFirst bool
+ StartCoin int64 //开始金币
+ Platform string `json:"-"`
+ Channel string `json:"-"`
+ Promoter string `json:"-"`
+ PackageTag string `json:"-"`
+ InviterId int32 `json:"-"`
+ WBLevel int32 //黑白名单等级
+}
+
+// 德州牛仔
+type DZNZCardInfo struct {
+ CardId int32 //手牌ID 牛仔 公共牌 公牛
+ CardsInfo []int32 //扑克牌值
+ CardsKind int32 //牌类型
+}
+type DZNZZoneInfo struct {
+ RegionId int32 //13个下注区域
+ IsWin int //边池输赢
+ Rate float32 //倍数
+ PlayerData []HundredPerson //玩家属性
+ IsSmartOperation bool //是否启用智能化运营
+}
+type DZNZHundredInfo struct {
+ CardData []DZNZCardInfo //发牌信息
+ ZoneInfo []DZNZZoneInfo //每个下注区域信息
+}
+
+// 财运之神
+type FortuneZhiShenType struct {
+ //基本信息
+ RoomId int //房间Id
+ BasicScore int32 //单注
+ PlayerSnId int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ TotalBetCoin int64 //总押注
+ TotalLine int32 //总线数(固定)
+ TotalWinCoin int64 //总派彩
+ NowGameState int //当前游戏模式(0,1,2,3)普通/免费/停留旋转/停留旋转2
+ NowNRound int //第几轮
+ IsOffline int //0,1 正常(不显示)/掉线(显示)
+ FirstFreeTimes int //免费游戏剩余次数
+ SecondFreeTimes int //停留旋转游戏剩余次数
+ //中奖统计
+ HitPrizePool []int64 //命中奖池(小奖|中奖|大奖|巨奖)
+ WinLineNum int //中奖线个数
+ WinLineRate int64 //中奖线总倍率
+ WinLineCoin int64 //中奖线派彩
+ GemstoneNum int //宝石数量
+ GemstoneWinCoin int64 //宝石派彩
+ //详情
+ Cards []int32 //元素顺序 横向
+ GemstoneRateCoin []int64 //宝石金额 横向
+ //中奖线详情
+ WinLine []FortuneZhiShenWinLine
+ WBLevel int32 //黑白名单等级
+ TaxCoin int64 //税收
+}
+type FortuneZhiShenWinLine struct {
+ Id int //线号
+ EleValue int32 //元素值
+ Num int //数量
+ Rate int64 //倍率
+ WinCoin int64 //单线派彩
+ WinFreeGame int //(0,1,2)旋转并停留*3/免费游戏*6/免费游戏*3
+}
+
+// 金鼓齐鸣记录详情
+type GoldDrumWinLineInfo struct {
+ EleValue int //元素值
+ Rate int64 //倍率
+ WinCoin int64 //单线派彩
+ GameType int //(0,1,2)无/免费游戏/聚宝盆游戏
+}
+type GoldDrumGameType struct {
+ //all
+ RoomId int32 //房间Id
+ BasicScore int32 //单注分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ BetCoin int64 //下注金额
+ WinCoin int64 //下注赢取金额总金额
+ //Smallgamewinscore int64 //小游戏赢取的分数
+ ChangeCoin int64 //本局游戏金额总变化
+ FreeTimes int32 //免费转动次数
+ AllWinNum int32 //中奖的线数
+ HitPoolIdx int //下注索引
+ Cards []int //15张牌
+
+ NowGameState int //当前游戏模式(0,1)普通/免费
+ HitPrizePool []int64 //命中奖池(多喜小奖|多寿中奖|多禄大奖|多福巨奖)
+ WinLineRate int64 //中奖线总倍率
+ WinLineCoin int64 //中奖线派彩
+ WinLineInfo []GoldDrumWinLineInfo //中奖线详情
+
+ NowFreeGameTime int32 //当前免费游戏第几次
+ CornucopiaCards []int32 //聚宝盆游戏数据 -1 未开启 0小将 1中奖 2大奖 3巨奖
+ IsOffline bool //玩家是否掉线 true 掉线
+ WBLevel int32 //黑白名单等级
+ TaxCoin int64 //税收
+}
+
+// 金福报喜记录详情
+type CopperInfo struct {
+ Pos int32 //铜钱元素索引,从0开始
+ Coin int64 //铜钱奖励金币
+}
+type GoldBlessWinLineInfo struct {
+ EleValue int //元素值
+ Rate int64 //倍率
+ WinCoin int64 //单线派彩
+ GameType int //(0,1,2,3)无/免费游戏/聚宝盆游戏/招福纳财游戏
+}
+type GoldBlessGameType struct {
+ //all
+ RoomId int32 //房间Id
+ BasicScore int32 //单注分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ BetCoin int64 //下注金额
+ WinCoin int64 //本局总赢取金额
+ //Smallgamewinscore int64 //小游戏赢取的分数
+ ChangeCoin int64 //本局游戏金额总变化
+ FreeTimes int32 //免费转动次数
+ AllWinNum int32 //中奖的线数
+ HitPoolIdx int //下注索引
+ Cards []int //15张牌
+
+ NowGameState int //当前游戏模式(0,1,2)普通/免费/招福纳财
+ HitPrizePool []int64 //命中奖池(多喜小奖|多寿中奖|多禄大奖|多福巨奖)
+ WinLineRate int64 //中奖线总倍率
+ WinLineCoin int64 //中奖线派彩
+ WinLineInfo []GoldBlessWinLineInfo //中奖线详情
+ CopperNum int32 //本局铜钱数量
+ CopperCoin int64 //本局铜钱金额
+ CoppersInfo []CopperInfo //铜钱结构
+
+ NowFreeGameTime int32 //当前免费游戏第几次
+ CornucopiaCards []int32 //聚宝盆游戏数据 -1 未开启 0小将 1中奖 2大奖 3巨奖
+ IsOffline bool //玩家是否掉线 true 掉线
+ WBLevel int32 //黑白名单等级
+ TaxCoin int64 //税收
+}
+
+// 发发发
+type Classic888Type struct {
+ //基本信息
+ RoomId int //房间Id
+ BasicScore int32 //单注
+ PlayerSnId int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ TotalBetCoin int64 //总押注
+ TotalLine int32 //总线数(固定)
+ TotalWinCoin int64 //总派彩
+ NowGameState int //当前游戏模式(0,1,2)普通/免费/停留旋转
+ NowNRound int //第几轮
+ IsOffline int //0,1 正常(不显示)/掉线(显示)
+ FirstFreeTimes int //免费游戏剩余次数
+ SecondFreeTimes int //停留旋转游戏剩余次数
+ //中奖统计
+ HitPrizePool []int64 //命中奖池(小奖|中奖|大奖|巨奖)
+ WinLineNum int //中奖线个数
+ WinLineRate int64 //中奖线总倍率
+ WinLineCoin int64 //中奖线派彩
+ LanternNum int //灯笼数量
+ LanternWinCoin int64 //灯笼派彩
+ //详情
+ Cards []int32 //元素顺序 横向
+ LanternRateCoin []int64 //灯笼金额 横向
+ //中奖线详情
+ WinLine []Classic888WinLine
+ WBLevel int32 //黑白名单等级
+ TaxCoin int64 //税收
+}
+type Classic888WinLine struct {
+ Id int //线号
+ EleValue int32 //元素值
+ Num int //数量
+ Rate int64 //倍率
+ WinCoin int64 //单线派彩
+ WinFreeGame int //(0,1,2,3)旋转并停留*3/免费游戏*6/免费游戏*9/免费游戏*15
+}
+
+// 多福
+type RichBlessedType struct {
+ //基本信息
+ RoomId int //房间Id
+ BasicScore int32 //单注
+ PlayerSnId int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ TotalBetCoin int64 //总押注
+ TotalLine int32 //总线数(固定)
+ TotalWinCoin int64 //总派彩
+ NowGameState int //当前游戏模式(0,1,2)普通/免费/jack小游戏
+ NowNRound int //第几轮
+ IsOffline int //0,1 正常(不显示)/掉线(显示)
+ FirstFreeTimes int //免费游戏剩余次数
+ //中奖统计
+ WinLineNum int //中奖线个数
+ WinLineRate int64 //中奖线总倍率
+ WinLineCoin int64 //中奖线派彩
+ //详情
+ Cards []int32 //普通游戏/免费游戏
+ //jack游戏
+ JackEleValue int32 //元素值
+ JackMidCards []int64 //掀开位置
+ //中奖线详情
+ WinLine []RichBlessedWinLine
+ WBLevel int32 //黑白名单等级
+ TaxCoin int64 //税收
+ RealCtrl bool //人工调控
+ WBState int32 //调控等级
+ WeightKey int32 //当前使用权重
+}
+type RichBlessedWinLine struct {
+ Id int //线号
+ EleValue int32 //元素值
+ Num int //数量
+ Rate int64 //倍率
+ WinCoin int64 //单线派彩
+}
+
+// 经典777
+type FruitsType struct {
+ //基本信息
+ RoomId int //房间Id
+ BasicScore int32 //单注
+ PlayerSnId int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ TotalBetCoin int64 //总押注
+ TotalLine int32 //总线数(固定)
+ TotalWinCoin int64 //总派彩
+ NowGameState int //当前游戏模式(0,1,2)普通/免费/小玛丽
+ NowNRound int //第几轮
+ IsOffline int //0,1 正常(不显示)/掉线(显示)
+ FirstFreeTimes int //免费游戏剩余次数
+ MaryFreeTimes int //停留旋转游戏剩余次数
+ //中奖统计
+ HitPrizePool int64 //命中奖池金额
+ WinLineNum int //中奖线个数
+ WinLineRate int64 //中奖线总倍率
+ WinLineCoin int64 //中奖线派彩
+ JackPotNum int //777数量
+ JackPotWinCoin int64 //777派彩
+ //详情
+ Cards []int32 //普通游戏/免费游戏
+ //玛丽游戏
+ MaryOutSide int32 //外圈
+ MaryMidCards []int32 //内圈
+ //中奖线详情
+ WinLine []FruitsWinLine
+ WBLevel int32 //黑白名单等级
+ TaxCoin int64 //税收
+ RealCtrl bool //人工调控
+ WBState int32 //调控等级
+ WeightKey int32 //当前使用权重
+}
+type FruitsWinLine struct {
+ Id int //线号
+ EleValue int32 //元素值
+ Num int //数量
+ Rate int64 //倍率
+ WinCoin int64 //单线派彩
+ WinFreeGame int //(0,1,2,3,4,5)小玛丽1/小玛丽2/小玛丽3/免费5/免费8/免费10
+}
+
+// 无尽宝藏记录详情
+type EndlessTreasureWinLineInfo struct {
+ EleValue int //元素值
+ Rate int64 //倍率
+ WinCoin int64 //单线派彩
+ GameType int //(0,1,2,3)无/免费游戏/聚宝盆游戏/节节高游戏
+}
+type EndlessTreasureGameType struct {
+ //all
+ RoomId int32 //房间Id
+ TotalBetScore int32 //总押注
+ BasicScore float32 //单注分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ BetCoin int64 //下注金额
+ WinCoin int64 //本局总赢取金额
+ ChangeCoin int64 //本局游戏金额总变化
+ FreeTimes int32 //免费转动次数
+ AllWinNum int32 //中奖的线数
+ Cards []int //15张牌
+
+ NowGameState int //当前游戏模式(0,1,2)普通/免费/节节高
+ HitPrizePool []int64 //命中奖池(小奖|中奖|大奖|巨奖)
+ WinLineRate int64 //中奖线总倍率
+ WinLineCoin int64 //中奖线派彩
+ WinLineInfo []EndlessTreasureWinLineInfo //中奖线详情
+ CopperNum int32 //本局铜钱数量
+ CopperCoin int64 //本局铜钱金额
+ CoppersInfo []CopperInfo //铜钱结构
+
+ NowFreeGameTime int32 //当前免费游戏第几次
+ IsOffline bool //玩家是否掉线 true 掉线
+ AddFreeTimes int32 //本局新增免费转动次数
+ WBLevel int32 //黑白名单等级
+ TaxCoin int64 //税收
+}
+
+// 拉霸类游戏 基础牌局记录
+type SlotBaseResultType struct {
+ RoomId int32 //房间Id
+ BasicBet int32 //基本分(单注金额)
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ IsFirst bool //是否第一次玩游戏
+ IsFree bool //是否免费
+ TotalBet int32 //总押注金额
+ WinRate int32 //中奖的倍率
+ FreeTimes int32 //免费转动次数
+ AllWinNum int32 //中奖的总线数
+ Tax int64 //暗税
+ WBLevel int32 //黑白名单等级
+ SingleFlag int32 //0不控1单控赢2单控输
+ WinLineScore int64 //中奖线赢取分数
+ WinJackpot int64 //奖池赢取分数
+ WinSmallGame int64 //小游戏赢取分数
+ WinTotal int64 //本次游戏总赢取(本次游戏赢取总金额=中奖线赢取+奖池赢取+小游戏赢取)
+ Cards []int32 //15张牌
+ IsFoolPlayer bool //是否是新手
+}
+
+// 小火箭游戏 每局记录
+type SmallRocketBaseResultType struct {
+ //all
+ RoomId int32 //房间Id
+ TotalBetVal int64 //总押注
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ WinCoin int64 //本局总赢取金额
+
+ BetCoin1 int64 //下注金额1
+ BetMul1 float64 //下注倍数1
+ IsAutoBetAndTake1 bool //自动领取1
+ TakeBetMul1 float64 //提款倍数1
+ BetCoin2 int64 //下注金额2
+ BetMul2 float64 //下注倍数2
+ IsAutoBetAndTake2 bool //自动领取2
+ TakeBetMul2 float64 //提款倍数2
+
+ TaxCoin int64 //税收
+}
+
+type GameResultLog struct {
+ BaseResult *SlotBaseResultType
+ AllLine int32 //线路数
+ UserName string //昵称
+ WinLines []int //赢分的线
+ BetLines []int64 //下注的线
+}
+
+// 幸运骰子
+type LuckyDiceType struct {
+ RoomId int32 //房间Id
+ RoundId int32 //局数编号
+ BaseScore int32 //底分
+ PlayerSnid int32 //玩家id
+ UserName string //昵称
+ BeforeCoin int64 //本局前金额
+ AfterCoin int64 //本局后金额
+ ChangeCoin int64 //金额变化
+ Bet int64 //总押注数
+ Refund int64 //返还押注数
+ Award int64 //获奖金额
+ BetSide int32 //压大压小 0大 1小
+ Dices []int32 //3个骰子值
+ Tax int64 //赢家税收
+ WBLevel int32 //黑白名单等级
+}
+
+type CandyType struct {
+ RoomId int32 //房间Id
+ SpinID int32 //局数编号
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ Score int32 //总押注数
+ Tax int64 //暗税
+ IsFirst bool
+ AllWinNum int32 //中奖的线数
+ WinScore int32 //中奖的倍率
+ AllLine int32 //线路数
+ Cards []int32 //9张牌
+ BetLines []int64 //下注的线
+ WBLevel int32 //黑白名单等级
+ UserName string // 昵称
+ TotalPriceValue int64 // 总赢分
+ WinLines []int // 赢分的线
+ WinJackpot int64 // 赢奖池分数
+}
+type MiniPokerType struct {
+ RoomId int32 //房间Id
+ SpinID int32 //局数编号
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ Score int32 //总押注数
+ Tax int64 //暗税
+ IsFirst bool
+ WinScore int32 //中奖的倍率
+ Cards []int32 //5张牌
+ WBLevel int32 //黑白名单等级
+ UserName string // 昵称
+ TotalPriceValue int64 // 总赢分
+ WinJackpot int64 // 赢奖池分数
+}
+type CaoThapType struct {
+ RoomId int32 //房间Id
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ Score int32 //总押注数
+ Tax int64 //暗税
+ IsFirst bool
+ Cards []int32 //翻的牌
+ WBLevel int32 //黑白名单等级
+ UserName string // 昵称
+ TotalPriceValue int64 // 总赢分
+ WinJackpot int64 // 赢奖池分数
+ BetInfo []CaoThapBetInfo // 每次下注信息
+}
+type CaoThapBetInfo struct {
+ TurnID int32 // 操作ID
+ TurnTime int64 // 操作时间
+ BetValue int64 // 下注金额
+ Card int32 // 牌值
+ PrizeValue int64 // 赢分
+}
+
+// 21点
+type BlackJackType struct {
+ RoomId int32 //房间ID
+ RoomType int32 //房间类型
+ NumOfGames int //当前局数
+ PlayerCount int //玩家数量
+ PlayerData []*BlackJackPlayer //玩家信息
+ BankerCards []int32 //庄家牌
+ BankerCardType int32 //牌型 1:黑杰克 2:五小龙 3:其它点数 4:爆牌
+ BankerCardPoint []int32 //点数
+ BetCoin int64 //总下注
+ GainCoinTax int64 //总输赢分(税前)
+}
+
+type BlackJackCardInfo struct {
+ Cards []int32 //闲家牌
+ CardType int32 //牌型 1:黑杰克 2:五小龙 3:其它点数 4:爆牌
+ CardPoint []int32 //点数
+ BetCoin int64 //下注
+ GainCoinNoTax int64 //总输赢分(税后)
+ IsWin int32 //输赢 1赢 0平 -1输
+}
+
+type BlackJackPlayer struct {
+ UserId int32 //玩家ID
+ UserIcon int32 //玩家头像
+ Platform string `json:"-"`
+ Channel string `json:"-"`
+ Promoter string `json:"-"`
+ PackageTag string `json:"-"`
+ InviterId int32 `json:"-"`
+ WBLevel int32 //黑白名单等级
+ IsRob bool //是否是机器人
+ Flag int //标识
+ IsFirst bool //是否第一次
+ Hands []BlackJackCardInfo //牌值
+ IsWin int32 //输赢
+ GainCoinNoTax int64 //总输赢分(税后)
+ Tax int64 //税,不一定有值,只是作为一个临时变量使用
+ BaoCoin int64 //保险金
+ BaoChange int64 //保险金输赢分
+ BetCoin int64 //下注额
+ BetChange int64 //下注输赢分
+ Seat int //座位号
+}
+
+type DezhouPots struct {
+ BetTotal int64 //边池下注
+ Player []DezhouPotPlayer //边池的玩家
+}
+type DezhouPotPlayer struct {
+ Snid int32 //玩家ID
+ IsWin int32 //边池输赢
+}
+
+// 德州牌局记录
+type DeZhouUserOp struct {
+ Snid int32 // 操作人
+ Op int32 // 操作类型 见 dezhoupoker.proto
+ Stage int // 所处牌局阶段 见 constants.go
+ Chip int64 // 操作筹码, (不下注为0)
+ ChipOnTable int64 // 操作后桌子上筹码
+ Round int32 // 轮数
+ Sec float64 // 操作时距离本局开始时的秒数
+ TargetId int32 // 操作对象ID(没其他玩家为对象为0)
+}
+
+// 德州
+type DezhouType struct {
+ RoomId int32 //房间ID
+ RoomType int32 //房间类型
+ NumOfGames int32 //当前局数
+ BankId int32 //庄家ID
+ PlayerCount int //玩家数量
+ BaseScore int32 //底分
+ BaseCards []int32 //公牌 只限于德州用
+ PlayerData []DezhouPerson //玩家信息
+ Pots []DezhouPots //边池情况
+ Actions string //牌局记录
+ UserOps []*DeZhouUserOp //牌局记录new
+}
+type DezhouPerson struct {
+ UserId int32 //玩家ID
+ UserIcon int32 //玩家头像
+ Platform string `json:"-"`
+ Channel string `json:"-"`
+ Promoter string `json:"-"`
+ PackageTag string `json:"-"`
+ InviterId int32 `json:"-"`
+ WBLevel int32 //黑白名单等级
+ IsRob bool //是否是机器人
+ IsFirst bool //是否第一次
+ IsLeave bool //中途离开
+ InitCard []int32 //初始牌值
+ Cardinfo []int32 //牌值
+ IsWin int32 //输赢
+ GainCoinNoTax int64 //总输赢分(税后)
+ Tax int64 //税,不一定有值,只是作为一个临时变量使用
+ BetTotal int64 //用户当局总下注
+ IsAllIn bool //是否全下
+ RoundFold int32 //第几轮弃牌
+ CardInfoEnd []int32 //结算时的牌型
+ Seat int //座位号
+}
+
+// tienlen
+type TienLenType struct {
+ GameId int //游戏id
+ BaseScore int32 //底分
+ PlayerData []TienLenPerson //玩家信息
+}
+
+type TienLenPerson struct {
+ UserId int32 //玩家ID
+ IsRob bool //是否是机器人
+ BillCoin int64 //最终得分(税后)
+ BillTaxCoin int64 //最终税收
+
+ BombCoin int64 //炸弹输赢分(税后)
+ BombTaxCoin int64 //炸弹税收
+ CardInfoEnd []int32 //结算时的牌型
+}
+
+// chesstitians
+type ChesstitiansType struct {
+ GameId int //游戏id
+ RoomId int32 //房间ID
+ RoomType int32 //房间类型
+ NumOfGames int32 //当前局数
+ BankId int32 //房主ID
+ PlayerCount int //玩家数量
+ BaseScore int32 //底分
+ TaxRate int32 //税率(万分比)
+ PlayerData []*ChesstitiansPerson //玩家信息
+ RoomMode int
+}
+type ChesstitiansPerson struct {
+ UserId int32 //玩家ID
+ UserIcon int32 //玩家头像
+ Platform string `json:"-"`
+ Channel string `json:"-"`
+ Promoter string `json:"-"`
+ PackageTag string `json:"-"`
+ InviterId int32 `json:"-"`
+ WBLevel int32 //黑白名单等级
+ IsRob bool //是否是机器人
+ IsFirst bool //是否第一次
+ IsLeave bool //中途离开
+ IsWin int32 //输赢
+ Seat int //座位号
+ GainCoin int64 //手牌输赢分(税后)
+ GainTaxCoin int64 //手牌税收
+}
+
+// tala
+type TaLaType struct {
+ GameId int //游戏id
+ RoomId int32 //房间ID
+ RoomType int32 //房间类型
+ NumOfGames int32 //当前局数
+ BankId int32 //房主ID
+ PlayerCount int //玩家数量
+ BaseScore int32 //底分
+ TaxRate int32 //税率(万分比)
+ RoomMode int
+ PlayerData []TaLaPerson //玩家信息
+}
+type TaLaPerson struct {
+ UserId int32 //玩家ID
+ UserIcon int32 //玩家头像
+ Platform string `json:"-"`
+ Channel string `json:"-"`
+ Promoter string `json:"-"`
+ PackageTag string `json:"-"`
+ InviterId int32 `json:"-"`
+ WBLevel int32 //黑白名单等级
+ IsRob bool //是否是机器人
+ IsFirst bool //是否第一次
+ IsLeave bool //中途离开
+ Seat int //座位号
+ ChiCoin int64 //吃输赢分(税后)
+ BillCoin int64 //最终得分(税后)
+ ChiTaxCoin int64 //吃税收
+ BillTaxCoin int64 //最终税收
+ IsHu bool //胡
+ IsWin int32 //胜负 0平 1胜 2负
+ IsLoseHu bool //包赔
+ Phoms [][]int32 //phom
+ IsNoPhom bool //瘪
+ Cards []int32 //手牌
+ CardsValue int32 //点数
+ OpPhom []int32 //寄
+ TaLaPersonOp []TaLaPersonOp //操作信息
+}
+type TaLaPersonOp struct {
+ Round int32
+ MoCard int32
+ ChuCard int32
+ ChiCard int32
+ Cards []int32
+}
+
+// samloc
+type SamLocType struct {
+ GameId int //游戏id
+ RoomId int32 //房间ID
+ RoomType int32 //房间类型
+ NumOfGames int32 //当前局数
+ BankId int32 //房主ID
+ PlayerCount int //玩家数量
+ BaseScore int32 //底分
+ TaxRate int32 //税率(万分比)
+ PlayerData []SamLocPerson //玩家信息
+ RoomMode int
+}
+type SamLocPerson struct {
+ UserId int32 //玩家ID
+ UserIcon int32 //玩家头像
+ Platform string `json:"-"`
+ Channel string `json:"-"`
+ Promoter string `json:"-"`
+ PackageTag string `json:"-"`
+ InviterId int32 `json:"-"`
+ WBLevel int32 //黑白名单等级
+ IsRob bool //是否是机器人
+ IsFirst bool //是否第一次
+ IsLeave bool //中途离开
+ IsWin int32 //输赢
+ Seat int //座位号
+ GainCoin int64 //手牌输赢分(税后)
+ BombCoin int64 //炸弹输赢分(税后)
+ BillCoin int64 //最终得分(税后)
+ GainTaxCoin int64 //手牌税收
+ BombTaxCoin int64 //炸弹税收
+ BillTaxCoin int64 //最终税收
+ DelOrderCards map[int][]int32 //已出牌
+ CardInfoEnd []int32 //结算时的牌型
+ IsTianHu bool //是否天胡
+}
+
+// 娃娃机 每局记录
+type ClawdollResultType struct {
+ //all
+ RoomId int32 //房间Id
+ MachineId int32 //娃娃机Id
+ PlayerSnid int32 //玩家id
+ BeforeClawdollItemNum int64 //变化前娃娃币
+ AfterClawdollItemNum int64 //变化后娃娃币
+ IsWin bool //是否成功
+ Channel string //渠道
+ Name string //场次名字
+}
diff --git a/statistics/task/task/gamerate.go b/statistics/task/task/gamerate.go
new file mode 100644
index 0000000..1fbf2de
--- /dev/null
+++ b/statistics/task/task/gamerate.go
@@ -0,0 +1,110 @@
+package task
+
+import (
+ "encoding/json"
+ "go.mongodb.org/mongo-driver/bson"
+ "mongo.games.com/goserver/core/logger"
+ "slices"
+)
+
+// 场次平均倍数
+
+func PlayerGameRate(plt string, startTime, endTime string, gamefreeid int) (total, bombTotal, remain2Total int, rateAvg, bombRateAvg float64, err error) {
+ var totalRate, totalBombRate float64
+ err = GameDetailFunc(plt, startTime, endTime, gamefreeid, func(data bson.M) error {
+ rate, isBomb, bombRate, remain2 := GameDetailRate(data)
+ total++
+ if isBomb {
+ bombTotal++
+ }
+ totalRate += rate
+ totalBombRate += bombRate
+ if remain2 {
+ remain2Total++
+ }
+ return nil
+ })
+ if total > 0 {
+ rateAvg = totalRate / float64(total)
+ }
+ if bombTotal > 0 {
+ bombRateAvg = totalBombRate / float64(bombTotal)
+ }
+ return
+}
+
+// rate 赢分/底分
+// isBomb 是否有炸弹
+// bombRate 炸弹倍数,炸弹赢分/底分
+// remain2 是否有剩余2
+func GameDetailRate(data bson.M) (rate float64, isBomb bool, bombRate float64, remain2 bool) {
+ if data == nil {
+ return
+ }
+ gameid := data["gameid"].(int32)
+ gamefreeid := data["gamefreeid"].(int32)
+ logger.Logger.Tracef("GameDetail gameid:%d, gamefreeid:%d", gameid, gamefreeid)
+
+ detail := data["gamedetailednote"]
+ if detail == nil {
+ return
+ }
+
+ raw := new(RabbitMQDataRaw)
+ if err := json.Unmarshal([]byte(detail.(string)), raw); err != nil {
+ logger.Logger.Errorf("GameDetailCount Unmarshal 1 error:%v %v", err, gameid)
+ return
+ }
+
+ switch gameid {
+ case 207, 208, 209, 210, 240, 241, 242, 243, 244, 245, 246, 247: // tienlen
+ d := new(TienLenType)
+ b, err := json.Marshal(raw.Data)
+ if err != nil {
+ logger.Logger.Errorf("GameDetailCount Marshal error:%v %v", err, gameid)
+ return
+ }
+ if err := json.Unmarshal(b, d); err != nil {
+ logger.Logger.Errorf("GameDetailCount Unmarshal 2 error:%v %v", err, gameid)
+ return
+ }
+
+ for _, v := range d.PlayerData {
+ if v.BillCoin > 0 {
+ rate = float64(v.BillCoin+v.BillTaxCoin) / float64(d.BaseScore)
+ }
+ if v.BombCoin > 0 {
+ isBomb = true
+ bombRate = float64(v.BombCoin+v.BombTaxCoin) / float64(d.BaseScore)
+ }
+ if slices.ContainsFunc(v.CardInfoEnd, func(i int32) bool {
+ switch i {
+ case 51, 38, 25, 12:
+ return true
+ }
+ return false
+ }) {
+ remain2 = true
+ }
+ }
+
+ case 211, 212, 213, 214:
+ d := new(ThirteenWaterType)
+ b, err := json.Marshal(raw.Data)
+ if err != nil {
+ logger.Logger.Errorf("GameDetailCount Marshal error:%v %v", err, gameid)
+ return
+ }
+ if err := json.Unmarshal(b, d); err != nil {
+ logger.Logger.Errorf("GameDetailCount Unmarshal 2 error:%v %v", err, gameid)
+ return
+ }
+
+ for _, v := range d.PlayerData {
+ if v.AllScore > 0 {
+ rate = float64(v.AllScore) / float64(d.BaseScore)
+ }
+ }
+ }
+ return
+}
diff --git a/statistics/task/task/gametime.go b/statistics/task/task/gametime.go
new file mode 100644
index 0000000..3c184c7
--- /dev/null
+++ b/statistics/task/task/gametime.go
@@ -0,0 +1,104 @@
+package task
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "go.mongodb.org/mongo-driver/bson"
+ "go.mongodb.org/mongo-driver/mongo"
+ "go.mongodb.org/mongo-driver/mongo/options"
+ "mongo.games.com/game/common"
+ mongomodel "mongo.games.com/game/statistics/modelmongo"
+ "mongo.games.com/goserver/core/logger"
+ mymongo "mongo.games.com/goserver/core/mongox"
+)
+
+// 新用户平均游戏时长
+
+// 返回 总游戏时长,总局数,错误
+func NewPlayerGameTime(plt string, ids []int, startTime, endTime string, gamefreeid int) (int, int, error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ c, err := mymongo.GetLogCollection(plt, mongomodel.LogGamePlayerListLog)
+ if err != nil {
+ return 0, 0, err
+ }
+
+ c2, err := mymongo.GetLogCollection(plt, "log_gamedetailed")
+ if err != nil {
+ return 0, 0, err
+ }
+
+ var ret int
+ var total int
+ for _, v := range ids {
+ // 查询玩家游戏时长
+ where := bson.M{"snid": v, "time": bson.M{"$gte": s, "$lt": e}}
+ if gamefreeid > 0 {
+ where["gamefreeid"] = gamefreeid
+ }
+ cur, err := c.Find(context.TODO(), where, options.Find().SetProjection(bson.M{"gamedetailedlogid": 1}))
+ if err != nil {
+ logger.Logger.Errorf("find player gamedetailedlogid get err: %v", err)
+ return 0, 0, err
+ }
+ for cur.TryNext(context.TODO()) {
+ var vv struct{ Gamedetailedlogid string }
+ if err = cur.Decode(&vv); err != nil {
+ logger.Logger.Errorf("find player gamedetailedlogid decode err: %v", err)
+ cur.Close(context.Background())
+ return 0, 0, err
+ }
+ // 查询游戏时长
+ var res2 struct{ Gametiming int }
+ r := c2.FindOne(context.TODO(), bson.M{"logid": vv.Gamedetailedlogid}, options.FindOne().SetProjection(bson.M{"gametiming": 1}))
+ if r.Err() != nil && !errors.Is(r.Err(), mongo.ErrNoDocuments) {
+ logger.Logger.Errorf("find game time get err: %v", err)
+ cur.Close(context.Background())
+ return 0, 0, err
+ }
+ if err := r.Decode(&res2); err != nil {
+ logger.Logger.Errorf("find game time decode err: %v", err)
+ cur.Close(context.Background())
+ return 0, 0, err
+ }
+ ret += res2.Gametiming
+ total++
+ }
+ cur.Close(context.Background())
+ }
+
+ return ret, total, nil
+}
+
+// NewPlayerGameTimeAvg 新用户平均游戏时长
+// 新用户平均游戏时长(不算大厅时间):当天注册的玩家在房间中的总时长/当天注册总人数
+// 返回 参与人数,游戏时长
+func NewPlayerGameTimeAvg(plt string, startTime, endTime string, gamefreeid int) (int, int, error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ if s.IsZero() || e.IsZero() {
+ return 0, 0, fmt.Errorf("time format error")
+ }
+ ids, err := GetNewPayerIds(plt, startTime, endTime)
+ if err != nil {
+ return 0, 0, err
+ }
+ if len(ids) == 0 {
+ return 0, 0, nil
+ }
+ a, _, err := NewPlayerGameTime(plt, ids, startTime, endTime, gamefreeid)
+ if err != nil {
+ return 0, 0, err
+ }
+ if len(ids) == 0 {
+ return 0, 0, nil
+ }
+
+ b, err := PlayingGameCount(plt, ids, startTime, endTime, gamefreeid)
+ if err != nil {
+ return 0, 0, err
+ }
+ if b == 0 {
+ return 0, 0, nil
+ }
+ return b, a, err
+}
diff --git a/statistics/task/task/rechargeoffline.go b/statistics/task/task/rechargeoffline.go
new file mode 100644
index 0000000..fe31c2d
--- /dev/null
+++ b/statistics/task/task/rechargeoffline.go
@@ -0,0 +1,62 @@
+package task
+
+import (
+ "context"
+ "fmt"
+ "go.mongodb.org/mongo-driver/mongo/options"
+
+ "go.mongodb.org/mongo-driver/bson"
+ "mongo.games.com/game/common"
+ mymongo "mongo.games.com/goserver/core/mongox"
+)
+
+type RechargeOfflineData struct {
+ Snid int32
+ Coin int64
+}
+
+func OfflineCoin(plt string, startTime, endTime string) (res []*RechargeOfflineData, err error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ if s.IsZero() || e.IsZero() {
+ return nil, fmt.Errorf("time format error")
+ }
+ c, err := mymongo.GetLogCollection(plt, "log_dbshop")
+ if err != nil {
+ return nil, err
+ }
+ cur, err := c.Find(context.TODO(), bson.M{"ts": bson.M{"$gte": s.Unix(), "$lt": e.Unix()}, "consume": 3, "state": 1})
+ if err != nil {
+ return nil, err
+ }
+ defer cur.Close(context.Background())
+
+ var list []struct{ Snid int32 }
+ if err = cur.All(context.Background(), &list); err != nil {
+ return nil, err
+ }
+
+ snids := map[int32]struct{}{}
+
+ for _, v := range list {
+ if _, ok := snids[v.Snid]; ok {
+ continue
+ }
+ snids[v.Snid] = struct{}{}
+
+ // 最后金币数量
+ c, err := mymongo.GetLogCollection(plt, "log_coinex")
+ if err != nil {
+ return nil, err
+ }
+ one := c.FindOne(context.TODO(), bson.M{"snid": v.Snid, "cointype": 0, "ts": bson.M{"$lt": e.Unix()}}, options.FindOne().SetSort(bson.M{"ts": -1}))
+ if one.Err() != nil {
+ return nil, one.Err()
+ }
+ var data struct{ Restcount int64 }
+ if err = one.Decode(&data); err != nil {
+ return nil, err
+ }
+ res = append(res, &RechargeOfflineData{Snid: v.Snid, Coin: data.Restcount})
+ }
+ return
+}
diff --git a/statistics/task/task/robotwin.go b/statistics/task/task/robotwin.go
new file mode 100644
index 0000000..37fa8cf
--- /dev/null
+++ b/statistics/task/task/robotwin.go
@@ -0,0 +1,138 @@
+package task
+
+import (
+ "encoding/json"
+ "go.mongodb.org/mongo-driver/bson"
+ "mongo.games.com/goserver/core/logger"
+ "sort"
+)
+
+// RobotWinRate 机器人胜利
+// 返回 玩家胜利局数,总局数
+func RobotWinRate(plt string, startTime, endTime string, gamefreeid int) (a, b int, err error) {
+ err = GameDetailFunc(plt, startTime, endTime, gamefreeid, func(data bson.M) error {
+ isPlayerWin, to := GameDetailRobot(data)
+ if isPlayerWin {
+ a++
+ }
+ b += to
+ return nil
+ })
+ return
+}
+
+func GameDetailRobot(data bson.M) (isPlayerWin bool, to int) {
+ if data == nil {
+ return
+ }
+ gameid := data["gameid"].(int32)
+ gamefreeid := data["gamefreeid"].(int32)
+ logger.Logger.Tracef("GameDetailRobot gameid:%d, gamefreeid:%d", gameid, gamefreeid)
+
+ detail := data["gamedetailednote"]
+ if detail == nil {
+ return
+ }
+
+ raw := new(RabbitMQDataRaw)
+ if err := json.Unmarshal([]byte(detail.(string)), raw); err != nil {
+ logger.Logger.Errorf("GameDetailRobot Unmarshal 1 error:%v %v", err, gameid)
+ return
+ }
+
+ switch gameid {
+ case 207, 208, 209, 210, 240, 241, 242, 243, 244, 245, 246, 247: // tienlen
+ data := new(TienLenType)
+ b, err := json.Marshal(raw.Data)
+ if err != nil {
+ logger.Logger.Errorf("GameDetailCount Marshal error:%v %v", err, gameid)
+ return
+ }
+ if err := json.Unmarshal(b, data); err != nil {
+ logger.Logger.Errorf("GameDetailCount Unmarshal 2 error:%v %v", err, gameid)
+ return
+ }
+
+ var has, hasPlayer bool
+ for _, v := range data.PlayerData {
+ if v.IsRob {
+ has = true
+ }
+ if !v.IsRob {
+ hasPlayer = true
+ }
+ }
+ if !has || !hasPlayer {
+ // 没有机器人
+ return
+ }
+
+ sort.Slice(data.PlayerData, func(i, j int) bool {
+ a, b := data.PlayerData[i].BillCoin+data.PlayerData[i].BillTaxCoin, data.PlayerData[j].BillCoin+data.PlayerData[j].BillTaxCoin
+ if a != b {
+ return a > b
+ }
+ if data.PlayerData[i].IsRob != data.PlayerData[j].IsRob {
+ return !data.PlayerData[i].IsRob
+ }
+ return data.PlayerData[i].UserId < data.PlayerData[j].UserId
+ })
+
+ for k, v := range data.PlayerData {
+ if !v.IsRob && v.BillCoin > 0 && k <= 1 {
+ isPlayerWin = true
+ break
+ }
+ }
+
+ to = 1
+
+ case 211, 212, 213, 214:
+ data := new(ThirteenWaterType)
+ b, err := json.Marshal(raw.Data)
+ if err != nil {
+ logger.Logger.Errorf("GameDetailCount Marshal error:%v %v", err, gameid)
+ return
+ }
+ if err := json.Unmarshal(b, data); err != nil {
+ logger.Logger.Errorf("GameDetailCount Unmarshal 2 error:%v %v", err, gameid)
+ return
+ }
+
+ var has, hasPlayer bool
+ for _, v := range data.PlayerData {
+ if v.IsRob {
+ has = true
+ }
+ if !v.IsRob {
+ hasPlayer = true
+ }
+ }
+ if !has || !hasPlayer {
+ // 没有机器人
+ return
+ }
+
+ sort.Slice(data.PlayerData, func(i, j int) bool {
+ a, b := data.PlayerData[i].AllScore, data.PlayerData[j].AllScore
+ if a != b {
+ return a > b
+ }
+ if data.PlayerData[i].IsRob != data.PlayerData[j].IsRob {
+ return !data.PlayerData[i].IsRob
+ }
+ return data.PlayerData[i].UserId < data.PlayerData[j].UserId
+ })
+
+ for k, v := range data.PlayerData {
+ if !v.IsRob && v.AllScore > 0 && k <= 1 {
+ isPlayerWin = true
+ break
+ }
+ }
+
+ to = 1
+ }
+
+ return
+}