add statistics

This commit is contained in:
sk 2024-12-03 13:13:24 +08:00
parent 2b88155177
commit 801200f4bd
58 changed files with 5593 additions and 9 deletions

View File

@ -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
}

10
go.mod
View File

@ -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
)

15
go.sum
View File

@ -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=

29
statistics/.gitignore vendored Normal file
View File

@ -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

7
statistics/README.md Normal file
View File

@ -0,0 +1,7 @@
# statistics
数据分析服务
* mongodb同步到mysql
* 接收消息队列数据
* 历史数据查询
* 数据统计

View File

@ -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

View File

@ -0,0 +1,8 @@
package constant
const (
InviteScoreTypeBind = 1 // 绑定邀请码
InviteScoreTypePay = 2 // 充值返佣
InviteScoreTypeRecharge = 3 // 充值完成
InviteScoreTypePayMe = 4 // 充值(自己)
)

View File

@ -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

53
statistics/etc/mongo.yaml Normal file
View File

@ -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:

38
statistics/etc/mysql.yaml Normal file
View File

@ -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

22
statistics/logger.xml Normal file
View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8" ?>
<seelog type="adaptive" mininterval="2000000" maxinterval="100000000" critmsgcount="500" minlevel="trace">
<exceptions>
<exception filepattern="test*" minlevel="error"/>
</exceptions>
<outputs formatid="all">
<rollingfile formatid="all" type="size" filename="./all.log" maxsize="50000000" maxrolls="5" />
<filter levels="info,trace,warn">
<console formatid="fmtinfo"/>
</filter>
<filter levels="error,critical" formatid="fmterror">
<console/>
<file path="errors.log"/>
</filter>
</outputs>
<formats>
<format id="fmtinfo" format="[%Date][%Time] [%Level] %Msg%n"/>
<format id="fmterror" format="[%Date][%Time] [%LEVEL] [%FuncShort @ %File.%Line] %Msg%n"/>
<format id="all" format="[%Date][%Time] [%Level] [@ %File.%Line] %Msg%n"/>
<format id="criticalemail" format="Critical error on our server!\n %Time %Date %RelFile %Func %Msg \nSent by Seelog"/>
</formats>
</seelog>

212
statistics/main.go Normal file
View File

@ -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()
}

View File

@ -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 //拉霸专用 输赢
}

View File

@ -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 // 代理层级 例如 1DownSnid 是 UpSnid 的 1 级代理; 2: DownSnid 是 UpSnid 的 2 级代理
Tp int // 返佣类型
Rate int // 返佣比例
Score int // 积分
Money int // 充值金额
Ts int // 时间戳
}

View File

@ -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兑换失败
}

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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"`
}

View File

@ -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"` // 代理层级 例如 1DownSnid 是 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"` // 代理层级 例如 1DownSnid 是 UpSnid 的 1 级代理; 2: DownSnid 是 UpSnid 的 2 级代理
Ts int `gorm:"index"` // 绑定时间
}

View File

@ -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 // 道具数量
}

View File

@ -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"`
}

View File

@ -0,0 +1,6 @@
package modelmysql
type LogLoginMid struct {
ID uint `gorm:"primaryKey"`
MID string
}

View File

@ -0,0 +1,11 @@
package modelmysql
const (
MidTypeItem = 1 // 道具记录
)
type LogMid struct {
ID uint `gorm:"primaryKey"`
Tp int `gorm:"index"` // 类型
MID string
}

View File

@ -0,0 +1,17 @@
package modelmysql
// 需要自动迁移的表添加在这里 Tables
var Tables = []interface{}{
&LogLogin{},
&LogLoginMid{},
&UserAccount{},
&UserLogin{},
&UserID{},
&LogInviteScoreMid{},
&LogInviteScore{},
&LogInviteUser{},
&LogMid{},
&ItemGain{},
&ItemTotalGain{},
}

View File

@ -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"`
}

View File

@ -0,0 +1,9 @@
package modelmysql
/*
服务定期查询注册和登录信息然后获取玩家id,保存到这张表中用于后续触发和玩家相关的数据统计
*/
type UserID struct {
Snid int `gorm:"primaryKey"`
}

View File

@ -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"` // 推广渠道
}

1
statistics/mq/handler.go Normal file
View File

@ -0,0 +1 @@
package mq

1
statistics/mq/readme Normal file
View File

@ -0,0 +1 @@
接收消息队列

1
statistics/static/readme Normal file
View File

@ -0,0 +1 @@
业务统计

View File

@ -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
}

102
statistics/syn/datasync.go Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -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()
}

181
statistics/syn/log_login.go Normal file
View File

@ -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
}

1
statistics/syn/readme Normal file
View File

@ -0,0 +1 @@
游戏服mongodb同步到mysql

View File

@ -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
}

View File

@ -0,0 +1,5 @@
set CGO_ENABLED=0
set GOOS=linux
set GOARCH=amd64
go build -o ./task2
pause

Binary file not shown.

View File

@ -0,0 +1,221 @@
StartTime: "2024-11-26T00:00:00+08:00"
EndTime: "2024-11-27T00:00:00+08:00"
Switch: # 1: open, 0: close
- 0 # 新用户游戏破产率
- 0 # 新用户平均游戏时长
- 0 # 新用户平均局数
- 0 # 平均倍数
- 1 # 活跃破产率
- 1 # 控输赢胜率
- 1 # 机器人胜率
- 1 # 人均获得金币
- 1 # 破产后离线
- 1 # 充值玩家金币余额
Gamefreeids:
- 2070001
- 2070002
- 2070003
- 2080001
- 2080002
- 2080003
- 2090001
- 2090002
- 2090003
- 2100001
- 2100002
- 2100003
- 2400001
- 2400002
- 2400003
- 2400004
- 2400005
- 2400006
- 2440001
- 2440002
- 2440003
- 2440004
- 2440005
- 2440006
- 2410001
- 2410002
- 2410003
- 2410004
- 2410005
- 2410006
- 2450001
- 2450002
- 2450003
- 2450004
- 2450005
- 2450006
- 2420001
- 2420002
- 2420003
- 2420004
- 2420005
- 2420006
- 2460001
- 2460002
- 2460003
- 2460004
- 2460005
- 2460006
- 2430001
- 2430002
- 2430003
- 2430004
- 2430005
- 2430006
- 2470001
- 2470002
- 2470003
- 2470004
- 2470005
- 2470006
- 2150001
- 2160001
- 2170001
- 2180001
- 5210001
- 5210002
- 5210003
- 5210004
- 5210005
- 2110001
- 2110002
- 2110003
- 2110004
- 2110005
- 2110006
- 2120001
- 2120002
- 2120003
- 2120004
- 2120005
- 2120006
- 2130001
- 2130002
- 2130003
- 2140001
- 2140002
- 2140003
- 6070001
- 3010001
- 3010002
- 3010003
- 3010004
- 3020001
- 3020002
- 3020003
- 3020004
- 3030001
- 3030002
- 3030003
- 3030004
- 3040001
- 3040002
- 3040003
- 3050001
- 3050002
- 3050003
- 3060001
- 3060002
- 3060003
- 3060004
- 3070001
- 3070002
- 3070003
- 3070004
- 3080001
- 3090001
- 3100001
- 3110001
- 3120001
Tienlen:
- 2070001
- 2070002
- 2070003
- 2080001
- 2080002
- 2080003
- 2090001
- 2090002
- 2090003
- 2100001
- 2100002
- 2100003
- 2400001
- 2400002
- 2400003
- 2400004
- 2400005
- 2400006
- 2440001
- 2440002
- 2440003
- 2440004
- 2440005
- 2440006
- 2410001
- 2410002
- 2410003
- 2410004
- 2410005
- 2410006
- 2450001
- 2450002
- 2450003
- 2450004
- 2450005
- 2450006
- 2420001
- 2420002
- 2420003
- 2420004
- 2420005
- 2420006
- 2460001
- 2460002
- 2460003
- 2460004
- 2460005
- 2460006
- 2430001
- 2430002
- 2430003
- 2430004
- 2430005
- 2430006
- 2470001
- 2470002
- 2470003
- 2470004
- 2470005
- 2470006
Thirteen:
- 2110001
- 2110002
- 2110003
- 2110004
- 2110005
- 2110006
- 2120001
- 2120002
- 2120003
- 2120004
- 2120005
- 2120006
- 2130001
- 2130002
- 2130003
- 2140001
- 2140002
- 2140003

View File

@ -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:

View File

@ -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

View File

@ -0,0 +1,88 @@
package main
import (
"fmt"
"github.com/xuri/excelize/v2"
)
type ExcelData struct {
*excelize.File
Head []string
Index int
IndexCell int
}
func (e *ExcelData) SetHead(head []string) *ExcelData {
if e == nil {
return nil
}
e.Index = 1
e.SetSheetRow("Sheet1", "A1", &head)
return e
}
func (e *ExcelData) SetRow(row []string) *ExcelData {
if e == nil {
return nil
}
e.Index++
e.SetSheetRow("Sheet1", "A"+fmt.Sprintf("%d", e.Index), &row)
return e
}
func (e *ExcelData) SetCell(val interface{}) *ExcelData {
if e == nil {
return nil
}
e.IndexCell++
cell := fmt.Sprintf("%c%d", 'A'+e.IndexCell-1, e.Index)
e.SetCellValue("Sheet1", cell, val)
return e
}
func (e *ExcelData) NewLine() *ExcelData {
if e == nil {
return nil
}
e.Index++
e.IndexCell = 0
return e
}
type ExcelMgr struct {
List map[int]*ExcelData
}
func NewExcelMgr() *ExcelMgr {
return &ExcelMgr{
List: make(map[int]*ExcelData),
}
}
func (e *ExcelMgr) Register(id int, head []string) *ExcelData {
e.List[id] = &ExcelData{
File: excelize.NewFile(),
Head: head,
Index: 0,
}
e.List[id].NewSheet("Sheet1")
if len(head) > 0 {
e.List[id].SetHead(head)
}
return e.List[id]
}
func (e *ExcelMgr) Get(id int) *ExcelData {
return e.List[id]
}
func (e *ExcelMgr) Save(id int, fileName string) error {
d := e.List[id]
if d == nil {
return nil
}
return d.SaveAs(fileName)
}

View File

@ -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
}

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8" ?>
<seelog type="adaptive" mininterval="2000000" maxinterval="100000000" critmsgcount="500" minlevel="trace">
<exceptions>
<exception filepattern="test*" minlevel="error"/>
</exceptions>
<outputs formatid="all">
<rollingfile formatid="all" type="size" filename="./all.log" maxsize="50000000" maxrolls="5" />
<filter levels="info,trace,warn">
<console formatid="fmtinfo"/>
</filter>
<filter levels="error,critical" formatid="fmterror">
<console/>
<file path="errors.log"/>
</filter>
</outputs>
<formats>
<format id="fmtinfo" format="[%Date][%Time] [%Level] %Msg%n"/>
<format id="fmterror" format="[%Date][%Time] [%LEVEL] [%FuncShort @ %File.%Line] %Msg%n"/>
<format id="all" format="[%Date][%Time] [%Level] [@ %File.%Line] %Msg%n"/>
<format id="criticalemail" format="Critical error on our server!\n %Time %Date %RelFile %Func %Msg \nSent by Seelog"/>
</formats>
</seelog>

427
statistics/task/main.go Normal file
View File

@ -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)
}

1
statistics/task/readme Normal file
View File

@ -0,0 +1 @@
查询历史数据

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

File diff suppressed because it is too large Load Diff

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}