From 801200f4bd011954da473b581886d53f050d4f2e Mon Sep 17 00:00:00 2001
From: sk <123456@qq.com>
Date: Tue, 3 Dec 2024 13:13:24 +0800
Subject: [PATCH 1/3] add statistics
---
common/time.go | 5 +
go.mod | 10 +-
go.sum | 15 +-
statistics/.gitignore | 29 +
statistics/README.md | 7 +
statistics/build_linux.bat | 8 +
statistics/constant/constant.go | 8 +
statistics/etc/config.yaml | 27 +
statistics/etc/mongo.yaml | 53 +
statistics/etc/mysql.yaml | 38 +
statistics/logger.xml | 22 +
statistics/main.go | 212 +++
.../modelmongo/log_gameplayerlistlog.go | 47 +
statistics/modelmongo/log_invitescore.go | 19 +
statistics/modelmongo/log_item.go | 27 +
statistics/modelmongo/log_login.go | 33 +
statistics/modelmongo/user_account.go | 24 +
statistics/modelmysql/bankrupt.go | 12 +
statistics/modelmysql/log_invitescore.go | 26 +
statistics/modelmysql/log_itemgain.go | 16 +
statistics/modelmysql/log_login.go | 26 +
statistics/modelmysql/log_login_mid.go | 6 +
statistics/modelmysql/log_mid.go | 11 +
statistics/modelmysql/tables.go | 17 +
statistics/modelmysql/user_account.go | 18 +
statistics/modelmysql/user_id.go | 9 +
statistics/modelmysql/user_login.go | 29 +
statistics/mq/handler.go | 1 +
statistics/mq/readme | 1 +
statistics/static/readme | 1 +
statistics/static/user_login.go | 371 ++++
statistics/syn/datasync.go | 102 ++
statistics/syn/log_invitescore.go | 291 +++
statistics/syn/log_itemgain.go | 61 +
statistics/syn/log_login.go | 181 ++
statistics/syn/readme | 1 +
statistics/syn/user_account.go | 105 ++
statistics/task/build_linux.bat | 5 +
statistics/task/etc/DB_GameFree.dat | Bin 0 -> 24144 bytes
statistics/task/etc/config.yaml | 221 +++
statistics/task/etc/mongo.yaml | 53 +
statistics/task/etc/mysql.yaml | 38 +
statistics/task/excelmgr.go | 88 +
statistics/task/gamefree/db_gamefree.go | 77 +
statistics/task/logger.xml | 22 +
statistics/task/main.go | 427 +++++
statistics/task/readme | 1 +
statistics/task/task/bankruptcy.go | 189 ++
statistics/task/task/bankruptoffline.go | 153 ++
statistics/task/task/coin.go | 77 +
statistics/task/task/ctrlrate.go | 214 +++
statistics/task/task/gamebankruptcy.go | 69 +
statistics/task/task/gamecount.go | 60 +
statistics/task/task/gamelogtype.go | 1625 +++++++++++++++++
statistics/task/task/gamerate.go | 110 ++
statistics/task/task/gametime.go | 104 ++
statistics/task/task/rechargeoffline.go | 62 +
statistics/task/task/robotwin.go | 138 ++
58 files changed, 5593 insertions(+), 9 deletions(-)
create mode 100644 statistics/.gitignore
create mode 100644 statistics/README.md
create mode 100644 statistics/build_linux.bat
create mode 100644 statistics/constant/constant.go
create mode 100644 statistics/etc/config.yaml
create mode 100644 statistics/etc/mongo.yaml
create mode 100644 statistics/etc/mysql.yaml
create mode 100644 statistics/logger.xml
create mode 100644 statistics/main.go
create mode 100644 statistics/modelmongo/log_gameplayerlistlog.go
create mode 100644 statistics/modelmongo/log_invitescore.go
create mode 100644 statistics/modelmongo/log_item.go
create mode 100644 statistics/modelmongo/log_login.go
create mode 100644 statistics/modelmongo/user_account.go
create mode 100644 statistics/modelmysql/bankrupt.go
create mode 100644 statistics/modelmysql/log_invitescore.go
create mode 100644 statistics/modelmysql/log_itemgain.go
create mode 100644 statistics/modelmysql/log_login.go
create mode 100644 statistics/modelmysql/log_login_mid.go
create mode 100644 statistics/modelmysql/log_mid.go
create mode 100644 statistics/modelmysql/tables.go
create mode 100644 statistics/modelmysql/user_account.go
create mode 100644 statistics/modelmysql/user_id.go
create mode 100644 statistics/modelmysql/user_login.go
create mode 100644 statistics/mq/handler.go
create mode 100644 statistics/mq/readme
create mode 100644 statistics/static/readme
create mode 100644 statistics/static/user_login.go
create mode 100644 statistics/syn/datasync.go
create mode 100644 statistics/syn/log_invitescore.go
create mode 100644 statistics/syn/log_itemgain.go
create mode 100644 statistics/syn/log_login.go
create mode 100644 statistics/syn/readme
create mode 100644 statistics/syn/user_account.go
create mode 100644 statistics/task/build_linux.bat
create mode 100644 statistics/task/etc/DB_GameFree.dat
create mode 100644 statistics/task/etc/config.yaml
create mode 100644 statistics/task/etc/mongo.yaml
create mode 100644 statistics/task/etc/mysql.yaml
create mode 100644 statistics/task/excelmgr.go
create mode 100644 statistics/task/gamefree/db_gamefree.go
create mode 100644 statistics/task/logger.xml
create mode 100644 statistics/task/main.go
create mode 100644 statistics/task/readme
create mode 100644 statistics/task/task/bankruptcy.go
create mode 100644 statistics/task/task/bankruptoffline.go
create mode 100644 statistics/task/task/coin.go
create mode 100644 statistics/task/task/ctrlrate.go
create mode 100644 statistics/task/task/gamebankruptcy.go
create mode 100644 statistics/task/task/gamecount.go
create mode 100644 statistics/task/task/gamelogtype.go
create mode 100644 statistics/task/task/gamerate.go
create mode 100644 statistics/task/task/gametime.go
create mode 100644 statistics/task/task/rechargeoffline.go
create mode 100644 statistics/task/task/robotwin.go
diff --git a/common/time.go b/common/time.go
index e54bfaa..907796b 100644
--- a/common/time.go
+++ b/common/time.go
@@ -204,3 +204,8 @@ func StrTimeToTime(s string) time.Time {
t, _ := time.ParseInLocation("2006-01-02 15:04:05", s, time.Local)
return t
}
+
+func StrRFC3339TimeToTime(s string) time.Time {
+ t, _ := time.Parse(time.RFC3339, s)
+ return t
+}
diff --git a/go.mod b/go.mod
index 1ae0dd7..0eee4a1 100644
--- a/go.mod
+++ b/go.mod
@@ -26,13 +26,13 @@ require (
github.com/tomas-qstarrs/boost v1.0.3
github.com/tomas-qstarrs/excel-converter v1.0.2
github.com/wendal/errors v0.0.0-20181209125328-7f31f4b264ec
+ github.com/xuri/excelize/v2 v2.9.0
github.com/zegoim/zego_server_assistant/token/go/src v0.0.0-20231013093807-4e80bab42ec3
github.com/zeromicro/go-zero v1.7.3
go.etcd.io/etcd/client/v3 v3.5.16
go.mongodb.org/mongo-driver v1.17.1
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c
google.golang.org/protobuf v1.35.1
- gorm.io/driver/mysql v1.5.7
gorm.io/gorm v1.25.12
mongo.games.com/goserver v0.0.0-00010101000000-000000000000
)
@@ -70,7 +70,7 @@ require (
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/richardlehane/mscfb v1.0.4 // indirect
- github.com/richardlehane/msoleps v1.0.3 // indirect
+ github.com/richardlehane/msoleps v1.0.4 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
@@ -92,7 +92,8 @@ require (
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/xtaci/kcp-go v5.4.20+incompatible // indirect
- github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 // indirect
+ github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d // indirect
+ github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
go.etcd.io/etcd/api/v3 v3.5.16 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.16 // indirect
@@ -103,7 +104,7 @@ require (
go.uber.org/multierr v1.9.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
- golang.org/x/image v0.13.0 // indirect
+ golang.org/x/image v0.18.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.26.0 // indirect
@@ -115,4 +116,5 @@ require (
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
+ gorm.io/driver/mysql v1.5.7 // indirect
)
diff --git a/go.sum b/go.sum
index 79422ec..00dcca3 100644
--- a/go.sum
+++ b/go.sum
@@ -279,8 +279,8 @@ github.com/richardlehane/mscfb v1.0.3/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
-github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM=
-github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
+github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00=
+github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
@@ -386,8 +386,12 @@ github.com/xtaci/kcp-go v5.4.20+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E=
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37/go.mod h1:HpMP7DB2CyokmAh4lp0EQnnWhmycP/TvwBGzvuie+H0=
github.com/xuri/efp v0.0.0-20200605144744-ba689101faaf/go.mod h1:uBiSUepVYMhGTfDeBKKasV4GpgBlzJ46gXUBAqV8qLk=
-github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 h1:6932x8ltq1w4utjmfMPVj09jdMlkY0aiA6+Skbtl3/c=
-github.com/xuri/efp v0.0.0-20220603152613-6918739fd470/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
+github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d h1:llb0neMWDQe87IzJLS4Ci7psK/lVsjIS2otl+1WyRyY=
+github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
+github.com/xuri/excelize/v2 v2.9.0 h1:1tgOaEq92IOEumR1/JfYS/eR0KHOCsRv/rYXXh6YJQE=
+github.com/xuri/excelize/v2 v2.9.0/go.mod h1:uqey4QBZ9gdMeWApPLdhm9x+9o2lq4iVmjiLfBS5hdE=
+github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 h1:hPVCafDV85blFTabnqKgNhDCkJX25eik94Si9cTER4A=
+github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -441,8 +445,9 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
golang.org/x/image v0.0.0-20200922025426-e59bae62ef32/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg=
golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk=
+golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
+golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
diff --git a/statistics/.gitignore b/statistics/.gitignore
new file mode 100644
index 0000000..3c58f2a
--- /dev/null
+++ b/statistics/.gitignore
@@ -0,0 +1,29 @@
+# ---> Go
+# If you prefer the allow list template instead of the deny list, see community template:
+# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
+#
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+*.xlsx
+
+# Test binary, built with `go test -c`
+*.test
+local_test.go
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+# Dependency directories (remove the comment below to include it)
+# vendor/
+
+# Go workspace file
+go.work
+
+.idea
+.vscode
+log
+
diff --git a/statistics/README.md b/statistics/README.md
new file mode 100644
index 0000000..71f24d5
--- /dev/null
+++ b/statistics/README.md
@@ -0,0 +1,7 @@
+# statistics
+
+数据分析服务
+ * mongodb同步到mysql
+ * 接收消息队列数据
+ * 历史数据查询
+ * 数据统计
\ No newline at end of file
diff --git a/statistics/build_linux.bat b/statistics/build_linux.bat
new file mode 100644
index 0000000..7962122
--- /dev/null
+++ b/statistics/build_linux.bat
@@ -0,0 +1,8 @@
+set GOPATH=D:\godev
+go env -w GO111MODULE=on
+
+set CGO_ENABLED=0
+set GOOS=linux
+set GOARCH=amd64
+go build
+pause
\ No newline at end of file
diff --git a/statistics/constant/constant.go b/statistics/constant/constant.go
new file mode 100644
index 0000000..78818e0
--- /dev/null
+++ b/statistics/constant/constant.go
@@ -0,0 +1,8 @@
+package constant
+
+const (
+ InviteScoreTypeBind = 1 // 绑定邀请码
+ InviteScoreTypePay = 2 // 充值返佣
+ InviteScoreTypeRecharge = 3 // 充值完成
+ InviteScoreTypePayMe = 4 // 充值(自己)
+)
diff --git a/statistics/etc/config.yaml b/statistics/etc/config.yaml
new file mode 100644
index 0000000..ba82d5c
--- /dev/null
+++ b/statistics/etc/config.yaml
@@ -0,0 +1,27 @@
+# 平台id
+platforms:
+ - 1
+
+# 几秒同步一次数据
+# 注册表,登录日志表
+update_second: 60
+# 注册表每次同步多少条数据
+update_account_num: 100
+# 登录日志每次同步多少条数据
+update_login_num: 100
+# 几秒读取一次玩家id列表
+update_second_snid: 30
+# 最多触发几个玩家数据更新
+update_snid_num: 100
+
+# 邀请数据统计
+# 几秒读取一次邀请记录
+update_second_invite: 10
+# 一次最多读取多少条邀请记录
+update_invite_num: 30
+
+# 道具获得数量统计
+# 几秒读取一次道具日志
+update_second_item: 10
+# 一次最多读取多少道具日志
+update_item_num: 100
\ No newline at end of file
diff --git a/statistics/etc/mongo.yaml b/statistics/etc/mongo.yaml
new file mode 100644
index 0000000..7f9e42d
--- /dev/null
+++ b/statistics/etc/mongo.yaml
@@ -0,0 +1,53 @@
+global:
+ user:
+ HostName: 127.0.0.1
+ HostPort: 27017
+ Database: win88_global
+ Username:
+ Password:
+ Options:
+ log:
+ HostName: 127.0.0.1
+ HostPort: 27017
+ Database: win88_log
+ Username:
+ Password:
+ Options:
+ monitor:
+ HostName: 127.0.0.1
+ HostPort: 27017
+ Database: win88_monitor
+ Username:
+ Password:
+ Options:
+platforms:
+ 0:
+ user:
+ HostName: 127.0.0.1
+ HostPort: 27017
+ Database: win88_user_plt_000
+ Username:
+ Password:
+ Options:
+ log:
+ HostName: 127.0.0.1
+ HostPort: 27017
+ Database: win88_log_plt_000
+ Username:
+ Password:
+ Options:
+ 1:
+ user:
+ HostName: 127.0.0.1
+ HostPort: 27017
+ Database: win88_user_plt_001
+ Username:
+ Password:
+ Options:
+ log:
+ HostName: 127.0.0.1
+ HostPort: 27017
+ Database: win88_log_plt_001
+ Username:
+ Password:
+ Options:
\ No newline at end of file
diff --git a/statistics/etc/mysql.yaml b/statistics/etc/mysql.yaml
new file mode 100644
index 0000000..82d1fe3
--- /dev/null
+++ b/statistics/etc/mysql.yaml
@@ -0,0 +1,38 @@
+platforms:
+ global:
+ HostName: 127.0.0.1
+ HostPort: 3306
+ Database: win88_user
+ Username: root
+ Password: 123456
+ Options: charset=utf8mb4&parseTime=True&loc=Local
+ 0:
+ HostName: 127.0.0.1
+ HostPort: 3306
+ Database: win88_plt_000
+ Username: root
+ Password: 123456
+ Options: charset=utf8mb4&parseTime=True&loc=Local
+ 1:
+ HostName: 127.0.0.1
+ HostPort: 3306
+ Database: win88_plt_001
+ Username: root
+ Password: 123456
+ Options: charset=utf8mb4&parseTime=True&loc=Local
+ count: # 破产日志库
+ HostName: 127.0.0.1
+ HostPort: 3306
+ Database: dbmis_count
+ Username: root
+ Password: 123456
+ Options: charset=utf8mb4&parseTime=True&loc=Local
+
+# 最大空闲连接数
+MaxIdleConns: 10
+# 最大连接数
+MaxOpenConns: 100
+# 连接可复用的最大时间
+ConnMaxLifetime: 3600
+# 连接最大空闲时间
+ConnMaxIdletime: 0
\ No newline at end of file
diff --git a/statistics/logger.xml b/statistics/logger.xml
new file mode 100644
index 0000000..f6eb37b
--- /dev/null
+++ b/statistics/logger.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/statistics/main.go b/statistics/main.go
new file mode 100644
index 0000000..23e6b9f
--- /dev/null
+++ b/statistics/main.go
@@ -0,0 +1,212 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "os/signal"
+ "sync"
+ "time"
+
+ "github.com/spf13/viper"
+ "mongo.games.com/goserver/core/logger"
+ "mongo.games.com/goserver/core/mongox"
+ "mongo.games.com/goserver/core/mysqlx"
+ "mongo.games.com/goserver/core/utils"
+ "mongo.games.com/goserver/core/viperx"
+
+ mongomodel "mongo.games.com/game/statistics/modelmongo"
+ mysqlmodel "mongo.games.com/game/statistics/modelmysql"
+ "mongo.games.com/game/statistics/static"
+ "mongo.games.com/game/statistics/syn"
+)
+
+var VP *viper.Viper
+
+// DoTick 定时执行
+func DoTick(ctx context.Context, wg *sync.WaitGroup, duration time.Duration, fu func(ctx context.Context)) {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ for {
+ select {
+ case <-ctx.Done():
+ return
+ case <-time.After(duration):
+ utils.RecoverPanicFunc() // 捕获异常
+ fu(ctx)
+ }
+ }
+ }()
+}
+
+// DoTickPlatform 定时执行,根据platform执行
+func DoTickPlatform(ctx context.Context, wg *sync.WaitGroup, duration time.Duration, batchSize int,
+ fu func(ctx context.Context, platform string, batchSize int)) {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ for {
+ select {
+ case <-ctx.Done():
+ return
+ case <-time.After(duration):
+ utils.RecoverPanicFunc() // 捕获异常
+ wg := new(sync.WaitGroup)
+ for _, v := range VP.GetStringSlice("platforms") {
+ platform := v
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ fu(ctx, platform, batchSize)
+ }()
+ }
+ wg.Wait()
+ }
+ }
+ }()
+}
+
+func main() {
+ VP = viperx.GetViper("config", "yaml")
+ // mongo
+ vp := viperx.GetViper("mongo", "yaml")
+ // mongo初始化
+ conf := &mongox.Config{}
+ err := vp.Unmarshal(conf)
+ if err != nil {
+ panic(fmt.Errorf("mongo config error: %v", err))
+ }
+ mongox.Init(conf)
+ defer mongox.Close()
+
+ // mysql
+ vp = viperx.GetViper("mysql", "yaml")
+ myConf := &mysqlx.Config{}
+ err = vp.Unmarshal(myConf)
+ if err != nil {
+ panic(fmt.Errorf("mysql config error: %v", err))
+ }
+ mysqlx.Init(myConf)
+ defer mysqlx.Close()
+
+ mysqlx.SetAutoMigrateTables(mysqlmodel.Tables)
+
+ wg := &sync.WaitGroup{}
+ ctx, cancel := context.WithCancel(context.Background())
+
+ DoTick(ctx, wg, time.Duration(VP.GetInt64("update_second"))*time.Second, SyncSnId)
+
+ DoTick(ctx, wg, time.Duration(VP.GetInt64("update_second_snid"))*time.Second, func(ctx context.Context) {
+ wg := new(sync.WaitGroup)
+ for _, v := range VP.GetStringSlice("platforms") {
+ platform := v
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ Static(platform)
+ }()
+ }
+ wg.Wait()
+ })
+
+ DoTick(ctx, wg, time.Duration(VP.GetInt64("update_second_invite"))*time.Second, SyncInvite)
+
+ DoTickPlatform(ctx, wg, time.Duration(VP.GetInt64("update_second_item"))*time.Second, VP.GetInt("update_item_num"),
+ func(ctx context.Context, platform string, batchSize int) {
+ err := syn.ItemGainDone(&syn.Data[mongomodel.ItemLog]{
+ Platform: platform,
+ BatchSize: batchSize,
+ })
+ if err != nil {
+ logger.Logger.Errorf("SyncItem error:%v", err)
+ }
+ })
+
+ logger.Logger.Info("start")
+
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, os.Interrupt, os.Kill)
+ sig := <-c
+ logger.Logger.Infof("closing down (signal: %v)", sig)
+
+ // release
+ cancel()
+ wg.Wait()
+
+ logger.Logger.Info("closed")
+}
+
+// SyncSnId 同步注册和登录日志
+func SyncSnId(ctx context.Context) {
+ wg := new(sync.WaitGroup)
+ for _, v := range VP.GetStringSlice("platforms") {
+ platform := v
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ _, err := syn.UserAccount(platform, VP.GetInt("update_account_num"))
+ if err != nil {
+ logger.Logger.Errorf("SyncUserAccount error: %v", err)
+ return
+ }
+
+ _, err = syn.LogLogin(platform, VP.GetInt("update_login_num"))
+ if err != nil {
+ logger.Logger.Errorf("SyncLogLogin error: %v", err)
+ return
+ }
+ }()
+ }
+ wg.Wait()
+}
+
+// Static 玩家id触发数据统计
+func Static(platform string) {
+ // 查询需要更新的玩家id
+ var ids []*mysqlmodel.UserID
+ db, err := mysqlx.GetDatabase(platform)
+ if err != nil {
+ logger.Logger.Errorf("GetDatabase error: %v", err)
+ return
+ }
+ if err := db.Limit(VP.GetInt("update_snid_num")).Find(&ids).Error; err != nil {
+ logger.Logger.Warnf("Get UserID error: %v", err)
+ return
+ }
+
+ if len(ids) == 0 {
+ logger.Logger.Tracef("Static: no need to update")
+ return
+ }
+
+ // 统计玩家跳出记录
+ if err := static.UserLogin(platform, ids); err != nil {
+ logger.Logger.Errorf("StaticUserLogin error: %v", err)
+ return
+ }
+
+ // 删除更新过的玩家id
+ if err := db.Delete(ids).Error; err != nil {
+ logger.Logger.Errorf("Delete error: %v", err)
+ return
+ }
+}
+
+// SyncInvite 同步邀请数据
+func SyncInvite(ctx context.Context) {
+ wg := new(sync.WaitGroup)
+ for _, v := range VP.GetStringSlice("platforms") {
+ platform := v
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ err := syn.SyncInviteScore(platform, VP.GetInt("update_invite_num"))
+ if err != nil {
+ logger.Logger.Errorf("SyncInviteScore error: %v", err)
+ return
+ }
+ }()
+ }
+ wg.Wait()
+}
diff --git a/statistics/modelmongo/log_gameplayerlistlog.go b/statistics/modelmongo/log_gameplayerlistlog.go
new file mode 100644
index 0000000..a2a2917
--- /dev/null
+++ b/statistics/modelmongo/log_gameplayerlistlog.go
@@ -0,0 +1,47 @@
+package modelmongo
+
+import (
+ "time"
+
+ "go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+const LogGamePlayerListLog = "log_gameplayerlistlog"
+
+type GamePlayerListLog struct {
+ LogId primitive.ObjectID `bson:"_id"`
+ SnId int32 //用户Id
+ Name string //名称
+ GameId int32 //游戏id
+ BaseScore int32 //游戏底注
+ ClubId int32 //俱乐部Id
+ ClubRoom string //俱乐部包间
+ TaxCoin int64 //税收
+ ClubPumpCoin int64 //俱乐部额外抽水
+ Platform string //平台id
+ Channel string //渠道
+ Promoter string //推广员
+ PackageTag string //包标识
+ SceneId int32 //场景ID
+ GameMode int32 //游戏类型
+ GameFreeid int32 //游戏类型房间号
+ GameDetailedLogId string //游戏记录Id
+ IsFirstGame bool //是否第一次游戏
+ //对于拉霸类:BetAmount=100 WinAmountNoAnyTax=0 (表示投入多少、收益多少,值>=0)
+ //拉霸类小游戏会是:BetAmount=0 WinAmountNoAnyTax=100 (投入0、收益多少,值>=0)
+ //对战场:BetAmount=0 WinAmountNoAnyTax=100 (投入会有是0、收益有正负,WinAmountNoAnyTax=100则盈利,WinAmountNoAnyTax=-100则输100)
+ BetAmount int64 //下注金额
+ WinAmountNoAnyTax int64 //盈利金额,不包含任何税
+ TotalIn int64 //本局投入
+ TotalOut int64 //本局产出
+ Time time.Time //记录时间
+ RoomType int32 //房间类型
+ GameDif string //游戏标识
+ GameClass int32 //游戏类型 1棋牌 2电子 3百人 4捕鱼 5视讯 6彩票 7体育
+ MatchId int32
+ MatchType int32 //0.普通场 1.锦标赛 2.冠军赛 3.vip专属
+ Ts int32
+ IsFree bool //拉霸专用 是否免费
+ WinSmallGame int64 //拉霸专用 小游戏奖励
+ WinTotal int64 //拉霸专用 输赢
+}
diff --git a/statistics/modelmongo/log_invitescore.go b/statistics/modelmongo/log_invitescore.go
new file mode 100644
index 0000000..4a0b636
--- /dev/null
+++ b/statistics/modelmongo/log_invitescore.go
@@ -0,0 +1,19 @@
+package modelmongo
+
+import (
+ "go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+const LogInviteScore = "log_invitescore"
+
+type InviteScore struct {
+ Id primitive.ObjectID `bson:"_id"`
+ UpSnid int // 上级代理
+ DownSnid int // 下级代理
+ Level int // 代理层级 例如 1:DownSnid 是 UpSnid 的 1 级代理; 2: DownSnid 是 UpSnid 的 2 级代理
+ Tp int // 返佣类型
+ Rate int // 返佣比例
+ Score int // 积分
+ Money int // 充值金额
+ Ts int // 时间戳
+}
diff --git a/statistics/modelmongo/log_item.go b/statistics/modelmongo/log_item.go
new file mode 100644
index 0000000..4d7221f
--- /dev/null
+++ b/statistics/modelmongo/log_item.go
@@ -0,0 +1,27 @@
+package modelmongo
+
+import "go.mongodb.org/mongo-driver/bson/primitive"
+
+const LogItem = "log_itemlog"
+
+type ItemInfo struct {
+ ItemId int32
+ ItemNum int64
+}
+
+type ItemLog struct {
+ LogId primitive.ObjectID `bson:"_id"`
+ Platform string //平台
+ SnId int32 //玩家id
+ LogType int32 //记录类型 0.获取 1.消耗
+ ItemId int32 //道具id
+ ItemName string //道具名称
+ Count int64 //个数
+ CreateTs int64 //记录时间
+ Remark string //备注
+ TypeId int32 // 变化类型
+ GameId int32 // 游戏id,游戏中获得时有值
+ GameFreeId int32 // 场次id,游戏中获得时有值
+ Cost []*ItemInfo // 消耗的道具
+ Id string // 撤销的id,兑换失败
+}
diff --git a/statistics/modelmongo/log_login.go b/statistics/modelmongo/log_login.go
new file mode 100644
index 0000000..5145ffe
--- /dev/null
+++ b/statistics/modelmongo/log_login.go
@@ -0,0 +1,33 @@
+package modelmongo
+
+import (
+ "time"
+
+ "go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+const LogLogin = "log_login"
+
+const (
+ LogTypeLogin int32 = iota // 登录
+ LogTypeLogout // 登出
+ LogTypeRehold // 重连
+ LogTypeDrop // 掉线
+)
+
+type LoginLog struct {
+ LogId primitive.ObjectID `bson:"_id"`
+ Platform string //平台id
+ SnId int32
+ LogType int32
+ Ts int64
+ Time time.Time
+ GameId int // 玩家掉线时所在游戏id
+ LastGameID int // 玩家最后所在游戏id
+ ChannelId string // 推广渠道
+
+ DeviceName string
+ AppVersion string
+ BuildVersion string
+ AppChannel string
+}
diff --git a/statistics/modelmongo/user_account.go b/statistics/modelmongo/user_account.go
new file mode 100644
index 0000000..89979bf
--- /dev/null
+++ b/statistics/modelmongo/user_account.go
@@ -0,0 +1,24 @@
+package modelmongo
+
+import (
+ "time"
+
+ "go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+const UserAccount = "user_account"
+
+type Account struct {
+ AccountId primitive.ObjectID `bson:"_id"`
+ SnId int32 // 玩家账号直接在这里生成
+ Platform string // 平台
+ RegisterTs int64 // 注册时间戳
+ RegisteTime time.Time
+ ChannelId string // 推广渠道
+
+ Tel string `gorm:"index"`
+ DeviceName string `gorm:"index"`
+ AppVersion string `gorm:"index"`
+ BuildVersion string `gorm:"index"`
+ AppChannel string `gorm:"index"`
+}
diff --git a/statistics/modelmysql/bankrupt.go b/statistics/modelmysql/bankrupt.go
new file mode 100644
index 0000000..1ade115
--- /dev/null
+++ b/statistics/modelmysql/bankrupt.go
@@ -0,0 +1,12 @@
+package modelmysql
+
+type Bankrupt struct {
+ Id int `json:"id" gorm:"column:id"`
+ Platform int `json:"platform" gorm:"column:platform"`
+ Snid int `json:"snid" gorm:"column:snid"`
+ RegTs int `json:"register_time" gorm:"column:register_time"`
+ GameId int `json:"game_id" gorm:"column:game_id"`
+ GameFreeId int `json:"game_free_id" gorm:"column:game_free_id"`
+ Coin int `json:"use_coin" gorm:"column:use_coin"`
+ Ts int `json:"bankrupt_time" gorm:"column:bankrupt_time"`
+}
diff --git a/statistics/modelmysql/log_invitescore.go b/statistics/modelmysql/log_invitescore.go
new file mode 100644
index 0000000..b5ffdf4
--- /dev/null
+++ b/statistics/modelmysql/log_invitescore.go
@@ -0,0 +1,26 @@
+package modelmysql
+
+type LogInviteScoreMid struct {
+ ID uint `gorm:"primaryKey"`
+ MID string
+}
+
+type LogInviteScore struct {
+ ID uint `gorm:"primaryKey"`
+ UpSnid int `gorm:"index"` // 上级代理
+ DownSnid int `gorm:"index"` // 下级代理
+ Level int `gorm:"index"` // 代理层级 例如 1:DownSnid 是 UpSnid 的 1 级代理; 2: DownSnid 是 UpSnid 的 2 级代理
+ Tp int `gorm:"index"` // 返佣类型
+ Rate int `gorm:"index"` // 返佣比例
+ Score int `gorm:"index"` // 积分
+ Money int `gorm:"index"` // 充值金额
+ Ts int `gorm:"index"` // 时间戳
+}
+
+type LogInviteUser struct {
+ ID uint `gorm:"primaryKey"`
+ Psnid int `gorm:"index"` // 当前玩家
+ Snid int `gorm:"index"` // 一级代理
+ Level int `gorm:"index"` // 代理层级 例如 1:DownSnid 是 UpSnid 的 1 级代理; 2: DownSnid 是 UpSnid 的 2 级代理
+ Ts int `gorm:"index"` // 绑定时间
+}
diff --git a/statistics/modelmysql/log_itemgain.go b/statistics/modelmysql/log_itemgain.go
new file mode 100644
index 0000000..a672611
--- /dev/null
+++ b/statistics/modelmysql/log_itemgain.go
@@ -0,0 +1,16 @@
+package modelmysql
+
+// ItemGain 道具获得数量,以小时,道具id,做主键
+type ItemGain struct {
+ ID uint `gorm:"primaryKey"`
+ Hour int64 `gorm:"index:idx_item"` // 小时时间戳,每小时统计一次
+ ItemId int32 `gorm:"index:idx_item"` // 道具id
+ ItemNum int64 // 道具数量
+}
+
+// ItemTotalGain 道具获得总数
+type ItemTotalGain struct {
+ ID uint `gorm:"primaryKey"`
+ ItemId int32 `gorm:"index"` // 道具id
+ ItemNum int64 // 道具数量
+}
diff --git a/statistics/modelmysql/log_login.go b/statistics/modelmysql/log_login.go
new file mode 100644
index 0000000..912d748
--- /dev/null
+++ b/statistics/modelmysql/log_login.go
@@ -0,0 +1,26 @@
+package modelmysql
+
+import "time"
+
+const (
+ LogTypeLogin = 1 // 登录
+ LogTypeRehold = 2 // 重连
+ LogTypeOffline = 3 // 离线
+)
+
+type LogLogin struct {
+ ID uint `gorm:"primaryKey"`
+ Snid int `gorm:"index"`
+ OnlineType int `gorm:"index"`
+ //OnlineTs int `gorm:"index"`
+ OnlineTime time.Time `gorm:"index"`
+ OfflineType int `gorm:"index"`
+ //OfflineTs int `gorm:"index"`
+ OfflineTime time.Time `gorm:"index"`
+ ChannelId string `gorm:"index"` // 推广渠道
+
+ DeviceName string `gorm:"index"`
+ AppVersion string `gorm:"index"`
+ BuildVersion string `gorm:"index"`
+ AppChannel string `gorm:"index"`
+}
diff --git a/statistics/modelmysql/log_login_mid.go b/statistics/modelmysql/log_login_mid.go
new file mode 100644
index 0000000..1b1385e
--- /dev/null
+++ b/statistics/modelmysql/log_login_mid.go
@@ -0,0 +1,6 @@
+package modelmysql
+
+type LogLoginMid struct {
+ ID uint `gorm:"primaryKey"`
+ MID string
+}
diff --git a/statistics/modelmysql/log_mid.go b/statistics/modelmysql/log_mid.go
new file mode 100644
index 0000000..ebca072
--- /dev/null
+++ b/statistics/modelmysql/log_mid.go
@@ -0,0 +1,11 @@
+package modelmysql
+
+const (
+ MidTypeItem = 1 // 道具记录
+)
+
+type LogMid struct {
+ ID uint `gorm:"primaryKey"`
+ Tp int `gorm:"index"` // 类型
+ MID string
+}
diff --git a/statistics/modelmysql/tables.go b/statistics/modelmysql/tables.go
new file mode 100644
index 0000000..8f9bd15
--- /dev/null
+++ b/statistics/modelmysql/tables.go
@@ -0,0 +1,17 @@
+package modelmysql
+
+// 需要自动迁移的表添加在这里 Tables
+
+var Tables = []interface{}{
+ &LogLogin{},
+ &LogLoginMid{},
+ &UserAccount{},
+ &UserLogin{},
+ &UserID{},
+ &LogInviteScoreMid{},
+ &LogInviteScore{},
+ &LogInviteUser{},
+ &LogMid{},
+ &ItemGain{},
+ &ItemTotalGain{},
+}
diff --git a/statistics/modelmysql/user_account.go b/statistics/modelmysql/user_account.go
new file mode 100644
index 0000000..0d56bbf
--- /dev/null
+++ b/statistics/modelmysql/user_account.go
@@ -0,0 +1,18 @@
+package modelmysql
+
+import "time"
+
+type UserAccount struct {
+ ID uint `gorm:"primaryKey"`
+ MID string
+ Snid int `gorm:"index"`
+ //RegisterTs int `gorm:"index"`
+ RegisterTime time.Time `gorm:"index"`
+ ChannelId string `gorm:"index"` // 推广渠道
+
+ DeviceName string `gorm:"index"`
+ AppVersion string `gorm:"index"`
+ BuildVersion string `gorm:"index"`
+ AppChannel string `gorm:"index"`
+ Tel string `gorm:"index"`
+}
diff --git a/statistics/modelmysql/user_id.go b/statistics/modelmysql/user_id.go
new file mode 100644
index 0000000..5a3ce33
--- /dev/null
+++ b/statistics/modelmysql/user_id.go
@@ -0,0 +1,9 @@
+package modelmysql
+
+/*
+ 服务定期查询注册和登录信息,然后获取玩家id,保存到这张表中;用于后续触发和玩家相关的数据统计
+*/
+
+type UserID struct {
+ Snid int `gorm:"primaryKey"`
+}
diff --git a/statistics/modelmysql/user_login.go b/statistics/modelmysql/user_login.go
new file mode 100644
index 0000000..4ed6e93
--- /dev/null
+++ b/statistics/modelmysql/user_login.go
@@ -0,0 +1,29 @@
+package modelmysql
+
+import "time"
+
+const (
+ OutTypRegister = 1 // 注册
+ OutTypeLogin = 2 // 登录
+ OutTypeGaming = 3 // 游戏中
+ OutTypeGameOver = 4 // 游戏结束
+)
+
+type UserLogin struct {
+ ID uint `gorm:"primaryKey"`
+ Snid int `gorm:"uniqueIndex"`
+ //OnlineTs int `gorm:"index"`
+ OnlineTime time.Time `gorm:"index"`
+ //OfflineTs int `gorm:"index"`
+ OfflineTime time.Time `gorm:"index"`
+ OutType int `gorm:"index"` // 跳出类型
+ GameID int `gorm:"index"` // 游戏id
+ Age int
+ Sex int
+ DeviceName string `gorm:"index"`
+ AppVersion string `gorm:"index"`
+ BuildVersion string `gorm:"index"`
+ AppChannel string `gorm:"index"`
+ Tel string `gorm:"index"`
+ ChannelId string `gorm:"index"` // 推广渠道
+}
diff --git a/statistics/mq/handler.go b/statistics/mq/handler.go
new file mode 100644
index 0000000..71893fd
--- /dev/null
+++ b/statistics/mq/handler.go
@@ -0,0 +1 @@
+package mq
diff --git a/statistics/mq/readme b/statistics/mq/readme
new file mode 100644
index 0000000..9c843ee
--- /dev/null
+++ b/statistics/mq/readme
@@ -0,0 +1 @@
+接收消息队列
\ No newline at end of file
diff --git a/statistics/static/readme b/statistics/static/readme
new file mode 100644
index 0000000..b98bf32
--- /dev/null
+++ b/statistics/static/readme
@@ -0,0 +1 @@
+业务统计
\ No newline at end of file
diff --git a/statistics/static/user_login.go b/statistics/static/user_login.go
new file mode 100644
index 0000000..d217e2c
--- /dev/null
+++ b/statistics/static/user_login.go
@@ -0,0 +1,371 @@
+package static
+
+import (
+ "context"
+ "errors"
+
+ "go.mongodb.org/mongo-driver/bson"
+ "go.mongodb.org/mongo-driver/mongo"
+ "go.mongodb.org/mongo-driver/mongo/options"
+ "mongo.games.com/goserver/core/logger"
+
+ mymongo "mongo.games.com/goserver/core/mongox"
+ mymysql "mongo.games.com/goserver/core/mysqlx"
+
+ mongomodel "mongo.games.com/game/statistics/modelmongo"
+ mysqlmodel "mongo.games.com/game/statistics/modelmysql"
+)
+
+func getAccountTel(platform string, id int) (string, error) {
+ acc := &mongomodel.Account{}
+ cc, err := mymongo.GetUserCollection(platform, mongomodel.UserAccount)
+ if err != nil {
+ logger.Logger.Errorf("get collection %s error %v", mongomodel.UserAccount, err)
+ return "", err
+ }
+ dd := cc.FindOne(context.TODO(), bson.M{"snid": id}, options.FindOne().SetProjection(bson.M{"tel": 1}))
+ err = dd.Err()
+ if err != nil {
+ if errors.Is(err, mongo.ErrNoDocuments) {
+ logger.Logger.Tracef("getAccountTel %v not found in user_account", id)
+ return "", nil
+ }
+ logger.Logger.Errorf("getAccountTel %v get user_account err: %v", id, err)
+ return "", err
+ }
+ if err := dd.Decode(acc); err != nil {
+ logger.Logger.Errorf("getAccountTel %v decode user_account err: %v", id, err)
+ return "", err
+ }
+ return acc.Tel, nil
+}
+
+// 游戏结束离开
+func checkGameOver(db *mymysql.Database, login *mysqlmodel.UserLogin, platform string, id int) (bool, error) {
+ // 最早的一条掉线记录并且是游戏结束离开
+ a := &mongomodel.LoginLog{}
+ c, err := mymongo.GetLogCollection(platform, mongomodel.LogLogin)
+ if err != nil {
+ logger.Logger.Errorf("get collection %s error %v", mongomodel.LogLogin, err)
+ return false, err
+ }
+ d := c.FindOne(context.TODO(), bson.M{"snid": id, "logtype": mongomodel.LogTypeDrop, "gameid": 0, "lastgameid": bson.D{{"$gt", 0}}},
+ options.FindOne().SetSort(bson.D{{"time", 1}}))
+ err = d.Err()
+ if err != nil {
+ if errors.Is(err, mongo.ErrNoDocuments) {
+ logger.Logger.Tracef("checkGameOver %v not found in log_login", id)
+ return false, nil
+ }
+ logger.Logger.Errorf("checkGameOver %v get log_login err: %v", id, err)
+ return false, err
+ }
+ if err := d.Decode(a); err != nil {
+ logger.Logger.Errorf("checkGameOver %v decode log_login err: %v", id, err)
+ return false, err
+ }
+
+ // account tel
+ tel, err := getAccountTel(platform, id)
+ if err != nil {
+ logger.Logger.Warnf("get account tel %v err: %v", id, err)
+ }
+
+ update := &mysqlmodel.UserLogin{
+ //OfflineTs: int(a.Ts),
+ OfflineTime: a.Time,
+ OutType: mysqlmodel.OutTypeGameOver,
+ GameID: a.LastGameID,
+ Tel: tel,
+ DeviceName: a.DeviceName,
+ AppVersion: a.AppVersion,
+ BuildVersion: a.BuildVersion,
+ AppChannel: a.AppChannel,
+ ChannelId: a.ChannelId,
+ }
+
+ if err := db.Model(login).Select(
+ "OfflineTime", "OutType", "GameID", "DeviceName", "AppVersion", "BuildVersion", "AppChannel", "Tel",
+ ).Updates(update).Error; err != nil {
+ logger.Logger.Errorf("checkLogin %v update user_login err: %v", id, err)
+ return false, err
+ }
+
+ return true, nil
+}
+
+// 游戏中离开
+func checkGaming(db *mymysql.Database, login *mysqlmodel.UserLogin, platform string, id int) (bool, error) {
+ // 最早的一条掉线记录并且是游戏中掉线
+ a := &mongomodel.LoginLog{}
+ c, err := mymongo.GetLogCollection(platform, mongomodel.LogLogin)
+ if err != nil {
+ logger.Logger.Errorf("get collection %s error %v", mongomodel.LogLogin, err)
+ return false, err
+ }
+ d := c.FindOne(context.TODO(), bson.M{"snid": id, "logtype": mongomodel.LogTypeDrop, "gameid": bson.D{{"$gt", 0}}},
+ options.FindOne().SetSort(bson.D{{"time", 1}}))
+ err = d.Err()
+ if err != nil {
+ if errors.Is(err, mongo.ErrNoDocuments) {
+ logger.Logger.Tracef("checkGaming %v not found in log_login", id)
+ return false, nil
+ }
+ logger.Logger.Errorf("checkGaming %v get log_login err: %v", id, err)
+ return false, err
+ }
+ if err := d.Decode(a); err != nil {
+ logger.Logger.Errorf("checkGaming %v decode log_login err: %v", id, err)
+ return false, err
+ }
+
+ // account tel
+ tel, err := getAccountTel(platform, id)
+ if err != nil {
+ logger.Logger.Warnf("get account tel %v err: %v", id, err)
+ }
+
+ update := &mysqlmodel.UserLogin{
+ //OfflineTs: int(a.Ts),
+ OfflineTime: a.Time,
+ OutType: mysqlmodel.OutTypeGaming,
+ GameID: a.GameId,
+ Tel: tel,
+ DeviceName: a.DeviceName,
+ AppVersion: a.AppVersion,
+ BuildVersion: a.BuildVersion,
+ AppChannel: a.AppChannel,
+ ChannelId: a.ChannelId,
+ }
+
+ if err := db.Model(login).Select(
+ "OfflineTime", "OutType", "GameID", "DeviceName", "AppVersion", "BuildVersion", "AppChannel", "Tel",
+ ).Updates(update).Error; err != nil {
+ logger.Logger.Errorf("checkLogin %v update user_login err: %v", id, err)
+ return false, err
+ }
+
+ return true, nil
+}
+
+// 登录后离开
+func checkLogin(db *mymysql.Database, login *mysqlmodel.UserLogin, platform string, id int) (bool, error) {
+ // 最早的一条掉线记录
+ a := &mongomodel.LoginLog{}
+ c, err := mymongo.GetLogCollection(platform, mongomodel.LogLogin)
+ if err != nil {
+ logger.Logger.Errorf("get collection %s error %v", mongomodel.LogLogin, err)
+ return false, err
+ }
+ d := c.FindOne(context.TODO(), bson.M{"snid": id, "logtype": mongomodel.LogTypeDrop}, options.FindOne().SetSort(bson.D{{"time", 1}}))
+ err = d.Err()
+ if err != nil {
+ if errors.Is(err, mongo.ErrNoDocuments) {
+ logger.Logger.Tracef("checkLogin %v not found in log_login", id)
+ return false, nil
+ }
+ logger.Logger.Errorf("checkLogin %v get log_login err: %v", id, err)
+ return false, err
+ }
+ if err := d.Decode(a); err != nil {
+ logger.Logger.Errorf("checkLogin %v decode log_login err: %v", id, err)
+ return false, err
+ }
+
+ // account tel
+ tel, err := getAccountTel(platform, id)
+ if err != nil {
+ logger.Logger.Warnf("get account tel %v err: %v", id, err)
+ }
+
+ update := &mysqlmodel.UserLogin{
+ //OfflineTs: int(a.Ts),
+ OfflineTime: a.Time,
+ OutType: mysqlmodel.OutTypeLogin,
+ Tel: tel,
+ DeviceName: a.DeviceName,
+ AppVersion: a.AppVersion,
+ BuildVersion: a.BuildVersion,
+ AppChannel: a.AppChannel,
+ ChannelId: a.ChannelId,
+ }
+
+ if err := db.Model(login).Select(
+ "OfflineTime", "OutType", "DeviceName", "AppVersion", "BuildVersion", "AppChannel", "Tel",
+ ).Updates(update).Error; err != nil {
+ logger.Logger.Errorf("checkLogin %v update user_login err: %v", id, err)
+ return false, err
+ }
+
+ return true, nil
+}
+
+// 注册后离开
+func checkRegister(db *mymysql.Database, login *mysqlmodel.UserLogin, platform string, id int) (bool, error) {
+ a := &mongomodel.Account{}
+ c, err := mymongo.GetUserCollection(platform, mongomodel.UserAccount)
+ if err != nil {
+ logger.Logger.Errorf("get collection %s error %v", mongomodel.UserAccount, err)
+ return false, err
+ }
+ d := c.FindOne(context.TODO(), bson.M{"snid": id})
+ err = d.Err()
+ if err != nil {
+ if errors.Is(err, mongo.ErrNoDocuments) {
+ logger.Logger.Warnf("checkRegister %v not found in user_account", id)
+ return false, nil
+ }
+ logger.Logger.Errorf("checkRegister %v get user_account err: %v", id, err)
+ return false, err
+ }
+ if err := d.Decode(a); err != nil {
+ logger.Logger.Errorf("checkRegister %v decode user_account err: %v", id, err)
+ return false, err
+ }
+
+ // account tel
+ tel, err := getAccountTel(platform, id)
+ if err != nil {
+ logger.Logger.Warnf("get account tel %v err: %v", id, err)
+ }
+
+ login.Snid = id
+ //login.OnlineTs = int(a.RegisterTs)
+ login.OnlineTime = a.RegisteTime
+ //login.OfflineTs = int(a.RegisterTs)
+ login.OfflineTime = a.RegisteTime
+ login.OutType = mysqlmodel.OutTypRegister
+ login.Tel = tel
+ login.DeviceName = a.DeviceName
+ login.AppVersion = a.AppVersion
+ login.BuildVersion = a.BuildVersion
+ login.AppChannel = a.AppChannel
+ login.ChannelId = a.ChannelId
+
+ if err := db.Create(login).Error; err != nil {
+ logger.Logger.Errorf("checkRegister create err: %v", err)
+ return false, err
+ }
+ return true, nil
+}
+
+// UserLogin 玩家跳出统计
+func UserLogin(platform string, ids []*mysqlmodel.UserID) error {
+ f := func(id int) error {
+ // 玩家是否已经统计结束,已经是游戏结束状态
+ login := &mysqlmodel.UserLogin{}
+ db, err := mymysql.GetDatabase(platform)
+ if err != nil {
+ logger.Logger.Errorf("UserLogin get db err: %v", err)
+ return err
+ }
+ if err = db.Where("snid = ?", id).Find(login).Error; err != nil {
+ logger.Logger.Errorf("UserLogin find %v err: %v", id, err)
+ return err
+ }
+
+ switch login.OutType {
+ case mysqlmodel.OutTypeGameOver:
+ return nil
+
+ case mysqlmodel.OutTypeGaming:
+ _, err := checkGameOver(db, login, platform, id)
+ if err != nil {
+ logger.Logger.Errorf("UserLogin checkGameOver %v err: %v", id, err)
+ return err
+ }
+ return nil
+
+ case mysqlmodel.OutTypeLogin:
+ ret, err := checkGameOver(db, login, platform, id)
+ if err != nil {
+ logger.Logger.Errorf("UserLogin checkGameOver %v err: %v", id, err)
+ return err
+ }
+ if ret {
+ return nil
+ }
+ ret, err = checkGaming(db, login, platform, id)
+ if err != nil {
+ logger.Logger.Errorf("UserLogin checkGaming %v err: %v", id, err)
+ return err
+ }
+ if ret {
+ return nil
+ }
+
+ case mysqlmodel.OutTypRegister:
+ ret, err := checkGameOver(db, login, platform, id)
+ if err != nil {
+ logger.Logger.Errorf("UserLogin checkGameOver %v err: %v", id, err)
+ return err
+ }
+ if ret {
+ return nil
+ }
+ ret, err = checkGaming(db, login, platform, id)
+ if err != nil {
+ logger.Logger.Errorf("UserLogin checkGaming %v err: %v", id, err)
+ return err
+ }
+ if ret {
+ return nil
+ }
+ ret, err = checkLogin(db, login, platform, id)
+ if err != nil {
+ logger.Logger.Errorf("UserLogin checkLogin %v err: %v", id, err)
+ return err
+ }
+ if ret {
+ return nil
+ }
+
+ default:
+ ret, err := checkRegister(db, login, platform, id)
+ if err != nil {
+ logger.Logger.Errorf("UserLogin checkRegister %v err: %v", id, err)
+ return err
+ }
+ if !ret {
+ logger.Logger.Warnf("UserLogin not found user_account checkRegister %v err: %v", id, err)
+ return nil
+ }
+
+ ret, err = checkGameOver(db, login, platform, id)
+ if err != nil {
+ logger.Logger.Errorf("UserLogin checkGameOver %v err: %v", id, err)
+ return err
+ }
+ if ret {
+ return nil
+ }
+ ret, err = checkGaming(db, login, platform, id)
+ if err != nil {
+ logger.Logger.Errorf("UserLogin checkGaming %v err: %v", id, err)
+ return err
+ }
+ if ret {
+ return nil
+ }
+ ret, err = checkLogin(db, login, platform, id)
+ if err != nil {
+ logger.Logger.Errorf("UserLogin checkLogin %v err: %v", id, err)
+ return err
+ }
+ if ret {
+ return nil
+ }
+ return nil
+ }
+
+ return nil
+ }
+
+ for _, v := range ids {
+ if err := f(v.Snid); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/statistics/syn/datasync.go b/statistics/syn/datasync.go
new file mode 100644
index 0000000..8f94fc7
--- /dev/null
+++ b/statistics/syn/datasync.go
@@ -0,0 +1,102 @@
+package syn
+
+import (
+ "context"
+ "errors"
+
+ "go.mongodb.org/mongo-driver/bson"
+ "go.mongodb.org/mongo-driver/bson/primitive"
+ "go.mongodb.org/mongo-driver/mongo"
+ "go.mongodb.org/mongo-driver/mongo/options"
+ "gorm.io/gorm"
+ "mongo.games.com/goserver/core/logger"
+
+ mysqlmodel "mongo.games.com/game/statistics/modelmysql"
+ mymongo "mongo.games.com/goserver/core/mongox"
+ mymysql "mongo.games.com/goserver/core/mysqlx"
+)
+
+// Data 数据同步方法
+// T mongodb数据表结构
+// F mongodb中的每条数据的处理操作,自行实现
+type Data[T any] struct {
+ Platform string // 平台
+ MidType int // 数据类型 例如 modelmysql.MidTypeItem
+ Database string // 库名称
+ CollectionName string // 集合名称
+ BatchSize int // 一次读取数量
+ // F 自定义数据处理方法
+ // data: mongodb中的一条日志
+ F func(data *T, db *gorm.DB) (string, error)
+}
+
+// CommonDone 数据获取方式,根据mongodb集合主键按时间顺序批量读取
+func (d *Data[T]) CommonDone() error {
+ db, err := mymysql.GetDatabase(d.Platform)
+ if err != nil {
+ logger.Logger.Errorf("mysql: failed to get database: %v", err)
+ return err
+ }
+ loginMID := &mysqlmodel.LogMid{Tp: d.MidType}
+ var n int64
+ err = db.Model(&mysqlmodel.LogMid{}).Find(loginMID).Count(&n).Error
+ if err != nil {
+ logger.Logger.Errorf("mysql: failed to get log_mid: %v", err)
+ return err
+ }
+ if n == 0 {
+ if err = db.Create(loginMID).Error; err != nil {
+ logger.Logger.Errorf("mysql: failed to create log_mid: %v", err)
+ return err
+ }
+ }
+
+ logger.Logger.Tracef("start log_mid tp:%v _id:%v", loginMID.Tp, loginMID.MID)
+
+ _id, _ := primitive.ObjectIDFromHex(loginMID.MID)
+ filter := bson.M{"_id": bson.M{"$gt": _id}}
+ c, err := mymongo.GetCollection(d.Platform, mymongo.DatabaseType(d.Database), d.CollectionName)
+ if err != nil {
+ logger.Logger.Errorf("get collection %s %s error %v", d.Database, d.CollectionName, err)
+ return err
+ }
+ l, err := c.Find(context.TODO(), filter,
+ options.Find().SetSort(bson.D{primitive.E{Key: "_id", Value: 1}}), options.Find().SetLimit(int64(d.BatchSize)))
+ if err != nil && !errors.Is(err, mongo.ErrNoDocuments) {
+ logger.Logger.Errorf("mongo: failed to get %v: %v", d.CollectionName, err)
+ return err
+ }
+
+ var logs []*T
+ if err = l.All(context.TODO(), &logs); err != nil {
+ l.Close(context.TODO())
+ if errors.Is(err, mongo.ErrNoDocuments) {
+ return nil
+ }
+
+ logger.Logger.Errorf("mongo: failed to get %v: %v", d.CollectionName, err)
+ return err
+ }
+ l.Close(context.TODO())
+ if len(logs) == 0 {
+ logger.Logger.Infof("sync %v finished", d.CollectionName)
+ return nil
+ }
+
+ err = db.Transaction(func(tx *gorm.DB) error {
+ for _, v := range logs {
+ loginMID.MID, err = d.F(v, tx)
+ if err != nil {
+ logger.Logger.Errorf("Process %v error:%v", d.CollectionName, err)
+ return err
+ }
+ if err = tx.Model(loginMID).Updates(loginMID).Error; err != nil {
+ logger.Logger.Errorf("mysql: failed to update %v log_mid: %v", d.CollectionName, err)
+ return err
+ }
+ }
+ return nil
+ })
+
+ return err
+}
diff --git a/statistics/syn/log_invitescore.go b/statistics/syn/log_invitescore.go
new file mode 100644
index 0000000..1531469
--- /dev/null
+++ b/statistics/syn/log_invitescore.go
@@ -0,0 +1,291 @@
+package syn
+
+import (
+ "context"
+ "errors"
+
+ "go.mongodb.org/mongo-driver/bson"
+ "go.mongodb.org/mongo-driver/bson/primitive"
+ "go.mongodb.org/mongo-driver/mongo"
+ "go.mongodb.org/mongo-driver/mongo/options"
+ "gorm.io/gorm"
+
+ "mongo.games.com/game/statistics/constant"
+ mongomodel "mongo.games.com/game/statistics/modelmongo"
+ mysqlmodel "mongo.games.com/game/statistics/modelmysql"
+ "mongo.games.com/goserver/core/logger"
+ mymongo "mongo.games.com/goserver/core/mongox"
+ mymysql "mongo.games.com/goserver/core/mysqlx"
+)
+
+func SyncInviteScore(platform string, batchSize int) error {
+ db, err := mymysql.GetDatabase(platform)
+ if err != nil {
+ logger.Logger.Errorf("mysql: SyncInviteScore failed to get database: %v", err)
+ return err
+ }
+ inviteMID := &mysqlmodel.LogInviteScoreMid{ID: 1}
+ var n int64
+ err = db.Model(&mysqlmodel.LogInviteScoreMid{}).Find(inviteMID).Count(&n).Error
+ if err != nil {
+ logger.Logger.Errorf("mysql: SyncInviteScore failed to get log_invitescore_mid: %v", err)
+ return err
+ }
+ if n == 0 {
+ if err = db.Create(inviteMID).Error; err != nil {
+ logger.Logger.Errorf("mysql: SyncInviteScore failed to create log_invitescore_mid: %v", err)
+ return err
+ }
+ }
+
+ logger.Logger.Tracef("start SyncInviteScore log_invitescore _id:%v", inviteMID.MID)
+
+ _id, _ := primitive.ObjectIDFromHex(inviteMID.MID)
+ filter := bson.M{"_id": bson.M{"$gt": _id}}
+ c, err := mymongo.GetLogCollection(platform, mongomodel.LogInviteScore)
+ if err != nil {
+ logger.Logger.Errorf("get collection %s error %v", mongomodel.LogInviteScore, err)
+ return err
+ }
+ l, err := c.Find(context.TODO(), filter,
+ options.Find().SetSort(bson.D{primitive.E{Key: "_id", Value: 1}}), options.Find().SetLimit(int64(batchSize)))
+ if err != nil && !errors.Is(err, mongo.ErrNoDocuments) {
+ logger.Logger.Errorf("mongo: SyncInviteScore failed to get log_invitescore: %v", err)
+ return err
+ }
+
+ var logs []*mongomodel.InviteScore
+ if err = l.All(context.TODO(), &logs); err != nil {
+ l.Close(context.TODO())
+ if errors.Is(err, mongo.ErrNoDocuments) {
+ return err
+ }
+
+ logger.Logger.Errorf("mongo: SyncInviteScore failed to get log_invitescore: %v", err)
+ return err
+ }
+ l.Close(context.TODO())
+
+ getPSnId := func(tx *gorm.DB, snid int) (int, error) {
+ if snid <= 0 {
+ return 0, nil
+ }
+ ret := new(mysqlmodel.LogInviteUser)
+ if err = tx.First(ret, "snid = ? and level = 1", snid).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+ logger.Logger.Errorf("mysql: SyncInviteScore failed to getPSnId: %v", err)
+ return 0, err
+ }
+ return ret.Psnid, nil
+ }
+
+ getDownSnId := func(tx *gorm.DB, snid []int) ([]int, error) {
+ if len(snid) == 0 {
+ return nil, nil
+ }
+ var ret []int
+ var us []*mysqlmodel.LogInviteUser
+ if err = tx.Select("snid").Where("psnid IN ? AND level = 1", snid).Find(&us).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+ logger.Logger.Errorf("mysql: SyncInviteScore failed to getDownSnId: %v", err)
+ return ret, err
+ }
+ for _, v := range us {
+ ret = append(ret, v.Snid)
+ }
+ return ret, nil
+ }
+
+ bind := func(tx *gorm.DB, psnid, snid int, ts int) ([]*mysqlmodel.LogInviteUser, error) {
+ var lu []*mysqlmodel.LogInviteUser
+ var a1, a2, a3, a4, b1 int
+ var b2, b3, b4 []int
+ a4 = psnid
+ a3, err = getPSnId(tx, a4)
+ if err != nil {
+ return nil, err
+ }
+ a2, err = getPSnId(tx, a3)
+ if err != nil {
+ return nil, err
+ }
+ a1, err = getPSnId(tx, a2)
+ if err != nil {
+ return nil, err
+ }
+ b1 = snid
+ b2, err = getDownSnId(tx, []int{b1})
+ if err != nil {
+ return nil, err
+ }
+ b3, err = getDownSnId(tx, b2)
+ if err != nil {
+ return nil, err
+ }
+ b4, err = getDownSnId(tx, b3)
+ if err != nil {
+ return nil, err
+ }
+ logger.Logger.Tracef("a1:%d, a2:%d, a3:%d, a4:%d, b1:%d, b2:%v, b3:%v, b4:%v", a1, a2, a3, a4, b1, b2, b3, b4)
+ if a1 > 0 {
+ if b1 > 0 {
+ lu = append(lu, &mysqlmodel.LogInviteUser{
+ Psnid: a1,
+ Snid: b1,
+ Level: 4,
+ Ts: ts,
+ })
+ logger.Logger.Tracef("a1: %v %v %v", b1, 4, ts)
+ }
+ }
+ if a2 > 0 {
+ if b1 > 0 {
+ lu = append(lu, &mysqlmodel.LogInviteUser{
+ Psnid: a2,
+ Snid: b1,
+ Level: 3,
+ Ts: ts,
+ })
+ logger.Logger.Tracef("a2: %v %v %v", b1, 3, ts)
+ }
+ for _, v := range b2 {
+ if v <= 0 {
+ continue
+ }
+ lu = append(lu, &mysqlmodel.LogInviteUser{
+ Psnid: a2,
+ Snid: v,
+ Level: 4,
+ Ts: ts,
+ })
+ logger.Logger.Tracef("a2: %v %v %v", v, 4, ts)
+ }
+ }
+ if a3 > 0 {
+ if b1 > 0 {
+ lu = append(lu, &mysqlmodel.LogInviteUser{
+ Psnid: a3,
+ Snid: b1,
+ Level: 2,
+ Ts: ts,
+ })
+ logger.Logger.Tracef("a3: %v %v %v", b1, 2, ts)
+ }
+ for _, v := range b2 {
+ if v <= 0 {
+ continue
+ }
+ lu = append(lu, &mysqlmodel.LogInviteUser{
+ Psnid: a3,
+ Snid: v,
+ Level: 3,
+ Ts: ts,
+ })
+ logger.Logger.Tracef("a3: %v %v %v", v, 3, ts)
+ }
+ for _, v := range b3 {
+ if v <= 0 {
+ continue
+ }
+ lu = append(lu, &mysqlmodel.LogInviteUser{
+ Psnid: a3,
+ Snid: v,
+ Level: 4,
+ Ts: ts,
+ })
+ logger.Logger.Tracef("a3: %v %v %v", v, 4, ts)
+ }
+ }
+ if a4 > 0 {
+ if b1 > 0 {
+ lu = append(lu, &mysqlmodel.LogInviteUser{
+ Psnid: a4,
+ Snid: b1,
+ Level: 1,
+ Ts: ts,
+ })
+ logger.Logger.Tracef("a4: %v %v %v", b1, 1, ts)
+ }
+ for _, v := range b2 {
+ if v <= 0 {
+ continue
+ }
+ lu = append(lu, &mysqlmodel.LogInviteUser{
+ Psnid: a4,
+ Snid: v,
+ Level: 2,
+ Ts: ts,
+ })
+ logger.Logger.Tracef("a4: %v %v %v", v, 2, ts)
+ }
+ for _, v := range b3 {
+ if v <= 0 {
+ continue
+ }
+ lu = append(lu, &mysqlmodel.LogInviteUser{
+ Psnid: a4,
+ Snid: v,
+ Level: 3,
+ Ts: ts,
+ })
+ logger.Logger.Tracef("a4: %v %v %v", v, 3, ts)
+ }
+ for _, v := range b4 {
+ if v <= 0 {
+ continue
+ }
+ lu = append(lu, &mysqlmodel.LogInviteUser{
+ Psnid: a4,
+ Snid: v,
+ Level: 4,
+ Ts: ts,
+ })
+ logger.Logger.Tracef("a4: %v %v %v", v, 4, ts)
+ }
+ }
+ return lu, nil
+ }
+
+ for _, v := range logs {
+ err = db.Transaction(func(tx *gorm.DB) error {
+ inviteMID.MID = v.Id.Hex()
+ if err = tx.Model(inviteMID).Updates(inviteMID).Error; err != nil {
+ logger.Logger.Errorf("mysql: SyncInviteScore failed to update log_invitescore_mid: %v", err)
+ return err
+ }
+
+ err = tx.Save(&mysqlmodel.LogInviteScore{
+ UpSnid: v.UpSnid,
+ DownSnid: v.DownSnid,
+ Level: v.Level,
+ Tp: v.Tp,
+ Rate: v.Rate,
+ Score: v.Score,
+ Money: v.Money,
+ Ts: v.Ts,
+ }).Error
+ if err != nil {
+ logger.Logger.Errorf("mysql: SyncInviteScore failed to insert: %v", err)
+ return err
+ }
+
+ if v.Tp == constant.InviteScoreTypeBind && v.Level == 0 {
+ // 绑定关系
+ lu, err := bind(tx, v.UpSnid, v.DownSnid, v.Ts)
+ if err != nil {
+ logger.Logger.Errorf("mysql: SyncInviteScore failed to bind: %v", err)
+ return err
+ }
+ if err = tx.CreateInBatches(lu, len(lu)).Error; err != nil {
+ logger.Logger.Errorf("mysql: SyncInviteScore failed to create log_invite_user: %v", err)
+ return err
+ }
+ }
+
+ return nil
+ })
+ if err != nil {
+ logger.Logger.Errorf("mysql: SyncInviteScore failed to transaction: %v", err)
+ return err
+ }
+ }
+ return nil
+}
diff --git a/statistics/syn/log_itemgain.go b/statistics/syn/log_itemgain.go
new file mode 100644
index 0000000..4751255
--- /dev/null
+++ b/statistics/syn/log_itemgain.go
@@ -0,0 +1,61 @@
+package syn
+
+import (
+ "errors"
+ "time"
+
+ "gorm.io/gorm"
+ "mongo.games.com/goserver/core/mongox"
+
+ mongomodel "mongo.games.com/game/statistics/modelmongo"
+ mysqlmodel "mongo.games.com/game/statistics/modelmysql"
+)
+
+func ItemGainDone(data *Data[mongomodel.ItemLog]) error {
+ data.MidType = mysqlmodel.MidTypeItem
+ data.Database = string(mongox.DatabaseLog)
+ data.CollectionName = mongomodel.LogItem
+ data.F = func(data *mongomodel.ItemLog, db *gorm.DB) (string, error) {
+ if data == nil || data.LogId.Hex() == "" {
+ return "", errors.New("null")
+ }
+ if data.LogType != 0 || data.Id != "" {
+ return data.LogId.Hex(), nil
+ }
+
+ hourTime := time.Unix(data.CreateTs, 0).Local()
+ hourTs := time.Date(hourTime.Year(), hourTime.Month(), hourTime.Day(), hourTime.Hour(), 0, 0, 0, time.Local).Unix()
+
+ item := &mysqlmodel.ItemGain{}
+ err := db.Model(item).Where("hour = ? and item_id = ?", hourTs, data.ItemId).First(item).Error
+ if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+ return "", err
+ }
+ item.Hour = hourTs
+ item.ItemId = data.ItemId
+ item.ItemNum += data.Count
+ if item.ID == 0 {
+ err = db.Create(item).Error
+ } else {
+ err = db.Model(item).Updates(item).Error
+ }
+ if err != nil {
+ return "", err
+ }
+
+ itemTotal := &mysqlmodel.ItemTotalGain{}
+ err = db.Model(itemTotal).Where("item_id = ?", data.ItemId).First(itemTotal).Error
+ if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+ return "", err
+ }
+ itemTotal.ItemId = data.ItemId
+ itemTotal.ItemNum += data.Count
+ if itemTotal.ID == 0 {
+ err = db.Create(itemTotal).Error
+ } else {
+ err = db.Model(itemTotal).Updates(itemTotal).Error
+ }
+ return data.LogId.Hex(), err
+ }
+ return data.CommonDone()
+}
diff --git a/statistics/syn/log_login.go b/statistics/syn/log_login.go
new file mode 100644
index 0000000..7f338e1
--- /dev/null
+++ b/statistics/syn/log_login.go
@@ -0,0 +1,181 @@
+package syn
+
+import (
+ "context"
+ "errors"
+
+ "go.mongodb.org/mongo-driver/bson"
+ "go.mongodb.org/mongo-driver/bson/primitive"
+ "go.mongodb.org/mongo-driver/mongo"
+ "go.mongodb.org/mongo-driver/mongo/options"
+ "gorm.io/gorm"
+
+ mongomodel "mongo.games.com/game/statistics/modelmongo"
+ mysqlmodel "mongo.games.com/game/statistics/modelmysql"
+ "mongo.games.com/goserver/core/logger"
+ mymongo "mongo.games.com/goserver/core/mongox"
+ mymysql "mongo.games.com/goserver/core/mysqlx"
+)
+
+/*
+ 登录日志同步使用了mongo的_id,从小到大每次同步n个
+*/
+
+// LogLogin 同步登录日志
+func LogLogin(platform string, batchSize int) ([]*mysqlmodel.LogLogin, error) {
+ db, err := mymysql.GetDatabase(platform)
+ if err != nil {
+ logger.Logger.Errorf("mysql: SyncLogLogin failed to get database: %v", err)
+ return nil, err
+ }
+ loginMID := &mysqlmodel.LogLoginMid{ID: 1}
+ var n int64
+ err = db.Model(&mysqlmodel.LogLoginMid{}).Find(loginMID).Count(&n).Error
+ if err != nil {
+ logger.Logger.Errorf("mysql: SyncLogLogin failed to get log_login_mid: %v", err)
+ return nil, err
+ }
+ if n == 0 {
+ if err = db.Create(loginMID).Error; err != nil {
+ logger.Logger.Errorf("mysql: SyncLogLogin failed to create log_login_mid: %v", err)
+ return nil, err
+ }
+ }
+
+ logger.Logger.Tracef("start SyncLogLogin log_login _id:%v", loginMID.MID)
+
+ _id, _ := primitive.ObjectIDFromHex(loginMID.MID)
+ filter := bson.M{"_id": bson.M{"$gt": _id}}
+ c, err := mymongo.GetLogCollection(platform, mongomodel.LogLogin)
+ if err != nil {
+ logger.Logger.Errorf("get collection %s error %v", mongomodel.LogLogin, err)
+ return nil, err
+ }
+ l, err := c.Find(context.TODO(), filter,
+ options.Find().SetSort(bson.D{primitive.E{Key: "_id", Value: 1}}), options.Find().SetLimit(int64(batchSize)))
+ if err != nil && !errors.Is(err, mongo.ErrNoDocuments) {
+ logger.Logger.Errorf("mongo: SyncLogLogin failed to get log_login: %v", err)
+ return nil, err
+ }
+
+ var logs []*mongomodel.LoginLog
+ if err = l.All(context.TODO(), &logs); err != nil {
+ l.Close(context.TODO())
+ if errors.Is(err, mongo.ErrNoDocuments) {
+ return nil, nil
+ }
+
+ logger.Logger.Errorf("mongo: SyncLogLogin failed to get loginlog: %v", err)
+ return nil, err
+ }
+ l.Close(context.TODO())
+
+ var ls []*mysqlmodel.LogLogin
+ for _, v := range logs {
+ logger.Logger.Tracef("mongo SyncLogLogin log_login: %+v", *v)
+ var e *mysqlmodel.LogLogin
+ switch v.LogType {
+ case mongomodel.LogTypeLogin, mongomodel.LogTypeRehold:
+ onlineType := mysqlmodel.LogTypeLogin
+ if v.LogType == mongomodel.LogTypeRehold {
+ onlineType = mysqlmodel.LogTypeRehold
+ }
+
+ // 创建数据
+ var n int64
+ if err = db.Model(&mysqlmodel.LogLogin{}).Where("snid = ? AND online_type = ? AND online_time = ?",
+ v.SnId, onlineType, v.Time).Count(&n).Error; err != nil {
+ logger.Logger.Errorf("mysql: SyncLogLogin failed to get log_login count: %v", err)
+ return ls, err
+ }
+
+ if n == 0 {
+ e = &mysqlmodel.LogLogin{
+ Snid: int(v.SnId),
+ OnlineType: onlineType,
+ //OnlineTs: int(v.Ts),
+ OnlineTime: v.Time,
+ OfflineType: 0,
+ //OfflineTs: 0,
+ OfflineTime: v.Time,
+ DeviceName: v.DeviceName,
+ AppVersion: v.AppVersion,
+ BuildVersion: v.BuildVersion,
+ AppChannel: v.AppChannel,
+ ChannelId: v.ChannelId,
+ }
+ if err = db.Create(e).Error; err != nil {
+ logger.Logger.Errorf("mysql: SyncLogLogin failed to create log_login: %v", err)
+ return ls, err
+ }
+ } else {
+ continue
+ }
+
+ case mongomodel.LogTypeLogout, mongomodel.LogTypeDrop:
+ // 修改数据
+ e = &mysqlmodel.LogLogin{}
+ err = db.Model(&mysqlmodel.LogLogin{}).Where("snid = ?", v.SnId).Order("online_time DESC").First(e).Error
+ if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+ logger.Logger.Errorf("mysql: SyncLogLogin failed to find log_login: %v", err)
+ return ls, err
+ }
+
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ logger.Logger.Warnf("mysql: SyncLogLogin not found log_login: %v", v)
+ continue
+ }
+
+ if e.OfflineType != 0 {
+ logger.Logger.Tracef("mysql: SyncLogLogin already offline: %+v", *e)
+ continue
+ }
+
+ e.OfflineType = mysqlmodel.LogTypeOffline
+ //e.OfflineTs = int(v.Ts)
+ e.OfflineTime = v.Time
+ if err = db.Model(e).Select("OfflineType", "OfflineTime").Updates(e).Error; err != nil {
+ logger.Logger.Errorf("mysql: SyncLogLogin failed to update log_login: %v", err)
+ return ls, err
+ }
+ default:
+ continue
+ }
+
+ if e != nil {
+ ls = append(ls, e)
+ }
+ }
+
+ if len(logs) > 0 {
+ err = db.Transaction(func(tx *gorm.DB) error {
+ loginMID.MID = logs[len(logs)-1].LogId.Hex()
+ if err = tx.Model(loginMID).Updates(loginMID).Error; err != nil {
+ logger.Logger.Errorf("mysql: SyncLogLogin failed to update log_login_mid: %v", err)
+ return err
+ }
+
+ for _, v := range ls {
+ if err = tx.First(&mysqlmodel.UserID{}, "snid = ?", v.Snid).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+ logger.Logger.Errorf("mysql: SyncLogLogin failed to find user_id: %v", err)
+ return err
+ }
+
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ if err = tx.Create(&mysqlmodel.UserID{Snid: v.Snid}).Error; err != nil {
+ logger.Logger.Errorf("mysql: SyncLogLogin failed to create user_id: %v", err)
+ return err
+ }
+ }
+ }
+
+ return nil
+ })
+ if err != nil {
+ logger.Logger.Errorf("mysql: SyncLogLogin failed to transaction: %v", err)
+ return nil, err
+ }
+ }
+
+ return ls, nil
+}
diff --git a/statistics/syn/readme b/statistics/syn/readme
new file mode 100644
index 0000000..37acc1c
--- /dev/null
+++ b/statistics/syn/readme
@@ -0,0 +1 @@
+游戏服mongodb同步到mysql
\ No newline at end of file
diff --git a/statistics/syn/user_account.go b/statistics/syn/user_account.go
new file mode 100644
index 0000000..72b1c52
--- /dev/null
+++ b/statistics/syn/user_account.go
@@ -0,0 +1,105 @@
+package syn
+
+import (
+ "context"
+ "errors"
+
+ "go.mongodb.org/mongo-driver/bson"
+ "go.mongodb.org/mongo-driver/bson/primitive"
+ "go.mongodb.org/mongo-driver/mongo"
+ "go.mongodb.org/mongo-driver/mongo/options"
+ "gorm.io/gorm"
+
+ mongomodel "mongo.games.com/game/statistics/modelmongo"
+ mysqlmodel "mongo.games.com/game/statistics/modelmysql"
+ "mongo.games.com/goserver/core/logger"
+ mymongo "mongo.games.com/goserver/core/mongox"
+ mymysql "mongo.games.com/goserver/core/mysqlx"
+)
+
+/*
+ 注册信息同步,使用mongo的_id,从小到大每次同步n个
+*/
+
+// UserAccount 同步注册表
+func UserAccount(platform string, batchSize int) ([]*mysqlmodel.UserAccount, error) {
+ db, err := mymysql.GetDatabase(platform)
+ if err != nil {
+ logger.Logger.Errorf("mysql: UserAccount failed to get database: %v", err)
+ return nil, err
+ }
+ account := &mysqlmodel.UserAccount{}
+ err = db.Model(&mysqlmodel.UserAccount{}).Last(account).Error
+ if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+ logger.Logger.Errorf("mysql: UserAccount failed to get account: %v", err)
+ return nil, err
+ }
+
+ logger.Logger.Tracef("start UserAccount account _id:%v", account.MID)
+
+ _id, _ := primitive.ObjectIDFromHex(account.MID)
+ filter := bson.M{"_id": bson.M{"$gt": _id}}
+ c, err := mymongo.GetUserCollection(platform, mongomodel.UserAccount)
+ if err != nil {
+ logger.Logger.Errorf("get collection %s error %v", mongomodel.UserAccount, err)
+ return nil, err
+ }
+ l, err := c.Find(context.TODO(), filter,
+ options.Find().SetSort(bson.D{primitive.E{Key: "_id", Value: 1}}), options.Find().SetLimit(int64(batchSize)))
+ if err != nil && !errors.Is(err, mongo.ErrNoDocuments) {
+ logger.Logger.Errorf("mongo: UserAccount failed to get account: %v", err)
+ return nil, err
+ }
+
+ var accounts []*mongomodel.Account
+ if err = l.All(context.TODO(), &accounts); err != nil {
+ l.Close(context.TODO())
+ if errors.Is(err, mongo.ErrNoDocuments) {
+ return nil, nil
+ }
+
+ logger.Logger.Errorf("mongo: UserAccount failed to get account: %v", err)
+ return nil, err
+ }
+ l.Close(context.TODO())
+
+ var as []*mysqlmodel.UserAccount
+ err = db.Transaction(func(tx *gorm.DB) error {
+ for _, v := range accounts {
+ logger.Logger.Tracef("mongo account: %+v", *v)
+ a := &mysqlmodel.UserAccount{
+ MID: v.AccountId.Hex(),
+ Snid: int(v.SnId),
+ //RegisterTs: int(v.RegisterTs),
+ RegisterTime: v.RegisteTime,
+ Tel: v.Tel,
+ ChannelId: v.ChannelId,
+ }
+
+ if err = tx.Create(a).Error; err != nil {
+ logger.Logger.Errorf("mysql: UserAccount failed to create account: %v", err)
+ return err
+ }
+
+ if err = tx.First(&mysqlmodel.UserID{}, "snid = ?", v.SnId).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+ logger.Logger.Errorf("mysql: UserAccount failed to find user_id: %v", err)
+ return err
+ }
+
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ if err = tx.Create(&mysqlmodel.UserID{Snid: int(v.SnId)}).Error; err != nil {
+ logger.Logger.Errorf("mysql: UserAccount failed to create user_id: %v", err)
+ return err
+ }
+ }
+
+ as = append(as, a)
+ }
+ return nil
+ })
+ if err != nil {
+ logger.Logger.Errorf("mysql: UserAccount failed to transaction: %v", err)
+ return as, err
+ }
+ return as, nil
+}
diff --git a/statistics/task/build_linux.bat b/statistics/task/build_linux.bat
new file mode 100644
index 0000000..0cc7a25
--- /dev/null
+++ b/statistics/task/build_linux.bat
@@ -0,0 +1,5 @@
+set CGO_ENABLED=0
+set GOOS=linux
+set GOARCH=amd64
+go build -o ./task2
+pause
\ No newline at end of file
diff --git a/statistics/task/etc/DB_GameFree.dat b/statistics/task/etc/DB_GameFree.dat
new file mode 100644
index 0000000000000000000000000000000000000000..9ed570013bf427b6cf7d22d1081b9237d012c275
GIT binary patch
literal 24144
zcmdU1du$ZP9cE_F=kyiq!sB1wq?L4YUbW
zt4eLxV23lo#TWy|IEf7=zCeI87&y$!F@(C0BnI**Bx+6KJD*7!zz`BeP1A2?cV~9j
z`!YB_2Z4QSvpe56^UXKE`OVD6`iOdI`yb=8?tgI6;$JRWoL{kRxT!1GzH0d3>TGT1
zjvwFutyv|x_32#QW7*c8(ice?Nxa@p=F{(z1*Gg3N|Ky=l{S#xmgy<>B~2Rc%aHUi
z?IT6yW%$!i$SFdmohQ+@diM7N5+QA^WEmyXR#HlpMjFL!y*2l=&|lA>3X
zG$nmhsVKXegnH>1@xs
z3y12gMs|{Etz*krtrgbDUL?x
zpEf+tJyg4hvGkyu%$f-Tt+3IWf`HB~Sc~?)^ALMXaI9u?>!TP+y`W1`3FuBq0XpK9
z@dRo1Skn>?^rp5(_`}A}V7V6qsUgAOx)mg&%})Ff7Z#<74tHOtDS`GLhybnE06&W_$*h64SgKWIitULiDs|
z)mj-nx#uD-VRm>Z$g~2}5ff}#h@Kj~pi8F`(0!eXPY8oD5EE`xV6fJUf%GTAz|)_2
zRx3o#VdCux(NmKb1L;bFfu}2BI52WqlN&v)^8zoeNPzdWB0YNI^7pbFOhciI=)~muFV%DoHf=hA}aj+49;EF~MQkDWbj+
zl8&@9K_}48WQwbZ51Aq{6#Y0eTp;ykNK(?*1Swx%lesSc_3MfEwHJsw6q1xQI6=zS
z;4De6Nt|C|6)_ub1;{YglJ6C?r+t}vM~aM|O}Mf#fH;_GXL%aI{o>-Og&?M!g-
zwX=d`%Mh>a^`{|COwjN(u|nm|5U=g?XCl2zFbVRmf@RQ7V$13v;o6LT%_EJ>ucT~C3H7{gAy
zt;9cNlY#IoRo4t&A@S+CXEx&mL9Tw?NY|lR)Aa)?
zxlB{;@inDultjk?IzPfss3ggmJp|`d*lH_2^${Ys8Qj`%9~QVVt;aq!R2eo)UECbe
zE=3xS7NPDed%slNZfyP1w}G+kU+67&m6i8X#pQg8-edl$G~73+;8A&H;YDTP`^pDo
zS!4w%yQ8w~_Od&x=2e#8fdv$NoI9_)to#n_gTUvz=T+cyCI6h5SBcNavfN%iuL_?M
z?9-i<^Ah;XmR@jCgvNX3>-dTZ1827Qus$?YA9Qi|A
zB$ke(OEQtRXzbrqZFH&J{D#%posSQ9HfJ|4mzB%b?~KaT$0=v+&erue*csRMeg>gO
z?~7j?eEloh`Lee6=Au3=)my?Iz_-+it#$po+P)N~f>Fkxf~S2#YIuaIYh7x1DAStT
zyMN@!#_Z1RvWD4;qFKYp`Ht1^mzgZqJ%bHX
zu2AI!P(Zb9L1?%yg{}Mgc}%y7Y-At3S~5!bH!E6?`}0R={3d%h`_YmG
zOP0=x4rN+~4sR@78A;@w9nGj$GXx^%S*N<*p7oLW$2gD6qR7qO@NkGXyJfqCXX#%K
zc=SB)N@F@y6cHPuits(k1o)AOupMjT-U#(}mk2H0%7IWhulJv^-6)DKKdu%YgRv89
z-kunzqZaekO-`hi9_2t90aB0dGZ9F)dLzwlIy|zm)``^8ksL^ifYhVcTsKnqe%BDr
z?9Is5R}W?CvWMETn>G#g^vojVrKhpTG4@nmL;H+o()Raem=ivxNpg-c0gJGxT38dA
zX7+A&pdJ5nVC(mAOt)cMoqn@CV^khALiH1{r
z-|nL}u>L2a!lHc|ix=)_DKLT0=tL9;73^+!uM%l3MRj&aDpu=L_hQ$eYtanMQ
zmYxKxICKrH_=?fV^MHq~T2=PHdv`xSkDYGLw)~dQ9xdl=!h+p_B?x!0RT8YFGd1wz
zP39pdxw2i%CE!)F!DpMo;@ypf26w!3C0_dmHg=>6fY~=3YVtp;Vd-Tteup;tSPJhb
zh-pI`2I71|a}AXYTX=SNwapf4w;Q(b5)LnIm5CnXbr*%Sg+6zv6{P~|RI-I*?r19|
z+Nom;$KA2sC9zH+TX@bLY?TB%wQS*pJKnhx?^Lq|927dKEhF*Ih-ks_avbZt_GMCr
zJ^D|%ImI=F4lL&2V`OmXV0{^m5j`Bl&inxA+4Smc&we($u|Rh^hjG$++@XY-D+yu7
zc`Dz4W1z<&q0>R=Tc0$}DO=DN$Vu{4zLCUW0O_22!9zM5pUCcLuuA6@CY@%>x{MrT
z3L~920-#IjSfEcC>AV?)UP#A+e(FeP8KiUJ62%HfO-D{^WD}>=O|wc+=ebNKmt0-?
zuSg_+lP1V
z0D{_Qvb9~g*80+aM-r`PmH9*@pQB+VlcJ{v+lDQe`uMk0L#Zxbt(brea?
zsaUuYuM$Hd7K<$Xl)6-iQT(6OQ_kYiEJKGjF!vP2^9~@2=Tn9O%ozFP29N-t$^qcE
zZ-fSLdJF*Zq6PtgYghol+#`kQsxf2H0aR33RDv3qT@ez1hLc221ajS9AmHJViGWRe
zQY3W@Q9T+OvwWit>=16gEhL2H=oe3CSdiXPKXhBgZY)qvb#4~#j%SfUVq!_
z6Z!X@;=oV*#Zl=i4p{f73k0bQ0l`#;4#BPr9fXfE0zq7M10iG~K$x=7LD*$s)P%Pz
z{6V0zUU2~-H6cKln&2RunlP)v(=z-~KoZp_4lqePn$GRsGn83B+_TLpi8M|0{>Lhb
z0jKx-^_10f_K
zK$w!yL0~(RWeDLQ#&*yd(!c{ztZC{&YYcBZl1nz{lFos=izYhWv9?Ag>1+-B#Mv6f
z$vO9~C$o#hFjD2)ATaPo7ZkEV1PZf3bQHXMhz8Md*cwE~@w-?^95BwcZa74n2pndc
za2$FM5yLvi;b;?;S4cOR9TExD(f$T&mt49#_e5*%vAU7g48pzSn
zs%Zn(7WMdwE!k@C;gy_x$<`&n(nGvT7IgZ=ST$HQazPm2gP|1X3*@TOmXdF*yU
zA>|=ZnDXE#Z1V7cVV8$L49H`T3k)d_0mGCBhhdk82M)VD{Bb}Y-7Yw!JOmC?9vp{L
z9v(>S^6*FUEA`p`F?HO#ME50Yk+_mY;=-rMuLTr&uH~>S@<$IW
zSn%LdR^&dKXdbl|i7QzoF1(8@FY_((A@x!oMV1&WfABk2+MO~Q=I=4
z_g8B5{v)y(zrW-+k1S#(W@uvYvXvmNWQn-&DbYV(U#eEGy38^Rspa^aYwekZ$h}j)
zV)N?3NJ4wfnq 0 {
+ e.List[id].SetHead(head)
+ }
+
+ return e.List[id]
+}
+
+func (e *ExcelMgr) Get(id int) *ExcelData {
+ return e.List[id]
+}
+
+func (e *ExcelMgr) Save(id int, fileName string) error {
+ d := e.List[id]
+ if d == nil {
+ return nil
+ }
+ return d.SaveAs(fileName)
+}
diff --git a/statistics/task/gamefree/db_gamefree.go b/statistics/task/gamefree/db_gamefree.go
new file mode 100644
index 0000000..81d8b55
--- /dev/null
+++ b/statistics/task/gamefree/db_gamefree.go
@@ -0,0 +1,77 @@
+package gamefree
+
+import (
+ "google.golang.org/protobuf/proto"
+ "mongo.games.com/game/protocol/server"
+ "os"
+)
+
+func init() {
+ buf, err := os.ReadFile("./etc/DB_GameFree.dat")
+ if err != nil {
+ panic(err)
+ }
+ err = PBDB_GameFreeMgr.unmarshal(buf)
+ if err != nil {
+ panic(err)
+ }
+}
+
+var PBDB_GameFreeMgr = &DB_GameFreeMgr{
+ Datas: &server.DB_GameFreeArray{},
+ pool: make(map[int32]*server.DB_GameFree),
+}
+
+type DB_GameFreeMgr struct {
+ Datas *server.DB_GameFreeArray
+ pool map[int32]*server.DB_GameFree
+}
+
+func (this *DB_GameFreeMgr) unmarshal(data []byte) error {
+ err := proto.Unmarshal(data, this.Datas)
+ if err == nil {
+ this.arrangeData()
+ }
+ return err
+}
+
+func (this *DB_GameFreeMgr) reunmarshal(data []byte) error {
+ newDatas := &server.DB_GameFreeArray{}
+ err := proto.Unmarshal(data, newDatas)
+ if err == nil {
+ for _, item := range newDatas.Arr {
+ existItem := this.GetData(item.GetId())
+ if existItem == nil {
+ this.pool[item.GetId()] = item
+ this.Datas.Arr = append(this.Datas.Arr, item)
+
+ } else {
+ *existItem = *item
+ }
+ }
+ }
+ return err
+}
+
+func (this *DB_GameFreeMgr) arrangeData() {
+ if this.Datas == nil {
+ return
+ }
+
+ dataArr := this.Datas.GetArr()
+ if dataArr == nil {
+ return
+ }
+
+ for _, data := range dataArr {
+ this.pool[data.GetId()] = data
+
+ }
+}
+
+func (this *DB_GameFreeMgr) GetData(id int32) *server.DB_GameFree {
+ if data, ok := this.pool[id]; ok {
+ return data
+ }
+ return nil
+}
diff --git a/statistics/task/logger.xml b/statistics/task/logger.xml
new file mode 100644
index 0000000..f6eb37b
--- /dev/null
+++ b/statistics/task/logger.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/statistics/task/main.go b/statistics/task/main.go
new file mode 100644
index 0000000..a582273
--- /dev/null
+++ b/statistics/task/main.go
@@ -0,0 +1,427 @@
+package main
+
+import (
+ "fmt"
+ "sort"
+ "time"
+
+ "github.com/spf13/viper"
+ "mongo.games.com/goserver/core/logger"
+ "mongo.games.com/goserver/core/mongox"
+ "mongo.games.com/goserver/core/mysqlx"
+ "mongo.games.com/goserver/core/viperx"
+
+ "mongo.games.com/game/statistics/task/gamefree"
+ "mongo.games.com/game/statistics/task/task"
+)
+
+const (
+ ExcelTypeNewPlayerBankrupt = iota // 新用户游戏破产率
+ ExcelTypeGameTimeAvg // 新用户平均游戏时长
+ ExcelTypeGameCountAvg // 新用户平均局数
+ ExcelTypeGameRate // 平均倍数
+ ExcelTypeActiveRate // 活跃破产率
+ ExcelTypeCtrlWinRate // 控输赢胜率
+ ExcelTypeRobotWinRate // 机器人胜率
+ ExcelTypeCoinAvg // 人均获得金币
+ ExcelTypeBankruptOffline // 破产后离线
+ ExcelTypeOfflineCoin // 充值当天最后金币
+)
+
+var VP *viper.Viper
+
+func main() {
+ defer func() {
+ logger.Logger.Flush()
+ logger.Logger.Close()
+ }()
+ VP = viperx.GetViper("config", "yaml")
+ // mongo
+ vp := viperx.GetViper("mongo", "yaml")
+ // mongo初始化
+ conf := &mongox.Config{}
+ err := vp.Unmarshal(conf)
+ if err != nil {
+ panic(fmt.Errorf("mongo config error: %v", err))
+ }
+ mongox.Init(conf)
+ defer mongox.Close()
+
+ // mysql
+ vp = viperx.GetViper("mysql", "yaml")
+ myConf := &mysqlx.Config{}
+ err = vp.Unmarshal(myConf)
+ if err != nil {
+ panic(fmt.Errorf("mysql config error: %v", err))
+ }
+ mysqlx.Init(myConf)
+ defer mysqlx.Close()
+
+ startTime, err := time.Parse(time.RFC3339, VP.GetString("StartTime"))
+ if err != nil {
+ panic(fmt.Sprintf("time.Parse err: %v", err))
+ return
+ }
+ endTime, err := time.Parse(time.RFC3339, VP.GetString("EndTime"))
+ if err != nil {
+ panic(fmt.Sprintf("time.Parse err: %v", err))
+ return
+ }
+
+ mgr := NewExcelMgr()
+ mgr.Register(ExcelTypeNewPlayerBankrupt, []string{"日期", "场次id", "破产人数", "参与人数", "破产率"})
+ mgr.Register(ExcelTypeGameTimeAvg, []string{"日期", "场次id", "参与人数", "平均游戏时长"})
+ mgr.Register(ExcelTypeGameCountAvg, []string{"日期", "场次id", "参与人数", "平均局数"})
+ mgr.Register(ExcelTypeGameRate, []string{"日期", "场次id", "总局数", "平均倍数", "有炸弹分局数", "炸弹分平均倍数", "2留在手里的局数"})
+ mgr.Register(ExcelTypeActiveRate, []string{"日期", "场次id", "破产人数", "参与人数", "破产率"})
+ mgr.Register(ExcelTypeCtrlWinRate, []string{"日期", "场次id", "总局数", "平均倍数", "有炸弹分局数", "炸弹分平均倍数", "2留在手里的局数"})
+ mgr.Register(ExcelTypeCtrlWinRate*10, []string{"日期", "场次id", "总局数", "平均倍数", "有炸弹分局数", "炸弹分平均倍数", "2留在手里的局数"})
+ mgr.Register(ExcelTypeRobotWinRate, []string{"日期", "场次id", "总局数", "平均倍数", "有炸弹分局数", "炸弹分平均倍数", "2留在手里的局数"})
+ mgr.Register(ExcelTypeCoinAvg, []string{"日期", "场次id", "参与人数", "人均获得金币"})
+ mgr.Register(ExcelTypeBankruptOffline, []string{"日期", "场次id", "破产人数", "参与人数", "破产率"})
+ mgr.Register(ExcelTypeOfflineCoin, []string{"日期", "场次id", "破产人数", "参与人数", "破产率"})
+ switchArr := VP.GetIntSlice("Switch")
+
+ for {
+ if startTime.Unix() >= endTime.Unix() {
+ break
+ }
+ startTimeStr := startTime.Format(time.RFC3339)
+ et := startTime.AddDate(0, 0, 1)
+ endTimeStr := et.Format(time.RFC3339)
+ logger.Logger.Infof("startTime: %v endTime: %v", startTimeStr, endTimeStr)
+
+ if switchArr[ExcelTypeNewPlayerBankrupt] == 1 {
+ // 新用户游戏破产率
+ mgr.GenNewPlayerBankruptRateExcel("1", startTimeStr, endTimeStr)
+ }
+ if switchArr[ExcelTypeGameTimeAvg] == 1 {
+ // 新用户平均游戏时长
+ mgr.GenNewPlayerGameTimeAvgExcel("1", startTimeStr, endTimeStr)
+ }
+ if switchArr[ExcelTypeGameCountAvg] == 1 {
+ // 新用户平均局数
+ mgr.GenGameCountExcel("1", startTimeStr, endTimeStr)
+ }
+ if switchArr[ExcelTypeGameRate] == 1 {
+ // 平均倍数
+ mgr.GenGameRateExcel("1", startTimeStr, endTimeStr)
+ }
+ if switchArr[ExcelTypeActiveRate] == 1 {
+ // 活跃破产率
+ mgr.GenActiveBankruptRateExcel("1", startTimeStr, endTimeStr)
+ }
+ if switchArr[ExcelTypeCtrlWinRate] == 1 {
+ // 控赢胜率
+ mgr.GenCtrlWinRateExcel("1", startTimeStr, endTimeStr)
+ }
+ if switchArr[ExcelTypeRobotWinRate] == 1 {
+ // 机器人胜率
+ mgr.GenRobotWinRateExcel("1", startTimeStr, endTimeStr)
+ }
+ if switchArr[ExcelTypeCoinAvg] == 1 {
+ // 人均获得金币
+ mgr.GenCoinAvgExcel("1", startTimeStr, endTimeStr)
+ }
+ if switchArr[ExcelTypeBankruptOffline] == 1 {
+ // 破产后离线
+ mgr.GenBankruptOfflineExcel("1", startTimeStr, endTimeStr)
+ }
+ if switchArr[ExcelTypeOfflineCoin] == 1 {
+ // 离线金币
+ mgr.GenOfflineCoinExcel("1", startTimeStr, endTimeStr)
+ }
+
+ startTime = et
+ }
+
+ mgr.SaveAll(VP.GetString("StartTime")[:10], endTime.AddDate(0, 0, -1).Format(time.DateOnly))
+}
+
+func (e *ExcelMgr) SaveAll(startTime, endTime string) {
+ switchArr := VP.GetIntSlice("Switch")
+ if switchArr[ExcelTypeNewPlayerBankrupt] == 1 {
+ e.Save(ExcelTypeNewPlayerBankrupt, fmt.Sprintf("新用户破产率_%s_%s.xlsx", startTime, endTime))
+ }
+ if switchArr[ExcelTypeGameTimeAvg] == 1 {
+ e.Save(ExcelTypeGameTimeAvg, fmt.Sprintf("新用户平局游戏时长_%s_%s.xlsx", startTime, endTime))
+ }
+ if switchArr[ExcelTypeGameCountAvg] == 1 {
+ e.Save(ExcelTypeGameCountAvg, fmt.Sprintf("新用户平均局数_%s_%s.xlsx", startTime, endTime))
+ }
+ if switchArr[ExcelTypeGameRate] == 1 {
+ e.Save(ExcelTypeGameRate, fmt.Sprintf("平均倍数_%s_%s.xlsx", startTime, endTime))
+ }
+ if switchArr[ExcelTypeActiveRate] == 1 {
+ e.Save(ExcelTypeActiveRate, fmt.Sprintf("活跃破产率_%s_%s.xlsx", startTime, endTime))
+ }
+ if switchArr[ExcelTypeCtrlWinRate] == 1 {
+ e.Save(ExcelTypeCtrlWinRate, fmt.Sprintf("控赢胜率_%s_%s.xlsx", startTime, endTime))
+ e.Save(ExcelTypeCtrlWinRate*10, fmt.Sprintf("控输胜率_%s_%s.xlsx", startTime, endTime))
+ }
+ if switchArr[ExcelTypeRobotWinRate] == 1 {
+ e.Save(ExcelTypeRobotWinRate, fmt.Sprintf("机器人输率_%s_%s.xlsx", startTime, endTime))
+ }
+ if switchArr[ExcelTypeCoinAvg] == 1 {
+ e.Save(ExcelTypeCoinAvg, fmt.Sprintf("人均获得金币_%s_%s.xlsx", startTime, endTime))
+ }
+ if switchArr[ExcelTypeBankruptOffline] == 1 {
+ e.Save(ExcelTypeBankruptOffline, fmt.Sprintf("破产后离线_%s_%s.xlsx", startTime, endTime))
+ }
+ if switchArr[ExcelTypeOfflineCoin] == 1 {
+ e.Save(ExcelTypeOfflineCoin, fmt.Sprintf("离线金币_%s_%s.xlsx", startTime, endTime))
+ }
+}
+
+func GetGameFreeName(id int) string {
+ d := gamefree.PBDB_GameFreeMgr.GetData(int32(id))
+ return fmt.Sprintf("%s_%s", d.Name, d.Title)
+}
+
+func (e *ExcelMgr) GenNewPlayerBankruptRateExcel(plt string, startTime, endTime string) {
+ for _, v := range VP.GetIntSlice("Gamefreeids") {
+ _, a, b, err := task.NewPlayerBankruptRate(plt, startTime, endTime, v)
+ if err != nil {
+ logger.Logger.Errorf("NewPlayerBankruptRate get StartTime:%v EndTime:%v GameFreeId:%v err: %v", startTime, endTime, v, err)
+ continue
+ }
+ ex := e.Get(ExcelTypeNewPlayerBankrupt)
+ ex.NewLine()
+ ex.SetCell(startTime[:10])
+ ex.SetCell(GetGameFreeName(v))
+ ex.SetCell(a)
+ ex.SetCell(b)
+ if b > 0 {
+ ex.SetCell(float64(a) / float64(b))
+ } else {
+ ex.SetCell(0)
+ }
+ logger.Logger.Tracef("NewPlayerBankruptRate GameFreeId: %v rate: %v", v, float64(a)/float64(b))
+ }
+}
+
+func (e *ExcelMgr) GenNewPlayerGameTimeAvgExcel(plt string, startTime, endTime string) {
+ for _, v := range VP.GetIntSlice("Gamefreeids") {
+ a, b, err := task.NewPlayerGameTimeAvg(plt, startTime, endTime, v)
+ if err != nil {
+ logger.Logger.Errorf("NewPlayerGameTimeAvg get StartTime:%v EndTime:%v GameFreeId:%v err: %v", startTime, endTime, v, err)
+ continue
+ }
+ ex := e.Get(ExcelTypeGameTimeAvg)
+ ex.NewLine()
+ ex.SetCell(startTime[:10])
+ ex.SetCell(GetGameFreeName(v))
+ ex.SetCell(a)
+ if a > 0 {
+ avg := float64(b) / float64(a)
+ show := fmt.Sprintf("%v", time.Second*time.Duration(avg))
+ ex.SetCell(show)
+ } else {
+ ex.SetCell(0)
+ }
+ logger.Logger.Tracef("NewPlayerGameTimeAvg GameFreeId: %v avg: %v", v, float64(b)/float64(a))
+ }
+}
+
+func (e *ExcelMgr) GenGameCountExcel(plt string, startTime, endTime string) {
+ for _, v := range VP.GetIntSlice("Gamefreeids") {
+ a, b, err := task.NewPlayerGameCountAvg(plt, startTime, endTime, v)
+ if err != nil {
+ logger.Logger.Errorf("NewPlayerGameCountAvg get StartTime:%v EndTime:%v GameFreeId:%v err: %v", startTime, endTime, v, err)
+ continue
+ }
+ ex := e.Get(ExcelTypeGameCountAvg)
+ ex.NewLine()
+ ex.SetCell(startTime[:10])
+ ex.SetCell(GetGameFreeName(v))
+ ex.SetCell(a)
+ if a > 0 {
+ ex.SetCell(float64(b) / float64(a))
+ } else {
+ ex.SetCell(0)
+ }
+ logger.Logger.Tracef("NewPlayerGameCountAvg GameFreeId: %v avg: %v", v, float64(b)/float64(a))
+ }
+}
+
+func (e *ExcelMgr) GenGameRateExcel(plt string, startTime, endTime string) {
+ for _, v := range VP.GetIntSlice("Gamefreeids") {
+ total, bombTotal, remain2Total, rateAvg, bombRateAvg, err := task.PlayerGameRate(plt, startTime, endTime, v)
+ if err != nil {
+ logger.Logger.Errorf("PlayerGameRate get StartTime:%v EndTime:%v GameFreeId:%v err: %v", startTime, endTime, v, err)
+ continue
+ }
+ ex := e.Get(ExcelTypeGameRate)
+ ex.NewLine()
+ ex.SetCell(startTime[:10])
+ ex.SetCell(GetGameFreeName(v))
+ ex.SetCell(total)
+ ex.SetCell(rateAvg)
+ ex.SetCell(bombTotal)
+ ex.SetCell(bombRateAvg)
+ ex.SetCell(remain2Total)
+ logger.Logger.Tracef("PlayerGameRate GameFreeId: %v total: %v rateAvg: %v bombTotal: %v bombRateAvg: %v remain2Total: %v",
+ v, total, rateAvg, bombTotal, bombRateAvg, remain2Total)
+ }
+}
+
+func (e *ExcelMgr) GenActiveBankruptRateExcel(plt string, startTime, endTime string) {
+ for _, v := range VP.GetIntSlice("Gamefreeids") {
+ b, t, err := task.ActivePlayerBankruptRate(plt, startTime, endTime, v)
+ if err != nil {
+ logger.Logger.Errorf("ActivePlayerBankruptRate get StartTime:%v EndTime:%v GameFreeId:%v err: %v", startTime, endTime, v, err)
+ continue
+ }
+
+ ex := e.Get(ExcelTypeActiveRate)
+ ex.NewLine()
+ ex.SetCell(startTime[:10])
+ ex.SetCell(GetGameFreeName(v))
+ ex.SetCell(b)
+ ex.SetCell(t)
+ if t > 0 {
+ if b > t {
+ b = t
+ }
+ ex.SetCell(float64(b) / float64(t))
+ } else {
+ ex.SetCell(0)
+ }
+ logger.Logger.Tracef("ActivePlayerBankruptRate GameFreeId: %v rate: %v", v, float64(b)/float64(t))
+ }
+}
+
+func (e *ExcelMgr) GenCtrlWinRateExcel(plt string, startTime, endTime string) {
+ for _, v := range append(VP.GetIntSlice("Tienlen")) {
+ ret, err := task.GameDetailWinRate(plt, startTime, endTime, v)
+ if err != nil {
+ logger.Logger.Errorf("GameDetailWinRate get StartTime:%v EndTime:%v GameFreeId:%v err: %v", startTime, endTime, v, err)
+ continue
+ }
+ if len(ret) > 0 {
+ ex := e.Get(ExcelTypeCtrlWinRate)
+ ex.NewLine()
+ ex.SetCell(startTime[:10])
+ ex.SetCell(GetGameFreeName(v))
+ ex.SetCell(ret[0].First)
+ ex.SetCell(ret[0].Second)
+ ex.SetCell(ret[0].Third)
+ ex.SetCell(ret[0].Total)
+ if ret[0].Total > 0 {
+ ex.SetCell(float64(ret[0].First+ret[0].Second) / float64(ret[0].Total))
+ } else {
+ ex.SetCell(0)
+ }
+ logger.Logger.Tracef("GameDetailWinRate GameFreeId:%v %+v", v, ret[0])
+ }
+ if len(ret) > 1 {
+ ex := e.Get(ExcelTypeCtrlWinRate * 10)
+ ex.NewLine()
+ ex.SetCell(startTime[:10])
+ ex.SetCell(GetGameFreeName(v))
+ ex.SetCell(ret[1].First)
+ ex.SetCell(ret[1].Second)
+ ex.SetCell(ret[1].Third)
+ ex.SetCell(ret[1].Total)
+ if ret[1].Total > 0 {
+ ex.SetCell(float64(ret[1].First+ret[1].Second) / float64(ret[1].Total))
+ } else {
+ ex.SetCell(0)
+ }
+ logger.Logger.Tracef("GameDetailWinRate GameFreeId:%v %+v", v, ret[1])
+ }
+ }
+}
+
+func (e *ExcelMgr) GenRobotWinRateExcel(plt string, startTime, endTime string) {
+ for _, v := range append(VP.GetIntSlice("Tienlen"), VP.GetIntSlice("Thirteen")...) {
+ a, b, err := task.RobotWinRate(plt, startTime, endTime, v)
+ if err != nil {
+ logger.Logger.Errorf("RobotWinRate get StartTime:%v EndTime:%v GameFreeId:%v err: %v", startTime, endTime, v, err)
+ continue
+ }
+
+ ex := e.Get(ExcelTypeRobotWinRate)
+ ex.NewLine()
+ ex.SetCell(startTime[:10])
+ ex.SetCell(GetGameFreeName(v))
+ ex.SetCell(a)
+ ex.SetCell(b)
+ if b > 0 {
+ ex.SetCell(float64(b-a) / float64(b))
+ } else {
+ ex.SetCell(0)
+ }
+ logger.Logger.Tracef("RobotWinRate GameFreeId: %v rate: %v", v, float64(a)/float64(b))
+ }
+}
+
+func (e *ExcelMgr) GenCoinAvgExcel(plt string, startTime, endTime string) {
+ type KV struct {
+ K int
+ V string
+ }
+
+ var list []KV
+ for k, v := range task.CoinName {
+ list = append(list, KV{K: k, V: v})
+ }
+
+ sort.Slice(list, func(i, j int) bool {
+ return list[i].K < list[j].K
+ })
+
+ for _, item := range list {
+ k, v := item.K, item.V
+ a, b, err := task.CoinAvg(plt, startTime, endTime, k)
+ if err != nil {
+ logger.Logger.Errorf("CoinAvg get StartTime:%v EndTime:%v tp:%v err: %v", startTime, endTime, k, err)
+ continue
+ }
+ ex := e.Get(ExcelTypeCoinAvg)
+ ex.NewLine()
+ ex.SetCell(startTime[:10])
+ ex.SetCell(v)
+ ex.SetCell(a)
+ ex.SetCell(b)
+ if a > 0 {
+ ex.SetCell(float64(b) / float64(a))
+ } else {
+ ex.SetCell(0)
+ }
+ logger.Logger.Tracef("CoinAvg tp: %v rate: %v", k, float64(b)/float64(a))
+ }
+}
+
+func (e *ExcelMgr) GenBankruptOfflineExcel(plt string, startTime, endTime string) {
+ res, err := task.BankruptOffline(plt, startTime, endTime)
+ if err != nil {
+ logger.Logger.Errorf("BankruptOffline get StartTime:%v EndTime:%v err: %v", startTime, endTime, err)
+ return
+ }
+ ex := e.Get(ExcelTypeBankruptOffline)
+ ex.NewLine()
+ ex.SetCell(startTime[:10])
+ ex.SetCell(res.One)
+ ex.SetCell(res.Two)
+ ex.SetCell(res.Three)
+ ex.SetCell(res.Recharge)
+ ex.SetCell(fmt.Sprintf("%v", res.D))
+ logger.Logger.Tracef("BankruptOffline %+v", res)
+}
+
+func (e *ExcelMgr) GenOfflineCoinExcel(plt string, startTime, endTime string) {
+ res, err := task.OfflineCoin(plt, startTime, endTime)
+ if err != nil {
+ logger.Logger.Errorf("OfflineCoin get StartTime:%v EndTime:%v err: %v", startTime, endTime, err)
+ return
+ }
+ for _, v := range res {
+ ex := e.Get(ExcelTypeOfflineCoin)
+ ex.NewLine()
+ ex.SetCell(startTime[:10])
+ ex.SetCell(v.Snid)
+ ex.SetCell(v.Coin)
+ }
+ logger.Logger.Tracef("OfflineCoin %+v", res)
+}
diff --git a/statistics/task/readme b/statistics/task/readme
new file mode 100644
index 0000000..5fdb673
--- /dev/null
+++ b/statistics/task/readme
@@ -0,0 +1 @@
+查询历史数据
\ No newline at end of file
diff --git a/statistics/task/task/bankruptcy.go b/statistics/task/task/bankruptcy.go
new file mode 100644
index 0000000..ea4212d
--- /dev/null
+++ b/statistics/task/task/bankruptcy.go
@@ -0,0 +1,189 @@
+package task
+
+import (
+ "context"
+ "errors"
+ "fmt"
+
+ "go.mongodb.org/mongo-driver/bson"
+ "go.mongodb.org/mongo-driver/mongo"
+ "go.mongodb.org/mongo-driver/mongo/options"
+ "mongo.games.com/goserver/core/logger"
+ mymongo "mongo.games.com/goserver/core/mongox"
+ mymysql "mongo.games.com/goserver/core/mysqlx"
+
+ "mongo.games.com/game/common"
+ mongomodel "mongo.games.com/game/statistics/modelmongo"
+)
+
+// 新用户id
+func GetNewPayerIds(plt string, startTime, endTime string) ([]int, error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ c, err := mymongo.GetUserCollection(plt, mongomodel.UserAccount)
+ if err != nil {
+ return nil, err
+ }
+
+ var res []struct{ Snid int }
+ dd, err := c.Find(context.TODO(), bson.M{"registetime": bson.M{"$gte": s, "$lt": e}}, options.Find().SetProjection(bson.M{"snid": 1}))
+ if err != nil {
+ if errors.Is(err, mongo.ErrNoDocuments) {
+ return nil, nil
+ }
+ logger.Logger.Errorf("find new player snid get err: %v", err)
+ return nil, err
+ }
+ if err := dd.All(context.TODO(), &res); err != nil {
+ logger.Logger.Errorf("find new player snid decode err: %v", err)
+ return nil, err
+ }
+ var ret []int
+ for _, v := range res {
+ ret = append(ret, v.Snid)
+ }
+ logger.Logger.Tracef("find new player snid: %v", ret)
+ return ret, nil
+}
+
+// 场次破产总人数
+func GameFreeIdBankruptPlayerCount(plt string, ids []int, startTime, endTime string, gamefreeid int) (int, error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ db, err := mymysql.GetDatabase("count")
+ if err != nil {
+ return 0, err
+ }
+ var ret int
+ for _, v := range ids {
+ var n int64
+ if err = db.Table("bankrupt_log").Where("snid = ? AND bankrupt_time >= ? AND bankrupt_time < ? AND game_free_id = ?",
+ v, s.Unix(), e.Unix(), gamefreeid).Count(&n).Error; err != nil {
+ logger.Logger.Errorf("find bankrupt player count get err: %v", err)
+ return 0, err
+ }
+ if n > 0 {
+ ret++
+ }
+ }
+ return ret, nil
+}
+
+// 场次参与总人数
+func PlayingGameCount(plt string, ids []int, startTime, endTime string, gamefreeid int) (int, error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ c, err := mymongo.GetLogCollection(plt, mongomodel.LogGamePlayerListLog)
+ if err != nil {
+ return 0, err
+ }
+
+ var ret int
+ for _, v := range ids {
+ // 参与过游戏
+ n, err := c.CountDocuments(context.TODO(), bson.M{"snid": v, "time": bson.M{"$gte": s, "$lt": e}, "gamefreeid": gamefreeid})
+ if err != nil {
+ logger.Logger.Errorf("find playing game count get err: %v", err)
+ return 0, err
+ }
+ if n > 0 {
+ ret++
+ }
+ }
+ return ret, nil
+}
+
+// NewPlayerBankruptRate 新用户破产率
+// 新用户游戏破产率 = 新用户游戏破产玩家数量/新用户游戏参与总人数;破产数量,每个玩家每个游戏破产只统计一次;参与人数,每个玩家每个游戏只统计一次;
+// 返回 新用户数量,破产人数,参与人数
+func NewPlayerBankruptRate(plt string, startTime, endTime string, gamefreeid int) (n, a, b int, err error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ if s.IsZero() || e.IsZero() {
+ return 0, 0, 0, fmt.Errorf("time format error")
+ }
+ ids, err := GetNewPayerIds(plt, startTime, endTime)
+ if err != nil {
+ return 0, 0, 0, err
+ }
+
+ a, err = GameFreeIdBankruptPlayerCount(plt, ids, startTime, endTime, gamefreeid)
+ if err != nil {
+ return 0, 0, 0, err
+ }
+ b, err = PlayingGameCount(plt, ids, startTime, endTime, gamefreeid)
+ if err != nil {
+ return 0, 0, 0, err
+ }
+ if b == 0 {
+ return 0, 0, 0, nil
+ }
+ return
+}
+
+// ActivePlayerCount 活跃玩家游戏总人数
+func ActivePlayerCount(plt string, startTime, endTime string, gamefreeid int) (int, error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ c, err := mymongo.GetLogCollection(plt, mongomodel.LogGamePlayerListLog)
+ if err != nil {
+ return 0, err
+ }
+
+ var count []struct {
+ Count int
+ }
+ cur, err := c.Aggregate(context.TODO(), bson.A{
+ bson.M{"$match": bson.M{"time": bson.M{"$gte": s, "$lt": e}, "gamefreeid": gamefreeid}},
+ bson.M{"$group": bson.M{"_id": "$snid"}},
+ bson.M{"$count": "count"},
+ })
+ if err != nil {
+ logger.Logger.Errorf("find active player count get err: %v", err)
+ return 0, err
+ }
+ if err := cur.All(context.TODO(), &count); err != nil {
+ logger.Logger.Errorf("find active player count decode err: %v", err)
+ return 0, err
+ }
+
+ if len(count) == 0 {
+ return 0, nil
+ }
+
+ return count[0].Count, nil
+}
+
+// ActivePlayerBankruptCount 活跃玩家破产总人数
+func ActivePlayerBankruptCount(plt string, startTime, endTime string, gamefreeid int) (int, error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ db, err := mymysql.GetDatabase("count")
+ if err != nil {
+ return 0, err
+ }
+ var n int64
+ if err = db.Table("bankrupt_log").Where("bankrupt_time >= ? AND bankrupt_time < ? AND game_free_id = ?",
+ s.Unix(), e.Unix(), gamefreeid).Group("snid").Count(&n).Error; err != nil {
+ logger.Logger.Errorf("find bankrupt count get err: %v", err)
+ return 0, err
+ }
+ return int(n), nil
+}
+
+// ActivePlayerBankruptRate 活跃玩家破产率
+// 活跃玩家游戏破产率 = 活跃玩家游戏破产玩家数量/活跃玩家游戏总人数;破产数量,每个玩家每个游戏破产只统计一次;参与人数,每个玩家每个游戏只统计一次;
+// 返回 破产人数,参与人数
+func ActivePlayerBankruptRate(plt string, startTime, endTime string, gamefreeid int) (a, b int, err error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ if s.IsZero() || e.IsZero() {
+ return 0, 0, fmt.Errorf("time format error")
+ }
+
+ a, err = ActivePlayerBankruptCount(plt, startTime, endTime, gamefreeid)
+ if err != nil {
+ return 0, 0, err
+ }
+ b, err = ActivePlayerCount(plt, startTime, endTime, gamefreeid)
+ if err != nil {
+ return 0, 0, err
+ }
+ if b == 0 {
+ return 0, 0, nil
+ }
+ return
+}
diff --git a/statistics/task/task/bankruptoffline.go b/statistics/task/task/bankruptoffline.go
new file mode 100644
index 0000000..c6f71c1
--- /dev/null
+++ b/statistics/task/task/bankruptoffline.go
@@ -0,0 +1,153 @@
+package task
+
+import (
+ "context"
+ "fmt"
+ "strconv"
+ "strings"
+ "time"
+
+ "go.mongodb.org/mongo-driver/bson"
+ "mongo.games.com/game/common"
+ mymongo "mongo.games.com/goserver/core/mongox"
+ mymysql "mongo.games.com/goserver/core/mysqlx"
+)
+
+// 破产后离线;破产后5分钟内有离线行为
+
+type BankruptOfflineData struct {
+ One int
+ Two int
+ Three int
+ Recharge int
+ D time.Duration
+}
+
+func BankruptOffline(plt string, startTime, endTime string) (ret *BankruptOfflineData, err error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ if s.IsZero() || e.IsZero() {
+ return nil, fmt.Errorf("time format error")
+ }
+ db, err := mymysql.GetDatabase("count")
+ if err != nil {
+ return nil, err
+ }
+
+ /*
+ SELECT
+ snid,
+ GROUP_CONCAT(bankrupt_time ORDER BY bankrupt_time ASC LIMIT 3) AS top_3_bankrupt_times,
+ COUNT(*) AS record_count
+ FROM
+ bankrupt_log
+ GROUP BY
+ snid;
+ */
+
+ type BankruptLog struct {
+ Snid int `gorm:"column:snid"`
+ MaxBankruptTime string `gorm:"column:top_3_bankrupt_times"`
+ Times []int64 `gorm:"-"`
+ }
+
+ var logs []*BankruptLog
+ tx := db.Raw(`
+ WITH RankedData AS (
+ SELECT
+ snid,
+ bankrupt_time,
+ ROW_NUMBER() OVER (PARTITION BY snid ORDER BY bankrupt_time ASC) AS ranks
+ FROM bankrupt_log
+ WHERE bankrupt_time >= ? AND bankrupt_time < ?
+ )
+ SELECT
+ snid,
+ GROUP_CONCAT(bankrupt_time ORDER BY bankrupt_time ASC) AS top_3_bankrupt_times
+ FROM RankedData
+ WHERE ranks <= 3
+ GROUP BY snid
+`, s.Unix(), e.Unix()).Scan(&logs)
+
+ if tx.Error != nil {
+ return nil, tx.Error
+ }
+
+ var timeSecond int
+ var total int
+
+ ret = &BankruptOfflineData{}
+ for _, v := range logs {
+ if v == nil || len(v.MaxBankruptTime) == 0 {
+ continue
+ }
+ for _, vv := range strings.Split(v.MaxBankruptTime, ",") {
+ n, err := strconv.Atoi(vv)
+ if err != nil {
+ return nil, err
+ }
+ v.Times = append(v.Times, int64(n))
+ }
+
+ // 破产后5分钟内有离线行为
+ db, err = mymysql.GetDatabase(plt)
+ if err != nil {
+ return nil, err
+ }
+
+ var isOffline bool
+ for k, vv := range v.Times {
+ var n int64
+ stime, etime := time.Unix(vv, 0), time.Unix(vv, 0).Add(5*time.Minute)
+ if err = db.Table("user_logins").Where("snid = ? AND offline_time >= ? AND offline_time < ?",
+ v.Snid, stime, etime).Count(&n).Error; err != nil {
+ return nil, err
+ }
+ switch k {
+ case 0:
+ if n > 0 {
+ ret.One++
+ isOffline = true
+ }
+ case 1:
+ if n > 0 {
+ ret.Two++
+ isOffline = true
+ }
+ case 2:
+ if n > 0 {
+ ret.Three++
+ isOffline = true
+ }
+ }
+ }
+
+ if isOffline {
+ // 充值
+ c, err := mymongo.GetLogCollection(plt, "log_dbshop")
+ if err != nil {
+ return nil, err
+ }
+ count, err := c.CountDocuments(context.TODO(), bson.M{"snid": v.Snid, "ts": bson.M{"$gte": s.Unix(), "$lt": e.Unix()}, "consume": 3, "state": 1})
+ if err != nil {
+ return nil, err
+ }
+ if count > 0 {
+ ret.Recharge++
+ }
+
+ // 平均对局时长
+ times, to, err := NewPlayerGameTime(plt, []int{v.Snid}, startTime, endTime, 0)
+ if err != nil {
+ return nil, err
+ }
+ timeSecond += times
+ total += to
+ }
+ }
+
+ if total > 0 {
+ ret.D = time.Second * time.Duration(timeSecond/total)
+ }
+
+ return ret, nil
+}
diff --git a/statistics/task/task/coin.go b/statistics/task/task/coin.go
new file mode 100644
index 0000000..10747e6
--- /dev/null
+++ b/statistics/task/task/coin.go
@@ -0,0 +1,77 @@
+package task
+
+import (
+ "context"
+ "fmt"
+ "go.mongodb.org/mongo-driver/bson"
+ "mongo.games.com/game/common"
+ "mongo.games.com/goserver/core/mongox"
+)
+
+var CoinName = map[int]string{
+ 44: "转盘",
+ 57: "轮盘视频",
+ 43: "签到",
+ 58: "签到视频",
+ 83: "累计签到",
+ 91: "累计签到进阶奖励",
+ 47: "vip每日礼包",
+ 105: "vip等级礼包",
+ 74: "集卡活动",
+ 67: "金币存钱罐",
+ 94: "赛季通行证等级奖励",
+ 95: "赛季通行证排行奖励",
+ 52: "排位赛段位奖励",
+ 65: "周卡奖励",
+ 5: "兑换",
+ 49: "礼包码兑换",
+ 38: "救济金",
+ 56: "救济金视频",
+}
+
+func CoinHistory(plt string, startTime, endTime string, tp int, f func(data bson.M) error) error {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ if s.IsZero() || e.IsZero() {
+ return fmt.Errorf("time format error")
+ }
+ c, err := mongox.GetLogCollection(plt, "log_coinex")
+ if err != nil {
+ return err
+ }
+
+ cur, err := c.Find(context.TODO(), bson.M{"time": bson.M{"$gte": s, "$lt": e}, "logtype": tp, "cointype": 0})
+ if err != nil {
+ return err
+ }
+ defer cur.Close(context.Background())
+
+ for cur.TryNext(context.Background()) {
+ data := bson.M{}
+ if err := cur.Decode(data); err != nil {
+ return err
+ }
+ if err := f(data); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func CoinAvg(plt string, startTime, endTime string, tp int) (playerNumber int, total int, err error) {
+ player := make(map[int32]struct{})
+ err = CoinHistory(plt, startTime, endTime, tp, func(data bson.M) error {
+ snid := data["snid"].(int32)
+ count := int(data["count"].(int64))
+ player[snid] = struct{}{}
+ if count > 0 {
+ total += count
+ }
+ return nil
+ })
+ if err != nil {
+ return
+ }
+ playerNumber = len(player)
+ return
+}
diff --git a/statistics/task/task/ctrlrate.go b/statistics/task/task/ctrlrate.go
new file mode 100644
index 0000000..25eaf26
--- /dev/null
+++ b/statistics/task/task/ctrlrate.go
@@ -0,0 +1,214 @@
+package task
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "sort"
+
+ "go.mongodb.org/mongo-driver/bson"
+ "mongo.games.com/goserver/core/logger"
+ "mongo.games.com/goserver/core/mongox"
+
+ "mongo.games.com/game/common"
+)
+
+func GameDetailFunc(plt string, startTime, endTime string, gamefreeid int, f func(data bson.M) error) error {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ if s.IsZero() || e.IsZero() {
+ return fmt.Errorf("time format error")
+ }
+
+ c, err := mongox.GetLogCollection(plt, "log_gamedetailed")
+ if err != nil {
+ return err
+ }
+
+ cur, err := c.Find(context.TODO(), bson.M{"time": bson.M{"$gte": s, "$lt": e}, "gamefreeid": gamefreeid})
+ if err != nil {
+ return err
+ }
+ defer cur.Close(context.Background())
+
+ for cur.TryNext(context.Background()) {
+ data := bson.M{}
+ if err := cur.Decode(data); err != nil {
+ return err
+ }
+ if err := f(data); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+type GameDetailWinRateRet struct {
+ First int
+ Second int
+ Third int
+ Total int
+ IsWin bool
+}
+
+func GameDetailWinRate(plt string, startTime, endTime string, gamefreeid int) (ret []GameDetailWinRateRet, err error) {
+ ret = make([]GameDetailWinRateRet, 2)
+ err = GameDetailFunc(plt, startTime, endTime, gamefreeid, func(data bson.M) error {
+ ff, ss, tt, to, ct := GameDetailCount(data)
+ if ct == 1 && to > 0 {
+ ret[0].First += ff
+ ret[0].Second += ss
+ ret[0].Third += tt
+ ret[0].Total += to
+ }
+ if ct == 2 && to > 0 {
+ ret[1].First += ff
+ ret[1].Second += ss
+ ret[1].Third += tt
+ ret[1].Total += to
+ }
+ return nil
+ })
+ return
+}
+
+type RabbitMQDataRaw struct {
+ Source int32
+ Data interface{}
+}
+
+func GameDetailCount(data bson.M) (first, second, third, to int, ctrlType int) {
+ if data == nil {
+ return
+ }
+ gameid := data["gameid"].(int32)
+ gamefreeid := data["gamefreeid"].(int32)
+ ctrlType = int(data["ctrltype"].(int32))
+ logger.Logger.Tracef("GameDetail gameid:%d, gamefreeid:%d", gameid, gamefreeid)
+
+ if ctrlType != 1 && ctrlType != 2 {
+ return
+ }
+
+ detail := data["gamedetailednote"]
+ if detail == nil {
+ return
+ }
+
+ raw := new(RabbitMQDataRaw)
+ if err := json.Unmarshal([]byte(detail.(string)), raw); err != nil {
+ logger.Logger.Errorf("GameDetailCount Unmarshal 1 error:%v %v", err, gameid)
+ return
+ }
+
+ switch gameid {
+ case 207, 208, 209, 210, 240, 241, 242, 243, 244, 245, 246, 247: // tienlen
+ data := new(TienLenType)
+ b, err := json.Marshal(raw.Data)
+ if err != nil {
+ logger.Logger.Errorf("GameDetailCount Marshal error:%v %v", err, gameid)
+ return
+ }
+ if err := json.Unmarshal(b, data); err != nil {
+ logger.Logger.Errorf("GameDetailCount Unmarshal 2 error:%v %v", err, gameid)
+ return
+ }
+
+ var has, hasPlayer bool
+ for _, v := range data.PlayerData {
+ if v.IsRob {
+ has = true
+ }
+ if !v.IsRob {
+ hasPlayer = true
+ }
+ }
+ if !has || !hasPlayer {
+ // 没有机器人
+ return 0, 0, 0, 0, ctrlType
+ }
+
+ sort.Slice(data.PlayerData, func(i, j int) bool {
+ a, b := data.PlayerData[i].BillCoin+data.PlayerData[i].BillTaxCoin, data.PlayerData[j].BillCoin+data.PlayerData[j].BillTaxCoin
+ if a != b {
+ return a > b
+ }
+ if data.PlayerData[i].IsRob != data.PlayerData[j].IsRob {
+ return !data.PlayerData[i].IsRob
+ }
+ return data.PlayerData[i].UserId < data.PlayerData[j].UserId
+ })
+
+ for k, v := range data.PlayerData {
+ if !v.IsRob && v.BillCoin > 0 && k <= 1 {
+ if k == 0 {
+ first = 1
+ }
+ if k == 1 {
+ second = 1
+ }
+ break
+ }
+ }
+ if len(data.PlayerData) > 2 && first+second == 0 && !data.PlayerData[2].IsRob {
+ third = 1
+ }
+ to = 1
+
+ case 211, 212, 213, 214:
+ data := new(ThirteenWaterType)
+ b, err := json.Marshal(raw.Data)
+ if err != nil {
+ logger.Logger.Errorf("GameDetailCount Marshal error:%v %v", err, gameid)
+ return
+ }
+ if err := json.Unmarshal(b, data); err != nil {
+ logger.Logger.Errorf("GameDetailCount Unmarshal 2 error:%v %v", err, gameid)
+ return
+ }
+
+ var has, hasPlayer bool
+ for _, v := range data.PlayerData {
+ if v.IsRob {
+ has = true
+ }
+ if !v.IsRob {
+ hasPlayer = true
+ }
+ }
+ if !has || !hasPlayer {
+ // 没有机器人
+ return 0, 0, 0, 0, ctrlType
+ }
+
+ sort.Slice(data.PlayerData, func(i, j int) bool {
+ a, b := data.PlayerData[i].AllScore, data.PlayerData[j].AllScore
+ if a != b {
+ return a > b
+ }
+ if data.PlayerData[i].IsRob != data.PlayerData[j].IsRob {
+ return !data.PlayerData[i].IsRob
+ }
+ return data.PlayerData[i].UserId < data.PlayerData[j].UserId
+ })
+
+ for k, v := range data.PlayerData {
+ if !v.IsRob && v.AllScore > 0 && k <= 1 {
+ if k == 0 {
+ first = 1
+ }
+ if k == 1 {
+ second = 1
+ }
+ break
+ }
+ }
+
+ if len(data.PlayerData) > 2 && first+second == 0 && !data.PlayerData[2].IsRob {
+ third = 1
+ }
+
+ to = 1
+ }
+ return
+}
diff --git a/statistics/task/task/gamebankruptcy.go b/statistics/task/task/gamebankruptcy.go
new file mode 100644
index 0000000..21bbbcf
--- /dev/null
+++ b/statistics/task/task/gamebankruptcy.go
@@ -0,0 +1,69 @@
+package task
+
+import (
+ "context"
+ "fmt"
+ "go.mongodb.org/mongo-driver/bson"
+ "mongo.games.com/game/common"
+ "mongo.games.com/goserver/core/logger"
+ mymongo "mongo.games.com/goserver/core/mongox"
+ mymysql "mongo.games.com/goserver/core/mysqlx"
+)
+
+// 场次破产率
+
+// GameFreeIdBankruptcyTimes 场次破产次数
+func GameFreeIdBankruptcyTimes(plt string, startTime, endTime string, gamefreeid int) (int, error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ db, err := mymysql.GetDatabase("count")
+ if err != nil {
+ return 0, err
+ }
+
+ var n int64
+ if err = db.Table("bankrupt_log").Where("bankrupt_time >= ? AND bankrupt_time < ? AND game_free_id = ?",
+ s.Unix(), e.Unix(), gamefreeid).Count(&n).Error; err != nil {
+ logger.Logger.Errorf("find bankrupt count get err: %v", err)
+ return 0, err
+ }
+
+ return int(n), nil
+}
+
+// GameFreeIdTotalTimes 场次总局数
+func GameFreeIdTotalTimes(plt string, startTime, endTime string, gamefreeid int) (int, error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ c, err := mymongo.GetLogCollection(plt, "log_gamedetailed")
+ if err != nil {
+ return 0, err
+ }
+
+ n, err := c.CountDocuments(context.TODO(), bson.M{"time": bson.M{"$gte": s, "$lt": e}, "gamefreeid": gamefreeid})
+ if err != nil {
+ logger.Logger.Errorf("find game total times get err: %v", err)
+ return 0, err
+ }
+
+ return int(n), nil
+}
+
+// GameBankruptcyRate 场次破产率
+// 返回 破产局数, 总局数, 错误
+func GameBankruptcyRate(plt string, startTime, endTime string, gamefreeid int) (int, int, error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ if s.IsZero() || e.IsZero() {
+ return 0, 0, fmt.Errorf("time format error")
+ }
+
+ b, err := GameFreeIdBankruptcyTimes(plt, startTime, endTime, gamefreeid)
+ if err != nil {
+ return 0, 0, err
+ }
+
+ t, err := GameFreeIdTotalTimes(plt, startTime, endTime, gamefreeid)
+ if err != nil {
+ return 0, 0, err
+ }
+
+ return b, t, nil
+}
diff --git a/statistics/task/task/gamecount.go b/statistics/task/task/gamecount.go
new file mode 100644
index 0000000..911c202
--- /dev/null
+++ b/statistics/task/task/gamecount.go
@@ -0,0 +1,60 @@
+package task
+
+import (
+ "context"
+ "fmt"
+ "go.mongodb.org/mongo-driver/bson"
+ "mongo.games.com/game/common"
+ mongomodel "mongo.games.com/game/statistics/modelmongo"
+ "mongo.games.com/goserver/core/logger"
+ mymongo "mongo.games.com/goserver/core/mongox"
+)
+
+// 新用户平均局数
+
+func NewPlayerGameCount(plt string, ids []int, startTime, endTime string, gamefreeid int) (int, error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ c, err := mymongo.GetLogCollection(plt, mongomodel.LogGamePlayerListLog)
+ if err != nil {
+ return 0, err
+ }
+
+ var ret int
+ for _, v := range ids {
+ n, err := c.CountDocuments(context.TODO(), bson.M{"snid": v, "gamefreeid": gamefreeid, "time": bson.M{"$gte": s, "$lt": e}})
+ if err != nil {
+ logger.Logger.Errorf("find player gamedetailedlogid get err: %v", err)
+ return 0, err
+ }
+ ret += int(n)
+ }
+
+ return ret, nil
+}
+
+// NewPlayerGameCountAvg 新用户平均局数
+// 返回 参与人数,总局数
+func NewPlayerGameCountAvg(plt string, startTime, endTime string, gamefreeid int) (int, int, error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ if s.IsZero() || e.IsZero() {
+ return 0, 0, fmt.Errorf("time format error")
+ }
+ ids, err := GetNewPayerIds(plt, startTime, endTime)
+ if err != nil {
+ return 0, 0, err
+ }
+ n, err := NewPlayerGameCount(plt, ids, startTime, endTime, gamefreeid)
+ if err != nil {
+ return 0, 0, err
+ }
+ if len(ids) == 0 {
+ return 0, 0, nil
+ }
+
+ b, err := PlayingGameCount(plt, ids, startTime, endTime, gamefreeid)
+ if err != nil {
+ return 0, 0, err
+ }
+
+ return b, n, nil
+}
diff --git a/statistics/task/task/gamelogtype.go b/statistics/task/task/gamelogtype.go
new file mode 100644
index 0000000..74cbf60
--- /dev/null
+++ b/statistics/task/task/gamelogtype.go
@@ -0,0 +1,1625 @@
+package task
+
+// 赢三张
+type WinThreeType struct {
+ RoomId int32 //房间ID
+ RoomRounds int32 //建房局数
+ RoomType int32 //房间类型
+ NowRound int32 //当前局数
+ PlayerCount int //玩家数量
+ BaseScore int32 //底分
+ PlayerData []WinThreePerson //玩家信息
+ Chip []PlayerChip //出牌详情
+ ClubRate int32 //俱乐部抽水比例
+ IsSmartOperation bool // 是否启用智能化运营
+ GamePreUseSmartState int //-1:正常
+ GameLastUseSmartState int //-1:正常
+ RobotUseSmartState int64 // Robot使用智能化运营状况
+}
+type PlayerChip struct {
+ UserId int32 //玩家ID
+ BetTotal int64 //玩家总投注
+ Chip int64 //玩家得分
+ StartCoin int64 //玩家开始前金币
+ BetAfterCoin int64 //玩家投注后金币,也就是客户端应该显示的金币
+ IsCheck bool //是否看牌
+ Round int32 //当前轮次
+ Op int32 //操作
+}
+
+type WinThreePerson struct {
+ UserId int32 //玩家ID
+ UserIcon int32 //玩家头像
+ ChangeCoin int64 //玩家得分
+ Cardinfo []int32 //牌值
+ KindOfCard int32 //牌型
+ IsWin int32 //输赢
+ IsRob bool //是否是机器人
+ Tax int64 //税,不一定有值,只是作为一个临时变量使用
+ ClubPump int64 //俱乐部额外抽水
+ Flag int //标识
+ Pos int32 //位置
+ StartCoin int64 //开始金币
+ BetCoin int64 //押注额度
+ RoundCheck int32 //轮次,看牌的
+ IsFirst bool //是否第一次
+ IsLeave bool
+ Platform string `json:"-"`
+ Channel string `json:"-"`
+ Promoter string `json:"-"`
+ PackageTag string `json:"-"`
+ InviterId int32 `json:"-"`
+ WBLevel int32 //黑白名单等级
+ SingleFlag int32 //单控标记
+}
+
+// 经典牛牛 抢庄牛牛
+type BullFightType struct {
+ RoomId int32 //房间ID
+ RoomRounds int32 //建房局数
+ RoomType int32 //房间类型
+ NowRound int32 //当前局数
+ BankId int32 //庄家ID
+ PlayerCount int //玩家数量
+ BaseScore int32 //底分
+ PlayerData []BullFightPerson //玩家信息
+ ClubRate int32 //俱乐部抽水比例
+ IsSmartOperation bool // 是否启用智能化运营
+}
+type BullFightPerson struct {
+ UserId int32 //玩家ID
+ UserIcon int32 //玩家头像
+ ChangeCoin int64 //玩家得分
+ Cardinfo []int32 //牌值
+ CardBakinfo []int32 //牌值
+ IsWin int32 //输赢
+ Tax int64 //税,不一定有值,只是作为一个临时变量使用
+ TaxLottery int64 //彩金池,增加值
+ ClubPump int64 //俱乐部额外抽水
+ IsRob bool //是否是机器人
+ Flag int //标识
+ IsFirst bool
+ StartCoin int64 //开始金币
+ Platform string `json:"-"`
+ Channel string `json:"-"`
+ Promoter string `json:"-"`
+ PackageTag string `json:"-"`
+ InviterId int32 `json:"-"`
+ Rate int64 //斗牛倍率
+ WBLevel int32 //黑白名单等级
+ RobBankRate int64 //抢庄倍率
+ SingleAdjust int32 // 单控输赢 1赢 2输
+}
+
+// 斗地主、跑得快
+type DoudizhuType struct {
+ RoomId int //房间Id
+ BasicScore int //基本分
+ Spring int //春天 1代表春天
+ BombScore int //炸弹分
+ BaseScore int32 //底分
+ PlayerCount int32 //玩家数量
+ BaseCards []int //底牌
+ BankerId int32 //斗地主地主Id
+ PlayerData []DoudizhuPerson //玩家信息
+ ClubRate int32 //俱乐部抽水比例
+ IsSmartOperation bool // 是否启用智能化运营
+}
+type DoudizhuPerson struct {
+ UserId int32 //玩家ID
+ UserIcon int32 //玩家头像
+ ChangeCoin int64 //玩家得分
+ OutCards [][]int64 //出的牌
+ SurplusCards []int32 //剩下的牌
+ IsWin int64 //输赢
+ IsRob bool //是否是机器人
+ IsFirst bool
+ Tax int64 //税,不一定有值,只是作为一个临时变量使用
+ ClubPump int64 //俱乐部额外抽水
+ StartCoin int64 //开始的金币数量
+ Platform string `json:"-"`
+ Channel string `json:"-"`
+ Promoter string `json:"-"`
+ PackageTag string `json:"-"`
+ InviterId int32 `json:"-"`
+ WBLevel int32 //黑白名单等级
+}
+
+// 推饼
+type TuibingType struct {
+ RoomId int32 //房间ID
+ RoomRounds int32 //建房局数
+ RoomType int32 //房间类型
+ NowRound int32 //当前局数
+ BankId int32 //庄家ID
+ PlayerCount int //玩家数量
+ BaseScore int32 //底分
+ PlayerData []TuibingPerson //玩家信息
+ ClubRate int32 //俱乐部抽水比例
+ IsSmartOperation bool // 是否启用智能化运营
+}
+type TuibingPerson struct {
+ UserId int32 //玩家ID
+ UserIcon int32 //玩家头像
+ ChangeCoin int64 //玩家得分
+ Cardinfo []int32 //牌值
+ IsWin int32 //输赢
+ IsRob bool //是否是机器人
+ IsFirst bool
+ Tax int64 //税,不一定有值,只是作为一个临时变量使用
+ ClubPump int64 //俱乐部额外抽水
+ Flag int //标识
+ StartCoin int64 //开始金币
+ Platform string `json:"-"`
+ Channel string `json:"-"`
+ Promoter string `json:"-"`
+ PackageTag string `json:"-"`
+ InviterId int32 `json:"-"`
+ Rate int64 //倍率
+ WBLevel int32 //黑白名单等级
+}
+
+// 十三水
+// 十三水牌值
+type ThirteenWaterType struct {
+ BaseScore int32 //底分
+ PlayerData []ThirteenWaterPerson //玩家信息
+}
+
+type ThirteenWaterPoker struct {
+ Head [3]int
+ Mid [5]int
+ End [5]int
+ PokerType int
+}
+
+type ThirteenWaterPerson struct {
+ UserId int32 //玩家ID
+ IsRob bool // 是否是机器人
+ AllScore int // 总得分
+}
+
+// 二人麻将
+type MahjongType struct {
+ RoomId int32 //房间ID
+ RoomRounds int32 //建房局数
+ RoomType int32 //房间类型
+ NowRound int32 //当前局数
+ BankId int32 //庄家ID
+ PlayerCount int32 //玩家数量
+ BaseScore int32 //底分
+ HuType []int64 //本局胡牌牌型
+ PlayerData []MahjongPerson //玩家信息
+}
+type MahjongPerson struct {
+ UserId int32 //玩家ID
+ UserIcon int32 //玩家头像
+ ChangeCoin int64 //玩家得分
+ SurplusCards []int64 //剩下的牌
+ CardsChow [][]int64 //吃的牌
+ CardsPong []int64 //碰的牌
+ CardsMingKong []int64 //明杠的牌
+ CardsAnKong []int64 //暗杠的牌
+ IsWin int32 //输赢
+ StartCoin int64 //开始金币
+ Tax int64 //税,不一定有值,只是作为一个临时变量使用
+ ClubPump int64 //俱乐部额外抽水
+ IsRob bool //是否是机器人
+ IsFirst bool
+ Platform string `json:"-"`
+ Channel string `json:"-"`
+ Promoter string `json:"-"`
+ PackageTag string `json:"-"`
+ InviterId int32 `json:"-"`
+ WBLevel int32 //黑白名单等级
+}
+
+// 百人牛牛 龙虎斗 百家乐 红黑大战
+type HundredType struct {
+ RegionId int32 //边池ID 庄家 天 地 玄 黄
+ IsWin int //边池输赢
+ Rate int //倍数
+ CardsInfo []int32 //扑克牌值
+ PlayerData []HundredPerson //玩家属性
+ CardsKind int32 //牌类型
+ CardPoint int32 //点数
+ IsSmartOperation bool // 是否启用智能化运营
+}
+type HundredPerson struct {
+ UserId int32 //用户Id
+ UserBetTotal int64 //用户下注
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ IsRob bool //是否是机器人
+ IsFirst bool
+ WBLevel int32 //黑白名单等级
+ Result int32 //单控结果
+ UserBetTotalDetail []int64 //用户下注明细
+}
+
+// 碰撞
+type CrashType struct {
+ RegionId int32 //边池ID 庄家 天 地 玄 黄
+ IsWin int //边池输赢
+ Rate int //倍数
+ CardsInfo []int32 //扑克牌值
+ PlayerData []CrashPerson //玩家属性
+ CardsKind int32 //牌类型
+ CardPoint int32 //点数
+ IsSmartOperation bool // 是否启用智能化运营
+ Hash string //Hash
+ Period int //当前多少期
+ Wheel int //第几轮
+}
+
+// 碰撞
+type CrashPerson struct {
+ UserId int32 //用户Id
+ UserBetTotal int64 //用户下注
+ UserMultiple int32 //下注倍数
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ IsRob bool //是否是机器人
+ IsFirst bool
+ WBLevel int32 //黑白名单等级
+ Result int32 //单控结果
+ UserBetTotalDetail []int64 //用户下注明细
+ Tax int64 //税收
+}
+
+// 十点半
+type TenHalfType struct {
+ RoomId int32 //房间ID
+ RoomRounds int32 //建房局数
+ RoomType int32 //房间类型
+ NowRound int32 //当前局数
+ BankId int32 //庄家ID
+ PlayerCount int //玩家数量
+ BaseScore int32 //底分
+ PlayerData []TenHalfPerson //玩家信息
+ ClubRate int32 //俱乐部抽水比例
+ IsSmartOperation bool // 是否启用智能化运营
+}
+
+type TenHalfPerson struct {
+ UserId int32 //玩家ID
+ UserIcon int32 //玩家头像
+ ChangeCoin int64 //玩家得分
+ Cardinfo []int32 //牌值
+ IsWin int32 //输赢
+ Tax int64 //税,不一定有值,只是作为一个临时变量使用
+ ClubPump int64 //俱乐部额外抽水
+ IsRob bool //是否是机器人
+ Flag int //标识
+ IsFirst bool
+ StartCoin int64 //开始金币
+ Platform string `json:"-"`
+ Channel string `json:"-"`
+ Promoter string `json:"-"`
+ PackageTag string `json:"-"`
+ InviterId int32 `json:"-"`
+ WBLevel int32 //黑白名单等级
+}
+
+type GanDengYanType struct {
+ RoomId int // 房间Id
+ RoomRounds int32 // 建房局数
+ NowRound int32 // 当前局数
+ BombScore int // 炸弹倍率
+ BaseScore int32 // 底分
+ PlayerCount int32 // 玩家数量
+ BankerId int32 // 庄家id
+ PlayerData []GanDengYanPlayer // 玩家信息
+ ClubRate int32 // 俱乐部抽水比例
+}
+
+type GanDengYanHandCards struct {
+ Cards []int64
+ Index int32
+}
+
+type GanDengYanPlayer struct {
+ UserId int32 // 玩家ID
+ UserIcon int32 // 玩家头像
+ ChangeCoin int64 // 玩家得分
+ OutCards []*GanDengYanHandCards // 出的牌
+ SurplusCards []int32 // 剩下的牌
+ IsWin int64 // 输赢
+ IsRob bool // 是否是机器人
+ IsFirst bool
+ Tax int64 // 税,不一定有值,只是作为一个临时变量使用
+ ClubPump int64 // 俱乐部额外抽水
+ StartCoin int64 // 开始的金币数量
+ Platform string `json:"-"`
+ Channel string `json:"-"`
+ Promoter string `json:"-"`
+ PackageTag string `json:"-"`
+ InviterId int32 `json:"-"`
+ WBLevel int32 //黑白名单等级
+ NumMagic int32 // 癞子或2倍数
+ NumCards int32 // 剩余牌数
+ NumHand int32 // 手牌倍数
+ Num int32 // 总倍数
+}
+
+// 红包扫雷
+type RedPackGameType struct {
+ BankerId int32 //庄家id
+ BankerBet int32 //庄家下注金额
+ BankerWin int32 //庄家赢取
+ BankerRest int32 //庄家退回钱数
+ Rate int32 //倍数
+ BombNum int32 //雷号
+ HitBombCnt int32 //中雷的人数
+ PlayerData []*RedPackPerson //玩家属性
+}
+type RedPackPerson struct {
+ UserId int32 //用户Id
+ IsFirst bool
+ BeforeCoin int64 //抢包前金额
+ AfterCoin int64 //抢包后金额
+ ChangeCoin int64 //金额变化
+ GrabCoin int32 //抢到红包的数量
+ IsHit bool //是否中雷
+ IsRob bool //是否是机器人
+}
+
+// 鱼场
+type BulletLevelTimes struct {
+ Level int32 //等级
+ Times int32 //次数
+}
+type FishCoinNum struct {
+ ID int32 // 鱼id
+ Num int32 //打死数量
+ Power int32 //子弹价值
+ Coin int32 //金币(总金币)
+ HitNum int32 //击中次数
+}
+
+type FishPlayerData struct {
+ UserId int32 //玩家ID
+ UserIcon int32 //玩家头像
+ TotalIn int64 //玩家该阶段总投入
+ TotalOut int64 //玩家该阶段总产出
+ CurrCoin int64 //记录时玩家当前金币量
+}
+
+type FishDetiel struct {
+ BulletInfo *[]BulletLevelTimes //子弹统计
+ HitInfo *[]FishCoinNum //
+ PlayData *FishPlayerData //统计
+}
+
+// 拉霸
+// 绝地求生记录详情
+type IslandSurvivalGameType struct {
+ //all
+ RoomId int32 //房间Id
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ BetCoin int64 //下注金额
+ WinCoin int64 //下注赢取金额
+ IsFirst bool
+ Smallgamewinscore int64 //吃鸡游戏赢取的分数
+ ChangeCoin int64 //本局游戏金额总变化
+ FreeTimes int32 //免费转动次数
+ AllWinNum int32 //中奖的线数
+ LeftEnemy int32 //剩余敌人,需保存
+ Killed int32 //总击杀敌人,需保存
+ HitPoolIdx int //下注索引
+ Cards []int //15张牌
+}
+
+const (
+ WaterMarginSmallGame_Unop int32 = iota
+ WaterMarginSmallGame_AddOp
+ WaterMarginSmallGame_SubOp
+)
+
+// 水浒传小游戏数据
+type WaterMarginSmallGameInfo struct {
+ AddOrSub int32 //加减操作 0:表示未操作 1:加 2:减
+ Score int64 //本局小游戏参与的分数
+ Multiple int32 //倍数 0:表示本局输了 >1:表示猜对小游戏
+ Dice1 int32 //骰子1的点数
+ Dice2 int32 //骰子2的点数
+}
+
+type WaterMarginType struct {
+ RoomId int32 //房间Id
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ IsFirst bool //是否一次游戏
+ ChangeCoin int64 //金额变化
+ Score int32 //总押注数
+ AllWinNum int32 //中奖的线数
+ FreeTimes int32 //免费转动次数
+ WinScore int32 //中奖的倍率
+ AllLine int32 //线路数
+ JackpotNowCoin int64 //爆奖金额
+ Cards []int //15张牌
+ HitPoolIdx int //压分的索引
+ JackpotHitFlag int //如果jackpotnowcoin>0;该值标识中了哪些奖池,二进制字段 1:小奖(第0位) 2:中奖(第1位) 4:大奖(第2位)
+ TrigFree bool //是否触发免费转动
+ SMGame []*WaterMarginSmallGameInfo //小游戏数据
+}
+
+type FootBallHeroesType struct {
+ RoomId int32 //房间Id
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ IsFirst bool
+ Score int32 //总押注数
+ FreeTimes int32 //免费转动次数
+ WinScore int32 //中奖的倍率
+ Cards []int //15张牌
+ CoinReward []int64 //礼物奖励
+ Smallgamescore int64 //小游戏分数
+ Smallgamewinscore int64 //小游戏赢取的分数
+ SmallgameList []int32 //小游戏记录
+ HitPoolIdx int //当前命中的奖池
+}
+type FruitMachineType struct {
+ RoomId int32 //房间Id
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ IsFirst bool
+ ChangeCoin int64 //金额变化
+ Score int32 //总押注数
+ AllWinNum int32 //中奖的线数
+ FreeTimes int32 //免费转动次数
+ WinScore int32 //中奖的倍率
+ AllLine int32 //线路数
+ JackpotNowCoin int64 //爆奖金额
+ Cards []int //15张牌
+ Smallgamescore int64 //小游戏分数
+ HitPoolIdx int //当前命中的奖池
+}
+type GoddessType struct {
+ RoomId int32 //房间Id
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ Score int32 //总押注数
+ IsFirst bool
+ Smallgamescore int64 //小游戏分数
+ Smallgamewinscore int64 //小游戏赢取的分数
+ SmallgameCard int32 //小游戏卡牌
+ CardsGoddess [3]int32 //3张牌
+ BetMultiple int32 //押倍倍数
+ SmallgameList []int32 //小游戏记录
+ HitPoolIdx int //当前命中的奖池
+}
+type RollLineType struct {
+ RoomId int32 //房间Id
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ Score int32 //总押注数
+ IsFirst bool
+ AllWinNum int32 //中奖的线数
+ FreeTimes int32 //免费转动次数
+ WinScore int32 //中奖的倍率
+ AllLine int32 //线路数
+ RoseCount int32 //玫瑰数量
+ Cards []int //15张牌
+ CoinReward []int64 //礼物奖励
+ HitPoolIdx int //当前命中的奖池
+}
+type IceAgeType struct {
+ RoomId int32 //房间Id
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ Score int32 //总押注数
+ Tax int64 //暗税
+ IsFirst bool
+ AllWinNum int32 //中奖的线数
+ FreeTimes int32 //免费转动次数
+ WinScore int32 //中奖的倍率
+ AllLine int32 //线路数
+ Cards [][]int32 // 消除前后的牌(消除前15张,消除后15张...)
+ BetLines []int64 //下注的线
+ UserName string // 昵称
+ TotalPriceValue int64 // 总赢分
+ IsFree bool // 是否免费
+ TotalBonusValue int64 // 总bonus数
+ WBLevel int32 //黑白名单等级
+ WinLines [][]int // 赢分的线
+ WinJackpot int64 // 赢奖池分数
+ WinBonus int64 // 赢小游戏分数
+}
+type TamQuocType struct {
+ RoomId int32 //房间Id
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ Score int32 //总押注数
+ Tax int64 //暗税
+ IsFirst bool
+ AllWinNum int32 //中奖的线数
+ FreeTimes int32 //免费转动次数
+ WinScore int32 //中奖的倍率
+ AllLine int32 //线路数
+ Cards []int32 //15张牌
+ BetLines []int64 //下注的线
+ WBLevel int32 //黑白名单等级
+ UserName string // 昵称
+ TotalPriceValue int64 // 总赢分
+ IsFree bool // 是否免费
+ TotalBonusValue int64 // 总bonus数
+ WinLines []int // 赢分的线
+ WinJackpot int64 // 赢奖池分数
+ WinBonus int64 // 赢小游戏分数
+}
+type CaiShenType struct {
+ RoomId int32 //房间Id
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ Score int32 //总押注数
+ Tax int64 //暗税
+ IsFirst bool
+ AllWinNum int32 //中奖的线数
+ FreeTimes int32 //免费转动次数
+ WinScore int32 //中奖的倍率
+ AllLine int32 //线路数
+ Cards []int32 //15张牌
+ WBLevel int32 //黑白名单等级
+ BetLines []int64 //下注的线
+ UserName string // 昵称
+ TotalPriceValue int64 // 总赢分
+ IsFree bool // 是否免费
+ TotalBonusValue int64 // 总bonus数
+ WinLines []int // 赢分的线
+ WinJackpot int64 // 赢奖池分数
+ WinBonus int64 // 赢小游戏分数
+}
+type AvengersType struct {
+ RoomId int32 //房间Id
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ Score int32 //总押注数
+ Tax int64 //暗税
+ IsFirst bool
+ AllWinNum int32 //中奖的线数
+ FreeTimes int32 //免费转动次数
+ WinScore int32 //中奖的倍率
+ AllLine int32 //线路数
+ Cards []int32 //15张牌
+ WBLevel int32 //黑白名单等级
+ BetLines []int64 //下注的线
+ UserName string // 昵称
+ TotalPriceValue int64 // 总赢分
+ IsFree bool // 是否免费
+ TotalBonusValue int64 // 总bonus数
+ WinLines []int // 赢分的线
+ WinJackpot int64 // 赢奖池分数
+ WinBonus int64 // 赢小游戏分数
+}
+type EasterIslandType struct {
+ RoomId int32 //房间Id
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ Score int32 //总押注数
+ Tax int64 //暗税
+ IsFirst bool
+ AllWinNum int32 //中奖的线数
+ FreeTimes int32 //免费转动次数
+ WinScore int32 //中奖的倍率
+ AllLine int32 //线路数
+ Cards []int32 //15张牌
+ WBLevel int32 //黑白名单等级
+ BetLines []int64 //下注的线
+ UserName string // 昵称
+ TotalPriceValue int64 // 总赢分
+ IsFree bool // 是否免费
+ TotalBonusValue int64 // 总bonus数
+ WinLines []int // 赢分的线
+ WinJackpot int64 // 赢奖池分数
+ WinBonus int64 // 赢小游戏分数
+}
+type RollTeamType struct {
+ RoomId int32 //房间Id
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ Score int32 //总押注数
+ AllWinNum int32 //中奖的线数
+ IsFirst bool
+ FreeTimes int32 //免费转动次数
+ WinScore int32 //中奖的倍率
+ AllLine int32 //线路数
+ PokerBaseCoin int32 //扑克游戏获得的金币
+ PokerRate int32 //游戏的翻倍值
+ GameCount int32 //游戏次数
+ HitPoolIdx int //当前命中的奖池
+ Cards []int //15张牌
+}
+type RollPerson struct {
+ UserId int32 //用户Id
+ UserBetTotal int64 //用户下注
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ IsFirst bool
+ IsRob bool //是否是机器人
+ WBLevel int32 //黑白名单等级
+}
+type RollHundredType struct {
+ RegionId int32 //边池ID -1.庄家 0.大众 1.雷克萨斯 2.宝马 3.奔驰 4.保时捷 5.玛莎拉蒂 6.兰博基尼 7.法拉利
+ IsWin int //边池输赢 1.赢 0.平 -1.输
+ Rate int //倍数
+ SType int32 //特殊牌型 临时使用
+ RollPerson []RollPerson
+}
+
+// 梭哈
+type FiveCardType struct {
+ RoomId int32 //房间ID
+ RoomRounds int32 //建房局数
+ RoomType int32 //房间类型
+ PlayerCount int //玩家数量
+ BaseScore int32 //底分
+ PlayerData []FiveCardPerson //玩家信息
+ ClubRate int32 //俱乐部抽水比例
+}
+
+// 梭哈
+type FiveCardPerson struct {
+ UserId int32 //玩家ID
+ UserIcon int32 //玩家头像
+ ChangeCoin int64 //玩家得分
+ Cardinfo []int32 //牌值
+ IsWin int32 //输赢
+ Tax int64 //税,不一定有值,只是作为一个临时变量使用
+ ClubPump int64 //俱乐部额外抽水
+ IsRob bool //是否是机器人
+ IsFirst bool //是否第一次
+ IsLeave bool //中途离开
+ Platform string `json:"-"`
+ Channel string `json:"-"`
+ Promoter string `json:"-"`
+ PackageTag string `json:"-"`
+ InviterId int32 `json:"-"`
+ BetTotal int64 //用户当局总下注
+ IsAllIn bool //是否全下
+ WBLevel int32 //黑白名单等级
+}
+
+// 骰子
+type RollPointPerson struct {
+ UserId int32 //用户Id
+ UserBetTotal int64 //用户下注
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ IsFirst bool
+ IsRob bool //是否是机器人
+ WBLevel int32 //黑白名单等级
+ BetCoin []int32 //押注金币
+}
+type RollPointType struct {
+ RoomId int32
+ Point []int32
+ Score []int32
+ BetCoin int64
+ WinCoin int64
+ Person []RollPointPerson
+}
+
+// 轮盘
+type RouletteType struct {
+ BankerInfo RouletteBanker //庄家信息
+ Person []RoulettePerson //下注玩家列表
+ RouletteRegion []RouletteRegion //下区域
+}
+type RouletteBanker struct {
+ Point int //当局开的点数
+ TotalBetCoin int64 //总下注金额
+ TotalWinCoin int64 //总输赢金额
+}
+type RouletteRegion struct {
+ Id int //0-156 下注位置编号
+ IsWin int //是否中奖
+ BetCoin int64 //当前区域总下注金额
+ WinCoin int64 //当前区域总输赢金额
+ Player []RoulettePlayer //当前区域下注玩家列表
+}
+type RoulettePlayer struct {
+ UserId int32 //用户Id
+ BetCoin int64 //当局下注额
+}
+type RoulettePerson struct {
+ UserId int32 //用户Id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ UserBetTotal int64 //用户总下注
+ UserWinCoin int64 //用户输赢
+ IsRob bool //是否是机器人
+ WBLevel int32 //黑白名单等级
+ BetCoin map[int]int64 //下注区域对应金额
+}
+
+// 九线拉王
+type NineLineKingType struct {
+ RoomId int32 //房间Id
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ IsFirst bool
+ ChangeCoin int64 //金额变化
+ Score int32 //总押注数
+ AllWinNum int32 //中奖的线数
+ FreeTimes int32 //免费转动次数
+ WinScore int32 //中奖的倍率
+ AllLine int32 //线路数
+ JackpotNowCoin int64 //爆奖金额
+ Cards []int //15张牌
+ HitPoolIdx int //当前命中的奖池
+ CommPool int64 //公共奖池
+ PersonPool int64 //私人奖池
+}
+
+// 飞禽走兽
+type RollAnimalsType struct {
+ BetTotal int64 //总下注
+ WinCoin int64 //用户输赢
+ WinFlag []int64 //中奖元素
+ RollLog []RollHundredType //每个区域下注信息
+}
+
+// 血战
+type BloodMahjongType struct {
+ RoomId int32 //房间ID
+ RoomRounds int32 //建房局数
+ RoomType int32 //房间类型
+ NowRound int32 //当前局数
+ BankId int32 //庄家ID
+ PlayerCount int32 //玩家数量
+ BaseScore int32 //底分
+ PlayerData []BloodMahjongPerson //玩家信息
+}
+
+// 碰杠牌
+type BloodMahjongCardsLog struct {
+ Card int64 //牌
+ Pos []int //0.东 1.南 2.西 3.北
+ Flag int //1.碰 2.明杠 3.暗杠 4.补杠
+}
+
+// 分数类型
+type BloodMahjongScoreTiles struct {
+ LogType int //0.胡 1.刮风 2.下雨 3.退税 4.查花猪 5.查大叫 6.被抢杠 补杠退钱 7.呼叫转移
+ OtherPos []int //源自哪个位置的玩家
+ Coin int64 //理论进账
+ ActualCoin int64 //实际进账
+ Rate int32 //倍率
+ Params []int64 //【Honor】//胡牌类型
+}
+type BloodMahjongPerson struct {
+ UserId int32 //玩家ID
+ UserIcon int32 //玩家头像
+ ChangeCoin int64 //玩家得分
+ Pos int32 //玩家位置 0.东 1.南 2.西 3.北
+ IsLeave bool //是否离场
+ Bankruptcy bool //是否破产
+ HuNumber int32 //第几胡 1 2 3
+ LackColor int64 //定缺花色
+ Hands []int64 //手牌
+ HuCards []int64 //胡牌
+ CardsLog []BloodMahjongCardsLog //碰杠牌
+ ScoreTiles []BloodMahjongScoreTiles //分数
+ IsWin int32 //输赢
+ Tax int64 //税,不一定有值,只是作为一个临时变量使用
+ StartCoin int64 //开始金币
+ ClubPump int64 //俱乐部额外抽水
+ IsRob bool //是否是机器人
+ Platform string `json:"-"`
+ Channel string `json:"-"`
+ Promoter string `json:"-"`
+ PackageTag string `json:"-"`
+ InviterId int32 `json:"-"`
+ WBLevel int32 //黑白名单等级
+}
+
+type PCProp struct {
+ Id uint32
+ TypeName string // 金币类型
+ AreaType int32 // 所在区域 0平台上 1有效区 2无效区 3小车内
+ CoinVal int64 // 金币面值
+ X float32
+ Y float32
+ Z float32
+ RotX float32
+ RotY float32
+ RotZ float32
+}
+
+// 推币机
+type PushingCoinRecord struct {
+ RoomId int // 房间id
+ RoomType int // 房间类型
+ GameMode int // 游戏模式
+ BaseScore int64 // 底分
+ ShakeTimes int32 // 震动次数
+ WallUpTimes int32 // 升墙次数
+ EventTimes []int32 // 事件次数
+ Props []*PCProp // 所有金币
+}
+
+type HuntingRecord struct {
+ RoomId int // 房间ID
+ BaseScore int // 底分
+ SnId int32 // 玩家ID
+ StartCoin int64 // 下注前金额
+ Coin int64 // 下注后金额
+ ChangeCoin int64 // 金币变化
+ CoinPool int64 // 爆奖金额
+ Point int64 // 单线点数
+ LineNum int64 // 线数
+ BetCoin int64 // 下注金额
+ WinCoin int64 // 产出金额
+ Level int // 当前关卡
+ Gain int64 // 翻牌奖励
+}
+
+// 对战三公
+type SanGongPVPType struct {
+ RoomId int32 //房间ID
+ RoomRounds int32 //建房局数
+ RoomType int32 //房间类型
+ NowRound int32 //当前局数
+ BankId int32 //庄家ID
+ PlayerCount int //玩家数量
+ BaseScore int32 //底分
+ PlayerData []SanGongPVPPerson //玩家信息
+ ClubRate int32 //俱乐部抽水比例
+ IsSmartOperation bool //是否启用智能化运营
+}
+type SanGongPVPPerson struct {
+ UserId int32 //玩家ID
+ UserIcon int32 //玩家头像
+ ChangeCoin int64 //玩家得分
+ Cardinfo []int32 //牌值
+ IsWin int32 //输赢
+ Tax int64 //税,不一定有值,只是作为一个临时变量使用
+ ClubPump int64 //俱乐部额外抽水
+ IsRob bool //是否是机器人
+ Flag int //标识
+ IsFirst bool
+ StartCoin int64 //开始金币
+ Platform string `json:"-"`
+ Channel string `json:"-"`
+ Promoter string `json:"-"`
+ PackageTag string `json:"-"`
+ InviterId int32 `json:"-"`
+ WBLevel int32 //黑白名单等级
+}
+
+// 德州牛仔
+type DZNZCardInfo struct {
+ CardId int32 //手牌ID 牛仔 公共牌 公牛
+ CardsInfo []int32 //扑克牌值
+ CardsKind int32 //牌类型
+}
+type DZNZZoneInfo struct {
+ RegionId int32 //13个下注区域
+ IsWin int //边池输赢
+ Rate float32 //倍数
+ PlayerData []HundredPerson //玩家属性
+ IsSmartOperation bool //是否启用智能化运营
+}
+type DZNZHundredInfo struct {
+ CardData []DZNZCardInfo //发牌信息
+ ZoneInfo []DZNZZoneInfo //每个下注区域信息
+}
+
+// 财运之神
+type FortuneZhiShenType struct {
+ //基本信息
+ RoomId int //房间Id
+ BasicScore int32 //单注
+ PlayerSnId int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ TotalBetCoin int64 //总押注
+ TotalLine int32 //总线数(固定)
+ TotalWinCoin int64 //总派彩
+ NowGameState int //当前游戏模式(0,1,2,3)普通/免费/停留旋转/停留旋转2
+ NowNRound int //第几轮
+ IsOffline int //0,1 正常(不显示)/掉线(显示)
+ FirstFreeTimes int //免费游戏剩余次数
+ SecondFreeTimes int //停留旋转游戏剩余次数
+ //中奖统计
+ HitPrizePool []int64 //命中奖池(小奖|中奖|大奖|巨奖)
+ WinLineNum int //中奖线个数
+ WinLineRate int64 //中奖线总倍率
+ WinLineCoin int64 //中奖线派彩
+ GemstoneNum int //宝石数量
+ GemstoneWinCoin int64 //宝石派彩
+ //详情
+ Cards []int32 //元素顺序 横向
+ GemstoneRateCoin []int64 //宝石金额 横向
+ //中奖线详情
+ WinLine []FortuneZhiShenWinLine
+ WBLevel int32 //黑白名单等级
+ TaxCoin int64 //税收
+}
+type FortuneZhiShenWinLine struct {
+ Id int //线号
+ EleValue int32 //元素值
+ Num int //数量
+ Rate int64 //倍率
+ WinCoin int64 //单线派彩
+ WinFreeGame int //(0,1,2)旋转并停留*3/免费游戏*6/免费游戏*3
+}
+
+// 金鼓齐鸣记录详情
+type GoldDrumWinLineInfo struct {
+ EleValue int //元素值
+ Rate int64 //倍率
+ WinCoin int64 //单线派彩
+ GameType int //(0,1,2)无/免费游戏/聚宝盆游戏
+}
+type GoldDrumGameType struct {
+ //all
+ RoomId int32 //房间Id
+ BasicScore int32 //单注分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ BetCoin int64 //下注金额
+ WinCoin int64 //下注赢取金额总金额
+ //Smallgamewinscore int64 //小游戏赢取的分数
+ ChangeCoin int64 //本局游戏金额总变化
+ FreeTimes int32 //免费转动次数
+ AllWinNum int32 //中奖的线数
+ HitPoolIdx int //下注索引
+ Cards []int //15张牌
+
+ NowGameState int //当前游戏模式(0,1)普通/免费
+ HitPrizePool []int64 //命中奖池(多喜小奖|多寿中奖|多禄大奖|多福巨奖)
+ WinLineRate int64 //中奖线总倍率
+ WinLineCoin int64 //中奖线派彩
+ WinLineInfo []GoldDrumWinLineInfo //中奖线详情
+
+ NowFreeGameTime int32 //当前免费游戏第几次
+ CornucopiaCards []int32 //聚宝盆游戏数据 -1 未开启 0小将 1中奖 2大奖 3巨奖
+ IsOffline bool //玩家是否掉线 true 掉线
+ WBLevel int32 //黑白名单等级
+ TaxCoin int64 //税收
+}
+
+// 金福报喜记录详情
+type CopperInfo struct {
+ Pos int32 //铜钱元素索引,从0开始
+ Coin int64 //铜钱奖励金币
+}
+type GoldBlessWinLineInfo struct {
+ EleValue int //元素值
+ Rate int64 //倍率
+ WinCoin int64 //单线派彩
+ GameType int //(0,1,2,3)无/免费游戏/聚宝盆游戏/招福纳财游戏
+}
+type GoldBlessGameType struct {
+ //all
+ RoomId int32 //房间Id
+ BasicScore int32 //单注分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ BetCoin int64 //下注金额
+ WinCoin int64 //本局总赢取金额
+ //Smallgamewinscore int64 //小游戏赢取的分数
+ ChangeCoin int64 //本局游戏金额总变化
+ FreeTimes int32 //免费转动次数
+ AllWinNum int32 //中奖的线数
+ HitPoolIdx int //下注索引
+ Cards []int //15张牌
+
+ NowGameState int //当前游戏模式(0,1,2)普通/免费/招福纳财
+ HitPrizePool []int64 //命中奖池(多喜小奖|多寿中奖|多禄大奖|多福巨奖)
+ WinLineRate int64 //中奖线总倍率
+ WinLineCoin int64 //中奖线派彩
+ WinLineInfo []GoldBlessWinLineInfo //中奖线详情
+ CopperNum int32 //本局铜钱数量
+ CopperCoin int64 //本局铜钱金额
+ CoppersInfo []CopperInfo //铜钱结构
+
+ NowFreeGameTime int32 //当前免费游戏第几次
+ CornucopiaCards []int32 //聚宝盆游戏数据 -1 未开启 0小将 1中奖 2大奖 3巨奖
+ IsOffline bool //玩家是否掉线 true 掉线
+ WBLevel int32 //黑白名单等级
+ TaxCoin int64 //税收
+}
+
+// 发发发
+type Classic888Type struct {
+ //基本信息
+ RoomId int //房间Id
+ BasicScore int32 //单注
+ PlayerSnId int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ TotalBetCoin int64 //总押注
+ TotalLine int32 //总线数(固定)
+ TotalWinCoin int64 //总派彩
+ NowGameState int //当前游戏模式(0,1,2)普通/免费/停留旋转
+ NowNRound int //第几轮
+ IsOffline int //0,1 正常(不显示)/掉线(显示)
+ FirstFreeTimes int //免费游戏剩余次数
+ SecondFreeTimes int //停留旋转游戏剩余次数
+ //中奖统计
+ HitPrizePool []int64 //命中奖池(小奖|中奖|大奖|巨奖)
+ WinLineNum int //中奖线个数
+ WinLineRate int64 //中奖线总倍率
+ WinLineCoin int64 //中奖线派彩
+ LanternNum int //灯笼数量
+ LanternWinCoin int64 //灯笼派彩
+ //详情
+ Cards []int32 //元素顺序 横向
+ LanternRateCoin []int64 //灯笼金额 横向
+ //中奖线详情
+ WinLine []Classic888WinLine
+ WBLevel int32 //黑白名单等级
+ TaxCoin int64 //税收
+}
+type Classic888WinLine struct {
+ Id int //线号
+ EleValue int32 //元素值
+ Num int //数量
+ Rate int64 //倍率
+ WinCoin int64 //单线派彩
+ WinFreeGame int //(0,1,2,3)旋转并停留*3/免费游戏*6/免费游戏*9/免费游戏*15
+}
+
+// 多福
+type RichBlessedType struct {
+ //基本信息
+ RoomId int //房间Id
+ BasicScore int32 //单注
+ PlayerSnId int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ TotalBetCoin int64 //总押注
+ TotalLine int32 //总线数(固定)
+ TotalWinCoin int64 //总派彩
+ NowGameState int //当前游戏模式(0,1,2)普通/免费/jack小游戏
+ NowNRound int //第几轮
+ IsOffline int //0,1 正常(不显示)/掉线(显示)
+ FirstFreeTimes int //免费游戏剩余次数
+ //中奖统计
+ WinLineNum int //中奖线个数
+ WinLineRate int64 //中奖线总倍率
+ WinLineCoin int64 //中奖线派彩
+ //详情
+ Cards []int32 //普通游戏/免费游戏
+ //jack游戏
+ JackEleValue int32 //元素值
+ JackMidCards []int64 //掀开位置
+ //中奖线详情
+ WinLine []RichBlessedWinLine
+ WBLevel int32 //黑白名单等级
+ TaxCoin int64 //税收
+ RealCtrl bool //人工调控
+ WBState int32 //调控等级
+ WeightKey int32 //当前使用权重
+}
+type RichBlessedWinLine struct {
+ Id int //线号
+ EleValue int32 //元素值
+ Num int //数量
+ Rate int64 //倍率
+ WinCoin int64 //单线派彩
+}
+
+// 经典777
+type FruitsType struct {
+ //基本信息
+ RoomId int //房间Id
+ BasicScore int32 //单注
+ PlayerSnId int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ TotalBetCoin int64 //总押注
+ TotalLine int32 //总线数(固定)
+ TotalWinCoin int64 //总派彩
+ NowGameState int //当前游戏模式(0,1,2)普通/免费/小玛丽
+ NowNRound int //第几轮
+ IsOffline int //0,1 正常(不显示)/掉线(显示)
+ FirstFreeTimes int //免费游戏剩余次数
+ MaryFreeTimes int //停留旋转游戏剩余次数
+ //中奖统计
+ HitPrizePool int64 //命中奖池金额
+ WinLineNum int //中奖线个数
+ WinLineRate int64 //中奖线总倍率
+ WinLineCoin int64 //中奖线派彩
+ JackPotNum int //777数量
+ JackPotWinCoin int64 //777派彩
+ //详情
+ Cards []int32 //普通游戏/免费游戏
+ //玛丽游戏
+ MaryOutSide int32 //外圈
+ MaryMidCards []int32 //内圈
+ //中奖线详情
+ WinLine []FruitsWinLine
+ WBLevel int32 //黑白名单等级
+ TaxCoin int64 //税收
+ RealCtrl bool //人工调控
+ WBState int32 //调控等级
+ WeightKey int32 //当前使用权重
+}
+type FruitsWinLine struct {
+ Id int //线号
+ EleValue int32 //元素值
+ Num int //数量
+ Rate int64 //倍率
+ WinCoin int64 //单线派彩
+ WinFreeGame int //(0,1,2,3,4,5)小玛丽1/小玛丽2/小玛丽3/免费5/免费8/免费10
+}
+
+// 无尽宝藏记录详情
+type EndlessTreasureWinLineInfo struct {
+ EleValue int //元素值
+ Rate int64 //倍率
+ WinCoin int64 //单线派彩
+ GameType int //(0,1,2,3)无/免费游戏/聚宝盆游戏/节节高游戏
+}
+type EndlessTreasureGameType struct {
+ //all
+ RoomId int32 //房间Id
+ TotalBetScore int32 //总押注
+ BasicScore float32 //单注分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ BetCoin int64 //下注金额
+ WinCoin int64 //本局总赢取金额
+ ChangeCoin int64 //本局游戏金额总变化
+ FreeTimes int32 //免费转动次数
+ AllWinNum int32 //中奖的线数
+ Cards []int //15张牌
+
+ NowGameState int //当前游戏模式(0,1,2)普通/免费/节节高
+ HitPrizePool []int64 //命中奖池(小奖|中奖|大奖|巨奖)
+ WinLineRate int64 //中奖线总倍率
+ WinLineCoin int64 //中奖线派彩
+ WinLineInfo []EndlessTreasureWinLineInfo //中奖线详情
+ CopperNum int32 //本局铜钱数量
+ CopperCoin int64 //本局铜钱金额
+ CoppersInfo []CopperInfo //铜钱结构
+
+ NowFreeGameTime int32 //当前免费游戏第几次
+ IsOffline bool //玩家是否掉线 true 掉线
+ AddFreeTimes int32 //本局新增免费转动次数
+ WBLevel int32 //黑白名单等级
+ TaxCoin int64 //税收
+}
+
+// 拉霸类游戏 基础牌局记录
+type SlotBaseResultType struct {
+ RoomId int32 //房间Id
+ BasicBet int32 //基本分(单注金额)
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ IsFirst bool //是否第一次玩游戏
+ IsFree bool //是否免费
+ TotalBet int32 //总押注金额
+ WinRate int32 //中奖的倍率
+ FreeTimes int32 //免费转动次数
+ AllWinNum int32 //中奖的总线数
+ Tax int64 //暗税
+ WBLevel int32 //黑白名单等级
+ SingleFlag int32 //0不控1单控赢2单控输
+ WinLineScore int64 //中奖线赢取分数
+ WinJackpot int64 //奖池赢取分数
+ WinSmallGame int64 //小游戏赢取分数
+ WinTotal int64 //本次游戏总赢取(本次游戏赢取总金额=中奖线赢取+奖池赢取+小游戏赢取)
+ Cards []int32 //15张牌
+ IsFoolPlayer bool //是否是新手
+}
+
+// 小火箭游戏 每局记录
+type SmallRocketBaseResultType struct {
+ //all
+ RoomId int32 //房间Id
+ TotalBetVal int64 //总押注
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ WinCoin int64 //本局总赢取金额
+
+ BetCoin1 int64 //下注金额1
+ BetMul1 float64 //下注倍数1
+ IsAutoBetAndTake1 bool //自动领取1
+ TakeBetMul1 float64 //提款倍数1
+ BetCoin2 int64 //下注金额2
+ BetMul2 float64 //下注倍数2
+ IsAutoBetAndTake2 bool //自动领取2
+ TakeBetMul2 float64 //提款倍数2
+
+ TaxCoin int64 //税收
+}
+
+type GameResultLog struct {
+ BaseResult *SlotBaseResultType
+ AllLine int32 //线路数
+ UserName string //昵称
+ WinLines []int //赢分的线
+ BetLines []int64 //下注的线
+}
+
+// 幸运骰子
+type LuckyDiceType struct {
+ RoomId int32 //房间Id
+ RoundId int32 //局数编号
+ BaseScore int32 //底分
+ PlayerSnid int32 //玩家id
+ UserName string //昵称
+ BeforeCoin int64 //本局前金额
+ AfterCoin int64 //本局后金额
+ ChangeCoin int64 //金额变化
+ Bet int64 //总押注数
+ Refund int64 //返还押注数
+ Award int64 //获奖金额
+ BetSide int32 //压大压小 0大 1小
+ Dices []int32 //3个骰子值
+ Tax int64 //赢家税收
+ WBLevel int32 //黑白名单等级
+}
+
+type CandyType struct {
+ RoomId int32 //房间Id
+ SpinID int32 //局数编号
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ Score int32 //总押注数
+ Tax int64 //暗税
+ IsFirst bool
+ AllWinNum int32 //中奖的线数
+ WinScore int32 //中奖的倍率
+ AllLine int32 //线路数
+ Cards []int32 //9张牌
+ BetLines []int64 //下注的线
+ WBLevel int32 //黑白名单等级
+ UserName string // 昵称
+ TotalPriceValue int64 // 总赢分
+ WinLines []int // 赢分的线
+ WinJackpot int64 // 赢奖池分数
+}
+type MiniPokerType struct {
+ RoomId int32 //房间Id
+ SpinID int32 //局数编号
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ Score int32 //总押注数
+ Tax int64 //暗税
+ IsFirst bool
+ WinScore int32 //中奖的倍率
+ Cards []int32 //5张牌
+ WBLevel int32 //黑白名单等级
+ UserName string // 昵称
+ TotalPriceValue int64 // 总赢分
+ WinJackpot int64 // 赢奖池分数
+}
+type CaoThapType struct {
+ RoomId int32 //房间Id
+ BasicScore int32 //基本分
+ PlayerSnid int32 //玩家id
+ BeforeCoin int64 //下注前金额
+ AfterCoin int64 //下注后金额
+ ChangeCoin int64 //金额变化
+ Score int32 //总押注数
+ Tax int64 //暗税
+ IsFirst bool
+ Cards []int32 //翻的牌
+ WBLevel int32 //黑白名单等级
+ UserName string // 昵称
+ TotalPriceValue int64 // 总赢分
+ WinJackpot int64 // 赢奖池分数
+ BetInfo []CaoThapBetInfo // 每次下注信息
+}
+type CaoThapBetInfo struct {
+ TurnID int32 // 操作ID
+ TurnTime int64 // 操作时间
+ BetValue int64 // 下注金额
+ Card int32 // 牌值
+ PrizeValue int64 // 赢分
+}
+
+// 21点
+type BlackJackType struct {
+ RoomId int32 //房间ID
+ RoomType int32 //房间类型
+ NumOfGames int //当前局数
+ PlayerCount int //玩家数量
+ PlayerData []*BlackJackPlayer //玩家信息
+ BankerCards []int32 //庄家牌
+ BankerCardType int32 //牌型 1:黑杰克 2:五小龙 3:其它点数 4:爆牌
+ BankerCardPoint []int32 //点数
+ BetCoin int64 //总下注
+ GainCoinTax int64 //总输赢分(税前)
+}
+
+type BlackJackCardInfo struct {
+ Cards []int32 //闲家牌
+ CardType int32 //牌型 1:黑杰克 2:五小龙 3:其它点数 4:爆牌
+ CardPoint []int32 //点数
+ BetCoin int64 //下注
+ GainCoinNoTax int64 //总输赢分(税后)
+ IsWin int32 //输赢 1赢 0平 -1输
+}
+
+type BlackJackPlayer struct {
+ UserId int32 //玩家ID
+ UserIcon int32 //玩家头像
+ Platform string `json:"-"`
+ Channel string `json:"-"`
+ Promoter string `json:"-"`
+ PackageTag string `json:"-"`
+ InviterId int32 `json:"-"`
+ WBLevel int32 //黑白名单等级
+ IsRob bool //是否是机器人
+ Flag int //标识
+ IsFirst bool //是否第一次
+ Hands []BlackJackCardInfo //牌值
+ IsWin int32 //输赢
+ GainCoinNoTax int64 //总输赢分(税后)
+ Tax int64 //税,不一定有值,只是作为一个临时变量使用
+ BaoCoin int64 //保险金
+ BaoChange int64 //保险金输赢分
+ BetCoin int64 //下注额
+ BetChange int64 //下注输赢分
+ Seat int //座位号
+}
+
+type DezhouPots struct {
+ BetTotal int64 //边池下注
+ Player []DezhouPotPlayer //边池的玩家
+}
+type DezhouPotPlayer struct {
+ Snid int32 //玩家ID
+ IsWin int32 //边池输赢
+}
+
+// 德州牌局记录
+type DeZhouUserOp struct {
+ Snid int32 // 操作人
+ Op int32 // 操作类型 见 dezhoupoker.proto
+ Stage int // 所处牌局阶段 见 constants.go
+ Chip int64 // 操作筹码, (不下注为0)
+ ChipOnTable int64 // 操作后桌子上筹码
+ Round int32 // 轮数
+ Sec float64 // 操作时距离本局开始时的秒数
+ TargetId int32 // 操作对象ID(没其他玩家为对象为0)
+}
+
+// 德州
+type DezhouType struct {
+ RoomId int32 //房间ID
+ RoomType int32 //房间类型
+ NumOfGames int32 //当前局数
+ BankId int32 //庄家ID
+ PlayerCount int //玩家数量
+ BaseScore int32 //底分
+ BaseCards []int32 //公牌 只限于德州用
+ PlayerData []DezhouPerson //玩家信息
+ Pots []DezhouPots //边池情况
+ Actions string //牌局记录
+ UserOps []*DeZhouUserOp //牌局记录new
+}
+type DezhouPerson struct {
+ UserId int32 //玩家ID
+ UserIcon int32 //玩家头像
+ Platform string `json:"-"`
+ Channel string `json:"-"`
+ Promoter string `json:"-"`
+ PackageTag string `json:"-"`
+ InviterId int32 `json:"-"`
+ WBLevel int32 //黑白名单等级
+ IsRob bool //是否是机器人
+ IsFirst bool //是否第一次
+ IsLeave bool //中途离开
+ InitCard []int32 //初始牌值
+ Cardinfo []int32 //牌值
+ IsWin int32 //输赢
+ GainCoinNoTax int64 //总输赢分(税后)
+ Tax int64 //税,不一定有值,只是作为一个临时变量使用
+ BetTotal int64 //用户当局总下注
+ IsAllIn bool //是否全下
+ RoundFold int32 //第几轮弃牌
+ CardInfoEnd []int32 //结算时的牌型
+ Seat int //座位号
+}
+
+// tienlen
+type TienLenType struct {
+ GameId int //游戏id
+ BaseScore int32 //底分
+ PlayerData []TienLenPerson //玩家信息
+}
+
+type TienLenPerson struct {
+ UserId int32 //玩家ID
+ IsRob bool //是否是机器人
+ BillCoin int64 //最终得分(税后)
+ BillTaxCoin int64 //最终税收
+
+ BombCoin int64 //炸弹输赢分(税后)
+ BombTaxCoin int64 //炸弹税收
+ CardInfoEnd []int32 //结算时的牌型
+}
+
+// chesstitians
+type ChesstitiansType struct {
+ GameId int //游戏id
+ RoomId int32 //房间ID
+ RoomType int32 //房间类型
+ NumOfGames int32 //当前局数
+ BankId int32 //房主ID
+ PlayerCount int //玩家数量
+ BaseScore int32 //底分
+ TaxRate int32 //税率(万分比)
+ PlayerData []*ChesstitiansPerson //玩家信息
+ RoomMode int
+}
+type ChesstitiansPerson struct {
+ UserId int32 //玩家ID
+ UserIcon int32 //玩家头像
+ Platform string `json:"-"`
+ Channel string `json:"-"`
+ Promoter string `json:"-"`
+ PackageTag string `json:"-"`
+ InviterId int32 `json:"-"`
+ WBLevel int32 //黑白名单等级
+ IsRob bool //是否是机器人
+ IsFirst bool //是否第一次
+ IsLeave bool //中途离开
+ IsWin int32 //输赢
+ Seat int //座位号
+ GainCoin int64 //手牌输赢分(税后)
+ GainTaxCoin int64 //手牌税收
+}
+
+// tala
+type TaLaType struct {
+ GameId int //游戏id
+ RoomId int32 //房间ID
+ RoomType int32 //房间类型
+ NumOfGames int32 //当前局数
+ BankId int32 //房主ID
+ PlayerCount int //玩家数量
+ BaseScore int32 //底分
+ TaxRate int32 //税率(万分比)
+ RoomMode int
+ PlayerData []TaLaPerson //玩家信息
+}
+type TaLaPerson struct {
+ UserId int32 //玩家ID
+ UserIcon int32 //玩家头像
+ Platform string `json:"-"`
+ Channel string `json:"-"`
+ Promoter string `json:"-"`
+ PackageTag string `json:"-"`
+ InviterId int32 `json:"-"`
+ WBLevel int32 //黑白名单等级
+ IsRob bool //是否是机器人
+ IsFirst bool //是否第一次
+ IsLeave bool //中途离开
+ Seat int //座位号
+ ChiCoin int64 //吃输赢分(税后)
+ BillCoin int64 //最终得分(税后)
+ ChiTaxCoin int64 //吃税收
+ BillTaxCoin int64 //最终税收
+ IsHu bool //胡
+ IsWin int32 //胜负 0平 1胜 2负
+ IsLoseHu bool //包赔
+ Phoms [][]int32 //phom
+ IsNoPhom bool //瘪
+ Cards []int32 //手牌
+ CardsValue int32 //点数
+ OpPhom []int32 //寄
+ TaLaPersonOp []TaLaPersonOp //操作信息
+}
+type TaLaPersonOp struct {
+ Round int32
+ MoCard int32
+ ChuCard int32
+ ChiCard int32
+ Cards []int32
+}
+
+// samloc
+type SamLocType struct {
+ GameId int //游戏id
+ RoomId int32 //房间ID
+ RoomType int32 //房间类型
+ NumOfGames int32 //当前局数
+ BankId int32 //房主ID
+ PlayerCount int //玩家数量
+ BaseScore int32 //底分
+ TaxRate int32 //税率(万分比)
+ PlayerData []SamLocPerson //玩家信息
+ RoomMode int
+}
+type SamLocPerson struct {
+ UserId int32 //玩家ID
+ UserIcon int32 //玩家头像
+ Platform string `json:"-"`
+ Channel string `json:"-"`
+ Promoter string `json:"-"`
+ PackageTag string `json:"-"`
+ InviterId int32 `json:"-"`
+ WBLevel int32 //黑白名单等级
+ IsRob bool //是否是机器人
+ IsFirst bool //是否第一次
+ IsLeave bool //中途离开
+ IsWin int32 //输赢
+ Seat int //座位号
+ GainCoin int64 //手牌输赢分(税后)
+ BombCoin int64 //炸弹输赢分(税后)
+ BillCoin int64 //最终得分(税后)
+ GainTaxCoin int64 //手牌税收
+ BombTaxCoin int64 //炸弹税收
+ BillTaxCoin int64 //最终税收
+ DelOrderCards map[int][]int32 //已出牌
+ CardInfoEnd []int32 //结算时的牌型
+ IsTianHu bool //是否天胡
+}
+
+// 娃娃机 每局记录
+type ClawdollResultType struct {
+ //all
+ RoomId int32 //房间Id
+ MachineId int32 //娃娃机Id
+ PlayerSnid int32 //玩家id
+ BeforeClawdollItemNum int64 //变化前娃娃币
+ AfterClawdollItemNum int64 //变化后娃娃币
+ IsWin bool //是否成功
+ Channel string //渠道
+ Name string //场次名字
+}
diff --git a/statistics/task/task/gamerate.go b/statistics/task/task/gamerate.go
new file mode 100644
index 0000000..1fbf2de
--- /dev/null
+++ b/statistics/task/task/gamerate.go
@@ -0,0 +1,110 @@
+package task
+
+import (
+ "encoding/json"
+ "go.mongodb.org/mongo-driver/bson"
+ "mongo.games.com/goserver/core/logger"
+ "slices"
+)
+
+// 场次平均倍数
+
+func PlayerGameRate(plt string, startTime, endTime string, gamefreeid int) (total, bombTotal, remain2Total int, rateAvg, bombRateAvg float64, err error) {
+ var totalRate, totalBombRate float64
+ err = GameDetailFunc(plt, startTime, endTime, gamefreeid, func(data bson.M) error {
+ rate, isBomb, bombRate, remain2 := GameDetailRate(data)
+ total++
+ if isBomb {
+ bombTotal++
+ }
+ totalRate += rate
+ totalBombRate += bombRate
+ if remain2 {
+ remain2Total++
+ }
+ return nil
+ })
+ if total > 0 {
+ rateAvg = totalRate / float64(total)
+ }
+ if bombTotal > 0 {
+ bombRateAvg = totalBombRate / float64(bombTotal)
+ }
+ return
+}
+
+// rate 赢分/底分
+// isBomb 是否有炸弹
+// bombRate 炸弹倍数,炸弹赢分/底分
+// remain2 是否有剩余2
+func GameDetailRate(data bson.M) (rate float64, isBomb bool, bombRate float64, remain2 bool) {
+ if data == nil {
+ return
+ }
+ gameid := data["gameid"].(int32)
+ gamefreeid := data["gamefreeid"].(int32)
+ logger.Logger.Tracef("GameDetail gameid:%d, gamefreeid:%d", gameid, gamefreeid)
+
+ detail := data["gamedetailednote"]
+ if detail == nil {
+ return
+ }
+
+ raw := new(RabbitMQDataRaw)
+ if err := json.Unmarshal([]byte(detail.(string)), raw); err != nil {
+ logger.Logger.Errorf("GameDetailCount Unmarshal 1 error:%v %v", err, gameid)
+ return
+ }
+
+ switch gameid {
+ case 207, 208, 209, 210, 240, 241, 242, 243, 244, 245, 246, 247: // tienlen
+ d := new(TienLenType)
+ b, err := json.Marshal(raw.Data)
+ if err != nil {
+ logger.Logger.Errorf("GameDetailCount Marshal error:%v %v", err, gameid)
+ return
+ }
+ if err := json.Unmarshal(b, d); err != nil {
+ logger.Logger.Errorf("GameDetailCount Unmarshal 2 error:%v %v", err, gameid)
+ return
+ }
+
+ for _, v := range d.PlayerData {
+ if v.BillCoin > 0 {
+ rate = float64(v.BillCoin+v.BillTaxCoin) / float64(d.BaseScore)
+ }
+ if v.BombCoin > 0 {
+ isBomb = true
+ bombRate = float64(v.BombCoin+v.BombTaxCoin) / float64(d.BaseScore)
+ }
+ if slices.ContainsFunc(v.CardInfoEnd, func(i int32) bool {
+ switch i {
+ case 51, 38, 25, 12:
+ return true
+ }
+ return false
+ }) {
+ remain2 = true
+ }
+ }
+
+ case 211, 212, 213, 214:
+ d := new(ThirteenWaterType)
+ b, err := json.Marshal(raw.Data)
+ if err != nil {
+ logger.Logger.Errorf("GameDetailCount Marshal error:%v %v", err, gameid)
+ return
+ }
+ if err := json.Unmarshal(b, d); err != nil {
+ logger.Logger.Errorf("GameDetailCount Unmarshal 2 error:%v %v", err, gameid)
+ return
+ }
+
+ for _, v := range d.PlayerData {
+ if v.AllScore > 0 {
+ rate = float64(v.AllScore) / float64(d.BaseScore)
+ }
+ }
+ }
+ return
+}
diff --git a/statistics/task/task/gametime.go b/statistics/task/task/gametime.go
new file mode 100644
index 0000000..3c184c7
--- /dev/null
+++ b/statistics/task/task/gametime.go
@@ -0,0 +1,104 @@
+package task
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "go.mongodb.org/mongo-driver/bson"
+ "go.mongodb.org/mongo-driver/mongo"
+ "go.mongodb.org/mongo-driver/mongo/options"
+ "mongo.games.com/game/common"
+ mongomodel "mongo.games.com/game/statistics/modelmongo"
+ "mongo.games.com/goserver/core/logger"
+ mymongo "mongo.games.com/goserver/core/mongox"
+)
+
+// 新用户平均游戏时长
+
+// 返回 总游戏时长,总局数,错误
+func NewPlayerGameTime(plt string, ids []int, startTime, endTime string, gamefreeid int) (int, int, error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ c, err := mymongo.GetLogCollection(plt, mongomodel.LogGamePlayerListLog)
+ if err != nil {
+ return 0, 0, err
+ }
+
+ c2, err := mymongo.GetLogCollection(plt, "log_gamedetailed")
+ if err != nil {
+ return 0, 0, err
+ }
+
+ var ret int
+ var total int
+ for _, v := range ids {
+ // 查询玩家游戏时长
+ where := bson.M{"snid": v, "time": bson.M{"$gte": s, "$lt": e}}
+ if gamefreeid > 0 {
+ where["gamefreeid"] = gamefreeid
+ }
+ cur, err := c.Find(context.TODO(), where, options.Find().SetProjection(bson.M{"gamedetailedlogid": 1}))
+ if err != nil {
+ logger.Logger.Errorf("find player gamedetailedlogid get err: %v", err)
+ return 0, 0, err
+ }
+ for cur.TryNext(context.TODO()) {
+ var vv struct{ Gamedetailedlogid string }
+ if err = cur.Decode(&vv); err != nil {
+ logger.Logger.Errorf("find player gamedetailedlogid decode err: %v", err)
+ cur.Close(context.Background())
+ return 0, 0, err
+ }
+ // 查询游戏时长
+ var res2 struct{ Gametiming int }
+ r := c2.FindOne(context.TODO(), bson.M{"logid": vv.Gamedetailedlogid}, options.FindOne().SetProjection(bson.M{"gametiming": 1}))
+ if r.Err() != nil && !errors.Is(r.Err(), mongo.ErrNoDocuments) {
+ logger.Logger.Errorf("find game time get err: %v", err)
+ cur.Close(context.Background())
+ return 0, 0, err
+ }
+ if err := r.Decode(&res2); err != nil {
+ logger.Logger.Errorf("find game time decode err: %v", err)
+ cur.Close(context.Background())
+ return 0, 0, err
+ }
+ ret += res2.Gametiming
+ total++
+ }
+ cur.Close(context.Background())
+ }
+
+ return ret, total, nil
+}
+
+// NewPlayerGameTimeAvg 新用户平均游戏时长
+// 新用户平均游戏时长(不算大厅时间):当天注册的玩家在房间中的总时长/当天注册总人数
+// 返回 参与人数,游戏时长
+func NewPlayerGameTimeAvg(plt string, startTime, endTime string, gamefreeid int) (int, int, error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ if s.IsZero() || e.IsZero() {
+ return 0, 0, fmt.Errorf("time format error")
+ }
+ ids, err := GetNewPayerIds(plt, startTime, endTime)
+ if err != nil {
+ return 0, 0, err
+ }
+ if len(ids) == 0 {
+ return 0, 0, nil
+ }
+ a, _, err := NewPlayerGameTime(plt, ids, startTime, endTime, gamefreeid)
+ if err != nil {
+ return 0, 0, err
+ }
+ if len(ids) == 0 {
+ return 0, 0, nil
+ }
+
+ b, err := PlayingGameCount(plt, ids, startTime, endTime, gamefreeid)
+ if err != nil {
+ return 0, 0, err
+ }
+ if b == 0 {
+ return 0, 0, nil
+ }
+ return b, a, err
+}
diff --git a/statistics/task/task/rechargeoffline.go b/statistics/task/task/rechargeoffline.go
new file mode 100644
index 0000000..fe31c2d
--- /dev/null
+++ b/statistics/task/task/rechargeoffline.go
@@ -0,0 +1,62 @@
+package task
+
+import (
+ "context"
+ "fmt"
+ "go.mongodb.org/mongo-driver/mongo/options"
+
+ "go.mongodb.org/mongo-driver/bson"
+ "mongo.games.com/game/common"
+ mymongo "mongo.games.com/goserver/core/mongox"
+)
+
+type RechargeOfflineData struct {
+ Snid int32
+ Coin int64
+}
+
+func OfflineCoin(plt string, startTime, endTime string) (res []*RechargeOfflineData, err error) {
+ s, e := common.StrRFC3339TimeToTime(startTime), common.StrRFC3339TimeToTime(endTime)
+ if s.IsZero() || e.IsZero() {
+ return nil, fmt.Errorf("time format error")
+ }
+ c, err := mymongo.GetLogCollection(plt, "log_dbshop")
+ if err != nil {
+ return nil, err
+ }
+ cur, err := c.Find(context.TODO(), bson.M{"ts": bson.M{"$gte": s.Unix(), "$lt": e.Unix()}, "consume": 3, "state": 1})
+ if err != nil {
+ return nil, err
+ }
+ defer cur.Close(context.Background())
+
+ var list []struct{ Snid int32 }
+ if err = cur.All(context.Background(), &list); err != nil {
+ return nil, err
+ }
+
+ snids := map[int32]struct{}{}
+
+ for _, v := range list {
+ if _, ok := snids[v.Snid]; ok {
+ continue
+ }
+ snids[v.Snid] = struct{}{}
+
+ // 最后金币数量
+ c, err := mymongo.GetLogCollection(plt, "log_coinex")
+ if err != nil {
+ return nil, err
+ }
+ one := c.FindOne(context.TODO(), bson.M{"snid": v.Snid, "cointype": 0, "ts": bson.M{"$lt": e.Unix()}}, options.FindOne().SetSort(bson.M{"ts": -1}))
+ if one.Err() != nil {
+ return nil, one.Err()
+ }
+ var data struct{ Restcount int64 }
+ if err = one.Decode(&data); err != nil {
+ return nil, err
+ }
+ res = append(res, &RechargeOfflineData{Snid: v.Snid, Coin: data.Restcount})
+ }
+ return
+}
diff --git a/statistics/task/task/robotwin.go b/statistics/task/task/robotwin.go
new file mode 100644
index 0000000..37fa8cf
--- /dev/null
+++ b/statistics/task/task/robotwin.go
@@ -0,0 +1,138 @@
+package task
+
+import (
+ "encoding/json"
+ "go.mongodb.org/mongo-driver/bson"
+ "mongo.games.com/goserver/core/logger"
+ "sort"
+)
+
+// RobotWinRate 机器人胜利
+// 返回 玩家胜利局数,总局数
+func RobotWinRate(plt string, startTime, endTime string, gamefreeid int) (a, b int, err error) {
+ err = GameDetailFunc(plt, startTime, endTime, gamefreeid, func(data bson.M) error {
+ isPlayerWin, to := GameDetailRobot(data)
+ if isPlayerWin {
+ a++
+ }
+ b += to
+ return nil
+ })
+ return
+}
+
+func GameDetailRobot(data bson.M) (isPlayerWin bool, to int) {
+ if data == nil {
+ return
+ }
+ gameid := data["gameid"].(int32)
+ gamefreeid := data["gamefreeid"].(int32)
+ logger.Logger.Tracef("GameDetailRobot gameid:%d, gamefreeid:%d", gameid, gamefreeid)
+
+ detail := data["gamedetailednote"]
+ if detail == nil {
+ return
+ }
+
+ raw := new(RabbitMQDataRaw)
+ if err := json.Unmarshal([]byte(detail.(string)), raw); err != nil {
+ logger.Logger.Errorf("GameDetailRobot Unmarshal 1 error:%v %v", err, gameid)
+ return
+ }
+
+ switch gameid {
+ case 207, 208, 209, 210, 240, 241, 242, 243, 244, 245, 246, 247: // tienlen
+ data := new(TienLenType)
+ b, err := json.Marshal(raw.Data)
+ if err != nil {
+ logger.Logger.Errorf("GameDetailCount Marshal error:%v %v", err, gameid)
+ return
+ }
+ if err := json.Unmarshal(b, data); err != nil {
+ logger.Logger.Errorf("GameDetailCount Unmarshal 2 error:%v %v", err, gameid)
+ return
+ }
+
+ var has, hasPlayer bool
+ for _, v := range data.PlayerData {
+ if v.IsRob {
+ has = true
+ }
+ if !v.IsRob {
+ hasPlayer = true
+ }
+ }
+ if !has || !hasPlayer {
+ // 没有机器人
+ return
+ }
+
+ sort.Slice(data.PlayerData, func(i, j int) bool {
+ a, b := data.PlayerData[i].BillCoin+data.PlayerData[i].BillTaxCoin, data.PlayerData[j].BillCoin+data.PlayerData[j].BillTaxCoin
+ if a != b {
+ return a > b
+ }
+ if data.PlayerData[i].IsRob != data.PlayerData[j].IsRob {
+ return !data.PlayerData[i].IsRob
+ }
+ return data.PlayerData[i].UserId < data.PlayerData[j].UserId
+ })
+
+ for k, v := range data.PlayerData {
+ if !v.IsRob && v.BillCoin > 0 && k <= 1 {
+ isPlayerWin = true
+ break
+ }
+ }
+
+ to = 1
+
+ case 211, 212, 213, 214:
+ data := new(ThirteenWaterType)
+ b, err := json.Marshal(raw.Data)
+ if err != nil {
+ logger.Logger.Errorf("GameDetailCount Marshal error:%v %v", err, gameid)
+ return
+ }
+ if err := json.Unmarshal(b, data); err != nil {
+ logger.Logger.Errorf("GameDetailCount Unmarshal 2 error:%v %v", err, gameid)
+ return
+ }
+
+ var has, hasPlayer bool
+ for _, v := range data.PlayerData {
+ if v.IsRob {
+ has = true
+ }
+ if !v.IsRob {
+ hasPlayer = true
+ }
+ }
+ if !has || !hasPlayer {
+ // 没有机器人
+ return
+ }
+
+ sort.Slice(data.PlayerData, func(i, j int) bool {
+ a, b := data.PlayerData[i].AllScore, data.PlayerData[j].AllScore
+ if a != b {
+ return a > b
+ }
+ if data.PlayerData[i].IsRob != data.PlayerData[j].IsRob {
+ return !data.PlayerData[i].IsRob
+ }
+ return data.PlayerData[i].UserId < data.PlayerData[j].UserId
+ })
+
+ for k, v := range data.PlayerData {
+ if !v.IsRob && v.AllScore > 0 && k <= 1 {
+ isPlayerWin = true
+ break
+ }
+ }
+
+ to = 1
+ }
+
+ return
+}
From 3f35e67a404ee631621c1c9c62a150a573a8759b Mon Sep 17 00:00:00 2001
From: sk <123456@qq.com>
Date: Tue, 3 Dec 2024 15:08:08 +0800
Subject: [PATCH 2/3] =?UTF-8?q?=E5=88=9B=E5=BB=BA=E6=B8=B8=E6=88=8F?=
=?UTF-8?q?=E5=AF=B9=E5=B1=80=E8=AE=B0=E5=BD=95=E7=BC=93=E5=AD=98=E5=AF=B9?=
=?UTF-8?q?=E8=B1=A1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
gamesrv/base/divisionsystem.go | 6 -
gamesrv/base/gamedetail.go | 255 ++++++++++++++++++++++++
gamesrv/base/gamewarning.go | 42 ----
gamesrv/base/handredJacklistmgr.go | 300 -----------------------------
gamesrv/base/player.go | 17 +-
gamesrv/base/scene.go | 157 ---------------
6 files changed, 270 insertions(+), 507 deletions(-)
delete mode 100644 gamesrv/base/divisionsystem.go
create mode 100644 gamesrv/base/gamedetail.go
delete mode 100644 gamesrv/base/handredJacklistmgr.go
diff --git a/gamesrv/base/divisionsystem.go b/gamesrv/base/divisionsystem.go
deleted file mode 100644
index f63a62c..0000000
--- a/gamesrv/base/divisionsystem.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package base
-
-// 提供税收和流水,根据代理需求后台进行分账
-func ProfitDistribution(p *Player, tax, taxex, validFlow int64) {
- //LogChannelSingleton.WriteMQData(model.GenerateTaxDivide(p.SnId, p.Platform, p.Channel, p.BeUnderAgentCode, p.PackageID, tax, taxex, validFlow, p.scene.GameId, p.scene.GameMode, p.scene.GetDBGameFree().GetId(), p.PromoterTree))
-}
diff --git a/gamesrv/base/gamedetail.go b/gamesrv/base/gamedetail.go
new file mode 100644
index 0000000..9b43209
--- /dev/null
+++ b/gamesrv/base/gamedetail.go
@@ -0,0 +1,255 @@
+package base
+
+import (
+ "time"
+
+ "github.com/globalsign/mgo/bson"
+
+ "mongo.games.com/game/model"
+ "mongo.games.com/game/mq"
+)
+
+/*
+ 记录游戏对局记录
+*/
+
+type SaveGameDetailedParam struct {
+ LogId string // 日志id
+ Detail string // 游戏详细信息
+ GameTime int64 // 游戏时长
+ Trend20Lately string // 最近20局开奖结果
+ CtrlType int // 调控类型 1控赢 2控输
+ PlayerPool map[int]int // 个人水池分
+ OnlyLog bool // 只返回日志,不保存
+}
+
+// SaveGameDetailedLog 保存游戏详细记录
+func (this *Scene) SaveGameDetailedLog(param *SaveGameDetailedParam) *SaveGameDetailedCopy {
+ if this == nil || param == nil {
+ return nil
+ }
+
+ if param.GameTime <= 0 {
+ param.GameTime = int64(time.Now().Sub(this.GameNowTime).Seconds())
+ }
+
+ if param.GameTime < 0 {
+ param.GameTime = 0
+ }
+
+ now := time.Now()
+
+ var ret SaveGameDetailedCopy
+ ret.Param = param
+ f := func(plt string) {
+ log := &model.GameDetailedLog{
+ Id: bson.NewObjectId(),
+ LogId: param.LogId,
+ GameId: this.GameId,
+ Platform: plt,
+ MatchId: this.GetMatch().GetMatchSortId(),
+ SceneId: this.SceneId,
+ GameMode: this.GameMode,
+ GameFreeid: this.GetGameFreeId(),
+ PlayerCount: int32(len(this.Players)),
+ GameTiming: int32(param.GameTime),
+ GameBaseBet: this.GetBaseScore(),
+ GameDetailedNote: param.Detail,
+ GameDetailVer: GameDetailedVer[int(this.GameId)],
+ CpCtx: this.CpCtx,
+ Time: now,
+ Trend20Lately: param.Trend20Lately,
+ Ts: now.Unix(),
+ CtrlType: param.CtrlType,
+ PlayerPool: make(map[int]int),
+ CycleId: this.CycleID,
+ }
+ for k, v := range param.PlayerPool {
+ log.PlayerPool[k] = v
+ }
+ if param.OnlyLog {
+ ret.Log = append(ret.Log, log)
+ } else {
+ mq.Write(log)
+ }
+ }
+
+ switch {
+ case this.IsCoinScene():
+ mapPlatform := make(map[string]bool)
+ for _, v := range this.Players {
+ if v == nil {
+ continue
+ }
+ if _, ok := mapPlatform[v.Platform]; ok {
+ continue
+ }
+ mapPlatform[v.Platform] = true
+ f(v.Platform)
+ }
+ default:
+ f(this.Platform)
+ }
+ return &ret
+}
+
+type SaveGamePlayerListLogParam struct {
+ LogId string // 详情日志id
+ Platform string // 平台
+ Snid int32 // 玩家id
+ PlayerName string // 玩家名字
+ Channel string // 渠道
+ ChannelId string // 推广渠道
+ TotalIn int64 // 总投入
+ TotalOut int64 // 总产出(税前)
+ TaxCoin int64 // 总税收
+ BetAmount int64 // 下注量
+ WinAmountNoAnyTax int64 // 税后赢取额(净利润,正负值)
+ IsFirstGame bool // 是否第一次游戏
+ IsFree bool // 拉霸专用 是否免费
+ WinSmallGame int64 // 拉霸专用 小游戏奖励
+ WinTotal int64 // 拉霸专用 本局输赢
+ GameTime int64 // 游戏时长
+ OnlyLog bool // 只返回日志,不保存
+}
+
+// SaveGamePlayerListLog 保存玩家对局记录
+func (this *Scene) SaveGamePlayerListLog(param *SaveGamePlayerListLogParam) *SaveGamePlayerListLogCopy {
+ if this == nil {
+ return nil
+ }
+ if param == nil {
+ return nil
+ }
+
+ p := this.GetPlayer(param.Snid)
+ if p == nil {
+ return nil
+ }
+
+ if param.PlayerName == "" {
+ param.PlayerName = p.Name
+ }
+
+ var ret SaveGamePlayerListLogCopy
+ ret.Param = param
+
+ baseScore := this.GetBaseScore()
+
+ // 上报玩家游戏记录
+ if !p.IsRob && (param.IsFree || param.TotalIn != 0 || param.TotalOut != 0) {
+ e := p.ReportGameEvent(&ReportGameEventParam{
+ Tax: param.TaxCoin,
+ Change: param.WinAmountNoAnyTax,
+ In: param.TotalIn,
+ Out: param.TotalOut,
+ GameTime: param.GameTime,
+ OnlyLog: param.OnlyLog,
+ })
+ if e != nil {
+ ret.UpLog = e
+ }
+ }
+
+ // 保存玩家游戏日志
+ now := time.Now()
+ log := &model.GamePlayerListLog{
+ LogId: bson.NewObjectId(),
+ SnId: p.SnId,
+ Name: param.PlayerName,
+ GameId: this.GameId,
+ BaseScore: baseScore,
+ TaxCoin: param.TaxCoin,
+ Platform: param.Platform,
+ Channel: param.Channel,
+ SceneId: this.SceneId,
+ GameMode: this.GameMode,
+ GameFreeid: this.GetGameFreeId(),
+ GameDetailedLogId: param.LogId,
+ IsFirstGame: param.IsFirstGame,
+ BetAmount: param.BetAmount,
+ WinAmountNoAnyTax: param.WinAmountNoAnyTax,
+ TotalIn: param.TotalIn,
+ TotalOut: param.TotalOut,
+ Time: now,
+ RoomType: this.SceneMode,
+ GameDif: this.GetDBGameFree().GetGameDif(),
+ GameClass: this.GetDBGameFree().GetGameClass(),
+ MatchId: this.GetMatch().GetMatchSortId(),
+ MatchType: int64(this.GetMatch().GetMatchType()),
+ Ts: now.Unix(),
+ IsFree: param.IsFree,
+ WinSmallGame: param.WinSmallGame,
+ WinTotal: param.WinTotal,
+ CycleId: this.CycleID,
+ }
+ if param.OnlyLog {
+ ret.Log = append(ret.Log, log)
+ } else {
+ mq.Write(log)
+ }
+ return &ret
+}
+
+// SaveGamePlayerListLogCopy 临时记录
+// 为了拉霸统计游戏时长,需要临时缓存游戏记录
+type SaveGamePlayerListLogCopy struct {
+ Param *SaveGamePlayerListLogParam
+ Log []*model.GamePlayerListLog
+ UpLog *ReportGameEventOnly // mq上报数据
+}
+
+func (s *SaveGamePlayerListLogCopy) Save() {
+ for _, v := range s.Log {
+ mq.Write(v)
+ }
+ if s.UpLog != nil {
+ for _, v := range s.UpLog.Log {
+ mq.Write(v, mq.BackGameRecord)
+ }
+ }
+}
+
+// SaveGameDetailedCopy 临时记录
+// 为了拉霸统计游戏时长,需要临时缓存游戏记录
+type SaveGameDetailedCopy struct {
+ Param *SaveGameDetailedParam
+ Log []*model.GameDetailedLog
+}
+
+func (s *SaveGameDetailedCopy) Save() {
+ for _, v := range s.Log {
+ mq.Write(v)
+ }
+}
+
+// LabaLog 拉霸缓存游戏记录
+type LabaLog struct {
+ PlayerListLog *SaveGamePlayerListLogCopy
+ GameDetailLog *SaveGameDetailedCopy
+}
+
+// Cache 临时缓存
+func (l *LabaLog) Cache(s *Scene, detailLog *SaveGameDetailedParam, playerListLog *SaveGamePlayerListLogParam) {
+ if s == nil {
+ return
+ }
+ detailLog.OnlyLog = true
+ playerListLog.OnlyLog = true
+ l.GameDetailLog = s.SaveGameDetailedLog(detailLog)
+ l.PlayerListLog = s.SaveGamePlayerListLog(playerListLog)
+}
+
+// Save 保存
+func (l *LabaLog) Save(f func(log *LabaLog)) {
+ f(l)
+ l.PlayerListLog.Save()
+ l.GameDetailLog.Save()
+ l.Clear()
+}
+
+// Clear 清空
+func (l *LabaLog) Clear() {
+ l.PlayerListLog = nil
+ l.GameDetailLog = nil
+}
diff --git a/gamesrv/base/gamewarning.go b/gamesrv/base/gamewarning.go
index 4a0ddbe..a4e0021 100644
--- a/gamesrv/base/gamewarning.go
+++ b/gamesrv/base/gamewarning.go
@@ -68,27 +68,6 @@ func NewGameWarning(param string) {
}), nil, "NewGameWarning").Start()
}
-// func WarningLoseCoin(gameFreeId int32, snid int32, loseCoin int64) {
-// if model.GameParamData.WarningLoseLimit == 0 {
-// return
-// }
-// if loseCoin < model.GameParamData.WarningLoseLimit {
-// return
-// }
-// NewGameWarning(fmt.Sprintf(`{"WarningType":%v,"WarningGame":%v,"WarningSnid":%v,"LoseCoin":%v}`,
-// Warning_LoseCoinLimit, gameFreeId, snid, loseCoin))
-//
-
-//func WarningBetCoinCheck(sceneId, gameFreeId int32, snid int32, betCoin int64) {
-// if model.GameParamData.WarningBetMax == 0 {
-// return
-// }
-// if betCoin > model.GameParamData.WarningBetMax {
-// NewGameWarning(fmt.Sprintf(`{"WarningType":%v,"WarningSnid":%v,"WarningGame":%v,"WarningScene":%v}`,
-// Warning_BetCoinMax, snid, gameFreeId, sceneId))
-// }
-//}
-
func WarningCoinPool(warnType int, gameFreeId int32) {
NewGameWarning(fmt.Sprintf(`{"WarningType":%v,"WarningGame":%v}`,
warnType, gameFreeId))
@@ -97,24 +76,3 @@ func WarningBlackPlayer(snid, gameFreeId int32) {
NewGameWarning(fmt.Sprintf(`{"WarningType":%v,"WarningSnid":%v,"WarningGame":%v}`,
Warning_BlackPlayer, snid, gameFreeId))
}
-
-//func WarningWinnerRate(snid int32, winCoin, loseCoin int64) {
-// if model.GameParamData.WarningWinRate == 0 {
-// return
-// }
-// if (winCoin+1)/(loseCoin+1) < model.GameParamData.WarningWinRate {
-// return
-// }
-// NewGameWarning(fmt.Sprintf(`{"WarningType":%v,"WarningSnid":%v,"WarningRate":%v,"WinCoin":%v,"LoseCoin":%v}`,
-// Warning_WinRate, snid, (winCoin+1)/(loseCoin+1), winCoin, loseCoin))
-//}
-//func WarningWinnerCoin(snid int32, winCoin, loseCoin int64) {
-// if model.GameParamData.WarningWinMoney == 0 {
-// return
-// }
-// if (winCoin - loseCoin) < model.GameParamData.WarningWinMoney {
-// return
-// }
-// NewGameWarning(fmt.Sprintf(`{"WarningType":%v,"WarningSnid":%v,"WarningCoin":%v,"WinCoin":%v,"LoseCoin":%v}`,
-// Warning_WinCoin, snid, (winCoin - loseCoin), winCoin, loseCoin))
-//}
diff --git a/gamesrv/base/handredJacklistmgr.go b/gamesrv/base/handredJacklistmgr.go
deleted file mode 100644
index e444859..0000000
--- a/gamesrv/base/handredJacklistmgr.go
+++ /dev/null
@@ -1,300 +0,0 @@
-package base
-
-import (
- "fmt"
- "strconv"
- "strings"
- "time"
-
- "mongo.games.com/game/model"
-
- "github.com/globalsign/mgo/bson"
- "mongo.games.com/goserver/core/basic"
- "mongo.games.com/goserver/core/logger"
- "mongo.games.com/goserver/core/module"
- "mongo.games.com/goserver/core/task"
-)
-
-// HundredJackListManager 排行榜 key: platform+gamefreeId
-type HundredJackListManager struct {
- HundredJackTsList map[string][]*HundredJackInfo
- HundredJackSortList map[string][]*HundredJackInfo
-}
-
-// HundredJackListMgr 实例化
-var HundredJackListMgr = &HundredJackListManager{
- HundredJackTsList: make(map[string][]*HundredJackInfo),
- HundredJackSortList: make(map[string][]*HundredJackInfo),
-}
-
-// HundredJackInfo 数据结构
-type HundredJackInfo struct {
- model.HundredjackpotLog
- linkSnids []int32 //点赞人数
-}
-
-// ModuleName .
-func (hm *HundredJackListManager) ModuleName() string {
- return "HundredJackListManager"
-}
-
-// Init .
-func (hm *HundredJackListManager) Init() {
- //data := model.
-}
-
-// Update .
-func (hm *HundredJackListManager) Update() {
-}
-
-// Shutdown .
-func (hm *HundredJackListManager) Shutdown() {
- module.UnregisteModule(hm)
-}
-
-// ISInitJackInfo 仅初始化一次
-var ISInitJackInfo bool
-
-// InitTsJackInfo 初始化TsJackInfo
-func (hm *HundredJackListManager) InitTsJackInfo(platform string, freeID int32) {
- key := fmt.Sprintf("%v-%v", platform, freeID)
- task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} {
- datas, err := model.GetHundredjackpotLogTsByPlatformAndGameFreeID(platform, freeID)
- if err != nil {
- logger.Logger.Error("HundredJackListManager DelOneJackInfo ", err)
- return nil
- }
- return datas
- }), task.CompleteNotifyWrapper(func(data interface{}, tt task.Task) {
- datas := data.([]model.HundredjackpotLog)
- if data != nil && datas != nil {
- for i := range datas {
- if i == model.HundredjackpotLogMaxLimitPerQuery {
- break
- }
- data := &HundredJackInfo{
- HundredjackpotLog: datas[i],
- }
- strlikeSnids := strings.Split(datas[i].LinkeSnids, "|")
- for _, v := range strlikeSnids {
- if v == "" {
- break
- }
- snid, err := strconv.Atoi(v)
- if err == nil {
- data.linkSnids = append(data.linkSnids, int32(snid))
- }
- }
- hm.HundredJackTsList[key] = append(hm.HundredJackTsList[key], data)
- }
- // logger.Logger.Warnf("InitTsJackInfo data:%v", datas)
- } else {
- hm.HundredJackTsList[key] = []*HundredJackInfo{}
- }
- return
- }), "InitTsJackInfo").Start()
-}
-
-// InitSortJackInfo 初始化SortJackInfo
-func (hm *HundredJackListManager) InitSortJackInfo(platform string, freeID int32) {
-
- key := fmt.Sprintf("%v-%v", platform, freeID)
- task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} {
- datas, err := model.GetHundredjackpotLogCoinByPlatformAndGameFreeID(platform, freeID)
- if err != nil {
- logger.Logger.Error("HundredJackListManager DelOneJackInfo ", err)
- return nil
- }
- return datas
- }), task.CompleteNotifyWrapper(func(data interface{}, tt task.Task) {
- datas := data.([]model.HundredjackpotLog)
- if data != nil && datas != nil {
- for i := range datas {
- if i == model.HundredjackpotLogMaxLimitPerQuery {
- break
- }
- data := &HundredJackInfo{
- HundredjackpotLog: datas[i],
- }
- strlikeSnids := strings.Split(datas[i].LinkeSnids, "|")
- for _, v := range strlikeSnids {
- snid, err := strconv.Atoi(v)
- if err == nil {
- data.linkSnids = append(data.linkSnids, int32(snid))
- }
- }
- hm.HundredJackSortList[key] = append(hm.HundredJackSortList[key], data)
- }
- // logger.Logger.Warnf("InitSortJackInfo data:%v", datas)
- } else {
- hm.HundredJackSortList[key] = []*HundredJackInfo{}
- }
- return
- }), "InitSortJackInfo").Start()
-}
-
-// InitHundredJackListInfo 初始化 HundredJackListInfo
-func (hm *HundredJackListManager) InitHundredJackListInfo(platform string, freeID int32) {
- if ISInitJackInfo {
- return
- }
- key := fmt.Sprintf("%v-%v", platform, freeID)
- if _, exist := hm.HundredJackTsList[key]; !exist {
- hm.InitTsJackInfo(platform, freeID)
- }
- if _, exist := hm.HundredJackSortList[key]; !exist {
- hm.InitSortJackInfo(platform, freeID)
- }
- ISInitJackInfo = true
- return
-}
-
-// GetJackTsInfo 返回TsInfo
-func (hm *HundredJackListManager) GetJackTsInfo(platform string, freeID int32) []*HundredJackInfo {
- key := fmt.Sprintf("%v-%v", platform, freeID)
- if _, exist := hm.HundredJackTsList[key]; !exist { // 玩家进入scene 已经初始化
- hm.InitTsJackInfo(platform, freeID)
- }
- return hm.HundredJackTsList[key]
-}
-
-// GetJackSortInfo 返回SortInfo
-func (hm *HundredJackListManager) GetJackSortInfo(platform string, freeID int32) []*HundredJackInfo {
- key := fmt.Sprintf("%v-%v", platform, freeID)
- if _, exist := hm.HundredJackSortList[key]; !exist {
- hm.InitSortJackInfo(platform, freeID)
- }
- return hm.HundredJackSortList[key]
-}
-
-// Insert 插入
-func (hm *HundredJackListManager) Insert(coin, turncoin int64, snid, roomid, jackType, inGame, vip int32, platform, channel, name string, gamedata []string) {
- key := fmt.Sprintf("%v-%v", platform, roomid)
- log := model.NewHundredjackpotLogEx(snid, coin, turncoin, roomid, jackType, inGame, vip, platform, channel, name, gamedata)
- ///////////////////实际不走这里
- if _, exist := hm.HundredJackTsList[key]; !exist {
- hm.InitTsJackInfo(platform, roomid)
- }
- if _, exist := hm.HundredJackSortList[key]; !exist {
- hm.InitSortJackInfo(platform, roomid)
- }
- /////////////////////
- hm.InsertLog(log)
- data := &HundredJackInfo{
- HundredjackpotLog: *log,
- }
- /*logger.Logger.Trace("HundredJackListManager log 1 ", log.SnID, log.LogID, data.GameData)
- for _, v := range hm.HundredJackTsList[key] {
- logger.Logger.Trace("HundredJackListManager log 2 ", v.SnID, v.LogID, v.GameData)
- }*/
- for i, v := range hm.HundredJackSortList[key] { // 插入
- if v.Coin < log.Coin {
- d1 := append([]*HundredJackInfo{}, hm.HundredJackSortList[key][i:]...)
- hm.HundredJackSortList[key] = append(hm.HundredJackSortList[key][:i], data)
- hm.HundredJackSortList[key] = append(hm.HundredJackSortList[key], d1...)
- goto Exit
- }
- }
- if len(hm.HundredJackSortList[key]) < model.HundredjackpotLogMaxLimitPerQuery {
- hm.HundredJackSortList[key] = append(hm.HundredJackSortList[key], data)
- }
-Exit:
- d1 := append([]*HundredJackInfo{}, hm.HundredJackTsList[key][0:]...)
- hm.HundredJackTsList[key] = append(hm.HundredJackTsList[key][:0], data)
- hm.HundredJackTsList[key] = append(hm.HundredJackTsList[key], d1...)
- var delList []*HundredJackInfo
- if len(hm.HundredJackTsList[key]) > model.HundredjackpotLogMaxLimitPerQuery {
- delList = append(delList, hm.HundredJackTsList[key][model.HundredjackpotLogMaxLimitPerQuery:]...)
- hm.HundredJackTsList[key] = hm.HundredJackTsList[key][:model.HundredjackpotLogMaxLimitPerQuery]
- }
- if len(hm.HundredJackSortList[key]) > model.HundredjackpotLogMaxLimitPerQuery {
- delList = append(delList, hm.HundredJackSortList[key][model.HundredjackpotLogMaxLimitPerQuery:]...)
- hm.HundredJackSortList[key] = hm.HundredJackSortList[key][:model.HundredjackpotLogMaxLimitPerQuery]
- }
- /*for _, v := range hm.HundredJackTsList[key] {
- logger.Logger.Trace("HundredJackListManager log 3 ", v.SnID, v.LogID, v.GameData)
- }*/
- for _, v := range delList {
- if hm.IsCanDel(v, hm.HundredJackTsList[key], hm.HundredJackSortList[key]) { // 两个排行帮都不包含
- logger.Logger.Info("HundredJackListManager DelOneJackInfo ", v.LogID)
- hm.DelOneJackInfo(v.Platform, v.LogID)
- }
- }
-}
-
-// IsCanDel 能否删除
-func (hm *HundredJackListManager) IsCanDel(deldata *HundredJackInfo, tsList, sortList []*HundredJackInfo) bool {
- for _, v := range tsList {
- if v.LogID == deldata.LogID {
- return false
- }
- }
- for _, v := range sortList {
- if v.LogID == deldata.LogID {
- return false
- }
- }
- return true
-}
-
-// InsertLog insert db
-func (hm *HundredJackListManager) InsertLog(log *model.HundredjackpotLog) {
- task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} {
- err := model.InsertHundredjackpotLog(log)
- if err != nil {
- logger.Logger.Error("HundredJackListManager Insert ", err)
- }
- return err
- }), nil, "InsertHundredJack").Start()
-}
-
-// UpdateLikeNum updata likenum
-func (hm *HundredJackListManager) UpdateLikeNum(plt string, gid bson.ObjectId, like int32, likesnids string) {
- task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} {
- err := model.UpdateLikeNum(plt, gid, like, likesnids)
- if err != nil {
- logger.Logger.Error("HundredJackListManager UpdateHundredLikeNum ", err)
- }
- return err
- }), nil, "UpdateHundredLikeNum").Start()
-}
-
-// UpdatePlayBlackNum updata playblacknum
-func (hm *HundredJackListManager) UpdatePlayBlackNum(plt string, gid bson.ObjectId, playblack int32) []string {
- var ret []string
- task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} {
- data, err := model.UpdatePlayBlackNum(plt, gid, playblack)
- if err != nil {
- logger.Logger.Error("HundredJackListManager DelOneJackInfo ", err)
- return nil
- }
- return data
- }), task.CompleteNotifyWrapper(func(data interface{}, tt task.Task) {
- if data != nil {
- ret = data.([]string)
- logger.Logger.Warnf("UpdatePlayBlackNum data:%v", ret)
- }
- return
- }), "UpdatePlayBlackNum").Start()
-
- logger.Logger.Error("HundredJackListManager UpdatePlayBlackNum ", ret)
- if len(ret) == 0 {
- return ret
- }
- return nil
-}
-
-// DelOneJackInfo del
-func (hm *HundredJackListManager) DelOneJackInfo(plt string, gid bson.ObjectId) {
- task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} {
- err := model.RemoveHundredjackpotLogOne(plt, gid)
- if err != nil {
- logger.Logger.Error("HundredJackListManager DelOneJackInfo ", err)
- }
- return err
- }), nil, "DelOneJackInfo").Start()
-}
-
-func init() {
- module.RegisteModule(HundredJackListMgr, time.Hour, 0)
-}
diff --git a/gamesrv/base/player.go b/gamesrv/base/player.go
index 5f47a55..1bcdf6e 100644
--- a/gamesrv/base/player.go
+++ b/gamesrv/base/player.go
@@ -627,9 +627,15 @@ type ReportGameEventParam struct {
Change int64 // 净输赢,正负值,不带税收
In, Out int64 // 投入,产出(税前)
GameTime int64 // 游戏时长,秒
+ OnlyLog bool // 只返回数据,不上报
}
-func (this *Player) ReportGameEvent(param *ReportGameEventParam) {
+type ReportGameEventOnly struct {
+ Param *ReportGameEventParam
+ Log []*model.PlayerGameRecEvent
+}
+
+func (this *Player) ReportGameEvent(param *ReportGameEventParam) *ReportGameEventOnly {
// 记录玩家 首次参与该场次的游戏时间 游戏次数
var gameFirstTime, gameFreeFirstTime time.Time
var gameTimes, gameFreeTimes int64
@@ -659,6 +665,8 @@ func (this *Player) ReportGameEvent(param *ReportGameEventParam) {
param.GameTime = 0
}
+ var ret ReportGameEventOnly
+ ret.Param = param
log := &model.PlayerGameRecEvent{
Platform: this.Platform,
RecordId: this.scene.GetRecordId(),
@@ -685,7 +693,12 @@ func (this *Player) ReportGameEvent(param *ReportGameEventParam) {
LastLoginTime: this.LastLoginTime.Unix(),
DeviceId: this.DeviceId,
}
- mq.Write(log, mq.BackGameRecord)
+ if param.OnlyLog {
+ ret.Log = append(ret.Log, log)
+ } else {
+ mq.Write(log, mq.BackGameRecord)
+ }
+ return &ret
}
// 汇总玩家该次游戏总产生的税收
diff --git a/gamesrv/base/scene.go b/gamesrv/base/scene.go
index 7dfe670..4d6a2e4 100644
--- a/gamesrv/base/scene.go
+++ b/gamesrv/base/scene.go
@@ -2,7 +2,6 @@ package base
import (
"fmt"
- "github.com/globalsign/mgo/bson"
"math"
"math/rand"
"strconv"
@@ -1457,162 +1456,6 @@ func (this *Scene) SaveFriendRecord(snid int32, isWin int32, billCoin int64, bas
}
}
-type SaveGameDetailedParam struct {
- LogId string // 日志id
- Detail string // 游戏详细信息
- GameTime int64 // 游戏时长
- Trend20Lately string // 最近20局开奖结果
- CtrlType int // 调控类型 1控赢 2控输
- PlayerPool map[int]int // 个人水池分
-}
-
-func (this *Scene) SaveGameDetailedLog(param *SaveGameDetailedParam) {
- if this == nil || param == nil {
- return
- }
-
- if param.GameTime <= 0 {
- param.GameTime = int64(time.Now().Sub(this.GameNowTime).Seconds())
- }
-
- if param.GameTime < 0 {
- param.GameTime = 0
- }
-
- now := time.Now()
-
- f := func(plt string) {
- log := &model.GameDetailedLog{
- Id: bson.NewObjectId(),
- LogId: param.LogId,
- GameId: this.GameId,
- Platform: plt,
- MatchId: this.GetMatch().GetMatchSortId(),
- SceneId: this.SceneId,
- GameMode: this.GameMode,
- GameFreeid: this.GetGameFreeId(),
- PlayerCount: int32(len(this.Players)),
- GameTiming: int32(param.GameTime),
- GameBaseBet: this.GetBaseScore(),
- GameDetailedNote: param.Detail,
- GameDetailVer: GameDetailedVer[int(this.GameId)],
- CpCtx: this.CpCtx,
- Time: now,
- Trend20Lately: param.Trend20Lately,
- Ts: now.Unix(),
- CtrlType: param.CtrlType,
- PlayerPool: make(map[int]int),
- CycleId: this.CycleID,
- }
- for k, v := range param.PlayerPool {
- log.PlayerPool[k] = v
- }
- mq.Write(log)
- }
-
- switch {
- case this.IsCoinScene():
- mapPlatform := make(map[string]bool)
- for _, v := range this.Players {
- if v == nil {
- continue
- }
- if _, ok := mapPlatform[v.Platform]; ok {
- continue
- }
- mapPlatform[v.Platform] = true
- f(v.Platform)
- }
- default:
- f(this.Platform)
- }
-}
-
-type SaveGamePlayerListLogParam struct {
- LogId string // 详情日志id
- Platform string // 平台
- Snid int32 // 玩家id
- PlayerName string // 玩家名字
- Channel string // 渠道
- ChannelId string // 推广渠道
- TotalIn int64 // 总投入
- TotalOut int64 // 总产出(税前)
- TaxCoin int64 // 总税收
- BetAmount int64 // 下注量
- WinAmountNoAnyTax int64 // 税后赢取额(净利润,正负值)
- IsFirstGame bool // 是否第一次游戏
- IsFree bool // 拉霸专用 是否免费
- WinSmallGame int64 // 拉霸专用 小游戏奖励
- WinTotal int64 // 拉霸专用 本局输赢
- GameTime int64 // 游戏时长
-}
-
-// SaveGamePlayerListLog 保存玩家对局记录
-func (this *Scene) SaveGamePlayerListLog(param *SaveGamePlayerListLogParam) {
- if this == nil {
- return
- }
- if param == nil {
- return
- }
-
- p := this.GetPlayer(param.Snid)
- if p == nil {
- return
- }
-
- if param.PlayerName == "" {
- param.PlayerName = p.Name
- }
-
- baseScore := this.GetBaseScore()
-
- // 上报玩家游戏记录
- if !p.IsRob && (param.IsFree || param.TotalIn != 0 || param.TotalOut != 0) {
- p.ReportGameEvent(&ReportGameEventParam{
- Tax: param.TaxCoin,
- Change: param.WinAmountNoAnyTax,
- In: param.TotalIn,
- Out: param.TotalOut,
- GameTime: param.GameTime,
- })
- }
-
- // 保存玩家游戏日志
- now := time.Now()
- log := &model.GamePlayerListLog{
- LogId: bson.NewObjectId(),
- SnId: p.SnId,
- Name: param.PlayerName,
- GameId: this.GameId,
- BaseScore: baseScore,
- TaxCoin: param.TaxCoin,
- Platform: param.Platform,
- Channel: param.Channel,
- SceneId: this.SceneId,
- GameMode: this.GameMode,
- GameFreeid: this.GetGameFreeId(),
- GameDetailedLogId: param.LogId,
- IsFirstGame: param.IsFirstGame,
- BetAmount: param.BetAmount,
- WinAmountNoAnyTax: param.WinAmountNoAnyTax,
- TotalIn: param.TotalIn,
- TotalOut: param.TotalOut,
- Time: now,
- RoomType: this.SceneMode,
- GameDif: this.GetDBGameFree().GetGameDif(),
- GameClass: this.GetDBGameFree().GetGameClass(),
- MatchId: this.GetMatch().GetMatchSortId(),
- MatchType: int64(this.GetMatch().GetMatchType()),
- Ts: now.Unix(),
- IsFree: param.IsFree,
- WinSmallGame: param.WinSmallGame,
- WinTotal: param.WinTotal,
- CycleId: this.CycleID,
- }
- mq.Write(log)
-}
-
func (this *Scene) IsPlayerFirst(p *Player) bool {
if p == nil {
return false
From 5a67d8f0732534930ee5d0d83177d6e6d8a8489d Mon Sep 17 00:00:00 2001
From: sk <123456@qq.com>
Date: Wed, 4 Dec 2024 10:06:59 +0800
Subject: [PATCH 3/3] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=8D=81=E4=B8=89?=
=?UTF-8?q?=E5=BC=A0=E8=AE=B0=E5=88=86=E6=98=BE=E7=A4=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
gamerule/thirteen/logic.go | 2 +-
gamesrv/thirteen/scene.go | 3 +++
gamesrv/thirteen/scenepolicy.go | 10 +++++++++-
3 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/gamerule/thirteen/logic.go b/gamerule/thirteen/logic.go
index 9a72eaa..d6f922f 100644
--- a/gamerule/thirteen/logic.go
+++ b/gamerule/thirteen/logic.go
@@ -105,7 +105,7 @@ type Group struct {
Head [3]int
Mid [5]int
End [5]int
- PokerType int
+ PokerType int // -1 无牌数据,0 有牌数据, 1-13 特殊牌型
}
func (p *Group) String() string {
diff --git a/gamesrv/thirteen/scene.go b/gamesrv/thirteen/scene.go
index 77b3318..bd04c0f 100644
--- a/gamesrv/thirteen/scene.go
+++ b/gamesrv/thirteen/scene.go
@@ -487,6 +487,7 @@ func (this *SceneEx) GetScore(player *PlayerEx) {
player.winAllPlayers[p.Pos] += rate
p.winAllPlayers[player.Pos] -= rate
player.tableScore[3] += rate - 1
+ p.tableScore[3] -= rate - 1
}
//中墩
rate = int64(1)
@@ -509,6 +510,7 @@ func (this *SceneEx) GetScore(player *PlayerEx) {
player.winAllPlayers[p.Pos] += rate
p.winAllPlayers[player.Pos] -= rate
player.tableScore[4] += rate - 1
+ p.tableScore[4] -= rate - 1
}
//尾墩
rate = int64(1)
@@ -529,6 +531,7 @@ func (this *SceneEx) GetScore(player *PlayerEx) {
player.winAllPlayers[p.Pos] += rate
p.winAllPlayers[player.Pos] -= rate
player.tableScore[5] += rate - 1
+ p.tableScore[5] -= rate - 1
}
if s == 3 {
player.winThreePos[p.Pos] = score
diff --git a/gamesrv/thirteen/scenepolicy.go b/gamesrv/thirteen/scenepolicy.go
index 05d9b36..2bb9a47 100644
--- a/gamesrv/thirteen/scenepolicy.go
+++ b/gamesrv/thirteen/scenepolicy.go
@@ -866,6 +866,10 @@ func (this *StateOp) OnPlayerOp(s *base.Scene, p *base.Player, opcode int, param
copy(playerEx.cardsO.Mid[:], common.Int64Toint(params[3:8]))
copy(playerEx.cardsO.End[:], common.Int64Toint(params[8:]))
playerEx.cardsO.PokerType = 0
+ tp := sceneEx.logic.GetSpecialType(playerEx.cards)
+ if tp > 0 {
+ playerEx.cardsO.PokerType = tp
+ }
sceneEx.SendSelectCards(playerEx, 0, int64(opcode))
} else {
sceneEx.SendSelectCards(playerEx, int(params[0]), int64(opcode))
@@ -912,6 +916,10 @@ func (this *StateOp) OnPlayerOp(s *base.Scene, p *base.Player, opcode int, param
copy(playerEx.preCardsO.Mid[:], common.Int64Toint(params[3:8]))
copy(playerEx.preCardsO.End[:], common.Int64Toint(params[8:]))
playerEx.preCardsO.PokerType = 0
+ tp := sceneEx.logic.GetSpecialType(playerEx.cards)
+ if tp > 0 {
+ playerEx.preCardsO.PokerType = tp
+ }
}
playerEx.SendToClient(int(thirteen.TWMmoPacketID_PACKET_SCThirteenPlayerOp), pack)
@@ -948,7 +956,7 @@ func (this *StateOp) OnLeave(s *base.Scene) {
}
// 判断是否倒水
if player.cardsO != nil && player.cardsO.PokerType != -1 {
- if player.cardsO.PokerType < 1000000 {
+ if player.cardsO.PokerType == 0 {
player.isDP = sceneEx.logic.IsDP(player.cardsO.Head, player.cardsO.Mid, player.cardsO.End)
}
continue