commit 4403d9284cf1a50590ea22605df3e0a32b481984 Author: mofangmin Date: Thu Jun 20 12:23:43 2024 +0800 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e3fb706 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +*.log +.vs/ + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +.idea + +*test* + +/pb/*.pb.go +/docs/backend/swagger.json +/docs/web/swagger.json + +gameserver* \ No newline at end of file diff --git a/bin/conf/andarbahar/conf.toml b/bin/conf/andarbahar/conf.toml new file mode 100644 index 0000000..c5e0984 --- /dev/null +++ b/bin/conf/andarbahar/conf.toml @@ -0,0 +1,3 @@ +WorkID=5000 +MaxPlayerCount=10000 +[Andarbahar] \ No newline at end of file diff --git a/bin/conf/andarbahar/module.json b/bin/conf/andarbahar/module.json new file mode 100644 index 0000000..af29b3a --- /dev/null +++ b/bin/conf/andarbahar/module.json @@ -0,0 +1,29 @@ +{ + "Module":{ + "game5000":[ + { + "Id":"game5000001", + "ProcessID":"development" + } + ] + }, + "Mqtt":{ + "WirteLoopChanNum": 10, + "ReadPackLoop": 1, + "ReadTimeout": 60, + "WriteTimeout": 60 + }, + "Rpc":{ + "MaxCoroutine":10000, + "RpcExpired": 1, + "LogSuccess":false + }, + "Log": { + "file":{ + "daily":true, + "level":7, + "maxsize":1024000000, + "maxlines":10000000 + } + } +} \ No newline at end of file diff --git a/bin/conf/backend/conf.toml b/bin/conf/backend/conf.toml new file mode 100644 index 0000000..7ef3d19 --- /dev/null +++ b/bin/conf/backend/conf.toml @@ -0,0 +1,10 @@ +[Backend] +Addr=":7616" +Release=false +DB="root:d47a98e748ccaf1f@tcp(172.105.117.6:3306)/backend" +[Web.ZYPay] +ZYPayURL="https://in.zyhws.com/tablegames/singleton/orderPay" +ZYWithdrawURL="https://in.zyhws.com/tablegames/singleton/orderdraw" +PayNotifyURL="http://192.168.1.141:7615/balance/recharge/ZYPayCallback" +WithdrawNotifyURL="http://192.168.1.141:7615/balance/withdraw/ZYWithdrawCallback" +WhiteIPs = ["47.241.254.165","8.214.46.27"] \ No newline at end of file diff --git a/bin/conf/backend/gm/gm.html b/bin/conf/backend/gm/gm.html new file mode 100644 index 0000000..252e077 --- /dev/null +++ b/bin/conf/backend/gm/gm.html @@ -0,0 +1,38 @@ + + + + + GM + + + +
+ + + +
+ +
+ 金额(分): + 用户ID: + +
+ +
+ 用户ID: + 类型: + +    +    + + +
+ +
+ 用户ID: + 充值商品id: + +
+ + + \ No newline at end of file diff --git a/bin/conf/backend/module.json b/bin/conf/backend/module.json new file mode 100644 index 0000000..6379969 --- /dev/null +++ b/bin/conf/backend/module.json @@ -0,0 +1,29 @@ +{ + "Module":{ + "backend":[ + { + "Id":"backend001", + "ProcessID":"development" + } + ] + }, + "Mqtt":{ + "WirteLoopChanNum": 10, + "ReadPackLoop": 1, + "ReadTimeout": 60, + "WriteTimeout": 60 + }, + "Rpc":{ + "MaxCoroutine":10000, + "RpcExpired": 1, + "LogSuccess":false + }, + "Log": { + "file":{ + "daily":true, + "level":7, + "maxsize":1024000000, + "maxlines":10000000 + } + } +} \ No newline at end of file diff --git a/bin/conf/baseConf.toml b/bin/conf/baseConf.toml new file mode 100644 index 0000000..cdb1fb6 --- /dev/null +++ b/bin/conf/baseConf.toml @@ -0,0 +1,59 @@ +[net] +registry="127.0.0.1:8500" # consul 地址 +nats="nats://127.0.0.1:4222" # nats地址 + +[redis] +addr="127.0.0.1:6379" +name="" +passwd="" +tls=false +db=0 +cluster=false + +[es] +#urls=["http://127.0.0.1:9200"] +urls=["http://elastic:joyyu7XRwlpJtXFEIw80@47.112.119.250:9400"] +sniff=false + +[mysql] +#dsn="xh_rwuser:!Xhwlkj2018@tcp(127.0.0.1:3306)/game" +dsn="root:d9JaXnG5QuuZLIrY@tcp(47.112.119.250:3306)/slotsgame" +debug=true + +[facebook] +prefix="fb" +appKey="" +appSecret="" +authURL="https://graph.facebook.com" + +[google] +prefix="gp" +authURL="oauth2.googleapis.com/tokeninfo?id_token=" + +[common] +savePicPath="" +avatarURL="" + +[server] +IsExamine=true +ExamineURL="172.105.117.6:7615" +GameURL="172.105.117.6:7615" + +[warn] +URL = "http://smsapi.5taogame.com/sms/httpSmsInterface2" +ID = "xlk168888" +Account = "xlk168888" +Password = "xlk168888" +Sign = "【章鱼游戏】" +Action = "sendhy" + +[mails] +Accounts = ["lila48w4@gmail.com"] +Pass = ["eqqpmbabzihirygu"] + +[newPacks] +PIDs = [117,116,102,157,121,141,162] + +[ad] +FBAPIURL = "https://graph.facebook.com/v18.0/" +KwaiAPIURL = "http://www.adsnebula.com/log/common/api" \ No newline at end of file diff --git a/bin/conf/blackred/conf.toml b/bin/conf/blackred/conf.toml new file mode 100644 index 0000000..b4409b2 --- /dev/null +++ b/bin/conf/blackred/conf.toml @@ -0,0 +1,2 @@ +WorkID=9000 +MaxPlayerCount=10000 \ No newline at end of file diff --git a/bin/conf/blackred/module.json b/bin/conf/blackred/module.json new file mode 100644 index 0000000..de76b70 --- /dev/null +++ b/bin/conf/blackred/module.json @@ -0,0 +1,29 @@ +{ + "Module":{ + "game9000":[ + { + "Id":"game9000001", + "ProcessID":"development" + } + ] + }, + "Mqtt":{ + "WirteLoopChanNum": 10, + "ReadPackLoop": 1, + "ReadTimeout": 60, + "WriteTimeout": 60 + }, + "Rpc":{ + "MaxCoroutine":10000, + "RpcExpired": 1, + "LogSuccess":false + }, + "Log": { + "file":{ + "daily":true, + "level":7, + "maxsize":1024000000, + "maxlines":10000000 + } + } +} \ No newline at end of file diff --git a/bin/conf/blockpay/conf.toml b/bin/conf/blockpay/conf.toml new file mode 100644 index 0000000..dd1c394 --- /dev/null +++ b/bin/conf/blockpay/conf.toml @@ -0,0 +1,3 @@ +[BlockPay] +[BlockPay.Tron] +Addrs = [] \ No newline at end of file diff --git a/bin/conf/blockpay/module.json b/bin/conf/blockpay/module.json new file mode 100644 index 0000000..3ed0633 --- /dev/null +++ b/bin/conf/blockpay/module.json @@ -0,0 +1,29 @@ +{ + "Module":{ + "blockpay":[ + { + "Id":"blockpay001", + "ProcessID":"development" + } + ] + }, + "Mqtt":{ + "WirteLoopChanNum": 10, + "ReadPackLoop": 1, + "ReadTimeout": 60, + "WriteTimeout": 60 + }, + "Rpc":{ + "MaxCoroutine":10000, + "RpcExpired": 1, + "LogSuccess":false + }, + "Log": { + "file":{ + "daily":true, + "level":7, + "maxsize":1024000000, + "maxlines":10000000 + } + } +} \ No newline at end of file diff --git a/bin/conf/car/conf.toml b/bin/conf/car/conf.toml new file mode 100644 index 0000000..01ed760 --- /dev/null +++ b/bin/conf/car/conf.toml @@ -0,0 +1,2 @@ +WorkID=16000 +MaxPlayerCount=10000 \ No newline at end of file diff --git a/bin/conf/car/module.json b/bin/conf/car/module.json new file mode 100644 index 0000000..7f23e22 --- /dev/null +++ b/bin/conf/car/module.json @@ -0,0 +1,29 @@ +{ + "Module":{ + "game16000":[ + { + "Id":"game16000001", + "ProcessID":"development" + } + ] + }, + "Mqtt":{ + "WirteLoopChanNum": 10, + "ReadPackLoop": 1, + "ReadTimeout": 60, + "WriteTimeout": 60 + }, + "Rpc":{ + "MaxCoroutine":10000, + "RpcExpired": 1, + "LogSuccess":false + }, + "Log": { + "file":{ + "daily":true, + "level":7, + "maxsize":1024000000, + "maxlines":10000000 + } + } +} \ No newline at end of file diff --git a/bin/conf/common/conf.toml b/bin/conf/common/conf.toml new file mode 100644 index 0000000..2872edc --- /dev/null +++ b/bin/conf/common/conf.toml @@ -0,0 +1,2 @@ +WorkID=1 +Release=true \ No newline at end of file diff --git a/bin/conf/common/module.json b/bin/conf/common/module.json new file mode 100644 index 0000000..c394a14 --- /dev/null +++ b/bin/conf/common/module.json @@ -0,0 +1,18 @@ +{ + "Module":{ + "common":[ + { + "Id":"common001", + "ProcessID":"development" + } + ] + }, + "Log": { + "file":{ + "daily":true, + "level":7, + "maxsize":1024000000, + "maxlines":10000000 + } + } +} \ No newline at end of file diff --git a/bin/conf/customer/conf.toml b/bin/conf/customer/conf.toml new file mode 100644 index 0000000..7ecba51 --- /dev/null +++ b/bin/conf/customer/conf.toml @@ -0,0 +1,4 @@ +[Customer] +Addr=":9616" +Release=false +DB="root:d9JaXnG5QuuZLIrY@tcp(47.112.119.250:3306)/customer" diff --git a/bin/conf/customer/module.json b/bin/conf/customer/module.json new file mode 100644 index 0000000..2533f0c --- /dev/null +++ b/bin/conf/customer/module.json @@ -0,0 +1,29 @@ +{ + "Module":{ + "customer":[ + { + "Id":"customer001", + "ProcessID":"development" + } + ] + }, + "Mqtt":{ + "WirteLoopChanNum": 10, + "ReadPackLoop": 1, + "ReadTimeout": 60, + "WriteTimeout": 60 + }, + "Rpc":{ + "MaxCoroutine":10000, + "RpcExpired": 1, + "LogSuccess":false + }, + "Log": { + "file":{ + "daily":true, + "level":7, + "maxsize":1024000000, + "maxlines":10000000 + } + } +} diff --git a/bin/conf/dragon/conf.toml b/bin/conf/dragon/conf.toml new file mode 100644 index 0000000..c5f8f25 --- /dev/null +++ b/bin/conf/dragon/conf.toml @@ -0,0 +1,3 @@ +WorkID=8000 +MaxPlayerCount=10000 +[Dragon] \ No newline at end of file diff --git a/bin/conf/dragon/module.json b/bin/conf/dragon/module.json new file mode 100644 index 0000000..5e70f71 --- /dev/null +++ b/bin/conf/dragon/module.json @@ -0,0 +1,29 @@ +{ + "Module":{ + "game8000":[ + { + "Id":"game8000001", + "ProcessID":"development" + } + ] + }, + "Mqtt":{ + "WirteLoopChanNum": 10, + "ReadPackLoop": 1, + "ReadTimeout": 60, + "WriteTimeout": 60 + }, + "Rpc":{ + "MaxCoroutine":10000, + "RpcExpired": 1, + "LogSuccess":false + }, + "Log": { + "file":{ + "daily":true, + "level":7, + "maxsize":1024000000, + "maxlines":10000000 + } + } +} \ No newline at end of file diff --git a/bin/conf/fruit/conf.toml b/bin/conf/fruit/conf.toml new file mode 100644 index 0000000..02fcf36 --- /dev/null +++ b/bin/conf/fruit/conf.toml @@ -0,0 +1,2 @@ +WorkID=15000 +MaxPlayerCount=10000 \ No newline at end of file diff --git a/bin/conf/fruit/module.json b/bin/conf/fruit/module.json new file mode 100644 index 0000000..b6a48f5 --- /dev/null +++ b/bin/conf/fruit/module.json @@ -0,0 +1,29 @@ +{ + "Module":{ + "game15000":[ + { + "Id":"game15000001", + "ProcessID":"development" + } + ] + }, + "Mqtt":{ + "WirteLoopChanNum": 10, + "ReadPackLoop": 1, + "ReadTimeout": 60, + "WriteTimeout": 60 + }, + "Rpc":{ + "MaxCoroutine":10000, + "RpcExpired": 1, + "LogSuccess":false + }, + "Log": { + "file":{ + "daily":true, + "level":7, + "maxsize":1024000000, + "maxlines":10000000 + } + } +} \ No newline at end of file diff --git a/bin/conf/gate/conf.toml b/bin/conf/gate/conf.toml new file mode 100644 index 0000000..2d5c8d3 --- /dev/null +++ b/bin/conf/gate/conf.toml @@ -0,0 +1,8 @@ +[Gate] +WSAddr="172.105.117.6" +WSPort=":6615" +HeartBeat=20 +BufSize=4096 +TLS=false +cert_file="" +key_file="" \ No newline at end of file diff --git a/bin/conf/gate/module.json b/bin/conf/gate/module.json new file mode 100644 index 0000000..f3e49e3 --- /dev/null +++ b/bin/conf/gate/module.json @@ -0,0 +1,18 @@ +{ + "Module":{ + "gate":[ + { + "Id":"gate001", + "ProcessID":"development" + } + ] + }, + "Log": { + "file":{ + "daily":true, + "level":7, + "maxsize":1024000000, + "maxlines":10000000 + } + } +} \ No newline at end of file diff --git a/bin/conf/hall/conf.toml b/bin/conf/hall/conf.toml new file mode 100644 index 0000000..3af1c12 --- /dev/null +++ b/bin/conf/hall/conf.toml @@ -0,0 +1,8 @@ +[Hall] +AvatarURL="" +SavePath="" +[Hall.FacebookParams] +Prefix="" +AppKey="" +AppScrect="" +AuthURL="" \ No newline at end of file diff --git a/bin/conf/hall/module.json b/bin/conf/hall/module.json new file mode 100644 index 0000000..0bef78f --- /dev/null +++ b/bin/conf/hall/module.json @@ -0,0 +1,29 @@ +{ + "Module":{ + "hall":[ + { + "Id":"hall001", + "ProcessID":"development" + } + ] + }, + "Mqtt":{ + "WirteLoopChanNum": 10, + "ReadPackLoop": 1, + "ReadTimeout": 60, + "WriteTimeout": 60 + }, + "Rpc":{ + "MaxCoroutine":10000, + "RpcExpired": 1, + "LogSuccess":false + }, + "Log": { + "file":{ + "daily":true, + "level":7, + "maxsize":1024000000, + "maxlines":10000000 + } + } +} \ No newline at end of file diff --git a/bin/conf/horse/conf.toml b/bin/conf/horse/conf.toml new file mode 100644 index 0000000..d67a76a --- /dev/null +++ b/bin/conf/horse/conf.toml @@ -0,0 +1,3 @@ +WorkID=10000 +MaxPlayerCount=10000 +[Andarbahar] \ No newline at end of file diff --git a/bin/conf/horse/module.json b/bin/conf/horse/module.json new file mode 100644 index 0000000..12650db --- /dev/null +++ b/bin/conf/horse/module.json @@ -0,0 +1,29 @@ +{ + "Module":{ + "game10000":[ + { + "Id":"game10000001", + "ProcessID":"development" + } + ] + }, + "Mqtt":{ + "WirteLoopChanNum": 10, + "ReadPackLoop": 1, + "ReadTimeout": 60, + "WriteTimeout": 60 + }, + "Rpc":{ + "MaxCoroutine":10000, + "RpcExpired": 1, + "LogSuccess":false + }, + "Log": { + "file":{ + "daily":true, + "level":7, + "maxsize":1024000000, + "maxlines":10000000 + } + } +} \ No newline at end of file diff --git a/bin/conf/ip2region.db b/bin/conf/ip2region.db new file mode 100644 index 0000000..d41b4a0 Binary files /dev/null and b/bin/conf/ip2region.db differ diff --git a/bin/conf/jm/conf.toml b/bin/conf/jm/conf.toml new file mode 100644 index 0000000..586da2a --- /dev/null +++ b/bin/conf/jm/conf.toml @@ -0,0 +1,3 @@ +WorkID=11000 +MaxPlayerCount=10000 +[JhandiMunda] \ No newline at end of file diff --git a/bin/conf/jm/module.json b/bin/conf/jm/module.json new file mode 100644 index 0000000..3b8f6fb --- /dev/null +++ b/bin/conf/jm/module.json @@ -0,0 +1,29 @@ +{ + "Module":{ + "game11000":[ + { + "Id":"game11000001", + "ProcessID":"development" + } + ] + }, + "Mqtt":{ + "WirteLoopChanNum": 10, + "ReadPackLoop": 1, + "ReadTimeout": 60, + "WriteTimeout": 60 + }, + "Rpc":{ + "MaxCoroutine":10000, + "RpcExpired": 1, + "LogSuccess":false + }, + "Log": { + "file":{ + "daily":true, + "level":7, + "maxsize":1024000000, + "maxlines":10000000 + } + } +} \ No newline at end of file diff --git a/bin/conf/matching/conf.toml b/bin/conf/matching/conf.toml new file mode 100644 index 0000000..9c6ef6f --- /dev/null +++ b/bin/conf/matching/conf.toml @@ -0,0 +1 @@ +WorkID=1 \ No newline at end of file diff --git a/bin/conf/matching/module.json b/bin/conf/matching/module.json new file mode 100644 index 0000000..39cfaae --- /dev/null +++ b/bin/conf/matching/module.json @@ -0,0 +1,18 @@ +{ + "Module":{ + "matching":[ + { + "Id":"matching001", + "ProcessID":"development" + } + ] + }, + "Log": { + "file":{ + "daily":true, + "level":7, + "maxsize":1024000000, + "maxlines":10000000 + } + } +} \ No newline at end of file diff --git a/bin/conf/pay/conf.toml b/bin/conf/pay/conf.toml new file mode 100644 index 0000000..defb144 --- /dev/null +++ b/bin/conf/pay/conf.toml @@ -0,0 +1,31 @@ +[Pay] +Addr=":8615" +Release=true +TLS=false +CertFile="" +KeyFile="" +CallbackURL = "http://47.112.119.250" +CheckLimit = 20 +RootChannel = [12,14] +SelectPayWay = true +PayCheckTime = 2 +PayFailWeight = 20 +PaySuccessWeight = 50 +BaseSuccess = 10 +WithdrawScanTime = 10 +[Pay.IGeek] +APIURL = "http://api.test.igeekpay.com/gateway" +APPID = "H4RIKR" +MID = "RSRV686X" +Key = "2YHPVB8DZKTALVVI" +[Web.Adjust] +URL = "https://s2s.adjust.com/event" +APIURL = "https://api.adjust.com/device_service/api/v1/inspect_device" +APIToken = "_2ZGYezfdZ7yD2he-zWx" +[Pay.BestPay] +APIURL = "https://very.goodrummy.co.in" +[Pay.GrePay] +Key = "" +WithdrawAccount = "" +MIDAmount = 3000 +BigChannel = "0452" \ No newline at end of file diff --git a/bin/conf/pay/module.json b/bin/conf/pay/module.json new file mode 100644 index 0000000..b29381f --- /dev/null +++ b/bin/conf/pay/module.json @@ -0,0 +1,29 @@ +{ + "Module":{ + "pay":[ + { + "Id":"pay001", + "ProcessID":"development" + } + ] + }, + "Mqtt":{ + "WirteLoopChanNum": 10, + "ReadPackLoop": 1, + "ReadTimeout": 60, + "WriteTimeout": 60 + }, + "Rpc":{ + "MaxCoroutine":10000, + "RpcExpired": 1, + "LogSuccess":false + }, + "Log": { + "file":{ + "daily":true, + "level":7, + "maxsize":1024000000, + "maxlines":10000000 + } + } +} \ No newline at end of file diff --git a/bin/conf/rocket/conf.toml b/bin/conf/rocket/conf.toml new file mode 100644 index 0000000..4f84b11 --- /dev/null +++ b/bin/conf/rocket/conf.toml @@ -0,0 +1,4 @@ +WorkID=22000 +MaxPlayerCount=10000 +[Rummy] +SettleTime=3 \ No newline at end of file diff --git a/bin/conf/rocket/module.json b/bin/conf/rocket/module.json new file mode 100644 index 0000000..5074d54 --- /dev/null +++ b/bin/conf/rocket/module.json @@ -0,0 +1,29 @@ +{ + "Module":{ + "game22000":[ + { + "Id":"game22000001", + "ProcessID":"development" + } + ] + }, + "Mqtt":{ + "WirteLoopChanNum": 10, + "ReadPackLoop": 1, + "ReadTimeout": 60, + "WriteTimeout": 60 + }, + "Rpc":{ + "MaxCoroutine":10000, + "RpcExpired": 1, + "LogSuccess":false + }, + "Log": { + "file":{ + "daily":true, + "level":7, + "maxsize":1024000000, + "maxlines":10000000 + } + } +} \ No newline at end of file diff --git a/bin/conf/seven/conf.toml b/bin/conf/seven/conf.toml new file mode 100644 index 0000000..62ea05f --- /dev/null +++ b/bin/conf/seven/conf.toml @@ -0,0 +1,3 @@ +WorkID=6000 +MaxPlayerCount=10000 +[Seven] \ No newline at end of file diff --git a/bin/conf/seven/module.json b/bin/conf/seven/module.json new file mode 100644 index 0000000..4981258 --- /dev/null +++ b/bin/conf/seven/module.json @@ -0,0 +1,29 @@ +{ + "Module":{ + "game6000":[ + { + "Id":"game6000001", + "ProcessID":"development" + } + ] + }, + "Mqtt":{ + "WirteLoopChanNum": 10, + "ReadPackLoop": 1, + "ReadTimeout": 60, + "WriteTimeout": 60 + }, + "Rpc":{ + "MaxCoroutine":10000, + "RpcExpired": 1, + "LogSuccess":false + }, + "Log": { + "file":{ + "daily":true, + "level":7, + "maxsize":1024000000, + "maxlines":10000000 + } + } +} \ No newline at end of file diff --git a/bin/conf/slot/conf.toml b/bin/conf/slot/conf.toml new file mode 100644 index 0000000..68281ee --- /dev/null +++ b/bin/conf/slot/conf.toml @@ -0,0 +1,3 @@ +WorkID=18000 +MaxPlayerCount=10000 +[TeenpattiWar] \ No newline at end of file diff --git a/bin/conf/slot/module.json b/bin/conf/slot/module.json new file mode 100644 index 0000000..2928fc1 --- /dev/null +++ b/bin/conf/slot/module.json @@ -0,0 +1,29 @@ +{ + "Module":{ + "game18000":[ + { + "Id":"game18000001", + "ProcessID":"development" + } + ] + }, + "Mqtt":{ + "WirteLoopChanNum": 10, + "ReadPackLoop": 1, + "ReadTimeout": 60, + "WriteTimeout": 60 + }, + "Rpc":{ + "MaxCoroutine":10000, + "RpcExpired": 1, + "LogSuccess":false + }, + "Log": { + "file":{ + "daily":true, + "level":7, + "maxsize":1024000000, + "maxlines":10000000 + } + } +} \ No newline at end of file diff --git a/bin/conf/slotastrologer/conf.toml b/bin/conf/slotastrologer/conf.toml new file mode 100644 index 0000000..a5ea8d2 --- /dev/null +++ b/bin/conf/slotastrologer/conf.toml @@ -0,0 +1,3 @@ +WorkID=58000 +MaxPlayerCount=10000 +[TeenpattiWar] \ No newline at end of file diff --git a/bin/conf/slotastrologer/module.json b/bin/conf/slotastrologer/module.json new file mode 100644 index 0000000..2b98d2b --- /dev/null +++ b/bin/conf/slotastrologer/module.json @@ -0,0 +1,29 @@ +{ + "Module":{ + "game58000":[ + { + "Id":"game58000001", + "ProcessID":"development" + } + ] + }, + "Mqtt":{ + "WirteLoopChanNum": 10, + "ReadPackLoop": 1, + "ReadTimeout": 60, + "WriteTimeout": 60 + }, + "Rpc":{ + "MaxCoroutine":10000, + "RpcExpired": 1, + "LogSuccess":false + }, + "Log": { + "file":{ + "daily":true, + "level":7, + "maxsize":1024000000, + "maxlines":10000000 + } + } +} \ No newline at end of file diff --git a/bin/conf/slotcashmania/conf.toml b/bin/conf/slotcashmania/conf.toml new file mode 100644 index 0000000..13388b4 --- /dev/null +++ b/bin/conf/slotcashmania/conf.toml @@ -0,0 +1,3 @@ +WorkID=53000 +MaxPlayerCount=10000 +[TeenpattiWar] \ No newline at end of file diff --git a/bin/conf/slotcashmania/module.json b/bin/conf/slotcashmania/module.json new file mode 100644 index 0000000..b63fafb --- /dev/null +++ b/bin/conf/slotcashmania/module.json @@ -0,0 +1,29 @@ +{ + "Module":{ + "game53000":[ + { + "Id":"game53000001", + "ProcessID":"development" + } + ] + }, + "Mqtt":{ + "WirteLoopChanNum": 10, + "ReadPackLoop": 1, + "ReadTimeout": 60, + "WriteTimeout": 60 + }, + "Rpc":{ + "MaxCoroutine":10000, + "RpcExpired": 1, + "LogSuccess":false + }, + "Log": { + "file":{ + "daily":true, + "level":7, + "maxsize":1024000000, + "maxlines":10000000 + } + } +} \ No newline at end of file diff --git a/bin/conf/slotchristmas/conf.toml b/bin/conf/slotchristmas/conf.toml new file mode 100644 index 0000000..7418e8c --- /dev/null +++ b/bin/conf/slotchristmas/conf.toml @@ -0,0 +1,3 @@ +WorkID=50000 +MaxPlayerCount=10000 +[TeenpattiWar] \ No newline at end of file diff --git a/bin/conf/slotchristmas/module.json b/bin/conf/slotchristmas/module.json new file mode 100644 index 0000000..5854c22 --- /dev/null +++ b/bin/conf/slotchristmas/module.json @@ -0,0 +1,29 @@ +{ + "Module":{ + "game50000":[ + { + "Id":"game50000001", + "ProcessID":"development" + } + ] + }, + "Mqtt":{ + "WirteLoopChanNum": 10, + "ReadPackLoop": 1, + "ReadTimeout": 60, + "WriteTimeout": 60 + }, + "Rpc":{ + "MaxCoroutine":10000, + "RpcExpired": 1, + "LogSuccess":false + }, + "Log": { + "file":{ + "daily":true, + "level":7, + "maxsize":1024000000, + "maxlines":10000000 + } + } +} \ No newline at end of file diff --git a/bin/conf/slotmaya/conf.toml b/bin/conf/slotmaya/conf.toml new file mode 100644 index 0000000..e52063d --- /dev/null +++ b/bin/conf/slotmaya/conf.toml @@ -0,0 +1,3 @@ +WorkID=55000 +MaxPlayerCount=10000 +[TeenpattiWar] \ No newline at end of file diff --git a/bin/conf/slotmaya/module.json b/bin/conf/slotmaya/module.json new file mode 100644 index 0000000..0505400 --- /dev/null +++ b/bin/conf/slotmaya/module.json @@ -0,0 +1,29 @@ +{ + "Module":{ + "game55000":[ + { + "Id":"game55000001", + "ProcessID":"development" + } + ] + }, + "Mqtt":{ + "WirteLoopChanNum": 10, + "ReadPackLoop": 1, + "ReadTimeout": 60, + "WriteTimeout": 60 + }, + "Rpc":{ + "MaxCoroutine":10000, + "RpcExpired": 1, + "LogSuccess":false + }, + "Log": { + "file":{ + "daily":true, + "level":7, + "maxsize":1024000000, + "maxlines":10000000 + } + } +} \ No newline at end of file diff --git a/bin/conf/slotminer/conf.toml b/bin/conf/slotminer/conf.toml new file mode 100644 index 0000000..c46f50e --- /dev/null +++ b/bin/conf/slotminer/conf.toml @@ -0,0 +1,3 @@ +WorkID=52000 +MaxPlayerCount=10000 +[TeenpattiWar] \ No newline at end of file diff --git a/bin/conf/slotminer/module.json b/bin/conf/slotminer/module.json new file mode 100644 index 0000000..53cc378 --- /dev/null +++ b/bin/conf/slotminer/module.json @@ -0,0 +1,29 @@ +{ + "Module":{ + "game52000":[ + { + "Id":"game52000001", + "ProcessID":"development" + } + ] + }, + "Mqtt":{ + "WirteLoopChanNum": 10, + "ReadPackLoop": 1, + "ReadTimeout": 60, + "WriteTimeout": 60 + }, + "Rpc":{ + "MaxCoroutine":10000, + "RpcExpired": 1, + "LogSuccess":false + }, + "Log": { + "file":{ + "daily":true, + "level":7, + "maxsize":1024000000, + "maxlines":10000000 + } + } +} \ No newline at end of file diff --git a/bin/conf/slotmonster/conf.toml b/bin/conf/slotmonster/conf.toml new file mode 100644 index 0000000..068aacf --- /dev/null +++ b/bin/conf/slotmonster/conf.toml @@ -0,0 +1,3 @@ +WorkID=56000 +MaxPlayerCount=10000 +[TeenpattiWar] \ No newline at end of file diff --git a/bin/conf/slotmonster/module.json b/bin/conf/slotmonster/module.json new file mode 100644 index 0000000..520dace --- /dev/null +++ b/bin/conf/slotmonster/module.json @@ -0,0 +1,29 @@ +{ + "Module":{ + "game56000":[ + { + "Id":"game56000001", + "ProcessID":"development" + } + ] + }, + "Mqtt":{ + "WirteLoopChanNum": 10, + "ReadPackLoop": 1, + "ReadTimeout": 60, + "WriteTimeout": 60 + }, + "Rpc":{ + "MaxCoroutine":10000, + "RpcExpired": 1, + "LogSuccess":false + }, + "Log": { + "file":{ + "daily":true, + "level":7, + "maxsize":1024000000, + "maxlines":10000000 + } + } +} \ No newline at end of file diff --git a/bin/conf/slotquick/conf.toml b/bin/conf/slotquick/conf.toml new file mode 100644 index 0000000..7911506 --- /dev/null +++ b/bin/conf/slotquick/conf.toml @@ -0,0 +1,3 @@ +WorkID=57000 +MaxPlayerCount=10000 +[TeenpattiWar] \ No newline at end of file diff --git a/bin/conf/slotquick/module.json b/bin/conf/slotquick/module.json new file mode 100644 index 0000000..cd13ba8 --- /dev/null +++ b/bin/conf/slotquick/module.json @@ -0,0 +1,29 @@ +{ + "Module":{ + "game57000":[ + { + "Id":"game57000001", + "ProcessID":"development" + } + ] + }, + "Mqtt":{ + "WirteLoopChanNum": 10, + "ReadPackLoop": 1, + "ReadTimeout": 60, + "WriteTimeout": 60 + }, + "Rpc":{ + "MaxCoroutine":10000, + "RpcExpired": 1, + "LogSuccess":false + }, + "Log": { + "file":{ + "daily":true, + "level":7, + "maxsize":1024000000, + "maxlines":10000000 + } + } +} \ No newline at end of file diff --git a/bin/conf/slotsurya/conf.toml b/bin/conf/slotsurya/conf.toml new file mode 100644 index 0000000..5431e2c --- /dev/null +++ b/bin/conf/slotsurya/conf.toml @@ -0,0 +1,3 @@ +WorkID=51000 +MaxPlayerCount=10000 +[TeenpattiWar] \ No newline at end of file diff --git a/bin/conf/slotsurya/module.json b/bin/conf/slotsurya/module.json new file mode 100644 index 0000000..3dd5919 --- /dev/null +++ b/bin/conf/slotsurya/module.json @@ -0,0 +1,29 @@ +{ + "Module":{ + "game51000":[ + { + "Id":"game51000001", + "ProcessID":"development" + } + ] + }, + "Mqtt":{ + "WirteLoopChanNum": 10, + "ReadPackLoop": 1, + "ReadTimeout": 60, + "WriteTimeout": 60 + }, + "Rpc":{ + "MaxCoroutine":10000, + "RpcExpired": 1, + "LogSuccess":false + }, + "Log": { + "file":{ + "daily":true, + "level":7, + "maxsize":1024000000, + "maxlines":10000000 + } + } +} \ No newline at end of file diff --git a/bin/conf/slotvegas777/conf.toml b/bin/conf/slotvegas777/conf.toml new file mode 100644 index 0000000..29c5935 --- /dev/null +++ b/bin/conf/slotvegas777/conf.toml @@ -0,0 +1,3 @@ +WorkID=54000 +MaxPlayerCount=10000 +[TeenpattiWar] \ No newline at end of file diff --git a/bin/conf/slotvegas777/module.json b/bin/conf/slotvegas777/module.json new file mode 100644 index 0000000..9bc4b2b --- /dev/null +++ b/bin/conf/slotvegas777/module.json @@ -0,0 +1,29 @@ +{ + "Module":{ + "game54000":[ + { + "Id":"game54000001", + "ProcessID":"development" + } + ] + }, + "Mqtt":{ + "WirteLoopChanNum": 10, + "ReadPackLoop": 1, + "ReadTimeout": 60, + "WriteTimeout": 60 + }, + "Rpc":{ + "MaxCoroutine":10000, + "RpcExpired": 1, + "LogSuccess":false + }, + "Log": { + "file":{ + "daily":true, + "level":7, + "maxsize":1024000000, + "maxlines":10000000 + } + } +} \ No newline at end of file diff --git a/bin/conf/web/conf.toml b/bin/conf/web/conf.toml new file mode 100644 index 0000000..e21d172 --- /dev/null +++ b/bin/conf/web/conf.toml @@ -0,0 +1,39 @@ +[Web] +Addr=":7615" +Release=true +TLS=false +CertFile="" +KeyFile="" +MaxPlayerAccountIP=2 +MaxBankCardCount=1 +PassRegion=["阿萨姆","奥里萨","锡金","那加兰","特伦甘纳","泰米尔纳德"] +BlackRegion=["马哈拉施特拉"] +IFSCURL="https://ifsc.razorpay.com" +TestWithdraw=-1 +TestPay=3 +FetchTime = 60 +OldChannels = [] +FreeSpinFirst = 500000000 +TotalWithdrawPer = 50 +BreakLimit = 100000000 +SelectID = 2 +[Web.OTP] +AntgstSmsReqURL="https://api.antgst.com/sms/txt/3/send/json" +AntgstAccessKey="5ac944dc65924f3dbfa1da2556a0f91b" +AntgstAccessSecret="f35a5d8b324d43a9be629a1f0dc62604" +AntgstModel="[TERFTE] Your OTP is {%v}. Valid for only 10 minutes." +AliSmsReqURL="http://smsapi.5taogame.com/sms/httpSmsInterface?action=sendhy" +AliSmsAccount="zygj6688" +AliSmsPass="zygj6688" +AliSmsModel="【Rummy Gold Mango】Your Verification code is %v.The verification code is valid within %v minutes.Don't share it to other people." +BukaUrl = "https://api.onbuka.com/v3/sendSms" +BukaAppID = "7g8gVTyL" +BukaAPIKey = "KFN1IKOd" +BukaAPISecret = "hX80mNh2" +BukaModel="Your Verification code is %v." +[Web.Adjust] +URL = "https://s2s.adjust.com/event" +APIURL = "https://api.adjust.com/device_service/api/v1/inspect_device" +APIToken = "_2ZGYezfdZ7yD2he-zWx" +[Web.FB] +APIURL = "https://graph.facebook.com/v18.0/" \ No newline at end of file diff --git a/bin/conf/web/gm/gm.html b/bin/conf/web/gm/gm.html new file mode 100644 index 0000000..ce993ff --- /dev/null +++ b/bin/conf/web/gm/gm.html @@ -0,0 +1,108 @@ + + + + + GM + + + +
+ + + + + +
+ +
+ + + +
+ +
+ 金额(分): + 用户ID: + 类型: + +   +   +   +   +   +   +   + + +
+ + +
+ + 选择模块: + + +   +   + + + +
+ +
+ + 内容: + 优先级: + 次数: + + +
+ +

+ 玩家匹配分 +

+ +
+ + uid: + 游戏: + 参赛类型: + +    +    + + + +
+ +
+ + uid: + 游戏: + 参赛类型: + +    +    + + 匹配分: + 难度: + +
+ + +
+ +
+ +

+ +

+ +
+ 奖池: + +
+ + + + \ No newline at end of file diff --git a/bin/conf/web/module.json b/bin/conf/web/module.json new file mode 100644 index 0000000..db979ca --- /dev/null +++ b/bin/conf/web/module.json @@ -0,0 +1,29 @@ +{ + "Module":{ + "web":[ + { + "Id":"web001", + "ProcessID":"development" + } + ] + }, + "Mqtt":{ + "WirteLoopChanNum": 10, + "ReadPackLoop": 1, + "ReadTimeout": 60, + "WriteTimeout": 60 + }, + "Rpc":{ + "MaxCoroutine":10000, + "RpcExpired": 1, + "LogSuccess":false + }, + "Log": { + "file":{ + "daily":true, + "level":7, + "maxsize":1024000000, + "maxlines":10000000 + } + } +} \ No newline at end of file diff --git a/bin/start.sh b/bin/start.sh new file mode 100644 index 0000000..2c02039 --- /dev/null +++ b/bin/start.sh @@ -0,0 +1,103 @@ +#!/bin/bash + +#nohup ./gameserver-blitz21 -module=conf/modules.json -log=logs/ -conf=conf/config.json -rule=conf/rule.toml >/dev/null 2>&1 & + +#nohup ./gameserver -module=conf/blitz21/modules.json -log=logs/blitz21 -conf=conf/blitz21/config.json -rule=conf/blitz21/rule.toml >/dev/null 2>&1 & + +#source ./stop.sh + +echo "start gameserver..." + +if [[ $# -gt 0 ]] +then + echo "stop $1..." + for f in `ls conf/$1` + do + if [[ $f =~ ^gameserver ]] + then + array=(`echo $f | tr '.' ' '` ) + echo "stop $array" + tmp=$(ps -ef | grep $array | grep -v "grep" | awk '{print $2}') + #echo $tmp + if [ "$tmp" = "" ];then + echo "$array not running" + else + kill $tmp + fi + break + fi + done +#source ./stop.sh $1 + + cp gameserver gameserver-$1 + mv gameserver-$1 conf/$1 + echo "start $1..." + tmp="-log=../../logs/$1 -baseConf=../baseConf.toml -debug=true" + for c in `ls conf/$1` + do + if [[ $c =~ ^gameserver ]] + then continue + fi + + array=(`echo $c | tr '.' ' '` ) + #echo $array + if [[ ${#array[@]} -ne 2 ]] || [[ $c =~ ^ip ]] + then continue + fi + + tmp="$tmp -$array=$c" + #echo $tmp + done + echo $tmp + cd conf/$1 + nohup ./gameserver-$1 $tmp >/dev/null 2>&1 & + exit 0 +fi + +source ./stop.sh +#cd conf/ +for f in `ls conf` +do + #echo "start $f..." + if [[ $f =~ ^_ ]] || [[ $f = *.toml ]] || [[ -f $f ]] || [[ $f = *.db ]] + then + echo "pass $f..." + continue + fi + echo "start $f..." + cp gameserver gameserver-$f + mv gameserver-$f conf/$f + + tmp="-log=../../logs/$f -baseConf=../baseConf.toml -debug=true" + for c in `ls conf/$f` + do + if [[ $c =~ ^gameserver|bi ]] + then continue + fi + array=(`echo $c | tr '.' ' '` ) + #echo $array + if [[ ${#array[@]} -ne 2 ]] || [[ $c =~ ^ip ]] + then continue + fi + tmp="$tmp -$array=$c" + #echo $tmp + done + #echo $tmp + #tmp="-module=conf/$f/modules.json -log=logs/$f -conf=conf/$f/config.json -rule=conf/$f/rule.toml" + #mv gameserver gameserver1 + #nohup ./gameserver -module=conf/$f/modules.json -log=logs/$f -conf=conf/$f/config.json -rule=conf/$f/rule.toml >/dev/null 2>&1 & + cd conf/$f + nohup ./gameserver-$f $tmp >/dev/null 2>&1 & + cd ../../ +done + +#for f in `ls` +#do +# if [[ $f =~ ^arena.*(.exe)?$ && -x $f ]] +# then +# echo "start $f" +# nohup ./$f -module=conf/modules.json -log=logs/ -conf=conf/config.json -rule=conf/rule.toml >/dev/null 2>&1 & +# fi +#done + +echo 'finish ...' diff --git a/bin/stop.sh b/bin/stop.sh new file mode 100644 index 0000000..53bbeb3 --- /dev/null +++ b/bin/stop.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +echo 'stop server...' + +if [[ $# -gt 0 ]] +then + echo "stop $1..." + for f in `ls conf/$1` + do + if [[ $f =~ ^gameserver ]] + then + array=(`echo $f | tr '.' ' '` ) + #echo $array + echo "stop $array" + tmp=$(ps -ef | grep $array | grep -v "grep" | awk '{print $2}') + #echo $tmp + if [ "$tmp" = "" ];then + echo "$array not running" + else + kill $tmp + fi + break + fi + done + exit 0 +fi + +for f in `ls` +do + if [[ $f =~ ^.*robot(.exe)?|gameserver.*(.exe)?$ && -x $f ]] + then + array=(`echo $f | tr '.' ' '` ) + echo "stop $array" + tmp=$(ps -ef | grep $array | grep -v "grep" | awk '{print $2}') + #echo $tmp + if [ "$tmp" = "" ];then + echo "$array not running" + else + kill $tmp + fi + fi +done + +#ps -ef | grep "gateway_server\ -port" | grep -v "grep" | awk '{print $2}' | xargs kill -9 + +echo 'stop ok' diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..7da0625 --- /dev/null +++ b/build.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -e +cd pb/proto +./gener.sh +# cd ../../ +# cd tools +# go generate +cd ../.. +go build main.go diff --git a/call/Snowflake.go b/call/Snowflake.go new file mode 100644 index 0000000..4b00c99 --- /dev/null +++ b/call/Snowflake.go @@ -0,0 +1,22 @@ +package call + +import ( + "github.com/bwmarrin/snowflake" +) + +var ( + snowNode *snowflake.Node +) + +func NewSnowflake(id int64) { + id = id % 1023 + var err error + snowNode, err = snowflake.NewNode(id) + if err != nil { + panic(err) + } +} + +func SnowNode() *snowflake.Node { + return snowNode +} diff --git a/call/activity.go b/call/activity.go new file mode 100644 index 0000000..7aa6441 --- /dev/null +++ b/call/activity.go @@ -0,0 +1,328 @@ +package call + +import ( + "fmt" + "server/common" + "server/config" + "server/db" + "server/pb" + "server/util" + "time" + + "github.com/liangdas/mqant/log" + "github.com/olivere/elastic/v7" + "gorm.io/gorm" +) + +func IsActivityValid(actID int) bool { + act := GetConfigActivityByID(actID) + if act == nil { + return false + } + return act.IsValid() +} + +// NotifyActivityItem 通知客户端获得活动物品 +func NotifyActivityItem(uid, activityID int, items []*pb.ActivityItem) { + activityNotify := &pb.ActivityResp{ + ActivityID: int64(activityID), + ActivityItems: items, + } + SendNR(uid, int(pb.ServerCommonResp_CommonActivityItemResp), activityNotify, "common") +} + +func GetAcitivityPddData(uid int) *common.PddData { + pddData := &common.PddData{UID: uid} + db.Mysql().Get(pddData) + con := GetConfigActivityPdd() + if con == nil { + return nil + } + now := time.Now().Unix() + if now-pddData.Time < con.Expire*60 { + // 每天有一次免费旋转 + if !util.IsSameDayTimeStamp(now, pddData.FreeSpinTime) { + pddData.Spin++ + } + return pddData + } + resetSpinCount := 1 + if !config.GetBase().Release { + resetSpinCount = 100 + } + if pddData.ID == 0 { + pddData.Time = now + pddData.Spin = resetSpinCount + db.Mysql().Create(pddData) + } else { + db.Mysql().Update(&common.PddData{UID: pddData.UID, Time: pddData.Time}, + map[string]interface{}{"amount": 0, "time": now, "Spin": resetSpinCount, "free_spin_time": 0, "new_record_time": 0}) + pddData.Amount = 0 + pddData.Time = now + pddData.Spin = resetSpinCount + pddData.FreeSpinTime = 0 + pddData.NewRecordTime = 0 + } + return pddData +} + +// 是否有新邀请 +func HasNewAcitivityPddShare(uid int) bool { + if !IsActivityValid(common.ActivityIDPDD) { + return false + } + pddData := GetAcitivityPddData(uid) + if pddData.NewRecordTime <= 0 { + return false + } + q := elastic.NewBoolQuery() + q.Filter(elastic.NewTermQuery("Referer", uid)) + q.Filter(elastic.NewRangeQuery("Time").Gte(pddData.NewRecordTime)) + return db.ES().Count(common.ESIndexBackPddRecord, q) > 0 +} + +func GetUserFreeSpinData(uid int) *common.ActivityFreeSpinData { + data := &common.ActivityFreeSpinData{UID: uid} + db.Mysql().Get(data) + return data +} + +func GetUserFirstRechargeBackData(uid int) *common.ActivityFirstRechargeBackData { + data := &common.ActivityFirstRechargeBackData{UID: uid} + db.Mysql().Get(data) + return data +} + +func ShouldShowActivityFirstRechargeBack(uid int) bool { + if !IsActivityValid(common.ActivityIDFirstRechargeBack) { + return false + } + if uid == 0 { + return true + } + now := time.Now().Unix() + data := GetUserFirstRechargeBackData(uid) + if data.RechargeTime == 0 { + return true + } + if now-data.RechargeTime > 2*common.ActivityFirstRechargeBackTime { + return false + } + if now-data.RechargeTime > common.ActivityFirstRechargeBackTime && data.Lost == 0 { + return false + } + return true +} + +func UploadActivityData(uid, activityID, t int, amount int64) { + db.ES().InsertToESGO(common.ESIndexBackActivity, &common.ESActivity{ + ActivityID: activityID, + UID: uid, + Time: time.Now().Unix(), + Type: t, + Amount: amount, + }) +} + +func ShouldShowActivitySign(uid int) bool { + if !IsActivityValid(common.ActivityIDSign) { + return false + } + if uid == 0 { + return true + } + p, _ := GetUserXInfo(uid, "birth") + now := util.GetZeroTime(time.Now()).Unix() + birth := util.GetZeroTime(time.Unix(p.Birth, 0)).Unix() + return now-birth <= 6*common.OneDay +} + +func GetUserWeekCard(uid, level int) *common.ActivityWeekCardData { + data := &common.ActivityWeekCardData{UID: uid, Level: level} + db.Mysql().Get(data) + return data +} + +func GetUserWeekCards(uid int) []*common.ActivityWeekCardData { + list := []*common.ActivityWeekCardData{} + db.Mysql().QueryAll(fmt.Sprintf("uid = %d", uid), "", &common.ActivityWeekCardData{}, &list) + return list +} + +func ShouldShowActivityWeekCard(uid int) bool { + if !IsActivityValid(common.ActivityIDWeekCard) { + return false + } + if uid == 0 { + return true + } + cards := GetUserWeekCards(uid) + for _, v := range cards { + if v.Day > 0 { + return false + } + } + return true +} + +func GetUserActivitySlotsData(uid int) *common.ActivitySlotsData { + data := &common.ActivitySlotsData{UID: uid} + db.Mysql().Get(data) + now := time.Now().Unix() + isSingle := IsActivitySingleDay(common.ActivityIDSlots) + t := data.Time2 + if isSingle { + t = data.Time1 + } + if !util.IsSameDayTimeStamp(now, t) { + data.Spin = 0 + } + if data.ID == 0 { + if isSingle { + data.Time1 = now + } else { + data.Time2 = now + } + p, _ := GetUserXInfo(uid, "mobile", "avatar") + data.Avatar = p.Avatar + data.Nick = p.Nick + db.Mysql().Create(data) + } + return data +} + +func UpdateUserActivitySlotsData(uid int, count int) { + data := &common.ActivitySlotsData{UID: uid} + db.Mysql().Get(data) + now := time.Now().Unix() + isSingle := IsActivitySingleDay(common.ActivityIDSlots) + if data.ID == 0 { + if isSingle { + data.Time1 = now + } else { + data.Time2 = now + } + data.Spin = count + db.Mysql().Create(data) + return + } + update := map[string]interface{}{} + t := data.Time2 + str := "time2" + if isSingle { + t = data.Time1 + str = "time1" + } + if !util.IsSameDayTimeStamp(now, t) { + update["spin"] = count + update[str] = now + } else { + update["spin"] = gorm.Expr("spin + ?", count) + } + log.Debug("UpdateUserActivitySlotsData uid:%v,count:%v,isSingle:%v,update:%v", uid, count, isSingle, update) + db.Mysql().Update(&common.ActivitySlotsData{UID: uid}, update) +} + +func ShouldShowActivityLuckShop(uid int) bool { + if !IsActivityValid(common.ActivityIDLuckyShop) { + return false + } + if uid == 0 { + return true + } + return GetShowActivityLuckShopData(uid) != nil +} + +func GetShowActivityLuckShopData(uid int) *common.ActivityLuckyShopData { + if uid == 0 { + return nil + } + re := GetRechargeInfo(uid) + if re.TotalRecharge == 0 { + return nil + } + datas := []*common.ActivityLuckyShopData{} + db.Mysql().QueryAll(fmt.Sprintf("uid = %d", uid), "type asc", &common.ActivityLuckyShopData{}, &datas) + now := time.Now().Unix() + // 先判断首充礼包,两种礼包互斥,弹了其中一种,另一种不会弹了 + var rechargeData *common.ActivityLuckyShopData + for i, v := range datas { + if v.Type == common.ActivityLuckyShopTypeRechargeLess || v.Type == common.ActivityLuckyShopTypeRechargeMore { + rechargeData = datas[i] + break + } + } + if rechargeData != nil && rechargeData.IsValid() { + return rechargeData + } + + for i := common.ActivityLuckyShopType + 1; i < common.ActivityLuckyShopTypeAll; i++ { + // 如果已有首充礼包,那么直接跳过后续判断 + if rechargeData != nil { + if i == common.ActivityLuckyShopTypeRechargeLess || i == common.ActivityLuckyShopTypeRechargeMore { + continue + } + } + con := GetConfigActivityLuckShop(i) + var tmp *common.ActivityLuckyShopData + for i, v := range datas { + if con.Type == v.Type { + tmp = datas[i] + break + } + } + if tmp == nil { + if con.Type == common.ActivityLuckyShopTypeRechargeLess { + if re.TotalRecharge < con.Recharge { + return &common.ActivityLuckyShopData{UID: uid, Type: con.Type, Push: now, ProductID: con.ProductID} + } + continue + } + if con.Type == common.ActivityLuckyShopTypeRechargeMore { + if re.TotalRecharge >= con.Recharge { + return &common.ActivityLuckyShopData{UID: uid, Type: con.Type, Push: now, ProductID: con.ProductID} + } + continue + } + p, _ := GetUserXInfo(uid, "birth") + tmp := p.Birth + 3*24*3600 + if util.IsSameDayTimeStamp(now, tmp) || now > tmp { + return &common.ActivityLuckyShopData{UID: uid, Type: con.Type, Push: now, ProductID: con.ProductID} + } + } else if tmp.IsValid() { + return tmp + } + } + return nil +} + +// IsActivitySingleDay 返回活动是否是单数天 +func IsActivitySingleDay(id int) bool { + act := GetConfigActivityByID(id) + if act == nil || !act.IsValid() { + return false + } + // if config.GetBase().Release { + diff := int((util.GetZeroTime(time.Now()).Unix() - util.GetZeroTime(time.Unix(act.Start, 0)).Unix()) / (24 * 60 * 60)) + return util.IsSingle(diff) + // } + // now := time.Now() + // _, m, _ := now.Clock() + // return util.IsSingle(m / 5) +} + +func GetUserActivitySuperData(uid int) *common.ActivitySuperData { + p, _ := GetUserXInfo(uid, "birth") + data := &common.ActivitySuperData{UID: uid} + db.Mysql().Get(data) + if util.GetZeroTime(time.Now()).Unix()-util.GetZeroTime(time.Unix(p.Birth, 0)).Unix() < 5*common.OneDay { + data.Type = 1 + } else { + data.Type = 2 + } + data.CanBuy = !util.IsSameDayTimeStamp(time.Now().Unix(), data.Time) + if data.ID == 0 { + db.Mysql().Create(data) + } + return data +} diff --git a/call/config.go b/call/config.go new file mode 100644 index 0000000..381fe3d --- /dev/null +++ b/call/config.go @@ -0,0 +1,1448 @@ +package call + +import ( + "encoding/json" + "errors" + "net" + "server/common" + "server/config" + "server/db" + "server/util" + "sort" + "time" + + "github.com/liangdas/mqant/log" + "github.com/oschwald/geoip2-golang" +) + +var ( + channels []*common.Channel + region *geoip2.Reader + configPlatform *common.ConfigPlatform + configRWPer []*common.ConfigRWPer + configActivity []*common.ConfigActivity + configPayProduct []*common.ConfigPayProduct + ConfigPayChannels []*common.ConfigPayChannels + configWithdrawChannels []*common.ConfigWithdrawChannels + configVIP []*common.ConfigVIP + configH5 *common.ConfigH5 + ConfigTron *common.ConfigTron + configWithdrawProduct []*common.ConfigWithdrawProduct + configGameList []*common.ConfigGameList + configGameProvider []*common.ConfigGameProvider + configGameTypes []*common.ConfigGameType + configGameMarks []*common.ConfigGameMark + configFirstPageGames []*common.ConfigFirstPageGames + configBroadcast []*common.ConfigBroadcast + configNotice []*common.ConfigNotice + configCurrencyRateUSD []*common.ConfigCurrencyRateUSD + configServerVersions []*common.ServerVersion + configGameRooms []*common.ConfigGameRoom + configRobots []*common.ConfigRobot + configAppSpin []*common.ConfigAppSpin + configShare []*common.ConfigShare + configShareSys *common.ConfigShareSys + configActivityPddSpin []*common.ConfigActivityPddSpin + configActivityPdd *common.ConfigActivityPdd + configTask []*common.ConfigTask + configCurrencyResource []*common.ConfigCurrencyResource + configFirstPay []*common.ConfigFirstPay + configActivityFreeSpin []*common.ConfigActivityFreeSpin + configActivityFirstRechargeBack *common.ConfigActivityFirstRechargeBack + configActivityLuckyCode []*common.ConfigActivityLuckyCode + configBanner []*common.ConfigBanner + configActivitySign []*common.ConfigActivitySign + configActivityBreakGift []*common.ConfigActivityBreakGift + configShareRobot []*common.ConfigShareRobot + configActivityWeekCard []*common.ConfigActivityWeekCard + configActivitySlots []*common.ConfigActivitySlots + configActivityLuckyShop []*common.ConfigActivityLuckyShop + configServerFlag []*common.ConfigServerFlag + configActivitySevenDayBox []*common.ConfigActivitySevenDayBox + configActivitySuper []*common.ConfigActivitySuper +) + +var ( + gameListTimer *time.Timer + gameListReloadTime = 1 * time.Minute +) + +// 加载渠道信息 +func LoadChannels() (err error) { + one := []*common.Channel{} + if err := db.Mysql().C().Find(&one).Error; err != nil { + log.Error("GetChannels err:%v", err) + return err + } + channels = one + return nil +} + +func GetChannelListReal() []*common.Channel { + one := []*common.Channel{} + if err := db.Mysql().C().Find(&one).Error; err != nil { + log.Error("GetChannels err:%v", err) + return nil + } + return one +} + +func GetChannelList() []*common.Channel { + if channels == nil { + if err := LoadChannels(); err != nil { + log.Error("err:%v", err) + return nil + } + } + return channels +} + +func GetChannelByID(id int) *common.Channel { + if channels == nil { + if err := LoadChannels(); err != nil { + log.Error("err:%v", err) + return nil + } + } + for i, v := range channels { + if int(v.ChannelID) == id { + return channels[i] + } + } + return nil +} + +func GetChannelByURL(url string) *common.Channel { + for i, v := range channels { + if v.URL == url { + return channels[i] + } + } + return nil +} + +// LoadIpDB 初始化ip数据库 +func LoadIpDB() (err error) { + if region != nil { + region.Close() + } + region, err = geoip2.Open("../city.mmdb") + if err != nil { + log.Error("err:%v", err) + return + } + return +} + +// SearchIP 查询ip +func SearchIP(ip string) (ipInfo *geoip2.City, err error) { + if region == nil { + if err = LoadIpDB(); err != nil { + return + } + } + pip := net.ParseIP(ip) + return region.City(pip) +} + +func GetCountry(ip string) string { + pip := net.ParseIP(ip) + data, err := region.City(pip) + if err != nil { + log.Error("err:%v", err) + return "" + } + return data.Country.IsoCode +} + +// LoadConfigPlatform 加载平台配置 +func LoadConfigPlatform() (err error) { + one := new(common.ConfigPlatform) + if err := db.Mysql().Get(one); err != nil { + return err + } + configPlatform = one + return nil +} + +// GetConfigPlatform 获取平台配置 +func GetConfigPlatform() *common.ConfigPlatform { + if configPlatform == nil { + if err := LoadConfigPlatform(); err != nil { + return nil + } + } + return configPlatform +} + +// LoadConfigRWPer 加载充提比配置 +func LoadConfigRWPer() (err error) { + one := []*common.ConfigRWPer{} + if _, err := db.Mysql().QueryAll("", "down", &common.ConfigRWPer{}, &one); err != nil { + return err + } + configRWPer = one + return nil +} + +// LoadConfigRWPer 获取充提比配置 +func GetConfigRWPerByRecharge(recharge int64, t int) int64 { + for _, v := range configRWPer { + if v.Type != t { + continue + } + if recharge >= v.Down && (recharge <= v.Up || v.Up < 0) { + return v.Per + } + } + return 0 +} + +// CanAutoWithdraw 是否可自动退出 +func CanAutoWithdraw(uid int, recharge, withdraw int64) bool { + if !config.GetBase().Release { + return false + } + t := common.RWPerTypeMulti + if withdraw == 0 { + t = common.RWPerTypeFirst + } + per := GetConfigRWPerByRecharge(recharge, t) + log.Debug("uid:%d,per:%d,recharge:%d,withdraw:%d", uid, per, recharge, withdraw) + if per == 0 { + return false + } + return recharge*per >= withdraw*100 +} + +// LoadConfigActivity 加载活动总配置表 +func LoadConfigActivity() (err error) { + one := []*common.ConfigActivity{} + if _, err := db.Mysql().QueryAll("", "sort", &common.ConfigActivity{}, &one); err != nil { + return err + } + configActivity = one + for _, v := range configActivity { + log.Debug("finish load activity:%+v", *v) + } + return nil +} + +// GetConfigActivity 获取活动配置 +func GetConfigActivityAll() []*common.ConfigActivity { + return configActivity +} + +// GetConfigActivityActiveAll 获取所有开放中的活动配置 +func GetConfigActivityActiveAll(uid int) []*common.ConfigActivity { + ret := []*common.ConfigActivity{} + for _, v := range configActivity { + if !v.IsValid() { + continue + } + if v.ActivityID == common.ActivityIDFirstRechargeBack && !ShouldShowActivityFirstRechargeBack(uid) { + continue + } + if v.Push != 1 { + continue + } + ret = append(ret, v) + } + return ret +} + +// GetConfigActivity 获取活动配置 +func GetConfigActivityByID(actID int) *common.ConfigActivity { + for _, v := range configActivity { + if v.ActivityID == actID { + return v + } + } + return nil +} + +// LoadConfigPayProduct 加载商品配置表 +func LoadConfigPayProduct() (err error) { + one := []*common.ConfigPayProduct{} + if _, err := db.Mysql().QueryAll("", "type", &common.ConfigPayProduct{}, &one); err != nil { + return err + } + sort.Slice(one, func(i, j int) bool { + a := one[i] + b := one[j] + if a.Type > b.Type { + return false + } else if a.Type < b.Type { + return true + } + return a.Sort < b.Sort + }) + configPayProduct = one + return nil +} + +// GetConfigPayProduct 获取商品配置 +func GetConfigPayProduct() []*common.ConfigPayProduct { + if len(configPayProduct) == 0 { + if err := LoadConfigPayProduct(); err != nil { + return nil + } + } + return configPayProduct +} + +// GetConfigPayProductByKind 获取商品配置 +func GetConfigPayProductByKind(kind int) []*common.ConfigPayProduct { + if len(configPayProduct) == 0 { + if err := LoadConfigPayProduct(); err != nil { + return nil + } + } + ret := []*common.ConfigPayProduct{} + for _, v := range configPayProduct { + if v.Kind == kind { + ret = append(ret, v) + } + } + return ret +} + +func GetConfigPayProductByID(id int) *common.ConfigPayProduct { + if len(configPayProduct) == 0 { + if err := LoadConfigPayProduct(); err != nil { + return nil + } + } + for _, v := range configPayProduct { + if v.ProductID == id { + return v + } + } + return nil +} + +func GetConfigPayProductByActivityID(actID int) []*common.ConfigPayProduct { + ret := []*common.ConfigPayProduct{} + for _, v := range configPayProduct { + if v.ActivityID == actID { + ret = append(ret, v) + } + } + return ret +} + +// LoadConfigPayChannels 加载代收渠道配置 +func LoadConfigPayChannels() (err error) { + var one []*common.ConfigPayChannels + if _, err = db.Mysql().QueryAll("", "pay_per desc", &common.ConfigPayChannels{}, &one); err != nil { + log.Error("err:%v", err) + return err + } + ret := []*common.ConfigPayChannels{} + for _, v := range one { + if v.PayPer <= 0 { + continue + } + ret = append(ret, v) + log.Debug("finish load channel:%+v", *v) + } + ConfigPayChannels = ret + return nil +} + +// GetConfigPayChannels 获取代收渠道配置 +func GetConfigPayChannels() []*common.ConfigPayChannels { + ret := []*common.ConfigPayChannels{} + for i, v := range ConfigPayChannels { + if v.PayPer <= 0 { + continue + } + ret = append(ret, ConfigPayChannels[i]) + } + return ret +} + +// LoadConfigWithdrawChannels 加载代付渠道配置 +func LoadConfigWithdrawChannels() (err error) { + var one []*common.ConfigWithdrawChannels + if _, err = db.Mysql().QueryAll("", "withdraw_per desc", &common.ConfigWithdrawChannels{}, &one); err != nil { + log.Error("err:%v", err) + return err + } + configWithdrawChannels = one + return nil +} + +// GetConfigWithdrawChannels 获取代付渠道配置 +func GetConfigWithdrawChannels() []*common.ConfigWithdrawChannels { + ret := []*common.ConfigWithdrawChannels{} + for i, v := range configWithdrawChannels { + if v.WithdrawPer <= 0 { + continue + } + ret = append(ret, configWithdrawChannels[i]) + } + return ret +} + +// GetConfigWithdrawLimits 获取赠送上下限配置 +func GetConfigWithdrawLimits() (down, up int64) { + for _, v := range configWithdrawChannels { + if v.PayDown < down || down == 0 { + down = v.PayDown + } + if up < v.PayUp { + up = v.PayUp + } + } + return +} + +// GetConfigWithdrawChannelsByID 获取代付渠道配置 +func GetConfigWithdrawChannelsByID(id int, currencyType common.CurrencyType) *common.ConfigWithdrawChannels { + for i, v := range configWithdrawChannels { + if v.ChannelID == id && v.CurrencyType == currencyType { + return configWithdrawChannels[i] + } + } + return nil +} + +// GetConfigWithdrawChannelsBest 选择推荐的代付渠道 +func GetConfigWithdrawChannelsBest(currencyType common.CurrencyType) *common.ConfigWithdrawChannels { + for i, v := range configWithdrawChannels { + if v.CurrencyType == currencyType && v.WithdrawPer > 0 { + return configWithdrawChannels[i] + } + } + return nil +} + +// LoadConfigVIP 加载vip配置 +func LoadConfigVIP() (err error) { + var one []*common.ConfigVIP + if _, err = db.Mysql().QueryAll("", "level", &common.ConfigVIP{}, &one); err != nil { + log.Error("err:%v", err) + return err + } + configVIP = one + return nil +} + +// GetConfigVIP 获取vip配置 +func GetConfigVIP() []*common.ConfigVIP { + return configVIP +} + +// GetConfigVIPByLevel 获取vip配置 +func GetConfigVIPByLevel(level int) *common.ConfigVIP { + for _, v := range configVIP { + if v.Level == level { + return v + } + } + return nil +} + +// LoadConfigH5 h5配置 +func LoadConfigH5() (err error) { + one := new(common.ConfigH5) + err = db.Mysql().Get(one) + if err != nil { + return + } + configH5 = one + return +} + +// GetConfigH5 h5配置 +func GetConfigH5() *common.ConfigH5 { + return configH5 +} + +// LoadConfigTron tron配置 +func LoadConfigTron() (err error) { + one := new(common.ConfigTron) + err = db.Mysql().Get(one) + if err != nil { + return + } + ConfigTron = one + return nil +} + +// GetConfigTron tron配置 +func GetConfigTron() *common.ConfigTron { + return ConfigTron +} + +// LoadConfigWithdrawProduct 退出商品 +func LoadConfigWithdrawProduct() (err error) { + one := []*common.ConfigWithdrawProduct{} + if _, err := db.Mysql().QueryAll("", "", &common.ConfigWithdrawProduct{}, &one); err != nil { + return err + } + configWithdrawProduct = one + return nil +} + +// GetConfigWithdrawProduct 退出商品 +func GetConfigWithdrawProduct(t ...common.CurrencyType) []*common.ConfigWithdrawProduct { + if len(t) > 0 { + ret := []*common.ConfigWithdrawProduct{} + for _, v := range configWithdrawProduct { + if v.Type == t[0] { + ret = append(ret, v) + } + } + return ret + } + return configWithdrawProduct +} + +func GetConfigWithdrawProductByID(id int) *common.ConfigWithdrawProduct { + for _, v := range configWithdrawProduct { + if v.ProductID == id { + return v + } + } + return nil +} + +// LoadGames 无论修改供应商还是单个游戏,都会触发所有重新加载 +func LoadGames() (err error) { + if gameListTimer != nil { + gameListTimer.Reset(gameListReloadTime) + } else { + // 每分钟刷新一次 + gameListTimer = time.AfterFunc(gameListReloadTime, func() { + LoadGames() + }) + } + providers := []*common.ConfigGameProvider{} + if _, err = db.Mysql().QueryAll("", "sort", &common.ConfigGameProvider{}, &providers); err != nil { + log.Error("err:%v", err) + return err + } + for _, v := range providers { + if v.WhiteIPs != "" { + err := json.Unmarshal([]byte(v.WhiteIPs), &v.SubIp) + if err != nil { + log.Error("err:%v", err) + } + } + } + tmplist := []*common.ConfigGameList{} + if _, err = db.Mysql().QueryAll("open = 1", "sort desc", &common.ConfigGameList{}, &tmplist); err != nil { + log.Error("err:%v", err) + return err + } + list := []*common.ConfigGameList{} + for _, v := range tmplist { + for _, k := range providers { + if v.GameProvider == k.ProviderID { + if k.Open == 1 { + list = append(list, v) + } + break + } + } + } + for _, v := range providers { + count := 0 + for _, j := range list { + if j.GameProvider == v.ProviderID { + count++ + } + } + v.GamesNum = count + } + configGameProvider = providers + // sort.SliceStable(list, func(i int, j int) bool { + // sortI := GetConfigGameProvider(list[i].GameProvider).Sort + // sortJ := GetConfigGameProvider(list[j].GameProvider).Sort + // if sortI < sortJ { + // return true + // } else if sortI > sortJ { + // return false + // } + // return list[i].Sort < list[j].Sort + // }) + configGameList = list + return nil +} + +// 游戏提供商 +// func LoadConfigGameProvider() (err error) { +// one := []*common.ConfigGameProvider{} +// if _, err = db.Mysql().QueryAll("", "sort", &common.ConfigGameProvider{}, &one); err != nil { +// log.Error("err:%v", err) +// return err +// } +// if len(configGameList) == 0 { +// LoadConfigGameList() +// } +// for _, v := range configGameProvider { +// count := 0 +// for _, j := range configGameList { +// if j.GameProvider == v.ProviderID { +// count++ +// } +// } +// v.GamesNum = count +// } +// configGameProvider = one +// return nil +// } + +// 游戏提供商 +func GetConfigGameProviderAll() []*common.ConfigGameProvider { + return configGameProvider +} + +// 游戏提供商 +func GetConfigGameProviderAllOpen() []*common.ConfigGameProvider { + ret := []*common.ConfigGameProvider{} + for _, v := range configGameProvider { + if v.Show == 2 { + continue + } + if v.Open == 2 { + continue + } + ret = append(ret, v) + } + return ret +} + +// 游戏提供商 +func GetConfigGameProviderByName(name string) *common.ConfigGameProvider { + for _, v := range configGameProvider { + if v.ProviderName == name { + return v + } + } + return nil +} + +// 游戏提供商 +func GetConfigGameProviderByPath(path string) *common.ConfigGameProvider { + for _, v := range configGameProvider { + if v.Callback == path { + return v + } + } + return nil +} + +// 游戏提供商 +func GetConfigGameProvider(provider int) *common.ConfigGameProvider { + // if len(configGameProvider) == 0 { + // LoadConfigGameProvider() + // } + for _, v := range configGameProvider { + if v.ProviderID == provider { + return v + } + } + return nil +} + +// 游戏列表配置 +// func LoadConfigGameList() (err error) { +// one := []*common.ConfigGameList{} +// if _, err = db.Mysql().QueryAll("", "sort", &common.ConfigGameList{}, &one); err != nil { +// log.Error("err:%v", err) +// return err +// } +// sort.SliceStable(one, func(i int, j int) bool { +// sortI := GetConfigGameProvider(one[i].GameProvider).Sort +// sortJ := GetConfigGameProvider(one[j].GameProvider).Sort +// if sortI < sortJ { +// return true +// } else if sortI > sortJ { +// return false +// } +// return one[i].Sort < one[j].Sort +// }) +// configGameList = one +// return nil +// } + +// 获取游戏列表配置 num拉取的个数,cond 约定第一个为provider,第二个为mark,第三个为type +func GetConfigGameList(num int, cond ...int) []*common.ConfigGameList { + if len(cond) == 0 { + if num <= 0 || num > len(configGameList) { + return configGameList + } + return configGameList[:num] + } + ret := []*common.ConfigGameList{} + for _, v := range configGameList { + if v.GameType == common.GameTypeSportBook || v.GameType == common.GameTypeESport { // 屏蔽sport + continue + } + if cond[0] != 0 && v.GameProvider != cond[0] { + continue + } + if len(cond) > 1 { + if cond[1] != 0 && v.Mark != cond[1] { + continue + } + } + if len(cond) > 2 { + if cond[2] != 0 && v.GameType != cond[2] { + continue + } + } + ret = append(ret, v) + if num > 0 && len(ret) >= num { + return ret + } + } + return ret +} + +func GetConfigGameListByID(provider, gameID int) *common.ConfigGameList { + for _, v := range configGameList { + if v.GameProvider == provider && v.GameID == gameID { + return v + } + } + return nil +} + +func GetConfigGameListByType(t int) []*common.ConfigGameList { + ret := []*common.ConfigGameList{} + for _, v := range configGameList { + if v.GameType == t { + ret = append(ret, v) + } + } + return ret +} + +func GetConfigGameListByCode(provider int, gameCode string) *common.ConfigGameList { + for _, v := range configGameList { + if v.GameProvider == provider && v.GameCode == gameCode { + return v + } + } + return nil +} + +// 游戏类别配置 +func LoadConfigGameTypes() (err error) { + one := []*common.ConfigGameType{} + if _, err = db.Mysql().QueryAll("open = 1", "sort", &common.ConfigGameType{}, &one); err != nil { + log.Error("err:%v", err) + return err + } + configGameTypes = one + return nil +} + +// 游戏类别配置 +func GetConfigGameTypes() []*common.ConfigGameType { + return configGameTypes +} + +// 游戏角标 +func LoadConfigGameMarks() (err error) { + one := []*common.ConfigGameMark{} + if _, err = db.Mysql().QueryAll("", "sort", &common.ConfigGameMark{}, &one); err != nil { + log.Error("err:%v", err) + return err + } + configGameMarks = one + return nil +} + +// 游戏角标 +func GetConfigGameMarks() []*common.ConfigGameMark { + return configGameMarks +} + +// 游戏角标 +func GetConfigGameMarkByName(name string) *common.ConfigGameMark { + for _, v := range configGameMarks { + if v.MarkName == name { + return v + } + } + return nil +} + +// 首页游戏 +func LoadConfigFirstPageGames() (err error) { + one := []*common.ConfigFirstPageGames{} + if _, err = db.Mysql().QueryAll("open = 1", "sort", &common.ConfigFirstPageGames{}, &one); err != nil { + log.Error("err:%v", err) + return err + } + configFirstPageGames = one + return nil +} + +// 首页游戏 +func GetConfigFirstPageGames() []*common.ConfigFirstPageGames { + return configFirstPageGames +} + +// 首页游戏 +func LoadConfigBroadcast() (err error) { + one := []*common.ConfigBroadcast{} + if _, err = db.Mysql().QueryAll("", "", &common.ConfigBroadcast{}, &one); err != nil { + log.Error("err:%v", err) + return err + } + configBroadcast = one + return nil +} + +// 首页游戏 +func GetConfigBroadcast() []*common.ConfigBroadcast { + return configBroadcast +} + +func GetBroadcastConfigWithID(id int) *common.ConfigBroadcast { + for _, v := range configBroadcast { + if v.BroadcastID == id { + return v + } + } + return nil +} + +// notice +func LoadConfigNotice() (err error) { + one := []*common.ConfigNotice{} + if _, err = db.Mysql().QueryAll("", "", &common.ConfigNotice{}, &one); err != nil { + log.Error("err:%v", err) + return err + } + configNotice = one + return nil +} + +func GetConfigNotice() []*common.ConfigNotice { + return configNotice +} + +func LoadConfigCurrencyRateUSD() (err error) { + one := []*common.ConfigCurrencyRateUSD{} + if _, err = db.Mysql().QueryAll("", "", &common.ConfigCurrencyRateUSD{}, &one); err != nil { + log.Error("err:%v", err) + return err + } + configCurrencyRateUSD = one + return nil +} + +func GetConfigCurrencyRateUSD(t common.CurrencyType) int64 { + for _, v := range configCurrencyRateUSD { + if v.CurrencyType == t { + return v.Rate + } + } + return 0 +} + +func LoadServerVersions() error { + one := []*common.ServerVersion{} + if err := db.Mysql().C().Find(&one).Error; err != nil { + log.Error("LoadServerVersions err:%v", err) + return err + } + configServerVersions = one + return nil +} + +func GetServerVersion(serverID int) int { + for _, v := range configServerVersions { + if serverID == v.ServerID { + return v.Version + } + } + return 0 +} + +func IsMainServer(serverID int) bool { + for _, v := range configServerVersions { + if serverID == v.ServerID { + return config.GetConfig().Version == v.Version + } + } + return true +} + +func LoadConfigGameRooms() (err error) { + one := []*common.ConfigGameRoom{} + if _, err = db.Mysql().QueryAll("", "", &common.ConfigGameRoom{}, &one); err != nil { + log.Error("err:%v", err) + return err + } + for _, v := range one { + if v.BetLimitStr != "" { + err := json.Unmarshal([]byte(v.BetLimitStr), &v.BetLimit) + if err != nil { + log.Error("err:%v", err) + } + } + } + configGameRooms = one + return nil +} + +func GetConfigGameRoom(gid int) []*common.ConfigGameRoom { + list := []*common.ConfigGameRoom{} + for _, v := range configGameRooms { + if v.GameID == gid { + list = append(list, v) + } + } + return list +} + +func GetConfigWaterReal(gid, rid int) *common.ConfigWater { + con := &common.ConfigWater{GameID: gid, RoomID: rid} + db.Mysql().Get(con) + if con.ID == 0 { + return nil + } + return con +} + +func LoadConfigRobots() (err error) { + one := []*common.ConfigRobot{} + if _, err = db.Mysql().QueryAll("", "", &common.ConfigRobot{}, &one); err != nil { + log.Error("err:%v", err) + return err + } + configRobots = one + return nil +} + +func GetConfigRobots() []*common.ConfigRobot { + return configRobots +} + +func LoadConfigAppSpin() (err error) { + one := []*common.ConfigAppSpin{} + if _, err = db.Mysql().QueryAll("", "sort desc", &common.ConfigAppSpin{}, &one); err != nil { + log.Error("err:%v", err) + return err + } + configAppSpin = one + return nil +} + +func GetConfigAppSpin() []*common.ConfigAppSpin { + return configAppSpin +} + +func LoadConfigShare() (err error) { + one := []*common.ConfigShare{} + if _, err = db.Mysql().QueryAll("", "level", &common.ConfigShare{}, &one); err != nil { + log.Error("err:%v", err) + return err + } + configShare = one + return nil +} + +func GetConfigShare() []*common.ConfigShare { + return configShare +} + +func GetConfigShareLevelByBet(bet int64) *common.ConfigShare { + for i := len(configShare) - 1; i >= 0; i-- { + if bet >= configShare[i].Bet { + return configShare[i] + } + } + return nil +} + +func GetConfigShareByLevel(level int) *common.ConfigShare { + for _, v := range configShare { + if level == v.Level { + return v + } + } + return nil +} + +// LoadConfigShareSys 分享系统配置 +func LoadConfigShareSys() (err error) { + one := new(common.ConfigShareSys) + err = db.Mysql().Get(one) + if err != nil { + return + } + configShareSys = one + return +} + +func GetConfigShareSys() *common.ConfigShareSys { + return configShareSys +} + +// LoadConfigActivityPdd 拼多多配置 +func LoadConfigActivityPdd() (err error) { + one := new(common.ConfigActivityPdd) + err = db.Mysql().Get(one) + if err != nil { + return + } + configActivityPdd = one + return +} + +func GetConfigActivityPdd() *common.ConfigActivityPdd { + return configActivityPdd +} + +// LoadConfigActivityPddSpin 拼多多转盘配置 +func LoadConfigActivityPddSpin() (err error) { + one := []*common.ConfigActivityPddSpin{} + if _, err = db.Mysql().QueryAll("", "amount_down,sort", &common.ConfigActivityPddSpin{}, &one); err != nil { + log.Error("err:%v", err) + return err + } + configActivityPddSpin = one + return +} + +func GetConfigActivityPddSpinByAmount(amount int64) []*common.ConfigActivityPddSpin { + ret := []*common.ConfigActivityPddSpin{} + for _, v := range configActivityPddSpin { + if v.AmountDown <= amount && v.AmountUp >= amount { + ret = append(ret, v) + } + } + return ret +} + +func GetConfigActivityPddSpin() []*common.ConfigActivityPddSpin { + return configActivityPddSpin +} + +// LoadConfigTask 任务配置 +func LoadConfigTask() (err error) { + one := []*common.ConfigTask{} + if _, err = db.Mysql().QueryAll("open = 1", "sort asc", &common.ConfigTask{}, &one); err != nil { + log.Error("err:%v", err) + return err + } + configTask = one + return +} + +func GetConfigTask() []*common.ConfigTask { + return configTask +} + +func GetConfigTaskByTaskID(taskID int) *common.ConfigTask { + for _, v := range configTask { + if v.TaskID == taskID { + return v + } + } + return nil +} + +func GetConfigTaskByTaskType(t int) (ret []*common.ConfigTask) { + for _, v := range configTask { + if v.Type == t { + ret = append(ret, v) + } + } + return +} + +// LoadConfigCurrencyResource 货币来源配置 +func LoadConfigCurrencyResource() (err error) { + one := []*common.ConfigCurrencyResource{} + if _, err = db.Mysql().QueryAll("", "", &common.ConfigCurrencyResource{}, &one); err != nil { + log.Error("err:%v", err) + return err + } + configCurrencyResource = one + return +} + +func GetConfigCurrencyResourceMultiple(t common.CurrencyRes) int64 { + for _, v := range configCurrencyResource { + if v.Type == t { + return v.Multiple + } + } + return 0 +} + +func GetConfigCurrencyResourceNeedBet(t common.CurrencyRes, amount int64) (needBet int64) { + if amount <= 0 { + return + } + needBet = GetConfigCurrencyResourceMultiple(t) * amount / 100 + if needBet < 0 { + needBet = 0 + } + return +} + +// LoadConfigFirstPay 首充 +func LoadConfigFirstPay() (err error) { + one := []*common.ConfigFirstPay{} + if _, err = db.Mysql().QueryAll("", "amount", &common.ConfigFirstPay{}, &one); err != nil { + log.Error("err:%v", err) + return err + } + configFirstPay = one + return +} + +func GetConfigFirstPay() []*common.ConfigFirstPay { + return configFirstPay +} + +func GetConfigFirstPayLevelByAmount(amount int64) int { + for _, v := range configFirstPay { + if amount >= v.Amount { + return v.Level + } + } + return 0 +} + +func GetConfigFirstPayPerByAmount(first bool, amount int64) int64 { + for i := len(configFirstPay) - 1; i >= 0; i-- { + v := configFirstPay[i] + if amount >= v.Amount { + if first { + return v.FirstPer + } + return v.Per + } + } + return 0 +} + +// LoadConfigActivityFreeSpin 每日免费转盘配置 +func LoadConfigActivityFreeSpin() (err error) { + one := []*common.ConfigActivityFreeSpin{} + if _, err = db.Mysql().QueryAll("", "sort", &common.ConfigActivityFreeSpin{}, &one); err != nil { + log.Error("err:%v", err) + return err + } + for _, v := range one { + if v.Type == common.ActivityFreeSpinItemRandomCash && v.CashUp < v.CashDown { + log.Error("invalid cashup cashdown:%+v", v) + return errors.New("invalid cashup cashdown") + } + } + configActivityFreeSpin = one + return +} + +func GetConfigActivityFreeSpin() []*common.ConfigActivityFreeSpin { + return configActivityFreeSpin +} + +// 获取指定类型的物品 +func GetConfigActivityFreeSpinByType(t int) (ret []*common.ConfigActivityFreeSpin) { + for _, v := range configActivityFreeSpin { + if v.Type == t { + ret = append(ret, v) + } + } + return +} + +// LoadConfigActivityFirstRechargeBack 加载首日充值返还配置 +func LoadConfigActivityFirstRechargeBack() (err error) { + one := new(common.ConfigActivityFirstRechargeBack) + if err := db.Mysql().Get(one); err != nil { + return err + } + configActivityFirstRechargeBack = one + return nil +} + +func GetConfigActivityFirstRechargeBack() *common.ConfigActivityFirstRechargeBack { + return configActivityFirstRechargeBack +} + +func LoadConfigActivityLuckCode() (err error) { + list := []*common.ConfigActivityLuckyCode{} + if _, err = db.Mysql().QueryAll("", "", &common.ConfigActivityLuckyCode{}, &list); err != nil { + log.Error("err:%v", err) + return err + } + configActivityLuckyCode = list + return nil +} + +func GetConfigAcitivityLuckyCode() []*common.ConfigActivityLuckyCode { + return configActivityLuckyCode +} + +func GetConfigAcitivityLuckyCodeTotalWeight() (total int64) { + for _, v := range configActivityLuckyCode { + total += int64(v.Per) + } + return +} + +func LoadConfigBanner() (err error) { + list := []*common.ConfigBanner{} + if _, err = db.Mysql().QueryAll("", "sort", &common.ConfigBanner{}, &list); err != nil { + log.Error("err:%v", err) + return err + } + configBanner = list + return nil +} + +func GetConfigBanner(uid int) []*common.ConfigBanner { + list := []*common.ConfigBanner{} + for _, v := range configBanner { + if !v.IsValid() { + continue + } + if v.ActivityID == common.ActivityIDFirstRechargeBack && !ShouldShowActivityFirstRechargeBack(uid) { + continue + } + list = append(list, v) + } + return list +} + +func LoadConfigActivitySign() (err error) { + list := []*common.ConfigActivitySign{} + if _, err = db.Mysql().QueryAll("", "day", &common.ConfigActivitySign{}, &list); err != nil { + log.Error("err:%v", err) + return err + } + configActivitySign = list + return nil +} + +func GetConfigActivitySign() []*common.ConfigActivitySign { + return configActivitySign +} + +func LoadConfigActivityBreakGift() (err error) { + list := []*common.ConfigActivityBreakGift{} + if _, err = db.Mysql().QueryAll("", "recharge_down", &common.ConfigActivityBreakGift{}, &list); err != nil { + log.Error("err:%v", err) + return err + } + configActivityBreakGift = list + return nil +} + +func GetConfigActivityBreakGiftByRecharge(recharge int64, data *common.PlayerPayData) *common.ConfigActivityBreakGift { + for _, v := range configActivityBreakGift { + if util.SliceContain(data.SubBreakGift, v.Level) { + continue + } + if v.RechargeDown <= recharge && (recharge < v.RechargeUp || v.RechargeUp <= 0) { + return v + } + } + return nil +} + +func GetConfigActivityBreakGiftByProductID(productID int) *common.ConfigActivityBreakGift { + for _, v := range configActivityBreakGift { + if v.ProductID == productID { + return v + } + } + return nil +} + +func LoadConfigShareRobot() (err error) { + list := []*common.ConfigShareRobot{} + if _, err = db.Mysql().QueryAll("", "", &common.ConfigShareRobot{}, &list); err != nil { + log.Error("err:%v", err) + return err + } + configShareRobot = list + return nil +} + +func GetConfigShareRobot() []*common.ConfigShareRobot { + return configShareRobot +} + +func GetConfigShareRobotByID(robotID int) *common.ConfigShareRobot { + for _, v := range configShareRobot { + if v.RobotID == robotID { + return v + } + } + return nil +} + +func LoadConfigActivityWeekCard() (err error) { + list := []*common.ConfigActivityWeekCard{} + if _, err = db.Mysql().QueryAll("", "level", &common.ConfigActivityWeekCard{}, &list); err != nil { + log.Error("err:%v", err) + return err + } + for _, v := range list { + if len(v.Range) > 0 { + err := json.Unmarshal([]byte(v.Range), &v.SubRange) + if err != nil { + log.Error("err:%v", err) + } + } + } + configActivityWeekCard = list + return nil +} + +func GetConfigActivityWeekCard() []*common.ConfigActivityWeekCard { + return configActivityWeekCard +} + +func GetConfigActivityWeekCardByLevel(level int) *common.ConfigActivityWeekCard { + for _, v := range configActivityWeekCard { + if v.Level == level { + return v + } + } + return nil +} + +func LoadConfigActivitySlots() (err error) { + list := []*common.ConfigActivitySlots{} + if _, err = db.Mysql().QueryAll("", "rank_down", &common.ConfigActivitySlots{}, &list); err != nil { + log.Error("err:%v", err) + return err + } + configActivitySlots = list + return nil +} + +func GetConfigActivitySlots() []*common.ConfigActivitySlots { + return configActivitySlots +} + +func GetConfigActivitySlotsRewardByRank(rank int) int64 { + for _, v := range configActivitySlots { + if rank >= v.RankDown && rank <= v.RankUp { + return v.Reward + } + } + return 0 +} + +func LoadConfigActivityLuckyShop() (err error) { + list := []*common.ConfigActivityLuckyShop{} + if _, err = db.Mysql().QueryAll("", "type asc", &common.ConfigActivityLuckyShop{}, &list); err != nil { + log.Error("err:%v", err) + return err + } + configActivityLuckyShop = list + return nil +} + +func GetConfigActivityLuckShop(t int) *common.ConfigActivityLuckyShop { + for _, v := range configActivityLuckyShop { + if v.Type == t { + return v + } + } + return nil +} + +func LoadConfigServerFlag() (err error) { + list := []*common.ConfigServerFlag{} + if _, err = db.Mysql().QueryAll("", "", &common.ConfigServerFlag{}, &list); err != nil { + log.Error("err:%v", err) + return err + } + configServerFlag = list + return nil +} + +func GetConfigServerFlag(flag string) *common.ConfigServerFlag { + for _, v := range configServerFlag { + if v.Flag == flag { + return v + } + } + return nil +} + +func LoadConfigActivitySevenDayBox() (err error) { + list := []*common.ConfigActivitySevenDayBox{} + if _, err = db.Mysql().QueryAll("", "type asc,recharge desc", &common.ConfigActivitySevenDayBox{}, &list); err != nil { + log.Error("err:%v", err) + return err + } + for _, v := range list { + if v.CashRange != "" { + err := json.Unmarshal([]byte(v.CashRange), &v.SubCashRange) + if err != nil { + log.Error("err:%v", err) + } + } + } + configActivitySevenDayBox = list + return nil +} + +func GetConfigActivitySevenDayBoxByRechargeAndType(recharge int64, t int) (ret []*common.ConfigActivitySevenDayBox) { + var rechargeLevel int64 + for _, v := range configActivitySevenDayBox { + if v.Type != t { + continue + } + if rechargeLevel != 0 && rechargeLevel != v.Recharge { + continue + } + if recharge >= v.Recharge { + rechargeLevel = v.Recharge + ret = append(ret, v) + } + } + return +} + +func LoadConfigActivitySuper() (err error) { + list := []*common.ConfigActivitySuper{} + if _, err = db.Mysql().QueryAll("", "type asc,index asc,sort asc", &common.ConfigActivitySuper{}, &list); err != nil { + log.Error("err:%v", err) + return err + } + configActivitySuper = list + return nil +} + +func GetConfigActivitySuperByType(t int) (ret []*common.ConfigActivitySuper) { + for _, v := range configActivitySuper { + if v.Type != t { + continue + } + ret = append(ret, v) + } + return +} + +func GetConfigActivitySuperByTypeAndIndex(t, index int) (ret []*common.ConfigActivitySuper) { + for _, v := range configActivitySuper { + if v.Type != t { + continue + } + if v.Index != index { + continue + } + ret = append(ret, v) + } + return +} diff --git a/call/currency.go b/call/currency.go new file mode 100644 index 0000000..401eca1 --- /dev/null +++ b/call/currency.go @@ -0,0 +1,388 @@ +package call + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "runtime" + "server/common" + "server/db" + "server/natsClient" + "server/pb" + "server/util" + "time" + + "github.com/liangdas/mqant/log" + "gorm.io/gorm" +) + +var ( + ErrNotEnoughBalance = errors.New("not enough balance") +) + +const ( + ProTypeSettle = iota + ProTypeMineCash // 扣除可退出余额,判断余额是否足够 + ProTypeAll +) + +// BaseCurrency 更新玩家字段的基本对象 +type BaseCurrency struct { + subCurrency subCurrency + tx *gorm.DB + *common.CurrencyBalance + + notNotify bool // 是否需要通知客户端 + notifyChan chan *ProRes + ProType int + // IsMine bool // 是否判断玩家余额是否充足 + // IsRecharge bool // 是否更新充值账户 +} + +// NewCurrency 创建一个更新玩家字段对象 +func NewCurrency(data *common.UpdateCurrency) *BaseCurrency { + b := new(BaseCurrency) + if data.CurrencyBalance.Type <= 0 || data.CurrencyBalance.Type >= common.CurrencyAll { + return nil + } + if data.Value == 0 { + return nil + } + b.notNotify = data.NotNotify + b.tx = data.Tx + b.CurrencyBalance = data.CurrencyBalance + b.Time = time.Now().Unix() + if b.ChannelID == 0 { + playerInfo, _ := GetUserXInfo(data.UID, "channel_id") + b.ChannelID = playerInfo.ChannelID + } + log.Debug("new update:%+v", *b.CurrencyBalance) + return b +} + +// UpdateCurrency 更新玩家数据并通知客户端 +func UpdateCurrency(data *common.UpdateCurrency, tx *gorm.DB) error { + b := NewCurrency(data) + if b == nil { + return errors.New("invalid update") + } + b.tx = tx + err := b.UpdateCurrency() + if err != nil { + return err + } + b.Notify() + return nil +} + +func (b *BaseCurrency) Notify() { + if err := Publish(natsClient.TopicInnerRefreshGold, &pb.InnerRefreshGold{UID: uint32(b.UID), Pair: []*pb.CurrencyPair{{Type: int64(b.Type), Value: b.Value}}, + Event: uint32(b.Event)}); err != nil { + log.Error("err:%v", err) + } + if b.notNotify { + return + } + var resp pb.PlayerBalanceResp + resp.Type = int64(b.Type) + resp.Balance = b.Balance + resp.Value = b.Value + resp.Event = int64(b.Event) + + if caller != nil { + SendNR(b.UID, int(pb.ServerCommonResp_CommonPlayerBalanceResp), &resp, "common") + } +} + +func Notify(uid int, bal, changeVal int64, t common.CurrencyType, event common.CurrencyEvent, exs1, exs2 string) { + var resp pb.PlayerBalanceResp + resp.Balance = bal + resp.Type = int64(t) + resp.Event = int64(event) + resp.Exs1 = exs1 + resp.Exs2 = exs2 + resp.Value = changeVal + + if caller != nil { + SendNR(uid, int(pb.ServerCommonResp_CommonPlayerBalanceResp), &resp, "common") + } +} + +// ProRes 存储过程返回结构 +type ProRes struct { + Result int + Type common.CurrencyType + Balance int64 // 余额 + Err error +} + +// 存储过程修改金币 +func UpdateCurrencyPro(data *common.UpdateCurrency) (*ProRes, error) { + b := NewCurrency(data) + if b == nil { + return nil, errors.New("invalid update") + } + b.notifyChan = make(chan *ProRes, 1) + b.UpdateCurrencyPro() + // retPro := new(ProRes) + // for pro := range b.notifyChan { + // if pro.Err != nil { + // return pro, pro.Err + // } + // retPro = pro + // break + // } + retPro := <-b.notifyChan + b.Notify() + return retPro, nil +} + +// 存储过程修改金币,立即返回,失败后不重试(不判断玩家余额是否充足,可能扣成负数) +func UpdateCurrencyProReal(data *common.UpdateCurrency) *ProRes { + pro := &ProRes{} + b := NewCurrency(data) + if b == nil { + pro.Err = errors.New("invalid update") + return pro + } + pro, err := b.UpdatePro() + if err != nil { + pro.Err = err + return pro + } + if pro.Err != nil { + return pro + } + b.Notify() + return pro +} + +// 存储过程修改金币,立即返回,失败后不重试(会判断玩家余额是否充足) +func MineCurrencyProReal(data *common.UpdateCurrency) *ProRes { + pro := &ProRes{} + b := NewCurrency(data) + if b == nil { + pro.Err = errors.New("invalid update") + return pro + } + b.ProType = ProTypeMineCash + pro, err := b.UpdatePro() + if err != nil { + pro.Err = err + return pro + } + if pro.Err != nil { + return pro + } + if pro.Result == 1 { + pro.Err = ErrNotEnoughBalance + return pro + } + b.Notify() + return pro +} + +// 通过存储过程改变金币(不判断玩家余额是否充足,可能扣成负数) +func (b *BaseCurrency) UpdateCurrencyPro() { + util.IndexTryCallback(func() error { + pro, err := b.UpdatePro() + if err != nil { + return err + } + b.notifyChan <- pro + return nil + }, func() { + b.notifyChan <- &ProRes{Err: fmt.Errorf("index try fail")} + }) +} + +func (b *BaseCurrency) UpdatePro() (*ProRes, error) { + uid := b.UID + field := b.Type.GetCurrencyName() + pro := &ProRes{Type: b.Type} + callName := "settleCurrency" + if b.ProType == ProTypeMineCash { + callName = "mineCurrency" + } + err := db.Mysql().C().Raw(fmt.Sprintf("call %s(%d,'%v',%d,%d,@a,@b)", callName, uid, field, b.Value, b.NeedBet)).Scan(pro).Error + if err != nil { + log.Error("err:%v", err) + return pro, err + } + b.Balance = pro.Balance + util.Go(func() { + WriteBalance(b.CurrencyBalance) + }) + return pro, nil +} + +func (b *BaseCurrency) UpdateCurrency() (err error) { + tx := b.tx + if tx == nil { + tx = db.Mysql().C() + } + defer func() { + if r := recover(); r != nil { + buf := make([]byte, 1024) + runtime.Stack(buf, false) + log.Error(string(buf)) + return + } + }() + uid := b.UID + field := b.Type.GetCurrencyName() + log.Debug("player %v update currency:%+v", uid, *b) + pc := &common.PlayerCurrency{UID: uid} + tableName := pc.TableName() + err = db.Mysql().C().Table(tableName).Where(&common.PlayerCurrency{UID: uid}).Select(field).Scan(&b.Balance).Error + if err != nil { + return + } + if b.Value > 0 { + err = tx.Model(&common.PlayerCurrency{UID: uid}).Updates(map[string]interface{}{field: gorm.Expr(fmt.Sprintf("%s + ?", field), b.Value)}).Error + if err != nil { + return + } + } else { + if b.Balance < -b.Value { + err = errors.New("not enough balance") + return + } + res := tx.Table(tableName).Where(&common.PlayerCurrency{UID: uid}).Where(fmt.Sprintf("uid = %d and %s>=%d", uid, field, -b.Value)). + Updates(map[string]interface{}{field: gorm.Expr(fmt.Sprintf("%s + ?", field), -b.Value)}) + if res.RowsAffected == 0 || res.Error != nil { + log.Error("err:%e", res.Error) + err = errors.New("not enough balance") + return + } + } + if b.NeedBet > 0 { + err = tx.Model(&common.PlayerProfile{}).Where("uid = ?", uid).Updates(map[string]interface{}{"need_bet": gorm.Expr("need_bet + ?", b.NeedBet)}).Error + if err != nil { + log.Error("err:%v", err) + return + } + } + + b.Balance += b.Value + err = WriteBalance(b.CurrencyBalance, tx) + if err != nil { + log.Error("err:%v", err) + return + } + if b.subCurrency != nil { + err = b.subCurrency.updateCurrency() + if err != nil { + log.Error("err:%v", err) + } + return + } + return +} + +type subCurrency interface { + updateCurrency() error +} + +func WriteBalance(b *common.CurrencyBalance, d ...*gorm.DB) error { + tx := db.Mysql().C() + if d != nil { + tx = d[0] + } + if len(b.Exs1) > 64 { + b.Exs1 = b.Exs1[:64] + } + if len(b.Exs2) > 64 { + b.Exs2 = b.Exs2[:64] + } + if len(b.Exs3) > 64 { + b.Exs3 = b.Exs3[:64] + } + err := tx.Table(b.TableName()).Create(b).Error + if err != nil { + log.Error("dbUpdateCurrency AddCurrencyBalance error:%v", err) + return err + } + db.ES().InsertToESGO(common.ESIndexBalance, b) + return nil +} + +// 汇率转换(转成美元) +func Rate(t common.CurrencyType, amount int64) int64 { + switch t { + case common.CurrencyBrazil: + // 汇率每天刷新一次 + rate := GetConfigCurrencyRateUSD(t) + if rate == 0 { + rate = 2000 + } + return amount * rate / 10000 + default: + return amount + } +} + +func RateBRL(t common.CurrencyType, amount int64) int64 { + switch t { + case common.CurrencyBrazil: + return amount + case common.CurrencyUSDT: + // 汇率每天刷新一次 + rate := GetConfigCurrencyRateUSD(common.CurrencyBrazil) + if rate == 0 { + rate = 2000 + } + return amount * 10000 / rate + default: + return amount + } +} + +const ( + RateKey = "0P0U7HP7G0GIDJJ6" // 汇率查询网站apikey +) + +type FXRate struct { + FromCurrencyCode string `json:"1. From_Currency Code"` + FromCurrencyName string `json:"2. From_Currency Name"` + ToCurrencyCode string `json:"3. To_Currency Code"` + ToCurrencyName string `json:"4. To_Currency Name"` + ExchangeRate float64 `json:"5. Exchange Rate,string"` +} + +// GetRateFromAPI 查询货币汇率,返回万分位 +func GetRateFromAPI(t common.CurrencyType) int64 { + if t == common.CurrencyUSDT { + return 10000 + } + url := fmt.Sprintf("https://www.alphavantage.co/query?function=CURRENCY_EXCHANGE_RATE&from_currency=%v&to_currency=USD&apikey=%v", t.GetCurrencyName(), RateKey) + + resp, err := http.Get(url) + if err != nil { + log.Error("err:%v", err) + return 0 + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Error("err:%v", err) + return 0 + } + log.Debug("GetRateFromAPI:%v", string(body)) + + var data map[string]FXRate + err = json.Unmarshal(body, &data) + if err != nil { + log.Error("err:%v", err) + return 0 + } + + fxRate, ok := data["Realtime Currency Exchange Rate"] + if !ok { + return 0 + } + return int64(fxRate.ExchangeRate * 10000) +} diff --git a/call/game.go b/call/game.go new file mode 100644 index 0000000..4bd8d30 --- /dev/null +++ b/call/game.go @@ -0,0 +1,46 @@ +package call + +import ( + "fmt" + "server/common" + "server/db" + "server/util" + + "github.com/liangdas/mqant/log" + "gorm.io/gorm" +) + +func UpdateJackpot(t, tid int, amount int64) { + if err := db.Mysql().C().Exec(fmt.Sprintf("call jackpot(%v,%v,%v)", t, tid, amount)).Error; err != nil { + log.Error("err:%v", err) + } +} + +func GetJackpot(t, tid int) int64 { + data := &common.Jackpot{Type: t, TypeID: tid} + db.Mysql().Get(data) + return data.Amount +} + +func GetJack(t, tid int) *common.Jackpot { + data := &common.Jackpot{Type: t, TypeID: tid} + db.Mysql().Get(data) + return data +} + +func UpdateGameJackpot(t, tid int, amount, playerAmount int64) { + jack := GetJack(t, tid) + u := map[string]interface{}{"player_amount": gorm.Expr("player_amount + ?", playerAmount)} + if jack.Amount+amount >= jack.Max { + u["amnout"] = jack.Max + } else { + u["amount"] = gorm.Expr("amount + ?", amount) + } + db.Mysql().Update(&common.Jackpot{Type: t, TypeID: tid}, u) +} + +func UpdateGameSort(provider, gameID int) { + util.Go(func() { + db.Mysql().Update(&common.ConfigGameList{GameProvider: provider, GameID: gameID}, map[string]interface{}{"sort": gorm.Expr("sort + 1")}) + }) +} diff --git a/call/item.go b/call/item.go new file mode 100644 index 0000000..e16a2f1 --- /dev/null +++ b/call/item.go @@ -0,0 +1,40 @@ +package call + +import ( + "fmt" + "server/common" + "server/db" + "time" +) + +func GetUserItem(uid, itemID int) []*common.PlayerItems { + list := []*common.PlayerItems{} + db.Mysql().QueryAll(fmt.Sprintf("uid = %d and item_id = %d", uid, itemID), "", &common.PlayerItems{}, &list) + return list +} + +func GetUserValidItems(uid, itemID int) []*common.PlayerItems { + list := []*common.PlayerItems{} + db.Mysql().QueryAll(fmt.Sprintf("uid = %d and item_id = %d and status = %d", uid, itemID, common.ItemStatusNormal), "", &common.PlayerItems{}, &list) + return list +} + +func GetUserItemByExi1(uid, itemID, exi1 int) []*common.PlayerItems { + list := []*common.PlayerItems{} + db.Mysql().QueryAll(fmt.Sprintf("uid = %d and item_id = %d and exi1 = %d and status = %d", uid, itemID, exi1, common.ItemStatusNormal), "", &common.PlayerItems{}, &list) + return list +} + +// 获取力度最大的折扣券 +func GetUserBestDiscountTicket(uid int) *common.PlayerItems { + list := []*common.PlayerItems{} + db.Mysql().QueryAll(fmt.Sprintf("uid = %d and item_id = %d and status = %d", uid, common.ItemDiscountTicket, common.ItemStatusNormal), "exi1 desc", &common.PlayerItems{}, &list) + if len(list) > 0 { + return list[0] + } + return nil +} + +func AddUserDiscountTicket(uid, exi1 int) { + db.Mysql().Create(&common.PlayerItems{UID: uid, ItemID: common.ItemDiscountTicket, Time: time.Now().Unix(), Status: common.ItemStatusNormal, Exi1: exi1}) +} diff --git a/call/mail.go b/call/mail.go new file mode 100644 index 0000000..ebebc13 --- /dev/null +++ b/call/mail.go @@ -0,0 +1,65 @@ +package call + +import ( + "fmt" + "server/common" + "server/db" + "time" + + "github.com/liangdas/mqant/log" + "gorm.io/gorm" +) + +func checkMail(uid int, red *common.PlayerRed) { + data := &common.PlayerData{UID: uid} + db.Mysql().Get(data) + t := data.LastSysEmailDraw + all := []common.Mail{} + // mailCount := db.Mysql().QueryNewMailCount(uid) + mailCount := 0 + db.Mysql().QueryAll(fmt.Sprintf("receiver = %v and time > %v", 0, t), "", &common.Mail{}, &all) + if len(all) > 0 { + now := time.Now().Unix() + u, err := db.Mysql().UpdateRes(&common.PlayerData{UID: uid, LastSysEmailDraw: data.LastSysEmailDraw}, &common.PlayerData{LastSysEmailDraw: now}) + if err != nil || u == 0 { + log.Error("err:%v", err) + return + } + for _, v := range all { + one := v + one.ID = 0 + one.Receiver = uid + db.Mysql().Create(&one) + mailCount++ + } + if red.ID == 0 { + red.Mail = mailCount + db.Mysql().Create(red) + } else { + db.Mysql().Update(red, map[string]interface{}{"mail": gorm.Expr("mail + ?", mailCount)}) + } + } + red.Mail += mailCount +} + +const ( + MailWithdrawType2 = "Your withdrawal order is already in payment. We've paid cash to bank, the final time is determined by the bank, please be patient!\nIf cash has not yet arrived, please click online customer service on the upper right corner or WhatsApp us at %v." + MailWithdrawType3 = "1.Please check your bank/UPI information and try to withdraw again: Bank IFSC Code is formatted as 'AAAA0XXXXXX'; there shouldn't be space in UPI account;\n2.If withdrawal still fails, please click online customer service on the upper right corner or WhatsApp us at %v." +) + +func SendWithdrawMail(uid int, contentType string) { + one := &common.Mail{ + Sender: "System", + Receiver: uid, + Title: "Withdraw Notice", + Content: fmt.Sprintf(contentType, GetConfigPlatform().Whatsapp), + Time: time.Now().Unix(), + } + db.Mysql().Create(one) + UpsertRedPointAndNotify(uid, 1, ModuleMail) +} + +func SendMail(mail *common.Mail) { + db.Mysql().Create(mail) + UpsertRedPointAndNotify(mail.Receiver, 1, ModuleMail) +} diff --git a/call/module.go b/call/module.go new file mode 100644 index 0000000..3498523 --- /dev/null +++ b/call/module.go @@ -0,0 +1,70 @@ +package call + +import ( + "strconv" + + "github.com/liangdas/mqant/registry" + "github.com/liangdas/mqant/selector" +) + +func GetGameOriginID(id int) int { + return id / 100 * 100 +} + +// 选择器 +var ( + WorkIDSelector = func(nodeID string) selector.SelectOption { + return selector.WithFilter(func(services []*registry.Service) []*registry.Service { + // log.Debug("nodeID:%v", nodeID) + ret := []*registry.Service{} + for _, service := range services { + nodes := []*registry.Node{} + for _, node := range service.Nodes { + if node.Metadata["workID"] == nodeID { + // log.Debug("find node:%v", node) + nodes = append(nodes, node) + } + } + service.Nodes = nodes + ret = append(ret, service) + } + return ret + }) + } + // 服务器版本选择器 + VersionSelector = func(nodeID interface{}) selector.SelectOption { + return selector.WithFilter(func(services []*registry.Service) []*registry.Service { + origin := 0 + switch t := nodeID.(type) { + case int: + origin = t + case string: + origin, _ = strconv.Atoi(t) + default: + return services + } + if origin >= 3000 { + origin = GetGameOriginID(origin) + } + version := GetServerVersion(origin) + // log.Debug("nodeID:%v,version:%v", nodeID, version) + if version == 0 { + return services + } + ret := []*registry.Service{} + for _, service := range services { + nodes := []*registry.Node{} + for _, node := range service.Nodes { + ver, _ := strconv.Atoi(node.Metadata["version"]) + if ver == version { + // log.Debug("find node:%v", node) + nodes = append(nodes, node) + } + } + service.Nodes = nodes + ret = append(ret, service) + } + return ret + }) + } +) diff --git a/call/pay.go b/call/pay.go new file mode 100644 index 0000000..8aee0cb --- /dev/null +++ b/call/pay.go @@ -0,0 +1,813 @@ +package call + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "server/common" + "server/config" + "server/db" + "server/natsClient" + "server/pb" + "server/util" + "time" + + "github.com/gogo/protobuf/proto" + "github.com/liangdas/mqant/log" + mqrpc "github.com/liangdas/mqant/rpc" + "gorm.io/gorm" +) + +func GetRechargeInfo(uid int) *common.RechargeInfo { + info := &common.RechargeInfo{UID: uid} + err := db.Mysql().Get(info) + if err != nil { + return info + } + now := time.Now().Unix() + if !util.IsSameDayTimeStamp(now, info.LastRecharge) { + info.DayRecharge = 0 + } + return info +} + +// Recharge 内部充值调用 +func Recharge(data *pb.InnerRechargeReq) (*pb.InnerRechargeResp, error) { + to, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + module := "pay" + if data.PaySource == common.PaySourceBlockPay { + module = "blockpay" + } + ret, err := GetCaller().GetApp().Call(to, module, "recharge", mqrpc.Param(data)) + if err != "" { + return nil, errors.New(err) + } + retData := new(pb.InnerRechargeResp) + if err := proto.Unmarshal(ret.([]byte), retData); err != nil { + log.Error("err:%v", err) + return nil, err + } + return retData, nil +} + +// Withdraw 内部退出调用 +func Withdraw(data *pb.InnerWithdrawReq) (*pb.InnerWithdrawResp, error) { + log.Debug("withdraw req:%+v", *data) + to, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + module := "pay" + if data.PaySource == common.PaySourceBlockPay { + module = "blockpay" + } + ret, err := GetCaller().GetApp().Call(to, module, "withdraw", mqrpc.Param(data)) + retData := new(pb.InnerWithdrawResp) + if data, ok := ret.([]byte); ok { + if err := proto.Unmarshal(data, retData); err != nil { + log.Error("err:%v", err) + } + } + if err != "" { + return retData, errors.New(err) + } + return retData, nil +} + +// RechargeCallback 充值回调 +func RechargeCallback(r *common.RechargeOrder, success bool, payAccount, extra string) (err error) { + log.Info("RechargeCallback:%+v,%v,%v,%v,", r, success, payAccount, extra) + if r == nil { + return errors.New("order invalid") + } + + if !success { + ro := &common.RechargeOrder{OrderID: r.OrderID, Status: common.StatusROrderCreate} + return db.Mysql().Update(ro, &common.RechargeOrder{Status: common.StatusROrderFail}) + } + + uid := r.UID + re := new(common.RechargeInfo) + re.UID = uid + err = db.Mysql().Get(re) + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + log.Error("get recharage info err:%v,uid:%v", err, r.UID) + return err + } + // product := GetConfigPayProductByID(r.ProductID) + // if product == nil { + // log.Error("unkonwn product:%d", r.ProductID) + // return errors.New("unkonwn product") + // } + amount := r.Amount + + notCharge := re.TotalRecharge == 0 + re.TotalRecharge += amount + now := time.Now().Unix() + // var dayR, weekR bool + var dayR bool + if util.IsSameDayTimeStamp(re.LastRecharge, now) || re.LastRecharge == 0 { + dayR = true + re.DayRecharge += amount + } + // if util.IsSameWeek(re.LastRecharge, now) || re.LastRecharge == 0 { + // weekR = true + // re.WeekRecharge += amount + // } + + re.LastRecharge = now + re.TotalRechargeCount++ + // re.ProductPayCount = common.AddProductPayCount(re.ProductPayCount, r.ProductID) + // if product.IsFirstPayProduct() { + // json.Unmarshal([]byte(re.ProductFirstPay), &re.ProductFirstPaySub) + // if util.SliceContain(re.ProductFirstPaySub, product.ProductID) { // 该商品已购买,替换为可多次购买的同价值商品 + // product = GetConfigPayProductShopByAmount(product.Amount) + // if product == nil { + // return errors.New("product invalid") + // } + // } else { + // re.ProductFirstPaySub = append(re.ProductFirstPaySub, int(product.ProductID)) + // str, _ := json.Marshal(re.ProductFirstPaySub) + // re.ProductFirstPay = string(str) + // } + // } + + // var per int64 = 0 + // level := GetConfigFirstPayLevelByAmount(r.Amount) + // playerPayData := GetPlayerPayData(uid) + tx := db.Mysql().Begin() + defer func() { + if err == nil { + if err := tx.Commit().Error; err != nil { + tx.Rollback() + return + } + } else { + log.Error("err:%v", err) + tx.Rollback() + return + } + }() + + // 第一步,更新订单状态 + updates := common.RechargeOrder{Status: common.StatusROrderPay, APIPayID: r.APIPayID, PayAccount: payAccount, Extra: extra, CallbackTime: now} + dbtx := tx.Table("recharge_order").Where(fmt.Sprintf("orderid = '%v' and status <> %v", r.OrderID, common.StatusROrderPay)).Updates(updates) + if dbtx.Error != nil { + return fmt.Errorf("update order err:%v", dbtx.Error) + } + // 已处理的单不返回错误了 + if dbtx.RowsAffected == 0 { + return nil + } + + // 更新paydata + // if playerPayData.ID == 0 { + // err = tx.Model(playerPayData).Create(playerPayData).Error + // if err != nil { + // log.Error("err:%v", err) + // return + // } + // } else { + // playerPayData.SubFirstPay = append(playerPayData.SubFirstPay, level) + // updatePayData, _ := json.Marshal(playerPayData.SubFirstPay) + // res := tx.Model(playerPayData).Where("id = ? and first_pay = ?", playerPayData.ID, playerPayData.FirstPay).Updates(map[string]interface{}{"first_pay": string(updatePayData)}) + // if res.RowsAffected == 0 { + // log.Error("err:%v", err) + // return errors.New("update payData fail") + // } + // if err != nil { + // log.Error("update payData err:%v", err) + // return err + // } + // } + + // 第二步,更新recharge_info + payData := &common.ESPlayerPayData{UID: uid, Channel: r.ChannelID, Type: 1, CurrencyType: r.CurrencyType} + if re.ID == 0 { + err = tx.Model(re).Create(re).Error + if err != nil { + // 说明数据库出问题或者两单并发回调,一分钟后重试 + time.AfterFunc(1*time.Minute, func() { + RechargeCallback(r, success, payAccount, extra) + }) + log.Error("err:%v", err) + return + } + payData.FirstAmount = amount + } else { + updates := map[string]interface{}{ + "total_recharge_count": gorm.Expr("total_recharge_count + 1"), + "total_recharge": gorm.Expr("total_recharge + ?", amount), + "last_recharge": now, + // "product_paycount": re.ProductPayCount, + // "product_firstpay": re.ProductFirstPay, + } + if dayR { + updates["day_recharge"] = gorm.Expr("day_recharge + ?", amount) + } else { + updates["day_recharge"] = amount + } + // if weekR { + // updates["week_recharge"] = gorm.Expr("week_recharge + ?", amount) + // } else { + // updates["week_recharge"] = amount + // } + err = tx.Model(re).Where("uid=?", re.UID).Updates(updates).Error + } + if err != nil { + log.Error("recharge err:%v", err) + return err + } + // 第三步,给玩家发货 + // 正常商城充值 + var bonus int64 + if r.ProductID == 0 { + amount := PayExtra(r) // 判断特殊购买,如优惠券 + if amount == 0 { + amount = r.Amount + } + // cb := &common.CurrencyBalance{ + // UID: r.UID, + // Type: r.CurrencyType, + // Value: amount, + // Event: common.CurrencyEvent(r.Event), + // Exs1: r.OrderID, + // Exi1: int(amount), // 充值金额传递 + // Exi2: r.ProductID, + // NeedBet: GetConfigCurrencyResourceNeedBet(common.CurrencyResourceRecharge, amount), + // } + // err = UpdateCurrencyProReal(&common.UpdateCurrency{ + // CurrencyBalance: cb, + // }).Err + // if err != nil { + // return + // } + // } else { + per := GetConfigFirstPayPerByAmount(notCharge, amount) + if per > 0 { + bonus = amount * per / 100 + } + cb := &common.CurrencyBalance{ + UID: r.UID, + Type: r.CurrencyType, + Value: amount + bonus, + Event: common.CurrencyEvent(r.Event), + Exs1: r.OrderID, + Exi1: int(amount), // 充值金额传递 + Exi2: r.ProductID, + NeedBet: GetConfigCurrencyResourceNeedBet(common.CurrencyResourceRecharge, amount) + GetConfigCurrencyResourceNeedBet(common.CurrencyResourceBonus, bonus), + } + err = UpdateCurrencyProReal(&common.UpdateCurrency{ + CurrencyBalance: cb, + }).Err + if err != nil { + return + } + } + // 以上逻辑为处理订单,更新玩家充值信息,给玩家加钱。 + // 下面逻辑处理活动,数据统计等,不影响以上效率,接下来逻辑并发执行 + util.Go(func() { + // 更新活动数据上传 + if bonus > 0 { + UploadActivityData(uid, common.ActivityIDRecharge, common.ActivityDataJoin, bonus) + } + user, _ := GetUserInfo(uid) + PayActivity(r, notCharge, user) + if r.Event != int(common.CurrencyEventGMRecharge) { + payData.Amount = amount + WritePlayerPayData(payData) + } + var uploads []func() + uploads = append(uploads, func() { UploadAdjust(common.AdjustEventAllPay, user, nil) }) + // 24小时内注册的用户所有付费事件上报 + if now-user.Birth <= 24*60*60 { + uploads = append(uploads, func() { + UploadAdjust(common.AdjustEventNewPay, user, map[string]string{"revenue": util.RoundFloat(float64(amount/1e6)/100, 2), "currency": "BRL"}) + }) + + // 上报fb + UploadFB(uid, FBEventPurchase, amount/common.DecimalDigits) + UploadKwai(uid, KwaiEventPay, amount) + } + for _, f := range uploads { + f() + } + }) + return nil +} + +func WithdrawCallback(order *common.WithdrawOrder) error { + uid := order.UID + amount := order.Amount + var err error + // 不成功退款 + if order.Status == common.StatusROrderFail { + err = ReturnBackWithdraw(order, common.StatusROrderFinish, common.StatusROrderFail) + } else { + tx := db.Mysql().Begin() + or := new(common.WithdrawOrder) + or.ID = order.ID + or.Status = uint8(common.StatusROrderFinish) + or.CallbackTime = time.Now().Unix() + or.APIPayID = order.APIPayID + or.FailReason = order.FailReason + if len(or.FailReason) > 200 { + or.FailReason = or.FailReason[:200] + } + res := tx.Model(or).Where("id = ? and status <> ?", or.ID, common.StatusROrderFinish).Updates(or) + if res.Error != nil { + log.Error("Withdraw callback err:%v", res.Error) + tx.Rollback() + return err + } + // 已处理的情况不返回错误了 + if res.RowsAffected == 0 { + log.Error("Withdraw callback order:%v done", order.OrderID) + tx.Rollback() + return nil + } + rei := &common.RechargeInfo{UID: uid} + db.Mysql().Get(rei) + payData := &common.ESPlayerPayData{UID: uid, Channel: order.ChannelID, Amount: amount, Type: 2} + if rei.TotalWithdrawCount == 0 { + payData.FirstAmount = amount + } + WritePlayerPayData(payData) + u := map[string]interface{}{ + "total_withdraw_count": gorm.Expr("total_withdraw_count + ?", 1), + "total_withdraw": gorm.Expr("total_withdraw + ?", order.Amount), + "total_withdrawing": gorm.Expr("total_withdrawing - ?", amount), + "withdrawing_cash": gorm.Expr("withdrawing_cash - ?", order.WithdrawCash), + } + err = tx.Model(rei).Where("uid = ?", uid).Updates(u).Error + if err != nil { + log.Error("BaseWithdraw err :%v", err) + tx.Rollback() + return err + } + // err = UpdatePlayerRechargeInfoCurrency(uid, order.CurrencyType, map[string]interface{}{ + // "total_withdrawing": gorm.Expr("total_withdrawing - ?", order.WithdrawCash), + // "total_withdraw": gorm.Expr("total_withdraw + ?", order.Amount), + // }, tx) + // if err != nil { + // log.Error("BaseWithdraw err :%v", err) + // tx.Rollback() + // return err + // } + if err := tx.Commit().Error; err != nil { + log.Error("ZYWithdraw callback err:%v", err) + tx.Rollback() + return err + } + PublishWarn(WarnTypeWithdraw, 2, []int64{int64(order.ChannelID), int64(order.UID)}) + con := GetBroadcastConfigWithID(common.BrocastIDWithdraw) + if con != nil { + up := con.ConditionUp + down := con.ConditionDown + if (amount <= int64(up) || up <= 0) && amount >= int64(down) { + BroadcastReq(con, fmt.Sprintf(con.Content, uid, amount)) + } + } + // util.Go(func() { + // SendRealWithdrawMail(order) + // }) + } + if err != nil { + return err + } + Publish(natsClient.TopicInnerPlayerWithdraw, &pb.InnerWithdrawCallback{UID: uint32(uid)}) + return nil +} + +// ReturnBackWithdraw 退出被拒绝或者失败,返还金币 +func ReturnBackWithdraw(or *common.WithdrawOrder, originStatus, status uint8, payChannel ...int) error { + order := &common.WithdrawOrder{} + order.ID = or.ID + tx := db.Mysql().Begin() + u := map[string]interface{}{ + "status": status, + "fail_reason": or.FailReason, + "callback_time": time.Now().Unix(), + } + if len(payChannel) > 0 { + u["pay_channel"] = payChannel[0] + } + if len(or.APIPayID) > 0 { + u["apipayid"] = or.APIPayID + } + update := tx.Model(order).Where("status <= ?", originStatus).Updates(u) + if update.Error != nil { + log.Error("err:%v", update.Error) + tx.Rollback() + return update.Error + } + // 已处理的情况不返回错误了 + if update.RowsAffected == 0 { + err := fmt.Errorf("update fail:%+v", or) + log.Error("err:%v", err) + tx.Rollback() + return nil + } + uid := or.UID + re := new(common.RechargeInfo) + re.UID = uid + updateRe := map[string]interface{}{ + "withdrawing_cash": gorm.Expr("withdrawing_cash - ?", or.WithdrawCash), + "total_withdrawing": gorm.Expr("total_withdrawing - ?", or.Amount), + "day_withdraw": gorm.Expr("day_withdraw - ?", or.Amount), + } + if status == common.StatusROrderFail { + updateRe["withdraw_count"] = gorm.Expr("withdraw_count + ?", -1) + } + err := tx.Model(re).Where("uid = ? and withdrawing_cash >= ? and total_withdrawing >= ?", or.UID, or.WithdrawCash, or.Amount). + Updates(updateRe).Error + if err != nil { + log.Error("err :%v", err) + tx.Rollback() + return err + } + if err := UpdateCurrency(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: or.UID, + Type: or.CurrencyType, + Value: or.WithdrawCash, + Event: common.CurrencyEventWithDrawBack, + Exs1: or.OrderID, + }, + }, tx); err != nil { + log.Error("Withdraw callback err:%v", err) + tx.Rollback() + return err + } + // 退还代付券 + // if or.Extra == "useFreeWithdraw" { + // if err := tx.Model(&common.PlayerItems{UID: uid}).Updates(map[string]interface{}{"free_withdraw": gorm.Expr("free_withdraw + 1")}).Error; err != nil { + // log.Error("err:%v", err) + // tx.Rollback() + // return err + // } + // } + if err := tx.Commit().Error; err != nil { + log.Error("err:%v", err) + tx.Rollback() + return err + } + // 失败的时候才发送邮件 + // if status == common.StatusROrderFail { + // util.Go(func() { + // SendWithdrawMail(uid, MailWithdrawType3) + // SendRealWithdrawMail(or) + // }) + // } + return nil +} + +func WritePlayerPayData(data *common.ESPlayerPayData) { + data.Time = time.Now().Unix() + ret, _ := GetUserXInfo(data.UID, "birth") + data.IsNew = IsNewPlayer(ret.Birth) + data.IsNew = util.IsSameDayTimeStamp(ret.Birth, data.Time) + db.ES().InsertToESGO(common.ESIndexBackPlayerPayData, data) +} + +// PayExtra 支付extra字段判断 +func PayExtra(r *common.RechargeOrder) (amount int64) { + if len(r.Extra) == 0 { + return + } + extraData := &common.ActivityRechargeData{} + json.Unmarshal([]byte(r.Extra), extraData) + switch extraData.ID { + case common.ItemDiscountTicket: + list := GetUserItemByExi1(r.UID, common.ItemDiscountTicket, extraData.I1) + if len(list) == 0 { + return + } + item := list[0] + rows, err := db.Mysql().UpdateRes(&common.PlayerItems{ID: item.ID, Status: common.ItemStatusNormal}, + map[string]interface{}{"status": common.ItemStatusInvalid}) + if err != nil || rows == 0 { + log.Error("err:%v", err) + return + } + amount = extraData.I2 + } + return +} + +// PayActivity 支付活动 +func PayActivity(r *common.RechargeOrder, notCharge bool, user *common.PlayerDBInfo) (err error) { + // VIP + UpdateVip(r.UID, r.Amount, 0, 0) + // 检查任务 + CheckTask(r) + // 检查所有活动 + CheckAllActivity(r) + + // 更新loginrecord + if notCharge { + InsertLoginRecord(r.UID, r.ChannelID, user.IP, user.Birth, user.Platform) + CheckShare(r) + } + + return nil +} + +func CheckTask(r *common.RechargeOrder) { + now := time.Now().Unix() + con := GetConfigTask() + for _, v := range con { + if (v.Type == common.TaskTypeOnceRecharge || v.Type == common.TaskTypeFirstRecharge) && r.Amount >= v.Target { // 单次充值任务 || 首次充值任务 + data := GetUserTaskDataByTaskID(r.UID, v.TaskID) + if data.ID == 0 { + db.Mysql().Create(&common.TaskData{UID: r.UID, TaskID: v.TaskID, Time: now, Progress: v.Target}) + } else if data.Progress == 0 { + db.Mysql().Update(&common.TaskData{UID: r.UID, TaskID: v.TaskID}, map[string]interface{}{"progress": v.Target, "time": now}) + } + } else if v.Type == common.TaskTypeTotalRecharge { // 累充任务 + data := GetUserTaskDataByTaskID(r.UID, v.TaskID) + if data.ID == 0 { + db.Mysql().Create(&common.TaskData{UID: r.UID, TaskID: v.TaskID, Time: now, Progress: r.Amount}) + } else if data.Progress >= 0 { + db.Mysql().Update(&common.TaskData{UID: r.UID, TaskID: v.TaskID}, map[string]interface{}{"progress": gorm.Expr("progress + ?", r.Amount), "time": now}) + } + } + } +} + +func ActivityFirstRechargeBack(r *common.RechargeOrder) { + now := time.Now().Unix() + if IsActivityValid(common.ActivityIDFirstRechargeBack) { + rechargeBackData := GetUserFirstRechargeBackData(r.UID) + if rechargeBackData.RechargeTime == 0 { + db.Mysql().Create(&common.ActivityFirstRechargeBackData{UID: r.UID, RechargeTime: time.Now().Unix(), Amount: r.Amount}) + } else if now-rechargeBackData.RechargeTime < common.ActivityFirstRechargeBackTime { + db.Mysql().Update(&common.ActivityFirstRechargeBackData{UID: r.UID}, map[string]interface{}{"amount": gorm.Expr("amount + ?", r.Amount)}) + } + } +} + +func CheckAllActivity(r *common.RechargeOrder) { + // 首充返还活动 + ActivityFirstRechargeBack(r) + + var product *common.ConfigPayProduct + if r.ProductID > 0 { + product = GetConfigPayProductByID(r.ProductID) + } + // slots奖池活动 + ActivitySlots(r, product) + + if product == nil { + return + } + + switch product.ActivityID { + case common.ActivityIDBreakGift: + ActivityBreakGift(r, product) + case common.ActivityIDWeekCard: + ActivityWeekCard(r, product) + case common.ActivityIDLuckyShop: + ActivityLuckyShop(r, product) + case common.ActivityIDSevenDayBox: + ActivitySevenDayBox(r, product) + case common.ActivityIDSuper: + ActivitySuper(r, product) + } +} + +func ActivityBreakGift(r *common.RechargeOrder, product *common.ConfigPayProduct) { + payData := GetPlayerPayData(r.UID) + gift := GetConfigActivityBreakGiftByProductID(r.ProductID) + if util.SliceContain(payData.SubBreakGift, gift.Level) { + return + } + payData.SubBreakGift = append(payData.SubBreakGift, gift.Level) + str, _ := json.Marshal(payData.SubBreakGift) + rows, err := db.Mysql().UpdateRes(&common.PlayerPayData{UID: r.UID, BreakGift: payData.BreakGift}, map[string]interface{}{"break_gift": string(str)}) + if rows == 0 || err != nil { + log.Error("err:%v", err) + return + } + // ok + UpdateCurrencyPro(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: r.UID, + Value: product.Value, + Event: common.CurrencyEventActivityBreakGift, + Type: common.CurrencyBrazil, + NeedBet: GetConfigCurrencyResourceNeedBet(common.CurrencyResourceBonus, product.Value), + ChannelID: r.ChannelID, + Exi1: product.ProductID, + }, + }) + UploadActivityData(r.UID, common.ActivityIDBreakGift, common.ActivityDataJoin, product.Value) +} + +func ActivityWeekCard(r *common.RechargeOrder, product *common.ConfigPayProduct) { + level := product.Exi + card := GetUserWeekCard(r.UID, level) + con := GetConfigActivityWeekCardByLevel(level) + if con == nil { + return + } + if card.ID == 0 { + err := db.Mysql().Create(&common.ActivityWeekCardData{ + UID: r.UID, + Level: level, + DayReward: con.DayReward, + Day: con.Day, + // LastDraw: time.Now().Unix(), + }) + if err != nil { + return + } + } else { + if card.Day > 0 { + return + } + rows, err := db.Mysql().UpdateResW(&common.ActivityWeekCardData{}, map[string]interface{}{ + "day": con.Day, "day_reward": con.DayReward, "get_discount": 0, "last_draw": 0}, + fmt.Sprintf("uid = %d and level = %d and day = 0", r.UID, level)) + if err != nil || rows == 0 { + log.Error("err:%v", err) + return + } + } + // ok + UpdateCurrencyPro(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: r.UID, + Value: product.Value, + Event: common.CurrencyEventActivityWeekCard, + Type: common.CurrencyBrazil, + NeedBet: GetConfigCurrencyResourceNeedBet(common.CurrencyResourceBonus, product.Value), + ChannelID: r.ChannelID, + Exi1: product.ProductID, + }, + }) + UploadActivityData(r.UID, common.ActivityIDWeekCard, common.ActivityDataJoin, product.Value) +} + +func ActivityLuckyShop(r *common.RechargeOrder, product *common.ConfigPayProduct) { + rows, err := db.Mysql().UpdateResW(&common.ActivityLuckyShopData{}, map[string]interface{}{"buy": 1}, + fmt.Sprintf("uid = %d and product_id = %d and buy = 0", r.UID, product.ProductID)) + value := product.Value + if rows == 0 || err != nil { + // 直接给玩家按商品原价加钱 + value = r.Amount + } + UpdateCurrencyProReal(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: r.UID, + Type: r.CurrencyType, + Value: value, + Event: common.CurrencyEvent(r.Event), + Exs1: r.OrderID, + Exi1: int(r.Amount), // 充值金额传递 + Exi2: r.ProductID, + NeedBet: GetConfigCurrencyResourceNeedBet(common.CurrencyResourceBonus, value), + }, + }) + UploadActivityData(r.UID, common.ActivityIDLuckyShop, common.ActivityDataJoin, product.Value) +} + +func ActivitySlots(r *common.RechargeOrder, product *common.ConfigPayProduct) { + if !IsActivityValid(common.ActivityIDSlots) { + return + } + if product != nil && product.ActivityID == common.ActivityIDSlots { + if product.Exi > 0 { + UpdateUserActivitySlotsData(r.UID, int(product.Exi)) + } + UpdateCurrencyProReal(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: r.UID, + Type: r.CurrencyType, + Value: product.Value, + Event: common.CurrencyEvent(r.Event), + Exs1: r.OrderID, + Exi1: int(r.Amount), // 充值金额传递 + Exi2: r.ProductID, + NeedBet: GetConfigCurrencyResourceNeedBet(common.CurrencyResourceBonus, product.Value), + }, + }) + return + } + count := r.Amount / 1000000000 + if count >= 1 { + UpdateUserActivitySlotsData(r.UID, int(count)) + } +} + +func ActivitySevenDayBox(r *common.RechargeOrder, product *common.ConfigPayProduct) { + data := &common.ActivitySevenDayBoxData{UID: r.UID} + db.Mysql().Get(data) + now := time.Now().Unix() + value := product.Value + if util.IsSameDayTimeStamp(now, data.Time) { // 一天只能买一次 + value = r.Amount + } else { + if data.ID == 0 { + data.Time = now + data.Count = 1 + err := db.Mysql().Create(data) + if err != nil { + value = r.Amount + } + } else { + rows, err := db.Mysql().UpdateResW(&common.ActivitySevenDayBoxData{}, map[string]interface{}{"count": gorm.Expr("count + 1"), "time": now}, + fmt.Sprintf("uid = %d and count = %d and time = %d", r.UID, data.Count, data.Time)) + if rows == 0 || err != nil { + // 直接给玩家按商品原价加钱 + value = r.Amount + } + } + } + if value > 0 { + UpdateCurrencyProReal(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: r.UID, + Type: r.CurrencyType, + Value: value, + Event: common.CurrencyEvent(r.Event), + Exs1: r.OrderID, + Exi1: int(r.Amount), // 充值金额传递 + Exi2: r.ProductID, + NeedBet: GetConfigCurrencyResourceNeedBet(common.CurrencyResourceBonus, value), + }, + }) + } +} + +func ActivitySuper(r *common.RechargeOrder, product *common.ConfigPayProduct) { + data := GetUserActivitySuperData(r.UID) + value := product.Value + if !data.CanBuy { + value = r.Amount + } else { + rows, err := db.Mysql().UpdateResW(&common.ActivitySuperData{}, map[string]interface{}{"open": 0, "time": time.Now().Unix()}, + fmt.Sprintf("uid = %d and time = %d", r.UID, data.Time)) + if rows == 0 || err != nil { + log.Error("err:%v", err) + value = r.Amount + } + } + if value > 0 { + UpdateCurrencyProReal(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: r.UID, + Type: r.CurrencyType, + Value: value, + Event: common.CurrencyEvent(r.Event), + Exs1: r.OrderID, + Exi1: int(r.Amount), // 充值金额传递 + Exi2: r.ProductID, + NeedBet: GetConfigCurrencyResourceNeedBet(common.CurrencyResourceBonus, value), + }, + }) + } +} + +type IFSCRet struct { + Ifsc string `json:"IFSC"` +} + +func CheckIFSC(ifsc string) bool { + ret := &IFSCRet{} + client := http.Client{ + Timeout: 2 * time.Second, + } + url := config.GetConfig().Web.IFSCURL + "/" + ifsc + log.Debug("url:%v", url) + resp, err := client.Get(url) + if err != nil { + log.Error("get err:%v", err) + return true + } + // if resp.StatusCode != http.StatusOK { + // log.Error("req fail err code:%v", resp.StatusCode) + // return true + // } + defer resp.Body.Close() + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Error("read err %v", err) + return true + } + json.Unmarshal(data, ret) + return ret.Ifsc != "" +} + +func GetTotalRechargePer(thisWithdraw int64) int64 { + zero := util.GetZeroTime(time.Now()).Unix() + recharge := db.Mysql().Sum(&common.RechargeOrder{}, fmt.Sprintf("create_time >= %d and event = %d and status = %d", zero, common.CurrencyEventReCharge, common.StatusROrderPay), "amount") + withdraw := db.Mysql().Sum(&common.WithdrawOrder{}, fmt.Sprintf("create_time >= %d and event = %d and status = %d", zero, common.CurrencyEventWithDraw, common.StatusROrderFinish), "amount") + withdraw += thisWithdraw + if recharge == 0 { + return withdraw * 100 / common.DecimalDigits + } + return withdraw * 100 / recharge +} diff --git a/call/protocol.go b/call/protocol.go new file mode 100644 index 0000000..d95b183 --- /dev/null +++ b/call/protocol.go @@ -0,0 +1,38 @@ +package call + +import ( + "server/common" + "server/pb" + "strconv" + "strings" + + "github.com/liangdas/mqant/log" +) + +func GetModuleName(id int) string { + if id == 1000 { + return "hall" + } + if id == 1100 { + return "common" + } + if id == 1101 { + return "matching" + } + return common.GetGameModuleName(id) +} + +func GetModuleID(name string) int { + if name == "hall" { + return int(pb.ServerType_ServerTypeGate) + } + if name == "common" { + return int(pb.ServerType_ServerTypeCommon) + } + name = strings.ReplaceAll(name, common.GameModulePrefix, "") + id, err := strconv.Atoi(name) + if err != nil { + log.Error("err:%v", err) + } + return id +} diff --git a/call/queue.go b/call/queue.go new file mode 100644 index 0000000..063cdef --- /dev/null +++ b/call/queue.go @@ -0,0 +1,47 @@ +package call + +import "server/util" + +var ( + execQueue = NewQueue() +) + +// 顺序执行并发操作 +func InitExecQueue() { + execQueue.Run() +} + +func AddQueue(f func()) { + execQueue.AddQueue(f) +} + +type ExecQueue struct { + c chan func() +} + +func (e *ExecQueue) Run() { + util.Go(func() { + for { + f := <-e.c + func() { + defer util.Recover() + f() + }() + } + }) +} + +func (e *ExecQueue) AddQueue(f func()) { + e.c <- f +} + +func NewQueue(size ...int) *ExecQueue { + e := &ExecQueue{} + if size != nil { + e.c = make(chan func(), size[0]) + } else { + e.c = make(chan func(), 10000) + } + e.Run() + return e +} diff --git a/call/realMail.go b/call/realMail.go new file mode 100644 index 0000000..be11c8f --- /dev/null +++ b/call/realMail.go @@ -0,0 +1,203 @@ +package call + +import ( + "crypto/tls" + "encoding/json" + "errors" + "fmt" + "math/rand" + "net/smtp" + "server/common" + "server/config" + "server/db" + "strings" + "time" + + "github.com/liangdas/mqant/log" +) + +const ( + RealMailSuccessTitle = "Withdrawal succeeded! [%v]" + RealMailSuccessContext = "Deal %v:\n" + + "\n" + + "Congratulations! Your latest withdrawal has been successfully transferred to your bank account. Please check your account." + + "If there is any question about this order, please feel free to contact our customer service below.\n" + + "\n" + + "Email:%v\n" + + "\n" + + "Whatsapp:%v\n" + + "\n" + + "Thanks for your playing!\n" + + "\n" + + "\n" + + "Best wishes\n" + + "\n" + + "%v" + RealMailFailTitle = "Withdrawal failure! [%v]" + RealMailFailContext = "Deal %v:\n" + + "\n" + + "Important information about your withdrawal order:\n" + + "\n" + + "AccountName:%v\n" + + "\n" + + "Mobile:%v\n" + + "\n" + + "%v\n" + + "\n" + + "Your latest withdrawal order has failed for the following reasons:\n" + + "\n" + + "\t1.Wrong UPI/BANK information you submitted. Please CAREFULLY check your UPI/BANK information and try to withdraw again:\n" + + "\n" + + "\t\t(1) The UPI/BANK account shall not have any space;\n" + + "\t\t(2) The bank IFSC Code shall be 11 digital letters formatted as 'AAAA0XXXXXX;\n" + + "\n" + + "\t2.Bank refused. The bank refused this order because of your bank account problems.\n" + + "\n" + + "If withdrawal still fails after all your information was checked, please get in touch with customer service:\n" + + "\n" + + "\t1.Email:%v\n" + + "\t2.Whatsapp:%v\n" + + "\n" + + "Do not worry! The refunds of the withdrawal order that failed will be returned to your game account.\n" + + "\n" + + "\n" + + "Kind regards\n" + + "\n" + + "%v" +) + +type realMail struct { + user string + passwd string +} + +// 初始化用户名和密码 +func NewMail(u string, p string) *realMail { + temp := &realMail{user: u, passwd: p} + return temp +} + +// 标题 文本 目标邮箱 +func (m *realMail) Send(title string, text string, toId string) error { + auth := smtp.PlainAuth("", m.user, m.passwd, "smtp.gmail.com") + + tlsconfig := &tls.Config{ + InsecureSkipVerify: true, + ServerName: "smtp.gmail.com", + } + + conn, err := tls.Dial("tcp", "smtp.gmail.com:465", tlsconfig) + if err != nil { + log.Error("real:%+v,err:%v", *m, err) + return err + } + + client, err := smtp.NewClient(conn, "smtp.gmail.com") + if err != nil { + log.Error("real:%+v,err:%v", *m, err) + return err + } + + if err = client.Auth(auth); err != nil { + log.Error("real:%+v,err:%v", *m, err) + return err + } + + if err = client.Mail(m.user); err != nil { + log.Error("real:%+v,err:%v", *m, err) + return err + } + + if err = client.Rcpt(toId); err != nil { + log.Error("real:%+v,toId:%v,err:%v", *m, toId, err) + return err + } + + w, err := client.Data() + if err != nil { + log.Error("real:%+v,err:%v", *m, err) + return err + } + + msg := fmt.Sprintf("From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n%s", m.user, toId, title, text) + + _, err = w.Write([]byte(msg)) + if err != nil { + log.Error("real:%+v,err:%v", *m, err) + return err + } + + err = w.Close() + if err != nil { + log.Error("real:%+v,err:%v", *m, err) + return err + } + + client.Quit() + return nil +} + +// SendRealWithdrawMail 给玩家真实的邮箱发送邮件 +func SendRealWithdrawMail(or *common.WithdrawOrder) error { + withdrawCommon := &common.WithdrawCommon{} + if err := json.Unmarshal([]byte(or.PayAccount), withdrawCommon); err != nil { + log.Error("err:%v", err) + return err + } + if withdrawCommon.Email == "" { + log.Error("or:%+v,withdrawCommon:%+v,email is null", or, withdrawCommon) + return fmt.Errorf("or:%+v,email is null", or) + } + if strings.Contains(GetConfigPlatform().Email, withdrawCommon.Email) { + return nil + } + accounts := config.GetBase().Mails.Accounts + if len(accounts) == 0 { + return nil + } + + channel := GetChannelByID(or.ChannelID) + if channel == nil { + return errors.New("unknown channel") + } + title := "" + context := "" + status := 0 + if or.Status == common.StatusROrderFail { + status = 0 + payInfo := "" + // if withdrawCommon.DrawType == common.WithdrawTypeUPI { + // payInfo = fmt.Sprintf("UPI Account:%v", withdrawCommon.BankCode) + // } else if withdrawCommon.DrawType == common.WithdrawTypeBank { + // payInfo = fmt.Sprintf("BankCard Number:%v\n\nIFSC Code:%v", withdrawCommon.BankCardNo, withdrawCommon.BankCode) + // } + title = fmt.Sprintf(RealMailFailTitle, channel.Name) + context = fmt.Sprintf(RealMailFailContext, withdrawCommon.Name, withdrawCommon.Name, + withdrawCommon.Mobile, payInfo, GetConfigPlatform().Email, GetConfigPlatform().Whatsapp, channel.Name) + } else if or.Status == common.StatusROrderFinish { + status = 1 + title = fmt.Sprintf(RealMailSuccessTitle, channel.Name) + context = fmt.Sprintf(RealMailSuccessContext, withdrawCommon.Name, GetConfigPlatform().Email, + GetConfigPlatform().Whatsapp, channel.Name) + } + if db.Redis().Exist(common.GetRedisKeyRealMail(or.UID, status)) { + return nil + } + + pass := config.GetBase().Mails.Pass + rans := rand.Perm(len(accounts)) + // 只尝试一次 + for i := 0; i < 1; i++ { + index := rans[i] + if index > len(pass)-1 { + break + } + m := NewMail(accounts[index], pass[index]) + if err := m.Send(title, context, withdrawCommon.Email); err == nil { + // 每24小时同类型邮件只发送一次 + db.Redis().SetData(common.GetRedisKeyRealMail(or.UID, status), 1, 24*time.Hour) + return nil + } + } + return errors.New("send fail") +} diff --git a/call/redpoint.go b/call/redpoint.go new file mode 100644 index 0000000..9fa4a41 --- /dev/null +++ b/call/redpoint.go @@ -0,0 +1,53 @@ +package call + +import ( + "fmt" + "reflect" + "server/common" + "server/db" + "server/pb" + "strings" + + "gorm.io/gorm" +) + +const ( + ModuleMail = "Mail" +) + +// UpsertRedPointAndNotify 更新红点并通知客户端 +func UpsertRedPointAndNotify(uid, num int, module string) { + red := &common.PlayerRed{UID: uid} + db.Mysql().Get(red) + val := reflect.ValueOf(red).Elem().FieldByName(module) + diff := int(val.Int()) + num + if red.ID == 0 { + if diff < 0 { + diff = 0 + } + val.SetInt(int64(diff)) + db.Mysql().Create(red) + } else { + update := map[string]interface{}{} + if num == 0 || diff <= 0 { + update[module] = 0 + val.SetInt(0) + } else { + update[module] = gorm.Expr(fmt.Sprintf("%v + ?", strings.ToLower(module)), num) + val.SetInt(int64(diff)) + } + db.Mysql().Update(&common.PlayerRed{UID: uid}, update) + } + send := &pb.RedPoint{} + reflect.ValueOf(send).Elem().FieldByName(module).SetUint(uint64(diff)) + SendNR(uid, int(pb.ServerCommonResp_CommonRedPointResp), send, "common") +} + +func PushRed(uid int) { + redPoints := &common.PlayerRed{UID: uid} + db.Mysql().Get(redPoints) + checkMail(uid, redPoints) + one := new(pb.RedPoint) + one.Mail = uint32(redPoints.Mail) + SendNR(uid, int(pb.ServerCommonResp_CommonRedPointResp), one, "common") +} diff --git a/call/reload.go b/call/reload.go new file mode 100644 index 0000000..495e905 --- /dev/null +++ b/call/reload.go @@ -0,0 +1,442 @@ +package call + +import ( + "server/common" + "server/natsClient" + "server/pb" + + "github.com/liangdas/mqant/log" + "github.com/nats-io/nats.go" +) + +var ( + reloadFuncs = map[int][]func(*pb.ReloadGameConfig) error{} +) + +func LoadConfigs(funcs map[int][]func(*pb.ReloadGameConfig) error) error { + if funcs == nil { + funcs = map[int][]func(*pb.ReloadGameConfig) error{} + } + CommonReload(funcs) + for _, v := range funcs { + for _, k := range v { + if err := k(nil); err != nil { + log.Error("err:%v", err) + // return err + } + } + } + reloadFuncs = funcs + return nil +} + +// LoadSomeConfigs 加载特定配置 +func LoadSomeConfigs(funcs map[int][]func(*pb.ReloadGameConfig) error) error { + for _, v := range funcs { + for _, k := range v { + if err := k(nil); err != nil { + log.Error("err:%v", err) + return err + } + } + } + reloadFuncs = funcs + return nil +} + +func InitReload(conn *nats.Conn) { + natsClient.NewReloadNats(conn, ReloadConfig) +} + +func ReloadConfig(c *pb.ReloadGameConfig) { + log.Debug("reload %v", c.Type) + if fs, ok := reloadFuncs[int(c.Type)]; ok { + for _, v := range fs { + if err := v(c); err != nil { + log.Error("err:%v", err) + return + } + } + } +} + +func CommonReload(c map[int][]func(*pb.ReloadGameConfig) error) { + if _, ok := c[common.ReloadWhiteList]; !ok { + c[common.ReloadWhiteList] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := InitWhite(); err != nil { + log.Error("err:%v", err) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadChannel]; !ok { + c[common.ReloadChannel] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadChannels(); err != nil { + log.Error("err:%v", err) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadPlatform]; !ok { + c[common.ReloadPlatform] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigPlatform(); err != nil { + log.Error("err:%v", err) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadNotice]; !ok { + c[common.ReloadNotice] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigNotice(); err != nil { + log.Error("从mysql加载公告失败, error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadBroadcast]; !ok { + c[common.ReloadBroadcast] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigBroadcast(); err != nil { + log.Error("从mysql加载广播失败, error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigActivity]; !ok { + c[common.ReloadConfigActivity] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigActivity(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigPayProduct]; !ok { + c[common.ReloadConfigPayProduct] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigPayProduct(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigPayWeight]; !ok { + c[common.ReloadConfigPayWeight] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigPayChannels(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigWithdrawWeight]; !ok { + c[common.ReloadConfigWithdrawWeight] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigWithdrawChannels(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigVip]; !ok { + c[common.ReloadConfigVip] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigVIP(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigTron]; !ok { + c[common.ReloadConfigTron] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigTron(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigWithdrawProduct]; !ok { + c[common.ReloadConfigWithdrawProduct] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigWithdrawProduct(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigGameProvider]; !ok { + c[common.ReloadConfigGameProvider] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadGames(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigGameList]; !ok { + c[common.ReloadConfigGameList] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadGames(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigGameTypes]; !ok { + c[common.ReloadConfigGameTypes] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigGameTypes(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigGameMarks]; !ok { + c[common.ReloadConfigGameMarks] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigGameMarks(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigFirstPageGames]; !ok { + c[common.ReloadConfigFirstPageGames] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigFirstPageGames(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigRWPer]; !ok { + c[common.ReloadConfigRWPer] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigRWPer(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigCurrencyRateUSD]; !ok { + c[common.ReloadConfigCurrencyRateUSD] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigCurrencyRateUSD(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigServerVersion]; !ok { + c[common.ReloadConfigServerVersion] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadServerVersions(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigGameRoom]; !ok { + c[common.ReloadConfigGameRoom] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigGameRooms(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigRobot]; !ok { + c[common.ReloadConfigRobot] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigRobots(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigAppSpin]; !ok { + c[common.ReloadConfigAppSpin] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigAppSpin(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigShare]; !ok { + c[common.ReloadConfigShare] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigShare(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigShareSys]; !ok { + c[common.ReloadConfigShareSys] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigShareSys(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigActivityPddSpin]; !ok { + c[common.ReloadConfigActivityPddSpin] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigActivityPddSpin(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigActivityPdd]; !ok { + c[common.ReloadConfigActivityPdd] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigActivityPdd(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigTask]; !ok { + c[common.ReloadConfigTask] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigTask(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigCurrencyResource]; !ok { + c[common.ReloadConfigCurrencyResource] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigCurrencyResource(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigFirstPay]; !ok { + c[common.ReloadConfigFirstPay] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigFirstPay(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigActivityFreeSpin]; !ok { + c[common.ReloadConfigActivityFreeSpin] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigActivityFreeSpin(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigActivityFirstRechargeBack]; !ok { + c[common.ReloadConfigActivityFirstRechargeBack] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigActivityFirstRechargeBack(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigBanner]; !ok { + c[common.ReloadConfigBanner] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigBanner(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigLuckyCode]; !ok { + c[common.ReloadConfigLuckyCode] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigActivityLuckCode(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigActivitySign]; !ok { + c[common.ReloadConfigActivitySign] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigActivitySign(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigActivityBreakGift]; !ok { + c[common.ReloadConfigActivityBreakGift] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigActivityBreakGift(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigShareRobot]; !ok { + c[common.ReloadConfigShareRobot] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigShareRobot(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigActivityWeekCard]; !ok { + c[common.ReloadConfigActivityWeekCard] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigActivityWeekCard(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigActivitySlots]; !ok { + c[common.ReloadConfigActivitySlots] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigActivitySlots(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigActivityLuckyShop]; !ok { + c[common.ReloadConfigActivityLuckyShop] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigActivityLuckyShop(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigServerFlag]; !ok { + c[common.ReloadConfigServerFlag] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigServerFlag(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } + if _, ok := c[common.ReloadConfigActivitySevenDayBox]; !ok { + c[common.ReloadConfigActivitySevenDayBox] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := LoadConfigActivitySevenDayBox(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + return nil + }} + } +} diff --git a/call/session.go b/call/session.go new file mode 100644 index 0000000..0c9eef6 --- /dev/null +++ b/call/session.go @@ -0,0 +1,155 @@ +package call + +import ( + "context" + "fmt" + "server/common" + "server/db" + "server/natsClient" + "server/pb" + "server/util" + "strconv" + + "github.com/gogo/protobuf/proto" + "github.com/liangdas/mqant/gate" + basegate "github.com/liangdas/mqant/gate/base" + "github.com/liangdas/mqant/log" + "github.com/liangdas/mqant/module" + mqrpc "github.com/liangdas/mqant/rpc" + "github.com/liangdas/mqant/selector" +) + +var ( + caller module.Module +) + +// NewCaller 新建一个调用对象 +func NewCaller(m module.Module) { + caller = m +} + +func GetCaller() module.Module { + return caller +} + +func GetTopicName() string { + name := caller.GetType() + return name +} + +// SendSS 给玩家发送消息 +func SendSS(session gate.Session, pid int, data proto.Message, t ...string) error { + payload := []byte{} + var err error + if data != nil { + payload, err = proto.Marshal(data) + if err != nil { + log.Error("err:%v", err) + return err + } + } + moduleName := "" + if t != nil { + moduleName = t[0] + } else { + moduleName = GetTopicName() + } + // moduleType := pb.ServerType_name[pb.ModuleType_value[moduleName]] + // moduleType := GetModuleID(moduleName) + topic := fmt.Sprintf("%v:%v", moduleName, strconv.Itoa(pid)) + // log.Debug("topic:%v", topic) + session.SendNR(topic, payload) + return nil +} + +// SendSSBytes 给玩家发送消息 +func SendSSBytes(session gate.Session, pid int, payload []byte) error { + // moduleType := GetModuleID(caller.GetType()) + // moduleType := pb.ServerType_name[pb.ModuleType_value[caller.GetType()]] + topic := fmt.Sprintf("%v:%v", GetTopicName(), pid) + session.SendNR(topic, payload) + return nil +} + +// SendNR 给玩家发送消息 注意:必须先调用NewCaller初始化 +func SendNR(uid int, pid int, data proto.Message, t ...string) { + util.Go(func() { + payload, err := proto.Marshal(data) + if err != nil { + log.Error("err:%v", err) + return + } + moduleName := "" + if t != nil { + moduleName = t[0] + } else { + moduleName = GetTopicName() + } + // moduleType := GetModuleID(moduleName) + // moduleType := pb.ServerType_name[pb.ModuleType_value[moduleName]] + topic := fmt.Sprintf("%v:%v", moduleName, pid) + + g := GetUserSession(uid) + if g == nil { + // log.Error("get session fail") + return + } + if err := g.Send(topic, payload); err != "" { + log.Error("err:%v", err) + return + } + }) +} + +// Publish 发布消息 +func Publish(topic string, data proto.Message) error { + send, _ := proto.Marshal(data) + err := caller.GetApp().Transport().Publish(topic, send) + if err != nil { + log.Error("err:%v", err) + return err + } + return nil +} + +// Publish 发布消息 +func PublishRequest(topic, reply string, data proto.Message) error { + send, _ := proto.Marshal(data) + return caller.GetApp().Transport().PublishRequest(topic, reply, send) +} + +// RPC 服务器内部调用 +func RPC(module, method string, param proto.Message, opts ...selector.SelectOption) (interface{}, string) { + return caller.GetApp().Call(context.Background(), module, method, mqrpc.Param(param), opts...) +} + +func GetUserSession(uid int) gate.Session { + s := db.Redis().GetUserSession(uid) + if s == nil { + // log.Error("get session fail") + return nil + } + g, _ := basegate.NewSession(caller.GetApp(), nil) + g.SetSessionID(s.SessionID) + g.SetServerID(s.GateID) + g.SetUserID(strconv.Itoa(uid)) + return g +} + +// Broadcast 发送广播 +func BroadcastReq(one *common.ConfigBroadcast, content ...string) { + c := one.Content + for _, v := range content { + c = v + } + data := &pb.InnerBroadcast{ + ID: int32(one.ID), + Content: c, + Priority: int32(one.Priority), + Frequency: int32(one.LoopFrequency), + Interval: int32(one.Interval), + } + // log.Debug("broadcastReq:%+v", data) + send, _ := proto.Marshal(data) + caller.GetApp().Transport().Publish(natsClient.TopicBroadcastReq, send) +} diff --git a/call/share.go b/call/share.go new file mode 100644 index 0000000..9ae5cb5 --- /dev/null +++ b/call/share.go @@ -0,0 +1,156 @@ +package call + +import ( + "fmt" + "reflect" + "server/common" + "server/db" + "server/pb" + "server/util" + "time" + + "github.com/liangdas/mqant/log" + "gorm.io/gorm" +) + +func GetShareInfo(uid int) *common.ShareInfo { + shareInfo := &common.ShareInfo{UID: uid} + db.Mysql().Get(shareInfo) + if shareInfo.ID <= 0 { + info, _ := GetUserXInfo(uid, "channel_id") + shareInfo.ChannelID = info.ChannelID + shareInfo.Share = util.GetShareCode(uid) + shareInfo.Time = time.Now().Unix() + db.Mysql().Create(shareInfo) + } + return shareInfo +} + +// 分享查询 +func ShareBind(share string, isOld bool, uid, cid int) { + // 绑定 + if share == "" || isOld { + return + } + // 一级 + upInfo := &common.ShareInfo{Share: share} + db.Mysql().Get(upInfo) + if upInfo.ID <= 0 { + return + } + shareInfo := &common.ShareInfo{UID: uid, UP1: upInfo.UID, UP2: upInfo.UP1, UP3: upInfo.UP2, Time: time.Now().Unix(), ChannelID: cid, Share: util.GetShareCode(uid)} + db.Mysql().Create(shareInfo) + + // 更新上级邀请玩家数 + db.Mysql().Update(&common.ShareInfo{UID: upInfo.UID}, map[string]interface{}{"invites": gorm.Expr("invites + 1")}) +} + +// 判断分享,发放有效用户奖励 +func CheckShare(r *common.RechargeOrder) { + reward := GetConfigShareSys().ShareReward + if reward <= 0 { + return + } + if r.Amount < GetConfigShareSys().ShareRecharge { + return + } + share := GetShareInfo(r.UID) + if share.UP1 == 0 { + return + } + // 发放奖励 + db.Mysql().Update(&common.ShareInfo{UID: share.UP1}, map[string]interface{}{ + "invalid_invites": gorm.Expr("invalid_invites + 1"), + "invite_reward": gorm.Expr("invite_reward + ?", reward), + "available_reward": gorm.Expr("available_reward + ?", reward), + }) +} + +// 投注奖励结算 +func ShareSettle(d *pb.InnerAfterSettle) { + shareInfo := &common.ShareInfo{UID: int(d.UID)} + db.Mysql().Get(shareInfo) + if shareInfo.UP1 == 0 { + return + } + db.Mysql().Update(&common.ShareInfo{UID: int(d.UID)}, map[string]interface{}{"bet": gorm.Expr("bet + ?", d.TotalBet)}) + + ref := reflect.ValueOf(shareInfo).Elem() + // 循环查询上级 + for i := 1; i <= 3; i++ { + uid := int(ref.FieldByName(fmt.Sprintf("UP%d", i)).Int()) + if uid == 0 { + break + } + con := GetConfigShareByLevel(i) + if con == nil { + log.Error("unknown config share level:%v", i) + continue + } + // 发奖 + reward := d.TotalBet * con.Per / 1000 + if reward <= 0 { + continue + } + db.Mysql().Update(&common.ShareInfo{UID: uid}, map[string]interface{}{ + "bet_reward": gorm.Expr("bet_reward + ?", reward), + "available_reward": gorm.Expr("available_reward + ?", reward), + }) + } +} + +func PackLevelSql(uid, level int) (sql string) { + if level == 0 { + sql = fmt.Sprintf("up1 = %d or up2 = %d or up3 = %d", uid, uid, uid) + } else { + sql = fmt.Sprintf("up%d = %d", level, uid) + } + return +} + +// GetUserShares 查询玩家下级数量 +func GetUserShares(uid, level int) int64 { + return db.Mysql().Count(&common.ShareInfo{}, PackLevelSql(uid, level)) +} + +// GetUserShareRecharges 查询玩家下级充值人数 +func GetUserShareRecharges(uid, level int) (count int64) { + sql := fmt.Sprintf(`SELECT count(*) as count from + (SELECT uid from share_info WHERE %s)a + INNER JOIN + (SELECT uid from recharge_info WHERE total_recharge > 0)b + on a.uid = b.uid`, PackLevelSql(uid, level)) + err := db.Mysql().C().Raw(sql).Scan(&count).Error + if err != nil { + log.Error("err:%v", err) + } + return +} + +// GetUserShareValidRecharges 查询玩家下级有效充值人数 +func GetUserShareValidRecharges(uid, level int) (count int64) { + sql := fmt.Sprintf(`SELECT count(*) as count from + (SELECT uid from share_info WHERE %s)a + INNER JOIN + (SELECT uid from recharge_info WHERE total_recharge >= %d)b + on a.uid = b.uid`, PackLevelSql(uid, level), GetConfigShareSys().ShareRecharge) + err := db.Mysql().C().Raw(sql).Scan(&count).Error + if err != nil { + log.Error("err:%v", err) + } + return +} + +// GetUserShareRechargeAmount 查询玩家下级充值总额 +func GetUserShareRechargeAmount(uid, level int) (count int64) { + sql := fmt.Sprintf(`SELECT sum(b.total_recharge) as count from + (SELECT uid from share_info WHERE %s)a + INNER JOIN + (SELECT uid,total_recharge from recharge_info WHERE total_recharge > 0)b + on a.uid = b.uid`, PackLevelSql(uid, level)) + err := db.Mysql().C().Raw(sql).Scan(&count).Error + if err != nil { + log.Error("err:%v", err) + } + return +} diff --git a/call/statistics.go b/call/statistics.go new file mode 100644 index 0000000..1af5d08 --- /dev/null +++ b/call/statistics.go @@ -0,0 +1,573 @@ +package call + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "mime/multipart" + "net/http" + "net/url" + "server/common" + "server/config" + "server/db" + "server/util" + "strconv" + "strings" + "time" + + "github.com/liangdas/mqant/log" +) + +// 初始化在线人数上报 +func InitOnline(f func() []*common.ESNewOnline) { + now := time.Now() + zero := util.GetZeroTime(now) + h, m, _ := now.Clock() + m -= m % 5 + lastRecord := zero.Add(time.Duration(h) * time.Hour).Add(time.Duration(m) * time.Minute) + next := lastRecord.Add(5 * time.Minute).Sub(now) + time.AfterFunc(next, func() { + WriteOnline(lastRecord.Add(5*time.Minute).Unix(), f) + InitOnline(f) + }) +} + +// WriteOnline 写入在线统计 +func WriteOnline(time int64, f func() []*common.ESNewOnline) { + data := f() + if len(data) == 0 { + return + } + for i := range data { + if data[i].Total == 0 { + continue + } + data[i].Time = time + db.ES().InsertToESGO(common.ESIndexBackPlayerOnline, data[i]) + } +} + +type FBEvents struct { + EventName string `json:"event_name"` + EventTime int64 `json:"event_time"` + UserData struct { + Em string `json:"em,omitempty"` + Ph string `json:"ph,omitempty"` + FN string `json:"fn,omitempty"` + LN string `json:"ln,omitempty"` + Country string `json:"country,omitempty"` + IP string `json:"client_ip_address,omitempty"` + ClientUserAgent string `json:"client_user_agent"` + FBC string `json:"fbc,omitempty"` + FBP string `json:"fbp,omitempty"` + ExternalID string `json:"external_id,omitempty"` + CT string `json:"ct,omitempty"` // 城市,哈希处理 + ST string `json:"st,omitempty"` // 州,哈希处理 + ZP string `json:"zp,omitempty"` // 邮编,哈希处理 + } `json:"user_data"` + CustomData struct { + Currency string `json:"currency"` + Value int64 `json:"value"` + } `json:"custom_data"` + ActionSource string `json:"action_source"` + // AppData struct { + // AdvertiserTrackingEnabled int `json:"advertiser_tracking_enabled"` + // ApplicationTrackingEnabled int `json:"application_tracking_enabled"` + // Extinfo []string `json:"extinfo"` + // } `json:"app_data"` +} + +type FBEvent int + +const ( + FBEventRegist FBEvent = iota + FBEventPurchase +) + +func (f FBEvent) GetName() string { + switch f { + case FBEventRegist: + return "CompleteRegistration" + case FBEventPurchase: + return "Purchase" + default: + return "" + } +} + +// UploadFB 上报fb数据 +func UploadFB(uid int, event FBEvent, amount int64) { + u := &common.PlayerDBInfo{Id: uid} + db.Mysql().Get(u) + + channel := GetChannelByID(u.ChannelID) + if channel == nil || channel.FBPixelID == "" || channel.FBAccessToken == "" || config.GetBase().AD.FBAPIURL == "" { + return + } + if channel.ADUpload != common.ADFB { + return + } + + pi := &common.PayInfo{UID: uid} + db.Mysql().Get(pi) + randPi := &common.PayInfo{} + db.Mysql().C().Raw("SELECT * FROM pay_info ORDER BY RAND() LIMIT 1").Scan(randPi) + pa := &common.PlayerADData{UID: uid} + db.Mysql().GetLast(pa) + if pa.FBC == "" { + pa = &common.PlayerADData{ChannelID: u.ChannelID} + db.Mysql().C().Raw("SELECT * FROM player_ad_data ORDER BY RAND() LIMIT 1").Scan(pa) + } + // 拿取玩家信息 + em := pi.Email + if em == "" { + em = randPi.Email + } + ph := u.Mobile + if ph == "" { + ph = randPi.Mobile + } + fn, ln := util.FormatUserName(pi.Name) + if fn == "" { + fn, ln = util.FormatUserName(randPi.Name) + } + ua := "$CLIENT_USER_AGENT" + if pa.UserAgent != "" { + ua = pa.UserAgent + } + // 分析ip + var ct, st, zp string + ipinfo, err := SearchIP(u.IP) + if err != nil { + log.Error("err:%v", err) + } else { + ct = util.CalculateSHA256(strings.ToLower(ipinfo.City.Names["en"])) + if len(ipinfo.Subdivisions) > 0 { + st = util.CalculateSHA256(strings.ToLower(ipinfo.Subdivisions[0].Names["en"])) + } + zp = util.CalculateSHA256(strings.ToLower(ipinfo.Postal.Code)) + } + util.IndexTry(func() error { + // 准备事件数据 + var requestBody bytes.Buffer + multipartWriter := multipart.NewWriter(&requestBody) + + // 添加文本字段 + events := []FBEvents{{ + EventName: event.GetName(), + EventTime: time.Now().Unix(), + }} + events[0].UserData.Em = util.CalculateSHA256(strings.ToLower(em)) + events[0].UserData.Ph = util.CalculateSHA256("91" + ph) + events[0].UserData.FN = util.CalculateSHA256(strings.ToLower(fn)) + events[0].UserData.LN = util.CalculateSHA256(strings.ToLower(ln)) + events[0].UserData.IP = u.IP + events[0].UserData.ClientUserAgent = ua + events[0].UserData.FBC = pa.FBC + events[0].UserData.FBP = pa.FBP + events[0].UserData.Country = util.CalculateSHA256("in") + events[0].UserData.ExternalID = util.CalculateSHA256(fmt.Sprintf("%d", uid)) + events[0].UserData.ST = st + events[0].UserData.CT = ct + events[0].UserData.ZP = zp + events[0].ActionSource = "website" + if event == FBEventPurchase { + events[0].CustomData.Currency = "brl" + events[0].CustomData.Value = amount + } + + evJson, _ := json.Marshal(events) + err = multipartWriter.WriteField("data", string(evJson)) + if err != nil { + log.Error("Error writing text field:%v", err) + return err + } + + err = multipartWriter.WriteField("access_token", channel.FBAccessToken) + if err != nil { + log.Error("Error writing text field:%v", err) + return err + } + multipartWriter.Close() + + req, err := http.NewRequest("POST", config.GetBase().AD.FBAPIURL+channel.FBPixelID+"/events?access_token="+channel.FBAccessToken, &requestBody) + if err != nil { + log.Error("err:%v", err) + return err + } + + // 设置请求头,包括 Content-Type + req.Header.Set("Content-Type", multipartWriter.FormDataContentType()) + // 发送请求 + client := &http.Client{ + Timeout: 10 * time.Second, + } + log.Debug("UploadFB:%+v", req) + resp, err := client.Do(req) + if err != nil { + log.Error("err:%v", err) + return err + } + + defer resp.Body.Close() + ret, err := io.ReadAll(resp.Body) + if err != nil { + log.Error("http read body err:%v", err) + return err + } + log.Debug("fbres:%v", string(ret)) + return nil + }) +} + +// UploadAdjust 上报adjust数据 +func UploadAdjust(event common.AdjustEventType, u *common.PlayerDBInfo, param map[string]string) { + channel := GetChannelByID(u.ChannelID) + if channel == nil || (u.ADID == "" && u.GPSADID == "") || channel.AdjustAppToken == "" { + return + } + if channel.ADUpload != common.ADJust { + return + } + util.IndexTry(func() error { + var err error + // if u == nil { + // u, err = GetUserXInfo("channel_id", "adid", "gps_adid") + // if err != nil { + // log.Error("err:%v", err) + // return + // } + // } + reqURL, _ := url.Parse(config.GetConfig().Web.Adjust.URL) + params := url.Values{} + params.Add("app_token", channel.AdjustAppToken) + params.Add("event_token", channel.GetAdjustEventID(int(event))) + params.Add("s2s", "1") + params.Add("created_at", strconv.FormatInt(time.Now().Unix(), 10)) + params.Add("adid", u.ADID) + params.Add("gps_adid", u.GPSADID) + params.Add("web_uuid", u.GPSADID) + for k, v := range param { + params.Add(k, v) + } + reqURL.RawQuery = params.Encode() + + req, err := http.NewRequest("GET", reqURL.String(), nil) + if err != nil { + log.Error("err:%v", err) + return err + } + req.Header.Set("Authorization", "Bearer "+channel.AdjustAuth) + + client := &http.Client{ + Timeout: 5 * time.Second, + } + log.Debug("UploadAdjust:%+v", req) + res, err := client.Do(req) + if err != nil { + log.Error("http post call err:%v", err) + return err + } + ret, err := io.ReadAll(res.Body) + defer res.Body.Close() + if err != nil { + log.Error("http read body err:%v", err) + return err + } + log.Debug("adjustRes:%v", string(ret)) + return nil + }) +} + +// IsOrganic 查询设备是否是自然量 count 重试次数 +func IsOrganic(gpsID, appToken string, cid int, count int) (is bool) { + count-- + // reqURL, _ := url.Parse(config.GetConfig().Web.Adjust.APIURL) + reqURL, _ := url.Parse("https://api.adjust.com/device_service/api/v1/inspect_device") + params := url.Values{} + params.Add("advertising_id", gpsID) + params.Add("app_token", appToken) + reqURL.RawQuery = params.Encode() + var lg string + + defer func() { + if count > 0 && is { + return + } + db.ES().InsertToESByIDGO(common.ESIndexBackABLog, fmt.Sprintf("%d_%s", cid, gpsID), &common.ESABLog{ + Channel: cid, + Time: time.Now().Unix(), + DeviceID: gpsID, + IsA: is, + Log: lg, + }) + }() + + req, err := http.NewRequest("GET", reqURL.String(), nil) + if err != nil { + log.Error("err:%v", err) + lg = err.Error() + return + } + // req.Header.Set("Authorization", "Bearer "+config.GetConfig().Web.Adjust.APIToken) + req.Header.Set("Authorization", "Bearer _2ZGYezfdZ7yD2he-zWx") + + client := &http.Client{ + Timeout: 5 * time.Second, + } + log.Debug("check organic:%+v", req) + res, err := client.Do(req) + if err != nil { + log.Error("http post call err:%v", err) + lg = err.Error() + return + } + if res.Status == http.StatusText(http.StatusNotFound) { + is = true + lg = "http not found" + return + } + ret, err := ioutil.ReadAll(res.Body) + defer res.Body.Close() + if err != nil { + log.Error("http read body err:%v", err) + lg = err.Error() + return + } + log.Debug("AdjustDeviceResp:%v", string(ret)) + ad := new(AdjustDeviceResp) + if err := json.Unmarshal(ret, ad); err != nil { + log.Error("err:%v", err) + lg = string(ret) + } + is = ad.TrackerName == "Organic" + if is { + lg = "Organic" + } + if is && count > 0 { + time.Sleep(time.Second) + return IsOrganic(gpsID, appToken, cid, count) + } + return +} + +// IsOrganic2 查询设备是否是自然量(更为严格,凡是找不到设备号的,一律禁止) count 重试次数 +func IsOrganic2(gpsID, appToken string, cid int, count int) (is bool) { + count-- + // reqURL, _ := url.Parse(config.GetConfig().Web.Adjust.APIURL) + reqURL, _ := url.Parse("https://api.adjust.com/device_service/api/v1/inspect_device") + params := url.Values{} + params.Add("advertising_id", gpsID) + params.Add("app_token", appToken) + reqURL.RawQuery = params.Encode() + var lg string + + defer func() { + if count > 0 && is { + return + } + db.ES().InsertToESByIDGO(common.ESIndexBackABLog, fmt.Sprintf("%d_%s", cid, gpsID), &common.ESABLog{ + Channel: cid, + Time: time.Now().Unix(), + DeviceID: gpsID, + IsA: is, + Log: lg, + }) + }() + + req, err := http.NewRequest("GET", reqURL.String(), nil) + if err != nil { + is = true + log.Error("err:%v", err) + lg = err.Error() + return + } + // req.Header.Set("Authorization", "Bearer "+config.GetConfig().Web.Adjust.APIToken) + req.Header.Set("Authorization", "Bearer _2ZGYezfdZ7yD2he-zWx") + + client := &http.Client{ + Timeout: 5 * time.Second, + } + log.Debug("check organic:%+v", req) + res, err := client.Do(req) + if err != nil { + is = true + log.Error("http post call err:%v", err) + lg = err.Error() + return + } + if res.Status == http.StatusText(http.StatusNotFound) { + is = true + lg = "http not found" + return + } + ret, err := ioutil.ReadAll(res.Body) + defer res.Body.Close() + if err != nil { + is = true + log.Error("http read body err:%v", err) + lg = err.Error() + return + } + log.Debug("AdjustDeviceResp2:%v", string(ret)) + ad := new(AdjustDeviceResp) + if err := json.Unmarshal(ret, ad); err != nil { + log.Error("err:%v", err) + is = true + lg = string(ret) + } + if !is { + is = ad.TrackerName == "Organic" + if is { + lg = "Organic" + } + } + if is && count > 0 { + time.Sleep(time.Second) + return IsOrganic2(gpsID, appToken, cid, count) + } + return +} + +type AdjustDeviceResp struct { + Adid string `json:"Adid"` + AdvertisingID string `json:"AdvertisingId"` + Tracker string `json:"Tracker"` + TrackerName string `json:"TrackerName"` + // ClickTime time.Time `json:"ClickTime"` + // InstallTime time.Time `json:"InstallTime"` + // LastAppVersion string `json:"LastAppVersion"` + // LastAppVersionShort string `json:"LastAppVersionShort"` + // LastSessionTime time.Time `json:"LastSessionTime"` + // LastEventTimes struct { + // Zcqz1Y time.Time `json:"zcqz1y"` + // } `json:"LastEventTimes"` + // PushToken string `json:"PushToken"` + // State string `json:"State"` + // InstallState string `json:"InstallState"` + // SignatureAcceptanceStatus string `json:"SignatureAcceptanceStatus"` + // SignatureVerificationResult string `json:"SignatureVerificationResult"` +} + +// WriteRealOnline 写入各场次实时在线 +func WriteRealOnline(field string, f func() map[int]*common.RedisRealOnline) { + now := time.Now() + zero := util.GetZeroTime(now) + h, m, _ := now.Clock() + m -= m % 5 + lastRecord := zero.Add(time.Duration(h) * time.Hour).Add(time.Duration(m) * time.Minute) + next := lastRecord.Add(5 * time.Minute).Sub(now) + // _, _, s := now.Clock() + // next := now.Add(time.Duration(60-s) * time.Second).Sub(now) + log.Debug("next real:%v", next) + time.AfterFunc(next, func() { + real := f() + for i, v := range real { + keyField := fmt.Sprintf("%v", field) + if i > 0 { + keyField += fmt.Sprintf(":%v", i) + } + key := common.GetRedisKeyOnlineKey(keyField) + if !db.Redis().Exist(key) { + if err := db.Redis().HSet(key, v); err != nil { + log.Error("err:%v", err) + continue + } + if err := db.Redis().Expire(key, next-2*time.Second); err != nil { + log.Error("err:%v", err) + continue + } + } else { + if err := db.Redis().HIncrBy(key, "Total", int64(v.Total)); err != nil { + log.Error("err:%v", err) + continue + } + if err := db.Redis().HIncrBy(key, "New", int64(v.New)); err != nil { + log.Error("err:%v", err) + continue + } + } + } + WriteRealOnline(field, f) + }) +} + +type KwaiReq struct { + Clickid string `json:"clickid"` + EventName string `json:"event_name"` + PixelID string `json:"pixelId"` + AccessToken string `json:"access_token"` + TestFlag bool `json:"testFlag"` + TrackFlag bool `json:"trackFlag"` + IsAttributed int `json:"is_attributed"` // 归因标记,此处需写死为1 + Mmpcode string `json:"mmpcode"` // 标记数据来源,此处需写死为 PL + PixelSdkVersion string `json:"pixelSdkVersion"` // 版本信息,此处需写死为,9.9.9 + Properties string `json:"properties"` +} +type KwaiResp struct { + Result int `json:"result"` // 返回状态码 + ErrorMsg string `json:"error_msg"` // 返回信息详情 +} + +type KwaiProperties struct { + Currency string `json:"currency"` + Price float64 `json:"price"` +} + +const ( + KwaiEventRegist = iota + KwaiEventPay +) + +func GetKwaiEventName(event int) string { + switch event { + case KwaiEventRegist: + return "EVENT_COMPLETE_REGISTRATION" + case KwaiEventPay: + return "EVENT_PURCHASE" + } + return "" +} + +func UploadKwai(uid, event int, amount int64) { + u := &common.PlayerDBInfo{Id: uid} + db.Mysql().Get(u) + channel := GetChannelByID(u.ChannelID) + if channel == nil || (u.ADID == "" && u.GPSADID == "") || channel.AdjustAppToken == "" { + return + } + if channel.ADUpload != common.ADKwai { + return + } + pa := &common.PlayerADData{UID: uid} + db.Mysql().GetLast(pa) + if pa.FBC == "" { + pa = &common.PlayerADData{ChannelID: u.ChannelID} + db.Mysql().C().Raw("SELECT * FROM player_ad_data ORDER BY RAND() LIMIT 1").Scan(pa) + } + send := &KwaiReq{ + Clickid: pa.FBC, + EventName: GetKwaiEventName(event), + PixelID: channel.FBPixelID, + AccessToken: channel.FBAccessToken, + TestFlag: false, + TrackFlag: !config.GetBase().Release, + IsAttributed: 1, + Mmpcode: "PL", + PixelSdkVersion: "9.9.9", + } + if event == KwaiEventPay { + pro := &KwaiProperties{ + Currency: "brl", + Price: float64(amount/1e6) / 100, + } + byt, _ := json.Marshal(pro) + send.Properties = string(byt) + } + util.IndexTry(func() error { + ret := &KwaiResp{} + return util.HttpPost(config.GetBase().AD.KwaiAPIURL, send, ret, nil) + }) +} diff --git a/call/sys.go b/call/sys.go new file mode 100644 index 0000000..9c68a00 --- /dev/null +++ b/call/sys.go @@ -0,0 +1,183 @@ +package call + +import ( + "fmt" + "server/common" + "server/config" + "server/db" + "sync" + "time" + + "github.com/liangdas/mqant/log" + "github.com/olivere/elastic/v7" +) + +// WhitePass 白名单鉴权 +func WhitePass(ip, uuid, path string, version, channel int) bool { + if !IsWhiteOpen(channel, version, path) { + return true + } + log.Debug("check white ip:%v,uuid:%v,path:%v,version:%v,channel:%v", ip, uuid, path, version, channel) + one := GetWhite(ip, uuid) + log.Debug("white:%+v", one) + if one != nil { + // if one.Version < version { + // return false + // } + return one.PowerPass(path) + } + return false +} + +var ( + ChannelWhiteMap sync.Map + Whites []*common.WhiteList +) + +const ( + SysListType = iota // 用作系统开关时的值 + WhiteListType + BlackListType +) + +func ReloadWhite(channel, opt int) { + val, ok := ChannelWhiteMap.Load(channel) + if !ok { + return + } + w := val.(*common.WhiteList) + w.LimitType = opt +} + +func IsWhiteOpen(channel, version int, path string) bool { + val, ok := ChannelWhiteMap.Load(channel) + if !ok { + return false + } + w := val.(*common.WhiteList) + if w.LimitType == 2 { + return false + } + // 如果当前版本号大于白名单开启的发布版本,直接全功能限制 + if version > w.Version { + return true + } + // 如果当前版本小与等于白名单发布版本,只限制部分功能 + if common.IsLimitMap(path) { + return true + } + return false +} + +func GetWhite(ip, uuid string) *common.WhiteList { + for i, v := range Whites { + if v.Content == ip || v.Content == uuid { + return Whites[i] + } + } + return nil +} + +func IsWhite(ip, uuid string) bool { + for _, v := range Whites { + if ip != "" && v.Content == ip { + return true + } + if uuid != "" && v.Content == uuid { + return true + } + } + return false +} + +func InitWhite() error { + one := []*common.WhiteList{} + if _, err := db.ES().QueryList(common.ESIndexBackWhiteList, 0, 5000, elastic.NewTermQuery("ListType", SysListType), &one); err != nil { + log.Error("err:%v", err) + return err + } + for _, v := range one { + log.Debug("all white:%+v", *v) + } + for i := range one { + ChannelWhiteMap.Store(one[i].Channel, one[i]) + } + Whites = []*common.WhiteList{} + q := elastic.NewBoolQuery() + // q.MustNot(elastic.NewMatchQuery("ListType", SysListType)) + q.Must(elastic.NewMatchQuery("ListType", WhiteListType)) + if _, err := db.ES().QueryList(common.ESIndexBackWhiteList, 0, 5000, q, &Whites); err != nil { + log.Error("err:%v", err) + return err + } + for _, v := range Whites { + log.Debug("all Whites:%+v", *v) + } + return nil +} + +// IsExamine 是否是审核人员 +func IsExamine(ip string) bool { + log.Debug("check examine ip:%v", ip) + return db.ES().Exist(common.ESIndexBackExamineList, ip) +} + +// CheckChannel 检查渠道是否要屏蔽 +func CheckChannel(cid int, ip string) bool { + sip, err := SearchIP(ip) + if err != nil { + log.Error("err:%v", err) + return true + } + switch cid { + case 10002: + for _, v := range config.GetConfig().Web.PassRegion { + if sip.Subdivisions[0].Names["en"] == v { + return true + } + } + } + return false +} + +// CheckExamine 检查是否进审核服 +func CheckExamine(version int, ip, gpsID string, channel *common.Channel) (isExamine bool) { + if IsWhite(ip, "") || common.IsShareChannel(channel.ChannelID) { + return + } + if version == channel.Version { + log.Debug("examine ip:%v", ip) + isExamine = true + db.ES().InsertToESByIDGO(common.ESIndexBackExamineList, ip, &common.ExamineList{Time: time.Now().Unix()}) + return + } + // else if IsExamine(ip) { + // isExamine = true + // } else + if config.GetBase().Release { + ipInfo, err := SearchIP(ip) + if err != nil { + log.Error("err:%v", err) + return + } + if ipInfo.Country.IsoCode == "CN" { + isExamine = true + return + } + } + if channel.IgnoreOrganic == 2 && len(gpsID) > 0 { + if IsOrganic(gpsID, channel.AdjustAppToken, channel.ChannelID, 3) { + var count int64 + if channel.ChannelID >= 46 { + count = db.Mysql().Count(&common.PlayerDBInfo{}, fmt.Sprintf("channel_id = %v and deviceid = '%v'", channel.ChannelID, gpsID)) + } else { + count = db.Mysql().Count(&common.PlayerDBInfo{}, fmt.Sprintf("channel_id = %v and (deviceid = '%v' or gps_adid = '%v')", channel.ChannelID, gpsID, gpsID)) + } + if count == 0 { + isExamine = true + return + } + } + } + return +} diff --git a/call/timer.go b/call/timer.go new file mode 100644 index 0000000..d2fca10 --- /dev/null +++ b/call/timer.go @@ -0,0 +1,16 @@ +package call + +import ( + "server/util" + "time" + + timewheel "github.com/liangdas/mqant/module/modules/timer" +) + +func InitTimeWheel(closeSig chan bool) { + timewheel.GetTimeWheel().Stop() + timewheel.SetTimeWheel(timewheel.New(100*time.Millisecond, 36)) + util.Go(func() { + timewheel.GetTimeWheel().Start(closeSig) + }) +} diff --git a/call/user.go b/call/user.go new file mode 100644 index 0000000..765c21f --- /dev/null +++ b/call/user.go @@ -0,0 +1,664 @@ +package call + +import ( + "encoding/json" + "errors" + "fmt" + "math/rand" + "reflect" + "server/common" + "server/config" + "server/db" + "server/natsClient" + "server/pb" + "server/util" + "strconv" + "time" + + "github.com/liangdas/mqant/log" + "gorm.io/gorm" +) + +// 查询用户的信息 +func GetUserInfo(uid int) (ret *common.PlayerDBInfo, err error) { + ret = db.Redis().GetUserData(uid) + if ret != nil { + return + } + ret = &common.PlayerDBInfo{Id: uid} + err = db.Mysql().Get(ret) + return +} + +// 查询用户的信息 +func GetUserXInfo(uid int, fields ...string) (ret *common.PlayerDBInfo, err error) { + ret = new(common.PlayerDBInfo) + err = db.Redis().GetUserXInfo(uid, ret, fields...) + if err == nil { + return + } + // sqlFields := []string{} + // for _, v := range fields { + // sqlFields = append(sqlFields, v) + // } + ret = &common.PlayerDBInfo{Id: uid} + err = db.Mysql().SelectField(ret, fields...) + return +} + +// 查询用户的信息 +func GetUserXInfoByDB(uid int, fields ...string) (ret *common.PlayerDBInfo, err error) { + ret = &common.PlayerDBInfo{Id: uid} + err = db.Mysql().SelectField(ret, fields...) + return +} + +// UpdateUserXinfo更新玩家信息 +func UpdateUserXInfo(p *common.PlayerDBInfo, update map[string]interface{}) error { + if err := db.Mysql().Update(p, update); err != nil { + log.Error("err:%v", err) + return err + } + if err := db.Redis().UpdateUserFields(p.Id, update); err != nil { + log.Error("err:%v", err) + return err + } + return nil +} + +// AddUserXInfo 更新玩家某个字段 +func AddUserXInfo(uid int, field string, value int64, tx ...*gorm.DB) error { + p := &common.PlayerDBInfo{Id: uid} + dbUpdate := map[string]interface{}{field: gorm.Expr(fmt.Sprintf("%v + ?", field), value)} + if tx == nil { + if err := db.Mysql().Update(p, dbUpdate); err != nil { + log.Error("err:%v", err) + return err + } + } else { + if err := tx[0].Model(p).Where(p).Updates(dbUpdate).Error; err != nil { + log.Error("err:%v", err) + return err + } + } + if err := db.Redis().HIncrBy(common.GetRedisKeyUser(uid), field, value); err != nil { + log.Error("err:%v", err) + return err + } + return nil +} + +func initPlayer(uid, channel int) { + db.Mysql().Create(&common.PlayerData{UID: uid}) + initCoin := GetConfigPlatform().NewPlayerGift + if initCoin < 0 { + initCoin = 0 + } + db.Mysql().Create(&common.PlayerCurrency{UID: uid, ChannelID: channel, BRL: initCoin}) + // db.Mysql().C().Table(common.PlayerRechargeTableName).Create(&common.PlayerCurrency{UID: uid, ChannelID: channel}) + db.Mysql().Create(&common.PlayerProfile{UID: uid, ChannelID: channel, NeedBet: GetConfigCurrencyResourceNeedBet(common.CurrencyResourceBonus, initCoin)}) +} + +func NewUser(info *common.PlayerDBInfo, ip, share, fbc, fbp, agent string) error { + // if info.AreaCode == "" { + // i, err := call.SearchIP(ip) + // log.Debug("searchip:%v,res:%v", ip, i) + // if err != nil { + // log.Error("err:%v", err) + // } + // id := call.GetCountryIDByCName(i.Country) + // log.Debug("i.Country:%v", i.Country) + // if id == "" { + // id = "US" + // } + // info.AreaCode = id + // } + isOld := false + if config.GetBase().Release && !IsWhite(ip, "") { + // 首先判断ip + // if db.Mysql().Count(&common.PlayerDBInfo{}, fmt.Sprintf("ip = '%s' and channel_id = %d", ip, info.ChannelID)) >= int64(config.GetConfig().Web.MaxPlayerAccountIP) { + // log.Debug("ip:%v 创建过多账号", ip) + // return errors.New("ip") + // } + if info.DeviceId != "" { + if IsBlackListPlayer(&common.BlackList{DeviceID: info.DeviceId, Phone: info.Mobile}) { + log.Debug("deviceid:%v 在黑名单里", info.DeviceId) + return errors.New("ip") + } + count := db.Mysql().Count(&common.PlayerDBInfo{}, fmt.Sprintf("deviceid = '%v' and channel_id = %d", info.DeviceId, info.ChannelID)) + isOld = count > 1 + if count >= int64(config.GetConfig().Web.MaxPlayerAccountIP) { + log.Debug("deviceid:%v 创建过多账号", info.DeviceId) + return errors.New("ip") + } + } + } + + r := []rune(info.Nick) + if len(r) > 13 { + info.Nick = string(r[:13]) + } + if info.Avatar == "" { + info.Avatar = RandomUserAvatar() + } + info.Status = common.AccountStatusNormal + info.Birth = time.Now().Unix() + info.Country = GetCountry(ip) + // initCoin := GetConfigPlatform().NewPlayerGift + // if info.AccountType == common.PlatformPhone { + // initCoin += GetConfigPlatform().BindPhoneGift + // } + // info.BindCash = initCoin + if err := db.Mysql().Create(info); err != nil { + return err + } + // 游客昵称 + if len(r) < 2 { + info.Nick = fmt.Sprintf("User%v", info.Id) + if err := db.Mysql().Update(&common.PlayerDBInfo{Id: info.Id}, map[string]interface{}{"nick": info.Nick}); err != nil { + log.Error("err:%v", err) + return err + } + } + uid := info.Id + cid := info.ChannelID + // balance := &common.CurrencyBalance{UID: uid, Type: common.CurrencyTypeBindCash, + // Event: common.CurrencyEventNewPlayer, Value: initCoin, ChannelID: info.ChannelID, + // Balance: initCoin, Time: time.Now().Unix(), Exs1: "NewPlayer"} + util.Go(func() { + initPlayer(uid, cid) + UploadAdjust(common.AdjustEventNewPlayer, info, nil) + ShareBind(share, isOld, uid, cid) + + // 新手赠送 + first := config.GetConfig().Web.FreeSpinFirst + if first > 0 { + UpdateCurrencyPro(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: uid, + Value: first, + ChannelID: info.ChannelID, + Type: common.CurrencyBrazil, + Event: common.CurrencyEventActivityFreeSpin, + NeedBet: GetConfigCurrencyResourceNeedBet(common.CurrencyResourceBonus, first), + }, + }) + } + FBBind(ip, uid, cid, fbc, fbp, agent) + UploadFB(uid, FBEventRegist, 0) + UploadKwai(uid, KwaiEventRegist, 0) + }) + + return nil +} + +// fb绑定 +func FBBind(ip string, uid, cid int, fbc, fbp, agent string) { + // 优先ip绑定 + pa := &common.PlayerADData{IP: ip, ChannelID: cid} + db.Mysql().GetLast(pa) + if pa.ID != 0 { + if pa.UID == 0 { + db.Mysql().Update(pa, map[string]interface{}{"uid": uid}) + } else { + newPa := &common.PlayerADData{UID: uid, IP: ip, ChannelID: cid, FBC: pa.FBC, FBP: pa.FBP, UserAgent: agent} + db.Mysql().Create(newPa) + } + return + } + + // 绑定fb数据 + if len(fbc) != 0 || len(fbp) != 0 { + pa := &common.PlayerADData{ChannelID: cid, FBC: fbc, FBP: fbp} + db.Mysql().GetLast(pa) + if pa.ID == 0 { + newPa := &common.PlayerADData{UID: uid, IP: ip, ChannelID: cid, FBC: pa.FBC, FBP: pa.FBP, UserAgent: agent} + db.Mysql().Create(newPa) + return + } + if pa.UID == 0 { + db.Mysql().Update(pa, map[string]interface{}{"uid": uid}) + return + } + newPa := &common.PlayerADData{UID: uid, IP: ip, ChannelID: cid, FBC: pa.FBC, FBP: pa.FBP, UserAgent: agent} + db.Mysql().Create(newPa) + return + } +} + +func RandomUserAvatar() string { + max := GetConfigPlatform().AvatarCount + if max <= 0 { + max = 15 + } + ran := rand.Intn(max) + 1 + return strconv.Itoa(ran) +} + +func RandomRobotAvatar() string { + ranMethod := rand.Intn(100) + // 30%使用卡通头像 + if ranMethod < 30 { + return RandomUserAvatar() + } + max := GetConfigPlatform().AvatarCount + if max <= 0 { + max = 200 + } + ran := rand.Intn(max) + return strconv.Itoa(ran) +} + +func IsUserNickValid(nick string) bool { + n := []rune(nick) + if len(n) < 1 || len(n) > 15 { + return false + } + return true +} + +func InsertLoginRecord(uid, channel int, ip string, birth int64, deviceType int) { + date := time.Now().Format("20060102") + one := &common.LoginRecord{UID: uid, ChannelID: channel, Date: date} + db.Mysql().Get(one) + re := GetRechargeInfo(uid) + isRecharge := 2 + if re.TotalRecharge > 0 { + isRecharge = 1 + } + if one.ID > 0 { + u := &common.LoginRecord{} + u.ID = one.ID + db.Mysql().Update(u, map[string]interface{}{"time": time.Now().Unix(), "platform": deviceType, "ip": ip, "is_recharge": isRecharge}) + return + } + one.IP = ip + one.UID = uid + one.Date = date + one.ChannelID = channel + one.Time = time.Now().Unix() + one.Platform = deviceType + one.FirstTime = util.GetZeroTime(time.Unix(birth, 0)).Unix() + one.IsRecharge = isRecharge + + if util.IsSameDayTimeStamp(birth, one.Time) { + one.Status = common.NewUser + } else { + one.Status = common.OldUser + } + + db.Mysql().Create(one) +} + +func GetVIP(uid int) *common.VipData { + data := &common.VipData{UID: uid} + err := db.Mysql().Get(data) + if err == gorm.ErrRecordNotFound { + re := &common.RechargeInfo{UID: uid} + db.Mysql().Get(re) + data.Exp = re.TotalRecharge + if data.Exp > 0 { + cons := GetConfigVIP() + for i := len(cons) - 1; i >= 0; i-- { + if data.Exp >= cons[i].Exp && cons[i].Exp >= 0 { + data.Level = cons[i].Level + 1 + break + } + } + } + util.Go(func() { + db.Mysql().Create(data) + }) + } else { + level := GetVipLevel(data.Exp, data.Bet) + if level != data.Level { + data.Level = level + db.Mysql().UpdateRes(&common.VipData{UID: uid}, map[string]interface{}{"level": level}) + } + } + return data +} + +func GetVipCon(uid int) *common.ConfigVIP { + data := &common.VipData{UID: uid} + err := db.Mysql().Get(data) + if err == gorm.ErrRecordNotFound { + util.Go(func() { + db.Mysql().Create(data) + }) + } + return GetConfigVIPByLevel(data.Level) +} + +func GetVipLevel(exp, bet int64) int { + con := GetConfigVIP() + for i := len(con) - 2; i >= 0; i-- { + if con[i].Exp > 0 && exp >= con[i].Exp && con[i].Bet > 0 && bet >= con[i].Bet { // 升级 + return con[i].Level + } + } + return 0 +} + +// UpdateVip 增加vip经验 +func UpdateVip(uid int, exp, bet, settle int64) { + for i := 0; i < 3; i++ { + vip := GetVIP(uid) + update := map[string]interface{}{} + if exp > 0 { + update["exp"] = gorm.Expr("exp + ?", exp) + } + if bet > 0 { + update["bet"] = gorm.Expr("bet + ?", bet) + } + con := GetVipCon(uid) + win := bet - settle + // 刷新返利盈利 + now := time.Now().Unix() + reset := false + if config.GetBase().Release { + reset = !util.IsSameDayTimeStamp(now, vip.ProfitTime) + } else { + reset = util.GetNext5MinUnix()-vip.ProfitTime >= 5*60 + } + if reset { + if con != nil && vip.ProfitTime != 0 { + update["cashback"] = vip.Profit * con.Cashback / 1000 + } + update["profit"] = win + update["profit_time"] = now + } else { + update["profit"] = gorm.Expr("profit + ?", win) + } + level := GetVipLevel(vip.Exp+exp, vip.Bet+bet) + if level != vip.Level { // 升级 + update["level"] = level + } + res := db.Mysql().C().Model(&common.VipData{}).Where("uid = ? and exp = ? and bet = ?", uid, vip.Exp, vip.Bet).Updates(update) + if res.Error != nil { + log.Error("err:%v", res.Error) + } + if res.RowsAffected > 0 { + // 通知客户端等级变动 + if update["level"] != nil { + SendNR(uid, int(pb.ServerCommonResp_CommonVipResp), &pb.ConfigChangeResp{Type: pb.ConfigChangeType_ConfigVipLevel}, "common") + } + break + } + } +} + +// IsBlackListPlayer 判断用户是否是黑名单用户 +func IsBlackListPlayer(data *common.BlackList) bool { + if GetConfigPlatform().BlackList == 0 { + return false + } + phone := data.Phone + payAccount := data.PayAccount + email := data.Email + deviceID := data.DeviceID + if phone == "" && payAccount == "" && email == "" && deviceID == "" { + return false + } + sql := "" + if len(phone) > 0 { + sql += fmt.Sprintf("phone = '%s' ", phone) + } + if len(payAccount) > 0 { + if len(sql) > 0 { + sql += fmt.Sprintf("or pay_account = '%s' ", payAccount) + } else { + sql += fmt.Sprintf("pay_account = '%s' ", payAccount) + } + } + if len(deviceID) > 0 { + if len(sql) > 0 { + sql += fmt.Sprintf("or deviceid = '%s' ", deviceID) + } else { + sql += fmt.Sprintf("deviceid = '%s' ", deviceID) + } + } + if len(email) > 0 { + if len(sql) > 0 { + sql += fmt.Sprintf("or email = '%s' ", email) + } else { + sql += fmt.Sprintf("email = '%s' ", email) + } + } + if len(sql) == 0 { + return false + } + var count int64 + db.Mysql().C().Model(&common.BlackList{}).Where(sql).Limit(1).Count(&count) + return count > 0 +} + +// 封号 +func KickPlayer(uid int) { + UpdateUserXInfo(&common.PlayerDBInfo{Id: uid}, map[string]interface{}{"status": 2}) + player, _ := GetUserXInfo(uid, "token") + if player.Token != "" { + db.Redis().Delkey(common.GetRedisKeyToken(player.Token)) + } + db.Redis().Delkey(common.GetRedisKeyUser(uid)) + Publish(natsClient.TopicInnerOptPlayer, &pb.InnerOptPlayer{UID: uint32(uid), Opt: common.OptPlayerTypeKick}) +} + +// BlackListAndKick 判断用户是否是黑名单用户并且直接封号 +func BlackListAndKick(uid int, data *common.BlackList) bool { + if IsBlackListPlayer(data) { + KickPlayer(uid) + db.ES().InsertToESGO(common.ESIndexBackBlackList, &common.ESBlackList{UID: uid, Name: data.Name, + PayAccount: data.PayAccount, Email: data.Email, Phone: data.Phone, Time: time.Now().Unix()}) + return true + } + return false +} + +// GetItems 拉取用户道具 +func GetItems(uid int) *common.PlayerItems { + data := &common.PlayerItems{UID: uid} + err := db.Mysql().Get(data) + if err == gorm.ErrRecordNotFound { + util.Go(func() { + db.Mysql().Create(data) + }) + } + return data +} + +// IsUserBan 判断用户是否被禁 +func IsUserBan(uid int) bool { + p, _ := GetUserXInfo(uid, "status") + return p.Status == common.AccountStatusLimit +} + +// 赢钱账户的总额 +func GetUserCurrency(uid int, t common.CurrencyType) int64 { + var ret int64 + db.Mysql().C().Model(&common.PlayerCurrency{UID: uid}).Select(t.GetCurrencyName()).Scan(&ret) + return ret +} + +// 获取充值账户的总额 +func GetUserCurrencyRecharge(uid int, t common.CurrencyType) int64 { + var pcr int64 + db.Mysql().C().Table(common.PlayerRechargeTableName).Where("uid = ?", uid).Select(t.GetCurrencyName()).Scan(&pcr) + return pcr +} + +// 获取充值账户+赢钱账户的总额 +func GetUserCurrencyTotal(uid int, t common.CurrencyType) int64 { + var pc, pcr int64 + db.Mysql().C().Model(&common.PlayerCurrency{UID: uid}).Select(t.GetCurrencyName()).Scan(&pc) + db.Mysql().C().Table(common.PlayerRechargeTableName).Where("uid = ?", uid).Select(t.GetCurrencyName()).Scan(&pcr) + return pc + pcr +} + +func GetUserCurrencyFloat(uid int, t common.CurrencyType, num int) float64 { + var pc, pcr int64 + if err := db.Mysql().C().Model(&common.PlayerCurrency{UID: uid}).Select(t.GetCurrencyName()).Scan(&pc).Error; err != nil { + log.Error("err:%v", err) + } + if err := db.Mysql().C().Table(common.PlayerRechargeTableName).Where("uid = ?", uid).Select(t.GetCurrencyName()).Scan(&pcr).Error; err != nil { + log.Error("err:%v", err) + } + return util.Decimal(float64(pc+pcr)/common.DecimalDigits, num) +} + +func GetUserCurrencyByName(uid int, currencyName string) int64 { + pc := &common.PlayerCurrency{UID: uid} + db.Mysql().Get(pc) + ref := reflect.ValueOf(pc).Elem().FieldByName(currencyName) + if ref.IsValid() { + return ref.Int() + } + return 0 +} + +func IsNewPlayer(birth int64) bool { + return time.Now().Unix()-birth < 24*60*60 +} + +func GetPlayerRechargeInfoByCurrency(uid int, t common.CurrencyType) *common.RechargeInfoCurrency { + reu := &common.RechargeInfoCurrency{} + tableName := fmt.Sprintf("recharge_info_%s", t.GetCurrencyName()) + db.Mysql().C().Table(tableName).Where("uid = ?", uid).Scan(reu) + return reu +} + +func UpdatePlayerRechargeInfoCurrency(uid int, t common.CurrencyType, u map[string]interface{}, tx ...*gorm.DB) error { + tableName := fmt.Sprintf("recharge_info_%s", t.GetCurrencyName()) + if len(tx) > 0 { + return tx[0].Table(tableName).Where("uid = ?", uid).Updates(u).Error + } + return db.Mysql().C().Table(tableName).Where("uid = ?", uid).Updates(u).Error +} + +func GetPlayerProfileByCurrency(uid int, t common.CurrencyType) *common.PlayerProfile { + pp := &common.PlayerProfile{} + tableName := fmt.Sprintf("player_profile_%s", t.GetCurrencyName()) + db.Mysql().C().Table(tableName).Where("uid = ?", uid).Scan(pp) + return pp +} + +func UpdatePlayerProfile(data *common.ESGameData) error { + t := data.Type + bet := data.BetAmount + settle := data.SettleAmount + uid := data.UID + var err error + if data.Channel == 0 || data.Birth == 0 { + p, _ := GetUserXInfo(uid, "channel_id", "birth") + data.Channel = p.ChannelID + data.Birth = p.Birth + } + log.Debug("UpdatePlayerProfile:%+v", data) + + u := map[string]interface{}{"total_bet": gorm.Expr("total_bet + ?", bet), "total_settle": gorm.Expr("total_settle + ?", settle), + "total_counts": gorm.Expr("total_counts + 1")} + if db.Mysql().Exist(&common.PlayerProfile{UID: uid}) { + // 先更新总数据 + db.Mysql().Update(&common.PlayerProfile{UID: uid}, u) + } else { + db.Mysql().Create(&common.PlayerProfile{UID: uid, ChannelID: data.Channel, TotalBet: bet, TotalCounts: 1, TotalSettle: settle}) + } + // if err != nil { + // log.Error("err:%v", err) + // return err + // } + UpdateUserNeedBet(uid, data.BetAmount) + + // 更新货币数据 + tableName := fmt.Sprintf("player_profile_%s", t.GetCurrencyName()) + var count int64 + db.Mysql().C().Table(tableName).Where("uid = ?", uid).Count(&count) + if count > 0 { + u["total_bet"] = gorm.Expr("total_bet + ?", bet) + u["total_settle"] = gorm.Expr("total_settle + ?", settle) + err = db.Mysql().C().Table(tableName).Where("uid = ?", uid).Updates(u).Error + } else { + err = db.Mysql().C().Table(tableName).Create(&common.PlayerProfile{UID: uid, ChannelID: data.Channel, TotalBet: bet, TotalCounts: 1, TotalSettle: settle}).Error + } + if err != nil { + log.Error("err:%v", err) + } + + util.Go(func() { + // 更新vip + UpdateVip(uid, 0, bet, settle) + }) + + // 写入es + data.Time = time.Now().Unix() + data.IsNew = util.IsSameDayTimeStamp(data.Time, data.Birth) + db.ES().InsertToESGO(common.ESIndexGameData, data) + return nil +} + +func GetPlayerData(uid int) *common.PlayerData { + data := &common.PlayerData{UID: uid} + db.Mysql().Get(data) + return data +} + +func GetPlayerPayData(uid int) *common.PlayerPayData { + data := &common.PlayerPayData{UID: uid} + db.Mysql().Get(data) + if data.ID == 0 { + db.Mysql().Create(data) + } + if data.BreakGift != "" { + err := json.Unmarshal([]byte(data.BreakGift), &data.SubBreakGift) + if err != nil { + log.Error("err:%v", err) + } + } + return data +} + +func GetUserNeedBet(uid int) int64 { + pro := &common.PlayerProfile{UID: uid} + db.Mysql().Get(pro) + return pro.NeedBet +} + +func GetUserTaskData(uid int) (ret []common.TaskData) { + if uid == 0 { + return + } + db.Mysql().QueryAll(fmt.Sprintf("uid = %d", uid), "", &common.TaskData{}, &ret) + return +} + +func GetUserTaskDataByTaskID(uid, taskID int) *common.TaskData { + data := &common.TaskData{UID: uid, TaskID: taskID} + db.Mysql().Get(data) + return data +} + +func UpdateUserNeedBet(uid int, amount int64) { + util.Go(func() { + for i := 0; i < 3; i++ { + pro := &common.PlayerProfile{UID: uid} + db.Mysql().Get(pro) + if pro.NeedBet == 0 { + return + } + var rows int64 + var err error + if pro.NeedBet < amount { + rows, err = db.Mysql().UpdateResW(&common.PlayerProfile{}, map[string]interface{}{"need_bet": 0}, fmt.Sprintf("uid = %d and need_bet = %d", uid, pro.NeedBet)) + } else { + rows, err = db.Mysql().UpdateResW(&common.PlayerProfile{}, map[string]interface{}{"need_bet": gorm.Expr("need_bet - ?", amount)}, + fmt.Sprintf("uid = %d and need_bet = %d", uid, pro.NeedBet)) + } + if rows == 0 || err != nil { + log.Error("err:%v", err) + time.Sleep(time.Second) + continue + } + return + } + }) +} diff --git a/call/warn.go b/call/warn.go new file mode 100644 index 0000000..30152de --- /dev/null +++ b/call/warn.go @@ -0,0 +1,1058 @@ +package call + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "server/common" + "server/config" + "server/db" + "server/natsClient" + "server/pb" + "server/util" + "strconv" + "strings" + "sync" + "time" + + "github.com/gogo/protobuf/proto" + "github.com/liangdas/mqant/log" + timewheel "github.com/liangdas/mqant/module/modules/timer" + "github.com/mitchellh/mapstructure" + "github.com/nats-io/nats.go" + "github.com/olivere/elastic/v7" +) + +// 初始化预警监听 +var ( + warnMap map[int]*SysWarn + warnLock sync.RWMutex +) + +func InitWarn(conn *nats.Conn) { + warnMap = map[int]*SysWarn{} + natsClient.NewCommonNatsImp(conn, natsClient.TopicInnerUpdateWarn, func(data []byte) { + updateWarn(data) + }) + + if err := initSysWarns(); err != nil { + log.Error("err:%v", err) + } +} + +func initSysWarns() error { + all := []SysWarn{} + _, err := db.ES().QueryList(common.ESIndexBackWarn, 0, 5000, nil, &all) + if err != nil { + return err + } + warnLock.Lock() + defer warnLock.Unlock() + for _, v := range all { + sysWarn, err := NewSysWarn(v.Channel, v.Type, v.Interval, v.Condition, v.WarnWay, v.WarnMember, v.WarnPhone, v.OtherPhone) + if err != nil { + log.Error("error:%v", err) + continue + } + id := sysWarn.SysWarnInf.ID() + if _, ok := warnMap[id]; ok { + log.Error("重复添加预警:%v", id) + continue + } + warnMap[id] = sysWarn + sysWarn.Start() + } + return nil +} + +func AddSysWarn(channel []int, t, interval int, condition map[string]interface{}, way int, member []int, phone []string, otherPhone []string) error { + sysWarn, err := NewSysWarn(channel, t, interval, condition, way, member, phone, otherPhone) + if err != nil { + return err + } + id := sysWarn.SysWarnInf.ID() + warnLock.Lock() + defer warnLock.Unlock() + if _, ok := warnMap[id]; ok { + return errors.New("重复添加预警") + } + // ok + err = db.ES().InsertToESByID(common.ESIndexBackWarn, strconv.Itoa(id), sysWarn) + if err != nil { + return err + } + warnMap[id] = sysWarn + sysWarn.Start() + return nil +} + +func DelSysWarn(mid string) error { + id, err := strconv.Atoi(mid) + if err != nil { + log.Error("error:%v", err) + return err + } + warnLock.Lock() + defer warnLock.Unlock() + warn, ok := warnMap[id] + if !ok { + return errors.New("预警id不存在") + } + // ok + err = db.ES().DeleteByID(common.ESIndexBackWarn, mid) + if err != nil { + log.Error("error:%v", err) + return err + } + warn.Stop() + delete(warnMap, id) + return nil +} + +func updateWarn(data []byte) { + d := &pb.InnerUpdateWarn{} + err := proto.Unmarshal(data, d) + if err != nil { + log.Error("err:%v", err) + return + } + log.Debug("InnerUpdateWarn:%+v", *d) + warnLock.RLock() + warn, ok := warnMap[int(d.Type)] + warnLock.RUnlock() + if !ok { + return + } + switch d.Type { + case 1: // 更新 + warn.Update(d.Amount) + case 2: // 确认 + warn.Check(d.Amount) + } +} + +// 预警类型 +const ( + WarnTypeMin = iota + WarnTypeRecharge // 充值预警 + WarnTypeWithdraw // 退出预警 + WarnTypeError // 游戏报错预警 + WarnTypeData // 数据异常 + WarnTypeGold // 货币发放预警 + WarnTypeWithdrawGold // 退出存量预警 + WarnTypeExamineWithdraw // 退出审核预警 + WarnTypeActivity // 活动预警 + WarnTypeBroadcast // 广播预警 + WarnTypeShare // 分享预警 + WarnTypeOnline // 在线人数涨跌预警 + WarnTypeRechargeOrder // 充值订单预警 + WarnTypeProfit // 游戏场次利润预警 + WarnTypePay // 支付预警 + WarnTypeWithdrawPer // 退出成功率预警 + WarnTypeAll +) + +// 预警方式 +const ( + WarnWayMin = iota + WarnWayPhone + WarnWayAll +) + +// 预警系统一些常量 +const ( + WarnCheckInterval = 10 * time.Minute +) + +func PublishWarn(t, opt int, data []int64) { + Publish(natsClient.TopicInnerUpdateWarn, &pb.InnerUpdateWarn{Type: uint32(t), Opt: uint32(opt), Amount: data}) +} + +// SysWarn 后台预警 +// ID +// Channel 生效的渠道 +// Type 预警类型 +// Condition 预警条件 +// SysWarnInf 预警子类实现 +// WarnWay 预警方式 1手机短信 +// WarnMember 预警人员id +// Time 创建时间 +// WarnPhone 预警人员的手机号 +// Interval 提醒间隔单位分钟 +// LastWarn 上次预警时间 +type SysWarn struct { + ID string `json:"ID" Redis:"ID"` + Channel []int `json:"Channel" Redis:"Channel"` + Type int `json:"Type" Redis:"Type"` + Condition map[string]interface{} `json:"Condition" Redis:"Condition"` + SysWarnInf SysWarnInf `json:"-" Redis:"SysWarnInf"` + WarnWay int `json:"WarnWay" Redis:"WarnWay"` + WarnMember []int `json:"WarnMember" Redis:"WarnMember"` + Time int64 `json:"Time" Redis:"Time"` + WarnPhone []string `json:"WarnPhone" Redis:"WarnPhone"` + OtherPhone []string `json:"OtherPhone" Redis:"OtherPhone"` + Interval int `json:"Interval" Redis:"Interval"` + LastWarn int64 `json:"LastWarn" Redis:"LastWarn"` + ShouldStart bool `json:"-" Redis:"ShouldStart"` +} + +// NewSysWarn 新增预警 +func NewSysWarn(channel []int, t, interval int, condition map[string]interface{}, way int, member []int, phone []string, otherPhone []string) (*SysWarn, error) { + if len(channel) == 0 { + return nil, errors.New("生效渠道不能为空") + } + if t <= WarnTypeMin || t >= WarnTypeAll { + return nil, errors.New("预警类型不合法") + } + if way <= WarnWayMin || way >= WarnWayAll { + return nil, errors.New("预警方式不合法") + } + /*if len(member) == 0 || len(phone) == 0 { + return nil, errors.New("预警人员不能为空") + }*/ + if interval < 1 { + return nil, errors.New("预警时间间隔不合法") + } + + s := &SysWarn{Channel: channel, Type: t, Condition: condition, WarnWay: way, WarnMember: member, WarnPhone: phone, Interval: interval, OtherPhone: otherPhone} + var sInf SysWarnInf + switch t { + case WarnTypeRecharge: + sInf = new(SysWarnRecharge) + if err := sInf.Init(s); err != nil { + log.Error("err:%v", err) + return nil, err + } + case WarnTypeWithdraw: + sInf = new(SysWarnWithdraw) + if err := sInf.Init(s); err != nil { + log.Error("err:%v", err) + return nil, err + } + case WarnTypeError: + err := errors.New("当前种类预警未实现") + return nil, err + case WarnTypeData: + err := errors.New("当前种类预警未实现") + return nil, err + case WarnTypeWithdrawGold: + s.ShouldStart = true + sInf = new(SysWarnWithdrawStorage) + if err := sInf.Init(s); err != nil { + log.Error("err:%v", err) + return nil, err + } + case WarnTypeExamineWithdraw: + s.ShouldStart = true + sInf = new(SysWarnWithdrawExamine) + if err := sInf.Init(s); err != nil { + log.Error("err:%v", err) + return nil, err + } + case WarnTypeActivity: + s.ShouldStart = true + sInf = new(SysWarnActivity) + if err := sInf.Init(s); err != nil { + log.Error("err:%v", err) + return nil, err + } + case WarnTypeOnline: + s.ShouldStart = true + sInf = new(SysWarnOnline) + if err := sInf.Init(s); err != nil { + log.Error("err:%v", err) + return nil, err + } + case WarnTypeRechargeOrder: + s.ShouldStart = true + sInf = new(SysWarnRechargeOrder) + if err := sInf.Init(s); err != nil { + log.Error("err:%v", err) + return nil, err + } + case WarnTypePay: + s.ShouldStart = true + sInf = new(SysWarnPay) + if err := sInf.Init(s); err != nil { + log.Error("err:%v", err) + return nil, err + } + case WarnTypeWithdrawPer: + s.ShouldStart = true + sInf = new(SysWarnTypeWithdrawPer) + if err := sInf.Init(s); err != nil { + log.Error("err:%v", err) + return nil, err + } + case WarnTypeBroadcast: + err := errors.New("当前种类预警未实现") + return nil, err + case WarnTypeShare: + err := errors.New("当前种类预警未实现") + return nil, err + default: + err := errors.New("未知预警种类") + return nil, err + } + s.SysWarnInf = sInf + s.Time = time.Now().Unix() + return s, nil +} + +func (s *SysWarn) Warn(content string) { + if len(s.WarnPhone) == 0 && len(s.OtherPhone) == 0 { + log.Error("warn:%+v phone invalid", *s) + return + } + if time.Now().Unix()-s.LastWarn < int64(WarnCheckInterval.Seconds()) { + log.Error("预警:%+v过于频繁", *s) + return + } + + res := make(map[string]string) + res["userId"] = config.GetBase().Warn.ID + res["account"] = config.GetBase().Warn.Account + res["password"] = config.GetBase().Warn.Password + allPhones := "" + + for _, v := range util.RemoveDuplication(append(s.WarnPhone, s.OtherPhone...)) { + allPhones += v + "," + } + if len(allPhones) < 2 { + return + } + log.Info("需要通知的手机号码 : [%s]", allPhones) + res["mobile"] = allPhones[:len(allPhones)-1] + res["content"] = config.GetBase().Warn.Sign + content + res["sendTime"] = "" + res["action"] = config.GetBase().Warn.Action + res["checkcontent"] = "0" + + bytesData, err := json.Marshal(res) + if err != nil { + log.Error(err.Error()) + return + } + + req, err := http.NewRequest("POST", config.GetBase().Warn.URL, bytes.NewReader(bytesData)) + if err != nil { + log.Error("err:%v", err) + return + } + + req.Header.Set("Content-Type", "application/json;charset=UTF-8") + client := &http.Client{ + Timeout: 5 * time.Second, + } + + log.Debug("Warn req:%+v", req) + resp, err := client.Do(req) + if err != nil { + log.Error("Warn post err:%v", err) + return + } + + s.LastWarn = time.Now().Unix() + + defer resp.Body.Close() + body, _ := ioutil.ReadAll(resp.Body) + log.Debug("Warn response Body%v:", string(body)) +} + +func (s *SysWarn) Start() { + if !s.ShouldStart { + return + } + timewheel.GetTimeWheel().AddTimerCustom(time.Duration(s.Interval*60)*time.Second, getTimeKey(s.SysWarnInf.ID()), nil, func(arge interface{}) { + s.SysWarnInf.Start() + s.Start() + }) +} + +func (s *SysWarn) Stop() { + timewheel.GetTimeWheel().RemoveTimer(getTimeKey(s.SysWarnInf.ID())) +} + +func (s *SysWarn) Check(data []int64) { + if !util.SliceContain(s.Channel, int(data[0])) { + return + } + s.SysWarnInf.Check(data) +} +func (s *SysWarn) Update(data []int64) { + if !util.SliceContain(s.Channel, int(data[0])) { + return + } + s.SysWarnInf.Update(data) +} + +type SysWarnInf interface { + Update([]int64) // 更新预警数据 + Check([]int64) // 检查是否触发预警 第一个参数必须为渠道id + Start() // 开始预警监控 + Init(*SysWarn) error + ID() int // 获取子预警id +} + +func getTimeKey(id int) string { + return fmt.Sprintf("warnKey:%v", id) +} + +// ========================================================================= +// SysWarnRecharge 充值预警实现 +// Amount 异常次数 +// CurrentAmount 当前异常次数 +type SysWarnRecharge struct { + S *SysWarn + Amount int `json:"Amount" Redis:"Amount"` + CurrentAmount int `json:"CurrentAmount" Redis:"CurrentAmount"` +} + +func (s *SysWarnRecharge) Init(sys *SysWarn) error { + if err := mapstructure.Decode(sys.Condition, s); err != nil { + log.Error("err:%v", err) + return errors.New("解析出错") + } + if s.Amount <= 0 { + return errors.New("异常次数不合法") + } + s.S = sys + return nil +} +func (s *SysWarnRecharge) Start() {} +func (s *SysWarnRecharge) Update(data []int64) { + s.CurrentAmount += int(data[1]) + if s.CurrentAmount >= s.Amount { + s.S.Warn(fmt.Sprintf("充值异常达到%v次", s.CurrentAmount)) + s.CurrentAmount = 0 + } +} +func (s *SysWarnRecharge) Check(data []int64) {} +func (s *SysWarnRecharge) ID() int { + return s.S.Type +} + +// ========================================================================= +// SysWarnWithdraw 退出预警实现 +// MaxWithdraw 最大退出次数 +// WithdrawLimit 单人当日退出额度 +type SysWarnWithdraw struct { + S *SysWarn + MaxWithdraw int64 `json:"MaxWithdraw" Redis:"MaxWithdraw"` + WithdrawLimit int64 `json:"WithdrawLimit" Redis:"WithdrawLimit"` +} + +func (s *SysWarnWithdraw) Init(sys *SysWarn) error { + if err := mapstructure.Decode(sys.Condition, s); err != nil { + log.Error("err:%v", err) + return errors.New("解析出错") + } + if s.MaxWithdraw <= 0 { + return errors.New("最大退出次数不合法") + } + if s.WithdrawLimit <= 0 { + return errors.New("单人当日退出额度不合法") + } + s.S = sys + return nil +} +func (s *SysWarnWithdraw) Start() {} +func (s *SysWarnWithdraw) Update(data []int64) {} + +// 约定第二个参数为uid +func (s *SysWarnWithdraw) Check(data []int64) { + info := new(common.RechargeInfo) + info.UID = int(data[1]) + if err := db.Mysql().Get(info); err != nil { + log.Error("err:%v", err) + return + } + if int64(info.WithdrawCount) > s.MaxWithdraw { + s.S.Warn(fmt.Sprintf("玩家%v已退出%v次", info.UID, info.WithdrawCount)) + } + if info.TotalWithdraw > s.WithdrawLimit { + s.S.Warn(fmt.Sprintf("玩家%v退出金额已达到%v", info.UID, info.TotalWithdraw)) + } +} +func (s *SysWarnWithdraw) ID() int { + return s.S.Type +} + +// ========================================================================= +// SysWarnWithdrawStorage 日均用户退出存量预警 +// Storage 存量值 +type SysWarnWithdrawStorage struct { + S *SysWarn + Storage int64 `json:"Storage" Redis:"Storage"` +} + +func (s *SysWarnWithdrawStorage) Init(sys *SysWarn) error { + if err := mapstructure.Decode(sys.Condition, s); err != nil { + log.Error("err:%v", err) + return errors.New("解析出错") + } + if s.Storage <= 0 { + return errors.New("日均用户退出存量值设置不合法") + } + s.S = sys + return nil +} +func (s *SysWarnWithdrawStorage) Start() { + m := &common.RechargeOrder{} + start := time.Now().Unix() + end := time.Now().AddDate(0, 0, 1).Unix() + con := fmt.Sprintf(`create_time > %d and create_time < %d and event = %v`, start, end, common.StatusROrderFinish) + total := db.Mysql().Sum(m, con, "amount") + if total > s.Storage { + s.S.Warn(fmt.Sprintf("今日退出总额已超过%v", s.Storage)) + } +} +func (s *SysWarnWithdrawStorage) Update(data []int64) {} +func (s *SysWarnWithdrawStorage) Check(data []int64) {} +func (s *SysWarnWithdrawStorage) ID() int { + return s.S.Type +} + +// ========================================================================= +// SysWarnWithdrawExamine 退出审核预警 +// Minute 分钟 +// NoticeTime 通知时间间隔 +// LastNoticeTime 上一次通知时间 ------- +type SysWarnWithdrawExamine struct { + S *SysWarn + Minute int `json:"Minute" Redis:"Minute"` + NoticeTime int64 `json:"NoticeTime" Redis:"NoticeTime"` + LastNoticeTime int64 `json:"LastNoticeTime" Redis:"LastNoticeTime"` + OrderId map[string]bool +} + +func (s *SysWarnWithdrawExamine) Init(sys *SysWarn) error { + if err := mapstructure.Decode(sys.Condition, s); err != nil { + log.Error("err:%v", err) + return errors.New("解析出错") + } + if s.Minute < 0 { + return errors.New("审批退出时间设置不合法") + } + s.S = sys + s.OrderId = make(map[string]bool) + return nil +} +func (s *SysWarnWithdrawExamine) Start() { + datetime := time.Now().Unix() - int64(s.Minute*60) + sqlList := fmt.Sprintf("SELECT * FROM recharge_order WHERE create_time < %d AND event = %v AND status = %v", + datetime, + common.CurrencyEventWithDraw, + common.StatusROrderCreate) + + sqlCount := fmt.Sprintf("SELECT COUNT(*) FROM recharge_order WHERE create_time < %d AND event = %v AND status = %v", + datetime, + common.CurrencyEventWithDraw, + common.StatusROrderCreate) + + var count int64 + err := db.Mysql().QueryBySql(sqlCount, &count) + if err != nil { + log.Error(err.Error()) + return + } + if count == 0 { + return + } + + var order []common.RechargeOrder + err = db.Mysql().QueryBySql(sqlList, &order) + if err != nil { + log.Error(err.Error()) + return + } + + var flag bool + temp := make(map[string]bool) + for i := 0; i < len(order); i++ { + if _, ok := s.OrderId[order[i].OrderID]; ok { + // 历史退出订单未被处理 + flag = true + } + temp[order[i].OrderID] = true + } + s.OrderId = temp + + if flag { + // 历史退出订单未被处理 + now := time.Now().Unix() + if s.LastNoticeTime+s.NoticeTime*60 <= now { + s.S.Warn(fmt.Sprintf("当前有需要审核的订单数量 : [%d],请及时处理", count)) + s.LastNoticeTime = time.Now().Unix() + } + } else { + // 历史退出订单已被处理 短信通知 + s.S.Warn(fmt.Sprintf("当前有需要审核的订单数量 : [%d],请及时处理", count)) + s.LastNoticeTime = time.Now().Unix() + } + +} +func (s *SysWarnWithdrawExamine) Update(data []int64) {} +func (s *SysWarnWithdrawExamine) Check(data []int64) {} +func (s *SysWarnWithdrawExamine) ID() int { + return s.S.Type +} + +// ========================================================================= +// SysWarnActivity 活动预警 +// ActID 活动id +// WarnHour 过期前多久提醒,单位小时 +// RewardAmount 发放总量 +type SysWarnActivity struct { + S *SysWarn + ActID int `json:"ActID" Redis:"ActID"` + WarnHour int `json:"WarnHour" Redis:"WarnHour"` + RewardAmount int64 `json:"RewardAmount" Redis:"RewardAmount"` +} + +func (s *SysWarnActivity) Init(sys *SysWarn) error { + if err := mapstructure.Decode(sys.Condition, s); err != nil { + log.Error("err:%v", err) + return errors.New("解析出错") + } + if s.ActID <= 10000 { + return errors.New("活动id不合法") + } + if s.WarnHour <= 0 { + return errors.New("提醒小时不合法") + } + if s.RewardAmount <= 0 { + return errors.New("发放总量不合法") + } + s.S = sys + return nil +} +func (s *SysWarnActivity) Start() { + m := &common.CurrencyBalance{} + con := fmt.Sprintf(`extern = %v`, s.ActID) + total := db.Mysql().Sum(m, con, "value") + if total > 0 { + s.S.Warn(fmt.Sprintf("活动%v发放金币大于%v,请注意", s.ActID, total)) + } + act := GetConfigActivityByID(s.ActID) + if act.End-time.Now().Unix() < int64(s.WarnHour)*3600 { + s.S.Warn(fmt.Sprintf("活动%v即将过期,请注意", s.ActID)) + } +} +func (s *SysWarnActivity) Update(data []int64) {} +func (s *SysWarnActivity) Check(data []int64) {} +func (s *SysWarnActivity) ID() int { + return s.ActID +} + +// ========================================================================= +// OnlineWarn 在线预警实现 +// OldOnlineTotal 上一个时间间隔的在线人数纪录 +// OnlinePer 在线人数涨跌 百分比 +type SysWarnOnline struct { + S *SysWarn + OldOnlineTotal int64 `json:"OldOnlineTotal" Redis:"OldOnlineTotal"` + OnlinePer int64 `json:"OnlinePer" Redis:"OldOnlineTotal"` +} + +func (s *SysWarnOnline) Init(sys *SysWarn) error { + log.Info("初始化在线预警 ...") + if err := mapstructure.Decode(sys.Condition, s); err != nil { + log.Error("err:%v", err) + return errors.New("解析出错") + } + + if s.OnlinePer <= 0 { + return errors.New("在线人数涨跌值设置不合法") + } + + s.S = sys + return nil +} +func (s *SysWarnOnline) Start() { + // var OnlineData struct { + // Total int64 `redis:"Total"` + // New int64 `redis:"New"` + // } + // err := db.Redis().HGetAll("online:Total", &OnlineData) + // if err != nil { + // log.Error(err.Error()) + // } + // onlineTotal := OnlineData.Total + // defer func() { + // s.OldOnlineTotal = onlineTotal + // }() + + // 在线人数为0 预警 + // if onlineTotal == 0 { + // s.S.Warn(fmt.Sprintf("上一次记录在线人数:[%d], 当前在线人数:[%d], 在线人数差:[%d]", s.OldOnlineTotal, onlineTotal, onlineTotal-s.OldOnlineTotal)) + // return + // } + + // // 上一次在线人数为0 预警 + // if s.OldOnlineTotal == 0 { + // s.S.Warn(fmt.Sprintf("上一次记录在线人数:[%d], 当前在线人数:[%d], 在线人数差:[%d]", s.OldOnlineTotal, onlineTotal, onlineTotal-s.OldOnlineTotal)) + // return + // } + + // // 跌 预警 + // if onlineTotal < s.OldOnlineTotal { + // if (s.OldOnlineTotal - (s.OnlinePer*s.OldOnlineTotal)/100) >= onlineTotal { + // s.S.Warn(fmt.Sprintf("上一次记录在线人数:[%d], 当前在线人数:[%d], 在线人数差:[%d]", s.OldOnlineTotal, onlineTotal, onlineTotal-s.OldOnlineTotal)) + // return + // } + // } else { + // // 涨 预警 + // if (s.OldOnlineTotal + (s.OnlinePer*s.OldOnlineTotal)/100) <= onlineTotal { + // s.S.Warn(fmt.Sprintf("上一次记录在线人数:[%d], 当前在线人数:[%d], 在线人数差:[%d]", s.OldOnlineTotal, onlineTotal, onlineTotal-s.OldOnlineTotal)) + // } + // } + q := elastic.NewBoolQuery() + q.Must(elastic.NewMatchQuery("GameID", 0)) + q.Filter(elastic.NewRangeQuery("Time").Gte(time.Now().Unix() - 10*60)) + type GroupSumBuckets struct { + Buckets []struct { + Key interface{} + Doc_count int + Total struct { + Value float64 + } + } + } + ret := GroupSumBuckets{} + db.ES().GroupSumBy(common.ESIndexBackPlayerOnline, "Time", q, &ret, "", false, 0, "Total") + log.Debug("online:%v", ret) + if len(ret.Buckets) < 2 { + return + } + total0 := int64(ret.Buckets[0].Total.Value) + if total0 == 0 { + total0 = 1 + } + total1 := int64(ret.Buckets[1].Total.Value) + diff := util.Abs(total0 - total1) + if diff*100/total0 >= s.OnlinePer { + s.S.Warn(fmt.Sprintf("上一次记录在线人数:[%d], 当前在线人数:[%d], 在线人数差:[%d]", total0, total1, diff)) + } +} +func (s *SysWarnOnline) Update(data []int64) {} +func (s *SysWarnOnline) Check(data []int64) {} +func (s *SysWarnOnline) ID() int { + return s.S.Type +} + +// SysWarnRechargeOrder +// ========================================================================= +// 最近订单数量 +// 最近充值成功率 +// 通知时间间隔 +type SysWarnRechargeOrder struct { + S *SysWarn + RecentOrder int64 // 最近订单数量 + RechargePer int64 // 最近充值成功率 + NoticeTime int64 // 通知时间间隔 + LastWarnTime int64 // 上一次预警时间 +} + +func (s *SysWarnRechargeOrder) Init(sys *SysWarn) error { + if err := mapstructure.Decode(sys.Condition, s); err != nil { + log.Error("err:%v", err) + return errors.New("解析出错") + } + if s.RecentOrder <= 0 { + return errors.New("最近订单数量不合法") + } + if s.RechargePer <= 0 { + return errors.New("最近订单成功率不合法") + } + s.S = sys + return nil +} + +func (s *SysWarnRechargeOrder) Start() { + var order []common.RechargeOrder + _, err := db.Mysql().QueryList(int(s.RecentOrder), 1, fmt.Sprintf("event = %v", common.CurrencyEventReCharge), "create_time desc", &common.RechargeOrder{}, &order) + if err != nil { + log.Error(err.Error()) + return + } + successCount := db.Mysql().Count(&common.RechargeOrder{}, fmt.Sprintf("event = %v and status = %v and create_time >= %v", common.CurrencyEventReCharge, common.StatusROrderPay, order[0].CreateTime)) + + if s.RecentOrder*s.RechargePer >= successCount*100 { + if time.Now().Unix()-s.LastWarnTime >= s.NoticeTime { + s.LastWarnTime = time.Now().Unix() + s.S.Warn(fmt.Sprintf("当前最后%d条充值订单成功%d单, 低于预计值%d单", s.RecentOrder, successCount, s.RechargePer)) + } + } +} +func (s *SysWarnRechargeOrder) Update(data []int64) {} +func (s *SysWarnRechargeOrder) Check(data []int64) {} +func (s *SysWarnRechargeOrder) ID() int { + return s.S.Type +} + +// SysWarnPay 支付预警 +// ========================================================================= +// PayPer 充值成功率 +// PayPerBase 充值成功率基数,订单达到改基数时才预警 +// PayOrderCount 支付订单数 +// PayOrderCountBase 充值成功订单数基数,订单达到改基数时才预警 +// OneUserPayFailCount 单一用户连续充值失败笔数 +type SysWarnPay struct { + S *SysWarn + NoticeTime int64 // 通知时间间隔 + PayPer int64 // 充值成功率 + PayPerBase int64 // 充值成功率基数,订单达到改基数时才预警 + PayOrderCount int64 // 充值成功订单数 跟前一天同一时刻比较 + PayOrderCountBase int64 // 充值成功订单数基数,订单达到改基数时才预警 + OneUserPayFailCount int64 // 单一用户充值失败笔数 + + LastWarnData []string // 上一次预警数据 + LastWarnTime int64 // 上一次预警时间 +} + +func (s *SysWarnPay) Init(sys *SysWarn) error { + log.Info("初始化支付预警 ...") + if err := mapstructure.Decode(sys.Condition, s); err != nil { + log.Error("err:%v", err) + return errors.New("解析出错") + } + s.S = sys + s.LastWarnData = make([]string, 3) + s.LastWarnData[0] = "-1" // 初始化值 + s.LastWarnData[1] = "-1" // 初始化值 + s.LastWarnData[2] = "-1" // 初始化值 + return nil +} +func (s *SysWarnPay) Start() { + // 支付订单数预警 + nowPayPer := s.warnPayPer() + // 单一用户充值失败笔数预警 + nowPayOrderCount := s.warnPayOrderCount() + // 单一用户充值失败笔数预警 + nowOneUserPayFailCount := s.warnOneUserPayFailCount() + + if nowPayPer || nowPayOrderCount || nowOneUserPayFailCount { + var content string + if nowPayPer { + content += fmt.Sprintf(" 当前充值成功率百分之%s ", strings.Split(s.LastWarnData[0], "_")[0]) + } + if nowPayOrderCount { + content += fmt.Sprintf(" 当前支付订单数预警%s ", strings.Split(s.LastWarnData[1], "_")[0]) + } + if nowOneUserPayFailCount { + content += fmt.Sprintf(" 当前单一用户连续充值失败笔数预警%s ", strings.Split(s.LastWarnData[2], "_")[0]) + } + s.LastWarnTime = time.Now().Unix() + s.S.Warn(content) + } +} + +// 充值成功率预警 +func (s *SysWarnPay) warnPayPer() bool { + var oneDay int64 = 24 * 60 * 60 + st, _ := time.ParseInLocation("2006-01-02", time.Now().Format("2006-01-02"), time.Local) + su := st.Unix() + + su2 := time.Now().Unix() + eu2 := time.Now().AddDate(0, 0, 1).Unix() + + var successOrder int64 + successStr := fmt.Sprintf("SELECT COUNT(*) AS successOrder FROM recharge_order WHERE event = %v AND status = %v AND callback_time >= %d AND callback_time < %d", + common.CurrencyEventReCharge, common.StatusROrderPay, su, su+oneDay) + err := db.Mysql().QueryBySql(successStr, &successOrder) + if err != nil { + log.Error(err.Error()) + return false + } + var allOrder int64 + allOrderStr := fmt.Sprintf("SELECT COUNT(*) FROM recharge_order WHERE event = %v AND create_time >= %d AND create_time < %d", + common.CurrencyEventReCharge, su2, eu2) + err = db.Mysql().QueryBySql(allOrderStr, &allOrder) + if err != nil { + log.Error(err.Error()) + return false + } + + if allOrder < s.PayPerBase { + return false + } + // 当前成功率 + nowPayPer := (successOrder / allOrder) * 10000 + nowPayPerStr := util.FormatFloat(float64(successOrder*100)/float64(allOrder), 2) + + if s.LastWarnData[0] == fmt.Sprintf("%s_%s", nowPayPerStr, su2) { + if nowPayPer < (s.PayPer*100) && (time.Now().Unix()-s.LastWarnTime) > s.NoticeTime { + s.LastWarnData[0] = fmt.Sprintf("%s_%s", nowPayPerStr, su2) + return true + } + } else { + if nowPayPer < (s.PayPer * 100) { + s.LastWarnData[0] = fmt.Sprintf("%s_%s", nowPayPerStr, su2) + return true + } + } + return false +} + +// 支付成功订单数预警 +func (s *SysWarnPay) warnPayOrderCount() bool { + var oneDay int64 = 24 * 60 * 60 + now := time.Now().Unix() + st, _ := time.ParseInLocation("2006-01-02", time.Now().Format("2006-01-02"), time.Local) + su := st.Unix() + su2 := time.Now().Format("2006-01-02") + + var lastOrder int64 + lastOrderStr := fmt.Sprintf("SELECT COUNT(*) FROM recharge_order WHERE event = %v AND status = %v AND callback_time >= %d AND callback_time < %d", + common.CurrencyEventReCharge, common.StatusROrderPay, su-oneDay, now-oneDay) + err := db.Mysql().QueryBySql(lastOrderStr, &lastOrder) + if err != nil { + log.Error(err.Error()) + } + + var nowOrder int64 + nowOrderStr := fmt.Sprintf("SELECT COUNT(*) FROM recharge_order WHERE event = %v AND status = %v AND callback_time >= %d AND callback_time < %d", + common.CurrencyEventReCharge, common.StatusROrderPay, su, su+oneDay) + err = db.Mysql().QueryBySql(nowOrderStr, &nowOrder) + if err != nil { + log.Error(err.Error()) + } + + if nowOrder < s.PayOrderCountBase { + return false + } + + // 相差订单数 + var temp int64 + + if lastOrder > nowOrder { + temp = lastOrder - nowOrder + } else { + temp = nowOrder - lastOrder + } + + if s.LastWarnData[1] == fmt.Sprintf("%d_%s", temp, su2) { + if temp < s.PayOrderCount && (time.Now().Unix()-s.LastWarnTime) > s.NoticeTime { + s.LastWarnData[1] = fmt.Sprintf("%d_%s", temp, su2) + return true + } + } else { + if temp < s.PayOrderCount { + s.LastWarnData[1] = fmt.Sprintf("%d_%s", temp, su2) + return true + } + } + return false +} + +// 单一用户充值连续失败笔数预警 +func (s *SysWarnPay) warnOneUserPayFailCount() bool { + su := time.Now().Unix() + eu := time.Now().AddDate(0, 0, 1).Unix() + + var uidArr []int64 + str := fmt.Sprintf("Select DISTINCT(uid) From recharge_order Where (uid, `event`) In (Select uid, `event` From recharge_order WHERE `event` = %d Group By uid,`event` Having Count(*)>%d) AND `status` != %d AND create_time >= %d AND create_time < %d ", + common.CurrencyEventReCharge, + common.StatusROrderPay, + s.OneUserPayFailCount, + su, + eu, + ) + err := db.Mysql().QueryBySql(str, &uidArr) + if err != nil { + log.Error(err.Error()) + return false + } + + var count int64 + for i := 0; i < len(uidArr); i++ { + var order []common.RechargeOrder + queryStr := fmt.Sprintf(" SELECT * FROM recharge_order WHERE uid = %d AND event = %d AND status != %d AND create_time >= %d AND create_time < %d ", + uidArr[i], common.CurrencyEventReCharge, common.StatusROrderPay, su, eu) + err = db.Mysql().QueryBySql(queryStr, &order) + if err != nil { + log.Error(err.Error()) + continue + } + + var temp int64 + for j := 0; j < len(order); j++ { + if order[j].Status == common.StatusROrderPay { + temp = 0 + } else { + temp++ + } + if temp >= s.OneUserPayFailCount { + count++ + break + } + } + } + + if s.LastWarnData[2] == fmt.Sprintf("%d_%s", count, su) { + if count > s.OneUserPayFailCount && (time.Now().Unix()-s.LastWarnTime) > s.NoticeTime { + s.LastWarnData[2] = fmt.Sprintf("%d_%s", count, su) + return true + } + } else { + if count > 0 { + s.LastWarnData[2] = fmt.Sprintf("%d_%s", count, su) + return true + } + } + + return false +} + +func (s *SysWarnPay) Update(data []int64) {} +func (s *SysWarnPay) Check(data []int64) {} +func (s *SysWarnPay) ID() int { + return s.S.Type +} + +// SysWarnTypeWithdrawPer 退出成功率预警 +type SysWarnTypeWithdrawPer struct { + S *SysWarn + OrderGte int64 `json:"OrderGte" Redis:"OrderGte"` // 订单数量区间下限 + WithdrawSuccessPer int64 `json:"WithdrawSuccessPer" Redis:"WithdrawSuccessPer"` // 退出成功率 + NoticeTime int64 `json:"NoticeTime" Redis:"NoticeTime"` // 通知时间间隔 + LastWarnTime int64 `json:"LastWarnTime" Redis:"LastWarnTime"` // 上一次预警时间 +} + +func (s *SysWarnTypeWithdrawPer) Init(sys *SysWarn) error { + log.Info("初始化退出成功率预警 ...") + if err := mapstructure.Decode(sys.Condition, s); err != nil { + log.Error("err:%v", err) + return errors.New("解析出错") + } + s.S = sys + log.Info("OrderGte: %v, WithdrawSuccessPer: %v, NoticeTime: %v", s.OrderGte, s.WithdrawSuccessPer, s.NoticeTime) + return nil +} +func (s *SysWarnTypeWithdrawPer) Start() { + su := time.Now().Unix() + eu := time.Now().AddDate(0, 0, 1).Unix() + + withdrawTotalCount := db.Mysql().Count(&common.RechargeOrder{}, fmt.Sprintf("event = %v and create_time >= %d and create_time < %d", common.CurrencyEventWithDraw, su, eu)) + if withdrawTotalCount < s.OrderGte { + return + } + + WithdrawSuccessCount := db.Mysql().Count(&common.RechargeOrder{}, fmt.Sprintf("event = %v and status = %v and create_time >= %d and create_time < %d", common.CurrencyEventWithDraw, common.StatusROrderFinish, su, eu)) + + if int64((float64(WithdrawSuccessCount)/float64(withdrawTotalCount))*100) <= s.WithdrawSuccessPer { + if time.Now().Unix()-s.LastWarnTime >= s.NoticeTime { + s.LastWarnTime = time.Now().Unix() + s.S.Warn(fmt.Sprintf("当前退出成功率:%v", int64((float64(WithdrawSuccessCount)/float64(withdrawTotalCount))*100))) + } + } +} +func (s *SysWarnTypeWithdrawPer) Update(data []int64) {} +func (s *SysWarnTypeWithdrawPer) Check(data []int64) {} +func (s *SysWarnTypeWithdrawPer) ID() int { + return s.S.Type +} diff --git a/common/activity.go b/common/activity.go new file mode 100644 index 0000000..54ea59d --- /dev/null +++ b/common/activity.go @@ -0,0 +1,479 @@ +package common + +import ( + "time" +) + +const ( + ActivityID = iota + 10000 + ActivityIDRecharge // 首充活动 + ActivityIDAppSpin // 下载转盘活动 + ActivityIDPDD // pdd活动 + ActivityIDFreeSpin // 每日免费转盘 + ActivityIDFirstRechargeBack // 首日充值返还 + ActivityIDLuckyCode // 幸运码活动 + ActivityIDSign // 签到活动 + ActivityIDBreakGift // 破产礼包 + ActivityIDWeekCard // 周卡 + ActivityIDSlots // slots奖池活动 + ActivityIDLuckyShop // 幸运商店活动 + ActivityIDSevenDayBox // 7日签到宝箱 + ActivityIDSuper // 超级1+2 +) + +const ( + ActivityDataZero = iota + ActivityDataClick + ActivityDataJoin + ActivityDataAll +) + +// ConfigBanner banner配置 +type ConfigBanner struct { + ID int `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"ID" web:"id"` + ActivityID int `gorm:"column:activity_id;uniqueIndex:activity_id" json:"ActivityID" web:"activity_id"` + Sort int `gorm:"column:sort;type:int(11);" json:"Sort" web:"sort"` + Start int64 `gorm:"column:start;type:bigint(20);" json:"Start" web:"start"` + End int64 `gorm:"column:end;type:bigint(20);" json:"End" web:"end"` + IsRelease int `gorm:"column:is_release;type:int(11);default:1;comment:是否开启 1关闭 2开启" json:"IsRelease" web:"is_release"` + Push int `gorm:"column:push;type:int(11);default:1;comment:是否推送 1不推送 2推送" json:"Push" web:"push"` + PushFrequency int `gorm:"column:push_frequency;type:int(11);default:1;comment:推送频率" json:"PushFrequency" web:"push_frequency"` + Type int `gorm:"column:type;type:int(11);default:1;comment:类型" json:"Type" web:"type"` + Pic string `gorm:"column:pic;type:varchar(255);comment:活动图片" json:"Pic" web:"pic"` + Data string `gorm:"column:data;type:varchar(255);comment:跳转数据" json:"Data" web:"data"` +} + +func (c *ConfigBanner) TableName() string { + return "config_banner" +} + +func (s *ConfigBanner) IsValid() bool { + now := time.Now().Unix() + if s.IsRelease != 2 || s.Start > now || (s.End < now && s.End > 0) { + return false + } + return true +} + +// ConfigActivity +type ConfigActivity struct { + ID int `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"ID" web:"id"` + ActivityID int `gorm:"column:activity_id;uniqueIndex:activity_id" json:"ActivityID" web:"activity_id"` + Sort int `gorm:"column:sort;type:int(11);" json:"Sort" web:"sort"` + Start int64 `gorm:"column:start;type:bigint(20);" json:"Start" web:"start"` + End int64 `gorm:"column:end;type:bigint(20);" json:"End" web:"end"` + IsRelease int `gorm:"column:is_release;type:int(11);default:1;comment:是否开启 1关闭 2开启" json:"IsRelease" web:"is_release"` + Push int `gorm:"column:push;type:int(11);default:1;comment:是否推送 1推送 2不推送" json:"Push" web:"push"` + PushFrequency int `gorm:"column:push_frequency;type:int(11);default:1;comment:推送频率" json:"PushFrequency" web:"push_frequency"` + Type int `gorm:"column:type;type:int(11);default:1;comment:类型" json:"Type" web:"type"` + Pic string `gorm:"column:pic;type:varchar(255);comment:活动图片" json:"Pic" web:"pic"` + Data string `gorm:"column:data;type:varchar(255);comment:跳转数据" json:"Data" web:"data"` +} + +func (a *ConfigActivity) TableName() string { + return "config_activity" +} + +// Expire 判断活动是否可用 +func (s *ConfigActivity) IsValid() bool { + now := time.Now().Unix() + if s.IsRelease != 2 || s.Start > now || (s.End < now && s.End > 0) { + return false + } + return true +} + +type PddData struct { + ID int `gorm:"primarykey"` + UID int `gorm:"column:uid;not null;type:int(11);uniqueIndex:uid"` + Time int64 `gorm:"column:time;type:bigint(20);comment:创建时间"` + Amount int64 `gorm:"column:amount;type:bigint(20);comment:当前金额"` + Spin int `gorm:"column:spin;type:int(11);comment:剩余旋转次数"` + FreeSpinTime int64 `gorm:"column:free_spin_time;type:bigint(20);comment:免费旋转时间"` + NewRecordTime int64 `gorm:"column:new_record_time;type:bigint(20);comment:判断新邀请用户标记时间"` +} + +func (a *PddData) TableName() string { + return "pdd_data" +} + +// 拼多多活动转盘物品类型 +const ( + ActivityPddItemType = iota + ActivityPddItemTypeFinish // 直接补齐差额 + ActivityPddItemTypeCash // 固定金额 + ActivityPddItemTypeRandomCash // 随机金额 + ActivityPddItemTypeAll +) + +// ConfigActivityPddSpin 拼多多分享活动转盘配置 +type ConfigActivityPddSpin struct { + ID int `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"ID"` + AmountDown int64 `gorm:"column:amount_down;type:bigint(20);default:0;uniqueIndex:amount_sort;comment:区间金额下限" web:"amount_down"` + AmountUp int64 `gorm:"column:amount_up;type:bigint(20);default:0;comment:区间金额上限" web:"amount_up"` + Type int `gorm:"column:type;type:int(11);default:3;comment:物品类型" web:"type"` + Amount int64 `gorm:"column:amount;type:bigint(20);default:0;comment:物品数量" web:"amount"` + Weight int `gorm:"column:weight;type:int(11);default:0;comment:权重" web:"weight"` + Sort int `gorm:"column:sort;type:int(11);default:0;uniqueIndex:amount_sort;comment:排序" web:"sort"` + CashDown int64 `gorm:"column:cash_down;type:bigint(20);default:0;comment:当type是随机金额的时候,表示随机金额的下限" web:"cash_down"` + CashUp int64 `gorm:"column:cash_up;type:bigint(20);default:0;comment:当type是随机金额的时候,表示随机金额的上限" web:"cash_up"` +} + +func (c *ConfigActivityPddSpin) TableName() string { + return "config_activity_pdd_spin" +} + +// ConfigActivityPdd 拼多多分享活动配置 +type ConfigActivityPdd struct { + ID int `gorm:"primary_key;AUTO_INCREMENT;column:id"` + WithdrawAmount int64 `gorm:"column:withdraw_amount;type:bigint(20);default:0;comment:可退出门槛" web:"withdraw_amount"` + AmountDown int64 `gorm:"column:amount_down;type:bigint(20);default:0;comment:随机金额下限" web:"amount_down"` + AmountUp int64 `gorm:"column:amount_up;type:bigint(20);default:0;comment:随机金额上限" web:"amount_up"` + Expire int64 `gorm:"column:expire;type:bigint(20);default:0;comment:过期时间,单位分钟" web:"expire"` +} + +func (c *ConfigActivityPdd) TableName() string { + return "config_activity_pdd" +} + +// ConfigFirstPay 首充奖励配置 +type ConfigFirstPay struct { + ID int `gorm:"primary_key;AUTO_INCREMENT;column:id"` + Level int `gorm:"column:level;type:int(11);default:0;comment:等级" web:"level"` + Amount int64 `gorm:"column:amount;type:bigint(20);default:0;comment:充值额度" web:"amount"` + FirstPer int64 `gorm:"column:first_per;type:bigint(20);default:0;comment:首次充值赠送比例,百分位" web:"first_per"` + Per int64 `gorm:"column:per;type:bigint(20);default:0;comment:后续充值赠送比例,百分位" web:"per"` +} + +func (c *ConfigFirstPay) TableName() string { + return "config_first_pay" +} + +// ConfigActivityFreeSpin 每日免费转盘配置 +type ConfigActivityFreeSpin struct { + ID int `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"ID"` + Type int `gorm:"column:type;type:int(11);default:3;comment:物品类型,对应枚举" web:"type"` + Amount int64 `gorm:"column:amount;type:bigint(20);default:0;comment:物品数量" web:"amount"` + Weight int `gorm:"column:weight;type:int(11);default:0;comment:权重" web:"weight"` + Sort int `gorm:"column:sort;type:int(11);default:0;uniqueIndex:amount_sort;comment:排序" web:"sort"` + CashDown int64 `gorm:"column:cash_down;type:bigint(20);default:0;comment:当type是随机金额的时候,表示随机金额的下限" web:"cash_down"` + CashUp int64 `gorm:"column:cash_up;type:bigint(20);default:0;comment:当type是随机金额的时候,表示随机金额的上限" web:"cash_up"` +} + +func (c *ConfigActivityFreeSpin) TableName() string { + return "config_activity_free_spin" +} + +// 拼多多活动转盘物品类型 +const ( + ActivityFreeSpinItem = iota + ActivityFreeSpinItemNone // 转到无奖励 + ActivityFreeSpinItemCash // 固定金额 + ActivityFreeSpinItemRandomCash // 随机金额 + ActivityFreeSpinItemDoubleCash // 双倍奖励 + ActivityFreeSpinItemAll +) + +// ActivityFreeSpinData +type ActivityFreeSpinData struct { + UID int `gorm:"column:uid;not null;type:int(11);uniqueIndex:uid"` + LastSpin int64 `gorm:"column:last_spin;default:0;type:bigint(20);comment:上次旋转时间"` +} + +func (c *ActivityFreeSpinData) TableName() string { + return "activity_free_spin_data" +} + +// 首次充值后判断间隔 +const ( + ActivityFirstRechargeBackTime = 86400 // 一天 +) + +// ConfigActivityFirstRechargeBack 首日充值返还 +type ConfigActivityFirstRechargeBack struct { + ID int `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"ID"` + MinRecharge int64 `gorm:"column:min_recharge;type:bigint(20);default:10000000000;comment:最低充值额度" web:"min_recharge"` + MaxBack int64 `gorm:"column:max_back;type:bigint(20);default:-1;comment:最大返还额度" web:"max_back"` +} + +func (c *ConfigActivityFirstRechargeBack) TableName() string { + return "config_activity_first_recharge_back" +} + +type ActivityFirstRechargeBackData struct { + UID int `gorm:"column:uid;not null;type:int(11);uniqueIndex:uid"` + RechargeTime int64 `gorm:"column:recharge_time;type:bigint(20);default:0;comment:首次充值时间"` + Amount int64 `gorm:"column:amount;type:bigint(20);default:0;comment:总充值金额"` + Lost int64 `gorm:"column:lost;type:bigint(20);default:0;comment:活动时间内总损失"` +} + +func (c *ActivityFirstRechargeBackData) TableName() string { + return "activity_first_recharge_back_data" +} + +const ( + LuckyCodeTypeZero = iota + LuckyCodeTypeNormal + LuckyCodeTypeAll +) + +type ConfigActivityLuckyCode struct { + ID int `gorm:"primarykey"` + Type int `gorm:"column:type;type:int(11);default:1;comment:类型" web:"type"` + Reward int64 `gorm:"column:reward;not null;type:bigint(20);comment:奖励的物品" web:"reward"` + Per int64 `gorm:"column:per;type:int(11);default:60;comment:奖品的权重" web:"per"` +} + +func (c *ConfigActivityLuckyCode) TableName() string { + return "config_activity_lucky_code" +} + +type ActivityLuckyCodeData struct { + ID int `gorm:"primary_key;AUTO_INCREMENT;column:id"` + Type int `gorm:"column:type;default:1;type:int(11);"` + UID int `gorm:"column:uid;not null;type:int(11);uniqueIndex:uid"` + LastDraw int64 `gorm:"column:last_draw;default:0;type:bigint(20)"` +} + +func (c *ActivityLuckyCodeData) TableName() string { + return "activity_lucky_code_data" +} + +// 存放每天的兑换码记录 +type ActivityLuckyCode struct { + ID int `gorm:"primary_key;AUTO_INCREMENT;column:id"` + Type int `gorm:"column:type;default:1;type:int(11);"` + Code int `gorm:"column:code;default:0;type:int(11)"` + Date string `gorm:"column:date;default:'';type:varchar(64);uniqueIndex:date;comment:日期"` +} + +func (c *ActivityLuckyCode) TableName() string { + return "activity_lucky_code" +} + +type ConfigActivitySign struct { + ID int `gorm:"primarykey"` + Day int `gorm:"column:day;type:int(11);default:1;comment:签到天数" web:"day"` + Reward int64 `gorm:"column:reward;not null;type:bigint(20);comment:奖励" web:"reward"` + Recharge int64 `gorm:"column:recharge;type:bigint(20);default:0;comment:所需充值金额" web:"recharge"` +} + +func (c *ConfigActivitySign) TableName() string { + return "config_activity_sign" +} + +type ActivitySignData struct { + ID int `gorm:"primarykey"` + UID int `gorm:"column:uid;type:int(11);uniqueIndex:uid"` + Sign int `gorm:"column:sign;type:int(11);comment:签到天数,二进制"` + Time int64 `gorm:"column:time;type:bigint(20);comment:首次参与时间"` +} + +func (c *ActivitySignData) TableName() string { + return "activity_sign_data" +} + +type ConfigActivityBreakGift struct { + ID int `gorm:"primarykey"` + Level int `gorm:"column:level;type:int(11);default:1;comment:等级" web:"level"` + RechargeDown int64 `gorm:"column:recharge_down;type:bigint(11);default:1000000000;comment:充值金额下限" web:"recharge_down"` + RechargeUp int64 `gorm:"column:recharge_up;type:bigint(20);default:2000000000;comment:充值金额上限" web:"recharge_up"` + CountDown int `gorm:"column:count_down;type:int(11);default:30;comment:倒计时" web:"count_down"` + ProductID int `gorm:"column:product_id;type:int(11);default:0;comment:商品id" web:"product_id"` +} + +func (c *ConfigActivityBreakGift) TableName() string { + return "config_activity_break_gift" +} + +type ConfigActivityWeekCard struct { + ID int `gorm:"primarykey"` + Level int `gorm:"column:level;type:int(11);default:1;comment:等级" web:"level"` + ProductID int `gorm:"column:product_id;type:int(11);default:0;comment:商品id" web:"product_id"` + DayReward int64 `gorm:"column:day_reward;type:bigint(11);default:700000000;comment:每日可领取奖励" web:"day_reward"` + OriginPrice int64 `gorm:"column:origin_price;type:bigint(11);default:5000000000;comment:显示的原价" web:"origin_price"` + Day int `gorm:"column:day;type:int(11);default:6;comment:可领取天数" web:"day"` + Discount int `gorm:"column:discount;type:int(11);default:70;comment:折扣率,百分位" web:"discount"` + Rebate int `gorm:"column:rebate;type:int(11);default:240;comment:返利率,百分位" web:"rebate"` + Range string `gorm:"column:range;default:'[2000000000,100000000000]';type:varchar(255);comment:充值可用范围"` + SubRange []int64 `gorm:"-" json:"-"` +} + +func (c *ConfigActivityWeekCard) TableName() string { + return "config_activity_week_card" +} + +const ( + ActivityWeekCardTicketExpireTime = 48 * 3600 // 两个小时 +) + +type ActivityWeekCardData struct { + ID int `gorm:"primarykey"` + UID int `gorm:"column:uid;type:int(11);uniqueIndex:ul"` + Level int `gorm:"column:level;type:int(11);default:0;uniqueIndex:ul;comment:周卡等级"` + DayReward int64 `gorm:"column:day_reward;type:bigint(11);default:700000000;comment:每日可领取奖励"` + Day int `gorm:"column:day;type:int(11);default:6;comment:可领取天数"` + LastDraw int64 `gorm:"column:last_draw;type:bigint(20);default:0;comment:上次领取时间"` + GetDiscount int `gorm:"column:get_discount;type:tinyint(4);default:0;comment:前面是否已经获得了折扣券"` +} + +func (c *ActivityWeekCardData) TableName() string { + return "activity_week_card_data" +} + +// 支付时带有一些信息,如折扣券,赋值在recharge_order的extra字段 +type ActivityRechargeData struct { + ID int `json:"ID,omitempty"` // 活动id + I1 int `json:"I1,omitempty"` // 携带的信息1,不同活动可能含义不同,折扣券活动代表折扣券等级 + I2 int64 `json:"I2,omitempty"` // 携带信息2,不同活动可能含义不同,折扣券活动代表实际发放金额 +} + +type ConfigActivitySlots struct { + ID int `gorm:"primarykey"` + RankDown int `gorm:"column:rank_down;type:int(11);default:1;comment:排名下限" web:"rank_down"` + RankUp int `gorm:"column:rank_up;type:int(11);default:1;comment:排名上限" web:"rank_up"` + Reward int64 `gorm:"column:reward;type:bigint(11);default:99900000000;comment:奖励" web:"reward"` +} + +func (c *ConfigActivitySlots) TableName() string { + return "config_activity_slots" +} + +// 1,2通过对当天日期取余决定 +type ActivitySlotsData struct { + ID int `gorm:"primarykey"` + UID int `gorm:"column:uid;type:int(11);uniqueIndex:uid"` + Avatar string `gorm:"column:avatar;default:'';type:varchar(255);comment:头像"` + Nick string `gorm:"column:nick;default:'';type:varchar(255);comment:昵称"` + Spin int `gorm:"column:spin;type:int(11);default:0;comment:抽奖次数"` + BestNumber1 int `gorm:"column:best_number1;type:int(11);default:0;comment:抽到的最大的数字1"` + Time1 int64 `gorm:"column:time1;type:bigint(20);comment:参与时间1"` + BestNumber2 int `gorm:"column:best_number2;type:int(11);default:0;comment:抽到的最大的数字2"` + Time2 int64 `gorm:"column:time2;type:bigint(20);comment:参与时间2"` + Role int `gorm:"column:role;type:int(11);default:0;comment:角色,1代表机器人,2初始默认的机器人"` +} + +func (c *ActivitySlotsData) TableName() string { + return "activity_slots_data" +} + +// 结算记录 +type ActivitySlotsRecord struct { + ID int `gorm:"primarykey"` + Date string `gorm:"column:date;default:'';type:varchar(255);uniqueIndex:du;comment:日期"` + UID int `gorm:"column:uid;type:int(11);uniqueIndex:du"` + Settle int `gorm:"column:settle;type:int(11);default:0;comment:是否结算,1代表已结算"` + BestNumber int `gorm:"column:best_number;type:int(11);default:0;comment:当日的最大的数字"` + Reward int64 `gorm:"column:reward;type:bigint(20);default:0;comment:奖励" web:"reward"` + MyNumber int `gorm:"column:my_number;type:int(11);default:0;comment:玩家的数字"` + Rank int `gorm:"column:rank;type:int(11);default:0;comment:排名"` +} + +func (c *ActivitySlotsRecord) TableName() string { + return "activity_slots_Record" +} + +const ( + ActivityLuckyShopType = iota + ActivityLuckyShopTypeRechargeLess + ActivityLuckyShopTypeRechargeMore + ActivityLuckyShopTypeLogin + ActivityLuckyShopTypeAll +) + +type ConfigActivityLuckyShop struct { + ID int `gorm:"primarykey"` + Type int `gorm:"column:type;type:int(11);default:1;comment:类型,1是首充小于弹出,2是首充大于弹出,3是付费玩家登录弹出" web:"type"` + Recharge int64 `gorm:"column:recharge;type:bigint(20);default:2000000000;comment:首充玩家弹出的充值额度" web:"recharge"` + ProductID int `gorm:"column:product_id;type:int(11);default:0;comment:商品id" web:"product_id"` +} + +func (c *ConfigActivityLuckyShop) TableName() string { + return "config_activity_lucky_shop" +} + +const ( + ActivityLuckyShopExpire = 2 * 3600 // 2小时过期 +) + +type ActivityLuckyShopData struct { + ID int `gorm:"primarykey"` + UID int `gorm:"column:uid;not null;type:int(11);uniqueIndex:ut"` + Type int `gorm:"column:type;type:int(11);default:1;uniqueIndex:ut;comment:类型,1是首充小于弹出,2是首充大于弹出,3是付费玩家登录弹出"` + Push int64 `gorm:"column:push;type:bigint(20);default:0;comment:弹窗弹出时间"` + Buy int `gorm:"column:buy;type:tinyint(4);default:0;comment:前面是否已经完成购买"` + ProductID int `gorm:"column:product_id;type:int(11);default:0;comment:商品id"` +} + +func (c *ActivityLuckyShopData) TableName() string { + return "activity_lucky_shop_data" +} + +func (c *ActivityLuckyShopData) IsValid() bool { + if c.Buy == 1 { + return false + } + return time.Now().Unix()-c.Push < ActivityLuckyShopExpire +} + +const ( + ActivitySevenDayBoxType = iota + ActivitySevenDayBoxTypeCash + ActivitySevenDayBoxTypeDiscountTicket + ActivitySevenDayBoxTypeAll +) + +type ConfigActivitySevenDayBox struct { + ID int `gorm:"primarykey"` + Recharge int64 `gorm:"column:recharge;type:bigint(20);default:10000000000;comment:充值额度" web:"recharge"` + Type int `gorm:"column:type;type:int(11);default:1;comment:奖励类型 1金币 2折扣券" web:"type"` + CashRange string `gorm:"column:cash_range;type:varchar(255);default:[1000000000,2000000000];comment:随机金币范围,当type为1时有用" web:"cash_range"` + Discount int `gorm:"column:discount;type:int(11);default:70;comment:折扣,type为2时有用" web:"discount"` + Per int `gorm:"column:per;type:int(11);default:60;comment:概率" web:"per"` + SubCashRange []int64 `gorm:"-" json:"-"` + ProductID int `gorm:"column:product_id;type:int(11);default:0;comment:商品id" web:"product_id"` +} + +func (c *ConfigActivitySevenDayBox) TableName() string { + return "config_activity_seven_day_box" +} + +type ActivitySevenDayBoxData struct { + ID int `gorm:"primarykey"` + UID int `gorm:"column:uid;not null;type:int(11);uniqueIndex:uid"` + Count int `gorm:"column:count;type:int(11);default:0;comment:剩余开宝箱次数" web:"count"` + Time int64 `gorm:"column:time;type:bigint(20);comment:购买时间"` +} + +func (c *ActivitySevenDayBoxData) TableName() string { + return "activity_seven_day_box_data" +} + +type ConfigActivitySuper struct { + ID int `gorm:"primarykey"` + Type int `gorm:"column:type;type:int(11);default:1;comment:类型 1新用户 2老用户" web:"type"` + Recharge int64 `gorm:"column:recharge;type:bigint(20);default:20000000000;comment:充值额度" web:"recharge"` + Index int `gorm:"column:index;type:int(11);default:1;comment:奖池编号" web:"index"` + RewardType int `gorm:"column:reward_type;type:bigint(20);default:1;comment:奖励的类型 1金币 2折扣券" web:"reward_type"` + Reward int64 `gorm:"column:reward;default:0;type:bigint(20);comment:奖励的物品数量(折扣券时为折扣数额)" web:"reward"` + Sort int `gorm:"column:sort;type:int(11);default:0;comment:排序" web:"sort"` + Per int `gorm:"column:per;type:int(11);default:60;comment:概率" web:"per"` + ProductID int `gorm:"column:product_id;type:int(11);default:0;comment:商品id" web:"product_id"` +} + +func (c *ConfigActivitySuper) TableName() string { + return "config_activity_super" +} + +type ActivitySuperData struct { + ID int `gorm:"primarykey"` + UID int `gorm:"column:uid;not null;type:int(11);uniqueIndex:uid"` + Open int `gorm:"column:open;default:0;type:int(11);comment:箱子开启情况,二进制"` + Time int64 `gorm:"column:time;type:bigint(20);comment:购买时间"` + Type int `gorm:"-"` + CanBuy bool `gorm:"-"` +} + +func (c *ActivitySuperData) TableName() string { + return "activity_super_data" +} diff --git a/common/config.go b/common/config.go new file mode 100644 index 0000000..dc26a4a --- /dev/null +++ b/common/config.go @@ -0,0 +1,539 @@ +package common + +// 重新加载配置的id +const ( + ReloadWhiteList = iota + 1 + ReloadChannel + ReloadPlatform + ReloadNotice + ReloadBroadcast + ReloadConfigServerVersion // 服务器版本配置 + ReloadConfigRWPer // 充提比配置 + ReloadConfigActivity // 活动配置 + ReloadConfigPayWeight // 支付渠道权重配置 + ReloadConfigWithdrawWeight // 退出渠道配置 + ReloadConfigPayProduct // 商品配置 + ReloadConfigGameSwitch // 游戏开关配置变动 + ReloadConfigVip // VIP + ReloadConfigH5 // h5配置 + ReloadConfigTron // tron配置 + ReloadConfigWithdrawProduct // 退出商品配置 + ReloadConfigGameList // 游戏列表配置 + ReloadConfigGameProvider // 游戏提供商配置 + ReloadConfigGameTypes // 游戏类别配置 + ReloadConfigGameMarks // 游戏角标配置 + ReloadConfigFirstPageGames // 首页游戏配置 + ReloadConfigBroadcast // 广播配置 + ReloadConfigCurrencyRateUSD // 汇率配置 + ReloadConfigGameRoom // 房间配置 + ReloadConfigWater // 水位配置 + ReloadConfigRobot // 机器人配置 + ReloadConfigAppSpin // 下载app转盘 + ReloadConfigShare // 分享相关配置 + ReloadConfigShareSys // 分享系统相关配置 + ReloadConfigActivityPddSpin // 拼多多转盘配置 + ReloadConfigActivityPdd // 拼多多配置 + ReloadConfigTask // 任务配置 + ReloadConfigCurrencyResource // 奖励来源配置 + ReloadConfigFirstPay // 首充配置 + ReloadConfigActivityFreeSpin // 免费转盘活动 + ReloadConfigActivityFirstRechargeBack // 首日充值返还 + ReloadConfigLuckyCode // 幸运码活动 + ReloadConfigBanner // banner配置 + ReloadConfigActivitySign // 签到配置 + ReloadConfigActivityBreakGift // 破产礼包 + ReloadConfigShareRobot // 分享机器人 + ReloadConfigActivityWeekCard // 周卡 + ReloadConfigActivitySlots // slots奖池活动 + ReloadConfigActivityLuckyShop // 幸运商店活动 + ReloadConfigServerFlag // 服务器配置 + ReloadConfigActivitySevenDayBox // 7日宝箱活动 + ReloadConfigActivitySuper // 超级1+2 +) + +// GetConfigStructByType 获取相应配置的结构 +func GetConfigStructByType(t int) (interface{}, interface{}) { + switch t { + case ReloadConfigRWPer: + return &ConfigRWPer{}, &[]ConfigRWPer{} + case ReloadConfigActivity: + return &ConfigActivity{}, &[]ConfigActivity{} + case ReloadConfigPayProduct: + return &ConfigPayProduct{}, &[]ConfigPayProduct{} + case ReloadConfigPayWeight: + return &ConfigPayChannels{}, &[]ConfigPayChannels{} + case ReloadConfigWithdrawWeight: + return &ConfigWithdrawChannels{}, &[]ConfigWithdrawChannels{} + case ReloadConfigH5: + return &ConfigH5{}, &[]ConfigH5{} + case ReloadConfigTron: + return &ConfigTron{}, &[]ConfigTron{} + case ReloadConfigWithdrawProduct: + return &ConfigWithdrawProduct{}, &[]ConfigWithdrawProduct{} + case ReloadConfigGameList: + return &ConfigGameList{}, &[]ConfigGameList{} + case ReloadConfigGameProvider: + return &ConfigGameProvider{}, &[]ConfigGameProvider{} + case ReloadConfigGameTypes: + return &ConfigGameType{}, &[]ConfigGameType{} + case ReloadConfigGameMarks: + return &ConfigGameMark{}, &[]ConfigGameMark{} + case ReloadConfigFirstPageGames: + return &ConfigFirstPageGames{}, &[]ConfigFirstPageGames{} + case ReloadConfigBroadcast: + return &ConfigBroadcast{}, &[]ConfigBroadcast{} + case ReloadConfigVip: + return &ConfigVIP{}, &[]ConfigVIP{} + case ReloadConfigCurrencyRateUSD: + return &ConfigCurrencyRateUSD{}, &[]ConfigCurrencyRateUSD{} + case ReloadConfigGameRoom: + return &ConfigGameRoom{}, &[]ConfigGameRoom{} + case ReloadConfigWater: + return &ConfigWater{}, &[]ConfigWater{} + case ReloadConfigRobot: + return &ConfigRobot{}, &[]ConfigRobot{} + case ReloadConfigAppSpin: + return &ConfigAppSpin{}, &[]ConfigAppSpin{} + case ReloadConfigShare: + return &ConfigShare{}, &[]ConfigShare{} + case ReloadConfigShareSys: + return &ConfigShareSys{}, &[]ConfigShareSys{} + case ReloadConfigActivityPddSpin: + return &ConfigActivityPddSpin{}, &[]ConfigActivityPddSpin{} + case ReloadConfigActivityPdd: + return &ConfigActivityPdd{}, &[]ConfigActivityPdd{} + case ReloadConfigTask: + return &ConfigTask{}, &[]ConfigTask{} + case ReloadConfigCurrencyResource: + return &ConfigCurrencyResource{}, &[]ConfigCurrencyResource{} + case ReloadConfigFirstPay: + return &ConfigFirstPay{}, &[]ConfigFirstPay{} + case ReloadConfigActivityFreeSpin: + return &ConfigActivityFreeSpin{}, &[]ConfigActivityFreeSpin{} + case ReloadConfigActivityFirstRechargeBack: + return &ConfigActivityFirstRechargeBack{}, &[]ConfigActivityFirstRechargeBack{} + case ReloadConfigLuckyCode: + return &ConfigActivityLuckyCode{}, &[]ConfigActivityLuckyCode{} + case ReloadConfigBanner: + return &ConfigBanner{}, &[]ConfigBanner{} + case ReloadConfigActivitySign: + return &ConfigActivitySign{}, &[]ConfigActivitySign{} + case ReloadConfigActivityBreakGift: + return &ConfigActivityBreakGift{}, &[]ConfigActivityBreakGift{} + case ReloadConfigShareRobot: + return &ConfigShareRobot{}, &[]ConfigShareRobot{} + case ReloadConfigActivityWeekCard: + return &ConfigActivityWeekCard{}, &[]ConfigActivityWeekCard{} + case ReloadConfigActivitySlots: + return &ConfigActivitySlots{}, &[]ConfigActivitySlots{} + case ReloadConfigActivityLuckyShop: + return &ConfigActivityLuckyShop{}, &[]ConfigActivityLuckyShop{} + case ReloadConfigServerFlag: + return &ConfigServerFlag{}, &[]ConfigServerFlag{} + case ReloadConfigActivitySevenDayBox: + return &ConfigActivitySevenDayBox{}, &[]ConfigActivitySevenDayBox{} + case ReloadConfigActivitySuper: + return &ConfigActivitySuper{}, &[]ConfigActivitySuper{} + default: + return nil, nil + } +} + +// 平台配置 +// BreakAmount 破产金币 +// NewPlayerGift 新玩家初始赠送 +// NewGuideFirst 新手引导初始赠送 +// NewGuideGift 完成新手引导赠送 +// BindPhoneGift 绑定手机赠送 +// WithdrawRecharge 退出需付费的金额 +// NewControlEnd 新手调控结束阀值 +type ConfigPlatform struct { + ID int `gorm:"primarykey"` + NewPlayerGift int64 `gorm:"column:new_player_gift;type:int(11);default:1000;comment:新玩家赠送金币" json:"NewPlayerGift" web:"new_player_gift"` + BindPhoneGift int64 `gorm:"column:bind_phone_gift;type:int(11);default:1000;comment:绑定手机赠送" json:"BindPhoneGift" web:"bind_phone_gift"` + AvatarCount int `gorm:"column:avatar_count;type:int(11);default:400;comment:最大头像数目" json:"AvatarCount" web:"avatar_count"` + CartoonCount int `gorm:"column:cartoon_count;type:int(11);default:15;comment:卡通头像数目" json:"CartoonCount" web:"cartoon_count"` + SmsChannel int `gorm:"column:sms_channel;type:int(11);default:1;comment:短信服务商 1Antgst 2Buka" json:"SmsChannel" web:"sms_channel"` + Telegram string `gorm:"column:telegram;type:varchar(256);default:'+66636640245';comment:客服telegram" json:"Telegram" web:"telegram"` + Whatsapp string `gorm:"column:whatsapp;type:varchar(256);default:'+66636640245';comment:客服whatsapp" json:"Whatsapp" web:"whatsapp"` + Email string `gorm:"column:email;type:varchar(256);default:'rummywallah@gmail.com';comment:客服email" json:"Email" web:"email"` + PayTips string `gorm:"column:pay_tips;type:varchar(256);default:'';comment:充值提示语" json:"PayTips" web:"pay_tips"` + WithdrawTips string `gorm:"column:withdraw_tips;type:varchar(256);default:'';comment:tx提示语" json:"WithdrawTips" web:"withdraw_tips"` + BlackList int `gorm:"column:black_list;type:int(11);default:0;comment:是否开启黑名单 0不开 1开启" json:"BlackList" web:"black_list"` +} + +func (c *ConfigPlatform) TableName() string { + return "config_platform" +} + +const ( + RWPerTypeZero = iota + RWPerTypeFirst + RWPerTypeMulti + RWPerTypeAll +) + +// ConfigRWPer 充提比配置 +type ConfigRWPer struct { + ID int `gorm:"primarykey"` + Down int64 `gorm:"column:down;type:bigint(20);default:0;comment:充值范围下限" json:"Down" web:"down"` + Up int64 `gorm:"column:up;type:bigint(20);default:0;comment:充值范围上限" json:"Up" web:"up"` + Per int64 `gorm:"column:per;type:int(11);default:10;comment:充提比" json:"Per" web:"per"` + Type int `gorm:"column:type;type:int(11);default:1;comment:类型 1首次 2非首次" json:"Type" web:"type"` +} + +func (c *ConfigRWPer) TableName() string { + return "config_rwper" +} + +const ( + PayKindBank = iota + 1 + PayKindTron +) + +// ConfigPayProduct +type ConfigPayProduct struct { + ID int `gorm:"primarykey"` + Pic string `gorm:"column:pic;type:varchar(256);default:'';comment:图片" web:"pic"` + ProductID int `gorm:"column:product_id;type:int(11);default:0;comment:商品id" web:"product_id"` + ActivityID int `gorm:"column:activityid;type:int(11);default:0;comment:活动id" web:"activityid"` + Sort int `gorm:"column:sort;type:int(11);default:0;comment:排序" web:"sort"` + Recommend int `gorm:"column:recommend;type:int(11);default:0;comment:是否推荐 1推荐 2不推荐" web:"recommend"` + OriginAmount int64 `gorm:"column:origin_amount;type:bigint(20);default:0;comment:显示价格" web:"origin_amount"` + Amount int64 `gorm:"column:amount;type:bigint(20);default:0;comment:实际价格" web:"amount"` + Value int64 `gorm:"column:value;type:bigint(20);default:0;comment:发放金额" web:"value"` + Type CurrencyType `gorm:"column:type;type:int(11);default:1;comment:货币类型" web:"type"` + IfSell int `gorm:"column:if_sell;type:int(11);default:1;comment:是否上架 1上架 2不上架" web:"if_sell"` + Kind int `gorm:"column:kind;type:int(11);default:1;comment:支付类型 1银行 2区块链" web:"kind"` + Exi int `gorm:"column:exi;type:int(11);default:0;comment:商品额外信息" web:"exi"` + Channels []int `gorm:"-"` +} + +func (c *ConfigPayProduct) TableName() string { + return "config_pay_product" +} + +// ConfigPayChannels 代收渠道配置 +type ConfigPayChannels struct { + ID int `gorm:"primarykey"` + ChannelID int `gorm:"column:channel_id;not null;type:int(11);uniqueIndex:channel_id" web:"ChannelID"` + PayPer int `gorm:"column:pay_per;type:int(11);default:0;comment:代收权重" json:"PayPer" web:"pay_per"` + PayDown int64 `gorm:"column:pay_down;type:bigint(20);default:1;comment:代收下限" json:"PayDown" web:"pay_down"` + PayUp int64 `gorm:"column:pay_up;type:bigint(20);default:1;comment:代收上限" json:"PayUp" web:"pay_up"` + CurrencyType CurrencyType `gorm:"column:currency_type;type:bigint(20);default:1;uniqueIndex:channel_id;comment:货币类型" json:"CurrencyType" web:"currency_type"` + Kind int64 `gorm:"column:kind;type:bigint(20);default:1;comment:协议类型" json:"Kind" web:"kind"` +} + +func (c *ConfigPayChannels) TableName() string { + return "config_pay_channels" +} + +// ConfigWithdrawChannels 代付渠道配置 +type ConfigWithdrawChannels struct { + ID int `gorm:"primarykey"` + ChannelID int `gorm:"column:channel_id;not null;type:int(11);uniqueIndex:channel_id" web:"ChannelID"` + WithdrawPer int `gorm:"column:withdraw_per;type:int(11);default:0;comment:代付权重" json:"WithdrawPer" web:"WithdrawPer"` + PayDown int64 `gorm:"column:pay_down;type:bigint(20);default:1;comment:代收下限" json:"PayDown" web:"pay_down"` + PayUp int64 `gorm:"column:pay_up;type:bigint(20);default:1;comment:代收上限" json:"PayUp" web:"pay_up"` + CurrencyType CurrencyType `gorm:"column:currency_type;type:bigint(20);default:1;uniqueIndex:channel_id;comment:货币类型" json:"CurrencyType" web:"currency_type"` + Kind int64 `gorm:"column:kind;type:bigint(20);default:1;comment:协议类型" json:"Kind" web:"kind"` +} + +func (c *ConfigWithdrawChannels) TableName() string { + return "config_withdraw_channels" +} + +// ConfigVIP vip配置 +// Exp 该等级需要的经验值 +// WithdrawCount 每日可代付次数 +// Cashback 返利比例(千分位) +// Bonus 等级奖励 +// Bet 升级所需下注额 +// Fee 代付手续费(千分位) +type ConfigVIP struct { + ID int `gorm:"primarykey;AUTO_INCREMENT;column:id"` + Level int `gorm:"column:level;not null;type:int(11);default:0;comment:vip等级" json:"Level" web:"level"` + Exp int64 `gorm:"column:exp;not null;type:bigint(20);default:0;comment:该等级需要的经验值" json:"Exp" web:"exp"` + WithdrawCount int `gorm:"column:withdraw_count;not null;type:int(11);default:0;comment:每日可代付次数" json:"WithdrawCount" web:"withdraw_count"` + Cashback int64 `gorm:"column:cashback;type:bigint(20);default:0;comment:返利比例千分位" json:"Cashback" web:"cashback"` + Bonus int64 `gorm:"column:bonus;type:bigint(20);default:0;comment:等级奖励" json:"Bonus" web:"bonus"` + Bet int64 `gorm:"column:bet;type:bigint(20);default:0;comment:升级所需下注额" json:"Bet" web:"bet"` + Fee int64 `gorm:"column:fee;type:bigint(20);default:0;comment:手续费率千分比" json:"Fee" web:"fee"` + UFee int64 `gorm:"column:u_fee;type:bigint(20);default:0;comment:u手续费(固定值)" json:"UFee" web:"u_fee"` +} + +func (c *ConfigVIP) TableName() string { + return "config_vip" +} + +// vip商品类型 +const ( + VIPProductTypeDay = iota + 1 + VIPProductTypeWeek + VIPProductTypeMonth +) + +// H5配置 +type ConfigH5 struct { + ID int `gorm:"primarykey"` + CollectReward int64 `gorm:"column:collect_reward;type:bigint(20);default:0;comment:收藏奖励" web:"collect_reward"` + DownloadReward int64 `gorm:"column:download_reward;type:bigint(20);default:0;comment:下载奖励" web:"download_reward"` +} + +func (c *ConfigH5) TableName() string { + return "config_h5" +} + +// ConfigTron +type ConfigTron struct { + ID int `gorm:"primarykey"` + CurrentBlock int64 `gorm:"column:current_block;type:bigint(20);default:0;comment:当前扫描的区块" web:"current_block"` + CurrentBlockTest int64 `gorm:"column:current_block_test;type:bigint(20);default:0;comment:当前扫描的区块(测试链)" web:"current_block_test"` + Rate int64 `gorm:"column:rate;type:bigint(20);default:8193;comment:1U转换成当前货币的汇率,百分位" web:"rate"` +} + +func (c *ConfigTron) TableName() string { + return "config_tron" +} + +// ConfigWithdrawProduct +type ConfigWithdrawProduct struct { + ID int `gorm:"primarykey"` + Pic string `gorm:"column:pic;type:varchar(256);default:'';comment:图片" web:"pic"` + ProductID int `gorm:"column:product_id;type:int(11);default:0;comment:商品id" web:"product_id"` + Sort int `gorm:"column:sort;type:int(11);default:0;comment:排序" web:"sort"` + Amount int64 `gorm:"column:amount;type:bigint(20);default:0;comment:金额" web:"amount"` + Type CurrencyType `gorm:"column:type;type:int(11);default:1;comment:货币类型" web:"type"` + IfSell int `gorm:"column:if_sell;type:int(11);default:1;comment:是否上架 1上架 2不上架" web:"if_sell"` + Kind int `gorm:"column:kind;type:int(11);default:1;comment:支付类型 1银行 2区块链" web:"kind"` + Channels []int `gorm:"-"` +} + +func (c *ConfigWithdrawProduct) TableName() string { + return "config_withdraw_product" +} + +// ConfigGameList 游戏列表配置 +// GameID 游戏id +// Icon 游戏图标 +// URL 跳转url +// GameProvider 提供商id +// Mark 角标 +// Sort 排序(玩家每玩一次,sort值会自增1) +// Jackpot 奖池 +// Demo 是否支持demo 1支持 2不支持 +// SubID 一些游戏有子id 如桌子号 +type ConfigGameList struct { + ID int `gorm:"primarykey"` + GameID int `gorm:"column:game_id;not null;type:int(11);uniqueIndex:game;comment:游戏id" web:"game_id"` + GameCode string `gorm:"column:game_code;type:varchar(255);default:'';comment:一些游戏的唯一识别" web:"game_code"` + GameType int `gorm:"column:game_type;not null;type:int(11);comment:游戏类别id" web:"game_type"` + Name string `gorm:"column:name;not null;type:varchar(255);comment:游戏名" web:"name"` + Icon string `gorm:"column:icon;type:varchar(255);comment:游戏图标" web:"icon"` + URL string `gorm:"column:url;type:varchar(255);comment:跳转地址" web:"url"` + GameProvider int `gorm:"column:game_provider;not null;type:int(11);uniqueIndex:game;comment:游戏提供商" web:"game_provider"` + Mark int `gorm:"column:mark;default:0;type:int(11);comment:角标" web:"mark"` + Sort uint64 `gorm:"column:sort;type:bigint(20);comment:游戏排序,玩家每玩一次,sort值会自增1" web:"sort"` + Open int `gorm:"column:open;type:int(11);default:1;comment:是否开启 1开2不开" web:"open"` + Orientation int `gorm:"column:orientation;type:int(11);default:1;comment:横屏竖屏 1竖屏2横屏3两者都可" web:"orientation"` + Demo int `gorm:"column:demo;type:int(11);default:1;comment:是否支持demo 1支持 2不支持" web:"demo"` + SubID string `gorm:"column:subid;type:varchar(255);default:'';comment:一些游戏有子id" web:"subid"` + Jackpot int64 `gorm:"-" web:"-"` +} + +func (c *ConfigGameList) TableName() string { + return "config_game_list" +} + +// ConfigGameProvider 游戏提供商配置 +type ConfigGameProvider struct { + ID int `gorm:"primarykey"` + ProviderID int `gorm:"column:provider_id;not null;type:int(11);uniqueIndex:id;comment:游戏提供商id"` + ProviderName string `gorm:"column:provider_name;type:varchar(255);comment:游戏提供商名称" web:"provider_name"` + Icon string `gorm:"column:icon;type:varchar(255);comment:供应商图标" web:"icon"` + Sort int `gorm:"column:sort;type:int(11);comment:供应商排序" web:"sort"` + Callback string `gorm:"column:callback;type:varchar(255);comment:回调地址" web:"callback"` + WhiteIPs string `gorm:"column:white_ips;type:varchar(1024);comment:ip白名单" web:"white_ips"` + SubIp []string `gorm:"-" web:"-"` + GamesNum int `gorm:"-" web:"-"` + Open int `gorm:"column:open;type:int(11);default:1;comment:是否开启 1开2不开" web:"open"` + Show int `gorm:"column:show;type:int(11);default:1;comment:是否显示在banner栏 1显示2不显示" web:"show"` + Method int `gorm:"column:method;type:int(11);default:1;comment:打开方式 1正常url 2html格式" web:"method"` +} + +func (c *ConfigGameProvider) TableName() string { + return "config_game_provider" +} + +func (c *ConfigGameProvider) IsIpWhite(ip string) bool { + if len(c.SubIp) == 0 { + return true + } + for _, v := range c.SubIp { + if v == ip { + return true + } + } + return false +} + +type ConfigGameType struct { + ID int `gorm:"primarykey"` + TypeID int `gorm:"column:type_id;not null;type:int(11);uniqueIndex:type_id;comment:游戏类别id" web:"type_id"` + TypeName string `gorm:"column:type_name;type:varchar(64);comment:游戏类别名" web:"type_name"` + Icon string `gorm:"column:icon;type:varchar(255);comment:游戏类别图标" web:"icon"` + Sort int `gorm:"column:sort;type:int(11);comment:排序" web:"sort"` + Open int `gorm:"column:open;type:int(11);default:0;comment:是否打开 1打开" web:"open"` +} + +func (c *ConfigGameType) TableName() string { + return "config_game_type" +} + +type ConfigGameMark struct { + ID int `gorm:"primarykey"` + MarkID int `gorm:"column:mark_id;not null;type:int(11);uniqueIndex:type_id;comment:游戏角标id" web:"mark_id"` + MarkName string `gorm:"column:mark_name;type:varchar(64);comment:游戏角标名" web:"mark_name"` + Icon string `gorm:"column:icon;type:varchar(255);comment:游戏角标图标" web:"icon"` + Sort int `gorm:"column:sort;type:int(11);comment:排序" web:"sort"` +} + +func (c *ConfigGameMark) TableName() string { + return "config_game_mark" +} + +type ConfigFirstPageGames struct { + ID int `gorm:"primarykey"` + Icon string `gorm:"column:icon;type:varchar(255);comment:游戏标题图标" web:"icon"` + Name string `gorm:"column:name;type:varchar(64);comment:游戏标题名" web:"name"` + JumpType int `gorm:"column:jump_type;default:1;type:int(11);comment:跳转类型 1type 2mark" web:"jump_type"` + JumpID int `gorm:"column:jump_id;default:1;type:int(11);comment:跳转查询id" web:"jump_id"` + Sort int `gorm:"column:sort;type:int(11);comment:排序" web:"sort"` + Open int `gorm:"column:open;type:int(11);default:0;comment:是否打开 1打开" web:"open"` +} + +func (c *ConfigFirstPageGames) TableName() string { + return "config_first_page_games" +} + +type ConfigBroadcast struct { + ID int `gorm:"primarykey"` + BroadcastID int `gorm:"column:broadcast_id;type:int(11);comment:广播类型id" web:"broadcast_id"` + Content string `gorm:"column:content;type:varchar(255);comment:广播正文" web:"content"` + Open int `gorm:"column:open;type:int(11);comment:是否打开 1打开" web:"open"` + Event int `gorm:"column:event;type:int(11);comment:事件" web:"event"` + TargetID int `gorm:"column:target_id;type:int(11);comment:跳转id" web:"target_id"` + Type int `gorm:"column:type;type:int(11);comment:类型" web:"type"` + Priority int `gorm:"column:priority;type:int(11);comment:优先级" web:"priority"` + LoopFrequency int `gorm:"column:loop_frequency;type:int(11);comment:循环次数" web:"loop_frequency"` + Interval int `gorm:"column:interval;type:int(11);comment:间隔" web:"interval"` + ConditionDown int `gorm:"column:condition_down;type:int(11);comment:触发条件下限" web:"condition_down"` + ConditionUp int `gorm:"column:condition_up;type:int(11);comment:触发条件上限" web:"condition_up"` +} + +func (c *ConfigBroadcast) TableName() string { + return "config_broadcast" +} + +type ConfigNotice struct { + ID int `gorm:"primarykey"` + Title1 string `gorm:"column:title1;type:varchar(255);comment:标题1" web:"title1"` // 公告标题_1(英语) + Content1 string `gorm:"column:content1;type:varchar(255);comment:正文1" web:"content1"` // 公告内容_1(英语) + Title2 string `gorm:"column:title2;type:varchar(255);comment:标题2" web:"title2"` // 公告标题_2 + Content2 string `gorm:"column:content2;type:varchar(255);comment:正文2" web:"content2"` // 公告内容_2 + Type int `gorm:"column:type;type:int(11);comment:公告类型 (1.紧急 2.常规)" web:"type"` // 公告类型 (1.紧急 2.常规) + Open int `gorm:"column:open;type:int(11);comment:是否打开 1打开" web:"open"` // 是否发布 + Method int `gorm:"column:method;type:int(11);comment:发布方式" web:"method"` // 发布方式 + Time int64 `gorm:"column:time;type:int(11);comment:发布时间" web:"time"` // 发布时间 + Interval int `gorm:"column:interval;type:int(11);comment:间隔" web:"interval"` + PushTimes int `gorm:"column:push_times;type:int(11);comment:推送次数" web:"push_times"` // 推送次数 +} + +func (c *ConfigNotice) TableName() string { + return "config_notice" +} + +// 货币汇率(各种货币转换成美元的汇率) +type ConfigCurrencyRateUSD struct { + ID int `gorm:"primary_key;AUTO_INCREMENT;column:id"` + CurrencyType CurrencyType `gorm:"column:currency_type;type:int(11);default:1;comment:货币类型"` + Rate int64 `gorm:"column:rate;type:int(11);default:1;comment:汇率(万分位)"` + RefreshTime int64 `gorm:"column:refresh_time;type:bigint(20);default:0;comment:刷新时间,一天刷一次"` +} + +func (c *ConfigCurrencyRateUSD) TableName() string { + return "config_currency_rate_usd" +} + +type ConfigGameRoom struct { + ID int `gorm:"primary_key;AUTO_INCREMENT;column:id"` + GameID int `gorm:"column:game_id;type:int(11);default:0;comment:游戏id" web:"game_id"` + RoomID int `gorm:"column:room_id;type:int(11);default:0;comment:房间id" web:"room_id"` + RoomName string `gorm:"column:room_name;type:varchar(64);default:0;comment:房间名字" web:"room_name"` + RoomType int `gorm:"column:room_type;type:int(11);default:0;comment:房间类型 1金币 2练习" web:"room_type"` + Open bool `gorm:"column:open;type:int(11);default:0;comment:是否开放 0不开 1开" web:"open"` + Initial int64 `gorm:"column:initial;type:int(11);default:0;comment:初始额度" web:"initial"` + BetTime int64 `gorm:"column:bet_time;type:int(11);default:0;comment:下注时长" web:"bet_time"` + SettleTime int64 `gorm:"column:settle_time;type:int(11);default:0;comment:结算时长" web:"settle_time"` + MaxSeats int `gorm:"column:max_seats;type:int(11);default:0;comment:座位数" web:"max_seats"` + BetLimitStr string `gorm:"column:bet_limit_str;type:varchar(256);default:'[0]';comment:下注限额数组形式[0,0]" web:"bet_limit_str"` + BetLimit []int64 `gorm:"-" json:"-"` +} + +func (c *ConfigGameRoom) TableName() string { + return "config_game_room" +} + +// 水位 +type ConfigWater struct { + ID int `gorm:"primarykey"` + GameID int `gorm:"column:game_id;not null;type:int(11);comment:游戏id" json:"GameID" web:"game_id"` + RoomID int `gorm:"column:room_id;not null;type:int(11);comment:房间id" json:"RoomID" web:"room_id"` + WaterLower int64 `gorm:"column:water_lower;not null;type:bigint(20);comment:下水位" json:"WaterLower" web:"water_lower"` + WaterUp int64 `gorm:"column:water_up;not null;type:bigint(20);comment:上水位" json:"WaterUp" web:"water_up"` + // ControlPer int `gorm:"column:control_per;not null;type:int(11);comment:控制概率" json:"ControlPer" web:"control_per"` + // RebatePer int64 `gorm:"column:rebate_per;not null;type:int(11);comment:返税比例" json:"RebatePer" web:"rebate_per"` + Value int64 `gorm:"column:vale;not null;type:bigint(20);comment:当前水位" web:"value"` + Rtp int64 `gorm:"column:rtp;not null;type:int(11);comment:正常时候的rtp,万分位" web:"rtp"` + DownRtp int64 `gorm:"column:down_rtp;not null;type:int(11);comment:下水位的rtp,万分位" web:"down_rtp"` + UpRtp int64 `gorm:"column:up_rtp;not null;type:int(11);comment:上水位的rtp,万分位" web:"up_rtp"` +} + +func (c *ConfigWater) TableName() string { + return "config_water" +} + +// 机器人 +type ConfigRobot struct { + ID int `gorm:"primarykey"` + Avatar string `gorm:"column:avatar;type:varchar(512);default:''" web:"avatar"` + Nick string `gorm:"column:nick;type:varchar(512);default:''" web:"nick"` +} + +func (c *ConfigRobot) TableName() string { + return "config_robot" +} + +// 下载app奖励转盘 +type ConfigAppSpin struct { + ID int `gorm:"primarykey"` + Amount int64 `gorm:"column:amount;type:bigint(20);default:0" web:"amount"` + CurrencyType CurrencyType `gorm:"column:type;type:int(11);default:1;comment:货币类型" web:"type"` + Sort int `gorm:"column:sort;type:int(11);default:1;comment:排序" web:"sort"` + Weight int `gorm:"column:weight;type:int(11);default:1;comment:权重" web:"weight"` +} + +func (c *ConfigAppSpin) TableName() string { + return "config_app_spin" +} + +// ConfigCurrencyResource 奖励来源配置 +type ConfigCurrencyResource struct { + ID int `gorm:"primary_key;AUTO_INCREMENT;column:id"` + Type CurrencyRes `gorm:"column:type;type:int(11);default:0;uniqueIndex:type;comment:奖励类型" web:"type"` + Multiple int64 `gorm:"column:multiple;type:bigint(20);default:2000;comment:所需下注倍数,百分位" web:"multiple"` +} + +func (c *ConfigCurrencyResource) TableName() string { + return "config_Currency_resource" +} diff --git a/common/currency.go b/common/currency.go new file mode 100644 index 0000000..a558e4e --- /dev/null +++ b/common/currency.go @@ -0,0 +1,239 @@ +package common + +import ( + "fmt" + "reflect" + "strconv" + "strings" + + "gorm.io/gorm" +) + +// 货币类型 +type CurrencyType int + +const ( + CurrencyTypeZero CurrencyType = iota + CurrencyBrazil + CurrencyUSDT + CurrencyAll +) + +type CurrencyRes int + +// 货币来源(会影响所需下注量) +const ( + CurrencyResourceZero CurrencyRes = iota + CurrencyResourceRecharge // 一般充值 + CurrencyResourceBonus // 额外赠送 + CurrencyResourceAll +) + +const ( + USDTKindZero = iota + USDTKindTRC20 + USDTKindAll +) + +func (t CurrencyType) IsValid() bool { + return t > CurrencyTypeZero && t < CurrencyAll +} + +func (t CurrencyType) GetCurrencyName() string { + return strings.ToLower(reflect.TypeOf(PlayerCurrency{}).Field(int(t + 1)).Name) +} + +func (t CurrencyType) GetRechargeInfoTable() string { + return fmt.Sprintf("recharge_info_%s", t.GetCurrencyName()) +} + +func GetCurrencyID(currency string) CurrencyType { + str := strings.ToUpper(currency) + ref := reflect.ValueOf(PlayerCurrency{}) + reft := reflect.TypeOf(PlayerCurrency{}) + for i := 2; i < ref.NumField(); i++ { + if reft.Field(i).Name == str { + return CurrencyType(i - 1) + } + } + return 0 +} + +type CurrencyEvent int + +const ( + CurrencyEventZero CurrencyEvent = iota // 无意义 + CurrencyEventNewPlayer // 新注册赠送 + CurrencyEventGameSettle // 游戏场结算 + CurrencyEventGameBet // 游戏场牌局模式下注 + CurrencyEventGameCancelBet // 取消下注 + CurrencyEventReCharge // 充值 + CurrencyEventWithDraw // 退出 + CurrencyEventWithDrawBack // 退出失败退回 + CurrencyEventMailDraw // 邮件领取 + CurrencyEventGameVoidSettle // 游戏取消结算 + CurrencyEventGameActivity // 游戏场活动赠与 + CurrencyEventGameReSettle // 游戏场调整结算 + CurrencyEventGameAdjustment // 游戏场调整余额 + CurrencyEventBindPhone // 绑定手机奖励 + CurrencyEventVIPBonus // 领取vip等级奖励 + CurrencyEventVIPCashback // 领取vip返利 + CurrencyEventActivityAppSpin // 下载app转盘奖励 + CurrencyEventShareWithdraw // 分享奖励领取 + CurrencyEventActivityPdd // pdd分享奖励领取 + CurrencyEventGameAdjustBet // 投注额调整 + CurrencyEventGameBonus // 游戏场bonus + CurrencyEventGameJackpot // 游戏场jackpot + CurrencyEventGameBuyIn // 游戏场扣钱操作 + CurrencyEventGameBuyOut // 游戏场加钱操作 + CurrencyEventTask // 任务奖励 + CurrencyEventActivityFreeSpin // 免费旋转 + CurrencyEventActivityFirstRechargeBack // 首日充值返还 + CurrencyEventActivityLuckyCode // 兑换码活动 + CurrencyEventActivitySign // 签到活动 + CurrencyEventActivityBreakGift // 破产礼包活动 + CurrencyEventActivityWeekCard // 周卡 + CurrencyEventActivitySlots // slots奖池 + CurrencyEventActivitySuper // 超级1+2 + CurrencyEventAll + + CurrencyEventGM = 1000 // 后台修改货币 + CurrencyEventGMRecharge = 1001 // 后台模拟充值 +) + +func GetCurrencyTypeName(ct CurrencyEvent) string { + switch ct { + case CurrencyEventNewPlayer: + return "新增注册赠送" + case CurrencyEventGameSettle: + return "游戏场结算" + case CurrencyEventGameBet: + return "游戏场下注" + case CurrencyEventGameCancelBet: + return "游戏场取消下注" + case CurrencyEventReCharge: + return "充值" + case CurrencyEventWithDraw: + return "退出" + case CurrencyEventWithDrawBack: + return "退出失败返回" + case CurrencyEventMailDraw: + return "邮件领取" + case CurrencyEventGameVoidSettle: + return "游戏场取消结算" + case CurrencyEventGameActivity: + return "游戏场活动赠与" + case CurrencyEventGameReSettle: + return "游戏场调整结算" + case CurrencyEventGameAdjustment: + return "游戏场调整余额" + case CurrencyEventBindPhone: + return "绑定手机奖励" + case CurrencyEventVIPBonus: + return "领取vip等级奖励" + case CurrencyEventVIPCashback: + return "领取输钱返利" + case CurrencyEventActivityAppSpin: + return "下载转盘奖励" + case CurrencyEventShareWithdraw: + return "分享奖励领取" + case CurrencyEventActivityPdd: + return "拼多多奖励领取" + case CurrencyEventGameAdjustBet: + return "调整投注" + case CurrencyEventGameBonus: + return "游戏场bonus奖励" + case CurrencyEventGameJackpot: + return "游戏场jackpot奖励" + case CurrencyEventGameBuyIn: + return "游戏场扣钱操作" + case CurrencyEventGameBuyOut: + return "游戏场加钱操作" + case CurrencyEventGM: + return "后台修改货币" + case CurrencyEventGMRecharge: + return "后台模拟充值" + case CurrencyEventTask: + return "领取任务奖励" + case CurrencyEventActivityFreeSpin: + return "免费转盘" + case CurrencyEventActivityFirstRechargeBack: + return "首充返还" + case CurrencyEventActivityLuckyCode: + return "幸运码活动" + case CurrencyEventActivitySign: + return "签到" + case CurrencyEventActivityBreakGift: + return "破产礼包活动" + case CurrencyEventActivityWeekCard: + return "周卡活动" + case CurrencyEventActivitySlots: + return "slots奖池活动" + case CurrencyEventActivitySuper: + return "超级1+2活动" + } + return strconv.Itoa(int(ct)) +} + +func GetGameEvents() []interface{} { + return []interface{}{CurrencyEventGameSettle, CurrencyEventGameBet, CurrencyEventGameCancelBet, CurrencyEventGameVoidSettle, + CurrencyEventGameActivity, CurrencyEventGameReSettle, CurrencyEventGameAdjustment, CurrencyEventGameAdjustBet, + CurrencyEventGameBonus, CurrencyEventGameJackpot, CurrencyEventGameBuyIn, CurrencyEventGameBuyOut} +} + +// 游戏投入 +func GetGameInEvents() []interface{} { + return []interface{}{CurrencyEventGameBet, CurrencyEventGameCancelBet, CurrencyEventGameAdjustBet, CurrencyEventGameBuyIn} +} + +// 游戏产出 +func GetGameOutEvents() []interface{} { + return []interface{}{CurrencyEventGameSettle, CurrencyEventGameVoidSettle, + CurrencyEventGameActivity, CurrencyEventGameReSettle, CurrencyEventGameAdjustment, + CurrencyEventGameBonus, CurrencyEventGameJackpot, CurrencyEventGameBuyOut} +} + +// RoundCurrency 去除法币的无意义小数位数 +func RoundCurrency(t CurrencyType, amount int64) int64 { + switch t { + case CurrencyBrazil: + return amount / 1e6 * 1e6 + case CurrencyUSDT: + return amount / 100 * 100 + default: + return amount + } +} + +type UpdateCurrency struct { + NotNotify bool // 为true时不通知客户端 + Tx *gorm.DB + *CurrencyBalance +} + +// Time 时间 +// Value 变化的值 +// Event 事件 +// Type 货币类型 +type CurrencyBalance struct { + Id int `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"id" redis:"id"` + UID int `gorm:"column:uid" json:"uid"` + Time int64 `gorm:"column:time" json:"time"` + Value int64 `gorm:"column:value" json:"value"` + Balance int64 `gorm:"column:balance;type:bigint(20);default:0" json:"balance"` + Event CurrencyEvent `gorm:"column:event" json:"event"` + Type CurrencyType `gorm:"column:type" json:"type"` + ChannelID int `gorm:"column:channel_id;type:bigint(20);default:1;comment:渠道id" json:"channel_id"` + NeedBet int64 `gorm:"column:resource;type:bigint(20);default:0;comment:本次流水增加的打码量" json:"need_bet"` + + Exi1 int `gorm:"column:exi1;type:bigint(20);default:0;comment:额外int字段1" json:"exi1"` + Exi2 int `gorm:"column:exi2;type:bigint(20);default:0;comment:额外int字段2" json:"exi2"` + Exi3 int `gorm:"column:exi3;type:bigint(11);default:0;comment:额外int字段3" json:"exi3"` + Exs1 string `gorm:"column:exs1;type:varchar(64);comment:额外string字段1" json:"exs1"` + Exs2 string `gorm:"column:exs2;type:varchar(64);comment:额外string字段2" json:"exs2"` + Exs3 string `gorm:"column:exs3;type:varchar(64);comment:额外string字段3" json:"exs3"` +} + +func (c *CurrencyBalance) TableName() string { + return fmt.Sprintf("currency_balance_%02d", c.UID%100) +} diff --git a/common/email.go b/common/email.go new file mode 100644 index 0000000..8ecbd31 --- /dev/null +++ b/common/email.go @@ -0,0 +1,89 @@ +package common + +import "server/pb" + +// type Mail struct { +// ID int `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"id" redis:"id"` +// Sender int `gorm:"column:sender;type:int(11);default:0;comment:发件人uid,系统为0" json:"sender" redis:"sender"` +// Receiver int `gorm:"column:receiver;type:int(11);default:0;comment:收件人uid,系统为0" json:"receiver" redis:"receiver"` +// } + +const ( + MailExpireTime = 30 * 24 * 60 * 60 // 30天 + MailMaxCount = 100 +) + +const ( + MailTypeNormal = iota + 1 + MailTypeUrgent +) + +const ( + MailSendMethodImmediately = iota + 1 + MailSendMethodTiming +) + +const ( + MailStatusDelete = iota + MailStatusNew + MailStatusRead + MailStatusDraw +) + +// Mail 邮件 +// DraftID 发送邮件的模板id +// sender 发件人 +// Receiver 收件人uid 0是系统邮件 +// Type 邮件类型 1跳转游戏 +// Status 邮件状态 0删除 1未读 2已读未领取 3已领取 +// Time 收到邮件的时间 +type Mail struct { + ID int `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"ID"` + DraftID string `gorm:"column:draft_id;type:varchar(256);comment:发送邮件的模板id" json:"DraftID"` + Sender string `gorm:"column:sender;type:varchar(64);default:0;comment:发件人名称" json:"Sender"` + Receiver int `gorm:"column:receiver;type:int(11);default:0;comment:收件人uid,系统为0" json:"Receiver"` + Tag int `gorm:"column:tag;type:int(11);default:1;comment:页签 1平台 2个人" json:"Tag"` + Type int `gorm:"column:type;type:int(11);default:0;comment:邮件类型 1正常 2紧急" json:"Type"` + Data string `gorm:"column:data;type:varchar(512);default:'';comment:数据" json:"Data"` + Title string `gorm:"column:title;type:varchar(256);default:0;comment:标题" json:"Title"` + Content string `gorm:"column:content;type:varchar(512);default:0;comment:正文" json:"Content"` + Status int `gorm:"column:status;type:tinyint(4);default:1;comment:邮件状态 0删除 1未读 2已读未领取 3已领取" json:"Status"` + Time int64 `gorm:"column:time;type:bigint(20);default:0;comment:收到邮件时间" json:"Time"` +} + +func (u *Mail) TableName() string { + return "mail" +} + +const ( + MailDraftStatusDelete = iota + MailDraftStatusNew + MailDraftStatusSent + MailDraftStatusBack // 撤销 +) + +// MailDraft 邮件草稿 +// ID 草稿ID +// sender 发件人 +// Receiver 收件人uid(可以是数组),空数组代表所有人 +// Type 邮件类型 1正常 2紧急 +// Enclosure 附件 +// SendMethod 发送方式 1立刻 2定时 +// SendTime 发送时间 +// Status 邮件状态 0删除 1未发送 2已发送 3已撤销 +// Operator 操作人 +// Time 创建或修改的时间戳 +type MailDraft struct { + ID string + Sender string + Receiver []int + Type int + Title string + Content string + Enclosure []*pb.CurrencyPair + SendMethod int + SendTime int64 + Status int + Operator string + Time int64 +} diff --git a/common/es.go b/common/es.go new file mode 100644 index 0000000..961407b --- /dev/null +++ b/common/es.go @@ -0,0 +1,331 @@ +package common + +const ( + ESIndexBalance = "bal" // 流水记录,改为别名 + ESIndexJackpot = "jackpot" // 游戏奖池中奖记录 + ESIndexLongPay = "long_pay" // 充值返回速度过慢记录 + ESIndexTron = "tron" // tron转账记录 + ESIndexGameData = "game_data" // 玩家游戏记录 + ESIndexShareProfitReport = "share_profit_report" // 分享者每日收益汇总 + ESIndexShareProfitRecord = "share_profit_record" // 分享者所有推荐人贡献的每日收益详情 +) + +// backend +const ( + ESIndexBackMailDraft = "back_mail_draft" // 后台邮件草稿 + ESIndexBackWhiteList = "back_white_list" // 后台白名单 + ESIndexBackPlayerOnline = "back_player_online" // 后台在线统计 + ESIndexBackExamineList = "back_examine_list" // 后台审核人员名单 + ESIndexBackWarn = "back_warn" // 后台预警 + ESIndexBackDailyData = "back_daily_data" // 后台每日统计数据 + ESIndexBackOpenRecord = "back_open_record" // 后台统计玩家打开安装数 + ESIndexBackIncomeStatistics = "back_income_statistics" // 后台收入统计 + ESIndexBackPlayerPayData = "back_player_pay_data" // 后台玩家充值/退出统计 + ESIndexBackReviewData = "back_review_data" // 数据概要 + ESIndexBackAppSummary = "back_app_summary" // 应用概要 + ESIndexBackRechargeFrequency = "back_recharge_frequency" // 付费分析 + ESIndexBackKeepData = "back_keep_data" // 留存 + ESIndexBackBlackList = "back_black_list" // 封号拉黑的玩家 + ESIndexBackABLog = "back_ab_log" // 玩家进入ab面统计 + ESIndexBackMillionGameRecord = "back_million_game_record" + ESIndexBackMillionPlayerRecord = "back_million_player_record" + ESIndexBackPddRecord = "back_pdd_record" + ESIndexBackFeedback = "back_feedback" + ESIndexBackActivity = "back_activity" +) + +// GroupBuckets group聚合查询对象 +type GroupBuckets struct { + Buckets []struct { + Key interface{} + Doc_count int + } +} + +// GroupCardBuckets group聚合查询对象 +type GroupCardBuckets struct { + Buckets []struct { + Key interface{} + Doc_count int + Sub struct { + Value int + } + } +} + +// Group2CardBuckets group聚合查询对象 +type Group2CardBuckets struct { + Buckets []struct { + Key interface{} + Doc_count int + Sub1 struct { + Buckets []struct { + Key interface{} + Doc_count int + Sub2 struct { + Value int + } + } + } + } +} + +// GroupSumBuckets group聚合查询对象 +type GroupSumBuckets struct { + Buckets []struct { + Key interface{} + Doc_count int + Value struct { + Value float64 + } + } +} + +// Group2SumBuckets group聚合查询对象 +type Group2SumBuckets struct { + Buckets []struct { + Key interface{} + Doc_count int + Sub1 struct { + Buckets []struct { + Key interface{} + Doc_count int + Sub2 struct { + Value float64 + } + } + } + } +} + +// SumByResult sum聚合查询对象 +type SumByResult struct { + Value float64 +} + +// ESPlayerStats 玩家充值/退出记录表 +type ESPlayerPayData struct { + ID string `json:"-"` + UID int `json:"UID"` + Channel int `json:"Channel"` + Time int64 `json:"Time"` + IsNew bool `json:"IsNew"` + Type int `json:"Type"` // 类型 1充值 2退出 + CurrencyType CurrencyType `json:"CurrencyType"` + Amount int64 `json:"Amount"` // 充值/退出金额 + FirstAmount int64 `json:"FirstAmount"` // 是否是首次,如果是,则金额大于0 +} + +type ESNewOnline struct { + Channel int `json:"Channel"` + Time int64 `json:"Time"` + Total int `json:"Total"` + GameID int `json:"GameID"` + Recharge int `json:"Recharge"` + New int `json:"New"` + Old int `json:"Old"` +} + +// ESNewOnlineBucket 在线聚合对象 +type ESNewOnlineBucket struct { + Buckets []struct { + Key interface{} + Doc_count int + Total struct { + Value float64 + } + Recharge struct { + Value float64 + } + New struct { + Value float64 + } + Old struct { + Value float64 + } + } +} + +// ESDailySysData 每日系统统计的一些数据 +// USDT USDT总量 +// BRL BRL总量 +// Time 统计日零点时间戳 +// Date 日期 +type ESDailySysData struct { + Date string + Channel int + Time int64 + USDT int64 + BRL int64 +} + +// ESOpenRecord 统计安装打开app的数目 +type ESOpenRecord struct { + Time int64 + UUID string + Channel int +} + +// ESGameJackpot 游戏场jackpot大奖记录 +type ESGameJackpot struct { + UID int // 用户uid + Nick string // 用户昵称 + Time int64 // 时间 + Avatar string // 用户头像 + Channel int // 渠道 + Bet int64 // 投注总额 + Get int64 // 获得的奖励 + Total int64 // 总奖励 + GameID int + RoomID int + Winners int // 瓜分奖池的总玩家 + Multi int // 倍率 +} + +// ESBlackList 游戏拉黑玩家 +type ESBlackList struct { + UID int // 用户uid + Name string + Phone string + Email string + PayAccount string + Time int64 +} + +// ESLongPay 充值返回记录 +type ESLongPay struct { + Channel int + Time int64 + Date string + Cost int64 // 时间消耗单位毫秒 + UID int + Amount int64 +} + +// ESTron 转账记录 +type ESTron struct { + TxID string // 交易号 + From string + To string + OrderID string // 系统订单号 + Amount int64 + Time int64 + Type int // 1trx 2usdt + Success bool + Status int // 1玩家退出打u 2系统自动从玩家地址转u回主地址 3后台转账操作 +} + +// ESABLog 进入ab面统计 +type ESABLog struct { + Channel int + Time int64 + DeviceID string + IsA bool // 是否进入A面 + Log string // 日志记录 +} + +// ESGameData 玩家游戏记录 +type ESGameData struct { + UID int // 用户uid + Channel int + Provider int // 提供商 + GameID int // 游戏id + UUID string // 厂商唯一id + Type CurrencyType + BetAmount int64 // 下注金额 + SettleAmount int64 // 输赢金额 + MyUUID string // 自己的唯一id + Time int64 + Birth int64 + PlayTime int64 + SubID int + IsNew bool +} + +// ESIncomeStatistics 收入统计 +// Investment 投入资金 +// Period 回本周期 +type ESIncomeStatistics struct { + Time int64 // 时间 + Channel int // 渠道 + Investment int64 // 投入资金 + Period int64 // 回本周期 +} + +// ESMillionGameRecord 百人模式游戏数据统计 +type ESMillionGameRecord struct { + Time int64 + GameID int // 游戏id + RoomID int // 场次id + UUID string + PlayerCount int // 本局总人数 + PlayerBet int64 // 用户下注金额 + Reward int64 // 发放金额 + Profit int64 // 盈亏 + Result string // 开奖结果 + ResultDetail string // 开奖结果 具体的牌/骰子 + Channel int + Area int // 区域 + IsWin bool // 该区域是否获胜 +} + +// ESMillionPlayerRecord 百人模式游戏数据统计 +type ESMillionPlayerRecord struct { + UID int + Time int64 + GameID int // 游戏id + RoomID int // 场次id + UUID string + Settle int64 // 本局结算结果 + Result string // 开奖结果 + Channel int + Bets []int64 +} + +// ESShareProfitReport 分享者每日分享收益记录 +type ESShareProfitReport struct { + UID int // 分享者 + Regist int64 // 注册人数 + Bet int64 // 有效下注 + Level int // 分享者等级 + Reward int64 + Date string + Time int64 +} + +// ESShareProfitRecord 分享推荐收益记录 +type ESShareProfitRecord struct { + UID int // 被分享人 + Bet int64 // 被分享人投注 + DownBet int64 // 被分享人的下级投注总和 + Up int // 分享者uid + Reward int64 // 佣金 + Date string + Time int64 +} + +// UID int 被分享人 +// Referer int 分享人 +type ESPddRecord struct { + UID int // 被分享人 + Referer int // 分享人 + Time int64 + Nick string + Avatar string +} + +type ESFeedback struct { + UID int + QuestionIndex int + Choose int + Context string + Time int64 +} + +type ESActivity struct { + ActivityID int + UID int + Time int64 + Type int // 1点击 2参与 + Amount int64 // 赠送金额 +} diff --git a/common/game.go b/common/game.go new file mode 100644 index 0000000..ff976e1 --- /dev/null +++ b/common/game.go @@ -0,0 +1,79 @@ +package common + +import "fmt" + +const ( + JackpotTypeActivity = iota + 1 + JackpotTypeGame +) + +var ( + GameModulePrefix = "game" // 游戏模块名字前缀 +) + +func GetGameModuleName(gid int) string { + return fmt.Sprintf("%v%v", GameModulePrefix, gid) +} + +// Jackpot 记录各个奖池 +type Jackpot struct { + ID int `gorm:"primarykey"` + Type int `gorm:"column:type;not null;type:int(11);comment:类型 活动1 游戏2" json:"Type"` + TypeID int `gorm:"column:type_id;not null;type:int(11);uniqueIndex:type_id;comment:奖池标记id,可以是活动id,游戏id" json:"TypeID"` + Amount int64 `gorm:"column:amount;type:bigint(20);default:0;comment:奖池" json:"Amount"` + Max int64 `gorm:"column:max;type:bigint(20);default:0;comment:奖池最大额度" json:"Max"` +} + +func (j *Jackpot) TableName() string { + return "jackpot" +} + +const ( + SessionTypeBet = iota + 1 + SessionTypeSettle + SessionTypeActivity + SessionTypeAdjustment // 调整玩家余额 + SessionTypeJackpot + SessionTypeBonus + SessionTypeBuyIn // 直接扣钱操作 + SessionTypeBuyOut // 直接加钱操作 +) + +// 提供商下单记录 +type ProviderBetRecord struct { + ID int `gorm:"primarykey"` + UID int `gorm:"column:uid;not null;type:int(11);index:uid;comment:uid"` + Provider int `gorm:"column:provider;not null;type:int(11);uniqueIndex:p_uuid;comment:供应商id"` + Currency string `gorm:"column:currency;type:varchar(64);comment:货币名称"` + CurrencyType CurrencyType `gorm:"column:currency_type;default:1;type:int(11);comment:货币id"` + GameID int `gorm:"column:game_id;not null;type:int(11);comment:游戏id"` + GameName string `gorm:"column:game_name;type:varchar(64);default:'';comment:游戏名"` + UUID string `gorm:"column:uuid;type:varchar(255);uniqueIndex:p_uuid;comment:供应商唯一识别值"` + SessionID string `gorm:"column:session_id;type:varchar(255);comment:牌局类型时的牌局id"` + Type int `gorm:"column:type;type:bigint(20);uniqueIndex:p_uuid;comment:类型 1下注 2结算"` + Time int64 `gorm:"column:time;type:bigint(20);default:0;comment:时间戳"` + ProviderTime int64 `gorm:"column:provider_time;type:bigint(20);default:0;comment:厂商时间戳"` + Amount int64 `gorm:"column:amount;type:bigint(20);default:0;comment:下注额"` + Settle int64 `gorm:"column:settle;type:bigint(20);default:0;comment:结算额"` + Preserve int64 `gorm:"column:preserve;type:bigint(20);default:0;comment:预扣款"` + TurnOver int64 `gorm:"column:turnover;type:bigint(20);default:0;comment:有效投注额"` + MyUUID int64 `gorm:"column:my_uuid;type:bigint(20);comment:我方唯一识别值"` + Esi int64 `gorm:"column:esi;type:bigint(20);default:1;comment:额外字段"` // tada 表示本局是否已失败 2已失败,后续不再接收 + Esi1 int64 `gorm:"column:esi1;type:bigint(20);default:1;comment:额外字段1"` // awc标识注单无效原因 + Ess string `gorm:"column:ess;type:varchar(64);default:'';comment:额外字符串字段"` // 活动时,标识活动代码 +} + +func (t *ProviderBetRecord) TableName() string { + return "provider_bet_record" +} + +const ( + GameType = iota + GameTypeInHouse + GameTypeSlots + GameTypeLive + GameTypeTable + GameTypeSpecial + GameTypeESport + GameTypeSportBook +) diff --git a/common/lang.go b/common/lang.go new file mode 100644 index 0000000..3e21e5e --- /dev/null +++ b/common/lang.go @@ -0,0 +1,20 @@ +package common + +const ( + EN = "en" + PT = "pt" +) + +func IsLangValid(lan string) bool { + if lan != EN && lan != PT { + return false + } + return true +} + +func CheckLang(lan string) string { + if lan != EN && lan != PT { + return EN + } + return lan +} diff --git a/common/platform.go b/common/platform.go new file mode 100644 index 0000000..b00c09b --- /dev/null +++ b/common/platform.go @@ -0,0 +1,373 @@ +package common + +import ( + "fmt" + "server/util" + "strconv" + "strings" +) + +// 平台登录方式 +const ( + AccountTypeGuest = iota + AccountTypePhone + AccountTypeFacebook + AccountTypeGoogleplay + AccountTypeEmail + AccountTypeAccount + AccountTypeRobot = 100 +) + +// 账号状态 +const ( + AccountStatus = iota + AccountStatusNormal // 正常 + AccountStatusLimit // 封禁 + AccountStatusAll +) + +// 对账号进行操作 +const ( + OptPlayerType = iota + OptPlayerTypeKick // 踢出玩家 + OptPlayerTypeDisconnect // 操作玩家断线 + OptPlayerTypeAll +) + +const ( + LanguageEN = iota + 1 + LanguageHindi +) + +const ( + NewUser = iota + 1 // 新用户登录 + OldUser // 老用户登录 +) + +const ( + DecimalDigits = 100000000 // 计算时精确到小数点后8位 +) + +var ( + DecimalCounts = 0 + OneDay int64 = 24 * 60 * 60 // 一天的秒数 +) + +func init() { + tmp := DecimalDigits + for { + if tmp <= 1 { + break + } + tmp /= 10 + DecimalCounts++ + } +} + +func CashFloat64ToInt64(value float64) int64 { + negative := value < 0 + // 将浮点数转换为字符串 + text := fmt.Sprintf("%v", value) + // 查找小数点位置 + pointIndex := strings.Index(text, ".") + var left, right string + var big, small int64 + if pointIndex != -1 { + left = text[:pointIndex] + // 去除小数点后面尾随的0 + right = strings.TrimRight(text[pointIndex+1:], "0") + } else { + left = text + } + diff := len(right) - DecimalCounts + if diff > 0 { + right = right[:DecimalCounts] + } else { + for i := 0; i < -diff; i++ { + right += "0" + } + } + big, _ = strconv.ParseInt(left, 10, 64) + small, _ = strconv.ParseInt(right, 10, 64) + ret := util.Abs(big*DecimalDigits) + small + if negative { + return -ret + } + return ret +} + +const ( + DeviceType = iota + DeviceTypeMobile + DeviceTypePC + DeviceTypeWebview + DeviceTypeIOSH5 + DeviceTypeMac + DeviceTypePWA + DeviceTypeAll +) + +func IsPC(t int) bool { + return t == DeviceTypePC +} + +func GetAccountPre(t int) string { + switch t { + case AccountTypeGuest: + return "guest" + case AccountTypePhone: + return "phone" + case AccountTypeFacebook: + return "fb" + case AccountTypeGoogleplay: + return "gp" + case AccountTypeEmail: + return "email" + case AccountTypeAccount: + return "account" + case AccountTypeRobot: + return "robot" + } + return "" +} + +// 渠道号分区 +var ( + ChannelRegionGoogle = []int{2, 10000} + ChannelRegionVivo = []int{10001, 11000} +) + +const ( + BrocastIDAll = iota // 全服广播 + BrocastIDWithdraw // tx事件 +) + +// 平台号 +const ( + PlatformIDGoogle = iota + 1 + PlatformIDVivo + PlatformIDAll +) + +func GetRegionByPlatformID(id int) []int { + switch id { + case PlatformIDGoogle: + return ChannelRegionGoogle + case PlatformIDVivo: + return ChannelRegionVivo + default: + return nil + } +} + +type AdjustEventType int + +// adjust事件顺序 +const ( + AdjustEventFinishLoad AdjustEventType = iota // 加载完成 + AdjustEventFirstPage // 首屏加载 + AdjustEventClickDemo // 点击游戏demo + AdjustEventClickPlay // 点击游戏play + AdjustEventNewPay // 新用户付费 + AdjustEventAllPay // 所有用户付费 + AdjustEventNewPlayer // 注册完成 +) + +const ( + ADZero = iota + ADFB + ADKwai + ADJust + ADAll +) + +// Channel 渠道表 +// GameControlm 游戏控制,0关闭,1开启 +type Channel struct { + ID uint `gorm:"primarykey"` + IgnoreOrganic int `gorm:"column:ignore_organic;type:tinyint(4);default:1;comment:是否屏蔽自然量 1不屏蔽 2屏蔽" json:"ignore_organic"` + AdjustEventID string `gorm:"column:adjust_eventid;type:varchar(256);not null;comment:adjust事件id,按顺序隔开" json:"adjust_eventid"` + AdjustAppToken string `gorm:"column:adjust_app_token;type:varchar(256);not null;comment:adjust应用token" json:"adjust_app_token"` + AdjustAuth string `gorm:"column:adjust_auth;type:varchar(256);not null;comment:adjust验证码" json:"adjust_auth"` + Name string `gorm:"column:name;type:varchar(32);not null;comment:渠道" json:"name"` + PackName string `gorm:"column:pack_name;type:varchar(256);not null;comment:包名" json:"pack_name"` + Version int `gorm:"column:version;type:int(11);not null;comment:审核版本号" json:"version"` + MainVersion int `gorm:"column:main_version;type:int(11);not null;comment:正式服版本号" json:"main_version"` + ChannelID int `gorm:"column:channel_id;type:bigint(20);uniqueIndex:channel_id;comment:渠道id" json:"channel_id"` + PlatformID int `gorm:"column:platform_id;type:int(11);comment:平台id,1 googleplay 2 vivo" json:"platform_id"` + IsHot int `gorm:"column:ishot;type:tinyint(4);default:1;comment:是否热更,1不热更,2热更" json:"ishot"` + URL string `gorm:"column:url;type:varchar(64);not null;default:'';comment:域名" json:"url"` + Show int `gorm:"column:show;type:int(11);default:0;comment:是否打开,1不打开,2打开" json:"show"` + FBPixelID string `gorm:"column:fb_pixelid;type:varchar(256);not null;comment:fb像素id" json:"fb_pixelid"` + FBAccessToken string `gorm:"column:fb_accesstoken;type:varchar(256);not null;comment:fb验证码" json:"fb_accesstoken"` + UP int `gorm:"column:up;type:int(11);default:0;comment:上级渠道" json:"up"` + ADUpload int `gorm:"column:ad_upload;type:int(11);default:0;comment:上报事件的平台" json:"ad_upload"` +} + +func (c *Channel) TableName() string { + return "channel" +} + +// ServerVersion 服务器版本表 +// GameControlm 游戏控制,0关闭,1开启 +type ServerVersion struct { + ID uint `gorm:"primarykey"` + ServerID int `gorm:"column:server_id;type:int(11);uniqueIndex:server_id;default:0;comment:服务器id" json:"server_id" redis:"server_id"` + Version int `gorm:"column:version;type:int(11);default:0;comment:服务器最新版本" json:"version" redis:"version"` +} + +func (c *ServerVersion) TableName() string { + return "server_version" +} + +func (c *Channel) GetAdjustEventID(e int) string { + s := strings.Split(c.AdjustEventID, ",") + if e > len(s)-1 { + return "" + } + return s[e] +} + +// 红点提示 +const ( + RedpointMail = iota + 1 // 邮件 +) + +// WhiteList 白名单 +// ListType 名单类型 1白名单 2黑名单(当为0时,用作系统开关) +// LimitType 限制类型 1ip限制 2设备限制(用作系统开关时 1开启 2关闭) +// Content ip或者设备码 +// Platform 平台类型 facebook 2 gooleplay 3 +// Channel 渠道号 +// Version 版本号 +// CreateTime 创建时间 +// Operator 操作人 +// Powers 权限 +// +// 第1位控制 手机/游客/渠道 +// 第2位控制 注册账号 +// 第3位控制 绑定账号 +// 第4位控制 账号充值 +// 第5位控制 账号退出 +// 第6位控制 游戏玩牌 +// 第7位控制 版本热更 +type WhiteList struct { + ID string + ListType int + LimitType int + Content string + Platform int + Channel int + Version int + Powers []int + CreateTime int64 + Operator string +} + +var ( + PowerMap = map[string]int{ + "/account/phoneCode/login": 0, + "/account/guestLogin": 0, + "/account/gpLogin": 0, + "/account/phoneCode/regist": 1, + "/account/phoneCode/bind": 2, + "/balance/recharge/do": 3, + "/balance/withdraw/do": 4, + "playCard": 5, + // "/sys/hotUpdate": 6, + } + LimitMap = map[string]struct{}{ + "/sys/hotUpdate": {}, + } +) + +func IsLimitMap(path string) bool { + _, ok := LimitMap[path] + return ok +} + +// PowerPass 鉴权 +func (w *WhiteList) PowerPass(path string) bool { + po, ok := PowerMap[path] + if !ok { + index := -1 + for k, v := range PowerMap { + if strings.Contains(path, k) { + index = v + break + } + } + if index == -1 { + return true + } + po = index + } + if po > len(w.Powers) { + return false + } + power := w.Powers[po] + if po == 0 { + index := strings.LastIndex(path, "/") + newStr := path[index:] + switch newStr { + case "/login": + return power&4 == 4 + case "/guestLogin": + return power&2 == 2 + case "/gpLogin": + return power&1 == 1 + default: + return false + } + } + return power == 1 +} + +// ExamineList 审核人员列表 +type ExamineList struct { + Time int64 +} + +// LoginRecord 玩家登录记录表 +type LoginRecord struct { + ID int `gorm:"primarykey"` + FirstTime int64 `gorm:"column:first_time;default:0;type:bigint(20);comment:首次登录时间"` + Time int64 `gorm:"column:time;default:0;type:bigint(20);unixIndex:ut;comment:最后登录时间"` + UID int `gorm:"column:uid;not null;type:int(11);unixIndex:ut;comment:玩家id"` + IP string `gorm:"column:ip;type:varchar(256);comment:玩家登录ip"` + Date string `gorm:"column:date;type:varchar(64);default:'';comment:日期"` + ChannelID int `gorm:"column:channel_id;type:bigint(20);default:0;comment:渠道id"` + Status int `gorm:"column:status;type:int(11);comment:1是新用户,2是老用户"` + Platform int `gorm:"column:platform;type:int(11);default:1;comment:最新登录平台"` + IsRecharge int `gorm:"column:is_recharge;type:tinyint(4);default:1;comment:1未充值,2已充值"` +} + +func (c *LoginRecord) TableName() string { + return "login_record" +} + +// RedisRealOnline 各场次实时在线 +type RedisRealOnline struct { + Total int // 总在线人数 + New int // 新用户在线人数 +} + +// IsShareChannel 是否是分享渠道 +func IsShareChannel(cid int) bool { + return cid > 10000 +} + +// BlackList 黑名单 +type BlackList struct { + ID int `gorm:"primarykey"` + Name string `gorm:"column:name;type:varchar(64);default:'';not null;uniqueIndex:black_all;comment:名字" json:"Name" web:"name"` + Phone string `gorm:"column:phone;type:varchar(64);default:'';not null;uniqueIndex:black_all;comment:手机号" json:"Phone" web:"phone"` + Email string `gorm:"column:email;type:varchar(64);default:'';not null;uniqueIndex:black_all;comment:email" json:"Email" web:"email"` + PayAccount string `gorm:"column:pay_account;type:varchar(255);default:'';not null;uniqueIndex:black_all;comment:支付账号" json:"PayAccount" web:"pay_account"` + DeviceID string `gorm:"column:deviceid;type:varchar(255);default:'';not null;uniqueIndex:black_all;comment:设备号" json:"DeviceID" web:"deviceid"` + IP string `gorm:"column:ip;type:varchar(64);default:'';not null;uniqueIndex:black_all;comment:ip" json:"IP" web:"ip"` +} + +func (c *BlackList) TableName() string { + return "black_list" +} diff --git a/common/player.go b/common/player.go new file mode 100644 index 0000000..f9e4a15 --- /dev/null +++ b/common/player.go @@ -0,0 +1,309 @@ +package common + +import ( + "fmt" + "reflect" +) + +// 账号身份 +const ( + PlayerRoleNormal = iota + PlayerRoleRobot = 100 +) + +// 玩家状态 +const ( + PlayerStatusNormal = iota + 1 + PlayerStatusMatching + PlayerStatusPlaying +) + +// 玩家是否在线 +const ( + PlayerOnline = iota + 1 + PlayerOffline +) + +type PlayerDBInfo struct { + Id int `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"id" redis:"id"` + ADID string `gorm:"column:adid;type:varchar(256);comment:玩家adjustID" json:"adid" redis:"adid"` + GPSADID string `gorm:"column:gps_adid;type:varchar(256);comment:玩家google广告ID" json:"gps_adid" redis:"gps_adid"` + Openid *string `gorm:"column:openid;type:varchar(128);uniqueIndex:channel_openid;comment:唯一识别号" json:"-" redis:"openid"` + IP string `gorm:"column:ip;type:varchar(256);comment:玩家登录ip" json:"ip" redis:"ip"` + Pass string `gorm:"column:pass;type:varchar(64)" json:"pass" redis:"pass"` + Nick string `gorm:"column:nick;type:varchar(64)" json:"nick" redis:"nick"` + Mobile string `gorm:"column:mobile;default:null;type:varchar(64);uniqueIndex:channel_mobile" json:"mobile" redis:"mobile"` + Avatar string `gorm:"column:avatar;type:varchar(512);default:''" json:"avatar" redis:"avatar"` + DeviceId string `gorm:"column:deviceid;type:varchar(256);default:'';comment:设备号" json:"deviceid" redis:"deviceid"` + SessionID string `gorm:"-" json:"sessionID" redis:"sessionID"` + AccountName string `gorm:"column:account_name;default:null;uniqueIndex:channel_account_name;commont:账户名" json:"account_name" redis:"account_name"` + GateID string `gorm:"-" json:"gateID" redis:"gateID"` + Token string `gorm:"-" json:"token" redis:"token"` + Birth int64 `gorm:"column:birth;type:bigint(20);default:0;comment:账号创建时间,时间戳" json:"birth" redis:"birth"` + Role int `gorm:"column:role;type:smallint(8);default:0;comment:玩家角色0普通玩家,100机器人" json:"role" redis:"role"` + ChannelID int `gorm:"column:channel_id;type:bigint(20);uniqueIndex:channel_openid;uniqueIndex:channel_mobile;uniqueIndex:channel_account_name;default:1;comment:渠道id" json:"channel_id" redis:"channel_id"` + AccountType int `gorm:"column:account_type;comment:0游客,1fb,2gp" json:"account_type" redis:"account_type"` + Status int `gorm:"column:status;type:tinyint(4);default:1;comment:账号状态,1正常,2封禁" json:"status" redis:"status"` + Online int `gorm:"column:online;type:tinyint(4);default:2;comment:是否在线,1在线,2不在线" json:"online" redis:"online"` + Tag string `gorm:"column:tag; type:varchar(512);default:'';comment:后台管理给账户打的标签,用户不可见" json:"tag" redis:"tag"` + Platform int `gorm:"column:platform;type:int(11);default:1;comment:最新登录平台" json:"platform" redis:"platform"` + LastLogin int64 `gorm:"column:last_login;type:bigint(20);default:0;comment:最近登录时间" json:"last_login" redis:"last_login"` + Country string `gorm:"column:country;type:varchar(64);default:'';comment:国家" json:"country" redis:"country"` +} + +func (u *PlayerDBInfo) TableName() string { + return "users" +} + +// PlayerSession 玩家会话信息 +type PlayerSession struct { + SessionID string `json:"sessionID" redis:"sessionID"` + GateID string `json:"gateID" redis:"gateID"` + Nick string `json:"nick" redis:"nick"` +} + +// FacebookUserInfo facebook返回的登录接口 +type FacebookUserInfo struct { + ID string + Name string + Picture struct { + Data struct { + URL string `json:"url"` + Height int + Width int + IsSilhouette bool + } `json:"data"` + } `json:"picture"` +} + +// GooglePlayUserInfo googlePlay返回的登录接口 +type GooglePlayUserInfo struct { + ID string `json:"sub"` + Email string + Name string + Given_name string + Family_name string + Picture string + Locale string + Verified_email bool +} + +// PlayerStatus 玩家状态 +// type PlayerStatus struct { +// GameName string `json:"GameName" redis:"GameName"` +// UUID string `json:"UUID" redis:"UUID"` +// TableID string `json:"TableID" redis:"TableID"` +// ActiveTime int64 `json:"ActiveTime" redis:"ActiveTime"` +// WorkID int `json:"WorkID" redis:"WorkID"` +// MatchID uint32 `json:"MatchID" redis:"MatchID"` +// Status uint32 `json:"Status" redis:"Status"` +// GameID uint32 `json:"GameID" redis:"GameID"` +// } + +// func (s *PlayerStatus) MarshalBinary() ([]byte, error) { +// return json.Marshal(s) +// } + +// func (s *PlayerStatus) UnmarshalBinary(data []byte) error { +// return json.Unmarshal(data, s) +// } + +// NormalPlayerSql 返回查询正常用户的sql条件语句 +func NormalPlayerSql(channel ...*int) string { + sql := "" + for _, v := range channel { + if v == nil { + continue + } + if sql == "" { + sql += fmt.Sprintf("(channel_id = %v", *v) + } else { + sql += fmt.Sprintf(" or channel_id = %v", *v) + } + } + if len(sql) > 0 { + sql += ")" + } + sql += fmt.Sprintf(" and role <> %v and status = %v", PlayerRoleRobot, AccountStatusNormal) + return sql +} + +// PlayerData 保存一些玩家记录 +type PlayerData struct { + UID int `gorm:"column:uid;not null;type:int(11);uniqueIndex:uid"` + LastSysEmailDraw int64 `gorm:"column:last_sys_email_draw;type:bigint(20);default:0;comment:最近一次接收系统邮件的时间"` + LastAppSpinDraw int64 `gorm:"column:last_app_spin_draw;type:bigint(20);default:0;comment:上次领取app下载转盘的时间"` + FeedbackTime int64 `gorm:"column:feedback_time;type:bigint(20);default:0;comment:完成问卷调查的时间"` +} + +func (u *PlayerData) TableName() string { + return "player_data" +} + +// PlayerRed 玩家红点信息 +type PlayerRed struct { + ID uint `gorm:"primarykey"` + UID int `gorm:"column:uid;not null;type:int(11);uniqueIndex:uid"` + Mail int `gorm:"column:mail;type:int(11);comment:未读取邮件数" json:"Mail"` +} + +func (u *PlayerRed) TableName() string { + return "player_red" +} + +const ( + ItemStatusZero = iota + ItemStatusNormal // 正常 + ItemStatusInvalid // 不可用 + ItemStatusAll +) + +// 物品的枚举 +const ( + ItemZero = iota + ItemDiscountTicket + ItemAll +) + +// PlayerItems 玩家道具 +type PlayerItems struct { + ID int `gorm:"primarykey"` + UID int `gorm:"column:uid;type:int(11)"` + ItemID int `gorm:"column:item_id;default:0;comment:物品id"` + Time int64 `gorm:"column:time;type:bigint(20);default:0;comment:获得的时间"` + Status int `gorm:"column:status;type:int(11);default:0;comment:物品状态"` + Exi1 int `gorm:"column:exi1;type:int(11);default:0;comment:物品标识字段1"` +} + +func (p *PlayerItems) TableName() string { + return "player_items" +} + +// PlayerH5Data 玩家H5数据 +type PlayerH5Data struct { + UID int `gorm:"primary_key;column:uid;type:int(11)"` + Collect int `gorm:"column:collect;type:int(11);default:1;comment:是否已领取H5收藏奖励,1未领取,2已领取"` + Download int `gorm:"column:download;type:int(11);default:1;comment:是否已领取H5下载奖励,1未领取,2已领取"` +} + +func (p *PlayerH5Data) TableName() string { + return "player_h5data" +} + +var ( + PlayerRechargeTableName = "player_currency_recharge" // 玩家充值账户 +) + +// PlayerCurrency 玩家货币数据(此结构只能往后新增字段,前面字段不能修改顺序) +type PlayerCurrency struct { + UID int `gorm:"primary_key;column:uid;type:int(11)"` + ChannelID int `gorm:"column:channel_id;type:bigint(20);default:1;comment:渠道id"` + BRL int64 `gorm:"column:brl;type:bigint(20);default:0;comment:雷亚尔"` + USDT int64 `gorm:"column:usdt;type:bigint(20);default:0;comment:usdt"` +} + +func (p *PlayerCurrency) TableName() string { + return "player_currency" +} + +func (p *PlayerCurrency) GetBalanceByType(ct CurrencyType) int64 { + ref := reflect.ValueOf(p).Elem() + val := ref.Field(int(ct) + 1) + if val.IsValid() { + return val.Int() + } + return 0 +} + +func (p *PlayerCurrency) SetBalance(ct CurrencyType, value int64) { + ref := reflect.ValueOf(p).Elem() + val := ref.Field(int(ct) + 1) + if val.IsValid() { + val.SetInt(value) + } +} + +// PlayerProfile 玩家生涯数据 +type PlayerProfile struct { + UID int `gorm:"primary_key;column:uid;type:int(11)"` + ChannelID int `gorm:"column:channel_id;type:bigint(20);default:1;comment:渠道id"` + NeedBet int64 `gorm:"column:need_bet;type:bigint(20);comment:当前所需下注量"` + TotalBet int64 `gorm:"column:total_bet;type:bigint(20);comment:总下注"` + TotalCounts int64 `gorm:"column:total_counts;type:bigint(20);comment:总下注次数"` + TotalSettle int64 `gorm:"column:total_settle;type:bigint(20);comment:总派发"` +} + +func (p *PlayerProfile) TableName() string { + return "player_profile" +} + +// Level vip等级 +// Bet 下注额 +// Exp vip经验 +// Draws 已经领取的vip等级奖励,从右至左,等级低到高 +// +// Cashback 当前可领取的cashback +type VipData struct { + ID int `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"ID"` + UID int `gorm:"column:uid;not null;type:int(11);uniqueIndex:uid" json:"UID"` + Level int `gorm:"column:level;not null;type:int(11);default:0;comment:vip等级" json:"Level"` + Bet int64 `gorm:"column:bet;not null;type:bigint(20);default:0;comment:下注额" json:"Bet"` + Exp int64 `gorm:"column:exp;not null;type:bigint(20);default:0;comment:vip经验" json:"Exp"` + Draws int64 `gorm:"column:draws;not null;type:bigint(20);default:0;comment:已经领取的vip等级奖励,从右至左,等级低到高" json:"Draws"` + Profit int64 `gorm:"column:profit;not null;type:bigint(20);default:0;comment:计算周期内的盈亏" json:"Profit"` + ProfitTime int64 `gorm:"column:profit_time;not null;type:bigint(20);default:0;comment:开始计算时间节点" json:"ProfitTime"` + Cashback int64 `gorm:"column:cashback;not null;type:bigint(20);default:0;comment:当前可领取的cashback" json:"Cashback"` +} + +func (c *VipData) TableName() string { + return "vip_data" +} + +type TronData struct { + ID int `gorm:"primary_key;AUTO_INCREMENT;column:id"` + UID int `gorm:"column:uid;not null;type:int(11);uniqueIndex:uid"` + Addr string `gorm:"column:addr; type:varchar(256);default:'';comment:钱包地址"` + Usdt int64 `gorm:"column:usdt;type:bigint(20);default:0;comment:地址里的u存量,单位1e6"` +} + +func (c *TronData) TableName() string { + return "tron_data" +} + +// PlayerStatus 玩家状态 +type PlayerStatus struct { + ModuleName string `json:"ModuleName" redis:"ModuleName"` + WorkID int `json:"WorkID" redis:"WorkID"` + GameID int `json:"GameID" redis:"GameID"` + SubID int `json:"SubID" redis:"SubID"` + TableID string `json:"TableID" redis:"TableID"` +} + +// 玩家广告相关数据 +type PlayerADData struct { + ID int `gorm:"primarykey"` + UID int `gorm:"column:uid;type:int(11);index:uid"` + IP string `gorm:"column:ip;type:varchar(256);comment:玩家ip"` + FBC string `gorm:"column:fbc;type:varchar(256);comment:玩家点击识别号"` + FBP string `gorm:"column:fbp;type:varchar(256);comment:玩家浏览器识别号"` + ChannelID int `gorm:"column:channel_id;type:bigint(20);default:1000;comment:渠道id"` + UserAgent string `gorm:"column:user_agent;type:varchar(256);comment:玩家浏览器版本"` +} + +func (c *PlayerADData) TableName() string { + return "player_ad_data" +} + +// 玩家购买记录 +type PlayerPayData struct { + ID uint `gorm:"primarykey"` + UID int `gorm:"column:uid;not null;type:int(11);uniqueIndex:uid"` + BreakGift string `gorm:"column:break_gift;type:varchar(256);default:'';comment:已购买破产礼包的档位"` + SubBreakGift []int `gorm:"-"` + // FirstPay string `gorm:"column:first_pay;default:'';type:varchar(256);comment:首次购买商品记录"` + // SubFirstPay []int `gorm:"-"` +} + +func (c *PlayerPayData) TableName() string { + return "player_pay_data" +} diff --git a/common/provider.go b/common/provider.go new file mode 100644 index 0000000..8580e8c --- /dev/null +++ b/common/provider.go @@ -0,0 +1,67 @@ +package common + +import "server/config" + +const ( + ProviderZero = iota + ProviderInhouse + ProviderTada + ProviderSexy + ProviderPGSoft + ProviderEvolutionGaming + ProviderAllBet + ProviderBigGaming + ProviderSAGaming + ProviderPragmaticPlay + ProviderCQ9 + ProviderPlayTech + ProviderJoker + ProviderDragonSoft + ProviderTFGaming + ProviderWMCasino + ProviderKing855 + ProviderAMAYA + ProviderHabanero + ProviderIBC + ProviderReevo + ProviderEvoPlay + ProviderPlayStar + ProviderDreamGaming + ProviderNexus4D + ProviderSlotXo + ProviderBTI + ProviderEzugi + ProviderAll +) + +const ( + ProviderAPIType = iota + ProviderAPITypeJson + ProviderAPITypePostform + ProviderAPITypeAll +) + +// ConfigServerFlag 配置服务器编号配置 +type ConfigServerFlag struct { + ID int `gorm:"primary_key;AUTO_INCREMENT;column:id"` + Flag string `gorm:"column:flag;type:varchar(255);default:'b';uniqueIndex:flag;comment:服务器编号" web:"flag"` + APIURL string `gorm:"column:api_url;type:varchar(255);default:'';comment:api请求地址" web:"api_url"` +} + +func (c *ConfigServerFlag) TableName() string { + return "config_server_flag" +} + +func GetProviderUserName(name string) string { + if config.GetBase().ServerFlag != "a" { + name = config.GetBase().ServerFlag + name + } + return name +} + +func GetProviderUserToken(token string) string { + if config.GetBase().ServerFlag != "a" { + token = config.GetBase().ServerFlag + token + } + return token +} diff --git a/common/recharge.go b/common/recharge.go new file mode 100644 index 0000000..4e1fd3d --- /dev/null +++ b/common/recharge.go @@ -0,0 +1,235 @@ +package common + +const ( + StatusROrderWaitting = iota // 等待提交状态 + StatusROrderCreate + StatusROrderPay + StatusROrderFinish + StatusROrderFail + StatusROrderRefuse + StatusROrderPending // 挂起 + StatusROrderAll +) + +type PayType int + +// 支付类型,巴西代表cpf|cnpj|email|phone|evp +const ( + PayTypeInvalid PayType = iota + PayTypeCPF + PayTypeCNPJ + PayTypePhone + PayTypeEmail + PayTypeEVP + PayTypeAll +) + +func (p PayType) Isvalid() bool { + return p > PayTypeInvalid && p < PayTypeAll +} + +func (p PayType) String() string { + switch p { + case PayTypeCPF: + return "CPF" + case PayTypeCNPJ: + return "CNPJ" + case PayTypePhone: + return "PHONE" + case PayTypeEmail: + return "EMAIL" + case PayTypeEVP: + return "EVP" + default: + return "" + } +} + +// ProductTypeRechargeWait 商品类型id +const ( + ProductTypeRechargeWait = iota + 1 + ProductTypeAll +) + +// 非常规支付方式 +const ( + PayTypePerson = -1 // 个卡支付 +) + +// 首充活动id +const ( + WelcomeBonusProductID = 1111 +) + +// 支付方式 +const ( + PaySourceZero = iota + 1 + PaySourceModulePay // pay模块支付 + PaySourceBlockPay // 区块链支付 + PaySourceAll +) + +// 用于记录商品id对应的购买次数,一个bigint可记录最多16个商品的最多15次购买记录 +var ( + ProductIDMap = map[int]int{} +) + +func GetProductPayCount(total int64, id int) int { + // log.Debug("total%v,id:%v", total, id) + tmp, ok := ProductIDMap[id] + if !ok { + return 0 + } + return int((total >> (tmp * 4)) & 15) +} + +func AddProductPayCount(total int64, id int) int64 { + tmp, ok := ProductIDMap[id] + if !ok { + return total + } + // 右移,将当前需要操作的商品移动到最右侧 + pos := tmp * 4 + num := total >> (pos) + + // 拿到操作商品右侧的商品信息值 + right := 1 + for i := 0; i < pos; i++ { + right *= 2 + } + right-- + rightNum := total & int64(right) + + // 拿到需要操作的商品真实次数 + realNum := num & 15 + if realNum >= 15 { + return total + } + num++ + return rightNum | (num << (pos)) +} + +type RechargeOrder struct { + ID uint `gorm:"primarykey"` + UID int `gorm:"column:uid;not null;type:int(11);index:uid"` + CreateTime int64 `gorm:"column:create_time;type:bigint(20);comment:创建时间" redis:"create_time"` + OrderID string `gorm:"column:orderid;not null;type:varchar(255);uniqueIndex:orderid;comment:本地ID"` + APIPayID string `gorm:"column:apipayid;not null;type:varchar(255);comment:支付第三方生成的id"` + Extra string `gorm:"column:extra;type:varchar(255);comment:额外信息字段"` + PayAccount string `gorm:"column:payaccount;not null;type:varchar(512);comment:支付账户"` + Amount int64 `gorm:"column:amount;not null;type:bigint(20);comment:单位8位小数"` + PaySource int `gorm:"column:pay_source;not null;type:int(11);comment:支付来源"` + PayChannel int `gorm:"column:pay_channel;type:int(11);comment:具体支付渠道"` + Event int `gorm:"column:event;not null;type:smallint(4);comment:事件类型"` + CurrencyType CurrencyType `gorm:"column:currency_type;not null;type:int(11);comment:货币类型"` + ProductID int `gorm:"column:productid;not null;type:int(11)"` + Status uint8 `gorm:"column:status;not null;type:tinyint(4);comment:1新建,2支付完成,3发货完成,4支付失败,5取消"` + FailReason string `gorm:"column:fail_reason;type:varchar(255);comment:失败原因"` + ChannelID int `gorm:"column:channel_id;type:bigint(20);default:1;comment:渠道id" redis:"channel_id"` + Remarks string `gorm:"column:remarks;type:varchar(255);comment:备注" redis:"remarks"` + CallbackTime int64 `gorm:"column:callback_time;type:bigint(20);comment:到账时间" redis:"callback_time"` + UPI int `gorm:"column:upi;not null;type:int(11);comment:玩家选择的upi"` +} + +func (r *RechargeOrder) TableName() string { + return "recharge_order" +} + +const ( + WithdrawOrderTypeZero = iota + WithdrawOrderTypeNormal + WithdrawOrderTypeShare + WithdrawOrderTypeAll +) + +type WithdrawOrder struct { + ID uint `gorm:"primarykey"` + UID int `gorm:"column:uid;not null;type:int(11)"` + CreateTime int64 `gorm:"column:create_time;type:bigint(20);comment:创建时间"` + OrderID string `gorm:"column:orderid;not null;type:varchar(255);uniqueIndex:orderid;comment:本地ID"` + APIPayID string `gorm:"column:apipayid;not null;type:varchar(255);comment:支付第三方生成的id"` + Extra string `gorm:"column:extra;type:varchar(64);comment:额外信息字段"` + PayAccount string `gorm:"column:payaccount;not null;type:varchar(512);comment:支付账户"` + Amount int64 `gorm:"column:amount;not null;type:bigint(20);comment:单位分"` + WithdrawCash int64 `gorm:"column:withdraw_cash;type:bigint(20);default:0;comment:退出扣除的金币"` + PaySource int `gorm:"column:pay_source;not null;type:int(11);comment:支付来源"` + CurrencyType CurrencyType `gorm:"column:currency_type;not null;type:int(11);comment:货币类型"` + PayChannel int `gorm:"column:pay_channel;type:int(11);comment:具体支付渠道"` + Event int `gorm:"column:event;not null;type:smallint(4);comment:事件类型"` + ProductID int `gorm:"column:productid;not null;type:int(11)"` + Status uint8 `gorm:"column:status;not null;type:tinyint(4);comment:1新建,2支付完成,3发货完成,4支付失败,5取消"` + FailReason string `gorm:"column:fail_reason;type:varchar(255);comment:失败原因"` + ChannelID int `gorm:"column:channel_id;type:bigint(20);default:1;comment:渠道id"` + Remarks string `gorm:"column:remarks;type:varchar(255);comment:备注"` + AuditTime int64 `gorm:"column:audit_time;type:bigint(20);comment:审核时间"` + CallbackTime int64 `gorm:"column:callback_time;type:bigint(20);comment:到账时间"` + UPI int `gorm:"column:upi;not null;type:int(11);comment:玩家选择的upi"` + Operator string `gorm:"column:operator;type:varchar(255);comment:操作人账号名"` + OrderType int `gorm:"column:order_type;default:1;type:int(11);comment:订单类型 1普通 2分享单"` +} + +func (r *WithdrawOrder) TableName() string { + return "withdraw_order" +} + +// RechargeInfo 玩家充值信息记录表 +type RechargeInfo struct { + ID uint `gorm:"primarykey"` + UID int `gorm:"column:uid;not null;type:int(11);uniqueIndex:uid"` + ProductPayCount int64 `gorm:"column:product_paycount;type:bigint(20);default:0;comment:记录玩家购买商品次数,映射表在代码里"` + DayRecharge int64 `gorm:"column:day_recharge;type:bigint(20);default:0;comment:日充值"` + TotalRechargeCount int64 `gorm:"column:total_recharge_count;type:bigint(20);default:0;comment:总充值次数"` + LastRecharge int64 `gorm:"column:last_recharge;type:bigint(20);default:0;comment:最近一次充值的时间戳"` + TotalRecharge int64 `gorm:"column:total_recharge;type:bigint(20);default:0;comment:总充值数额,不论货币"` + TotalWithdrawCount int64 `gorm:"column:total_withdraw_count;type:bigint(20);default:0;comment:总退出次数"` + TotalWithdraw int64 `gorm:"column:total_withdraw;type:bigint(20);default:0;comment:总退出数额,不论货币"` + TotalWithdrawing int64 `gorm:"column:total_withdrawing;type:bigint(20);default:0;comment:提现中的钱"` + DayWithdraw int64 `gorm:"column:day_withdraw;type:bigint(20);default:0;comment:当日退出数额,不论货币"` + LastWithdraw int64 `gorm:"column:last_withdraw;type:bigint(20);default:0;comment:最近一次退出的时间戳"` + WithdrawCount int `gorm:"column:withdraw_count;type:int(11);default:0;comment:当天累计退出次数"` + WithdrawingCash int64 `gorm:"column:withdrawing_cash;type:bigint(20);default:0;comment:提现中的金币" json:"withdrawing_cash"` +} + +func (r *RechargeInfo) TableName() string { + return "recharge_info" +} + +// RechargeInfoCurrency 玩家各货币充值信息记录表模板 +type RechargeInfoCurrency struct { + ID uint `gorm:"primarykey"` + UID int `gorm:"column:uid;not null;type:int(11);uniqueIndex:uid"` + TotalRecharge int64 `gorm:"column:total_recharge;type:bigint(20);default:0;comment:总充值" json:"total_recharge"` + TotoalWithdraw int64 `gorm:"column:total_withdraw;type:bigint(20);default:0;comment:总退出" json:"total_withdraw"` + TotoalWithdrawing int64 `gorm:"column:total_withdrawing;type:bigint(20);default:0;comment:总退出中" json:"total_withdrawing"` +} + +// PayInfo 支付信息 +// Name 收款人姓名 +// Mobile 收款人手机号码 +// Email 收款人邮箱 +// PayType 收款方式 +// Number 卡号或收款信息 +type PayInfo struct { + ID uint `gorm:"primarykey"` + UID int `gorm:"column:uid;not null;type:int(11);uniqueIndex:uid"` + Name string `gorm:"column:name;default:'';type:varchar(256);comment:收款人姓名"` + Mobile string `gorm:"column:mobile;default:'';type:varchar(256);comment:收款人手机号码"` + Email string `gorm:"column:email;default:'';type:varchar(256);comment:收款人邮箱"` + PayType PayType `gorm:"column:pay_type;default:1;type:int(11);comment:收款方式"` + Number string `gorm:"column:number;default:'';type:varchar(256);comment:卡号或收款信息"` +} + +func (p *PayInfo) TableName() string { + return "pay_info" +} + +// WithdrawCommon 支付信息公共部分 地址格式{"city":"Mumbai","street":"sarang street","houseNumber":"-54/a"} +type WithdrawCommon struct { + Name string // 收款人姓名 + Mobile string // 收款人手机号码 + Email string // 收款人邮箱 + PayType PayType + Number string + Address string + IP string +} diff --git a/common/redis_keys.go b/common/redis_keys.go new file mode 100644 index 0000000..699edb0 --- /dev/null +++ b/common/redis_keys.go @@ -0,0 +1,191 @@ +package common + +import ( + "fmt" + "time" +) + +const ( + RedisExpireLock = 10 * time.Second + RedisExpireToken = 7 * 24 * time.Hour + RedisExpireCode = 180 * time.Second + RedisExpireCodeCD = 60 * time.Second + RedisExpireMail = 3 * time.Second + RedisExpireControl = 30 * 24 * time.Hour + RedisExpireActivityRed = 1 * time.Minute // 红包雨活动,给予客户端1分钟去做领取 + RedisExpireGameEnter = 12 * time.Hour // 进入游戏记录玩家币种的超时时间 +) + +const ( + RedisTimeoutTx = 10 * time.Second +) + +const ( + RedisKeyUser = "user" + RedisKeyCode = "Code" + RedisKeyCodeCD = "CodeCD" + RedisKeyToken = "token" + RedisKeyPlayerStatus = "playerStatus" // 玩家当前游戏状态, 用于断线重连 + RedisKeyRobotPlaying = "robotPlaying" // 已使用的机器人 + RedisKeyGameFlow = "gameFlow" // 游戏流水总额统计,用于调控 + // RedisKeyPlayerControl = "playerControl" // 玩家幸运值/愤怒值 + RedisKeyWarn = "warn" // 后台预警 + RedisKeyActivityRed = "activityRed" // 红包雨活动 + RedisOnline = "online" // 实时在线 + RedisWaterLevel = "waterLevel" // 水位 + RedisKeyPayWeight = "payWeight" // 支付渠道权重 + RedisKeyWitdhrawWeight = "withrawWeight" // 支付渠道权重 + RedisKeyGamePlay = "gamePlay" // 各游戏玩家游戏局数 + RedisKeyGameResult = "gameResult" // 记录牌局结果,重启时直接拉取恢复 + RedisKeyGameWinner = "gameWinner" // 记录历史牌局大赢家 + RedisKeyCollectCardLottery = "collectCardLottery" // 本次大奖开奖控制 + RedisKeyCollectCardLotteryCards = "collectCardLotteryCards" // 本次大奖结果 + RedisKeyPayStatus = "payStatus" // 支付渠道策略 + RedisKeyRealMail = "realMail" // 给玩家发真实邮件控制 + // RedisKeyPlayerPay = "playerPay" // 玩家10分钟内付费拉起的渠道记录 + RedisKeyPlayerPayInterval = "playerPayInterval" // 玩家拉单间隔限制 + RedisKeyGameCurrency = "gameCurrency" // 玩家进入游戏时选择的币种 +) + +const ( + RedisLockKeyIP = "lockKeyIP" // 玩家操作频率限制 + RedisLockKeyEnter = "lockKeyEnter" // 玩家进游戏锁 + RedisLockKeyControl = "lockKeyControl" // 玩家更新控制参数锁 +) + +func GetRedisKeyGameCurrency(uid int) string { + return fmt.Sprintf("%v:%v", RedisKeyGameCurrency, uid) +} + +func GetRedisKeyPlayerPayInterval(uid int) string { + return fmt.Sprintf("%v:%v", RedisKeyPlayerPayInterval, uid) +} + +// func GetRedisKeyPlayerPay(uid int) string { +// return fmt.Sprintf("%v:%v", RedisKeyPlayerPay, uid) +// } + +// status 发邮件的模板,0是失败邮件,1是成功邮件 +func GetRedisKeyRealMail(uid int, status int) string { + desc := "fail" + if status == 1 { + desc = "success" + } + return fmt.Sprintf("%v:%v:%v", RedisKeyRealMail, desc, uid) +} + +func GetRedisKeyGameWinner(gid int) string { + return fmt.Sprintf("%v:%v", RedisKeyGameWinner, gid) +} + +func GetRedisKeyGameResult(gid, rid int) string { + return fmt.Sprintf("%v:%v:%v", RedisKeyGameResult, gid, rid) +} + +func GetRedisKeyWaterLevel(gid, rid int) string { + return fmt.Sprintf("%v:%v:%v", RedisWaterLevel, gid, rid) +} + +func GetActivityRedKey(uid int) string { + return fmt.Sprintf("%v:%v", RedisKeyActivityRed, uid) +} + +func GetRedisKeyOnlineKey(field string) string { + return fmt.Sprintf("%v:%v", RedisOnline, field) +} + +func GetWarnKey(id int) string { + return fmt.Sprintf("%v:%v", RedisKeyWarn, id) +} + +func GetRedisLockKeyControl(uid int) string { + return fmt.Sprintf("%v:%v", RedisLockKeyControl, uid) +} + +func GetRedisLockKeyEnter(uid int) string { + return fmt.Sprintf("%v:%v", RedisLockKeyEnter, uid) +} + +func GetRedisLockKeyIP(ip string) string { + return fmt.Sprintf("%v:%v", RedisLockKeyIP, ip) +} + +// func GetRedisKeyPlayerControl(uid int) string { +// return fmt.Sprintf("%v:%v", RedisKeyPlayerControl, uid) +// } + +func GetRedisKeyRobotPlaying(rid int) string { + return fmt.Sprintf("%v:%v", RedisKeyRobotPlaying, rid) +} + +func GetRedisKeyCode(content string) string { + return RedisKeyCode + ":" + content +} + +func GetRedisKeyCodeCD(content string) string { + return RedisKeyCodeCD + ":" + content +} + +func GetRedisKeyUser(uid int) string { + return fmt.Sprintf("%s:%d", RedisKeyUser, uid) +} + +func GetRedisKeyToken(token string) string { + return RedisKeyToken + ":" + token +} + +func GetRedisKeyPlayerStatus(uid int) string { + return fmt.Sprintf("%v:%v", RedisKeyPlayerStatus, uid) +} + +const ( + EventTrackDataVersion = "EventTrackDataVersion" // 打点数据version存入redis的key hash +) + +// ==============================================================配置文件 +const ( + RedisKeyConfigRoom = "config:Room" + RedisKeyConfigMillionRobotBet = "config:MillionRobotBet" + RedisKeyConfigRoomChat = "config:RoomChat" + RedisKeyConfigRecharge = "config:Recharge" + RedisKeyConfigRobotConfig = "config:RobotConfig" + RedisKeyConfigRobotPlayConfig = "config:RobotPlayConfig" + RedisKeyConfigTPRobotOperate = "config:TPRobotOperate" + RedisKeyConfigRobotTeenpatti = "config:RobotTeenpatti" + RedisKeyConfigRobotAKTeenpatti = "config:RobotAKTeenpatti" + RedisKeyConfigRobotJTeenpatti = "config:RobotJTeenpatti" + RedisKeyConfigWithdrawAmount = "config:WithdrawAmount" + RedisKeyConfigWithdrawCondition = "config:WithdrawCondition" + RedisKeyConfigActivityConfig = "config:ActivityConfig" + RedisKeyConfigReportActivityConfig = "config:ReportActivityConfig" + RedisKeyConfigRecharge1ActivityConfig = "config:Recharge1ActivityConfig" + RedisKeyConfigGoodsConfig = "config:GoodsConfig" + RedisKeyConfigMatchingTableConfig = "config:MatchingTableConfig" + RedisKeyConfigNoticeConfig = "config:NoticeConfig" + RedisKeyConfigBroadcastConfig = "config:BroadcastConfig" + RedisKeyConfigHelpConfig = "config:HelpConfig" + RedisKeyConfigDramaAction = "config:DramaAction" + RedisKeyConfigDramaDeal = "config:DramaDeal" +) + +func GetRedisKeyTPRobotOperateConfig(gameID, roomID int) string { + return fmt.Sprintf("%v:%v:%v", RedisKeyConfigTPRobotOperate, gameID, roomID) +} + +// ==============================================================后台 +const ( + BackendToken = "backendToken" + BackendReview = "backendReview" +) + +func GetBackendTokenKey(token string) string { + return BackendToken + ":" + token +} + +func GetBackendReviewKey(date string, channel *int) string { + ret := fmt.Sprintf("%v:%v", BackendReview, date) + if channel != nil { + ret += fmt.Sprintf(":%v", *channel) + } + return ret +} diff --git a/common/routers.go b/common/routers.go new file mode 100644 index 0000000..2535926 --- /dev/null +++ b/common/routers.go @@ -0,0 +1,9 @@ +package common + +import ( + "github.com/gin-gonic/gin" +) + +func HealthCheck(c *gin.Context) { + c.String(200, "ok") +} diff --git a/common/share.go b/common/share.go new file mode 100644 index 0000000..072504c --- /dev/null +++ b/common/share.go @@ -0,0 +1,109 @@ +package common + +// 分享配置 +type ConfigShare struct { + ID int `gorm:"primarykey"` + Level int `gorm:"column:level;type:int(11);default:0;comment:等级" web:"level"` + Bet int64 `gorm:"column:bet;type:bigint(20);default:0;comment:档次投注额" web:"bet"` + Per int64 `gorm:"column:per;type:bigint(20);default:0;comment:佣金比例,万分位" web:"per"` +} + +func (c *ConfigShare) TableName() string { + return "config_share" +} + +// 分享配置 +type ConfigShareSys struct { + ID int `gorm:"primarykey"` + WithdrawLimit int64 `gorm:"column:withdraw_limit;type:bigint(20);default:100000000;comment:最低领取数额" web:"withdraw_limit"` + ShareRecharge int64 `gorm:"column:share_recharge;type:bigint(20);default:2000000000;comment:有效玩家充值需求" web:"share_recharge"` + ShareReward int64 `gorm:"column:share_reward;type:bigint(20);default:1000000000;comment:分享有效玩家奖励" web:"share_reward"` + FakeInviteReward int64 `gorm:"column:fake_invite_reward;type:bigint(20);default:1250000000000000;comment:虚拟邀请奖励" web:"fake_invite_reward"` + FakeBetReward int64 `gorm:"column:fake_bet_reward;type:bigint(20);default:980000000000000;comment:虚拟投注奖励" web:"fake_bet_reward"` +} + +func (c *ConfigShareSys) TableName() string { + return "config_share_sys" +} + +// 绑定关系 +type ShareInfo struct { + ID int `gorm:"primarykey"` + UID int `gorm:"column:uid;not null;type:int(11);uniqueIndex:uid"` + Share string `gorm:"column:share;not null;type:varchar(64);uniqueIndex:share;comment:分享码"` + // UP int `gorm:"column:up;not null;type:int(11);comment:上级"` + UP1 int `gorm:"column:up1;type:int(11);default:0;comment:一级"` + UP2 int `gorm:"column:up2;type:int(11);default:0;comment:二级"` + UP3 int `gorm:"column:up3;type:int(11);default:0;comment:三级"` + ChannelID int `gorm:"column:channel_id;type:int(11);default:0;comment:渠道id"` + Invites int `gorm:"column:invites;type:int(11);default:0;comment:邀请人数"` + InvaidInvites int `gorm:"column:invalid_invites;type:int(11);default:0;comment:有效邀请人数"` + InviteReward int64 `gorm:"column:invite_reward;type:bigint(20);default:0;comment:邀请获得的奖励金额"` + BetReward int64 `gorm:"column:bet_reward;type:bigint(20);default:0;comment:邀请人下注获得的金额"` + AvailableReward int64 `gorm:"column:available_reward;type:bigint(20);default:0;comment:可支配佣金"` + Time int64 `gorm:"column:time;type:bigint(20);default:0;comment:加入的时间"` +} + +// 绑定关系 +// type ShareInfo struct { +// ID int `gorm:"primarykey"` +// UID int `gorm:"column:uid;not null;type:int(11);uniqueIndex:uid"` +// Share string `gorm:"column:share;not null;type:varchar(64);uniqueIndex:share;comment:分享码"` +// UP int `gorm:"column:up;not null;type:int(11);comment:上级"` +// Level int `gorm:"column:level;default:1;type:int(11);comment:等级"` +// ChannelID int `gorm:"column:channel_id;type:bigint(20);comment:渠道id"` + +// Bet int64 `gorm:"column:bet;type:bigint(20);default:0;comment:待结算下注额,结算后会清零"` +// TodayBet int64 `gorm:"column:today_bet;type:bigint(20);default:0;comment:今日总下注"` +// TotalBet int64 `gorm:"column:total_bet;type:bigint(20);default:0;comment:总下注"` + +// TodayAgentsBet int64 `gorm:"column:today_agents_bet;type:decimal(38);default:0;comment:团队今日下注"` +// TotalAgentsBet int64 `gorm:"column:total_agents_bet;type:decimal(38);default:0;comment:团队总下注"` + +// TodayAgents int64 `gorm:"column:today_agents;type:bigint(20);default:0;comment:今日下级数量"` +// TotalAgents int64 `gorm:"column:total_agents;type:bigint(20);default:0;comment:总下级数量"` + +// TodayRealAgents int64 `gorm:"column:today_real_agents;type:bigint(20);default:0;comment:今日有效下级数量"` +// TotalRealAgents int64 `gorm:"column:total_real_agents;type:bigint(20);default:0;comment:总有效下级数量"` + +// TodayReward int64 `gorm:"column:today_reward;type:bigint(20);default:0;comment:今日佣金"` +// TotalReward int64 `gorm:"column:total_reward;type:bigint(20);default:0;comment:总佣金"` + +// TodayUpReward int64 `gorm:"column:today_up_reward;type:bigint(20);default:0;comment:今日给上级创造的佣金"` +// AvailableReward int64 `gorm:"column:available_reward;type:bigint(20);default:0;comment:可支配佣金"` +// Time int64 `gorm:"column:time;type:bigint(20);default:0;comment:加入的时间"` +// } + +func (a *ShareInfo) TableName() string { + return "share_info" +} + +type ShareOrder struct { + ID int `gorm:"primarykey"` + UID int `gorm:"column:uid;not null;type:int(11)"` + CreateTime int64 `gorm:"column:create_time;type:bigint(20);comment:创建时间"` + ExamineTime int64 `gorm:"column:examine_time;type:bigint(20);default:0;comment:审核时间"` + OrderID string `gorm:"column:orderid;not null;type:varchar(255);uniqueIndex:orderid;comment:本地ID"` + Amount int64 `gorm:"column:amount;not null;type:bigint(20);comment:单位8位小数"` + ChannelID int `gorm:"column:channel_id;type:bigint(20);comment:渠道id"` + Status int `gorm:"column:status;not null;type:tinyint(4);comment:1新建,2支付完成,3发货完成,4支付失败,5取消"` +} + +func (a *ShareOrder) TableName() string { + return "share_order" +} + +// 机器人 +type ConfigShareRobot struct { + ID int `gorm:"primarykey"` + RobotID int `gorm:"column:robot_id;type:int(11);default:0" web:"robot_id"` + Avatar string `gorm:"column:avatar;type:varchar(255);default:''" web:"avatar"` + Nick string `gorm:"column:nick;type:varchar(255);default:''" web:"nick"` + InitCash int64 `gorm:"column:init_cash;type:bigint(20);default:0;comment:初始金币" web:"init_cash"` + DayCashDown int64 `gorm:"column:day_cash_down;type:bigint(20);default:0;comment:每日增加金币数下限" web:"day_cash_down"` + DayCashUp int64 `gorm:"column:day_cash_up;type:bigint(20);default:0;comment:每日增加金币数上限" web:"day_cash_up"` +} + +func (c *ConfigShareRobot) TableName() string { + return "config_share_robot" +} diff --git a/common/task.go b/common/task.go new file mode 100644 index 0000000..35324e8 --- /dev/null +++ b/common/task.go @@ -0,0 +1,66 @@ +package common + +const ( + TaskTypeZero = iota + TaskTypeRegist // 注册任务 + TaskTypeDownload // 下载app任务 + TaskTypeTotalRecharge // 累充任务 + TaskTypeOnceRecharge // 单次充值任务 + TaskTypeFirstRecharge // 首次充值任务 + TaskTypeAll +) + +// 判读任务的目标是否是次数 +func IsNumTaskType(t int) bool { + return t == TaskTypeRegist || t == TaskTypeDownload +} + +func GetTaskTitle(task *ConfigTask) string { + // switch task.Type { + // case TaskTypeTotalRecharge: + // return fmt.Sprintf(task.Title, task.Target/DecimalDigits) + // case TaskTypeOnceRecharge: + // return fmt.Sprintf(task.Title, task.Target/DecimalDigits) + // } + return task.Title +} + +const ( + TaskKindZero = iota + TaskKindOnce + TaskKindCycle + TaskKindAll +) + +// ConfigTask 任务配置 +// Kind 1单次 2循环 +// Type 1注册 2下载 3累充 4单次充 +type ConfigTask struct { + ID int `gorm:"primary_key;AUTO_INCREMENT;column:id"` + TaskID int `gorm:"column:task_id;type:int(11);default:0;uniqueIndex:task_id;comment:任务id" web:"task_id"` + Target int64 `gorm:"column:target;type:bigint(20);default:0;comment:任务目标" web:"target"` + Reward int64 `gorm:"column:reward;type:bigint(20);default:0;comment:奖励" web:"reward"` + Open int `gorm:"column:open;type:int(11);default:1;comment:开关 1打开" web:"open"` + Kind int `gorm:"column:kind;type:int(11);default:1;comment:1单次 2循环" web:"kind"` + Type int `gorm:"column:type;type:int(11);default:1;comment:1注册 2下载" web:"type"` + Title string `gorm:"column:title;type:varchar(256);default:'';comment:标题" web:"title"` + Icon string `gorm:"column:icon;type:varchar(256);default:'';comment:图标" web:"icon"` + Sort int `gorm:"column:sort;type:int(11);default:0;comment:排序" web:"sort"` + Action int `gorm:"column:action;type:int(11);default:1;comment:跳转类型" web:"action"` +} + +func (c *ConfigTask) TableName() string { + return "config_task" +} + +type TaskData struct { + ID int `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"ID"` + UID int `gorm:"column:uid;not null;type:int(11);uniqueIndex:uid" json:"UID"` + Time int64 `gorm:"column:time;not null;type:bigint(20);default:0;comment:任务生成时间" json:"Time"` + TaskID int `gorm:"column:task_id;type:int(11);default:0;uniqueIndex:uid;comment:任务id" web:"task_id"` + Progress int64 `gorm:"column:progress;type:bigint(20);default:0;comment:进度" web:"progress"` +} + +func (c *TaskData) TableName() string { + return "task_data" +} diff --git a/common/topics.go b/common/topics.go new file mode 100644 index 0000000..7041fda --- /dev/null +++ b/common/topics.go @@ -0,0 +1,7 @@ +package common + +// 内部调用 +const ( + TopicActionMatch = "/actionMatch" + TopicEnterMatch = "/enterMatch" +) diff --git a/config/baseConfig.go b/config/baseConfig.go new file mode 100644 index 0000000..bb5e9b9 --- /dev/null +++ b/config/baseConfig.go @@ -0,0 +1,141 @@ +package config + +import ( + "github.com/BurntSushi/toml" + "github.com/go-redis/redis/v8" +) + +type RedisConfig struct { + Addr string `json:"addr" redis:"addr"` + Name string `json:"name" redis:"name"` + Passwd string `json:"passwd" redis:"passwd"` + TLS bool `json:"tls" redis:"tls"` + Slots []redis.ClusterSlot `json:"slots"` + DB int `json:"db" redis:"db"` + Cluster bool `json:"cluster" redis:"cluster"` +} + +type ESConfig struct { + Urls []string `json:"urls" redis:"urls"` + Sniff bool `json:"sniff" redis:"sniff"` +} + +type MysqlConfig struct { + DSN string `json:"dsn" redis:"dsn"` + Debug bool `json:"debug" redis:"debug"` +} + +type MongoConfig struct { + Uri string `json:"uri" redis:"uri"` + DB string `json:"db" redis:"db"` +} + +type NetConfig struct { + Registry string `json:"registry"` + Nats string `json:"nats"` +} + +type FaceBook struct { + Prefix string `json:"prefix"` + AppKey string `json:"appKey"` + AppScrect string `json:"appSecret"` + AuthURL string `json:"authURL"` +} + +type Google struct { + Prefix string `json:"prefix"` + AuthURL string `json:"authURL"` +} + +type Common struct { + SavePicPath string `json:"savePicPath"` + AvatarURL string `json:"avatarURL"` +} + +type Server struct { + ExamineURL string + GameURL string + IsExamine bool + TelegramChannel string +} + +type Warn struct { + URL string + ID string + Account string + Password string + Sign string + Action string +} + +type Mails struct { + Accounts []string + Pass []string +} + +type AD struct { + FBAPIURL string + KwaiAPIURL string +} + +type BaseConfig struct { + Release bool + ServerFlag string // 服务器编号 b,c,d + FaceBook FaceBook + Net NetConfig + Common Common + Mongo MongoConfig + Google Google + Server Server + Mysql MysqlConfig + ES ESConfig + Redis RedisConfig + Warn Warn + Mails Mails + NewPacks NewPacks + AD AD +} + +// 新版本的渠道号 +type NewPacks struct { + PIDs []int +} + +var base BaseConfig + +func LoadBaseConfig(file string) error { + return LoadToml(file, &base) +} + +func LoadToml(file string, data interface{}) error { + _, err := toml.DecodeFile(file, data) + return err +} + +func GetBase() BaseConfig { + return base +} + +func GetRegistry() string { + return base.Net.Registry +} + +func GetNats() string { + return base.Net.Nats +} + +func GetRedisConfig() RedisConfig { + return base.Redis +} + +func GetMongoConfig() MongoConfig { + return base.Mongo +} + +func GetESConfig() ESConfig { + return base.ES +} + +func GetMysqlConfig() MysqlConfig { + return base.Mysql +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..75f8b0d --- /dev/null +++ b/config/config.go @@ -0,0 +1,146 @@ +package config + +type FacebookParams struct { + Prefix string + AppKey string + AppScrect string + AuthURL string +} + +type GooglePlayParams struct { + Prefix string + AuthURL string +} + +type Configure struct { + BlockPay struct { + Tron struct { + // Addrs []string // 官方地址 + } + } + Pay struct { + CertFile string + KeyFile string + CallbackURL string + Addr string + CheckLimit int // 触发充值权重检测的阀值 + RootChannel []int + PaySuccessWeight int // 成功改变的权重值 + PayFailWeight int // 失败改变的权重值 + BaseSuccess int // 基准成功率 + // Release bool + TLS bool + SelectPayWay bool + PayCheckTime int + WithdrawScanTime int + IGeek struct { + APIURL string + APPID string + MID string + Key string + } + BestPay struct { + APIURL string + } + GrePay struct { + Key string + WithdrawAccount string + MIDAmount int64 + BigChannel string + } + } + Web struct { + OTP struct { + AntgstSmsReqURL string + AntgstAccessKey string + AntgstAccessSecret string + AntgstModel string + AliSmsReqURL string + AliSmsAccount string + AliSmsPass string + AliSmsModel string + BukaUrl string + BukaAppID string + BukaAPIKey string + BukaAPISecret string + BukaModel string + } + Mail struct { + BukaAPIKey string + BukaAPISecret string + BukaEmailUrl string + BukaEmailAppID string + BukaEmailAddr string + BukaTemplateID string + } + Adjust struct { + URL string + APIURL string + APIToken string + } + FB struct { + APIURL string + } + CertFile string + KeyFile string + Addr string + // Release bool + TLS bool + MaxPlayerAccountIP int + MaxBankCardCount int + PassRegion []string + BlackRegion []string + IFSCURL string + TestWithdraw int + TestPay int + FetchTime int + OldChannels []int + FreeSpinFirst int64 + TotalWithdrawPer int64 // 整体总赠送比例限制 + BreakLimit int64 // 低于该值弹出破产礼包 + SelectID int + } + Hall struct { + FacebookParams FacebookParams + GooglePlayParams GooglePlayParams + AvatarURL string + SavePath string + } + Gate struct { + WSAddr string + WSPort string + CertFile string + KeyFile string + HeartBeat int + BufSize int + TLS bool + } + Backend struct { + Addr string + DB string + } + Rummy struct { + SettleTime int + } + Teenpatti struct { + OperateTime int + SettleTime int + } + Customer struct { + Addr string + DB string + } + MaxPlayerCount int64 + WorkID int + Version int +} + +func GetConfig() *Configure { + return &cfg +} + +var cfg Configure + +func LoadConfig(cfgFile string) error { + return LoadToml(cfgFile, &cfg) +} diff --git a/config/generated.go b/config/generated.go new file mode 100644 index 0000000..a88950a --- /dev/null +++ b/config/generated.go @@ -0,0 +1,85 @@ +package config + +import ( + "io" + "io/ioutil" + "os" + "server/util" + "strconv" + "strings" +) + +var ( + luas = []string{"cn", "en"} // 支持的语言版本 +) + +func BuildErrCode() { + f, err := ioutil.ReadFile(".." + string(os.PathSeparator) + "pb" + string(os.PathSeparator) + "proto" + string(os.PathSeparator) + "common.proto") + if err != nil { + panic("read fail") + } + proto := string(f) + eIndexS := strings.Index(proto, "ErrNo") + tmp := proto[eIndexS:] + eIndexS2 := strings.Index(tmp, "{") + tmp = tmp[eIndexS2:] + eIndexE := strings.Index(tmp, "}") + target := tmp[:eIndexE+1] + lines := strings.Split(target, "\n") + + var filename = "errcode.toml" + if util.CheckFileIsExist(filename) { //如果文件存在 + os.Remove(filename) + } + file, err := os.Create(filename) //创建文件 + if err != nil { + return + } + // file, err1 = os.OpenFile(filename, os.O_APPEND, 0666) //打开文件 + errNums := []string{} + errDes := make([][]string, len(luas)) + for _, v := range lines { + indexS := strings.Index(v, "=") + indexE := strings.Index(v, ";") + dIndexS := strings.Index(v, "//") + if indexS < 0 || indexE < 0 || dIndexS < 0 { + continue + } + numS := v[indexS+1 : indexE] + num := strings.Trim(numS, " ") + if tmp, err := strconv.Atoi(num); err != nil { + return + } else if tmp == 0 || tmp == 200 { + continue + } + errNums = append(errNums, num) + + if !strings.Contains(v[dIndexS+2:], ";") { + panic("config err") + } + + allDes := strings.Split(v[dIndexS+2:], ";") + for i := range allDes { + if i >= len(errDes) { + panic("config err") + } + errDes[i] = append(errDes[i], allDes[i]) + } + } + for i, v := range luas { + if _, err := io.WriteString(file, "["+v+"]"+"\n"); err != nil { + panic("config err") + } + for j, v := range errDes[i] { + des1 := strings.Trim(v, " ") + des2 := strings.Trim(des1, string(byte(13))) + writeS := errNums[j] + " =" + " " + `"` + des2 + `"` + "\n" + if j == len(errDes[i])-1 { + writeS += "\n" + } + if _, err := io.WriteString(file, writeS); err != nil { + panic("config err") + } + } + } +} diff --git a/db/es/es.go b/db/es/es.go new file mode 100644 index 0000000..6c33a43 --- /dev/null +++ b/db/es/es.go @@ -0,0 +1,40 @@ +package es + +import ( + "context" + + "github.com/olivere/elastic/v7" +) + +// EsClient es连接对象 +type EsClient struct { + client *elastic.Client + running bool + enable bool +} + +func InitEsClient(urls []string, sniff bool) (*EsClient, error) { + ES := &EsClient{} + + client, err := elastic.NewClient(elastic.SetURL(urls...), elastic.SetSniff(sniff)) + if err != nil { + return nil, err + } + + _, _, err = client.Ping(urls[0]).Timeout("1s").Do(context.TODO()) + if err != nil { + return nil, err + } + + ES.client = client + ES.running = true + ES.enable = true + + InitBulkQueue(client) + + return ES, nil +} + +func (ES *EsClient) UnInit() { + ES.running = false +} diff --git a/db/es/es_balance.go b/db/es/es_balance.go new file mode 100644 index 0000000..af9f648 --- /dev/null +++ b/db/es/es_balance.go @@ -0,0 +1,137 @@ +package es + +import ( + "server/common" + "strconv" + + "github.com/liangdas/mqant/log" + "github.com/olivere/elastic/v7" +) + +// QueryPlayerBalance 查询玩家牌局流水记录 +func (ES *EsClient) QueryPlayerBalance(uid, page, num, event int, start, end *int64, ret *[]common.CurrencyBalance, gameID ...int) (int64, error) { + q := elastic.NewBoolQuery() + q.Must(elastic.NewMatchQuery("uid", uid)) + q.Must(elastic.NewMatchQuery("event", event)) + if start != nil { + q.Filter(elastic.NewRangeQuery("time").Gte(*start)) + } + if end != nil { + q.Filter(elastic.NewRangeQuery("time").Lt(*end)) + } + // querys := []*elastic.BoolQuery{} + if gameID != nil { + q.Must(elastic.NewTermsQuery("desc.keyword", gameID[0])) + } + // for _, v := range gameID { + // // querys = append(querys, q.Should(elastic.NewMatchQuery("desc.keyword", v))) + // // q.Must(q.Should(elastic.NewMatchQuery("desc.keyword", v))) + // log.Debug("gameID:%v", v) + // q.Must(elastic.NewMatchQuery("desc.keyword", v)) + // } + // if len(querys) > 0 { + // q.Must(querys...) + // } + count, err := ES.QueryList(common.ESIndexBalance, page, num, q, ret, "time", false) + if err != nil { + log.Error("err:%v", err) + return 0, err + } + return count, err +} + +// QueryPlayerAllBalance 查询玩家所有流水记录 +func (ES *EsClient) QueryPlayerAllBalance(uid *int, page, num int, start, end *int64, ret *[]common.CurrencyBalance) (int64, error) { + q := elastic.NewBoolQuery() + if uid != nil { + q.Must(elastic.NewMatchQuery("uid", uid)) + } + if start != nil { + q.Filter(elastic.NewRangeQuery("time").Gte(*start)) + } + if end != nil { + q.Filter(elastic.NewRangeQuery("time").Lt(*end)) + } + + count, err := ES.QueryList(common.ESIndexBalance, page, num, q, ret, "time", false) + if err != nil { + log.Error("err:%v", err) + return 0, err + } + return count, err +} + +// QueryPlayerBalance 查询玩家控杀流水记录 +func (ES *EsClient) QueryPlayerControlBalance(uid, page, num int, start, end *int64, ret *[]common.CurrencyBalance, controlType *int, gameID ...int) (int64, error) { + q := elastic.NewBoolQuery() + q.Must(elastic.NewMatchQuery("uid", uid)) + q.Must(elastic.NewMatchQuery("event", common.CurrencyEventGameSettle)) + q.Filter(elastic.NewRangeQuery("control_type").Gt(0)) + if start != nil { + q.Filter(elastic.NewRangeQuery("time").Gte(*start)) + } + if end != nil { + q.Filter(elastic.NewRangeQuery("time").Lt(*end)) + } + if gameID != nil { + q.Must(elastic.NewTermsQuery("desc.keyword", gameID[0])) + } + + if controlType != nil { + q.Must(elastic.NewTermsQuery("control_type", *controlType)) + } + + count, err := ES.QueryList(common.ESIndexBalance, page, num, q, ret, "time", false) + if err != nil { + log.Error("err:%v", err) + return 0, err + } + return count, err +} + +// 通过流水表查询盈亏 +func (ES *EsClient) GetProfit(start, end *int64, channel *int, gameId *int, roomId *int, event *int) int64 { + q := NewQt(start, end, nil, channel) + + if event != nil { + q.Must(elastic.NewMatchQuery("event", *event)) + } + + if gameId != nil { + q.Must(elastic.NewMatchQuery("desc.keyword", strconv.Itoa(*gameId))) + } + + if roomId != nil { + q.Must(elastic.NewMatchQuery("room_name.keyword", strconv.Itoa(*roomId))) + } + million, err := ES.SumBy(common.ESIndexBalance, "value", q) + if err != nil { + log.Error("查询盈亏失败, error : [%s]", err.Error()) + return 0 + } + return -int64(million) +} + +func NewQt(start, end *int64, channel ...*int) *elastic.BoolQuery { + q := elastic.NewBoolQuery() + if start != nil { + q.Filter(elastic.NewRangeQuery("time").Gte(*start)) + } + if end != nil { + q.Filter(elastic.NewRangeQuery("time").Lt(*end)) + } + if len(channel) > 0 { + var valueArr []interface{} + for _, v := range channel { + if v == nil { + continue + } + valueArr = append(valueArr, *v) + } + if len(valueArr) > 0 { + q.Must(elastic.NewTermsQuery("ChannelID", valueArr...)) + } + } + + return q +} diff --git a/db/es/es_bulk.go b/db/es/es_bulk.go new file mode 100644 index 0000000..e4a4581 --- /dev/null +++ b/db/es/es_bulk.go @@ -0,0 +1,60 @@ +package es + +import ( + "context" + "server/util" + "time" + + "github.com/liangdas/mqant/log" + "github.com/olivere/elastic/v7" +) + +var ( + Bulk *elastic.BulkService + WriteChan = make(chan *WriteData, 10000) +) + +type WriteData struct { + ID string + Index string + Data interface{} +} + +func InitBulkQueue(es *elastic.Client) { + Bulk = es.Bulk() + util.Go(func() { + t := time.NewTimer(time.Second) + for { + select { + case <-t.C: + t.Reset(time.Second) + if Bulk.EstimatedSizeInBytes() > 0 { + bulk := Bulk + // util.IndexTry(func() error { + util.Go(func() { + _, err := bulk.Do(context.Background()) + if err != nil { + log.Error("err:%v", err) + // return err + } + }) + // return nil + // }) + Bulk = es.Bulk() + } + case one := <-WriteChan: + e := elastic.NewBulkCreateRequest().Index(one.Index) + if one.ID != "" { + e.Id(one.ID) + } + e.Doc(one.Data) + Bulk.Add(e) + } + // fmt.Println(total) + } + }) +} + +func AddBulk(w *WriteData) { + WriteChan <- w +} diff --git a/db/es/es_exec.go b/db/es/es_exec.go new file mode 100644 index 0000000..b915988 --- /dev/null +++ b/db/es/es_exec.go @@ -0,0 +1,601 @@ +package es + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "math/rand" + "reflect" + "server/common" + "server/util" + "time" + + "github.com/liangdas/mqant/log" + "github.com/olivere/elastic/v7" +) + +// 向Es数据库中新增数据 index 数据表名 params 一定要是json数据结构 +func (ES *EsClient) InsertToES(index string, params interface{}) error { + _, err := ES.client.Index().Index(index).BodyJson(params).Do(context.Background()) + if err != nil { + log.Error("index:%v,write:%v,err:%v", index, params, err) + return err + } + return nil +} + +// 向Es数据库中新增数据 指数退避写入 +func (ES *EsClient) InsertToESGO(index string, params interface{}) { + // util.IndexTry(func() error { + // _, err := ES.client.Index().Index(index).BodyJson(params).Do(context.Background()) + // if err != nil { + // log.Error("index:%v,write:%v,err:%v", index, params, err) + // return err + // } + // return nil + // }) + util.Go(func() { + AddBulk(&WriteData{Index: index, Data: params}) + }) +} + +// 向Es数据库中新增数据 index 数据表名 params 一定要是json数据结构 +func (ES *EsClient) InsertToESByID(index, id string, params interface{}) error { + _, err := ES.client.Index().Index(index).Id(id).BodyJson(params).Do(context.Background()) + if err != nil { + log.Error("index:%v,write:%v,err:%v", index, params, err) + return err + } + return nil +} + +// 向Es数据库中新增数据 指数退避写入 +func (ES *EsClient) InsertToESByIDGO(index, id string, params interface{}) { + // util.IndexTry(func() error { + // _, err := ES.client.Index().Index(index).Id(id).BodyJson(params).Do(context.Background()) + // if err != nil { + // log.Error("index:%v,write:%v,err:%v", index, params, err) + // return err + // } + // return nil + // }) + util.Go(func() { + AddBulk(&WriteData{ID: id, Index: index, Data: params}) + }) +} + +func (ES *EsClient) Count(index string, q elastic.Query) int64 { + count, err := ES.client.Count(index).Query(q).Do(context.Background()) + if err != nil { + return 0 + } + return count +} + +// CountCard 统计数量,按提供字段去重 +func (ES *EsClient) CountCard(index, field string, q elastic.Query) int64 { + result, err := ES.C().Search().Index(index).Query(q).Aggregation("Total", elastic.NewCardinalityAggregation().Field(field)).Size(0).Do(context.Background()) + if err != nil { + log.Error("err:%v", err) + return 0 + } + ret := map[string]int64{} + err = json.Unmarshal(result.Aggregations["Total"], &ret) + if err != nil { + log.Error("err:%v", err) + return 0 + } + return ret["value"] +} + +// Update 更新文档,val必须是map|||struct||es script +func (ES *EsClient) Update(index, id string, val interface{}) (res *elastic.UpdateResponse, err error) { + switch t := val.(type) { + case *elastic.Script: + res, err = ES.client.Update().Index(index).Id(id).Script(t).Do(context.Background()) + return + default: // struct or map + res, err = ES.client.Update().Index(index).Id(id).Doc(val).Do(context.Background()) + return + } +} + +func (ES *EsClient) DeleteByID(index string, id string) error { + _, err := ES.client.Delete().Index(index).Id(id).Do(context.Background()) + return err +} + +func (ES EsClient) DeleteByQuery(indices string, q elastic.Query) (err error) { + _, err = ES.client.DeleteByQuery(indices).Query(q).WaitForCompletion(false).Do(context.Background()) + if err != nil { + log.Error("err:%v", err) + } + return +} + +func (ES *EsClient) DeleteIndex(index ...string) (*elastic.IndicesDeleteResponse, error) { + return ES.client.DeleteIndex(index...).Do(context.Background()) +} + +func (ES *EsClient) C() *elastic.Client { + return ES.client +} + +// 查询一组数据 index表名,page页码,num一页数量,q查询语句,kind取值的类型(必须是指针,其中包含的ID会有特殊含义,与文档中的_id同步),sort排序(false为逆序) +func (ES *EsClient) QueryList(index string, page, num int, q elastic.Query, kind interface{}, sort ...interface{}) (int64, error) { + from := page * num + if from < 0 || num == 0 { + return 0, errors.New("invalid page or num") + } + oneType := reflect.TypeOf(kind).Kind() + if oneType != reflect.Ptr { + return 0, errors.New("invalid kind") + } + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + searchRes := new(elastic.SearchResult) + var err error + if len(sort) > 0 { + next := ES.client.Search(index).Query(q) + for i := 0; i < len(sort)-1; i += 2 { + field, ok := sort[i].(string) + if !ok { + log.Error("invalid sort:%v", sort...) + return 0, errors.New("invalid sort") + } + seq, ok := sort[i+1].(bool) + if !ok { + log.Error("invalid sort:%v", sort...) + return 0, errors.New("invalid sort") + } + next.Sort(field, seq) + } + searchRes, err = next.From(from).Size(num).Do(ctx) + } else { + searchRes, err = ES.client.Search(index).Query(q).From(from).Size(num).Do(ctx) + } + // ret := []interface{}{} + if elastic.IsNotFound(err) { + return 0, nil + } + if err != nil { + log.Error("search error:%v", err) + return 0, err + } + newArr := make([]reflect.Value, 0) + val := reflect.ValueOf(kind) + for _, v := range searchRes.Hits.Hits { + t := reflect.TypeOf(kind) + tee := reflect.New(t.Elem().Elem()) + ti := tee.Interface() + err := json.Unmarshal(v.Source, ti) + if err != nil { + return 0, err + } + e := reflect.ValueOf(ti).Elem() + if e.Kind() == reflect.Struct { + if id := e.FieldByName("ID"); id.Kind() == reflect.String { + id.SetString(v.Id) + } + } else if e.Kind() == reflect.Ptr { + if e.Elem().Kind() == reflect.Struct { + if id := e.Elem().FieldByName("ID"); id.Kind() == reflect.String { + id.SetString(v.Id) + } + } + } + newArr = append(newArr, reflect.ValueOf(ti).Elem()) + } + val.Elem().Set(reflect.Append(val.Elem(), newArr...)) + count := ES.Count(index, q) + return count, nil +} + +// 查询一组数据 index表名,after上次查询最后一个数据,num一页数量,q查询语句,kind取值的类型(必须是指针,其中包含的ID会有特殊含义,与文档中的_id同步),sort排序(false为逆序) +func (ES *EsClient) SearchAfter(index string, after interface{}, num int, q elastic.Query, kind interface{}, seqSort bool) (int64, error) { + oneType := reflect.TypeOf(kind).Kind() + if oneType != reflect.Ptr { + return 0, errors.New("invalid kind") + } + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + searchRes := new(elastic.SearchResult) + var err error + next := ES.client.Search(index).Query(q).Sort("_seq_no", seqSort) + // for i := 0; i < len(sort)-1; i += 2 { + // field, ok := sort[i].(string) + // if !ok { + // log.Error("invalid sort:%v", sort...) + // return 0, errors.New("invalid sort") + // } + // seq, ok := sort[i+1].(bool) + // if !ok { + // log.Error("invalid sort:%v", sort...) + // return 0, errors.New("invalid sort") + // } + // next.Sort(field, seq) + // } + if after != nil { + searchRes, err = next.Size(num).SearchAfter(after).SeqNoPrimaryTerm(true).Do(ctx) + } else { + searchRes, err = next.Size(num).SeqNoPrimaryTerm(true).Do(ctx) + } + // ret := []interface{}{} + if elastic.IsNotFound(err) { + return 0, nil + } + if err != nil { + log.Error("search error:%v", err) + return 0, err + } + newArr := make([]reflect.Value, 0) + val := reflect.ValueOf(kind) + var resAfter int64 + for i, v := range searchRes.Hits.Hits { + t := reflect.TypeOf(kind) + tee := reflect.New(t.Elem().Elem()) + ti := tee.Interface() + err := json.Unmarshal(v.Source, ti) + if err != nil { + return 0, err + } + e := reflect.ValueOf(ti).Elem() + if e.Kind() == reflect.Struct { + if id := e.FieldByName("ID"); id.Kind() == reflect.String { + id.SetString(v.Id) + } + } else if e.Kind() == reflect.Ptr { + if e.Elem().Kind() == reflect.Struct { + if id := e.Elem().FieldByName("ID"); id.Kind() == reflect.String { + id.SetString(v.Id) + } + } + } + fmt.Println(*v.SeqNo) + newArr = append(newArr, reflect.ValueOf(ti).Elem()) + if i == len(searchRes.Hits.Hits)-1 { + resAfter = *v.SeqNo + } + } + val.Elem().Set(reflect.Append(val.Elem(), newArr...)) + return resAfter, nil +} + +func (ES *EsClient) UpdateByScript(index string, q elastic.Query, script string) (updated int64, err error) { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + var res *elastic.BulkIndexByScrollResponse + res, err = ES.client.UpdateByQuery(index).Query(q).Script(elastic.NewScriptInline(script)).Do(ctx) + if err != nil { + log.Error("err:%v", err) + return + } + updated = res.Updated + return +} + +// QueryOne 查询单条数据,kind取值的类型(必须是指针,其中包含的ID会有特殊含义,与文档中的_id同步),sort排序(false为逆序) +func (ES *EsClient) QueryOne(index string, q elastic.Query, kind interface{}, sort ...interface{}) error { + // ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + // defer cancel() + k := reflect.ValueOf(kind) + oneType := k.Kind() + if oneType != reflect.Ptr { + return errors.New("invalid kind") + } + s := ES.client.Search(index).Query(q) + ret := new(elastic.SearchResult) + var err error + if sort != nil { + ret, err = s.Sort(sort[0].(string), sort[1].(bool)).Size(1).Do(context.Background()) + } else { + ret, err = s.Size(1).Do(context.Background()) + } + if err != nil && !elastic.IsNotFound(err) { + // if !elastic.IsNotFound(err) { + log.Error("err:%v", err) + // } + return err + } + if ret == nil || len(ret.Hits.Hits) == 0 { + return nil + } + // randomIdx := rand.Intn(len(ret.Hits.Hits)) + // hit := ret.Hits.Hits[randomIdx] + for _, v := range ret.Hits.Hits { + // log.Debug("source:%v", v) + err = json.Unmarshal(v.Source, kind) + if err != nil { + log.Error("err:%v,source:%v", err, string(v.Source)) + return err + } + if k.Elem().Kind() == reflect.Struct { + // log.Debug("set:%v", v) + if id := k.Elem().FieldByName("ID"); id.Kind() == reflect.String { + id.SetString(v.Id) + } + } + break + } + return nil +} + +// QueryOne 查询单条数据,kind取值的类型(必须是指针,其中包含的ID会有特殊含义,与文档中的_id同步),sort排序(false为逆序) +func (ES *EsClient) QueryOneRandom(index string, q elastic.Query, kind interface{}, sort ...interface{}) error { + // ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + // defer cancel() + k := reflect.ValueOf(kind) + oneType := k.Kind() + if oneType != reflect.Ptr { + return errors.New("invalid kind") + } + s := ES.client.Search(index).Query(q) + ret := new(elastic.SearchResult) + var err error + if sort != nil { + ret, err = s.Sort(sort[0].(string), sort[1].(bool)).Do(context.Background()) + } else { + ret, err = s.Do(context.Background()) + } + if err != nil { + if !elastic.IsNotFound(err) { + log.Error("err:%v", err) + } + return err + } + if len(ret.Hits.Hits) == 0 { + return nil + } + randomIdx := rand.Intn(len(ret.Hits.Hits)) + hit := ret.Hits.Hits[randomIdx] + err = json.Unmarshal(hit.Source, kind) + if err != nil { + log.Error("source:%v", string(hit.Source)) + return err + } + if k.Elem().Kind() == reflect.Struct { + // log.Debug("set:%v", v) + if id := k.Elem().FieldByName("ID"); id.Kind() == reflect.String { + id.SetString(hit.Id) + } + } + return nil +} + +// Upsert 没找到插入,找到则执行更新 +func (ES *EsClient) Upsert(index, id string, val interface{}, uVal ...interface{}) (ret *elastic.UpdateResponse, err error) { + ctx := context.Background() + var s *elastic.UpdateService + switch t := val.(type) { + case *elastic.Script: + s = ES.client.Update().Index(index).Id(id).Script(t) + default: // struct or map + s = ES.client.Update().Index(index).Id(id).Doc(val) + } + if uVal == nil { + ret, err = s.Do(ctx) + return + } + // 是否强制执行更新脚本 + if len(uVal) > 1 { + ret, err = s.Upsert(uVal[0]).ScriptedUpsert(uVal[1].(bool)).Do(ctx) + } else { + ret, err = s.Upsert(uVal[0]).Do(ctx) + } + return +} + +// IncrBy 根据字段叠加 +func (ES *EsClient) IncrBy(index, id, field string, val interface{}, uVal interface{}) (ret *elastic.UpdateResponse, err error) { + ctx := context.Background() + s := ES.client.Update().Index(index).Id(id).Script(elastic.NewScript(fmt.Sprintf("if(ctx._source.%v==null){ctx._source.%v=%v}else{ctx._source.%v+=%v}", field, field, val, field, val))) + if uVal == nil { + ret, err = s.Do(ctx) + return + } + ret, err = s.Upsert(uVal).ScriptedUpsert(true).Do(ctx) + return +} + +func (ES *EsClient) Refresh(index string) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + _, err := ES.client.Refresh(index).Do(ctx) + if err != nil && !elastic.IsNotFound(err) { + log.Error("refresh err:%v", err) + } +} + +// GroupBy 聚合查询数量 +// size 不设置默认为10 +func (ES *EsClient) GroupBy(index, field string, q elastic.Query, size int) (*common.GroupBuckets, error) { + query := ES.C().Search().Index(index).Query(q) + if size > 0 { + query.Aggregation("total", elastic.NewTermsAggregation().Field(field).Size(size)) + } else { + query.Aggregation("total", elastic.NewTermsAggregation().Field(field)) + } + result, err := query.Do(context.Background()) + if err != nil { + log.Error("err:%v", err) + return nil, err + } + res := new(common.GroupBuckets) + if err := json.Unmarshal(result.Aggregations["total"], &res); err != nil { + log.Error("err:%v", err) + return nil, err + } + return res, nil +} + +// GroupBySub 聚合查询数量 +// size 不设置默认为10 +func (ES *EsClient) GroupBySub(index, field, sub string, q elastic.Query, size int) (*common.Group2CardBuckets, error) { + query := ES.C().Search().Index(index).Query(q).Aggregation("total", elastic.NewTermsAggregation().Field(field).SubAggregation("sub1", elastic.NewTermsAggregation().Field(sub))) + if size > 0 { + query.Size(size) + } + result, err := query.Do(context.Background()) + if err != nil { + log.Error("err:%v", err) + return nil, err + } + res := new(common.Group2CardBuckets) + if err := json.Unmarshal(result.Aggregations["total"], &res); err != nil { + log.Error("err:%v", err) + return nil, err + } + return res, nil +} + +// GroupByCard 聚合查询数量,包含子查询card +// size 不设置默认为10 +func (ES *EsClient) GroupByCard(index, field, sub string, q elastic.Query, size int) (*common.GroupCardBuckets, error) { + query := ES.C().Search().Index(index).Query(q) + if size > 0 { + query.Aggregation("total", elastic.NewTermsAggregation().Field(field).SubAggregation("sub", elastic.NewCardinalityAggregation().Field(sub)).Size(size)) + } else { + query.Aggregation("total", elastic.NewTermsAggregation().Field(field).SubAggregation("sub", elastic.NewCardinalityAggregation().Field(sub))) + } + result, err := query.Do(context.Background()) + if err != nil { + log.Error("err:%v", err) + return nil, err + } + res := new(common.GroupCardBuckets) + if err := json.Unmarshal(result.Aggregations["total"], &res); err != nil { + log.Error("err:%v", err) + return nil, err + } + return res, nil +} + +// GroupBy2Card 聚合查询数量,包含双重子查询card +// size 不设置默认为10 +func (ES *EsClient) GroupBy2Card(index, field, sub1, sub2 string, q elastic.Query, size int) (*common.Group2CardBuckets, error) { + query := ES.C().Search().Index(index).Query(q).Aggregation("total", elastic.NewTermsAggregation().Field(field).SubAggregation("sub1", elastic.NewTermsAggregation().Field(sub1).SubAggregation("sub2", elastic.NewCardinalityAggregation().Field(sub2)))) + if size > 0 { + query.Size(size) + } + result, err := query.Do(context.Background()) + if err != nil { + log.Error("err:%v", err) + return nil, err + } + res := new(common.Group2CardBuckets) + if err := json.Unmarshal(result.Aggregations["total"], &res); err != nil { + log.Error("err:%v", err) + return nil, err + } + return res, nil +} + +// GroupBy 聚合查询数量 field聚合的字段 sumField求和的字段 +func (ES *EsClient) GroupSumBy(index, field string, q elastic.Query, ret interface{}, order string, or bool, size int, sumField ...string) error { + agg := elastic.NewTermsAggregation().Field(field) + for _, v := range sumField { + agg.SubAggregation(v, elastic.NewSumAggregation().Field(v)) + } + if order != "" { + agg.OrderByAggregation(order, or) + } + agg.SubAggregation("Top", elastic.NewTopHitsAggregation().From(0).Size(1)) + if size == 0 { + size = 5000 + } + agg.Size(size) + result, err := ES.C().Search().Index(index).Query(q).Aggregation("total", agg).Do(context.Background()) + if err != nil { + log.Error("err:%v", err) + return err + } + fmt.Println(string(result.Aggregations["total"])) + if err := json.Unmarshal(result.Aggregations["total"], &ret); err != nil { + log.Error("err:%v", err) + return err + } + return nil +} + +// Group2SumBy 聚合查询数量 field聚合的字段 sub2求和的字段 +func (ES *EsClient) Group2SumBy(index, field, sub1, sub2 string, q elastic.Query, order string, or bool, size int) (*common.Group2SumBuckets, error) { + agg := elastic.NewTermsAggregation().Field(field).SubAggregation("sub1", elastic.NewTermsAggregation().Field(sub1).SubAggregation("sub2", elastic.NewSumAggregation().Field(sub2))) + if order != "" { + agg.OrderByAggregation(order, or) + } + // agg.SubAggregation("Top", elastic.NewTopHitsAggregation().From(0).Size(1)) + if size == 0 { + size = 5000 + } + agg.Size(size) + ret := new(common.Group2SumBuckets) + result, err := ES.C().Search().Index(index).Query(q).Aggregation("total", agg).Do(context.Background()) + if err != nil { + log.Error("err:%v", err) + return nil, err + } + if err := json.Unmarshal(result.Aggregations["total"], &ret); err != nil { + log.Error("err:%v", err) + return nil, err + } + return ret, nil +} + +// SumBy 聚合求和数量 +func (ES *EsClient) SumBy(index, field string, q elastic.Query) (float64, error) { + result, err := ES.C().Search().Index(index).Query(q).Aggregation("total", elastic.NewSumAggregation().Field(field)).Do(context.Background()) + if err != nil { + log.Error("err:%v", err) + return 0, err + } + // fmt.Println(string(result.Aggregations["total"])) + res := new(common.SumByResult) + if err := json.Unmarshal(result.Aggregations["total"], &res); err != nil { + log.Error("err:%v", err) + return res.Value, err + } + return res.Value, nil +} + +// SumByInt64 聚合求和数量 +func (ES *EsClient) SumByInt64(index, field string, q elastic.Query) int64 { + result, err := ES.C().Search().Index(index).Query(q).Aggregation("total", elastic.NewSumAggregation().Field(field)).Size(0).Do(context.Background()) + if err != nil { + log.Error("err:%v", err) + return 0 + } + // fmt.Println(string(result.Aggregations["total"])) + res := new(common.SumByResult) + if err := json.Unmarshal(result.Aggregations["total"], &res); err != nil { + log.Error("err:%v", err) + return int64(res.Value) + } + return int64(res.Value) +} + +// AVG 聚合求平均值 +func (ES *EsClient) AVG(index, field string, q elastic.Query) (float64, error) { + result, err := ES.C().Search().Index(index).Query(q).Aggregation("total", elastic.NewAvgAggregation().Field(field)).Do(context.Background()) + if err != nil { + log.Error("err:%v", err) + return 0, err + } + // fmt.Println(string(result.Aggregations["total"])) + res := new(common.SumByResult) + if err := json.Unmarshal(result.Aggregations["total"], &res); err != nil { + log.Error("err:%v", err) + return res.Value, err + } + return res.Value, nil +} + +// Exist 判断是否存在 +func (ES *EsClient) Exist(index, id string) bool { + ret, err := ES.C().Exists().Index(index).Id(id).Do(context.Background()) + if err != nil { + log.Error("err:%v", err) + return false + } + return ret +} diff --git a/db/es/es_game.go b/db/es/es_game.go new file mode 100644 index 0000000..97ed3f6 --- /dev/null +++ b/db/es/es_game.go @@ -0,0 +1,62 @@ +package es + +// UpsertMatchData 刷新玩家战绩 +// func (ES *EsClient) UpsertMatchData(uid int, game string, one *common.OneGameData, md *common.PlayerMatchData) error { +// one.LastRecord = time.Now().Unix() +// if md.UID == 0 { // 记录不存在 +// one.DayMatch = 1 +// one.WeekMatch = 1 +// one.MonthMatch = 1 +// if one.WinMatch > 0 { +// one.DayWin = 1 +// } +// field := reflect.ValueOf(md).Elem().FieldByName(game) +// if field.CanSet() { +// field.Set(reflect.ValueOf(one).Elem()) +// } +// md.UID = uid +// return ES.InsertToESByID(common.ESIndexMatchData, strconv.Itoa(uid), md) +// } +// field := reflect.ValueOf(md).Elem().FieldByName(game) +// if !field.IsValid() { +// return errors.New("unknow game") +// } +// mdo := field.Interface().(common.OneGameData) +// if mdo.BestWin < one.BestWin { +// mdo.BestWin = one.BestWin +// } +// mdo.TotalMatch += one.TotalMatch +// mdo.WinMatch += one.WinMatch +// now := time.Now().Unix() +// if util.IsSameDayTimeStamp(mdo.LastRecord, now) { +// mdo.DayMatch += 1 +// mdo.DayMatchTime += one.DayMatchTime +// if one.WinMatch > 0 { +// mdo.DayMatch += 1 +// } +// } else { +// mdo.DayMatch = 1 +// mdo.DayMatchTime = one.DayMatchTime +// if one.WinMatch > 0 { +// mdo.DayMatch = 1 +// } +// } +// if util.IsSameWeek(mdo.LastRecord, now) { +// mdo.WeekMatch += 1 +// } else { +// mdo.WeekMatch = 1 +// } +// if util.IsSameMonth(mdo.LastRecord, now) { +// mdo.MonthMatch += 1 +// } else { +// mdo.MonthMatch = 1 +// } +// mdo.LastRecord = now +// field.Set(reflect.ValueOf(mdo)) +// _, err := ES.Update(common.ESIndexMatchData, strconv.Itoa(uid), md) +// if err != nil { +// log.Error("err:%v", err) +// return err +// } +// return nil +// } diff --git a/db/es/es_mail.go b/db/es/es_mail.go new file mode 100644 index 0000000..a16119e --- /dev/null +++ b/db/es/es_mail.go @@ -0,0 +1,116 @@ +package es + +import ( + "server/common" + + "github.com/liangdas/mqant/log" + "github.com/olivere/elastic/v7" +) + +// QueryMailList 查询玩家邮件列表 +// func (ES *EsClient) QueryMailList(uid, page, num int, list *[]common.Mail) (count int64, err error) { +// q := elastic.NewBoolQuery() +// q.Must(elastic.NewTermQuery("Receiver", uid)) +// q.Filter(elastic.NewRangeQuery("Time").Gt(time.Now().Unix()-common.MailExpireTime), elastic.NewRangeQuery("Time").Lte(time.Now().Unix()), +// elastic.NewRangeQuery("Status").Gt(common.MailStatusDelete)) +// count, err = ES.QueryList(common.ESIndexMail, page, num, q, list, "Status", true, "Type", true, "Time", false) +// if err != nil { +// log.Error("err:%v", err) +// return +// } +// if count > common.MailMaxCount { +// count = common.MailMaxCount +// } +// return +// } + +// // QueryNewMailCount 查询玩家未读邮件个数 +// func (ES *EsClient) QueryNewMailCount(uid int) int64 { +// q := elastic.NewBoolQuery() +// q.Must(elastic.NewTermQuery("Receiver", uid), elastic.NewTermQuery("Status", common.MailStatusNew)) +// q.Filter(elastic.NewRangeQuery("Time").Gt(time.Now().Unix()-common.MailExpireTime), elastic.NewRangeQuery("Time").Lte(time.Now().Unix())) +// return ES.Count(common.ESIndexMail, q) +// } + +// // ReadMail 查看一封邮件 +// func (ES *EsClient) ReadMail(id string) *common.Mail { +// one := new(common.Mail) +// if err := ES.QueryOne(common.ESIndexMail, elastic.NewTermQuery("_id", id), one); err != nil { +// log.Error("err:%v", err) +// return nil +// } +// if one.Status == common.MailStatusNew { +// q := elastic.NewBoolQuery() +// q.Must(elastic.NewTermQuery("_id", id), elastic.NewTermQuery("Status", common.MailStatusNew)) +// ES.UpdateByScript(common.ESIndexMail, q, fmt.Sprintf("ctx._source.Status=%v", common.MailStatusRead)) +// } +// return one +// } + +// // DrawMail 领取一封邮件 +// func (ES *EsClient) DrawMail(id string) error { +// q := elastic.NewBoolQuery() +// q.Must(elastic.NewTermQuery("_id", id), elastic.NewTermQuery("Status", common.MailStatusRead)) +// res, err := ES.UpdateByScript(common.ESIndexMail, q, fmt.Sprintf("ctx._source.Status=%v", common.MailStatusDraw)) +// if err != nil { +// return err +// } +// if res < 1 { +// return errors.New("not updated") +// } +// return nil +// } + +// // DeleteMail 删除一封邮件 +// func (ES *EsClient) DeleteMail(id string) error { +// q := elastic.NewBoolQuery() +// q.Must(elastic.NewTermQuery("_id", id)) +// q.MustNot(elastic.NewTermQuery("Status", common.MailStatusDelete)) +// res, err := ES.UpdateByScript(common.ESIndexMail, q, fmt.Sprintf("ctx._source.Status=%v", common.MailStatusDelete)) +// if err != nil { +// return err +// } +// if res < 1 { +// return errors.New("not updated") +// } +// return nil +// } + +// // DeleteMailAll 一键删除邮件 +// func (ES *EsClient) DeleteMailAll(uid int) error { +// q := elastic.NewBoolQuery() +// q.Must(elastic.NewTermQuery("Receiver", uid)) +// q.MustNot(elastic.NewTermQuery("Status", common.MailStatusDelete)) +// res, err := ES.UpdateByScript(common.ESIndexMail, q, fmt.Sprintf("ctx._source.Status=%v", common.MailStatusDelete)) +// if err != nil { +// return err +// } +// if res < 1 { +// return errors.New("not updated") +// } +// return nil +// } + +// QueryMailDraftList 查询邮件草稿列表 +func (ES *EsClient) QueryMailDraftList(page, num int, list *[]common.MailDraft) (count int64, err error) { + q := elastic.NewBoolQuery() + // q.Must(elastic.NewTermQuery("UID", uid)) + q.Filter(elastic.NewRangeQuery("Status").Gt(common.MailDraftStatusDelete)) + count, err = ES.QueryList(common.ESIndexBackMailDraft, page-1, num, q, list, "Status", true, "Type", true, "Time", false) + if err != nil { + log.Error("err:%v", err) + return + } + return +} + +// SearchMailDraftList 条件查询邮件草稿列表 +func (ES *EsClient) SearchMailDraftList(page, num int, q *elastic.BoolQuery, list *[]common.MailDraft) (count int64, err error) { + q.Filter(elastic.NewRangeQuery("Status").Gt(common.MailDraftStatusDelete)) + count, err = ES.QueryList(common.ESIndexBackMailDraft, page-1, num, q, list, "Status", true, "Type", true, "Time", false) + if err != nil { + log.Error("err:%v", err) + return + } + return +} diff --git a/db/init.go b/db/init.go new file mode 100644 index 0000000..a1f6171 --- /dev/null +++ b/db/init.go @@ -0,0 +1,70 @@ +package db + +import ( + "server/config" + edb "server/db/es" + mdb "server/db/mysql" + rdb "server/db/redis" + + "github.com/liangdas/mqant/log" +) + +var ( + redisClient *rdb.RedisClient + esClient *edb.EsClient + mysqlClient *mdb.MysqlClient +) + +// InitDB 初始化db +func InitDB(dbs ...interface{}) { + var err error + for _, v := range dbs { + switch v.(type) { + case *rdb.RedisClient: + c := config.GetRedisConfig() + redisClient, err = rdb.InitRedisCilent(c.Addr, c.Name, c.Passwd, c.DB, c.TLS, c.Cluster) + if err != nil { + log.Error("err:%v", err) + panic("connect redis fail") + } + case *edb.EsClient: + c := config.GetESConfig() + esClient, err = edb.InitEsClient(c.Urls, c.Sniff) + if err != nil { + log.Error("err:%v", err) + panic("connect es fail") + } + case *mdb.MysqlClient: + c := config.GetMysqlConfig() + mysqlClient, err = mdb.InitMysqlClient(c.DSN, c.Debug) + if err != nil { + log.Error("err:%v", err) + panic("connect mysql fail") + } + } + } +} + +func Redis() *rdb.RedisClient { + return redisClient +} + +func Mysql() *mdb.MysqlClient { + return mysqlClient +} + +func ES() *edb.EsClient { + return esClient +} + +func SetRedis(r *rdb.RedisClient) { + redisClient = r +} + +func SetDB(r *mdb.MysqlClient) { + mysqlClient = r +} + +func SetES(r *edb.EsClient) { + esClient = r +} diff --git a/db/mysql/mysql.go b/db/mysql/mysql.go new file mode 100644 index 0000000..93a8f4c --- /dev/null +++ b/db/mysql/mysql.go @@ -0,0 +1,42 @@ +package mysql + +import ( + "gorm.io/driver/mysql" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +// MysqlClient sql连接对象 +type MysqlClient struct { + client *gorm.DB +} + +func InitMysqlClient(dsn string, debug bool) (*MysqlClient, error) { + dsn += "?charset=utf8mb4&parseTime=True&loc=Local" + // "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local" + + var ormLogger logger.Interface + if debug { + ormLogger = logger.Default.LogMode(logger.Info) + } else { + ormLogger = logger.Default + } + + user_db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ + Logger: ormLogger, + }) + if err != nil { + return nil, err + } + + return &MysqlClient{client: user_db}, nil +} + +func (m *MysqlClient) C() *gorm.DB { + return m.client +} + +// Begin 开启事务 +func (u *MysqlClient) Begin() *gorm.DB { + return u.client.Begin() +} diff --git a/db/mysql/mysql_exec.go b/db/mysql/mysql_exec.go new file mode 100644 index 0000000..b55f841 --- /dev/null +++ b/db/mysql/mysql_exec.go @@ -0,0 +1,330 @@ +package mysql + +import ( + "fmt" + "reflect" + + "github.com/liangdas/mqant/log" + "gorm.io/gorm" +) + +// Upsert 存在更新不存在插入 +func (u *MysqlClient) Upsert(condition string, values interface{}, tx ...*gorm.DB) (int64, error) { + if tx == nil { + return Upsert(condition, values, u.client) + } + return Upsert(condition, values, tx[0]) +} + +// Upsert 存在更新不存在插入 +func Upsert(condition string, values interface{}, d *gorm.DB) (int64, error) { + var count int64 + if err := d.Model(values).Where(condition).Count(&count).Error; err != nil { + if err != nil { + log.Error("err:%v", err) + } + return 0, err + } + if count > 0 { + tx := d.Model(values).Where(condition).Updates(values) + if tx.Error != nil { + log.Error("err:%v", tx.Error) + } + return tx.RowsAffected, tx.Error + } + tx := d.Model(values).Create(values) + if tx.Error != nil { + log.Error("err:%v", tx.Error) + } + return tx.RowsAffected, tx.Error +} + +func (u *MysqlClient) UpsertMap(condition string, model, update interface{}) error { + var count int64 + if err := u.client.Model(model).Where(condition).Count(&count).Error; err != nil { + if err != nil { + log.Error("err:%v", err) + } + return err + } + if count > 0 { + if err := u.client.Model(model).Where(condition).Updates(update).Error; err != nil { + log.Error("err:%v", err) + return err + } + } else { + if err := u.client.Create(model).Error; err != nil { + log.Error("err:%v", err) + return err + } + } + return nil +} + +func (u *MysqlClient) UpdateW(model, update interface{}, condition string) error { + if err := u.client.Model(model).Where(condition).Updates(update).Error; err != nil { + log.Error("err:%v", err) + return err + } + return nil +} + +func (u *MysqlClient) Update(model, update interface{}) error { + if err := u.client.Model(model).Where(model).Updates(update).Error; err != nil { + log.Error("err:%v", err) + return err + } + return nil +} + +func (u *MysqlClient) Del(model interface{}, condi ...interface{}) error { + if err := u.client.Delete(model, condi).Error; err != nil { + log.Error("err:%v", err) + return err + } + return nil +} +func (u *MysqlClient) UpdateRes(model, update interface{}, tx ...*gorm.DB) (int64, error) { + d := u.client + if tx != nil { + d = tx[0] + } + res := d.Model(model).Where(model).Updates(update) + return res.RowsAffected, res.Error +} + +func (u *MysqlClient) UpdateResW(model, update interface{}, condition string, tx ...*gorm.DB) (int64, error) { + d := u.client + if tx != nil { + d = tx[0] + } + res := d.Model(model).Where(condition).Updates(update) + return res.RowsAffected, res.Error +} + +// SelectField 查询选择的字段,扫描进dst +func (u *MysqlClient) SelectField(dst interface{}, fields ...string) error { + return u.client.Model(dst).Where(dst).Select(fields).Scan(dst).Error +} + +func (u *MysqlClient) Get(data interface{}) error { + err := u.client.Model(data).Where(data).First(data).Error + if err != nil && err != gorm.ErrRecordNotFound { + log.Error("err:%v", err) + } + return err +} + +func (u *MysqlClient) GetLast(data interface{}) error { + err := u.client.Model(data).Where(data).Last(data).Error + if err != nil && err != gorm.ErrRecordNotFound { + log.Error("err:%v", err) + } + return err +} + +func (u *MysqlClient) Create(data interface{}) error { + if err := u.client.Create(data).Error; err != nil { + log.Error("err:%v", err) + return err + } + return nil +} + +// QueryListM 查询一组数据,以model为检索条件,model必须实现TableName方法 +func (u *MysqlClient) QueryListM(page, num int, model, ret interface{}, order ...interface{}) error { + m := reflect.ValueOf(model) + f := m.MethodByName("TableName") + fret := f.Call(nil) + tn := fret[0].String() + tx := u.client.Table(tn).Where(model) + if order != nil { + tx.Order(order[0]) + } + return tx.Offset(page * num).Limit(num).Scan(ret).Error +} + +// QueryListW 查询一组数据,以where为检索条件 +func (u *MysqlClient) QueryListW(page, num int, order string, model, ret, query interface{}, args ...interface{}) (int64, error) { + m := reflect.ValueOf(model) + f := m.MethodByName("TableName") + fret := f.Call(nil) + tn := fret[0].String() + tx := u.client.Table(tn).Where(query, args...) + if order != "" { + tx.Order(order) + } + var count int64 + if err := tx.Count(&count).Error; err != nil { + log.Error("err:%v", err) + return 0, err + } + if err := tx.Offset(page * num).Limit(num).Scan(ret).Error; err != nil { + log.Error("err:%v", err) + return 0, err + } + return count, nil +} + +// QueryList 查询一组数据 +func (u *MysqlClient) QueryList(page, num int, query, order string, model, ret interface{}) (int64, error) { + tx := u.client.Model(model).Where(query) + if order != "" { + tx.Order(order) + } + var count int64 + if err := tx.Count(&count).Error; err != nil { + log.Error("err:%v", err) + return 0, err + } + if err := tx.Offset(page * num).Limit(num).Scan(ret).Error; err != nil { + log.Error("err:%v", err) + return 0, err + } + return count, nil +} + +// QueryList 查询一组数据 +func (u *MysqlClient) QueryAll(query, order string, model, ret interface{}) (int64, error) { + tx := u.client.Model(model).Where(query) + if order != "" { + tx.Order(order) + } + var count int64 + if err := tx.Count(&count).Error; err != nil { + log.Error("err:%v", err) + return 0, err + } + if err := tx.Scan(ret).Error; err != nil { + log.Error("err:%v", err) + return 0, err + } + return count, nil +} + +// QueryCurrencyHistory 查询流水记录,屏蔽掉某些event +func (u *MysqlClient) QueryCurrencyHistory(page, num int, model, ret interface{}, query string, order ...interface{}) (int64, error) { + m := reflect.ValueOf(model) + f := m.MethodByName("TableName") + fret := f.Call(nil) + tn := fret[0].String() + tx := u.client.Table(tn).Where(query) + if order != nil { + tx.Order(order[0]) + } + var count int64 + if err := tx.Count(&count).Error; err != nil { + log.Error("err:%v", err) + return 0, err + } + if err := tx.Offset(page * num).Limit(num).Scan(ret).Error; err != nil { + log.Error("err:%v", err) + return 0, err + } + return count, nil +} + +// DistinctCount 去重查询个数 +func (u *MysqlClient) DistinctCount(model interface{}, con, name string) (count int64) { + if err := u.client.Model(model).Where(con).Distinct(name).Count(&count).Error; err != nil { + log.Error("err:%v", err) + } + return +} + +// Count 根据条件查询个数 +func (u *MysqlClient) Count(model interface{}, condition string) (count int64) { + if err := u.client.Model(model).Where(condition).Count(&count).Error; err != nil { + log.Error("err:%v", err) + } + return +} + +// Count 根据条件查询个数 +func (u *MysqlClient) CountTable(tableName, condition string) (count int64) { + if err := u.client.Table(tableName).Where(condition).Count(&count).Error; err != nil { + log.Error("err:%v", err) + } + return +} + +// Count 根据条件查询个数 +func (u *MysqlClient) Exist(model interface{}) bool { + var count int64 + if err := u.client.Model(model).Where(model).Count(&count).Error; err != nil { + log.Error("err:%v", err) + return false + } + return count > 0 +} + +// Sum 求和 +func (u *MysqlClient) Sum(model interface{}, condition, name string) (count int64) { + err := u.client.Model(model).Where(condition).Select(fmt.Sprintf("IFNULL(SUM(%v),0)", name)).Scan(&count).Error + if err != nil { + log.Error("err:%v", err) + } + return +} + +func (u *MysqlClient) SumTable(tableName, condition, name string) (count int64) { + err := u.client.Table(tableName).Where(condition).Select(fmt.Sprintf("IFNULL(SUM(%v),0)", name)).Scan(&count).Error + if err != nil { + log.Error("err:%v", err) + } + return +} + +// QueryPlayerRWHistory 查询玩家充值/退出历史 +func (u *MysqlClient) QueryPlayerRWHistory(uid *int, channel *int, page, num int, event []int, start, end *int64, model, ret interface{}, status ...*int) (int64, error) { + // var count int64 + // var err error + query := "" + for i, v := range event { + if i == 0 { + query += fmt.Sprintf("(event = %v", v) + } else { + query += fmt.Sprintf(" or event = %v", v) + } + if i == len(event)-1 { + query += ")" + } + } + if status != nil && status[0] != nil { + query += fmt.Sprintf(" and status = %v", *status[0]) + } + if uid != nil { + query += fmt.Sprintf(" and uid = %v", *uid) + } + if channel != nil { + query += fmt.Sprintf(" and channel_id = %v", *channel) + } + if start != nil { + query += fmt.Sprintf(" and create_time >= %d", *start) + } + if end != nil { + query += fmt.Sprintf(" and create_time < %d", *end) + } + return u.QueryList(page, num, query, "create_time desc", model, ret) +} + +// sql原生语句查询 +func (u *MysqlClient) QueryBySql(sqlStr string, res interface{}) error { + err := u.client.Raw(sqlStr).Scan(res).Error + if err != nil { + log.Error("err:%v", err) + return err + } else { + return nil + } +} + +// sql原生语句查询 +func (u *MysqlClient) QueryCountBySql(sqlStr string, count interface{}) error { + err := u.client.Raw(sqlStr).Scan(count).Error + if err != nil { + return err + } else { + return nil + } +} diff --git a/db/mysql/mysql_mail.go b/db/mysql/mysql_mail.go new file mode 100644 index 0000000..f8ae4aa --- /dev/null +++ b/db/mysql/mysql_mail.go @@ -0,0 +1,89 @@ +package mysql + +import ( + "fmt" + "server/common" + "time" +) + +// QueryMailList 查询玩家邮件列表 +func (c *MysqlClient) QueryMailList(uid, page, num int, list *[]common.Mail, tag ...int) (count int64, err error) { + now := time.Now().Unix() + start := now - common.MailExpireTime + sql := fmt.Sprintf("receiver = %v and status <> %v and time <= %v and time >= %v", uid, common.MailStatusDelete, now, start) + if len(tag) > 0 { + sql += fmt.Sprintf(" and tag = %d", tag[0]) + } + count, err = c.QueryList(page, num, sql, "status,time desc", &common.Mail{}, &list) + if count > common.MailMaxCount { + count = common.MailMaxCount + } + return +} + +// // QueryNewMailCount 查询玩家未读邮件个数 +func (c *MysqlClient) QueryNewMailCount(uid int) int64 { + now := time.Now().Unix() + start := now - common.MailExpireTime + sql := fmt.Sprintf("receiver = %v and status = %v and time <= %v and time >= %v", uid, common.MailStatusNew, now, start) + return c.Count(&common.Mail{}, sql) +} + +// // ReadMail 查看一封邮件 +// func (c *MysqlClient)ReadMail(id string) *common.Mail { +// one := new(common.Mail) +// if err := ES.QueryOne(common.ESIndexMail, elastic.NewTermQuery("_id", id), one); err != nil { +// log.Error("err:%v", err) +// return nil +// } +// if one.Status == common.MailStatusNew { +// q := elastic.NewBoolQuery() +// q.Must(elastic.NewTermQuery("_id", id), elastic.NewTermQuery("Status", common.MailStatusNew)) +// ES.UpdateByScript(common.ESIndexMail, q, fmt.Sprintf("ctx._source.Status=%v", common.MailStatusRead)) +// } +// return one +// } + +// // DrawMail 领取一封邮件 +// func (ES *EsClient) DrawMail(id string) error { +// q := elastic.NewBoolQuery() +// q.Must(elastic.NewTermQuery("_id", id), elastic.NewTermQuery("Status", common.MailStatusRead)) +// res, err := ES.UpdateByScript(common.ESIndexMail, q, fmt.Sprintf("ctx._source.Status=%v", common.MailStatusDraw)) +// if err != nil { +// return err +// } +// if res < 1 { +// return errors.New("not updated") +// } +// return nil +// } + +// // DeleteMail 删除一封邮件 +// func (ES *EsClient) DeleteMail(id string) error { +// q := elastic.NewBoolQuery() +// q.Must(elastic.NewTermQuery("_id", id)) +// q.MustNot(elastic.NewTermQuery("Status", common.MailStatusDelete)) +// res, err := ES.UpdateByScript(common.ESIndexMail, q, fmt.Sprintf("ctx._source.Status=%v", common.MailStatusDelete)) +// if err != nil { +// return err +// } +// if res < 1 { +// return errors.New("not updated") +// } +// return nil +// } + +// // DeleteMailAll 一键删除邮件 +// func (ES *EsClient) DeleteMailAll(uid int) error { +// q := elastic.NewBoolQuery() +// q.Must(elastic.NewTermQuery("Receiver", uid)) +// q.MustNot(elastic.NewTermQuery("Status", common.MailStatusDelete)) +// res, err := ES.UpdateByScript(common.ESIndexMail, q, fmt.Sprintf("ctx._source.Status=%v", common.MailStatusDelete)) +// if err != nil { +// return err +// } +// if res < 1 { +// return errors.New("not updated") +// } +// return nil +// } diff --git a/db/redis/redis.go b/db/redis/redis.go new file mode 100644 index 0000000..38fe719 --- /dev/null +++ b/db/redis/redis.go @@ -0,0 +1,414 @@ +package redis + +import ( + "context" + "crypto/tls" + "encoding/json" + "errors" + "reflect" + "server/common" + "time" + + "github.com/go-redis/redis/v8" + "github.com/liangdas/mqant/log" + "github.com/mitchellh/mapstructure" +) + +// RedisClient redis连接对象 +type RedisClient struct { + client redis.Cmdable +} + +// InitRedisCilent 连接redis +func InitRedisCilent(host, name, pwd string, db int, isTls, cluster bool) (*RedisClient, error) { + if !cluster { + opt := &redis.Options{ + Addr: host, + Username: name, + Password: pwd, + DB: db, + } + if isTls { + opt.TLSConfig = &tls.Config{} + } + cli := redis.NewClient(opt) + + err := cli.Ping(context.Background()).Err() + if err != nil { + return nil, err + } + return &RedisClient{client: cli}, nil + } + opt := &redis.ClusterOptions{ + Addrs: []string{host}, + Username: name, + Password: pwd, + } + if isTls { + opt.TLSConfig = &tls.Config{} + } + cli := redis.NewClusterClient(opt) + + err := cli.Ping(context.Background()).Err() + if err != nil { + return nil, err + } + return &RedisClient{client: cli}, nil +} + +// LRange 读取一组数据,ret必须实现MarshalBinary和UnmarshalBinary方法 +func (r *RedisClient) LRange(key string, ret interface{}) error { + err := r.client.LRange(context.Background(), key, 0, -1).ScanSlice(ret) + if err != nil { + log.Error("err:%v", err) + return err + } + return nil +} + +// LPushJsonSlice 插入数组(data 必须是切片类型,元素转换成json存储) +func (r *RedisClient) LPush(key string, data interface{}) error { + err := r.client.LPush(context.Background(), key, data).Err() + if err != nil { + log.Error("err:%v", err) + return err + } + return nil +} + +// RPop pop出一个数据 +func (r *RedisClient) RPop(key string) error { + err := r.client.RPop(context.Background(), key).Err() + if err != nil { + log.Error("err:%v", err) + return err + } + return nil +} + +func (r *RedisClient) GetRedis() redis.Cmdable { + return r.client +} + +// SetJsonData 通用设置json数据 +func (r *RedisClient) SetJsonData(key string, data interface{}, ex ...time.Duration) (err error) { + ret, err := json.Marshal(data) + if err != nil && err != redis.Nil { + log.Error("err:%v", err) + return + } + + if len(ex) > 0 { + err = r.client.Set(context.Background(), key, ret, ex[0]).Err() + } else { + err = r.client.Set(context.Background(), key, ret, 0).Err() + } + return +} + +// GetJsonData 通用获取json数据 +func (r *RedisClient) GetJsonData(key string, kind interface{}) (err error) { + ret, err := r.client.Get(context.Background(), key).Bytes() + if err != nil && err != redis.Nil { + log.Error("err:%v", err) + return + } + err = json.Unmarshal(ret, kind) + return +} + +// SetMapData 存入一组数据 +func (r *RedisClient) HSet(key string, data ...interface{}) error { + tmp := reflect.ValueOf(data[0]) + if tmp.Kind() == reflect.Ptr { + tmp = reflect.ValueOf(data[0]).Elem() + } + if tmp.Kind() == reflect.Struct { + out := map[string]interface{}{} + if err := mapstructure.Decode(data[0], &out); err != nil { + log.Error("err:%v", err) + return err + } + return r.GetRedis().HSet(context.Background(), key, out).Err() + } + return r.GetRedis().HSet(context.Background(), key, data).Err() +} + +// HGetJson 获取hash,以json存储 +func (r *RedisClient) HGetJson(key, field string, ret interface{}) error { + res, err := r.GetRedis().HGet(context.Background(), key, field).Result() + if err != nil { + if err != redis.Nil { + log.Error("key:%v,field:%v,err:%v", key, field, err) + } + return err + } + if err := json.Unmarshal([]byte(res), ret); err != nil { + log.Error("key:%v,field:%v,err:%v", key, field, err) + return err + } + return nil +} + +// HSetJson set hash,以json存储 +func (r *RedisClient) HSetJson(key, field string, ret interface{}) error { + data, err := json.Marshal(ret) + if err != nil { + return err + } + _, err = r.GetRedis().HSet(context.Background(), key, field, data).Result() + if err != nil { + return err + } + return nil +} + +// HDel 删除hash +func (r *RedisClient) HDel(key, field string) error { + err := r.GetRedis().HDel(context.Background(), key, field).Err() + if err != nil { + log.Error("key:%v,field:%v,err:%v", key, field, err) + return err + } + return nil +} + +// HGetAll 获得某个key的所有field +func (r *RedisClient) HGetAll(key string, ret interface{}) error { + err := r.GetRedis().HGetAll(context.Background(), key).Scan(ret) + if err != nil { + log.Error("HGetAll fail:%v", err) + return err + } + return nil +} + +// LPushJsonSlice 插入数组(data 必须是切片类型,元素转换成json存储) +func (r *RedisClient) LPushJsonSlice(key string, data interface{}) error { + val := reflect.ValueOf(data) + if val.Kind() != reflect.Slice { + return errors.New("unknown type") + } + for i := 0; i < val.Len(); i++ { + value, _ := json.Marshal(val.Index(i).Interface()) + err := r.client.LPush(context.Background(), key, string(value)).Err() + if err != nil { + log.Error("err:%v", err) + return err + } + } + return nil +} + +// RPushJsonSlice 插入数组(data 必须是切片类型,元素转换成json存储) +func (r *RedisClient) RPushJsonSlice(key string, data interface{}) error { + val := reflect.ValueOf(data) + if val.Kind() != reflect.Slice { + return errors.New("unknown type") + } + for i := 0; i < val.Len(); i++ { + value, _ := json.Marshal(val.Index(i).Interface()) + err := r.client.RPush(context.Background(), key, string(value)).Err() + if err != nil { + log.Error("err:%v", err) + return err + } + } + return nil +} + +// GetRankData 以score为参数倒序取set +func (r *RedisClient) GetRankData(key string) ([]redis.Z, error) { + return r.GetRedis().ZRevRangeWithScores(context.Background(), key, 0, -1).Result() +} + +// Lock redis锁 +func (r *RedisClient) Lock(key string, ex ...time.Duration) bool { + if len(ex) > 0 { + return r.client.SetNX(context.Background(), key, 0, ex[0]).Val() + } + return r.client.SetNX(context.Background(), key, 1, common.RedisExpireLock).Val() +} + +// UnLock redis 解锁 +func (r *RedisClient) UnLock(key string, ex ...time.Duration) int64 { + return r.client.Del(context.Background(), key).Val() +} + +// WaitForLock redis等待解锁 +func (r *RedisClient) WaitForLock(key string, ex ...time.Duration) { + for { + ok := r.Lock(key, ex...) + if ok { + break + } + // t, _ := r.GetRedis().TTL(context.Background(), key).Result() + time.Sleep(500 * time.Millisecond) + } +} + +// SetData 通用设置key value +func (r *RedisClient) SetData(key string, data interface{}, ex ...time.Duration) error { + if len(ex) > 0 { + return r.GetRedis().Set(context.Background(), key, data, ex[0]).Err() + } + return r.GetRedis().Set(context.Background(), key, data, 0).Err() +} + +func (u *RedisClient) GetInt(key string) (data int, err error) { + data, err = u.client.Get(context.Background(), key).Int() + if err != nil && err != redis.Nil { + log.Error("err:%v", err) + } + return +} + +func (u *RedisClient) GetInt64(key string) int64 { + data, err := u.client.Get(context.Background(), key).Int64() + if err != nil && err != redis.Nil { + log.Error("err:%v", err) + } + return data +} + +// GetString 通用获取string +func (r *RedisClient) GetString(key string) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + ret, err := r.GetRedis().Get(ctx, key).Result() + return ret, err +} + +func (u *RedisClient) Delkey(key string) int64 { + res := u.client.Del(context.Background(), key) + if err := res.Err(); err != nil { + log.Error("err:%v", err) + } + count, _ := res.Result() + return count +} + +func (u *RedisClient) Exist(key string) bool { + ret, err := u.client.Exists(context.Background(), key).Result() + if err != nil { + log.Error("err:%v", err) + return false + } + if ret == 1 { + return true + } + return false +} + +// SetData 通用设置key value +func (r *RedisClient) SetNX(key string, data interface{}, ex ...time.Duration) bool { + if len(ex) > 0 { + return r.GetRedis().SetNX(context.Background(), key, data, ex[0]).Val() + } + return r.GetRedis().SetNX(context.Background(), key, data, 0).Val() +} + +func (u *RedisClient) Incr(key string, value int64) (int64, error) { + return u.client.IncrBy(context.Background(), key, value).Result() + // if err != nil { + // log.Error("err:%v", err) + // return false + // } + // if ret == 1 { + // return true + // } + // return false +} + +// 更新玩家字段 +func (u *RedisClient) HIncrBy(key, field string, inc int64) (err error) { + if exists, _ := u.client.Exists(context.Background(), key).Result(); exists == 0 { + return + } + err = u.client.HIncrBy(context.Background(), key, field, inc).Err() + return +} + +// 设置超时 +func (u *RedisClient) Expire(key string, ex time.Duration) (err error) { + err = u.client.Expire(context.Background(), key, ex).Err() + return +} + +// 获取超时 +func (u *RedisClient) TTL(key string) (t time.Duration, err error) { + return u.client.TTL(context.Background(), key).Result() +} + +// HGetInt 获得某个key的制定field的int +func (r *RedisClient) HGetInt(key, field string) int { + data, err := r.GetRedis().HGet(context.Background(), key, field).Int() + if err != nil && err != redis.Nil { + log.Error("HGetAll fail:%v", err) + return 0 + } + return data +} + +func (r *RedisClient) ZAdd(key string, score int64, member interface{}) error { + err := r.GetRedis().ZAdd(context.Background(), key, &redis.Z{Score: float64(score), Member: member}).Err() + if err != nil { + log.Error("ZAdd fail:%v", err) + return err + } + return nil +} + +func (r *RedisClient) ZIncr(key string, score int64, member interface{}) error { + err := r.GetRedis().ZIncr(context.Background(), key, &redis.Z{Score: float64(score), Member: member}).Err() + if err != nil { + log.Error("ZIncr fail:%v", err) + return err + } + return nil +} + +func (r *RedisClient) ZRange(key string) []redis.Z { + ret := r.GetRedis().ZRevRangeWithScores(context.Background(), key, 0, -1) + if ret.Err() != nil { + log.Error("err:%v", ret.Err()) + return nil + } + return ret.Val() +} + +func (r *RedisClient) ZRem(key string, member interface{}) error { + err := r.GetRedis().ZRem(context.Background(), key, member).Err() + if err != nil { + log.Error("err:%v", err) + return err + } + return nil +} + +func (r *RedisClient) ZRangeRe(key string, start, end int) []redis.Z { + ret, err := r.GetRedis().ZRevRangeWithScores(context.Background(), key, int64(start), int64(end)).Result() + if err != nil { + log.Error("err:%v", err) + return nil + } + return ret +} + +func (r *RedisClient) ZRevRank(key string, member string) int { + ret, err := r.GetRedis().ZRevRank(context.Background(), key, member).Result() + if err != nil { + log.Error("err:%v", err) + return -1 + } + return int(ret) +} + +func (r *RedisClient) ZScore(key string, member string) int { + ret, err := r.GetRedis().ZScore(context.Background(), key, member).Result() + if err != nil { + log.Error("err:%v", err) + return -1 + } + return int(ret) +} diff --git a/db/redis/redis_robot.go b/db/redis/redis_robot.go new file mode 100644 index 0000000..61ec290 --- /dev/null +++ b/db/redis/redis_robot.go @@ -0,0 +1,34 @@ +package redis + +import ( + "context" + "server/common" + + "github.com/liangdas/mqant/log" +) + +// 投放机器人 +func (u *RedisClient) UseRobot(id int) bool { + ok, err := u.GetRedis().SetNX(context.Background(), common.GetRedisKeyRobotPlaying(id), 1, 0).Result() + if err != nil { + log.Error("err:%v", err) + return false + } + if !ok { + return false + } + if _, err := u.Incr(common.GetRedisKeyRobotPlaying(0), 1); err != nil { + log.Error("err:%v", err) + } + return true +} + +// 回收机器人 +func (u *RedisClient) DelRobot(id int) { + count := u.Delkey(common.GetRedisKeyRobotPlaying(id)) + if count > 0 { + if _, err := u.Incr(common.GetRedisKeyRobotPlaying(0), -1); err != nil { + log.Error("err:%v", err) + } + } +} diff --git a/db/redis/redis_user.go b/db/redis/redis_user.go new file mode 100644 index 0000000..f220a10 --- /dev/null +++ b/db/redis/redis_user.go @@ -0,0 +1,133 @@ +package redis + +import ( + "context" + "errors" + "server/common" + "server/util" + + "github.com/liangdas/mqant/log" +) + +// 更新玩家字段 +func (u *RedisClient) SetToken(uid int, token string) (err error) { + key := common.GetRedisKeyToken(token) + err = u.client.Set(context.Background(), key, uid, common.RedisExpireToken).Err() + return +} + +// 获取用户信息 +func (r *RedisClient) GetUserData(uid int) (player *common.PlayerDBInfo) { + key := common.GetRedisKeyUser(uid) + + res := r.client.HGetAll(context.Background(), key) + if res.Err() != nil { + log.Error("redis hgetall error %v", res.Err()) + return + } + + player = &common.PlayerDBInfo{} + + if err := res.Scan(player); err != nil { + log.Error("RedisClient GetUserData redis hgetall scan error %v", err) + return nil + } + + if player.Id == 0 { + return nil + } + + return +} + +// 获取玩家session +func (u *RedisClient) GetUserSession(uid int) (s *common.PlayerSession) { + s = &common.PlayerSession{} + if err := u.GetUserXInfo(uid, s, "sessionID", "gateID"); err != nil || s.SessionID == "" { + return nil + } + return +} + +// 获取玩家session +func (u *RedisClient) GetUserToken(uid int) string { + user := &common.PlayerDBInfo{} + if err := u.GetUserXInfo(uid, user, "token"); err != nil { + return "" + } + return user.Token +} + +// 获取用户自定义信息 +func (u *RedisClient) GetUserXInfo(uid int, ret interface{}, fields ...string) error { + key := common.GetRedisKeyUser(uid) + res := u.client.HMGet(context.Background(), key, fields...) + if res.Err() != nil { + log.Error("RedisClient GetUserBriefInfo redis HMGet error %v", res.Err()) + return res.Err() + } + if res.Val() == nil || res.Val()[0] == nil { + return errors.New("not found") + } + + if err := res.Scan(ret); err != nil { + log.Error("RedisClient GetUserBriefInfo redis HMGet scan error %v", res.Err()) + return err + } + + return nil +} + +// 设置机器人 +func (u *RedisClient) SetRobotData(data common.PlayerDBInfo) error { + m := util.StructToMap(data, "json") + key := common.GetRedisKeyUser(data.Id) + if err := u.client.HSet(context.Background(), key, m).Err(); err != nil { + log.Error("err:%v", err) + return err + } + return nil +} + +// 登陆成功后更新用户信息 +func (u *RedisClient) UpdateUserData(data common.PlayerDBInfo) error { + m := util.StructToMap(data, "json") + // sessionID 和 gateID 不需要在这里更新 + delete(m, "gateID") + delete(m, "sessionID") + key := common.GetRedisKeyUser(data.Id) + log.Debug("m:%v", m) + if err := u.client.HSet(context.Background(), key, m).Err(); err != nil { + log.Error("err:%v", err) + return err + } + u.client.Expire(context.Background(), key, common.RedisExpireToken) + //log.Debug("cache update user:%d cache, v:%v, err:%v", data.Id, v, e) + return nil +} + +// 更新玩家字段 +func (u *RedisClient) UpdateUserFields(uid int, values map[string]interface{}) (err error) { + key := common.GetRedisKeyUser(uid) + if exists, _ := u.client.Exists(context.Background(), key).Result(); exists == 0 { + return + } + err = u.client.HSet(context.Background(), key, values).Err() + if err != nil { + log.Error("err:%v", err) + } + return +} + +// AddUserExpire 增加玩家token过期时间 +func (u *RedisClient) AddUserExpire(uid int, token string) error { + if err := u.client.Expire(context.Background(), common.GetRedisKeyUser(uid), common.RedisExpireToken).Err(); err != nil { + log.Error("err:%v", err) + return err + } + if err := u.client.Expire(context.Background(), common.GetRedisKeyToken(token), common.RedisExpireToken).Err(); err != nil { + log.Error("err:%v", err) + return err + } + return nil +} diff --git a/docs/backend/account.go b/docs/backend/account.go new file mode 100644 index 0000000..1ed7de1 --- /dev/null +++ b/docs/backend/account.go @@ -0,0 +1,20 @@ +package backend + +import "server/modules/backend/values" + +// swagger:route POST /account/login account idOfLogin +// 登录 +// Responses: +// 200:LoginResp + +// swagger:response LoginResp +type LoginResp struct { + // in:body + Body values.LoginReq +} + +// swagger:parameters idOfLogin +type LoginReq struct { + // in:body + Body values.LoginResp +} diff --git a/docs/backend/base.go b/docs/backend/base.go new file mode 100644 index 0000000..aba7ac0 --- /dev/null +++ b/docs/backend/base.go @@ -0,0 +1,7 @@ +// Package backend server API. +// 服务器后台协议 +// +// Host: 119.23.175.120:7616 +// Version: 0.0.1 +// swagger:meta +package backend diff --git a/docs/backend/common.go b/docs/backend/common.go new file mode 100644 index 0000000..9afefae --- /dev/null +++ b/docs/backend/common.go @@ -0,0 +1,47 @@ +package backend + +import "server/modules/backend/values" + +// swagger:route GET /common/productList common idOfProductList +// 充值列表 +// Responses: +// 200:ProductListResp + +// swagger:response ProductListResp +type ProductListResp struct { + // in:body + Body values.ProductListResp +} + +// swagger:route GET /common/gameList common idOfGameList +// 游戏列表 +// Responses: +// 200:GameListResp + +// swagger:response GameListResp +type GameListResp struct { + // in:body + Body values.GamesListResp +} + +// swagger:route GET /common/channelList common idOfChannelList +// 渠道列表 +// Responses: +// 200:ChannelListResp + +// swagger:response ChannelListResp +type ChannelListResp struct { + // in:body + Body values.ChannelListResp +} + +// swagger:route GET /common/userInfo common idOfUserInfo +// 用户信息 +// Responses: +// 200:UserInfoResp + +// swagger:response UserInfoResp +type UserInfoResp struct { + // in:body + Body values.UserInfoResp +} diff --git a/docs/backend/examine.go b/docs/backend/examine.go new file mode 100644 index 0000000..d3acd30 --- /dev/null +++ b/docs/backend/examine.go @@ -0,0 +1,40 @@ +package backend + +import ( + "server/modules/backend/app" + "server/modules/backend/values" +) + +// swagger:route POST /examine/withdraw/list examine idOfExamineWithdrawList +// 获取审核退出列表 +// Responses: +// 200:ExamineWithdrawListResp + +// swagger:response ExamineWithdrawListResp +type ExamineWithdrawListResp struct { + // in:body + Body values.WithdrawListResp +} + +// swagger:parameters idOfExamineWithdrawList +type ExamineWithdrawListReq struct { + // in:body + Body values.WithdrawListReq +} + +// swagger:route POST /examine/withdraw/do examine idOfExamineWithdraw +// 审核退出 +// Responses: +// 200:ExamineWithdrawResp + +// swagger:response ExamineWithdrawResp +type ExamineWithdrawResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfExamineWithdraw +type ExamineWithdrawReq struct { + // in:body + Body values.WithdrawExamineReq +} diff --git a/docs/backend/gm.go b/docs/backend/gm.go new file mode 100644 index 0000000..666fcd4 --- /dev/null +++ b/docs/backend/gm.go @@ -0,0 +1,169 @@ +package backend + +import ( + "server/modules/backend/app" + "server/modules/backend/values" +) + +// swagger:route POST /gm/uploadExcel gm idOfUploadExcel +// 修改配置表,postform 提交表单格式,文件必须是excel,字段名"excel" +// Responses: +// 200:UploadExcelResp + +// swagger:response UploadExcelResp +type UploadExcelResp struct { + // in:body + Body app.R +} + +// swagger:route POST /gm/uploadExcel/tpRobotOperate gm idOfUploadExcelRobotOperate +// 修改配置表,postform 提交表单格式,文件必须是excel,字段名"excel" +// Responses: +// 200:UploadExcelRobotOperateResp + +// swagger:response UploadExcelRobotOperateResp +type UploadExcelRobotOperateResp struct { + // in:body + Body app.R +} + +// swagger:route GET /gm/downloadExcel gm idOfDownloadExcel +// 下载配置表 +// Responses: +// 200:DownloadExcelResp + +// swagger:response DownloadExcelResp +type DownloadExcelResp struct { + // in:body + Body app.R +} + +// swagger:route POST /gm/addCoin gm idOfGMAddCoin +// 后台加钱 +// Responses: +// 200:GmAddCoinResp + +// swagger:response GmAddCoinResp +type GmAddCoinResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfGMAddCoin +type GmAddCoinReq struct { + // in:body + Body values.GMAddCoinReq +} + +// swagger:route POST /gm/recharge gm idOfGMRecharge +// 后台模拟充值 +// Responses: +// 200:GMRechargeResp + +// swagger:response GMRechargeResp +type GMRechargeResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfGMRecharge +type GMRechargeReq struct { + // in:body + Body values.GMRechargeReq +} + +// swagger:route POST /gm/resetSign gm idOfGMResetSign +// 后台重置签到 +// Responses: +// 200:GMResetSignResp + +// swagger:response GMResetSignResp +type GMResetSignResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfGMResetSign +type GMResetSignReq struct { + // in:body + Body values.GMResetSignReq +} + +// swagger:route POST /gm/bindPhone gm idOfBindPhone +// 后台绑定手机 +// Responses: +// 200:GMBindPhoneResp + +// swagger:response GMBindPhoneResp +type GMBindPhoneResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfBindPhone +type GMBindPhoneReq struct { + // in:body + Body values.GMBindPhoneReq +} + +// swagger:route POST /gm/unbindPhone gm idOfUnBindPhone +// 后台解除绑定手机 +// Responses: +// 200:GMUnBindPhoneResp + +// swagger:response GMUnBindPhoneResp +type GMUnBindPhoneResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfUnBindPhone +type GMUnBindPhoneReq struct { + // in:body + Body values.GMUnBindPhoneReq +} + +// swagger:route POST /gm/getPhoneCode gm idOfGetPhoneCode +// 后台获取手机验证码 +// Responses: +// 200:GMGetPhoneCodeResp + +// swagger:response GMGetPhoneCodeResp +type GMGetPhoneCodeResp struct { + // in:body + Body values.GMGetPhoneCodeResp +} + +// swagger:parameters idOfGetPhoneCode +type GMGetPhoneCodeReq struct { + // in:body + Body values.GMGetPhoneCodeReq +} + +// swagger:route GET /gm/platform/list gm idOfGMPlatformConfigList +// 获取平台配置 +// Responses: +// 200:GMPlatformConfigListResp + +// swagger:parameters GMPlatformConfigListResp +type GMPlatformConfigListResp struct { + // in:body + Body values.GMConfigPlatformListResp +} + +// swagger:route POST /gm/platform/edit gm idOfGMPlatformConfigEdit +// 修改平台配置 +// Responses: +// 200:GMPlatformConfigEditResp + +// swagger:parameters GMPlatformConfigEditResp +type GMPlatformConfigEditResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfGMPlatformConfigEdit +type GMPlatformConfigEditReq struct { + // in:body + Body values.GMConfigPlatformEditReq +} diff --git a/docs/backend/gm_control.go b/docs/backend/gm_control.go new file mode 100644 index 0000000..b6a5877 --- /dev/null +++ b/docs/backend/gm_control.go @@ -0,0 +1,51 @@ +package backend + +import ( + "server/modules/backend/app" + "server/modules/backend/values" +) + +// swagger:route GET /gm/control/rummyDeal/list gm_control idOfGMConfigRummyDealList +// 获取rummy发牌配置 +// Responses: +// 200:GMConfigRummyDealListResp + +// swagger:parameters GMConfigRummyDealListResp +type GMConfigRummyDealListResp struct { + // in:body + Body values.GMConfigCommonListResp +} + +// swagger:route POST /gm/control/rummyDeal/edit gm_control idOfGMConfigRummyDealEdit +// 修改rummy发牌配置 +// Responses: +// 200:GMConfigRummyDealEditResp + +// swagger:parameters GMConfigRummyDealEditResp +type GMConfigRummyDealEditResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfGMConfigRummyDealEdit +type GMConfigRummyDealEditReq struct { + // in:body + Body values.GMConfigCommonEditReq +} + +// swagger:route POST /gm/control/rummyDeal/del gm_control idOfGMConfigRummyDealDel +// 删除rummy发牌配置 +// Responses: +// 200:GMConfigRummyDealDelResp + +// swagger:parameters GMConfigRummyDealDelResp +type GMConfigRummyDealDelResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfGMConfigRummyDealDel +type GMConfigRummyDealDelReq struct { + // in:body + Body values.GMConfigCommonDelReq +} diff --git a/docs/backend/guser.go b/docs/backend/guser.go new file mode 100644 index 0000000..d952a91 --- /dev/null +++ b/docs/backend/guser.go @@ -0,0 +1,161 @@ +package backend + +import "server/modules/backend/values" + +// swagger:route POST /guser/list guser idOfGUserList +// 获取游戏玩家列表 +// Responses: +// 200:GetGameUserListResp + +// swagger:response GetGameUserListResp +type GetGameUserListResp struct { + // in:body + Body values.GetGameUserListResp +} + +// swagger:parameters idOfGUserList +type GetGameUserListReq struct { + // in:body + Body values.GetGameUserListReq +} + +// swagger:route POST /guser/info guser idOfGUserInfo +// 获取游戏玩家信息 +// Responses: +// 200:GUserInfoResp + +// swagger:response GUserInfoResp +type GUserInfoResp struct { + // in:body + Body values.GetGameUserInfoResp +} + +// swagger:parameters idOfGUserInfo +type GUserInfoReq struct { + // in:body + Body values.GetGameUserInfoReq +} + +// swagger:route POST /guser/allBalance guser idOfGUserAllBalance +// 获取游戏玩家总流水记录 +// Responses: +// 200:GUserAllBalanceResp + +// swagger:response GUserAllBalanceResp +type GUserAllBalanceResp struct { + // in:body + Body values.GetGameUserAllBalanceResp +} + +// swagger:parameters idOfGUserAllBalance +type GUserAllBalanceReq struct { + // in:body + Body values.GetGameUserAllBalanceReq +} + +// swagger:route POST /guser/playData guser idOfGUserPlayData +// 获取游戏玩家牌局信息 +// Responses: +// 200:GUserPlayDataResp + +// swagger:response GUserPlayDataResp +type GUserPlayDataResp struct { + // in:body + Body values.GetGameUserPlayDataResp +} + +// swagger:parameters idOfGUserPlayData +type GUserPlayDataReq struct { + // in:body + Body values.GetGameUserPlayDataReq +} + +// swagger:route POST /guser/playDetail guser idOfGUserPlayDetail +// 获取游戏玩家牌局详情 +// Responses: +// 200:GUserPlayDetailResp + +// swagger:response GUserPlayDetailResp +type GUserPlayDetailResp struct { + // in:body + Body values.GetGameUserPlayDetailResp +} + +// swagger:parameters idOfGUserPlayDetail +type GUserPlayDetailReq struct { + // in:body + Body values.GetGameUserPlayDetailReq +} + +// swagger:route POST /guser/rechargeHistory guser idOfGUserRechargeHistory +// 获取游戏玩家充值订单记录 +// Responses: +// 200:GUserRechargeHistoryResp + +// swagger:response GUserRechargeHistoryResp +type GUserRechargeHistoryResp struct { + // in:body + Body values.GetGameUserRechargeHistoryResp +} + +// swagger:parameters idOfGUserRechargeHistory +type GUserRechargeHistoryReq struct { + // in:body + Body values.GetGameUserRechargeHistoryReq +} + +// swagger:route POST /guser/withdrawHistory guser idOfGUserWithdrawHistory +// 获取游戏玩家退出订单记录 +// Responses: +// 200:GUserWithdrawHistoryResp + +// swagger:response GUserWithdrawHistoryResp +type GUserWithdrawHistoryResp struct { + // in:body + Body values.GetGameUserWithdrawHistoryResp +} + +// swagger:parameters idOfGUserWithdrawHistory +type GUserWithdrawHistoryReq struct { + // in:body + Body values.GetGameUserWithdrawHistoryReq +} + +// swagger:route POST /guser/controlBalance guser idOfGUserControlBalance +// 获取游戏玩家控杀流水记录 +// Responses: +// 200:GUserControlBalanceResp + +// swagger:response GUserControlBalanceResp +type GUserControlBalanceResp struct { + // in:body + Body values.GetGameUserControlBalanceResp +} + +// swagger:parameters idOfGUserControlBalance +type GUserControlBalanceReq struct { + // in:body + Body values.GetGameUserControlBalanceReq +} + +// swagger:route POST /guser/editGold guser idOfGUserEditGold +// 修改玩家金币 +// Responses: +// 200:GUserEditGoldResp + +// swagger:parameters idOfGUserEditGold +type GUserEditGoldReq struct { + // in:body + Body values.EditGameUserGoldReq +} + +// swagger:route POST /guser/editStatus guser idOfGUserEditStatus +// 修改玩家状态 封禁 解封 +// Responses: +// 200:GUserEditStatusResp + +// swagger:parameters idOfGUserEditStatus +type GUserEditStatusReq struct { + // in:body + Body values.EditGameUserStatusReq +} diff --git a/docs/backend/mail.go b/docs/backend/mail.go new file mode 100644 index 0000000..25146c4 --- /dev/null +++ b/docs/backend/mail.go @@ -0,0 +1,74 @@ +package backend + +import ( + "server/modules/backend/app" + "server/modules/backend/values" +) + +// swagger:route POST /mail/draftList mail idOfGetDraftList +// 配置牌组 +// Responses: +// 200:GetDraftListResp + +// swagger:response GetDraftListResp +type GetDraftListResp struct { + // in:body + Body values.MailDraftListResp +} + +// swagger:parameters idOfGetDraftList +type GetDraftListReq struct { + // in:body + Body values.MailDraftListReq +} + +// swagger:route POST /mail/draftCreate mail idOfCreateDraft +// 配置牌组 +// Responses: +// 200:CreateDraftResp + +// swagger:response CreateDraftResp +type CreateDraftResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfCreateDraft +type CreateDraftReq struct { + // in:body + Body values.MailDraftCreateReq +} + +// swagger:route POST /mail/draftEdit mail idOfEditDraft +// 配置牌组 +// Responses: +// 200:EditDraftResp + +// swagger:response EditDraftResp +type EditDraftResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfEditDraft +type EditDraftReq struct { + // in:body + Body values.MailDraftEditReq +} + +// swagger:route POST /mail/draftOpt mail idOfDraftOpt +// 配置牌组 +// Responses: +// 200:DraftOptResp + +// swagger:response DraftOptResp +type DraftOptResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfDraftOpt +type DraftOptReq struct { + // in:body + Body values.MailDraftOptReq +} diff --git a/docs/backend/power.go b/docs/backend/power.go new file mode 100644 index 0000000..88b5c75 --- /dev/null +++ b/docs/backend/power.go @@ -0,0 +1,113 @@ +package backend + +import ( + "server/modules/backend/app" + "server/modules/backend/values" +) + +// swagger:route GET /power/user/list power idOfPowerUserList +// 账号列表 +// Responses: +// 200:PowerUserListResp + +// swagger:response PowerUserListResp +type PowerUserListResp struct { + // in:body + Body values.UserListResp +} + +// swagger:route POST /power/user/add power idOfPowerUserAdd +// 新增账号 +// Responses: +// 200:PowerUserAddResp + +// swagger:response PowerUserAddResp +type PowerUserAddResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfPowerUserAdd +type PowerUserAddReq struct { + // in:body + Body values.AddUserReq +} + +// swagger:route POST /power/user/edit power idOfPowerUserEdit +// 编辑账号权限 +// Responses: +// 200:PowerUserEditResp + +// swagger:response PowerUserEditResp +type PowerUserEditResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfPowerUserEdit +type PowerUserEditReq struct { + // in:body + Body values.EditPowerReq +} + +// swagger:route POST /power/user/del power idOfPowerUserDel +// 删除账号 +// Responses: +// 200:PowerUserDelResp + +// swagger:response PowerUserDelResp +type PowerUserDelResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfPowerUserDel +type PowerUserDelReq struct { + // in:body + Body values.DelUserReq +} + +// swagger:route GET /power/role/list power idOfPowerRoleList +// 角色列表 +// Responses: +// 200:PowerRoleListResp + +// swagger:response PowerRoleListResp +type PowerRoleListResp struct { + // in:body + Body values.RoleListResp +} + +// swagger:route POST /power/role/add power idOfPowerRoleAdd +// 新增角色 +// Responses: +// 200:PowerRoleAddResp + +// swagger:response PowerRoleAddResp +type PowerRoleAddResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfPowerRoleAdd +type PowerRoleAddReq struct { + // in:body + Body values.AddRoleReq +} + +// swagger:route POST /power/role/edit power idOfPowerRoleEdit +// 编辑角色 +// Responses: +// 200:PowerRoleEditResp + +// swagger:response PowerRoleEditResp +type PowerRoleEditResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfPowerRoleEdit +type PowerRoleEditReq struct { + // in:body + Body values.EditRoleReq +} diff --git a/docs/backend/statistics.go b/docs/backend/statistics.go new file mode 100644 index 0000000..6163827 --- /dev/null +++ b/docs/backend/statistics.go @@ -0,0 +1,413 @@ +package backend + +import ( + "server/modules/backend/values" +) + +// swagger:route POST /statistics/reviewData statistics idOfReviewData +// 数据总览 +// Responses: +// 200:ReviewDataResp + +// swagger:response ReviewDataResp +type ReviewDataResp struct { + // in:body + Body values.ReviewDataResp +} + +// swagger:parameters idOfReviewData +type ReviewDataReq struct { + // in:body + Body values.ReviewDataReq +} + +// swagger:route POST /statistics/reviewChannelData statistics idOfReviewChannelData +// 数据总览渠道详细数据 +// Responses: +// 200:ReviewChannelDataResp + +// swagger:response ReviewChannelDataResp +type ReviewChannelDataResp struct { + // in:body + Body values.ReviewChannelDataResp +} + +// swagger:parameters idOfReviewChannelData +type ReviewChannelDataReq struct { + // in:body + Body values.ReviewChannelDataReq +} + +// swagger:route POST /statistics/reviewLoginWay statistics idOfReviewLoginWay +// 数据总览各登录方式人数 +// Responses: +// 200:ReviewLoginWayResp + +// swagger:response ReviewLoginWayResp +type ReviewLoginWayResp struct { + // in:body + Body values.ReviewLoginWayResp +} + +// swagger:parameters idOfReviewLoginWay +type ReviewLoginWayReq struct { + // in:body + Body values.ReviewLoginWayReq +} + +// swagger:route POST /statistics/reviewWithdarwData statistics idOfWithdrawData +// 数据总览退出人数明细 +// Responses: +// 200:ReviewWithdrawDataResp + +// swagger:response ReviewWithdrawDataResp +type ReviewWithdrawDataResp struct { + // in:body + Body values.ReviewWithdrawDataResp +} + +// swagger:parameters idOfWithdrawData +type ReviewWithdrawDataReq struct { + // in:body + Body values.ReviewWithdrawDataReq +} + +// swagger:route POST /statistics/realData statistics idOfRealData +// 实时数据 +// Responses: +// 200:RealDataResp + +// swagger:response RealDataResp +type RealDataResp struct { + // in:body + Body values.RealDataResp +} + +// swagger:parameters idOfRealData +type RealDataReq struct { + // in:body + Body values.RealDataReq +} + +// swagger:route POST /statistics/newPlayerData statistics idOfNewPlayerData +// 新增用户分析数据 +// Responses: +// 200:NewPlayerDataResp + +// swagger:response NewPlayerDataResp +type NewPlayerDataResp struct { + // in:body + Body values.NewPlayerDataResp +} + +// swagger:parameters idOfNewPlayerData +type NewPlayerDataReq struct { + // in:body + Body values.NewPlayerDataReq +} + +// swagger:route POST /statistics/gameData statistics idOfGameData +// 游戏概况分析 +// Responses: +// 200:GameDataResp + +// swagger:response GameDataResp +type GameDataResp struct { + // in:body + Body values.GameDataResp +} + +// swagger:parameters idOfGameData +type GameDataReq struct { + // in:body + Body values.GameDataReq +} + +// swagger:route POST /statistics/activePlayerData statistics idOfActivePlayerData +// 活跃用户分析 +// Responses: +// 200:ActivePlayerDataResp + +// swagger:response ActivePlayerDataResp +type ActivePlayerDataResp struct { + // in:body + Body values.ActivePlayerDataResp +} + +// swagger:parameters idOfActivePlayerData +type ActivePlayerDataReq struct { + // in:body + Body values.ActivePlayerDataReq +} + +// swagger:route POST /statistics/financialData statistics idOfFinancialData +// 游戏经济分析 +// Responses: +// 200:FinancialDataResp + +// swagger:response FinancialDataResp +type FinancialDataResp struct { + // in:body + Body values.FinancialDataResp1 +} + +// swagger:parameters idOfFinancialData +type FinancialDataReq struct { + // in:body + Body values.FinancialDataReq +} + +// swagger:route POST /statistics/financialData/robotBalanceDeatil statistics idOfFinancialDataRobotDeatil +// 游戏经济分析ai盈亏详情 +// Responses: +// 200:FinancialDataRobotDetailResp + +// swagger:response FinancialDataRobotDetailResp +type FinancialDataRobotDetailResp struct { + // in:body + Body values.FinancialDataRobotDetailResp +} + +// swagger:parameters idOfFinancialDataRobotDeatil +type FinancialDataRobotDetailReq struct { + // in:body + Body values.FinancialDataRobotDetailReq +} + +// swagger:route POST /statistics/financialData/millionBalanceDetail statistics idOfFinancialDataMillionDeatil +// 游戏经济分析百人场盈亏详情 +// Responses: +// 200:FinancialDataMillionDetailResp + +// swagger:response FinancialDataMillionDetailResp +type FinancialDataMillionDetailResp struct { + // in:body + Body values.FinancialDataMillionDetailResp +} + +// swagger:parameters idOfFinancialDataMillionDeatil +type FinancialDataMillionDetailReq struct { + // in:body + Body values.FinancialDataMillionDetailReq +} + +// swagger:route POST /statistics/financialData/tableFee statistics idOfFinancialDataTableFee +// 游戏经济分析台费详情 +// Responses: +// 200:FinancialDataTableFeeResp + +// swagger:response FinancialDataTableFeeResp +type FinancialDataTableFeeResp struct { + // in:body + Body values.FinancialDataTableFeeResp +} + +// swagger:parameters idOfFinancialDataTableFee +type FinancialDataTableFeeReq struct { + // in:body + Body values.FinancialDataTableFeeReq +} + +// swagger:route POST /statistics/financialData/playerWin statistics idOfFinancialDataPlayerWin +// 游戏经济分析系统调控回收详情 +// Responses: +// 200:FinancialDataPlayerWinResp + +// swagger:response FinancialDataPlayerWinResp +type FinancialDataPlayerWinResp struct { + // in:body + Body values.FinancialDataPlayerWinResp +} + +// swagger:parameters idOfFinancialDataPlayerWin +type FinancialDataPlayerWinReq struct { + // in:body + Body values.FinancialDataPlayerWinReq +} + +// swagger:route POST /statistics/financialData/robotWin statistics idOfFinancialDataRobotWin +// 游戏经济分析机器人回收详情 +// Responses: +// 200:FinancialDataRobotWinResp + +// swagger:response FinancialDataRobotWinResp +type FinancialDataRobotWinResp struct { + // in:body + Body values.FinancialDataRobotWinResp +} + +// swagger:parameters idOfFinancialDataRobotWin +type FinancialDataRobotWinReq struct { + // in:body + Body values.FinancialDataRobotWinReq +} + +// swagger:route POST /statistics/financialData/black statistics idOfFinancialDataBlack +// 游戏经济分析小黑屋回收详情 +// Responses: +// 200:FinancialDataBlackResp + +// swagger:response FinancialDataBlackResp +type FinancialDataBlackResp struct { + // in:body + Body values.FinancialDataBlackResp +} + +// swagger:parameters idOfFinancialDataBlack +type FinancialDataBlackReq struct { + // in:body + Body values.FinancialDataBlackReq +} + +// swagger:route POST /statistics/rechargeData statistics idOfRechargeData +// 付费分析 +// Responses: +// 200:RechargeDataResp + +// swagger:response RechargeDataResp +type RechargeDataResp struct { + // in:body + Body values.RechargeDataResp1 +} + +// swagger:parameters idOfRechargeData +type RechargeDataReq struct { + // in:body + Body values.RechargeDataReq +} + +// swagger:route POST /statistics/rechargeChannelData statistics idOfRechargeChannelData +// 付费分析渠道详细数据 +// Responses: +// 200:RechargeChannelDataResp + +// swagger:response RechargeChannelDataResp +type RechargeChannelDataResp struct { + // in:body + Body values.RechargeChannelDataResp +} + +// swagger:parameters idOfRechargeChannelData +type RechargeChannelDataReq struct { + // in:body + Body values.RechargeChannelDataReq +} + +// swagger:route POST /statistics/rechargeData/rechargeOrderList statistics idOfRechargeOrderList +// 充值订单列表 +// Responses: +// 200:RechargeOrderListResp + +// swagger:response RechargeOrderListResp +type RechargeOrderListResp struct { + // in:body + Body values.RechargeOrderListResp +} + +// swagger:parameters idOfRechargeOrderList +type RechargeOrderListReq struct { + // in:body + Body values.RechargeOrderListReq +} + +// swagger:route POST /statistics/rechargeData/withdrawOrderList statistics idOfWithdrawOrderList +// 退出订单列表 +// Responses: +// 200:WithdrawOrderListResp + +// swagger:response WithdrawOrderListResp +type WithdrawOrderListResp struct { + // in:body + Body values.WithdrawOrderListResp +} + +// swagger:parameters idOfWithdrawOrderList +type WithdrawOrderListReq struct { + // in:body + Body values.WithdrawOrderListReq +} + +// swagger:route POST /statistics/playData statistics idOfPlayData +// 用户牌局分析 +// Responses: +// 200:PlayDataResp + +// swagger:response PlayDataResp +type PlayDataResp struct { + // in:body + Body values.PlayDataResp +} + +// swagger:parameters idOfPlayData +type PlayDataReq struct { + // in:body + Body values.PlayDataReq +} + +// swagger:route POST /statistics/keepData statistics idOfKeepData +// 获取留存数据 +// Responses: +// 200:KeepDataResp + +// swagger:response KeepDataResp +type KeepDataResp struct { + // in:body + Body values.KeepDataResp +} + +// swagger:parameters idOfKeepData +type KeepDataReq struct { + // in:body + Body values.KeepDataReq +} + +// swagger:route POST /statistics/rechargeKeepData statistics idOfRechargeKeepData +// 获取留存数据 +// Responses: +// 200:RechargeKeepDataResp + +// swagger:response RechargeKeepDataResp +type RechargeKeepDataResp struct { + // in:body + Body values.KeepDataResp +} + +// swagger:parameters idOfRechargeKeepData +type RechargeKeepDataReq struct { + // in:body + Body values.KeepDataReq +} + +// swagger:route POST /statistics/withdrawList statistics idOfWithdrawList +// 获取退出统计数据 +// Responses: +// 200:WithdrawListDataResp + +// swagger:response WithdrawListDataResp +type WithdrawListDataResp struct { + // in:body + Body values.WithdrawDataResp +} + +// swagger:parameters idOfWithdrawList +type WithdrawListDataReq struct { + // in:body + Body values.WithdrawDataReq +} + +// swagger:route POST /statistics/withdrawDetail statistics idOfWithdrawDetail +// 获取个人退出详细数据 +// Responses: +// 200:WithdrawDetailResp + +// swagger:response WithdrawDetailResp +type WithdrawDetailResp struct { + // in:body + Body values.WithdrawDetailResp +} + +// swagger:parameters idOfWithdrawDetail +type WithdrawDetailReq struct { + // in:body + Body values.WithdrawDetailReq +} diff --git a/docs/backend/sys.go b/docs/backend/sys.go new file mode 100644 index 0000000..33c058a --- /dev/null +++ b/docs/backend/sys.go @@ -0,0 +1,159 @@ +package backend + +import ( + "server/modules/backend/app" + "server/modules/backend/values" +) + +// swagger:route POST /sys/whiteList/list sys idOfWhiteList +// 获取白名单 +// Responses: +// 200:WhiteListResp + +// swagger:response WhiteListResp +type WhiteListResp struct { + // in:body + Body values.WhiteListResp +} + +// swagger:parameters idOfWhiteList +type WhiteListReq struct { + // in:body + Body values.WhiteListReq +} + +// swagger:route POST /sys/whiteList/add sys idOfWhiteListAdd +// 新增白名单 +// Responses: +// 200:WhiteListAddResp + +// swagger:response WhiteListAddResp +type WhiteListAddResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfWhiteListAdd +type WhiteListAddReq struct { + // in:body + Body values.AddWhiteListReq +} + +// swagger:route POST /sys/whiteList/edit sys idOfWhiteListEdit +// 修改白名单 +// Responses: +// 200:WhiteListEditResp + +// swagger:response WhiteListEditResp +type WhiteListEditResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfWhiteListEdit +type WhiteListEditReq struct { + // in:body + Body values.EditWhiteListReq +} + +// swagger:route POST /sys/whiteList/delete sys idOfWhiteListDelete +// 删除白名单 +// Responses: +// 200:WhiteListDeleteResp + +// swagger:response WhiteListDeleteResp +type WhiteListDeleteResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfWhiteListDelete +type WhiteListDeleteReq struct { + // in:body + Body values.DeleteWhiteListReq +} + +// swagger:route POST /sys/whiteList/switch sys idOfWhiteListSwitch +// 开启/关闭白名单 +// Responses: +// 200:WhiteListSwitchResp + +// swagger:response WhiteListSwitchResp +type WhiteListSwitchResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfWhiteListSwitch +type WhiteListSwitchReq struct { + // in:body + Body values.WhiteListSwitchReq +} + +// swagger:route POST /sys/channel/add sys idOfAddChannel +// 新增渠道 +// Responses: +// 200:AddChannelResp + +// swagger:response AddChannelResp +type AddChannelResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfAddChannel +type AddChannelReq struct { + // in:body + Body values.AddChannelReq +} + +// swagger:route POST /sys/channel/edit sys idOfEditChannel +// 修改渠道 +// Responses: +// 200:EditChannelResp + +// swagger:response EditChannelResp +type EditChannelResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfEditChannel +type EditChannelReq struct { + // in:body + Body values.EditChannelReq +} + +// swagger:route POST /sys/channel/del sys idOfDelChannel +// 删除渠道 +// Responses: +// 200:DelChannelResp + +// swagger:response DelChannelResp +type DelChannelResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfDelChannel +type DelChannelReq struct { + // in:body + Body values.DelChannelReq +} + +// swagger:route POST /sys/editHistory/list sys idOfEditHistory +// 获取操作日志 +// Responses: +// 200:EditHistoryResp + +// swagger:response EditHistoryResp +type EditHistoryResp struct { + // in:body + Body values.EditHistoryListResp +} + +// swagger:parameters idOfEditHistory +type EditHistoryReq struct { + // in:body + Body values.EditHistoryListReq +} diff --git a/docs/backend/warn.go b/docs/backend/warn.go new file mode 100644 index 0000000..4bbf539 --- /dev/null +++ b/docs/backend/warn.go @@ -0,0 +1,77 @@ +package backend + +import ( + "server/call" + "server/modules/backend/app" + "server/modules/backend/values" +) + +// swagger:route GET /warn/list warn idOfWarnList +// 获取预警列表 +// Responses: +// 200:WarnListResp + +// swagger:response WarnListResp +type WarnListResp struct { + // in:body + Body values.WarnListResp +} + +// swagger:route POST /warn/add warn idOfWarnAdd +// 添加预警 +// condition以键值对形式发送,每种预警需包含的字段在condition xx里 +// Responses: +// 200:WarnAddResp + +// swagger:response WarnAddResp +type WarnAddResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfWarnAdd +type WarnAddReq struct { + // in:body + Body struct { + Req values.AddWarnReq + ConditionRecharge call.SysWarnRecharge + ConditionWithdraw call.SysWarnWithdraw + ConditionWithdrawStorage call.SysWarnWithdrawStorage + ConditionWithdrawExamine call.SysWarnWithdrawExamine + ConditionActivity call.SysWarnActivity + } +} + +// swagger:route POST /warn/edit warn idOfWarnEdit +// 修改预警 +// Responses: +// 200:WarnEditResp + +// swagger:response WarnEditResp +type WarnEditResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfWarnEdit +type WarnEditReq struct { + // in:body + Body values.EditWarnReq +} + +// swagger:route POST /warn/del warn idOfWarnDel +// 删除预警 +// Responses: +// 200:WarnDelResp + +// swagger:response WarnDelResp +type WarnDelResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfWarnDel +type WarnDelReq struct { + // in:body + Body values.DelWarnReq +} diff --git a/docs/web/account.go b/docs/web/account.go new file mode 100644 index 0000000..017802e --- /dev/null +++ b/docs/web/account.go @@ -0,0 +1,244 @@ +package web + +import ( + "server/modules/web/app" + "server/modules/web/values" +) + +// swagger:route POST /account/guestLogin account idOfGuestLogin +// 游客登录 +// Responses: +// 200:GuestLoginResp + +// swagger:response GuestLoginResp +type GuestLoginResp struct { + // in:body + Body values.LoginResp +} + +// swagger:parameters idOfGuestLogin +type GuestLoginReq struct { + // in:body + Body values.GuestLoginReq +} + +// swagger:route POST /account/tokenLogin account idOfTokenLogin +// token快速登录 +// Responses: +// 200:TokenLogin + +// swagger:response TokenLogin +type TokenLoginResp struct { + // in:body + Body values.LoginResp +} + +// swagger:parameters idOfTokenLogin +type TokenLoginReq struct { + // in:body + Body values.TokenLoginReq +} + +// swagger:route POST /account/phoneCode/get account idOfGetPhoneCode +// 获取手机验证码 +// Responses: +// 200:GetPhoneCodeResp + +// swagger:response GetPhoneCodeResp +type GetPhoneCodeResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfGetPhoneCode +type GetPhoneCodeReq struct { + // in:body + Body values.PhoneCodeReq +} + +// swagger:route POST /account/phone/regist account idOfPhoneRegist +// 手机号注册 +// Responses: +// 200:PhoneRegistResp + +// swagger:parameters idOfPhoneRegist +type PhoneRegistReq struct { + // in:body + Body values.PhoneRegistReq +} + +// swagger:response PhoneRegistResp +type PhoneRegistResp struct { + // in:body + Body values.LoginResp +} + +// swagger:route POST /account/phone/login account idOfPhoneLogin +// 手机号登录 +// Responses: +// 200:PhoneLoginResp + +// swagger:parameters idOfPhoneLogin +type PhoneLoginReq struct { + // in:body + Body values.PhoneLoginReq +} + +// swagger:response PhoneLoginResp +type PhoneLoginResp struct { + // in:body + Body values.LoginResp +} + +// swagger:route POST /account/phone/resetPass account idOfPhoneResetPass +// 手机号重置密码 +// Responses: +// 200:PhoneResetPassResp + +// swagger:parameters idOfPhoneResetPass +type PhoneResetPassReq struct { + // in:body + Body values.PhoneResetPassReq +} + +// swagger:response PhoneResetPassResp +type PhoneResetPassResp struct { + // in:body + Body values.LoginResp +} + +// swagger:route POST /account/phoneCode/login account idOfPhoneLogin +// 手机验证码登录 +// Responses: +// 200:PhoneCodeLoginResp + +// swagger:response PhoneCodeLoginResp +type PhoneCodeLoginResp struct { + // in:body + Body values.LoginResp +} + +// swagger:parameters idOfPhoneLogin +type PhoneCodeLoginReq struct { + // in:body + Body values.PhoneCodeLoginReq +} + +// swagger:route POST /account/email/code account idOfGetEmailCode +// 获取邮箱验证码 +// Responses: +// 200:GetEmailCodeResp + +// swagger:response GetEmailCodeResp +type GetEmailCodeResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfGetEmailCode +type GetEmailCodeReq struct { + // in:body + Body values.EmailCodeReq +} + +// swagger:route POST /account/email/regist account idOfEmailRegist +// 邮箱注册 +// Responses: +// 200:EmailRegistResp + +// swagger:response EmailRegistResp +type EmailRegistResp struct { + // in:body + Body values.LoginResp +} + +// swagger:parameters idOfEmailRegist +type EmailRegistReq struct { + // in:body + Body values.EmailRegistReq +} + +// swagger:route POST /account/email/login account idOfEmailLogin +// 邮箱登录 +// Responses: +// 200:EmailLoginResp + +// swagger:response EmailLoginResp +type EmailLoginResp struct { + // in:body + Body values.LoginResp +} + +// swagger:parameters idOfEmailLogin +type EmailLoginReq struct { + // in:body + Body values.EmailLoginReq +} + +// swagger:route POST /account/email/resetPass account idOfEmailResetPass +// 邮箱密码重置 +// Responses: +// 200:EmailResetPassResp + +// swagger:response EmailResetPassResp +type EmailResetPassResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfEmailResetPass +type EmailResetPassReq struct { + // in:body + Body values.EmailResetPassReq +} + +// swagger:route POST /account/regist account idOfAccountRegist +// 用户名注册 +// Responses: +// 200:AccountRegistResp + +// swagger:response AccountRegistResp +type AccountRegistResp struct { + // in:body + Body values.LoginResp +} + +// swagger:parameters idOfAccountRegist +type AccountRegistReq struct { + // in:body + Body values.AccountRegistReq +} + +// swagger:route POST /account/login account idOfAccountLogin +// 用户名登录 +// Responses: +// 200:AccountLoginResp + +// swagger:response AccountLoginResp +type AccountLoginResp struct { + // in:body + Body values.LoginResp +} + +// swagger:parameters idOfAccountLogin +type AccountLoginReq struct { + // in:body + Body values.AccountLoginReq +} + +// swagger:route POST /account/resetPass account idOfAccountResetPass +// 用户名重置密码 +// Responses: +// 200:AccountResetPassResp + +// swagger:response AccountResetPassResp +type AccountResetPassResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfAccountResetPass +type AccountResetPassReq struct { + // in:body + Body values.AccountResetPassReq +} diff --git a/docs/web/activity.go b/docs/web/activity.go new file mode 100644 index 0000000..2da873d --- /dev/null +++ b/docs/web/activity.go @@ -0,0 +1,339 @@ +package web + +import ( + "server/modules/web/app" + "server/modules/web/values" +) + +// swagger:route POST /activity/all activity idOfActivityAll +// 获取所有活动信息 +// Responses: +// 200:GetAllActivityResp + +// swagger:response GetAllActivityResp +type GetAllActivityResp struct { + // in:body + Body values.GetAllActivityResp +} + +// swagger:route POST /activity/appSpin/info activity idOfActivityAppSpinInfo +// 获取下载app奖励转盘信息 +// Responses: +// 200:GetActivityAppSpinInfoResp + +// swagger:response GetActivityAppSpinInfoResp +type GetActivityAppSpinInfoResp struct { + // in:body + Body values.ActivityAppSpinInfoResp +} + +// swagger:route POST /activity/appSpin/draw activity idOfActivityAppSpinDraw +// 领取下载app奖励 +// Responses: +// 200:ActivityAppSpinDrawResp + +// swagger:response ActivityAppSpinDrawResp +type ActivityAppSpinDrawResp struct { + // in:body + Body values.ActivityAppSpinDrawResp +} + +// swagger:route POST /activity/pdd/info activity idOfActivityPddInfo +// 拉取拼多多信息 +// Responses: +// 200:ActivityPddInfoResp + +// swagger:response ActivityPddInfoResp +type ActivityPddInfoResp struct { + // in:body + Body values.ActivityPddInfoResp +} + +// swagger:route POST /activity/pdd/spin activity idOfActivityPddSpin +// 拼多多转盘 +// Responses: +// 200:ActivityPddSpinResp + +// swagger:response ActivityPddSpinResp +type ActivityPddSpinResp struct { + // in:body + Body values.ActivityPddSpinResp +} + +// swagger:route POST /activity/pdd/withdraw activity idOfActivityPddWithdraw +// 拼多多 +// Responses: +// 200:ActivityPddWithdrawResp + +// swagger:response ActivityPddWithdrawResp +type ActivityPddWithdrawResp struct { + // in:body + Body app.R +} + +// swagger:route POST /activity/pdd/newReference activity idOfActivityPddNewReferenceReq +// 拼多多新分享记录 +// Responses: +// 200:ActivityPddNewReferenceResp + +// swagger:response ActivityPddNewReferenceResp +type ActivityPddNewReferenceResp struct { + // in:body + Body values.ActivityPddNewReferenceResp +} + +// swagger:route POST /activity/pdd/reference activity idOfActivityPddReferenceReq +// 拼多多分享记录 +// Responses: +// 200:ActivityPddReferenceResp + +// swagger:response ActivityPddReferenceResp +type ActivityPddReferenceResp struct { + // in:body + Body values.ActivityPddReferenceResp +} + +// swagger:parameters idOfActivityPddReferenceReq +type ActivityPddReferenceReq struct { + // in:body + Body values.ActivityPddReferenceReq +} + +// swagger:route POST /activity/freeSpin/info activity idOfActivityFreeSpinInfo +// 获取免费转盘信息 +// Responses: +// 200:ActivityFreeSpinInfoResp + +// swagger:response ActivityFreeSpinInfoResp +type ActivityFreeSpinInfoResp struct { + // in:body + Body values.ActivityFreeSpinInfoResp +} + +// swagger:route POST /activity/freeSpin/draw activity idOfActivityFreeSpinDraw +// 领取免费转盘奖励 +// Responses: +// 200:ActivityFreeSpinDrawResp + +// swagger:response ActivityFreeSpinDrawResp +type ActivityFreeSpinDrawResp struct { + // in:body + Body values.ActivityFreeSpinDrawResp +} + +// swagger:route POST /activity/firstRechargeBack/info activity idOfFirstRechargeBackInfoReq +// 首充返还活动 +// Responses: +// 200:ActivityFirstRechargeBackInfoResp + +// swagger:response ActivityFirstRechargeBackInfoResp +type ActivityFirstRechargeBackInfoResp struct { + // in:body + Body values.ActivityFirstRechargeBackInfoResp +} + +// swagger:route POST /activity/firstRechargeBack/draw activity idOfFirstRechargeBackDrawReq +// 领取首充返还活动 +// Responses: +// 200:ActivityFirstRechargeBackDrawResp + +// swagger:response ActivityFirstRechargeBackDrawResp +type ActivityFirstRechargeBackDrawResp struct { + // in:body + Body app.R +} + +// swagger:route POST /activity/luckyCode/info activity idOfLuckyCodeInfoReq +// 兑换码活动 +// Responses: +// 200:ActivityLuckyCodeInfoResp + +// swagger:response ActivityLuckyCodeInfoResp +type ActivityLuckyCodeInfoResp struct { + // in:body + Body values.ActivityLuckyCodeInfoResp +} + +// swagger:route POST /activity/luckyCode/draw activity idOfLuckyCodeDrawReq +// 领取兑换码活动 +// Responses: +// 200:ActivityLuckyCodeDrawResp + +// swagger:response ActivityLuckyCodeDrawResp +type ActivityLuckyCodeDrawResp struct { + // in:body + Body values.ActivityLuckyCodeDrawResp +} + +// swagger:parameters idOfLuckyCodeDrawReq +type ActivityLuckyCodeDrawReq struct { + // in:body + Body values.ActivityLuckyCodeDrawReq +} + +// swagger:route POST /activity/sign/info activity idOfSignInfoReq +// 签到活动 +// Responses: +// 200:ActivitySignInfoResp + +// swagger:response ActivitySignInfoResp +type ActivitySignInfoResp struct { + // in:body + Body values.ActivitySignInfoResp +} + +// swagger:route POST /activity/sign/draw activity idOfSignDrawReq +// 兑换码活动 +// Responses: +// +// 200:ActivitySignDrawResp + +// swagger:response ActivitySignDrawResp +type ActivitySignDrawResp struct { + // in:body + Body app.R +} + +// swagger:route POST /activity/breakGift/info activity idOfActivityBreakGiftReq +// 破产礼包活动 +// Responses: +// +// 200:ActivityBreakGiftInfoResp + +// swagger:response ActivityBreakGiftInfoResp +type ActivityBreakGiftInfoResp struct { + // in:body + Body values.ActivityBreakGiftInfoResp +} + +// swagger:route POST /activity/weekCard/info activity idOfActivityWeekCardInfoReq +// 周卡活动 +// Responses: +// +// 200:ActivityWeekCardInfoResp + +// swagger:response ActivityWeekCardInfoResp +type ActivityWeekCardInfoResp struct { + // in:body + Body values.ActivityWeekCardInfoResp +} + +// swagger:route POST /activity/weekCard/draw activity idOfActivityWeekCardDrawReq +// 周卡活动 +// Responses: +// +// 200:ActivityWeekCardDrawResp + +// swagger:response ActivityWeekCardDrawResp +type ActivityWeekCardDrawResp struct { + // in:body + Body values.ActivityWeekCardDrawResp +} + +// swagger:parameters idOfActivityWeekCardDrawReq +type ActivityWeekCardDrawReq struct { + // in:body + Body values.ActivityWeekCardDrawReq +} + +// swagger:route POST /activity/slots/info activity idOfActivitySlotsInfoReq +// slots奖池活动 +// Responses: +// +// 200:ActivitySlotsInfoResp + +// swagger:response ActivitySlotsInfoResp +type ActivitySlotsInfoResp struct { + // in:body + Body values.ActivitySlotsResp +} + +// swagger:route POST /activity/slots/draw activity idOfActivitySlotsDrawReq +// slots奖池活动 +// Responses: +// +// 200:ActivitySlotsDrawResp + +// swagger:response ActivitySlotsDrawResp +type ActivitySlotsDrawResp struct { + // in:body + Body values.ActivitySlotsDrawResp +} + +// swagger:route POST /activity/slots/drawLast activity idOfActivitySlotsDrawLastReq +// slots奖池活动领取昨日奖励 +// Responses: +// +// 200:ActivitySlotsDrawLastResp + +// swagger:response ActivitySlotsDrawLastResp +type ActivitySlotsDrawLastResp struct { + // in:body + Body values.ActivitySlotsDrawLastResp +} + +// swagger:route POST /activity/luckyShop/info activity idOfActivityLuckyShopInfoReq +// 幸运商店活动 +// Responses: +// +// 200:ActivityLuckShopInfoResp + +// swagger:response ActivityLuckShopInfoResp +type ActivityLuckShopInfoResp struct { + // in:body + Body values.ActivityLuckyShopResp +} + +// swagger:route POST /activity/sevenDayBox/info activity idOfActivitySevenDayBoxInfoReq +// 7日签到宝箱信息 +// Responses: +// +// 200:ActivitySevenDayBoxInfoResp + +// swagger:response ActivitySevenDayBoxInfoResp +type ActivitySevenDayBoxInfoResp struct { + // in:body + Body values.ActivitySevenDayBoxInfoResp +} + +// swagger:route POST /activity/sevenDayBox/draw activity idOfActivitySevenDayBoxDrawReq +// 7日签到宝箱领取奖励 +// Responses: +// +// 200:ActivitySevenDayBoxDrawResp + +// swagger:response ActivitySevenDayBoxDrawResp +type ActivitySevenDayBoxDrawResp struct { + // in:body + Body values.ActivitySevenDayBoxDrawResp +} + +// swagger:route POST /activity/super/info activity idOfActivitySuperInfoReq +// 超级1+2活动信息 +// Responses: +// +// 200:ActivitySuperInfoResp + +// swagger:response ActivitySuperInfoResp +type ActivitySuperInfoResp struct { + // in:body + Body values.ActivitySuperInfoResp +} + +// swagger:route POST /activity/super/draw activity idOfActivitySuperDrawReq +// 超级1+2开奖池 +// Responses: +// +// 200:ActivitySuperDrawResp + +// swagger:response ActivitySuperDrawResp +type ActivitySuperDrawResp struct { + // in:body + Body values.ActivitySuperDrawResp +} + +// swagger:parameters idOfActivitySuperDrawReq +type ActivitySuperDrawReq struct { + // in:body + Body values.ActivitySuperDrawResp +} diff --git a/docs/web/balance.go b/docs/web/balance.go new file mode 100644 index 0000000..2047a56 --- /dev/null +++ b/docs/web/balance.go @@ -0,0 +1,113 @@ +package web + +import ( + "server/modules/backend/app" + "server/modules/web/values" +) + +// swagger:route POST /balance/recharge/info balance idOfRechargeInfo +// 支付界面信息 +// Responses: +// 200:RechargeInfoResp + +// swagger:response RechargeInfoResp +type RechargeInfoResp struct { + // in:body + Body values.RechargeInfoResp +} + +// swagger:route POST /balance/recharge/history balance idOfRechargeHistory +// 充值记录 +// Responses: +// 200:RechargeHistoryResp + +// swagger:response RechargeHistoryResp +type RechargeHistoryResp struct { + // in:body + Body values.RechargeHistoryResp +} + +// swagger:parameters idOfRechargeHistory +type RechargeHistoryReq struct { + // in:body + Body values.RechargeHistoryReq +} + +// swagger:route POST /balance/recharge/do balance idOfRechargeDo +// 支付 +// Responses: +// 200:RechargeDoResp + +// swagger:response RechargeDoResp +type RechargeDoResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfRechargeDo +type RechargeDoReq struct { + // in:body + Body values.RechargeReq +} + +// swagger:route POST /balance/withdraw/info balance idOfWithdrawInfo +// 退出列表 +// Responses: +// 200:WithdrawInfoResp + +// swagger:response WithdrawInfoResp +type WithdrawInfoResp struct { + // in:body + Body values.WithDrawInfoResp +} + +// swagger:route POST /balance/withdraw/do balance idOfWithdrawDo +// 退出 +// Responses: +// 200:WithdrawDoResp + +// swagger:parameters idOfWithdrawDo +type WithdrawDoReq struct { + // in:body + Body values.WithdrawReq +} + +// swagger:response WithdrawDoResp +type WithdrawDoResp struct { + // in:body + Body app.R +} + +// swagger:route POST /balance/withdraw/block/do balance idOfWithdrawBlockDo +// 退出 +// Responses: +// 200:WithdrawBlockDoResp + +// swagger:parameters idOfWithdrawBlockDo +type WithdrawBlockDoReq struct { + // in:body + Body values.WithdrawBlockReq +} + +// swagger:response WithdrawBlockDoResp +type WithdrawBlockDoResp struct { + // in:body + Body app.R +} + +// swagger:route POST /balance/withdraw/history balance idOfWithdrawHistory +// 退出记录 +// Responses: +// 200:WithdrawHistoryResp + +// swagger:response WithdrawHistoryResp +type WithdrawHistoryResp struct { + // in:body + Body values.WithdrawHistoryResp +} + +// swagger:parameters idOfWithdrawHistory +type WithdrawHistoryReq struct { + // in:body + Body values.WithdrawHistoryReq +} diff --git a/docs/web/docs.go b/docs/web/docs.go new file mode 100644 index 0000000..07a1b7c --- /dev/null +++ b/docs/web/docs.go @@ -0,0 +1,18 @@ +// Package game server API. +// 服务器web协议 +// +// Host: 47.106.150.32:7615 +// Version: 0.0.1 +// +// swagger:meta +package web + +import ( + "server/modules/web/values" +) + +// swagger:parameters idOfVerifyPhoneCode +type PhoneCodeverifyReq struct { + // in:body + Body values.PhoneCodeLoginReq +} diff --git a/docs/web/firstpage.go b/docs/web/firstpage.go new file mode 100644 index 0000000..923b9d1 --- /dev/null +++ b/docs/web/firstpage.go @@ -0,0 +1,20 @@ +package web + +import "server/modules/web/values" + +// swagger:route POST /firstpage firstpage idOfFirstPage +// 首页信息 +// Responses: +// 200:FirstPageResp + +// swagger:response FirstPageResp +type FirstPageResp struct { + // in:body + Body values.FirstPageResp +} + +// swagger:parameters idOfFirstPage +type FirstPageReq struct { + // in:body + Body values.FirstPageReq +} diff --git a/docs/web/game.go b/docs/web/game.go new file mode 100644 index 0000000..e6bc7b0 --- /dev/null +++ b/docs/web/game.go @@ -0,0 +1,65 @@ +package web + +import "server/modules/web/values" + +// swagger:route POST /game/list game idOfGameList +// 请求游戏列表 +// Responses: +// 200:GameListResp + +// swagger:response GameListResp +type GameListResp struct { + // in:body + Body values.GameListResp +} + +// swagger:parameters idOfGameList +type GameListReq struct { + // in:body + Body values.GameListReq +} + +// swagger:route POST /game/enter game idOfEnterGame +// 进入游戏 +// Responses: +// 200:EnterGameResp + +// swagger:response EnterGameResp +type EnterGameResp struct { + // in:body + Body values.EnterGameResp +} + +// swagger:parameters idOfEnterGame +type EnterGameReq struct { + // in:body + Body values.EnterGameReq +} + +// swagger:route POST /game/history game idOfGameHistory +// 游戏历史 +// Responses: +// 200:GameHistoryResp + +// swagger:response GameHistoryResp +type GameHistoryResp struct { + // in:body + Body values.GameHistoryResp +} + +// swagger:parameters idOfGameHistory +type GameHistoryReq struct { + // in:body + Body values.GameHistoryReq +} + +// swagger:route POST /game/profile game idOfGameProfile +// 游戏生涯数据 +// Responses: +// 200:GameProfileResp + +// swagger:response GameProfileResp +type GameProfileResp struct { + // in:body + Body values.GameProfileResp +} diff --git a/docs/web/mail.go b/docs/web/mail.go new file mode 100644 index 0000000..a8b5f58 --- /dev/null +++ b/docs/web/mail.go @@ -0,0 +1,68 @@ +package web + +import ( + "server/modules/web/app" + "server/modules/web/values" +) + +// swagger:route POST /mail/list mail idOfMailList +// 邮箱 +// Responses: +// 200:MailListResp + +// swagger:response MailListResp +type MailListResp struct { + // in:body + Body values.MailListResp +} + +// swagger:parameters idOfMailList +type MailListReq struct { + // in:body + Body values.MailListReq +} + +// swagger:route POST /mail/read mail idOfReadMail +// 阅读邮件 +// Responses: +// 200:ReadMailResp + +// swagger:response ReadMailResp +type ReadMailResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfReadMail +type ReadMailReq struct { + // in:body + Body values.ReadMailReq +} + +// swagger:route POST /mail/delete mail idOfDeleteMail +// 删除邮件 +// Responses: +// 200:DeleteMailResp + +// swagger:response DeleteMailResp +type DeleteMailResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfDeleteMail +type DeleteMailReq struct { + // in:body + Body values.DeleteMailReq +} + +// swagger:route POST /mail/deleteAll mail idOfDeleteAllMail +// 删除所有邮件 +// Responses: +// 200:DeleteAllMailResp + +// swagger:response DeleteAllMailResp +type DeleteAllMailResp struct { + // in:body + Body app.R +} diff --git a/docs/web/share.go b/docs/web/share.go new file mode 100644 index 0000000..1a238d9 --- /dev/null +++ b/docs/web/share.go @@ -0,0 +1,93 @@ +package web + +import "server/modules/web/values" + +// swagger:route POST /share/info share idOfShareInfo +// 分享 +// Responses: +// 200:ShareInfoResp + +// swagger:response ShareInfoResp +type ShareInfoResp struct { + // in:body + Body values.ShareInfoResp +} + +// swagger:route POST /share/platform share idOfSharePlatformInfo +// 分享平台总数据 +// Responses: +// 200:SharePlatformInfoResp + +// swagger:response SharePlatformInfoResp +type SharePlatformInfoResp struct { + // in:body + Body values.SharePlatformResp +} + +// swagger:route POST /share/withdraw share idOfShareWithdraw +// 分享赠送 +// Responses: +// 200:ShareWithdrawResp + +// swagger:parameters idOfShareWithdraw +type ShareWithdrawReq struct { + // in:body + Body values.ShareWithdrawReq +} + +// swagger:response ShareWithdrawResp +type ShareWithdrawResp struct { + // in:body + Body values.ShareWithdrawResp +} + +// swagger:route POST /share/reference share idOfShareReference +// 分享记录 +// Responses: +// 200:ShareReferenceResp + +// swagger:response ShareReferenceResp +type ShareReferenceResp struct { + // in:body + Body values.ShareReferenceResp +} + +// swagger:parameters idOfShareReference +type ShareReferenceReq struct { + // in:body + Body values.ShareReferenceReq +} + +// swagger:route POST /share/report share idOfShareReport +// 分享报告 +// Responses: +// 200:ShareReportResp + +// swagger:response ShareReportResp +type ShareReportResp struct { + // in:body + Body values.ShareReportResp +} + +// swagger:parameters idOfShareReport +type ShareReportReq struct { + // in:body + Body values.ShareReportReq +} + +// swagger:route POST /share/transfer share idOfShareTransfer +// 分享转账 +// Responses: +// 200:ShareTransferResp + +// swagger:response ShareTransferResp +type ShareTransferResp struct { + // in:body + Body values.ShareTransferResp +} + +// swagger:parameters idOfShareTransfer +type ShareTransferReq struct { + // in:body + Body values.ShareTransferReq +} diff --git a/docs/web/sys.go b/docs/web/sys.go new file mode 100644 index 0000000..1e63cf0 --- /dev/null +++ b/docs/web/sys.go @@ -0,0 +1,22 @@ +package web + +import ( + "server/modules/web/values" +) + +// swagger:route POST /sys/config system idOfSysConfig +// 获取服务器配置信息 +// Responses: +// 200:SysConfigResp + +// swagger:response SysConfigResp +type SysConfigResp struct { + // in:body + Body values.SysConfigResp +} + +// swagger:parameters idOfSysConfig +type SysConfigReq struct { + // in:body + Body values.SysConfigReq +} diff --git a/docs/web/task.go b/docs/web/task.go new file mode 100644 index 0000000..20f64a9 --- /dev/null +++ b/docs/web/task.go @@ -0,0 +1,34 @@ +package web + +import ( + "server/modules/web/app" + handler "server/modules/web/handler" +) + +// swagger:route POST /task/info balance idOfTaskInfo +// 任务信息 +// Responses: +// 200:TaskInfoResp + +// swagger:response TaskInfoResp +type TaskInfoResp struct { + // in:body + Body handler.TaskInfoResp +} + +// swagger:route POST /task/draw balance idOfTaskDraw +// 领取任务奖励 +// Responses: +// 200:TaskInfoResp + +// swagger:response TaskDrawResp +type TaskDrawResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfTaskDraw +type TaskDrawReq struct { + // in:body + Body handler.DrawTaskReq +} diff --git a/docs/web/user.go b/docs/web/user.go new file mode 100644 index 0000000..5049cec --- /dev/null +++ b/docs/web/user.go @@ -0,0 +1,57 @@ +package web + +import ( + "server/modules/web/app" + "server/modules/web/values" +) + +// swagger:route POST /user/info/edit user idOfEditUserInfo +// 支付 +// Responses: +// 200:EditUserInfoResp + +// swagger:response EditUserInfoResp +type EditUserInfoResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfEditUserInfo +type EditUserInfoReq struct { + // in:body + Body values.EditUserInfoReq +} + +// swagger:route POST /user/info/get user idOfGetUserInfo +// 查询用户信息 +// Responses: +// 200:GetUserInfoResp + +// swagger:response GetUserInfoResp +type GetUserInfoResp struct { + // in:body + Body values.UserInfoResp +} + +// swagger:parameters idOfGetUserInfo +type GetUserInfoReq struct { + // in:body + Body values.GetUserInfoReq +} + +// swagger:route POST /user/feedback user idOfFeedback +// 反馈 +// Responses: +// 200:FeedbackResp + +// swagger:response FeedbackResp +type FeedbackResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfFeedback +type FeedbackReq struct { + // in:body + Body values.FeedbackReq +} diff --git a/docs/web/vip.go b/docs/web/vip.go new file mode 100644 index 0000000..f55c014 --- /dev/null +++ b/docs/web/vip.go @@ -0,0 +1,51 @@ +package web + +import ( + "server/modules/web/app" + "server/modules/web/values" +) + +// swagger:route POST /vip/info vip idOfGetVipInfo +// vip信息 +// Responses: +// 200:GetVipInfoResp + +// swagger:response GetVipInfoResp +type GetVipInfoResp struct { + // in:body + Body values.VipInfoResp +} + +// swagger:parameters idOfGetVipInfo +type GetVipInfoReq struct { + // in:body + Body values.VipInfoReq +} + +// swagger:route POST /vip/drawBonus vip idOfDrawBonus +// vip领取bonus +// Responses: +// 200:DrawBonusResp + +// swagger:response DrawBonusResp +type DrawBonusResp struct { + // in:body + Body app.R +} + +// swagger:parameters idOfDrawBonus +type DrawBonusReq struct { + // in:body + Body values.DrawVipBonusReq +} + +// swagger:route POST /vip/drawCashback vip idOfDrawCashback +// vip领取cashback +// Responses: +// 200:DrawCashbackResp + +// swagger:response DrawCashbackResp +type DrawCashbackResp struct { + // in:body + Body app.R +} diff --git a/fieldalignment.sh b/fieldalignment.sh new file mode 100644 index 0000000..bf8c727 --- /dev/null +++ b/fieldalignment.sh @@ -0,0 +1,48 @@ +#! /bin/bash +function scandir() { + local cur_dir parent_dir workdir + workdir=$1 + cd ${workdir} + if [ ${workdir} = "/" ] + then + cur_dir="" + else + cur_dir=$(pwd) + fi + + for dirlist in $(ls ${cur_dir}) + do + if [[ ${dirlist} = bin || ${dirlist} = *test* || ${dirlist} = db || ${dirlist} = docs || ${dirlist} = pb || ${dirlist} = tools ]] || [[ ! -d ${dirlist} ]] + then + # echo 'passing' ${dirlist} + continue + fi + if test -d ${dirlist};then + echo 'checking' ${cur_dir}/${dirlist} + fieldalignment -fix ${cur_dir}/${dirlist} + cd ${dirlist} + scandir ${cur_dir}/${dirlist} + cd .. + # else + # c=${cur_dir}/${dirlist} + # echo $c + # if [[ $c = *.go ]] + # then + # echo 'checking' ${cur_dir}/${dirlist} + # fieldalignment ${cur_dir}/${dirlist} + # fi + fi + done +} + +if test -d $1 +then + scandir $1 +elif test -f $1 +then + echo "you input a file but not a directory,pls reinput and try again" + exit 1 +else + echo "the Directory isn't exist which you input,pls input a new one!!" + exit 1 +fi \ No newline at end of file diff --git a/game/card.go b/game/card.go new file mode 100644 index 0000000..5ea0bd0 --- /dev/null +++ b/game/card.go @@ -0,0 +1,136 @@ +package game + +import ( + "math/rand" +) + +const ( + ColorDiamonds = iota + ColorClubs + ColorHearts + ColorSpades +) + +// 牌库 +type CardLevelType int + +const ( + CardLevelA CardLevelType = iota + 1 + CardLevelB + CardLevelS +) + +var ( + AllNormalCards = [52]int{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, // 方片 + + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, // 梅花 + + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, // 红桃 + + 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, // 黑桃 + } + Jokers = []int{70, 71} // 两张joker牌 + // 包含一张王 + AllFullCards = [53]int{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, // 方片 + + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, // 梅花 + + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, // 红桃 + + 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, // 黑桃 + + 70, // 王 + } + // 包含两张王 + AllFullGhostCards = [54]int{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, // 方片 + + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, // 梅花 + + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, // 红桃 + + 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, // 黑桃 + + 70, 71, // 王 + } + AllTwoFullCards = [106]int{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, // 方片 + + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, // 梅花 + + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, // 红桃 + + 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, // 黑桃 + + 70, // 王 + + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, // 方片 + + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, // 梅花 + + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, // 红桃 + + 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, // 黑桃 + + 71, // 王 + } +) + +func IsValidCard(c int) bool { + for _, v := range AllFullGhostCards { + if v == c { + return true + } + } + return false +} + +func GetRandomCard(num int) []int { + ret := []int{} + seq := rand.Perm(len(AllNormalCards)) + for i := 0; i < num; i++ { + ret = append(ret, AllNormalCards[seq[i]]) + } + return ret +} + +func CompaireTwoCard(a, b int) bool { + valA := GetCardCompaireValue(a) + valB := GetCardCompaireValue(b) + if valA > valB { + return true + } + if valA == valB { + colA := GetCardColor(a) + colB := GetCardColor(b) + return colA > colB + } + return false +} + +func ShuffleCards(cards []int) []int { + indexs := rand.Perm(len(cards)) + c := make([]int, len(cards)) + for i, v := range indexs { + c[i] = cards[v] + } + return c +} + +func GetCardValue(card int) int { + return card & 0x0F +} + +func GetCardCompaireValue(card int) int { + v := GetCardValue(card) + if v == 1 { + v = 14 + } + return v +} + +func GetCardColor(card int) int { + return card / 16 +} diff --git a/game/handler.go b/game/handler.go new file mode 100644 index 0000000..c13d9a6 --- /dev/null +++ b/game/handler.go @@ -0,0 +1,103 @@ +package game + +import ( + "server/call" + "server/common" + "server/pb" + + "github.com/liangdas/mqant/gate" + "github.com/liangdas/mqant/log" +) + +func DisConnect(data *pb.ClientDisConnectNotify) { + p := GetPlayer(int(data.UserID)) + if p == nil { + return + } + p.T.PutQueue("nf", func() { + p.Disconnect() + }) +} + +func OptPlayer(data *pb.InnerOptPlayer) { + p := GetPlayer(int(data.UID)) + if p == nil { + return + } + switch data.Opt { + case common.OptPlayerTypeKick: + p.T.PutQueue("nf", func() { + p.Disconnect() + }) + } +} + +func OnTableInfo(session gate.Session, req *pb.GameCommonReq) (string, error) { + p := FindPlayer(session) + if p == nil { + log.Error("player %v not found", session.GetUserID()) + return "", nil + } + p.T.PutQueue("nf", func() { + p.SubPlayer.TableInfo() + }) + return "", nil +} + +func Enter(session gate.Session, req *pb.GameMsgEnterGameReq) (string, error) { + p := FindPlayer(session) + if p == nil { + uid := session.GetUserIDInt64() + if uid <= 0 { + return "", nil + } + rid := findRoom(int(req.SubID)) + gameRoom := GetRoom(rid) + if gameRoom == nil { + return "", nil + } + gameRoom.Enter(int(uid), session, common.CurrencyType(req.CurrencyType)) + return "", nil + } + p.T.PutQueue("nf", func() { + p.Send(int(pb.GameProtocol_EnterGameResp), &pb.GameCommonResp{}) + p.SubPlayer.TableInfo() + }) + return "", nil +} + +func findRoom(room int) (roomID int) { + list := call.GetConfigGameRoom(ThisGameID) + practice := []*common.ConfigGameRoom{} + for i := len(list) - 1; i >= 0; i-- { + one := list[i] + rid := one.RoomID + if room > 0 && rid != room { + continue + } + if !one.Open { + continue + } + if one.RoomType == RoomTypePractice { + practice = append(practice, one) + continue + } + return rid + } + // 以上找不到合适的,进入练习场 + for _, v := range practice { + return v.RoomID + } + return +} + +func OnLeave(session gate.Session, req *pb.GameCommonReq) (string, error) { + p := FindPlayer(session) + if p == nil { + return "", nil + } + p.T.PutQueue("nf", func() { + p.Leave() + }) + return "", nil +} diff --git a/game/handler_million.go b/game/handler_million.go new file mode 100644 index 0000000..92caa0d --- /dev/null +++ b/game/handler_million.go @@ -0,0 +1,44 @@ +package game + +import ( + "server/pb" + + "github.com/liangdas/mqant/gate" +) + +func OnMillionBet(session gate.Session, req *pb.GameMsgBetReq) (string, error) { + p := FindPlayer(session) + if p == nil { + return "", nil + } + p.T.PutQueue("nf", func() { + p.MillionBet(req.Area, req.Amount) + }) + return "", nil +} + +func OnHistory(session gate.Session, req *pb.GameCommonReq) (string, error) { + p := FindPlayer(session) + if p == nil { + return "", nil + } + p.T.PutQueue("nf", func() { + p.Send(int(pb.GameProtocol_HistoryResp), &pb.GameMsgHistoryResp{ + Results: p.T.History, + }) + }) + return "", nil +} + +func OnBetList(session gate.Session, req *pb.GameCommonReq) (string, error) { + p := FindPlayer(session) + if p == nil { + return "", nil + } + p.T.PutQueue("nf", func() { + p.Send(int(pb.GameProtocol_BetListResp), &pb.GameMsgBetListResp{ + List: p.T.BetPlayers, + }) + }) + return "", nil +} diff --git a/game/inits.go b/game/inits.go new file mode 100644 index 0000000..4ef81de --- /dev/null +++ b/game/inits.go @@ -0,0 +1,111 @@ +package game + +import ( + "server/call" + "server/common" + "server/config" + "server/natsClient" + "server/pb" + "strconv" + "time" + + "github.com/gogo/protobuf/proto" + "github.com/liangdas/mqant/conf" + "github.com/liangdas/mqant/log" + "github.com/liangdas/mqant/module" + basemodule "github.com/liangdas/mqant/module/base" + "github.com/liangdas/mqant/server" + "github.com/nats-io/nats.go" +) + +func InitNats(conn *nats.Conn) { + // natsClient.NewReloadNats(conn, reload) + call.InitReload(conn) + natsClient.NewCommonNatsImp(conn, natsClient.TopicClientDisconnect, func(data []byte) { + msg := new(pb.ClientDisConnectNotify) + if err := proto.Unmarshal(data, msg); err != nil { + log.Error("err:%v", err) + return + } + DisConnect(msg) + }) + natsClient.NewCommonNatsImp(conn, natsClient.TopicInnerOptPlayer, func(data []byte) { + msg := new(pb.InnerOptPlayer) + if err := proto.Unmarshal(data, msg); err != nil { + log.Error("err:%v", err) + return + } + OptPlayer(msg) + }) +} + +func InitModule(b *basemodule.BaseModule, subclass module.RPCModule, app module.App, settings *conf.ModuleSettings) { + metadata := make(map[string]string) + metadata["workID"] = strconv.Itoa(config.GetConfig().WorkID) + metadata["version"] = strconv.Itoa(config.GetConfig().Version) + b.OnInit(subclass, app, settings, + server.RegisterInterval(5*time.Second), + server.RegisterTTL(10*time.Second), + server.Metadata(metadata), + ) +} + +func LoadConfigs() { + c := map[int][]func(*pb.ReloadGameConfig) error{} + c[common.ReloadConfigGameRoom] = []func(rgc *pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := call.LoadConfigGameRooms(); err != nil { + log.Error("err:%v", err) + return err + } + if rgc != nil { + ReloadRooms(NewTable) + } + return nil + }} + call.LoadConfigs(c) +} + +func InitRooms(newTable func() SubTable) { + list := call.GetConfigGameRoom(ThisGameID) + for _, v := range list { + if !v.Open { + continue + } + log.Debug("init room:%+v", *v) + r := NewRoom(v) + if ThisGameType == GameTypeRoom { + continue + } + table, err := r.CreateById(call.GetCaller().GetApp(), r.GetTableID(), r.NewTable) + if err != nil { + log.Error("err:%v", err) + return + } + log.Debug("create table:%v", table.TableId()) + t := table.(*Table) + sub := r.NewSubTable() + sub.Init(t) + t.SubTable = sub + t.Init() + t.Reset() + t.List = r.TableList[0] + t.Ele = r.TableList[0].PushBack(t) + if MainTable == nil { + MainTable = t + } + table.Run() + r.Tables.Store(t.TableIDInt(), t) + } +} + +func ReloadRooms(newTable func() SubTable) { + list := call.GetConfigGameRoom(ThisGameID) + for _, v := range list { + if r, ok := RoomMap.Load(v.RoomID); ok { + room := r.(*GameRoom) + room.Reload(v) + } else { + NewRoom(v) + } + } +} diff --git a/game/module.go b/game/module.go new file mode 100644 index 0000000..ccc1307 --- /dev/null +++ b/game/module.go @@ -0,0 +1,94 @@ +package game + +import ( + "fmt" + "server/call" + "server/common" + "server/config" + "server/db" + "server/pb" + "strconv" + + edb "server/db/es" + mdb "server/db/mysql" + rdb "server/db/redis" + + "github.com/liangdas/mqant/conf" + "github.com/liangdas/mqant/log" + "github.com/liangdas/mqant/module" + basemodule "github.com/liangdas/mqant/module/base" +) + +type Module struct { + *basemodule.BaseModule + GameID int + GameName string + Sub SubModule +} + +func NewModule(gameID, gameType int, gameName string, newTable func() SubTable) *Module { + one := &Module{ + BaseModule: new(basemodule.BaseModule), + GameName: gameName, + GameID: gameID, + } + ThisGameID = gameID + ThisGameType = gameType + NewTable = newTable + return one +} + +type SubModule interface { + RegistHandler() +} + +func (m *Module) GetType() string { + return fmt.Sprintf("%s%d", common.GameModulePrefix, m.GameID) +} + +func (m *Module) Version() string { + //可以在监控时了解代码版本 + return "1.0.0" +} + +func (m *Module) OnInit(app module.App, settings *conf.ModuleSettings) { + InitModule(m.BaseModule, m, app, settings) + db.InitDB(&mdb.MysqlClient{}, &rdb.RedisClient{}, &edb.EsClient{}) + + LoadConfigs() + InitNats(m.App.Transport()) + m.Regist() + m.Sub.RegistHandler() + log.Info("[%v]module init finish", m.GetType()) +} + +func (m *Module) Regist() { + m.GetServer().RegisterGO("/"+strconv.Itoa(int(pb.GameProtocol_EnterGameReq)), Enter) + m.GetServer().RegisterGO("/"+strconv.Itoa(int(pb.GameProtocol_TableInfoReq)), OnTableInfo) + m.GetServer().RegisterGO("/"+strconv.Itoa(int(pb.GameProtocol_LeaveReq)), OnLeave) + + if ThisGameType == GameTypeMillion { + m.GetServer().RegisterGO("/"+strconv.Itoa(int(pb.GameProtocol_BetReq)), OnMillionBet) + m.GetServer().RegisterGO("/"+strconv.Itoa(int(pb.GameProtocol_HistoryReq)), OnHistory) + m.GetServer().RegisterGO("/"+strconv.Itoa(int(pb.GameProtocol_BetListReq)), OnBetList) + } +} + +func (m *Module) Run(closeSig chan bool) { + log.Info("[%v]module running", m.GetType()) + call.InitTimeWheel(closeSig) + call.NewCaller(m) + call.InitExecQueue() + call.NewSnowflake(int64(config.GetConfig().WorkID)) + call.InitOnline(GetOnline) + call.WriteRealOnline(m.GameName, GetRealOnline) + InitRooms(NewTable) + <-closeSig + log.Info("[%v]module stop", m.GetType()) +} + +func (m *Module) OnDestroy() { + ModuleOnDestroy() + m.BaseModule.OnDestroy() + log.Info("[%v]module destroy", m.GetType()) +} diff --git a/game/online.go b/game/online.go new file mode 100644 index 0000000..d3355f3 --- /dev/null +++ b/game/online.go @@ -0,0 +1,51 @@ +package game + +import ( + "server/call" + "server/common" + "server/util" + "time" +) + +// GetOnline 获取在线人数的func +func GetOnline() []*common.ESNewOnline { + data := map[int]int{} + AllPlayers.Range(func(k, v interface{}) bool { + p := v.(*Player) + if !p.IsLeave { + data[p.PlayerDBInfo.ChannelID]++ + } + return true + }) + ret := []*common.ESNewOnline{} + for i, v := range data { + one := &common.ESNewOnline{Channel: i, Total: v, GameID: ThisGameID} + ret = append(ret, one) + } + return ret +} + +func GetRealOnline() map[int]*common.RedisRealOnline { + rooms := map[int]*common.RedisRealOnline{} + for _, v := range call.GetConfigGameRoom(ThisGameID) { + rooms[v.RoomID] = new(common.RedisRealOnline) + } + zero := util.GetZeroTime(time.Now()).Unix() + AllPlayers.Range(func(k, v interface{}) bool { + p := v.(*Player) + if !p.IsLeave { + id := p.T.Room.RoomId() + ro, ok := rooms[id] + if !ok { + ro = new(common.RedisRealOnline) + } + ro.Total++ + if p.Birth >= zero { + ro.New++ + } + rooms[id] = ro + } + return true + }) + return rooms +} diff --git a/game/player.go b/game/player.go new file mode 100644 index 0000000..817fd70 --- /dev/null +++ b/game/player.go @@ -0,0 +1,512 @@ +package game + +import ( + "fmt" + "server/call" + "server/common" + "server/config" + "server/db" + "server/natsClient" + "server/pb" + "server/util" + "sync" + "time" + + "github.com/gogo/protobuf/proto" + "github.com/liangdas/mqant-modules/room" + "github.com/liangdas/mqant/gate" + "github.com/liangdas/mqant/log" + timewheel "github.com/liangdas/mqant/module/modules/timer" + "gorm.io/gorm" +) + +var ( + // MatchingPlayers sync.Map + AllPlayers sync.Map + AllRobots sync.Map + PlayerCount *int64 = new(int64) +) + +type SubPlayer interface { + Enter() + OnEnter() + Leave() bool + Reset() + Settle() + TableInfo() +} + +type Player struct { + SubPlayer SubPlayer + T *Table + *room.BasePlayerImp + UID int + SeatID int + Timer string + Role int + *common.PlayerDBInfo + // Currency *common.PlayerCurrency + Balance int64 + Status pb.PlayerStatus + IsLeave bool + StartTime int64 + + GameCurrencyType common.CurrencyType // 本局参与游戏的货币类型 + FinalSettle int64 + TotalBet int64 + UUID string + *MillionSubPlayer + // *RoomSubPlayer + // *SlotPlayer +} + +func GetPlayer(uid int) *Player { + p, ok := AllPlayers.Load(uid) + if !ok { + return nil + } + return p.(*Player) +} + +func NewPlayer(uid int, t *Table) *Player { + p := &Player{UID: uid, T: t, BasePlayerImp: &room.BasePlayerImp{}, SeatID: -1} + p.Status = pb.PlayerStatus_PlayerStatusNormal + return p +} + +func (p *Player) Refresh(session gate.Session) { + p.IsLeave = false + if p.Session().GetSessionID() != session.GetSessionID() { + delete(p.T.Players, p.Session().GetSessionID()) + p.T.Players[session.GetSessionID()] = p + p.Bind(session) + } +} + +func (p *Player) Enter() { + uid := p.UID + AllPlayers.Store(uid, p) + util.Go(func() { + p.PlayerDBInfo, _ = call.GetUserXInfo(uid, "role", "nick", "avatar", "channel_id", "birth", "mobile") + // p.Currency = &common.PlayerCurrency{UID: uid} + // db.Mysql().Get(p.Currency) + p.Balance = call.GetUserCurrencyTotal(p.UID, p.GameCurrencyType) + p.UpdatePlayerStatus() + p.T.PutQueue("nf", func() { + p.Send(int(pb.GameProtocol_EnterGameResp), &pb.GameCommonResp{}) + // 如果是房间模式,玩家进入后自动找位置坐下 + // if p.RoomSubPlayer != nil { + // p.RoomSubPlayer.Enter() + // } + p.SubPlayer.Enter() + }) + }) +} + +// Send 发送消息 +func (p *Player) Send(pid int, send proto.Message) { + if p.IsLeave || p.IsRobot() { + return + } + p.OnResponse(p.Session()) + topic := fmt.Sprintf("%v:%v", call.GetTopicName(), pid) + body, _ := proto.Marshal(send) + if err := p.T.SendCallBackMsgNR([]string{p.Session().GetSessionID()}, topic, body); err != nil { + p.Error("err:%v", err) + } +} + +// SendWithModule 发送消息 +func (p *Player) SendWithModule(module string, pid int, send proto.Message) { + if p.IsLeave || p.IsRobot() { + return + } + p.OnResponse(p.Session()) + topic := fmt.Sprintf("%v:%v", module, pid) + body, _ := proto.Marshal(send) + if err := p.T.SendCallBackMsgNR([]string{p.Session().GetSessionID()}, topic, body); err != nil { + p.Error("err:%v", err) + } +} + +// StopTimer 开始准备倒计时 +func (p *Player) StopTimer() { + if p.Timer != "" { + timewheel.GetTimeWheel().RemoveTimer(p.Timer) + p.Timer = "" + } +} + +func (p *Player) Reset() { + p.StopTimer() + p.Status = pb.PlayerStatus_PlayerStatusNormal + p.FinalSettle = 0 + p.TotalBet = 0 + p.UUID = "" + // if p.RoomSubPlayer != nil { + // p.RoomSubPlayer.Reset() + // } + if p.MillionSubPlayer != nil { + p.MillionSubPlayer.Reset() + } + // if p.SlotPlayer != nil { + // p.SlotPlayer.Reset() + // } + p.SubPlayer.Reset() +} + +func (p *Player) DelAllPlayers() { + player, ok := AllPlayers.Load(p.UID) + if !ok { + return + } + ap := player.(*Player) + if p == ap { + AllPlayers.Delete(p.UID) + } +} + +func (p *Player) IsRobot() bool { + return p.Role == common.PlayerRoleRobot +} + +func (p *Player) Leave() { + t := p.T + if p.Status == pb.PlayerStatus_PlayerStatusLeave { + return + } + p.Debug("leave table") + // p.SettleShouldSend = true + if !p.SubPlayer.Leave() { + return + } + p.DelAllPlayers() + if p.SeatID >= 0 { + t.SeatPlayers[p.SeatID] = nil + } + p.Status = pb.PlayerStatus_PlayerStatusLeave + p.StopTimer() + + t.RemoveOnePlayer(p) + + if len(t.Room.TableList) > 1 { + // 处理一下list + t.Room.MineOne(t) + } + p.Debug("finish leave table") + + // if t.Status == TableStatusPlaying { + // return + // } + + // if !p.IsRobot() && t.IsSingle && t.CountReal() == 0 { + // if ThisGameType == GameTypeMillion { + // t.Finish() + // return + // } + // t.RobotLeave() + // t.StopRobotTimer() + // t.Reset() + // t.Debug("push back to single queue") + // // 该房间空置后,重新丢回队列使用 + // t.Room.Queue.AddQueue(func() { + // t.Ele = t.Room.TableListSingle.PushBack(t) + // t.List = t.Room.TableListSingle + // }) + // } +} + +func (p *Player) SendReal(pid int, resp proto.Message) { + if p.IsRobot() || p.IsLeave { + return + } + p.OnResponse(p.Session()) + body, _ := proto.Marshal(resp) + call.SendSSBytes(p.Session(), pid, body) +} + +func (p *Player) Disconnect() { + p.Debug("disconnect") + // p.IsLeave = true + // if p.MillionSubPlayer != nil { + // p.IsAuto = false + // } + // // slot游戏重连直接退出房间 + // if p.SlotPlayer != nil { + p.Leave() + // } +} + +// PackBalance 统一封装流水记录 +func (p *Player) PackUpdate(data *common.UpdateCurrency) { + t := p.T + uuid := t.UUID + if uuid == "" { + uuid = p.UUID + } + data.UID = p.UID + data.Exi1 = ThisGameID + data.Exi2 = t.Room.RoomId() + data.Exs1 = uuid + data.ChannelID = p.ChannelID +} + +// PackBalance 统一封装流水记录 +func (p *Player) PackBalance(data *common.CurrencyBalance) { + t := p.T + data.UID = p.UID + data.ChannelID = p.ChannelID + data.Exs1 = p.UUID + if data.Exs1 == "" { + data.Exs1 = t.UUID + } + data.Exi1 = ThisGameID + data.Exi2 = t.Room.RoomId() + data.Time = time.Now().Unix() +} + +// Settle 统一结算 +func (p *Player) Settle() { + t := p.T + if p.Status != pb.PlayerStatus_PlayerStatusPlaying { + return + } + p.Debug("settle") + p.StopTimer() + p.SubPlayer.Settle() + p.Status = pb.PlayerStatus_PlayerStatusSettle + if t.Room.RoomType == RoomTypePractice { + return + } + // slot玩法直接reset + // if p.SlotPlayer != nil { + // if p.DBFreeCount == 0 { + // p.AfterSettle() + // } + // p.Reset() + // } else { + p.AfterSettle() + // } +} + +// AfterSettle 结算后检查一些数据 +func (p *Player) AfterSettle() { + if p.IsRobot() { + return + } + // 写入统计 + // p.WriteStats() + // 检查房间水位 + p.CalWater() + + // 结算后,处理活动等逻辑 + if p.UUID == "" { + p.UUID = p.T.UUID + } + now := time.Now().Unix() + if p.StartTime == 0 { + p.StartTime = p.T.StartTime + } + data := &pb.InnerAfterSettle{ + UID: int64(p.UID), + ProviderID: common.ProviderInhouse, + GameID: int64(ThisGameID), + TotalBet: p.TotalBet, + OriginSettle: p.FinalSettle, + FinalSettle: p.FinalSettle, + Phone: p.Mobile, + UUID: p.UUID, + Nick: p.Nick, + MyUUID: p.UUID, + CurrencyType: int64(p.GameCurrencyType), + IsNew: util.IsSameDayTimeStamp(now, p.Birth), + SubID: int64(p.T.Room.RoomId()), + PlayTime: now - p.StartTime, + } + + util.IndexTryS(func() error { + return call.Publish(natsClient.TopicInnerAfterSettle, data) + }) +} + +// CalWater 计算水位 +func (p *Player) CalWater() { + gid := ThisGameID + rid := p.T.Room.RoomId() + conf := call.GetConfigWaterReal(gid, rid) + // 未发生控制,则更新房间水位 + if conf != nil { + add := p.FinalSettle + // if conf.RebatePer != 100 { + // add = add * conf.RebatePer / 100 + // } + // field := "brl" + // if p.GameCurrencyType == common.CurrencyUSDT { + // field = "usdt" + // } + db.Mysql().Update(&common.ConfigWater{GameID: gid, RoomID: rid}, map[string]interface{}{"value": gorm.Expr(fmt.Sprintf("value + %d", add))}) + } +} + +// Debug 封装玩家打印日志方法(机器人也会打印) +func (p *Player) DebugA(fomat string, a ...interface{}) { + t := p.T + log.Debug("uuid:%v,roomID:%v,tableID:%v,player:%v,"+fomat, append([]interface{}{t.UUID, t.Room.RoomId(), t.TableId(), p.UID}, a...)...) +} + +// Debug 封装玩家打印日志方法 +func (p *Player) Debug(fomat string, a ...interface{}) { + if p.IsRobot() { + return + } + t := p.T + log.Debug("uuid:%v,roomID:%v,tableID:%v,player:%v,"+fomat, append([]interface{}{t.UUID, t.Room.RoomId(), t.TableId(), p.UID}, a...)...) +} + +// Error 封装玩家打印日志方法 +func (p *Player) Error(fomat string, a ...interface{}) { + if p.IsRobot() { + return + } + t := p.T + log.Error("uuid:%v,roomID:%v,tableID:%v,player:%v,"+fomat, append([]interface{}{t.UUID, t.Room.RoomId(), t.TableId(), p.UID}, a...)...) +} + +// WriteStats 写入新的统计记录 +func (p *Player) WriteStats() { + t := p.T + now := time.Now().Unix() + if p.StartTime == 0 { + p.StartTime = t.StartTime + } + if p.UUID == "" { + p.UUID = t.UUID + } + pt := now - p.StartTime + st := &common.ESGameData{PlayTime: pt} + st.UID = p.UID + st.Channel = p.ChannelID + st.Provider = common.ProviderInhouse + st.GameID = ThisGameID + st.UUID = p.UUID + st.Type = p.GameCurrencyType + st.BetAmount = p.TotalBet + st.SettleAmount = p.FinalSettle + st.MyUUID = p.UUID + st.Time = now + st.Birth = p.Birth + st.SubID = t.Room.RoomId() + st.IsNew = util.IsSameDayTimeStamp(now, p.Birth) + call.UpdatePlayerProfile(st) +} + +func (p *Player) DeleteSeats() { + t := p.T + if _, ok := t.GetSeats()[p.Session().GetSessionID()]; ok { + delete(t.GetSeats(), p.Session().GetSessionID()) + } else { + for k, v := range t.GetSeats() { + if v.(*Player).UID == p.UID { + delete(t.GetSeats(), k) + break + } + } + } +} + +// 房间控制 +// func (p *Player) CheckWaterControl() (is bool) { +// conf := call.GetConfigWaterReal(ThisGameID, p.T.Room.RoomId()) +// if conf == nil { +// return false +// } +// // 根据触发概率判断是否触发房间控制 +// if conf.RebatePer <= 0 || p.T.Ran.Intn(100)+1 > conf.ControlPer { +// return +// } + +// // 当前水位 +// water := conf.Value +// up := conf.WaterUp * 100 +// down := conf.WaterLower * 100 + +// // 水位大于上限 放 +// if water > up { +// is = true +// } + +// // 水位小于下限 杀 +// if water <= down { +// is = true +// } +// return +// } + +func (p *Player) UpdatePlayerStatus() { + one := common.PlayerStatus{ + GameID: ThisGameID, + ModuleName: call.GetCaller().GetType(), + SubID: p.T.Room.RoomId(), + WorkID: config.GetConfig().WorkID, + TableID: p.T.TableId(), + } + if err := db.Redis().HSet(common.GetRedisKeyPlayerStatus(p.UID), one); err != nil { + p.Error("err:%v", err) + } +} + +func (p *Player) DelPlayerStatus() { + one := new(common.PlayerStatus) + if err := db.Redis().HGetAll(common.GetRedisKeyPlayerStatus(p.UID), one); err != nil { + return + } + if one.TableID == p.T.TableId() && one.GameID == ThisGameID { + db.Redis().Delkey(common.GetRedisKeyPlayerStatus(p.UID)) + } +} + +func (p *Player) PackTableInfo(tableInfo *pb.GameCommonTableInfo) { + tableInfo.User = &pb.GameUser{ + UID: int64(p.UID), + Nick: p.Nick, + Avatar: p.Avatar, + CurrencyType: int64(p.GameCurrencyType), + Balance: p.Balance, + Bet: p.TotalBet, + Status: p.Status, + } + tableInfo.List = p.T.BetPlayers + tableInfo.Status = p.T.Status + tableInfo.TimeLeft = p.T.OperateTimeout - time.Now().UnixMilli() + if tableInfo.TimeLeft < 0 { + tableInfo.TimeLeft = 0 + } + tableInfo.BetLimit = p.T.Config.BetLimit + tableInfo.History = p.T.History +} + +func (p *Player) GetPlayerBetList() { + t := p.T + resp := &pb.GameMsgBetListResp{ + List: t.BetPlayers, + } + p.Send(int(pb.GameProtocol_BetListResp), resp) +} + +func (p *Player) UpdateBetList() { + t := p.T + bet := p.TotalBet + for i := range t.BetPlayers { + if t.BetPlayers[i].UID == int64(p.UID) { + t.BetPlayers[i].BetAmount = bet + return + } + } + t.BetPlayers = append(t.BetPlayers, &pb.BetPlayers{ + UID: int64(p.UID), + Avatar: p.Avatar, + Nick: p.Nick, + CurrencyType: int64(p.GameCurrencyType), + BetAmount: bet, + }) +} diff --git a/game/room.go b/game/room.go new file mode 100644 index 0000000..854da8c --- /dev/null +++ b/game/room.go @@ -0,0 +1,438 @@ +package game + +import ( + "container/list" + "errors" + "fmt" + "math" + "reflect" + "server/call" + "server/common" + "server/pb" + "sync" + + "github.com/liangdas/mqant-modules/room" + "github.com/liangdas/mqant/gate" + "github.com/liangdas/mqant/log" + "github.com/liangdas/mqant/module" +) + +var ( + RoomMap sync.Map + tableIndex uint = 1 +) + +const ( + RoomTypeCash = iota + 1 + RoomTypePractice +) + +const ( + GameTypeRoom = iota + 1 // 房间模式 + GameTypeMillion // 百人场 +) + +type GameRoom struct { + Config *common.ConfigGameRoom + NewSubTable func() SubTable + Queue *call.ExecQueue + SubAdd func(t *Table) + SubMine func(t *Table) + *room.Room + PlayerCount *int64 + Cards []int + TableList []*TableList + TableListPlaying []*TableList + TableListSingle *TableList // 单独匹配的房间,玩家与机器人游戏 + RoomType int // 练习场/cash场 + RoomName string + Tables sync.Map +} + +type TableList struct { + *list.List + Index int +} + +func GetRoom(id int) *GameRoom { + r, ok := RoomMap.Load(id) + if !ok { + return nil + } + return r.(*GameRoom) +} + +func NewRoom(c *common.ConfigGameRoom) *GameRoom { + app := call.GetCaller().GetApp() + roomID := c.RoomID + roomType := c.RoomType + roomName := c.RoomName + // max := c.MaxPlayer + r := &GameRoom{ + Room: room.NewRoom(app), + Config: c, + PlayerCount: new(int64), + NewSubTable: NewTable, + RoomType: roomType, + Queue: call.NewQueue(), + RoomName: roomName, + } + r.OnInit(app, roomID) + + // r.GetMatchConfig() + // r.GetBroadcastConfig() + // r.GetWaterConfig() + + // if ThisGameType == GameTypeRoom { + // for i := 0; i < max; i++ { + // list := list.New() + // r.TableList = append(r.TableList, &TableList{List: list, Index: i}) + // } + // for i := 0; i < max; i++ { + // list := list.New() + // r.TableListPlaying = append(r.TableListPlaying, &TableList{List: list, Index: i}) + // } + // } else if ThisGameType == GameTypeMillion { + r.TableList = append(r.TableList, &TableList{List: list.New(), Index: 0}) + // } + r.TableListSingle = &TableList{List: list.New()} + RoomMap.Store(roomID, r) + return r +} + +func (r *GameRoom) Reload(c *common.ConfigGameRoom) { + // 标识桌子配置有改动 + r.Queue.AddQueue(func() { + r.Config = c + // r.GetMatchConfig() + // r.GetBroadcastConfig() + // if r.GameType == common.GameTypeRoom { + // l := len(r.TableList) + // diff := c.MaxPlayer - l + // if diff > 0 { + // for i := 0; i < diff; i++ { + // list := list.New() + // r.TableList = append(r.TableList, &TableList{List: list, Index: i + l}) + // } + // for i := 0; i < diff; i++ { + // list := list.New() + // r.TableListPlaying = append(r.TableListPlaying, &TableList{List: list, Index: i + l}) + // } + // } else if diff < 0 { + // if c.MaxPlayer == 0 { + // r.TableList = nil + // r.TableListPlaying = nil + // } else { + // r.TableList = r.TableList[:l+diff] + // r.TableListPlaying = r.TableListPlaying[:l+diff] + // } + // } + // } + r.Tables.Range(func(key, value interface{}) bool { + t := value.(*Table) + t.ChangeConfig() + return true + }) + }) +} + +func (r *GameRoom) GetTableID() string { + if tableIndex > math.MaxUint32 { + tableIndex = 2 + } + t := tableIndex + tableIndex++ + return fmt.Sprintf("%v", t) +} + +func (r *GameRoom) Enter(uid int, session gate.Session, ct common.CurrencyType, tid ...int) { + // 顺序进入游戏 + r.Queue.AddQueue(func() { + var err error + if ThisGameType == GameTypeMillion { + err = r.MatchMillionTable(uid, session, ct) + } + // else { + // err = r.Match(uid, tid...) + // } + if err != nil { + call.SendNR(uid, int(pb.GameProtocol_EnterGameResp), &pb.GameCommonResp{Result: 1}) + } + }) +} + +func (r *GameRoom) NewTable(module module.App, tableId string) (room.BaseTable, error) { + t := &Table{ + app: module, + Room: r, + Players: map[string]room.BasePlayer{}, + Config: *r.Config, + } + opts := []room.Option{ + room.TableId(tableId), + room.DestroyCallbacks(func(table room.BaseTable) error { + log.Info("回收了房间: %v", table.TableId()) + _ = r.DestroyTable(table.TableId()) + return nil + }), + } + opts = append(opts, room.TimeOut(1200)) + if ThisGameType == GameTypeMillion { + opts = append(opts, room.Capaciity(2000)) + opts = append(opts, room.SendMsgCapaciity(2000)) + } + opts = append(opts, room.Update(t.Update)) + opts = append(opts, room.NoFound(func(msg *room.QueueMsg) (value reflect.Value, e error) { + //return reflect.ValueOf(t.doSay), nil + return reflect.Zero(reflect.ValueOf("").Type()), errors.New("no found handler") + })) + opts = append(opts, room.SetRecoverHandle(func(msg *room.QueueMsg, err error) { + + log.Error("Recover %v Error: %v", msg.Func, err.Error()) + })) + opts = append(opts, room.SetErrorHandle(func(msg *room.QueueMsg, err error) { + log.Error("Error %v Error: %v", msg.Func, err.Error()) + })) + t.OnInit(t, opts...) + t.Register("nf", t.NF) // 下一帧执行 + return t, nil +} + +func FindTable(uid int) *Table { + data, ok := AllPlayers.Load(uid) + if !ok { + return nil + } + p := data.(*Player) + return p.T +} + +func FindPlayer(session gate.Session) *Player { + uid := int(session.GetUserIdInt64()) + data, ok := AllPlayers.Load(uid) + if !ok { + return nil + } + p := data.(*Player) + p.T.PutQueue("nf", func() { + p.Refresh(session) + }) + return p +} + +// AddOne 保证在queue中运行 +func (r *GameRoom) AddOne(t *Table) { + if len(t.Lists) == 0 { + return + } + if r.SubAdd != nil { + r.SubAdd(t) + return + } + log.Debug("table %v add one,list:%+v", t.TableId(), t.List) + list := t.List + lists := t.Lists + // 当前列表已清空 + if list == nil { + list = lists[0] + t.List = list + t.Ele = list.PushBack(t) + return + } + list.Remove(t.Ele) + t.List = nil + index := list.Index + if index != len(lists)-1 { + t.List = lists[index+1] + t.Ele = lists[index+1].PushBack(t) + } + log.Debug("table %v finish add one,list:%+v", t.TableId(), t.List) +} + +func (r *GameRoom) MineOne(t *Table) { + if len(t.Lists) == 0 { + return + } + r.Queue.AddQueue(func() { + log.Debug("table %v mine one,list:%+v", t.TableId(), t.List) + if r.SubMine != nil { + r.SubMine(t) + return + } + list := t.List + lists := t.Lists + // 当前是满人状态 + if list == nil { + newList := lists[len(lists)-1] + t.List = newList + t.Ele = newList.PushBack(t) + return + } + index := list.Index + list.Remove(t.Ele) + // 当前已在第一个列表 + if index > 0 { + newList := lists[index-1] + t.List = newList + t.Ele = newList.PushBack(t) + } + log.Debug("table %v finish mine one,list:%+v", t.TableId(), t.List) + }) +} + +// ChangeOne 换到另一个list +func (r *GameRoom) ChangeOne(t *Table) { + if len(t.Lists) == 0 || len(r.TableListPlaying) == 0 { + return + } + r.Queue.AddQueue(func() { + target := r.TableList + if t.Lists[0] == r.TableList[0] { + target = r.TableListPlaying + } + t.Lists = target + list := t.List + if list == nil { + return + } + list.Remove(t.Ele) + index := list.Index + newList := target[index] + t.List = newList + t.Ele = newList.PushBack(t) + }) +} + +func (r *GameRoom) RemoveOne(table *Table) { + r.Queue.AddQueue(func() { + list := table.List + if list == nil { + return + } + list.Remove(table.Ele) + table.List = nil + }) +} + +// func (r *GameRoom) Match(uid int, tid ...int) error { +// log.Debug("GameRoom %v match:%v", r.RoomId(), uid) +// find := false +// con := r.MatchConfig +// // 单人匹配 +// if con[0].MatchType == common.MatchTypeSingle { +// for e := r.TableListSingle.Front(); e != nil; e = e.Next() { +// table := e.Value.(*Table) +// log.Debug("single player %v checking table %v", uid, table.TableId()) +// find = true +// r.TableListSingle.Remove(e) +// table.Run() +// table.PutQueue("enter", uid) +// break +// } +// if !find { +// r.CreateAndEnterTable(uid, true) +// } +// return nil +// } + +// // 根据匹配策略顺序匹配 +// for _, v := range con { +// // if v.TableState == 0 { +// // continue +// // } +// list := r.TableList +// if v.TableState == 2 { +// list = r.TableListPlaying +// } +// empty := v.EmptyPositionNumber[0] +// if empty < 1 || empty > len(list) { +// continue +// } +// l := list[len(list)-empty] +// // l.Lock() +// log.Debug("index:%v,mc:%+v", len(list)-empty, v) +// for e := l.Front(); e != nil; e = e.Next() { +// table := e.Value.(*Table) +// log.Debug("player %v checking table %v", uid, table.TableId()) +// if tid != nil && table.TableIDInt() == tid[0] { +// continue +// } +// // 屏蔽自己提前离开的房间 +// if p := table.GetPlayer(uid); p != nil { +// continue +// } +// find = true +// // 如果存在队列则处理一下队列 +// if len(r.TableList) > 1 { +// r.AddOne(table) +// } +// // table.Run() +// table.PutQueue("enter", uid) +// break +// } +// if find { +// break +// } +// // l.Unlock() +// } + +// // 创建房间 +// if !find { +// r.CreateAndEnterTable(uid, false) +// } +// return nil +// } + +// MatchMillionTable 百人场进场方式 +func (r *GameRoom) MatchMillionTable(uid int, session gate.Session, ct common.CurrencyType) error { + log.Debug("GameRoom %v match:%v,ct:%v", r.RoomId(), uid, ct) + find := false + for i := 0; i < len(r.TableList); i++ { + l := r.TableList[i].List + for e := l.Front(); e != nil; e = e.Next() { + table := e.Value.(*Table) + find = true + table.PutQueue("nf", func() { + table.Enter(uid, session, ct) + }) + break + } + } + + // 创建房间 + if !find { + r.CreateAndEnterTable(uid, session, ct, false) + } + return nil +} + +func (r *GameRoom) CreateAndEnterTable(uid int, session gate.Session, ct common.CurrencyType, isSingle bool) { + table, err := r.CreateById(call.GetCaller().GetApp(), r.GetTableID(), r.NewTable) + if err != nil { + log.Error("err:%v", err) + return + } + log.Debug("create table:%v isSingle:%v", table.TableId(), isSingle) + t := table.(*Table) + sub := r.NewSubTable() + sub.Init(t) + t.SubTable = sub + t.Init() + t.IsSingle = isSingle + t.Reset() + if !isSingle { + if ThisGameType == GameTypeMillion { + t.List = r.TableList[0] + t.Ele = r.TableList[0].PushBack(t) + } else { + t.List = r.TableList[1] + t.Ele = r.TableList[1].PushBack(t) + t.Lists = r.TableList + } + } + table.Run() + table.PutQueue("nf", func() { + t.Enter(uid, session, ct) + }) + r.Tables.Store(t.TableIDInt(), t) +} diff --git a/game/sub_million.go b/game/sub_million.go new file mode 100644 index 0000000..e82f3da --- /dev/null +++ b/game/sub_million.go @@ -0,0 +1,262 @@ +package game + +import ( + "server/call" + "server/common" + "server/db" + "server/pb" + "server/util" + "time" + + "github.com/liangdas/mqant/log" +) + +const ( + MillionMaxSeat = 4 +) + +// MillionSubTable 百人场子游戏数据 +type MillionSubTable struct { + Table *Table + History []int64 + TotalBet []int64 + Jackpot *common.Jackpot + MillionSubTableInter +} + +type MillionSubTableInter interface { + GetResult() (string, string) +} + +func (t *Table) NewMillionSubTable() { + t.MillionSubTable = &MillionSubTable{Table: t} + db.Redis().GetJsonData(common.GetRedisKeyGameResult(ThisGameID, t.Room.RoomId()), &t.History) +} + +func (t *MillionSubTable) GetTotal() (total int64) { + for _, v := range t.TotalBet { + total += v + } + return +} + +func (t *MillionSubTable) Reset() { + t.Table.ResetTimeOut() + // 如果配置有改动,重新加载配置,广播告知客户端 + if t.Table.ConfigChange { + t.Table.ConfigChange = false + t.Table.Config = *t.Table.Room.Config + for _, v := range t.Table.GetSeats() { + p := v.(*Player) + if p.IsRobot() { + continue + } + p.SubPlayer.TableInfo() + } + } + t.TotalBet = make([]int64, len(t.Table.Config.BetLimit)) + for _, v := range t.Table.GetSeats() { + p := v.(*Player) + if p.IsLeave || p.WatchCount >= MaxWatchCount { + p.TotalBet = 0 + p.Status = pb.PlayerStatus_PlayerStatusNormal + p.Leave() + continue + } + p.Reset() + // if !t.Table.IsSingle && !p.IsRobot() && call.ShouldMillionBlack(p.UID, t.Table.GameID) { + // p.IsLeave = true + // p.Leave() + // InnerActionGame(&pb.ActionGame{ + // Action: 0, + // UID: int32(p.UID), + // GameKind: common.GetGameKindByID(ThisGameID), + // }) + // } + } + t.Table.StartGame() +} + +type MillionSubPlayerInter interface { + GetHistory() +} + +// MillionSubPlayer 百人场子游戏玩家数据 +type MillionSubPlayer struct { + *Player + AreaBet []int64 + AreaWin []int64 // 每个区域输赢 + WatchCount int // 观看不下注局数 + MillionSubPlayerInter +} + +func NewMillionSubPlayer(player *Player, sp MillionSubPlayerInter) *MillionSubPlayer { + p := &MillionSubPlayer{Player: player, MillionSubPlayerInter: sp} + p.AreaBet = make([]int64, len(p.Player.T.Config.BetLimit)) + p.AreaWin = make([]int64, len(p.Player.T.Config.BetLimit)) + return p +} + +func (sp *MillionSubPlayer) Reset() { + sp.AreaBet = make([]int64, len(sp.Player.T.Config.BetLimit)) + sp.AreaWin = make([]int64, len(sp.Player.T.Config.BetLimit)) +} + +func (sp *MillionSubPlayer) Settle() { + if sp.T.Room.RoomType == RoomTypePractice { + return + } + ct := sp.GameCurrencyType + settle := sp.FinalSettle + update := &common.UpdateCurrency{ + NotNotify: true, + CurrencyBalance: &common.CurrencyBalance{ + UID: sp.UID, + Type: ct, + Value: settle, + }, + } + sp.PackUpdate(update) + util.Go(func() { + call.UpdateCurrencyPro(update) + }) + +} + +func (p *MillionSubPlayer) MillionLeave() bool { + t := p.T + p.Debug("leave table,totalBet:%v", p.TotalBet) + resp := &pb.GameLeaveResp{Result: CodeOk} + if p.WatchCount >= MaxWatchCount { + resp.Reason = 1 + } + p.SendReal(int(pb.GameProtocol_LeaveResp), resp) + if p.TotalBet > 0 || (t.IsSingle && t.Status != pb.GameStatus_GameStatusNormal) { + p.IsLeave = true + return false + } + // if p.SeatID >= 0 { + // t.SeatPlayers[p.SeatID] = nil + // t.Broadcast(int(pb.MillionProtocol_MillionSitdownResp), &pb.GameSitdownResp{Index: uint32(p.SeatID), Opt: 2}) + // } + return true +} + +func (p *MillionSubPlayer) MillionBet(area, amount int64) { + t := p.T + p.Debug("Bet area %v amount %v", area, amount) + code := CodeOk + defer func() { + p.DeferBet(area, amount, code) + }() + + if t.Status != pb.GameStatus_GameStatusPlaying || int(area) > len(t.Config.BetLimit)-1 { + p.Debug("bet area:%v,areaBet:%v", area, len(t.Config.BetLimit)) + code = CodeBetInvalid + return + } + if p.AreaBet[area]+int64(amount) > t.Config.BetLimit[area] { + code = CodeBetLimit + return + } + update := &common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + Type: p.GameCurrencyType, + Value: -amount, + Event: common.CurrencyEventGameBet, + }, + } + p.PackUpdate(update) + pro := call.MineCurrencyProReal(update) + if pro.Err != nil { + log.Error("err:%v", pro.Err) + code = CodeSeverError + return + } + // p.Currency.SetBalance(p.GameCurrencyType, pro.Balance) + p.Balance = pro.Balance + + p.Status = pb.PlayerStatus_PlayerStatusPlaying + t.TotalBet[area] += int64(amount) + p.AreaBet[area] += int64(amount) + p.TotalBet += int64(amount) + // 更新排行榜 + p.UpdateBetList() +} + +// MillionSettle 百人场结算逻辑 +func (p *Player) MillionSettle() { + if p.Status == pb.PlayerStatus_PlayerStatusNormal { + p.WatchCount++ + } else { + p.WatchCount = 0 + p.Settle() + } +} + +func (mt *MillionSubTable) Settle() { + t := mt.Table + now := time.Now().Unix() + records := map[int]*common.ESMillionGameRecord{} + for _, v := range t.GetSeats() { + p := v.(*Player) + if p.Status == pb.PlayerStatus_PlayerStatusNormal || p.IsRobot() || p.TotalBet == 0 { + continue + } + record := records[p.ChannelID] + if record == nil { + record = &common.ESMillionGameRecord{ + Time: now, + GameID: ThisGameID, + RoomID: t.Room.RoomId(), + UUID: t.UUID, + Channel: p.ChannelID, + } + record.Result, record.ResultDetail = mt.GetResult() + } + doc := &common.ESMillionPlayerRecord{ + UID: p.UID, + Time: now, + GameID: ThisGameID, + RoomID: t.Room.RoomId(), + UUID: t.UUID, + Settle: p.FinalSettle, + Result: record.Result, + Channel: p.ChannelID, + Bets: p.AreaBet, + } + db.ES().InsertToESGO(common.ESIndexBackMillionPlayerRecord, doc) + record.PlayerCount++ + record.PlayerBet += p.MillionSubPlayer.TotalBet + if p.FinalSettle > 0 { + record.Reward += p.FinalSettle + } + record.Profit += p.FinalSettle + records[p.ChannelID] = record + } + for i := range records { + db.ES().InsertToESGO(common.ESIndexBackMillionGameRecord, records[i]) + } +} + +func (p *MillionSubPlayer) DeferBet(area, amount int64, code int) { + t := p.T + resp := &pb.GameMsgBetResp{ + Code: int64(code), + CurrencyType: int64(p.GameCurrencyType), + Amount: amount, + Balance: p.Balance, + Area: area, + } + p.Send(int(pb.GameProtocol_BetResp), resp) + if code != CodeOk { + return + } + respBroadcast := &pb.GameMsgBetBroadcastResp{ + UID: int64(p.UID), + CurrencyType: int64(p.GameCurrencyType), + Amount: amount, + Area: area, + } + t.Broadcast(int(pb.GameProtocol_BetBroadcast), respBroadcast, p.UID) +} diff --git a/game/table.go b/game/table.go new file mode 100644 index 0000000..ce38573 --- /dev/null +++ b/game/table.go @@ -0,0 +1,568 @@ +package game + +import ( + "container/list" + "fmt" + "math/rand" + "server/call" + "server/common" + "server/pb" + "server/util" + "strconv" + "sync/atomic" + "time" + + "github.com/gogo/protobuf/proto" + "github.com/liangdas/mqant-modules/room" + "github.com/liangdas/mqant/gate" + "github.com/liangdas/mqant/log" + "github.com/liangdas/mqant/module" + timewheel "github.com/liangdas/mqant/module/modules/timer" +) + +// 百人游戏主桌子 +var ( + MainTable *Table +) + +type SubTable interface { + Init(t *Table) + Reset() + Enter(p *Player) + StartGame() + Settle() + Destroy() +} + +type Table struct { + app module.App + SubTable SubTable + IsDestroy bool + Players map[string]room.BasePlayer + Room *GameRoom + List *TableList + Lists []*TableList + Ele *list.Element + Timer string + RobotTimer string + UUID string + room.QTable + Status pb.GameStatus + OperateTimeout int64 + SeatPlayers []*Player // 有座位的玩家 + SlicePlayers []*Player // 所有玩家 + BetPlayers []*pb.BetPlayers // 本局下注玩家按下注量排序 + StartTime int64 // 开局时间 + EndTime int64 // 结束时间 + ConfigChange bool // 配置修改后,该桌子等玩家打完回收 + IsSingle bool // 是否是独立房间 + Ran *rand.Rand + Config common.ConfigGameRoom + *MillionSubTable + // *RoomSubTable +} + +func (t *Table) Init() { + t.Ran = rand.New(rand.NewSource(time.Now().UnixNano())) + t.SeatPlayers = make([]*Player, t.Config.MaxSeats) +} + +func (t *Table) GetSeats() map[string]room.BasePlayer { + return t.Players +} +func (t *Table) GetApp() module.App { + return t.app +} + +func (t *Table) OnCreate() { + //可以加载数据 + //一定要调用QTable.OnCreate() + t.QTable.OnCreate() +} + +func (t *Table) Update(ds time.Duration) { + +} + +func (t *Table) OnTimeOut() { + if ThisGameType == GameTypeRoom { + t.Debug("OnTimeOut") + t.Finish() + } +} + +func (t *Table) Reset() { + t.UUID = "" + t.Status = pb.GameStatus_GameStatusNormal + t.StartTime = 0 + t.EndTime = 0 + t.BetPlayers = []*pb.BetPlayers{} + t.StopTimer() + // if t.RoomSubTable != nil { + // t.RoomSubTable.Reset() + // } + if t.MillionSubTable != nil { + t.MillionSubTable.Reset() + } + t.SubTable.Reset() +} + +func (t *Table) Enter(uid int, s gate.Session, ct common.CurrencyType) { + t.Debug("player %v enter", uid) + + // 如果桌子已经销毁,重新分配一张桌子给玩家 + if t.IsDestroy { + rid := findRoom(t.Room.RoomId()) + if rid == 0 { + rid = findRoom(0) + } + gameRoom := GetRoom(rid) + if gameRoom != nil { + gameRoom.Enter(uid, s, ct) + } + return + } + + // 如果玩家已在游戏中,直接返回 + p := t.GetPlayer(uid) + if p != nil { + delete(t.Players, p.Session().GetSessionID()) + t.Players[s.GetSessionID()] = p + p.Bind(s) + p.IsLeave = false + AllPlayers.Store(uid, p) + util.Go(func() { + p.UpdatePlayerStatus() + }) + p.SubPlayer.OnEnter() + return + } + p = NewPlayer(uid, t) + p.GameCurrencyType = ct + p.Bind(s) + t.AddOnePlayer(p) + p.T.SubTable.Enter(p) + p.Enter() +} + +func (t *Table) AddOnePlayer(p *Player) { + atomic.AddInt64(t.Room.PlayerCount, 1) + // if !p.IsRobot() { + atomic.AddInt64(PlayerCount, 1) + t.Players[p.Session().GetSessionID()] = p + // } else { + // t.Robots = append(t.Robots, p.Robot) + // t.Players[p.Robot.RobotSessionID] = p + // } + t.SlicePlayers = append(t.SlicePlayers, p) +} + +func (t *Table) RemoveOnePlayer(p *Player) { + atomic.AddInt64(t.Room.PlayerCount, -1) + // if !p.IsRobot() { + atomic.AddInt64(PlayerCount, -1) + p.DeleteSeats() + // } else { + // p.Robot.StopTimer() + // if p.Robot.ID > 0 { + // p.Robot.DelRobotPlaying() + // AllRobots.Delete(p.Robot.ID) + // } + // for i := 0; i < len(t.Robots); i++ { + // if t.Robots[i] == p.Robot { + // t.Robots = append(t.Robots[:i], t.Robots[i+1:]...) + // break + // } + // } + // delete(t.GetSeats(), p.Robot.RobotSessionID) + // } + for i := 0; i < len(t.SlicePlayers); i++ { + if t.SlicePlayers[i] == p { + t.SlicePlayers = append(t.SlicePlayers[:i], t.SlicePlayers[i+1:]...) + break + } + } +} + +// Broadcast 桌子广播 +func (t *Table) Broadcast(pid int, send proto.Message, uid ...int) { + topic := fmt.Sprintf("%v:%v", call.GetTopicName(), pid) + sendP := []string{} + reset := false + + for _, v := range t.Players { + p := v.(*Player) + if p.IsLeave || p.IsRobot() { + continue + } + except := false + for _, k := range uid { + if p.UID == k { + except = true + break + } + } + if !except { + reset = true + if p.MillionSubPlayer != nil { + sendP = append(sendP, v.Session().GetSessionID()) + } else { + p.Send(pid, send) + } + } + } + if reset { + t.ResetTimeOut() + } + if len(sendP) == 0 { + return + } + body, _ := proto.Marshal(send) + err := t.SendCallBackMsgNR(sendP, topic, body) + if err != nil { + t.Error("err:%v", err) + } +} + +func (t *Table) StartGame() { + if t.Status != pb.GameStatus_GameStatusNormal || t.IsDestroy { + return + } + now := time.Now().Unix() + t.StopTimer() + t.Status = pb.GameStatus_GameStatusPlaying + t.StartTime = now + t.UUID = call.SnowNode().Generate().String() + t.Debug("start game") + t.SubTable.StartGame() + for _, v := range t.GetSeats() { + p := v.(*Player) + if p.IsRobot() { + continue + } + p.StartTime = now + p.UUID = t.UUID + } + if len(t.Room.TableList) > 1 { + t.Room.ChangeOne(t) + } +} + +func (t *Table) Settle() { + t.Debug("settle,status:%v", t.Status) + if t.Status == pb.GameStatus_GameStatusSettle { + return + } + t.StopTimer() + t.EndTime = time.Now().Unix() + t.Status = pb.GameStatus_GameStatusSettle + t.SubTable.Settle() + if len(t.Room.TableList) > 1 { + t.Room.ChangeOne(t) + } + if t.Room.RoomType == RoomTypePractice { + return + } + if ThisGameType == GameTypeMillion { + t.MillionSubTable.Settle() + } +} + +func (t *Table) NF(f func()) { + f() +} + +func (t *Table) OnDestroy() { + t.Debug("Destroy") + t.QTable.OnDestroy() + t.IsDestroy = true + t.StopTimer() + for _, v := range t.GetSeats() { + p := v.(*Player) + if !p.IsRobot() && p.TotalBet > 0 && p.Status == pb.PlayerStatus_PlayerStatusPlaying { + p.Debug("destroy return,total:%v", p.TotalBet) + call.UpdateCurrencyPro(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: p.UID, + Event: common.CurrencyEventGameCancelBet, + Type: p.GameCurrencyType, + Value: p.TotalBet, + }, + }) + } + p.Status = pb.PlayerStatus_PlayerStatusNormal + if p.MillionSubPlayer != nil { + p.TotalBet = 0 + } + p.Leave() + } + t.Room.RemoveOne(t) + t.Room.Tables.Delete(t.TableIDInt()) + t.SubTable.Destroy() +} + +func (t *Table) Leave(session gate.Session) { + player := t.FindPlayer(session) + if player == nil { + return + } + p := player.(*Player) + p.Leave() +} + +// 计算当前还在游戏中的玩家 +func (t *Table) CountPlayingPlayers() int { + count := 0 + for _, v := range t.GetSeats() { + if v.(*Player).Status == pb.PlayerStatus_PlayerStatusPlaying { + count++ + } + } + return count +} + +func (t *Table) CountRealPlayings() int { + count := 0 + for _, v := range t.Players { + if !v.(*Player).IsRobot() && v.(*Player).Status == pb.PlayerStatus_PlayerStatusPlaying { + count++ + } + } + return count +} + +// 计算参与了游戏的玩家 +func (t *Table) CountPlayedPlayers() int { + count := 0 + for _, v := range t.GetSeats() { + if v.(*Player).Status != pb.PlayerStatus_PlayerStatusNormal { + count++ + } + } + return count +} + +func (t *Table) StopTimer() { + t.OperateTimeout = 0 + if t.Timer == "" { + return + } + timewheel.GetTimeWheel().RemoveTimer(t.Timer) + t.Timer = "" +} + +func (t *Table) GetPlayer(uid int) *Player { + for _, v := range t.GetSeats() { + p := v.(*Player) + if p.UID == uid { + return p + } + } + return nil +} + +func (t *Table) TableIDInt() int { + id, _ := strconv.Atoi(t.TableId()) + return id +} + +// 计算当前还在游戏中的玩家 +func (t *Table) GetOnePlaying() *Player { + for _, v := range t.GetSeats() { + p := v.(*Player) + if p.Status == pb.PlayerStatus_PlayerStatusPlaying { + return p + } + } + return nil +} + +// 获取一名真实玩家 +func (t *Table) GetRealOne() *Player { + for _, v := range t.GetSeats() { + p := v.(*Player) + if !p.IsRobot() { + return p + } + } + return nil +} + +// 获取一名玩牌中真实玩家 +func (t *Table) GetRealOnePlaying() *Player { + for _, v := range t.GetSeats() { + p := v.(*Player) + if p.Role == common.PlayerRoleNormal && p.Status == pb.PlayerStatus_PlayerStatusPlaying { + return p + } + } + return nil +} + +// 获取一名真实玩家 +func (t *Table) GetRealPlayings() []*Player { + ret := []*Player{} + for _, v := range t.GetSeats() { + p := v.(*Player) + if p.IsRobot() || p.Status != pb.PlayerStatus_PlayerStatusPlaying { + continue + } + ret = append(ret, p) + } + return ret +} + +func (t *Table) CountEmptySeat() int { + count := 0 + for i := 0; i < len(t.SeatPlayers); i++ { + if t.SeatPlayers[i] == nil { + count++ + } + } + return count +} + +func (t *Table) FindEmptySeat() int { + for i := 0; i < len(t.SeatPlayers); i++ { + if t.SeatPlayers[i] == nil { + return i + } + } + return -1 +} + +func (t *Table) GetLastPlayingPlayer(id int) *Player { + max := len(t.SeatPlayers) + seat := id + max + for i := 0; i < max-1; i++ { + seat-- + seatID := seat % max + other := t.SeatPlayers[seatID] + if other != nil && other.Status == pb.PlayerStatus_PlayerStatusPlaying { + return other + } + } + return nil +} + +func (t *Table) GetNextPlayingPlayer(seatID int) *Player { + for i := 0; i < len(t.SeatPlayers)-1; i++ { + seatID = (seatID + 1) % len(t.SeatPlayers) + one := t.SeatPlayers[seatID] + if one != nil && one.Status == pb.PlayerStatus_PlayerStatusPlaying { + return one + } + } + return nil +} + +// 随机获取一个玩家 +func (t *Table) GetRandomPlayer() *Player { + Players := []*Player{} + for _, v := range t.SeatPlayers { + if v == nil || v.Status != pb.PlayerStatus_PlayerStatusPlaying { + continue + } + Players = append(Players, v) + } + return Players[t.Ran.Intn(len(Players))] +} + +func (t *Table) GetSeatPlayer(seat int) *Player { + for i, v := range t.SeatPlayers { + if v == nil { + continue + } + if v.SeatID == seat { + return t.SeatPlayers[i] + } + } + return nil +} + +func (t *Table) GetOtherPlayer(seat int) *Player { + for i, v := range t.SeatPlayers { + if v == nil || v.SeatID == seat || v.Status != pb.PlayerStatus_PlayerStatusPlaying { + continue + } + return t.SeatPlayers[i] + } + return nil +} + +func (t *Table) StartGameTimer(dur time.Duration) { + if t.IsDestroy { + return + } + t.Timer = fmt.Sprintf("startGame_%v", t.TableId()) + timewheel.GetTimeWheel().AddTimerCustom(dur, t.Timer, nil, func(arge interface{}) { + t.Debug("startGame timer callback") + t.Put("nf", t.Settle) + }) +} + +func (t *Table) SettleGameTimer(dur time.Duration) { + if t.IsDestroy { + return + } + t.Timer = fmt.Sprintf("settleGame_%v", t.TableId()) + timewheel.GetTimeWheel().AddTimerCustom(dur, t.Timer, nil, func(arge interface{}) { + t.Debug("settleGame timer callback") + t.PutQueue("nf", func() { + t.Reset() + }) + }) +} + +// GetPlayingPlayers 获取本局所有正在游戏中的玩家 +func (t *Table) GetPlayingPlayers() []*Player { + ret := []*Player{} + for _, v := range t.GetSeats() { + p := v.(*Player) + if p.Status != pb.PlayerStatus_PlayerStatusPlaying { + continue + } + ret = append(ret, p) + } + return ret +} + +// Debug 封装桌子打印日志方法 +func (t *Table) Debug(fomat string, a ...interface{}) { + log.Debug("uuid:%v,roomID:%v,tableID:%v,"+fomat, append([]interface{}{t.UUID, t.Room.RoomId(), t.TableId()}, a...)...) +} + +// Error 封装桌子打印日志方法 +func (t *Table) Error(fomat string, a ...interface{}) { + log.Error("uuid:%v,roomID:%v,tableID:%v,"+fomat, append([]interface{}{t.UUID, t.Room.RoomId(), t.TableId()}, a...)...) +} + +func (t *Table) Put(_func string, params ...interface{}) { + for i := 0; i < 3; i++ { + err := t.PutQueue(_func, params...) + if err == nil { + return + } + log.Error("err:%v", err) + } +} + +func (t *Table) GetRealBet() int64 { + var realBet int64 + for _, v := range t.Players { + p := v.(*Player) + if p.IsRobot() { + continue + } + realBet += p.TotalBet + } + return realBet +} + +// ChangeConfig 配置改变 +func (t *Table) ChangeConfig() { + t.Debug("config change") + t.PutQueue("nf", func() { + t.ConfigChange = true + }) +} diff --git a/game/values.go b/game/values.go new file mode 100644 index 0000000..9919b24 --- /dev/null +++ b/game/values.go @@ -0,0 +1,55 @@ +package game + +// 错误码 +const ( + CodeOk = iota + CodeSeverError // 内部错误 + CodeCashNotEnough // 金币不足 + CodeBetInvalid // 投注金额不合法 + CodeBetLimit // 投注金额超出限额 + CodePlaying // 游戏中不能退出 + CodeSeatIDInvalid // 座位号不合法 + CodeSeatInvalid // 座位已有人 + CodeNoSeat // 当前没有座位 + CodePayBet // 未充值不能下注 +) + +const ( + MaxWatchCount = 10 // 最大观看局数 +) + +var ( + ThisGameID int + ThisGameType int + NewTable func() SubTable +) + +func ModuleOnDestroy() { + // AllPlayers.Range(func(key, value interface{}) bool { + // uid := key.(int) + // p := value.(*Player) + // p.Debug("destroy remove") + // if p.IsRobot() { + // db.Redis().DelRobot(p.Robot.ID) + // } else { + // db.Redis().Delkey(common.GetRedisKeyPlayerStatus(uid)) + // } + // return true + // }) + // AllRobots.Range(func(key, value interface{}) bool { + // id := key.(int) + // p := value.(*Player) + // p.Robot.Debug("destroy remove robot %v", id) + // db.Redis().DelRobot(p.Robot.ID) + // return true + // }) + RoomMap.Range(func(key, value interface{}) bool { + r := value.(*GameRoom) + r.Tables.Range(func(key, value interface{}) bool { + t := value.(*Table) + t.Finish() + return true + }) + return true + }) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..00b6096 --- /dev/null +++ b/go.mod @@ -0,0 +1,29 @@ +module server + +go 1.16 + +require ( + github.com/BurntSushi/toml v1.1.0 + github.com/bwmarrin/snowflake v0.3.0 + github.com/fbsobreira/gotron-sdk v0.0.0-20230418195951-b7bfbf1c0ade + github.com/gin-gonic/gin v1.7.4 + github.com/go-redis/redis/v8 v8.11.3 + github.com/gogo/protobuf v1.3.2 + github.com/google/uuid v1.3.0 // indirect + github.com/hashicorp/go-uuid v1.0.2 // indirect + github.com/json-iterator/go v1.1.10 // indirect + github.com/liangdas/armyant v0.0.4 + github.com/liangdas/mqant v1.4.11 + github.com/liangdas/mqant-modules v1.3.8 + github.com/lionsoul2014/ip2region v2.2.0-release+incompatible + github.com/mitchellh/mapstructure v1.4.2 + github.com/nats-io/nats.go v1.12.1 + github.com/olivere/elastic/v7 v7.0.29 + github.com/oschwald/geoip2-golang v1.9.0 + github.com/wenzhenxi/gorsa v0.0.0-20230530123828-0320cce15d81 + golang.org/x/net v0.8.0 + golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba + google.golang.org/grpc v1.37.0 + gorm.io/driver/mysql v1.1.2 + gorm.io/gorm v1.21.15 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..22aaff6 --- /dev/null +++ b/go.sum @@ -0,0 +1,953 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM= +github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0/go.mod h1:tPaiy8S5bQ+S5sOiDlINkp7+Ef339+Nz5L5XO+cnOHo= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= +github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= +github.com/araddon/dateparse v0.0.0-20200409225146-d820a6159ab1/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aws/aws-sdk-go v1.40.43/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= +github.com/aws/aws-sdk-go-v2 v1.2.0/go.mod h1:zEQs02YRBw1DjK0PoJv3ygDYOFTre1ejlJWl8FwAuQo= +github.com/aws/aws-sdk-go-v2/config v1.1.1/go.mod h1:0XsVy9lBI/BCXm+2Tuvt39YmdHwS5unDQmxZOYe8F5Y= +github.com/aws/aws-sdk-go-v2/credentials v1.1.1/go.mod h1:mM2iIjwl7LULWtS6JCACyInboHirisUUdkBPoTHMOUo= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.2/go.mod h1:3hGg3PpiEjHnrkrlasTfxFqUsZ2GCk/fMUn4CbKgSkM= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.2/go.mod h1:45MfaXZ0cNbeuT0KQ1XJylq8A6+OpVV2E5kvY/Kq+u8= +github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1/go.mod h1:rLiOUrPLW/Er5kRcQ7NkwbjlijluLsrIbu/iyl35RO4= +github.com/aws/aws-sdk-go-v2/service/sso v1.1.1/go.mod h1:SuZJxklHxLAXgLTc1iFXbEWkXs7QRTQpCLGaKIprQW0= +github.com/aws/aws-sdk-go-v2/service/sts v1.1.1/go.mod h1:Wi0EBZwiz/K44YliU0EKxqTCJGUfYTWXrrBwkq736bM= +github.com/aws/smithy-go v1.1.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw= +github.com/belogik/goes v0.0.0-20151229125003-e54d722c3aff/go.mod h1:PhH1ZhyCzHKt4uAasyx+ljRCgoezetRNf59CUtwUkqY= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= +github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= +github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= +github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= +github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= +github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= +github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= +github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= +github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= +github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= +github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/docker/docker v1.6.2/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= +github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= +github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ethereum/go-ethereum v1.10.26 h1:i/7d9RBBwiXCEuyduBQzJw/mKmnvzsN14jqBmytw72s= +github.com/ethereum/go-ethereum v1.10.26/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/fbsobreira/gotron-sdk v0.0.0-20230418195951-b7bfbf1c0ade h1:nBd+g+UO23Kcj+vnw6a9bmH8o1XuREyb7CnAjtZo6G4= +github.com/fbsobreira/gotron-sdk v0.0.0-20230418195951-b7bfbf1c0ade/go.mod h1:nu7RbxA7PUWdyMI9O/tBVLDjTMyU0MuRERlTyShJKGg= +github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c/go.mod h1:AzA8Lj6YtixmJWL+wkKoBGsLWy9gFrAzi4g+5bCKwpY= +github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61/go.mod h1:Q0X6pkwTILDlzrGEckF6HKjXe48EgsY/l7K7vhY4MW8= +github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= +github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= +github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.7.4 h1:QmUZXrvJ9qZ3GfWvQ+2wnW/1ePrTEJqPKMYEU3lD/DM= +github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= +github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= +github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= +github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/go-redis/redis/v8 v8.11.3 h1:GCjoYp8c+yQTJfc0n69iwSiHjvuAdruxl7elnZCxgt8= +github.com/go-redis/redis/v8 v8.11.3/go.mod h1:xNJ9xDG09FsIPwh3bWdk+0oDWHbtF9rPN0F/oD9XeKc= +github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= +github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.11.2/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.1.0 h1:BNQPM9ytxj6jbjjdRPioQ94T6YXriSopn0i8COv6SRA= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1 h1:LnuDWGNsoajlhGyHJvuWW6FVqRl8JOTPqS6CPTsYjhY= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= +github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= +github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= +github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= +github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= +github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= +github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8= +github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= +github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE= +github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= +github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= +github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19ybifQhZoQNF5D8= +github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= +github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= +github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= +github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI= +github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= +github.com/karalabe/usb v0.0.2/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= +github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v0.0.0-20170224010052-a616ab194758/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= +github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= +github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/liangdas/armyant v0.0.4 h1:AzkRfh/Xu0itKR9OyJOZTe/eS3bA6hDodke8EZj3yCE= +github.com/liangdas/armyant v0.0.4/go.mod h1:T2BqlWt5AAPATZVBccS5PHMaoZ+oqi+Hb2kJOR+oLPw= +github.com/liangdas/mqant v1.3.9/go.mod h1:CVUjpJez2aFMVsbcP3NJdedxobY8RIXB9gaP41LJKto= +github.com/liangdas/mqant v1.4.11 h1:lBMj6FrHgursR2YW3KPsOizkyzVP4wBEzbY4eCwO6IU= +github.com/liangdas/mqant v1.4.11/go.mod h1:vXyBtk3qU4UrkWwtLOgedV+8Jdd3E3/1t+TnkJBDiME= +github.com/liangdas/mqant-modules v1.3.8 h1:pWiOQSN2HS0rXOMW3reh7Id4UTFSHLim/nnhhMnsZoQ= +github.com/liangdas/mqant-modules v1.3.8/go.mod h1:OadvLghzeXabACyO3bACDK0tjd9Vt5vs7vz9AfQJaUY= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lionsoul2014/ip2region v2.2.0-release+incompatible h1:1qp9iks+69h7IGLazAplzS9Ca14HAxuD5c0rbFdPGy4= +github.com/lionsoul2014/ip2region v2.2.0-release+incompatible/go.mod h1:+ZBN7PBoh5gG6/y0ZQ85vJDBe21WnfbRrQQwTfliJJI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/hashstructure v1.0.0 h1:ZkRJX1CyOoTkar7p/mLS5TZU4nJ1Rn/F8u9dGS02Q3Y= +github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo= +github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= +github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= +github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= +github.com/nats-io/jwt v0.3.0 h1:xdnzwFETV++jNc4W1mw//qFyJGb2ABOombmZJQS4+Qo= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/nats-server/v2 v2.1.0 h1:Yi0+ZhRPtPAGeIxFn5erIeJIV9wXA+JznfSxK621Fbk= +github.com/nats-io/nats-server/v2 v2.1.0/go.mod h1:r5y0WgCag0dTj/qiHkHrXAcKQ/f5GMOZaEGdoxxnJ4I= +github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nats.go v1.12.1 h1:+0ndxwUPz3CmQ2vjbXdkC1fo3FdiOQDim4gl3Mge8Qo= +github.com/nats-io/nats.go v1.12.1/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= +github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8= +github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/olivere/elastic/v7 v7.0.29 h1:zvorjSPHFli/0owqfoLq0ZOtVhZSyHsMbRi29Vj7T14= +github.com/olivere/elastic/v7 v7.0.29/go.mod h1:8PlkMD2Xb690IPhIPii2SypuuXtXX3dDcSKGqnEGXzE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU= +github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/oschwald/geoip2-golang v1.9.0 h1:uvD3O6fXAXs+usU+UGExshpdP13GAqp4GBrzN7IgKZc= +github.com/oschwald/geoip2-golang v1.9.0/go.mod h1:BHK6TvDyATVQhKNbQBdrj9eAvuwOMi2zSFXizL3K81Y= +github.com/oschwald/maxminddb-golang v1.11.0 h1:aSXMqYR/EPNjGE8epgqwDay+P30hCBZIveY0WZbAWh0= +github.com/oschwald/maxminddb-golang v1.11.0/go.mod h1:YmVI+H0zh3ySFR3w+oz8PCfglAFj3PuCmui13+P9zDg= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= +github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= +github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= +github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= +github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= +github.com/rjeczalik/notify v0.9.3 h1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY= +github.com/rjeczalik/notify v0.9.3/go.mod h1:gF3zSOrafR9DQEWSE8TjfI9NkooDxbyT4UgRGKZA0lc= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= +github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shengdoushi/base58 v1.0.0 h1:tGe4o6TmdXFJWoI31VoSWvuaKxf0Px3gqa3sUWhAxBs= +github.com/shengdoushi/base58 v1.0.0/go.mod h1:m5uIILfzcKMw6238iWAhP4l3s5+uXyF3+bJKUNhAL9I= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= +github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/gunit v1.4.2/go.mod h1:ZjM1ozSIMJlAz/ay4SG8PeKF00ckUp+zMHZXV9/bvak= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/supranational/blst v0.3.8-0.20220526154634-513d2456b344/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= +github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= +github.com/tyler-smith/go-bip39 v1.0.2 h1:+t3w+KwLXO6154GNJY+qUtIxLTmFjfUmpguQT1OlOT8= +github.com/tyler-smith/go-bip39 v1.0.2/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/urfave/cli/v2 v2.10.2/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/wenzhenxi/gorsa v0.0.0-20230530123828-0320cce15d81 h1:mCWpbHxvTg/p9wIKzyR34b65g9p142HFpp6YtsyXTDw= +github.com/wenzhenxi/gorsa v0.0.0-20230530123828-0320cce15d81/go.mod h1:nfhBTKji6rC8lrjyikx8NJ85JHg6ZQam0a9Je+2RVOg= +github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +github.com/yireyun/go-queue v0.0.0-20180809062148-5e6897360dac h1:a4krU0HaACgZ1ai3epUH6U6Kji7WHiucldhzWv6oD2o= +github.com/yireyun/go-queue v0.0.0-20180809062148-5e6897360dac/go.mod h1:NS8O3p7NiPwC1Yw9xTd9DnDBguxFJG/BL1VPTSfJ5Gw= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zondax/hid v0.9.1 h1:gQe66rtmyZ8VeGFcOpbuH3r7erYtNEAezCAYu8LdkJo= +github.com/zondax/hid v0.9.1/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd v3.3.15+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= +go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20220426173459-3bcf042a4bf5/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191126055441-b0650ceb63d9/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= +gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200108215221-bd8f9a0ef82f/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 h1:PDIOdWxZ8eRizhKa1AAvY53xsvLB1cWorMjslvY3VA8= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.37.0 h1:uSZWeQJX5j11bIQ4AJoj+McDBo29cY1MCoC1wO3ts+c= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.1.2 h1:OofcyE2lga734MxwcCW9uB4mWNXMr50uaGRVwQL2B0M= +gorm.io/driver/mysql v1.1.2/go.mod h1:4P/X9vSc3WTrhTLZ259cpFd6xKNYiSSdSZngkSBGIMM= +gorm.io/gorm v1.21.12/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= +gorm.io/gorm v1.21.15 h1:gAyaDoPw0lCyrSFWhBlahbUA1U4P5RViC1uIqoB+1Rk= +gorm.io/gorm v1.21.15/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.1.3 h1:qTakTkI6ni6LFD5sBwwsdSO+AQqbSIxOauHTTQKZ/7o= +honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/indiaprovider b/indiaprovider new file mode 100644 index 0000000..12d2c97 Binary files /dev/null and b/indiaprovider differ diff --git a/main.go b/main.go new file mode 100644 index 0000000..a375ac3 --- /dev/null +++ b/main.go @@ -0,0 +1,132 @@ +package main + +import ( + "flag" + "fmt" + "math/rand" + "os" + "runtime" + "server/modules/common" + "server/modules/crash" + "server/modules/pay" + "strconv" + "time" + + "github.com/liangdas/mqant/selector" + + mqant "github.com/liangdas/mqant" + "github.com/nats-io/nats.go" + + "server/config" + "server/modules/backend" + mgate "server/modules/gate" + "server/modules/hall" + "server/modules/web" + + "net/http" + _ "net/http/pprof" + + "github.com/liangdas/mqant/log" + "github.com/liangdas/mqant/module" + "github.com/liangdas/mqant/registry" + "github.com/liangdas/mqant/registry/consul" +) + +func main() { + log.Info("启动游戏服务器 ...") + rand.Seed(time.Now().UTC().UnixNano()) + runtime.GOMAXPROCS(runtime.NumCPU()) + + modulefile := flag.String("module", "module.json", "module configure file") + logdir := flag.String("log", "logs", "log file directory") + wddir := flag.String("wd", ".", "Server work directory") + processID := flag.String("pid", "development", "Server ProcessID?") + bidir := flag.String("bi", "bi", "bi file directory?") + + baseConf := flag.String("baseConf", "../baseConf.toml", "base configure file") + + conf := flag.String("conf", "conf.toml", "server configure file") + + //flag.String("rule", "rule.toml", "rule config toml file") + + // flag.String("Game_config", "Game_config.xlsx", "excel game configure file") + // flag.String("robot_config", "robot_config.xlsx", "excel robot configure file") + // flag.String("drama_config", "drama_config.xlsx", "excel drama configure file") + flag.String("name", "name.xlsx", "excel name file") + + // flag.String("errcode", "errcode.toml", "error code message define") + + debug := flag.Bool("debug", true, "debug mode, output to screen?") + + flag.Parse() + wd1, _ := os.Getwd() + log.Info(wd1) + if err := config.LoadBaseConfig(*baseConf); err != nil { + panic(fmt.Errorf("load base config file error %+v", err)) + } + + if err := config.LoadConfig(*conf); err != nil { + panic(fmt.Errorf("load config file error %+v", err)) + } + + log.Info("-------------- configure -------------------") + log.Info("%+v", config.GetBase()) + log.Info("%+v", config.GetConfig()) + log.Info("---------------------------------------------") + + if config.GetConfig().WorkID > 0 { + go http.ListenAndServe("localhost:"+strconv.Itoa(int(8080+config.GetConfig().WorkID)), nil) + } + + rs := consul.NewRegistry(func(options *registry.Options) { + options.Addrs = []string{config.GetRegistry()} + }) + + nc, err := nats.Connect(config.GetNats(), nats.MaxReconnects(10000)) + if err != nil { + panic(fmt.Errorf("nats error %v", err)) + } + + log.Info("================ flags ================") + log.Info("module %s", *modulefile) + log.Info("log dir %s", *logdir) + log.Info("wd dir %s", *wddir) + log.Info("pid %s", *processID) + log.Info("bi dir %s", *bidir) + log.Info("debug %v", *debug) + wd, _ := os.Getwd() + log.Info("work directory %s", wd) + log.Info("========================================") + + app := mqant.CreateApp( + module.Parse(false), + module.Configure(*modulefile), + module.LogDir(*logdir), + module.WorkDir(*wddir), + module.ProcessID(*processID), + module.BILogDir(*bidir), + module.KillWaitTTL(1*time.Minute), + module.Debug(*debug), //只有是在调试模式下才会在控制台打印日志, 非调试模式下只在日志文件中输出日志 + module.Nats(nc), //指定nats rpc + module.Registry(rs), //指定服务发现 + module.RegisterTTL(20*time.Second), + module.RegisterInterval(10*time.Second), + module.Selector(selector.NewSelector(selector.Registry(rs))), + ) + + err = app.Run( + //已实现的模块都应该在此处传入 + hall.Module(), + common.Module(), + mgate.Module(), + web.Module(), + backend.Module(), + pay.Module(), + // blockpay.Module(), + crash.Module(), + ) + + if err != nil { + panic(fmt.Errorf("app run failed %v", err)) + } +} diff --git a/modules/backend/app/response.go b/modules/backend/app/response.go new file mode 100644 index 0000000..a223b91 --- /dev/null +++ b/modules/backend/app/response.go @@ -0,0 +1,249 @@ +package app + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "server/call" + "server/common" + "server/db" + "server/modules/backend/bdb" + "server/modules/backend/values" + "server/natsClient" + "server/pb" + "strings" + "time" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" + "gorm.io/gorm" +) + +type Gin struct { + C *gin.Context + R + User *values.User +} + +type R struct { + Data interface{} `json:"data"` + Msg string `json:"msg"` + Code int `json:"code"` +} + +func NewApp(c *gin.Context) *Gin { + g := &Gin{C: c, R: R{Code: values.CodeOK, Msg: "ok"}} + user, ex := c.Get("user") + if ex { + g.User = user.(*values.User) + if len(g.User.Channels) > 0 { + json.Unmarshal([]byte(g.User.Channels), &g.User.SChannels) + } + } + return g +} + +// Response setting gin.JSON +func (g *Gin) Response() { + if g.Code == values.CodeRetry { + g.Msg = "内部错误" + } else if g.Code == values.CodePower { + g.Msg = "您无权操作此项" + } + g.C.JSON(http.StatusOK, g.R) +} + +func (g *Gin) RecordEdit(t int, detail string) { + u, _ := g.C.Get("user") + user := u.(*values.User) + one := &values.EditHistory{Model: t, Detail: detail, Time: time.Now().Unix(), Operator: user.Name, UID: int(user.ID)} + bdb.BackDB.Create(one) +} + +func (g *Gin) S(one interface{}) (pass bool) { + err := g.C.ShouldBind(one) + if err != nil { + g.R.Code = values.CodeParam + g.R.Msg = err.Error() + log.Error("bind %v err:%v", reflect.TypeOf(one), err) + return + } + pass = true + return +} + +// U 统一json解析方法 +func (g *Gin) U(src []byte, tar interface{}) (pass bool) { + err := json.Unmarshal(src, tar) + if err != nil { + log.Error("%v err:%v", reflect.TypeOf(tar), err) + g.R.Code = values.CodeRetry + g.R.Msg = err.Error() + return + } + pass = true + return +} + +// 从数据库中读取数据 +func (g *Gin) MGet(tar interface{}) (pass bool) { + err := db.Mysql().Get(tar) + if err != nil { + log.Error("err:%v", err) + g.R.Code = values.CodeRetry + g.R.Msg = err.Error() + return + } + pass = true + return +} + +// 从数据库中读取数据 +func (g *Gin) MGetAll(model, tar interface{}) (pass bool) { + sql, sort := values.CheckQueryControl(model) + if _, err := db.Mysql().QueryAll(sql, sort, model, tar); err != nil { + log.Error("err:%v", err) + g.Code = values.CodeRetry + g.R.Msg = err.Error() + return + } + pass = true + return +} + +func (g *Gin) MUpdateAll(updates []map[string]interface{}, element interface{}) (pass bool) { + for _, v := range updates { + tmp := reflect.New(reflect.TypeOf(element).Elem()).Interface() + // log.Debug("%v", v) + tmpbyte, _ := json.Marshal(v) + err := json.Unmarshal(tmpbyte, &tmp) + if err != nil { + log.Error("err%v", err) + return + } + // mapstructure.Decode(v, &tmp) + // log.Debug("%+v", tmp) + values.CheckUpdateControl(tmp) + ref := reflect.ValueOf(tmp).Elem() + reft := reflect.TypeOf(tmp).Elem() + id := reflect.ValueOf(tmp).Elem().FieldByName("ID").Int() + if id == 0 { + if err := db.Mysql().Create(tmp); err != nil { + g.Code = values.CodeRetry + return + } + } else { + update := map[string]interface{}{} + for k := range v { + field, ok := reft.FieldByName(k) + if !ok { + continue + } + tag := field.Tag.Get("web") + if tag == "" || tag == "-" { + continue + } + update[tag] = ref.FieldByName(k).Interface() + } + // update := util.StructToMap(tmp, "web") + model := reflect.New(reflect.TypeOf(element).Elem()) + model.Elem().FieldByName("ID").SetInt(id) + // log.Debug("element:%v", element) + log.Debug("update:%v", update) + // log.Debug("model:%v", model) + if err := db.Mysql().Update(model.Interface(), update); err != nil { + g.Code = values.CodeRetry + return + } + } + } + pass = true + return +} + +func (g *Gin) MDel(id int, element interface{}) (pass bool) { + tmp := reflect.New(reflect.TypeOf(element).Elem()) + tmp.Elem().FieldByName("ID").SetInt(int64(id)) + if !db.Mysql().Exist(tmp.Interface()) { + g.Code = values.CodeParam + g.Msg = "该id不存在" + return + } + if err := db.Mysql().Del(tmp.Interface()); err != nil { + g.Code = values.CodeRetry + return + } + pass = true + return +} + +// MCommit 提交事务 +func (g *Gin) MCommit(tx *gorm.DB) { + if g.Code == values.CodeOK { + if err := tx.Commit().Error; err != nil { + tx.Rollback() + return + } + } else { + tx.Rollback() + return + } +} + +// CheckPower 检查权限是否合法 +func (g *Gin) CheckPower(power string) bool { + newMap := map[int][]int{} + if err := json.Unmarshal([]byte(power), &newMap); err != nil { + log.Error("err:%v", err) + g.Code = values.CodeParam + g.Msg = err.Error() + return false + } + for i := range newMap { + if !values.IsValidPower(i) { + log.Error("invalid power:%v", i) + g.Code = values.CodeParam + g.Msg = fmt.Sprintf("未知权限参数%v", i) + return false + } + } + return true +} + +func (g *Gin) CommonConfig() { + path := g.C.Request.URL.Path + index := strings.LastIndex(path, "/") + opt := path[index+1:] + path = path[:index] + index = strings.LastIndex(path, "/") + t := path[index+1:] + reloadType := values.GetControlType(t) + element, list := common.GetConfigStructByType(reloadType) + var resp interface{} + if opt == "list" { + if !g.MGetAll(element, list) { + return + } + resp = values.GMConfigCommonListResp{Config: list} + } else if opt == "edit" { + req := new(values.GMConfigCommonEditReq) + if !g.S(req) { + return + } + if !g.MUpdateAll(req.Config, element) { + return + } + call.Publish(natsClient.TopicReloadConfig, &pb.ReloadGameConfig{Type: int32(reloadType)}) + } else if opt == "del" { + req := new(values.GMConfigCommonDelReq) + if !g.S(req) { + return + } + if !g.MDel(req.ID, element) { + return + } + call.Publish(natsClient.TopicReloadConfig, &pb.ReloadGameConfig{Type: int32(reloadType)}) + } + g.Data = resp +} diff --git a/modules/backend/bdb/exec.go b/modules/backend/bdb/exec.go new file mode 100644 index 0000000..966cf41 --- /dev/null +++ b/modules/backend/bdb/exec.go @@ -0,0 +1,21 @@ +package bdb + +import ( + "encoding/json" + "server/modules/backend/values" + + "github.com/liangdas/mqant/log" +) + +func GetPowerByRole(role int) map[int][]int { + one := &values.Role{Role: role} + BackDB.Get(one) + ret := map[int][]int{} + if len(one.Power) > 0 { + err := json.Unmarshal([]byte(one.Power), &ret) + if err != nil { + log.Error("err:%v", err) + } + } + return ret +} diff --git a/modules/backend/bdb/mysql.go b/modules/backend/bdb/mysql.go new file mode 100644 index 0000000..b6a31de --- /dev/null +++ b/modules/backend/bdb/mysql.go @@ -0,0 +1,32 @@ +package bdb + +import ( + "server/config" + mdb "server/db/mysql" + "server/modules/backend/values" +) + +var BackDB *mdb.MysqlClient + +func InitMysql() { + var err error + BackDB, err = mdb.InitMysqlClient(config.GetConfig().Backend.DB, false) + if err != nil { + panic(err) + } +} + +// MigrateDB 自动数据库迁移 +func MigrateDB() { + err := BackDB.C().AutoMigrate( + new(values.User), + new(values.EditHistory), + new(values.Role), + new(values.ReviewData), + new(values.FirstPageData), + new(values.PlatformData), + ) + if err != nil { + panic(err) + } +} diff --git a/modules/backend/config.go b/modules/backend/config.go new file mode 100644 index 0000000..3f757bc --- /dev/null +++ b/modules/backend/config.go @@ -0,0 +1,12 @@ +package backend + +import ( + "server/call" + "server/pb" +) + +func loadConfig() error { + c := map[int][]func(*pb.ReloadGameConfig) error{} + call.LoadConfigs(c) + return nil +} diff --git a/modules/backend/handler/account/account.go b/modules/backend/handler/account/account.go new file mode 100644 index 0000000..5ed8298 --- /dev/null +++ b/modules/backend/handler/account/account.go @@ -0,0 +1,38 @@ +// 账号相关的接口实现 +package handler + +import ( + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/bdb" + "server/modules/backend/values" + "server/util" + + "github.com/liangdas/mqant/log" + + "github.com/gin-gonic/gin" +) + +func Login(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.LoginReq) + if !a.S(req) { + return + } + one := &values.User{Account: req.Account, Password: req.Pass} + if err := bdb.BackDB.Get(one); err != nil { + a.Code = values.CodeParam + a.Msg = "账号不存在" + return + } + token := util.GetSimpleRandomString(6) + err := db.Redis().SetJsonData(common.GetBackendTokenKey(token), one, values.RedisTokenEx) + if err != nil { + log.Error(err.Error()) + } + a.Data = values.LoginResp{Role: one.Role, Token: token, Power: one.Power} +} diff --git a/modules/backend/handler/blockpay/blockpay.go b/modules/backend/handler/blockpay/blockpay.go new file mode 100644 index 0000000..fd02766 --- /dev/null +++ b/modules/backend/handler/blockpay/blockpay.go @@ -0,0 +1,294 @@ +package handler + +import ( + "context" + "fmt" + "server/call" + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/values" + "server/pb" + "time" + + utils "server/modules/backend/util" + + "github.com/gin-gonic/gin" + "github.com/gogo/protobuf/proto" + "github.com/liangdas/mqant/log" + mqrpc "github.com/liangdas/mqant/rpc" + "github.com/olivere/elastic/v7" +) + +func QueryOrder(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.BlockOrderReq) + if !a.S(req) { + return + } + to, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + ret, err := call.GetCaller().GetApp().Call(to, "blockpay", "queryOrder", mqrpc.Param(&pb.BlockPayQueryOrderReq{ + OrderID: req.OrderID, + TxID: req.TxID, + })) + if err != "" { + log.Error("err:%e", err) + a.Code = values.CodeRetry + return + } + retData := new(pb.CommonResp) + if err := proto.Unmarshal(ret.([]byte), retData); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + if retData.Code != 0 { + a.Code = values.CodeRetry + a.Msg = retData.Msg + return + } +} + +func LocalAddrs(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + to, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + ret, err := call.GetCaller().GetApp().Call(to, "blockpay", "queryAddr", mqrpc.Param(&pb.CommonMsg{})) + if err != "" { + log.Error("err:%e", err) + a.Code = values.CodeRetry + return + } + retData := new(pb.BlockPayQueryLocalAddrResp) + if err := proto.Unmarshal(ret.([]byte), retData); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + resp := &values.BlockAddrsResp{Addrs: retData.Addrs} + a.Data = resp +} + +func AddAddrs(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.AddBlockAddrsReq) + if !a.S(req) { + return + } + to, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + ret, err := call.GetCaller().GetApp().Call(to, "blockpay", "addAddr", mqrpc.Param(&pb.BlockPayAddLocalAddrReq{ + Address: req.Address, + Private: req.Private, + })) + if err != "" { + log.Error("err:%e", err) + a.Code = values.CodeRetry + return + } + retData := new(pb.CommonResp) + if err := proto.Unmarshal(ret.([]byte), retData); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + if retData.Code != 0 { + a.Code = int(retData.Code) + a.Msg = retData.Msg + return + } +} + +func RemoveAddrs(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.AddBlockAddrsReq) + if !a.S(req) { + return + } + to, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + ret, err := call.GetCaller().GetApp().Call(to, "blockpay", "removeAddr", mqrpc.Param(&pb.BlockPayAddLocalAddrReq{ + Address: req.Address, + })) + if err != "" { + log.Error("err:%e", err) + a.Code = values.CodeRetry + return + } + retData := new(pb.CommonResp) + if err := proto.Unmarshal(ret.([]byte), retData); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + if retData.Code != 0 { + a.Code = values.CodeRetry + a.Msg = retData.Msg + return + } +} + +func Transfer(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.BlockTransferReq) + if !a.S(req) { + return + } + to, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + ret, err := call.GetCaller().GetApp().Call(to, "blockpay", "transfer", mqrpc.Param(&pb.BlockPayTransferReq{ + FromAddr: req.FromAddr, + ToAddr: req.ToAddr, + Type: req.Type, + Amount: req.Amount, + })) + if err != "" { + log.Error("err:%e", err) + a.Code = values.CodeRetry + return + } + retData := new(pb.CommonResp) + if err := proto.Unmarshal(ret.([]byte), retData); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + if retData.Code != 0 { + a.Code = values.CodeRetry + a.Msg = retData.Msg + return + } +} + +func TransferList(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.BlockTransferListReq) + if !a.S(req) { + return + } + resp := &values.BlockTransferListResp{List: []common.ESTron{}} + a.Data = resp + su, eu := utils.GetQueryUnix(req.Start, req.End) + if req.Num > 500 { + req.Num = 500 + } + q := elastic.NewBoolQuery() + q.Filter(elastic.NewRangeQuery("Time").Gte(su)) + q.Filter(elastic.NewRangeQuery("Time").Lt(eu)) + if req.Type > 0 { + q.Filter(elastic.NewTermQuery("Type", req.Type)) + } + if req.Status > 0 { + q.Filter(elastic.NewTermQuery("Status", req.Status)) + } + if req.Success != nil { + q.Filter(elastic.NewTermQuery("Success", *req.Success)) + } + db.ES().QueryList(common.ESIndexTron, req.Page-1, req.Num, q, &resp.List, "Time", false) + resp.Count = db.ES().Count(common.ESIndexTron, q) +} + +// 获取玩家钱包的usdt总和,且返回预估回收所需要的trx +func GetPlayerUsdts(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.PlayerWalletScanReq) + if !a.S(req) { + return + } + fee := GetFee(a) + if a.Code != values.CodeOK { + return + } + sql := "" + if req.Down > 0 { + sql = fmt.Sprintf("usdt >= %v", req.Down) + } else { + sql = "usdt > 0" + } + if req.Up > 0 { + sql += fmt.Sprintf(" and usdt <= %v", req.Up) + } + count := db.Mysql().Count(&common.TronData{}, sql) + a.Data = values.PlayerWalletUsdtsResp{Fee: fee * count, TotalAmount: db.Mysql().Sum(&common.TronData{}, sql, "usdt"), Count: count} +} + +// 扫描符合条件的所有玩家的 +func ScanPlayerWallet(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.PlayerWalletScanReq) + if !a.S(req) { + return + } + + to, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + ret, err := call.GetCaller().GetApp().Call(to, "blockpay", "scanPlayerWallet", mqrpc.Param(&pb.BlockPayScanPlayerWalletReq{ + Down: req.Down, + Up: req.Up, + })) + if err != "" { + log.Error("err:%e", err) + a.Code = values.CodeRetry + return + } + retData := new(pb.BlockPayScanPlayerWalletResp) + if err := proto.Unmarshal(ret.([]byte), retData); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + if retData.Code != 0 { + a.Code = values.CodeRetry + a.Msg = retData.Msg + return + } + a.Data = values.PlayerWalletScanResp{Success: retData.Success, Fail: retData.Fail} +} + +func GetFee(a *app.Gin) int64 { + to, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + ret, err := call.GetCaller().GetApp().Call(to, "blockpay", "getFee", mqrpc.Param(&pb.CommonMsg{})) + if err != "" { + log.Error("err:%e", err) + a.Code = values.CodeParam + return 0 + } + retData := new(pb.BlockPayGetFeeResp) + if err := proto.Unmarshal(ret.([]byte), retData); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeParam + return 0 + } + if retData.Code != 0 { + a.Code = values.CodeParam + a.Msg = retData.Msg + return 0 + } + return retData.Fee +} diff --git a/modules/backend/handler/common/common.go b/modules/backend/handler/common/common.go new file mode 100644 index 0000000..6f91ef6 --- /dev/null +++ b/modules/backend/handler/common/common.go @@ -0,0 +1,79 @@ +package handler + +import ( + "server/call" + "server/modules/backend/app" + "server/modules/backend/bdb" + "server/modules/backend/values" + "server/util" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +func ProductList(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + products := call.GetConfigPayProduct() + resp := &values.ProductListResp{List: products} + a.Data = resp +} + +func ChannelList(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + ret := call.GetChannelList() + if ret == nil { + a.Code = values.CodeRetry + return + } + resp := values.ChannelListResp{} + for _, v := range ret { + if v.Show != 2 { + continue + } + if len(a.User.SChannels) > 0 { + if !util.SliceContain(a.User.SChannels, v.ChannelID) { + continue + } + } + resp.List = append(resp.List, v) + } + a.Data = resp +} + +func UserInfo(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + resp := values.UserInfoResp{Name: a.User.Name, Role: a.User.Role, Power: a.User.Power} + a.Data = resp +} + +func AccountList(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + ret := call.GetChannelList() + if ret == nil { + a.Code = values.CodeRetry + return + } + resp := &values.AccountListResp{} + a.Data = resp + list := []values.User{} + if err := bdb.BackDB.C().Find(&list).Error; err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + for _, v := range list { + resp.List = append(resp.List, v.Account) + } +} diff --git a/modules/backend/handler/examine/examine.go b/modules/backend/handler/examine/examine.go new file mode 100644 index 0000000..67602c6 --- /dev/null +++ b/modules/backend/handler/examine/examine.go @@ -0,0 +1,326 @@ +package handler + +import ( + "fmt" + "server/call" + "server/common" + "server/db" + "server/modules/backend/app" + utils "server/modules/backend/util" + "server/modules/backend/values" + "time" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +func WithdrawList(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.WithdrawListReq) + if !a.S(req) { + return + } + if req.Type <= common.WithdrawOrderTypeZero || req.Type >= common.WithdrawOrderTypeAll { + a.Code = values.CodeRetry + return + } + + table1 := `(SELECT id,uid,channel_id,pay_channel,orderid,apipayid,payaccount,amount,create_time,callback_time,status,operator from withdraw_order %s)a` + table2 := `(SELECT id,platform,mobile,tag from users %s)b` + table3 := `(SELECT uid,brl from player_currency)c` + table4 := `(SELECT uid,total_recharge,total_recharge_count,total_withdraw,total_withdraw_count FROM recharge_info)d` + + // 表1的查询条件 + table1Condition := fmt.Sprintf(" where event = %v and order_type = %d", common.CurrencyEventWithDraw, req.Type) + if req.Start != nil { + st, _ := utils.GetQueryUnix(*req.Start, "") + table1Condition += fmt.Sprintf(" AND create_time >= %d ", st) + } + if req.End != nil { + _, et := utils.GetQueryUnix("", *req.End) + table1Condition += fmt.Sprintf(" AND create_time < %d ", et) + } + if req.Status != nil { + table1Condition += fmt.Sprintf(" AND status = %d ", *req.Status) + } + if req.Channel != nil { + table1Condition += fmt.Sprintf(" AND channel_id = %d ", *req.Channel) + } + if req.UID != nil { + table1Condition += fmt.Sprintf(" AND uid = %d ", *req.UID) + } + if req.Amount != nil { + table1Condition += fmt.Sprintf(" AND amount = %d ", *req.Amount) + } + if req.OderID != nil { + table1Condition += fmt.Sprintf(" AND orderid = %s ", *req.OderID) + } + if req.APIPayID != nil { + table1Condition += fmt.Sprintf(" AND apipayid = %s ", *req.APIPayID) + } + if req.PayChannel != nil { + table1Condition += fmt.Sprintf(" AND pay_channel = %d ", *req.PayChannel) + } + if req.Operator != nil { + table1Condition += fmt.Sprintf(" AND operator = %s ", *req.Operator) + } + + // 表2的查询条件 + table2Condition := "" + if req.Platform != nil { + table2Condition += fmt.Sprintf(" where platform = %d ", *req.Platform) + } + + sqlCount := " SELECT COUNT(*) AS count from" + sqlList := `SELECT a.id,a.uid,a.channel_id,a.pay_channel,a.orderid as OrderID,a.apipayid as APIPayID,a.payaccount,a.amount,a.create_time,a.callback_time, + a.status,a.operator,b.platform,b.mobile,b.tag,c.brl,d.total_recharge,d.total_recharge_count,d.total_withdraw,d.total_withdraw_count from ` + querySql := fmt.Sprintf(table1, table1Condition) + " inner join " + fmt.Sprintf(table2, table2Condition) + " on a.uid = b.id inner join " + + table3 + " on a.uid = c.uid inner join " + table4 + " on a.uid = d.uid " + + var count int64 + err := db.Mysql().QueryBySql(sqlCount+querySql, &count) + if err != nil { + log.Error(err.Error()) + a.Code = values.CodeRetry + return + } + + switch req.Sort { + case 1: + querySql += " ORDER BY a.create_time DESC " + case 2: + querySql += " ORDER BY a.audit_time DESC " + default: + querySql += " ORDER BY a.create_time DESC " + } + + querySql += fmt.Sprintf(" LIMIT %d, %d", (req.Page-1)*req.Num, req.Num) + + WithdrawOrderArr := []values.WithdrawInfo{} + err = db.Mysql().QueryBySql(sqlList+querySql, &WithdrawOrderArr) + if err != nil { + log.Error("查询体现审核失败, error : %s", err.Error()) + a.Code = values.CodeRetry + return + } + a.Data = values.WithdrawListResp{List: WithdrawOrderArr, Count: count} +} + +func WithdrawExamine(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.WithdrawExamineReq) + if !a.S(req) { + return + } + if req.Opt < 1 || req.Opt > 3 { + a.Code = values.CodeParam + a.Msg = "非法操作" + return + } + one := new(common.WithdrawOrder) + one.ID = uint(req.ID) + err := db.Mysql().Get(one) + if err != nil || one.UID == 0 { + log.Error("err:%v", err) + a.Code = values.CodeParam + a.Msg = "订单不存在" + return + } + if one.Event != int(common.CurrencyEventWithDraw) { + a.Code = values.CodeParam + a.Msg = "订单不合法" + return + } + if one.Status != common.StatusROrderCreate && one.Status != common.StatusROrderPending { + a.Code = values.CodeParam + a.Msg = "订单已审核,不能重复操作" + return + } + + u := map[string]interface{}{"audit_time": time.Now().Unix(), "operator": a.User.Account} + if len(req.Remark) > 0 { + u["remarks"] = req.Remark + } + // 审核通过,发起提现,会在pay模块扫描提交 + if req.Opt == 1 { + u["status"] = common.StatusROrderWaitting + } else if req.Opt == 3 { + u["status"] = common.StatusROrderPending + } + if res := db.Mysql().C().Model(&common.WithdrawOrder{}).Where("id = ? and status = ?", req.ID, common.StatusROrderCreate).Updates(u); res.Error != nil || res.RowsAffected == 0 { + log.Error("err:%v", res.Error) + a.Code = values.CodeRetry + a.Msg = "系统错误,审核失败" + return + } + + if req.Opt == 2 { // 拒绝 + err := call.ReturnBackWithdraw(one, common.StatusROrderCreate, common.StatusROrderRefuse) + if err != nil { + log.Error(err.Error()) + } + } + a.RecordEdit(values.PowerExamineWithdraw, fmt.Sprintf("审核退出订单ID:%v", req.ID)) +} + +// WithdrawReturn 强制取消代付,直接退 +func WithdrawReturn(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.WithdrawReturnReq) + if !a.S(req) { + return + } + one := &common.WithdrawOrder{OrderID: req.OrderID} + err := db.Mysql().Get(one) + if err != nil { + one = &common.WithdrawOrder{APIPayID: req.OrderID} + err := db.Mysql().Get(one) + if err != nil { + a.Code = values.CodeParam + a.Msg = "订单不存在" + return + } + } + if one.Event != int(common.CurrencyEventWithDraw) { + a.Code = values.CodeParam + a.Msg = "订单不合法" + return + } + if one.Status == common.StatusROrderFinish { + a.Code = values.CodeParam + a.Msg = "订单已完成打款,不能退回" + return + } + + err = call.ReturnBackWithdraw(one, common.StatusROrderPay, common.StatusROrderFail) + if err != nil { + log.Error(err.Error()) + } + a.RecordEdit(values.PowerExamineWithdraw, fmt.Sprintf("退回退出订单ID:%v", req.OrderID)) +} + +// PayCallback 后台模拟订单回调 +func PayCallback(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.PayCallbackReq) + if !a.S(req) { + return + } + if req.Status != common.StatusROrderPay && req.Status != common.StatusROrderFail { + a.Code = values.CodeParam + return + } + one := &common.RechargeOrder{OrderID: req.OrderID} + err := db.Mysql().Get(one) + if err != nil { + one = &common.RechargeOrder{APIPayID: req.OrderID} + err := db.Mysql().Get(one) + if err != nil { + a.Code = values.CodeParam + a.Msg = "订单不存在" + return + } + } + if one.Event != int(common.CurrencyEventReCharge) && one.Event != common.CurrencyEventGMRecharge { + a.Code = values.CodeParam + a.Msg = "订单不合法" + return + } + if one.Status == common.StatusROrderPay { + a.Code = values.CodeParam + a.Msg = "订单已完成" + return + } + if req.Status == common.StatusROrderFail && one.Status == common.StatusROrderFail { + a.Code = values.CodeParam + a.Msg = "订单已回调失败" + return + } + status := false + str := "失败" + if req.Status == common.StatusROrderPay { + status = true + str = "成功" + } + err = call.RechargeCallback(one, status, "", "") + if err != nil { + log.Error(err.Error()) + } + a.RecordEdit(values.PowerExamineWithdraw, fmt.Sprintf("修改充值订单ID:%v,回调状态:%s", req.OrderID, str)) +} + +func ShareOrderList(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.ShareOrderListReq) + if !a.S(req) { + return + } + su, eu := utils.GetQueryUnix(req.Start, req.End) + sql := fmt.Sprintf("create_time>=%d and create_time<%d", su, eu) + if req.Channel > 0 { + sql += fmt.Sprintf(" and channel_id = %d", req.Channel) + } + if req.Status != nil { + sql += fmt.Sprintf(" and status = %d", *req.Status) + } + + resp := new(values.ShareOrderListResp) + a.Data = resp + db.Mysql().QueryListW(int(req.Page)-1, int(req.Num), "create_time desc", &common.ShareOrder{}, &resp.List, sql) +} + +func ShareOrderExamine(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.ShareOrderExamineReq) + if !a.S(req) { + return + } + if req.Opt != 1 && req.Opt != 2 { + a.Code = values.CodeParam + a.Msg = "非法操作" + return + } + order := &common.ShareOrder{OrderID: req.OrderID} + db.Mysql().Get(order) + if order.Status >= common.StatusROrderFinish { + a.Code = values.CodeParam + return + } + if req.Opt == 1 { + res, err := db.Mysql().UpdateResW(&common.ShareOrder{}, map[string]interface{}{"status": common.StatusROrderFinish}, fmt.Sprintf("orderid = '%s' and status = %d", req.OrderID, common.StatusROrderCreate)) + if err != nil || res == 0 { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + call.UpdateCurrencyPro(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: order.UID, + Value: order.Amount, + Event: common.CurrencyEventShareWithdraw, + Type: common.CurrencyBrazil, + }, + }) + } else if req.Opt == 2 { // 拒绝 + db.Mysql().UpdateResW(&common.ShareOrder{}, map[string]interface{}{"status": common.StatusROrderFail}, fmt.Sprintf("orderid = '%s' and status = %d", req.OrderID, common.StatusROrderCreate)) + } + a.RecordEdit(values.PowerExamineWithdraw, fmt.Sprintf("审核分享订单ID:%v,opt:%v", req.OrderID, req.Opt)) +} diff --git a/modules/backend/handler/firstpage.go b/modules/backend/handler/firstpage.go new file mode 100644 index 0000000..4ed5ea6 --- /dev/null +++ b/modules/backend/handler/firstpage.go @@ -0,0 +1,140 @@ +package handler + +import ( + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/bdb" + "server/modules/backend/models" + "server/modules/backend/values" + "server/util" + "time" + + utils "server/modules/backend/util" + + "github.com/gin-gonic/gin" + "github.com/olivere/elastic/v7" +) + +func FirstPageRealData(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + resp := &values.FirstPageRealDataResp{} + a.Data = resp + + cids := []*int{} + if len(a.User.SChannels) > 0 { + for i := range a.User.SChannels { + cids = append(cids, &a.User.SChannels[i]) + } + } + su := util.GetZeroTime(time.Now()).Unix() + q := models.NewQ(&su, nil, cids...) + q.Filter(elastic.NewTermQuery("GameID", 0)) + one := common.ESNewOnline{} + db.ES().QueryOne(common.ESIndexBackPlayerOnline, q, &one, "Time", false) + + resp.Online = int64(one.Total) + resp.NewOnline = int64(one.New) + resp.RechargeOnline = int64(one.Recharge) + resp.RechargeAmount = models.GetAmountTotalBySQLs(su, su, cids...) +} + +func FirstPageData(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.FirstPageDataReq) + if !a.S(req) { + return + } + resp := &values.FirstPageDataResp{} + a.Data = resp + su, eu := utils.GetQueryUnix(req.Date, req.Date) + cids := []*int{} + if len(a.User.SChannels) > 0 { + for i := range a.User.SChannels { + cids = append(cids, &a.User.SChannels[i]) + } + } + + data := &values.FirstPageData{Time: su} + if len(cids) == 0 { + bdb.BackDB.Get(data) + tag := data.ID == 0 + if tag || !data.Check { + data = GetFirstPageData(su, eu) + data.Check = true + if su >= util.GetZeroTime(time.Now()).Unix() { + data.Check = false + } + if tag { + bdb.BackDB.Create(data) + } else { + bdb.BackDB.Update(&values.FirstPageData{Time: su}, data) + } + } + } else { + data = GetFirstPageData(su, eu, cids...) + } + resp.Data = data +} + +func GetFirstPageData(start, end int64, channels ...*int) *values.FirstPageData { + ret := &values.FirstPageData{} + ret.Time = start + ret.Date = time.Unix(start, 0).Format("20060102") + + // 完成注册数 + ret.NewCount = models.GetNewPlayerCountBySqls(start, end, channels...) + + // 新增付费人数 + ret.NewPayCount = models.GetNewPayCountBySqls(start, end, channels...) + + // 付费总额 + ret.RechargeTotal = models.GetAmountTotalBySQLs(start, end, channels...) + + // 赠送总额 + ret.WithdrawTotal = models.GetWithdrawAmountTotalBySQLs(start, end, common.CurrencyBrazil, channels...) + + // 发起充值订单数 + ret.RechargeOrderCreate = models.GetOrderCount(start, end, common.StatusROrderCreate, 1, channels...) + + // 完成充值订单数 + ret.RechargeOrderFinish = models.GetOrderCount(start, end, common.StatusROrderPay, 1, channels...) + + // 发起赠送订单数 + ret.WithdrawOrderCreate = models.GetOrderCount(start, end, common.StatusROrderCreate, 2, channels...) + + // 完成赠送订单数 + ret.WithdrawOrderFinish = models.GetOrderCount(start, end, common.StatusROrderFinish, 2, channels...) + + // 充值成功率 + ret.RechargeSuccessPer = utils.GetPer(ret.RechargeOrderFinish, ret.RechargeOrderCreate) + + // 充值赠送比 + ret.WithdrawPer = utils.GetPer(ret.WithdrawTotal, ret.RechargeTotal) + + // 平台下注 + ret.Bet = models.GetGameInOut(start, end, 1, channels...) + + // 平台返奖 + ret.Settle = models.GetGameInOut(start, end, 2, channels...) + + // 平台rtp + ret.RTP = utils.GetPer(ret.Settle, ret.Bet) + + // 厂商下注 + ret.ProviderBet = ret.Bet + + // 厂商返奖 + ret.ProviderSettle = ret.Settle + + // 厂商rtp + ret.ProviderRTP = utils.GetPer(ret.ProviderSettle, ret.ProviderBet) + + return ret +} diff --git a/modules/backend/handler/gm/control.go b/modules/backend/handler/gm/control.go new file mode 100644 index 0000000..857ec25 --- /dev/null +++ b/modules/backend/handler/gm/control.go @@ -0,0 +1,176 @@ +package handler + +import ( + "reflect" + "server/call" + "server/common" + "server/modules/backend/app" + "server/modules/backend/values" + "server/natsClient" + "server/pb" + "sort" + "strings" + + "github.com/gin-gonic/gin" +) + +// ConfigControlCommon 所有调控配置通用逻辑 +func ConfigControlCommon(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + path := c.Request.URL.Path + path = strings.ReplaceAll(path, "/gm/control/", "") + all := strings.Split(path, "/") + if len(all) > 2 { + a.Code = values.CodeRetry + return + } + t := all[0] + opt := all[1] + reloadType := values.GetControlType(t) + element, list := common.GetConfigStructByType(reloadType) + var resp interface{} + if opt == "list" { + if !a.MGetAll(element, list) { + return + } + resp = CheckSpecial(reloadType, list) + if resp == nil { + resp = values.GMConfigCommonListResp{Config: list} + } + } else if opt == "edit" { + req := new(values.GMConfigCommonEditReq) + if !a.S(req) { + return + } + if !a.MUpdateAll(req.Config, element) { + return + } + call.Publish(natsClient.TopicReloadConfig, &pb.ReloadGameConfig{Type: int32(reloadType)}) + } else if opt == "del" { + req := new(values.GMConfigCommonDelReq) + if !a.S(req) { + return + } + if !a.MDel(req.ID, element) { + return + } + call.Publish(natsClient.TopicReloadConfig, &pb.ReloadGameConfig{Type: int32(reloadType)}) + } + a.Data = resp +} + +func CheckSpecial(t int, list interface{}) interface{} { + switch t { + } + return nil +} + +// ConfigPayWeight 支付渠道权重配置 +func ConfigPayWeight(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + path := c.Request.URL.Path + index := strings.LastIndex(path, "/") + if index < 0 { + a.Code = values.CodeParam + return + } + opt := path[index+1:] + reloadType := common.ReloadConfigPayWeight + element, list := common.GetConfigStructByType(reloadType) + resp := values.GMConfigPayWeightResp{} + if opt == "list" { + if !a.MGetAll(element, list) { + return + } + list = reflect.ValueOf(list).Elem().Interface() + retList := []common.ConfigPayChannels{} + for _, v := range list.([]common.ConfigPayChannels) { + if v.CurrencyType == common.CurrencyBrazil { + retList = append(retList, v) + } + } + sort.Slice(retList, func(i, j int) bool { + return retList[i].PayPer > retList[j].PayPer + }) + resp.Config = retList + // resp.Status, _ = db.Redis().GetInt(common.RedisKeyPayStatus) + } else if opt == "edit" { + req := new(values.GMConfigCommonEditReq) + if !a.S(req) { + return + } + if !a.MUpdateAll(req.Config, element) { + return + } + call.Publish(natsClient.TopicReloadConfig, &pb.ReloadGameConfig{Type: int32(reloadType)}) + } else if opt == "del" { + req := new(values.GMConfigCommonDelReq) + if !a.S(req) { + return + } + if !a.MDel(req.ID, element) { + return + } + call.Publish(natsClient.TopicReloadConfig, &pb.ReloadGameConfig{Type: int32(reloadType)}) + } + a.Data = resp +} + +// ConfigWithdrawWeight 退出渠道配置 +func ConfigWithdrawWeight(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + path := c.Request.URL.Path + index := strings.LastIndex(path, "/") + if index < 0 { + a.Code = values.CodeParam + return + } + opt := path[index+1:] + reloadType := common.ReloadConfigWithdrawWeight + element, list := common.GetConfigStructByType(reloadType) + var resp interface{} + if opt == "list" { + if !a.MGetAll(element, list) { + return + } + list = reflect.ValueOf(list).Elem().Interface() + retList := []common.ConfigWithdrawChannels{} + for _, v := range list.([]common.ConfigWithdrawChannels) { + if v.CurrencyType == common.CurrencyBrazil { + retList = append(retList, v) + } + } + sort.Slice(retList, func(i, j int) bool { + return retList[i].WithdrawPer > retList[j].WithdrawPer + }) + resp = values.GMConfigCommonListResp{Config: retList} + } else if opt == "edit" { + req := new(values.GMConfigCommonEditReq) + if !a.S(req) { + return + } + if !a.MUpdateAll(req.Config, element) { + return + } + call.Publish(natsClient.TopicReloadConfig, &pb.ReloadGameConfig{Type: int32(reloadType)}) + } else if opt == "del" { + req := new(values.GMConfigCommonDelReq) + if !a.S(req) { + return + } + if !a.MDel(req.ID, element) { + return + } + call.Publish(natsClient.TopicReloadConfig, &pb.ReloadGameConfig{Type: int32(reloadType)}) + } + a.Data = resp +} diff --git a/modules/backend/handler/gm/gm.go b/modules/backend/handler/gm/gm.go new file mode 100644 index 0000000..141ae58 --- /dev/null +++ b/modules/backend/handler/gm/gm.go @@ -0,0 +1,268 @@ +package handler + +import ( + "fmt" + "server/call" + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/values" + "server/natsClient" + "server/pb" + "server/util" + "time" + + "github.com/gin-gonic/gin" + "github.com/go-redis/redis/v8" + "github.com/liangdas/mqant/log" +) + +func GMAddCoin(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.GMAddCoinReq) + if !a.S(req) { + return + } + log.Debug("gm add coin req:%+v", req) + uid := req.UID + err := call.MineCurrencyProReal(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: uid, + Type: req.CurrencyType, + Value: req.Amount, + Event: common.CurrencyEventGM, + Exs1: "GM", + }}).Err + if err != nil { + log.Error("GMAddCoins errs:%v", err) + a.Code = values.CodeRetry + a.Msg = "添加金币失败!" + return + } + a.RecordEdit(values.PowerGM, fmt.Sprintf("给玩家%v增加金币:%v", req.UID, req.Amount)) +} + +func GMRecharge(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.GMRechargeReq) + if !a.S(req) { + return + } + log.Debug("GMRecharge:%+v", req) + if req.CurrencyType.IsValid() && req.ProductID == 0 { + a.Code = values.CodeParam + a.Msg = "请求错误" + return + } + order := new(common.RechargeOrder) + id := util.NewOrderID(req.UID) + order.OrderID = id + order.APIPayID = id + order.UID = req.UID + order.Status = common.StatusROrderCreate + order.Event = common.CurrencyEventGMRecharge + order.CreateTime = time.Now().Unix() + if req.ProductID > 0 { + product := call.GetConfigPayProductByID(req.ProductID) + if product == nil { + a.Code = values.CodeParam + a.Msg = "请求错误" + return + } + order.ProductID = req.ProductID + order.Amount = product.Amount + order.CurrencyType = common.CurrencyBrazil + } else { + if !req.CurrencyType.IsValid() { + log.Error("unknow CurrencyType:%v", req.CurrencyType) + a.Code = values.CodeParam + a.Msg = "货币类型不存在" + return + } + order.Amount = req.Amount + order.CurrencyType = req.CurrencyType + } + if err := db.Mysql().C().Model(order).Create(order).Error; err != nil { + log.Error("create order err:%v", err) + a.Code = values.CodeRetry + return + } + if err := call.RechargeCallback(order, true, "", ""); err != nil { + log.Error(err.Error()) + a.Code = values.CodeRetry + return + } + a.RecordEdit(values.PowerGM, fmt.Sprintf("给玩家%v模拟充值货币:%v,数额:%v", req.UID, req.CurrencyType, req.Amount)) +} + +func GMBindPhone(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.GMBindPhoneReq) + if !a.S(req) { + return + } + uid := req.UID + + one := new(common.PlayerDBInfo) + one.Id = uid + err := db.Mysql().Get(one) + if err != nil { + log.Error(err.Error()) + } + if one.Id == 0 { + a.Code = values.CodeParam + a.Msg = "用户不存在" + return + } + if len(one.Mobile) > 0 { + a.Code = values.CodeParam + a.Msg = fmt.Sprintf("该用户已绑定手机:%v", one.Mobile) + return + } + if err := db.Mysql().Update(one, map[string]interface{}{"Mobile": req.Phone}); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeParam + a.Msg = "绑定失败" + return + } + a.RecordEdit(values.PowerGM, fmt.Sprintf("给玩家%v绑定手机:%v", req.UID, req.Phone)) +} + +func GMUnBindPhone(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.GMUnBindPhoneReq) + if !a.S(req) { + return + } + uid := req.UID + + one := new(common.PlayerDBInfo) + one.Id = uid + err := db.Mysql().Get(one) + if err != nil { + log.Error(err.Error()) + } + if one.Id == 0 { + a.Code = values.CodeParam + a.Msg = "用户不存在" + return + } + if len(one.Mobile) == 0 { + a.Code = values.CodeParam + a.Msg = "该用户未绑定手机" + return + } + if err := db.Mysql().Update(one, map[string]interface{}{"Mobile": nil}); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeParam + a.Msg = "解绑失败" + return + } + a.RecordEdit(values.PowerGM, fmt.Sprintf("给玩家%v解绑手机", req.UID)) +} + +func GMGetPhoneCode(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.GMGetPhoneCodeReq) + if !a.S(req) { + return + } + code, err := db.Redis().GetString(common.GetRedisKeyCode(req.Phone)) + if err != nil && err != redis.Nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + if code == "" { + a.Code = values.CodeParam + a.Msg = "该手机未发送验证码" + return + } + a.Data = values.GMGetPhoneCodeResp{Code: code} +} + +func ConfigPlatformList(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + resp := values.GMConfigPlatformListResp{Config: new(common.ConfigPlatform)} + err := db.Mysql().Get(resp.Config) + if err != nil { + log.Error(err.Error()) + } + a.Data = resp +} + +func ConfigPlatformEdit(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := &values.GMConfigPlatformEditReq{Config: new(common.ConfigPlatform)} + if !a.S(req.Config) { + return + } + log.Debug("req:%+v", *req.Config) + update := util.StructToMap(req.Config, "web") + if err := db.Mysql().Update(&common.ConfigPlatform{ID: 1}, update); err != nil { + a.Code = values.CodeRetry + return + } + err := call.Publish(natsClient.TopicReloadConfig, &pb.ReloadGameConfig{Type: common.ReloadPlatform}) + if err != nil { + log.Error(err.Error()) + } +} + +func ReloadServerVersion(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.GMConfigReloadServerVersionReq) + if !a.S(req) { + return + } + u := &common.ServerVersion{ServerID: req.ServerID, Version: req.Version} + if _, err := db.Mysql().Upsert(fmt.Sprintf("server_id = %v", req.ServerID), u); err != nil { + log.Error("err:%v", err) + return + } + call.Publish(natsClient.TopicReloadConfig, &pb.ReloadGameConfig{Type: common.ReloadConfigServerVersion}) +} + +// 后台控制玩家掉线 +func OptPlayerDisconnect(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := &values.OptPlayerDisconnectReq{} + if !a.S(req) { + return + } + + for i := 0; i < len(req.UserId); i++ { + err := call.Publish(natsClient.TopicInnerOptPlayer, &pb.InnerOptPlayer{UID: uint32(req.UserId[i]), Opt: common.OptPlayerTypeDisconnect}) + if err != nil { + log.Error(err.Error()) + } + } + +} diff --git a/modules/backend/handler/guser/activeUserList.go b/modules/backend/handler/guser/activeUserList.go new file mode 100644 index 0000000..f4ce553 --- /dev/null +++ b/modules/backend/handler/guser/activeUserList.go @@ -0,0 +1,122 @@ +package guser + +import ( + "fmt" + "server/common" + "server/db" + "server/modules/backend/app" + utils "server/modules/backend/util" + "server/modules/backend/values" + "server/util" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" + "github.com/olivere/elastic/v7" +) + +func ActiveUserList(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.ActiveUserListReq) + if !a.S(req) { + return + } + var resp values.ActiveUserListResp + + su, eu := utils.GetQueryUnix(req.Start, req.End) + + queryCount := " SELECT COUNT(*) FROM login_record WHERE " + + str := fmt.Sprintf(" time >= %d AND time < %d ", su, eu) + + if req.Channel != nil { + str += fmt.Sprintf(" AND channel_id = %d ", *req.Channel) + } + + if req.Status != nil { + str += fmt.Sprintf(" AND status = %v ", req.Status) + } + + var count int64 + err := db.Mysql().QueryBySql(queryCount+str, &count) + if err != nil { + log.Error(err.Error()) + } + + sql := fmt.Sprintf( + `SELECT d.id,d.nick,d.status,d.birth,d.cash,d.bind_cash,d.online,d.time,IFNULL(c.total_recharge,0) as total_recharge,IFNULL(c.total_withdraw,0) as total_withdraw from + (SELECT id,nick,status,birth,cash,bind_cash,online,b.time from users as a + INNER JOIN + (SELECT uid,max(time) as time from login_record WHERE time >= %d and time < %d GROUP BY uid) as b + on a.id = b.uid + LIMIT %d,%d + ) as d + LEFT JOIN + (SELECT uid,total_recharge,total_withdraw from recharge_info) as c + on d.id = c.uid`, su, eu, (req.Page-1)*req.Num, req.Num) + + list := []values.ActiveUserOne{} + + if err := db.Mysql().C().Raw(sql).Scan(&list).Error; err != nil { + log.Error("err:%v", err) + } + + uids := []interface{}{} + for _, v := range list { + uids = append(uids, v.ID) + resp.List = append(resp.List, values.ActiveUserInfo{ + UID: int64(v.ID), + Nick: v.Nick, + Status: v.Status, + Birth: v.Birth, + LastLogin: v.Time, + Recharge: v.TotalCharge, + Withdraw: v.TotalWithdraw, + Cash: v.Cash, + TotalCash: v.Cash + v.BindCash, + Online: v.Online, + }) + } + + q := elastic.NewBoolQuery() + q.Filter(elastic.NewRangeQuery("time").Gte(list[0].Birth)) + q.Filter(elastic.NewRangeQuery("event").Gte(common.CurrencyEventGameSettle)) + q.Filter(elastic.NewRangeQuery("event").Lt(common.CurrencyEventReCharge)) + q.Must(elastic.NewTermsQuery("uid", uids...)) + totalGameCount, _ := db.ES().GroupBy(common.ESIndexBalance, "uid", q, len(list)) + q.Filter(elastic.NewRangeQuery("value").Gt(0)) + winGameCount, _ := db.ES().GroupBy(common.ESIndexBalance, "uid", q, len(list)) + q = elastic.NewBoolQuery() + q.Filter(elastic.NewRangeQuery("time").Gte(su)) + q.Filter(elastic.NewRangeQuery("time").Lt(eu)) + q.Filter(elastic.NewRangeQuery("event").Gte(common.CurrencyEventGameSettle)) + q.Filter(elastic.NewRangeQuery("event").Lt(common.CurrencyEventReCharge)) + q.Must(elastic.NewTermsQuery("uid", uids...)) + todayGameCount, _ := db.ES().GroupBy(common.ESIndexBalance, "uid", q, len(list)) + for i, v := range resp.List { + for _, u := range totalGameCount.Buckets { + if util.GetInt64(u.Key) == v.UID { + resp.List[i].GameCount = int64(u.Doc_count) + break + } + } + var winCount int64 + for _, u := range winGameCount.Buckets { + if util.GetInt64(u.Key) == v.UID { + winCount = int64(u.Doc_count) + break + } + } + resp.List[i].WinPer = utils.GetPer(winCount, resp.List[i].GameCount) + for _, u := range todayGameCount.Buckets { + if util.GetInt64(u.Key) == v.UID { + resp.List[i].TodayCount = int64(u.Doc_count) + break + } + } + } + resp.Count = count + a.Data = resp +} diff --git a/modules/backend/handler/guser/addUserBlackList.go b/modules/backend/handler/guser/addUserBlackList.go new file mode 100644 index 0000000..7e6e7b5 --- /dev/null +++ b/modules/backend/handler/guser/addUserBlackList.go @@ -0,0 +1,87 @@ +package guser + +import ( + "encoding/json" + "fmt" + "server/call" + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/values" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +// 将用户相关信息拉入黑名单 +func AddUserBlackList(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.AddUserBlackListReq) + if !a.S(req) { + return + } + user, err := call.GetUserInfo(req.UID) + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + list := []common.RechargeOrder{} + db.Mysql().QueryList(0, 20, fmt.Sprintf("uid = %v and event = %v", user.Id, common.CurrencyEventWithDraw), "", common.RechargeOrder{}, &list) + banAccounts := map[string]struct{}{} + for _, v := range list { + info := common.WithdrawCommon{} + json.Unmarshal([]byte(v.PayAccount), &info) + acc := info.Number + // if info.DrawType == common.WithdrawTypeBank { + // if info.BankCardNo != "" { + // acc = info.BankCardNo + // } + // } else if info.DrawType == common.WithdrawTypeUPI { + // if info.BankCode != "" { + // acc = info.BankCode + // } + // } + if acc == "" { + continue + } + if _, ok := banAccounts[acc]; !ok { + banAccounts[acc] = struct{}{} + } + } + if len(banAccounts) == 0 { + black := &common.BlackList{ + Phone: user.Mobile, + IP: user.IP, + } + if user.DeviceId != "00000000-0000-0000-0000-000000000000" { + black.DeviceID = user.DeviceId + } + err := db.Mysql().Create(black) + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeParam + a.Msg = "该用户信息已在黑名单里" + } + } else { + for k := range banAccounts { + black := &common.BlackList{ + Phone: user.Mobile, + IP: user.IP, + PayAccount: k, + } + if user.DeviceId != "00000000-0000-0000-0000-000000000000" { + black.DeviceID = user.DeviceId + } + err := db.Mysql().Create(black) + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeParam + a.Msg = "该用户信息已在黑名单里" + } + } + } +} diff --git a/modules/backend/handler/guser/addUserTag.go b/modules/backend/handler/guser/addUserTag.go new file mode 100644 index 0000000..56fe4cf --- /dev/null +++ b/modules/backend/handler/guser/addUserTag.go @@ -0,0 +1,31 @@ +package guser + +import ( + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/values" +) + +func AddUserTag(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.UserTagReq) + if !a.S(req) { + return + } + + err := db.Mysql().C().Model(&common.PlayerDBInfo{}).Where("id = ?", req.Uid).Updates(map[string]interface{}{ + "tag": req.Tag, + }).Error + if err != nil { + log.Error(err.Error()) + a.Code = values.CodeRetry + return + } + +} diff --git a/modules/backend/handler/guser/banUserList.go b/modules/backend/handler/guser/banUserList.go new file mode 100644 index 0000000..a977b7d --- /dev/null +++ b/modules/backend/handler/guser/banUserList.go @@ -0,0 +1,57 @@ +package guser + +import ( + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" + "github.com/olivere/elastic/v7" + "server/call" + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/models" + utils "server/modules/backend/util" + "server/modules/backend/values" +) + +func BanUserList(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.BanUserListReq) + if !a.S(req) { + return + } + var resp values.BanUserListResp + su, eu := utils.GetQueryUnix(req.Start, req.End) + + q := elastic.NewBoolQuery() + q.Must(elastic.NewRangeQuery("Time").Gt(su)) + q.Must(elastic.NewRangeQuery("Time").Lt(eu)) + + var list []*common.ESBlackList + + count, err := db.ES().QueryList(common.ESIndexBackBlackList, req.Page-1, req.Num, q, &list, "Time", false) + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + + for i := 0; i < len(list); i++ { + user, err := call.GetUserInfo(list[i].UID) + if err != nil { + log.Error(err.Error()) + continue + } + resp.List = append(resp.List, &values.BanUserInfo{ + ESBlackList: *list[i], + Birth: user.Birth, + Recharge: models.GetRechargeTotalByUid(&user.Id), + Withdraw: models.GetWithdrawTotalByUid(&user.Id), + Channel: user.ChannelID, + }) + } + resp.Count = count + a.Data = resp +} diff --git a/modules/backend/handler/guser/bigRUserData.go b/modules/backend/handler/guser/bigRUserData.go new file mode 100644 index 0000000..9526e03 --- /dev/null +++ b/modules/backend/handler/guser/bigRUserData.go @@ -0,0 +1,106 @@ +package guser + +import ( + "fmt" + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/models" + utils "server/modules/backend/util" + "server/modules/backend/values" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +func BigRUserData(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.BigRUserDataReq) + if !a.S(req) { + return + } + + resp := values.BigRUserDataResp{} + su, eu := utils.GetQueryUnix(req.Start, req.End) + + totalRecharge := req.TotalRecharge + + queryUser := " SELECT DISTINCT(a.id), a.nick, a.birth, a.bind_cash, a.cash, a.mobile FROM users AS a " + + " LEFT JOIN (SELECT uid, total_recharge FROM recharge_info WHERE total_recharge >= %d) AS b ON a.id = b.uid " + + " WHERE a.id = b.uid AND " + queryCount := " SELECT COUNT(DISTINCT(a.id)) FROM users AS a " + + " LEFT JOIN (SELECT uid, total_recharge FROM recharge_info WHERE total_recharge >= %d) AS b ON a.id = b.uid " + + " WHERE a.id = b.uid AND " + + str := fmt.Sprintf(" a.birth >= %d AND a.birth < %d ", su, eu) + + if req.Channel != nil { + str += fmt.Sprintf(" AND a.channel_id = %d ", *req.Channel) + } + + var count int64 + err := db.Mysql().QueryBySql(fmt.Sprintf(queryCount+str, totalRecharge), &count) + if err != nil { + log.Error(err.Error()) + } + + switch req.Sort { + case 1: + str += " ORDER BY a.birth DESC " + case 2: + str += " ORDER BY b.total_recharge DESC " + case 3: + str += " ORDER BY c.time DESC " + default: + str += " ORDER BY a.birth DESC " + } + + str += fmt.Sprintf(" LIMIT %d, %d ", (req.Page-1)*req.Num, req.Num) + + var users []common.PlayerDBInfo + err = db.Mysql().QueryBySql(fmt.Sprintf(queryUser+str, totalRecharge), &users) + if err != nil { + log.Error(err.Error()) + } + + for i := 0; i < len(users); i++ { + var bigRUserData values.BigRUserData + bigRUserData.Nick = users[i].Nick + bigRUserData.Uid = users[i].Id + bigRUserData.Birth = users[i].Birth + + var record common.LoginRecord + err = db.Mysql().C().Model(&common.LoginRecord{}).Where(" uid = ?", users[i].Id).Last(&record).Error + if err != nil { + log.Error(err.Error()) + } + bigRUserData.LastLogin = record.Time + + // 玩家充值金额 + bigRUserData.RechargeAmount = models.GetRechargeTotalByUid(&users[i].Id) + + // 充值成功率 + bigRUserData.RechargePer = utils.GetPer(models.GetOneRechargeSuccessCountBySql(users[i].Id), models.GetOneRechargeCountBySql(users[i].Id)) + + // 玩家退出金额 + bigRUserData.WithDrawAmount = models.GetWithdrawTotalByUid(&users[i].Id) + + // 玩家游戏局数 + gameCount := make(map[string]int64) + bigRUserData.GameCount = gameCount + + // 充提比 + bigRUserData.RechargeAndWithDrawRate = utils.GetPer(bigRUserData.WithDrawAmount, bigRUserData.RechargeAmount) + + // 手机号 + bigRUserData.Phone = users[i].Mobile + + resp.List = append(resp.List, bigRUserData) + } + + resp.Count = count + a.Data = resp +} diff --git a/modules/backend/handler/guser/editGameUserGold.go b/modules/backend/handler/guser/editGameUserGold.go new file mode 100644 index 0000000..d86d7b9 --- /dev/null +++ b/modules/backend/handler/guser/editGameUserGold.go @@ -0,0 +1,42 @@ +package guser + +import ( + "fmt" + "server/call" + "server/common" + "server/modules/backend/app" + "server/modules/backend/values" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +// 修改玩家金币 +func EditGameUserGold(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.EditGameUserGoldReq) + if !a.S(req) { + return + } + user, _ := call.GetUserXInfo(req.UID, "channel_id") + update := &common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: req.UID, + Type: req.CurrencyType, + Value: req.Amount, + Event: common.CurrencyEventGM, + ChannelID: user.ChannelID, + Exs1: "后台操作修改玩家金币", + }, + } + err := call.MineCurrencyProReal(update).Err + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + a.RecordEdit(values.PowerGUser, fmt.Sprintf("修改玩家%v金币:%v", req.UID, req.Amount)) +} diff --git a/modules/backend/handler/guser/editGameUserStatus.go b/modules/backend/handler/guser/editGameUserStatus.go new file mode 100644 index 0000000..12414c8 --- /dev/null +++ b/modules/backend/handler/guser/editGameUserStatus.go @@ -0,0 +1,56 @@ +package guser + +import ( + "fmt" + "server/call" + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/values" + "server/natsClient" + "server/pb" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +func EditGameUserStatus(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.EditGameUserStatusReq) + if !a.S(req) { + return + } + if req.Status <= common.AccountStatus || req.Status >= common.AccountStatusAll { + a.Code = values.CodeParam + a.Msg = "请求状态不合法" + return + } + user, err := call.GetUserInfo(req.UID) + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + if user.Status == req.Status { + a.Code = values.CodeParam + a.Msg = "无内容修改" + return + } + if err = call.UpdateUserXInfo(&common.PlayerDBInfo{Id: req.UID}, map[string]interface{}{"status": req.Status}); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + if req.Status == common.AccountStatusLimit { // 封禁账号要通知全服,踢出玩家 + db.Redis().Delkey(common.GetRedisKeyToken(db.Redis().GetUserToken(req.UID))) + db.Redis().Delkey(common.GetRedisKeyUser(req.UID)) + err = call.Publish(natsClient.TopicInnerOptPlayer, &pb.InnerOptPlayer{UID: uint32(req.UID), Opt: common.OptPlayerTypeKick}) + if err != nil { + log.Error(err.Error()) + } + } + a.RecordEdit(values.PowerGUser, fmt.Sprintf("修改玩家%v状态:%v", req.UID, req.Status)) +} diff --git a/modules/backend/handler/guser/getGameUserAllBalance.go b/modules/backend/handler/guser/getGameUserAllBalance.go new file mode 100644 index 0000000..08326da --- /dev/null +++ b/modules/backend/handler/guser/getGameUserAllBalance.go @@ -0,0 +1,34 @@ +package guser + +import ( + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/values" +) + +func GetGameUserAllBalance(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.GetGameUserAllBalanceReq) + if !a.S(req) { + return + } + resp := values.GetGameUserAllBalanceResp{ + List: []common.CurrencyBalance{}, + } + var count int64 + var err error + count, err = db.ES().QueryPlayerAllBalance(&req.UID, req.Page-1, req.Num, req.Start, req.End, &resp.List) + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + resp.Count = count + a.Data = resp +} diff --git a/modules/backend/handler/guser/getGameUserControlBalance.go b/modules/backend/handler/guser/getGameUserControlBalance.go new file mode 100644 index 0000000..14ab8ad --- /dev/null +++ b/modules/backend/handler/guser/getGameUserControlBalance.go @@ -0,0 +1,47 @@ +package guser + +import ( + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/models" + utils "server/modules/backend/util" + "server/modules/backend/values" +) + +func GetGameUserControlBalance(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.GetGameUserControlBalanceReq) + if !a.S(req) { + return + } + resp := values.GetGameUserControlBalanceResp{ + List: []common.CurrencyBalance{}, + } + s, e := utils.GetQueryUnix(req.Start, req.End) + count, err := db.ES().QueryPlayerControlBalance(req.UID, req.Page-1, req.Num, &s, &e, &resp.List, req.ControlType) + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + resp.Count = count + + event := int(common.CurrencyEventGameSettle) + // 查询控杀次数 + resp.ControlCount = models.GetControlCount(&s, &e, &req.UID, &event, false) + loseCount := models.GetControlCount(&s, &e, &req.UID, &event, true) + resp.ControlPer = utils.GetPer(loseCount, resp.ControlCount) + + // 幸运次数 + resp.LuckCount = models.GetLuckCount(&s, &e, &req.UID, nil, &event, false, false) + winCount := models.GetLuckCount(&s, &e, &req.UID, nil, &event, true, false) + resp.LuckPer = utils.GetPer(winCount, resp.LuckCount) + + a.Data = resp +} diff --git a/modules/backend/handler/guser/getGameUserInfo.go b/modules/backend/handler/guser/getGameUserInfo.go new file mode 100644 index 0000000..dfdba9c --- /dev/null +++ b/modules/backend/handler/guser/getGameUserInfo.go @@ -0,0 +1,181 @@ +package guser + +import ( + "fmt" + "server/call" + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/models" + "server/modules/backend/values" + "strconv" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" + "github.com/olivere/elastic/v7" + + uutil "server/util" +) + +func GetGameUserInfo(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.GetGameUserInfoReq) + if !a.S(req) { + return + } + user := &common.PlayerDBInfo{} + switch req.Data.(type) { + case int, int64, float32, float64: + id := uutil.GetInt(req.Data) + if id <= 999999999 { + user.Id = id + } else { + user.Mobile = strconv.Itoa(id) + } + case string: + str := req.Data.(string) + if len(str) == 10 { + user.Mobile = str + } else { + id, err := strconv.Atoi(str) + if err != nil { + a.Code = values.CodeParam + a.Msg = "玩家不存在" + return + } + user.Id = id + } + } + err := db.Mysql().Get(user) + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeParam + a.Msg = "玩家不存在" + return + } + uid := user.Id + ur, _ := call.GetUserXInfo(uid, "online") + info := &common.RechargeInfo{UID: uid} + db.Mysql().Get(info) + lg := &common.LoginRecord{UID: uid} + db.Mysql().GetLast(lg) + resp := values.GetGameUserInfoResp{ + UID: user.Id, + Nick: user.Nick, + Channel: user.ChannelID, + Phone: user.Mobile, + OpenID: *user.Openid, + Online: ur.Online == common.PlayerOnline, + Status: user.Status, + Birth: user.Birth, + // PlayCount: util.GetGUserPlayCount(uid), + IP: user.IP, + LastLogin: lg.Time, + Tag: user.Tag, + UserGameData: GetUserGameInfo(user.Id), + OutputData: models.GetOutputData(0, 0, 0, uid), + Gpsadid: user.DeviceId, + } + + currency := &common.PlayerCurrency{UID: uid} + db.Mysql().Get(currency) + // currencyRecharge := &common.PlayerCurrency{UID: uid} + // db.Mysql().C().Table(common.PlayerRechargeTableName).Where("uid = ?", uid).Scan(currencyRecharge) + // resp.CashBrl = currency.BRL + currencyRecharge.BRL + resp.CashBrl = currency.BRL + resp.WithdrawalBrl = currency.BRL + // resp.CashUsdt = currency.USDT + currencyRecharge.USDT + // resp.WithdrawalUsdt = currency.USDT + brlInfo := call.GetRechargeInfo(uid) + // usdtInfo := call.GetPlayerRechargeInfoByCurrency(uid, common.CurrencyUSDT) + resp.RechargeBrl = brlInfo.TotalRecharge + resp.WithdrawBrl = brlInfo.TotalWithdraw + // resp.RechargeUsdt = usdtInfo.TotalRecharge + // resp.WithdrawUsdt = usdtInfo.TotoalWithdraw + + db.Mysql().C().Table("users").Select("id").Where("deviceid = ?", user.DeviceId).Scan(&resp.SubAccount) + // db.Mysql().QueryAll(fmt.Sprintf("deviceid = '%s'", user.DeviceId), "", &common.PlayerDBInfo{}, &resp.SubAccount) + a.Data = resp +} + +// 获取用户游戏信息 +func GetUserGameInfo(uid int) map[string][]values.UserGameInfo { + q := elastic.NewBoolQuery() + q.Filter(elastic.NewTermQuery("uid", uid)) + q.Filter(elastic.NewTermsQuery("event", common.GetGameEvents()...)) + buk, err := db.ES().Group2SumBy(common.ESIndexBalance, "exi1", "exi2", "value", q, "", false, 0) + if err != nil { + log.Error("err:%v", err) + } + ret := map[string][]values.UserGameInfo{} + var totalCount, totalProfit int64 + for _, v := range buk.Buckets { + provider := call.GetConfigGameProvider(uutil.GetInt(v.Key)) + if provider == nil { + continue + } + games := []values.UserGameInfo{} + for _, k := range v.Sub1.Buckets { + thisGame := call.GetConfigGameListByID(provider.ProviderID, uutil.GetInt(k.Key)) + if thisGame == nil { + continue + } + count := uutil.GetInt64(k.Doc_count) + profit := uutil.GetInt64(k.Sub2.Value) + totalCount += count + totalProfit += profit + games = append(games, values.UserGameInfo{ + GameName: thisGame.Name + fmt.Sprintf("(%d)", thisGame.GameID), + GameCount: count, + Profit: profit, + }) + } + ret[provider.ProviderName] = games + } + ret["总计"] = []values.UserGameInfo{{GameCount: totalCount, Profit: totalProfit}} + return ret +} + +// func getUserGameData(uid int, gameId *int, win, lost *bool) values.UserGameInfo { +// var userGameInfo values.UserGameInfo +// if gameId == nil { +// userGameInfo.GameId = 0 +// } else { +// userGameInfo.GameId = *gameId +// } +// userGameInfo.GameCount = models.GetWinGameCountByBalance(nil, nil, &uid, nil, gameId, nil, nil) +// userGameInfo.WinCount = models.GetWinGameCountByBalance(nil, nil, &uid, nil, gameId, nil, win) +// userGameInfo.LoseCount = models.GetWinGameCountByBalance(nil, nil, &uid, nil, gameId, nil, lost) + +// userGameInfo.Profit = models.GetGameProfitByUid(nil, nil, &uid, nil, gameId, nil, nil) +// userGameInfo.WinProfit = models.GetGameProfitByUid(nil, nil, &uid, nil, gameId, nil, win) +// userGameInfo.LoseProfit = models.GetGameProfitByUid(nil, nil, &uid, nil, gameId, nil, lost) +// userGameInfo.WinPer = util.GetPer(userGameInfo.WinCount, userGameInfo.GameCount) + +// return userGameInfo +// } + +func ShareData(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.ShareDataReq) + if !a.S(req) { + return + } + resp := &values.ShareDataResp{} + a.Data = resp + for i := 1; i <= 3; i++ { + resp.List = append(resp.List, values.OneShareData{ + Level: i, + ShareCount: call.GetUserShares(req.UID, i), + RechargeCount: call.GetUserShareRecharges(req.UID, i), + ValidRechargeCount: call.GetUserShareValidRecharges(req.UID, i), + TotalRecharge: call.GetUserShareRechargeAmount(req.UID, i), + }) + } +} diff --git a/modules/backend/handler/guser/getGameUserList.go b/modules/backend/handler/guser/getGameUserList.go new file mode 100644 index 0000000..457cec5 --- /dev/null +++ b/modules/backend/handler/guser/getGameUserList.go @@ -0,0 +1,217 @@ +package guser + +import ( + "fmt" + "server/db" + "server/modules/backend/app" + "server/modules/backend/models" + utils "server/modules/backend/util" + "server/modules/backend/values" + "server/util" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +func GetGameUserList(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.GetGameUserListReq) + if !a.S(req) { + return + } + su, eu := utils.GetQueryUnix(req.Start, req.End) + var userSql, shareInfoSql, vipSql, rechargeInfoSql, cashSql []string + userSql = append(userSql, fmt.Sprintf("birth >= %d", su), fmt.Sprintf("birth < %d", eu)) + if req.UID > 0 { + userSql = append(userSql, fmt.Sprintf("id = %d", req.UID)) + } + if req.Channel > 0 { + userSql = append(userSql, fmt.Sprintf("channel_id = %d", req.Channel)) + } + if req.Country != "" { + userSql = append(userSql, fmt.Sprintf("country = %s", req.Country)) + } + if req.Phone != "" { + userSql = append(userSql, fmt.Sprintf("mobile = %s", req.Phone)) + } + if req.Status > 0 { + userSql = append(userSql, fmt.Sprintf("status = %d", req.Status)) + } + if req.Platform > 0 { + userSql = append(userSql, fmt.Sprintf("platform = %d", req.Platform)) + } + if req.Online > 0 { + userSql = append(userSql, fmt.Sprintf("online = %d", req.Online)) + } + if len(req.LastLogin) > 0 { + userSql = append(userSql, fmt.Sprintf("last_login >= %d", req.LastLogin[0])) + if len(req.LastLogin) > 1 { + userSql = append(userSql, fmt.Sprintf("last_login < %d", req.LastLogin[1])) + } + } + if req.UP > 0 { + shareInfoSql = append(shareInfoSql, fmt.Sprintf("up1 = %d", req.UP)) + } + if len(req.VIP) > 0 { + vipSql = append(vipSql, fmt.Sprintf("level >= %d", req.VIP[0])) + if len(req.VIP) > 1 { + vipSql = append(vipSql, fmt.Sprintf("level <= %d", req.VIP[1])) + } + } + if req.IsRecharge > 0 { + if req.IsRecharge == 1 { + rechargeInfoSql = append(rechargeInfoSql, "total_recharge > 0") + } else { + rechargeInfoSql = append(rechargeInfoSql, "total_recharge = 0") + } + } + if len(req.Recharge) > 0 { + rechargeInfoSql = append(rechargeInfoSql, fmt.Sprintf("total_recharge >= %d", req.Recharge[0])) + if len(req.Recharge) > 1 { + rechargeInfoSql = append(rechargeInfoSql, fmt.Sprintf("total_recharge <= %d", req.Recharge[1])) + } + } + if len(req.RechargeCount) > 0 { + rechargeInfoSql = append(rechargeInfoSql, fmt.Sprintf("total_recharge_count >= %d", req.RechargeCount[0])) + if len(req.RechargeCount) > 1 { + rechargeInfoSql = append(rechargeInfoSql, fmt.Sprintf("total_recharge_count <= %d", req.RechargeCount[1])) + } + } + if len(req.Withdraw) > 0 { + rechargeInfoSql = append(rechargeInfoSql, fmt.Sprintf("total_withdraw >= %d", req.Withdraw[0])) + if len(req.Withdraw) > 1 { + rechargeInfoSql = append(rechargeInfoSql, fmt.Sprintf("total_withdraw <= %d", req.Withdraw[1])) + } + } + if len(req.Cash) > 0 { + cashSql = append(cashSql, fmt.Sprintf("brl >= %d", req.Cash[0])) + if len(req.Cash) > 1 { + cashSql = append(cashSql, fmt.Sprintf("brl <= %d", req.Cash[1])) + } + } + + linkB := "LEFT JOIN" + if len(shareInfoSql) > 0 { + linkB = "INNER JOIN" + } + linkC := "LEFT JOIN" + if len(vipSql) > 0 { + linkC = "INNER JOIN" + } + linkD := "LEFT JOIN" + if len(rechargeInfoSql) > 0 { + linkD = "INNER JOIN" + } + + baseSql := fmt.Sprintf(` + (SELECT id,channel_id,country,mobile,status,platform,birth,last_login,tag,online from users %s)a + %s + (SELECT uid,up1 from share_info %s)b + on a.id = b.uid + %s + (SELECT uid,level from vip_data %s)c + on a.id = c.uid + %s + (SELECT uid,total_recharge,total_recharge_count,total_withdraw,total_withdraw_count,total_recharge-total_withdraw as diff from recharge_info %s)d + on a.id = d.uid + INNER JOIN + (SELECT uid,brl from player_currency %s)e + on a.id = e.uid + `, models.LinkMysqlCondi(userSql), linkB, models.LinkMysqlCondi(shareInfoSql), linkC, models.LinkMysqlCondi(vipSql), + linkD, models.LinkMysqlCondi(rechargeInfoSql), models.LinkMysqlCondi(cashSql)) + + // 拉列表语句 + sql := `SELECT a.id as UID,a.channel_id as ChannelID,a.country as Country,c.level as Level,a.mobile as Mobile,b.up1 as Up,a.status as Status, + a.platform as Platform,a.online as Online,d.total_recharge as TotalRecharge,d.total_recharge_count as TotalRechargeCount,d.total_withdraw as TotalWithdraw, + d.total_withdraw_count as TotalWithdrawCount,e.brl as Brl,d.diff as Diff,a.last_login as LastLogin,a.birth as Birth,a.tag as Tag From ` + baseSql + + // 求和语句 + sqlCount := `SELECT count(*) From ` + baseSql + + // 排序 + if req.Order != 0 { + orderSql := "order by" + abs := util.Abs(int64(req.Order)) + switch abs { + case 1: + orderSql += " d.total_recharge" + case 2: + orderSql += " d.total_recharge_count" + case 3: + orderSql += " d.total_withdraw" + case 4: + orderSql += " e.brl" + case 5: + orderSql += " d.diff" + case 6: + orderSql += " a.last_login" + case 7: + orderSql += " a.birth" + } + if req.Order < 0 { + orderSql += " desc" + } + sql += orderSql + } else { + sql += " order by a.id desc" + } + + // 分页 + sql += fmt.Sprintf(" limit %d,%d", (req.Page-1)*req.Num, req.Num) + + resp := values.GetGameUserListResp{} + if err := db.Mysql().C().Raw(sql).Scan(&resp.List).Error; err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + + // isWin := true + // uids := []interface{}{} + // for i, v := range resp.List { + // lg := &common.LoginRecord{UID: v.UID} + // db.Mysql().GetLast(lg) + // // if err != nil { + // // log.Error(err.Error()) + // // } + // resp.List[i].LastLogin = lg.Time + + // uids = append(uids, v.UID) + // // winCount := models.GetWinGameCountByBalance(nil, nil, &resp.List[i].UID, req.Channel, nil, nil, &isWin) + // // resp.List[i].GameCount = models.GetGameCountByBalance(nil, nil, &resp.List[i].UID, req.Channel, nil, nil) + // // resp.List[i].WinPer = utils.GetPer(winCount, resp.List[i].GameCount) + + // user, _ := call.GetUserInfo(v.UID) + // resp.List[i].AccountCount = int(db.Mysql().Count(&common.PlayerDBInfo{}, fmt.Sprintf("deviceid = '%v'", user.DeviceId))) + // } + + // total := models.GetGameCountByUIDs(uids, false) + // win := models.GetGameCountByUIDs(uids, true) + // for i, v := range resp.List { + // for _, u := range total.Buckets { + // if util.GetInt(u.Key) == v.UID { + // resp.List[i].GameCount = int64(u.Doc_count) + // break + // } + // } + // var winCount int64 + // for _, u := range win.Buckets { + // if util.GetInt(u.Key) == v.UID { + // winCount = int64(u.Doc_count) + // break + // } + // } + // resp.List[i].WinPer = utils.GetPer(winCount, resp.List[i].GameCount) + // } + + if err := db.Mysql().C().Raw(sqlCount).Scan(&resp.Count).Error; err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + + a.Data = resp +} diff --git a/modules/backend/handler/guser/getGameUserPlayData.go b/modules/backend/handler/guser/getGameUserPlayData.go new file mode 100644 index 0000000..194650c --- /dev/null +++ b/modules/backend/handler/guser/getGameUserPlayData.go @@ -0,0 +1,53 @@ +package guser + +import ( + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/values" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +func GetGameUserPlayData(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.GetGameUserPlayDataReq) + if !a.S(req) { + return + } + var ret []common.CurrencyBalance + + var count int64 + var err error + if req.Games != nil { + count, err = db.ES().QueryPlayerBalance(req.UID, req.Page-1, req.Num, int(common.CurrencyEventGameSettle), req.Start, req.End, &ret, *req.Games) + } else { + count, err = db.ES().QueryPlayerBalance(req.UID, req.Page-1, req.Num, int(common.CurrencyEventGameSettle), req.Start, req.End, &ret) + } + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + resp := values.GetGameUserPlayDataResp{ + List: []values.OneGameUserPlayData{}, + Count: count, + } + for _, v := range ret { + resp.List = append(resp.List, values.OneGameUserPlayData{ + Time: v.Time, + GameID: v.Exi1, + RoomName: v.Exi2, + Value: v.Value, + Balance: v.Balance, + UUID: v.Exs1, + // ControlType: v.Exi3, + // DramaID: v.Exi4, + }) + } + a.Data = resp +} diff --git a/modules/backend/handler/guser/getGameUserRechargeHistory.go b/modules/backend/handler/guser/getGameUserRechargeHistory.go new file mode 100644 index 0000000..c062081 --- /dev/null +++ b/modules/backend/handler/guser/getGameUserRechargeHistory.go @@ -0,0 +1,63 @@ +package guser + +import ( + "fmt" + "server/common" + "server/db" + "server/modules/backend/app" + utils "server/modules/backend/util" + "server/modules/backend/values" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +func GetGameUserRechargeHistory(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.GetGameUserRechargeHistoryReq) + if !a.S(req) { + return + } + resp := values.GetGameUserRechargeHistoryResp{} + var ret []common.RechargeOrder + if req.UID > 0 { + count, err := db.Mysql().QueryPlayerRWHistory(&req.UID, nil, req.Page-1, req.Num, []int{int(common.CurrencyEventReCharge), + common.CurrencyEventGMRecharge}, req.Start, req.End, &common.RechargeOrder{}, &ret, req.Status) + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + resp.Count = count + for _, v := range ret { + resp.List = append(resp.List, values.OneRechargeList{UID: v.UID, Time: v.CreateTime, CallbackTime: v.CallbackTime, CurrencyType: v.CurrencyType, + Amount: v.Amount, Status: int(v.Status), MyOrderID: v.OrderID, OrderID: v.APIPayID, PayChannel: v.PayChannel, PaySource: v.PaySource}) + } + + resp.RechargeCount = db.Mysql().Count(&common.RechargeOrder{}, fmt.Sprintf("uid = %v and (event = %v or event = %v)", req.UID, common.CurrencyEventReCharge, common.CurrencyEventGMRecharge)) + re := &common.RechargeInfo{UID: req.UID} + err = db.Mysql().Get(re) + if err != nil { + log.Error(err.Error()) + } + resp.RechargeTotal = re.TotalRecharge + successCount := db.Mysql().Count(&common.RechargeOrder{}, fmt.Sprintf("uid = %v and (event = %v or event = %v) and status = %v", req.UID, common.CurrencyEventReCharge, common.CurrencyEventGMRecharge, common.StatusROrderPay)) + resp.RechargeSuccessPer = utils.GetPer(successCount, resp.RechargeCount) + } else if req.OrderID != "" { + resp.Count, _ = db.Mysql().QueryList(req.Page-1, req.Num, fmt.Sprintf(`event = %v and (apipayid = "%v" or orderid = "%v")`, common.CurrencyEventReCharge, req.OrderID, req.OrderID), "create_time desc", &common.RechargeOrder{}, &ret) + // log.Debug("ret:%v", ret) + for _, v := range ret { + resp.List = append(resp.List, values.OneRechargeList{UID: v.UID, Time: v.CreateTime, CallbackTime: v.CallbackTime, CurrencyType: v.CurrencyType, + MyOrderID: v.OrderID, OrderID: v.APIPayID, Amount: v.Amount, Status: int(v.Status), PayChannel: v.PayChannel, PaySource: v.PaySource}) + } + } else { + a.Code = values.CodeParam + a.Msg = "查询条件有误" + return + } + + a.Data = resp +} diff --git a/modules/backend/handler/guser/getGameUserWithdrawHistory.go b/modules/backend/handler/guser/getGameUserWithdrawHistory.go new file mode 100644 index 0000000..2abcec9 --- /dev/null +++ b/modules/backend/handler/guser/getGameUserWithdrawHistory.go @@ -0,0 +1,67 @@ +package guser + +import ( + "fmt" + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/models" + utils "server/modules/backend/util" + "server/modules/backend/values" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +func GetGameUserWithdrawHistory(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.GetGameUserWithdrawHistoryReq) + if !a.S(req) { + return + } + resp := values.GetGameUserWithdrawHistoryResp{} + var ret []common.WithdrawOrder + if req.UID > 0 { + count, err := db.Mysql().QueryPlayerRWHistory(&req.UID, nil, req.Page-1, req.Num, []int{int(common.CurrencyEventWithDraw)}, req.Start, req.End, &common.WithdrawOrder{}, &ret) + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + resp.Count = count + for _, v := range ret { + resp.List = append(resp.List, values.OneWithdrawList{UID: v.UID, Time: v.CreateTime, PayAccount: v.PayAccount, + Amount: v.Amount, Status: int(v.Status), OrderID: v.APIPayID, MyOrderID: v.OrderID, FailReason: v.FailReason, OrderCreateAt: v.CreateTime, + AuditTime: v.AuditTime, CallbackTime: v.CallbackTime, Channel: v.PayChannel}) + } + resp.WithdrawCount = db.Mysql().Count(&common.RechargeOrder{}, fmt.Sprintf("uid = %v and event = %v", req.UID, common.CurrencyEventWithDraw)) + re := &common.RechargeInfo{UID: req.UID} + err = db.Mysql().Get(re) + if err != nil { + log.Error(err.Error()) + } + resp.WithdrawTotal = re.TotalWithdraw + successCount := db.Mysql().Count(&common.RechargeOrder{}, fmt.Sprintf("uid = %v and event = %v and status = %v", req.UID, common.CurrencyEventWithDraw, common.StatusROrderFinish)) + resp.WithdrawSuccessPer = utils.GetPer(successCount, resp.WithdrawCount) + resp.WithdrawCash = db.Mysql().Sum(&common.WithdrawOrder{}, fmt.Sprintf("uid = %v and event = %v and status = %v", req.UID, common.CurrencyEventWithDraw, common.StatusROrderFinish), "withdraw_cash") + resp.WithdrawTax = resp.WithdrawCash - resp.WithdrawTotal*100 + resp.RechargeTotal = models.GetRechargeTotalByUid(&req.UID) + resp.WithDrawPer = utils.GetPer(resp.WithdrawTotal, resp.RechargeTotal) + } else if req.Data != "" { + resp.Count, _ = db.Mysql().QueryList(req.Page-1, req.Num, fmt.Sprintf(`event = %v and (apipayid = "%v" or orderid = '%v')`, common.CurrencyEventWithDraw, req.Data, req.Data), "create_time desc", &common.WithdrawOrder{}, &ret) + for _, v := range ret { + resp.List = append(resp.List, values.OneWithdrawList{UID: v.UID, Time: v.CreateTime, PayAccount: v.PayAccount, + Amount: v.Amount, Status: int(v.Status), OrderID: v.APIPayID, MyOrderID: v.OrderID, FailReason: v.FailReason, OrderCreateAt: v.CreateTime, + AuditTime: v.AuditTime, CallbackTime: v.CallbackTime, Channel: v.PayChannel}) + } + } else { + a.Code = values.CodeParam + a.Msg = "查询条件有误" + return + } + + a.Data = resp +} diff --git a/modules/backend/handler/guser/lostPlayerData.go b/modules/backend/handler/guser/lostPlayerData.go new file mode 100644 index 0000000..fa5031e --- /dev/null +++ b/modules/backend/handler/guser/lostPlayerData.go @@ -0,0 +1,100 @@ +package guser + +import ( + "fmt" + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/models" + utils "server/modules/backend/util" + "server/modules/backend/values" + "strconv" + "time" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +func LostPlayerData(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.LostPlayerDataReq) + if !a.S(req) { + return + } + resp := values.LostPlayerDataResp{} + + su, eu := utils.GetQueryUnix(req.Start, req.End) + var oneDay int64 = 24 * 60 * 60 + now := time.Now().Unix() + + queryUser := " SELECT u.id, u.nick, u.bind_cash, u.cash, u.birth FROM users u LEFT JOIN (SELECT a.* FROM login_record a INNER JOIN ( SELECT uid, MAX( created_at ) created_at FROM login_record WHERE UNIX_TIMESTAMP(date) >= %d GROUP BY uid ) b ON a.uid = b.uid AND a.created_at = b.created_at WHERE a.uid = b.uid AND a.created_at = b.created_at AND UNIX_TIMESTAMP(a.date) >= %d) r ON u.id = r.uid WHERE " + queryCount := " SELECT COUNT(DISTINCT(u.id)) FROM users u LEFT JOIN (SELECT a.* FROM login_record a INNER JOIN ( SELECT uid, MAX( created_at ) created_at FROM login_record WHERE UNIX_TIMESTAMP(date) >= %d GROUP BY uid ) b ON a.uid = b.uid AND a.created_at = b.created_at WHERE a.uid = b.uid AND a.created_at = b.created_at AND UNIX_TIMESTAMP(a.date) >= %d) r ON u.id = r.uid WHERE " + + str := fmt.Sprintf(" u.birth >= %d AND u.birth < %d ", su, eu) + + if req.Channel != nil { + str += fmt.Sprintf(" AND u.channel_id = %d AND r.channel_id = %d", *req.Channel, *req.Channel) + } + + // 三天未登录的用户 UNIX_TIMESTAMP('20210816') + str += fmt.Sprintf(" AND (%d - UNIX_TIMESTAMP(r.date) > %d ) ", now, 3*oneDay) + + var count int64 + err := db.Mysql().QueryBySql(fmt.Sprintf(queryCount+str, eu, eu), &count) + if err != nil { + log.Error(err.Error()) + } + + str += " GROUP BY u.id " + + str += fmt.Sprintf(" LIMIT %d, %d ", (req.Page-1)*req.Num, req.Num) + + var users []common.PlayerDBInfo + err = db.Mysql().QueryBySql(fmt.Sprintf(queryUser+str, eu, eu), &users) + if err != nil { + log.Error(err.Error()) + } + + for i := 0; i < len(users); i++ { + var lostPlayerData values.LostPlayerData + lostPlayerData.Date = su + lostPlayerData.Nick = users[i].Nick + lostPlayerData.Uid = users[i].Id + lostPlayerData.Birth = users[i].Birth + + var record common.LoginRecord + err = db.Mysql().C().Model(&common.LoginRecord{}).Where(" uid = ?", users[i].Id).Last(&record).Error + if err != nil { + log.Error(err.Error()) + } + lostPlayerData.LastLogin = record.Time + + // 玩家退出金额 + lostPlayerData.WithDrawAmount = models.GetWithdrawTotalByUid(&users[i].Id) + + // 玩家游戏局数 + gameCount := make(map[string]int64) + lostPlayerData.MostGameCount = gameCount + + // 最后三局游戏记录 + event := int(common.CurrencyEventGameSettle) + var balance []common.CurrencyBalance + uid := strconv.Itoa(users[i].Id) + _, err = models.QueryUserBalance(&uid, 0, 3, nil, nil, nil, nil, nil, req.Channel, &event, &balance) + if err != nil { + log.Error(err.Error()) + return + } + for j := 0; j < len(balance); j++ { + lostPlayerData.GameRecord = append(lostPlayerData.GameRecord, balance[j].Value) + } + + resp.List = append(resp.List, lostPlayerData) + } + + resp.Count = count + a.Data = resp +} diff --git a/modules/backend/handler/guser/lostUserData.go b/modules/backend/handler/guser/lostUserData.go new file mode 100644 index 0000000..7fdf7da --- /dev/null +++ b/modules/backend/handler/guser/lostUserData.go @@ -0,0 +1,239 @@ +package guser + +import ( + "fmt" + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/models" + utils "server/modules/backend/util" + "server/modules/backend/values" + "time" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +// 流失玩家数据 +func LostUserData(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.LostUserDataReq) + if !a.S(req) { + return + } + resp := values.LostUserDataResp{} + + su, eu := utils.GetQueryUnix(req.Start, req.End) + + switch req.Sort { + case 1: + resp.Count, resp.List = getAllLostUser(su, eu, req.Page, req.Num, req.Channel) + case 2: + resp.Count, resp.List = getPayLostUser(su, eu, req.Page, req.Num, req.Channel) + case 3: + resp.Count, resp.List = getActiveLostUser(su, eu, req.Page, req.Num, req.Channel) + case 4: + resp.Count, resp.List = getNewLostUser(su, eu, req.Page, req.Num, req.Channel) + default: + resp.Count, resp.List = getAllLostUser(su, eu, req.Page, req.Num, req.Channel) + } + + a.Data = resp +} + +// 获取所有流失用户 +func getAllLostUser(su, eu int64, page, num int, channel *int) (int64, []values.LostUserData) { + var oneDay = 24 * 60 * 60 + now := time.Now().Unix() + + queryUser := " SELECT u.id, u.nick, u.bind_cash, u.cash, u.birth FROM users u LEFT JOIN (SELECT a.* FROM login_record a INNER JOIN ( SELECT uid, MAX( created_at ) created_at FROM login_record WHERE UNIX_TIMESTAMP(date) >= %d GROUP BY uid ) b ON a.uid = b.uid AND a.created_at = b.created_at WHERE a.uid = b.uid AND a.created_at = b.created_at AND UNIX_TIMESTAMP(a.date) >= %d) r ON u.id = r.uid WHERE " + queryCount := " SELECT COUNT(DISTINCT(u.id)) FROM users u LEFT JOIN (SELECT a.* FROM login_record a INNER JOIN ( SELECT uid, MAX( created_at ) created_at FROM login_record WHERE UNIX_TIMESTAMP(date) >= %d GROUP BY uid ) b ON a.uid = b.uid AND a.created_at = b.created_at WHERE a.uid = b.uid AND a.created_at = b.created_at AND UNIX_TIMESTAMP(a.date) >= %d) r ON u.id = r.uid WHERE " + + str := fmt.Sprintf(" u.birth >= %d AND u.birth < %d ", su, eu) + + if channel != nil { + str += fmt.Sprintf(" AND u.channel_id = %d AND r.channel_id = %d", *channel, *channel) + } + + // 七天未登录的用户 UNIX_TIMESTAMP('20210816') + str += fmt.Sprintf(" AND (%d - UNIX_TIMESTAMP(r.date) > %d ) ", now, 7*oneDay) + + var count int64 + err := db.Mysql().QueryBySql(fmt.Sprintf(queryCount+str, su, su), &count) + if err != nil { + log.Error(err.Error()) + return 0, nil + } + + str += " GROUP BY u.id " + + str += fmt.Sprintf(" LIMIT %d, %d ", (page-1)*num, num) + + var users []common.PlayerDBInfo + err = db.Mysql().QueryBySql(fmt.Sprintf(queryUser+str, su, su), &users) + if err != nil { + log.Error(err.Error()) + return 0, nil + } + + return count, getLostUserInfo(users) +} + +// 获取付费流失用户 +func getPayLostUser(su, eu int64, page, num int, channel *int) (int64, []values.LostUserData) { + var oneDay = 24 * 60 * 60 + now := time.Now().Unix() + + queryUser := " SELECT u.id, u.nick, u.bind_cash, u.cash, u.birth FROM users u LEFT JOIN (SELECT a.* FROM login_record a INNER JOIN ( SELECT uid, MAX( created_at ) created_at FROM login_record WHERE UNIX_TIMESTAMP(date) >= %d GROUP BY uid ) b ON a.uid = b.uid AND a.created_at = b.created_at WHERE a.uid = b.uid AND a.created_at = b.created_at AND UNIX_TIMESTAMP(a.date) >= %d) r ON u.id = r.uid LEFT JOIN (SELECT * FROM recharge_order WHERE `event` = %d AND `status` = %d AND callback_time > %d) re ON u.id = re.uid WHERE u.id = re.uid AND " + queryCount := " SELECT COUNT(DISTINCT(u.id)) FROM users u LEFT JOIN (SELECT a.* FROM login_record a INNER JOIN ( SELECT uid, MAX( created_at ) created_at FROM login_record WHERE UNIX_TIMESTAMP(date) >= %d GROUP BY uid ) b ON a.uid = b.uid AND a.created_at = b.created_at WHERE a.uid = b.uid AND a.created_at = b.created_at AND UNIX_TIMESTAMP(a.date) >= %d) r ON u.id = r.uid LEFT JOIN (SELECT * FROM recharge_order WHERE `event` = %d AND `status` = %d AND callback_time > %d) re ON u.id = re.uid WHERE u.id = re.uid AND " + + str := fmt.Sprintf(" u.birth >= %d AND u.birth < %d ", su, eu) + + if channel != nil { + str += fmt.Sprintf(" AND u.channel_id = %d AND r.channel_id = %d", *channel, *channel) + } + + // 三天未登录的用户 UNIX_TIMESTAMP('20210816') + str += fmt.Sprintf(" AND (%d - UNIX_TIMESTAMP(r.date) > %d ) ", now, 7*oneDay) + + var count int64 + err := db.Mysql().QueryBySql(fmt.Sprintf(queryCount+str, su, su, common.CurrencyEventReCharge, common.StatusROrderPay, su), &count) + if err != nil { + log.Error(err.Error()) + return 0, nil + } + + str += " GROUP BY u.id " + + str += fmt.Sprintf(" LIMIT %d, %d ", (page-1)*num, num) + + var users []common.PlayerDBInfo + err = db.Mysql().QueryBySql(fmt.Sprintf(queryUser+str, su, su, common.CurrencyEventReCharge, common.StatusROrderPay, su), &users) + if err != nil { + log.Error(err.Error()) + return 0, nil + } + + return count, getLostUserInfo(users) +} + +// 获取活跃流失用户 +func getActiveLostUser(su, eu int64, page, num int, channel *int) (int64, []values.LostUserData) { + var oneDay = 24 * 60 * 60 + now := time.Now().Unix() + + queryUser := fmt.Sprintf(" SELECT u.id, u.nick, u.bind_cash, u.cash, u.birth FROM users u LEFT JOIN ( SELECT uid, MAX(date) date, MAX( created_at ) created_at FROM login_record WHERE UNIX_TIMESTAMP(date) >= %d GROUP BY uid ) b ON u.id = b.uid WHERE u.id = b.uid AND ", su) + + " FROM_UNIXTIME(u.birth,'%Y%m%d') != b.date AND " + queryCount := fmt.Sprintf(" SELECT COUNT(DISTINCT(u.id)) FROM users u LEFT JOIN ( SELECT uid, MAX(date) date, MAX( created_at ) created_at FROM login_record WHERE UNIX_TIMESTAMP(date) >= %d GROUP BY uid ) b ON u.id = b.uid WHERE u.id = b.uid AND ", su) + + " FROM_UNIXTIME(u.birth,'%Y%m%d') != b.date AND " + + str := fmt.Sprintf(" u.birth >= %d AND u.birth < %d ", su, eu) + + if channel != nil { + str += fmt.Sprintf(" AND u.channel_id = %d AND b.channel_id = %d", *channel, *channel) + } + + // 七天未登录的用户 UNIX_TIMESTAMP('20210816') + str += fmt.Sprintf(" AND (%d - UNIX_TIMESTAMP(b.date) > %d ) ", now, 7*oneDay) + + var count int64 + err := db.Mysql().QueryBySql(queryCount+str, &count) + if err != nil { + log.Error(err.Error()) + return 0, nil + } + + str += " GROUP BY u.id " + + str += fmt.Sprintf(" LIMIT %d, %d ", (page-1)*num, num) + + var users []common.PlayerDBInfo + err = db.Mysql().QueryBySql(queryUser+str, &users) + if err != nil { + log.Error(err.Error()) + return 0, nil + } + + return count, getLostUserInfo(users) +} + +// 获取新用户流失 +func getNewLostUser(su, eu int64, page, num int, channel *int) (int64, []values.LostUserData) { + + queryUser := fmt.Sprintf(" SELECT u.id, u.nick, u.bind_cash, u.cash, u.birth FROM users u LEFT JOIN ( SELECT uid, MAX( created_at ) created_at, MAX(date) date FROM login_record WHERE UNIX_TIMESTAMP(date) >= %d GROUP BY uid ) b ON u.id = b.uid WHERE u.id = b.uid ", su) + + "AND FROM_UNIXTIME(u.birth,'%Y%m%d') = b.date AND" + queryCount := fmt.Sprintf(" SELECT COUNT(DISTINCT(u.id)) FROM users u LEFT JOIN ( SELECT uid, MAX( created_at )created_at, MAX(date) date FROM login_record WHERE UNIX_TIMESTAMP(date) >= %d GROUP BY uid ) b ON u.id = b.uid WHERE u.id = b.uid ", su) + + "AND FROM_UNIXTIME(u.birth,'%Y%m%d') = b.date AND" + + str := fmt.Sprintf(" u.birth >= %d AND u.birth < %d ", su, eu) + if channel != nil { + str += fmt.Sprintf(" AND u.channel_id = %d ", *channel) + str += fmt.Sprintf(" AND u.channel_id = %d ", *channel) + } + + // 7天未登录的用户 UNIX_TIMESTAMP('20210816') + str += fmt.Sprintf(" AND (%d - UNIX_TIMESTAMP(b.date) > %d ) ", time.Now().Unix(), 7*24*60*60) + + var count int64 + err := db.Mysql().QueryBySql(queryCount+str, &count) + if err != nil { + log.Error(err.Error()) + return 0, nil + } + + str += fmt.Sprintf(" LIMIT %d, %d ", (page-1)*num, num) + + var users []common.PlayerDBInfo + err = db.Mysql().QueryBySql(queryUser+str, &users) + if err != nil { + log.Error(err.Error()) + return 0, nil + } + + return count, getLostUserInfo(users) +} + +func getLostUserInfo(users []common.PlayerDBInfo) []values.LostUserData { + var res []values.LostUserData + for i := 0; i < len(users); i++ { + var data values.LostUserData + + // 用户昵称 + data.Nick = users[i].Nick + // 用户uid + data.Uid = users[i].Id + // 用户生日 + data.Birth = users[i].Birth + + var record common.LoginRecord + err := db.Mysql().C().Model(&common.LoginRecord{}).Where(" uid = ?", users[i].Id).Last(&record).Error + if err != nil { + log.Error(err.Error()) + } + // 最后登录时间 + data.LastLogin = record.Time + // 玩家游戏局数 + data.GameCount = models.GetGameCountByBalance(nil, nil, &users[i].Id, nil, nil, nil) + // 玩家总退出金额 + data.WithDrawAmount = getPlayerAmountBySql(users[i].Id, int(common.CurrencyEventWithDraw), common.StatusROrderFinish) + // 玩家总充值金额 + data.RechargeAmount = getPlayerAmountBySql(users[i].Id, int(common.CurrencyEventReCharge), common.StatusROrderPay) + + res = append(res, data) + } + return res +} + +// 获取玩家充值金额 +func getPlayerAmountBySql(uid, event, status int) int64 { + var amount int64 + amountTotal := "SELECT IFNULL(SUM(amount),0) as Amount FROM recharge_order WHERE uid = %d AND event = %v AND status = %v " + err := db.Mysql().QueryBySql(fmt.Sprintf(amountTotal, uid, event, status), &amount) + if err != nil { + log.Error(err.Error()) + } + return amount +} diff --git a/modules/backend/handler/guser/lostUserDetail.go b/modules/backend/handler/guser/lostUserDetail.go new file mode 100644 index 0000000..0732aa6 --- /dev/null +++ b/modules/backend/handler/guser/lostUserDetail.go @@ -0,0 +1,39 @@ +package guser + +import ( + "server/modules/backend/app" + "server/modules/backend/models" + utils "server/modules/backend/util" + "server/modules/backend/values" + + "github.com/gin-gonic/gin" +) + +func LostUserDetail(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.LostUserDetailReq) + if !a.S(req) { + return + } + resp := values.LostUserDetailResp{} + a.Data = resp +} + +func getLostUserDetail(uid int, gameId int) values.LostUserDetail { + var lostUserDetail values.LostUserDetail + + lostUserDetail.GameId = gameId + + isWin := true + winCount := models.GetWinGameCountByBalance(nil, nil, &uid, nil, &gameId, nil, &isWin) + lostUserDetail.GameCount = models.GetGameCountByBalance(nil, nil, &uid, nil, &gameId, nil) + + lostUserDetail.WinPer = utils.GetPer(winCount, lostUserDetail.GameCount) + + // lostUserDetail.BreakPer = utils.GetPer(models.GetPlayerBreakCount(nil, nil, &uid, &gameId, nil, nil), lostUserDetail.GameCount) + + return lostUserDetail +} diff --git a/modules/backend/handler/guser/rechargeRank.go b/modules/backend/handler/guser/rechargeRank.go new file mode 100644 index 0000000..a272504 --- /dev/null +++ b/modules/backend/handler/guser/rechargeRank.go @@ -0,0 +1,97 @@ +package guser + +import ( + "fmt" + "server/call" + "server/common" + "server/db" + "server/modules/backend/app" + utils "server/modules/backend/util" + "server/modules/backend/values" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +// 充值排行榜 +func RechargeRank(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.RechargeRankReq) + if !a.S(req) { + return + } + su, eu := utils.GetQueryUnix(req.Start, req.End) + resp := values.RechargeRankResp{} + + var num int64 = 100 + if req.Num != nil { + num = *req.Num + } + + var str string + + if req.Status == 1 { + str = fmt.Sprintf("SELECT uid, SUM(amount) AS amount, COUNT(amount) as event FROM recharge_order WHERE event = %d AND status = %d AND callback_time >= %d AND callback_time < %d GROUP BY uid ORDER BY amount DESC LIMIT 0, %d", + common.CurrencyEventReCharge, + common.StatusROrderPay, + su, + eu, + num) + } else { + str = fmt.Sprintf("SELECT uid, SUM(amount) AS amount, COUNT(amount) as event FROM recharge_order WHERE event = %d AND status = %d AND callback_time >= %d AND callback_time < %d GROUP BY uid ORDER BY amount DESC LIMIT 0, %d", + common.CurrencyEventWithDraw, + common.StatusROrderFinish, + su, + eu, + num) + } + + var users []common.RechargeOrder + + err := db.Mysql().QueryBySql(str, &users) + if err != nil { + log.Error(err.Error()) + a.Code = values.CodeRetry + return + } + + for i := 0; i < len(users); i++ { + var temp values.RechargeRank + temp.Date = su + temp.Uid = users[i].UID + if req.Status == 1 { + temp.TodayRechargeAmount = users[i].Amount + temp.TodayRechargeOrderCount = int64(users[i].Event) + + sql := fmt.Sprintf("event = %v and status = %v and callback_time >= '%v' and callback_time < '%v' and uid = %v", common.CurrencyEventWithDraw, common.StatusROrderFinish, su, eu, users[i].UID) + temp.TodayWithDrawAmount = db.Mysql().Sum(&common.RechargeOrder{}, sql, "amount") + temp.TodayWithDrawOrderCount = db.Mysql().Count(&common.RechargeOrder{}, sql) + } else { + temp.TodayWithDrawAmount = users[i].Amount + temp.TodayWithDrawOrderCount = int64(users[i].Event) + + sql := fmt.Sprintf("event = %v and status = %v and callback_time >= '%v' and callback_time < '%v' and uid = %v", common.CurrencyEventReCharge, common.StatusROrderPay, su, eu, users[i].UID) + temp.TodayRechargeAmount = db.Mysql().Sum(&common.RechargeOrder{}, sql, "amount") + temp.TodayRechargeOrderCount = db.Mysql().Count(&common.RechargeOrder{}, sql) + } + + // 玩家总退出金额 + temp.WithDrawAmount = getPlayerAmountBySql(users[i].UID, int(common.CurrencyEventWithDraw), common.StatusROrderFinish) + // 玩家总充值金额 + temp.RechargeAmount = getPlayerAmountBySql(users[i].UID, int(common.CurrencyEventReCharge), common.StatusROrderPay) + // 玩家生日 + res, _ := call.GetUserInfo(users[i].UID) + temp.Birth = res.Birth + + // 用户渠道 + temp.Channel = users[i].ChannelID + + resp.List = append(resp.List, &temp) + } + resp.Count = int64(len(users)) + + a.Data = resp +} diff --git a/modules/backend/handler/mail/mail.go b/modules/backend/handler/mail/mail.go new file mode 100644 index 0000000..5d91504 --- /dev/null +++ b/modules/backend/handler/mail/mail.go @@ -0,0 +1,286 @@ +package handler + +import ( + "fmt" + "server/call" + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/values" + "server/natsClient" + "server/pb" + "server/util" + "time" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" + "github.com/olivere/elastic/v7" +) + +func DraftList(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.MailDraftListReq) + if !a.S(req) { + return + } + var list []common.MailDraft + var count int64 + var err error + if req.Title == nil && req.StartSendTime == nil && req.EndSendTime == nil { + count, err = db.ES().QueryMailDraftList(int(req.Page), int(req.Num), &list) + } else { + q := elastic.NewBoolQuery() + if req.Title != nil { + log.Debug("title:%v", *req.Title) + q.Must(elastic.NewFuzzyQuery("Title.keyword", *req.Title)) + } + if req.StartSendTime != nil { + q.Filter(elastic.NewRangeQuery("SendTime").Gte(*req.StartSendTime)) + } + if req.EndSendTime != nil { + q.Filter(elastic.NewRangeQuery("SendTime").Lte(*req.EndSendTime)) + } + count, err = db.ES().SearchMailDraftList(int(req.Page), int(req.Num), q, &list) + } + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + resp := values.MailDraftListResp{List: list, Count: int(count)} + a.Data = resp +} + +func DraftCreate(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.MailDraftCreateReq) + if !a.S(req) { + return + } + if req.SendMethod == common.MailSendMethodTiming { + if req.SendTime < time.Now().Unix() { + a.Code = values.CodeParam + a.Msg = "发送时间有误" + return + } + } else { + req.SendTime = time.Now().Unix() + } + save := new(common.MailDraft) + save.Sender = req.Sender + save.Receiver = req.Receiver + save.Type = req.Type + save.Title = req.Title + save.Content = req.Content + save.Enclosure = req.Enclosure + save.SendMethod = req.SendMethod + save.SendTime = req.SendTime + save.Status = common.MailDraftStatusNew + save.Time = time.Now().Unix() + save.Operator = a.User.Account + if err := db.ES().InsertToES(common.ESIndexBackMailDraft, save); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + a.RecordEdit(values.PowerMail, fmt.Sprintf("创建邮件:%v", req.Title)) +} + +func DraftEdit(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.MailDraftEditReq) + if !a.S(req) { + return + } + one := new(common.MailDraft) + if err := db.ES().QueryOne(common.ESIndexBackMailDraft, elastic.NewTermQuery("_id", req.ID), one); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + if req.ID == "" { + a.Code = values.CodeParam + a.Msg = "草稿不存在" + return + } + update := false + if req.Sender != nil { + update = true + one.Sender = *req.Sender + } + if req.Receiver != nil { + update = true + one.Receiver = *req.Receiver + } + if req.Type != nil { + update = true + one.Type = *req.Type + } + if req.Title != nil { + update = true + one.Title = *req.Title + } + if req.Content != nil { + update = true + one.Content = *req.Content + } + if req.Enclosure != nil { + update = true + one.Enclosure = *req.Enclosure + } + if req.SendMethod != nil { + update = true + one.SendMethod = *req.SendMethod + if one.SendMethod == common.MailSendMethodTiming { + if req.SendTime == nil || *req.SendTime < time.Now().Unix() { + a.Code = values.CodeParam + a.Msg = "发送时间有误" + return + } + one.SendTime = *req.SendTime + } else { + one.SendTime = time.Now().Unix() + } + } + if !update { + a.Code = values.CodeParam + a.Msg = "无内容修改" + return + } + one.Operator = a.User.Account + one.Time = time.Now().Unix() + if _, err := db.ES().Update(common.ESIndexBackMailDraft, req.ID, one); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + a.RecordEdit(values.PowerMail, fmt.Sprintf("修改邮件:%v", req.ID)) +} + +func DraftOpt(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.MailDraftOptReq) + if !a.S(req) { + return + } + one := new(common.MailDraft) + if err := db.ES().QueryOne(common.ESIndexBackMailDraft, elastic.NewTermQuery("_id", req.ID), one); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + if one.ID == "" { + a.Code = values.CodeParam + a.Msg = "草稿不存在" + return + } + updateStatus := common.MailDraftStatusDelete + switch req.Opt { + case 1: + if one.Status == common.MailStatusDelete { + a.Code = values.CodeParam + a.Msg = "该草稿已删除" + return + } + updateStatus = common.MailDraftStatusDelete + case 2: + if one.Status == common.MailDraftStatusBack { + a.Code = values.CodeParam + a.Msg = "该草稿已撤回" + return + } + updateStatus = common.MailDraftStatusBack + if err := db.Mysql().Update(&common.Mail{DraftID: one.ID}, map[string]interface{}{"status": common.MailStatusDelete}); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + case 3: + if one.Status == common.MailDraftStatusSent { + a.Code = values.CodeParam + a.Msg = "该草稿已发送" + return + } + updateStatus = common.MailDraftStatusSent + // enclosure := "" + // if len(one.Enclosure) > 0 { + // e, _ := json.Marshal(one.Enclosure) + // enclosure = string(e) + // } + if len(one.Receiver) > 0 { + for _, v := range one.Receiver { + mail := new(common.Mail) + mail.DraftID = one.ID + mail.Sender = one.Sender + mail.Receiver = v + mail.Type = one.Type + mail.Title = one.Title + mail.Content = one.Content + // mail.Enc = enclosure + // mail.SendMethod = one.SendMethod + mail.Status = common.MailStatusNew + if one.SendMethod == common.MailSendMethodTiming { + mail.Time = one.SendTime + } else if one.SendMethod == common.MailSendMethodImmediately { + mail.Time = time.Now().Unix() + } + // util.Go(func() { + if err := db.Mysql().Create(mail); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + // }) + } + } else { + mail := new(common.Mail) + mail.DraftID = one.ID + mail.Sender = one.Sender + mail.Receiver = 0 + mail.Type = one.Type + mail.Title = one.Title + mail.Content = one.Content + // mail.Enc = enclosure + // mail.SendMethod = one.SendMethod + mail.Status = common.MailStatusNew + if one.SendMethod == common.MailSendMethodTiming { + mail.Time = one.SendTime + } else if one.SendMethod == common.MailSendMethodImmediately { + mail.Time = time.Now().Unix() + } + if err := db.Mysql().Create(mail); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + } + if one.SendMethod == common.MailSendMethodImmediately { + // 如果是立即发送,需要通知在线玩家刷新邮件 + err := call.Publish(natsClient.TopicBackRefreshMail, &pb.InnerRefreshMail{UIDs: util.SliceInt2Int32(one.Receiver)}) + if err != nil { + log.Error(err.Error()) + } + } + } + one.Status = updateStatus + one.Operator = a.User.Account + one.Time = time.Now().Unix() + if _, err := db.ES().Update(common.ESIndexBackMailDraft, req.ID, one); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + a.RecordEdit(values.PowerMail, fmt.Sprintf("操作邮件:%v,操作码:%v", req.ID, req.Opt)) +} diff --git a/modules/backend/handler/power/power.go b/modules/backend/handler/power/power.go new file mode 100644 index 0000000..81f00b4 --- /dev/null +++ b/modules/backend/handler/power/power.go @@ -0,0 +1,213 @@ +package handler + +import ( + "fmt" + "server/modules/backend/app" + "server/modules/backend/bdb" + "server/modules/backend/values" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +func UserList(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + resp := values.UserListResp{} + if err := bdb.BackDB.C().Find(&resp.List).Error; err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + a.Data = resp +} + +func AddUser(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.AddUserReq) + if !a.S(req) { + return + } + log.Debug("adduser:%+v", req) + if req.Role == values.UserRoleAdmin { + a.Code = values.CodeParam + a.Msg = "不可添加超级管理员" + return + } + var roles []*values.Role + bdb.BackDB.C().Find(&roles) + var r *values.Role + for _, v := range roles { + if req.Role == v.Role { + r = v + } + } + if r == nil { + log.Error("invalid role:%v", req.Role) + a.Code = values.CodeParam + a.Msg = "请求角色不存在" + return + } + one := &values.User{Name: req.Name, Account: req.Account, Password: req.Password, Role: req.Role, Power: r.Power, Phone: req.Phone, Channels: req.Channels} + if err := bdb.BackDB.Create(one); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeParam + a.Msg = err.Error() + return + } + a.RecordEdit(values.PowerManageUser, fmt.Sprintf("新增用户:%v", req.Name)) +} + +func EditUserPower(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.EditPowerReq) + if !a.S(req) { + return + } + log.Debug("edit power:%+v", req) + user := new(values.User) + user.ID = uint(req.ID) + if err := bdb.BackDB.Get(user); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeParam + a.Msg = "账户不存在" + return + } + if user.Role == values.UserRoleAdmin { + a.Code = values.CodeParam + a.Msg = "不可修改超级管理员" + return + } + update := map[string]interface{}{} + if req.Power != nil { + // if user.Power == *req.Power { + // a.Code = values.CodeParam + // a.Msg = "无内容修改" + // return + // } + if !a.CheckPower(*req.Power) { + return + } + update["power"] = *req.Power + } + if req.Name != nil { + update["name"] = *req.Name + } + if req.Account != nil { + update["account"] = *req.Account + } + if req.Password != nil { + update["password"] = *req.Password + } + if req.Role != nil { + if *req.Role == values.UserRoleAdmin { + a.Code = values.CodeParam + a.Msg = "不可修改为超级管理员" + return + } + update["role"] = *req.Role + } + if req.Phone != nil { + update["phone"] = *req.Phone + } + if req.Channels != nil { + update["channels"] = *req.Channels + } + if len(update) == 0 { + a.Code = values.CodeParam + a.Msg = "无内容修改" + return + } + if err := bdb.BackDB.Update(&values.User{ID: uint(req.ID)}, update); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + a.RecordEdit(values.PowerManageUser, fmt.Sprintf("修改用户权限:%v", req.ID)) +} + +func DelUser(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.DelUserReq) + if !a.S(req) { + return + } + user := new(values.User) + user.ID = uint(req.ID) + if err := bdb.BackDB.C().Delete(user).Where("id = ?", req.ID).Error; err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } +} + +func RoleList(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + resp := values.RoleListResp{} + if err := bdb.BackDB.C().Find(&resp.List).Error; err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + a.Data = resp +} + +func AddRole(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.AddRoleReq) + if !a.S(req) { + return + } + if !a.CheckPower(req.Power) { + return + } + one := &values.Role{Role: req.Role, Name: req.Name, Power: req.Power} + if err := bdb.BackDB.Create(one); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + a.RecordEdit(values.PowerManageRole, fmt.Sprintf("新增角色:%v", req.Name)) +} + +func EditRole(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.EditRoleReq) + if !a.S(req) { + return + } + if !a.CheckPower(req.Power) { + return + } + one := &values.Role{Role: req.Role, Name: req.Name, Power: req.Power} + if err := bdb.BackDB.Update(&values.Role{ID: uint(req.ID)}, one); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + err := bdb.BackDB.Update(&values.User{Role: req.Role}, map[string]interface{}{"power": req.Power}) + if err != nil { + log.Error(err.Error()) + } + a.RecordEdit(values.PowerManageRole, fmt.Sprintf("修改角色:%v", req.ID)) +} diff --git a/modules/backend/handler/profit/editProfit.go b/modules/backend/handler/profit/editProfit.go new file mode 100644 index 0000000..5b9080f --- /dev/null +++ b/modules/backend/handler/profit/editProfit.go @@ -0,0 +1,50 @@ +package profit + +import ( + "fmt" + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" + "server/common" + "server/db" + "server/modules/backend/app" + utils "server/modules/backend/util" + "server/modules/backend/values" +) + +func EditProfit(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + + req := new(values.EditProfitReq) + if !a.S(req) { + return + } + + for i := 0; i < len(req.List); i++ { + var data common.ESIncomeStatistics + + if req.List[i].Period != nil { + data.Period = *req.List[i].Period + } + + if req.List[i].Investment != nil { + data.Investment = *req.List[i].Investment + } + + if req.List[i].Channel != nil { + data.Channel = *req.List[i].Channel + } + + data.Time, _ = utils.GetQueryUnix(req.List[i].Start, req.List[i].Start) + + id := fmt.Sprintf("%d_%s", req.List[i].Channel, req.List[i].Start) + _, err := db.ES().Upsert(common.ESIndexBackIncomeStatistics, id, &data, &data) + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + } +} diff --git a/modules/backend/handler/profit/playerProfit.go b/modules/backend/handler/profit/playerProfit.go new file mode 100644 index 0000000..1fb5363 --- /dev/null +++ b/modules/backend/handler/profit/playerProfit.go @@ -0,0 +1,103 @@ +package profit + +import ( + "github.com/gin-gonic/gin" + "server/common" + "server/modules/backend/app" + "server/modules/backend/models" + utils "server/modules/backend/util" + "server/modules/backend/values" + "server/util" + "time" +) + +var playerProfitCache = make(map[int]map[int64]*values.PlayerProfitInfo) + +func PlayerProfit(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + + req := new(values.PlayerProfitReq) + if !a.S(req) { + return + } + + resp := values.PlayerProfitResp{} + + var oneDay int64 = 24 * 60 * 60 + su, eu := utils.GetQueryUnix(req.Start, req.End) + + zeroTime := util.GetZeroTime(time.Now()).Unix() + + var channelId int + if req.Channel != nil { + channelId = *req.Channel + } + + // 缓存数据 + data := playerProfitCache[channelId] + var flag bool + if data == nil { + flag = true + } else { + if _, ok := data[zeroTime-oneDay]; !ok { + flag = true + } + } + + if flag { + temp := make(map[int64]*values.PlayerProfitInfo) + for i := zeroTime - 29*oneDay; i < zeroTime; i += oneDay { + temp[i] = getPlayerProfitInfo(i, i+oneDay, req.Channel) + } + playerProfitCache[channelId] = temp + } + + // 缓存中读取数据 + for i := su; i < eu; i += oneDay { + if val, ok := playerProfitCache[channelId]; ok { + if val[i] != nil { + var income common.ESIncomeStatistics + t := i + models.QueryOneIncome(&t, req.Channel, &income) + val[i].Investment = income.Investment + val[i].Period = income.Period + resp.List = append(resp.List, []*values.PlayerProfitInfo{val[i]}...) + } else { + resp.List = append(resp.List, []*values.PlayerProfitInfo{getPlayerProfitInfo(i, i+oneDay, req.Channel)}...) + } + } else { + resp.List = append(resp.List, []*values.PlayerProfitInfo{getPlayerProfitInfo(i, i+oneDay, req.Channel)}...) + } + } + + a.Data = resp +} + +func getPlayerProfitInfo(su, eu int64, channel *int) *values.PlayerProfitInfo { + var playerProfitInfo values.PlayerProfitInfo + + playerProfitInfo.Date = su + + newPlayer := true + var income common.ESIncomeStatistics + + models.QueryOneIncome(&su, channel, &income) + playerProfitInfo.Investment = income.Investment + playerProfitInfo.NewPlayer = models.GetNewPlayerCountBySql(channel, su, eu) + playerProfitInfo.TheDayPayCount = models.GetRechargePlayers(&su, &eu, channel, &newPlayer, "UID") + playerProfitInfo.TheDayPayPer = utils.GetPer(playerProfitInfo.TheDayPayCount, playerProfitInfo.NewPlayer) + playerProfitInfo.TheDayPayAmount = models.GetNewPayAmountBySql(channel, su, eu) + playerProfitInfo.TheDayAvgPayment = utils.GetPoint(playerProfitInfo.TheDayPayAmount, playerProfitInfo.TheDayPayCount) + playerProfitInfo.NextDayPayCount = models.GetPlayerCountByBirth(channel, su) + playerProfitInfo.NextPayAmount = models.GetAmountTotalByBirth(channel, su) + playerProfitInfo.NextDayPayPer = utils.GetPer(playerProfitInfo.NextDayPayCount, models.GetNextDayReserved(channel, su)) + playerProfitInfo.PayCount = models.GetNewAllPayCountBySql(channel, su, eu) + playerProfitInfo.PayAmount = models.GetNewAllPayAmountBySql(channel, su) + playerProfitInfo.WithdrawAmount = models.GetNewAllWithDrawAmountBySql(channel, su) + playerProfitInfo.Period = income.Period + + return &playerProfitInfo +} diff --git a/modules/backend/handler/statistics/keepData.go b/modules/backend/handler/statistics/keepData.go new file mode 100644 index 0000000..a2d86fd --- /dev/null +++ b/modules/backend/handler/statistics/keepData.go @@ -0,0 +1,681 @@ +package statistics + +import ( + "fmt" + "reflect" + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/models" + utils "server/modules/backend/util" + "server/modules/backend/values" + "server/util" + "sync" + "time" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" + "github.com/olivere/elastic/v7" +) + +// KeepData 获取留存数据 +func KeepData(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.KeepDataReq) + if !a.S(req) { + return + } + log.Debug("keep data req:%+v", req) + resp := values.KeepDataResp{} + su, eu := utils.GetQueryUnix(req.Start, req.End) + + resp.List = append(resp.List, GetKeepData(su, eu, req.ActiveKeep, req.Channel)...) + + // 七天内的新增付费数据直接拉取缓存 + /*last7 := util.GetZeroTime(time.Now()).Unix() - 7*24*60*60 + if req.ActiveKeep == 4 { + resp.List = values.GetKeepRechargeData(su, eu) + } + + if len(resp.List) == 0 { + resp.List = GetKeepData(su, eu, req.ActiveKeep, req.Channel) + } else if su < last7 { + if eu > last7 { + eu = last7 + } + resp.List = append(resp.List, GetKeepData(su, eu, req.ActiveKeep, req.Channel)...) + }*/ + // total := make([]int64, 9) + // for _, v := range resp.List { + // resp.Total.NewCount += v.NewCount + // total[0] += int64(utils.AddPerFloat(0, v.K1) * float64(v.NewCount) / 100) + // total[1] += int64(utils.AddPerFloat(0, v.K2) * float64(v.NewCount) / 100) + // total[2] += int64(utils.AddPerFloat(0, v.K3) * float64(v.NewCount) / 100) + // total[3] += int64(utils.AddPerFloat(0, v.K4) * float64(v.NewCount) / 100) + // total[4] += int64(utils.AddPerFloat(0, v.K5) * float64(v.NewCount) / 100) + // total[5] += int64(utils.AddPerFloat(0, v.K6) * float64(v.NewCount) / 100) + // total[6] += int64(utils.AddPerFloat(0, v.K7) * float64(v.NewCount) / 100) + // total[7] += int64(utils.AddPerFloat(0, v.K15) * float64(v.NewCount) / 100) + // total[8] += int64(utils.AddPerFloat(0, v.K30) * float64(v.NewCount) / 100) + // } + // if req.Channel != nil { + // resp.Total.Channel = *req.Channel + // } + resp.Count = int64(len(resp.List)) + s := (req.Page - 1) * req.Num + if s > len(resp.List)-1 { + a.Code = values.CodeParam + a.Msg = "查询参数有误" + return + } + e := req.Page * req.Num + if e > len(resp.List) { + e = len(resp.List) + } + resp.List = resp.List[s:e] + // resp.Total.K1 = utils.GetPer(total[0], resp.Total.NewCount) + // resp.Total.K2 = utils.GetPer(total[1], resp.Total.NewCount) + // resp.Total.K3 = utils.GetPer(total[2], resp.Total.NewCount) + // resp.Total.K4 = utils.GetPer(total[3], resp.Total.NewCount) + // resp.Total.K5 = utils.GetPer(total[4], resp.Total.NewCount) + // resp.Total.K6 = utils.GetPer(total[5], resp.Total.NewCount) + // resp.Total.K7 = utils.GetPer(total[6], resp.Total.NewCount) + // resp.Total.K15 = utils.GetPer(total[7], resp.Total.NewCount) + // resp.Total.K30 = utils.GetPer(total[8], resp.Total.NewCount) + a.Data = resp +} + +func GetKeepData(s, e int64, t int, channel *int) []values.KeepData { + q := elastic.NewBoolQuery() + cid := 0 + if channel != nil { + cid = *channel + } + var list []values.KeepData + q.Must(elastic.NewMatchQuery("Channel", cid)) + q.Must(elastic.NewMatchQuery("Type", t)) + q.Filter(elastic.NewRangeQuery("Time").Gte(s)) + q.Filter(elastic.NewRangeQuery("Time").Lt(e)) + db.ES().QueryList(common.ESIndexBackKeepData, 0, 5000, q, &list, "Time", false) + var oneDay int64 = 24 * 60 * 60 + now := util.GetZeroTime(time.Now()).Unix() + flag := 0 + channelSql := "" + if channel != nil { + channelSql = fmt.Sprintf(" and channel_id = %v", *channel) + } + total := (e - s) / oneDay + ret := make([]values.KeepData, total) + count := 0 + group := new(sync.WaitGroup) + group.Add(int(total)) + for j := e; j > s; j -= oneDay { + var all int64 = 0 + start := j - oneDay + end := j + thisCount := count + d := time.Unix(start, 0).Format("20060102") + keep := &values.KeepData{Date: d, Type: t, Time: start, Channel: cid} + + var exist bool // es中是否有该条数据 + var change bool // 该条数据是否需要写入es + if flag < len(list) && list[flag].Time == start { + keep = &list[flag] + all = list[flag].NewCount + exist = true + flag++ + } else { + if t == 1 { + db.Mysql().C().Table("login_record").Where(fmt.Sprintf("date = '%v'%v", d, channelSql)).Distinct("uid").Count(&all) + } else if t == 2 { + all = models.GetNewPlayerCountBySql(channel, start, end) + } else if t == 3 { + all = models.GetPayCountBySql(channel, start, end) + } else if t == 4 { + all = models.GetNewPayCountBySql(channel, start, end) + } else if t == 5 { + all = models.GetNewPayWithdrawCountBySql(channel, start, end) + } + keep.NewCount = all + } + ref := reflect.ValueOf(keep).Elem() + + util.Go(func() { + index := 1 + nowIndex := -1 + nowVal := "" + for { + k := start + int64(index)*oneDay + if index > 30 || k > now { + break + } + /*if index > 7 && index != 15 && index != 30 { + index++ + continue + }*/ + if index > 15 && index != 30 { + index++ + continue + } + kField := ref.FieldByName(fmt.Sprintf("K%v", index)) + if kField.String() != "" { + index++ + continue + } + oneD := time.Unix(k, 0).Format("20060102") + isNow := k == now + if isNow { + nowIndex = index + } + sql := "" + if t == 1 { + sql = fmt.Sprintf(`SELECT count(a.uid) FROM + (SELECT DISTINCT(uid) FROM login_record WHERE date = '%v'%v)a + INNER JOIN + (SELECT DISTINCT(uid) FROM login_record WHERE date = '%v'%v)b + on a.uid = b.uid `, + d, channelSql, oneD, channelSql) + // if k == now { + // sql += " and status = 2" + // } + } else if t == 2 { + sql = fmt.Sprintf("SELECT count(DISTINCT(uid)) from login_record WHERE first_time = %d and date = '%s' %s", + start, oneD, channelSql) + } else if t == 3 { + sqlStr := "SELECT COUNT(DISTINCT(b.uid)) from login_record as b " + + " INNER JOIN " + + "(SELECT DISTINCT(uid) from recharge_order WHERE callback_time >=%d and callback_time <%d and `event` = %d and `status` = %d) as a " + + "on a.uid = b.uid " + + "WHERE date = '%s' %v AND a.uid = b.uid" + sql = fmt.Sprintf(sqlStr, + start, // 支付回调时间下限 + start+oneDay, // 支付回调时间上限 + common.CurrencyEventReCharge, // 支付事件 + common.StatusROrderPay, // 支付状态 + oneD, // 统计结尾时间 + channelSql, // 渠道 + ) + } else if t == 4 { + sqlStr := + "SELECT Count(DISTINCT(re.uid)) FROM recharge_order AS re " + + " INNER JOIN " + + " (SELECT DISTINCT(uid) FROM login_record WHERE date = '%s' and first_time = %d %v) as lo " + + " ON re.uid = lo.uid " + + " WHERE event = %d AND status = %d " + + " AND callback_time >= %d " + + " AND callback_time < %d %v" + sql = fmt.Sprintf(sqlStr, + oneD, // 统计登录时间 + start, // 注册时间下限 + channelSql, // 渠道 + common.CurrencyEventReCharge, // 支付事件 + common.StatusROrderPay, // 支付状态 + start, // 支付回调时间下限 + start+oneDay, // 支付回调时间上限 + channelSql, // 渠道 + ) + } else if t == 5 { + sqlStr := + "SELECT Count(DISTINCT(re.uid)) FROM withdraw_order AS re " + + " INNER JOIN " + + " (SELECT DISTINCT(uid) FROM login_record WHERE date = '%s' and first_time = %d %v) as lo " + + " ON re.uid = lo.uid " + + " WHERE event = %d AND status = %d " + + " AND callback_time >= %d " + + " AND callback_time < %d %v" + sql = fmt.Sprintf(sqlStr, + oneD, // 统计登录时间 + start, // 注册时间下限 + channelSql, // 渠道 + common.CurrencyEventReCharge, // 支付事件 + common.StatusROrderPay, // 支付状态 + start, // 支付回调时间下限 + start+oneDay, // 支付回调时间上限 + channelSql, // 渠道 + ) + } + var next int64 + if err := db.Mysql().C().Raw(sql).Scan(&next).Error; err != nil { + log.Error("err:%v", err) + } + + // 当天的数据会变动,不写入es + // log.Debug("k:%v,now:%v", k, now) + if isNow { + nowVal = utils.GetPer(next, all) + } else { + change = true + kField.SetString(utils.GetPer(next, all)) + } + index++ + } + if change { + id := fmt.Sprintf("%v_%v_%v", d, cid, t) + if exist { + db.ES().DeleteByID(common.ESIndexBackKeepData, id) + } + db.ES().InsertToESByID(common.ESIndexBackKeepData, id, keep) + } + // 赋值当天的留存数据 + if nowIndex > 0 { + ref.FieldByName(fmt.Sprintf("K%v", nowIndex)).SetString(nowVal) + } + ret[thisCount] = *keep + group.Done() + }) + count++ + } + group.Wait() + return ret +} + +// 活跃玩家留存 +func getActiveKeep(s, e int64, channel *int) ([]values.KeepData, []int64) { + var ret []values.KeepData + var oneDay int64 = 24 * 60 * 60 + + now := time.Now().Unix() + total := make([]int64, 9) + for j := e; j > s; j -= oneDay { + var index int64 = 1 + var all int64 = 0 + i := j - oneDay + d := time.Unix(i, 0).Format("20060102") + sql := fmt.Sprintf("date = '%v'", d) + keep := values.KeepData{Date: d} + if channel != nil { + keep.Channel = *channel + sql += fmt.Sprintf(" and channel_id = %v", *channel) + } + all = db.Mysql().Count(&common.LoginRecord{}, sql) + keep.NewCount = all + if all == 0 { + ret = append(ret, keep) + continue + } + // log.Debug("t:%v,i:%v,j:%v,all:%v", t, i, j, all) + for { + k := i + index*oneDay + if index > 30 || k > now { + break + } + if index <= 7 || index == 15 || index == 30 { + oneD := time.Unix(k, 0).Format("20060102") + channelSql := "" + if channel != nil { + channelSql = fmt.Sprintf(" and channel_id = %v", *channel) + } + sql := fmt.Sprintf("SELECT count(b.uid) FROM (SELECT uid FROM login_record WHERE date = '%v'%v)a LEFT JOIN (SELECT uid FROM login_record WHERE date = '%v'%v)b on a.uid=b.uid", d, channelSql, oneD, channelSql) + var next int64 + if err := db.Mysql().C().Raw(sql).Scan(&next).Error; err != nil { + log.Error("err:%v", err) + } + switch index { + case 1: + keep.K1 = utils.GetPer(next, all) + total[0] += next + case 2: + keep.K2 = utils.GetPer(next, all) + total[1] += next + case 3: + keep.K3 = utils.GetPer(next, all) + total[2] += next + case 4: + keep.K4 = utils.GetPer(next, all) + total[3] += next + case 5: + keep.K5 = utils.GetPer(next, all) + total[4] += next + case 6: + keep.K6 = utils.GetPer(next, all) + total[5] += next + case 7: + keep.K7 = utils.GetPer(next, all) + total[6] += next + case 15: + keep.K15 = utils.GetPer(next, all) + total[7] += next + case 30: + keep.K30 = utils.GetPer(next, all) + total[8] += next + } + } + index++ + } + + log.Debug("keep:%+v", keep) + ret = append(ret, keep) + } + return ret, total +} + +// 新增玩家留存 +func getPlayerKeep(s, e int64, channel *int) ([]values.KeepData, []int64) { + var ret []values.KeepData + var oneDay int64 = 24 * 60 * 60 + + now := time.Now().Unix() + total := make([]int64, 16) + for j := e; j > s; j -= oneDay { + var index int64 = 1 + var all int64 = 0 + i := j - oneDay + + all = models.GetNewPlayerCountBySql(channel, i, i) + t := time.Unix(i, 0).Format("20060102") + keep := values.KeepData{Date: t, NewCount: all} + if channel != nil { + keep.Channel = *channel + } + if all == 0 { + ret = append(ret, keep) + continue + } + // log.Debug("t:%v,i:%v,j:%v,all:%v", t, i, j, all) + for { + k := i + index*oneDay + if index > 30 || k > now { + break + } + Kt := time.Unix(k, 0).Format("20060102") + if index <= 15 || index == 30 { + var str string + if channel != nil { + str = fmt.Sprintf("SELECT COUNT(DISTINCT(l.uid)) FROM login_record AS l INNER JOIN (SELECT * FROM users WHERE birth >= %d AND birth < %d AND channel_id = %d) AS b ON l.uid = b.id WHERE l.date = %s AND l.channel_id = %d", i, i+oneDay, *channel, Kt, *channel) + } else { + str = fmt.Sprintf("SELECT COUNT(DISTINCT(l.uid)) FROM login_record AS l INNER JOIN (SELECT * FROM users WHERE birth >= %d AND birth < %d) AS b ON l.uid = b.id WHERE l.date = %s ", i, i+oneDay, Kt) + } + + var next int64 + err := db.Mysql().QueryBySql(str, &next) + if err != nil { + log.Error(err.Error()) + } + + switch index { + case 1: + keep.K1 = utils.GetPer(next, all) + total[0] += next + case 2: + keep.K2 = utils.GetPer(next, all) + total[1] += next + case 3: + keep.K3 = utils.GetPer(next, all) + total[2] += next + case 4: + keep.K4 = utils.GetPer(next, all) + total[3] += next + case 5: + keep.K5 = utils.GetPer(next, all) + total[4] += next + case 6: + keep.K6 = utils.GetPer(next, all) + total[5] += next + case 7: + keep.K7 = utils.GetPer(next, all) + total[6] += next + case 8: + keep.K8 = utils.GetPer(next, all) + total[7] += next + case 9: + keep.K9 = utils.GetPer(next, all) + total[8] += next + case 10: + keep.K10 = utils.GetPer(next, all) + total[9] += next + case 11: + keep.K11 = utils.GetPer(next, all) + total[10] += next + case 12: + keep.K12 = utils.GetPer(next, all) + total[11] += next + case 13: + keep.K13 = utils.GetPer(next, all) + total[12] += next + case 14: + keep.K14 = utils.GetPer(next, all) + total[13] += next + case 15: + keep.K15 = utils.GetPer(next, all) + total[14] += next + case 30: + keep.K30 = utils.GetPer(next, all) + total[15] += next + } + } + index++ + } + + log.Debug("keep:%+v", keep) + ret = append(ret, keep) + } + return ret, total +} + +// 活跃付费存留 +func getActivePayKeep(s, e int64, channel *int) ([]values.KeepData, []int64) { + var ret []values.KeepData + var oneDay int64 = 24 * 60 * 60 + + now := time.Now().Unix() + total := make([]int64, 9) + + var channelStr string + if channel != nil { + channelStr = fmt.Sprintf(" AND channel_id = %v ", *channel) + } + + sqlStr := " SELECT COUNT(DISTINCT(re.uid)) FROM recharge_order re " + + " LEFT JOIN " + + " ( " + + " SELECT uid FROM login_record WHERE " + + " date = %s " + + " %v " + + " GROUP BY uid " + + " ) lo " + + " ON re.uid = lo.uid " + + " WHERE re.uid = lo.uid " + + " AND re.event = %d " + + " AND re.status = %d " + + " AND re.callback_time >= %d " + + " AND re.callback_time < %d " + + " %v " + + for j := e; j > s; j -= oneDay { + i := j - oneDay + var index int64 = 1 + var all int64 = 0 + var keep values.KeepData + var d = time.Unix(i, 0).Format("20060102") + keep.Date = d + if channel != nil { + keep.Channel = *channel + } + all = models.GetPayCountBySql(channel, i, j) + keep.NewCount = all + if all == 0 { + ret = append(ret, keep) + continue + } + + for { + k := i + index*oneDay + if index > 30 || k > now { + break + } + if index <= 7 || index == 15 || index == 30 { + oneD := time.Unix(k, 0).Format("20060102") + + sql := fmt.Sprintf(sqlStr, + oneD, // 统计结尾时间 + channelStr, // 渠道 + common.CurrencyEventReCharge, // 支付事件 + common.StatusROrderPay, // 支付状态 + i, // 支付回调时间下限 + i+24*60*60, // 支付回调时间上限 + channelStr, // 渠道 + ) + + var next int64 + if err := db.Mysql().C().Raw(sql).Scan(&next).Error; err != nil { + log.Error("err:%v", err) + } + switch index { + case 1: + keep.K1 = utils.GetPer(next, all) + total[0] += next + case 2: + keep.K2 = utils.GetPer(next, all) + total[1] += next + case 3: + keep.K3 = utils.GetPer(next, all) + total[2] += next + case 4: + keep.K4 = utils.GetPer(next, all) + total[3] += next + case 5: + keep.K5 = utils.GetPer(next, all) + total[4] += next + case 6: + keep.K6 = utils.GetPer(next, all) + total[5] += next + case 7: + keep.K7 = utils.GetPer(next, all) + total[6] += next + case 15: + keep.K15 = utils.GetPer(next, all) + total[7] += next + case 30: + keep.K30 = utils.GetPer(next, all) + total[8] += next + } + } + index++ + } + + ret = append(ret, keep) + } + return ret, total +} + +// 新增付费存留 +func getPlayerPayKeep(s, e int64, channel *int) ([]values.KeepData, []int64) { + var ret []values.KeepData + var oneDay int64 = 24 * 60 * 60 + + now := time.Now().Unix() + total := make([]int64, 9) + + sqlStr := " SELECT COUNT(DISTINCT(id)) FROM users " + + " LEFT JOIN " + + " ( " + + " SELECT uid FROM recharge_order WHERE " + + " event = %d " + + " AND status = %d " + + " AND callback_time >= %d" + + " AND callback_time < %d" + + " %v " + + " GROUP BY uid" + + " ) " + + " AS re ON id = re.uid " + + " LEFT JOIN " + + " ( SELECT uid FROM login_record WHERE " + + " date = %s " + + " %v " + + " GROUP BY uid " + + " ) " + + " AS lo ON id = lo.uid" + + " WHERE " + + " id = re.uid " + + " AND id = lo.uid " + + " AND birth >= %d " + + " AND birth < %d " + + " %v " + + var channelStr string + if channel != nil { + channelStr = fmt.Sprintf(" AND channel_id = %v ", *channel) + } + + for j := e; j > s; j -= oneDay { + var index int64 = 1 + var all int64 = 0 + i := j - oneDay + + all = models.GetNewPayCountBySql(channel, i, i) + t := time.Unix(i, 0).Format("20060102") + keep := values.KeepData{Date: t, NewCount: all} + if channel != nil { + keep.Channel = *channel + } + if all == 0 { + ret = append(ret, keep) + continue + } + for { + k := i + index*oneDay + if index > 30 || k > now { + break + } + Kt := time.Unix(k, 0).Format("20060102") + if index <= 7 || index == 15 || index == 30 { + str := fmt.Sprintf(sqlStr, + common.CurrencyEventReCharge, // 支付事件 + common.StatusROrderPay, // 支付状态 + i, // 支付回调时间下限 + i+oneDay, // 支付回调时间上限 + channelStr, // 渠道 + Kt, // 统计登录时间 + channelStr, // 渠道 + i, // 注册时间下限 + i+oneDay, // 注册时间上限 + channelStr, // 渠道 + ) + + var next int64 + err := db.Mysql().QueryBySql(str, &next) + if err != nil { + log.Error(err.Error()) + } + + switch index { + case 1: + keep.K1 = utils.GetPer(next, all) + total[0] += next + case 2: + keep.K2 = utils.GetPer(next, all) + total[1] += next + case 3: + keep.K3 = utils.GetPer(next, all) + total[2] += next + case 4: + keep.K4 = utils.GetPer(next, all) + total[3] += next + case 5: + keep.K5 = utils.GetPer(next, all) + total[4] += next + case 6: + keep.K6 = utils.GetPer(next, all) + total[5] += next + case 7: + keep.K7 = utils.GetPer(next, all) + total[6] += next + case 15: + keep.K15 = utils.GetPer(next, all) + total[7] += next + case 30: + keep.K30 = utils.GetPer(next, all) + total[8] += next + } + } + index++ + } + + log.Debug("keep:%+v", keep) + ret = append(ret, keep) + } + return ret, total +} diff --git a/modules/backend/handler/statistics/newPlayerData.go b/modules/backend/handler/statistics/newPlayerData.go new file mode 100644 index 0000000..ff2b72f --- /dev/null +++ b/modules/backend/handler/statistics/newPlayerData.go @@ -0,0 +1,98 @@ +package statistics + +import ( + "server/modules/backend/app" + "server/modules/backend/models" + utils "server/modules/backend/util" + "server/modules/backend/values" + "time" + + "github.com/gin-gonic/gin" +) + +// 新增用户分析 +func NewPlayerData(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.NewPlayerDataReq) + if !a.S(req) { + return + } + resp := values.NewPlayerDataResp{} + su, eu := utils.GetQueryUnix(req.Start, req.End) + resp.New = models.GetNewPlayerCountBySql(req.Channel, su, eu) + + resp.NewRechargeCount = models.GetNewPayCountBySql(req.Channel, su, eu) + resp.NewRechargePer = utils.GetPer(resp.NewRechargeCount, resp.New) + resp.NewRecharge = models.GetNewPayAmountBySql(req.Channel, su, eu) + + resp.Map1 = getNewPlayerDataMap1(su, eu, req.Channel, req.Games) + resp.Map2 = getNewPlayerDataMap2(su, eu, req.Channel) + resp.Map3 = getNewPlayerDataMap3(su, eu, req.Channel, req.Games) + resp.Map4 = getNewPlayerDataMap4(su, eu, req.Channel) + + a.Data = resp +} + +func getNewPlayerDataMap1(s, e int64, channel *int, games *string) map[string]values.NewPlayerDataMap1 { + ret := map[string]values.NewPlayerDataMap1{} + var oneDay int64 = 24 * 60 * 60 + + for i := s; i < e; i += oneDay { + j := i + oneDay + t := time.Unix(i, 0).Format("20060102") + one := values.NewPlayerDataMap1{} + one.New = models.GetNewPlayerCountBySql(channel, i, j) + one.NewRechargeCount = models.GetRecharge(&i, &j, true, channel) + ret[t] = one + } + return ret +} + +func getNewPlayerDataMap2(s, e int64, channel *int) map[string]values.NewPlayerDataMap2 { + ret := map[string]values.NewPlayerDataMap2{} + var oneDay int64 = 24 * 60 * 60 + for i := s; i < e; i += oneDay { + j := i + oneDay + t := time.Unix(i, 0).Format("20060102") + one := values.NewPlayerDataMap2{} + one.Recharge = models.GetRechargeTotal(&i, &j, true, channel) + recharge := models.GetRecharge(&i, &j, true, channel) + one.ARPPU = utils.GetPoint(one.Recharge, recharge) + ret[t] = one + } + return ret +} + +func getNewPlayerDataMap3(s, e int64, channel *int, games *string) map[string]values.NewPlayerDataMap3 { + ret := map[string]values.NewPlayerDataMap3{} + var oneDay int64 = 24 * 60 * 60 + for i := s; i < e; i += oneDay { + j := i + oneDay + t := time.Unix(i, 0).Format("20060102") + one := values.NewPlayerDataMap3{} + recharge := models.GetRecharge(&i, &j, true, channel) + rechargeTotal := models.GetRecharge(&i, &j, false, channel) + one.NewRechargePer = utils.GetPer(recharge, rechargeTotal) + ret[t] = one + } + return ret +} + +func getNewPlayerDataMap4(s, e int64, channel *int) map[string]values.NewPlayerDataMap4 { + ret := map[string]values.NewPlayerDataMap4{} + var oneDay int64 = 24 * 60 * 60 + // isNew := true + for i := s; i < e; i += oneDay { + // j := i + oneDay + t := time.Unix(i, 0).Format("20060102") + one := values.NewPlayerDataMap4{} + // breakCount := models.GetBreak(&i, &j, &isNew, channel) + // active := models.GetActive(&i, &j, true, false, channel) + // one.BrekPer = utils.GetPer(breakCount, active) + ret[t] = one + } + return ret +} diff --git a/modules/backend/handler/statistics/newRechargeData.go b/modules/backend/handler/statistics/newRechargeData.go new file mode 100644 index 0000000..13973c2 --- /dev/null +++ b/modules/backend/handler/statistics/newRechargeData.go @@ -0,0 +1,146 @@ +package statistics + +import ( + "fmt" + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/models" + utils "server/modules/backend/util" + "server/modules/backend/values" + "server/util" + "strconv" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" + "github.com/olivere/elastic/v7" +) + +func NewRechargeData(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.NewRechargeDataReq) + if !a.S(req) { + return + } + resp := values.NewRechargeDataResp{} + + su, eu := utils.GetQueryUnix(req.Start, req.End) + + queryUser := " SELECT * FROM users AS u " + queryCount := " SELECT COUNT(*) as count FROM users AS u " + + str := " INNER JOIN " + + " ( SELECT uid FROM recharge_order WHERE " + + " event = %d " + + " AND status = %d " + + " %v " + + " GROUP BY uid " + + ") r " + + " ON u.id = r.uid WHERE " + + " u.birth >= %d AND u.birth < %d " + + " %v " + + var channelStr string + if req.Channel != nil { + channelStr = fmt.Sprintf(" AND channel_id = %d", *req.Channel) + } + + str = fmt.Sprintf(str, + common.CurrencyEventReCharge, + common.StatusROrderPay, + channelStr, + su, + eu, + channelStr, + ) + + var count int64 + err := db.Mysql().QueryBySql(queryCount+str, &count) + if err != nil { + log.Error(err.Error()) + } + + str += fmt.Sprintf(" LIMIT %d, %d ", (req.Page-1)*req.Num, req.Num) + + var users []common.PlayerDBInfo + err = db.Mysql().QueryBySql(queryUser+str, &users) + if err != nil { + log.Error(err.Error()) + } + + for i := 0; i < len(users); i++ { + var newRechargeData values.NewRechargeData + newRechargeData.Date = su + newRechargeData.Nick = users[i].Nick + newRechargeData.Uid = users[i].Id + newRechargeData.Birth = users[i].Birth + + // recharge := &common.RechargeOrder{UID: users[i].Id, Event: int(common.CurrencyEventReCharge), Status: common.StatusROrderPay} + // db.Mysql().Get(recharge) + // var recharge common.RechargeOrder + // err = db.Mysql().C().Model(&common.RechargeOrder{}).Where(" uid = ? AND event = ? AND status = ? ", users[i].Id, common.CurrencyEventReCharge, common.StatusROrderPay).First(&recharge).Error + // if err != nil { + // log.Error(err.Error()) + // } + + data := common.CurrencyBalance{} + q := elastic.NewBoolQuery() + q.Must(elastic.NewMatchQuery("uid", users[i].Id)) + q.Must(elastic.NewMatchQuery("event", common.CurrencyEventReCharge)) + err = db.ES().QueryOne(common.ESIndexBalance, q, &data, "time", true) + if err != nil { + log.Error(err.Error()) + } + newRechargeData.RechargeDate = data.Time + + // 充值前玩家金额 + newRechargeData.RechargeAmount = data.Balance - data.Value + + // 充值前的可退出金额 + newRechargeData.WithdrawAmount = data.Balance + + // 玩家游戏局数 + gameCount := make(map[string]int64) + // 房间游戏数据 + // for j := 0; j < len(common.RoomGameIDs); j++ { + // gameCount[strconv.Itoa(common.RoomGameIDs[j])] = models.GetUserGameCount(nil, nil, &users[i].Id, &common.RoomGameIDs[j], nil, req.Channel) + // } + + // 百人游戏数据 + // for j := 0; j < len(common.MillionGameIDs); j++ { + // millionGameID := common.MillionGameIDs[j].(int) + // gameCount[strconv.Itoa(millionGameID)] = models.GetUserGameCount(nil, nil, &users[i].Id, &millionGameID, nil, req.Channel) + // } + q = elastic.NewBoolQuery() + q.Must(elastic.NewMatchQuery("UID", users[i].Id)) + games, _ := db.ES().GroupBy(common.ESIndexGameData, "GameID", q, 0) + for _, v := range games.Buckets { + gameCount[strconv.Itoa(util.GetInt(v.Key))] = int64(v.Doc_count) + } + newRechargeData.MostGameCount = gameCount + + // 最后三局游戏记录 + event := int(common.CurrencyEventGameSettle) + var balance []common.CurrencyBalance + uid := strconv.Itoa(users[i].Id) + _, err = models.QueryUserBalance(&uid, 0, 3, nil, &data.Time, nil, nil, nil, req.Channel, &event, &balance) + if err != nil { + log.Error(err.Error()) + return + } + for j := 0; j < len(balance); j++ { + newRechargeData.GameRecord = append(newRechargeData.GameRecord, balance[j].Value) + } + + // 玩家充值前的游戏局数 + // newRechargeData.GameCountBeforeRecharge = models.GetUserGameCount(nil, &data.Time, &users[i].Id, nil, nil, nil) + + resp.List = append(resp.List, newRechargeData) + } + + resp.Count = count + a.Data = resp +} diff --git a/modules/backend/handler/statistics/output.go b/modules/backend/handler/statistics/output.go new file mode 100644 index 0000000..ca651ac --- /dev/null +++ b/modules/backend/handler/statistics/output.go @@ -0,0 +1,293 @@ +package statistics + +import ( + "fmt" + "server/call" + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/models" + "server/modules/backend/util" + "server/modules/backend/values" + utils "server/util" + "sync" + "time" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" + "github.com/olivere/elastic/v7" +) + +type OutputDataReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Page int `json:"Page" binding:"required"` + Num int `json:"Num" binding:"required"` + Channel int `json:"Channel"` +} + +type OutputDataResp struct { + Titles []string + Count int64 + AllData []OneOutputData + Total OneOutputData +} + +type OneOutputData struct { + Date string + Time int64 + Data map[string]string +} + +// OutputData 获取产出统计 +func OutputData(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(OutputDataReq) + if !a.S(req) { + return + } + log.Debug("OutputDataReq:%+v", req) + if req.Page < 1 { + a.Code = values.CodeParam + return + } + resp := &OutputDataResp{} + for i := common.CurrencyEventZero + 1; i < common.CurrencyEventAll; i++ { + if i == common.CurrencyEventMailDraw || i == common.CurrencyEventBindPhone || i == common.CurrencyEventActivityAppSpin { + continue + } + resp.Titles = append(resp.Titles, common.GetCurrencyTypeName(i)) + } + a.Data = resp + var oneDay int64 = 24 * 60 * 60 + + su, eu := util.GetQueryUnix(req.Start, req.End) + if req.Num > 60 { + a.Code = values.CodeParam + return + } + resp.Count = (eu - su) / oneDay + + start := eu - int64(req.Page-1)*int64(req.Num)*oneDay + end := start - int64(req.Num)*oneDay + if end < su { + end = su + } + count := (start - end) / oneDay + group := new(sync.WaitGroup) + group.Add(int(count)) + resp.AllData = make([]OneOutputData, count) + resp.Total = OneOutputData{ + Data: models.GetOutputData(su, eu, req.Channel, 0), + } + step := 0 + for i := start - oneDay; i >= end; i -= oneDay { + tmp := step + t := i + utils.Go(func() { + resp.AllData[tmp] = OneOutputData{ + Date: time.Unix(t, 0).Format("20060102"), + Time: t, + Data: models.GetOutputData(t, t+oneDay, req.Channel, 0)} + + group.Done() + }) + step++ + } + group.Wait() +} + +// func GetOutputData(su, eu int64, cid int) map[string]string { +// ret := map[string]string{} +// q := elastic.NewBoolQuery() +// q.Filter(elastic.NewRangeQuery("time").Gte(su)) +// q.Filter(elastic.NewRangeQuery("time").Lt(eu)) +// if cid > 0 { +// q.Filter(elastic.NewTermsQuery("channel_id", cid)) +// } +// buk := &common.GroupSumBuckets{} +// db.ES().GroupSumBy(common.ESIndexBalance, "event", q, buk, "", false, 0, "value") +// for _, v := range buk.Buckets { +// ret[common.GetCurrencyTypeName(common.CurrencyEvent(utils.GetInt(v.Key)))] = utils.RoundFloat(v.Value.Value/common.DecimalDigits, 2) +// } +// return ret +// } + +type OutputDataProviderReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Channel int `json:"Channel"` + Page int `json:"Page" binding:"required"` + Num int `json:"Num" binding:"required"` +} + +type OutputDataProviderResp struct { + Count int64 + Titles []string + GameProviderData []OneOutputProviderData + Total OneOutputProviderData +} + +type OneOutputProviderData struct { + Date string + Time int64 + Data []OneProviderData +} + +type OneProviderData struct { + ProviderID int + ProviderName string + Value string +} + +// OutputDataProvider 厂商产出统计 +func OutputDataProvider(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(OutputDataProviderReq) + if !a.S(req) { + return + } + log.Debug("OutputDataProviderReq:%+v", req) + if req.Page < 1 { + a.Code = values.CodeParam + return + } + resp := &OutputDataProviderResp{} + a.Data = resp + for i := common.ProviderZero + 1; i < common.ProviderAll; i++ { + if i > common.ProviderEvolutionGaming && i != common.ProviderPragmaticPlay && i != common.ProviderIBC && i != common.ProviderPlayStar { + continue + } + provider := call.GetConfigGameProvider(i) + if provider == nil { + continue + } + resp.Titles = append(resp.Titles, provider.ProviderName) + } + var oneDay int64 = 24 * 60 * 60 + + su, eu := util.GetQueryUnix(req.Start, req.End) + if req.Num > 60 { + a.Code = values.CodeParam + return + } + resp.Count = (eu - su) / (24 * 60 * 60) + + start := eu - int64(req.Page-1)*int64(req.Num)*oneDay + end := start - int64(req.Num)*oneDay + if end < su { + end = su + } + group := new(sync.WaitGroup) + count := (start - end) / oneDay + group.Add(int(count)) + resp.GameProviderData = make([]OneOutputProviderData, count) + resp.Total = OneOutputProviderData{ + Data: GetOutputProviderData(su, eu, req.Channel), + } + step := 0 + for i := start - oneDay; i >= end; i -= oneDay { + tmp := step + t := i + utils.Go(func() { + resp.GameProviderData[tmp] = OneOutputProviderData{ + Date: time.Unix(t, 0).Format("20060102"), + Time: t, + Data: GetOutputProviderData(t, t+oneDay, req.Channel), + } + group.Done() + }) + step++ + } + group.Wait() +} + +func GetOutputProviderData(su, eu int64, cid int) []OneProviderData { + ret := []OneProviderData{} + q := elastic.NewBoolQuery() + q.Filter(elastic.NewRangeQuery("time").Gte(su)) + q.Filter(elastic.NewRangeQuery("time").Lt(eu)) + if cid > 0 { + q.Filter(elastic.NewTermsQuery("channel_id", cid)) + } + buk := &common.GroupSumBuckets{} + q.Filter(elastic.NewTermsQuery("event", common.GetGameEvents()...)) + db.ES().GroupSumBy(common.ESIndexBalance, "exi1", q, buk, "", false, 0, "value") + for _, v := range buk.Buckets { + provider := call.GetConfigGameProvider(utils.GetInt(v.Key)) + if provider == nil { + continue + } + ret = append(ret, OneProviderData{ + ProviderID: provider.ProviderID, + ProviderName: provider.ProviderName, + Value: utils.RoundFloat(v.Value.Value/common.DecimalDigits, 2), + }) + } + return ret +} + +type OutputDataGamesReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Channel int `json:"Channel"` + ProviderID int `json:"ProviderID"` +} + +type OutputDataGamesResp struct { + GameData map[string]struct { + Count int64 + Value string + } +} + +func OutputDataGames(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(OutputDataGamesReq) + if !a.S(req) { + return + } + log.Debug("OutputDataGamesReq:%+v", req) + resp := &OutputDataGamesResp{ + GameData: make(map[string]struct { + Count int64 + Value string + }), + } + a.Data = resp + su, eu := util.GetQueryUnix(req.Start, req.End) + q := elastic.NewBoolQuery() + q.Filter(elastic.NewRangeQuery("time").Gte(su)) + q.Filter(elastic.NewRangeQuery("time").Lt(eu)) + q.Filter(elastic.NewTermsQuery("event", common.GetGameEvents()...)) + if req.Channel > 0 { + q.Filter(elastic.NewTermQuery("channel_id", req.Channel)) + } + if req.ProviderID > 0 { + q.Filter(elastic.NewTermQuery("exi1", req.ProviderID)) + } + buk := &common.GroupSumBuckets{} + db.ES().GroupSumBy(common.ESIndexBalance, "exi2", q, buk, "", false, 0, "value") + for _, v := range buk.Buckets { + thisGame := call.GetConfigGameListByID(req.ProviderID, utils.GetInt(v.Key)) + if thisGame == nil { + continue + } + resp.GameData[fmt.Sprintf("%s(%d)", thisGame.Name, thisGame.GameID)] = struct { + Count int64 + Value string + }{ + Count: utils.GetInt64(v.Doc_count), + Value: utils.RoundFloat(v.Value.Value/common.DecimalDigits, 2), + } + } +} diff --git a/modules/backend/handler/statistics/platformData.go b/modules/backend/handler/statistics/platformData.go new file mode 100644 index 0000000..d6d122b --- /dev/null +++ b/modules/backend/handler/statistics/platformData.go @@ -0,0 +1,196 @@ +package statistics + +import ( + "fmt" + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/bdb" + "server/modules/backend/models" + utils "server/modules/backend/util" + "server/modules/backend/values" + "server/util" + "sort" + "sync" + "time" + + "github.com/gin-gonic/gin" +) + +// 数据总览 +func PlatformData(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.PlatformDataReq) + if !a.S(req) { + return + } + resp := values.PlatformDataResp{} + cids := []*int{} + if req.Channel != nil { + cids = []*int{req.Channel} + } else if len(a.User.SChannels) > 0 { + for i := range a.User.SChannels { + cids = append(cids, &a.User.SChannels[i]) + } + } + var oneDay int64 = 24 * 60 * 60 + + su, eu := utils.GetQueryUnix(req.Start, req.End) + if eu-su > 60*oneDay { + a.Code = values.CodeParam + return + } + zeroTime := util.GetZeroTime(time.Now()).Unix() + resp.Count = (eu - su) / (24 * 60 * 60) + + list := []*values.PlatformData{} + cid := -1 + if len(cids) == 0 { + cid = 0 + } else if len(cids) == 1 { + cid = *cids[0] + } + if cid >= 0 { + bdb.BackDB.QueryAll(fmt.Sprintf("Time >= %d and Time <= %d and PlatformID = %d", su, eu, cid), "", &values.PlatformData{}, &list) + } + // 协程并发查询 + resp.PlatformData = make([]*values.PlatformData, resp.Count) + step := 0 + group := new(sync.WaitGroup) + group.Add(int(resp.Count)) + for i := su; i < eu; i += oneDay { + tmp := step + t := i + util.Go(func() { + var one *values.PlatformData + if cid < 0 { + one = GetPlatformData(t, t+oneDay, cids...) + } else { + for _, v := range list { + if v.Time == t { + one = v + break + } + } + tag := one == nil + + if tag || !one.Check { + one = GetPlatformData(t, t+oneDay, &cid) + one.Check = true + // 当天数据不缓存 + if t >= zeroTime { + one.Check = false + } + if tag { + bdb.BackDB.Create(one) + } else { + bdb.BackDB.UpdateW(&values.PlatformData{}, one, fmt.Sprintf("Time = %d and PlatformID = %d", t, cid)) + } + } + } + resp.PlatformData[tmp] = one + group.Done() + }) + step++ + } + group.Wait() + sort.Slice(resp.PlatformData, func(i, j int) bool { + return resp.PlatformData[i].Time > resp.PlatformData[j].Time + }) + + left := (req.Page - 1) * req.Num + right := req.Page * req.Num + if right > len(resp.PlatformData) { + right = len(resp.PlatformData) + } + resp.PlatformData = resp.PlatformData[left:right] + a.Data = resp +} + +func GetPlatformData(start, end int64, platform ...*int) *values.PlatformData { + ret := &values.PlatformData{} + if len(platform) == 1 { + ret.PlatformID = *platform[0] + if ret.PlatformID == 0 { + platform = []*int{} + } + } + ret.Time = start + ret.Date = time.Unix(start, 0).Format("20060102") + + // 新增安装数 + ret.DownloadCount = models.GetDownloadCounts(&start, &end, platform...) + // 完成注册数 + ret.NewCount = models.GetNewPlayerCountBySqls(start, end, platform...) + + // 新增注册率 = 新增注册用户/新增安装用户 + // 计算去重注册人数 + sqlNew := fmt.Sprintf("SELECT count(DISTINCT(deviceid)) FROM users WHERE birth >= %v and birth < %v %v", start, end, models.PackChannels(platform...)) + var disNewCount int64 + db.Mysql().C().Raw(sqlNew).Scan(&disNewCount) + ret.NewRegisterPer = utils.GetPer(disNewCount, ret.DownloadCount) + // 新增付费人数 + ret.NewPayCount = models.GetNewPayCountBySqls(start, end, platform...) + // 新增付费率 = 新增付费人数/新增注册人数 + ret.NewPayPer = utils.GetPer(ret.NewPayCount, ret.NewCount) + // 新增付费次数 + ret.NewPayCountAll = models.GetNewPayCounts(start, end, platform...) + // 新户复充率 + ret.NewPayMultiPer = utils.GetPer(models.GetNewMultiPayCountBySqls(start, end, platform...), ret.NewPayCount) + // 新增充值总额 + ret.NewPayAmount = models.GetNewPayAmountBySqls(start, end, common.CurrencyBrazil, platform...) + // 新增arppu + ret.NewPayARPPU = utils.GetPoint(ret.NewPayAmount, ret.NewPayCount*common.DecimalDigits) + // 新增赠送人数 + ret.NewWithdrawCount = models.GetNewWithdrawPlayerNum(start, end, platform...) + // 新增赠送总额 + ret.NewWithdrawTotal = models.GetNewWithdrawAmount(start, end, platform...) + // 新增赠送比例 + ret.NewWithdrawPer = utils.GetPer(ret.NewWithdrawTotal, ret.NewPayAmount) + // 老用户付费人数 + ret.OldPayCount = models.GetOldPayCountBySqls(start, end, platform...) + // 老用户付费次数 + ret.OldPayCountAll = models.GetOldPayCounts(start, end, platform...) + // 老户复充率 + ret.OldPayMultiPer = utils.GetPer(models.GetOldMultiPayCountBySqls(start, end, platform...), ret.OldPayCount) + // 老用户付费金额 + ret.OldPayAmount = models.GetOldPayAmountBySqls(start, end, common.CurrencyBrazil, platform...) + // 老户ARPPU + ret.OldPayARPPU = utils.GetPoint(ret.OldPayAmount, ret.OldPayCount*common.DecimalDigits) + // 老户赠送人数 + ret.OldWithdrawCount = models.GetOldWithdrawplayerNum(start, end, platform...) + // 老户总赠送 + ret.OldWithdrawTotal = models.GetOldWithdrawAmount(start, end, platform...) + // 老户赠送比例 + ret.OldWithdrawPer = utils.GetPer(ret.OldWithdrawTotal, ret.OldPayAmount) + // 总付费人数 + ret.PayCount = ret.NewPayCount + ret.OldPayCount + // 总付费 + ret.RechargeTotal = ret.NewPayAmount + ret.OldPayAmount + // 总ARPPU + ret.PayARPPU = utils.GetPoint(ret.RechargeTotal, ret.PayCount*common.DecimalDigits) + // 总赠送人数 + ret.WithdrawPlayerNum = ret.NewWithdrawCount + ret.OldWithdrawCount + // 总赠送 + ret.WithdrawTotal = ret.NewWithdrawTotal + ret.OldWithdrawTotal + // 总赠送比例 + ret.WithdrawPer = utils.GetPer(ret.WithdrawTotal, ret.RechargeTotal) + // 总投注 + ret.PlatformBet = models.GetGameInOut(start, end, 1, platform...) + // 总返奖 + ret.PlatformSettle = models.GetGameInOut(start, end, 2, platform...) + // 厂商投注 + ret.ProviderBet = ret.PlatformBet + // 厂商返奖 + ret.ProviderSettle = ret.PlatformSettle + // 活跃留存 + ret.ActiveKeep = models.GetKeepPer(1, ret.Date, start, platform...) + // 总付费留存 + ret.RechargeKeep = models.GetKeepPer(2, ret.Date, start, platform...) + // 新增付费留存 + ret.NewRechargeKeep = models.GetKeepPer(3, ret.Date, start, platform...) + return ret +} diff --git a/modules/backend/handler/statistics/playData.go b/modules/backend/handler/statistics/playData.go new file mode 100644 index 0000000..17713a0 --- /dev/null +++ b/modules/backend/handler/statistics/playData.go @@ -0,0 +1,92 @@ +package statistics + +import ( + "server/modules/backend/app" + "server/modules/backend/models" + utils "server/modules/backend/util" + "server/modules/backend/values" + "time" + + "github.com/gin-gonic/gin" +) + +// 用户牌局分析 +func PlayData(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.PlayDataReq) + if !a.S(req) { + return + } + resp := values.PlayDataResp{} + su, eu := utils.GetQueryUnix(req.Start, req.End) + + var newCount, oldCount int64 + + // 新用户数 + newCount = models.GetNewPlayerCountBySql(req.Channel, su, eu) + // 老用户数 + oldCount = models.GetOldPlayerCountBySql(req.Channel, su, eu) + + // 活跃玩牌率 + resp.ActivePlayPer = utils.GetPer(resp.Play, newCount+oldCount) + + // 新用户玩牌率 + resp.NewPlayerPer = utils.GetPer(resp.NewPlay, newCount) + + resp.Map1 = getPlayDataMap1(su, eu, req.Channel, req.Games) + resp.Map2 = getPlayDataMap2(su, eu, req.Channel, req.Games) + resp.Map3 = getPlayDataMap3(su, eu, req.Channel) + + a.Data = resp +} + +func getPlayDataMap1(s, e int64, channel *int, games *string) map[string]values.PlayDataMap1 { + ret := map[string]values.PlayDataMap1{} + // var oneDay int64 = 24 * 60 * 60 + // s := util.GetZeroTime(time.Unix(int64(start), 0)).Unix() + // e := util.GetZeroTime(time.Unix(int64(end), 0)).AddDate(0, 0, 1).Unix() + + return ret +} + +func getPlayDataMap2(s, e int64, channel *int, games *string) map[string]values.PlayDataMap2 { + ret := map[string]values.PlayDataMap2{} + var oneDay int64 = 24 * 60 * 60 + // s := util.GetZeroTime(time.Unix(int64(start), 0)).Unix() + // e := util.GetZeroTime(time.Unix(int64(end), 0)).AddDate(0, 0, 1).Unix() + // IsNew := true + // flag := false + // var gameId *int + // if games != nil { + // gId := common.GetGameIDByGames(*games) + // gameId = &gId + // } + + for i := s; i < e; i += oneDay { + // j := i + oneDay + t := time.Unix(i, 0).Format("20060102") + one := values.PlayDataMap2{} + // active := models.GetActive(&i, &j, false, false, channel) + + // play := models.GetPlayGameUserCount(&i, &j, gameId, nil, channel, &flag) + + // one.ActivePlayPer = utils.GetPer(play, active) + // newUser := models.GetNewPlayerCountBySql(channel, i, j) + // newPlay := models.GetPlayGameUserCount(&i, &j, gameId, nil, channel, &IsNew) + // one.NewPlayPer = utils.GetPer(newPlay, newUser) + ret[t] = one + } + return ret +} + +func getPlayDataMap3(s, e int64, channel *int) map[int]int64 { + ret := map[int]int64{} + // for _, v := range common.Games { + // gameId := common.GetGameIDByGames(v) + // ret[common.GetGameIDByGames(v)] = models.GetPlayGameUserCount(&s, &e, &gameId, nil, channel, nil) + // } + return ret +} diff --git a/modules/backend/handler/statistics/playGameData.go b/modules/backend/handler/statistics/playGameData.go new file mode 100644 index 0000000..1a76678 --- /dev/null +++ b/modules/backend/handler/statistics/playGameData.go @@ -0,0 +1,51 @@ +package statistics + +import ( + "server/modules/backend/app" + "server/modules/backend/models" + utils "server/modules/backend/util" + "server/modules/backend/values" + + "github.com/gin-gonic/gin" +) + +// 用户牌局分析 +func PlayGameData(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.PlayDataReq) + if !a.S(req) { + return + } + resp := values.PlayGameDataResp{} + su, eu := utils.GetQueryUnix(req.Start, req.End) + + var oneDay int64 = 24 * 60 * 60 + for i := su; i < eu; i += oneDay { + resp.List = append(resp.List, getPlayData(i, i+oneDay, req.Channel)) + } + + a.Data = resp +} + +func getPlayData(s, e int64, channel *int) values.PlayDataInfo { + ret := values.PlayDataInfo{} + + // 日期 + ret.Date = s + + ret.PlayerGameData = getPlayerGameData(s, e, channel, nil) + + return ret +} + +func getPlayerGameData(s, e int64, channel *int, gameId *int) values.PlayerGameData { + var res values.PlayerGameData + + res.PlayerCount = models.GetPlayerCount(&s, &e, channel, gameId, nil) + res.GameCount = models.GetGameTotal(&s, &e, channel, gameId, nil, nil) + + return res +} diff --git a/modules/backend/handler/statistics/playGameDataMillionGame.go b/modules/backend/handler/statistics/playGameDataMillionGame.go new file mode 100644 index 0000000..cb40ff0 --- /dev/null +++ b/modules/backend/handler/statistics/playGameDataMillionGame.go @@ -0,0 +1,96 @@ +package statistics + +import ( + "server/modules/backend/app" + "server/modules/backend/models" + utils "server/modules/backend/util" + "server/modules/backend/values" + + "github.com/gin-gonic/gin" +) + +func PlayGameDataMillionGame(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.PlayGameDataReq) + if !a.S(req) { + return + } + + resp := values.PlayMillionGameDataResp{} + + su, eu := utils.GetQueryUnix(req.Start, req.End) + + var oneDay int64 = 24 * 60 * 60 + for i := su; i < eu; i += oneDay { + s := i + e := i + oneDay + resp.List = append(resp.List, getMillionGameStatisticsInfo(&s, &e, req.Channel, &req.GameId)) + } + + a.Data = resp +} + +// 获取百人场统计数据 +func getMillionGameStatisticsInfo(s, e *int64, channel *int, gameId *int) values.MillionStatisticsInfo { + var millionStatistics values.MillionStatisticsInfo + + // 日期 + millionStatistics.Date = *s + + // 总人数 + millionStatistics.PlayerCount = models.GetPlayerCount(s, e, channel, gameId, nil) + + // 总局数 + // GameCount := models.GetMillionGameTotal(s, e, channel, gameId, nil, nil) + // millionStatistics.GameCount = GameCount + + // 总利润 + // millionStatistics.Profit = models.GetMillionGameProfit(s, e, channel, nil, gameId, nil) + + millionGameData := make(map[string]map[string]interface{}) + // for i := 0; i < len(common.RoomIDs); i++ { + // gameData := make(map[string]interface{}) + // for j := 0; j < len(common.MillionGameResult); j++ { + // result := strconv.Itoa(common.MillionGameResult[j]) + // gameData[result] = getMillionStatisticsData(s, e, channel, gameId, &common.RoomIDs[i], &result, &GameCount) + // } + // millionGameData[strconv.Itoa(common.RoomIDs[i])] = gameData + // } + + millionStatistics.MillionGameData = millionGameData + + return millionStatistics +} + +// func getMillionStatisticsData(s, e *int64, channel, gameId, roomID *int, result *string, gameCount *int64) interface{} { + +// if result != nil && gameId != nil { +// if *result == "1" && *gameId == common.MillionGameIDs[2].(int) { +// data := make(map[string]int64) +// for i := 0; i < 8; i++ { +// if i == 0 { +// field := "TigerBet" +// data[strconv.Itoa(i)] = models.GetMillionBetCount(s, e, nil, channel, gameId, roomID, &field) +// continue +// } +// field := fmt.Sprintf("Bet%d", i+2) +// data[strconv.Itoa(i)] = models.GetMillionBetCount(s, e, nil, channel, gameId, roomID, &field) +// } +// return data +// } else { +// var res values.MillionGameStatisticsData + +// var start int64 = 0 +// gameTotal := models.GetMillionGameTotal(s, e, channel, gameId, roomID, result) +// res.GameCount = gameTotal +// res.WinPer = utils.GetPer(models.GetMillionGameProfitCount(s, e, channel, roomID, gameId, &start, result), gameTotal) +// res.GameCountPer = utils.GetPer(gameTotal, *gameCount) +// res.Balance = models.GetMillionGameProfit(s, e, channel, roomID, gameId, result) +// return res +// } +// } +// return nil +// } diff --git a/modules/backend/handler/statistics/playGameDataRoomGame.go b/modules/backend/handler/statistics/playGameDataRoomGame.go new file mode 100644 index 0000000..670ddbe --- /dev/null +++ b/modules/backend/handler/statistics/playGameDataRoomGame.go @@ -0,0 +1,69 @@ +package statistics + +import ( + "server/modules/backend/app" + "server/modules/backend/models" + utils "server/modules/backend/util" + "server/modules/backend/values" + + "github.com/gin-gonic/gin" +) + +func PlayGameDataRoomGame(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.PlayGameDataReq) + if !a.S(req) { + return + } + + resp := values.PlayRoomGameDataResp{} + + su, eu := utils.GetQueryUnix(req.Start, req.End) + + var oneDay int64 = 24 * 60 * 60 + for i := su; i < eu; i += oneDay { + s := i + e := i + oneDay + resp.List = append(resp.List, getRoomGameStatisticsInfo(&s, &e, req.Channel, &req.GameId)) + } + + a.Data = resp +} + +func getRoomGameStatisticsInfo(s, e *int64, channel *int, gameId *int) values.RoomStatisticsInfo { + var res values.RoomStatisticsInfo + + res.Date = *s + + res.GameCount = models.GetGameTotal(s, e, channel, gameId, nil, nil) + res.PlayerCount = models.GetPlayerCount(s, e, channel, gameId, nil) + + roomGameData := make(map[string]values.GameStatisticsData) + // for i := 0; i < len(common.RoomIDs); i++ { + // roomGameData[strconv.Itoa(common.RoomIDs[i])] = getStatisticsData(s, e, channel, gameId, &common.RoomIDs[i]) + // } + res.RoomGameData = roomGameData + + return res +} + +func getStatisticsData(s, e *int64, channel *int, gameId *int, roomID *int) values.GameStatisticsData { + var GameStatisticsData values.GameStatisticsData + + GameStatisticsData.GameCount = models.GetGameTotal(s, e, channel, gameId, roomID, nil) + GameStatisticsData.PlayerCount = models.GetPlayerCount(s, e, channel, gameId, roomID) + + // isBreak := true + // Break := models.GetRoomGameCount(s, e, channel, gameId, roomID, &isBreak, nil, "UID") + + // isWin := true + // Win := models.GetRoomGameCount(s, e, channel, gameId, roomID, nil, &isWin, "UUID.keyword") + + // GameStatisticsData.Break = utils.GetPer(Break, GameStatisticsData.PlayerCount) + // GameStatisticsData.WinPer = utils.GetPer(Win, GameStatisticsData.GameCount) + + return GameStatisticsData +} diff --git a/modules/backend/handler/statistics/playGameRoomData.go b/modules/backend/handler/statistics/playGameRoomData.go new file mode 100644 index 0000000..a2accf2 --- /dev/null +++ b/modules/backend/handler/statistics/playGameRoomData.go @@ -0,0 +1,43 @@ +package statistics + +import ( + "server/common" + "server/modules/backend/app" + "server/modules/backend/models" + utils "server/modules/backend/util" + "server/modules/backend/values" + + "github.com/gin-gonic/gin" +) + +func PlayGameRoomData(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.PlayGameRoomDataReq) + if !a.S(req) { + return + } + + resp := values.PlayGameRoomDataResp{} + + a.Data = resp +} + +func getPlayGameRoomDataInfo(s, e *int64, channel *int, gameId *int, roomId *int, controlType int) values.PlayGameRoomDataInfo { + + var playGameRoomDataInfo values.PlayGameRoomDataInfo + + playGameRoomDataInfo.Date = *s + playGameRoomDataInfo.GameId = *gameId + playGameRoomDataInfo.ControlType = controlType + + event := int(common.CurrencyEventGameSettle) + playGameRoomDataInfo.Amount = models.GetProfit(s, e, channel, gameId, roomId, &event, &controlType) + playGameRoomDataInfo.GameCount = models.GetControlTotal(s, e, channel, gameId, roomId, &controlType, false) + winCount := models.GetControlTotal(s, e, channel, gameId, roomId, &controlType, true) + playGameRoomDataInfo.WinPer = utils.GetPer(winCount, playGameRoomDataInfo.GameCount) + + return playGameRoomDataInfo +} diff --git a/modules/backend/handler/statistics/realData.go b/modules/backend/handler/statistics/realData.go new file mode 100644 index 0000000..4ccd025 --- /dev/null +++ b/modules/backend/handler/statistics/realData.go @@ -0,0 +1,81 @@ +package statistics + +import ( + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/models" + utils "server/modules/backend/util" + "server/modules/backend/values" + + "github.com/gin-gonic/gin" + "github.com/olivere/elastic/v7" +) + +// 实时数据 +func RealData(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.RealDataReq) + if !a.S(req) { + return + } + resp := &values.RealDataResp{} + a.Data = resp + su, eu := utils.GetQueryUnix(req.Start, req.End) + + // 实时激活 + resp.Active = models.GetDownloadCount(&su, &eu, req.Channel) + + // 实时注册 + resp.Regist = models.GetNewPlayerCountBySql(req.Channel, su, eu) + + // 实时在线 + q := models.NewQ(&su, &eu, nil, req.Channel) + q.Filter(elastic.NewTermQuery("GameID", 0)) + one := common.ESNewOnline{} + db.ES().QueryOne(common.ESIndexBackPlayerOnline, q, &one, "Time", false) + resp.Online = one.Total + + // 发起充值订单数 + resp.TotalRechargeOrder = models.GetOrderCount(su, eu, common.StatusROrderCreate, 1, req.Channel) + resp.FinishRechargeOrder = models.GetOrderCount(su, eu, common.StatusROrderPay, 1, req.Channel) + resp.RechargeSuccessPer = utils.GetPer(resp.FinishRechargeOrder, resp.TotalRechargeOrder) + + // 赠送订单 + resp.TotalWithdrawOrder = models.GetOrderCount(su, eu, common.StatusROrderCreate, 2, req.Channel) + resp.FinishWithdrawOrder = models.GetOrderCount(su, eu, common.StatusROrderFinish, 2, req.Channel) + resp.WithdrawSuccessPer = utils.GetPer(resp.FinishWithdrawOrder, resp.TotalWithdrawOrder) + + // 充值统计 + resp.TotalRechargeData.RechargePlayerNum = models.GetOrderSuccessPlayerCountBySql(req.Channel, su, eu) + resp.TotalRechargeData.TotalRecharge = models.GetAmountTotalBySQL(su, eu, req.Channel) + resp.TotalRechargeData.Arppu = utils.GetPoint(resp.TotalRechargeData.TotalRecharge, resp.TotalRechargeData.RechargePlayerNum) + + resp.NewRechargeData.RechargePlayerNum = models.GetOrderSuccessNewPlayerCountBySql(req.Channel, su, eu) + resp.NewRechargeData.TotalRecharge = models.GetNewPayAmountBySql(req.Channel, su, eu) + resp.NewRechargeData.Arppu = utils.GetPoint(resp.NewRechargeData.TotalRecharge, resp.NewRechargeData.RechargePlayerNum*common.DecimalDigits) + + resp.OldRechargeData.RechargePlayerNum = resp.TotalRechargeData.RechargePlayerNum - resp.NewRechargeData.RechargePlayerNum + resp.OldRechargeData.TotalRecharge = resp.TotalRechargeData.TotalRecharge - resp.NewRechargeData.TotalRecharge + resp.OldRechargeData.Arppu = utils.GetPoint(resp.OldRechargeData.TotalRecharge, resp.OldRechargeData.RechargePlayerNum*common.DecimalDigits) + + // 下注统计 + resp.BetData.TotalBet = models.GetGameInOut(su, eu, 1, req.Channel) + resp.BetData.RealBet = resp.BetData.TotalBet + resp.BetData.RealSettle = models.GetGameInOut(su, eu, 2, req.Channel) + resp.BetData.RealProfit = resp.BetData.RealSettle - resp.BetData.RealBet + resp.BetData.BonusBet = resp.BetData.TotalBet + resp.BetData.BonusSettle = resp.BetData.RealSettle + resp.BetData.BonusProfit = resp.BetData.RealProfit + + // 前十游戏统计 + resp.BetGameData = models.GetTopGames(su, eu, 1, 10, req.Channel) + resp.WinGameData = models.GetTopGames(su, eu, 2, 10, req.Channel) + resp.LoseGameData = models.GetTopGames(su, eu, 3, 10, req.Channel) + + // 活动统计 + resp.ActivityData = models.GetActivityData(su, eu, req.Channel) +} diff --git a/modules/backend/handler/statistics/realDataBalance.go b/modules/backend/handler/statistics/realDataBalance.go new file mode 100644 index 0000000..864682a --- /dev/null +++ b/modules/backend/handler/statistics/realDataBalance.go @@ -0,0 +1,110 @@ +package statistics + +import ( + "fmt" + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/models" + utils "server/modules/backend/util" + "server/modules/backend/values" + "server/util" + "time" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +// 实时数据盈亏 +func RealDataBalance(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.RealDataBalanceReq) + if !a.S(req) { + return + } + resp := values.RealDataBalanceResp{} + + su, eu := utils.GetQueryUnix(req.Start, req.End) + var oneDay int64 = 24 * 60 * 60 + + sql := "SELECT COUNT(*) as NewCount from users u WHERE %d <= u.birth and u.birth < %d " + + // 在线人数 + resp.Online = models.GetOnline() + + currencyEventGameSettle := int(common.CurrencyEventGameSettle) + for i := su; i < eu; i += oneDay { + s := i + e := i + oneDay + var realDataBalance values.RealDataBalance + // 日期 + realDataBalance.Date = s + + // 实时在线人数 + if util.GetZeroTime(time.Now()).Unix() == s { + realDataBalance.Online = models.GetOnlineTotal().Total + } else { + realDataBalance.Online = 0 + } + + // 新用户数 + err := db.Mysql().QueryBySql(fmt.Sprintf(sql, i, i+oneDay), &realDataBalance.NewCount) + if err != nil { + log.Error("获取新用户数量失败, error : [%s]", err.Error()) + } + + // 利润 (所有场次AI盈亏+百人场系统盈亏) + realDataBalance.Profit += models.GetProfit(&s, &e, req.Channel, nil, nil, ¤cyEventGameSettle, nil) + + // 台费 + // realDataBalance.TableFee = models.GetTableFee(&s, &e, req.Channel, nil, nil) + models.GetMillionFee(&s, &e, req.Channel, nil, nil) + + // roomGame + roomGameData := make(map[string]values.RoomGameBalanceData) + realDataBalance.RoomGame = roomGameData + + // millionGame + millionGameData := make(map[string]values.MillionGameBalanceData) + realDataBalance.MillionGame = millionGameData + + resp.List = append(resp.List, realDataBalance) + } + a.Data = resp +} + +func getRoomGameBalanceData(start, end *int64, channel *int, gameId *int) values.RoomGameBalanceData { + var roomGameBalanceData values.RoomGameBalanceData + + event := int(common.CurrencyEventGameSettle) + + // 台费 + // roomGameBalanceData.TableFee = models.GetTableFee(start, end, channel, gameId, nil) + + // 下注额 + roomGameBalanceData.BetTotal = models.GetBet(start, end, channel, gameId, nil, nil) + + // 利润 + roomGameBalanceData.Profit = models.GetProfit(start, end, channel, gameId, nil, &event, nil) + + // 活跃人数 + // roomGameBalanceData.Active = models.GetRoomGamePlayerCount(start, end, channel, gameId, nil) + return roomGameBalanceData +} + +func getMillionGameBalanceData(start, end *int64, channel *int, gameId *int) values.MillionGameBalanceData { + var millionGameBalanceData values.MillionGameBalanceData + + event := int(common.CurrencyEventGameSettle) + // 利润 + millionGameBalanceData.Profit = models.GetProfit(start, end, channel, gameId, nil, &event, nil) + + // 台费 + // millionGameBalanceData.Profit = models.GetMillionFee(start, end, channel, gameId, nil) + + // 活跃人数 + // millionGameBalanceData.Active = models.GetMillionPlayerCount(start, end, channel, gameId, nil) + return millionGameBalanceData +} diff --git a/modules/backend/handler/statistics/realDataBalanceMillionGame.go b/modules/backend/handler/statistics/realDataBalanceMillionGame.go new file mode 100644 index 0000000..97d0f8d --- /dev/null +++ b/modules/backend/handler/statistics/realDataBalanceMillionGame.go @@ -0,0 +1,90 @@ +package statistics + +import ( + "fmt" + "server/common" + "server/modules/backend/app" + "server/modules/backend/models" + "server/modules/backend/values" + + "github.com/gin-gonic/gin" +) + +func RealDataBalanceMillionGame(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + + req := new(values.RealBalanceDataMillionGameReq) + if !a.S(req) { + return + } + + resp := values.RealBalanceDataMillionGameResp{} + + // su, eu := utils.GetQueryUnix(req.Start, req.End) + + // for i := 0; i < len(common.MillionGameIDs); i++ { + // MillionGameID := common.MillionGameIDs[i].(int) + // resp.List = append(resp.List, getBalanceDataMillionGame(&su, &eu, req.Channel, &MillionGameID)) + // } + + a.Data = resp +} + +func getBalanceDataMillionGame(su, eu *int64, channel, gameId *int) values.BalanceDataMillionGame { + var balanceDataMillionGame values.BalanceDataMillionGame + currencyEventGameSettle := int(common.CurrencyEventGameSettle) + balanceDataMillionGame.GameId = *gameId + + OnlineData := models.GetOnlineByGameId(*gameId) + for _, value := range OnlineData { + balanceDataMillionGame.Online += value.Total + balanceDataMillionGame.OnlineNew += value.New + } + + // balanceDataMillionGame.Active = models.GetMillionPlayerCount(su, eu, channel, gameId, nil) + // balanceDataMillionGame.TableFee = models.GetMillionFee(su, eu, channel, gameId, nil) + balanceDataMillionGame.Profit = models.GetProfit(su, eu, channel, gameId, nil, ¤cyEventGameSettle, nil) + + /*for i := 0; i < len(common.RoomIDs); i++ { + // 百人游戏只有两个房间 + if i > 1 { + continue + } + balanceDataMillionGame.RoomDetail = append(balanceDataMillionGame.RoomDetail, getBalanceDataMillionGameRoom(su, eu, channel, gameId, &common.RoomIDs[i])) + }*/ + // balanceDataMillionGame.RoomDetail = append(balanceDataMillionGame.RoomDetail, getBalanceDataMillionGameRoom(su, eu, channel, gameId, &common.RoomIDs[0])) + + return balanceDataMillionGame +} + +func getBalanceDataMillionGameRoom(su, eu *int64, channel, gameId, roomId *int) values.BalanceDataMillionGameRoom { + var millionGameRoom values.BalanceDataMillionGameRoom + currencyEventGameSettle := int(common.CurrencyEventGameSettle) + + millionGameRoom.RoomId = *roomId + // millionGameRoom.GameCount = models.GetMillionGameCount(su, eu, channel, gameId, roomId) + // millionGameRoom.BetAmount = models.GetMillionGameBet(su, eu, channel, gameId, roomId, nil) + millionGameRoom.Profit = models.GetProfit(su, eu, channel, gameId, roomId, ¤cyEventGameSettle, nil) + // millionGameRoom.Active = models.GetMillionPlayerCount(su, eu, channel, gameId, roomId) + + // res := common.GetMillionResultByGameId(*gameId) + // for i := 0; i < len(res); i++ { + // millionGameRoom.Detail = append(millionGameRoom.Detail, getMillionGameDetail(su, eu, channel, gameId, roomId, &res[i])) + // } + return millionGameRoom +} + +func getMillionGameDetail(su, eu *int64, channel, gameId, roomId, result *int) values.MillionGameDetail { + var millionGameDetail values.MillionGameDetail + + millionGameDetail.Result = fmt.Sprintf("%d", *result) + // millionGameDetail.BetAmount = models.GetMillionGameBet(su, eu, channel, gameId, roomId, result) + // millionGameDetail.BetCount = models.GetMillionGameBetCount(su, eu, channel, gameId, roomId, result) + // millionGameDetail.WinCount = models.GetMillionGameWinCount(su, eu, channel, gameId, roomId, result) + // millionGameDetail.Profit = models.GetMillionGameAreaProfit(su, eu, channel, gameId, roomId, result) + + return millionGameDetail +} diff --git a/modules/backend/handler/statistics/realDataBalanceRoomGame.go b/modules/backend/handler/statistics/realDataBalanceRoomGame.go new file mode 100644 index 0000000..9c06e9a --- /dev/null +++ b/modules/backend/handler/statistics/realDataBalanceRoomGame.go @@ -0,0 +1,265 @@ +package statistics + +import ( + "server/common" + "server/modules/backend/app" + "server/modules/backend/models" + utils "server/modules/backend/util" + "server/modules/backend/values" + + "github.com/gin-gonic/gin" +) + +func RealDataBalanceRoomGame(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.RealBalanceDataRoomGameReq) + if !a.S(req) { + return + } + resp := values.RealBalanceDataRoomGameResp{} + + su, eu := utils.GetQueryUnix(req.Start, req.End) + // var oneDay int64 = 24 * 60 * 60 + + // res := make(map[string]values.RoomGameData) + res := GetRoomGameData2(&su, &eu, req.Channel) + // for i := su; i < eu; i += oneDay { + // s := i + // e := i + oneDay + // for j := 0; j < len(common.RoomGameIDs); j++ { + // res[strconv.Itoa(common.RoomGameIDs[j])] = GetRoomGameData(&s, &e, req.Channel, &common.RoomGameIDs[j]) + // } + // } + resp.List = res + a.Data = resp +} + +func GetRoomGameData2(start, end *int64, channel *int) map[string]values.RoomGameData { + res := make(map[string]values.RoomGameData) + // for _, v := range common.RoomGameIDs { + // one := values.RoomGameData{Date: *start, GameId: v} + // OnlineData := models.GetOnlineByGameId(v) + // for _, value := range OnlineData { + // one.Online += value.Total + // one.OnlineNew += value.New + // } + // roomData := map[string]values.RoomGameRealData{} + // for _, v := range common.RoomIDs { + // roomData[strconv.Itoa(v)] = values.RoomGameRealData{DropPer: "0%"} + // } + // one.RoomData = roomData + // res[strconv.Itoa(v)] = one + // } + + // 聚合查询活跃人数 + // act := models.GetRoomGamePlayerCountGroup(start, end, channel) + // for k, v := range act { + // one := res[k] + // one.Active = v + // res[k] = one + // } + + // 按roomid聚合活跃人数 + // actR := models.GetRoomGamePlayerCountGroup2(start, end, channel) + // for gid, v := range actR { + // oneG := res[gid] + // roomGameRealData := oneG.RoomData + // for rid, val := range v { + // roomGameRealData[rid] = values.RoomGameRealData{Active: val} + // } + // oneG.RoomData = roomGameRealData + // res[gid] = oneG + // } + // 聚合查询下注 + // bet := models.GetBetGroup(start, end, channel, common.RoomGameIDs) + // for gid, v := range bet { + // oneG := res[gid] + // roomGameRealData := oneG.RoomData + // for rid, val := range v { + // oneR := roomGameRealData[rid] + // oneR.Bet = val + // roomGameRealData[rid] = oneR + // oneG.Bet += val + // } + // oneG.RoomData = roomGameRealData + // res[gid] = oneG + // } + + // 聚合查询利润 + // profit := models.GetProfitGroup(start, end, channel, nil, common.RoomGameIDs) + // for gid, v := range profit { + // oneG := res[gid] + // roomGameRealData := oneG.RoomData + // for rid, val := range v { + // oneR := roomGameRealData[rid] + // oneR.Profit = val + // roomGameRealData[rid] = oneR + // oneG.Profit += val + // } + // oneG.RoomData = roomGameRealData + // res[gid] = oneG + // } + + // 聚合查询台费 + // tableFee := models.GetTableFeeGroup(start, end, channel) + // for gid, v := range tableFee { + // oneG := res[gid] + // roomGameRealData := oneG.RoomData + // for rid, val := range v { + // oneR := roomGameRealData[rid] + // oneR.Fee = val + // roomGameRealData[rid] = oneR + // oneG.Fee += val + // } + // oneG.RoomData = roomGameRealData + // res[gid] = oneG + // } + + // 聚合查询弃牌率 + // all := models.GetGameTotalGroup(start, end, channel, false, common.RoomGameIDs) + // total := map[string]int64{} + // for gid, v := range all { + // oneG := res[gid] + // roomGameRealData := oneG.RoomData + // for rid, val := range v { + // oneR := roomGameRealData[rid] + // oneR.GameCount = val + // roomGameRealData[rid] = oneR + // total[gid] += val + // } + // oneG.RoomData = roomGameRealData + // res[gid] = oneG + // } + // dropTotal := map[string]int64{} + // drop := models.GetGameTotalGroup(start, end, channel, true, common.RoomGameIDs) + // for gid, v := range drop { + // oneG := res[gid] + // roomGameRealData := oneG.RoomData + // for rid, val := range v { + // oneR := roomGameRealData[rid] + // oneR.DropPer = utils.GetPer(val, oneR.GameCount) + // roomGameRealData[rid] = oneR + // dropTotal[gid] += val + // } + // oneG.RoomData = roomGameRealData + // res[gid] = oneG + // } + // for k := range res { + // one := res[k] + // one.DropPer = utils.GetPer(dropTotal[k], total[k]) + // res[k] = one + // } + + // 聚合查询控杀局数 + // kills := models.GetControlTotalGroup(start, end, channel, false, common.RoomGameIDs) + // for gid, v := range kills { + // oneG := res[gid] + // roomGameRealData := oneG.RoomData + // for rid, val := range v { + // oneR := roomGameRealData[rid] + // oneR.ControlKillCount = val + // roomGameRealData[rid] = oneR + // } + // oneG.RoomData = roomGameRealData + // res[gid] = oneG + // } + // frees := models.GetControlTotalGroup(start, end, channel, true, common.RoomGameIDs) + // for gid, v := range frees { + // oneG := res[gid] + // roomGameRealData := oneG.RoomData + // for rid, val := range v { + // oneR := roomGameRealData[rid] + // oneR.ControlFreeCount = val + // roomGameRealData[rid] = oneR + // } + // oneG.RoomData = roomGameRealData + // res[gid] = oneG + // } + + return res +} + +func GetRoomGameData(start, end *int64, channel *int, gameId *int) values.RoomGameData { + var roomGameData values.RoomGameData + + // 日期 + roomGameData.Date = *start + + // 游戏id + roomGameData.GameId = *gameId + + //在线人数 + OnlineData := models.GetOnlineByGameId(*gameId) + for _, value := range OnlineData { + roomGameData.Online += value.Total + roomGameData.OnlineNew += value.New + } + + // 活跃人数 + // roomGameData.Active = models.GetRoomGamePlayerCount(start, end, channel, gameId, nil) + + // 下注 + roomGameData.Bet = models.GetBet(start, end, channel, gameId, nil, nil) + + // 利润 + event := int(common.CurrencyEventGameSettle) + roomGameData.Profit = models.GetProfit(start, end, channel, gameId, nil, &event, nil) + + // 台费 + // roomGameData.Fee = models.GetTableFee(start, end, channel, gameId, nil) + + // 放牌率 + var dropValue = 0 + total := models.GetGameTotal(start, end, channel, gameId, nil, nil) + dropTotal := models.GetGameTotal(start, end, channel, gameId, nil, &dropValue) + roomGameData.DropPer = utils.GetPer(dropTotal, total) + + roomGameRealData := make(map[string]values.RoomGameRealData) + // for i := 0; i < len(common.RoomIDs); i++ { + // roomGameRealData[strconv.Itoa(common.RoomIDs[i])] = getRoomGameRealData(start, end, channel, gameId, &common.RoomIDs[i]) + // } + roomGameData.RoomData = roomGameRealData + return roomGameData +} + +func getRoomGameRealData(start, end *int64, channel *int, gameId *int, roomId *int) values.RoomGameRealData { + var roomGameRealData values.RoomGameRealData + + // 活跃人数 + // roomGameRealData.Active = models.GetRoomGamePlayerCount(start, end, channel, gameId, roomId) + + // 下注额 + roomGameRealData.Bet = models.GetBet(start, end, channel, gameId, roomId, nil) + + // 利润 + event := int(common.CurrencyEventGameSettle) + roomGameRealData.Profit = models.GetProfit(start, end, channel, gameId, roomId, &event, nil) + + // roomGameRealData.Fee = models.GetTableFee(start, end, channel, gameId, roomId) + + // 放牌率 + var dropValue = 0 + total := models.GetGameTotal(start, end, channel, gameId, roomId, nil) + dropTotal := models.GetGameTotal(start, end, channel, gameId, roomId, &dropValue) + roomGameRealData.DropPer = utils.GetPer(dropTotal, total) + + // 游戏局数 + roomGameRealData.GameCount = models.GetControlTotal(start, end, channel, gameId, roomId, nil, false) + + // 控杀局数 + // ControlKill := []int{common.ControlTypeNewKill, common.ControlTypePointKill, common.ControlTypeSingleKill, common.ControlTypeRoomKill} + // for i := 0; i < len(ControlKill); i++ { + // roomGameRealData.ControlKillCount += models.GetControlTotal(start, end, channel, gameId, roomId, &ControlKill[i], false) + // } + + // 控放局数 + // ControlFree := []int{common.ControlTypeNewFee, common.ControlTypePointFree, common.ControlTypeSingleFree, common.ControlTypeRoomFree} + // for i := 0; i < len(ControlFree); i++ { + // roomGameRealData.ControlFreeCount += models.GetControlTotal(start, end, channel, gameId, roomId, &ControlFree[i], false) + // } + + return roomGameRealData +} diff --git a/modules/backend/handler/statistics/rechargeChannelData.go b/modules/backend/handler/statistics/rechargeChannelData.go new file mode 100644 index 0000000..dc98b9e --- /dev/null +++ b/modules/backend/handler/statistics/rechargeChannelData.go @@ -0,0 +1,31 @@ +package statistics + +import ( + "github.com/gin-gonic/gin" + "server/modules/backend/app" + utils "server/modules/backend/util" + "server/modules/backend/values" +) + +// 渠道详细充值数据 +func RechargeChannelData(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.RechargeChannelDataReq) + if !a.S(req) { + return + } + resp := values.RechargeChannelDataResp{} + su, eu := utils.GetQueryUnix(req.Start, req.End) + + var oneDay int64 = 24 * 60 * 60 + for i := su; i < eu; i += oneDay { + start := i + end := i + oneDay + resp.List = append([]values.OneRechargeData{GetRechargeData(start, end, &req.ChannelID)}, resp.List...) + } + + a.Data = resp +} diff --git a/modules/backend/handler/statistics/rechargeData.go b/modules/backend/handler/statistics/rechargeData.go new file mode 100644 index 0000000..8cad53f --- /dev/null +++ b/modules/backend/handler/statistics/rechargeData.go @@ -0,0 +1,105 @@ +package statistics + +import ( + "fmt" + "server/call" + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/models" + utils "server/modules/backend/util" + "server/modules/backend/values" + "time" + + "github.com/gin-gonic/gin" +) + +// 付费分析 +func RechargeData(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.RechargeDataReq) + if !a.S(req) { + return + } + + var oneDay int64 = 24 * 60 * 60 + resp := values.RechargeDataResp1{} + su, eu := utils.GetQueryUnix(req.Start, req.End) + + e := eu - int64(((req.Page-1)*req.Num)*24*60*60) + s := e - int64((req.Num-1)*24*60*60) + if s < su { + s = su + } + resp.Count = (eu - su) / (24 * 60 * 60) + + for i := s; i < e; i += oneDay { + j := i + oneDay + resp.List = append([]values.OneRechargeData{GetRechargeData(i, j, req.ChannelID)}, resp.List...) + } + + resp.Total = GetRechargeData(su, eu, req.ChannelID) + + a.Data = resp + +} + +func GetRechargeData(start, end int64, channel *int) values.OneRechargeData { + var res values.OneRechargeData + + res.Date = time.Unix(start, 0).Format("20060102") + + if channel != nil { + res.Channel = *channel + } + + // 新玩家数 + res.NewCount = models.GetNewPlayerCountBySql(channel, start, end) + + // s := time.Unix(start, 0).Format("2006-01-02") + // e := time.Unix(end, 0).Format("2006-01-02") + sqlr := fmt.Sprintf("(event = %v or event = %v) and status = %v and create_time >= %d and create_time < %d", common.CurrencyEventReCharge, common.CurrencyEventGMRecharge, common.StatusROrderPay, start, end) + if channel != nil { + sqlr = fmt.Sprintf("(event = %v or event = %v) and status = %v and create_time >= %d and create_time < %d and channel_id = %v", common.CurrencyEventReCharge, common.CurrencyEventGMRecharge, common.StatusROrderPay, start, end, *channel) + } + // 充值订单数 + res.RechargeCount = db.Mysql().Count(&common.RechargeOrder{}, sqlr) + + // 充值用户数(新用户+老用户) + res.RechargePlayerNum = models.GetPayCount(&start, &end, channel, nil) + + var gitTotal int64 + db.Mysql().C().Model(&common.RechargeOrder{}).Where(sqlr).Count(&gitTotal) + allGift := call.GetConfigPayProduct() + for i := 0; i < len(allGift); i++ { + var count int64 + db.Mysql().C().Model(&common.RechargeOrder{}).Where(sqlr+" AND productid = ?", allGift[i].ProductID).Count(&count) + + var rec values.RechargeStatistics + rec.GiftBag = allGift[i].ProductID + rec.Count = count + rec.RechargePer = utils.GetPer(count, gitTotal) + res.RechargeStatisticsList = append(res.RechargeStatisticsList, rec) + } + + // 充值总额 + res.RechargeTotal = models.GetAmountTotalBySQL(start, end, channel) + + sql := fmt.Sprintf("event = %v and status = %v and create_time >= '%v' and create_time < '%v'", common.CurrencyEventWithDraw, common.StatusROrderFinish, start, end) + if channel != nil { + sql += fmt.Sprintf(" and channel_id = %v", *channel) + } + res.WithdrawTotal = db.Mysql().Sum(&common.RechargeOrder{}, sql, "amount") + res.WRPer = utils.GetPer(res.WithdrawTotal, res.RechargeTotal) + res.Profit = res.RechargeTotal - res.WithdrawTotal + + active := models.GetNewPlayerCountBySql(channel, start, end) + models.GetOldPlayerCountBySql(channel, start, end) + res.ARPU = utils.GetPoint(res.RechargeTotal, active) + res.ARPPU = utils.GetPoint(res.RechargeTotal, res.RechargePlayerNum) + res.RechargePer = utils.GetPer(res.RechargePlayerNum, active) + + return res +} diff --git a/modules/backend/handler/statistics/rechargeFrequency.go b/modules/backend/handler/statistics/rechargeFrequency.go new file mode 100644 index 0000000..34b4a89 --- /dev/null +++ b/modules/backend/handler/statistics/rechargeFrequency.go @@ -0,0 +1,191 @@ +package statistics + +import ( + "fmt" + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/models" + utils "server/modules/backend/util" + "server/modules/backend/values" + "server/util" + "strconv" + "time" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" + "github.com/olivere/elastic/v7" +) + +// rechargeFrequency 充值频率 +func RechargeFrequency(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.RechargeFrequencyReq) + if !a.S(req) { + return + } + + var oneDay int64 = 24 * 60 * 60 + var resp values.RechargeFrequencyResp + su, eu := utils.GetQueryUnix(req.Start, req.End) + + e := eu - int64(((req.Page-1)*req.Num)*24*60*60) + s := e - int64((req.Num-1)*24*60*60) + if s < su { + s = su + } + resp.Count = (eu - su) / (24 * 60 * 60) + + var channelId int + if req.Channel != nil { + channelId = *req.Channel + } + + // 今天的数据直接从缓存读取 + now := util.GetZeroTime(time.Now()).Unix() + if su >= now { + one := values.GetRechargeFrequencyData(su, channelId) + if one == nil { + one = GetRechargeFrequency(su, req.Channel) + } + resp.List = append(resp.List, one) + } else { + one := &values.RechargeFrequency{} + q := elastic.NewBoolQuery() + q.Must(elastic.NewMatchQuery("Channel", channelId)) + q.Must(elastic.NewMatchQuery("Date", su)) + db.ES().QueryOne(common.ESIndexBackRechargeFrequency, q, &one) + // 两种情况需要重新拉取 1没有数据 2订单总数有变动的时候 + if len(one.List) == 0 { + one = GetRechargeFrequency(su, req.Channel) + id := fmt.Sprintf("%v_%v", su, channelId) + db.ES().InsertToESByIDGO(common.ESIndexBackRechargeFrequency, id, one) + } else { + var total int64 + for _, v := range one.List { + total += v.RechargeOrderCount + v.RechargeSuccessOrderCount + } + newTotal := models.GetNewRechargeOrderCountBySql2(req.Channel, su, su+oneDay) + models.GetNewRechargeSuccessOrderCountBySql2(req.Channel, su, su+oneDay) + if newTotal != total { + var hour int64 = 60 * 60 + channel := req.Channel + for i := 0; i < 24; i++ { + detail := one.List[i] + // 充值玩家数量 + detail.RechargePlayerCount = models.GetRechargeSuPlayerCountBySql(channel, su+int64(i)*hour, su+(int64(i)+1)*hour) + var err error + // 超过24小时的老数据不会改变 + if time.Now().Unix()-(su+int64(i)*hour) < 24*hour { + // 玩家24小时内付费金额 + detail.PayAmount24 = models.Get24HourNewPlayerRechargeAmount(channel, su+int64(i)*hour, su+(int64(i)+1)*hour) + // 玩家24小时付费率 + point2 := utils.GetPoint2(models.Get24HourNewPlayerPayCount(channel, su+int64(i)*hour, su+(int64(i)+1)*hour), detail.RegisterPlayerCount) + detail.PayAmount24Per, err = strconv.ParseFloat(point2, 64) + if err != nil { + log.Error(err.Error()) + } + } + + // 玩家总付费金额 + detail.PayAmountTotal = models.GetNewPlayerRechargeAmount(channel, su+int64(i)*hour, su+(int64(i)+1)*hour) + // 玩家总付费率 + point3 := utils.GetPoint2(detail.RechargePlayerCount, detail.RegisterPlayerCount) + detail.PayAmountTotalPer, err = strconv.ParseFloat(point3, 64) + if err != nil { + log.Error(err.Error()) + } + + // 充值成功订单数 + detail.RechargeSuccessOrderCount = models.GetNewRechargeSuccessOrderCountBySql2(channel, su+int64(i)*hour, su+(int64(i)+1)*hour) + // 玩家充值总订单数 + detail.RechargeOrderCount = models.GetNewRechargeOrderCountBySql2(channel, su+int64(i)*hour, su+(int64(i)+1)*hour) + // 支付成功率 + detail.RechargeSuccessPer, err = strconv.ParseFloat(utils.GetPoint2(detail.RechargeSuccessOrderCount, detail.RechargeOrderCount), 5) + if err != nil { + log.Error(err.Error()) + } + one.List[i] = detail + } + id := fmt.Sprintf("%v_%v", su, channelId) + db.ES().DeleteByID(common.ESIndexBackRechargeFrequency, id) + db.ES().InsertToESByIDGO(common.ESIndexBackRechargeFrequency, id, one) + } + } + resp.List = append(resp.List, one) + } + + a.Data = resp +} + +func GetRechargeFrequency(su int64, channel *int) *values.RechargeFrequency { + var recharge values.RechargeFrequency + var hour int64 = 60 * 60 + recharge.Date = su + now := util.GetZeroTime(time.Now()).Unix() + recharge.List = make([]values.RechargeFrequencyDetail, 24) + nowHour := time.Now().Hour() + 1 + recharge.Channel = 0 + if channel != nil { + recharge.Channel = *channel + } + for i := 0; i < 24; i++ { + detail := values.RechargeFrequencyDetail{} + // 时间 + detail.Date = su + (int64(i)+1)*hour - 30*60 + + // 查询今日的 + if now == su && i > nowHour { + break + } + + // 玩家注册数量 + detail.RegisterPlayerCount = models.GetNewPlayerCountBySqlT(channel, su+int64(i)*hour, su+(int64(i)+1)*hour) + + // 充值玩家数量 + detail.RechargePlayerCount = models.GetRechargeSuPlayerCountBySql(channel, su+int64(i)*hour, su+(int64(i)+1)*hour) + + // 当天玩家付费率 + point := utils.GetPoint2(models.GetNewPlayerPayDisCount(channel, su+int64(i)*hour, su+(int64(i)+1)*hour, su+24*hour), detail.RegisterPlayerCount) + res, err := strconv.ParseFloat(point, 64) + if err != nil { + log.Error(err.Error()) + } + // 新用户付费率 + detail.NewPlayerRechargePer = res + + // 玩家24小时内付费金额 + detail.PayAmount24 = models.Get24HourNewPlayerRechargeAmount(channel, su+int64(i)*hour, su+(int64(i)+1)*hour) + // 玩家24小时付费率 + point2 := utils.GetPoint2(models.Get24HourNewPlayerPayCount(channel, su+int64(i)*hour, su+(int64(i)+1)*hour), detail.RegisterPlayerCount) + detail.PayAmount24Per, err = strconv.ParseFloat(point2, 64) + if err != nil { + log.Error(err.Error()) + } + + // 玩家总付费金额 + detail.PayAmountTotal = models.GetNewPlayerRechargeAmount(channel, su+int64(i)*hour, su+(int64(i)+1)*hour) + // 玩家总付费率 + point3 := utils.GetPoint2(detail.RechargePlayerCount, detail.RegisterPlayerCount) + detail.PayAmountTotalPer, err = strconv.ParseFloat(point3, 64) + if err != nil { + log.Error(err.Error()) + } + + // 充值成功订单数 + detail.RechargeSuccessOrderCount = models.GetNewRechargeSuccessOrderCountBySql2(channel, su+int64(i)*hour, su+(int64(i)+1)*hour) + // 玩家充值总订单数 + detail.RechargeOrderCount = models.GetNewRechargeOrderCountBySql2(channel, su+int64(i)*hour, su+(int64(i)+1)*hour) + // 支付成功率 + detail.RechargeSuccessPer, err = strconv.ParseFloat(utils.GetPoint2(detail.RechargeSuccessOrderCount, detail.RechargeOrderCount), 5) + if err != nil { + log.Error(err.Error()) + } + + recharge.List[i] = detail + } + + return &recharge +} diff --git a/modules/backend/handler/statistics/rechargeKeepData.go b/modules/backend/handler/statistics/rechargeKeepData.go new file mode 100644 index 0000000..ded297c --- /dev/null +++ b/modules/backend/handler/statistics/rechargeKeepData.go @@ -0,0 +1,28 @@ +package statistics + +import ( + "github.com/gin-gonic/gin" + "server/modules/backend/app" + utils "server/modules/backend/util" + "server/modules/backend/values" +) + +// RechargeKeepData 获取付费留存数据 +func RechargeKeepData(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.KeepDataReq) + if !a.S(req) { + return + } + if req.Start > req.End { + a.Code = values.CodeParam + return + } + resp := values.KeepDataResp{} + su, eu := utils.GetQueryUnix(req.Start, req.End) + resp.List, _ = getPlayerKeep(su, eu, req.Channel) + a.Data = resp +} diff --git a/modules/backend/handler/statistics/rechargeOrderList.go b/modules/backend/handler/statistics/rechargeOrderList.go new file mode 100644 index 0000000..232b968 --- /dev/null +++ b/modules/backend/handler/statistics/rechargeOrderList.go @@ -0,0 +1,137 @@ +package statistics + +import ( + "fmt" + "server/call" + "server/common" + "server/db" + "server/modules/backend/app" + utils "server/modules/backend/util" + "server/modules/backend/values" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +func RechargeOrderList(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.RechargeOrderListReq) + if !a.S(req) { + return + } + + su, eu := utils.GetQueryUnix(req.Start, req.End) + // st, et := utils.GetQueryDate(req.Start, req.End) + var count, amount int64 + var err error + var ret []common.RechargeOrder + + queryOrder := " SELECT * FROM recharge_order AS re " + queryCount := " SELECT COUNT(*) as count FROM recharge_order AS re " + queryAmount := " SELECT SUM(amount) as amount FROM recharge_order AS re " + + var str string + if req.Status != nil && *req.Status == 2 { + str = fmt.Sprintf(" callback_time >= %d AND callback_time < %d AND event = %d ", su, eu, common.CurrencyEventReCharge) + } else { + str = fmt.Sprintf(" create_time >= %d AND create_time < %d AND event = %d ", su, eu, common.CurrencyEventReCharge) + } + if req.Channel != nil { + str += fmt.Sprintf(" AND channel_id = %d", *req.Channel) + } + if req.Status != nil { + str += fmt.Sprintf(" AND status = %d", *req.Status) + } + + queryOrder += " LEFT JOIN ( SELECT uid, SUM(amount) AS totalAmount FROM recharge_order WHERE " + str + " GROUP BY uid) rm ON rm.uid = re.uid " + + if req.Birth != nil { + birthSu, birthEu := utils.GetQueryUnix(*req.Birth, *req.Birth) + queryOrder += fmt.Sprintf(" LEFT JOIN ( SELECT id, birth FROM users WHERE birth >= %d AND birth < %d ) AS u ON u.id = re.uid WHERE u.id = re.uid AND ", birthSu, birthEu) + queryCount += fmt.Sprintf(" LEFT JOIN ( SELECT id, birth FROM users WHERE birth >= %d AND birth < %d ) AS u ON u.id = re.uid WHERE u.id = re.uid AND ", birthSu, birthEu) + queryAmount += fmt.Sprintf(" LEFT JOIN ( SELECT id, birth FROM users WHERE birth >= %d AND birth < %d ) AS u ON u.id = re.uid WHERE u.id = re.uid AND ", birthSu, birthEu) + } else { + queryOrder += " WHERE re.uid = rm.uid AND " + queryCount += " WHERE " + queryAmount += " WHERE " + } + + var desc string + switch req.Sort { + case 1: + desc += " ORDER BY re.create_time DESC" + case 2: + desc += " ORDER BY rm.totalAmount DESC" + case 3: + desc += " ORDER BY re.amount DESC" + default: + desc += " ORDER BY create_time DESC" + } + + // 查询总数量 + err = db.Mysql().QueryBySql(queryCount+str, &count) + if err != nil { + log.Error("err:%v", err) + } + + // 查询总金额 + err = db.Mysql().QueryBySql(queryAmount+str, &amount) + if err != nil { + log.Error("err:%v", err) + } + + limit := fmt.Sprintf(" LIMIT %d, %d ", (req.Page-1)*req.Num, req.Num) + + // 查询订单 + err = db.Mysql().QueryBySql(queryOrder+str+desc+limit, &ret) + if err != nil { + log.Error("err:%v", err) + } + + resp := values.RechargeOrderListResp{Count: count, Amount: amount} + for _, v := range ret { + user, _ := call.GetUserInfo(v.UID) + // TotalAmount + /*var totalAmount int64 + err = db.Mysql().QueryBySql(fmt.Sprintf(" SELECT SUM(amount) AS totalAmount FROM recharge_order WHERE uid = %d AND ", v.UID)+str, &totalAmount) + if err != nil { + log.Error("err:%v", err) + }*/ + + resp.List = append(resp.List, values.OneRechargeOrderList{ + Channel: v.ChannelID, + Nick: user.Nick, + UID: v.UID, + OrderID: v.APIPayID, + CreatedTime: v.CreateTime, + CallbackTime: v.CallbackTime, + Amount: v.Amount, + Status: int(v.Status), + Birth: user.Birth, + // OrderCount: db.Mysql().Count(&common.RechargeOrder{}, fmt.Sprintf(" uid = %d AND event = %d ", v.UID, common.CurrencyEventReCharge)), + // OrderSuccessCount: db.Mysql().Count(&common.RechargeOrder{}, fmt.Sprintf(" uid = %d AND event = %d AND status = %d", v.UID, common.CurrencyEventReCharge, common.StatusROrderPay)), + // TotalAmount: totalAmount, + ProductId: v.ProductID, + ActivityId: getActivityId(v.ProductID), // 活动id + PayChannel: v.PayChannel, // 支付渠道 + PaySource: v.PaySource, // 支付来源 + }) + } + + a.Data = resp +} + +func getActivityId(productId int) int { + all := call.GetConfigPayProduct() + for i := 0; i < len(all); i++ { + if all[i] != nil { + if all[i].ProductID == productId { + return all[i].ActivityID + } + } + } + return 0 +} diff --git a/modules/backend/handler/statistics/rechargeTrend.go b/modules/backend/handler/statistics/rechargeTrend.go new file mode 100644 index 0000000..ab8cf0f --- /dev/null +++ b/modules/backend/handler/statistics/rechargeTrend.go @@ -0,0 +1,83 @@ +package statistics + +import ( + "server/modules/backend/app" + "server/modules/backend/models" + utils "server/modules/backend/util" + "server/modules/backend/values" + + "github.com/gin-gonic/gin" +) + +func RechargeTrend(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.RechargeTrendReq) + if !a.S(req) { + return + } + + var resp values.RechargeTrendResp + + var oneDay int64 = 24 * 60 * 60 + su, eu := utils.GetQueryUnix(req.Start, req.End) + + for i := su; i < eu; i += oneDay { + s := i + e := i + oneDay + resp.List = append(resp.List, getRechargeTrendInfo(&s, &e, req.Channel)) + } + + a.Data = resp +} + +func getRechargeTrendInfo(s, e *int64, channel *int) values.RechargeTrendInfo { + var rechargeTrendInfo values.RechargeTrendInfo + + // 日期 + rechargeTrendInfo.Date = *s + + // 总充值订单数:当天所有用户创建充值订单数 + rechargeTrendInfo.RechargeOrderCount = models.GetCreateOrderCountBySql(channel, *s, *e) + + // 成功充值订单数(全):成功支付(√) 按回调统计 + rechargeTrendInfo.RechargeOrderSuccessCount = models.GetSuccessRechargeOrderCountBySql(channel, *s, *e) + + // 新用户充值总订单数 + rechargeTrendInfo.NewRechargeOrderCount = models.GetNewRechargeOrderCountBySql(channel, *s, *e) + + // 新用户充值订单数:当天新用户成功充值的订单(按回调统计) + rechargeTrendInfo.NewRechargeSuccessOrderCount = models.GetNewRechargeSuccessOrderCountBySql(channel, *s, *e) + + // 新用户充值成功率 + rechargeTrendInfo.NewRechargeSuccessPer = utils.GetPer(rechargeTrendInfo.NewRechargeSuccessOrderCount, rechargeTrendInfo.NewRechargeOrderCount) + + // 充值订单人数(全):创建订单总人数 + rechargeTrendInfo.CreateRechargeOrderPlayerCount = models.GetCreateOrderPlayerCountBySql(channel, *s, *e) + + // 充值成功人数(全):充值成功的总人数 + rechargeTrendInfo.RechargeSuccessPlayerCount = models.GetOrderSuccessPlayerCountBySql(channel, *s, *e) + + // 新用户人数:充值成功的新用户人数 + rechargeTrendInfo.NewRechargeSuccessPlayerCount = models.GetOrderSuccessNewPlayerCountBySql(channel, *s, *e) + + // 充值总金额 + rechargeTrendInfo.RechargeAmount = models.GetAmountTotalBySQL(*s, *e, channel) + // 充值成功率 + rechargeTrendInfo.RechargeSuccessPer = utils.GetPer(rechargeTrendInfo.RechargeOrderSuccessCount, rechargeTrendInfo.RechargeOrderCount) + + // var channelStr *string + // if channel != nil { + // str := strconv.Itoa(*channel) + // channelStr = &str + // } + // var status = 1 + // rechargeTrendInfo.PayOrderTrigger = models.GetKeyCountByStatus(s, e, channelStr, nil, "PayOrderTrigger", nil) + // rechargeTrendInfo.PayOrderSuccess = models.GetKeyCountByStatus(s, e, channelStr, nil, "PayOrderSuccess", nil) + // rechargeTrendInfo.NewPayOrderTrigger = models.GetKeyCountByStatus(s, e, channelStr, nil, "PayOrderTrigger", &status) + // rechargeTrendInfo.NewPayOrderSuccess = models.GetKeyCountByStatus(s, e, channelStr, nil, "PayOrderSuccess", &status) + + return rechargeTrendInfo +} diff --git a/modules/backend/handler/statistics/rechargeTrendMap.go b/modules/backend/handler/statistics/rechargeTrendMap.go new file mode 100644 index 0000000..bef1e96 --- /dev/null +++ b/modules/backend/handler/statistics/rechargeTrendMap.go @@ -0,0 +1,109 @@ +package statistics + +import ( + "fmt" + "server/common" + "server/db" + "server/modules/backend/app" + utils "server/modules/backend/util" + "server/modules/backend/values" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +func RechargeTrendMap(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.RechargeTrendMapReq) + if !a.S(req) { + return + } + + var resp values.RechargeTrendMapResp + + var oneDay int64 = 24 * 60 * 60 + var half int64 = 1 * 60 * 60 + su, eu := utils.GetQueryUnix(req.Start, req.End) + + for i := su; i < eu; i += oneDay { + dayS := i + dayE := i + oneDay + for j := i; j < i+oneDay; j += half { + s := j + e := j + half + resp.List = append(resp.List, getRechargeTrendMapInfo(&s, &e, &dayS, &dayE, req.Channel)) + } + } + + a.Data = resp +} + +func getRechargeTrendMapInfo(s, e, dayS, dayE *int64, channel *int) values.RechargeTrendMapInfo { + var rechargeTrendInfo values.RechargeTrendMapInfo + + // sts := time.Unix(*dayS, 0).Format("2006-01-02 15:04:05") + // ete := time.Unix(*dayE, 0).Format("2006-01-02 15:04:05") + sts := *dayS + ete := *dayE + st := *s + et := *e + + // st := time.Unix(*s, 0).Format("2006-01-02 15:04:05") + // et := time.Unix(*e, 0).Format("2006-01-02 15:04:05") + + // 日期 + rechargeTrendInfo.Date = *s + + // 新用户充值订单数 + newSql1 := ` SELECT COUNT(a.id) as NewRechargeOrderCount FROM users a ` + + ` LEFT JOIN recharge_order b on a.id = b.uid ` + + ` where a.role <> 100 ` + + ` and "%s" <= FROM_UNIXTIME(a.birth) and "%s" > FROM_UNIXTIME(a.birth) ` + + ` and create_time >= %d and create_time < %d` + + ` and (event = %v or event = %v) ` + + // 充值总金额 + amountTotal := `SELECT SUM(amount) as RechargeAmount FROM recharge_order WHERE (event = %v or event = %v) and status = %v and create_time >= %d and create_time < %d ` + + if channel != nil { + // 新用户充值订单数 + err := db.Mysql().QueryBySql(fmt.Sprintf(newSql1+" and channel_id = %d ", sts, ete, st, et, common.CurrencyEventReCharge, common.CurrencyEventGMRecharge, *channel), &rechargeTrendInfo.NewRechargeOrderCount) + if err != nil { + log.Error(err.Error()) + } + + // 成功充值订单数 + sqlStr := fmt.Sprintf(" (event = %v or event = %v) and status = %v and create_time >= %d and create_time < %d and channel_id = %d ", common.CurrencyEventReCharge, common.CurrencyEventGMRecharge, common.StatusROrderPay, *s, *e, channel) + rechargeTrendInfo.RechargeOrderSuccessCount = db.Mysql().Count(&common.RechargeOrder{}, sqlStr) + + // 充值总订单数 + rechargeTrendInfo.RechargeOrderCount = db.Mysql().Count(&common.RechargeOrder{}, fmt.Sprintf(" ( event = %v or event = %v ) and create_time >= %d and create_time < %d and channel_id = %d ", common.CurrencyEventReCharge, common.CurrencyEventGMRecharge, st, et, channel)) + + // 充值总金额 + _ = db.Mysql().QueryBySql(fmt.Sprintf(amountTotal+" and channel_id = %v ", common.CurrencyEventReCharge, common.CurrencyEventGMRecharge, common.StatusROrderPay, st, et, *channel), &rechargeTrendInfo.RechargeAmount) + } else { + // 新用户充值订单数 + err := db.Mysql().QueryBySql(fmt.Sprintf(newSql1, sts, ete, st, et, common.CurrencyEventReCharge, common.CurrencyEventGMRecharge), &rechargeTrendInfo.NewRechargeOrderCount) + if err != nil { + log.Error(err.Error()) + } + + // 成功充值订单数 + sqlStr := fmt.Sprintf(" (event = %v or event = %v) and status = %v and create_time >= %d and create_time < %d", common.CurrencyEventReCharge, common.CurrencyEventGMRecharge, common.StatusROrderPay, st, et) + rechargeTrendInfo.RechargeOrderSuccessCount = db.Mysql().Count(&common.RechargeOrder{}, sqlStr) + + // 充值总订单数 + rechargeTrendInfo.RechargeOrderCount = db.Mysql().Count(&common.RechargeOrder{}, fmt.Sprintf(" ( event = %v or event = %v ) and create_time >= %d and create_time < %d ", common.CurrencyEventReCharge, common.CurrencyEventGMRecharge, st, et)) + + // 充值总金额 + _ = db.Mysql().QueryBySql(fmt.Sprintf(amountTotal, common.CurrencyEventReCharge, common.CurrencyEventGMRecharge, common.StatusROrderPay, st, et), &rechargeTrendInfo.RechargeAmount) + } + + // 充值成功率 + rechargeTrendInfo.RechargeSuccessPer = utils.GetPer(rechargeTrendInfo.RechargeOrderSuccessCount, rechargeTrendInfo.RechargeOrderCount) + + return rechargeTrendInfo +} diff --git a/modules/backend/handler/statistics/reviewAppSummary.go b/modules/backend/handler/statistics/reviewAppSummary.go new file mode 100644 index 0000000..0c8254d --- /dev/null +++ b/modules/backend/handler/statistics/reviewAppSummary.go @@ -0,0 +1,194 @@ +package statistics + +import ( + "server/call" + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/bdb" + utils "server/modules/backend/util" + "server/modules/backend/values" + "server/util" + "sort" + "strconv" + "strings" + "sync" + "time" + + "github.com/gin-gonic/gin" + "github.com/olivere/elastic/v7" +) + +// 应用概要 +func ReviewAppSummary(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.ReviewAppSummaryReq) + if !a.S(req) { + return + } + resp := values.ReviewAppSummaryResp{} + + su, eu := utils.GetQueryUnix(req.Start, req.End) + zero := util.GetZeroTime(time.Now()).Unix() + channelArr := call.GetChannelList() + var tempChannelArr []int + + for k := 0; k < len(channelArr); k++ { + if len(a.User.SChannels) > 0 { + if !util.SliceContain(a.User.SChannels, channelArr[k].ChannelID) { + continue + } + } + if req.IsShow { + if channelArr[k].Show == 2 { + tempChannelArr = append(tempChannelArr, channelArr[k].ChannelID) + } + } else { + if channelArr[k].Show != 2 { + tempChannelArr = append(tempChannelArr, channelArr[k].ChannelID) + } + } + } + + group := new(sync.WaitGroup) + group.Add(len(tempChannelArr)) + resp.PlatformData = make([]*values.ReviewData, len(tempChannelArr)) + if su >= zero { + for m := 0; m < len(tempChannelArr); m++ { + index := m + util.Go(func() { + one := values.GetReviewData(tempChannelArr[index]) + if one == nil { + one = GetReviewPlatformData(su, eu, &tempChannelArr[index]) + } + resp.PlatformData[index] = one + group.Done() + }) + } + } else { + list := []*values.ReviewData{} + q := elastic.NewBoolQuery() + q.Filter(elastic.NewRangeQuery("Time").Gte(su)) + q.Filter(elastic.NewRangeQuery("Time").Lt(eu)) + db.ES().QueryList(common.ESIndexBackAppSummary, 0, 5000, q, &list) + + for m := range tempChannelArr { + index := m + util.Go(func() { + var one *values.ReviewData + for i, v := range list { + if tempChannelArr[index] == v.PlatformID { + one = list[i] + break + } + } + if one == nil { + one = GetReviewPlatformData(su, eu, &tempChannelArr[index]) + one.Time = su + bdb.BackDB.Create(one) + } + resp.PlatformData[index] = one + group.Done() + }) + } + } + group.Wait() + resp.Total = &values.ReviewData{} + // var newRigist, newPlayTime, oldPlayTime, activePlayTime, arppu, withdrawSuccess float64 + var newRigist float64 + if len(tempChannelArr) != 0 { + resp.Total.NewCount = 0 + resp.Total.DownloadCount = 0 + // resp.Total.NewPlayCount = 0 + resp.Total.NewPayCount = 0 + resp.Total.NewPayAmount = 0 + // resp.Total.OldCount = 0 + // resp.Total.OldPayCount = 0 + // resp.Total.OldPayAmount = 0 + // resp.Total.OldPlayCount = 0 + resp.Total.ActiveCount = 0 + // resp.Total.ActivePlayCount = 0 + resp.Total.ActivePayCount = 0 + resp.Total.RechargeTotal = 0 + resp.Total.WithdrawTotal = 0 + // resp.Total.WithdrawCount = 0 + resp.Total.WithdrawPlayerNum = 0 + + for i := 0; i < len(resp.PlatformData); i++ { + resp.Total.NewCount += resp.PlatformData[i].NewCount + resp.Total.DownloadCount += resp.PlatformData[i].DownloadCount + // resp.Total.NewPlayCount += resp.PlatformData[i].NewPlayCount + resp.Total.NewPayCount += resp.PlatformData[i].NewPayCount + resp.Total.NewPayAmount += resp.PlatformData[i].NewPayAmount + // resp.Total.OldCount += resp.PlatformData[i].OldCount + // resp.Total.OldPayCount += resp.PlatformData[i].OldPayCount + // resp.Total.OldPayAmount += resp.PlatformData[i].OldPayAmount + // resp.Total.OldPlayCount += resp.PlatformData[i].OldPlayCount + resp.Total.ActiveCount += resp.PlatformData[i].ActiveCount + // resp.Total.ActivePlayCount += resp.PlatformData[i].ActivePlayCount + resp.Total.ActivePayCount += resp.PlatformData[i].ActivePayCount + resp.Total.RechargeTotal += resp.PlatformData[i].RechargeTotal + resp.Total.WithdrawTotal += resp.PlatformData[i].WithdrawTotal + // resp.Total.WithdrawCount += resp.PlatformData[i].WithdrawCount + resp.Total.WithdrawPlayerNum += resp.PlatformData[i].WithdrawPlayerNum + nr, _ := strconv.ParseFloat(strings.ReplaceAll(resp.PlatformData[i].NewRegisterPer, "%", ""), 64) + newRigist += nr + // np, _ := strconv.ParseFloat(resp.PlatformData[i].NewPlayTime, 64) + // newPlayTime += np + // op, _ := strconv.ParseFloat(resp.PlatformData[i].OldPlayTime, 64) + // oldPlayTime += op + // ap, _ := strconv.ParseFloat(resp.PlatformData[i].ActivePlayTime, 64) + // activePlayTime += ap + // ar, _ := strconv.ParseFloat(resp.PlatformData[i].ARPPU, 64) + // arppu += ar + // ws, _ := strconv.ParseFloat(strings.ReplaceAll(resp.PlatformData[i].WithdrawSuccess, "%", ""), 64) + // withdrawSuccess += ws + } + + resp.Total.NewRegisterPer = utils.GetPer(resp.Total.NewCount, resp.Total.DownloadCount) + resp.Total.NewPayPer = utils.GetPer(resp.Total.NewPayCount, resp.Total.NewCount) + // resp.Total.NewPlayPer = utils.GetPer(resp.Total.NewPlayCount, resp.Total.NewCount) + // resp.Total.NewARPU = utils.GetPoint(resp.Total.NewPayAmount, resp.Total.NewCount) + // resp.Total.OldPayPer = utils.GetPer(resp.Total.OldPayCount, resp.Total.OldCount) + // resp.Total.OldPlayPer = utils.GetPer(resp.Total.OldPlayCount, resp.Total.OldCount) + // resp.Total.ActivePayPer = utils.GetPer(resp.Total.ActivePayCount, resp.Total.ActiveCount) + // resp.Total.ActivePlayPer = utils.GetPer(resp.Total.ActivePlayCount, resp.Total.ActiveCount) + // resp.Total.ActiveARPU = utils.GetPoint(resp.Total.RechargeTotal, resp.Total.ActiveCount) + resp.Total.WithdrawPer = utils.GetPer(resp.Total.WithdrawTotal, resp.Total.RechargeTotal) + + resp.Total.NewRegisterPer = utils.GetPer(int64(newRigist*100), int64(len(tempChannelArr))*10000) + // resp.Total.NewPlayTime = util.FormatFloat(newPlayTime/float64(len(tempChannelArr)), 2) + // resp.Total.OldPlayTime = util.FormatFloat(oldPlayTime/float64(len(tempChannelArr)), 2) + // resp.Total.ActivePlayTime = util.FormatFloat(activePlayTime/float64(len(tempChannelArr)), 2) + // resp.Total.ARPPU = util.FormatFloat(arppu/float64(len(tempChannelArr)), 2) + // resp.Total.WithdrawSuccess = utils.GetPer(int64(withdrawSuccess*100), int64(len(tempChannelArr))*10000) + } + + switch req.Sort { + case 1: + // 注册用户排序 + sort.SliceStable(resp.PlatformData, func(i, j int) bool { + return resp.PlatformData[i].NewCount > resp.PlatformData[j].NewCount + }) + case 2: + // 活跃用户排序 + sort.SliceStable(resp.PlatformData, func(i, j int) bool { + return resp.PlatformData[i].ActiveCount > resp.PlatformData[j].ActiveCount + }) + case 3: + // 充值数排序 + sort.SliceStable(resp.PlatformData, func(i, j int) bool { + return resp.PlatformData[i].RechargeTotal > resp.PlatformData[j].RechargeTotal + }) + default: + // 注册用户排序 + sort.SliceStable(resp.PlatformData, func(i, j int) bool { + return resp.PlatformData[i].NewCount > resp.PlatformData[j].NewCount + }) + } + + a.Data = resp +} diff --git a/modules/backend/handler/statistics/reviewChannelData.go b/modules/backend/handler/statistics/reviewChannelData.go new file mode 100644 index 0000000..3edcca5 --- /dev/null +++ b/modules/backend/handler/statistics/reviewChannelData.go @@ -0,0 +1,25 @@ +package statistics + +import ( + "github.com/gin-gonic/gin" + "server/modules/backend/app" + utils "server/modules/backend/util" + "server/modules/backend/values" +) + +func ReviewChannelData(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.ReviewChannelDataReq) + if !a.S(req) { + return + } + resp := values.ReviewChannelDataResp{} + su, eu := utils.GetQueryUnix(req.Start, req.End) + resp.PlatformData = GetReviewChannelData(su, eu, &req.ChannelID) + resp.Total = GetReviewPlatformData(su, eu, &req.ChannelID) + + a.Data = resp +} diff --git a/modules/backend/handler/statistics/reviewData.go b/modules/backend/handler/statistics/reviewData.go new file mode 100644 index 0000000..71c2c67 --- /dev/null +++ b/modules/backend/handler/statistics/reviewData.go @@ -0,0 +1,493 @@ +package statistics + +import ( + "fmt" + "server/call" + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/bdb" + "server/modules/backend/models" + utils "server/modules/backend/util" + "server/modules/backend/values" + "server/util" + "sort" + "sync" + "time" + + "github.com/gin-gonic/gin" +) + +// 数据总览 +func ReviewData(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.ReviewDataReq) + if !a.S(req) { + return + } + resp := values.ReviewDataResp{} + // if values.CompaireData != nil { + // resp.CompaireData = values.CompaireData + // } else { + // compairCIDs := a.User.SChannels + cids := []*int{} + if req.ChannelID != nil { + cids = []*int{req.ChannelID} + // compairCIDs = []int{*req.ChannelID} + } else if len(a.User.SChannels) > 0 { + for i := range a.User.SChannels { + cids = append(cids, &a.User.SChannels[i]) + } + } + // resp.CompaireData = GetCompaireData(compairCIDs) + // } + total := &values.ReviewData{} + var oneDay int64 = 24 * 60 * 60 + + su, eu := utils.GetQueryUnix(req.Start, req.End) + if eu-su > 60*oneDay { + a.Code = values.CodeParam + return + } + zeroTime := util.GetZeroTime(time.Now()).Unix() + resp.Count = (eu - su) / (24 * 60 * 60) + + list := []*values.ReviewData{} + cid := -1 + if len(cids) == 0 { + cid = 0 + } else if len(cids) == 1 { + cid = *cids[0] + } + if cid >= 0 { + bdb.BackDB.QueryAll(fmt.Sprintf("Time >= %d and Time <= %d and PlatformID = %d", su, eu, cid), "", &values.ReviewData{}, &list) + } + // var newRigist, newPlayTime, oldPlayTime, activePlayTime, arppu, withdrawSuccess float64 + var newRigist float64 + // 协程并发查询 + resp.PlatformData = make([]*values.ReviewData, resp.Count) + step := 0 + group := new(sync.WaitGroup) + group.Add(int(resp.Count)) + for i := su; i < eu; i += oneDay { + tmp := step + t := i + util.Go(func() { + var one *values.ReviewData + if cid < 0 { + one = GetReviewPlatformData(t, t+oneDay, cids...) + } else { + for _, v := range list { + if v.Time == t { + one = v + break + } + } + tag := one == nil + + var channelFee, adFee int64 + var profit, roi, remark string + + if !tag { + channelFee = one.ChannelFee + profit = one.Profit + roi = one.ROI + remark = one.Remark + adFee = one.ADFee + } + if tag || !one.Check { + one = GetReviewPlatformData(t, t+oneDay, &cid) + one.Check = true + // 当天数据不缓存 + if t >= zeroTime { + one.Check = false + } + if tag { + bdb.BackDB.Create(one) + } else { + bdb.BackDB.UpdateW(&values.ReviewData{}, one, fmt.Sprintf("Time = %d and PlatformID = %d", t, cid)) + one.ChannelFee = channelFee + one.Profit = profit + one.ROI = roi + one.Remark = remark + one.ADFee = adFee + } + } + } + resp.PlatformData[tmp] = one + group.Done() + }) + step++ + } + group.Wait() + for _, one := range resp.PlatformData { + total.NewCount += one.NewCount + total.DownloadCount += one.DownloadCount + // total.NewPlayCount += one.NewPlayCount + total.NewPayCount += one.NewPayCount + total.NewPayAmount += one.NewPayAmount + // total.OldCount += one.OldCount + // total.OldPayCount += one.OldPayCount + // total.OldPayAmount += one.OldPayAmount + // total.OldPlayCount += one.OldPlayCount + total.ActiveCount += one.ActiveCount + // total.ActivePlayCount += one.ActivePlayCount + total.ActivePayCount += one.ActivePayCount + total.RechargeTotal += one.RechargeTotal + total.WithdrawTotal += one.WithdrawTotal + // total.WithdrawCount += one.WithdrawCount + total.WithdrawPlayerNum += one.WithdrawPlayerNum + + newRigist = utils.AddPerFloat(newRigist, one.NewRegisterPer) + // np, _ := strconv.ParseFloat(one.NewPlayTime, 64) + // newPlayTime += np + // op, _ := strconv.ParseFloat(one.OldPlayTime, 64) + // oldPlayTime += op + // ap, _ := strconv.ParseFloat(one.ActivePlayTime, 64) + // activePlayTime += ap + // ar, _ := strconv.ParseFloat(one.ARPPU, 64) + // arppu += ar + // withdrawSuccess = utils.AddPerFloat(withdrawSuccess, one.WithdrawSuccess) + } + sort.Slice(resp.PlatformData, func(i, j int) bool { + return resp.PlatformData[i].Time > resp.PlatformData[j].Time + }) + + // var newFlag = true + // var oldFlag = false + // total.NewPlayTime = utils.GetPoint(models.GetPlayTime(&su, &eu, nil, nil, req.ChannelID, &newFlag), total.NewPlayCount) + // total.OldPlayTime = utils.GetPoint(models.GetPlayTime(&su, &eu, nil, nil, req.ChannelID, &oldFlag), total.OldPlayCount) + // total.ActivePlayTime = utils.GetPoint(models.GetPlayTime(&su, &eu, nil, nil, req.ChannelID, nil), total.ActivePlayCount) + + // total.ARPPU = utils.GetPoint(models.GetAmountTotalBySQL(su, eu, req.ChannelID), total.NewPayCount+total.OldPayCount) + + total.NewRegisterPer = utils.GetPer(int64(newRigist*100), resp.Count*10000) + // total.NewPlayTime = util.FormatFloat(newPlayTime/float64(resp.Count), 2) + // total.OldPlayTime = util.FormatFloat(oldPlayTime/float64(resp.Count), 2) + // total.ActivePlayTime = util.FormatFloat(activePlayTime/float64(resp.Count), 2) + // total.ARPPU = util.FormatFloat(arppu/float64(resp.Count), 2) + // total.WithdrawSuccess = utils.GetPer(int64(withdrawSuccess*100), resp.Count*10000) + + total.NewPayPer = utils.GetPer(total.NewPayCount, total.NewCount) + // total.NewPlayPer = utils.GetPer(total.NewPlayCount, total.NewCount) + // total.NewARPU = utils.GetPoint(total.NewPayAmount, total.NewCount) + // total.OldPayPer = utils.GetPer(total.OldPayCount, total.OldCount) + // total.OldPlayPer = utils.GetPer(total.OldPlayCount, total.OldCount) + // total.ActivePayPer = utils.GetPer(total.ActivePayCount, total.ActiveCount) + // total.ActivePlayPer = utils.GetPer(total.ActivePlayCount, total.ActiveCount) + // total.ActiveARPU = utils.GetPoint(total.RechargeTotal, total.ActiveCount) + total.WithdrawPer = utils.GetPer(total.WithdrawTotal, total.RechargeTotal) + + resp.Total = total + left := (req.Page - 1) * req.Num + right := req.Page * req.Num + if right > len(resp.PlatformData) { + right = len(resp.PlatformData) + } + resp.PlatformData = resp.PlatformData[left:right] + a.Data = resp +} + +func GetReviewChannelData(start, end int64, channel *int) []*values.ReviewData { + var ret []*values.ReviewData + var oneDay int64 = 24 * 60 * 60 + // s := util.GetZeroTime(time.Unix(int64(start), 0)).Unix() + // e := util.GetZeroTime(time.Unix(int64(end), 0)).AddDate(0, 0, 1).Unix() + now := time.Now() + for i := end; i > start; i -= oneDay { + j := i - oneDay + if j > now.Unix() { + continue + } + t := time.Unix(j, 0).Format("20060102") + // key := common.GetBackendReviewKey(t, channel) + // one := new(values.ReviewPlatformData) + + one := GetReviewPlatformData(j, i, channel) + one.Date = t + ret = append(ret, one) + + } + return ret +} + +func GetReviewPlatformData(start, end int64, platform ...*int) *values.ReviewData { + ret := &values.ReviewData{} + if len(platform) == 1 { + ret.PlatformID = *platform[0] + if ret.PlatformID == 0 { + platform = []*int{} + } + } + ret.Time = start + ret.Date = time.Unix(start, 0).Format("20060102") + // var newFlag = true + // var oldFlag = false + // s := time.Unix(start, 0).Format("2006-01-02") + // e := time.Unix(end, 0).Format("2006-01-02") + + // 完成注册数 + ret.NewCount = models.GetNewPlayerCountBySqls(start, end, platform...) + + // 新增安装数 + ret.DownloadCount = models.GetDownloadCounts(&start, &end, platform...) + + // 新增玩牌人数 + // ret.NewPlayCount = models.GetPlayGameUserCounts(&start, &end, &newFlag, platform...) + + // 新增注册率 = 新增注册用户/新增安装用户 + // 计算去重注册人数 + sqlNew := fmt.Sprintf("SELECT count(DISTINCT(deviceid)) FROM users WHERE birth >= %v and birth < %v %v", start, end, models.PackChannels(platform...)) + var disNewCount int64 + db.Mysql().C().Raw(sqlNew).Scan(&disNewCount) + // ret.NewRegisterPer = utils.GetPer(disNewCount2-disNewCount, ret.DownloadCount) + ret.NewRegisterPer = utils.GetPer(disNewCount, ret.DownloadCount) + // ret.NewPlayPer = utils.GetPer(ret.NewPlayCount, ret.NewCount) + + // 首先聚合求出新老用户玩牌总时长 + // newPlayTime, oldPlayTime := models.GetPlayTimesGroupByIsNew(&start, &end, platform...) + + // 新增用户平均玩牌时长 + // ret.NewPlayTime = utils.GetPoint(newPlayTime, ret.NewPlayCount) + + newRechargeBrl := models.GetNewPayAmountBySqls(start, end, common.CurrencyBrazil, platform...) + // newRechargeUsdt := models.GetNewPayAmountBySqls(start, end, common.CurrencyUSDT, platform...) + // oldRechargeBrl := models.GetOldPayAmountBySqls(start, end, common.CurrencyBrazil, platform...) + // oldRechargeUsdt := models.GetOldPayAmountBySqls(start, end, common.CurrencyUSDT, platform...) + + // ret.RechargeBrl = newRechargeBrl + oldRechargeBrl + // ret.RechargeUsdt = newRechargeUsdt + oldRechargeUsdt + + // 新增付费人数 + ret.NewPayCount = models.GetNewPayCountBySqls(start, end, platform...) + // 新增付费率 = 新增付费人数/新增注册人数 + ret.NewPayPer = utils.GetPer(ret.NewPayCount, ret.NewCount) + // 首次付费人数 + ret.FirstPayCount = models.GetFirstPayCount(start, end, common.CurrencyBrazil, platform...) + // 首充付费率 + ret.FirstPayPer = utils.GetPer(ret.FirstPayCount, ret.NewCount) + + // 新增付费总额 + ret.NewPayAmount = call.RateBRL(common.CurrencyBrazil, newRechargeBrl) + + // 新增ARPU = 新增付费总额/新增注册人数 + // ret.NewARPU = utils.GetPoint(ret.NewPayAmount/common.DecimalDigits, ret.NewCount) + + // 老用户活跃人数 + // ret.OldCount = models.GetOldPlayerCountBySqls(start, end, platform...) + + // 老用户付费人数 + // ret.OldPayCount = models.GetOldPayCountBySqls(start, end, platform...) + + // 老用户付费总额(预估成美元) + // ret.OldPayAmount = call.RateBRL(common.CurrencyBrazil, oldRechargeBrl) + oldRechargeUsdt + + // 老用户玩牌人数 + // ret.OldPlayCount = models.GetPlayGameUserCounts(&start, &end, &oldFlag, platform...) + + // 老用户付费率 = 老用户付费人数/老用户活跃人数 + // ret.OldPayPer = utils.GetPer(ret.OldPayCount, ret.OldCount) + + // 老用户玩牌率 = 当天老用户玩牌人数/老用户数 + // ret.OldPlayPer = utils.GetPer(ret.OldPlayCount, ret.OldCount) + + // 老用户平均玩牌时长 + // ret.OldPlayTime = utils.GetPoint(oldPlayTime, ret.OldPlayCount) + + // 活跃玩牌数 = 新用户玩牌人数 + 老用户玩牌人数 + // ret.ActivePlayCount = ret.NewPlayCount + ret.OldPlayCount + + // 活跃用户 = 新增用户数 + 老用户数 + ret.ActiveCount = ret.NewCount + models.GetOldPlayerCountBySqls(start, end, platform...) + + // 活跃平均玩牌时长 + // ret.ActivePlayTime = utils.GetPoint(newPlayTime+oldPlayTime, ret.ActivePlayCount) + + // 活跃付费人数 = 新增用户付费人数 + 老用户付费人数 + ret.ActivePayCount = ret.NewPayCount + models.GetOldPayCountBySqls(start, end, platform...) + + // 活跃玩牌率 = 当天活跃玩牌人数/活跃用户数 + // ret.ActivePlayPer = utils.GetPer(ret.ActivePlayCount, ret.ActiveCount) + + // 活跃付费率 = 所有付费人数/活跃人数 + // ret.ActivePayPer = utils.GetPer(ret.ActivePayCount, ret.ActiveCount) + + // 付费总额 = 新用户付费 + 老用户付费 + ret.RechargeTotal = models.GetAmountTotalBySQLs(start, end, platform...) + + // 每付费用户收益= 付费总额/付费用户数 + // ret.ARPPU = utils.GetPoint(ret.RechargeTotal/common.DecimalDigits, ret.NewPayCount+ret.OldPayCount) + + // 付费总额/用户数 + // ret.ActiveARPU = utils.GetPoint(ret.RechargeTotal/common.DecimalDigits, ret.ActiveCount) + + sql := fmt.Sprintf("create_time >= %d and create_time < %d ", start, end) + sql += models.PackChannels(platform...) + + // 获取退出订单总数 + var withdrawAll, totalWithdrawPlayer int64 + db.Mysql().C().Table("withdraw_order").Where(sql).Count(&withdrawAll) + + // 代付总人数 + db.Mysql().C().Table("withdraw_order").Where(sql).Distinct("uid").Count(&totalWithdrawPlayer) + + sql += fmt.Sprintf(" and status = %v", common.StatusROrderFinish) + + // 退出总额 当天所有退出成功总额 + withdrawBrl := models.GetWithdrawAmountTotalBySQLs(start, end, common.CurrencyBrazil, platform...) + // withdrawUsdt := models.GetWithdrawAmountTotalBySQLs(start, end, common.CurrencyUSDT, platform...) + ret.WithdrawTotal = call.RateBRL(common.CurrencyBrazil, withdrawBrl) + // ret.WithdrawBrl = withdrawBrl + // ret.WithdrawUsdt = withdrawUsdt + + // 退出比例 退出总额/充值总额 + ret.WithdrawPer = utils.GetPer(ret.WithdrawTotal, ret.RechargeTotal) + + // 成功笔数 + // db.Mysql().C().Table("withdraw_order").Where(sql).Count(&ret.WithdrawCount) + + // 退出人数 + db.Mysql().C().Table("withdraw_order").Where(sql).Distinct("uid").Count(&ret.WithdrawPlayerNum) + + // 退出成功率 + // ret.WithdrawSuccess = utils.GetPer(ret.WithdrawCount, withdrawAll) + + // 人次成功率 + // ret.WithdrawPlayerSuccess = utils.GetPer(ret.WithdrawPlayerNum, totalWithdrawPlayer) + + // 新用户充值占比 + ret.NewPayPerTotal = utils.GetPer(ret.NewPayAmount, ret.RechargeTotal) + + ret.PayWithdrawDiff = ret.RechargeTotal - ret.WithdrawTotal + + ret.RTP = utils.GetPer(models.GetGameInOut(start, end, 2, platform...), models.GetGameInOut(start, end, 1, platform...)) + + return ret +} + +func GetCompaireData(channels []int) *values.Compaire { + begin := time.Now() + // 查询对比数据 + compair := &values.Compaire{ + Recharge: make(map[common.CurrencyType][]int64), + Withdraw: make(map[common.CurrencyType][]int64), + } + zero := util.GetZeroTime(begin) + yesZero := util.GetZeroTime(begin).AddDate(0, 0, -1) + yesNow := begin.AddDate(0, 0, -1) + + sql := fmt.Sprintf("create_time >= %d and create_time < %d", zero.Unix(), begin.Unix()) + sqly := fmt.Sprintf("create_time >= %d and create_time < %d", yesZero.Unix(), yesNow.Unix()) + sql2 := fmt.Sprintf("callback_time >= %d and callback_time < %d", zero.Unix(), begin.Unix()) + sqly2 := fmt.Sprintf("callback_time >= %d and callback_time < %d", yesZero.Unix(), yesNow.Unix()) + sqlRe := fmt.Sprintf(" and event = %d", common.CurrencyEventReCharge) + sqlRe2 := sqlRe + fmt.Sprintf(" and status = %d", common.StatusROrderPay) + + if len(channels) > 0 { + sql += " and (" + sqly += " and (" + sql2 += " and (" + sqly2 += " and (" + for i, v := range channels { + sql += fmt.Sprintf(" channel_id = %v", v) + sqly += fmt.Sprintf(" channel_id = %v", v) + sql2 += fmt.Sprintf(" channel_id = %v", v) + sqly2 += fmt.Sprintf(" channel_id = %v", v) + if i != len(channels)-1 { + sql += " or" + sqly += " or" + sql2 += " or" + sqly2 += " or" + } else { + sql += ")" + sqly += ")" + sql2 += ")" + sqly2 += ")" + } + } + } + + sqlWi := fmt.Sprintf(" and event = %d", common.CurrencyEventWithDraw) + sqlWi2 := sqlWi + fmt.Sprintf(" and status = %d", common.StatusROrderFinish) + // 充值总额 + brl := []int64{} + usdt := []int64{} + brl = append(brl, db.Mysql().Sum(&common.RechargeOrder{}, sql2+sqlRe2+fmt.Sprintf(" and currency_type = %v", common.CurrencyBrazil), "amount")) + brl = append(brl, db.Mysql().Sum(&common.RechargeOrder{}, sqly2+sqlRe2+fmt.Sprintf(" and currency_type = %v", common.CurrencyBrazil), "amount")) + compair.Recharge[common.CurrencyBrazil] = brl + usdt = append(usdt, db.Mysql().Sum(&common.RechargeOrder{}, sql2+sqlRe2+fmt.Sprintf(" and currency_type = %v", common.CurrencyUSDT), "amount")) + usdt = append(usdt, db.Mysql().Sum(&common.RechargeOrder{}, sqly2+sqlRe2+fmt.Sprintf(" and currency_type = %v", common.CurrencyUSDT), "amount")) + compair.Recharge[common.CurrencyUSDT] = usdt + // 充值成功率 + compair.RechargeSuccess = append(compair.RechargeSuccess, utils.GetPer(db.Mysql().Count(&common.RechargeOrder{}, sql2+sqlRe2), db.Mysql().Count(&common.RechargeOrder{}, sql+sqlRe))) + compair.RechargeSuccess = append(compair.RechargeSuccess, utils.GetPer(db.Mysql().Count(&common.RechargeOrder{}, sqly2+sqlRe2), db.Mysql().Count(&common.RechargeOrder{}, sqly+sqlRe))) + // 充值人次成功率 + compair.RechargePlayerSuccess = append(compair.RechargePlayerSuccess, utils.GetPer(db.Mysql().DistinctCount(&common.RechargeOrder{}, sql2+sqlRe2, "uid"), db.Mysql().DistinctCount(&common.RechargeOrder{}, sql+sqlRe, "uid"))) + compair.RechargePlayerSuccess = append(compair.RechargePlayerSuccess, utils.GetPer(db.Mysql().DistinctCount(&common.RechargeOrder{}, sqly2+sqlRe2, "uid"), db.Mysql().DistinctCount(&common.RechargeOrder{}, sqly+sqlRe, "uid"))) + // 代付总金额 + // compair.Withdraw = append(compair.Withdraw, db.Mysql().Sum(&common.WithdrawOrder{}, sql+sqlWi2, "amount")) + // compair.Withdraw = append(compair.Withdraw, db.Mysql().Sum(&common.WithdrawOrder{}, sqly+sqlWi2, "amount")) + + brlWithdraw := []int64{} + usdtWithdraw := []int64{} + brlWithdraw = append(brlWithdraw, db.Mysql().Sum(&common.WithdrawOrder{}, sql+sqlWi2+fmt.Sprintf(" and currency_type = %v", common.CurrencyBrazil), "amount")) + brlWithdraw = append(brlWithdraw, db.Mysql().Sum(&common.WithdrawOrder{}, sqly+sqlWi2+fmt.Sprintf(" and currency_type = %v", common.CurrencyBrazil), "amount")) + compair.Withdraw[common.CurrencyBrazil] = brlWithdraw + usdtWithdraw = append(usdtWithdraw, db.Mysql().Sum(&common.WithdrawOrder{}, sql+sqlWi2+fmt.Sprintf(" and currency_type = %v", common.CurrencyUSDT), "amount")) + usdtWithdraw = append(usdtWithdraw, db.Mysql().Sum(&common.WithdrawOrder{}, sqly+sqlWi2+fmt.Sprintf(" and currency_type = %v", common.CurrencyUSDT), "amount")) + compair.Withdraw[common.CurrencyUSDT] = usdtWithdraw + + // 代付成功率 + compair.WithdrawSuccess = append(compair.WithdrawSuccess, utils.GetPer(db.Mysql().Count(&common.WithdrawOrder{}, sql+sqlWi2), db.Mysql().Count(&common.WithdrawOrder{}, sql+sqlWi))) + compair.WithdrawSuccess = append(compair.WithdrawSuccess, utils.GetPer(db.Mysql().Count(&common.WithdrawOrder{}, sqly+sqlWi2), db.Mysql().Count(&common.WithdrawOrder{}, sqly+sqlWi))) + // 代付人次成功率 + compair.WithdrawPlayerSuccess = append(compair.WithdrawPlayerSuccess, utils.GetPer(db.Mysql().DistinctCount(&common.WithdrawOrder{}, sql+sqlWi2, "uid"), db.Mysql().DistinctCount(&common.WithdrawOrder{}, sql+sqlWi, "uid"))) + compair.WithdrawPlayerSuccess = append(compair.WithdrawPlayerSuccess, utils.GetPer(db.Mysql().DistinctCount(&common.WithdrawOrder{}, sqly+sqlWi2, "uid"), db.Mysql().DistinctCount(&common.WithdrawOrder{}, sqly+sqlWi, "uid"))) + return compair +} + +// 编辑数据概要 +func ReviewDataEdit(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.ReviewDataEditReq) + if !a.S(req) { + return + } + data := &values.ReviewData{} + err := bdb.BackDB.QueryBySql(fmt.Sprintf("select * from ReviewData where Time = %d and PlatformID = %d", req.Time, req.Channel), data) + if err != nil { + a.Code = values.CodeRetry + return + } + u := map[string]interface{}{} + if req.ADFee != nil { + u["ADFee"] = *req.ADFee + if *req.ADFee > 0 { + // 计算毛利、roi + profit := call.Rate(common.CurrencyBrazil, data.RechargeTotal*(1000-data.ChannelFee)/1000-data.WithdrawTotal) + u["Profit"] = util.FormatFloat(float64(profit)/common.DecimalDigits, 2) + roi := utils.GetPer(profit, *req.ADFee*common.DecimalDigits) + u["ROI"] = roi + } + } + if req.Remark != nil { + u["Remark"] = *req.Remark + } + if len(u) == 0 { + a.Code = values.CodeParam + a.Msg = "无内容修改" + return + } + + err = bdb.BackDB.UpdateW(&values.ReviewData{}, u, fmt.Sprintf("Time = %d and PlatformID = %d", req.Time, req.Channel)) + if err != nil { + a.Code = values.CodeRetry + return + } +} diff --git a/modules/backend/handler/statistics/reviewLoginWay.go b/modules/backend/handler/statistics/reviewLoginWay.go new file mode 100644 index 0000000..ef25550 --- /dev/null +++ b/modules/backend/handler/statistics/reviewLoginWay.go @@ -0,0 +1,36 @@ +package statistics + +import ( + "fmt" + "server/common" + "server/db" + "server/modules/backend/app" + utils "server/modules/backend/util" + "server/modules/backend/values" + + "github.com/gin-gonic/gin" +) + +func ReviewLoginWay(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.ReviewLoginWayReq) + if !a.S(req) { + return + } + resp := values.ReviewLoginWayResp{List: map[int]int64{}} + su, eu := utils.GetQueryUnix(req.Start, req.End) + sql := fmt.Sprintf("birth >= %v and birth < %v", su, eu) + if req.ChannelID != nil && *req.ChannelID > 0 { + sql += fmt.Sprintf(" and channel_id = %v", *req.ChannelID) + } + resp.List[common.AccountTypeGuest] = db.Mysql().Count(&common.PlayerDBInfo{}, sql+fmt.Sprintf(" and account_type = %v", common.AccountTypeGuest)) + resp.List[common.AccountTypePhone] = db.Mysql().Count(&common.PlayerDBInfo{}, sql+fmt.Sprintf(" and account_type = %v", common.AccountTypePhone)) + resp.List[common.AccountTypeFacebook] = db.Mysql().Count(&common.PlayerDBInfo{}, sql+fmt.Sprintf(" and account_type = %v", common.AccountTypeFacebook)) + resp.List[common.AccountTypeGoogleplay] = db.Mysql().Count(&common.PlayerDBInfo{}, sql+fmt.Sprintf(" and account_type = %v", common.AccountTypeGoogleplay)) + resp.List[common.AccountTypeEmail] = db.Mysql().Count(&common.PlayerDBInfo{}, sql+fmt.Sprintf(" and account_type = %v", common.AccountTypeEmail)) + + a.Data = resp +} diff --git a/modules/backend/handler/statistics/reviewWithdrawData.go b/modules/backend/handler/statistics/reviewWithdrawData.go new file mode 100644 index 0000000..35bce59 --- /dev/null +++ b/modules/backend/handler/statistics/reviewWithdrawData.go @@ -0,0 +1,65 @@ +package statistics + +import ( + "server/modules/backend/app" + "server/modules/backend/models" + utils "server/modules/backend/util" + "server/modules/backend/values" + + "github.com/gin-gonic/gin" +) + +func ReviewWithdrawData(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.ReviewWithdrawDataReq) + if !a.S(req) { + return + } + resp := values.ReviewWithdrawDataResp{} + su, eu := utils.GetQueryUnix(req.Start, req.End) + + flag1 := false + flag2 := true + + // 老用户退出成功人数 + oldWithdrawSuccessCount := models.GetWithdrawPlayer(&su, &eu, req.ChannelID, &flag1, flag1) + + // 老用户退出人数 + oldWithdrawCount := models.GetOldWithdrawCount(req.ChannelID, su, eu) + + // 老用户退出次数 + oldWithdrawCount2 := models.GetOldWithdrawCount2(req.ChannelID, su, eu) + + resp.List = append(resp.List, values.OneReviewWithdrawData{Count: oldWithdrawSuccessCount, AVG: utils.GetPoint(oldWithdrawCount2, oldWithdrawCount)}) + + // 新用户退出成功人数 + newWithdrawSuccessCount := models.GetWithdrawPlayer(&su, &eu, req.ChannelID, &flag2, flag1) + + // 新用户退出人数 + newWithdrawCount := models.GetNewWithdrawCount(req.ChannelID, su, eu) + + // 新用户退出次数 + newWithdrawCount2 := models.GetNewWithdrawCount2(req.ChannelID, su, eu) + + resp.List = append(resp.List, values.OneReviewWithdrawData{Count: newWithdrawSuccessCount, AVG: utils.GetPoint(newWithdrawCount2, newWithdrawCount)}) + + // 首次退出人数 + firstWithdrawPlayer := models.GetWithdrawPlayer(&su, &eu, req.ChannelID, nil, flag2) + resp.List = append(resp.List, values.OneReviewWithdrawData{Count: firstWithdrawPlayer}) + + a.Data = resp +} + +/* +活跃用户(改名为:老用户): + 定义是老用户今日成功退出总人数。平均退出次数:当天老用户退出总次数/老用户退出人数 + +新增用户: +定义是今天注册并且成功退出的人数 平均退出次数:当天新增用户退出总次数/新增退出人数 + +首次退出人数: +活跃用户(新老用户)首次成功退出人数 +*/ diff --git a/modules/backend/handler/statistics/roomGameAnalysis.go b/modules/backend/handler/statistics/roomGameAnalysis.go new file mode 100644 index 0000000..f59dfad --- /dev/null +++ b/modules/backend/handler/statistics/roomGameAnalysis.go @@ -0,0 +1,157 @@ +package statistics + +import ( + "server/modules/backend/app" + "server/modules/backend/models" + utils "server/modules/backend/util" + "server/modules/backend/values" + "strconv" + + "github.com/gin-gonic/gin" + "github.com/olivere/elastic/v7" +) + +func RoomGameAnalysis(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.RoomGameAnalysisReq) + if !a.S(req) { + return + } + resp := values.RoomGameAnalysisResp{} + + su, eu := utils.GetQueryUnix(req.Start, req.End) + var oneDay int64 = 24 * 60 * 60 + + for i := su; i < eu; i += oneDay { + s := i + e := i + oneDay + resp.List = append(resp.List, getRoomGameAnalysisInfo(&s, &e, req.IsNew)) + } + a.Data = resp +} + +func getRoomGameAnalysisInfo(s, e *int64, isNew *bool) values.RoomGameAnalysisInfo { + var roomGameAnalysisInfo values.RoomGameAnalysisInfo + + // 时间 + roomGameAnalysisInfo.Date = *s + + // 新增用户 + roomGameAnalysisInfo.NewPlayerCount = models.GetNewPlayerCountBySql(nil, *s, *e) + + // 老用户数 + roomGameAnalysisInfo.OldPlayerCount = models.GetOldPlayerCountBySql(nil, *s, *e) + + // 玩游戏数量 + // roomGameAnalysisInfo.PlayerCount = models.GetPlayGameUserCount(s, e, nil, nil, nil, isNew) + + // 房间游戏数据 + gameCountDetail := make(map[string]int64) + // for i := 0; i < len(common.RoomGameIDs); i++ { + // gameCountDetail[strconv.Itoa(common.RoomGameIDs[i])] = models.GetGameCount(s, e, &common.RoomGameIDs[i], nil, nil, isNew) + // roomGameAnalysisInfo.GameCount += gameCountDetail[strconv.Itoa(common.RoomGameIDs[i])] + // } + + // 百人游戏数据 + // for i := 0; i < len(common.MillionGameIDs); i++ { + // millionGameID := common.MillionGameIDs[i].(int) + // gameCountDetail[strconv.Itoa(millionGameID)] = models.GetGameCount(s, e, &millionGameID, nil, nil, isNew) + // roomGameAnalysisInfo.GameCount += gameCountDetail[strconv.Itoa(millionGameID)] + // } + + // 游戏数据 + roomGameAnalysisInfo.GameCountDetail = gameCountDetail + + // 平均游戏局数 + var PlayerCount int64 + if isNew == nil { + PlayerCount = roomGameAnalysisInfo.NewPlayerCount + roomGameAnalysisInfo.OldPlayerCount + } else { + if *isNew == true { + PlayerCount = roomGameAnalysisInfo.NewPlayerCount + } else { + PlayerCount = roomGameAnalysisInfo.OldPlayerCount + } + } + roomGameAnalysisInfo.AverageGameCount = utils.GetPoint(roomGameAnalysisInfo.GameCount, PlayerCount) + + playerGameDetail := make(map[string]int64) + + for i := 0; i < 12; i++ { + playerGameDetail[strconv.Itoa(i)] = 0 + } + + // 0局 + if isNew != nil { + if *isNew { + playerGameDetail[strconv.Itoa(0)] = roomGameAnalysisInfo.NewPlayerCount - roomGameAnalysisInfo.PlayerCount + } else { + playerGameDetail[strconv.Itoa(0)] = roomGameAnalysisInfo.OldPlayerCount - roomGameAnalysisInfo.PlayerCount + } + } else { + playerGameDetail[strconv.Itoa(0)] = roomGameAnalysisInfo.OldPlayerCount + roomGameAnalysisInfo.NewPlayerCount - roomGameAnalysisInfo.PlayerCount + } + + q := elastic.NewBoolQuery() + if isNew != nil { + q.Must(elastic.NewMatchQuery("IsNew", *isNew)) + } + q.Filter(elastic.NewRangeQuery("Time").Gte(*s)) + q.Filter(elastic.NewRangeQuery("Time").Lt(*e)) + ret := new(ESBucket) + // err := db.ES().GroupSumBy(common.ESIndexBackPlayerStats, "UID", q, ret, "", false, int(models.GetPlayGameUserCount(s, e, nil, nil, nil, nil)), "PlayCount") + // if err != nil { + // log.Error(err.Error()) + // } + for _, v := range ret.Buckets { + if v.PlayCount.Value <= 10 { + switch int(v.PlayCount.Value) { + case 1: + playerGameDetail[strconv.Itoa(1)]++ + case 2: + playerGameDetail[strconv.Itoa(2)]++ + case 3: + playerGameDetail[strconv.Itoa(3)]++ + case 4: + playerGameDetail[strconv.Itoa(4)]++ + case 5: + playerGameDetail[strconv.Itoa(5)]++ + case 6: + playerGameDetail[strconv.Itoa(6)]++ + case 7: + playerGameDetail[strconv.Itoa(7)]++ + case 8: + playerGameDetail[strconv.Itoa(8)]++ + case 9: + playerGameDetail[strconv.Itoa(9)]++ + case 10: + playerGameDetail[strconv.Itoa(10)]++ + } + + } else { + playerGameDetail[strconv.Itoa(11)]++ + } + } + roomGameAnalysisInfo.PlayerGameDetail = playerGameDetail + return roomGameAnalysisInfo +} + +type ESBucket struct { + Buckets []struct { + Key interface{} + Doc_count int + PlayCount struct { + Value float64 + } + Top struct { + Hits struct { + Hits []struct { + // Source common.ESPlayerStats `json:"_source"` + } + } + } + } +} diff --git a/modules/backend/handler/statistics/withdrawListData.go b/modules/backend/handler/statistics/withdrawListData.go new file mode 100644 index 0000000..dda68bb --- /dev/null +++ b/modules/backend/handler/statistics/withdrawListData.go @@ -0,0 +1,93 @@ +package statistics + +import ( + "fmt" + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/models" + utils "server/modules/backend/util" + "server/modules/backend/values" + "time" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +// WithdrawListData 获取退出统计数据 +func WithdrawListData(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.WithdrawDataReq) + if !a.S(req) { + return + } + start := req.Start + end := req.End + if start > end { + a.Code = values.CodeParam + a.Msg = "查询时间不合法" + return + } + resp := values.WithdrawDataResp{} + var ret []values.OneWithdrawData + var oneDay int64 = 24 * 60 * 60 + s, e := utils.GetQueryUnix(req.Start, req.End) + skipStart := s + int64((req.Page-1)*req.Num)*oneDay + skipEnd := s + int64(req.Page*req.Num)*oneDay + if skipEnd > e { + skipEnd = e + } + + for i := skipEnd; i > skipStart; i -= oneDay { + j := i - oneDay + t := time.Unix(j, 0).Format("2006/01/02") + // t1 := time.Unix(i, 0).Format("2006/01/02") + // log.Debug("i:%v,j:%v,t:%v,t1:%v", i, j, t, t1) + m := &common.RechargeOrder{} + one := values.OneWithdrawData{} + one.Date = t + // one.Active = models.GetActive(&j, &i, false, false) + con := fmt.Sprintf(`create_time > %d and create_time < %d and event = %v`, j, i, common.CurrencyEventWithDraw) + one.WithdrawNum = db.Mysql().DistinctCount(m, con, "uid") + one.WithdrawTotal = db.Mysql().Count(m, con) + con1 := con + fmt.Sprintf(" and status = %v", common.StatusROrderFinish) + one.SuccessCount = db.Mysql().Count(m, con1) + // con2 := con + fmt.Sprintf(" and status = %v", common.StatusROrderRefuse) + // one.RefuseCount = db.Mysql().Count(m, con2) + con3 := con + fmt.Sprintf(" and status = %v", common.StatusROrderPay) + one.PayingCount = db.Mysql().Count(m, con3) + con4 := con + fmt.Sprintf(" and status = %v", common.StatusROrderFail) + one.FailCount = db.Mysql().Count(m, con4) + one.WithdrawPer = utils.GetPer(one.WithdrawTotal, one.Active) + one.SuccessPer = utils.GetPer(one.SuccessCount, one.WithdrawTotal) + one.WithdrawAmount = db.Mysql().Sum(m, con1, "amount") + one.WithdrawCost = db.Mysql().Sum(m, con1, "withdraw_cash") + one.DayRecharge = models.GetRechargeTotal(&j, &i, false) + + // 退出成功的金额 + one.WithdrawSuccessAmount = getWithDrawAmountBySql(j, i, common.StatusROrderFinish) + // 打款中的金额 + one.PayingAmount = getWithDrawAmountBySql(j, i, common.StatusROrderCreate) + //re := &common.RechargeInfo{UID: req.UID} + //db.Mysql().Get(re) + + ret = append(ret, one) + } + + resp.List = ret + a.Data = resp +} + +// 退出成功金额 +func getWithDrawAmountBySql(s, e int64, event int) int64 { + var Amount int64 + amountTotal := `SELECT SUM(amount) as RechargeAmount FROM recharge_order WHERE event = %v AND status = %v AND u.create_time >= %d AND u.create_time < %d` + err := db.Mysql().QueryBySql(fmt.Sprintf(amountTotal, common.CurrencyEventWithDraw, event, s, e), &Amount) + if err != nil { + log.Error("查询用户总退出失败, error : [%s]", err.Error()) + } + return Amount +} diff --git a/modules/backend/handler/statistics/withdrawListDetail.go b/modules/backend/handler/statistics/withdrawListDetail.go new file mode 100644 index 0000000..8512aa9 --- /dev/null +++ b/modules/backend/handler/statistics/withdrawListDetail.go @@ -0,0 +1,35 @@ +package statistics + +import ( + "server/modules/backend/app" + utils "server/modules/backend/util" + "server/modules/backend/values" + + "github.com/gin-gonic/gin" +) + +// WithdrawListDetail 获取个人退出详细统计数据 +func WithdrawListDetail(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.WithdrawDetailReq) + if !a.S(req) { + return + } + start := req.Start + end := req.End + if start > end { + a.Code = values.CodeParam + a.Msg = "查询时间不合法" + return + } + resp := values.WithdrawDetailResp{} + + for i := 0; i < len(resp.List); i++ { + resp.List[i].WithDrawPer = utils.GetPer(resp.List[i].WithdrawAmount, resp.List[i].RechargeTotal) + } + + a.Data = resp +} diff --git a/modules/backend/handler/statistics/withdrawOrderList.go b/modules/backend/handler/statistics/withdrawOrderList.go new file mode 100644 index 0000000..ae6638d --- /dev/null +++ b/modules/backend/handler/statistics/withdrawOrderList.go @@ -0,0 +1,183 @@ +package statistics + +import ( + "fmt" + "server/call" + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/models" + utils "server/modules/backend/util" + "server/modules/backend/values" + "sort" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +func WithdrawOrderList(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.WithdrawOrderListReq) + if !a.S(req) { + return + } + st, et := utils.GetQueryUnix(req.Start, req.End) + + queryOrder := " SELECT * FROM withdraw_order AS re where" + queryCount := " SELECT COUNT(*) as count FROM withdraw_order AS re where" + + str := fmt.Sprintf(" event = %d AND create_time >= %d AND create_time < %d ", common.CurrencyEventWithDraw, st, et) + if req.Channel != nil { + str += fmt.Sprintf(" AND channel_id = %d ", *req.Channel) + } + if req.Status != nil { + str += fmt.Sprintf(" AND status = %d ", *req.Status) + } + + // queryOrder += " LEFT JOIN ( SELECT uid, SUM(amount) AS totalAmount FROM withdraw_order WHERE " + str + " GROUP BY uid) rm ON rm.uid = re.uid WHERE rm.uid = re.uid AND " + // queryCount += " LEFT JOIN ( SELECT uid, SUM(amount) AS totalAmount FROM withdraw_order WHERE " + str + " GROUP BY uid) rm ON rm.uid = re.uid WHERE rm.uid = re.uid AND " + + var ret []common.WithdrawOrder + var count int64 + + // 查询总数量 + err := db.Mysql().QueryBySql(queryCount+str, &count) + if err != nil { + log.Error("err:%v", err) + } + + var desc string + switch req.Sort { + case 1: + desc += " ORDER BY re.create_time DESC " + case 2: + desc += " ORDER BY rm.totalAmount DESC " + default: + desc += " ORDER BY re.create_time DESC " + } + + limit := fmt.Sprintf(" LIMIT %d, %d ", (req.Page-1)*req.Num, req.Num) + + // 查询订单 + err = db.Mysql().QueryBySql(queryOrder+str+desc+limit, &ret) + if err != nil { + log.Error("err:%v", err) + } + + resp := values.WithdrawOrderListResp{Count: count} + for _, v := range ret { + user, _ := call.GetUserXInfo(v.UID, "birth") + + var totalWithdraw int64 + err = db.Mysql().QueryBySql(fmt.Sprintf("SELECT IFNULL(SUM(amount),0) AS totalAmount FROM withdraw_order WHERE uid = %d AND ", v.UID)+str, &totalWithdraw) + if err != nil { + log.Error("err:%v", err) + } + + resp.List = append([]values.OneWithdrawOrderList{{ + Channel: v.ChannelID, + UID: v.UID, + Time: v.CreateTime, + FinishTime: v.CallbackTime, + Amount: v.Amount, + Status: int(v.Status), + PayAccount: v.PayAccount, + OrderID: v.APIPayID, + MyOrderID: v.OrderID, + FailReason: v.FailReason, + Birth: user.Birth, + TotalWithdraw: totalWithdraw, + WithDrawPer: utils.GetPer(models.GetWithdrawTotalByUid(&v.UID), models.GetRechargeTotalByUid(&v.UID)), + PayChannel: v.PayChannel, + PaySource: v.PaySource, + }}, resp.List...) + } + + sort.SliceStable(resp.List, func(i, j int) bool { + return resp.List[i].Time > resp.List[j].Time + }) + + // total + s, e := utils.GetQueryUnix(req.Start, req.End) + sql := fmt.Sprintf("event = %v and status = %v and create_time >= %d and create_time < %d", common.CurrencyEventWithDraw, common.StatusROrderFinish, s, e) + if req.Channel != nil { + sql += fmt.Sprintf(" and channel_id = %v", *req.Channel) + } + resp.WithdrawTotal = db.Mysql().SumTable("withdraw_order", sql, "amount") + resp.WithdrawCount = db.Mysql().CountTable("withdraw_order", sql) + + sql = fmt.Sprintf("event = %v and create_time >= %d and create_time < %d", common.CurrencyEventWithDraw, s, e) + if req.Channel != nil { + sql += fmt.Sprintf(" and channel_id = %v", *req.Channel) + } + withdrawTotalCount := db.Mysql().CountTable("withdraw_order", sql) + resp.WithdrawSuccess = utils.GetPer(resp.WithdrawCount, withdrawTotalCount) + + a.Data = resp +} + +func QueryWithdrawOrder(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.WithdrawOrderReq) + if !a.S(req) { + return + } + var ret []common.WithdrawOrder + + queryOrder := fmt.Sprintf(" SELECT * FROM withdraw_order WHERE event = %d AND (apipayid = '%s' or orderid = '%s') ", common.CurrencyEventWithDraw, req.OrderId, req.OrderId) + + // 查询订单 + err := db.Mysql().QueryBySql(queryOrder, &ret) + if err != nil { + log.Error("err:%v", err) + } + + resp := values.WithdrawOrderListResp{Count: 1} + for _, v := range ret { + user, _ := call.GetUserInfo(v.UID) + + var totalWithdraw int64 + err = db.Mysql().QueryBySql(fmt.Sprintf("SELECT total_withdraw FROM recharge_info WHERE uid = %d ", v.UID), &totalWithdraw) + if err != nil { + log.Error("err:%v", err) + } + + resp.List = append([]values.OneWithdrawOrderList{{ + Channel: v.ChannelID, + UID: v.UID, + Time: v.CreateTime, + Amount: v.Amount, + Status: int(v.Status), + PayAccount: v.PayAccount, + OrderID: v.APIPayID, + FailReason: v.FailReason, + Birth: user.Birth, + TotalWithdraw: totalWithdraw, + WithDrawPer: utils.GetPer(models.GetWithdrawTotalByUid(&v.UID), models.GetRechargeTotalByUid(&v.UID)), + PayChannel: v.PayChannel, + PaySource: v.PaySource, + }}, resp.List...) + } + + if len(ret) > 0 { + // total + sql := fmt.Sprintf("event = %v and status = %v and uid = %d", common.CurrencyEventWithDraw, common.StatusROrderFinish, ret[0].UID) + + resp.WithdrawTotal = db.Mysql().Sum(&common.WithdrawOrder{}, sql, "amount") + resp.WithdrawCount = db.Mysql().Count(&common.WithdrawOrder{}, sql) + + sql = fmt.Sprintf("event = %v and uid = %d", common.CurrencyEventWithDraw, ret[0].UID) + + withdrawTotalCount := db.Mysql().Count(&common.WithdrawOrder{}, sql) + resp.WithdrawSuccess = utils.GetPer(resp.WithdrawCount, withdrawTotalCount) + } + + a.Data = resp + +} diff --git a/modules/backend/handler/sys/sys.go b/modules/backend/handler/sys/sys.go new file mode 100644 index 0000000..ce9dc26 --- /dev/null +++ b/modules/backend/handler/sys/sys.go @@ -0,0 +1,408 @@ +package handler + +import ( + "fmt" + "server/call" + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/bdb" + "server/modules/backend/values" + "server/natsClient" + "server/pb" + "strconv" + "time" + + "github.com/gin-gonic/gin" + "github.com/gogo/protobuf/proto" + "github.com/liangdas/mqant/log" + "github.com/olivere/elastic/v7" +) + +func WhiteListSwitch(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.WhiteListSwitchReq) + if !a.S(req) { + return + } + one := &common.WhiteList{ListType: call.SysListType, LimitType: req.Opt, Channel: req.Channel, Version: req.Version} + _, err := db.ES().Upsert(common.ESIndexBackWhiteList, strconv.Itoa(req.Channel), one, one) + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + db.ES().Refresh(common.ESIndexBackWhiteList) + send := &pb.InnerWhiteSwitch{Channel: uint32(req.Channel), Opt: uint32(req.Opt)} + data, _ := proto.Marshal(send) + if err := call.Publish(natsClient.TopicReloadConfig, &pb.ReloadGameConfig{Type: common.ReloadWhiteList, Data: data}); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + a.RecordEdit(values.PowerWhiteList, fmt.Sprintf("操作白名单开关:%v", req.Opt)) +} + +func GetWhiteList(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.WhiteListReq) + if !a.S(req) { + return + } + var list []common.WhiteList + q := elastic.NewBoolQuery() + q.MustNot(elastic.NewMatchQuery("ListType", call.SysListType)) + if req.ListType > 0 { + q.Must(elastic.NewTermQuery("ListType", req.ListType)) + } + count, err := db.ES().QueryList(common.ESIndexBackWhiteList, int(req.Page-1), int(req.Num), q, &list, "CreateTime", false) + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + var sysConfig []common.WhiteList + if _, err := db.ES().QueryList(common.ESIndexBackWhiteList, 0, 5000, elastic.NewTermQuery("ListType", call.SysListType), &sysConfig); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + resp := values.WhiteListResp{List: list, Count: count, SwitchList: sysConfig} + a.Data = resp +} + +func AddWhiteList(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.AddWhiteListReq) + if !a.S(req) { + return + } + if (req.ListType != 1 && req.ListType != 2) && (req.LimitType != 1 && req.LimitType != 2) && + (req.Channel != common.AccountTypeFacebook && req.Channel != common.AccountTypeGoogleplay) { + a.Code = values.CodeParam + return + } + one := common.WhiteList{ + ListType: req.ListType, + LimitType: req.LimitType, + Content: req.Content, + Platform: req.Channel, + Version: req.Version, + Powers: req.Powers, + CreateTime: time.Now().Unix(), + Operator: a.User.Account, + } + if err := db.ES().InsertToES(common.ESIndexBackWhiteList, one); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + db.ES().Refresh(common.ESIndexBackWhiteList) + if err := call.Publish(natsClient.TopicReloadConfig, &pb.ReloadGameConfig{Type: common.ReloadWhiteList}); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + a.RecordEdit(values.PowerWhiteList, fmt.Sprintf("新增白名单:%v", req.Content)) +} + +func EditWhiteList(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.EditWhiteListReq) + if !a.S(req) { + return + } + update := map[string]interface{}{} + if req.ListType != nil && (*req.ListType == 1 || *req.ListType == 2) { + update["ListType"] = *req.ListType + } + if req.LimitType != nil && (*req.LimitType == 1 || *req.LimitType == 2) { + update["LimitType"] = *req.LimitType + } + if req.Channel != nil && (*req.Channel == common.AccountTypeFacebook || *req.Channel == common.AccountTypeGoogleplay) { + update["Platform"] = *req.Channel + } + if req.Content != nil { + update["Content"] = *req.Content + } + if req.Powers != nil { + update["Powers"] = *req.Powers + } + if req.Version != nil { + update["Version"] = *req.Version + } + if len(update) == 0 { + a.Code = values.CodeParam + a.Msg = "没有内容修改" + return + } + if _, err := db.ES().Update(common.ESIndexBackWhiteList, req.ID, update); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + db.ES().Refresh(common.ESIndexBackWhiteList) + if err := call.Publish(natsClient.TopicReloadConfig, &pb.ReloadGameConfig{Type: common.ReloadWhiteList}); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + a.RecordEdit(values.PowerWhiteList, fmt.Sprintf("修改白名单:%v", req.ID)) +} + +func DeleteWhiteList(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.DeleteWhiteListReq) + if !a.S(req) { + return + } + if err := db.ES().DeleteByQuery(common.ESIndexBackWhiteList, elastic.NewMatchQuery("_id", req.ID)); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + db.ES().Refresh(common.ESIndexBackWhiteList) + if err := call.Publish(natsClient.TopicReloadConfig, &pb.ReloadGameConfig{Type: common.ReloadWhiteList}); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + a.RecordEdit(values.PowerWhiteList, fmt.Sprintf("删除白名单:%v", req.ID)) +} + +func AddChannel(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.AddChannelReq) + if !a.S(req) { + return + } + + one := new(common.Channel) + one.ChannelID = req.ChannelID + one.PlatformID = req.PlatformID + one.Name = req.Name + one.PackName = req.PackName + one.Version = req.Version + one.IsHot = req.IsHot + one.AdjustAppToken = req.AdjustAppToken + one.AdjustAuth = req.AdjustAuth + one.AdjustEventID = req.AdjustEventID + one.MainVersion = req.MainVersion + one.Show = req.Show + one.URL = req.URL + one.IgnoreOrganic = req.IgnoreOrganic + one.FBPixelID = req.FBPixelID + one.FBAccessToken = req.FBAccessToken + one.UP = req.UP + one.ADUpload = req.ADUpload + // m := map[int]int{} + // for _, v := range req.Games { + // id := common.GetGameIDByGames(v) + // if id == 0 { + // continue + // } + // m[id] = 1 + // } + // if len(m) > 0 { + // s, _ := json.Marshal(m) + // one.GameControl = string(s) + // } + if err := db.Mysql().Create(one); err != nil { + log.Error("err:%v", err) + return + } + err := call.Publish(natsClient.TopicReloadConfig, &pb.ReloadGameConfig{Type: common.ReloadChannel}) + if err != nil { + log.Error(err.Error()) + } + a.RecordEdit(values.PowerChannel, fmt.Sprintf("新增渠道:%v", req.ChannelID)) +} + +func EditChannel(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.EditChannelReq) + if !a.S(req) { + return + } + one := new(common.Channel) + one.ID = uint(req.ID) + if err := db.Mysql().Get(one); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeParam + a.Msg = "渠道不存在" + return + } + update := map[string]interface{}{} + if req.ChannelID != nil && one.ChannelID != *req.ChannelID { + // update = true + // one.ChannelID = *req.ChannelID + update["channel_id"] = *req.ChannelID + } + if req.PlatformID != nil && one.PlatformID != *req.PlatformID { + // update = true + // one.PlatformID = *req.PlatformID + update["platform_id"] = *req.PlatformID + } + if req.Name != nil && one.Name != *req.Name { + // update = true + // one.Name = *req.Name + update["name"] = *req.Name + } + if req.PackName != nil && one.PackName != *req.PackName { + // update = true + // one.PackName = *req.PackName + update["pack_name"] = *req.PackName + } + if req.Version != nil && one.Version != *req.Version { + // update = true + // one.Version = *req.Version + update["version"] = *req.Version + } + if req.MainVersion != nil && one.MainVersion != *req.MainVersion { + // update = true + // one.MainVersion = *req.MainVersion + update["main_version"] = *req.MainVersion + } + if req.IsHot != nil && one.IsHot != *req.IsHot { + // update = true + // one.IsHot = *req.IsHot + update["ishot"] = *req.IsHot + } + if req.AdjustAuth != nil && one.AdjustAuth != *req.AdjustAuth { + // update = true + // one.AdjustAuth = *req.AdjustAuth + update["adjust_auth"] = *req.AdjustAuth + } + if req.AdjustAppToken != nil && one.AdjustAppToken != *req.AdjustAppToken { + // update = true + // one.AdjustAppToken = *req.AdjustAppToken + update["adjust_app_token"] = *req.AdjustAppToken + } + if req.AdjustEventID != nil && one.AdjustEventID != *req.AdjustEventID { + // update = true + // one.AdjustEventID = *req.AdjustEventID + update["adjust_eventid"] = *req.AdjustEventID + } + if req.URL != nil && one.URL != *req.URL { + // update = true + // one.URL = *req.URL + update["url"] = *req.URL + } + if req.Show != nil && one.Show != *req.Show { + // update = true + // one.Show = *req.Show + update["show"] = *req.Show + } + if req.IgnoreOrganic != nil && one.IgnoreOrganic != *req.IgnoreOrganic { + // update = true + // one.IgnoreOrganic = *req.IgnoreOrganic + update["ignore_organic"] = *req.IgnoreOrganic + } + if req.FBPixelID != nil && one.FBPixelID != *req.FBPixelID { + // update = true + // one.FBPixelID = *req.FBPixelID + update["fb_pixelid"] = *req.FBPixelID + } + if req.FBAccessToken != nil && one.FBAccessToken != *req.FBAccessToken { + // update = true + // one.FBAccessToken = *req.FBAccessToken + update["fb_accesstoken"] = *req.FBAccessToken + } + if req.UP != nil && one.UP != *req.UP { + // update = true + // one.FBAccessToken = *req.FBAccessToken + update["up"] = *req.UP + } + if req.ADUpload != nil && one.ADUpload != *req.ADUpload { + // update = true + // one.FBAccessToken = *req.FBAccessToken + update["ad_upload"] = *req.ADUpload + } + if len(update) == 0 { + a.Code = values.CodeParam + a.Msg = "无内容修改" + return + } + if err := db.Mysql().Update(&common.Channel{ID: uint(req.ID)}, update); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + err := call.Publish(natsClient.TopicReloadConfig, &pb.ReloadGameConfig{Type: common.ReloadChannel}) + if err != nil { + log.Error(err.Error()) + } + a.RecordEdit(values.PowerChannel, fmt.Sprintf("修改渠道:%v", *req.ChannelID)) +} + +func DelChannel(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.DelChannelReq) + if !a.S(req) { + return + } + + // 传入-1时清表 + if req.ID == -1 { + if err := db.Mysql().C().Delete(&common.Channel{}).Where("id > ?", req.ID); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + } else { + one := &common.Channel{ID: uint(req.ID)} + if err := db.Mysql().C().Delete(one).Where("id = ?", req.ID).Error; err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + } + + err := call.Publish(natsClient.TopicReloadConfig, &pb.ReloadGameConfig{Type: common.ReloadChannel}) + if err != nil { + log.Error(err.Error()) + } + a.RecordEdit(values.PowerChannel, fmt.Sprintf("删除渠道:%v", req.ID)) +} + +// OptList 操作日志 +func OptList(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.EditHistoryListReq) + if !a.S(req) { + return + } + resp := values.EditHistoryListResp{} + if err := bdb.BackDB.C().Order("time Desc").Limit(int(req.Num)).Offset(int((req.Page - 1) * req.Num)).Find(&resp.List).Error; err != nil { + log.Error("err:%v", err) + } + resp.Count = bdb.BackDB.Count(&values.EditHistory{}, "") + a.Data = resp +} diff --git a/modules/backend/handler/warn/warn.go b/modules/backend/handler/warn/warn.go new file mode 100644 index 0000000..8de557d --- /dev/null +++ b/modules/backend/handler/warn/warn.go @@ -0,0 +1,137 @@ +package handler + +import ( + "fmt" + "server/call" + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/bdb" + "server/modules/backend/values" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +// WarnList 查看预警列表 +func WarnList(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + + resp := values.WarnListResp{List: []call.SysWarn{}} + _, err := db.ES().QueryList(common.ESIndexBackWarn, 0, 5000, nil, &resp.List, "Time", false) + if err != nil { + log.Error(err.Error()) + } + a.Data = resp +} + +// AddWarn 新增预警 +func AddWarn(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.AddWarnReq) + if !a.S(req) { + return + } + log.Debug("add warn:%+v", req) + var phones []string + // 首先检查预警人员的情况 + for _, v := range req.WarnMember { + one := &values.User{ID: uint(v)} + if err := bdb.BackDB.Get(one); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeParam + a.Msg = "预警人员有误" + return + } + if one.Phone == "" { + a.Code = values.CodeParam + a.Msg = fmt.Sprintf("预警人员%v还未设置手机号", one.Name) + return + } + phones = append(phones, one.Phone) + } + err := call.AddSysWarn(req.Channel, req.Type, req.Interval, req.Condition, req.WarnWay, req.WarnMember, phones, req.OtherPhone) + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeParam + a.Msg = err.Error() + return + } + db.ES().Refresh(common.ESIndexBackWarn) +} + +// EditWarn 修改预警 +func EditWarn(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.EditWarnReq) + if !a.S(req) { + return + } + + // 删除预警 逻辑 + err := call.DelSysWarn(req.ID) + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeParam + a.Msg = err.Error() + return + } + + // 添加预警 + log.Debug("add warn:%+v", req) + var phones []string + // 首先检查预警人员的情况 + for _, v := range *req.WarnMember { + one := &values.User{ID: uint(v)} + if err := bdb.BackDB.Get(one); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeParam + a.Msg = "预警人员有误" + return + } + if one.Phone == "" { + a.Code = values.CodeParam + a.Msg = fmt.Sprintf("预警人员%v还未设置手机号", one.Name) + return + } + phones = append(phones, one.Phone) + } + + err = call.AddSysWarn(*req.Channel, *req.Type, *req.Interval, *req.Condition, *req.WarnWay, *req.WarnMember, phones, *req.OtherPhone) + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeParam + a.Msg = err.Error() + return + } + db.ES().Refresh(common.ESIndexBackWarn) +} + +// DelWarn 删除预警 +func DelWarn(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.DelWarnReq) + if !a.S(req) { + return + } + err := call.DelSysWarn(req.ID) + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeParam + a.Msg = err.Error() + return + } + db.ES().Refresh(common.ESIndexBackWarn) +} diff --git a/modules/backend/middleware/cross.go b/modules/backend/middleware/cross.go new file mode 100644 index 0000000..3abc720 --- /dev/null +++ b/modules/backend/middleware/cross.go @@ -0,0 +1,48 @@ +package middleware + +import ( + "net/http" + "server/modules/backend/app" + + "github.com/gin-gonic/gin" +) + +//跨域访问:cross origin resource share +func CrosHandler() gin.HandlerFunc { + return func(context *gin.Context) { + method := context.Request.Method + origin := context.Request.Header.Get("Origin") //请求头部 + if origin != "" { + //接收客户端发送的origin (重要!) + context.Writer.Header().Set("Access-Control-Allow-Origin", "*") + + // 设置允许访问所有域 + context.Header("Access-Control-Allow-Origin", "*") + + //服务器支持的所有跨域请求的方法 + context.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE") + + //允许跨域设置可以返回其他子段,可以自定义字段 + context.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token,session,X_Requested_With,Accept, Origin, Host, Connection, Accept-Encoding, Accept-Language,DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Pragma,token,openid,opentoken,istoken") + + // 允许浏览器(客户端)可以解析的头部 (重要) + context.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar") + + //设置缓存时间 + context.Header("Access-Control-Max-Age", "172800") + + //允许客户端传递校验信息比如 cookie (重要) + context.Header("Access-Control-Allow-Credentials", "true") + + // 设置返回格式是json + context.Set("content-type", "application/json") + } + + if method == "OPTIONS" { + context.JSON(http.StatusOK, app.R{Code: http.StatusOK, Data: "Options Request!"}) + } + + //处理请求 + context.Next() + } +} diff --git a/modules/backend/middleware/power.go b/modules/backend/middleware/power.go new file mode 100644 index 0000000..6884922 --- /dev/null +++ b/modules/backend/middleware/power.go @@ -0,0 +1,68 @@ +package middleware + +import ( + "server/modules/backend/app" + "server/modules/backend/bdb" + "server/modules/backend/values" + "strings" + + "github.com/gin-gonic/gin" +) + +// 进行权限校验 +func PowerMiddleWare() gin.HandlerFunc { + return func(c *gin.Context) { + path := c.Request.RequestURI + if PassURL(path) { + c.Next() + return + } + a := app.NewApp(c) + if !powerPass(a.User, path) { + a.Code = values.CodePower + a.Response() + c.Abort() + return + } + c.Next() + } +} + +func powerPass(u *values.User, path string) bool { + if u == nil { + return false + } + if u.Role == values.UserRoleAdmin { + return true + } + // 第一步找到主页签 + p := 0 + for s, v := range values.PowerMap { + if strings.Contains(path, s) { + p = v + break + } + } + // 不在权限控制范围 + if p == 0 { + return true + } + buttons, ok := bdb.GetPowerByRole(u.Role)[p] + if !ok { + return false + } + pbutton, ok2 := values.PowerButtonMap[p] + // 该页签没有子按钮 + if !ok2 { + return true + } + for i, v := range pbutton { + if v == path { + if i > len(pbutton)-1 { + return true + } + return buttons[i] == 1 + } + } + return true +} diff --git a/modules/backend/middleware/token.go b/modules/backend/middleware/token.go new file mode 100644 index 0000000..40e42f7 --- /dev/null +++ b/modules/backend/middleware/token.go @@ -0,0 +1,91 @@ +package middleware + +import ( + "server/common" + "server/db" + "server/modules/backend/app" + "server/modules/backend/values" + "strings" + + "github.com/liangdas/mqant/log" + + "github.com/gin-gonic/gin" +) + +var ( + passURLs = map[string]struct{}{ + "/account/login": {}, + } +) + +// 进行token校验 +func TokenMiddleWare() gin.HandlerFunc { + return func(c *gin.Context) { + + // 检查是否排除当前url + + // exclude := regexp.MustCompile("/static/*") + + path := c.Request.RequestURI + + if PassURL(path) { + c.Next() + return + } + + // 对用户的token进行校验 并对token续期 + token := c.GetHeader("token") + one := new(values.User) + if err := db.Redis().GetJsonData(common.GetBackendTokenKey(token), one); err != nil || one.Account == "" { + app := app.NewApp(c) + app.Code = values.CodeToken + app.Msg = "登录已过期,请重新登录" + app.Response() + c.Abort() + return + } + // if len(one.Power) > 0 { + // err := json.Unmarshal([]byte(one.Power), &one.PowerMap) + // if err != nil { + // log.Error("err:%v", err) + // app := app.NewApp(c) + // app.Code = values.CodePower + // app.Response() + // c.Abort() + // return + // } + // } + c.Set("user", one) + c.Next() + // 刷新token过期时间 + defer func() { + if token == values.AdminToken { + return + } + err := db.Redis().Expire(common.GetBackendTokenKey(token), values.RedisTokenEx) + if err != nil { + log.Error(err.Error()) + } + }() + } +} + +// PassURL 过滤url +func PassURL(path string) bool { + index := strings.Index(path, "?") + if index > 0 { + path = path[:index] + } + _, ok := passURLs[path] + if !ok { + // index := strings.LastIndex(path, "/") + // url := path[:index+1] + "*" + // _, ok = passURLs[url] + for k := range passURLs { + if strings.Contains(path, k) { + return true + } + } + } + return ok +} diff --git a/modules/backend/migrate.go b/modules/backend/migrate.go new file mode 100644 index 0000000..8e9fed6 --- /dev/null +++ b/modules/backend/migrate.go @@ -0,0 +1,116 @@ +package backend + +import ( + "fmt" + "server/common" + "server/db" +) + +// MigrateDB 自动数据库迁移 +func MigrateDB() { + for i := 0; i < 100; i++ { + bt := new(common.CurrencyBalance) + if err := db.Mysql().C().Table(fmt.Sprintf("currency_balance_%02d", i)).AutoMigrate(bt); err != nil { + panic("Migrate db fail") + } + } + for i := common.CurrencyTypeZero + 1; i < common.CurrencyAll; i++ { + re := new(common.RechargeInfoCurrency) + if err := db.Mysql().C().Table(fmt.Sprintf("recharge_info_%s", i.GetCurrencyName())).AutoMigrate(re); err != nil { + panic("Migrate db fail") + } + } + for i := common.CurrencyTypeZero + 1; i < common.CurrencyAll; i++ { + pp := new(common.PlayerProfile) + if err := db.Mysql().C().Table(fmt.Sprintf("player_profile_%s", i.GetCurrencyName())).AutoMigrate(pp); err != nil { + panic("Migrate db fail") + } + } + if err := db.Mysql().C().Table(common.PlayerRechargeTableName).AutoMigrate(&common.PlayerCurrency{}); err != nil { + panic("Migrate db fail") + } + err := db.Mysql().C().AutoMigrate( + new(common.PlayerDBInfo), + new(common.RechargeOrder), + new(common.WithdrawOrder), + new(common.RechargeInfo), + new(common.Channel), + new(common.LoginRecord), + new(common.PayInfo), + new(common.ConfigPlatform), + new(common.ServerVersion), + new(common.ConfigRWPer), + new(common.PlayerData), + new(common.Mail), + new(common.PlayerRed), + new(common.ConfigActivity), + new(common.Jackpot), + new(common.ConfigPayProduct), + new(common.ConfigWithdrawChannels), + new(common.BlackList), + new(common.PlayerItems), + new(common.ConfigPayChannels), + new(common.ConfigH5), + new(common.PlayerH5Data), + new(common.ConfigTron), + new(common.ConfigWithdrawProduct), + new(common.ConfigGameProvider), + new(common.ConfigGameList), + new(common.ConfigGameType), + new(common.ConfigGameMark), + new(common.ConfigFirstPageGames), + new(common.PlayerCurrency), + new(common.PlayerProfile), + new(common.VipData), + new(common.ConfigVIP), + new(common.ConfigBroadcast), + new(common.ConfigNotice), + new(common.ProviderBetRecord), + new(common.ConfigCurrencyRateUSD), + new(common.TronData), + new(common.ConfigGameRoom), + new(common.ConfigWater), + new(common.ConfigRobot), + new(common.ConfigAppSpin), + new(common.ConfigShare), + new(common.ConfigShareSys), + new(common.ShareInfo), + new(common.ShareOrder), + new(common.ConfigActivityPddSpin), + new(common.ConfigActivityPdd), + new(common.PddData), + new(common.ConfigTask), + new(common.TaskData), + new(common.PlayerADData), + new(common.ConfigCurrencyResource), + new(common.ConfigFirstPay), + new(common.PlayerPayData), + new(common.ConfigActivityFreeSpin), + new(common.ActivityFreeSpinData), + new(common.ConfigActivityFirstRechargeBack), + new(common.ActivityFirstRechargeBackData), + new(common.ConfigActivityLuckyCode), + new(common.ActivityLuckyCodeData), + new(common.ActivityLuckyCode), + new(common.ConfigBanner), + new(common.ConfigActivitySign), + new(common.ActivitySignData), + new(common.ConfigActivityBreakGift), + new(common.ConfigShareRobot), + new(common.ConfigActivityWeekCard), + new(common.ActivityWeekCardData), + new(common.ConfigActivitySlots), + new(common.ActivitySlotsData), + new(common.ActivitySlotsRecord), + new(common.ConfigActivityLuckyShop), + new(common.ActivityLuckyShopData), + new(common.ConfigServerFlag), + new(common.ConfigActivitySevenDayBox), + new(common.ActivitySevenDayBoxData), + new(common.ConfigActivitySuper), + new(common.ActivitySuperData), + ) + if err != nil { + panic("Migrate db fail") + } +} diff --git a/modules/backend/models/back_daily_data.go b/modules/backend/models/back_daily_data.go new file mode 100644 index 0000000..5cbab88 --- /dev/null +++ b/modules/backend/models/back_daily_data.go @@ -0,0 +1,18 @@ +package models + +import ( + "github.com/liangdas/mqant/log" + "server/common" + "server/db" +) + +func GetDailyData(s, e *int64, channel *int, field string) int64 { + q := NewQ(s, e, nil, channel) + + res, err := db.ES().SumBy(common.ESIndexBackDailyData, field, q) + if err != nil { + log.Error(err.Error()) + } + + return int64(res) +} diff --git a/modules/backend/models/back_game_data.go b/modules/backend/models/back_game_data.go new file mode 100644 index 0000000..54b78ad --- /dev/null +++ b/modules/backend/models/back_game_data.go @@ -0,0 +1,21 @@ +package models + +import ( + "server/common" + "server/db" + + "github.com/olivere/elastic/v7" +) + +// 获取玩了游戏的用户数 Gt +func GetPlayGameUserCounts(start, end *int64, isNew *bool, channel ...*int) int64 { + q := NewQ(start, end, channel...) + + if isNew != nil { + q.Filter(elastic.NewTermQuery("IsNew", *isNew)) + } + + // q.Filter(elastic.NewRangeQuery("PlayCount").Gt(0)) + + return db.ES().CountCard(common.ESIndexGameData, "UID", q) +} diff --git a/modules/backend/models/back_income_statistics.go b/modules/backend/models/back_income_statistics.go new file mode 100644 index 0000000..265348c --- /dev/null +++ b/modules/backend/models/back_income_statistics.go @@ -0,0 +1,26 @@ +package models + +import ( + "github.com/liangdas/mqant/log" + "github.com/olivere/elastic/v7" + "server/common" + "server/db" +) + +func QueryOneIncome(su *int64, channel *int, income *common.ESIncomeStatistics) { + q := elastic.NewBoolQuery() + + if su != nil { + q.Must(elastic.NewMatchQuery("Time", *su)) + } + + if channel != nil { + q.Must(elastic.NewMatchQuery("Channel", *channel)) + } else { + q.Must(elastic.NewMatchQuery("Channel", 0)) + } + err := db.ES().QueryOne(common.ESIndexBackIncomeStatistics, q, income) + if err != nil { + log.Error(err.Error()) + } +} diff --git a/modules/backend/models/back_open_record.go b/modules/backend/models/back_open_record.go new file mode 100644 index 0000000..85a9043 --- /dev/null +++ b/modules/backend/models/back_open_record.go @@ -0,0 +1,23 @@ +package models + +import ( + "server/common" + "server/db" + + "github.com/olivere/elastic/v7" +) + +// 获取下载数量 +func GetDownloadCount(start, end *int64, Channel *int) int64 { + q := NewQ(start, end, nil, Channel) + if Channel != nil { + q.Must(elastic.NewMatchQuery("Channel", *Channel)) + } + return db.ES().Count(common.ESIndexBackOpenRecord, q) +} + +// 获取下载数量 +func GetDownloadCounts(start, end *int64, Channel ...*int) int64 { + q := NewQ(start, end, Channel...) + return db.ES().Count(common.ESIndexBackOpenRecord, q) +} diff --git a/modules/backend/models/back_player_pay_data.go b/modules/backend/models/back_player_pay_data.go new file mode 100644 index 0000000..806fecd --- /dev/null +++ b/modules/backend/models/back_player_pay_data.go @@ -0,0 +1,134 @@ +package models + +import ( + "server/common" + "server/db" + + "github.com/liangdas/mqant/log" + "github.com/olivere/elastic/v7" +) + +// 付费用户数 +func GetRecharge(start, end *int64, isNew bool, channel ...*int) int64 { + q := NewQ(start, end, nil) + q.Filter(elastic.NewRangeQuery("Amount").Gt(0)) + if isNew { + q.Must(elastic.NewMatchQuery("IsNew", true)) + } + q.Must(elastic.NewMatchQuery("Type", 1)) + getChannelQ(q, channel...) + return db.ES().CountCard(common.ESIndexBackPlayerPayData, "UID", q) +} + +// 付费总额 +func GetRechargeTotal(start, end *int64, isNew bool, channel ...*int) int64 { + q := NewQ(start, end, channel...) + if isNew { + q.Must(elastic.NewMatchQuery("IsNew", true)) + } + q.Must(elastic.NewMatchQuery("Type", 1)) + total, err := db.ES().SumBy(common.ESIndexBackPlayerPayData, "Amount", q) + if err != nil { + log.Error("err:%v", err) + return 0 + } + return int64(total) +} + +// 付费玩家数量 Gt +func GetPayCount(start, end *int64, channel *int, isNew *bool) int64 { + q := NewQ(start, end, nil, channel) + if isNew != nil { + q.Must(elastic.NewMatchQuery("IsNew", true)) + } + q.Must(elastic.NewMatchQuery("Type", 1)) + q.Filter(elastic.NewRangeQuery("Amount").Gt(0)) + return db.ES().CountCard(common.ESIndexBackPlayerPayData, "UID", q) +} + +// 获取付费人数 Gt +func GetRechargePlayers(start, end *int64, channel *int, isNew *bool, field string) int64 { + q := NewQ(start, end, nil, channel) + if isNew != nil { + q.Must(elastic.NewMatchQuery("IsNew", *isNew)) + } + q.Must(elastic.NewMatchQuery("Type", 1)) + q.Filter(elastic.NewRangeQuery("Amount").Gt(0)) + return db.ES().CountCard(common.ESIndexBackPlayerPayData, field, q) +} + +// 付费总额 +func GetRechargeTotalByUid(uid *int) int64 { + q := NewQ(nil, nil, nil, nil) + if uid != nil { + q.Must(elastic.NewMatchQuery("UID", *uid)) + } + q.Must(elastic.NewMatchQuery("Type", 1)) + total, err := db.ES().SumBy(common.ESIndexBackPlayerPayData, "Amount", q) + if err != nil { + log.Error("err:%v", err) + return 0 + } + return int64(total) +} + +// 退出总额 +func GetWithdrawTotalByUid(uid *int) int64 { + q := NewQ(nil, nil, nil, nil) + if uid != nil { + q.Must(elastic.NewMatchQuery("UID", *uid)) + } + q.Must(elastic.NewMatchQuery("Type", 2)) + total, err := db.ES().SumBy(common.ESIndexBackPlayerPayData, "Amount", q) + if err != nil { + log.Error("err:%v", err) + return 0 + } + return int64(total) +} + +func GetWithdrawPlayer(start, end *int64, channel *int, isNew *bool, firstWithdraw bool) int64 { + q := NewQ(start, end, nil, channel) + + if isNew != nil { + q.Must(elastic.NewMatchQuery("IsNew", *isNew)) + } + + if firstWithdraw { + q.Filter(elastic.NewRangeQuery("FirstAmount").Gt(0)) + } + q.Must(elastic.NewMatchQuery("Type", 2)) + + q.Filter(elastic.NewRangeQuery("Amount").Gt(0)) + + return db.ES().CountCard(common.ESIndexBackPlayerPayData, "UID", q) +} + +func GetWithdrawCount(start, end *int64, channel *int, isNew *bool, field string) int64 { + q := NewQ(start, end, nil, channel) + + if isNew != nil { + q.Must(elastic.NewMatchQuery("IsNew", *isNew)) + } + q.Must(elastic.NewMatchQuery("Type", 2)) + q.Filter(elastic.NewRangeQuery("Amount").Gt(0)) + + res, err := db.ES().SumBy(common.ESIndexBackPlayerPayData, field, q) + if err != nil { + log.Error(err.Error()) + } + return int64(res) +} + +// 获取玩家首充金额 +func GetUserFirstRecharge(uid int) int64 { + q := NewQ(nil, nil, nil, nil) + q.Must(elastic.NewMatchQuery("UID", uid)) + q.Must(elastic.NewMatchQuery("Type", 1)) + total, err := db.ES().SumBy(common.ESIndexBackPlayerPayData, "FirstAmount", q) + if err != nil { + log.Error("err:%v", err) + return 0 + } + return int64(total) +} diff --git a/modules/backend/models/balance.go b/modules/backend/models/balance.go new file mode 100644 index 0000000..4c2a8d2 --- /dev/null +++ b/modules/backend/models/balance.go @@ -0,0 +1,525 @@ +package models + +import ( + "server/common" + "server/db" + "server/util" + "strconv" + + "github.com/liangdas/mqant/log" + "github.com/olivere/elastic/v7" +) + +// 通过流水表查询盈亏 +func GetProfit(start, end *int64, channel *int, gameId *int, roomId *int, event *int, controlType *int) int64 { + q := NewQt(start, end, nil, channel) + + if event != nil { + q.Must(elastic.NewMatchQuery("event", *event)) + } + + if gameId != nil { + q.Must(elastic.NewMatchQuery("desc.keyword", strconv.Itoa(*gameId))) + } + + if roomId != nil { + q.Must(elastic.NewMatchQuery("room_name.keyword", strconv.Itoa(*roomId))) + } + + if controlType != nil { + q.Must(elastic.NewMatchQuery("control_type", *controlType)) + } + + million, err := db.ES().SumBy(common.ESIndexBalance, "value", q) + if err != nil { + log.Error("查询盈亏失败, error : [%s]", err.Error()) + return 0 + } + return -int64(million) +} + +// 聚合通过流水表查询盈亏 +func GetProfitGroup(start, end *int64, channel *int, controlType *int, gameIDs []int) map[string]map[string]int64 { + q := NewQt(start, end, nil, channel) + q.Must(elastic.NewMatchQuery("event", common.CurrencyEventGameSettle)) + if len(gameIDs) > 0 { + terms := []interface{}{} + for _, v := range gameIDs { + terms = append(terms, v) + } + q.Must(elastic.NewTermsQuery("desc.keyword", terms...)) + } + ret := map[string]map[string]int64{} + totalBulk, err := db.ES().Group2SumBy(common.ESIndexBalance, "desc.keyword", "room_name.keyword", "value", q, "", false, 0) + if err == nil { + for _, v := range totalBulk.Buckets { + one := map[string]int64{} + for _, j := range v.Sub1.Buckets { + one[strconv.Itoa(util.GetInt(j.Key))] = -int64(j.Sub2.Value) + } + ret[strconv.Itoa(util.GetInt(v.Key))] = one + } + } + return ret +} + +// 通过流水表查询盈亏 Value Gt +func GetProfitLimitValue(start, end *int64, channel *int, gameId *int, roomId *int, value *int64) int64 { + q := NewQt(start, end, nil, channel) + + q.Must(elastic.NewMatchQuery("event", common.CurrencyEventGameSettle)) + + if gameId != nil { + q.Must(elastic.NewMatchQuery("desc.keyword", strconv.Itoa(*gameId))) + } + + if roomId != nil { + q.Must(elastic.NewMatchQuery("room_name.keyword", strconv.Itoa(*roomId))) + } + + if value != nil { + q.Filter(elastic.NewRangeQuery("value").Gt(*value)) + } + + million, err := db.ES().SumBy(common.ESIndexBalance, "value", q) + if err != nil { + log.Error("查询盈亏失败, error : [%s]", err.Error()) + return 0 + } + return -int64(million) +} + +// 通过流水表获取游戏下注总额 +func GetBet(start, end *int64, channel *int, gameId *int, roomId *int, uid *int) int64 { + q := NewQt(start, end, nil, channel) + + // q.Must(elastic.NewMatchQuery("event", common.CurrencyEventGameBet)) + + if gameId != nil { + q.Must(elastic.NewMatchQuery("desc.keyword", strconv.Itoa(*gameId))) + } + + if roomId != nil { + q.Must(elastic.NewMatchQuery("room_name.keyword", strconv.Itoa(*roomId))) + } + if uid != nil { + q.Must(elastic.NewMatchQuery("uid", strconv.Itoa(*uid))) + } + + Bet, err := db.ES().SumBy(common.ESIndexBalance, "value", q) + if err != nil { + log.Error("查询下注额度失败, error : [%s]", err.Error()) + return 0 + } + return -int64(Bet) +} + +// 聚合通过流水表获取游戏下注总额 +func GetBetGroup(start, end *int64, channel *int, gameIDs []int) map[string]map[string]int64 { + q := NewQt(start, end, nil, channel) + // q.Must(elastic.NewMatchQuery("event", common.CurrencyEventGameBet)) + if len(gameIDs) > 0 { + terms := []interface{}{} + for _, v := range gameIDs { + terms = append(terms, v) + } + q.Must(elastic.NewTermsQuery("desc.keyword", terms...)) + } + ret := map[string]map[string]int64{} + totalBulk, err := db.ES().Group2SumBy(common.ESIndexBalance, "desc.keyword", "room_name.keyword", "value", q, "", false, 0) + if err == nil { + for _, v := range totalBulk.Buckets { + one := map[string]int64{} + for _, j := range v.Sub1.Buckets { + one[strconv.Itoa(util.GetInt(j.Key))] = -int64(j.Sub2.Value) + } + ret[strconv.Itoa(util.GetInt(v.Key))] = one + } + } + return ret +} + +// 通过流水表获取总局数 +func GetGameTotal(start, end *int64, channel *int, gameId *int, roomId *int, dropStart *int) int64 { + q := NewQt(start, end, nil, channel) + + q.Must(elastic.NewMatchQuery("event", common.CurrencyEventGameSettle)) + + if gameId != nil { + q.Must(elastic.NewMatchQuery("desc.keyword", strconv.Itoa(*gameId))) + } + + if roomId != nil { + q.Must(elastic.NewMatchQuery("room_name.keyword", strconv.Itoa(*roomId))) + } + + if dropStart != nil { + q.Must(elastic.NewRangeQuery("drop").Gt(*dropStart)) + } + return db.ES().CountCard(common.ESIndexBalance, "extern.keyword", q) +} + +// 通过流水表获取总局数 +func GetGameTotalGroup(start, end *int64, channel *int, drop bool, gameIDs []int) map[string]map[string]int64 { + q := NewQt(start, end, nil, channel) + q.Must(elastic.NewMatchQuery("event", common.CurrencyEventGameSettle)) + if len(gameIDs) > 0 { + terms := []interface{}{} + for _, v := range gameIDs { + terms = append(terms, v) + } + q.Must(elastic.NewTermsQuery("desc.keyword", terms...)) + } + if drop { + q.Must(elastic.NewRangeQuery("drop").Gt(0)) + } + ret := map[string]map[string]int64{} + totalBulk, err := db.ES().GroupBy2Card(common.ESIndexBalance, "desc.keyword", "room_name.keyword", "extern.keyword", q, 0) + if err == nil { + for _, v := range totalBulk.Buckets { + one := map[string]int64{} + for _, j := range v.Sub1.Buckets { + one[strconv.Itoa(util.GetInt(j.Key))] = int64(j.Sub2.Value) + } + ret[strconv.Itoa(util.GetInt(v.Key))] = one + } + } + return ret +} + +// 获取控杀次数(怒气 + 小黑屋) +func GetControlCount(start, end *int64, uid *int, event *int, valueLt bool) int64 { + q := NewQt(start, end, nil, nil) + if uid != nil { + q.Must(elastic.NewMatchQuery("uid", uid)) + } + if event != nil { + q.Must(elastic.NewMatchQuery("event", *event)) + } + q.Filter(elastic.NewRangeQuery("control_type").Gt(1)) + + if valueLt { + q.Filter(elastic.NewRangeQuery("value").Lt(0)) + } + + return db.ES().Count(common.ESIndexBalance, q) +} + +// 获取幸运次数 +func GetLuckCount(start, end *int64, uid *int, channel *int, event *int, valueGt, isUidCountCard bool) int64 { + q := NewQt(start, end, nil, channel) + if uid != nil { + q.Must(elastic.NewMatchQuery("uid", uid)) + } + if event != nil { + q.Must(elastic.NewMatchQuery("event", *event)) + } + if valueGt { + q.Filter(elastic.NewRangeQuery("value").Gt(0)) + } + + q.Must(elastic.NewMatchQuery("control_type", 1)) + + if isUidCountCard { + return db.ES().CountCard(common.ESIndexBalance, "uid", q) + } else { + return db.ES().Count(common.ESIndexBalance, q) + } +} + +// QueryPlayerAllBalance 查询玩家所有流水记录 +func QueryPlayerAllBalance(uid *string, page, num int, start, end *int64, gameId, roomId, controlType, channelId, event *int, ret *[]common.CurrencyBalance) (int64, error) { + q := NewQt(start, end, nil, channelId) + if uid != nil { + id := *uid + if len(id) == 19 { // 牌局的唯一id + q.Must(elastic.NewMatchQuery("extern.keyword", id)) + } else { + Id, err := strconv.Atoi(id) + if err != nil { + log.Error(err.Error()) + } else { + q.Must(elastic.NewMatchQuery("uid", Id)) + } + } + } + + if gameId != nil { + q.Must(elastic.NewMatchQuery("desc.keyword", *gameId)) + } + + if roomId != nil { + q.Must(elastic.NewMatchQuery("room_name.keyword", *roomId)) + } + + if controlType != nil { + q.Must(elastic.NewMatchQuery("control_type", *controlType)) + } + + if channelId != nil { + q.Must(elastic.NewMatchQuery("ChannelID", *channelId)) + } + + if event != nil { + q.Must(elastic.NewMatchQuery("event", *event)) + } + + if start != nil { + q.Filter(elastic.NewRangeQuery("time").Gte(*start)) + } + if end != nil { + q.Filter(elastic.NewRangeQuery("time").Lt(*end)) + } + + count, err := db.ES().QueryList(common.ESIndexBalance, page, num, q, ret, "time", false) + if err != nil { + log.Error("err:%v", err) + return 0, err + } + return count, err +} + +// 通过流水表获取玩家人数 +func GetPlayerCount(start, end *int64, channel *int, gameId *int, roomId *int) int64 { + q := NewQt(start, end, nil, channel) + + q.Must(elastic.NewMatchQuery("event", common.CurrencyEventGameSettle)) + + if gameId != nil { + q.Must(elastic.NewMatchQuery("desc.keyword", strconv.Itoa(*gameId))) + } + + if roomId != nil { + q.Must(elastic.NewMatchQuery("room_name.keyword", strconv.Itoa(*roomId))) + } + + return db.ES().CountCard(common.ESIndexBalance, "uid", q) +} + +// 获取玩家游玩场次 +func GetGameCountByBalance(start, end *int64, uid *int, channel *int, gameId *int, roomId *int) int64 { + q := NewQt(start, end, nil, channel) + + q.Must(elastic.NewMatchQuery("event", common.CurrencyEventGameSettle)) + + if uid != nil { + q.Must(elastic.NewMatchQuery("uid", *uid)) + } + + if gameId != nil { + q.Must(elastic.NewMatchQuery("desc.keyword", strconv.Itoa(*gameId))) + } + + if roomId != nil { + q.Must(elastic.NewMatchQuery("room_name.keyword", strconv.Itoa(*roomId))) + } + + return db.ES().Count(common.ESIndexBalance, q) +} + +// 获取玩家赢的游戏场次 +func GetWinGameCountByBalance(start, end *int64, uid *int, channel *int, gameId *int, roomId *int, isWin *bool) int64 { + q := NewQt(start, end, nil, channel) + + q.Must(elastic.NewMatchQuery("event", common.CurrencyEventGameSettle)) + + if uid != nil { + q.Must(elastic.NewMatchQuery("uid", *uid)) + } + + if gameId != nil { + q.Must(elastic.NewMatchQuery("desc.keyword", strconv.Itoa(*gameId))) + } + + if roomId != nil { + q.Must(elastic.NewMatchQuery("room_name.keyword", strconv.Itoa(*roomId))) + } + + if isWin != nil { + if *isWin { + q.Must(elastic.NewRangeQuery("value").Gt(0)) + } else { + q.Must(elastic.NewRangeQuery("value").Lt(0)) + } + } + + return db.ES().Count(common.ESIndexBalance, q) +} + +// 获取控杀次数 +func GetControlTotal(start, end *int64, channel, gameId, roomId, controlType *int, isWin bool) int64 { + q := NewQt(start, end, nil, channel) + + if gameId != nil { + q.Must(elastic.NewMatchQuery("desc.keyword", strconv.Itoa(*gameId))) + } + + if roomId != nil { + q.Must(elastic.NewMatchQuery("room_name.keyword", strconv.Itoa(*roomId))) + } + + if controlType != nil { + q.Must(elastic.NewMatchQuery("control_type", *controlType)) + } + + if isWin { + q.Must(elastic.NewRangeQuery("value").Gt(0)) + } + + q.Must(elastic.NewMatchQuery("event", common.CurrencyEventGameSettle)) + + return db.ES().CountCard(common.ESIndexBalance, "extern.keyword", q) +} + +// 获取玩家赢的游戏场次 +func GetGameProfitByUid(start, end *int64, uid *int, channel *int, gameId *int, roomId *int, isWin *bool) int64 { + q := NewQt(start, end, nil, channel) + + q.Must(elastic.NewMatchQuery("event", common.CurrencyEventGameSettle)) + + if uid != nil { + q.Must(elastic.NewMatchQuery("uid", *uid)) + } + + if gameId != nil { + q.Must(elastic.NewMatchQuery("desc.keyword", strconv.Itoa(*gameId))) + } + + if roomId != nil { + q.Must(elastic.NewMatchQuery("room_name.keyword", strconv.Itoa(*roomId))) + } + + if isWin != nil { + if *isWin { + q.Must(elastic.NewRangeQuery("value").Gt(0)) + } else { + q.Must(elastic.NewRangeQuery("value").Lt(0)) + } + } + + million, err := db.ES().SumBy(common.ESIndexBalance, "value", q) + if err != nil { + log.Error("查询盈亏失败, error : [%s]", err.Error()) + return 0 + } + return int64(million) +} + +// QueryPlayerAllBalance 查询玩家所有流水记录 +func QueryUserBalance(uid *string, page, num int, start, end *int64, gameId, roomId, controlType, channelId, event *int, ret *[]common.CurrencyBalance) (int64, error) { + q := NewQt(start, end, nil, channelId) + if uid != nil { + Id, err := strconv.Atoi(*uid) + if err != nil { + log.Error(err.Error()) + } else { + q.Must(elastic.NewMatchQuery("uid", Id)) + } + } + + if gameId != nil { + q.Must(elastic.NewMatchQuery("desc.keyword", *gameId)) + } + + if roomId != nil { + q.Must(elastic.NewMatchQuery("room_name.keyword", *roomId)) + } + + if controlType != nil { + q.Must(elastic.NewMatchQuery("control_type", *controlType)) + } + + if channelId != nil { + q.Must(elastic.NewMatchQuery("ChannelID", *channelId)) + } + + if event != nil { + q.Must(elastic.NewMatchQuery("event", *event)) + } + + if start != nil { + q.Filter(elastic.NewRangeQuery("time").Gte(*start)) + } + if end != nil { + q.Filter(elastic.NewRangeQuery("time").Lt(*end)) + } + + count, err := db.ES().QueryList(common.ESIndexBalance, page, num, q, ret, "time", false) + if err != nil { + log.Error("err:%v", err) + return 0, err + } + return count, err +} + +// 通过流水表获取玩家人数 +func GetPlayerCountByEvent(start, end *int64, channel *int, event *int) int64 { + q := NewQt(start, end, nil, channel) + + if event != nil { + q.Must(elastic.NewMatchQuery("event", *event)) + } + + return db.ES().CountCard(common.ESIndexBalance, "uid", q) +} + +// 通过流水表获取玩家人数 +func GetPlayerCountByValue(start, end *int64, channel *int, event *int, value int) int64 { + q := NewQt(start, end, nil, channel) + + if event != nil { + q.Must(elastic.NewMatchQuery("event", *event)) + } + + switch value { + case 1: + q.Filter(elastic.NewRangeQuery("value").Lt(1000)) + break + case 2: + q.Filter(elastic.NewRangeQuery("value").Gte(1000)) + q.Filter(elastic.NewRangeQuery("value").Lt(3000)) + break + case 3: + q.Filter(elastic.NewRangeQuery("value").Gte(3000)) + q.Filter(elastic.NewRangeQuery("value").Lt(5000)) + break + case 4: + q.Filter(elastic.NewRangeQuery("value").Gte(5000)) + q.Filter(elastic.NewRangeQuery("value").Lt(10000)) + break + case 5: + q.Filter(elastic.NewRangeQuery("value").Gte(10000)) + break + } + + return db.ES().Count(common.ESIndexBalance, q) +} + +// 获取幸运次数 +func GetShareAmountByBalance(uid *int, channel *int, event int) int64 { + q := NewQt(nil, nil, nil, channel) + if uid != nil { + q.Must(elastic.NewMatchQuery("uid", uid)) + } + + q.Must(elastic.NewMatchQuery("event", event)) + + million, err := db.ES().SumBy(common.ESIndexBalance, "value", q) + if err != nil { + log.Error("查询盈亏失败, error : [%s]", err.Error()) + return 0 + } + return int64(million) +} + +func GetGameCountByUIDs(uids []interface{}, win bool) (ret *common.GroupBuckets) { + q := elastic.NewBoolQuery() + q.Filter(elastic.NewRangeQuery("event").Gte(common.CurrencyEventGameSettle)) + q.Filter(elastic.NewRangeQuery("event").Lt(common.CurrencyEventGameSettle + 1)) + q.Must(elastic.NewTermsQuery("uid", uids...)) + if win { + q.Filter(elastic.NewRangeQuery("value").Gt(0)) + } + ret, _ = db.ES().GroupBy(common.ESIndexBalance, "uid", q, len(uids)) + return +} diff --git a/modules/backend/models/common.go b/modules/backend/models/common.go new file mode 100644 index 0000000..1b0df1b --- /dev/null +++ b/modules/backend/models/common.go @@ -0,0 +1,97 @@ +package models + +import ( + "server/db" + "server/modules/backend/values" + + "github.com/liangdas/mqant/log" + "github.com/olivere/elastic/v7" +) + +func NewQ(start, end *int64, channel ...*int) *elastic.BoolQuery { + q := elastic.NewBoolQuery() + if start != nil { + q.Filter(elastic.NewRangeQuery("Time").Gte(*start)) + } + if end != nil { + q.Filter(elastic.NewRangeQuery("Time").Lt(*end)) + } + getChannelQ(q, channel...) + return q +} + +func NewQt(start, end *int64, channel ...*int) *elastic.BoolQuery { + q := elastic.NewBoolQuery() + if start != nil { + q.Filter(elastic.NewRangeQuery("time").Gte(*start)) + } + if end != nil { + q.Filter(elastic.NewRangeQuery("time").Lt(*end)) + } + if len(channel) > 0 { + var valueArr []interface{} + for _, v := range channel { + if v == nil { + continue + } + valueArr = append(valueArr, *v) + } + if len(valueArr) > 0 { + q.Must(elastic.NewTermsQuery("ChannelID", valueArr...)) + } + } + + return q +} + +func getChannelQ(q *elastic.BoolQuery, channel ...*int) { + if len(channel) == 0 { + return + } + var arr []interface{} + for _, v := range channel { + if v == nil { + continue + } + arr = append(arr, *v) + } + if len(arr) == 0 { + return + } + q.Filter(elastic.NewTermsQuery("Channel", arr...)) +} + +// 获取当前在线人数 +func GetOnline() map[string]interface{} { + res := make(map[string]interface{}) + var OnlineData values.OnlineData + err := db.Redis().HGetAll("online:Total", &OnlineData) + if err != nil { + log.Error(err.Error()) + } + res["Total"] = OnlineData + return res +} + +func GetOnlineByGameId(gameId int) map[string]values.OnlineData { + data := make(map[string]values.OnlineData) + return data +} + +func GetOnlineTotal() values.OnlineData { + var OnlineData values.OnlineData + err := db.Redis().HGetAll("online:Total", &OnlineData) + if err != nil { + log.Error(err.Error()) + } + return OnlineData +} + +func QueryOne(start, end *int64, channel *int, tableName string, table interface{}) error { + q := NewQ(start, end, nil, channel) + err := db.ES().QueryOne(tableName, q, table) + if err != nil { + return err + } + return nil +} diff --git a/modules/backend/models/db.go b/modules/backend/models/db.go new file mode 100644 index 0000000..93ef983 --- /dev/null +++ b/modules/backend/models/db.go @@ -0,0 +1,1677 @@ +package models + +import ( + "fmt" + "server/call" + "server/common" + "server/db" + "server/util" + "time" + + "server/modules/backend/values" + + utils "server/modules/backend/util" + + "github.com/liangdas/mqant/log" + "github.com/olivere/elastic/v7" +) + +/* + 该区域存放mysql查询方法 + + 1.充值金额数据 + 2.退出金额数据 + 3.当天新用户 +*/ +// 通过sql查询充值金额 +func GetAmountTotalBySQL(s, e int64, channel *int) int64 { + var RechargeAmount int64 + + if s == e { + e += 24 * 60 * 60 + } + + amountTotal := `SELECT IFNULL(SUM(amount),0) as RechargeAmount FROM recharge_order WHERE event = %v and status = %v and callback_time >= %d and callback_time < %d ` + if channel != nil { + // 充值总金额 + err := db.Mysql().QueryBySql(fmt.Sprintf(amountTotal+" and channel_id = %v ", common.CurrencyEventReCharge, common.StatusROrderPay, s, e, *channel), &RechargeAmount) + if err != nil { + log.Error("查询支付总金额失败, error : [%s]", err.Error()) + } + } else { + // 充值总金额 + err := db.Mysql().QueryBySql(fmt.Sprintf(amountTotal, common.CurrencyEventReCharge, common.StatusROrderPay, s, e), &RechargeAmount) + if err != nil { + log.Error("查询支付总金额失败, error : [%s]", err.Error()) + } + } + return RechargeAmount +} + +// 通过sql查询代付金额 +func GetWithdrawAmountTotalBySQL(s, e int64, channel *int) int64 { + var amount int64 + if s == e { + e += 24 * 60 * 60 + } + amountTotal := `SELECT IFNULL(SUM(amount),0) as amount FROM recharge_order WHERE event = %v and status = %v and callback_time >= "%d" and callback_time < "%d" ` + if channel != nil { + err := db.Mysql().QueryBySql(fmt.Sprintf(amountTotal+" and channel_id = %v ", common.CurrencyEventWithDraw, common.StatusROrderFinish, s, e, *channel), &amount) + if err != nil { + log.Error("查询支付总金额失败, error : [%s]", err.Error()) + } + } else { + err := db.Mysql().QueryBySql(fmt.Sprintf(amountTotal, common.CurrencyEventWithDraw, common.StatusROrderFinish, s, e), &amount) + if err != nil { + log.Error("查询支付总金额失败, error : [%s]", err.Error()) + } + } + return amount +} + +// 通过出生日期查询用户次日付费金额 +func GetAmountTotalByBirth(channel *int, birth int64) int64 { + var oneDay int64 = 24 * 60 * 60 + s := birth + oneDay + e := birth + oneDay + oneDay + var RechargeAmount int64 + amountTotal := `SELECT SUM(amount) as RechargeAmount FROM recharge_order AS re LEFT JOIN users AS u ON re.uid = u.id WHERE re.event = %v AND re.status = %v AND re.callback_time >= "%d" AND re.callback_time < "%d" AND u.birth >= "%d" AND u.birth < "%d"` + if channel != nil { + // 充值总金额 + err := db.Mysql().QueryBySql(fmt.Sprintf(amountTotal+" AND re.channel_id = %v AND u.channel_id = %v", common.CurrencyEventReCharge, common.StatusROrderPay, s, e, birth, birth+24*60*60, *channel, *channel), &RechargeAmount) + if err != nil { + log.Error("通过出生日期查询用户次日付费金额失败, error : [%s]", err.Error()) + } + } else { + // 充值总金额 + err := db.Mysql().QueryBySql(fmt.Sprintf(amountTotal, common.CurrencyEventReCharge, common.StatusROrderPay, s, e, birth, birth+24*60*60), &RechargeAmount) + if err != nil { + log.Error("通过出生日期查询用户次日付费金额失败, error : [%s]", err.Error()) + } + } + return RechargeAmount +} + +// 通过出生日期查询用户次日付费人数 +func GetPlayerCountByBirth(channel *int, birth int64) int64 { + var oneDay int64 = 24 * 60 * 60 + s := birth + oneDay + e := birth + oneDay + oneDay + + var count int64 + str := `SELECT COUNT(DISTINCT(re.uid)) AS count FROM recharge_order AS re LEFT JOIN users AS u ON re.uid = u.id WHERE re.event = %v AND re.status = %v AND re.callback_time >= "%d" AND re.callback_time < %d AND u.birth >= %d AND u.birth < %d` + if channel != nil { + // 充值总金额 + err := db.Mysql().QueryBySql(fmt.Sprintf(str+" AND re.channel_id = %v AND u.channel_id = %v ", common.CurrencyEventReCharge, common.StatusROrderPay, s, e, birth, s, *channel, *channel), &count) + if err != nil { + log.Error("通过出生日期查询用户次日付费人数失败, error : [%s]", err.Error()) + } + } else { + // 充值总金额 + err := db.Mysql().QueryBySql(fmt.Sprintf(str, common.CurrencyEventReCharge, common.StatusROrderPay, s, e, birth, s), &count) + if err != nil { + log.Error("通过出生日期查询用户次日付费人数失败, error : [%s]", err.Error()) + } + } + return count +} + +// 查询玩家次日存留数量 +func GetNextDayReserved(channel *int, birth int64) int64 { + var count int64 + nextDay := time.Unix(birth, 0).Format("20060102") + str := `SELECT COUNT(*) AS count FROM login_record AS lo LEFT JOIN users AS u ON lo.uid = u.id WHERE u.birth >= "%d" AND u.birth < "%d" AND lo.date = "%s"` + if channel != nil { + err := db.Mysql().QueryBySql(fmt.Sprintf(str+" AND lo.channel_id = %v AND u.channel_id = %v", birth, birth+24*60*60, nextDay, *channel, *channel), &count) + if err != nil { + log.Error("查询玩家次日存留数量失败, error : [%s]", err.Error()) + } + } else { + err := db.Mysql().QueryBySql(fmt.Sprintf(str, birth, birth+24*60*60, nextDay), &count) + if err != nil { + log.Error("查询玩家次日存留数量失败, error : [%s]", err.Error()) + } + } + return count +} + +// 获取当天注册用户数量 按天计算 +func GetNewPlayerCountBySql(channel *int, start, end int64) int64 { + var count int64 + var oneDay int64 = 24 * 60 * 60 + + if end == start { + end += oneDay + } + + for i := start; i < end; i += oneDay { + var temp int64 + str := fmt.Sprintf(" SELECT COUNT(*) AS count FROM users WHERE birth >= %d AND birth < %d ", i, i+oneDay) + if channel != nil { + str += fmt.Sprintf(" AND channel_id = %d", *channel) + } + err := db.Mysql().QueryBySql(str, &temp) + if err != nil { + log.Error("查询当天注册用户数失败, error : [%s]", err.Error()) + } + count += temp + } + + return count +} + +// 获取当天注册用户数量 按天计算 +func GetNewPlayerCountBySqlT(channel *int, start, end int64) int64 { + var count int64 + str := fmt.Sprintf(" SELECT COUNT(*) FROM users WHERE birth >= %d AND birth < %d ", start, end) + if channel != nil { + str += fmt.Sprintf(" AND channel_id = %d", *channel) + } + err := db.Mysql().QueryBySql(str, &count) + if err != nil { + log.Error("查询注册用户数失败, error : [%s]", err.Error()) + } + return count +} + +// 获取当天老用户数 +func GetOldPlayerCountBySql(channel *int, s, e int64) int64 { + var count int64 + var str string + var oneDay int64 = 24 * 60 * 60 + if e == s { + e += oneDay + } + + for i := s; i < e; i += oneDay { + var temp int64 + + date := time.Unix(i, 0).Format("20060102") + if channel != nil { + str = fmt.Sprintf("SELECT count(DISTINCT(uid)) FROM login_record WHERE date = '%s' and channel_id = %d and status = 2", date, *channel) + } else { + str = fmt.Sprintf("SELECT count(DISTINCT(uid)) FROM login_record WHERE date = '%s' and status = 2", date) + } + + err := db.Mysql().QueryBySql(str, &temp) + if err != nil { + log.Error("查询当天老用户数失败, error : [%s]", err.Error()) + } + count += temp + } + + return count +} + +// 查询当天注册用户付费人数 +func GetNewPayCountBySql(channel *int, s, e int64) int64 { + var oneDay int64 = 24 * 60 * 60 + var count int64 + + if e == s { + e += oneDay + } + + for i := s; i < e; i += oneDay { + var temp int64 + + str := `SELECT COUNT(DISTINCT(re.uid)) AS count FROM recharge_order AS re LEFT JOIN users AS u ON re.uid = u.id WHERE re.event = %v AND re.status = %v AND re.callback_time >= %d AND re.callback_time < %d AND u.birth >= %d AND u.birth < %d` + if channel != nil { + // 充值总金额 + err := db.Mysql().QueryBySql(fmt.Sprintf(str+" AND re.channel_id = %v AND u.channel_id = %v", common.CurrencyEventReCharge, common.StatusROrderPay, i, i+oneDay, i, i+oneDay, *channel, *channel), &temp) + if err != nil { + log.Error("查询当日新增付费人数失败, error : [%s]", err.Error()) + } + } else { + // 充值总金额 + err := db.Mysql().QueryBySql(fmt.Sprintf(str, common.CurrencyEventReCharge, common.StatusROrderPay, i, i+oneDay, i, i+oneDay), &temp) + if err != nil { + log.Error("查询当日新增付费人数失败, error : [%s]", err.Error()) + } + } + + count += temp + } + + return count +} + +// 查询当天注册用户付费且成功退出人数 +func GetNewPayWithdrawCountBySql(channel *int, s, e int64) int64 { + var oneDay int64 = 24 * 60 * 60 + var count int64 + + if e == s { + e += oneDay + } + + for i := s; i < e; i += oneDay { + var temp int64 + + str := `SELECT COUNT(DISTINCT(re.uid)) AS count FROM withdraw_order AS re LEFT JOIN users AS u ON re.uid = u.id + WHERE re.event = %v AND re.status = %v AND re.callback_time >= %d AND re.callback_time < %d AND u.birth >= %d AND u.birth < %d` + if channel != nil { + // 充值总金额 + err := db.Mysql().QueryBySql(fmt.Sprintf(str+" AND re.channel_id = %v AND u.channel_id = %v", + common.CurrencyEventWithDraw, common.StatusROrderFinish, i, i+oneDay, i, i+oneDay, *channel, *channel), &temp) + if err != nil { + log.Error("查询当日新增付费人数失败, error : [%s]", err.Error()) + } + } else { + // 充值总金额 + err := db.Mysql().QueryBySql(fmt.Sprintf(str, common.CurrencyEventWithDraw, common.StatusROrderFinish, i, i+oneDay, i, i+oneDay), &temp) + if err != nil { + log.Error("查询当日新增付费人数失败, error : [%s]", err.Error()) + } + } + + count += temp + } + + return count +} + +// 查询当天新增付费总额 +func GetNewPayAmountBySql(channel *int, s, e int64) int64 { + var oneDay int64 = 24 * 60 * 60 + var RechargeAmount int64 + + if e == s { + e += oneDay + } + + for i := s; i < e; i += oneDay { + var temp int64 + amountTotal := `SELECT IFNULL(SUM(amount),0) as RechargeAmount FROM recharge_order AS re LEFT JOIN users AS u ON re.uid = u.id WHERE re.event = %v AND re.status = %v AND re.callback_time >= "%d" AND re.callback_time < "%d" AND u.birth >= "%d" AND u.birth < "%d"` + if channel != nil { + // 充值总金额 + err := db.Mysql().QueryBySql(fmt.Sprintf(amountTotal+" AND re.channel_id = %v AND u.channel_id = %v", common.CurrencyEventReCharge, common.StatusROrderPay, i, i+oneDay, i, i+oneDay, *channel, *channel), &temp) + if err != nil { + log.Error("通过出生日期查询用户次日付费金额失败, error : [%s]", err.Error()) + } + } else { + // 充值总金额 + err := db.Mysql().QueryBySql(fmt.Sprintf(amountTotal, common.CurrencyEventReCharge, common.StatusROrderPay, i, i+oneDay, i, i+oneDay), &temp) + if err != nil { + log.Error("通过出生日期查询用户次日付费金额失败, error : [%s]", err.Error()) + } + } + RechargeAmount += temp + } + + return RechargeAmount +} + +// 查询当天老用户付费总额 +func GetOldPayAmountBySql(channel *int, s, e int64) int64 { + var oneDay int64 = 24 * 60 * 60 + var RechargeAmount int64 + + if e == s { + e += oneDay + } + + for i := s; i < e; i += oneDay { + var temp int64 + + amountTotal := `SELECT IFNULL(SUM(re.amount),0) as RechargeAmount FROM recharge_order AS re LEFT JOIN users AS u ON re.uid = u.id WHERE re.event = %v AND re.status = %v AND re.callback_time >= "%d" AND re.callback_time < "%d" AND u.birth < "%d"` + if channel != nil { + // 充值总金额 + err := db.Mysql().QueryBySql(fmt.Sprintf(amountTotal+" AND re.channel_id = %v AND u.channel_id = %v ", common.CurrencyEventReCharge, common.StatusROrderPay, i, i+oneDay, i, *channel, *channel), &temp) + if err != nil { + log.Error("通过出生日期查询用户次日付费金额失败, error : [%s]", err.Error()) + } + } else { + // 充值总金额 + err := db.Mysql().QueryBySql(fmt.Sprintf(amountTotal, common.CurrencyEventReCharge, common.StatusROrderPay, i, i+oneDay, i), &temp) + if err != nil { + log.Error("通过出生日期查询用户次日付费金额失败, error : [%s]", err.Error()) + } + } + RechargeAmount += temp + } + + return RechargeAmount +} + +// 查询老用户付费人数 +func GetOldPayCountBySql(channel *int, s, e int64) int64 { + var oneDay int64 = 24 * 60 * 60 + var count int64 + + if e == s { + e += oneDay + } + + for i := s; i < e; i += oneDay { + var temp int64 + str := `SELECT COUNT(DISTINCT(re.uid)) AS count FROM recharge_order AS re LEFT JOIN users AS u ON re.uid = u.id WHERE re.event = %v AND re.status = %v AND re.callback_time >= %d AND re.callback_time < %d AND u.birth < %d ` + if channel != nil { + err := db.Mysql().QueryBySql(fmt.Sprintf(str+" AND re.channel_id = %v AND u.channel_id = %v", common.CurrencyEventReCharge, common.StatusROrderPay, i, i+oneDay, i, *channel, *channel), &temp) + if err != nil { + log.Error("查询老用户付费人数失败, error : [%s]", err.Error()) + } + } else { + err := db.Mysql().QueryBySql(fmt.Sprintf(str, common.CurrencyEventReCharge, common.StatusROrderPay, i, i+oneDay, i), &temp) + if err != nil { + log.Error("查询老用户付费人数失败, error : [%s]", err.Error()) + } + } + count += temp + } + return count +} + +// 查询付费人数(新用户 + 老用户付费人数) +func GetPayCountBySql(channel *int, s, e int64) int64 { + var oneDay int64 = 24 * 60 * 60 + var count int64 + + if e == s { + e += oneDay + } + + for i := s; i < e; i += oneDay { + var temp int64 + str := `SELECT COUNT(DISTINCT(uid)) AS count FROM recharge_order WHERE event = %v AND status = %v AND callback_time >= %d AND callback_time < %d ` + if channel != nil { + // 充值总金额 + err := db.Mysql().QueryBySql(fmt.Sprintf(str+" AND channel_id = %v", common.CurrencyEventReCharge, common.StatusROrderPay, i, i+oneDay, *channel), &temp) + if err != nil { + log.Error("查询活跃付费人数失败, error : [%s]", err.Error()) + } + } else { + // 充值总金额 + err := db.Mysql().QueryBySql(fmt.Sprintf(str, common.CurrencyEventReCharge, common.StatusROrderPay, i, i+oneDay), &temp) + if err != nil { + log.Error("查询活跃付费人数失败, error : [%s]", err.Error()) + } + } + count += temp + } + return count +} + +/*---------------------------------退出查询-----------------------------*/ +// 查询老用户退出人数 +func GetOldWithdrawCount(channel *int, s, e int64) int64 { + var oneDay int64 = 24 * 60 * 60 + var count int64 + + if e == s { + e += oneDay + } + + for i := s; i < e; i += oneDay { + var temp int64 + str := `SELECT COUNT(DISTINCT(re.uid)) AS count FROM recharge_order AS re LEFT JOIN users AS u ON re.uid = u.id WHERE re.event = %v AND re.callback_time >= %d AND re.callback_time < %d AND u.birth < %d ` + if channel != nil { + err := db.Mysql().QueryBySql(fmt.Sprintf(str+" AND re.channel_id = %v AND u.channel_id = %v", common.CurrencyEventWithDraw, i, i+oneDay, i, *channel, *channel), &temp) + if err != nil { + log.Error("查询老用户退出人数, error : [%s]", err.Error()) + } + } else { + err := db.Mysql().QueryBySql(fmt.Sprintf(str, common.CurrencyEventWithDraw, i, i+oneDay, i), &temp) + if err != nil { + log.Error("查询老用户退出人数, error : [%s]", err.Error()) + } + } + count += temp + } + return count +} + +// 查询老用户退出次数 +func GetOldWithdrawCount2(channel *int, s, e int64) int64 { + var oneDay int64 = 24 * 60 * 60 + var count int64 + + if e == s { + e += oneDay + } + + for i := s; i < e; i += oneDay { + var temp int64 + str := `SELECT COUNT(re.uid) AS count FROM recharge_order AS re LEFT JOIN users AS u ON re.uid = u.id WHERE re.event = %v AND re.callback_time >= %d AND re.callback_time < %d AND u.birth < %d ` + if channel != nil { + err := db.Mysql().QueryBySql(fmt.Sprintf(str+" AND re.channel_id = %v AND u.channel_id = %v", common.CurrencyEventWithDraw, i, i+oneDay, i, *channel, *channel), &temp) + if err != nil { + log.Error("查询老用户退出人数, error : [%s]", err.Error()) + } + } else { + err := db.Mysql().QueryBySql(fmt.Sprintf(str, common.CurrencyEventWithDraw, i, i+oneDay, i), &temp) + if err != nil { + log.Error("查询老用户退出人数, error : [%s]", err.Error()) + } + } + count += temp + } + return count +} + +// 查询当天注册用户退出人数 +func GetNewWithdrawCount(channel *int, s, e int64) int64 { + var oneDay int64 = 24 * 60 * 60 + var count int64 + + if e == s { + e += oneDay + } + + for i := s; i < e; i += oneDay { + var temp int64 + + str := `SELECT COUNT(DISTINCT(re.uid)) AS count FROM recharge_order AS re LEFT JOIN users AS u ON re.uid = u.id WHERE re.event = %v AND re.callback_time >= %d AND re.callback_time < %d AND u.birth >= %d AND u.birth < %d` + if channel != nil { + err := db.Mysql().QueryBySql(fmt.Sprintf(str+" AND re.channel_id = %v AND u.channel_id = %v", common.CurrencyEventWithDraw, i, i+oneDay, i, i+oneDay, *channel, *channel), &temp) + if err != nil { + log.Error("查询当日新增付费人数失败, error : [%s]", err.Error()) + } + } else { + err := db.Mysql().QueryBySql(fmt.Sprintf(str, common.CurrencyEventWithDraw, i, i+oneDay, i, i+oneDay), &temp) + if err != nil { + log.Error("查询当日新增付费人数失败, error : [%s]", err.Error()) + } + } + + count += temp + } + + return count +} + +// 查询当天注册用户退出次数 +func GetNewWithdrawCount2(channel *int, s, e int64) int64 { + var oneDay int64 = 24 * 60 * 60 + var count int64 + + if e == s { + e += oneDay + } + + for i := s; i < e; i += oneDay { + var temp int64 + + str := `SELECT COUNT(re.uid) AS count FROM recharge_order AS re LEFT JOIN users AS u ON re.uid = u.id WHERE re.event = %v AND re.callback_time >= "%d" AND re.callback_time < "%d" AND u.birth >= "%d" AND u.birth < "%d"` + if channel != nil { + // 充值总金额 + err := db.Mysql().QueryBySql(fmt.Sprintf(str+" AND re.channel_id = %v AND u.channel_id = %v", common.CurrencyEventWithDraw, i, i+oneDay, i, i+oneDay, *channel, *channel), &temp) + if err != nil { + log.Error("查询当日新增付费人数失败, error : [%s]", err.Error()) + } + } else { + // 充值总金额 + err := db.Mysql().QueryBySql(fmt.Sprintf(str, common.CurrencyEventWithDraw, i, i+oneDay, i, i+oneDay), &temp) + if err != nil { + log.Error("查询当日新增付费人数失败, error : [%s]", err.Error()) + } + } + + count += temp + } + + return count +} + +/*----------------------------------------------------------------------*/ + +// 新用户付费人数 +func GetNewAllPayCountBySql(channel *int, s, e int64) int64 { + var oneDay int64 = 24 * 60 * 60 + var count int64 + + if e == s { + e += oneDay + } + + str := `SELECT COUNT(DISTINCT(re.uid)) AS count FROM recharge_order AS re LEFT JOIN users AS u ON re.uid = u.id WHERE re.event = %v AND re.status = %v AND u.birth >= %d AND u.birth < %d` + + for i := s; i < e; i += oneDay { + var temp int64 + if channel != nil { + // 充值总金额 + err := db.Mysql().QueryBySql(fmt.Sprintf(str+" AND re.channel_id = %v AND u.channel_id = %v", common.CurrencyEventReCharge, common.StatusROrderPay, i, i+oneDay, *channel, *channel), &temp) + if err != nil { + log.Error("获取当批次新用户总付费人数失败, error : [%s]", err.Error()) + } + } else { + // 充值总金额 + err := db.Mysql().QueryBySql(fmt.Sprintf(str, common.CurrencyEventReCharge, common.StatusROrderPay, i, i+oneDay), &temp) + if err != nil { + log.Error("获取当批次新用户总付费人数失败, error : [%s]", err.Error()) + } + } + count += temp + } + + return count +} + +// 获取当批次新用户总付费总额 +func GetNewAllPayAmountBySql(channel *int, s int64) int64 { + var oneDay int64 = 24 * 60 * 60 + var RechargeAmount int64 + + amountTotal := `SELECT SUM(amount) as RechargeAmount FROM recharge_order AS re LEFT JOIN users AS u ON re.uid = u.id WHERE re.event = %v AND re.status = %v AND u.birth >= %d AND u.birth < %d` + if channel != nil { + // 充值总金额 + err := db.Mysql().QueryBySql(fmt.Sprintf(amountTotal+" AND re.channel_id = %v AND u.channel_id = %v", common.CurrencyEventReCharge, common.StatusROrderPay, s, s+oneDay, *channel, *channel), &RechargeAmount) + if err != nil { + log.Error("获取当批次新用户总付费总额失败, error : [%s]", err.Error()) + } + } else { + // 充值总金额 + err := db.Mysql().QueryBySql(fmt.Sprintf(amountTotal, common.CurrencyEventReCharge, common.StatusROrderPay, s, s+oneDay), &RechargeAmount) + if err != nil { + log.Error("获取当批次新用户总付费总额失败, error : [%s]", err.Error()) + } + } + + return RechargeAmount +} + +// 获取当批次新用户总退出 +func GetNewAllWithDrawAmountBySql(channel *int, s int64) int64 { + var oneDay int64 = 24 * 60 * 60 + var RechargeAmount int64 + + amountTotal := `SELECT SUM(amount) as RechargeAmount FROM recharge_order AS re LEFT JOIN users AS u ON re.uid = u.id WHERE re.event = %v AND re.status = %v AND u.birth >= %d AND u.birth < %d` + if channel != nil { + // 充值总金额 + err := db.Mysql().QueryBySql(fmt.Sprintf(amountTotal+" AND re.channel_id = %v AND u.channel_id = %v", common.CurrencyEventWithDraw, common.StatusROrderFinish, s, s+oneDay, *channel, *channel), &RechargeAmount) + if err != nil { + log.Error("获取当批次新用户总退出失败, error : [%s]", err.Error()) + } + } else { + // 充值总金额 + err := db.Mysql().QueryBySql(fmt.Sprintf(amountTotal, common.CurrencyEventWithDraw, common.StatusROrderFinish, s, s+oneDay), &RechargeAmount) + if err != nil { + log.Error("获取当批次新用户总退出失败, error : [%s]", err.Error()) + } + } + + return RechargeAmount +} + +// 查询当天创建的充值订单数 +func GetCreateOrderCountBySql(channel *int, s, e int64) int64 { + var oneDay int64 = 24 * 60 * 60 + var count int64 + + if e == s { + e += oneDay + } + + // su := time.Unix(s, 0).Format("2006-01-02") + // eu := time.Unix(e, 0).Format("2006-01-02") + + for i := s; i < e; i += oneDay { + var temp int64 + str := " SELECT COUNT(*) AS count FROM recharge_order WHERE event = %v AND create_time >= %d AND create_time < %d " + if channel != nil { + err := db.Mysql().QueryBySql(fmt.Sprintf(str+" AND channel_id = %v ", common.CurrencyEventReCharge, s, e, *channel), &temp) + if err != nil { + log.Error("查询当天创建的充值订单数, error : [%s]", err.Error()) + } + } else { + err := db.Mysql().QueryBySql(fmt.Sprintf(str, common.CurrencyEventReCharge, s, e), &temp) + if err != nil { + log.Error("查询当天创建的充值订单数, error : [%s]", err.Error()) + } + } + count += temp + } + return count +} + +// 获取成功充值订单数 +func GetSuccessRechargeOrderCountBySql(channel *int, s, e int64) int64 { + var oneDay int64 = 24 * 60 * 60 + var count int64 + + if e == s { + e += oneDay + } + + for i := s; i < e; i += oneDay { + var temp int64 + str := `SELECT COUNT(*) FROM recharge_order WHERE event = %v AND status = %v AND callback_time >= %d AND callback_time < %d` + if channel != nil { + err := db.Mysql().QueryBySql(fmt.Sprintf(str+" AND channel_id = %v ", common.CurrencyEventReCharge, common.StatusROrderPay, i, i+oneDay, *channel), &temp) + if err != nil { + log.Error("获取成功充值订单数失败, error : [%s]", err.Error()) + } + } else { + err := db.Mysql().QueryBySql(fmt.Sprintf(str, common.CurrencyEventReCharge, common.StatusROrderPay, i, i+oneDay), &temp) + if err != nil { + log.Error("获取成功充值订单数失败, error : [%s]", err.Error()) + } + } + count += temp + } + + return count +} + +// 获取新用户充值订单数 +func GetNewRechargeOrderCountBySql(channel *int, s, e int64) int64 { + var oneDay int64 = 24 * 60 * 60 + var count int64 + + if e == s { + e += oneDay + } + + for i := s; i < e; i += oneDay { + var temp int64 + + str := `SELECT COUNT(*) FROM recharge_order AS re LEFT JOIN users AS u ON re.uid = u.id WHERE re.uid = u.id AND re.event = %v AND u.birth >= %d AND u.birth < %d` + if channel != nil { + err := db.Mysql().QueryBySql(fmt.Sprintf(str+" AND re.channel_id = %v AND u.channel_id = %v", common.CurrencyEventReCharge, i, i+oneDay, *channel, *channel), &temp) + if err != nil { + log.Error("获取新用户充值订单数失败, error : [%s]", err.Error()) + } + } else { + err := db.Mysql().QueryBySql(fmt.Sprintf(str, common.CurrencyEventReCharge, i, i+oneDay), &temp) + if err != nil { + log.Error("获取新用户充值订单数失败, error : [%s]", err.Error()) + } + } + count += temp + } + + return count +} + +// 获取新用户充值订单数(成功) +func GetNewRechargeSuccessOrderCountBySql(channel *int, s, e int64) int64 { + var oneDay int64 = 24 * 60 * 60 + var count int64 + + if e == s { + e += oneDay + } + + for i := s; i < e; i += oneDay { + var temp int64 + + str := `SELECT COUNT(*) FROM recharge_order AS re LEFT JOIN users AS u ON re.uid = u.id WHERE re.uid = u.id AND re.event = %v AND re.status = %v AND re.callback_time >= %d AND re.callback_time < %d AND u.birth >= %d AND u.birth < %d` + if channel != nil { + err := db.Mysql().QueryBySql(fmt.Sprintf(str+" AND re.channel_id = %v AND u.channel_id = %v", common.CurrencyEventReCharge, common.StatusROrderPay, i, i+oneDay, i, i+oneDay, *channel, *channel), &temp) + if err != nil { + log.Error("获取新用户充值订单数失败, error : [%s]", err.Error()) + } + } else { + err := db.Mysql().QueryBySql(fmt.Sprintf(str, common.CurrencyEventReCharge, common.StatusROrderPay, i, i+oneDay, i, i+oneDay), &temp) + if err != nil { + log.Error("获取新用户充值订单数失败, error : [%s]", err.Error()) + } + } + count += temp + } + + return count +} + +// 获取创建订单人数 +func GetCreateOrderPlayerCountBySql(channel *int, s, e int64) int64 { + var oneDay int64 = 24 * 60 * 60 + var count int64 + + if e == s { + e += oneDay + } + + // su := time.Unix(s, 0).Format("2006-01-02 15:04:05") + // eu := time.Unix(e, 0).Format("2006-01-02 15:04:05") + + for i := s; i < e; i += oneDay { + var temp int64 + str := " SELECT COUNT(DISTINCT(uid)) FROM recharge_order WHERE event = %v AND create_time >= %d AND create_time < %d " + if channel != nil { + err := db.Mysql().QueryBySql(fmt.Sprintf(str+" AND channel_id = %v ", common.CurrencyEventReCharge, s, e, *channel), &temp) + if err != nil { + log.Error("查询创建充值订单人数, error : [%s]", err.Error()) + } + } else { + err := db.Mysql().QueryBySql(fmt.Sprintf(str, common.CurrencyEventReCharge, s, e), &temp) + if err != nil { + log.Error("查询创建充值订单人数, error : [%s]", err.Error()) + } + } + count += temp + } + return count +} + +// 查询充值成功订单人数(新用户 + 老用户人数) +func GetOrderSuccessPlayerCountBySql(channel *int, s, e int64) int64 { + var oneDay int64 = 24 * 60 * 60 + var count int64 + + if e == s { + e += oneDay + } + + for i := s; i < e; i += oneDay { + var temp int64 + str := " SELECT COUNT(DISTINCT(uid)) FROM recharge_order WHERE event = %v AND status = %v AND callback_time >= %d AND callback_time < %d " + if channel != nil { + err := db.Mysql().QueryBySql(fmt.Sprintf(str+" AND channel_id = %v ", common.CurrencyEventReCharge, common.StatusROrderPay, i, i+oneDay, *channel), &temp) + if err != nil { + log.Error("查询充值成功订单人数失败, error : [%s]", err.Error()) + } + } else { + err := db.Mysql().QueryBySql(fmt.Sprintf(str, common.CurrencyEventReCharge, common.StatusROrderPay, i, i+oneDay), &temp) + if err != nil { + log.Error("查询充值成功订单人数失败, error : [%s]", err.Error()) + } + } + count += temp + } + return count +} + +// 充值成功的新用户人数 +func GetOrderSuccessNewPlayerCountBySql(channel *int, s, e int64) int64 { + var oneDay int64 = 24 * 60 * 60 + var count int64 + + if e == s { + e += oneDay + } + + for i := s; i < e; i += oneDay { + var temp int64 + + str := `SELECT COUNT(DISTINCT(re.uid)) FROM recharge_order AS re LEFT JOIN users AS u ON re.uid = u.id WHERE re.event = %v AND re.status = %v AND re.callback_time >= %d AND re.callback_time < %d AND u.birth >= %d AND u.birth < %d` + if channel != nil { + err := db.Mysql().QueryBySql(fmt.Sprintf(str+" AND re.channel_id = %v AND u.channel_id = %v", common.CurrencyEventReCharge, common.StatusROrderPay, i, i+oneDay, i, i+oneDay, *channel, *channel), &temp) + if err != nil { + log.Error("获取充值成功的新用户人数失败, error : [%s]", err.Error()) + } + } else { + err := db.Mysql().QueryBySql(fmt.Sprintf(str, common.CurrencyEventReCharge, common.StatusROrderPay, i, i+oneDay, i, i+oneDay), &temp) + if err != nil { + log.Error("获取充值成功的新用户人数失败, error : [%s]", err.Error()) + } + } + count += temp + } + + return count +} + +// 未充值新用户的货币总额 +func GetUnRechargePlayerAmount(channel *int, s, e int64) (int64, int64) { + var oneDay int64 = 24 * 60 * 60 + if e == s { + e += oneDay + } + + var CashTotal int64 + var AmountTotal int64 + + amountTotal := `SELECT SUM(cash) AS CashTotal, SUM(cash + bind_cash) AS AmountTotal FROM users AS a LEFT JOIN (SELECT uid, total_recharge FROM recharge_info) AS b ON a.id = b.uid WHERE a.id = b.uid AND b.total_charge > 0 AND birth >= %d AND birth < %d` + for i := s; i < e; i += oneDay { + var res struct { + CashTotal int64 + AmountTotal int64 + } + + if channel != nil { + // 充值总金额 + err := db.Mysql().QueryBySql(fmt.Sprintf(amountTotal+" AND a.channel_id = %v ", s, s+oneDay, *channel), &res) + if err != nil { + log.Error("获取未充值新用户的货币总额失败, error : [%s]", err.Error()) + continue + } + } else { + // 充值总金额 + err := db.Mysql().QueryBySql(fmt.Sprintf(amountTotal, s, s+oneDay), &res) + if err != nil { + log.Error("获取未充值新用户的货币总额失败, error : [%s]", err.Error()) + continue + } + } + CashTotal += res.CashTotal + AmountTotal += res.AmountTotal + } + return CashTotal, AmountTotal +} + +// 获取一天内某个时间段进来的新用户在当天的充值人数 +func GetNewPlayerPayDisCount(channel *int, s, e, end int64) int64 { + var count int64 + str := fmt.Sprintf(" SELECT COUNT(DISTINCT(a.id)) FROM users AS a LEFT JOIN (SELECT DISTINCT(uid) FROM recharge_order"+ + " WHERE `event` = %d AND `status` = %d AND callback_time < %d and callback_time > %d) AS b ON a.id = b.uid WHERE a.id = b.uid AND a.birth >= %d AND a.birth < %d ", + common.CurrencyEventReCharge, + common.StatusROrderPay, + end, + s, + s, + e) + if channel != nil { + // 充值总金额 + err := db.Mysql().QueryBySql(fmt.Sprintf(str+" AND a.channel_id = %v ", *channel), &count) + if err != nil { + log.Error("获取一天内某个时间段进来的新用户在当天的充值人数失败, error : [%s]", err.Error()) + + } + } else { + // 充值总金额 + err := db.Mysql().QueryBySql(str, &count) + if err != nil { + log.Error("获取一天内某个时间段进来的新用户在当天的充值人数失败, error : [%s]", err.Error()) + } + } + return count +} + +// 查询玩家充值总次数 +func GetOneRechargeCountBySql(uid int) int64 { + var temp int64 + str := `SELECT COUNT(*) FROM recharge_order WHERE event = %v AND uid = %d ` + err := db.Mysql().QueryBySql(fmt.Sprintf(str, common.CurrencyEventReCharge, uid), &temp) + if err != nil { + log.Error("获取玩家充值总次数失败, error : [%s]", err.Error()) + } + return temp +} + +// 查询玩家充值成功次数 +func GetOneRechargeSuccessCountBySql(uid int) int64 { + var temp int64 + str := `SELECT COUNT(*) FROM recharge_order WHERE event = %v AND status = %v AND uid = %d ` + err := db.Mysql().QueryBySql(fmt.Sprintf(str, common.CurrencyEventReCharge, common.StatusROrderPay, uid), &temp) + if err != nil { + log.Error("获取玩家成功充值订单数失败, error : [%s]", err.Error()) + } + return temp +} + +// 获取充值人数 +func GetRechargeSuPlayerCountBySql(channel *int, su, eu int64) int64 { + var count int64 + + str := " SELECT COUNT(DISTINCT(a.id)) FROM users AS a LEFT JOIN ( SELECT uid FROM recharge_order WHERE event = %v AND status = %v ) AS b ON a.id = b.uid WHERE a.id = b.uid AND a.birth >= %d AND a.birth < %d " + if channel != nil { + err := db.Mysql().QueryBySql(fmt.Sprintf(str+" AND a.channel_id = %v ", common.CurrencyEventReCharge, common.StatusROrderPay, su, eu, *channel), &count) + if err != nil { + log.Error("查询创建充值订单人数, error : [%s]", err.Error()) + } + } else { + err := db.Mysql().QueryBySql(fmt.Sprintf(str, common.CurrencyEventReCharge, common.StatusROrderPay, su, eu), &count) + if err != nil { + log.Error("查询创建充值订单人数, error : [%s]", err.Error()) + } + } + + return count +} + +// 获取指定时间注册用户在24小时内付费金额 +func Get24HourNewPlayerRechargeAmount(channel *int, s, e int64) int64 { + var count int64 + str := fmt.Sprintf(" SELECT IFNULL(SUM(b.amount),0) FROM users AS a LEFT JOIN (SELECT * FROM recharge_order WHERE `event` = %d AND `status` = %d AND callback_time < %d) AS b ON a.id = b.uid WHERE a.id = b.uid AND a.birth >= %d AND a.birth < %d ", + common.CurrencyEventReCharge, + common.StatusROrderPay, + s+24*60*60, + s, + e) + if channel != nil { + // 充值总金额 + err := db.Mysql().QueryBySql(fmt.Sprintf(str+" AND a.channel_id = %v ", *channel), &count) + if err != nil { + log.Error("获取指定时间注册用户在24小时内付费金额失败, error : [%s]", err.Error()) + + } + } else { + // 充值总金额 + err := db.Mysql().QueryBySql(str, &count) + if err != nil { + log.Error("获取指定时间注册用户在24小时内付费金额失败, error : [%s]", err.Error()) + } + } + return count +} + +// 获取指定时间注册用户付费金额 +func GetNewPlayerRechargeAmount(channel *int, s, e int64) int64 { + var count int64 + str := fmt.Sprintf(" SELECT IFNULL(SUM(b.amount),0) FROM users AS a LEFT JOIN (SELECT * FROM recharge_order WHERE `event` = %d AND `status` = %d ) AS b ON a.id = b.uid WHERE a.id = b.uid AND a.birth >= %d AND a.birth < %d ", + common.CurrencyEventReCharge, + common.StatusROrderPay, + s, + e) + if channel != nil { + // 充值总金额 + err := db.Mysql().QueryBySql(fmt.Sprintf(str+" AND a.channel_id = %v ", *channel), &count) + if err != nil { + log.Error("获取指定时间注册用户在24小时内付费金额失败, error : [%s]", err.Error()) + + } + } else { + // 充值总金额 + err := db.Mysql().QueryBySql(str, &count) + if err != nil { + log.Error("获取指定时间注册用户在24小时内付费金额失败, error : [%s]", err.Error()) + } + } + return count +} + +// 获取指定时间注册用户在24小时内付费人数 +func Get24HourNewPlayerPayCount(channel *int, s, e int64) int64 { + var count int64 + str := fmt.Sprintf("SELECT COUNT(DISTINCT(a.id)) FROM users AS a LEFT JOIN (SELECT DISTINCT(uid) FROM recharge_order WHERE `event` = %d"+ + " AND `status` = %d AND callback_time < %d and callback_time > %d) AS b ON a.id = b.uid WHERE a.id = b.uid AND a.birth >= %d AND a.birth < %d ", + common.CurrencyEventReCharge, + common.StatusROrderPay, + e+24*60*60, + s, + s, + e) + if channel != nil { + // 充值总金额 + err := db.Mysql().QueryBySql(fmt.Sprintf(str+" AND a.channel_id = %v ", *channel), &count) + if err != nil { + log.Error("获取指定时间注册用户在24小时内付费人数失败, error : [%s]", err.Error()) + + } + } else { + // 充值总金额 + err := db.Mysql().QueryBySql(str, &count) + if err != nil { + log.Error("获取指定时间注册用户在24小时内付费人数失败, error : [%s]", err.Error()) + } + } + return count +} + +// 获取一天内某个时间段进来的新用户在当天的充值人数 +func GetNewPlayerPayDisCount1(channel *int, s, e int64) int64 { + var count int64 + str := fmt.Sprintf(" SELECT COUNT(DISTINCT(a.id)) FROM users AS a LEFT JOIN (SELECT DISTINCT(uid) FROM recharge_order WHERE `event` = %d AND `status` = %d) AS b ON a.id = b.uid WHERE a.id = b.uid AND a.birth >= %d AND a.birth < %d ", + common.CurrencyEventReCharge, + common.StatusROrderPay, + s, + e) + if channel != nil { + // 充值总金额 + err := db.Mysql().QueryBySql(fmt.Sprintf(str+" AND a.channel_id = %v ", *channel), &count) + if err != nil { + log.Error("获取一天内某个时间段进来的新用户在当天的充值人数失败, error : [%s]", err.Error()) + + } + } else { + // 充值总金额 + err := db.Mysql().QueryBySql(str, &count) + if err != nil { + log.Error("获取一天内某个时间段进来的新用户在当天的充值人数失败, error : [%s]", err.Error()) + } + } + return count +} + +// 获取新用户充值订单数(成功) +func GetNewRechargeSuccessOrderCountBySql2(channel *int, s, e int64) int64 { + var count int64 + + str := `SELECT COUNT(*) FROM recharge_order AS re LEFT JOIN users AS u ON re.uid = u.id WHERE re.uid = u.id AND re.event = %v AND re.status = %v AND re.callback_time >= %d AND u.birth >= %d AND u.birth < %d` + if channel != nil { + err := db.Mysql().QueryBySql(fmt.Sprintf(str+" AND re.channel_id = %v AND u.channel_id = %v", common.CurrencyEventReCharge, common.StatusROrderPay, s, s, e, *channel, *channel), &count) + if err != nil { + log.Error("获取新用户充值订单数失败, error : [%s]", err.Error()) + } + } else { + err := db.Mysql().QueryBySql(fmt.Sprintf(str, common.CurrencyEventReCharge, common.StatusROrderPay, s, s, e), &count) + if err != nil { + log.Error("获取新用户充值订单数失败, error : [%s]", err.Error()) + } + } + + return count +} + +// 获取新用户充值订单数 +func GetNewRechargeOrderCountBySql2(channel *int, s, e int64) int64 { + var count int64 + + str := `SELECT COUNT(*) FROM recharge_order AS re LEFT JOIN users AS u ON re.uid = u.id WHERE re.uid = u.id AND re.event = %v AND u.birth >= %d AND u.birth < %d` + if channel != nil { + err := db.Mysql().QueryBySql(fmt.Sprintf(str+" AND re.channel_id = %v AND u.channel_id = %v", common.CurrencyEventReCharge, s, e, *channel, *channel), &count) + if err != nil { + log.Error("获取新用户充值订单数失败, error : [%s]", err.Error()) + } + } else { + err := db.Mysql().QueryBySql(fmt.Sprintf(str, common.CurrencyEventReCharge, s, e), &count) + if err != nil { + log.Error("获取新用户充值订单数失败, error : [%s]", err.Error()) + } + } + + return count +} + +func PackChannels(channel ...*int) string { + if len(channel) == 0 { + return "" + } + sql := "" + for i, v := range channel { + if v == nil { + continue + } + if i == 0 { + sql += " and (" + } + sql += fmt.Sprintf(" channel_id = %v", *v) + if i != len(channel)-1 { + sql += " or" + } else { + sql += ")" + } + } + return sql +} + +// 获取当天注册用户数量 按天计算 +func GetNewPlayerCountBySqls(start, end int64, channel ...*int) int64 { + var count int64 + var oneDay int64 = 24 * 60 * 60 + + if end == start { + end += oneDay + } + + for i := start; i < end; i += oneDay { + var temp int64 + str := fmt.Sprintf(" SELECT COUNT(*) AS count FROM users WHERE birth >= %d AND birth < %d ", i, i+oneDay) + if len(channel) > 0 { + str += PackChannels(channel...) + } + err := db.Mysql().QueryBySql(str, &temp) + if err != nil { + log.Error("查询当天注册用户数失败, error : [%s]", err.Error()) + } + count += temp + } + + return count +} + +// 查询当天注册用户付费人数 +func GetNewPayCountBySqls(s, e int64, channel ...*int) int64 { + var oneDay int64 = 24 * 60 * 60 + var count int64 + + if e == s { + e += oneDay + } + + for i := s; i < e; i += oneDay { + var temp int64 + sql := fmt.Sprintf(`SELECT COUNT(u.id) AS count FROM users AS u + inner JOIN + (select distinct(uid) from recharge_order WHERE event = %v AND status = %v AND callback_time >= %d AND callback_time < %d )as re + ON re.uid = u.id + where u.birth >= %d AND u.birth < %d`, common.CurrencyEventReCharge, common.StatusROrderPay, i, i+oneDay, i, i+oneDay) + if len(channel) > 0 { + sql += PackChannels(channel...) + } + err := db.Mysql().QueryBySql(sql, &temp) + if err != nil { + log.Error("查询当日新增付费人数失败, error : [%s]", err.Error()) + } + count += temp + } + + return count +} + +// 查询新用户复充人数 +func GetNewMultiPayCountBySqls(s, e int64, channel ...*int) int64 { + var oneDay int64 = 24 * 60 * 60 + var count int64 + + if e == s { + e += oneDay + } + + for i := s; i < e; i += oneDay { + var temp int64 + sql := fmt.Sprintf(`SELECT count(*) as count from + (SELECT uid as u,count(*) as count FROM recharge_order WHERE event = %d AND status = %d AND callback_time >= %d AND callback_time < %d %s GROUP BY uid)a + INNER JOIN + (SELECT id from users WHERE birth >= %d and birth < %d)b + ON a.u = b.id + WHERE a.count>1`, common.CurrencyEventReCharge, common.StatusROrderPay, i, i+oneDay, PackChannels(channel...), i, i+oneDay) + err := db.Mysql().QueryBySql(sql, &temp) + if err != nil { + log.Error("查询老用户付费人数失败, error : [%s]", err.Error()) + } + count += temp + } + return count +} + +// 查询当天新增付费总额 +func GetNewPayAmountBySqls(s, e int64, t common.CurrencyType, channel ...*int) int64 { + var oneDay int64 = 24 * 60 * 60 + var RechargeAmount int64 + + if e == s { + e += oneDay + } + + for i := s; i < e; i += oneDay { + var temp int64 + sql := fmt.Sprintf(`SELECT IFNULL(SUM(amount),0) as RechargeAmount from recharge_order as re + INNER JOIN + (SELECT id from users where birth >= %d AND birth < %d) as u + ON re.uid = u.id + WHERE event = %d AND status = %d and callback_time >= %d AND callback_time < %d and currency_type = %d`, + i, i+oneDay, common.CurrencyEventReCharge, common.StatusROrderPay, i, i+oneDay, t) + if len(channel) > 0 { + sql += PackChannels(channel...) + } + err := db.Mysql().QueryBySql(sql, &temp) + if err != nil { + log.Error("通过出生日期查询用户次日付费金额失败, error : [%s]", err.Error()) + } + // temp = call.Rate(t, temp) + RechargeAmount += temp + } + + return RechargeAmount +} + +func GetOldPlayerCountBySqls(s, e int64, channel ...*int) int64 { + var count int64 + var oneDay int64 = 24 * 60 * 60 + if e == s { + e += oneDay + } + + for i := s; i < e; i += oneDay { + var temp int64 + + date := time.Unix(i, 0).Format("20060102") + sql := fmt.Sprintf("SELECT count(DISTINCT(uid)) FROM login_record WHERE date = '%s' and status = 2", date) + if len(channel) > 0 { + sql += PackChannels(channel...) + } + + err := db.Mysql().QueryBySql(sql, &temp) + if err != nil { + log.Error("查询当天老用户数失败, error : [%s]", err.Error()) + } + count += temp + } + + return count +} + +// 查询老用户付费人数 +func GetOldPayCountBySqls(s, e int64, channel ...*int) int64 { + var oneDay int64 = 24 * 60 * 60 + var count int64 + + if e == s { + e += oneDay + } + + for i := s; i < e; i += oneDay { + var temp int64 + sql := fmt.Sprintf(`SELECT COUNT(DISTINCT(re.uid)) AS count FROM recharge_order AS re + INNER JOIN + (SELECT id from users WHERE birth < %d)AS u + ON re.uid = u.id + WHERE event = %d AND status = %d AND re.callback_time >= %d AND re.callback_time < %d AND event = %d`, + i, common.CurrencyEventReCharge, common.StatusROrderPay, i, i+oneDay, common.CurrencyEventReCharge) + if len(channel) > 0 { + sql += PackChannels(channel...) + } + err := db.Mysql().QueryBySql(sql, &temp) + if err != nil { + log.Error("查询老用户付费人数失败, error : [%s]", err.Error()) + } + count += temp + } + return count +} + +// 查询老用户复充人数 +func GetOldMultiPayCountBySqls(s, e int64, channel ...*int) int64 { + var oneDay int64 = 24 * 60 * 60 + var count int64 + + if e == s { + e += oneDay + } + + for i := s; i < e; i += oneDay { + var temp int64 + sql := fmt.Sprintf(`SELECT count(*) as count from + (SELECT uid as u,count(*) as count FROM recharge_order WHERE event = %d AND status = %d AND callback_time >= %d AND callback_time < %d %s GROUP BY uid)a + INNER JOIN + (SELECT id from users WHERE birth < %d)b + ON a.u = b.id + WHERE a.count>1`, common.CurrencyEventReCharge, common.StatusROrderPay, i, i+oneDay, PackChannels(channel...), i) + err := db.Mysql().QueryBySql(sql, &temp) + if err != nil { + log.Error("查询老用户付费人数失败, error : [%s]", err.Error()) + } + count += temp + } + return count +} + +// 查询当天老用户付费总额 +func GetOldPayAmountBySqls(s, e int64, t common.CurrencyType, channel ...*int) int64 { + var oneDay int64 = 24 * 60 * 60 + var RechargeAmount int64 + + if e == s { + e += oneDay + } + + for i := s; i < e; i += oneDay { + var temp int64 + sql := fmt.Sprintf(`SELECT IFNULL(SUM(amount),0) as RechargeAmount from recharge_order as re + INNER JOIN + (SELECT id from users where birth < %d) as u + ON re.uid = u.id + WHERE event = %d AND status = %d and callback_time >= %d AND callback_time < %d and currency_type = %d`, + i, common.CurrencyEventReCharge, common.StatusROrderPay, i, i+oneDay, t) + if len(channel) > 0 { + sql += PackChannels(channel...) + } + err := db.Mysql().QueryBySql(sql, &temp) + if err != nil { + log.Error("通过出生日期查询用户次日付费金额失败, error : [%s]", err.Error()) + } + // temp = call.Rate(t, temp) + RechargeAmount += temp + } + + return RechargeAmount +} + +// 通过sql查询充值金额 +func GetAmountTotalBySQLs(s, e int64, channel ...*int) int64 { + var RechargeAmount int64 + + if s == e { + e += 24 * 60 * 60 + } + + sql := fmt.Sprintf(`SELECT IFNULL(SUM(amount),0) as RechargeAmount FROM recharge_order + WHERE event = %v and status = %v and callback_time >= %d and callback_time < %d `, + common.CurrencyEventReCharge, common.StatusROrderPay, s, e, + ) + if len(channel) > 0 { + sql += PackChannels(channel...) + } + err := db.Mysql().QueryBySql(sql, &RechargeAmount) + if err != nil { + log.Error("查询支付总金额失败, error : [%s]", err.Error()) + } + return RechargeAmount +} + +// 通过sql查询代付金额 +func GetWithdrawAmountTotalBySQLs(s, e int64, t common.CurrencyType, channel ...*int) int64 { + var amount int64 + if s == e { + e += 24 * 60 * 60 + } + sql := fmt.Sprintf(`SELECT IFNULL(SUM(amount),0) as amount FROM withdraw_order WHERE event = %v and status = %v and callback_time >= %d and callback_time < %d and currency_type = %d`, + common.CurrencyEventWithDraw, common.StatusROrderFinish, s, e, t) + sql += PackChannels(channel...) + err := db.Mysql().QueryBySql(sql, &amount) + if err != nil { + log.Error("查询支付总金额失败, error : [%s]", err.Error()) + } + return amount +} + +// 获取首充人数 +func GetFirstPayCount(s, e int64, t common.CurrencyType, channel ...*int) int64 { + q := elastic.NewBoolQuery() + q.Filter(elastic.NewTermQuery("Type", 1)) + q.Filter(elastic.NewRangeQuery("FirstAmount").Gt(0)) + q.Filter(elastic.NewRangeQuery("Time").Gte(s)) + q.Filter(elastic.NewRangeQuery("Time").Lt(e)) + cids := []interface{}{} + if len(channel) > 0 { + for _, v := range channel { + if v == nil { + continue + } + cids = append(cids, *v) + } + } + if len(cids) > 0 { + q.Filter(elastic.NewTermsQuery("Channel", cids...)) + } + return db.ES().CountCard(common.ESIndexBackPlayerPayData, "UID", q) +} + +func GetGameInOut(su, eu int64, opt int, channel ...*int) int64 { + q := elastic.NewBoolQuery() + q.Filter(elastic.NewRangeQuery("time").Gte(su)) + q.Filter(elastic.NewRangeQuery("time").Lt(eu)) + cids := []interface{}{} + if len(channel) > 0 { + for _, v := range channel { + if v == nil { + continue + } + cids = append(cids, *v) + } + } + if len(cids) > 0 { + q.Filter(elastic.NewTermsQuery("channel_id", cids...)) + } + if opt == 1 { + q.Filter(elastic.NewTermsQuery("event", common.GetGameInEvents()...)) + } else { + q.Filter(elastic.NewTermsQuery("event", common.GetGameOutEvents()...)) + } + return util.Abs(db.ES().SumByInt64(common.ESIndexBalance, "value", q)) +} + +func GetOrderCount(su, eu int64, status, opt int, channel ...*int) int64 { + condi := "" + var model interface{} + if opt == 1 { + model = &common.RechargeOrder{} + condi = fmt.Sprintf("event = %d ", common.CurrencyEventReCharge) + } else { + model = &common.WithdrawOrder{} + condi = fmt.Sprintf("event = %d ", common.CurrencyEventWithDraw) + } + if status == common.StatusROrderCreate { + condi += fmt.Sprintf(" and create_time >= %d and create_time < %d", su, eu) + } else { + condi += fmt.Sprintf(" and callback_time >= %d and callback_time < %d and status = %d", su, eu, status) + } + condi += PackChannels(channel...) + // if channel != nil { + // condi += fmt.Sprintf(" and channel_id = %d", *channel) + // } + return db.Mysql().Count(model, condi) +} + +// 链接sql查询语句 +func LinkMysqlCondi(sql []string) (str string) { + if len(sql) == 0 { + return + } + for i, v := range sql { + if i == 0 { + str = "where " + v + } else { + str += " and " + v + } + } + return +} + +// opt 1总下注前n 2总盈利前n 3总亏损前n +func GetTopGames(su, eu int64, opt, num int, channel ...*int) []*values.OneGameData { + sql := "" + switch opt { + case 1: + sql = fmt.Sprintf(`SELECT provider as Provider,game_id as GameID,sum(amount) as Amount from provider_bet_record WHERE time>=%d and time <%d and (type = %d or type = %d or type = %d)`, + su, eu, common.SessionTypeBet, common.SessionTypeSettle, common.SessionTypeBuyIn) + sql += PackChannels(channel...) + " group by game_id order by amount desc" + case 2: + sql = fmt.Sprintf(`SELECT provider as Provider,game_id as GameID,sum(amount - settle) as Amount from provider_bet_record WHERE time>=%d and time <%d`, su, eu) + sql += PackChannels(channel...) + " group by game_id order by amount desc" + case 3: + sql = fmt.Sprintf(`SELECT provider as Provider,game_id as GameID,sum(settle - amount) as Amount from provider_bet_record WHERE time>=%d and time <%d`, su, eu) + sql += PackChannels(channel...) + " group by game_id order by amount desc" + default: + return nil + } + sql += fmt.Sprintf(" limit %d", num) + + ret := []*values.OneGameData{} + db.Mysql().QueryBySql(sql, &ret) + + index := -1 + for i, v := range ret { + game := call.GetConfigGameListByID(v.Provider, v.GameID) + if game != nil { + v.Name = game.Name + } + // 去掉amount<0的部分 + if opt > 1 { + if v.Amount < 0 { + index = i + break + } + } + } + if index >= 0 { + ret = ret[:index] + } + + return ret +} + +type ActivityDataGroupSumBuckets struct { + Buckets []struct { + Key interface{} + Doc_count int + Amount struct { + Value float64 + } + } +} + +func GetActivityData(su, eu int64, channel ...*int) (ret []*values.RealActivityData) { + q := NewQ(&su, &eu) + getChannelQ(q, channel...) + q.Filter(elastic.NewTermQuery("Type", common.ActivityDataClick)) + esRet, _ := db.ES().GroupByCard(common.ESIndexBackActivity, "ActivityID", "UID", q, 5000) + for _, v := range esRet.Buckets { + ret = append(ret, &values.RealActivityData{ + ActivityID: util.GetInt(v.Key), + ClickCount: util.GetInt64(v.Sub.Value), + }) + } + // fmt.Println("1:", esRet) + + q = NewQ(&su, &eu) + getChannelQ(q, channel...) + q.Filter(elastic.NewTermQuery("Type", common.ActivityDataJoin)) + esRet, _ = db.ES().GroupByCard(common.ESIndexBackActivity, "ActivityID", "UID", q, 5000) + for _, v := range esRet.Buckets { + find := false + for _, k := range ret { + if k.ActivityID == util.GetInt(v.Key) { + k.JoinCount = util.GetInt64(v.Sub.Value) + find = true + break + } + } + if !find { + ret = append(ret, &values.RealActivityData{ + ActivityID: util.GetInt(v.Key), + JoinCount: util.GetInt64(v.Sub.Value), + }) + } + } + // fmt.Println("2:", esRet) + + q = NewQ(&su, &eu) + // q = elastic.NewBoolQuery() + // q.Filter(elastic.NewRangeQuery("Time").Gte(1713404103)) + // q.Filter(elastic.NewTermQuery("ActivityID", 10006)) + getChannelQ(q, channel...) + sumBucket := ActivityDataGroupSumBuckets{} + db.ES().GroupSumBy(common.ESIndexBackActivity, "ActivityID", q, &sumBucket, "", false, 5000, "Amount") + for _, v := range sumBucket.Buckets { + find := false + for _, k := range ret { + if k.ActivityID == util.GetInt(v.Key) { + k.Reward = util.GetInt64(v.Amount.Value) + find = true + break + } + } + if !find { + ret = append(ret, &values.RealActivityData{ + ActivityID: util.GetInt(v.Key), + Reward: util.GetInt64(v.Amount.Value), + }) + } + } + // fmt.Println("3:", sumBucket) + return ret +} + +// 查询当天注册用户付费次数 +func GetNewPayCounts(s, e int64, channel ...*int) (count int64) { + channelStr := PackChannels(channel...) + str := fmt.Sprintf(`SELECT count(*) as count FROM + (SELECT id FROM users WHERE birth>= %d and birth < %d %s)a + INNER JOIN + (SELECT uid from recharge_order WHERE status = %d and event = %d and callback_time >= %d and callback_time < %d %s)b + on a.id = b.uid`, s, e, channelStr, common.StatusROrderPay, common.CurrencyEventReCharge, s, e, channelStr) + // 充值次数 + db.Mysql().QueryBySql(str, &count) + return +} + +// 查询当天老用户付费次数 +func GetOldPayCounts(s, e int64, channel ...*int) (count int64) { + channelStr := PackChannels(channel...) + str := fmt.Sprintf(`SELECT count(*) as count FROM + (SELECT id FROM users WHERE birth < %d %s)a + INNER JOIN + (SELECT uid from recharge_order WHERE status = %d and event = %d and callback_time >= %d and callback_time < %d %s)b + on a.id = b.uid`, s, channelStr, common.StatusROrderPay, common.CurrencyEventReCharge, s, e, channelStr) + // 充值次数 + db.Mysql().QueryBySql(str, &count) + return +} + +// 查询当天注册用户赠送次数 +func GetNewWithdrawCounts(s, e int64, channel ...*int) (count int64) { + channelStr := PackChannels(channel...) + str := fmt.Sprintf(`SELECT count(*) as count FROM + (SELECT id FROM users WHERE birth>= %d and birth < %d %s)a + INNER JOIN + (SELECT uid from withdraw_order WHERE status = %d and callback_time >= %d and callback_time < %d %s)b + on a.id = b.uid`, s, e, channelStr, common.StatusROrderFinish, s, e, channelStr) + // 充值次数 + db.Mysql().QueryBySql(str, &count) + return +} + +// 查询当天注册用户赠送人数 +func GetNewWithdrawPlayerNum(s, e int64, channel ...*int) (count int64) { + channelStr := PackChannels(channel...) + str := fmt.Sprintf(`SELECT count(*) as count FROM + (SELECT id FROM users WHERE birth>= %d and birth < %d %s)a + INNER JOIN + (SELECT DISTINCT(uid) from withdraw_order WHERE status = %d and callback_time >= %d and callback_time < %d %s)b + on a.id = b.uid`, s, e, channelStr, common.StatusROrderFinish, s, e, channelStr) + // 充值次数 + db.Mysql().QueryBySql(str, &count) + return +} + +// 查询当天注册用户赠送总额 +func GetNewWithdrawAmount(s, e int64, channel ...*int) (count int64) { + channelStr := PackChannels(channel...) + str := fmt.Sprintf(`SELECT IFNULL(sum(b.amount),0) as count FROM + (SELECT id FROM users WHERE birth>= %d and birth < %d %s)a + INNER JOIN + (SELECT uid,amount from withdraw_order WHERE status = %d and callback_time >= %d and callback_time < %d %s)b + on a.id = b.uid`, s, e, channelStr, common.StatusROrderFinish, s, e, channelStr) + // 充值次数 + db.Mysql().QueryBySql(str, &count) + return +} + +// 查询当天老用户赠送次数 +func GetOldWithdrawCounts(s, e int64, channel ...*int) (count int64) { + channelStr := PackChannels(channel...) + str := fmt.Sprintf(`SELECT count(*) as count FROM + (SELECT id FROM users WHERE birth < %d %s)a + INNER JOIN + (SELECT uid from withdraw_order WHERE status = %d and callback_time >= %d and callback_time < %d %s)b + on a.id = b.uid`, s, channelStr, common.StatusROrderFinish, s, e, channelStr) + // 充值次数 + db.Mysql().QueryBySql(str, &count) + return +} + +// 查询当天老用户赠送人数 +func GetOldWithdrawplayerNum(s, e int64, channel ...*int) (count int64) { + channelStr := PackChannels(channel...) + str := fmt.Sprintf(`SELECT count(*) as count FROM + (SELECT id FROM users WHERE birth < %d %s)a + INNER JOIN + (SELECT DISTINCT(uid) from withdraw_order WHERE status = %d and callback_time >= %d and callback_time < %d %s)b + on a.id = b.uid`, s, channelStr, common.StatusROrderFinish, s, e, channelStr) + // 充值次数 + db.Mysql().QueryBySql(str, &count) + return +} + +// 查询当天老用户赠送总数 +func GetOldWithdrawAmount(s, e int64, channel ...*int) (count int64) { + channelStr := PackChannels(channel...) + str := fmt.Sprintf(`SELECT IFNULL(sum(b.amount),0) as count FROM + (SELECT id FROM users WHERE birth < %d %s)a + INNER JOIN + (SELECT uid,amount from withdraw_order WHERE status = %d and callback_time >= %d and callback_time < %d %s)b + on a.id = b.uid`, s, channelStr, common.StatusROrderFinish, s, e, channelStr) + // 充值次数 + db.Mysql().QueryBySql(str, &count) + return +} + +// 获取某日的次日留存 opt 1活跃留存 2总付费留存 3新增付费留存 +func GetKeepPer(opt int, date string, su int64, channel ...*int) string { + // 前一天 + yes := time.Unix(su, 0).AddDate(0, 0, -1).Format("20060102") + channelStr := PackChannels(channel...) + var sql, totalSql string + var total, count int64 + if opt == 1 { + totalSql = fmt.Sprintf(`SELECT count(*) FROM login_record WHERE date = '%v'%v`, yes, channelStr) + sql = fmt.Sprintf(`SELECT count(a.uid) FROM + (SELECT uid FROM login_record WHERE date = '%v'%v)a + INNER JOIN + (SELECT uid FROM login_record WHERE date = '%v'%v)b + on a.uid = b.uid `, + yes, channelStr, date, channelStr) + } else if opt == 2 { + totalSql = fmt.Sprintf(`SELECT count(*) FROM login_record WHERE is_recharge = 2 and date = '%v'%v`, yes, channelStr) + sql = fmt.Sprintf(`SELECT count(a.uid) FROM + (SELECT uid FROM login_record WHERE is_recharge = 2 and date = '%v'%v)a + INNER JOIN + (SELECT uid FROM login_record WHERE is_recharge = 2 and date = '%v'%v)b + on a.uid = b.uid `, + yes, channelStr, date, channelStr) + } else { + totalSql = fmt.Sprintf(`SELECT count(*) FROM login_record WHERE is_recharge = 2 and status = 1 and date = '%v'%v`, yes, channelStr) + sql = fmt.Sprintf(`SELECT count(a.uid) FROM + (SELECT uid FROM login_record WHERE is_recharge = 2 and status = 1 and date = '%v'%v)a + INNER JOIN + (SELECT uid FROM login_record WHERE is_recharge = 2 and date = '%v'%v)b + on a.uid = b.uid `, + yes, channelStr, date, channelStr) + } + db.Mysql().QueryBySql(sql, &count) + db.Mysql().QueryBySql(totalSql, &total) + return utils.GetPer(count, total) +} diff --git a/modules/backend/models/output.go b/modules/backend/models/output.go new file mode 100644 index 0000000..c47469b --- /dev/null +++ b/modules/backend/models/output.go @@ -0,0 +1,32 @@ +package models + +import ( + "server/common" + "server/db" + "server/util" + + "github.com/olivere/elastic/v7" +) + +func GetOutputData(su, eu int64, cid, uid int) map[string]string { + ret := map[string]string{} + q := elastic.NewBoolQuery() + if su > 0 { + q.Filter(elastic.NewRangeQuery("time").Gte(su)) + } + if eu > 0 { + q.Filter(elastic.NewRangeQuery("time").Lt(eu)) + } + if cid > 0 { + q.Filter(elastic.NewTermsQuery("channel_id", cid)) + } + if uid > 0 { + q.Filter(elastic.NewTermQuery("uid", uid)) + } + buk := &common.GroupSumBuckets{} + db.ES().GroupSumBy(common.ESIndexBalance, "event", q, buk, "", false, 0, "value") + for _, v := range buk.Buckets { + ret[common.GetCurrencyTypeName(common.CurrencyEvent(util.GetInt(v.Key)))] = util.RoundFloat(v.Value.Value/common.DecimalDigits, 2) + } + return ret +} diff --git a/modules/backend/module.go b/modules/backend/module.go new file mode 100644 index 0000000..e9b2727 --- /dev/null +++ b/modules/backend/module.go @@ -0,0 +1,112 @@ +package backend + +import ( + "context" + "net/http" + "server/call" + "server/config" + "server/db" + edb "server/db/es" + mdb "server/db/mysql" + rdb "server/db/redis" + "server/modules/backend/bdb" + "server/modules/backend/routers" + "time" + + "github.com/liangdas/mqant/conf" + "github.com/liangdas/mqant/log" + "github.com/liangdas/mqant/module" + basemodule "github.com/liangdas/mqant/module/base" +) + +var ( + Module = func() module.Module { + this := new(Backend) + return this + } + BackDB = new(mdb.MysqlClient) +) + +type Backend struct { + basemodule.BaseModule + httpSvr *http.Server + addr string +} + +func (b *Backend) GetType() string { + //很关键,需要与配置文件中的Module配置对应 + return "backend" +} +func (b *Backend) Version() string { + //可以在监控时了解代码版本 + return "1.0.0" +} +func (b *Backend) OnInit(app module.App, settings *conf.ModuleSettings) { + b.BaseModule.OnInit(b, app, settings) + + call.NewCaller(b) + + db.InitDB(&mdb.MysqlClient{}, &rdb.RedisClient{}, &edb.EsClient{}) + + bdb.InitMysql() + + // 自动初始化后台数据库 + bdb.MigrateDB() + // 自动初始化游戏服数据库 + MigrateDB() + + b.addr = config.GetConfig().Backend.Addr + + call.InitReload(b.App.Transport()) + + // 开启定时查询 + StartTimer() + + loadConfig() +} + +func (b *Backend) startHttpServer() { + router := routers.SetUpRouter() + srv := &http.Server{ + Addr: b.addr, + Handler: router, + } + + go func() { + if err := srv.ListenAndServe(); err != nil { + panic(err) + } + }() + // returning reference so caller can call Shutdown() + b.httpSvr = srv +} + +func (b *Backend) Run(closeSig chan bool) { + log.Info("backend: starting HTTP server :%s", b.addr) + call.InitTimeWheel(closeSig) + call.InitWarn(b.App.Transport()) + b.startHttpServer() + <-closeSig + log.Info("backend: stopping HTTP server") + +} + +func (b *Backend) OnDestroy() { + // The context is used to inform the server it has 5 seconds to finish + // the request it is currently handling + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + // now close the server gracefully ("shutdown") + // timeout could be given instead of nil as a https://golang.org/pkg/context/ + if err := b.httpSvr.Shutdown(ctx); err != nil { + log.Error("OnDestroy Backend Shutdown error:%v", err) + } + + log.Info("Backend: done. exiting") + + //一定别忘了继承 + b.BaseModule.OnDestroy() + + log.Info("Backend 模块已销毁") +} diff --git a/modules/backend/routers/routers.go b/modules/backend/routers/routers.go new file mode 100644 index 0000000..41ae446 --- /dev/null +++ b/modules/backend/routers/routers.go @@ -0,0 +1,49 @@ +package routers + +import ( + "runtime" + sc "server/common" + "server/config" + "server/modules/backend/middleware" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +func SetUpRouter() *gin.Engine { + release := config.GetBase().Release + if release { + gin.SetMode(gin.ReleaseMode) + // 禁用控制台颜色 + gin.DisableConsoleColor() + } else { + gin.SetMode(gin.DebugMode) + } + r := gin.New() + // 跨域处理 + r.Use(middleware.CrosHandler()) + r.Use(middleware.TokenMiddleWare()) + r.Use(middleware.PowerMiddleWare()) + r.Use(gin.RecoveryWithWriter(log.LogBeego(), func(c *gin.Context, err interface{}) { + buf := make([]byte, 1024) + runtime.Stack(buf, false) + log.Error("panic(%+v), stack:\n%s", err, string(buf)) + })) + + r.GET("/", sc.HealthCheck) + gmHandle(r) + account(r) + mail(r) + common(r) + sys(r) + statistics(r) + examine(r) + power(r) + warn(r) + guser(r) + profit(r) + blockpay(r) + output(r) + firstPage(r) + return r +} diff --git a/modules/backend/routers/routers_account.go b/modules/backend/routers/routers_account.go new file mode 100644 index 0000000..361ebab --- /dev/null +++ b/modules/backend/routers/routers_account.go @@ -0,0 +1,11 @@ +// 账号相关的接口 +package routers + +import ( + "github.com/gin-gonic/gin" + handler "server/modules/backend/handler/account" +) + +func account(e *gin.Engine) { + e.POST("/account/login", handler.Login) +} diff --git a/modules/backend/routers/routers_blockpay.go b/modules/backend/routers/routers_blockpay.go new file mode 100644 index 0000000..30a1a99 --- /dev/null +++ b/modules/backend/routers/routers_blockpay.go @@ -0,0 +1,19 @@ +// 账号相关的接口 +package routers + +import ( + handler "server/modules/backend/handler/blockpay" + + "github.com/gin-gonic/gin" +) + +func blockpay(e *gin.Engine) { + e.POST("/blockpay/order/query", handler.QueryOrder) + e.GET("/blockpay/addrs/query", handler.LocalAddrs) + e.POST("/blockpay/addrs/add", handler.AddAddrs) + e.POST("/blockpay/addrs/remove", handler.RemoveAddrs) + e.POST("/blockpay/addrs/transfer", handler.Transfer) + e.POST("/blockpay/addrs/transferList", handler.TransferList) + e.POST("/blockpay/addrs/getPlayerUsdts", handler.GetPlayerUsdts) + e.POST("/blockpay/addrs/scanPlayerWallet", handler.ScanPlayerWallet) +} diff --git a/modules/backend/routers/routers_common.go b/modules/backend/routers/routers_common.go new file mode 100644 index 0000000..6d48479 --- /dev/null +++ b/modules/backend/routers/routers_common.go @@ -0,0 +1,17 @@ +package routers + +import ( + handler "server/modules/backend/handler/common" + + "github.com/gin-gonic/gin" +) + +func common(e *gin.Engine) { + // e.GET("/common/goodsList", handler.GoodList) + e.GET("/common/productList", handler.ProductList) + // e.GET("/common/gameList", handler.GamesList) + e.GET("/common/channelList", handler.ChannelList) + e.GET("/common/userInfo", handler.UserInfo) + // e.GET("/common/getGameInfo", handler.GameInfo) + e.GET("/common/accountList", handler.AccountList) +} diff --git a/modules/backend/routers/routers_examine.go b/modules/backend/routers/routers_examine.go new file mode 100644 index 0000000..0b7673d --- /dev/null +++ b/modules/backend/routers/routers_examine.go @@ -0,0 +1,20 @@ +package routers + +import ( + handler "server/modules/backend/handler/examine" + + "github.com/gin-gonic/gin" +) + +func examine(e *gin.Engine) { + e.POST("/examine/pay/callback", handler.PayCallback) + e.POST("/examine/withdraw/list", handler.WithdrawList) + e.POST("/examine/withdraw/do", handler.WithdrawExamine) + e.POST("/examine/withdraw/return", handler.WithdrawReturn) + e.POST("/examine/share/list", handler.ShareOrderList) + e.POST("/examine/share/do", handler.ShareOrderExamine) + + // e.POST("/examine/tpData", handler.TpData) + + // e.POST("/examine/tpStatistic", handler.TpStatistic) +} diff --git a/modules/backend/routers/routers_firstPage.go b/modules/backend/routers/routers_firstPage.go new file mode 100644 index 0000000..465566b --- /dev/null +++ b/modules/backend/routers/routers_firstPage.go @@ -0,0 +1,13 @@ +// 账号相关的接口 +package routers + +import ( + "server/modules/backend/handler" + + "github.com/gin-gonic/gin" +) + +func firstPage(e *gin.Engine) { + e.POST("/firstPage/real", handler.FirstPageRealData) + e.POST("/firstPage/data", handler.FirstPageData) +} diff --git a/modules/backend/routers/routers_gm.go b/modules/backend/routers/routers_gm.go new file mode 100644 index 0000000..cfafae0 --- /dev/null +++ b/modules/backend/routers/routers_gm.go @@ -0,0 +1,29 @@ +package routers + +import ( + "server/config" + handler "server/modules/backend/handler/gm" + + "github.com/gin-gonic/gin" +) + +func gmHandle(e *gin.Engine) { + release := config.GetBase().Release + e.POST("/gm/getPhoneCode", handler.GMGetPhoneCode) + e.GET("/gm/platform/list", handler.ConfigPlatformList) + e.POST("/gm/platform/edit", handler.ConfigPlatformEdit) + e.GET("/gm/control/*action", handler.ConfigControlCommon) + e.POST("/gm/control/*action", handler.ConfigControlCommon) + e.POST("/gm/payWeight/*action", handler.ConfigPayWeight) + e.POST("/gm/withdrawWeight/*action", handler.ConfigWithdrawWeight) + e.POST("/gm/serverVersion/reload", handler.ReloadServerVersion) + if !release { + e.POST("/gm/addCoin", handler.GMAddCoin) + } + e.POST("/gm/recharge", handler.GMRecharge) + e.POST("/gm/bindPhone", handler.GMBindPhone) + e.POST("/gm/unbindPhone", handler.GMUnBindPhone) + + // 后台强制玩家掉线 + e.POST("/gm/player/OptPlayerDisconnect", handler.OptPlayerDisconnect) +} diff --git a/modules/backend/routers/routers_guser.go b/modules/backend/routers/routers_guser.go new file mode 100644 index 0000000..bed36fe --- /dev/null +++ b/modules/backend/routers/routers_guser.go @@ -0,0 +1,41 @@ +package routers + +import ( + handler "server/modules/backend/handler/guser" + + "github.com/gin-gonic/gin" +) + +func guser(e *gin.Engine) { + e.POST("/guser/list", handler.GetGameUserList) + e.POST("/guser/activeUserList", handler.ActiveUserList) + + e.POST("/guser/info", handler.GetGameUserInfo) + e.POST("/guser/allBalance", handler.GetGameUserAllBalance) + e.POST("/guser/playData", handler.GetGameUserPlayData) + // e.POST("/guser/playDetail", handler.GetGameUserPlayDetail) + e.POST("/guser/rechargeHistory", handler.GetGameUserRechargeHistory) + e.POST("/guser/withdrawHistory", handler.GetGameUserWithdrawHistory) + e.POST("/guser/controlBalance", handler.GetGameUserControlBalance) + // e.POST("/guser/controlCardData", handler.ControlCardData) + e.POST("/guser/editGold", handler.EditGameUserGold) + e.POST("/guser/editStatus", handler.EditGameUserStatus) + e.POST("/guser/addUserTag", handler.AddUserTag) + e.POST("/guser/deleteUserTag", handler.AddUserTag) + e.POST("/guser/addUserBlackList", handler.AddUserBlackList) + e.POST("/guser/shareData", handler.ShareData) + + // 流失用户数据接口 + e.POST("/statistics/lostPlayerData", handler.LostPlayerData) + e.POST("/statistics/lostUserData", handler.LostUserData) + e.POST("/statistics/lostUserDetail", handler.LostUserDetail) + + // 大R用户 + e.POST("/statistics/bigRUserData", handler.BigRUserData) + + // 充值排行榜 + e.POST("/statistics/rechargeRank", handler.RechargeRank) + + // 封禁用户 + e.POST("/statistics/banUserList", handler.BanUserList) +} diff --git a/modules/backend/routers/routers_mail.go b/modules/backend/routers/routers_mail.go new file mode 100644 index 0000000..42ca781 --- /dev/null +++ b/modules/backend/routers/routers_mail.go @@ -0,0 +1,13 @@ +package routers + +import ( + "github.com/gin-gonic/gin" + handler "server/modules/backend/handler/mail" +) + +func mail(e *gin.Engine) { + e.POST("/mail/draftList", handler.DraftList) + e.POST("/mail/draftCreate", handler.DraftCreate) + e.POST("/mail/draftEdit", handler.DraftEdit) + e.POST("/mail/draftOpt", handler.DraftOpt) +} diff --git a/modules/backend/routers/routers_output.go b/modules/backend/routers/routers_output.go new file mode 100644 index 0000000..0f7c08c --- /dev/null +++ b/modules/backend/routers/routers_output.go @@ -0,0 +1,13 @@ +package routers + +import ( + handler "server/modules/backend/handler/statistics" + + "github.com/gin-gonic/gin" +) + +func output(e *gin.Engine) { + e.POST("/output/all", handler.OutputData) + e.POST("/output/provider", handler.OutputDataProvider) + e.POST("/output/games", handler.OutputDataGames) +} diff --git a/modules/backend/routers/routers_power.go b/modules/backend/routers/routers_power.go new file mode 100644 index 0000000..4fce938 --- /dev/null +++ b/modules/backend/routers/routers_power.go @@ -0,0 +1,16 @@ +package routers + +import ( + "github.com/gin-gonic/gin" + handler "server/modules/backend/handler/power" +) + +func power(e *gin.Engine) { + e.GET("/power/user/list", handler.UserList) + e.POST("/power/user/add", handler.AddUser) + e.POST("/power/user/edit", handler.EditUserPower) + e.POST("/power/user/del", handler.DelUser) + e.GET("/power/role/list", handler.RoleList) + e.POST("/power/role/add", handler.AddRole) + e.POST("/power/role/edit", handler.EditRole) +} diff --git a/modules/backend/routers/routers_statistics.go b/modules/backend/routers/routers_statistics.go new file mode 100644 index 0000000..2c369be --- /dev/null +++ b/modules/backend/routers/routers_statistics.go @@ -0,0 +1,67 @@ +package routers + +import ( + handler "server/modules/backend/handler/statistics" + + "github.com/gin-gonic/gin" +) + +func statistics(e *gin.Engine) { + e.POST("/statistics/reviewData", handler.ReviewData) + e.POST("/statistics/reviewDataEdit", handler.ReviewDataEdit) + + e.POST("/statistics/reviewAppSummary", handler.ReviewAppSummary) + e.POST("/statistics/platformData", handler.PlatformData) + + e.POST("/statistics/reviewChannelData", handler.ReviewChannelData) + e.POST("/statistics/reviewLoginWay", handler.ReviewLoginWay) + e.POST("/statistics/reviewWithdarwData", handler.ReviewWithdrawData) + e.POST("/statistics/realData", handler.RealData) + e.POST("/statistics/realDataBalance", handler.RealDataBalance) + e.POST("/statistics/realDataBalanceRoomGame", handler.RealDataBalanceRoomGame) + e.POST("/statistics/realDataBalanceMillionGame", handler.RealDataBalanceMillionGame) + // e.POST("/statistics/realDataBalanceAndarbahar", handler.RealDataBalanceAndarbahar) + e.POST("/statistics/newPlayerData", handler.NewPlayerData) + e.POST("/statistics/output", handler.NewPlayerData) + // e.POST("/statistics/gameData", handler.GameData) + // e.POST("/statistics/activePlayerData", handler.ActivePlayerData) + // e.POST("/statistics/financialData", handler.FinancialData) + // e.POST("/statistics/financialData/robotBalanceDeatil", handler.FinancialDataRobotDetail) + // e.POST("/statistics/financialData/millionBalanceDetail", handler.FinancialDataMillionDetail) + // e.POST("/statistics/financialData/tableFee", handler.FinancialDataTableFee) + // e.POST("/statistics/financialData/playerWin", handler.FinancialDataPlayerWin) + // e.POST("/statistics/financialData/robotWin", handler.FinancialDataRobotWin) + // e.POST("/statistics/financialData/black", handler.FinancialDataBlack) + // e.POST("/statistics/rechargeData", handler.RechargeData) + // e.POST("/statistics/rechargeChannelData", handler.RechargeChannelData) + e.POST("/statistics/rechargeData/rechargeOrderList", handler.RechargeOrderList) + e.POST("/statistics/rechargeData/withdrawOrderList", handler.WithdrawOrderList) + e.POST("/statistics/rechargeData/queryWithdrawOrder", handler.QueryWithdrawOrder) + e.POST("/statistics/playData", handler.PlayData) + + e.POST("/statistics/playGameData", handler.PlayGameData) + e.POST("/statistics/playGameDataRoomGame", handler.PlayGameDataRoomGame) + e.POST("/statistics/playGameDataMillionGame", handler.PlayGameDataMillionGame) + e.POST("/statistics/playGameRoomData", handler.PlayGameRoomData) + + e.POST("/statistics/keepData", handler.KeepData) + e.POST("/statistics/rechargeKeepData", handler.RechargeKeepData) + e.POST("/statistics/withdrawList", handler.WithdrawListData) + e.POST("/statistics/withdrawDetail", handler.WithdrawListDetail) + // e.POST("/statistics/shareData", handler.ShareDataList) + e.POST("/statistics/rechargeTrend", handler.RechargeTrend) + e.POST("/statistics/rechargeTrendMap", handler.RechargeTrendMap) + + e.POST("/statistics/roomGameAnalysis", handler.RoomGameAnalysis) + e.POST("/statistics/newRechargeData", handler.NewRechargeData) + + e.POST("/statistics/rechargeFrequency", handler.RechargeFrequency) + + // e.POST("/statistics/tpCardStatistics", handler.TpCardStatistics) + + // slot 游戏历史 + // e.POST("/statistics/slotGameHistory", handler.SlotGameHistory) + // slot游戏统计 + // e.POST("/statistics/slotGameStatistics", handler.SlotGameStatistics) + +} diff --git a/modules/backend/routers/routers_sys.go b/modules/backend/routers/routers_sys.go new file mode 100644 index 0000000..26624b9 --- /dev/null +++ b/modules/backend/routers/routers_sys.go @@ -0,0 +1,18 @@ +package routers + +import ( + "github.com/gin-gonic/gin" + handler "server/modules/backend/handler/sys" +) + +func sys(e *gin.Engine) { + e.POST("/sys/whiteList/list", handler.GetWhiteList) + e.POST("/sys/whiteList/add", handler.AddWhiteList) + e.POST("/sys/whiteList/edit", handler.EditWhiteList) + e.POST("/sys/whiteList/delete", handler.DeleteWhiteList) + e.POST("/sys/whiteList/switch", handler.WhiteListSwitch) + e.POST("/sys/channel/add", handler.AddChannel) + e.POST("/sys/channel/edit", handler.EditChannel) + e.POST("/sys/channel/del", handler.DelChannel) + e.POST("/sys/editHistory/list", handler.OptList) +} diff --git a/modules/backend/routers/routers_warn.go b/modules/backend/routers/routers_warn.go new file mode 100644 index 0000000..9964df5 --- /dev/null +++ b/modules/backend/routers/routers_warn.go @@ -0,0 +1,13 @@ +package routers + +import ( + "github.com/gin-gonic/gin" + handler "server/modules/backend/handler/warn" +) + +func warn(e *gin.Engine) { + e.GET("/warn/list", handler.WarnList) + e.POST("/warn/add", handler.AddWarn) + e.POST("/warn/edit", handler.EditWarn) + e.POST("/warn/del", handler.DelWarn) +} diff --git a/modules/backend/routers/routiers_profit.go b/modules/backend/routers/routiers_profit.go new file mode 100644 index 0000000..c0f9113 --- /dev/null +++ b/modules/backend/routers/routiers_profit.go @@ -0,0 +1,11 @@ +package routers + +import ( + "github.com/gin-gonic/gin" + handler "server/modules/backend/handler/profit" +) + +func profit(e *gin.Engine) { + e.POST("/profit/playerProfit", handler.PlayerProfit) + e.POST("/profit/editProfit", handler.EditProfit) +} diff --git a/modules/backend/timer.go b/modules/backend/timer.go new file mode 100644 index 0000000..f2429a6 --- /dev/null +++ b/modules/backend/timer.go @@ -0,0 +1,137 @@ +package backend + +import ( + "server/call" + "server/common" + "server/db" + "server/modules/backend/handler/statistics" + "server/modules/backend/values" + "server/natsClient" + "server/pb" + "server/util" + "time" + + "github.com/liangdas/mqant/log" +) + +func StartTimer() { + ReviewTimer() + // RechargeFrequencyTimer() + // KeepRechargeTimer() + CurrencyRefreshTimer() + CleanTimer() +} + +// ReviewTimer 定时查询数据概要 +func ReviewTimer() { + time.AfterFunc(2*time.Minute, func() { + begin := time.Now() + log.Debug("start ReviewTimer......") + ret := []*values.ReviewData{} + su := util.GetZeroTime(time.Now()).Unix() + eu := util.GetZeroTime(time.Now().AddDate(0, 0, 1)).Unix() + channels := call.GetChannelList() + for _, v := range channels { + if v.Show != 2 { + continue + } + ret = append(ret, statistics.GetReviewPlatformData(su, eu, &v.ChannelID)) + } + ret = append(ret, statistics.GetReviewPlatformData(su, eu)) + values.AppReviewData = ret + + // values.CompaireData = statistics.GetCompaireData() + + log.Debug("finish ReviewTimer since:%v", time.Since(begin)) + ReviewTimer() + }) +} + +// RechargeFrequencyTimer 定时查询数据概要,缓存今天的数据 +func RechargeFrequencyTimer() { + time.AfterFunc(2*time.Minute, func() { + begin := time.Now() + log.Debug("start RechargeFrequencyTimer......") + ret := []*values.RechargeFrequency{} + su := util.GetZeroTime(time.Now()).Unix() + // channels := call.GetChannelList() + // for _, v := range channels { + // if v.Show != 2 { + // continue + // } + // ret = append(ret, statistics.GetRechargeFrequency(su, &v.ChannelID)) + // } + ret = append(ret, statistics.GetRechargeFrequency(su, nil)) + values.RechargeFrequencyData = ret + log.Debug("finish RechargeFrequencyTimer since:%v", time.Since(begin)) + RechargeFrequencyTimer() + }) +} + +// KeepRechargeTimer 定时查询新增付费留存数据,缓存7天的数据 +func KeepRechargeTimer() { + time.AfterFunc(2*time.Minute, func() { + begin := time.Now() + log.Debug("start KeepRechargeTimer......") + su := util.GetZeroTime(begin).Unix() + var oneDay int64 = 24 * 60 * 60 + ret := statistics.GetKeepData(su-7*oneDay, su+oneDay, 4, nil) + values.KeepRechargeData = ret + log.Debug("finish KeepRechargeTimer since:%v", time.Since(begin)) + KeepRechargeTimer() + }) +} + +// CleanTimer 定期清理数据库日志数据 +func CleanTimer() { + now := time.Now() + next := util.GetZeroTime(now.AddDate(0, 0, 1).Add(5 * time.Second)).Sub(now) + log.Debug("next clean %v", next) + time.AfterFunc(next, func() { + // 牌局操作日志保留七天 + // now := time.Now() + // zero := util.GetZeroTime(now).Unix() + // t := zero - 7*24*60*60 + // q := elastic.NewBoolQuery() + // q.Filter(elastic.NewRangeQuery("Time").Lt(t)) + // db.ES().DeleteByQuery(common.ESIndexTPActionLog, q) + // db.ES().DeleteByQuery(common.ESIndexRummyActionLog, q) + // db.ES().DeleteByQuery(common.ESIndexBackEventTrackData, q) + CleanTimer() + }) +} + +// CurrencyRefreshTimer 每天刷新一次汇率 +func CurrencyRefreshTimer() { + now := time.Now() + next := util.GetZeroTime(now.AddDate(0, 0, 1).Add(10 * time.Second)).Sub(now) + log.Debug("next CurrencyRefresh %v", next) + + update := false + for i := common.CurrencyTypeZero + 1; i < common.CurrencyAll; i++ { + if i == common.CurrencyUSDT { + continue + } + currency := &common.ConfigCurrencyRateUSD{CurrencyType: i} + db.Mysql().Get(currency) + if util.IsSameDayTimeStamp(now.Unix(), currency.RefreshTime) { + continue + } + rate := call.GetRateFromAPI(i) + if currency.ID > 0 { + db.Mysql().Update(&common.ConfigCurrencyRateUSD{CurrencyType: i}, map[string]interface{}{"rate": rate, "refresh_time": now.Unix()}) + } else { + currency.Rate = rate + currency.RefreshTime = now.Unix() + db.Mysql().Create(currency) + } + update = true + } + if update { + call.Publish(natsClient.TopicReloadConfig, &pb.ReloadGameConfig{Type: int32(common.ReloadConfigCurrencyRateUSD)}) + } + + time.AfterFunc(next, func() { + CurrencyRefreshTimer() + }) +} diff --git a/modules/backend/util/config.go b/modules/backend/util/config.go new file mode 100644 index 0000000..0fcc36c --- /dev/null +++ b/modules/backend/util/config.go @@ -0,0 +1,19 @@ +package util + +import ( + "server/modules/backend/bdb" + "server/modules/backend/values" + + "github.com/liangdas/mqant/log" +) + +// GetEditHistory 获取操作记录历史 +func GetEditHistory(page, num int) (int64, []values.EditHistory) { + one := []values.EditHistory{} + if err := bdb.BackDB.C().Order("time desc").Limit(num).Offset(page * num).Find(&one).Error; err != nil { + log.Error("GetEditHistory err:%v", err) + return 0, nil + } + count := bdb.BackDB.Count(&values.EditHistory{}, "") + return count, one +} diff --git a/modules/backend/util/guser.go b/modules/backend/util/guser.go new file mode 100644 index 0000000..f73d7f2 --- /dev/null +++ b/modules/backend/util/guser.go @@ -0,0 +1,17 @@ +package util + +// GetGUserPlayCount 查询游戏玩家各游戏局数 +func GetGUserPlayCount(uid int) map[int]int64 { + // q := elastic.NewBoolQuery() + // q.Must(elastic.NewMatchQuery("UID", uid)) + ret := map[int]int64{} + // for _, v := range common.Games { + // s, _ := db.ES().SumBy(common.ESIndexBackPlayerStatistics, v, q) + // id := common.GetGameIDByGames(v) + // if id == 0 { + // continue + // } + // ret[id] = int64(s) + // } + return ret +} diff --git a/modules/backend/util/utils.go b/modules/backend/util/utils.go new file mode 100644 index 0000000..dbb3fde --- /dev/null +++ b/modules/backend/util/utils.go @@ -0,0 +1,89 @@ +package util + +import ( + "fmt" + "server/modules/backend/values" + "server/util" + "strconv" + "strings" + "time" +) + +// 将查询日期的字符串转化成实际查询日期 +func GetQueryDate(start, end string) (string, string) { + if start == "" { + start = values.StartDate + } + if end == "" { + end = time.Now().Format("2006-01-02") + } + e, _ := time.ParseInLocation("2006-01-02", end, time.Local) + return start, e.AddDate(0, 0, 1).Format("2006-01-02") +} + +// 将查询日期的字符串转化成实际查询时间戳 +func GetQueryUnix(start, end string) (int64, int64) { + var s, e int64 + if start == "" { + // s = values.StartTime + s = util.GetZeroTime(time.Now().AddDate(0, -2, 0)).Unix() // 设定默认最多查询两个月 + } else { + st, _ := time.ParseInLocation("2006-01-02", start, time.Local) + s = st.Unix() + } + if end == "" { + e = util.GetZeroTime(time.Now().AddDate(0, 0, 1)).Unix() + } else { + st, _ := time.ParseInLocation("2006-01-02", end, time.Local) + e = st.AddDate(0, 0, 1).Unix() + } + if s < values.StartTime { + s = values.StartTime + } + now := util.GetZeroTime(time.Now().AddDate(0, 0, 1)).Unix() + if e > now { + e = now + } + return s, e +} + +// 获取分页查询参数,返回开始时间,结束时间,总数 +func GetPageQuery(start, end string, page, num int) (int64, int64, int64) { + su, eu := GetQueryUnix(start, end) + e := eu - int64(((page-1)*num)*24*60*60) + s := e - int64((num-1)*24*60*60) + if s < su { + s = su + } + count := (eu - su) / (24 * 60 * 60) + return s, e, count +} + +// 获取百分比 +func GetPer(a, b int64) string { + if b == 0 { + return fmt.Sprintf("%d", a) + "%" + } + return util.FormatFloat(float64(a*100)/float64(b), 2) + "%" +} + +// 获取比例小数点 +func GetPoint(a, b int64) string { + if b == 0 { + return "0" + } + return util.FormatFloat(float64(a)/float64(b), 2) +} + +// 获取比例小数点 +func GetPoint2(a, b int64) string { + if b == 0 { + return "0" + } + return util.FormatFloat(float64(a)/float64(b), 5) +} + +func AddPerFloat(f float64, s string) float64 { + num, _ := strconv.ParseFloat(strings.ReplaceAll(s, "%", ""), 64) + return f + num +} diff --git a/modules/backend/values/account.go b/modules/backend/values/account.go new file mode 100644 index 0000000..f7008cc --- /dev/null +++ b/modules/backend/values/account.go @@ -0,0 +1,34 @@ +package values + +import ( + "time" +) + +// 用户身份 +const ( + UserRoleAdmin = iota + 1 // 管理员 + UserRole1 // 1级 + UserRole2 // 2级 + UserRole3 // 3级 +) + +const ( + RedisTokenEx = 60 * time.Minute + AdminToken = "lrmfe2hFQOSqvv4Z" // 常驻调用token +) + +// LoginReq 登录请求 +type LoginReq struct { + Account string `json:"Account" binding:"required"` + Pass string `json:"Pass" binding:"required"` +} + +// LoginResp 登录返回 +// Power:权限 +// Role:账户等级,1管理员 +// Token:身份码,登录成功后的所有请求需在header里加入token字段以进行后续请求 +type LoginResp struct { + Power interface{} + Token string + Role int +} diff --git a/modules/backend/values/blockpay.go b/modules/backend/values/blockpay.go new file mode 100644 index 0000000..9276e75 --- /dev/null +++ b/modules/backend/values/blockpay.go @@ -0,0 +1,58 @@ +package values + +import ( + "server/common" + "server/pb" +) + +type BlockOrderReq struct { + OrderID string `json:"OrderID" binding:"required"` + TxID string `json:"TxID" binding:"required"` +} + +type BlockAddrsResp struct { + Addrs []*pb.OneBlockAddr +} + +type AddBlockAddrsReq struct { + Address string `json:"Address" binding:"required"` + Private string `json:"Private" binding:"required"` +} + +type BlockTransferListReq struct { + Start string `json:"Start"` + End string `json:"End"` + Page int `json:"Page" binding:"required"` + Num int `json:"Num" binding:"required"` + Type int + Status int + Success *bool +} + +type BlockTransferListResp struct { + Count int64 + List []common.ESTron +} + +type BlockTransferReq struct { + FromAddr string `json:"FromAddr" binding:"required"` + ToAddr string `json:"ToAddr" binding:"required"` + Amount int64 `json:"Amount" binding:"required"` + Type int64 `json:"Type" binding:"required"` // 1TRX 2USDT +} + +type PlayerWalletUsdtsResp struct { + TotalAmount int64 // 总共的usdt + Count int64 // 总共能需转账的个数 + Fee int64 // 所需能量预估 +} + +type PlayerWalletScanReq struct { + Down int64 `json:"Down"` // 扫描u下限 + Up int64 `json:"Up"` // 扫描u上限 +} + +type PlayerWalletScanResp struct { + Success int64 // 成功转账个数 + Fail int64 // 失败转账个数 +} diff --git a/modules/backend/values/cache.go b/modules/backend/values/cache.go new file mode 100644 index 0000000..1e3f83e --- /dev/null +++ b/modules/backend/values/cache.go @@ -0,0 +1,69 @@ +package values + +import "server/common" + +// 缓存的数据 +var ( + // CompaireData *Compaire + AppReviewData = []*ReviewData{} // 数据概要缓存数据 + RechargeFrequencyData = []*RechargeFrequency{} // 付费分析缓存数据 + KeepRechargeData = []KeepData{} // 新增付费缓存数据(缓存7日的数据) +) + +type Compaire struct { + Recharge map[common.CurrencyType][]int64 // 昨日今日充值对比 + RechargeSuccess []string // 昨日今日充值成功率对比 + RechargePlayerSuccess []string // 昨日今日充值人次成功率对比 + Withdraw map[common.CurrencyType][]int64 // 昨日今日代付对比 + WithdrawSuccess []string // 昨日今日代付成功率对比 + WithdrawPlayerSuccess []string // 昨日今日代付人次成功率对比 +} + +func GetReviewData(channelID ...int) *ReviewData { + if len(AppReviewData) == 0 { + return nil + } + cid := 0 + if len(channelID) > 0 { + cid = channelID[0] + } + for i, v := range AppReviewData { + if v.PlatformID == cid { + return AppReviewData[i] + } + } + return nil +} + +func GetRechargeFrequencyData(su int64, channelID ...int) *RechargeFrequency { + if len(RechargeFrequencyData) == 0 { + return nil + } + cid := 0 + if len(channelID) > 0 { + cid = channelID[0] + } + if cid != 0 { + return nil + } + for i, v := range RechargeFrequencyData { + if v.Channel == cid && su == v.Date { + return RechargeFrequencyData[i] + } + } + return nil +} + +func GetKeepRechargeData(su, eu int64) []KeepData { + if len(KeepRechargeData) == 0 { + return nil + } + ret := []KeepData{} + for i, v := range KeepRechargeData { + if v.Time < su || v.Time > eu { + continue + } + ret = append(ret, KeepRechargeData[i]) + } + return ret +} diff --git a/modules/backend/values/errorCode.go b/modules/backend/values/errorCode.go new file mode 100644 index 0000000..92c426f --- /dev/null +++ b/modules/backend/values/errorCode.go @@ -0,0 +1,10 @@ +package values + +// 错误码 +const ( + CodeOK = iota + CodeRetry + CodeToken + CodeParam + CodePower // 权限不足 +) diff --git a/modules/backend/values/examine.go b/modules/backend/values/examine.go new file mode 100644 index 0000000..e906ad8 --- /dev/null +++ b/modules/backend/values/examine.go @@ -0,0 +1,137 @@ +package values + +import ( + "server/common" +) + +// WithdrawListReq 请求赠送列表 +// Page 页码 +// Num 一页的数目 +// Start 开始时间戳 +// End 结束时间戳 +// Status 订单状态 1待审核,2打款中,3已完成,4打款失败,5玩家撤回,6拒绝打款 +// Platform 设备类型 +// Operator 操作人 +// Sort 排序 1:按创建时间排序 2:按总充值金额 3:按总退出金额 4:按订单审核时间 +type WithdrawListReq struct { + Page uint `json:"Page" binding:"required"` + Num uint `json:"Num" binding:"required"` + Type int `json:"Type" binding:"required"` + Start *string `json:"Start"` + End *string `json:"End"` + Status *int `json:"Status"` + Channel *int `json:"Channel"` + Platform *int `json:"Platform"` + UID *int `json:"UID"` + Amount *int `json:"Amount"` + OderID *string `json:"OderID"` + APIPayID *string `json:"APIPayID"` + PayChannel *int `json:"PayChannel"` + Operator *string `json:"Operator"` + Sort int `json:"Sort"` +} + +// WithdrawListResp 退出列表返回 +type WithdrawListResp struct { + List []WithdrawInfo + Count int64 +} + +// WithdrawInfo退出信息 +// RegistTime 注册时间 +// WithDrawPer 提存比 +// RechargeTotal 充值总额 +// WithDrawTotal 退出总额 +// WithDrawCount 总退出次数 +// PayAccountInfo 体现账户信息 +// AuditTime 人工审核时间 +// CallbackTime 支付回调时间 +type WithdrawInfo struct { + ID int + UID int + CreateTime int64 + OrderID string + APIPayID string + PayAccount string + Amount int64 + PayChannel int + Status uint8 + FailReason string + ChannelID int + AuditTime int64 + CallbackTime int64 + Operator string + Platform int + Mobile string + Tag string + Brl int64 + TotalRecharge int64 + TotalRechargeCount int64 + TotalWithdraw int64 + TotalWithdrawCount int64 +} + +// WithdrawOrder 退出订单 +// WithDrawTotal 退出总额 +// RechargeTotal 充值总额 +// WithDrawCount 退出次数 +// Birth 注册时间 +type WithdrawOrder struct { + common.WithdrawOrder + WithDrawTotal int64 `gorm:"column:total_withdraw"` + WithDrawCount int64 `gorm:"column:total_withdraw_count"` + RechargeTotal int64 `gorm:"column:total_charge"` + Birth int64 +} + +// WithdrawExamineReq 审核赠送 +// id 审核条目的id +// Opt 审核状态 1 通过 2拒绝 3挂起 +// Remark 备注 +type WithdrawExamineReq struct { + ID int `json:"ID" binding:"required"` + Opt int `json:"Opt" binding:"required"` + Remark string `json:"Remark"` +} + +type TpDataReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` +} + +type TpStatisticReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` +} + +type TpStatisticResp struct { + List []TpStatisticData +} + +type TpStatisticData struct { + Uid int // 用户id + Birth string // 注册日期 + Cash int64 // 可退出金币 + BindCash int64 // 不可退出金币 + TpCount int64 // 玩tp局数 + FirstRecharge int64 // 首充金额 + AmountBeforeRecharge int64 // 充值前金额 + TpCountBeforeRecharge int64 // 充值前玩tp局数 + WithDrawAmount int64 // 退出金额 + WithDrawCount int64 // 退出次数 + WithDrawPer string // 退出成功率 +} + +// WithdrawReturnReq 退出退回 +// OrderID 订单id +type WithdrawReturnReq struct { + OrderID string `json:"OrderID" binding:"required"` +} + +// PayCallbackReq 充值订单回调 +// OrderID 订单id +// Status 订单回调状态,2成功,4失败 +type PayCallbackReq struct { + OrderID string `json:"OrderID" binding:"required"` + Status int `json:"Status" binding:"required"` +} diff --git a/modules/backend/values/firstpage.go b/modules/backend/values/firstpage.go new file mode 100644 index 0000000..c55dce7 --- /dev/null +++ b/modules/backend/values/firstpage.go @@ -0,0 +1,16 @@ +package values + +type FirstPageRealDataResp struct { + Online int64 + RechargeOnline int64 + NewOnline int64 + RechargeAmount int64 +} + +type FirstPageDataReq struct { + Date string `json:"Date" binding:"required"` +} + +type FirstPageDataResp struct { + Data *FirstPageData +} diff --git a/modules/backend/values/gameuser.go b/modules/backend/values/gameuser.go new file mode 100644 index 0000000..8606ee8 --- /dev/null +++ b/modules/backend/values/gameuser.go @@ -0,0 +1,521 @@ +package values + +import "server/common" + +// GetGameUserListReq 获取游戏玩家列表 +// Start 开始时间 等同于注册时间 +// End 结束时间 等同于注册时间 +// Status 筛选状态 1正常,2封禁 +// Online 在线状态 1在线 2不在线 +// Channel 渠道 +// Country 国家 +// Phone 手机 +// UP 上级id +// Platform 登录类型 1h5 2pc 3手机h5 4手机原生 +// vip 数组范围 +// IsRecharge 1充值 2没充值 +// Recharge 充值金额范围搜索 +// RechargeCount 充值次数范围搜索 +// Withdraw 赠送金额范围搜索 +// LastLogin 最后登录时间范围搜索 +// Cash 余额筛选 +// Order 1总充值正序 2充值次数正序 3总赠送正序 4总赠送次数正序 5余额正序 6盈亏正序 7最后登录时间正序 8注册时间正序 / 以上排序序号改为负数则是逆序 +type GetGameUserListReq struct { + // Order *int `json:"Order"` + Page int `json:"Page" binding:"required"` + Num int `json:"Num" binding:"required"` + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + UID int + Channel int + Country string + Phone string + UP int + Status int + Platform int + VIP []int + IsRecharge int + Recharge []int64 + RechargeCount []int64 + Withdraw []int64 + LastLogin []int64 + Cash []int64 + Order int + Online int +} + +// GetGameUserListResp 获取游戏玩家列表 +// UID 玩家uid +type GetGameUserListResp struct { + List []GUser + Count int64 +} + +type GameUserListTotal struct { + BrlTotal int64 // brl总量 + RechargeBrlTotal int64 + WithdrawBrlTotal int64 + UsdtTotal int64 // usdt总量 + RechargeUsdtTotal int64 + WithdrawUsdtTotal int64 +} + +type GUser struct { + UID int + ChannelID int + Country string + Level int + Mobile string + Up int + Status int + Platform int + TotalRecharge int64 + TotalRechargeCount int + TotalWithdraw int64 + TotalWithdrawCount int + Brl int64 + Diff int64 + LastLogin int64 + Birth int64 + Tag string + Online int +} + +// PlayCount 游戏场次 +// GameCountPer 局数占比 +type GUserGameStatistics struct { + GameCount int64 `json:"GameCount"` + GameCountPer string `json:"GameCountPer"` +} + +// GetGameUserInfoReq 获取游戏玩家信息 +// Data 查询条件 可以是玩家uid 或者手机号 +// Phone 玩家手机号 +type GetGameUserInfoReq struct { + Data interface{} `json:"Data"` +} + +// GetGameUserInfoResp 返回玩家信息 +// Nick 用户昵称 +// Recharge 充值金额 +// Withdraw 退出金额 +// Channel 渠道 +// Phone 手机号 +// Birth 注册时间 +// OpenID 注册fb或者google id +// CashBrl brl +// CashUsdt usdt +// Online 是否在线 +// Status 账号状态 1正常 2封禁 +// IP 最近一次登录ip +// LastLogin 最后一次登录时间 +// Tag 标签 +// UserGameInfo 用户游戏信息 +// Gpsadid 玩家google广告ID +// AccountCount 账户数量 +type GetGameUserInfoResp struct { + UID int + Nick string + Channel int + Phone string + OpenID string + CashBrl int64 + WithdrawalBrl int64 + CashUsdt int64 + WithdrawalUsdt int64 + RechargeBrl int64 + WithdrawBrl int64 + RechargeUsdt int64 + WithdrawUsdt int64 + Online bool + Status int + Birth int64 + IP string + LastLogin int64 + Tag string + UserGameData map[string][]UserGameInfo + OutputData map[string]string + Gpsadid string + SubAccount []int +} + +type UserGameInfo struct { + // GameId int // 游戏id + GameName string + GameCount int64 // 游戏局数 + // WinPer string // 胜率 + Profit int64 // 利润 + // WinCount int64 // 胜利局数 + // WinProfit int64 // 胜利的金钱总额 + // LoseCount int64 // 输的局数 + // LoseProfit int64 // 失败的金钱总额 +} + +// GetGameUserAllBalanceReq 获取游戏玩家所有流水详情 +// UID 玩家uid +// Page 页码 +// Num 一页的数目 +// Start 开始时间戳 +// End 结束时间戳 +type GetGameUserAllBalanceReq struct { + UID int `json:"UID" binding:"required"` + Page int `json:"Page" binding:"required"` + Num int `json:"Num" binding:"required"` + Start *int64 `json:"Start"` + End *int64 `json:"End"` +} + +// GetGameUserAllBalanceResp 获取游戏玩家所有流水详情 +// List 数据列表 +// Count 总数目 +type GetGameUserAllBalanceResp struct { + List []common.CurrencyBalance + Count int64 +} + +// GetGameUserPlayDataReq 获取游戏玩家牌局详情 +// UID 玩家uid +// Page 页码 +// Num 一页的数目 +// Games 筛选游戏 +// Start 开始时间戳 +// End 结束时间戳 +type GetGameUserPlayDataReq struct { + UID int `json:"UID" binding:"required"` + Page int `json:"Page" binding:"required"` + Num int `json:"Num" binding:"required"` + Games *int `json:"Games"` + Start *int64 `json:"Start"` + End *int64 `json:"End"` +} + +// GetGameUserPlayDataResp 获取游戏玩家牌局详情 +// List 数据列表 +// Count 总数目 +type GetGameUserPlayDataResp struct { + List []OneGameUserPlayData + Count int64 +} + +// Time 时间 +// GameID 游戏ID +// RoomName 场次 +// Value 变化 +// Balance 结余 +// UUID 本局游戏的唯一id +// ProviderID +type OneGameUserPlayData struct { + Time int64 + GameID int + RoomName int + Value int64 + Balance int64 + UUID string + ProviderID int +} + +// GetGameUserControlBalanceReq 获取游戏玩家控杀流水 +// UID 玩家uid +// Page 页码 +// Num 一页的数目 +// Start 开始时间 +// End 结束时间 +type GetGameUserControlBalanceReq struct { + UID int `json:"UID" binding:"required"` + Page int `json:"Page" binding:"required"` + Num int `json:"Num" binding:"required"` + Start string `json:"Start"` + End string `json:"End"` + ControlType *int `json:"control_type"` +} + +// GetGameUserControlBalanceResp 获取游戏玩家控杀流水 +// List 数据列表 +// Count 总数目 +// ControlCount 总控杀次数 +// LuckCount 幸运次数 +// ControlPer 控杀率 +// LuckPer 幸运率 +type GetGameUserControlBalanceResp struct { + List []common.CurrencyBalance + Count int64 + ControlCount int64 + LuckCount int64 + ControlPer string + LuckPer string +} + +// GetGameUserPlayDetailReq 获取某一局游戏的详细情况 +// UID 用户id +// UUID 本局游戏的唯一id +type GetGameUserPlayDetailReq struct { + UID int `json:"UID" binding:"required"` + UUID string `json:"UUID" binding:"required"` +} + +// GetGameUserPlayDetailResp 获取某一局游戏的详细情况 +// UUID 本局游戏的唯一id +// GameId 游戏id 根据游戏id返回结构TP,RM,DVT +// Players 参与玩家输赢明细 +type GetGameUserPlayDetailResp1 struct { + GameId int + Players []OneGameUserPlayDetail + DVTPlayer DVTGameUserPlayDetail +} + +type GetGameUserPlayDetailResp struct { + GameId int + Result interface{} + History interface{} +} + +type DVTGameUserPlayDetail struct { + UID int + Nick string + Result string // 开奖结果 龙:0, 平:1, 虎:2 + DragonResult0 DVTGameUserPlayDetailInfo // 开龙 + DragonResult2 DVTGameUserPlayDetailInfo // 开虎 + DragonResult1 DVTGameUserPlayDetailInfo // 开平 +} + +type DVTGameUserPlayDetailInfo struct { + PurchasePer string // 购买占比 购买此区域总局数/总局数 + VictoryPer string // 胜利占比 胜利局数/购买此区域总局数 +} + +type OneGameUserPlayDetail struct { + UID int + Nick string + Value int64 + Cards string + IsRobot bool + ControlType int +} + +// GetGameUserRechargeHistoryReq 获取游戏充值列表 +// Status 筛选成功/失败的订单 1未支付 2成功 4失败 +// UID 玩家uid 现支持uid查询或者订单号查询 +// OrderID 订单号 +// Page 页码 +// Num 一页的数目 +// Start 开始时间戳 +// End 结束时间戳 +type GetGameUserRechargeHistoryReq struct { + Status *int `json:"Status"` + UID int `json:"UID"` + OrderID string `json:"OrderID"` + Page int `json:"Page" binding:"required"` + Num int `json:"Num" binding:"required"` + Start *int64 `json:"Start"` + End *int64 `json:"End"` +} + +// GetGameUserRechargeHistoryResp 获取游戏充值列表 +// List 列表 +// Count 总数 +// RechargeCount 充值笔数 +// RechargeSuccessPer 充值成功率 +// RechargeTotal 充值总额 +type GetGameUserRechargeHistoryResp struct { + List []OneRechargeList + Count int64 + RechargeCount int64 + RechargeSuccessPer string + RechargeTotal int64 +} + +// Time 时间 +// Amount 支付金额 +// Status 支付结果 1未支付 2成功 4失败 +// PayChannel 支付渠道 +// PaySource 支付来源 +type OneRechargeList struct { + UID int + Time int64 + CallbackTime int64 + Amount int64 + Status int + MyOrderID string + OrderID string + PayChannel int + PaySource int + CurrencyType common.CurrencyType +} + +// GetGameUserWithdrawHistoryReq 获取玩家游戏退出列表 +// UID 玩家uid +// Page 页码 +// Num 一页的数目 +// Start 开始时间戳 +// End 结束时间戳 +// Data 查询条件,可以是订单号/手机号/银行卡 +type GetGameUserWithdrawHistoryReq struct { + UID int `json:"UID"` + Data string `json:"Data"` + Page int `json:"Page" binding:"required"` + Num int `json:"Num" binding:"required"` + Start *int64 `json:"Start"` + End *int64 `json:"End"` +} + +// GetGameUserWithdrawHistoryResp 获取游戏退出列表 +// List 列表 +// Count 总数 +// WithdrawCount 退出笔数 +// WithdrawSuccessPer 退出成功率 +// WithdrawTotal 退出总额 +// WithdrawCash 退出货币数量 +// WithdrawTax 退出税费 +// WithDrawPer 提存比 +type GetGameUserWithdrawHistoryResp struct { + List []OneWithdrawList + Count int64 + WithdrawCount int64 + WithdrawSuccessPer string + WithdrawTotal int64 + WithdrawCash int64 + WithdrawTax int64 + RechargeTotal int64 + WithDrawPer string +} + +// Time 时间 +// Amount 退出金额 +// Status 退出结果 1未通过审核 2打款中 3已打款 4失败 +// FailReason 失败原因 +// PayAccount 退出账户信息 +// OrderID 订单号 +// OrderCreateAt 订单创建时间 +// AuditTime 人工审核时间 +// CallbackTime 支付回调时间 +// Channel 渠道 +type OneWithdrawList struct { + UID int + Time int64 + Amount int64 + Status int + FailReason string + PayAccount string + OrderID string + MyOrderID string + OrderCreateAt int64 + AuditTime int64 + CallbackTime int64 + Channel int +} + +// EditGameUserGoldReq 修改玩家金币 +// Type 增加的类型 1增加不可退出货币 2增加可退出货币 +// UID 玩家uid +// Amount 修改的数目,扣除就是负数 +type EditGameUserGoldReq struct { + Type int `json:"Type" binding:"required"` + CurrencyType common.CurrencyType `json:"CurrencyType" binding:"required"` + UID int `json:"UID" binding:"required"` + Amount int64 `json:"Amount" binding:"required"` +} + +// EditGameUserGoldReq 修改玩家状态,解封/封禁 +// UID 玩家uid +// Status 修改的状态 1正常 2封禁 +type EditGameUserStatusReq struct { + UID int `json:"UID" binding:"required"` + Status int `json:"Status" binding:"required"` +} + +// ActiveUserList +type ActiveUserListReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Page int `json:"Page" binding:"required"` + Num int `json:"Num" binding:"required"` + Channel *int + Status *int // 1 新用户, 2老用户, 不传就是所有 +} + +type ActiveUserListResp struct { + List []ActiveUserInfo + Count int64 +} + +type ActiveUserOne struct { + ID int + Nick string + Status int + Birth int64 + Cash int64 + BindCash int64 + Online int + Time int64 + TotalCharge int64 + TotalWithdraw int64 +} + +type ActiveUserInfo struct { + UID int64 // 用户uid + Nick string // 用户昵称 + Status int // 帐号状态 + Birth int64 // 注册日期 + LastLogin int64 // 最后登录时间 + TodayCount int64 // 今日局数 + GameCount int64 // 游戏局数 + WinPer string // 胜率 + Recharge int64 // 充值金额 + Withdraw int64 // 退出金额 + Cash int64 // 可退出现金 + TotalCash int64 // 总金额 + Online int // 1在线2不在线 +} + +// RechargeRankReq 充值排行榜 +type RechargeRankReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Channel *int + Status int // 1 表示充值, 2 表示退出 + Num *int64 // 数量,默认100 +} + +type RechargeRankResp struct { + List []*RechargeRank + Count int64 +} + +type RechargeRank struct { + Date int64 // 时间 + Uid int // 玩家id + Birth int64 // 玩家注册日期 + Channel int // 玩家渠道 + TodayRechargeAmount int64 // 今天充值总金额 + TodayRechargeOrderCount int64 // 今日成功充值订单数 + TodayWithDrawAmount int64 // 今日退出金额 + TodayWithDrawOrderCount int64 // 今日成功退出订单数 + RechargeAmount int64 // 总充值金额 + WithDrawAmount int64 // 总退出金额 +} + +type BanUserListReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Page int `json:"Page" binding:"required"` + Num int `json:"Num" binding:"required"` + Channel *int +} + +type BanUserListResp struct { + List []*BanUserInfo + Count int64 +} + +type BanUserInfo struct { + common.ESBlackList + Recharge int64 // 充值金额 + Withdraw int64 // 退出金额 + Birth int64 // 注册日期 + Channel int // 渠道 +} + +// 将指定玩家相关信息拉入黑名单 +type AddUserBlackListReq struct { + UID int `json:"UID" binding:"required"` +} diff --git a/modules/backend/values/gm.go b/modules/backend/values/gm.go new file mode 100644 index 0000000..ad235b5 --- /dev/null +++ b/modules/backend/values/gm.go @@ -0,0 +1,192 @@ +package values + +import ( + "server/common" +) + +// GMAddCoinReq 后台修改玩家金币 +// UID 玩家id +// Currency 货币类型 +// Amount 请求的金币数量,可以是负数 +type GMAddCoinReq struct { + UID int `json:"UID" binding:"required"` + CurrencyType common.CurrencyType `json:"CurrencyType" binding:"required"` + Amount int64 `json:"Amount" binding:"required"` +} + +// GMRechargeReq 后台模拟充值 +// UID 玩家id +// CurrencyType 请求的货币类型 +// Amount 数量 +type GMRechargeReq struct { + UID int `json:"UID" binding:"required"` + CurrencyType common.CurrencyType `json:"CurrencyType"` + Amount int64 `json:"Amount"` + ProductID int `json:"ProductID"` +} + +// GMResetSignReq 后台重置签到 +// UID 玩家id +// Type 请求重置的类型 1 今日 2 全部 +type GMResetSignReq struct { + UID int `json:"UID" binding:"required"` + Type int `json:"Type" binding:"required"` +} + +// GMBindPhoneReq 后台绑定手机 +// UID 玩家id +// Phone 请求绑定的手机号 +type GMBindPhoneReq struct { + UID int `json:"UID" binding:"required"` + Phone string `json:"Phone" binding:"required"` +} + +// GMUnBindPhoneReq 后台解除绑定手机 +// UID 玩家id +type GMUnBindPhoneReq struct { + UID int `json:"UID" binding:"required"` +} + +// GMUnBindPhoneReq 后台获取手机验证码 +// Phone 手机号 +type GMGetPhoneCodeReq struct { + Phone string `json:"Phone" binding:"required"` +} + +// GMGetPhoneCodeResp 后台获取手机验证码 +// Code 验证码 +type GMGetPhoneCodeResp struct { + Code string `json:"Code" binding:"required"` +} + +// GMConfigPlatformListResp 后台获取平台配置参数 +type GMConfigPlatformListResp struct { + Config *common.ConfigPlatform +} + +// GMConfigPlatformEditReq 后台修改平台配置参数 +type GMConfigPlatformEditReq struct { + Config *common.ConfigPlatform +} + +// GMConfigCommonListResp 后台获取调控配置通用接口 +type GMConfigCommonListResp struct { + Config interface{} +} + +// GMConfigCommonEditReq 后台修改调控配置通用接口 +type GMConfigCommonEditReq struct { + Config []map[string]interface{} +} + +// GMConfigCommonDelReq 后台删除调控配置通用接口 +type GMConfigCommonDelReq struct { + ID int +} + +func GetControlType(path string) int { + switch path { + case "rwPer": + return common.ReloadConfigRWPer + case "all": + return common.ReloadConfigActivity + case "payProduct": + return common.ReloadConfigPayProduct + case "h5": + return common.ReloadConfigH5 + case "tron": + return common.ReloadConfigTron + case "withdraw": + return common.ReloadConfigWithdrawProduct + case "gameProvider": + return common.ReloadConfigGameProvider + case "gameList": + return common.ReloadConfigGameList + case "gameTypes": + return common.ReloadConfigGameTypes + case "gameMarks": + return common.ReloadConfigGameMarks + case "firstPageGames": + return common.ReloadConfigFirstPageGames + case "vip": + return common.ReloadConfigVip + case "water": + return common.ReloadConfigWater + case "robot": + return common.ReloadConfigRobot + case "appSpin": + return common.ReloadConfigAppSpin + case "share": + return common.ReloadConfigShare + case "shareSys": + return common.ReloadConfigShareSys + case "pddSpin": + return common.ReloadConfigActivityPddSpin + case "pdd": + return common.ReloadConfigActivityPdd + case "task": + return common.ReloadConfigTask + case "currencyResource": + return common.ReloadConfigCurrencyResource + case "firstPay": + return common.ReloadConfigFirstPay + case "freespin": + return common.ReloadConfigActivityFreeSpin + case "firstRechargeBack": + return common.ReloadConfigActivityFirstRechargeBack + case "luckCode": + return common.ReloadConfigLuckyCode + case "banner": + return common.ReloadConfigBanner + case "sign": + return common.ReloadConfigActivitySign + case "breakGift": + return common.ReloadConfigActivityBreakGift + case "shareRobot": + return common.ReloadConfigShareRobot + case "weekCard": + return common.ReloadConfigActivityWeekCard + case "slots": + return common.ReloadConfigActivitySlots + case "luckyShop": + return common.ReloadConfigActivityLuckyShop + case "serverFlag": + return common.ReloadConfigServerFlag + case "sevenDayBox": + return common.ReloadConfigActivitySevenDayBox + case "super": + return common.ReloadConfigActivitySuper + default: + return 0 + } +} + +// GMConfigReloadServerVersionReq 后台热更服务器更新 +type GMConfigReloadServerVersionReq struct { + ServerID int + Version int +} + +// GMConfigPayWeightResp 后台获取支付权重 +type GMConfigPayWeightResp struct { + Config interface{} + Status int +} + +// 强制玩家短线 +type OptPlayerDisconnectReq struct { + UserId []int // 用户id +} + +// CheckQueryControl 某些控制查询时,有特殊条件 +func CheckQueryControl(data interface{}) (string, string) { + switch data.(type) { + case *common.ConfigActivityPddSpin: + return "", "amount_down,sort" + } + return "", "" +} + +// CheckUpdateControl 某些控制更新时,有特殊条件 +func CheckUpdateControl(data interface{}) { +} diff --git a/modules/backend/values/platformData.go b/modules/backend/values/platformData.go new file mode 100644 index 0000000..802afe8 --- /dev/null +++ b/modules/backend/values/platformData.go @@ -0,0 +1,14 @@ +package values + +type PlatformDataReq struct { + Start string `json:"Start"` + End string `json:"End"` + Channel *int `json:"Channel"` + Page int `json:"Page" binding:"required"` + Num int `json:"Num" binding:"required"` +} + +type PlatformDataResp struct { + PlatformData []*PlatformData + Count int64 +} diff --git a/modules/backend/values/powers.go b/modules/backend/values/powers.go new file mode 100644 index 0000000..9b5c244 --- /dev/null +++ b/modules/backend/values/powers.go @@ -0,0 +1,154 @@ +package values + +// 权限对应页签 +const ( + PowerAll = iota + // 系统管理从1开始 + PowerFirst // 首页 + PowerGM // 系统配置 + PowerMail // 邮件 + PowerWhiteList // 黑白名单 + PowerChannel // 渠道管理 + PowerExamineWithdraw // 退出订单审核 + PowerStatisticsWithdraw // 退出订单统计 + PowerManageRole // 角色管理账户权限 + PowerManageUser // 用户管理账户权限 + PowerOptLog // 操作日志权限 + PowerWarn // 预警模块权限 + PowerGUser // 游戏玩家管理 + PowerGUserList // 游戏玩家列表 + PowerShareChannel // 分享配置 + PowerBlockpayQuery // 区块支付查询功能 + PowerBlockpayAddrs // 区块支付地址功能(包括转账权限等) + PowerMax1 +) + +// 数据管理从100开始 +const ( + PowerRealData = iota + 100 // 实时数据 100 + PowerGameData // 游戏概况 101 + PowerNewPlayData // 新增用户分析 102 + PowerActivePlayData // 用户活跃分析 103 + PowerFinancialData // 系统货币 104 + PowerRechargeData // 收入概要 105 + PowerPlayData // 用户牌局 106 + PowerRechargeOrder // 订单明细 107 + PowerShareData // 分享数据 108 + PowerWithdrawOrder // 退出明细 109 + PowerGame // 游戏牌局 110 + PowerRealProfit // 实时盈亏 111 + PowerRedActivity // 红包雨活动 112 + PowerPlayerProfit // 玩家收益 113 + PowerEventTrack // 打点数据 114 + PowerOutput // 产出统计 115 + + // PowerControlCardData // 调控牌局 113 + // PowerPlayGameData // 玩家游戏数据 114 + // PowerRechargeTrend // 充值趋势 115 + // PowerBalanceRoomGame // 房间游戏盈亏 116 + // PowerBalanceMillionGame // 百人场游戏盈亏 117 + // PowerRouletteActivityData // 轮盘活动数据 118 + PowerMax2 +) + +func IsValidPower(p int) bool { + if p > PowerAll && p < PowerMax1 || p >= PowerRealData && p < PowerMax2 { + return true + } + return false +} + +var ( + // 权限映射表 + PowerMap = map[string]int{ + "/statistics/reviewData": PowerFirst, + "/gm": PowerGM, + "/mail": PowerMail, + "/sys/whiteList": PowerWhiteList, + "/sys/channel": PowerChannel, + "/examine/withdraw": PowerExamineWithdraw, + "/statistics/realData": PowerRealData, + "/statistics/gameData": PowerGameData, + "/statistics/newPlayerData": PowerNewPlayData, + "/statistics/activePlayerData": PowerActivePlayData, + "/statistics/rechargeData": PowerRechargeData, + "/statistics/rechargeData/rechargeOrderList": PowerRechargeOrder, + "/statistics/rechargeData/withdrawOrderList": PowerWithdrawOrder, + "/statistics/playData": PowerPlayData, + "/statistics/withdraw": PowerStatisticsWithdraw, + "/statistics/output": PowerOutput, + "/power/role": PowerManageRole, + "/power/user": PowerManageUser, + "/sys/editHistory/list": PowerOptLog, + "/warn": PowerWarn, + "/guser": PowerGUser, + "/guser/list": PowerGUserList, + "/share": PowerShareChannel, + "/statistics/shareData": PowerShareData, + "/statistics/realDataBalance": PowerRealProfit, + "/statistics/activityRedData": PowerRedActivity, + "/blockpay/order/query": PowerBlockpayQuery, + "/blockpay/addrs": PowerBlockpayAddrs, + "/profit/playerProfit": PowerPlayerProfit, + // "/guser/controlCardData": PowerControlCardData, + // "/statistics/playGameData": PowerPlayGameData, + // "/statistics/rechargeTrend": PowerRechargeTrend, + // "/statistics/realDataBalanceRoomGame": PowerBalanceRoomGame, + // "/statistics/realDataBalanceMillionGame": PowerBalanceMillionGame, + // "/activity/rouletteActivityData": PowerRouletteActivityData, + } + // 页面按钮权限 + PowerButtonMap = map[int][]string{} +) + +type UserListResp struct { + List []User +} + +// AddUserReq 新增角色 +type AddUserReq struct { + Role int `json:"Role" binding:"required"` + Name string `json:"Name" binding:"required"` + Account string `json:"Account" binding:"required"` + Password string `json:"Password" binding:"required"` + Phone string `json:"Phone" binding:"required"` + Channels string `json:"Channels"` +} + +// DelUserReq 删除角色 +type DelUserReq struct { + ID int `json:"ID" binding:"required"` +} + +type RoleListResp struct { + List []Role +} + +// AddRoleReq 新增角色 +type AddRoleReq struct { + Role int `json:"Role" binding:"required"` + Name string `json:"Name" binding:"required"` + Power string `json:"Power" binding:"required"` +} + +// EditRoleReq 编辑角色 +type EditRoleReq struct { + ID int `json:"ID" binding:"required"` + Role int `json:"Role" binding:"required"` + Name string `json:"Name" binding:"required"` + Power string `json:"Power" binding:"required"` +} + +// EditPowerReq 编辑权限 +// ID 账户id +// Power 用户编辑后的权限 +type EditPowerReq struct { + ID int `json:"ID" binding:"required"` + Power *string `json:"Power"` + Role *int `json:"Role"` + Name *string `json:"Name"` + Account *string `json:"Account"` + Password *string `json:"Password"` + Phone *string `json:"Phone"` + Channels *string `json:"Channels"` +} diff --git a/modules/backend/values/profit.go b/modules/backend/values/profit.go new file mode 100644 index 0000000..f847a32 --- /dev/null +++ b/modules/backend/values/profit.go @@ -0,0 +1,39 @@ +package values + +type PlayerProfitReq struct { + Start string `json:"Start" binding:"required"` // 开始时间 + End string `json:"End" binding:"required"` // 结束时间 + Channel *int `json:"Channel"` // 渠道 +} + +type PlayerProfitResp struct { + List []*PlayerProfitInfo +} + +type PlayerProfitInfo struct { + Date int64 // 时间 + Investment int64 // 投入资金 + NewPlayer int64 // 当日新增人数 + TheDayPayCount int64 // 当日新增付费人数 + TheDayPayPer string // 当日新增付费率 + TheDayPayAmount int64 // 当日新增付费金额 + TheDayAvgPayment string // 当日新增平均付费 + NextDayPayCount int64 // 次日付费人数 + NextPayAmount int64 // 次日付费金额 + NextDayPayPer string // 次日付费率 + PayCount int64 // 总付费人数 + PayAmount int64 // 总付费金额 + WithdrawAmount int64 // 总退出 + Period int64 // 回本周期 +} + +type EditProfitReq struct { + List []EditProfitInfo +} + +type EditProfitInfo struct { + Start string // 开始时间 + Channel *int // 渠道 + Investment *int64 // 投入资金 + Period *int64 // 回本周期 +} diff --git a/modules/backend/values/protocol.go b/modules/backend/values/protocol.go new file mode 100644 index 0000000..62bea30 --- /dev/null +++ b/modules/backend/values/protocol.go @@ -0,0 +1,266 @@ +package values + +import ( + "server/common" + "server/pb" +) + +// MailDraftListReq 请求草稿列表 +// Page 页码 +// Num 一页的数目 +// 根据搜索条件发送相应字段即可,多个条件可叠加搜索 +// Title 搜索条件标题 +// StartSendTime 搜索条件发送时间的时间戳 +// EndSendTime 搜索条件发送时间的时间戳 +type MailDraftListReq struct { + Page uint `json:"Page" binding:"required"` + Num uint `json:"Num" binding:"required"` + Title *string `json:"Title"` + StartSendTime *int64 `json:"StartSendTime"` + EndSendTime *int64 `json:"EndSendTime"` +} + +// MailDraftListResp 请求草稿列表返回 +// count 总数 +type MailDraftListResp struct { + List []common.MailDraft + Count int +} + +// MailDraftCreateReq 创建草稿 +// Sender 发件人 +// Receiver 收件人可以是数组(空数组代表全服群发) +// Type 邮件类型 1正常 2紧急 +// Title 邮件标题 +// content 邮件正文 +// Enclosure 附件 +// SendMethod 发送方式 1立刻 2定时 +// SendTime 发送时间 当SendMethod为2时不能为空 +type MailDraftCreateReq struct { + Sender string `json:"Sender" binding:"required"` + Receiver []int `json:"Receiver" binding:"required"` + Type int `json:"Type" binding:"required"` + Title string `json:"Title" binding:"required"` + Content string `json:"Content" binding:"required"` + Enclosure []*pb.CurrencyPair `json:"Enclosure" binding:"required"` + SendMethod int `json:"SendMethod" binding:"required"` + SendTime int64 `json:"SendTime"` +} + +// MailDraftEditReq 编辑草稿 +// ID 编辑的草稿id +// Sender 发件人 +// Receiver 收件人可以是数组(空数组代表全服群发) +// Type 邮件类型 1正常 2紧急 +// Title 邮件标题 +// content 邮件正文 +// Enclosure 附件 +// SendMethod 发送方式 1立刻 2定时 +// SendTime 发送时间 +type MailDraftEditReq struct { + ID string `json:"ID" binding:"required"` + Sender *string `json:"Sender"` + Receiver *[]int `json:"Receiver"` + Type *int `json:"Type"` + Title *string `json:"Title"` + Content *string `json:"Content"` + Enclosure *[]*pb.CurrencyPair `json:"Enclosure"` + SendMethod *int `json:"SendMethod"` + SendTime *int64 `json:"SendTime"` +} + +// MailDraftOptReq 操作草稿 +// ID 编辑的草稿id +// Opt 操作码 1删除 2撤销 3发送 +type MailDraftOptReq struct { + ID string `json:"ID" binding:"required"` + Opt int `json:"Opt" binding:"required"` +} + +// ProductListResp 请求充值列表返回 +type ProductListResp struct { + List []*common.ConfigPayProduct +} + +// GamesListResp 游戏列表 +type GamesListResp struct { + List []string +} + +// ChannelListResp 渠道列表 +type ChannelListResp struct { + List []*common.Channel +} + +// WhiteListSwitchReq 白名单开关 +// Channel 渠道 +// Opt 1开启 2关闭 +// Version 配置的版本 +type WhiteListSwitchReq struct { + Channel int `json:"Channel" binding:"required"` + Opt int `json:"Opt" binding:"required"` + Version int `json:"Version" binding:"required"` +} + +// WhiteListReq 请求白名单 +type WhiteListReq struct { + Page uint `json:"Page" binding:"required"` + Num uint `json:"Num" binding:"required"` + ListType int `json:"ListType"` +} + +// WhiteListResp 请求白名单返回 +// count 总数 +// SwitchList 渠道开关列表 +type WhiteListResp struct { + List []common.WhiteList + Count int64 + SwitchList []common.WhiteList +} + +// AddWhiteListReq 新增白名单 +// ListType 名单类型 1白名单 2黑名单 +// LimitType 限制类型 1ip限制 2设备限制 +// Content ip或者设备码 +// Channel 渠道类型 facebook 2 gooleplay 3 +// Version 版本号 +// Powers 权限 +// CreateTime 创建时间 +// Operator 操作人 +type AddWhiteListReq struct { + ListType int `json:"ListType" binding:"required"` + LimitType int `json:"LimitType" binding:"required"` + Content string `json:"Content" binding:"required"` + Channel int `json:"Channel" binding:"required"` + Version int `json:"Version" binding:"required"` + Powers []int `json:"Powers" binding:"required"` +} + +// EditWhiteListReq 修改白名单 +// ListType 名单类型 1白名单 2黑名单 +// LimitType 限制类型 1ip限制 2设备限制 +// Content ip或者设备码 +// Channel 渠道类型 facebook 2 gooleplay 3 +// Version 版本号 +// Powers 权限 +// CreateTime 创建时间 +// Operator 操作人 +type EditWhiteListReq struct { + ID string `json:"ID" binding:"required"` + ListType *int `json:"ListType"` + LimitType *int `json:"LimitType"` + Content *string `json:"Content"` + Channel *int `json:"Channel"` + Version *int `json:"Version"` + Powers *[]int `json:"Powers"` +} + +// DeleteWhiteListReq 删除白名单 +type DeleteWhiteListReq struct { + ID string `json:"ID" binding:"required"` +} + +// AddChannelReq 新增渠道 +// ChannelID 渠道ID +// PlatformID 平台ID 1 gooleplay +// Name 渠道名 +// PackName 包名 +// PayID 支付商户id +// PayKey 支付商户key +// PaySecret 支付商户秘钥 +// Version 审核版本号(高于该版本的渠道包将进入审核服) +// MainVersion 正式服版本 +// IsHot 是否热更 1不热更 2热更 +// Games 开启的游戏 +// AdjustAuth adjust验证码 +// AdjustAppToken adjust应用token +// AdjustEventID adjust事件id,按顺序隔开 +// URL 应用域名 +// Show 是否开启展示 0不开 1开启 +// IgnoreOrganic 是否屏蔽自然量 1不屏蔽 2屏蔽 +type AddChannelReq struct { + ChannelID int `json:"ChannelID" binding:"required"` + PlatformID int `json:"PlatformID" binding:"required"` + Name string `json:"Name" binding:"required"` + PackName string `json:"PackName" binding:"required"` + Version int `json:"Version" binding:"required"` + MainVersion int `json:"MainVersion"` + IsHot int `json:"IsHot" binding:"required"` + AdjustAuth string `json:"AdjustAuth" binding:"required"` + AdjustAppToken string `json:"AdjustAppToken" binding:"required"` + AdjustEventID string `json:"AdjustEventID" binding:"required"` + URL string `json:"URL" binding:"required"` + Show int `json:"Show"` + IgnoreOrganic int `json:"IgnoreOrganic"` + FBPixelID string + FBAccessToken string + UP int + ADUpload int +} + +// EditChannelReq 编辑渠道 +// ID 记录ID,唯一标识 +// ChannelID 渠道ID +// PlatformID 平台ID 1 gooleplay +// Name 渠道名 +// PackName 包名 +// Version 审核版本号(高于该版本的渠道包将进入审核服) +// MainVersion 正式服版本 +// IsHot 是否热更 1不热更 2热更 +// Games 开启的游戏 +// AdjustAuth adjust验证码 +// AdjustAppToken adjust应用token +// AdjustEventID adjust事件id,按顺序隔开 +// URL 应用域名 +// Show 是否开启展示 0不开 1开启 +// IgnoreOrganic 是否屏蔽自然量 1不屏蔽 2屏蔽 +type EditChannelReq struct { + ID int `json:"ID" binding:"required"` + PlatformID *int `json:"PlatformID"` + ChannelID *int `json:"ChannelID"` + Name *string `json:"Name"` + PackName *string `json:"PackName"` + Version *int `json:"Version"` + MainVersion *int `json:"MainVersion"` + IsHot *int `json:"IsHot"` + AdjustAuth *string `json:"AdjustAuth"` + AdjustAppToken *string `json:"AdjustAppToken"` + AdjustEventID *string `json:"AdjustEventID"` + URL *string `json:"URL"` + Show *int `json:"Show"` + IgnoreOrganic *int `json:"IgnoreOrganic"` + FBPixelID *string + FBAccessToken *string + UP *int + ADUpload *int +} + +// DelChannelReq 删除渠道 +// ID 记录ID,唯一标识 +type DelChannelReq struct { + ID int `json:"ID" binding:"required"` +} + +// EditHistoryListReq 操作日志请求 +type EditHistoryListReq struct { + Page uint `json:"Page" binding:"required"` + Num uint `json:"Num" binding:"required"` +} + +// EditHistoryListResp 操作日志 +type EditHistoryListResp struct { + List []EditHistory + Count int64 +} + +// UserInfoResp 获取用户信息 +type UserInfoResp struct { + Name string + Role int + Power string +} + +// AccountListResp 账号列表 +type AccountListResp struct { + List []string +} diff --git a/modules/backend/values/share.go b/modules/backend/values/share.go new file mode 100644 index 0000000..937ed46 --- /dev/null +++ b/modules/backend/values/share.go @@ -0,0 +1,42 @@ +package values + +import "server/common" + +// Page 页码 +// Num 一页的数目 +// Start 开始时间戳 +// End 结束时间戳 +// Status 订单状态 1待审核,2打款中,3已完成,4打款失败,5玩家撤回,6拒绝打款 +type ShareOrderListReq struct { + Page uint `json:"Page" binding:"required"` + Num uint `json:"Num" binding:"required"` + Start string `json:"Start"` + End string `json:"End"` + Status *int `json:"Status"` + Channel int `json:"Channel"` +} + +type ShareOrderListResp struct { + List []common.ShareOrder +} + +type ShareOrderExamineReq struct { + OrderID string `json:"OrderID" binding:"required"` + Opt int `json:"Opt" binding:"required"` +} + +type OneShareData struct { + Level int + ShareCount int64 + RechargeCount int64 + ValidRechargeCount int64 + TotalRecharge int64 +} + +type ShareDataResp struct { + List []OneShareData +} + +type ShareDataReq struct { + UID int +} diff --git a/modules/backend/values/statistics.go b/modules/backend/values/statistics.go new file mode 100644 index 0000000..54e215c --- /dev/null +++ b/modules/backend/values/statistics.go @@ -0,0 +1,1621 @@ +package values + +import ( + "server/common" +) + +const ( + AreaIndia = iota + 1 +) + +const ( + StartTime = 1640975400 + StartDate = "2020-01-01" +) + +// KeepData 留存数据 +type KeepData struct { + Date string + Type int // 1活跃留存 2新增留存 3活跃付费留存 4新增付费留存 + Time int64 + Channel int + NewCount int64 + K1 string // 次留 + K2 string + K3 string // 3留 + K4 string + K5 string + K6 string + K7 string // 7留 + K8 string // 8留 + K9 string // 9留 + K10 string // 10留 + K11 string // 11留 + K12 string // 12留 + K13 string // 13留 + K14 string // 14留 + K15 string // 15留 + K30 string // 30留 +} + +// ReviewDataReq 请求数据总览 +// Area 地区 1 印度 +// Games 查询的游戏 不发就是查所有游戏 游戏列表通过common接口获取 +// Platform 平台 1 goole 2 vivo +// ChannelID 渠道id +// Start 开始时间 格式"2022-02-23",月日必须是两位数,个位数时,高位要用0填充 +// End 结束时间 +// Page 页码 +type ReviewDataReq struct { + Area int `json:"Area" binding:"required"` + Games *string `json:"Games"` + Platform *int `json:"Platform"` + ChannelID *int `json:"ChannelID"` + Start string `json:"Start"` + End string `json:"End"` + Page int `json:"Page" binding:"required"` + Num int `json:"Num" binding:"required"` +} + +// ReviewDataResp 请求数据总览返回 +// New 当天新注册用户 +// Active 当天活跃用户 +// Play 当天玩牌用户 +// Recharge 当天付费用户 +// TotalRecharge 当天付费充值总额 +// ChannelData 渠道数据 +// Total 汇总数据 +type ReviewDataResp struct { + // New int64 + // Active int64 + // Play int64 + // Recharge int64 + // TotalRecharge int64 + PlatformData []*ReviewData + Total *ReviewData + Count int64 + // CompaireData *Compaire +} + +type ReviewDataEditReq struct { + Time int64 `json:"Time" binding:"required"` + Channel int + ADFee *int64 + Remark *string +} + +// ReviewChannelDataReq 请求数据总览渠道详细数据 +// ChannelID 渠道id +// Start 开始时间 +// End 结束时间 +type ReviewChannelDataReq struct { + ChannelID int `json:"ChannelID" binding:"required"` + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` +} + +// ReviewChannelDataResp 请求数据总览渠道详细数据 +// PlatformData 数据返回 +// Total 汇总数据 +type ReviewChannelDataResp struct { + PlatformData []*ReviewData + Total *ReviewData +} + +// ReviewLoginWayReq 请求各登录方式人数 +// ChannelID 渠道id +// Start 开始时间 +// End 结束时间 +type ReviewLoginWayReq struct { + ChannelID *int `json:"ChannelID"` + Start string `json:"Start"` + End string `json:"End"` +} + +// ReviewLoginWayResp 请求各登录方式人数 +// List 各登录方式键值对,0游客 1手机 2fb 3gp +type ReviewLoginWayResp struct { + List map[int]int64 +} + +// ReviewWithdrawDataReq 请求退出人数具体分类 +// ChannelID 渠道id +// Start 开始时间 +// End 结束时间 +type ReviewWithdrawDataReq struct { + ChannelID *int `json:"ChannelID"` + Start string `json:"Start"` + End string `json:"End"` +} + +// ReviewWithdrawDataResp 请求退出人数具体分类 +// List 退出人数分类,数组按顺序依次为活跃用户,新增用户,首次退出人数 +type ReviewWithdrawDataResp struct { + List []OneReviewWithdrawData +} + +// OneReviewWithdrawData 退出人数分类 +// Count 人数 +// AVG 平均次数 +type OneReviewWithdrawData struct { + Count int64 + AVG string +} + +// ReviewPlatformData 首页渠道数据 +// Date 日期 +// Time 零点时间戳 +// ChannelID 渠道id +// NewCount 完成注册数 +// DownloadCount 当天下载数 +// NewPlayCount 新增玩牌人数 +// NewRegisterPer 新增注册率 = 新增注册用户/新增安装用户 +// NewPlayPer 新增玩牌率 = 新增玩牌/新增注册 +// NewPlayTime 新增用户平均玩牌时长 +// NewPayCount 新增付费人数 +// NewPayAmount 新增付费总额 +// NewPayPer 新增付费率 = 新增付费人数/新增注册人数 +// NewARPU 新增ARPU = 新增付费总额/新增注册人数 +// OldCount 老用户活跃人数 +// OldPayCount 老用户付费人数 +// OldPayAmount 老用户付费总额 +// OldPlayCount 老用户玩牌数 +// OldPayPer 老用户付费率 = 老用户付费人数/老用户活跃人数 +// OldPlayPer 老用户玩牌率 = 当天老用户玩牌人数/老用户数 +// OldPlayTime 老用户平均玩牌时长 +// ActivePayPer 活跃付费率 = 所有付费人数/活跃人数 +// ActivePlayPer 活跃玩牌率 = 当天活跃玩牌人数/活跃用户数 +// ActivePlayTime 活跃玩牌时长 +// ActiveCount 活跃用户 = 新增用户数 + 老用户数 +// ActivePayCount 活跃付费人数 = 新增用户付费人数 + 老用户付费人数 +// ActivePlayCount 活跃玩牌数 = 新用户玩牌人数 + 老用户玩牌人数 +// ActiveARPU 付费总额/活跃用户(新用户 + 老用户) +// ARPPU 每付费用户收益= 付费总额/付费用户数 +// RechargeTotal 付费总额 = 新用户付费 + 老用户付费 +// WithdrawTotal 退出总额 当天所有退出成功总额 +// WithdrawPer 退出比例 退出总额/充值总额 +// WithdrawCount 退出比数 成功笔数 +// WithdrawPlayerNum 退出人数 +// WithdrawSuccess 退出成功率 +// WithdrawPlayerSuccess 人次成功率 +// NewPayPerTotal 新增付费占比 = 新增付费/总付费 +type ReviewPlatformData struct { + Date string + Time int64 + PlatformID int + NewCount int64 + DownloadCount int64 + // NewPlayCount int64 + NewRegisterPer string + // NewPlayPer string + // NewPlayTime string + NewPayCount int64 + NewPayPer string + FirstPayCount int64 + FirstPayPer string + NewPayAmount int64 + // NewARPU string + // OldCount int64 + // OldPayCount int64 + // OldPayAmount int64 + // OldPlayCount int64 + // OldPayPer string + // OldPlayPer string + // OldPlayTime string + // ActivePayPer string + // ActivePlayPer string + // ActivePlayTime string + ActiveCount int64 + // ActivePlayCount int64 + ActivePayCount int64 + // ActiveARPU string + // ARPPU string + RechargeTotal int64 + WithdrawTotal int64 + WithdrawPer string + // WithdrawCount int64 + WithdrawPlayerNum int64 + WithdrawSuccess string + WithdrawPlayerSuccess string + RechargeBrl int64 + // RechargeUsdt int64 + WithdrawBrl int64 + // WithdrawUsdt int64 + NewPayPerTotal string + PayWithdrawDiff int64 + RTP string +} + +// RealDataReq 请求实时 +// Area 地区 1 印度 +// Games 查询的游戏 不发就是查所有游戏 游戏列表通过common接口获取 +// Start 开始时间 +// End 结束时间 +// Channel 渠道 +type RealDataReq struct { + // Area int `json:"Area" binding:"required"` + // Games *string `json:"Games"` + Start string `json:"Start"` + End string `json:"End"` + Channel *int `json:"Channel"` + // GameID int `json:"GameID"` +} + +// RealDataResp 请求数据总览返回 +type RealDataResp struct { + Active int64 + Regist int64 + Online int + TotalRechargeOrder int64 + FinishRechargeOrder int64 + RechargeSuccessPer string + TotalWithdrawOrder int64 + FinishWithdrawOrder int64 + WithdrawSuccessPer string + TotalRechargeData RealRechargeData + NewRechargeData RealRechargeData + OldRechargeData RealRechargeData + BetData RealBetData + BetGameData []*OneGameData + WinGameData []*OneGameData + LoseGameData []*OneGameData + ActivityData []*RealActivityData +} + +type RealRechargeData struct { + RechargePlayerNum int64 + TotalRecharge int64 + Arppu string +} + +type RealBetData struct { + TotalBet int64 + RealBet int64 + RealSettle int64 + RealProfit int64 + BonusBet int64 + BonusSettle int64 + BonusProfit int64 +} + +type OneGameData struct { + GameID int + Name string + Provider int + Amount int64 +} + +type RealActivityData struct { + ActivityID int + ClickCount int64 + JoinCount int64 + Reward int64 +} + +// RealDataBalanceReq 请求实时 +// Games 查询的游戏 不发就是查所有游戏 游戏列表通过common接口获取 +// Start 开始时间 +// End 结束时间 +// Games 游戏类型 +// Channel 渠道 +type RealDataBalanceReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Games *string `json:"Games"` + Channel *int `json:"Channel"` +} + +// RealDataBalanceResp 请求数据总览返回 +type RealDataBalanceResp struct { + Online map[string]interface{} // 当前在线人数 + List []RealDataBalance `json:"List"` +} + +type OnlineData struct { + Total int64 `redis:"Total"` + New int64 `redis:"New"` +} + +type RealDataBalance struct { + Date int64 // 日期 + Online int64 // 当前在线人数 + NewCount int64 `gorm:"column:NewCount"` // 新用户总数 + Profit int64 // 总利润 (所有场次AI盈亏+百人场系统盈亏) + TableFee int64 // 总台费 + RoomGame map[string]RoomGameBalanceData // string 表示房间游戏id + MillionGame map[string]MillionGameBalanceData // string 表是百人游戏id +} + +type RoomGameBalanceData struct { + BetTotal int64 // 下注总额 + Profit int64 // 总利润 + TableFee int64 // 总台费 + Active int64 // 活跃人数 +} + +type MillionGameBalanceData struct { + TableFee int64 // 总台费 + Profit int64 // 总利润 + Active int64 // 活跃人数 +} + +type RealBalanceDataRoomGameReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Channel *int `json:"Channel"` // 渠道 不传就是所有 +} + +type RealBalanceDataRoomGameResp struct { + List map[string]RoomGameData // string表示游戏id +} + +type RoomGameData struct { + Date int64 // 日期 + GameId int // 游戏id + Online int64 // 在线人数 + OnlineNew int64 // 在线新用户数 + Active int64 // 活跃人数 + Bet int64 // 下注 + Profit int64 // 利润 + Fee int64 // 台费 + DropPer string // 棋牌率 + RoomData map[string]RoomGameRealData // 房间id +} + +type RoomGameRealData struct { + Active int64 // 活跃人数 + Bet int64 // 下注 + Profit int64 // 利润 + Fee int64 // 台费 + DropPer string // 弃牌占比 (弃牌局数/该场总局数) + GameCount int64 // 游戏局数 + ControlKillCount int64 // 控杀局数 + ControlFreeCount int64 // 控放局数 +} + +type RealBalanceDataMillionGameReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Channel *int `json:"Channel"` // 渠道 不传就是所有 +} + +type RealBalanceDataMillionGameResp struct { + List []BalanceDataMillionGame +} + +type BalanceDataMillionGame struct { + GameId int // 游戏id + Online int64 // 在线用户 + OnlineNew int64 // 在线新用户 + Active int64 // 活跃用户 + Profit int64 // 利润 + TableFee int64 // 台费 + RoomDetail []BalanceDataMillionGameRoom // 细则 string表示房间id +} + +type BalanceDataMillionGameRoom struct { + RoomId int // 房间id + GameCount int64 // 游戏局数 + BetAmount int64 // 下注金额 + Profit int64 // 利润 + Active int64 // 活跃人数 + Detail []MillionGameDetail // 细则 +} + +type MillionGameDetail struct { + Result string // 结果 + BetAmount int64 // 下注金额 + BetCount int64 // 下注次数 + WinCount int64 // 开奖次数 + Profit int64 // 利润 +} + +// GameDataReq 请求游戏概况 +// Channel 渠道 +// Games 查询的游戏 不发就是查所有游戏 游戏列表通过common接口获取 +// Start 开始时间 +// End 结束时间 +type GameDataReq struct { + Channel *int `json:"Channel"` + Games *string `json:"Games"` + Start string `json:"Start"` + End string `json:"End"` +} + +// GameDataResp 请求游戏概况返回 +// New 当天新注册用户 +// Active 当天活跃用户 +// Play 当天玩牌用户 +// TotalRecharge 当天付费充值总额 +// ARPPU 总收入/付费用户数 +type GameDataResp struct { + New int64 + Active int64 + Play int64 + TotalRecharge int64 + ARPPU string +} + +// NewPlayerDataReq 请求新增用户分析数据 +// Channel 渠道 +// Games 查询的游戏 不发就是查所有游戏 游戏列表通过common接口获取 +// Start 开始时间 +// End 结束时间 +type NewPlayerDataReq struct { + Channel *int `json:"Channel"` + Games *string `json:"Games"` + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` +} + +// NewPlayerDataResp 请求新增用户分析数据返回 +// New 新注册用户 +// PlayPer 新增玩牌率 +// NewRechargePer 首日付费率 +// NewRechargeCount 首日付费人数 +// NewRecharge 首日付费额度 +// Map1 新增/玩牌/付费 图表 +// Map2 付费额度/ARPPU 图表 +// Map3 新增玩牌率 图表 +// Map4 破产 图表 +type NewPlayerDataResp struct { + New int64 + PlayPer string + NewRechargePer string + NewRechargeCount int64 + NewRecharge int64 + Map1 map[string]NewPlayerDataMap1 + Map2 map[string]NewPlayerDataMap2 + Map3 map[string]NewPlayerDataMap3 + Map4 map[string]NewPlayerDataMap4 +} + +// New 新注册用户 +// NewPlayCount 新增玩牌 +// NewRechargeCount 首日付费人数 +type NewPlayerDataMap1 struct { + New int64 + NewPlayCount int64 + NewRechargeCount int64 +} + +// Recharge 首日付费额度 +// ARPPU +type NewPlayerDataMap2 struct { + Recharge int64 + ARPPU string +} + +// NewPlayCount 新增玩牌率 +// NewRechargePer 首日付费率 +type NewPlayerDataMap3 struct { + NewPlayCount string + NewRechargePer string +} + +// BrekPer 新增破产率 +type NewPlayerDataMap4 struct { + BrekPer string +} + +// ActivePlayerDataReq 请求活跃用户分析数据 +// Channel 渠道 +// Games 查询的游戏 不发就是查所有游戏 游戏列表通过common接口获取 +// Start 开始时间 +// End 结束时间 +type ActivePlayerDataReq struct { + Channel *int `json:"Channel"` + Games *string `json:"Games"` + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` +} + +// ActivePlayerDataResp 请求活跃用户分析数据返回 +// Active 活跃用户数 +// ActivePlayPer 活跃玩牌率 +// RechargePer 付费渗透率 +// Map1 新增/活跃 图表 +// Map2 DAU/MAU 图表 +// Map3 玩牌/破产/付费 图表 +// Map4 付费总额/ARPU 图表 +type ActivePlayerDataResp struct { + Active int64 + ActivePlayPer string + RechargePer string + Map1 map[string]ActivePlayerDataMap1 + Map2 map[string]ActivePlayerDataMap2 + Map3 map[string]ActivePlayerDataMap3 + Map4 map[string]ActivePlayerDataMap4 +} + +// Active 活跃用户数 +// New 新注册用户 +type ActivePlayerDataMap1 struct { + Active int64 + New int64 +} + +// DMAU 日活跃用户/月活跃用户 +type ActivePlayerDataMap2 struct { + DMAU string +} + +// ActivePlayPer 活跃玩牌率 +// RechargePer 付费渗透率 +type ActivePlayerDataMap3 struct { + ActivePlayPer string + RechargePer string +} + +// Recharge 付费总额 +// ARPU 总收入/活跃总人数 +type ActivePlayerDataMap4 struct { + Recharge int64 + ARPU string +} + +// FinancialDataReq 请求游戏经济分析数据 +// Start 开始时间 +// End 结束时间 +// Channel 渠道号 +type FinancialDataReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Channel *int `json:"Channel"` +} + +// FinancialDataResp1 请求游戏经济分析数据返回 +type FinancialDataResp1 struct { + List []*OneFinancialData + Total *OneFinancialData +} + +// OneFinancialData 每日系统货币统计数据 +// Date 日期 +// PlayerBalance 用户货币 +// PlayerCash 可退出货币 +// RobotBalance AI盈亏 +// MillionBalance 百人场系统盈亏 +// Active 活跃用户 +// RobotWin 机器人赢取用户金币数量 +// PlayerWinTotal 用户总赢流水 +// TableFee 台费 +// ActiveCash 近7日活跃用户的可退出货币 +// ActiveCashPlayer 近7日可退出的活跃用户人数 +// BlackWinTotal 小黑屋回收 +type OneFinancialData struct { + Date string + CurrencyDetails CurrencyDetails // 货币明细 + DetailsOfMonetaryIncome DetailsOfMonetaryIncome // 货币收益明细 + MonetaryOutputDetails MonetaryOutputDetails // 货币产出明细 + BankruptcyDetails BankruptcyDetails // 破产明细 +} + +// 货币明细 +type CurrencyDetails struct { + PlayerBalance int64 // 用户货币 + PlayerCash int64 // 可退出货币 + ActiveCash int64 // 近7日活跃用户的可退出货币 + ActiveCashPlayer int64 // 近7日可退出的活跃用户人数 +} + +// 货币收益明细 +type DetailsOfMonetaryIncome struct { + TableFee int64 // 台费 + RobotBalance int64 // AI盈亏 + MillionBalance int64 // 百人场系统盈亏 + TotalMonetaryIncome int64 // 货币总收益 货币总收益=现有AI盈亏+百人场盈亏+台费 + // RoomGame map[string]map[string]RoomGameIncomeDetails // 1.string 游戏id 2.string 场次id + // MillionGame map[string]map[string]MillionGameIncomeDetails // 1.string 游戏id 2.string 场次id +} + +// 收益明细 +type RoomGameIncomeDetails struct { + Profit int64 // 盈亏 + TableFee int64 // 台费 +} + +// 收益明细 +type MillionGameIncomeDetails struct { + Profit int64 // 盈亏 +} + +// 货币产出明细 +type MonetaryOutputDetails struct { + RechargeGift int64 // 充值货币 + RegisGift int64 // 注册赠送货币 + SignGift int64 // 签到赠送货币 + ShareGift int64 // 分享赠送 + ActivityGift int64 // 活动赠送 + TotalMonetaryOutput int64 // 货币总产出=现有注册赠送+签到赠送+充值货币 +} + +// 破产明细 +type BankruptcyDetails struct { + BankruptcyDetailsInfo + NewUser BankruptcyDetailsInfo // 新用户破信息 + OldUser BankruptcyDetailsInfo // 老用户破产信息 +} + +// 破产明细信息 +type BankruptcyDetailsInfo struct { + BreakCount int64 // 破产总人数 + BreakPer string //破产率 +} + +// FinancialDataResp 请求游戏经济分析数据返回 +// SysOut 系统产出 +// RegistAmount 注册赠送 +// WithDrawAmount 退出货币 +// SysIn 系统回收 +// PlayerBalance 用户身上游戏币流水结余总额 +// PlayerStock 用户身上游戏币总额 +// FreeOut 系统免费产出 +// RechargeOut 付费产出 +// BreakCount 破产人数 +// BreakPer 破产率 +// Map1 系统产出 系统消耗 系统流水结余 图表 +// Map2 系统免费产出 系统付费产出 图表 +type FinancialDataResp struct { + SysOut int64 + RegistAmount int64 + WithDrawAmount int64 + SysIn int64 + PlayerBalance int64 + PlayerStock int64 + FreeOut int64 + RechargeOut int64 + BreakCount int64 + BreakPer string + Map1 map[string]FinancialDataMap1 + Map2 map[string]FinancialDataMap2 +} + +// FinancialDataRobotDetailReq 请求游戏经济分析ai盈亏详情 +// Start 开始时间 +// End 结束时间 +// Channel 渠道号 +type FinancialDataRobotDetailReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Channel *int `json:"Channel"` +} + +// FinancialDataRobotDetailResp 请求游戏经济分析ai盈亏详情 +// Detail 按游戏id区分数据场次数据 +type FinancialDataRobotDetailResp struct { + Detail map[int][]int64 +} + +// FinancialDataMillionDetailReq 请求游戏经济分析百人场盈亏详情 +// Start 开始时间 +// End 结束时间 +// Channel 渠道号 +type FinancialDataMillionDetailReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Channel *int `json:"Channel"` +} + +// FinancialDataRobotDetailResp 请求游戏经济分析百人场盈亏详情 +// Detail 按游戏id区分数据 +type FinancialDataMillionDetailResp struct { + Detail map[int][]int64 +} + +// SysOut 系统产出 +// SysIn 系统消耗 +// Balance 系统流水结余 +type FinancialDataMap1 struct { + SysOut int64 + SysIn int64 + Balance int64 +} + +// FinancialDataTableFeeReq 请求游戏经济分析台费详情 +// Start 开始时间 +// End 结束时间 +// Channel 渠道号 +type FinancialDataTableFeeReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Channel *int `json:"Channel"` +} + +// FinancialDataTableFeeResp 请求游戏经济分析台费详情 +// Detail 按游戏id区分数据 +type FinancialDataTableFeeResp struct { + Detail map[string]map[string]int64 // 1.int 游戏Id 2.int 房间Id 3.台费 +} + +// FinancialDataPlayerWinReq 请求游戏经济分析系统调控回收详情 +// Start 开始时间 +// End 结束时间 +// Channel 渠道号 +type FinancialDataPlayerWinReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Channel *int `json:"Channel"` +} + +// FinancialDataPlayerWinResp 请求游戏经济分析系统调控回收详情 +// Detail 按游戏id区分数据 +type FinancialDataPlayerWinResp struct { + Detail map[int][]int64 +} + +// FinancialDataRobotWinReq 请求游戏经济分析系统机器人回收详情 +// Start 开始时间 +// End 结束时间 +// Channel 渠道号 +type FinancialDataRobotWinReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Channel *int `json:"Channel"` +} + +// FinancialDataRobotWinResp 请求游戏经济分析系统机器人回收详情 +// Detail 按游戏id区分数据 +type FinancialDataRobotWinResp struct { + Detail map[int][]int64 +} + +// FinancialDataBlackReq 请求游戏经济分析系统机器人小黑屋回收详情 +// Start 开始时间 +// End 结束时间 +// Channel 渠道号 +type FinancialDataBlackReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Channel *int `json:"Channel"` +} + +// FinancialDataBlackResp 请求游戏经济分析系统机器人小黑屋回收详情 +// Detail 按游戏id区分数据 +type FinancialDataBlackResp struct { + Detail map[int][]int64 +} + +// FreeOut 系统免费产出 +// RechargeOut 系统付费产出 +type FinancialDataMap2 struct { + FreeOut int64 + RechargeOut int64 +} + +// RechargeDataReq 请求付费分析数据 +// ChannelID 渠道id +// Start 开始时间 +// End 结束时间 +type RechargeDataReq struct { + Start string `json:"Start"` + End string `json:"End"` + ChannelID *int `json:"ChannelID"` + Page int `json:"Page" binding:"required"` + Num int `json:"Num" binding:"required"` + // Games *string `json:"Games"` +} + +// RechargeDataResp1 新付费分析返回 +// List 按包列出渠道数据 +type RechargeDataResp1 struct { + List []OneRechargeData + Total OneRechargeData + Count int64 +} + +// RechargeStatistics 付费统计 +// GiftBag 礼包参数 +// Count 购买礼包数量 +// RechargePer 购买比( 购买人数 / 总购买人数) +type RechargeStatistics struct { + GiftBag int + Count int64 + RechargePer string +} + +// Date 时间 +// Channel 渠道id +// NewCount 新增用户数 +// RechargeCount 充值笔数 当天充值成功笔数 +// RechargePlayerNum 充值用户数 +// RechargeTotal 充值总额 +// WithdrawTotal 退出总额 +// WRPer 退出比例 退出总额/充值总额 +// Profit 利润 充值总额-退出总额 +// ARPU 总收入/活跃总人数 +// ARPPU 总收入/付费用户数 +// RechargePer 付费渗透率 付费人数/DAU +// RechargeStatisticsList 消费统计 +type OneRechargeData struct { + Date string + Channel int + NewCount int64 + RechargeCount int64 + RechargePlayerNum int64 + RechargeTotal int64 + WithdrawTotal int64 + WRPer string + Profit int64 + ARPU string + ARPPU string + RechargePer string + RechargeStatisticsList []RechargeStatistics +} + +// RechargeChannelDataReq 请求充值渠道详细数据 +// ChannelID 渠道id +// Start 开始时间 +// End 结束时间 +type RechargeChannelDataReq struct { + ChannelID int `json:"ChannelID" binding:"required"` + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` +} + +// RechargeChannelDataResp 请求充值渠道详细数据 +// List 数据返回 +type RechargeChannelDataResp struct { + List []OneRechargeData +} + +// RechargeDataResp 请求付费分析数据返回 +// RechargeCount 付费用户数 +// RechargeTotal 付费总金额 +// ARPU 总收入/活跃总人数 +// ARPPU 总收入/付费用户数 +// RechargePer 付费渗透率 付费人数/DAU +// RechargeFirst 首付用户数 +// Map1 付费用户数/首付用户数 图表 +// Map2 付费总额/首付金额/ARPPU 图表 +type RechargeDataResp struct { + RechargeCount int64 + RechargeTotal int64 + ARPU string + ARPPU string + RechargePer string + RechargeFirst int64 + Map1 map[string]RechargeDataMap1 + Map2 map[string]RechargeDataMap2 +} + +// RechargeCount 付费用户数 +// FirstRechargeCount 首付用户数 +type RechargeDataMap1 struct { + RechargeCount int64 + FirstRechargeCount int64 +} + +// RechargeTotal 付费总额 +// FirstRechargeAmount 首付金额 +// ARPPU 总收入/付费用户数 +type RechargeDataMap2 struct { + RechargeTotal int64 + FirstRechargeAmount int64 + ARPPU string +} + +// PlayDataReq 请求用户牌局分析数据 +// Channel 渠道 +// Games 查询的游戏 不发就是查所有游戏 游戏列表通过common接口获取 +// Start 开始时间 +// End 结束时间 +type PlayDataReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Channel *int `json:"Channel"` + Games *string `json:"Games"` +} + +// PlayDataResp 请求用户牌局分析数据返回 +// Play 玩牌用户数 +// NewPlay 注册玩牌用户 +// GameCount 牌局数 +// ActivePlayPer 活跃玩牌率 +// NewPlayerPer 注册玩牌率 +// Map1 玩牌用户数/注册玩牌用户数 图表 +// Map2 活跃玩牌率/注册玩牌率 图表 +// Map3 各游戏玩牌统计 图表 +type PlayDataResp struct { + Play int64 + NewPlay int64 + GameCount int64 + ActivePlayPer string + NewPlayerPer string + Map1 map[string]PlayDataMap1 + Map2 map[string]PlayDataMap2 + Map3 map[int]int64 + Map4 []PlayDataInfo +} + +type PlayGameDataResp struct { + List []PlayDataInfo +} + +type PlayDataInfo struct { + Date int64 // 日期 + PlayerGameData + GameData map[string]PlayerGameData // string 表示房间游戏id +} + +type PlayerGameData struct { + PlayerCount int64 // 玩家总人数 + GameCount int64 // 游戏总局数 +} + +type PlayGameDataReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Channel *int `json:"Channel"` + GameId int `json:"GameId" binding:"required"` +} + +type PlayRoomGameDataResp struct { + List []RoomStatisticsInfo +} + +type PlayMillionGameDataResp struct { + List []MillionStatisticsInfo +} + +type RoomStatisticsInfo struct { + Date int64 //时间 + PlayerCount int64 // 玩家总人数 + GameCount int64 // 游戏总局数 + RoomGameData map[string]GameStatisticsData // string 表示场次 +} + +type GameStatisticsData struct { + GameCount int64 `json:"GameCount"` // 游戏局数 + PlayerCount int64 `json:"PlayerCount"` // 玩家人数 + WinPer string `json:"WinPer"` // 胜率 + Break string `json:"Break"` // 破产率 +} + +type MillionStatisticsInfo struct { + Date int64 // 日期 + GameCount int64 // 总局数 + PlayerCount int64 // 总人数 + Profit int64 // 总利润 (算法:用户下注金额-中奖发放金额) + MillionGameData map[string]map[string]interface{} // 1.string 表示场次 2.string 表示返回结果 3.interface (MillionGameStatisticsData || map[string]int64) +} + +type MillionGameStatisticsData struct { + GameCount int64 `json:"GameCount"` // 游戏局数 + WinPer string `json:"WinPer"` // 胜率 + GameCountPer string `json:"GameCountPer"` // 局数占比 + Balance int64 `json:"Balance"` // 盈亏数量 +} + +type PlayGameRoomDataReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Channel *int `json:"Channel"` + GameId *int `json:"GameId"` + RoomId *int `json:"RoomId"` +} + +type PlayGameRoomDataResp struct { + List []PlayGameRoomDataInfo +} + +type PlayGameRoomDataInfo struct { + Date int64 // 日期 + GameId int // 游戏Id + ControlType int // 控制类型 + Amount int64 // 金额 + GameCount int64 // 游戏局数 + WinPer string // 胜率 +} + +// PlayCount 玩牌用户数 +// NewPlayCount 注册玩牌用户数 +type PlayDataMap1 struct { + PlayCount int64 + NewPlayCount int64 +} + +// ActivePlayPer 活跃玩牌率 +// NewPlayPer 注册玩牌率 +type PlayDataMap2 struct { + ActivePlayPer string + NewPlayPer string +} + +// KeepDataReq 请求用户留存数据数据 +// Channel 渠道 +// Start 开始时间 +// End 结束时间 +// ActiveKeep 1活跃留存/2新增留存/3活跃付费留存/4新增付费留存 +type KeepDataReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Channel *int `json:"Channel"` + ActiveKeep int `json:"ActiveKeep"` + Page int `json:"Page" binding:"required"` + Num int `json:"Num" binding:"required"` +} + +// KeepDataResp 请求用户牌局分析数据返回 +// List 键值对形式返回数据,key为日期,value为留存数据 +type KeepDataResp struct { + List []KeepData + Count int64 + Total KeepData +} + +// WithdrawDataReq 请求退出统计数据 +// Area 地区 1 印度 +// Start 开始时间 +// End 结束时间 +// Page 页码 +// Num 一页的数目 +type WithdrawDataReq struct { + Area *int `json:"Area"` + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Page int `json:"Page" binding:"required"` + Num int `json:"Num" binding:"required"` +} + +// WithdrawDataResp 请求退出统计数据返回 +// List 数据 +type WithdrawDataResp struct { + List []OneWithdrawData +} + +// OneWithdrawData 单条退出统计数据 +// Date日期 +// Active 活跃用户 +// WithdrawNum 退出人数 +// WithdrawTotal 退出笔数 +// SuccessCount 成功笔数 +// RefuseCount 拒绝笔数 +// WithdrawPer 退出率 +// SuccessPer 成功率 +// WithdrawAmount 退出总额 +// DayRecharge 当天充值总额 +// WithdrawCost 退出对应扣除的金币数量 +// PayingCount 打款中 +// FailCount 失败 +// WithdrawSuccessAmount 退出成功总额 +// PayingAmount 打款中的金额 +type OneWithdrawData struct { + Date string + Active int64 + WithdrawNum int64 + WithdrawTotal int64 + SuccessCount int64 + RefuseCount int64 + WithdrawPer string + SuccessPer string + WithdrawAmount int64 + DayRecharge int64 + WithdrawCost int64 + PayingCount int64 + FailCount int64 + WithdrawSuccessAmount int64 + PayingAmount int64 +} + +// WithdrawDetailReq 请求退出详细统计数据 +// Area 地区 1 印度 +// Start 开始时间 +// End 结束时间 +// Page 页码 +// Num 一页的数目 +type WithdrawDetailReq struct { + Area *int `json:"Area"` + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Page int `json:"Page" binding:"required"` + Num int `json:"Num" binding:"required"` +} + +// WithdrawDetailResp 请求退出统计数据返回 +// List 数据 +type WithdrawDetailResp struct { + List []OneWithdrawDetail +} + +// OneWithdrawDetail 个人退出详细数据 +// UID uid +// WithdrawCount 退出笔数 +// SuccessCount 成功退出笔数 +// RefuseCount 拒绝笔数 +// SuccessPer 成功率 +// WithdrawAmount 退出总额 +// RechargeTotal 用户充值总金额 +// RegistTime 注册时间 +// WithDrawPer 提存比 +type OneWithdrawDetail struct { + UID int + WithdrawCount int64 + SuccessCount int64 + RefuseCount int64 + SuccessPer string + WithdrawAmount int64 + RechargeTotal int64 + RegistTime int64 + WithDrawPer string +} + +// RechargeOrderListReq 请求充值订单列表 +// Channel 渠道 +// Status 筛选成功/失败的订单 1未支付 2成功 4失败 +// Start 开始时间 +// End 结束时间 +// Page 页码 +// Num 一页的数目 +// Birth 生日 +// Sort 排序 1:按创建时间排序 2:按历史充值排序 3:当前订单金额 +type RechargeOrderListReq struct { + Channel *int `json:"Channel"` + Status *int `json:"Status"` + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Page int `json:"Page" binding:"required"` + Num int `json:"Num" binding:"required"` + Birth *string `json:"Birth"` + Sort int `json:"Sort"` +} + +type OneRechargeOrderList struct { + Channel int // 渠道 + CreatedTime int64 // 开始时间 + CallbackTime int64 // 回调时间 + Nick string // 昵称 + UID int // 用户id + OrderID string // 订单id + Amount int64 // 当前订单金额 + Status int // 订单状态 + Birth int64 // 生日 + OrderCount int64 // 订单总数 + OrderSuccessCount int64 // 成功订单数 + TotalAmount int64 // 历史充值金额 + ProductId int // 商品id + ActivityId int // 活动id + PayChannel int // 支付渠道 + PaySource int // 支付来源 +} + +type RechargeOrderListResp struct { + List []OneRechargeOrderList + Count int64 + Amount int64 // 货币数量 +} + +// WithdrawOrderListReq 请求退出订单列表 +// Channel 渠道 +// Status 筛选成功/失败的订单 1未支付 2打款中 3成功 4失败 +// Start 开始时间 +// End 结束时间 +// Page 页码 +// Num 一页的数目 +// Sort 1:按创建时间排序 2:按退出金额排序 +type WithdrawOrderListReq struct { + Channel *int `json:"Channel"` + Status *int `json:"Status"` + Start string `json:"Start"` + End string `json:"End"` + Page int `json:"Page" binding:"required"` + Num int `json:"Num" binding:"required"` + Sort int `json:"Sort"` +} + +// WithdrawOrderListResp 请求退出订单列表 +// WithdrawTotal 退出总额 +// WithdrawCount 退出笔数 +// WithdrawSuccess 退出成功率 +type WithdrawOrderListResp struct { + WithdrawTotal int64 + WithdrawCount int64 + WithdrawSuccess string + List []OneWithdrawOrderList + Count int64 +} + +// Time 时间 +// Amount 退出金额 +// Status 退出结果 1未通过审核 2打款中 3已打款 4失败 +// PayAccount 退出账户信息 +// OrderID 订单号 +// Birth 用户注册时间 +// TotalWithdraw 历史退出 +// WithDrawPer 提存比 +// PayChannel 支付渠道 +// PaySource 支付来源 +type OneWithdrawOrderList struct { + Channel int + UID int + Time int64 + FinishTime int64 + Amount int64 + Status int + FailReason string + PayAccount string + OrderID string + MyOrderID string + Birth int64 + TotalWithdraw int64 + WithDrawPer string + PayChannel int // 支付渠道 + PaySource int // 支付来源 +} + +// ShareDataReq 请求分享数据 +// Channel 渠道 +// Start 开始时间戳 +// End 结束时间戳 +// Page 页码 +// Num 一页的数目 +// type ShareDataReq struct { +// Channel *int `json:"Channel"` +// Start string `json:"Start" binding:"required"` +// End string `json:"End" binding:"required"` +// Page int `json:"Page" binding:"required"` +// Num int `json:"Num" binding:"required"` +// } + +// ShareCount 分享点击次数 +// SharePlayer 分享人数 +// SharePer 分享转化率 分享成功,用户打开分享链接的次数/分享总次数 +// DownloadCount 下载点击次数 +// BindCount 成功绑定人数 +// BindPer 绑定成功率 +// RewardTotal 奖励总数 +// DrawTotal 领取奖励总数 +// type ShareDataResp struct { +// ShareCount int64 +// SharePlayer int64 +// SharePer string +// DownloadCount int64 +// BindCount int64 +// BindPer string +// RewardTotal int64 +// DrawTotal int64 +// List []OneShareInfo +// Count int +// } + +// ShareCount 分享次数 +// ClickCount 点击链接次数 +// DownloadCount 点击下载次数 +// OpenCount 打开次数 +// BindCount 成功绑定次数 +// BindPer 绑定成功率 成功绑定/分享次数 +// Level 分享者等级 +// RewardTotal 奖励合计 +// DrawTotal 已领取奖励 +type OneShareInfo struct { + Date string + Channel int + Nick string + UID int + ShareCount int64 + ClickCount int64 + DownloadCount int64 + OpenCount int64 + BindCount int64 + BindPer string + Level int + RewardTotal int64 + DrawTotal int64 +} + +// ControlCardDataReq 调控牌局 +type ControlCardDataReq struct { + Page int `json:"Page" binding:"required"` + Num int `json:"Num" binding:"required"` + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Channel *int `json:"Channel"` // 渠道 + GameId *int `json:"GameId"` // 游戏id + RoomId *int `json:"RoomId"` // 房间id + ControlType *int `json:"ControlType"` // 幸运/怒气/小黑屋 + Uid *string `json:"Uid"` // 用户id/牌局id +} + +type ControlCardDataResp struct { + List []PlayerBalance // 玩家信息 + Total int64 // 条数 +} + +type PlayerBalance struct { + common.CurrencyBalance + MillionBetArea map[string]int64 // 百人场下注区域 + Result []string // 百人场结果 +} + +// 充值走势 req +type RechargeTrendReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Channel *int `json:"Channel"` +} + +// 充值走势 resp +type RechargeTrendResp struct { + List []RechargeTrendInfo +} + +// 充值走势 info +type RechargeTrendInfo struct { + Date int64 + RechargeOrderCount int64 // 总充值订单数 + RechargeOrderSuccessCount int64 // 成功充值订单数 + NewRechargeOrderCount int64 // 新用户充值总订单数 + NewRechargeSuccessOrderCount int64 // 新用户充值成功订单数 + CreateRechargeOrderPlayerCount int64 // 创建充值订单人数 + RechargeSuccessPlayerCount int64 // 充值成功人数 + NewRechargeSuccessPlayerCount int64 // 新用户充值成功人数 + RechargeAmount int64 // 充值总金额 + RechargeSuccessPer string // 充值成功率 + NewRechargeSuccessPer string // 新用户充值成功率 + PayOrderTrigger int64 // 所有用户点击充值次数 + PayOrderSuccess int64 // 所有用户点击充值成功次数 + NewPayOrderTrigger int64 // 新用户点击充值次数 + NewPayOrderSuccess int64 // 新用户点击充值成功次数 +} + +// 充值走势图 req +type RechargeTrendMapReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Channel *int `json:"Channel"` +} + +// 充值趋势图resp +type RechargeTrendMapResp struct { + List []RechargeTrendMapInfo +} + +// 充值趋势图信息 +type RechargeTrendMapInfo struct { + Date int64 // 时间 + RechargeOrderCount int64 // 充值订单总数 + RechargeOrderSuccessCount int64 // 成功充值订单数 + NewRechargeOrderCount int64 // 新用户充值订单数 + RechargeSuccessPer string // 充值成功率 + RechargeAmount int64 // 充值总金额 +} + +// 添加用户tag请求 +type UserTagReq struct { + Uid int `json:"Uid" binding:"required"` // 用户id + Tag string +} + +type TagInfo struct { + Id int + Tag string +} + +// 房间游戏分析req +type RoomGameAnalysisReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + IsNew *bool // 不传就是所有玩家,true是新玩家, false是老玩家 +} + +// 房间游戏分析resp +type RoomGameAnalysisResp struct { + List []RoomGameAnalysisInfo +} + +// 房间游戏分析 +type RoomGameAnalysisInfo struct { + Date int64 // 时间 + NewPlayerCount int64 // 新增总人数 + OldPlayerCount int64 // 新增总人数 + PlayerCount int64 // 玩游戏总人数 + GameCount int64 // 游戏总局数 + GameCountDetail map[string]int64 // 游戏局数详情 string表示游戏id int64表示游戏局数 + AverageGameCount string // 平均游戏局数 + PlayerGameDetail map[string]int64 // 用户玩牌详情 +} + +// ReviewDataReq 请求数据总览 +type ReviewAppSummaryReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Sort int // 1.注册用户排序 2.活跃排序 3.充值排序 + IsShow bool // 是否上架 +} + +type ReviewAppSummaryResp struct { + PlatformData []*ReviewData // 上架包数据 + Total *ReviewData // 总数据 +} + +// PlayGameRummyDataReq +type PlayGameRummyDataReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + RoomId *int +} + +// PlayGameRummyDataResp +type PlayGameRummyDataResp struct { + List []PlayGameRummyDataInfo + Total PlayGameRummyDataInfo +} + +type PlayGameRummyDataInfo struct { + Date int64 // 日期 + RoomId int // 房间id + PlayerCount int64 // 人数 + GameCount int64 // 游戏局数 + HuPaiCount int64 // 胡牌局数 + DropCount int64 // 弃牌局数 + HuPaiPer string // 平局胡牌局数 + DropPer string // 平均弃牌局数 + PlayerNum int // 牌局人数 +} + +// LostPlayerDataReq 流失玩家数据请求 +type LostPlayerDataReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Page int `json:"Page" binding:"required"` + Num int `json:"Num" binding:"required"` + Channel *int `json:"Channel"` +} + +type LostPlayerDataResp struct { + List []LostPlayerData + Count int64 +} + +type LostPlayerData struct { + Date int64 // 时间 + Nick string // 昵称 + Uid int // 用户id + Birth int64 // 注册时间 + LastLogin int64 // 最后登录时间 + GameCount int64 // 游戏局数 + Amount int64 // 当前金额 + WithDrawAmount int64 // 退出金额 + GameRecord []int64 // 最后三局游戏记录 输:50,赢:30,输:30 + MostGameCount map[string]int64 // 最多游戏局数 +} + +// 新用户充值统计 NewRechargeData +type NewRechargeDataReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Page int `json:"Page" binding:"required"` + Num int `json:"Num" binding:"required"` + Channel *int `json:"Channel"` +} + +type NewRechargeDataResp struct { + List []NewRechargeData + Count int64 +} + +type NewRechargeData struct { + Date int64 // 时间 + Nick string // 昵称 + Uid int // 用户id + Birth int64 // 注册时间 + RechargeDate int64 // 充值时间 + RechargeAmount int64 // 未充值前的用户金额 + WithdrawAmount int64 // 充值前的可退出金额 + GameRecord []int64 // 充值前三局游戏记录 + MostGameCount map[string]int64 // 最多游戏局数 + GameCountBeforeRecharge int64 // 充值前游戏局数 +} + +// 流失用户数据 +type LostUserDataReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Page int `json:"Page" binding:"required"` + Num int `json:"Num" binding:"required"` + Channel *int + Sort int // 1:所有 2:付费 3:活跃 4:新增 +} + +type LostUserDataResp struct { + List []LostUserData + Count int64 +} + +type LostUserData struct { + Nick string // 用户昵称 + Uid int // 用户uid + Birth int64 // 生日 + LastLogin int64 // 上一次登录 + GameCount int64 // 游戏局数 + Amount int64 // 用户余额 + Cash int64 // 可退出金币 + WithDrawAmount int64 // 退出金额 + RechargeAmount int64 // 充值金额 +} + +// 流失用户牌局详情 +type LostUserDetailReq struct { + Uid int +} + +// 流失用户牌局详情 +type LostUserDetailResp struct { + List []LostUserDetail +} + +type LostUserDetail struct { + GameId int // 游戏id + GameCount int64 // 游戏局数 + WinPer string // 获胜率 + BreakPer string // 破产率 +} + +// 充值频率 +type RechargeFrequencyReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Page int `json:"Page" binding:"required"` + Num int `json:"Num" binding:"required"` + Channel *int +} + +type RechargeFrequencyResp struct { + List []*RechargeFrequency + Count int64 +} + +type RechargeFrequency struct { + Date int64 // 日期 + Channel int + List []RechargeFrequencyDetail +} + +type RechargeFrequencyDetail struct { + Date int64 // 时间 + RegisterPlayerCount int64 // 注册玩家数量 + RechargePlayerCount int64 // 充值玩家数量 + NewPlayerRechargePer float64 // 新用当日户付费率 + PayAmount24 int64 // 玩家24小时内付费金额 + PayAmount24Per float64 // 玩家24小时付费率 + PayAmountTotal int64 // 玩家总付费金额 + PayAmountTotalPer float64 // 玩家总付费率 + RechargeSuccessOrderCount int64 // 充值成功订单数 + RechargeOrderCount int64 // 玩家充值总订单数 + RechargeSuccessPer float64 // 支付成功率 +} + +// 大R用户数据 +type BigRUserDataReq struct { + Start string `json:"Start" binding:"required"` + End string `json:"End" binding:"required"` + Page int `json:"Page" binding:"required"` + Num int `json:"Num" binding:"required"` + Sort int // 1:按注册时间排序 2:按充值排序 3:按最后登录时间排序 + Channel *int + TotalRecharge int64 // 总充值 +} + +type BigRUserDataResp struct { + List []BigRUserData + Count int64 +} + +type BigRUserData struct { + Nick string // 昵称 + Uid int // 用户uid + Birth int64 // 用户注册时间 + LastLogin int64 // 最后登录时间 + RechargeAmount int64 // 充值总额 + RechargePer string // 充值成功率 + WithDrawAmount int64 // 退出金额 + RechargeAndWithDrawRate string // 充提比 + Amount int64 // 用户余额 + GameCount map[string]int64 // 游戏局数 + Phone string // 手机号码 +} + +// 查询退出订单 +type WithdrawOrderReq struct { + OrderId string // 订单id +} diff --git a/modules/backend/values/tables.go b/modules/backend/values/tables.go new file mode 100644 index 0000000..1be3aef --- /dev/null +++ b/modules/backend/values/tables.go @@ -0,0 +1,159 @@ +package values + +// User 后台用户 +type User struct { + Name string `gorm:"column:name;type:varchar(32);uniqueIndex:name;not null;comment:名字" json:"Name"` + Account string `gorm:"column:account;type:varchar(32);uniqueIndex:account;not null;comment:账号" json:"Account"` + Password string `gorm:"column:password;type:varchar(32);not null;comment:密码" json:"Password"` + ID uint `gorm:"primarykey"` + Role int `gorm:"column:role;type:tinyint(4);not null;comment:角色" json:"Role"` + Power string `gorm:"column:power;type:varchar(512);not null;comment:权限" json:"Power"` + PowerMap map[int][]int `gorm:"-" json:"PowerMap"` + Phone string `gorm:"column:phone;type:varchar(32);uniqueIndex:phone;comment:手机号" json:"Phone"` + Channels string `gorm:"column:channels;type:varchar(512);not null;comment:拥有权限的包,为空时代表所有包都有权限" json:"Channels"` + SChannels []int `gorm:"-"` +} + +func (u *User) TableName() string { + return "users" +} + +// EditHistory 后台修改操作历史 +// Operator 操作人 +// Detail 修改内容 +// Time 时间 +// Model 操作模块,与权限列表对应 +type EditHistory struct { + Operator string `gorm:"column:operator;type:varchar(32);not null;comment:操作人" json:"Operator"` + Detail string `gorm:"column:detail;type:varchar(256);not null;comment:修改明细" json:"Detail"` + ID uint `gorm:"primarykey" json:"-"` + Time int64 `gorm:"column:time;type:bigint(20);default:0;comment:操作时间" json:"Time"` + UID int `gorm:"column:uid;type:int(11);comment:操作人id" json:"UID"` + Model int `gorm:"column:model;type:int(11);comment:操作功能模块" json:"Model"` +} + +func (e *EditHistory) TableName() string { + return "edit_history" +} + +// Role 后台角色 +// Role 角色等级 1是超管 +// Power 权限 +// Name 角色名 +type Role struct { + ID uint `gorm:"primarykey"` + Role int `gorm:"column:role;type:tinyint(4);uniqueIndex:role;not null;comment:角色等级" json:"Role"` + Power string `gorm:"column:power;type:varchar(512);not null;comment:权限" json:"Power"` + Name string `gorm:"column:name;type:varchar(32);uniqueIndex:name;not null;comment:角色名" json:"Name"` +} + +func (u *Role) TableName() string { + return "role" +} + +type ReviewData struct { + ID uint `gorm:"primarykey"` + Date string `gorm:"column:Date;type:varchar(64);not null;comment:日期"` + Time int64 `gorm:"column:Time;type:bigint(20);uniqueIndex:tid;not null;comment:时间"` + PlatformID int `gorm:"column:PlatformID;type:int(11);uniqueIndex:tid;not null;comment:渠道号"` + DownloadCount int64 `gorm:"column:DownloadCount;type:int(11);default:0;comment:安装数"` + ActiveCount int64 `gorm:"column:ActiveCount;type:int(11);default:0;comment:活跃人数"` + NewCount int64 `gorm:"column:NewCount;type:int(11);default:0;comment:新增人数"` + NewRegisterPer string `gorm:"column:NewRegisterPer;type:varchar(64);default:'';comment:新增注册率"` + FirstPayCount int64 `gorm:"column:FirstPayCount;type:int(11);default:0;comment:首次付费人数"` + FirstPayPer string `gorm:"column:FirstPayPer;type:varchar(64);default:'';comment:首充付费率"` + NewPayCount int64 `gorm:"column:NewPayCount;type:int(11);default:0;comment:新增付费人数"` + NewPayPer string `gorm:"column:NewPayPer;type:varchar(64);default:'';comment:新增付费率"` + NewPayAmount int64 `gorm:"column:NewPayAmount;type:bigint(20);default:0;comment:新增付费金额"` + ActivePayCount int64 `gorm:"column:ActivePayCount;type:int(11);default:0;comment:活跃付费人数"` + RechargeTotal int64 `gorm:"column:RechargeTotal;type:bigint(20);default:0;comment:总付费"` + NewPayPerTotal string `gorm:"column:NewPayPerTotal;type:varchar(64);default:'';comment:新增充值占比"` + WithdrawPlayerNum int64 `gorm:"column:WithdrawPlayerNum;type:int(11);default:0;comment:赠送人数"` + WithdrawTotal int64 `gorm:"column:WithdrawTotal;type:bigint(20);default:0;comment:总赠送"` + PayWithdrawDiff int64 `gorm:"column:PayWithdrawDiff;type:bigint(20);default:0;comment:充值赠送差"` + WithdrawPer string `gorm:"column:WithdrawPer;type:varchar(64);default:'';comment:赠送比例"` + RTP string `gorm:"column:RTP;type:varchar(64);default:'0%';comment:总返奖/总下注"` + ChannelFee int64 `gorm:"column:ChannelFee;type:bigint(20);default:6;comment:支付费率,千分位"` + ADFee int64 `gorm:"column:ADFee;type:bigint(20);default:0;comment:广告费"` + ROI string `gorm:"column:ROI;type:varchar(64);default:'0%';comment:毛利/广告费*%"` + Profit string `gorm:"column:Profit;type:varchar(64);default:'0';comment:毛利"` + Remark string `gorm:"column:Remark;type:varchar(256);default:'';comment:备注"` + Check bool `gorm:"column:Check;type:tinyint(4);default:0;comment:数据是否已确认(当天数据实时变化)"` +} + +func (u *ReviewData) TableName() string { + return "ReviewData" +} + +type FirstPageData struct { + ID uint `gorm:"primarykey"` + Date string `gorm:"column:Date;type:varchar(64);not null;comment:日期"` + Time int64 `gorm:"column:Time;type:bigint(20);uniqueIndex:tid;not null;comment:时间"` + PlatformID int `gorm:"column:PlatformID;type:int(11);uniqueIndex:tid;not null;comment:渠道号"` + NewCount int64 `gorm:"column:NewCount;type:int(11);default:0;comment:新增人数"` + NewPayCount int64 `gorm:"column:NewPayCount;type:int(11);default:0;comment:新增付费人数"` + RechargeTotal int64 `gorm:"column:RechargeTotal;type:bigint(20);default:0;comment:总付费"` + WithdrawTotal int64 `gorm:"column:WithdrawTotal;type:bigint(20);default:0;comment:总赠送"` + RechargeOrderCreate int64 `gorm:"column:RechargeOrderCreate;type:int(11);default:0;comment:发起充值订单总数"` + RechargeOrderFinish int64 `gorm:"column:RechargeOrderFinish;type:int(11);default:0;comment:完成充值订单总数"` + WithdrawOrderCreate int64 `gorm:"column:WithdrawOrderCreate;type:int(11);default:0;comment:发起赠送订单总数"` + WithdrawOrderFinish int64 `gorm:"column:WithdrawOrderFinish;type:int(11);default:0;comment:完成赠送订单总数"` + RechargeSuccessPer string `gorm:"column:RechargeSuccessPer;type:varchar(64);default:'';comment:充值成功率"` + WithdrawPer string `gorm:"column:WithdrawPer;type:varchar(64);default:'';comment:赠送比例"` + RTP string `gorm:"column:RTP;type:varchar(64);default:'0%';comment:平台"` + ProviderRTP string `gorm:"column:ProviderRTP;type:varchar(64);default:'0%';comment:厂商"` + Bet int64 `gorm:"column:Bet;type:bigint(20);default:0;comment:总下注"` + Settle int64 `gorm:"column:Settle;type:bigint(20);default:0;comment:总返奖"` + ProviderBet int64 `gorm:"column:ProviderBet;type:bigint(20);default:0;comment:厂商下注"` + ProviderSettle int64 `gorm:"column:ProviderSettle;type:bigint(20);default:0;comment:厂商返奖"` + Check bool `gorm:"column:Check;type:tinyint(4);default:0;comment:数据是否已确认(当天数据实时变化)"` +} + +func (u *FirstPageData) TableName() string { + return "FirstPageData" +} + +type PlatformData struct { + ID uint `gorm:"primarykey"` + Date string `gorm:"column:Date;type:varchar(64);not null;comment:日期"` + Time int64 `gorm:"column:Time;type:bigint(20);uniqueIndex:tid;not null;comment:时间"` + PlatformID int `gorm:"column:PlatformID;type:int(11);uniqueIndex:tid;not null;comment:渠道号"` + DownloadCount int64 `gorm:"column:DownloadCount;type:int(11);default:0;comment:安装数"` + NewCount int64 `gorm:"column:NewCount;type:int(11);default:0;comment:新增人数"` + NewRegisterPer string `gorm:"column:NewRegisterPer;type:varchar(64);default:'';comment:新增注册率"` + NewPayCount int64 `gorm:"column:NewPayCount;type:int(11);default:0;comment:新增付费人数"` + NewPayPer string `gorm:"column:NewPayPer;type:varchar(64);default:'';comment:新增付费率"` + NewPayCountAll int64 `gorm:"column:NewPayCountAll;type:int(11);default:0;comment:新增付费次数"` + NewPayMultiPer string `gorm:"column:NewPayMultiPer;type:varchar(64);default:'';comment:新户复充率"` + NewPayAmount int64 `gorm:"column:NewPayAmount;type:bigint(20);default:0;comment:新增付费金额"` + NewPayARPPU string `gorm:"column:NewPayARPPU;type:varchar(64);default:'';comment:新增ARPPU"` + NewWithdrawCount int64 `gorm:"column:NewWithdrawCount;type:int(11);default:0;comment:新增赠送人数"` + NewWithdrawTotal int64 `gorm:"column:NewWithdrawTotal;type:bigint(20);default:0;comment:新增总赠送"` + NewWithdrawPer string `gorm:"column:NewWithdrawPer;type:varchar(64);default:'';comment:新增赠送比例"` + OldPayCount int64 `gorm:"column:OldPayCount;type:int(11);default:0;comment:老户付费人数"` + OldPayCountAll int64 `gorm:"column:OldPayCountAll;type:int(11);default:0;comment:老户付费次数"` + OldPayMultiPer string `gorm:"column:OldPayMultiPer;type:varchar(64);default:'';comment:老户复充率"` + OldPayAmount int64 `gorm:"column:OldPayAmount;type:bigint(20);default:0;comment:老户付费金额"` + OldPayARPPU string `gorm:"column:OldPayARPPU;type:varchar(64);default:'';comment:老户ARPPU"` + OldWithdrawCount int64 `gorm:"column:OldWithdrawCount;type:int(11);default:0;comment:老户赠送人数"` + OldWithdrawTotal int64 `gorm:"column:OldWithdrawTotal;type:bigint(20);default:0;comment:老户总赠送"` + OldWithdrawPer string `gorm:"column:OldWithdrawPer;type:varchar(64);default:'';comment:老户赠送比例"` + PayCount int64 `gorm:"column:PayCount;type:int(11);default:0;comment:总付费人数"` + RechargeTotal int64 `gorm:"column:RechargeTotal;type:bigint(20);default:0;comment:总付费"` + PayARPPU string `gorm:"column:PayARPPU;type:varchar(64);default:'';comment:总ARPPU"` + WithdrawPlayerNum int64 `gorm:"column:WithdrawPlayerNum;type:int(11);default:0;comment:总赠送人数"` + WithdrawTotal int64 `gorm:"column:WithdrawTotal;type:bigint(20);default:0;comment:总赠送"` + WithdrawPer string `gorm:"column:WithdrawPer;type:varchar(64);default:'';comment:总赠送比例"` + PlatformBet int64 `gorm:"column:PlatformBet;type:bigint(20);default:0;comment:总投注"` + PlatformSettle int64 `gorm:"column:PlatformSettle;type:bigint(20);default:0;comment:总返奖"` + ProviderBet int64 `gorm:"column:ProviderBet;type:bigint(20);default:0;comment:厂商投注"` + ProviderSettle int64 `gorm:"column:ProviderSettle;type:bigint(20);default:0;comment:厂商返奖"` + ActiveKeep string `gorm:"column:ActiveKeep;type:varchar(64);default:'';comment:活跃留存"` + RechargeKeep string `gorm:"column:RechargeKeep;type:varchar(64);default:'';comment:总充值留存"` + NewRechargeKeep string `gorm:"column:NewRechargeKeep;type:varchar(64);default:'';comment:新增充值留存"` + Check bool `gorm:"column:Check;type:tinyint(4);default:0;comment:数据是否已确认(当天数据实时变化)"` +} + +func (u *PlatformData) PlatformData() string { + return "PlatformData" +} diff --git a/modules/backend/values/track.go b/modules/backend/values/track.go new file mode 100644 index 0000000..9092c24 --- /dev/null +++ b/modules/backend/values/track.go @@ -0,0 +1,35 @@ +package values + +// EvenTrackDataReq 打点数据请求 +type EvenTrackDataReq struct { + Start string `json:"Start" binding:"required"` // 开始时间 + End string `json:"End" binding:"required"` // 结束时间 + Channel *string `json:"Channel" ` // 渠道id + Version *string `json:"Version"` // 版本号 +} + +type EvenTrackDataResp struct { + List []map[string]interface{} +} + +// EventTrackGameDataReq 游戏打点数据请求 +type EventTrackGameDataReq struct { + Start string `json:"Start" binding:"required"` // 开始时间 + End string `json:"End" binding:"required"` // 结束时间 + Channel *string `json:"Channel" ` // 渠道id + Version *string `json:"Version"` // 版本号 +} + +type EventTrackGameDataResp struct { + List []EventTrackGameData +} + +type EventTrackGameData struct { + GameId int // 游戏id + UpdateCount int64 // 更新游戏次数 + EnterCount int64 // 进入游戏次数 + DuringUpdatePer string // 平均更新时长 + DuringUpdate30s int64 // 更新时长小于等于30s次数 + DuringUpdate30sTo120s int64 // 更新时长大于30s小于等于120s次数 + DuringUpdate120s int64 // 更新时长大于120s次数 +} diff --git a/modules/backend/values/warn.go b/modules/backend/values/warn.go new file mode 100644 index 0000000..948dd74 --- /dev/null +++ b/modules/backend/values/warn.go @@ -0,0 +1,52 @@ +package values + +import ( + "server/call" +) + +// WarnListResp 预警列表返回 +type WarnListResp struct { + List []call.SysWarn +} + +// AddWarnReq 新增预警 +// Channel 生效的渠道 +// Type 预警类型 +// Condition 预警条件 +// WarnWay 预警方式 1手机短信 +// WarnMember 预警人员id +// Interval 提醒间隔单位分钟 +// Phone 电话号码 +type AddWarnReq struct { + Channel []int `json:"Channel" binding:"required"` + Type int `json:"Type" binding:"required"` + Condition map[string]interface{} `json:"Condition" binding:"required"` + WarnWay int `json:"WarnWay" binding:"required"` + WarnMember []int `json:"WarnMember"` + Interval int `json:"Interval" binding:"required"` + OtherPhone []string `json:"OtherPhone"` +} + +// EditWarnReq 修改预警 +// ID 预警id +// Channel 生效的渠道 +// Type 预警类型 +// Condition 预警条件 +// WarnWay 预警方式 1手机短信 +// WarnMember 预警人员id +// Interval 提醒间隔单位分钟 +type EditWarnReq struct { + ID string `json:"ID" binding:"required"` + Channel *[]int `json:"Channel"` + Type *int `json:"Type"` + Condition *map[string]interface{} `json:"Condition"` + WarnWay *int `json:"WarnWay"` + WarnMember *[]int `json:"WarnMember"` + Interval *int `json:"Interval"` + OtherPhone *[]string `json:"OtherPhone"` +} + +// DelWarnReq 删除预警 +type DelWarnReq struct { + ID string `json:"ID" binding:"required"` +} diff --git a/modules/blockpay/config.go b/modules/blockpay/config.go new file mode 100644 index 0000000..5431a10 --- /dev/null +++ b/modules/blockpay/config.go @@ -0,0 +1,12 @@ +package blockpay + +import ( + "server/call" + "server/pb" +) + +func loadConfig() error { + c := map[int][]func(*pb.ReloadGameConfig) error{} + call.LoadConfigs(c) + return nil +} diff --git a/modules/blockpay/handler.go b/modules/blockpay/handler.go new file mode 100644 index 0000000..f084c31 --- /dev/null +++ b/modules/blockpay/handler.go @@ -0,0 +1,355 @@ +package blockpay + +import ( + "fmt" + "server/call" + "server/common" + "server/config" + "server/db" + "server/pb" + "server/util" + "time" + + "github.com/fbsobreira/gotron-sdk/pkg/account" + "github.com/fbsobreira/gotron-sdk/pkg/proto/core" + "github.com/fbsobreira/gotron-sdk/pkg/store" + "github.com/gogo/protobuf/proto" + "github.com/liangdas/mqant/log" + "gorm.io/gorm" +) + +func Recharge(req *pb.InnerRechargeReq) (ret []byte, err error) { + log.Debug("player recharge:%+v", *req) + accountName := fmt.Sprintf("user%d", req.UID) + addr, _ := store.AddressFromAccountName(accountName) + if addr == "" { + account.CreateNewLocalAccount(&account.Creation{ + Name: accountName, + }) + addr, _ = store.AddressFromAccountName(accountName) + } else { + _, _, err := store.UnlockedKeystore(addr, "") + if err != nil { + log.Error("UnlockedKeystore err:%v", err) + + account.RemoveAccount(accountName) + account.CreateNewLocalAccount(&account.Creation{ + Name: accountName, + }) + addr, _ = store.AddressFromAccountName(accountName) + } + } + util.Go(func() { + td := &common.TronData{UID: int(req.UID)} + db.Mysql().Get(td) + if td.Addr == addr { + return + } + if td.ID > 0 { + db.Mysql().Update(&common.TronData{UID: int(req.UID)}, map[string]interface{}{"addr": addr}) + } else { + db.Mysql().Create(&common.TronData{UID: int(req.UID), Addr: addr}) + } + }) + mux.Lock() + one := &OneListen{OrderID: req.OrderID, Amount: req.Amount, Expire: time.Now().Unix() + ExpireTime} + listens, ok := listenMap[addr] + if !ok { + listenMap[addr] = []*OneListen{one} + } else { + listens = append(listens, one) + listenMap[addr] = listens + } + mux.Unlock() + + pbResp := &pb.InnerRechargeResp{APIOrderID: req.OrderID, URL: addr} + ret, _ = proto.Marshal(pbResp) + return +} + +func Withdraw(req *pb.InnerWithdrawReq) (ret []byte, err error) { + log.Debug("player withdraw:%+v", *req) + or := &common.WithdrawOrder{OrderID: req.OrderID} + db.Mysql().Get(or) + if or.Status > common.StatusROrderPay { + return + } + amount := req.Amount / 100 + if config.GetBase().Release { + or.APIPayID, err = transferUSDT("", req.Address, or.OrderID, amount, 1) + } else { + or.APIPayID, err = transferTRX("", req.Address, or.OrderID, amount, 1) + } + success := err == nil + if success { + or.Status = common.StatusROrderFinish + } else { + or.Status = common.StatusROrderFail + } + call.WithdrawCallback(or) + return +} + +func QueryOrder(req *pb.BlockPayQueryOrderReq) (ret []byte, err error) { + log.Debug("query order:%+v", req) + resp := new(pb.CommonResp) + defer func() { + ret, err = proto.Marshal(resp) + }() + + or := &common.RechargeOrder{OrderID: req.OrderID} + db.Mysql().Get(or) + if or.Status == common.StatusROrderPay { + resp.Code = 2 + resp.Msg = "该订单已完成" + return + } + + or = &common.RechargeOrder{APIPayID: req.TxID, Status: common.StatusROrderPay} + db.Mysql().Get(or) + if len(or.OrderID) > 0 { + resp.Code = 2 + resp.Msg = fmt.Sprintf("该交易单号已绑定订单%s", or.OrderID) + return + } + + tx, err := tc.GetTransactionByID(req.TxID) + if err != nil { + log.Error("err:%e", err) + resp.Code = 2 + resp.Msg = "查询交易出错" + return + } + if len(tx.Ret) == 0 { + resp.Code = 2 + resp.Msg = "该交易存疑,请联系管理员" + return + } + if tx.Ret[0].ContractRet != core.Transaction_Result_SUCCESS { + resp.Code = 2 + resp.Msg = "该交易已失败" + return + } + for _, v := range tx.RawData.Contract { + toAddr, amount := scanContracts(v) + if toAddr == "" || amount == 0 { + continue + } + if amount != or.Amount { + resp.Code = 2 + resp.Msg = "交易金额与订单不符" + return + } + addr, err1 := store.AddressFromAccountName(fmt.Sprintf("user%d", or.UID)) + if err1 != nil { + log.Error("err:%e", err) + resp.Code = 2 + resp.Msg = "获取地址失败" + return + } + if addr != toAddr { + resp.Code = 2 + resp.Msg = "交易地址有误" + return + } + _, _, err2 := store.UnlockedKeystore(addr, "") + if err2 != nil { + log.Error("err:%e", err2) + resp.Code = 2 + resp.Msg = "获取私钥失败" + return + } + // ok + paySuccess(or.OrderID, toAddr, req.TxID, or.Amount) + break + } + + return +} + +func QueryLocalOfficialAddr(req *pb.CommonMsg) (ret []byte, err error) { + resp := &pb.BlockPayQueryLocalAddrResp{} + for _, v := range OfficialAddrs { + if v == "" { + continue + } + one := &pb.OneBlockAddr{ + Addrres: v, + } + trx, err := tc.GetAccount(v) + if err != nil { + log.Error("err:%v") + } + var bal int64 + if trx != nil { + bal = trx.Balance + } + one.TRX = util.FormatFloat(float64(bal)/1e6, 6) + usdt, err := tc.TRC20ContractBalance(v, TronUSDTAddr) + if err != nil { + log.Error("err:%v") + } + var usBal int64 + if usdt != nil { + usBal = usdt.Int64() + } + one.USDT = util.FormatFloat(float64(usBal)/1e6, 6) + resp.Addrs = append(resp.Addrs, one) + } + log.Debug("get local addr:%v", OfficialAddrs) + ret, err = proto.Marshal(resp) + return +} + +func AddLocalOfficialAddr(req *pb.BlockPayAddLocalAddrReq) (ret []byte, err error) { + log.Debug("add addr:%+v", *req) + resp := &pb.CommonResp{} + defer func() { + ret, err = proto.Marshal(resp) + }() + if req.Address == "" || req.Private == "" { + resp.Code = 2 + resp.Msg = "请求参数有误" + return + } + if store.FromAddress(req.Address) != nil { + resp.Code = 2 + resp.Msg = "该地址已添加" + return + } + for i := 1; i < MaxLocalAddress; i++ { + name := fmt.Sprintf("official%d", i) + addr, _ := store.AddressFromAccountName(name) + if addr == "" { + _, err = account.ImportFromPrivateKey(req.Private, name, "") + OfficialAddrs[i-1] = req.Address + if err != nil { + log.Error("err:%e", err) + resp.Code = 2 + resp.Msg = "导入地址失败" + } + return + } + } + resp.Code = 2 + resp.Msg = "本地地址过多" + return +} + +func RemoveLocalOfficialAddr(req *pb.BlockPayAddLocalAddrReq) (ret []byte, err error) { + log.Debug("remove addr:%+v", *req) + resp := &pb.CommonResp{} + defer func() { + ret, err = proto.Marshal(resp) + }() + if req.Address == "" { + resp.Code = 2 + resp.Msg = "请求参数有误" + return + } + for i, v := range OfficialAddrs { + if v == req.Address { + err1 := account.RemoveAccount(fmt.Sprintf("official%d", i+1)) + if err1 != nil { + log.Error("err:%e", err1) + resp.Code = 2 + resp.Msg = "移除地址失败" + } + return + } + } + resp.Code = 2 + resp.Msg = "地址不存在" + return +} + +func Transfer(req *pb.BlockPayTransferReq) (ret []byte, err error) { + log.Debug("transfer:%+v", *req) + resp := &pb.BlockPayTransferResp{} + defer func() { + ret, err = proto.Marshal(resp) + }() + if req.Amount <= 0 || req.FromAddr == "" || req.ToAddr == "" { + resp.Code = 2 + resp.Msg = "请求参数有误" + return + } + var txid string + if req.Type == 1 { + txid, err = transferTRX(req.FromAddr, req.ToAddr, "", req.Amount, 3) + } else if req.Type == 2 { + txid, err = transferUSDT(req.FromAddr, req.ToAddr, "", req.Amount, 3) + } else { + resp.Code = 2 + resp.Msg = "请求参数有误" + return + } + if err != nil { + resp.Code = 2 + resp.Msg = "转账失败" + return + } + resp.TxID = txid + return +} + +func GetFee(req *pb.CommonMsg) (ret []byte, err error) { + resp := &pb.BlockPayGetFeeResp{} + defer func() { + ret, err = proto.Marshal(resp) + }() + resp.Fee = getEnergyUse(0, "") + return +} + +func ScanPlayerWallet(req *pb.BlockPayScanPlayerWalletReq) (ret []byte, err error) { + log.Debug("ScanPlayerWallet:%+v", *req) + resp := &pb.BlockPayScanPlayerWalletResp{} + defer func() { + ret, err = proto.Marshal(resp) + }() + sql := "" + if req.Down > 0 { + sql = fmt.Sprintf("usdt >= %v", req.Down) + } else { + sql = "usdt > 0" + } + if req.Up > 0 { + sql += fmt.Sprintf(" and usdt <= %v", req.Up) + } + list := []common.TronData{} + db.Mysql().QueryAll(sql, "", &common.TronData{}, &list) + + fee := getEnergyUse(0, "") + needTotal := fee * int64(len(list)) + toaddr := "" + for _, v := range OfficialAddrs { + if v == "" { + continue + } + trx, _ := tc.GetAccount(v) + if trx.Balance < needTotal { + continue + } + toaddr = v + break + } + + if toaddr == "" { + resp.Code = 2 + resp.Msg = "trx余额不足" + return + } + + for _, v := range list { + _, err := transferUSDT(v.Addr, toaddr, "", v.Usdt, 2) + if err != nil { + log.Error("err:%v", err) + resp.Fail++ + } else { + resp.Success++ + db.Mysql().UpdateW(&common.TronData{}, map[string]interface{}{"usdt": gorm.Expr("usdt - ?", v.Usdt)}, fmt.Sprintf("uid = %v and usdt >=%v", v.UID, v.Usdt)) + } + } + return +} diff --git a/modules/blockpay/module.go b/modules/blockpay/module.go new file mode 100644 index 0000000..6d5e143 --- /dev/null +++ b/modules/blockpay/module.go @@ -0,0 +1,79 @@ +package blockpay + +import ( + "server/call" + "server/config" + "server/db" + edb "server/db/es" + mdb "server/db/mysql" + rdb "server/db/redis" + "strconv" + "time" + + "github.com/liangdas/mqant/conf" + "github.com/liangdas/mqant/log" + "github.com/liangdas/mqant/module" + basemodule "github.com/liangdas/mqant/module/base" + "github.com/liangdas/mqant/server" +) + +type Pay struct { + basemodule.BaseModule +} + +var Module = func() module.Module { + this := new(Pay) + return this +} + +func (p *Pay) GetType() string { + return "blockpay" +} + +func (p *Pay) Version() string { + //可以在监控时了解代码版本 + return "1.0.0" +} + +func (p *Pay) OnInit(app module.App, settings *conf.ModuleSettings) { + metadata := make(map[string]string) + metadata["workID"] = strconv.Itoa(int(config.GetConfig().WorkID)) + p.BaseModule.OnInit(p, app, settings, + server.RegisterInterval(5*time.Second), + server.RegisterTTL(10*time.Second), + server.Metadata(metadata), + ) + call.NewCaller(p) + db.InitDB(&mdb.MysqlClient{}, &rdb.RedisClient{}, &edb.EsClient{}) + log.Info("[%v]module init finish, base:%+v", p.GetType(), config.GetBase()) + + if err := loadConfig(); err != nil { + panic(err) + } + call.InitReload(p.App.Transport()) + InitTron() + + p.GetServer().RegisterGO("recharge", Recharge) + p.GetServer().RegisterGO("withdraw", Withdraw) + p.GetServer().Register("queryOrder", QueryOrder) + p.GetServer().Register("queryAddr", QueryLocalOfficialAddr) + p.GetServer().Register("addAddr", AddLocalOfficialAddr) + p.GetServer().Register("removeAddr", RemoveLocalOfficialAddr) + p.GetServer().Register("transfer", Transfer) + p.GetServer().Register("getFee", GetFee) + p.GetServer().Register("scanPlayerWallet", ScanPlayerWallet) +} + +func (p *Pay) Run(closeSig chan bool) { + log.Info("[%v]module running", p.GetType()) + <-closeSig + log.Info("[%v]module stop", p.GetType()) +} + +func (p *Pay) OnDestroy() { + + //一定别忘了继承 + p.BaseModule.OnDestroy() + + log.Info("模块已销毁") +} diff --git a/modules/blockpay/timer.go b/modules/blockpay/timer.go new file mode 100644 index 0000000..5097a96 --- /dev/null +++ b/modules/blockpay/timer.go @@ -0,0 +1,67 @@ +package blockpay + +import ( + "fmt" + "server/call" + "server/common" + "server/config" + "server/db" + "server/util" + "sync" + "time" + + "github.com/liangdas/mqant/log" +) + +var ( + withdrawGroup sync.WaitGroup +) + +func WithdrawTimer() { + list := []*common.WithdrawOrder{} + // startData := time.Now().AddDate(0, 0, -1).Unix() + db.Mysql().QueryAll(fmt.Sprintf("status = %d and pay_source = %d", + common.StatusROrderWaitting, common.PaySourceBlockPay), "", &common.WithdrawOrder{}, &list) + l := len(list) + if l > 0 { + log.Debug("start scan orders:%v", l) + withdrawGroup.Add(l) + for _, v := range list { + // 提交订单 + res, err := db.Mysql().UpdateRes(&common.WithdrawOrder{OrderID: v.OrderID, Status: common.StatusROrderWaitting, + PaySource: common.PaySourceBlockPay}, + map[string]interface{}{"status": common.StatusROrderPay}) + if err != nil { + log.Error("err:%v", err) + withdrawGroup.Done() + continue + } + if res == 0 { + withdrawGroup.Done() + continue + } + or := v + log.Debug("trying:%v", or.OrderID) + util.Go(func() { + if config.GetBase().Release { + or.APIPayID, err = transferUSDT("", or.PayAccount, or.OrderID, or.Amount/100, 1) + } else { + or.APIPayID, err = transferTRX("", or.PayAccount, or.OrderID, or.Amount/100, 1) + } + success := err == nil + if success { + or.Status = common.StatusROrderFinish + } else { + or.Status = common.StatusROrderFail + } + call.WithdrawCallback(or) + withdrawGroup.Done() + }) + } + } + t := config.GetConfig().Pay.WithdrawScanTime + if t <= 0 { + t = 10 + } + time.AfterFunc(time.Duration(t)*time.Second, WithdrawTimer) +} diff --git a/modules/blockpay/tron.go b/modules/blockpay/tron.go new file mode 100644 index 0000000..a165093 --- /dev/null +++ b/modules/blockpay/tron.go @@ -0,0 +1,457 @@ +package blockpay + +import ( + "encoding/hex" + "errors" + "fmt" + "math/rand" + "server/call" + c "server/common" + "server/config" + "server/db" + "server/util" + "sync" + "time" + + "github.com/fbsobreira/gotron-sdk/pkg/client" + "github.com/fbsobreira/gotron-sdk/pkg/client/transaction" + "github.com/fbsobreira/gotron-sdk/pkg/common" + "github.com/fbsobreira/gotron-sdk/pkg/proto/api" + "github.com/fbsobreira/gotron-sdk/pkg/proto/core" + "github.com/fbsobreira/gotron-sdk/pkg/store" + "github.com/liangdas/mqant/log" + "google.golang.org/grpc" + "gorm.io/gorm" +) + +const ( + ScanInterval = 10 // 每10秒扫描一次block + ScanNum = 5 // 每次扫描的block数 + ExpireTime = 30 * 60 // 订单超时时间设置为30分钟 + MaxLocalAddress = 5 // 本地最大存储5个官方地址 + ExtractEnergy = 1000000 // 额外预估的能量 + FeeLimitMultiple = 5 // feelimit设置为预估的倍数 + + EnergyRate = 420 // 能量价值 + TronAddr = "grpc.trongrid.io:50051" + TestTronAddr = "grpc.nile.trongrid.io:50051" + TronUSDTAddr = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t" + TronSmartContractLen = 68 + MyAddr = "TCFpT5AsYREPWSdGmgNY4ePJFBduTe43QW" // 用于计算能量时用一下 +) + +var ( + tc *client.GrpcClient // tron client + ti int64 // 当前tron扫描到的块高度 + listenMap = map[string][]*OneListen{} // 监听地址的map,key为地址,value为监听的次数 + mux = new(sync.RWMutex) + OfficialAddrs = make([]string, MaxLocalAddress) +) + +type OneListen struct { + OrderID string // 订单号 + Expire int64 // 该监听超时时间 + Amount int64 // 充值金额 +} + +func InitTron() { + // 加载本地官方地址 + for i := 1; i <= 5; i++ { + str, err := store.AddressFromAccountName(fmt.Sprintf("official%d", i)) + if err == nil { + OfficialAddrs[i] = str + } + } + + // 从数据库中加载tron相关数据 + ti = call.GetConfigTron().CurrentBlock + // 拉取当前任然活跃需要监听的订单 + list := []c.RechargeOrder{} + db.Mysql().QueryAll(fmt.Sprintf("create_time >= %d and event = %d and pay_source = %d and status = %d", time.Now().Add(-30*time.Minute).Unix(), + c.CurrencyEventReCharge, c.PaySourceBlockPay, c.StatusROrderCreate), "", &c.RechargeOrder{}, &list) + mux.Lock() + for _, v := range list { + listens, ok := listenMap[v.PayAccount] + one := &OneListen{OrderID: v.OrderID, Expire: v.CreateTime + ExpireTime, Amount: v.Amount} + if !ok { + listenMap[v.PayAccount] = []*OneListen{one} + } else { + listens = append(listens, one) + listenMap[v.PayAccount] = listens + } + } + mux.Unlock() + addr := TronAddr + if !config.GetBase().Release { + addr = TestTronAddr + ti = call.GetConfigTron().CurrentBlockTest + } + tc = client.NewGrpcClient(addr) + if err := tc.Start(grpc.WithInsecure()); err != nil { + log.Error("err:%v", err) + } + time.AfterFunc(ScanInterval*time.Second, func() { + ScanTronBlock() + }) + WithdrawTimer() +} + +// ScanTronBlock 扫描块 +func ScanTronBlock() { + defer func() { + time.AfterFunc(ScanInterval*time.Second, func() { + ScanTronBlock() + }) + }() + now := time.Now().Unix() + // 首先清理过期监听 + mux.Lock() + for k, v := range listenMap { + listens := []*OneListen{} + for _, l := range v { + if l.Expire <= now { // 已过期 + orderID := l.OrderID + util.Go(func() { + db.Mysql().Update(&c.RechargeOrder{OrderID: orderID, Status: c.StatusROrderCreate, Event: int(c.CurrencyEventReCharge)}, + map[string]interface{}{"status": c.StatusROrderFail}) + }) + continue + } + listens = append(listens, l) + } + if len(listens) == 0 { + delete(listenMap, k) + continue + } + if len(listens) != len(v) { + listenMap[k] = listens + } + } + mux.Unlock() + + list, err := tc.GetBlockByLatestNum(ScanNum) + if err != nil { + log.Error("scan err:%v", err) + return + } + blocks := list.Block + if len(blocks) == 0 { + return + } + log.Debug("start scanning current block %d", ti+1) + // 已扫描的块高度没有达到最近的10个块,则需要重新扫描块 + if blocks[0].BlockHeader.RawData.Number > ti { + start := ti + 1 + end := blocks[0].BlockHeader.RawData.Number - 1 + diff := end - start + // 块数过多,分批扫描 + if diff > 700 { + start = blocks[0].BlockHeader.RawData.Number - 700 // 最大扫描30分钟内的块 + } + for begin := start; begin < end; begin += 100 { + e := begin + 99 + if e > end { + e = end + } + if e == begin { + e++ + } + blocksEx, err := tc.GetBlockByLimitNext(begin, e) + if err != nil { + log.Error("scan err:%v", err) + return + } + checkBlocks(blocksEx.Block) + } + } + checkBlocks(blocks) + ti = blocks[len(blocks)-1].BlockHeader.RawData.Number + // 扫描完成后更新当前扫描块 + if config.GetBase().Release { + db.Mysql().Update(&c.ConfigTron{ID: call.GetConfigTron().ID}, map[string]interface{}{"current_block": ti}) + } else { + db.Mysql().Update(&c.ConfigTron{ID: call.GetConfigTron().ID}, map[string]interface{}{"current_block_test": ti}) + } +} + +func checkBlocks(blocks []*api.BlockExtention) { + // 读取监听map + mux.Lock() + defer mux.Unlock() + log.Debug("scanning blocks %d-%d", blocks[0].BlockHeader.RawData.Number, blocks[len(blocks)-1].BlockHeader.RawData.Number) + for _, v := range blocks { + n := v.BlockHeader.RawData.Number + if n <= ti { + // log.Debug("block %d scan already", n) + continue + } + for _, j := range v.Transactions { + if j.Result.Code != api.Return_SUCCESS { + continue + } + for _, k := range j.Transaction.RawData.Contract { + toAddr, amount := scanContracts(k) + if toAddr == "" { + continue + } + listens, ok := listenMap[toAddr] + if !ok { + continue + } + var one *OneListen + index := -1 + for i, v := range listens { + if v.Amount/c.DecimalDigits == amount/1e6 { + index = i + one = v + break + } + } + if one == nil { + continue + } + + // 监听命中,发货 + if len(listens) == 1 { + delete(listenMap, toAddr) + } else { + listens = append(listens[:index], listens[index+1:]...) + listenMap[toAddr] = listens + } + txid := hex.EncodeToString(j.Txid) + paySuccess(one.OrderID, toAddr, txid, amount) + break + } + } + } +} + +// 提取contract中的toAddr,amount +func scanContracts(k *core.Transaction_Contract) (toAddr string, amount int64) { + if config.GetBase().Release { + if k.Type != core.Transaction_Contract_TriggerSmartContract { + return + } + sc := new(core.TriggerSmartContract) + if err := k.Parameter.UnmarshalTo(sc); err != nil { + log.Error("TriggerSmartContract UnmarshalTo err:%e", err) + return + } + if len(sc.Data) != TronSmartContractLen { + return + } + // 只监听usdt事件 + contractAddress := common.EncodeCheck(sc.ContractAddress) + if contractAddress != TronUSDTAddr { + return + } + sc.Data[15] = 65 // 地址前端补41 + // 取15到36位,拿到to addr + toAddr = common.EncodeCheck(sc.Data[15:36]) + // 取36到68位,拿到amount + amount = convertToInt(sc.Data[36:]) + } else { + if k.Type != core.Transaction_Contract_TransferContract { + return + } + tc := new(core.TransferContract) + if err := k.Parameter.UnmarshalTo(tc); err != nil { + log.Error("TransferContract UnmarshalTo err:%e", err) + return + } + toAddr = common.EncodeCheck(tc.ToAddress) + amount = tc.Amount + } + return +} + +// 充值成功发货 +func paySuccess(orderID, toAddr, txid string, amount int64) { + or := &c.RechargeOrder{OrderID: orderID} + log.Debug("pay success:%s,txid:%s,orderID:%s", toAddr, txid, orderID) + util.Go(func() { + db.Mysql().Get(or) + if or.Status != c.StatusROrderCreate { + return + } + or.APIPayID = txid + call.RechargeCallback(or, true, "", toAddr) + // 确认交易后,转账至官方账号,正式服出于成本考虑,后台操作扫描全服玩家。 + if config.GetBase().Release { + // 支付成功,刷新玩家地址余额 + db.Mysql().Update(&c.TronData{UID: or.UID}, map[string]interface{}{"usdt": gorm.Expr("usdt + ?", amount)}) + } else { + if _, err := transferTRX(toAddr, "", orderID, amount, 2); err != nil { + log.Error("transferTrx err:%e", err) + } + } + }) +} + +func convertToInt(b []byte) int64 { + var ret int64 + index := 0 + for i := len(b) - 1; i >= 0; i-- { + var tmp int64 = 1 + for j := 0; j < index; j++ { + tmp *= 256 + } + index++ + ret += tmp * int64(b[i]) + } + return ret +} + +func getRandomOfficiallAddr() string { + ret := []string{} + for _, v := range OfficialAddrs { + if v != "" { + ret = append(ret, v) + } + } + if len(ret) == 0 { + return "" + } + return ret[rand.Intn(len(ret))] +} + +// 转trx +// status 1玩家退出打u 2系统自动从玩家地址转u回主地址 3后台转账操作 +func transferTRX(fromAddr, toAddr, orderID string, amount int64, status int) (txid string, err error) { + defer func() { + // 此时Tx才上链 + record := &c.ESTron{ + OrderID: orderID, + TxID: txid, + From: fromAddr, + To: toAddr, + Amount: amount, + Time: time.Now().Unix(), + Type: 1, + Success: err == nil, + Status: status, + } + db.ES().InsertToESGO(c.ESIndexTron, record) + }() + if toAddr == "" { + toAddr = getRandomOfficiallAddr() + if toAddr == "" { + err = errors.New("no pay addrs") + return + } + } + if fromAddr == "" { + fromAddr = getRandomOfficiallAddr() + if fromAddr == "" { + err = errors.New("no pay addrs") + return + } + } + tx, err1 := tc.Transfer(fromAddr, toAddr, amount) + if err1 != nil { + log.Error("err:%v", err1) + err = err1 + return + } + kss, accc, err1 := store.UnlockedKeystore(fromAddr, "") + if err1 != nil { + log.Error("err:%v", err1) + err = err1 + return + } + ctrll := transaction.NewController(tc, kss, accc, tx.Transaction) + if err1 := ctrll.ExecuteTransaction(); err1 != nil { + log.Error("err:%v", err1) + err = err1 + return + } + txid = hex.EncodeToString(tx.GetTxid()) + return +} + +// 转usdt +// status 1玩家退出打u 2系统自动从玩家地址转u回主地址 3后台转账操作 +func transferUSDT(fromAddr, toAddr, orderID string, amount int64, status int) (txid string, err error) { + defer func() { + // 此时Tx才上链 + record := &c.ESTron{ + OrderID: orderID, + TxID: txid, + From: fromAddr, + To: toAddr, + Amount: amount, + Time: time.Now().Unix(), + Type: 2, + Success: err == nil, + Status: status, + } + db.ES().InsertToESGO(c.ESIndexTron, record) + }() + if toAddr == "" { + toAddr = getRandomOfficiallAddr() + if toAddr == "" { + err = errors.New("no pay addrs") + return + } + } + if fromAddr == "" { + fromAddr = getRandomOfficiallAddr() + if fromAddr == "" { + err = errors.New("no pay addrs") + return + } + } + tx, err := tc.TriggerConstantContract(fromAddr, TronUSDTAddr, "transfer(address,uint256)", + fmt.Sprintf("[{\"address\":\"%s\"},{\"uint256\":\"%d\"}]", toAddr, amount)) + if err != nil { + log.Error("err:%e", err) + return "", err + } + base := tx.EnergyUsed*EnergyRate + ExtractEnergy + tx.Transaction.RawData.FeeLimit = base * FeeLimitMultiple + // 所需能量 + need := base + ac, _ := tc.GetAccount(fromAddr) + if ac != nil { + need -= ac.Balance + } + if status == 2 && need > 0 { + _, err = transferTRX("", fromAddr, orderID, need, status) + if err != nil { + return "", err + } + } + + kss, accc, err1 := store.UnlockedKeystore(fromAddr, "") + if err1 != nil { + log.Error("err:%e", err1) + err = err1 + return + } + ctrll := transaction.NewController(tc, kss, accc, tx.Transaction) + err = ctrll.ExecuteTransaction() + if err != nil { + log.Error("err:%e", err) + return + } + txid = hex.EncodeToString(tx.GetTxid()) + log.Debug("txResult:%v", ctrll.Result) + return +} + +// 获取转U所需能量消耗 +func getEnergyUse(amount int64, toAddr string) int64 { + if toAddr == "" { + toAddr = MyAddr + } + if amount == 0 { + amount = 1e6 + } + addr := getRandomOfficiallAddr() + tc, err := tc.TriggerConstantContract(addr, TronUSDTAddr, "transfer(address,uint256)", + fmt.Sprintf("[{\"address\":\"%s\"},{\"uint256\":\"%d\"}]", toAddr, amount)) + if err != nil { + return 0 + } + return tc.EnergyUsed*EnergyRate + ExtractEnergy +} diff --git a/modules/common/activity.go b/modules/common/activity.go new file mode 100644 index 0000000..40babb5 --- /dev/null +++ b/modules/common/activity.go @@ -0,0 +1,25 @@ +package common + +import ( + "server/call" + "server/common" + "server/db" + "time" + + "gorm.io/gorm" +) + +// 首日充值返还 +func (p *Player) ActivityFirstRechargeBack() { + if !call.IsActivityValid(common.ActivityIDFirstRechargeBack) { + return + } + data := call.GetUserFirstRechargeBackData(p.uid) + // 首次充值一天内才计算 + if time.Now().Unix()-data.RechargeTime > common.ActivityFirstRechargeBackTime { + return + } + // 下注-结算=这把输钱 + lost := p.settleData.TotalBet - p.settleData.FinalSettle + db.Mysql().Update(&common.ActivityFirstRechargeBackData{UID: p.uid}, map[string]interface{}{"lost": gorm.Expr("lost + ?", lost)}) +} diff --git a/modules/common/config.go b/modules/common/config.go new file mode 100644 index 0000000..56407b5 --- /dev/null +++ b/modules/common/config.go @@ -0,0 +1,12 @@ +package common + +import ( + "server/call" + "server/pb" +) + +func loadConfig() error { + c := map[int][]func(*pb.ReloadGameConfig) error{} + call.LoadConfigs(c) + return nil +} diff --git a/modules/common/module.go b/modules/common/module.go new file mode 100644 index 0000000..9e74d4f --- /dev/null +++ b/modules/common/module.go @@ -0,0 +1,61 @@ +package common + +import ( + "server/call" + "server/db" + "server/db/mysql" + rdb "server/db/redis" + "time" + + edb "server/db/es" + + "github.com/liangdas/mqant/conf" + "github.com/liangdas/mqant/log" + "github.com/liangdas/mqant/module" + basemodule "github.com/liangdas/mqant/module/base" + "github.com/liangdas/mqant/server" +) + +type Call struct { + basemodule.BaseModule +} + +var Module = func() module.Module { + this := new(Call) + return this +} + +func (c *Call) GetType() string { + //很关键,需要与配置文件中的Module配置对应 + return "common" +} +func (c *Call) Version() string { + //可以在监控时了解代码版本 + return "1.0.0" +} + +func (c *Call) OnInit(app module.App, settings *conf.ModuleSettings) { + c.BaseModule.OnInit(c, app, settings, + server.RegisterInterval(5*time.Second), + server.RegisterTTL(10*time.Second), + ) + + db.InitDB(&rdb.RedisClient{}, &edb.EsClient{}, &mysql.MysqlClient{}) + + call.NewCaller(c) + loadConfig() + initNats(c.App.Transport()) + initTimer() +} + +func (c *Call) Run(closeSig chan bool) { + log.Info("[%v]module running", c.GetType()) + <-closeSig + log.Info("[%v]module stop", c.GetType()) +} + +func (c *Call) OnDestroy() { + //一定别忘了继承 + c.BaseModule.OnDestroy() + log.Info("[%v]module destroy", c.GetType()) +} diff --git a/modules/common/nats.go b/modules/common/nats.go new file mode 100644 index 0000000..c12a7b8 --- /dev/null +++ b/modules/common/nats.go @@ -0,0 +1,63 @@ +package common + +import ( + "server/call" + "server/common" + "server/natsClient" + "server/pb" + + "github.com/gogo/protobuf/proto" + "github.com/liangdas/mqant/log" + "github.com/nats-io/nats.go" +) + +func initNats(conn *nats.Conn) { + call.InitReload(conn) + natsClient.NewCommonNatsImp(conn, natsClient.TopicInnerAfterSettle, func(data []byte) { + d := new(pb.InnerAfterSettle) + err := proto.Unmarshal(data, d) + if err != nil { + log.Error("err:%v", err) + return + } + afterSettle(d) + }, natsClient.QueueAfterSettle) +} + +type Player struct { + uid int + gid int + settleData *pb.InnerAfterSettle +} + +func afterSettle(d *pb.InnerAfterSettle) { + log.Debug("afterSettle:%+v", *d) + UpdateGameData(d) + call.ShareSettle(d) + p := &Player{uid: int(d.UID), gid: int(d.GameID), settleData: d} + p.ActivityFirstRechargeBack() +} + +func UpdateGameData(d *pb.InnerAfterSettle) { + call.UpdatePlayerProfile(&common.ESGameData{ + UID: int(d.UID), + Provider: int(d.ProviderID), + GameID: int(d.GameID), + UUID: d.UUID, + Type: common.CurrencyType(d.CurrencyType), + BetAmount: d.TotalBet, + SettleAmount: d.FinalSettle, + MyUUID: d.MyUUID, + }) + call.UpdateGameSort(int(d.ProviderID), int(d.GameID)) +} + +// 刷新分享投注 +// func Share(d *pb.InnerAfterSettle) { +// shareInfo := &common.ShareInfo{UID: int(d.UID)} +// db.Mysql().Get(shareInfo) +// if shareInfo.UP == 0 { +// return +// } +// db.Mysql().Update(&common.ShareInfo{UID: int(d.UID)}, map[string]interface{}{"bet": gorm.Expr("bet + ?", d.TotalBet)}) +// } diff --git a/modules/common/timer.go b/modules/common/timer.go new file mode 100644 index 0000000..90718a0 --- /dev/null +++ b/modules/common/timer.go @@ -0,0 +1,616 @@ +package common + +import ( + "fmt" + "math/rand" + "reflect" + "server/call" + "server/common" + "server/config" + "server/db" + "server/util" + "time" + + "github.com/liangdas/mqant/log" + "gorm.io/gorm" +) + +// 老的分享结算方案暂不使用 +func initTimer() { + // 结算分享 + // ShareSettle() + // 结算重置每日数据 + // ShareReset() + // 分享机器人 + ShareRobotTimer() + // slots奖池活动机器人 + ActivitySlotsRobotTimer() + // slots奖池活动结算 + ActivitySlotsSettleTimer() +} + +var ( + ShareSettleTime = 3 * time.Minute + Day5Minute int64 = 288 // 一天有288个5分钟 +) + +type updateShare struct { + Level int + MyBets int64 // 自己本次结算下注额 + Bets int64 // 记录需要更新的总下注 + Reals int // 记录需要更新的有效下线个数 + Rewards int64 // 自己的佣金 + UpRewards int64 // 对上级的贡献佣金 +} + +func ShareRobotTimer() { + robots := call.GetConfigShareRobot() + // 每5分钟刷新一次数据 + for _, v := range robots { + if v.RobotID == 0 { + continue + } + if db.Mysql().Exist(&common.PlayerDBInfo{Id: v.RobotID}) { + log.Error("invalid robotid:%v", v.RobotID) + continue + } + one := &common.ShareInfo{UID: v.RobotID} + diff := v.DayCashUp - v.DayCashDown + if diff <= 0 { + continue + } + add := rand.Int63n(diff) + v.DayCashDown + add /= Day5Minute + db.Mysql().Get(one) + if one.ID == 0 { + one.Share = util.GetShareCode(one.UID) + one.BetReward = v.InitCash + add + db.Mysql().Create(one) + } else { + db.Mysql().Update(&common.ShareInfo{UID: v.RobotID}, map[string]interface{}{"bet_reward": gorm.Expr("bet_reward + ?", add)}) + } + } + minute := time.Now().Minute() + next := minute % 5 + if next == 0 { + next = 5 + } + log.Debug("next robot timer:%v", next) + time.AfterFunc(time.Duration(5)*time.Minute, ShareRobotTimer) +} + +/* +每日使用10个AI在数字900-990之间随机分别选取一个数字(若选取数字大于910的AI玩家小于3,则重新选取直到选取数字大于910的玩家大于3) +每日使用5个AI在数字850-900之间随机分别选取一个数字 +每日中午12点至下午6点每隔一小时随机上场一个ai,6点后每隔15~25分钟上场一个 +*/ +func ActivitySlotsRobotTimer() { + now := time.Now() + hour := now.Hour() + var next int64 + defer func() { + log.Debug("next ActivitySlotsRobotTimer:%v", next) + time.AfterFunc(time.Duration(next)*time.Second, ActivitySlotsRobotTimer) + }() + if !config.GetBase().Release { + next = 5 * 60 + ActivitySlotsRobotJoin() + return + } + if hour < 12 { + next = util.GetZeroTime(now).Unix() + 12*60*60 + 1 - now.Unix() + return + } else if hour < 18 { + // 一小时内随机生成一个机器人 + diff := util.GetZeroTime(now).Unix() + int64(hour+1)*60*60 - time.Now().Unix() + next = diff + ran := rand.Intn(int(diff)) + 1 + log.Debug("next ActivitySlotsRobotJoin:%v", ran) + time.AfterFunc(time.Duration(ran)*time.Second, func() { + ActivitySlotsRobotJoin() + }) + return + } else if hour < 23 { + ran := rand.Int63n(10) + 15 + next = ran * 60 + log.Debug("next ActivitySlotsRobotJoin:%v", ran) + ActivitySlotsRobotJoin() + } else { + next = util.GetZeroTime(now.AddDate(0, 0, 1)).Unix() + 12*60*60 + 1 - now.Unix() + } +} + +const ( + Max910Robot = 3 // 数字大于910的机器人 + Max900Robot = 7 // 900-990的机器人数 + Max850Robot = 5 // 850-900的机器人数 + MaxRobotCount = 15 +) + +// 每日结算后初始10个随机低分机器人 +func ActivitySlotsFirstRobotJoin() { + list := []*common.ActivitySlotsData{} + _, err := db.Mysql().QueryAll("role = 2", "uid asc", &common.ActivitySlotsData{}, &list) + if err != nil { + log.Error("err:%v", err) + return + } + log.Debug("ActivitySlotsFirstRobotJoin,list:%v", len(list)) + isSingle := call.IsActivitySingleDay(common.ActivityIDSlots) + for i := 0; i < 10; i++ { + phone := util.CheckPhone("") + avatar := call.RandomUserAvatar() + robotUID := 100 + i + number := rand.Intn(400) + 1 + if i < len(list) { + u := map[string]interface{}{"best_number2": number, "time2": time.Now().Unix(), "nick": phone, "avatar": avatar} + if isSingle { + u = map[string]interface{}{"best_number1": number, "time1": time.Now().Unix(), "nick": phone, "avatar": avatar} + } + db.Mysql().Update(&common.ActivitySlotsData{ID: list[i].ID}, u) + continue + } + data := &common.ActivitySlotsData{UID: robotUID, Role: 2, Nick: phone, Avatar: avatar} + if isSingle { + data.BestNumber1 = number + data.Time1 = time.Now().Unix() + } else { + data.BestNumber2 = number + data.Time2 = time.Now().Unix() + } + db.Mysql().Create(data) + } +} + +// 生成一个机器人 +func ActivitySlotsRobotJoin() { + isSingle := call.IsActivitySingleDay(common.ActivityIDSlots) + zero := util.GetZeroTime(time.Now()).Unix() + list := []*common.ActivitySlotsData{} + _, err := db.Mysql().QueryAll("role = 1", "uid asc", &common.ActivitySlotsData{}, &list) + if err != nil { + return + } + var count850, count900, count910 int + var canUpdate *common.ActivitySlotsData + for i, v := range list { + ref := reflect.ValueOf(v).Elem() + index := 2 + if isSingle { + index = 1 + } + t := ref.FieldByName(fmt.Sprintf("Time%d", index)).Int() + if t < zero { + if canUpdate == nil { + canUpdate = list[i] + } + continue + } + number := ref.FieldByName(fmt.Sprintf("BestNumber%d", index)).Int() + if number < 900 { + count850++ + } else if number <= 910 { + count900++ + } else { + count910++ + } + } + total := count850 + count900 + count910 + if total >= MaxRobotCount { + return + } + all := make([]int, 3) + all[0] = Max850Robot - count850 + all[1] = Max900Robot - count900 + all[2] = Max910Robot - count910 + ran := rand.Intn(all[0] + all[1] + all[2]) + this := 0 + index := 0 + for i := 0; i < 3; i++ { + this += all[i] + if ran < this { + index = i + break + } + } + number := 0 + switch index { + case 0: + number = rand.Intn(50) + 850 + case 1: + number = rand.Intn(11) + 900 + case 2: + number = rand.Intn(89) + 911 + } + if number == 0 { + return + } + phone := util.CheckPhone("") + avatar := call.RandomUserAvatar() + if canUpdate != nil { + u := map[string]interface{}{"best_number2": number, "time2": time.Now().Unix(), "nick": phone, "avatar": avatar} + if isSingle { + u = map[string]interface{}{"best_number1": number, "time1": time.Now().Unix(), "nick": phone, "avatar": avatar} + } + db.Mysql().Update(&common.ActivitySlotsData{ID: canUpdate.ID}, u) + } else { + robotUID := 2 + if len(list) > 0 { + robotUID = list[len(list)-1].UID + 1 + } + data := &common.ActivitySlotsData{UID: robotUID, Role: 1, Nick: phone, Avatar: avatar} + if isSingle { + data.BestNumber1 = number + data.Time1 = time.Now().Unix() + } else { + data.BestNumber2 = number + data.Time2 = time.Now().Unix() + } + db.Mysql().Create(data) + } +} + +func ActivitySlotsSettleTimer() { + now := time.Now() + next := util.GetZeroTime(now.AddDate(0, 0, 1)).Unix() + 5 - now.Unix() // 零点过5秒结算 + if !config.GetBase().Release { + next = util.GetNext5MinUnix() - now.Unix() + } + defer func() { + log.Debug("next ActivitySlotsSettleTimer:%v", next) + time.AfterFunc(time.Duration(next)*time.Second, ActivitySlotsSettleTimer) + }() + // if !call.IsActivityValid(common.ActivityIDSlots) { + // return + // } + date := now.AddDate(0, 0, -1).Format("20060102") + record := &common.ActivitySlotsRecord{ + UID: -1, // 代表系统结算标志 + Date: date, + } + db.Mysql().Get(record) + if record.Settle == 1 { + return + } + con := call.GetConfigActivitySlots() + if len(con) == 0 { + return + } + + list := []*common.ActivitySlotsData{} + order := "best_number1 desc,time1 asc" + str := "time1" + isSingle := call.IsActivitySingleDay(common.ActivityIDSlots) + + if !config.GetBase().Release { + if !isSingle { // 测试服结算当天的 + order = "best_number2 desc,time2 asc" + str = "time2" + } + } else { + if isSingle { // 由于是结算前一天,所以查询逻辑是相反的 + order = "best_number2 desc,time2 asc" + str = "time2" + } + } + db.Mysql().QueryList(0, con[len(con)-1].RankUp, fmt.Sprintf("%s >= %d", str, util.GetZeroTime(time.Now().AddDate(0, 0, -1)).Unix()), + order, &common.ActivitySlotsData{}, &list) + + for i, v := range list { + if i == 0 { + if config.GetBase().Release { + if isSingle { + record.BestNumber = v.BestNumber2 + } else { + record.BestNumber = v.BestNumber1 + } + } else { + if isSingle { + record.BestNumber = v.BestNumber1 + } else { + record.BestNumber = v.BestNumber2 + } + } + } + if v.Role > 0 { + continue + } + reward := call.GetConfigActivitySlotsRewardByRank(i + 1) + if reward == 0 { + continue + } + // call.UpdateCurrencyPro(&common.UpdateCurrency{ + // CurrencyBalance: &common.CurrencyBalance{ + // UID: v.UID, + // Time: now.Unix(), + // Value: reward, + // Type: common.CurrencyBrazil, + // Event: common.CurrencyEventActivitySlots, + // NeedBet: call.GetConfigCurrencyResourceNeedBet(common.CurrencyResourceBonus, reward), + // }, + // }) + one := &common.ActivitySlotsRecord{Date: date, UID: v.UID, Reward: reward, BestNumber: record.BestNumber, Settle: 0, Rank: i + 1} + if config.GetBase().Release { + if isSingle { + one.MyNumber = v.BestNumber2 + } else { + one.MyNumber = v.BestNumber1 + } + } else { + if isSingle { + one.MyNumber = v.BestNumber1 + } else { + one.MyNumber = v.BestNumber2 + } + } + db.Mysql().Create(one) + } + record.Settle = 1 + db.Mysql().Create(record) + + ActivitySlotsFirstRobotJoin() +} + +// func ShareSettle() { +// if !config.GetBase().Release { +// ShareSettleTime = 30 * time.Second +// } +// log.Debug("next ShareSettle:%v", ShareSettleTime) +// time.AfterFunc(ShareSettleTime, func() { +// defer ShareSettle() +// all := map[int]*updateShare{} +// ret := []*common.ShareInfo{} +// db.Mysql().QueryAll("bet>0 and up>0", "", &common.ShareInfo{}, &ret) +// log.Debug("settle:%v", ret) +// if len(ret) == 0 { +// return +// } +// for _, v := range ret { +// bet := v.Bet +// reals := 0 +// if v.TotalBet == 0 { // 产生有效投注,需要更新上级有效下线个数 +// reals = 1 +// PddUpdate(v.UID, v.UP) // 产生有效投注,更新pdd数据 +// } +// // 循环查询结算上级 +// this := v +// lastLevel := 0 // 上一次结算时的上级等级,需要比较该值确定上级是否还能分得佣金 +// for { +// log.Debug("this:%+v", this) +// if this.UP == 0 { +// break +// } +// upInfo := &common.ShareInfo{UID: this.UP} +// db.Mysql().Get(upInfo) +// if upInfo.ID <= 0 { +// break +// } +// u, ok := all[upInfo.UID] +// if !ok { +// u = &updateShare{ +// Bets: bet, +// Reals: reals, +// } +// all[upInfo.UID] = u +// } else { +// u.Bets += bet +// u.Reals += reals +// } +// // 首先计算是否升级 +// con := call.GetConfigShareLevelByBet(upInfo.TotalAgentsBet + u.Bets) +// if con != nil { +// // 升级了标记 +// if con.Level > upInfo.Level { +// u.Level = con.Level +// } +// // 计算等级差 +// var per int64 +// if con.Level > lastLevel { +// if lastLevel == 0 { +// per = con.Per +// } else { +// lastCon := call.GetConfigShareByLevel(lastLevel) +// if lastCon != nil { +// per = con.Per - lastCon.Per +// } +// } +// lastLevel = con.Level +// } +// if per > 0 { +// reward := per * bet / 10000 +// if reward > 0 { +// u.Rewards += reward +// thisU, ok := all[this.UID] +// if !ok { +// all[this.UID] = &updateShare{ +// UpRewards: reward, +// } +// } else { +// thisU.UpRewards += reward +// } +// } +// } +// } +// this = upInfo +// } +// // 结算完后,更新自己的下注 +// my, ok := all[v.UID] +// if !ok { +// all[v.UID] = &updateShare{ +// MyBets: bet, +// } +// } else { +// my.MyBets = bet +// } +// } +// // 计算完成后统一更新玩家数据 +// log.Debug("start share update:%d", len(all)) +// success := 0 +// fail := 0 +// for uid, v := range all { +// update := map[string]interface{}{} +// if v.MyBets > 0 { +// update["bet"] = gorm.Expr("bet - ?", v.MyBets) +// update["today_bet"] = gorm.Expr("today_bet + ?", v.MyBets) +// update["total_bet"] = gorm.Expr("total_bet + ?", v.MyBets) +// } +// if v.Bets > 0 { +// update["today_agents_bet"] = gorm.Expr("today_agents_bet + ?", v.Bets) +// update["total_agents_bet"] = gorm.Expr("total_agents_bet + ?", v.Bets) +// } +// if v.Reals > 0 { +// update["today_real_agents"] = gorm.Expr("today_real_agents + ?", v.Reals) +// update["total_real_agents"] = gorm.Expr("total_real_agents + ?", v.Reals) +// } +// if v.Rewards > 0 { +// update["today_reward"] = gorm.Expr("today_reward + ?", v.Rewards) +// update["total_reward"] = gorm.Expr("total_reward + ?", v.Rewards) +// update["available_reward"] = gorm.Expr("available_reward + ?", v.Rewards) +// } +// if v.UpRewards > 0 { +// update["today_up_reward"] = gorm.Expr("today_up_reward + ?", v.UpRewards) +// } +// err := db.Mysql().Update(&common.ShareInfo{UID: uid}, update) +// if err != nil { +// fail++ +// } else { +// success++ +// } +// } +// log.Debug("finish share update:%d,success:%d,fail:%d", len(all), success, fail) +// }) +// } + +var ( + ShareQueryNum = 5000 +) + +// func ShareReset() { +// next := time.Until(util.GetZeroTime(time.Now().AddDate(0, 0, 1)).Add(5 * time.Second)) +// log.Debug("next ShareReset:%v", next) +// time.AfterFunc(next, func() { +// defer ShareReset() +// recordTime := time.Now().AddDate(0, 0, -1) +// recordTimeUnix := recordTime.Unix() +// recordTimeDate := recordTime.Format("20060102") +// start := 0 +// for { +// ret := []common.ShareInfo{} +// db.Mysql().QueryListW(start, ShareQueryNum, "", &common.ShareInfo{}, &ret, "today_agents_bet+today_agents>0") +// for _, v := range ret { +// uid := v.UID +// db.ES().InsertToESGO(common.ESIndexShareProfitReport, &common.ESShareProfitReport{ +// UID: uid, +// Date: recordTimeDate, +// Regist: v.TodayAgents, +// Bet: v.TodayAgentsBet, +// Level: v.Level, +// Time: recordTimeUnix, +// Reward: v.TodayReward, +// }) +// thisStart := 0 +// for { +// agents := []common.ShareInfo{} +// db.Mysql().QueryListW(thisStart, ShareQueryNum, "", &common.ShareInfo{}, &agents, fmt.Sprintf("up = %d", uid)) +// for _, v := range agents { +// one := &common.ESShareProfitRecord{ +// UID: v.UID, +// Bet: v.TodayBet, +// DownBet: v.TodayAgentsBet, +// Up: uid, +// Reward: v.TodayUpReward, +// Date: recordTimeDate, +// Time: recordTimeUnix, +// } +// db.ES().InsertToESGO(common.ESIndexShareProfitRecord, one) +// } +// if len(agents) < ShareQueryNum { +// break +// } +// thisStart++ +// } +// } +// if len(ret) < ShareQueryNum { +// break +// } +// start++ +// } +// db.Mysql().UpdateW(&common.ShareInfo{}, map[string]interface{}{"today_agents_bet": 0, "today_agents": 0, "today_real_agents": 0, "today_reward": 0, "today_bet": 0}, +// "today_agents_bet+today_agents>0") +// }) +// } + +// 更新玩家pdd邀请数据 +func PddUpdate(uid, referer int) { + if referer == 0 { + return + } + con := call.GetConfigActivityPdd() + if con == nil { + return + } + now := time.Now().Unix() + pddData := &common.PddData{UID: referer} + db.Mysql().Get(pddData) + if now-pddData.Time >= con.Expire*60 { + return + } + db.Mysql().Update(&common.PddData{UID: referer, Time: pddData.Time}, map[string]interface{}{"spin": gorm.Expr("spin + 1")}) + user, _ := call.GetUserXInfo(uid, "nick", "avatar") + db.ES().InsertToESGO(common.ESIndexBackPddRecord, common.ESPddRecord{ + UID: uid, + Referer: referer, + Time: now, + Nick: user.Nick, + Avatar: user.Avatar, + }) +} + +// initRecordCurrency 记录玩家身上货币,可退出货币 +func initRecordCurrency() { + next := time.Until(util.GetZeroTime(time.Now().AddDate(0, 0, 1)).Add(time.Second)) + // next := time.Until(time.Now().Add(time.Minute)) + time.AfterFunc(next, recordCurrency) + log.Debug("next initRecordCurrency:%v", next) +} + +func recordCurrency() { + initRecordCurrency() + // var total, totalCash int64 + // channel := call.GetChannelListReal() + // con := call.GetConfigWithdrawProduct(common.WithdrawSorceBank) + // var least int64 // 最低退出所需金币 + // for _, v := range con { + // if least == 0 || v.PayGold < least { + // least = v.PayGold + // } + // } + // for _, v := range channel { + // sql := fmt.Sprintf("channel_id = %v and role <> %v and status = %v", v.ChannelID, common.PlayerRoleRobot, common.AccountStatusNormal) + // err := db.Mysql().C().Table("users").Where(sql).Select("sum(bind_cash+cash)").Scan(&total).Error + // if err != nil { + // log.Error("err:%v", err) + // } + // tsql := sql + fmt.Sprintf(" and cash > %v", least) + // err = db.Mysql().C().Table("users").Where(tsql).Select("sum(cash)").Scan(&totalCash).Error + // if err != nil { + // log.Error("err:%v", err) + // } + // if total == 0 && totalCash == 0 { + // continue + // } + // brl := db.Mysql().Sum(&common.PlayerCurrency{}, "", "brl") + // usdt := db.Mysql().Sum(&common.PlayerCurrency{}, "", "usdt") + + // recordDate := time.Now().AddDate(0, 0, -1) + // date := recordDate.Format("2006-01-02") + + // log.Debug("record date:%v,total:%v,totalCash:%v", date, total, totalCash) + // db.ES().InsertToESGO(common.ESIndexBackDailyData, &common.ESDailySysData{ + // Date: date, + // Channel: v.ChannelID, + // BRL: brl, + // USDT: usdt, + // }) + // } +} diff --git a/modules/crash/handler.go b/modules/crash/handler.go new file mode 100644 index 0000000..476beaa --- /dev/null +++ b/modules/crash/handler.go @@ -0,0 +1,52 @@ +package crash + +import ( + "server/game" + "server/pb" + + "github.com/liangdas/mqant/gate" + "github.com/liangdas/mqant/log" + timewheel "github.com/liangdas/mqant/module/modules/timer" +) + +func OnCashout(session gate.Session, req *pb.GameCommonReq) (string, error) { + p := game.FindPlayer(session) + if p == nil { + log.Error("player %v not found", session.GetUserID()) + return "", nil + } + log.Debug("player %v OnCashout %+v", p.UID, *req) + p.T.PutQueue("nf", func() { + if p.Status != pb.PlayerStatus_PlayerStatusPlaying || p.T.Status != pb.GameStatus_GameStatusSpecial { + return + } + p.SubPlayer.(*Player).CashOut() + }) + return "", nil +} + +func OnAutoCashout(session gate.Session, req *pb.CrashMsgAutoCashoutReq) (string, error) { + p := game.FindPlayer(session) + if p == nil { + log.Error("player %v not found", session.GetUserID()) + return "", nil + } + log.Debug("player %v OnAutoCashout %+v", p.UID, *req) + if req.ODD <= 100 { + req.ODD = 0 + } + p.T.PutQueue("nf", func() { + player := p.SubPlayer.(*Player) + player.SetAutoCashoutOdd = req.ODD + // 飞行过程中不允许改变自动下车倍数 + // if req.ODD > 100 && p.T.Status == pb.GameStatus_GameStatusSpecial { + // return + // } + if req.ODD == 0 && p.T.Status == pb.GameStatus_GameStatusSpecial { + player.AutoCashoutOdd = req.ODD + timewheel.GetTimeWheel().RemoveTimer(player.AutoCashoutTimer) + } + player.Send(int(pb.CrashProtocol_CrashAutoCashoutResp), &pb.GameCommonResp{}) + }) + return "", nil +} diff --git a/modules/crash/module.go b/modules/crash/module.go new file mode 100644 index 0000000..48401b2 --- /dev/null +++ b/modules/crash/module.go @@ -0,0 +1,24 @@ +package crash + +import ( + "server/game" + "server/pb" + "strconv" + + "github.com/liangdas/mqant/module" +) + +type Sub struct { + *game.Module +} + +var Module = func() module.Module { + m := game.NewModule(int(pb.ServerType_ServerTypeCrash), game.GameTypeMillion, "Crash", NewTable) + m.Sub = &Sub{Module: m} + return m +} + +func (s *Sub) RegistHandler() { + s.GetServer().RegisterGO("/"+strconv.Itoa(int(pb.CrashProtocol_CrashCashOutReq)), OnCashout) + s.GetServer().RegisterGO("/"+strconv.Itoa(int(pb.CrashProtocol_CrashAutoCashoutReq)), OnAutoCashout) +} diff --git a/modules/crash/player.go b/modules/crash/player.go new file mode 100644 index 0000000..a2a0a63 --- /dev/null +++ b/modules/crash/player.go @@ -0,0 +1,106 @@ +package crash + +import ( + "server/game" + "server/pb" + + timewheel "github.com/liangdas/mqant/module/modules/timer" +) + +type Player struct { + *game.Player + SetAutoCashoutOdd int64 // 客户端设置的值 + AutoCashoutOdd int64 // 自动下车生效倍数 + IsAutoSettle bool // 是否是自动下车触发的结算 + AutoCashoutTimer string +} + +func NewPlayer(player *game.Player) *Player { + p := &Player{Player: player} + player.MillionSubPlayer = game.NewMillionSubPlayer(player, p) + return p +} + +func (p *Player) Reset() { + p.IsAutoSettle = false + p.AutoCashoutOdd = 0 + p.AutoCashoutTimer = "" +} + +func (p *Player) Enter() { + p.OnEnter() +} + +func (p *Player) OnEnter() { + p.TableInfo() +} + +func (p *Player) TableInfo() { + t := p.T.SubTable.(*Table) + resp := &pb.CrashMsgTableInfoResp{TableInfo: &pb.GameCommonTableInfo{}} + p.PackTableInfo(resp.TableInfo) + resp.AutoCashOut = p.SetAutoCashoutOdd + if t.Status == pb.GameStatus_GameStatusSettle { + resp.Multiple = t.Result + } else if t.Status == pb.GameStatus_GameStatusSpecial { + resp.Multiple = t.CalOdds() + 100 + } + p.Send(int(pb.GameProtocol_TableInfoResp), resp) +} + +func (p *Player) Settle() { + t := p.T.SubTable.(*Table) + odds := GetOdds(int64(t.FlyTime)) + shouldSettleCashout := t.Status == pb.GameStatus_GameStatusSpecial // 是否应该结算退出获利 + if t.Status == pb.GameStatus_GameStatusSpecial { + if p.IsAutoSettle { + odds = p.AutoCashoutOdd - 100 + } else { + odds = t.CalOdds() + } + } else if p.AutoCashoutOdd-100 <= odds { + odds = p.AutoCashoutOdd - 100 + shouldSettleCashout = true + } + if odds <= 0 { + shouldSettleCashout = false + } + if p.AutoCashoutTimer != "" { + timewheel.GetTimeWheel().RemoveTimer(p.AutoCashoutTimer) + } + p.Debug("settle,odds:%v", odds) + // 爆炸前结算或者开出jackpot时玩家还未退出,按最高倍数结算 + if shouldSettleCashout { + p.FinalSettle = (odds + 100) * p.TotalBet / 100 + if t.Status == pb.GameStatus_GameStatusSpecial { + t.Broadcast(int(pb.CrashProtocol_CrashCashOutResp), &pb.CrashMsgOtherCashOutResp{UID: uint32(p.UID), ODD: odds + 100, Nick: p.Nick}, p.UID) + p.Send(int(pb.CrashProtocol_CrashCashOutResp), &pb.CrashMsgOtherCashOutResp{UID: uint32(p.UID), ODD: odds + 100, Nick: p.Nick, Amount: p.FinalSettle}) + } + } + p.Debug("set:%v", p.FinalSettle) + p.MillionSubPlayer.Settle() +} + +func (p *Player) Leave() bool { + return p.MillionLeave() +} + +func (p *Player) CashOut() { + t := p.T + if t.Status != pb.GameStatus_GameStatusSpecial { + return + } + p.Player.Settle() +} + +func (p *Player) AutoCashOut() { + t := p.T + if t.Status != pb.GameStatus_GameStatusSpecial { + return + } + if p.AutoCashoutOdd == 0 { + return + } + p.IsAutoSettle = true + p.Player.Settle() +} diff --git a/modules/crash/table.go b/modules/crash/table.go new file mode 100644 index 0000000..9936430 --- /dev/null +++ b/modules/crash/table.go @@ -0,0 +1,216 @@ +package crash + +import ( + "fmt" + "server/call" + "server/common" + "server/db" + "server/game" + "server/pb" + "time" + + timewheel "github.com/liangdas/mqant/module/modules/timer" +) + +type Table struct { + *game.Table + Result int64 + StartFlyTime int64 + FlyTime int +} + +func NewTable() game.SubTable { + t := &Table{} + return t +} + +func (t *Table) Init(table *game.Table) { + t.Table = table + table.NewMillionSubTable() + table.MillionSubTableInter = t +} + +func (t *Table) Reset() { + t.StartFlyTime = 0 + t.FlyTime = 0 + t.MillionSubTable.Reset() +} + +func (t *Table) Enter(player *game.Player) { + player.SubPlayer = NewPlayer(player) +} + +func (t *Table) StartGame() { + dur := t.Config.BetTime + t.OperateTimeout = dur*1000 + time.Now().UnixMilli() + t.StartFlyingTimer(time.Duration(dur) * time.Second) + resp := &pb.CrashMsgGameStartResp{ + TimeLeft: int64(dur), + } + t.Broadcast(int(pb.GameProtocol_GameStartResp), resp) +} + +// SettleResult 获取结算结果 +func (t *Table) SettleResult() { + // diff := time.Now().UnixMilli() - t.StartFlyTime + // t.Debug("fly diff:%v", diff) + t.Result = GetOdds(int64(t.FlyTime)) + 100 + t.Debug("result:%v", t.Result) + l := len(t.History) + t.History = append([]int64{t.Result}, t.History...) + if l >= MaxHistoryLen { + t.History = t.History[:MaxHistoryLen] + } + if call.IsMainServer(game.ThisGameID) { + db.Redis().SetJsonData(common.GetRedisKeyGameResult(game.ThisGameID, t.Room.RoomId()), t.History) + } +} + +func (t *Table) Settle() { + t.Debug("settle") + t.SettleResult() + dur := t.Config.SettleTime * 1000 + t.SettleGameTimer(time.Duration(dur) * time.Millisecond) + now := time.Now().UnixMilli() + t.OperateTimeout = now + dur + for _, v := range t.GetSeats() { + p := v.(*game.Player).SubPlayer.(*Player) + p.MillionSettle() + } + for _, v := range t.GetSeats() { + p := v.(*game.Player).SubPlayer.(*Player) + resp := &pb.CrashMsgGameSettleResp{ + TimeLeft: dur, + Result: t.Result, + SettleAmount: p.FinalSettle, + } + p.Send(int(pb.GameProtocol_GameSettleResp), resp) + } +} + +// GetResult 返回开奖区域 +func (t *Table) GetResult() (string, string) { + res := fmt.Sprintf("%v", t.Result) + return res, res +} + +// MaxSeats 返回最大座位数 +func (t *Table) MaxSeats() int { + return game.MillionMaxSeat +} + +func (t *Table) Destroy() { + t.Debug("sub Destroy") +} + +// StartFlyingTimer 火箭飞行计时器 +func (t *Table) StartFlyingTimer(dur time.Duration) { + t.Timer = fmt.Sprintf("flying_%v", t.TableId()) + timewheel.GetTimeWheel().AddTimerCustom(dur, t.Timer, nil, func(arge interface{}) { + t.Debug("flying timer callback") + t.PutQueue("nf", t.Fly) + }) +} + +// Fly 火箭开始飞行 +func (t *Table) Fly() { + t.Debug("start flying") + waterCon := call.GetConfigWaterReal(game.ThisGameID, t.Room.RoomId()) + rtp := waterCon.Rtp + if waterCon.Value > waterCon.WaterUp { + rtp = waterCon.UpRtp + } else if waterCon.Value < waterCon.WaterLower { + rtp = waterCon.DownRtp + } + t.FlyTime = t.CalFlyingTime(rtp) + + t.StartFlyTime = time.Now().UnixMilli() + t.Status = pb.GameStatus_GameStatusSpecial + t.Timer = fmt.Sprintf("fly_%v", t.TableId()) + + // t.OperateTimeout = t.StartFlyTime + dur := time.Duration(t.FlyTime) * time.Millisecond + timewheel.GetTimeWheel().AddTimerCustom(dur, t.Timer, nil, func(arge interface{}) { + t.Put("nf", func() { + t.DelaySettle() + }) + }) + t.Debug("flyTime:%v", t.FlyTime) + t.Broadcast(int(pb.CrashProtocol_CrashFlyingStartResp), &pb.GameCommonResp{}) + // 设定玩家自动退出 + for _, v := range t.GetSeats() { + p := v.(*game.Player).SubPlayer.(*Player) + p.AutoCashoutOdd = p.SetAutoCashoutOdd + if p.AutoCashoutOdd == 0 || p.TotalBet == 0 { + continue + } + p.AutoCashoutTimer = fmt.Sprintf("autocashout%v", p.UID) + timewheel.GetTimeWheel().AddTimerCustom(time.Duration(GetFlyTime(p.AutoCashoutOdd-100))*time.Millisecond, p.AutoCashoutTimer, nil, func(arge interface{}) { + t.PutQueue("nf", func() { + p.AutoCashOut() + }) + }) + } +} + +func (t *Table) CalFlyingTime(rtp int64) int { + t.Debug("CalFlyingTime rtp:%v", rtp) + var odd int64 = 100 + origin := t.Ran.Int63n(MaxWeight) // 随机一个数 + for i := int64(100); i <= MaxOdd; i++ { // 判断随机数落在哪个权重区间 + total := rtp * MaxWeight / i / 100 // 由于rtp为万分位,i为扩大了一百倍的倍率,所以分母需要再除以100 + if origin >= total { + odd = i + break + } + } + + odd -= 100 // 获取的倍数为赢的额外倍数 + + period := GetFlyTime(odd) + t.Debug("period:%v,odd:%v", period, odd) + if period <= 0 { + return t.Ran.Intn(95) + 5 + } + return int(period) +} + +// CalOdds 计算赔率 +func (t *Table) CalOdds() (odds int64) { + now := time.Now().UnixMilli() + diff := now - t.StartFlyTime + return GetOdds(diff) +} + +// DelaySettle 判断是否延迟结算 +func (t *Table) DelaySettle() { + // 当前还未退出的真实玩家为0,延长飞行时间 + // if t.CountRealPlayings() == 0 && t.FlyTime < MaxFlyTime { + // // 延迟飞行不会飞到jackpot + // mf := MaxFlyTime - t.FlyTime - 100 + // if mf <= 0 { + // t.Table.Settle() + // return + // } + // if mf > 3000 { + // mf = 3000 + // } + // t.FlyTime += t.Ran.Intn(mf) + // now := time.Now().UnixMilli() + // diff := int64(t.FlyTime) - (now - t.StartFlyTime) + // if t.FlyTime > MaxFlyTime { + // diff = int64(MaxFlyTime) - (now - t.StartFlyTime) + // } + // t.Debug("fly delay:%v,diff:%v", t.FlyTime, diff) + // t.Timer = fmt.Sprintf("table%vdelaySettle", t.TableId()) + // if diff <= 0 { + // t.Table.Settle() + // } else { + // timewheel.GetTimeWheel().AddTimerCustom(time.Duration(diff)*time.Millisecond, t.Timer, nil, func(arge interface{}) { + // t.Put("settle") + // }) + // } + // return + // } + t.Table.Settle() +} diff --git a/modules/crash/values.go b/modules/crash/values.go new file mode 100644 index 0000000..b3ee5ae --- /dev/null +++ b/modules/crash/values.go @@ -0,0 +1,57 @@ +package crash + +const ( + MaxHistoryLen = 100 + MaxOdd = 10000 // 最大100倍 + MaxWeight = 1000000 // 权重总和 +) + +var ( + times = []int64{20000, 25000, 30000, 35000, 55000, 75000, -1} + // 上涨赔率(百分位)/秒 + odd = []int64{10, 20, 40, 80, 100, 150, 200} + // 每个时间段对应的赔率(扩大了100倍) + odds = []int64{200, 300, 500, 900, 2900, 4900, -1} +) + +func getTimes(t int64) int { + for i, v := range times { + if t <= v || v <= 0 { + return i + } + } + return 0 +} + +func GetOdds(t int64) (odds int64) { + index := getTimes(t) + if index > 0 { + odds += (t - times[index-1]) * odd[index] + } else { + odds += t * odd[0] + } + for i := index - 1; i >= 0; i-- { + if i == 0 { + odds += times[0] * odd[0] + } else { + odds += (times[i] - times[i-1]) * odd[i] + } + } + odds /= 1000 + return +} + +func GetFlyTime(od int64) (t int64) { + index := 0 + for i := len(odds) - 2; i >= 0; i-- { + if od >= odds[i] { + index = i + break + } + } + if index == 0 && od <= odds[0] { + return od * 1000 / odd[index] + } + diff := od - odds[index] + return times[index] + diff*1000/odd[index+1] +} diff --git a/modules/gate/agent.go b/modules/gate/agent.go new file mode 100644 index 0000000..36350eb --- /dev/null +++ b/modules/gate/agent.go @@ -0,0 +1,435 @@ +package gate + +import ( + "bufio" + "errors" + "fmt" + "io" + "runtime" + "server/call" + "server/config" + "server/pb" + "server/util" + "strconv" + "strings" + "time" + + "github.com/liangdas/mqant/gate" + basegate "github.com/liangdas/mqant/gate/base" + "github.com/liangdas/mqant/log" + "github.com/liangdas/mqant/module" + timewheel "github.com/liangdas/mqant/module/modules/timer" + "github.com/liangdas/mqant/network" + mqanttools "github.com/liangdas/mqant/utils" +) + +// type Agent interface { +// OnInit(gate Gate, conn network.Conn) error +// WriteMsg(topic string, body []byte) error +// Close() +// Run() (err error) +// OnClose() error +// Destroy() +// ConnTime() time.Time +// RevNum() int64 +// SendNum() int64 +// IsClosed() bool +// ProtocolOK() bool +// GetError() error //连接断开的错误日志 +// GetSession() Session +// } + +type agent struct { + connTime time.Time + module module.RPCModule + session gate.Session + conn network.Conn + gate gate.Gate + w *bufio.Writer + r *bufio.Reader + heartbeatTimer string + revNum int64 + sendNum int64 + lastHeartbeat int64 + protocol_ok bool + isclose bool + queue *PackQueue + // msgMutex *sync.Mutex + // msgIndex uint32 // 当前消息序列号 + // msgMap map[uint32]Msg +} + +type Msg struct { + Topic string + Body []byte +} + +func (a *agent) OnInit(gate gate.Gate, conn network.Conn) error { + // a.ch = make(chan int, gate.Options().ConcurrentTasks) + a.conn = conn + a.gate = gate + a.r = bufio.NewReaderSize(conn, gate.Options().BufSize) + a.w = bufio.NewWriterSize(conn, gate.Options().BufSize) + a.isclose = false + a.protocol_ok = false + // a.msgMutex = new(sync.Mutex) + // a.msgIndex = 0 + // a.msgMap = make(map[uint32]Msg) + return nil +} + +// func (a *agent) LoopWrite() { +// for !a.IsClosed() { +// msg, ok := a.msgMap[a.msgIndex] +// if !ok { +// break +// } +// a.WriteMsg(msg.Topic, msg.Body) +// delete(a.msgMap, a.msgIndex) +// a.msgIndex++ +// } +// } + +func (a *agent) WriteMsg(topic string, body []byte) error { + // log.Debug("write to agent:%v", topic) + if a.conn == nil { + return errors.New("mqtt.Client nil") + } + // a.sendNum++ + if a.gate.Options().SendMessageHook != nil { + bb, err := a.gate.Options().SendMessageHook(a.GetSession(), topic, body) + if err != nil { + return err + } + body = bb + } + send := []byte{} + length, err := util.IntToBytes(len(body)+10, 2) + if err != nil { + log.Error("err:%v", err) + return err + } + send = append(send, length...) + ret := strings.Split(topic, ":") + if len(ret) < 2 { + err = fmt.Errorf("invalid protocol:%v", topic) + return err + } + // else if len(ret) == 3 { + // index, err1 := strconv.ParseUint(ret[2], 10, 32) + // if err1 != nil { + // err = fmt.Errorf("invalid protocol:%v", topic) + // return err + // } + // a.msgMutex.Lock() + // if index == 0 { // 说明是第一条消息,直接发送 + // a.msgIndex = 1 + // a.WriteMsg(ret[0]+":"+ret[1], body) + // a.LoopWrite() + // } else if uint32(index) == a.msgIndex { // 顺序正确,直接发送 + // a.msgIndex++ + // a.WriteMsg(ret[0]+":"+ret[1], body) + // a.LoopWrite() + // } else { + // a.msgMap[uint32(index)] = Msg{Topic: fmt.Sprintf("%v:%v", ret[0], ret[1]), Body: body} + // } + // a.msgMutex.Unlock() + // return nil + // } + + // moduleID := int(pb.ServerType_value[ret[0]]) + moduleID := call.GetModuleID(ret[0]) + // if err != nil { + // log.Error("err:%v", err) + // return err + // } + // pid := call.GetProtocolNameType(moduleID)[ret[1]] + // protocol, _ := util.IntToBytes(int(pid), 2) + pid, err := strconv.Atoi(ret[1]) + if err != nil { + log.Error("err:%v", err) + return err + } + protocol, _ := util.IntToBytes(pid, 2) + + module, err := util.IntToBytes(moduleID, 2) + if err != nil { + log.Error("err:%v", err) + return err + } + // log.Debug("发到客户端的proto协议:%v", pid) + send = append(send, module...) + send = append(send, protocol...) + send = append(send, []byte{0, 0, 0, 0}...) + send = append(send, body...) + + // a.conn.Write(send) + + shouldClose := false + if moduleID == int(pb.ServerType_ServerTypeGate) && pid == int(pb.ServerGateResp_GateRepeatResp) { + shouldClose = true + } + return a.queue.WritePack(send, shouldClose) +} + +func (a *agent) Close() { + go func() { + //关闭连接部分情况下会阻塞超时,因此放协程去处理 + if a.conn != nil { + a.conn.Close() + } + }() +} + +func (a *agent) Run() (err error) { + defer func() { + if err := recover(); err != nil { + buff := make([]byte, 1024) + runtime.Stack(buff, false) + log.Error("conn.serve() panic(%v)\n info:%s", err, string(buff)) + } + a.Close() + + }() + go func() { + defer func() { + if err := recover(); err != nil { + buff := make([]byte, 1024) + runtime.Stack(buff, false) + log.Error("OverTime panic(%v)\n info:%s", err, string(buff)) + } + }() + select { + case <-time.After(a.gate.Options().OverTime): + if a.GetSession() == nil || a.GetSession().GetUserID() == "" { + //超过一段时间还没有建立连接则直接关闭网络连接 + a.Close() + } + + } + }() + + addr := a.conn.RemoteAddr() + sessionID := mqanttools.GenerateID().String() + a.heartbeatTimer = sessionID + // 心跳逻辑 + timewheel.GetTimeWheel().AddTimerCustom(a.gate.Options().Heartbeat, sessionID, nil, timewheel.Job(func(arge interface{}) { + a.heartbeat() + })) + a.session, err = basegate.NewSessionByMap(a.module.GetApp(), map[string]interface{}{ + "Sessionid": sessionID, + "Network": addr.Network(), + "IP": addr.String(), + "Serverid": a.module.GetServerID(), + "Settings": make(map[string]string), + }) + if err != nil { + log.Error("gate create agent fail", err.Error()) + return + } + log.Debug("new session:%v", sessionID) + a.queue = NewPackQueue(sessionID, a.r, a.w, a.conn, -1) + util.Go(func() { + a.queue.Flusher() + }) + a.session.JudgeGuest(a.gate.GetJudgeGuest()) + a.session.CreateTrace() //代码跟踪 + //回复客户端 CONNECT + // err = mqtt.WritePack(mqtt.GetConnAckPack(0), a.w) + // if err != nil { + // log.Error("ConnAckPack error %v", err.Error()) + // return + // } + a.connTime = time.Now() + a.protocol_ok = true + a.gate.GetAgentLearner().Connect(a) //发送连接成功的事件 + a.ReadLoop() + return nil +} + +func (age *agent) OnClose() error { + defer func() { + if err := recover(); err != nil { + buff := make([]byte, 1024) + runtime.Stack(buff, false) + log.Error("agent OnClose panic(%v)\n info:%s", err, string(buff)) + } + }() + timewheel.GetTimeWheel().RemoveTimer(age.heartbeatTimer) + + age.isclose = true + age.gate.GetAgentLearner().DisConnect(age) //发送连接断开的事件 + return nil +} + +func (a *agent) Destroy() { + if a.conn != nil { + a.conn.Destroy() + } +} +func (a *agent) ConnTime() time.Time { + return a.connTime +} +func (a *agent) RevNum() int64 { + return a.revNum +} +func (a *agent) SendNum() int64 { + return a.sendNum +} +func (a *agent) IsClosed() bool { + return a.isclose +} +func (a *agent) ProtocolOK() bool { + return a.protocol_ok +} +func (a *agent) GetError() error { //连接断开的错误日志 + return nil +} +func (a *agent) GetSession() gate.Session { + return a.session +} + +func (a *agent) ReadLoop() { + defer a.queue.Close(errors.New("read err")) + for !a.IsClosed() { + // 第一步拿到数据包长度 + length, err := a.readInt(2) + if err != nil { + // log.Error("err:%v", err) + return + } + if length > a.gate.Options().BufSize { + log.Error("max bufSize limit:%v,length:%v", a.gate.Options().BufSize, length) + return + } + // 第二步拿到模块协议类型 + moduleType, err := a.readInt(2) + if err != nil { + log.Error("err:%v,session:%v", err, a.session.GetSessionID()) + return + } + if moduleType == 0 { + // log.Error("invalid moduleType") + return + } + origin := moduleType + if moduleType >= 3000 { + moduleType = call.GetGameOriginID(moduleType) + } + + // 第三步拿到协议类型 + protocolType, err := a.readInt(2) + if err != nil { + log.Error("err:%v", err) + return + } + if protocolType == 0 { + // log.Error("invalid protocolType") + return + } + + // 第四步拿到uid + _, err = a.readInt(4) + if err != nil { + // log.Error("err:%v", err) + return + } + // log.Debug("uid:%v", uid) + + // 路由 + // moduleName := pb.ModuleType_name[int32(moduleType)] + moduleName := call.GetModuleName(moduleType) + // if moduleName == "" { + // log.Error("unknow moduleType:%v", moduleType) + // return + // } + + protocolName := strconv.Itoa(protocolType) + // var protocolName string + // pr := call.GetProtocolType(moduleType) + // protocolName = pr[int32(protocolType)] + // if protocolName == "" { + // log.Error("invalid protocolType:%v", protocolType) + // return + // } + + a.lastHeartbeat = time.Now().Unix() + request := []byte{} + if length > 10 { + // 第五步拿到协议数据 + request = make([]byte, length-10) + l, err := io.ReadFull(a.r, request) + if err != nil { + log.Error("err:%v", err) + return + } + if l != length-10 { + log.Error("session:%v,pack len:%v,read len:%v", a.session.GetSessionID(), length-10, l) + return + } + } + + // 心跳包 + if moduleType == int(pb.ServerType_ServerTypeGate) && protocolType == int(pb.ServerGateReq_GatePingReq) { + topic := fmt.Sprintf("%v:%v", int(pb.ServerType_ServerTypeGate), int(pb.ServerGateResp_GatePingResp)) + a.session.Send(topic, nil) + continue + } + + route := "" + if origin >= 3000 { + route = fmt.Sprintf("%v://%v/%v", moduleName, origin, protocolName) + } else { + route = fmt.Sprintf("%v://modules/%v", moduleName, protocolName) + } + + if moduleName == "hall" && protocolType == int(pb.ServerGateReq_GateLoginReq) { + log.Debug("session %v login", a.session.GetSessionID()) + } + // log.Debug("route:%v", route) + a.onRoute(route, request) + } +} + +func (a *agent) readInt(n int) (ret int, err error) { + tmp := []byte{} + tmp, err = a.readByte(n) + if err != nil { + // log.Error("err:%v", err) + return + } + ret, err = util.BytesToInt(tmp) + return +} + +func (a *agent) readByte(n int) (data []byte, err error) { + for i := 0; i < n; i++ { + var tmp byte + tmp, err = a.r.ReadByte() + if err != nil { + // log.Error("err:%v", err) + return + } + data = append(data, tmp) + } + return +} + +func (a *agent) onRoute(topic string, data []byte) { + defer util.Recover() + _, result, err := a.gate.GetRouteHandler().OnRoute(a.GetSession(), topic, data) + if err != nil { + log.Error("result:%v,err:%v,topic:%v", result, err, topic) + } +} + +func (a *agent) heartbeat() { + if time.Now().Unix()-a.lastHeartbeat > int64(config.GetConfig().Gate.HeartBeat) { + log.Debug("player:%v heatbeat stop....", a.GetSession().GetUserID()) + a.Close() + return + } + timewheel.GetTimeWheel().AddTimerCustom(a.gate.Options().Heartbeat, a.heartbeatTimer, nil, timewheel.Job(func(arge interface{}) { + a.heartbeat() + })) +} diff --git a/modules/gate/config.go b/modules/gate/config.go new file mode 100644 index 0000000..faa3743 --- /dev/null +++ b/modules/gate/config.go @@ -0,0 +1,13 @@ +package gate + +import ( + "server/call" + "server/common" + "server/pb" +) + +func loadConfig() error { + c := map[int][]func(*pb.ReloadGameConfig) error{} + c[common.ReloadConfigServerVersion] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { return call.LoadServerVersions() }} + return call.LoadSomeConfigs(c) +} diff --git a/modules/gate/gate.go b/modules/gate/gate.go new file mode 100644 index 0000000..9348aff --- /dev/null +++ b/modules/gate/gate.go @@ -0,0 +1,256 @@ +package gate + +import ( + "fmt" + "reflect" + "time" + + "github.com/liangdas/mqant/conf" + "github.com/liangdas/mqant/gate" + basegate "github.com/liangdas/mqant/gate/base" + "github.com/liangdas/mqant/log" + "github.com/liangdas/mqant/module" + basemodule "github.com/liangdas/mqant/module/base" + "github.com/liangdas/mqant/network" +) + +var RPCParamSessionType = gate.RPCParamSessionType +var RPCParamProtocolMarshalType = gate.RPCParamProtocolMarshalType + +type BaseGate struct { + //module.RPCSerialize + basemodule.BaseModule + opts gate.Options + judgeGuest func(session gate.Session) bool + + createAgent func() gate.Agent +} + +func (gt *BaseGate) SetJudgeGuest(judgeGuest func(session gate.Session) bool) error { + gt.judgeGuest = judgeGuest + return nil +} + +/* +设置Session信息持久化接口 +*/ +func (gt *BaseGate) SetRouteHandler(router gate.RouteHandler) error { + gt.opts.RouteHandler = router + return nil +} + +/* +设置Session信息持久化接口 +*/ +func (gt *BaseGate) SetStorageHandler(storage gate.StorageHandler) error { + gt.opts.StorageHandler = storage + return nil +} + +/* +设置客户端连接和断开的监听器 +*/ +func (gt *BaseGate) SetSessionLearner(sessionLearner gate.SessionLearner) error { + gt.opts.SessionLearner = sessionLearner + return nil +} + +/* +设置创建客户端Agent的函数 +*/ +func (gt *BaseGate) SetCreateAgent(cfunc func() gate.Agent) error { + gt.createAgent = cfunc + return nil +} +func (gt *BaseGate) Options() gate.Options { + return gt.opts +} +func (gt *BaseGate) GetStorageHandler() (storage gate.StorageHandler) { + return gt.opts.StorageHandler +} +func (gt *BaseGate) GetGateHandler() gate.GateHandler { + return gt.opts.GateHandler +} +func (gt *BaseGate) GetAgentLearner() gate.AgentLearner { + return gt.opts.AgentLearner +} +func (gt *BaseGate) GetSessionLearner() gate.SessionLearner { + return gt.opts.SessionLearner +} +func (gt *BaseGate) GetRouteHandler() gate.RouteHandler { + return gt.opts.RouteHandler +} +func (gt *BaseGate) GetJudgeGuest() func(session gate.Session) bool { + return gt.judgeGuest +} +func (gt *BaseGate) GetModule() module.RPCModule { + return gt.GetSubclass() +} + +func (gt *BaseGate) NewSession(data []byte) (gate.Session, error) { + return basegate.NewSession(gt.App, data) +} +func (gt *BaseGate) NewSessionByMap(data map[string]interface{}) (gate.Session, error) { + return basegate.NewSessionByMap(gt.App, data) +} + +func (gt *BaseGate) OnConfChanged(settings *conf.ModuleSettings) { + +} + +/* +自定义rpc参数序列化反序列化 Session +*/ +func (gt *BaseGate) Serialize(param interface{}) (ptype string, p []byte, err error) { + rv := reflect.ValueOf(param) + if rv.Kind() != reflect.Ptr || rv.IsNil() { + //不是指针 + return "", nil, fmt.Errorf("Serialize [%v ] or not pointer type", rv.Type()) + } + switch v2 := param.(type) { + case gate.Session: + bytes, err := v2.Serializable() + if err != nil { + return RPCParamSessionType, nil, err + } + return RPCParamSessionType, bytes, nil + case module.ProtocolMarshal: + bytes := v2.GetData() + return RPCParamProtocolMarshalType, bytes, nil + default: + return "", nil, fmt.Errorf("args [%s] Types not allowed", reflect.TypeOf(param)) + } +} + +func (gt *BaseGate) Deserialize(ptype string, b []byte) (param interface{}, err error) { + switch ptype { + case RPCParamSessionType: + mps, errs := basegate.NewSession(gt.App, b) + if errs != nil { + return nil, errs + } + return mps.Clone(), nil + case RPCParamProtocolMarshalType: + return gt.App.NewProtocolMarshal(b), nil + default: + return nil, fmt.Errorf("args [%s] Types not allowed", ptype) + } +} + +func (gt *BaseGate) GetTypes() []string { + return []string{RPCParamSessionType} +} +func (gt *BaseGate) OnAppConfigurationLoaded(app module.App) { + //添加Session结构体的序列化操作类 + gt.BaseModule.OnAppConfigurationLoaded(app) //这是必须的 + err := app.AddRPCSerialize("gate", gt) + if err != nil { + log.Warning("Adding session structures failed to serialize interfaces %s", err.Error()) + } +} +func (gt *BaseGate) OnInit(subclass module.RPCModule, app module.App, settings *conf.ModuleSettings, opts ...gate.Option) { + gt.opts = gate.NewOptions(opts...) + gt.BaseModule.OnInit(subclass, app, settings, gt.opts.Opts...) //这是必须的 + if gt.opts.WsAddr == "" { + if WSAddr, ok := settings.Settings["WSAddr"]; ok { + gt.opts.WsAddr = WSAddr.(string) + } + } + if gt.opts.TCPAddr == "" { + if TCPAddr, ok := settings.Settings["TCPAddr"]; ok { + gt.opts.TCPAddr = TCPAddr.(string) + } + } + + if gt.opts.TLS == false { + if tls, ok := settings.Settings["TLS"]; ok { + gt.opts.TLS = tls.(bool) + } else { + gt.opts.TLS = false + } + } + + if gt.opts.CertFile == "" { + if CertFile, ok := settings.Settings["CertFile"]; ok { + gt.opts.CertFile = CertFile.(string) + } else { + gt.opts.CertFile = "" + } + } + + if gt.opts.KeyFile == "" { + if KeyFile, ok := settings.Settings["KeyFile"]; ok { + gt.opts.KeyFile = KeyFile.(string) + } else { + gt.opts.KeyFile = "" + } + } + + handler := basegate.NewGateHandler(gt) + + gt.opts.AgentLearner = handler + gt.opts.GateHandler = handler + gt.GetServer().RegisterGO("Update", gt.opts.GateHandler.Update) + gt.GetServer().RegisterGO("Bind", gt.opts.GateHandler.Bind) + gt.GetServer().RegisterGO("UnBind", gt.opts.GateHandler.UnBind) + gt.GetServer().RegisterGO("Push", gt.opts.GateHandler.Push) + gt.GetServer().RegisterGO("Set", gt.opts.GateHandler.Set) + gt.GetServer().RegisterGO("Remove", gt.opts.GateHandler.Remove) + gt.GetServer().Register("Send", gt.opts.GateHandler.Send) + gt.GetServer().RegisterGO("SendBatch", gt.opts.GateHandler.SendBatch) + gt.GetServer().RegisterGO("BroadCast", gt.opts.GateHandler.BroadCast) + gt.GetServer().RegisterGO("IsConnect", gt.opts.GateHandler.IsConnect) + gt.GetServer().RegisterGO("Close", gt.opts.GateHandler.Close) +} + +func (gt *BaseGate) Run(closeSig chan bool) { + var wsServer *network.WSServer + if gt.opts.WsAddr != "" { + wsServer = new(network.WSServer) + wsServer.Addr = gt.opts.WsAddr + wsServer.HTTPTimeout = 30 * time.Second + wsServer.TLS = gt.opts.TLS + wsServer.CertFile = gt.opts.CertFile + wsServer.KeyFile = gt.opts.KeyFile + wsServer.NewAgent = func(conn *network.WSConn) network.Agent { + agent := gt.createAgent() + agent.OnInit(gt, conn) + return agent + } + } + + var tcpServer *network.TCPServer + if gt.opts.TCPAddr != "" { + tcpServer = new(network.TCPServer) + tcpServer.Addr = gt.opts.TCPAddr + tcpServer.TLS = gt.opts.TLS + tcpServer.CertFile = gt.opts.CertFile + tcpServer.KeyFile = gt.opts.KeyFile + tcpServer.NewAgent = func(conn *network.TCPConn) network.Agent { + agent := gt.createAgent() + agent.OnInit(gt, conn) + return agent + } + } + + if wsServer != nil { + wsServer.Start() + } + if tcpServer != nil { + tcpServer.Start() + } + <-closeSig + if gt.opts.GateHandler != nil { + gt.opts.GateHandler.OnDestroy() + } + if wsServer != nil { + wsServer.Close() + } + if tcpServer != nil { + tcpServer.Close() + } +} + +func (gt *BaseGate) OnDestroy() { + gt.BaseModule.OnDestroy() //这是必须的 +} diff --git a/modules/gate/module.go b/modules/gate/module.go new file mode 100644 index 0000000..6d921eb --- /dev/null +++ b/modules/gate/module.go @@ -0,0 +1,216 @@ +/* +* +一定要记得在confin.json配置这个模块的参数,否则无法使用 +*/ +package gate + +import ( + "fmt" + "net/url" + "server/call" + "server/config" + "server/db" + "server/natsClient" + "server/pb" + "time" + + mdb "server/db/mysql" + + "github.com/gogo/protobuf/proto" + "github.com/liangdas/mqant/conf" + "github.com/liangdas/mqant/gate" + "github.com/liangdas/mqant/gate/uriroute" + "github.com/liangdas/mqant/log" + "github.com/liangdas/mqant/module" + "github.com/liangdas/mqant/server" +) + +var Module = func() module.Module { + this := new(Gate) + return this +} + +type Gate struct { + BaseGate + // basegate.Gate //继承 + Route *uriroute.URIRoute +} + +func (g *Gate) GetType() string { + //很关键,需要与配置文件中的Module配置对应 + return "gate" +} +func (g *Gate) Version() string { + //可以在监控时了解代码版本 + return "1.0.0" +} +func (g *Gate) OnInit(app module.App, settings *conf.ModuleSettings) { + + // 初始化调用 + call.NewCaller(g) + + route := uriroute.NewURIRoute(g, + uriroute.Selector(g.Selector), + uriroute.DataParsing(func(topic string, u *url.URL, msg []byte) (bean interface{}, err error) { + // 如果客户端消息要加密, 在这里解密 + // log.Info("msg:%v", msg) + // one := new(pb.JoinMatchReq) + // fmt.Println(proto.Unmarshal(msg, one)) + // fmt.Println(one) + // var tmp []byte + // bean, err = utils.AesDecrypt(msg) + // if err != nil { + // log.Error("decrypt err:%v,msg:%v", err, msg) + // } + // bean = msg + // str := strings.Split(string(tmp), ",") + // res := []byte{} + // for _, v := range str { + // tmp, _ := strconv.Atoi(v) + // res = append(res, byte(tmp)) + // } + // log.Info("res:%v", res) + // bean = res + // log.Info("bean:%v", bean) + return + }), + // uriroute.CallTimeOut(3*time.Second), + ) + //注意这里一定要用 gate.Gate 而不是 module.BaseModule + sec := config.GetConfig().Gate.HeartBeat + bufsize := config.GetConfig().Gate.BufSize + wsAddr := config.GetConfig().Gate.WSAddr + wsPort := config.GetConfig().Gate.WSPort + tls := config.GetConfig().Gate.TLS + cert := config.GetConfig().Gate.CertFile + key := config.GetConfig().Gate.KeyFile + fmt.Println("GATE:===================", wsAddr, wsPort) + if wsAddr == "" { + panic("invalid gate addr") + } + + metadata := make(map[string]string) + metadata["addr"] = wsAddr + options := []server.Option{server.Metadata(metadata)} + g.BaseGate.OnInit(g, app, settings, + gate.Heartbeat(time.Second*time.Duration(sec)), + gate.BufSize(bufsize), + gate.SetRouteHandler(route), + gate.SetSessionLearner(g), + gate.SetStorageHandler(g), + gate.WsAddr(wsPort), + gate.TLS(tls), + gate.CertFile(cert), + gate.KeyFile(key), + gate.ServerOpts(options), + gate.SetSendMessageHook(MessageHook), + ) + g.BaseGate.SetCreateAgent(g.NewAgent) + db.InitDB(&mdb.MysqlClient{}) + loadConfig() + call.InitReload(g.App.Transport()) +} + +func (g *Gate) OnDestroy() { + log.Debug("%s OnDestroy", g.GetType()) + g.BaseModule.OnDestroy() +} + +// 当连接建立 并且MQTT协议握手成功 +func (g *Gate) Connect(session gate.Session) { + //log.Info("client connect session %v, ip %s, total num:%d", session.GetSessionID(), session.GetIP(), g.GetGateHandler().GetAgentNum()) + //_ = session.SetLocalUserData(&sync.Map{}) +} + +// DisConnect 当连接关闭 +func (g *Gate) DisConnect(session gate.Session) { + uid := session.GetUserIDInt64() + if uid <= 0 { + return + } + + log.Info("client disconnect session %v, ip %s, userid:%s, total num:%d", session.GetSessionID(), session.GetIP(), session.GetUserID(), g.GetGateHandler().GetAgentNum()) + + notify := &pb.ClientDisConnectNotify{UserID: uint32(uid), SessionId: session.GetSessionID()} + payload, _ := proto.Marshal(notify) + _ = g.App.Transport().Publish(natsClient.TopicClientDisconnect, payload) +} + +func (g *Gate) OnRoute(session gate.Session, topic string, msg []byte) (bool, interface{}, error) { + return g.Route.OnRoute(session, topic, msg) +} + +/* +* +存储用户的Session信息 +Session Bind Userid以后每次设置 settings都会调用一次Storage +*/ +func (g *Gate) Storage(session gate.Session) (err error) { + return nil +} + +/* +* +强制删除Session信息 +*/ +func (g *Gate) Delete(session gate.Session) (err error) { + return +} + +/* +* +获取用户Session信息 +用户登录以后会调用Query获取最新信息 +*/ +func (g *Gate) Query(Userid string) ([]byte, error) { + return nil, nil +} + +/* +* +用户心跳,一般用户在线时60s发送一次 +可以用来延长Session信息过期时间 +*/ +func (g *Gate) Heartbeat(session gate.Session) { + //log.Debug("client heart beat, session:%v, uid:%v", session.GetSessionID(), session.GetUserID()) +} + +// Selector 客户端路由规则自定义函数 +func (g *Gate) Selector(session gate.Session, topic string, u *url.URL) (s module.ServerSession, err error) { + moduleType := u.Scheme + nodeID := u.Hostname() + + //使用自己的 + if nodeID == "modules" { + return g.GetRouteServer(moduleType, call.VersionSelector(nodeID)) + //取模 + // } else if nodeID == "cache" { + // //缓存 + // } else if nodeID == "random" { + // //随机 + // } else { + // + //指定节点规则就是 module://[user:pass@]nodeId/path + //方式1 + //moduleType=fmt.Sprintf("%v@%v",moduleType,u.Hostname()) + //方式2 + // serverID := fmt.Sprintf("%v@%v", moduleType, nodeId) + } + return g.GetRouteServer(moduleType, call.WorkIDSelector(nodeID)) +} + +// MessageHook 下发消息前的进一步处理 +func MessageHook(session gate.Session, topic string, msg []byte) ([]byte, error) { + // log.Info("msg:%v", msg) + // send := utils.AesEncrypt(msg) + // log.Info("send:%v", string(send)) + return msg, nil +} + +func (g *Gate) NewAgent() gate.Agent { + a := &agent{ + module: g.GetModule(), + lastHeartbeat: time.Now().Unix(), + } + return a +} diff --git a/modules/gate/queue.go b/modules/gate/queue.go new file mode 100644 index 0000000..b7bed6f --- /dev/null +++ b/modules/gate/queue.go @@ -0,0 +1,176 @@ +package gate + +import ( + "bufio" + "errors" + "fmt" + "runtime" + + "github.com/liangdas/mqant/log" + "github.com/liangdas/mqant/network" +) + +// Tcp write queue +type PackQueue struct { + writeError error + // Notice read the error + fch chan fs + // writelock sync.Mutex + // Pack connection + r *bufio.Reader + w *bufio.Writer + + conn network.Conn + + MaxPackSize int + + sessionID string + + status int +} + +type fs struct { + msg []byte + shouldClose bool +} + +const ( + DISCONNECTED = iota + CONNECTED + CLOSED +) + +var ( + queueMaxLen = 1024 +) + +// Init a pack queue +func NewPackQueue(sessionID string, r *bufio.Reader, w *bufio.Writer, conn network.Conn, MaxPackSize int) *PackQueue { + if MaxPackSize < 1 { + MaxPackSize = 65535 + } + return &PackQueue{ + sessionID: sessionID, + MaxPackSize: MaxPackSize, + r: r, + w: w, + conn: conn, + fch: make(chan fs, queueMaxLen), + status: CONNECTED, + } +} + +func (queue *PackQueue) isConnected() bool { + return queue.status == CONNECTED +} + +// Get a read pack queue +// Only call once +func (queue *PackQueue) Flusher() { + for queue.isConnected() { + f, ok := <-queue.fch + if !ok { + break + } + // queue.writelock.Lock() + if !queue.isConnected() { + // queue.writelock.Unlock() + break + } + err := writeFull(queue.w, f.msg) + if err != nil { + log.Error("err:%v", err) + break + } + if queue.w.Buffered() > 0 { + if err := queue.w.Flush(); err != nil { + // queue.writelock.Unlock() + break + } + } + if f.shouldClose { + queue.Close(errors.New("shouldclose")) + break + } + // queue.writelock.Unlock() + } +} + +// Write a pack , and get the last error +func (queue *PackQueue) WritePack(msg []byte, shouldClose bool) (err error) { + defer func() { + if r := recover(); r != nil { + buf := make([]byte, 1024) + l := runtime.Stack(buf, false) + errstr := string(buf[:l]) + err = fmt.Errorf("WritePack error %v", errstr) + queue.Close(err) + } + }() + // queue.writelock.Lock() + if len(queue.fch) >= queueMaxLen/2 { + err := fmt.Errorf("session %v write full", queue.sessionID) + go func() { + queue.Close(err) + }() + // queue.writelock.Unlock() + return err + } + if !queue.isConnected() { + // queue.writelock.Unlock() + return errors.New("disconnect") + } + if queue.writeError != nil { + // queue.writelock.Unlock() + return queue.writeError + } + if queue.w.Available() <= 0 { + // queue.writelock.Unlock() + return fmt.Errorf("bufio.Writer is full") + } + // queue.writelock.Unlock() + queue.fch <- fs{ + shouldClose: shouldClose, + msg: msg, + } + // if err != nil { + // Tell listener the error + // Notice the read + // queue.Close(err) + // } + return err +} + +func writeFull(w *bufio.Writer, b []byte) (err error) { + hasRead, n := 0, 0 + for n < len(b) { + n, err = w.Write(b[hasRead:]) + if err != nil { + break + } + hasRead += n + } + return err +} + +func (queue *PackQueue) CloseFch() { + defer func() { + if recover() != nil { + // close(ch) panic occur + } + }() + + close(queue.fch) // panic if ch is closed +} + +// Close the all of queue's channels +func (queue *PackQueue) Close(err error) error { + queue.writeError = err + queue.CloseFch() + queue.status = CLOSED + if queue.conn != nil { + //再关闭一下,防止文件描述符发生泄漏 + queue.conn.Close() + } + return nil +} diff --git a/modules/hall/broadcast.go b/modules/hall/broadcast.go new file mode 100644 index 0000000..d486867 --- /dev/null +++ b/modules/hall/broadcast.go @@ -0,0 +1,155 @@ +package hall + +import ( + "fmt" + "math/rand" + "server/call" + "server/common" + "server/pb" + "time" + + "github.com/gogo/protobuf/proto" + "github.com/liangdas/mqant/gate" + "github.com/liangdas/mqant/log" + timewheel "github.com/liangdas/mqant/module/modules/timer" +) + +var ( + broadcasts = map[int]*oneBroadcast{} +) + +type oneBroadcast struct { + content string + id int + priority int + frequency int + interval int + targetID int + sub subBroadcast +} + +type subBroadcast interface { + stop() +} + +func startBroadcast() { + stopAll() + all := call.GetConfigBroadcast() + for _, v := range all { + if v.Open == 0 { + continue + } + one := &oneBroadcast{ + id: v.ID, + priority: v.Priority, + frequency: v.LoopFrequency, + interval: v.Interval, + content: v.Content, + targetID: v.TargetID, + } + broadcasts[v.ID] = one + one.NewSub() + } +} + +func (b *oneBroadcast) stopBroadcast() { + timewheel.GetTimeWheel().RemoveTimer(b.id) +} + +func (b *oneBroadcast) NewSub() { + switch b.targetID { + case common.BrocastIDAll: + b.broadcast() + default: + return + } +} + +func (b *oneBroadcast) broadcast() { + msg := &pb.BroadcastMsg{ + Content: b.content, + Priority: uint32(b.priority), + Loop: int64(b.frequency), + Interval: int64(b.interval), + } + // log.Debug("broadcast:%+v", *msg) + send, _ := proto.Marshal(msg) + broadcastNatsImp(send) + // if b.frequency == -1 { + // timewheel.GetTimeWheel().AddTimerCustom(time.Duration(b.interval)*time.Second, b.id, nil, func(arge interface{}) { + // b.broadcast() + // }) + // return + // } + // if b.frequency > 0 { + // b.frequency-- + // timewheel.GetTimeWheel().AddTimerCustom(time.Duration(b.interval)*time.Second, b.id, nil, func(arge interface{}) { + // b.broadcast() + // }) + // } +} + +func stopAll() { + for _, v := range broadcasts { + v.stopBroadcast() + if v.sub != nil { + v.sub.stop() + } + } + broadcasts = map[int]*oneBroadcast{} +} + +type redSub struct { + sid string + *oneBroadcast +} + +func (s *redSub) stop() { + timewheel.GetTimeWheel().RemoveTimer(s.sid) +} + +// 红包活动广播,9:00-16:00随机播放一次,16:00-24:00随机播放一次 +func (s *redSub) broadcast() { + hour, _, _ := time.Now().Clock() + diff := 0 + s.stop() + if hour < 16 { + diff = 16 - hour + timewheel.GetTimeWheel().AddTimerCustom(time.Duration(diff)*time.Hour, s.sid, nil, func(arge interface{}) { + s.broadcast() + }) + } else { + diff = 24 - hour + timewheel.GetTimeWheel().AddTimerCustom(time.Duration(diff+9)*time.Hour, s.sid, nil, func(arge interface{}) { + s.broadcast() + }) + } + rhour := rand.Intn(diff) + rmin := rand.Intn(60) + rsec := rand.Intn(60) + after := time.Duration(rhour)*time.Hour + time.Duration(rmin)*time.Minute + time.Duration(rsec)*time.Second + timewheel.GetTimeWheel().AddTimerCustom(after, s.id, nil, func(arge interface{}) { + s.oneBroadcast.broadcast() + }) +} + +// 广播协议 +func broadcastMsg(pid int, msg proto.Message) { + var session gate.Session + sids := "" + playerIdMap.Range(func(key, value interface{}) bool { + p := value.(*player) + if session == nil { + session = p.session + } + sids += p.session.GetSessionID() + "," + return true + }) + log.Debug("broadcast pid:%v,msg:%v,sids:%v", pid, msg, sids) + if sids == "" { + return + } + sids = sids[:len(sids)-1] + data, _ := proto.Marshal(msg) + session.SendBatch(sids, fmt.Sprintf("%v:%v", int(pb.ServerType_ServerTypeCommon), pid), data) +} diff --git a/modules/hall/client_handle.go b/modules/hall/client_handle.go new file mode 100644 index 0000000..705caaf --- /dev/null +++ b/modules/hall/client_handle.go @@ -0,0 +1,104 @@ +package hall + +import ( + "fmt" + "server/call" + "server/db" + "server/natsClient" + "server/pb" + "server/util" + "strconv" + "time" + + "server/common" + + "github.com/liangdas/mqant/gate" + "github.com/liangdas/mqant/log" + timewheel "github.com/liangdas/mqant/module/modules/timer" +) + +func (h *Hall) login(session gate.Session, req *pb.LoginRequest) (string, error) { + log.Info("login sessionid:%v,serverid:%v,userid:%v,req:%+v", session.GetSessionID(), session.GetServerID(), session.GetUserID(), req) + resp := &pb.LoginResponse{ + Result: 0, + UserId: 0, + } + rUID, err := db.Redis().GetInt(common.GetRedisKeyToken(req.Token)) + if err != nil || rUID != int(req.UserId) { + log.Error("err:%v,ruid:%v", err, rUID) + resp.Result = 1 + call.SendSS(session, int(pb.ServerGateResp_GateLoginResp), resp) + return "", nil + } + p, ok := playerSessionMap.Load(session.GetSessionID()) + if ok { + id := p.(*player).db.Id + if id != int(req.UserId) { + util.Go(func() { + delPlayerByID(id) + call.UpdateUserXInfo(&common.PlayerDBInfo{Id: id}, map[string]interface{}{"online": 2}) + }) + } + } + h.onLogin(session, resp, rUID) + return "", nil +} + +func (h *Hall) logout(session gate.Session, req *pb.LoginRequest) (string, error) { + fmt.Println("Call Logout") + return "", nil +} + +func (h *Hall) onLogin(session gate.Session, resp *pb.LoginResponse, uid int) { + resp.UserId = uint32(uid) + defer func() { + call.SendSS(session, int(pb.ServerGateResp_GateLoginResp), resp) + }() + errbind := session.Bind(strconv.Itoa(uid)) + if errbind != "" { + log.Error("login bind userid error:%s,uid:%v", errbind, uid) + resp.Result = 1 + return + } + + p := new(player) + // 发布新玩家登录的消息 + if v, ok := playerIdMap.Load(uid); ok { + p = v.(*player) + psid := p.session.GetSessionID() + sid := session.GetSessionID() + if psid == sid { + log.Debug("onLogin player same session, uid:%d, session:%s", p.db.Id, psid) + return + } + delPlayerBySession(psid) + call.SendSS(p.session, int(pb.ServerGateResp_GateRepeatResp), nil) + log.Debug("onLogin player duplicate login, uid:%d, old session:%s, new session:%s", p.db.Id, psid, sid) + p.session = session + } else { + p = newPlayer(uid) + p.session = session + addPlayerById(uid, p) + err := call.Publish(natsClient.TopicNewPlayer, &pb.NewPlayerLogin{Uid: int32(uid), GateServerID: session.GetServerID(), HallServerID: h.GetServerID()}) + if err != nil { + log.Error("player login nats publish topic player duplogin error:%v", err) + } + } + p.lastLogin = time.Now().Unix() + + sessionID := session.GetSessionID() + addPlayerBySession(sessionID, p) + serverID := session.GetServerID() + db.Redis().UpdateUserFields(uid, map[string]interface{}{"sessionID": sessionID, "gateID": serverID}) + // ip := session.GetIP() + // cid := p.db.ChannelID + // birth := p.db.Birth + util.Go(func() { + timewheel.GetTimeWheel().AddTimer(3*time.Second, nil, func(arge interface{}) { + p.PushBroadcast() + }) + p.PushRedPoint() + call.UpdateUserXInfo(&common.PlayerDBInfo{Id: uid}, map[string]interface{}{"online": 1}) + // call.InsertLoginRecord(uid, cid, ip, 0, birth) + }) +} diff --git a/modules/hall/config.go b/modules/hall/config.go new file mode 100644 index 0000000..f822c27 --- /dev/null +++ b/modules/hall/config.go @@ -0,0 +1,64 @@ +package hall + +import ( + "server/call" + "server/common" + "server/pb" + + "github.com/liangdas/mqant/log" +) + +func loadConfig() error { + c := map[int][]func(*pb.ReloadGameConfig) error{} + c[common.ReloadBroadcast] = []func(*pb.ReloadGameConfig) error{ReloadBroadcast} + c[common.ReloadConfigPayProduct] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := call.LoadConfigPayProduct(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + broadcastMsg(int(pb.ServerCommonResp_CommonConfigChangeResp), &pb.ConfigChangeResp{Type: pb.ConfigChangeType_ConfigPay}) + return nil + }} + c[common.ReloadConfigActivity] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := call.LoadConfigActivity(); err != nil { + log.Error("error : [%s]", err.Error()) + return err + } + broadcastMsg(int(pb.ServerCommonResp_CommonConfigChangeResp), &pb.ConfigChangeResp{Type: pb.ConfigChangeType_ConfigActivity}) + return nil + }} + c[common.ReloadConfigPayWeight] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + broadcastMsg(int(pb.ServerCommonResp_CommonConfigChangeResp), &pb.ConfigChangeResp{Type: pb.ConfigChangeType_ConfigPay}) + return nil + }} + c[common.ReloadPlatform] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := call.LoadConfigPlatform(); err != nil { + log.Error("err:%v", err) + return err + } + broadcastMsg(int(pb.ServerCommonResp_CommonConfigChangeResp), &pb.ConfigChangeResp{Type: pb.ConfigChangeType_ConfigPay}) + return nil + }} + c[common.ReloadConfigWithdrawWeight] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + if err := call.LoadConfigWithdrawChannels(); err != nil { + log.Error("err:%v", err) + return err + } + broadcastMsg(int(pb.ServerCommonResp_CommonConfigChangeResp), &pb.ConfigChangeResp{Type: pb.ConfigChangeType_ConfigWithdraw}) + return nil + }} + c[common.ReloadConfigGameSwitch] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { + broadcastMsg(int(pb.ServerCommonResp_CommonConfigChangeResp), &pb.ConfigChangeResp{Type: pb.ConfigChangeType_ConfigGame}) + return nil + }} + call.LoadConfigs(c) + return nil +} + +func ReloadBroadcast(c *pb.ReloadGameConfig) error { + if err := call.LoadConfigBroadcast(); err != nil { + return err + } + startBroadcast() + return nil +} diff --git a/modules/hall/module.go b/modules/hall/module.go new file mode 100644 index 0000000..6d6baf6 --- /dev/null +++ b/modules/hall/module.go @@ -0,0 +1,97 @@ +package hall + +import ( + "server/call" + "server/common" + "server/config" + "server/db" + edb "server/db/es" + mdb "server/db/mysql" + rdb "server/db/redis" + "server/natsClient" + "server/pb" + "strconv" + "time" + + "github.com/liangdas/mqant/conf" + "github.com/liangdas/mqant/log" + "github.com/liangdas/mqant/module" + basemodule "github.com/liangdas/mqant/module/base" + "github.com/liangdas/mqant/server" +) + +type Hall struct { + basemodule.BaseModule +} + +var Module = func() module.Module { + this := new(Hall) + return this +} + +func (h *Hall) GetType() string { + //很关键,需要与配置文件中的Module配置对应 + return "hall" +} +func (h *Hall) Version() string { + //可以在监控时了解代码版本 + return "1.0.0" +} + +func (h *Hall) OnInit(app module.App, settings *conf.ModuleSettings) { + h.BaseModule.OnInit(h, app, settings, + server.RegisterInterval(5*time.Second), + server.RegisterTTL(10*time.Second), + ) + db.InitDB(&mdb.MysqlClient{}, &rdb.RedisClient{}, &edb.EsClient{}) + + log.Info("[%v]module init finish, config:%+v", h.GetType(), config.GetConfig().Hall) + + // 初始化调用 + call.NewCaller(h) + + //LOGIN + h.GetServer().RegisterGO("/"+strconv.Itoa(int(pb.ServerGateReq_GateLoginReq)), h.login) + //LOGOUT + //h.GetServer().RegisterGO("/"+pb.ServerGatewayCmd_name[int32(pb.ServerGatewayCmd_CMD_GATEWAY_LOGOUT_REQ)], h.logout) + + // 初始化订阅事件 + h.NewNatsImp(natsClient.TopicNewPlayer) + initNats(h.App.Transport()) + if err := loadConfig(); err != nil { + log.Error("err:%v", err) + panic(err) + } + StartTimer() +} + +func (h *Hall) Run(closeSig chan bool) { + log.Info("[%v]module running", h.GetType()) + call.InitTimeWheel(closeSig) + call.InitExecQueue() + // 初始化在线人数上报 + call.InitOnline(GetOnline) + call.WriteRealOnline("Total", GetRealOnline) + + // 加载配置 + if err := loadConfig(); err != nil { + panic(err) + } + <-closeSig + log.Info("[%v]module stop", h.GetType()) +} + +func (h *Hall) OnDestroy() { + playerIdMap.Range(func(key, value interface{}) bool { + p := value.(*player) + p.session.Close() + // db.Redis().Delkey(common.GetRedisKeyToken(p.token)) + return true + }) + if err := db.Mysql().C().Model(&common.PlayerDBInfo{}).Where("online = 1").UpdateColumn("online", 2).Error; err != nil { + log.Error("err:%v", err) + } + //一定别忘了继承 + h.BaseModule.OnDestroy() + log.Info("[%v]module destroy", h.GetType()) +} diff --git a/modules/hall/nats.go b/modules/hall/nats.go new file mode 100644 index 0000000..dc25fc6 --- /dev/null +++ b/modules/hall/nats.go @@ -0,0 +1,231 @@ +package hall + +import ( + "fmt" + "server/call" + "server/common" + "server/db" + "server/natsClient" + "server/pb" + + "github.com/gogo/protobuf/proto" + "github.com/liangdas/mqant/gate" + "github.com/liangdas/mqant/log" + "github.com/nats-io/nats.go" +) + +func initNats(conn *nats.Conn) { + // 广播 + natsClient.NewCommonNatsImp(conn, natsClient.TopicBroadcast, broadcastNatsImp) + // 接受广播请求 + natsClient.NewCommonNatsImp(conn, natsClient.TopicBroadcastReq, broadcastReqNatsImp) + // 断线 + natsClient.NewCommonNatsImp(conn, natsClient.TopicClientDisconnect, disconnectNatsImp) + // 配置更新 + call.InitReload(conn) + // 后台发邮件 + natsClient.NewCommonNatsImp(conn, natsClient.TopicBackRefreshMail, refreshMailNatsImp) + // 操作玩家 + natsClient.NewCommonNatsImp(conn, natsClient.TopicInnerOptPlayer, optPlayer) + // 只监听充值操作 + natsClient.NewCommonNatsImp(conn, natsClient.TopicInnerRefreshGold, refreshPlayerGold) + // 广播任意协议 + natsClient.NewCommonNatsImp(conn, natsClient.TopicBroadcastAll, broadcastAllNatsImp) +} + +// natsImp nats对象 +type natsImp struct { + m *Hall +} + +func (h *Hall) NewNatsImp(topic string) *natsImp { + n := new(natsImp) + base := natsClient.NewNatsClient() + base.Topic = topic + base.NatsImp = n + base.Conn = h.App.Transport() + n.m = h + go func() { + err := base.OnRequestHandle() + if err != nil { + log.Error("NewNatsImp OnRequestHandle error:%v", err) + } + }() + return n +} + +// OnMsgCallBack 订阅回调 +func (n *natsImp) OnMsgCallBack(nm *nats.Msg) { + m := new(pb.NewPlayerLogin) + if err := proto.Unmarshal(nm.Data, m); err != nil { + log.Error("error:%v", err) + return + } + // 过滤自己发布的消息 + if m.HallServerID == n.m.GetServerID() { + return + } + if v, ok := playerIdMap.Load(m.Uid); ok { + p := v.(*player) + call.SendSS(p.session, int(pb.ServerGateResp_GateRepeatResp), nil) + + log.Debug("redPointTipsNotifyNatsImp player duplicate login, uid:%d, old session:%s", p.db.Id, p.session.GetSessionID()) + + // 在另外一个大厅登陆的, 所以这里要删除 + delPlayerByID(p.db.Id) + + // db.Redis().Delkey(common.GetRedisKeyToken(p.token)) + } +} + +func disconnectNatsImp(data []byte) { + notify := &pb.ClientDisConnectNotify{} + err := proto.Unmarshal(data, notify) + if err != nil { + log.Error("clientDissconnectNatsImp OnMsgCallBack proto Unmarshal error %v", err) + return + } + log.Debug("disconnect notify:%+v", notify) + delPlayerBySession(notify.SessionId) + p := getPlayerById(int(notify.UserID)) + if p != nil && p.session.GetSessionID() == notify.SessionId { + delPlayerByID(int(notify.UserID)) + call.UpdateUserXInfo(&common.PlayerDBInfo{Id: p.db.Id}, map[string]interface{}{"online": 2}) + } +} + +func broadcastNatsImp(data []byte) { + var session gate.Session + sids := "" + playerIdMap.Range(func(key, value interface{}) bool { + p := value.(*player) + if session == nil { + session = p.session + } + sids += p.session.GetSessionID() + "," + return true + }) + if sids == "" { + return + } + sids = sids[:len(sids)-1] + session.SendBatch(sids, fmt.Sprintf("%v:%v", int(pb.ServerType_ServerTypeCommon), int(pb.ServerCommonResp_CommonBroadcastResp)), data) +} + +func broadcastReqNatsImp(data []byte) { + msg := new(pb.InnerBroadcast) + if err := proto.Unmarshal(data, msg); err != nil { + log.Error("err:%v", err) + return + } + log.Debug("broadcastreq:%+v", *msg) + one := &oneBroadcast{ + id: int(msg.ID), + priority: int(msg.Priority), + frequency: int(msg.Frequency), + interval: int(msg.Interval), + content: msg.Content, + } + one.broadcast() +} + +func refreshMailNatsImp(data []byte) { + one := new(pb.InnerRefreshMail) + if err := proto.Unmarshal(data, one); err != nil { + log.Error("err:%v", err) + return + } + log.Debug("back refresh mail...%+v", *one) + if len(one.UIDs) == 0 { + playerIdMap.Range(func(key, value interface{}) bool { + p := value.(*player) + p.PushRedPoint() + return true + }) + } else { + playerIdMap.Range(func(key, value interface{}) bool { + p := value.(*player) + contain := false + for _, v := range one.UIDs { + if v == uint32(p.db.Id) { + contain = true + break + } + } + if !contain { + return true + } + call.UpsertRedPointAndNotify(p.db.Id, 1, call.ModuleMail) + // p.PushRedPoint() + return true + }) + } +} + +func optPlayer(data []byte) { + one := new(pb.InnerOptPlayer) + if err := proto.Unmarshal(data, one); err != nil { + log.Error("err:%v", err) + return + } + log.Debug("back opt player...%+v", *one) + switch one.Opt { + case common.OptPlayerTypeKick: + p, ok := playerIdMap.Load(int(one.UID)) + if !ok { + return + } + player := p.(*player) + db.Redis().Delkey(common.GetRedisKeyToken(player.token)) + db.Redis().Delkey(common.GetRedisKeyUser(player.db.Id)) + player.session.Close() + case common.OptPlayerTypeDisconnect: + p, ok := playerIdMap.Load(int(one.UID)) + if !ok { + return + } + player := p.(*player) + player.session.Close() + } +} + +func refreshPlayerGold(data []byte) { + msg := new(pb.InnerRefreshGold) + if err := proto.Unmarshal(data, msg); err != nil { + log.Error("err:%v", err) + return + } + if msg.Event != uint32(common.CurrencyEventReCharge) && msg.Event != common.CurrencyEventGMRecharge { + return + } + p := getPlayerById(int(msg.UID)) + if p == nil { + return + } + log.Debug("player %v recharge", p.db.Id) + p.isRecharge = true +} + +func broadcastAllNatsImp(data []byte) { + inner := &pb.InnerBroadcastAll{} + if err := proto.Unmarshal(data, inner); err != nil { + log.Error("err:%v", err) + return + } + var session gate.Session + sids := "" + playerIdMap.Range(func(key, value interface{}) bool { + p := value.(*player) + if session == nil { + session = p.session + } + sids += p.session.GetSessionID() + "," + return true + }) + if sids == "" { + return + } + sids = sids[:len(sids)-1] + + session.SendBatch(sids, fmt.Sprintf("%v:%v", int(inner.ProtocolID), int(inner.ProtocolType)), inner.Data) +} diff --git a/modules/hall/online.go b/modules/hall/online.go new file mode 100644 index 0000000..7cc9eaa --- /dev/null +++ b/modules/hall/online.go @@ -0,0 +1,69 @@ +package hall + +import ( + "server/call" + "server/common" + "server/util" + "time" + + "github.com/liangdas/mqant/log" +) + +// GetOnline 获取在线人数的func +func GetOnline() []*common.ESNewOnline { + total := map[int]int{} + recharge := map[int]int{} + newPlayer := map[int]int{} + oldPlayer := map[int]int{} + all := []int{} + now := time.Now().Unix() + offlines := []*player{} // 判断为已掉线玩家 + playerIdMap.Range(func(k, v interface{}) bool { + p := v.(*player) + total[p.db.ChannelID]++ + if p.isRecharge { + recharge[p.db.ChannelID]++ + } + if !util.IsSameDayTimeStamp(time.Now().Unix(), p.db.Birth) { + newPlayer[p.db.ChannelID]++ + } else { + oldPlayer[p.db.ChannelID]++ + } + // 上次登录在一天前,判定为掉线玩家 + if now-p.lastLogin > 24*60*60 { + offlines = append(offlines, p) + } + all = append(all, p.db.Id) + return true + }) + log.Debug("onlines:%v", all) + ret := []*common.ESNewOnline{} + for i, v := range total { + one := &common.ESNewOnline{Channel: i, Total: v, Recharge: recharge[i], New: newPlayer[i], Old: oldPlayer[i]} + ret = append(ret, one) + } + util.Go(func() { + for _, v := range offlines { + log.Debug("player %v offline", v.db.Id) + delPlayerBySession(v.session.GetSessionID()) + delPlayerByID(v.db.Id) + call.UpdateUserXInfo(&common.PlayerDBInfo{Id: v.db.Id}, map[string]interface{}{"online": 2}) + } + }) + return ret +} + +func GetRealOnline() map[int]*common.RedisRealOnline { + rooms := map[int]*common.RedisRealOnline{} + rooms[0] = new(common.RedisRealOnline) + zero := util.GetZeroTime(time.Now()).Unix() + playerIdMap.Range(func(k, v interface{}) bool { + p := v.(*player) + rooms[0].Total++ + if p.db.Birth >= zero { + rooms[0].New++ + } + return true + }) + return rooms +} diff --git a/modules/hall/player.go b/modules/hall/player.go new file mode 100644 index 0000000..4a9dffb --- /dev/null +++ b/modules/hall/player.go @@ -0,0 +1,46 @@ +package hall + +import ( + "fmt" + "server/call" + "server/common" + "server/pb" + + "github.com/gogo/protobuf/proto" + "github.com/liangdas/mqant/gate" + "github.com/liangdas/mqant/log" +) + +type player struct { + session gate.Session + token string + db common.PlayerDBInfo + lastLogin int64 // 最近一次登录时间 + isRecharge bool // 是否充值 +} + +func (p *player) PushRedPoint() { + call.PushRed(p.db.Id) +} + +// func (p *player) Send(topic string, data proto.Message) { +// call.SendSS(p.session, topic, data) +// } + +func (p *player) PushBroadcast() { + all := call.GetConfigBroadcast() + for _, v := range all { + if v.Open == 0 || v.TargetID != common.BrocastIDAll { + continue + } + msg := &pb.BroadcastMsg{ + Content: v.Content, + Priority: uint32(v.Priority), + Loop: int64(v.LoopFrequency), + Interval: int64(v.Interval), + } + send, _ := proto.Marshal(msg) + log.Debug("broadcast:%v", msg.Content) + p.session.Send(fmt.Sprintf("%v:%v", int(pb.ServerType_ServerTypeCommon), int(pb.ServerCommonResp_CommonBroadcastResp)), send) + } +} diff --git a/modules/hall/playermanage.go b/modules/hall/playermanage.go new file mode 100644 index 0000000..8b831f0 --- /dev/null +++ b/modules/hall/playermanage.go @@ -0,0 +1,77 @@ +package hall + +import ( + "server/call" + "server/common" + "server/db" + "sync" + + "github.com/liangdas/mqant/log" +) + +var ( + playerSessionMap sync.Map + playerIdMap sync.Map +) + +func newPlayer(uid int) *player { + p := &player{} + p.db.Id = uid + ret, err := call.GetUserXInfo(uid, "channel_id", "token", "birth") + if err != nil { + log.Error("err:%v", err) + } + p.db.ChannelID = ret.ChannelID + p.token = ret.Token + p.db.Birth = ret.Birth + + re := &common.RechargeInfo{UID: uid} + db.Mysql().Get(re) + p.isRecharge = re.TotalRecharge > 0 + return p +} + +func addPlayerBySession(s string, p *player) { + playerSessionMap.Store(s, p) +} + +func delPlayerBySession(s string) { + playerSessionMap.Delete(s) +} + +func addPlayerById(id int, p *player) { + log.Debug("add player %v", id) + playerIdMap.Store(id, p) +} + +// func getPlayerBySession(s gate.Session) *player { +// p, ok := playerSessionMap.Load(s) +// if !ok { +// return nil +// } + +// return p.(*player) +// } + +func getPlayerById(id int) *player { + p, ok := playerIdMap.Load(id) + if !ok { + return nil + } + + return p.(*player) +} + +func delPlayerByID(id int) { + log.Debug("del player %v", id) + playerIdMap.Delete(id) +} + +func playerNumber() (num int) { + playerIdMap.Range(func(_, _ interface{}) bool { + num++ + return true + }) + + return +} diff --git a/modules/hall/timer.go b/modules/hall/timer.go new file mode 100644 index 0000000..22cdf75 --- /dev/null +++ b/modules/hall/timer.go @@ -0,0 +1,4 @@ +package hall + +func StartTimer() { +} diff --git a/modules/pay/allpay/all.go b/modules/pay/allpay/all.go new file mode 100644 index 0000000..58dab76 --- /dev/null +++ b/modules/pay/allpay/all.go @@ -0,0 +1,41 @@ +package allpay + +import ( + "reflect" + "server/modules/pay/base" + "server/modules/pay/bfpay" + "server/modules/pay/grepay" + "server/modules/pay/igeekpay" + "server/modules/pay/luckinpay" + "server/modules/pay/pluspay" + + "github.com/liangdas/mqant/log" +) + +type AllPay struct { + Invalid struct{} + Igeek func(b *base.Base) + Plus func(b *base.Base) + Luckin func(b *base.Base) + BF func(b *base.Base) + Grepay func(b *base.Base) +} + +var All = &AllPay{} + +func init() { + All.Igeek = igeekpay.NewSub + All.Plus = pluspay.NewSub + All.Luckin = luckinpay.NewSub + All.BF = bfpay.NewSub + All.Grepay = grepay.NewSub +} + +func NewSub(b *base.Base, index int) { + ref := reflect.ValueOf(All).Elem().Field(index) + if !ref.IsValid() { + log.Error("invalid index:%v", index) + return + } + ref.Call([]reflect.Value{reflect.ValueOf(b)}) +} diff --git a/modules/pay/base/base.go b/modules/pay/base/base.go new file mode 100644 index 0000000..cb539a9 --- /dev/null +++ b/modules/pay/base/base.go @@ -0,0 +1,141 @@ +package base + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "reflect" + "server/modules/pay/values" + "server/pb" + "strings" + + "github.com/gin-gonic/gin" + "github.com/gogo/protobuf/proto" + "github.com/liangdas/mqant/log" +) + +type HttpType int + +const ( + HttpTypeInvalid = iota + HttpTypeJson + HttpTypeForm + HttpTypeAll +) + +type Base struct { + Channel values.PayWay + PayReq *pb.InnerRechargeReq + WithdrawReq *pb.InnerWithdrawReq + SignKey string + ShouldSignUpper bool + HttpType HttpType + ReqURL string + Opt int // 1支付 2退出 + Status int // 返回的状态 + Resp interface{} + CallbackReq interface{} + CallbackResp CallbackResp + SignPassStr []string // 不参与签名的字段 + WhiteIPs []string // ip白名单 + Sub + C *gin.Context +} + +type CallbackResp struct { + Msg string + OrderID string + APIOrderID string + FailMessage string + Success bool +} + +type Sub interface { + PackHeader(header http.Header) // 生成请求头 + PackReq() interface{} // 生成支付请求体 + GetResp() (proto.Message, error) // 获取返回 + CheckSign(str string) bool // 验签同时给callbackresp赋值 +} + +func NewRechargeBase(req *pb.InnerRechargeReq) *Base { + values.PackPay(req) + base := &Base{ + Channel: values.PayWay(req.Channel), + PayReq: req, + Opt: 1, + } + return base +} + +func NewWithdrawBase(req *pb.InnerWithdrawReq) *Base { + values.PackWithdraw(req) + base := &Base{ + Channel: values.PayWay(req.Channel), + WithdrawReq: req, + Opt: 2, + } + return base +} + +func NewCallbackBase(opt int) *Base { + base := &Base{ + Opt: opt, + } + return base +} + +func (b *Base) PackHeader(header http.Header) { + switch b.HttpType { + case HttpTypeJson: + header.Set("Content-Type", "application/json") + case HttpTypeForm: + header.Set("Content-Type", "application/x-www-form-urlencoded") + } + b.Sub.PackHeader(header) +} + +func (b *Base) Req() ([]byte, error) { + send := b.Sub.PackReq() + reqData := b.PackReq(send) + log.Debug("Req to:%v,req:%v", b.ReqURL, reqData) + req, err := http.NewRequest("POST", b.ReqURL, strings.NewReader(reqData)) + if err != nil { + log.Error("err:%v", err) + return nil, errors.New("pay fail") + } + b.PackHeader(req.Header) + b.Status = values.Post(req, b.Resp) + ret, err := b.Sub.GetResp() + if err != nil { + log.Error("err:%v", err) + return nil, err + } + data, _ := proto.Marshal(ret) + return data, nil +} + +func (b *Base) PackReq(send interface{}) string { + ref := reflect.ValueOf(send) + reft := reflect.TypeOf(send) + if ref.Kind() == reflect.Ptr { + ref = ref.Elem() + reft = reft.Elem() + } + switch b.HttpType { + case HttpTypeJson: + reqData, _ := json.Marshal(send) + return string(reqData) + case HttpTypeForm: + data := url.Values{} + for i := 0; i < ref.NumField(); i++ { + if ref.Field(i).IsZero() { + continue + } + data.Set(reft.Field(i).Tag.Get("json"), fmt.Sprintf("%v", ref.Field(i).Interface())) + } + return data.Encode() + } + return "" +} diff --git a/modules/pay/base/callback.go b/modules/pay/base/callback.go new file mode 100644 index 0000000..fd5eef8 --- /dev/null +++ b/modules/pay/base/callback.go @@ -0,0 +1,142 @@ +package base + +import ( + "encoding/json" + "io" + "net" + "net/http" + "reflect" + "server/call" + "server/common" + "server/db" + "server/modules/pay/values" + "strings" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" + iptool "github.com/liangdas/mqant/utils/ip" +) + +func (b *Base) PayCallback(c *gin.Context) { + b.C = c + if len(b.WhiteIPs) > 0 { + ip := iptool.RealIP(c.Request) + if strings.Contains(ip, ":") { + ip, _, _ = net.SplitHostPort(ip) + } + white := false + for _, v := range b.WhiteIPs { + if v == ip { + white = true + break + } + } + if !white { + return + } + } + var str string + var err error + if b.HttpType == HttpTypeJson { + str, err = b.CallbackJson(c) + } else if b.HttpType == HttpTypeForm { + str, err = b.CallbackForm(c) + } + defer func() { + c.String(http.StatusOK, b.CallbackResp.Msg) + }() + if err != nil || !b.CheckSign(str) { + b.CallbackResp.Msg = "callback fail" + return + } + orderID := b.CallbackResp.OrderID + or := &common.RechargeOrder{OrderID: orderID} + db.Mysql().Get(or) + if or.ID == 0 { + log.Error("order:%v not exist", orderID) + return + } + if or.Status == common.StatusROrderPay { + log.Error("order:%v exec done", orderID) + return + } + success := b.CallbackResp.Success + if err := call.RechargeCallback(or, success, "", ""); err != nil { + b.CallbackResp.Msg = "callback fail" + return + } + if success { + values.PayCallback(b.Channel) + } +} + +func (b *Base) WithdrawCallback(c *gin.Context) { + b.C = c + var str string + var err error + if b.HttpType == HttpTypeJson { + str, err = b.CallbackJson(c) + } else if b.HttpType == HttpTypeForm { + str, err = b.CallbackForm(c) + } + defer func() { + c.String(http.StatusOK, b.CallbackResp.Msg) + }() + if err != nil || !b.CheckSign(str) { + b.CallbackResp.Msg = "callback fail" + return + } + orderID := b.CallbackResp.OrderID + or := &common.WithdrawOrder{OrderID: orderID} + db.Mysql().Get(or) + if or.ID == 0 { + log.Error("order:%v not exist", orderID) + return + } + if or.APIPayID == "" { + or.APIPayID = b.CallbackResp.APIOrderID + } + success := b.CallbackResp.Success + or.FailReason = b.CallbackResp.FailMessage + if success { + or.Status = common.StatusROrderFinish + } else { + or.Status = common.StatusROrderFail + } + if err := call.WithdrawCallback(or); err != nil { + b.CallbackResp.Msg = "callback fail" + return + } +} + +func (b *Base) CallbackJson(c *gin.Context) (str string, err error) { + log.Debug("CallbackJson:%+v", *c.Request) + var tmp []byte + tmp, err = io.ReadAll(c.Request.Body) + defer func() { + c.Request.Body.Close() + }() + if err != nil { + log.Error("err:%v", err) + return + } + str = string(tmp) + log.Debug("CallbackJson body:%v", str) + err = json.Unmarshal(tmp, b.CallbackReq) + if err != nil { + log.Error("err:%v", err) + return + } + return +} + +func (b *Base) CallbackForm(c *gin.Context) (str string, err error) { + req := b.CallbackReq + typ := reflect.TypeOf(req).Elem() + val := reflect.ValueOf(req).Elem() + for i := 0; i < typ.NumField(); i++ { + val.Field(i).SetString(c.PostForm(typ.Field(i).Tag.Get("json"))) + } + str = values.GetSignStrForm(c, b.SignPassStr...) + return +} diff --git a/modules/pay/base/signmd5.go b/modules/pay/base/signmd5.go new file mode 100644 index 0000000..f38bfa3 --- /dev/null +++ b/modules/pay/base/signmd5.go @@ -0,0 +1,16 @@ +package base + +import ( + "server/util" + "strings" +) + +func (b *Base) SignMD5(send interface{}) string { + signStr := GetSignStr(send) + signStr += "&key=" + b.SignKey + ret := util.CalculateMD5(signStr) + if b.ShouldSignUpper { + ret = strings.ToUpper(ret) + } + return ret +} diff --git a/modules/pay/base/signrsa.go b/modules/pay/base/signrsa.go new file mode 100644 index 0000000..d132b3a --- /dev/null +++ b/modules/pay/base/signrsa.go @@ -0,0 +1,235 @@ +package base + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "errors" + "math/big" + + "github.com/liangdas/mqant/log" + "github.com/wenzhenxi/gorsa" +) + +// 使用SHA256withRSA算法对数据进行签名 +func SignWithSHA256(data []byte, privateKey string) (string, error) { + // 解析私钥 + block, _ := pem.Decode([]byte(privateKey)) + if block == nil { + return "", errors.New("invalid private key") + } + priKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return "", err + } + + // 计算数据的SHA256哈希值 + hash := sha256.Sum256(data) + + // 对哈希值进行签名 + signature, err := rsa.SignPKCS1v15(rand.Reader, priKey, crypto.SHA256, hash[:]) + if err != nil { + return "", err + } + + // 将签名结果进行Base64编码 + return base64.StdEncoding.EncodeToString(signature), nil +} + +// 使用SHA256withRSA算法对数据进行验签 +func VerifyWithSHA256(data []byte, signature string, publicKey string) (bool, error) { + // 解析公钥 + block, _ := pem.Decode([]byte(publicKey)) + if block == nil { + return false, errors.New("invalid public key") + } + pubKey, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return false, err + } + + // 计算数据的SHA256哈希值 + hash := sha256.Sum256(data) + + // 对签名进行Base64解码 + signatureBytes, err := base64.StdEncoding.DecodeString(signature) + if err != nil { + return false, err + } + + // 进行验签操作 + err = rsa.VerifyPKCS1v15(pubKey.(*rsa.PublicKey), crypto.SHA256, hash[:], signatureBytes) + if err == nil { + return true, nil + } else { + return false, err + } +} + +// 使用SHA512withRSA算法对数据进行签名 +func SignWithSHA512(data []byte, privateKey string) (string, error) { + // 解析私钥 + block, _ := pem.Decode([]byte(privateKey)) + if block == nil { + return "", errors.New("invalid private key") + } + priKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return "", err + } + + // 计算数据的SHA256哈希值 + hash := sha512.Sum512(data) + + // 对哈希值进行签名 + signature, err := rsa.SignPKCS1v15(rand.Reader, priKey, crypto.SHA512, hash[:]) + if err != nil { + return "", err + } + + // 将签名结果进行Base64编码 + return base64.StdEncoding.EncodeToString(signature), nil +} + +// 使用SHA512withRSA算法对数据进行验签 +func VerifyWithSHA512(data []byte, signature string, publicKey string) (bool, error) { + // 解析公钥 + block, _ := pem.Decode([]byte(publicKey)) + if block == nil { + return false, errors.New("invalid public key") + } + pubKey, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return false, err + } + + // 计算数据的SHA512哈希值 + hash := sha512.Sum512(data) + + // 对签名进行Base64解码 + signatureBytes, err := base64.StdEncoding.DecodeString(signature) + if err != nil { + return false, err + } + + // 进行验签操作 + err = rsa.VerifyPKCS1v15(pubKey.(*rsa.PublicKey), crypto.SHA512, hash[:], signatureBytes) + if err == nil { + return true, nil + } else { + return false, err + } +} + +// 私钥签名 +func RsaSignSha1(data []byte, privateKey string) ([]byte, error) { + h := sha1.New() + h.Write(data) + hashed := h.Sum(nil) + //获取私钥 + block, _ := pem.Decode([]byte(privateKey)) + if block == nil { + return nil, errors.New("private key error") + } + //解析PKCS1格式的私钥 + priv, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + return rsa.SignPKCS1v15(rand.Reader, priv, crypto.SHA1, hashed) +} + +// 公钥验证 +func RsaSignVer(data []byte, signature, publicKey string) error { + sign, _ := base64.StdEncoding.DecodeString(signature) + hashed := sha1.Sum(data) + block, _ := pem.Decode([]byte(publicKey)) + // 解析公钥 + pubInterface, _ := x509.ParsePKIXPublicKey(block.Bytes) + + // 类型断言 + pub := pubInterface.(*rsa.PublicKey) + //验证签名 + return rsa.VerifyPKCS1v15(pub, crypto.SHA1, hashed[:], sign) +} + +func RSA_public_decrypt(pubKey *rsa.PublicKey, data []byte) []byte { + c := new(big.Int) + m := new(big.Int) + m.SetBytes(data) + e := big.NewInt(int64(pubKey.E)) + c.Exp(m, e, pubKey.N) + out := c.Bytes() + skip := 0 + for i := 2; i < len(out); i++ { + if i+1 >= len(out) { + break + } + if out[i] == 0xff && out[i+1] == 0 { + skip = i + 2 + break + } + } + return out[skip:] +} + +// 公钥解密 +func RsaDecode(signature, publicKey string) error { + block, _ := pem.Decode([]byte(publicKey)) + public, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + log.Error("加载公钥错误 %+v\n", err) + return err + } + decodeBytes, _ := base64.StdEncoding.DecodeString(signature) + var result string + // 因为加密解密用rsa有长度限值,因此这个地方就看具体限值的多少了 + for len(decodeBytes) > 0 { + decodePart := decodeBytes[:128] + plain := RSA_public_decrypt(public.(*rsa.PublicKey), decodePart) + result += string(plain) + decodeBytes = decodeBytes[128:] + } + log.Debug("%+v", result) + if len(result) > 0 { + return nil + } + return errors.New("check sign fail") +} + +// 私钥加密 +func SignRSA(data string, privateKey string) (string, error) { + gRsa := gorsa.RSASecurity{} + gRsa.SetPrivateKey(privateKey) + + rsaData, err := gRsa.PriKeyENCTYPT([]byte(data)) + if err != nil { + return "", err + } + return base64.StdEncoding.EncodeToString(rsaData), nil +} + +func SignRSADecode(data string, publicKey string) (string, error) { + dataBs, err := base64.RawURLEncoding.DecodeString(data) + if err != nil { + return "", err + } + + gRsa := gorsa.RSASecurity{} + if err := gRsa.SetPublicKey(publicKey); err != nil { + return "", err + } + + rsaData, err := gRsa.PubKeyDECRYPT(dataBs) + if err != nil { + return "", err + } + + return string(rsaData), nil +} diff --git a/modules/pay/base/util.go b/modules/pay/base/util.go new file mode 100644 index 0000000..6e15ece --- /dev/null +++ b/modules/pay/base/util.go @@ -0,0 +1,57 @@ +package base + +import ( + "fmt" + "reflect" + "sort" + "strings" + + "github.com/liangdas/mqant/log" +) + +func GetSignStr(send interface{}) string { + m := StructToMapJson(send) + str := []string{} + for i := range m { + if i == "sign" { + continue + } + str = append(str, i) + } + sort.Strings(str) + signStr := "" + for i, v := range str { + signStr += fmt.Sprintf("%v=%v", v, m[v]) + if i != len(str)-1 { + signStr += "&" + } + } + log.Debug("signStr:%s", signStr) + return signStr +} + +func StructToMapJson(obj interface{}) map[string]interface{} { + t := reflect.TypeOf(obj) + v := reflect.ValueOf(obj) + if t.Kind() == reflect.Ptr { + t = t.Elem() + v = v.Elem() + } + var result = make(map[string]interface{}) + for i := 0; i < t.NumField(); i++ { + if v.Field(i).Kind() == reflect.Ptr && v.Field(i).IsNil() { + continue + } + tagName := t.Field(i).Tag.Get("json") + if tagName != "" && tagName != "-" && !v.Field(i).IsZero() { + data := v.Field(i).Interface() + if v.Field(i).Kind() == reflect.Struct { + data = GetSignStr(data) + } + name := strings.Split(tagName, ",") + result[name[0]] = data + } + } + + return result +} diff --git a/modules/pay/bfpay/base.go b/modules/pay/bfpay/base.go new file mode 100644 index 0000000..8e16ab9 --- /dev/null +++ b/modules/pay/bfpay/base.go @@ -0,0 +1,181 @@ +package bfpay + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "server/common" + "server/modules/pay/base" + "server/modules/pay/values" + "server/pb" + "server/util" + "strings" + "time" + + "github.com/gogo/protobuf/proto" + "github.com/liangdas/mqant/log" +) + +func NewSub(b *base.Base) { + sub := &Sub{ + Base: b, + } + b.SignKey = key + b.HttpType = base.HttpTypeJson + b.ShouldSignUpper = false + if b.Opt == 1 { + b.Resp = new(PayResp) + b.ReqURL = baseURL + payURL + } else if b.Opt == 2 { + b.Resp = new(WithdrawResp) + b.ReqURL = baseURL + withdrawURL + } else if b.Opt == 3 { + b.SignPassStr = []string{"sign"} + b.CallbackResp.Msg = "SUCCESS" + b.CallbackReq = new(PayCallbackReq) + } else if b.Opt == 4 { + b.SignPassStr = []string{"sign", "msg"} + b.CallbackResp.Msg = "SUCCESS" + b.CallbackReq = new(WithdrawCallbackReq) + } + b.Sub = sub +} + +type Sub struct { + Base *base.Base +} + +func (s *Sub) PackHeader(header http.Header) { + // header.Set("mchtId", mid) + // header.Set("version", "20") + // if s.Base.Opt == 1 { + // header.Set("biz", "bq102") + // } else if s.Base.Opt == 2 { + // header.Set("biz", "df104") + // } +} + +func (s *Sub) PackReq() interface{} { + if s.Base.Opt == 1 { + return s.PackPayReq() + } + return s.PackWithdrawReq() +} + +func (s *Sub) GetResp() (proto.Message, error) { + if s.Base.Opt == 1 { + resp := s.Base.Resp.(*PayResp) + if resp.Head.RespCode != "0000" || resp.Body.PayUrl == "" { + return nil, errors.New("pay fail") + } + return &pb.InnerRechargeResp{APIOrderID: resp.Body.TradeId, URL: resp.Body.PayUrl, Channel: uint32(values.BFPay)}, nil + } + resp := s.Base.Resp.(*WithdrawResp) + if s.Base.Status == 0 && resp.Head.RespCode != "0000" { + return nil, errors.New("withdraw fail") + } + withdrawRespBody := &WithdrawRespBody{} + err := Decode(resp.Body, withdrawRespBody) + if err != nil { + log.Error("err:%v", err) + } + return &pb.InnerWithdrawResp{APIOrderID: withdrawRespBody.TradeId, Channel: uint32(values.BFPay)}, nil +} + +func (s *Sub) PackPayReq() interface{} { + r := s.Base.PayReq + send := &PayReq{ + Head: Head{ + MchtId: mid, + Version: "20", + Biz: "bq101", + }, + + Body: Body{ + OrderId: r.OrderID, + OrderTime: time.Now().Format("20060102150405"), + Amount: fmt.Sprintf("%d", r.Amount/1e6), + CurrencyType: "BRL", + Goods: "baxipix", + NotifyUrl: values.GetPayCallback(values.BFPay), + CallBackUrl: values.GetFrontCallback(), + }, + } + send.Sign = s.Base.SignMD5(send.Body) + return send +} + +func (s *Sub) PackWithdrawReq() interface{} { + r := s.Base.WithdrawReq + if common.PayType(r.PayType) == common.PayTypeCPF { + r.Number = strings.ReplaceAll(r.Number, "-", "") + r.Number = strings.ReplaceAll(r.Number, ".", "") + } + send := &WithdrawReq{ + Head: Head{ + MchtId: mid, + Version: "20", + Biz: "df104", + }, + } + amount := fmt.Sprintf("%d", r.Amount/1e6) + withdrawReqBody := WithdrawBody{ + BatchOrderNo: r.OrderID, + TotalNum: 1, + TotalAmount: amount, + NotifyUrl: values.GetWithdrawCallback(values.BFPay), + CurrencyType: "BRL", + } + detail := []Detail{{ + Seq: "0", + Amount: amount, + AccType: "0", + CertType: fmt.Sprintf("%d", r.PayType-1), + CertId: r.Number, + BankCardName: r.Name, + }} + withdrawReqBody.Detail = detail + jsonStr, _ := json.Marshal(withdrawReqBody) + encodeStr := string(jsonStr) + log.Debug("encodeStr:%s", encodeStr) + send.Body = Encode(encodeStr) + return send +} + +func (s *Sub) CheckSign(str string) bool { + // signStr := values.GetSignStr(str, "sign") + s.Base.CallbackResp.Msg = "success" + if s.Base.Opt == 3 { + req := s.Base.CallbackReq.(*PayCallbackReq) + log.Debug("checkSign pay:%+v", *req) + s.Base.CallbackResp.OrderID = req.Body.OrderId + s.Base.CallbackResp.Success = req.Body.Status == "SUCCESS" + return util.CalculateMD5(base.GetSignStr(req.Body)+"&key="+key) == req.Sign + } + req := s.Base.CallbackReq.(*WithdrawCallbackReq) + log.Debug("checkSign withdraw:%+v", *req) + if req.Head.RespCode != "0000" { + return false + } + withdrawCallbackBody := &WithdrawCallbackBody{} + err := Decode(req.Body, withdrawCallbackBody) + if err != nil { + log.Error("err:%v", err) + return false + } + log.Debug("withdrawCallbackBody:%+v", withdrawCallbackBody) + if len(withdrawCallbackBody.Detail) == 0 { + return false + } + detail := withdrawCallbackBody.Detail[0] + if detail.Status == "AUDIT_DOING" || detail.Status == "AUDIT_SUCCESS" || detail.Status == "COMMITTED" || detail.Status == "COMMITTED_SUCCESS" || + detail.Status == "DOING" { + return false + } + s.Base.CallbackResp.OrderID = withdrawCallbackBody.BatchOrderNo + s.Base.CallbackResp.Success = detail.Status == "SUCCESS" + s.Base.CallbackResp.APIOrderID = withdrawCallbackBody.TradeId + s.Base.CallbackResp.FailMessage = req.Head.RespMsg + return true +} diff --git a/modules/pay/bfpay/values.go b/modules/pay/bfpay/values.go new file mode 100644 index 0000000..851febf --- /dev/null +++ b/modules/pay/bfpay/values.go @@ -0,0 +1,250 @@ +package bfpay + +import ( + "encoding/json" + "net/url" + "server/util" + + "github.com/liangdas/mqant/log" +) + +const ( + baseURL = "https://brl.bf-pay.com" + payURL = "/gateway/api/commPay" + withdrawURL = "/df/gateway/proxyrequest" + mid = "2000611000770152" + key = "afca030583d24e6faceceed3cddff3c9" +) + +var privateKeyPkCs8 = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAJ5hQDWLXdURqCIc +Er3ID1D/ovQw2K+UaXP4vJ5t8d56gzFitOeCx92Qeu4wO8rINH599AZyiO3HuQM+ +P7ueBqJyDmv/vOAOld+PwiTk42DVHlEj/h9kuGXV8vZfsU6BhgNGlq9bsSn0olbM +kz2WX8Qw2CRnGbMZvYK73yEl83E1AgMBAAECgYA+9p6WKs+k0x4qYUq6E/yy0M4x +kfGy66d4qVwjB8ZuEfpx+bG9j+pxFO0qIBbFKQ5lcyE+Ju50yT+uIGMp7UrpTYuX +aU55OVFrUaLAt/XeyaKcOSPB9xK+ObRnKUrZ92FN/PU/tHkaRjFxPaNiZGqwbBcQ +RdtcHpo/ZkooRvbWyQJBAM/tLpvLIwnC6Gi8JhGunSDwZFxbLyLW/S3x4b6S0IaC +Uor/AsplshWs8VQ0L0edyShnyYcFpxw4t5oP0RtHCaMCQQDC/30hiwPJMfRZ/6oF +ljV4nyN47rQssHG2EYo/HfL7tZn+URlcZ5oSSoM2Qe6cmzg4Vaoo+CRWwKbjsh/n +d3dHAkA2uAV/DHuBEyEUhwdBugEx7PGMeJa0BX4FfFVbUMm9zEgquiei2hZ+q8+q +yDz1DOomTwHzHaK3w5lV2vm9wvkfAkA7or1XI9e9kWyElb8exEiIIktL8dziiffM +0eJw2Sz1tB1rfMv/yaOCEo28az+ZX5M7D1/h9bnPWk3v9wrw1EWDAkBZNzIZ47TS +Mj9ipSQDsSklZYPGquqlRsjRm7OcDZXC7zW6qs3mTPPuISRuMVhmFr3Lz3YMT1o/ +S0Agrn9Dq3fM +-----END RSA PRIVATE KEY-----`) + +// var privateKey = []byte(`-----BEGIN RSA PRIVATE KEY----- +// MIICXgIBAAKBgQDgIesHgi+Qj0kkhwvJdGzt/QZDYR8VFzDxVgt5KdA8Ec1WCrhv +// GrmIEF2aKaqnPesv4/xn/0cLrXizYnF7wbgy/rrSzDmbWKEB6GZZc8Xz9iTxqoDT +// 2TpZclSUwKjeicdnw35PDGdBKKCbfLJeg9pXMs/L9VnY/QtDp03a3760owIDAQAB +// AoGBANQkZoA54elzJej0Bd0NXNk5x8bY04Gz7LhRGBT71cQ1mWQaS42l/vvheack +// TwlzGvu+UDbjMgzEid1IjV908XFVAnmJQxFxsHJt3HUz9qK5caXtqzwqP7Vt0Bl/ +// Nt7wXhn1xq9gljLRuVV9+MoaF6punFuksqp4Qh/AAZ/zGVcJAkEA/ITUGawSY+AX +// Gz1uAYadEnkA/Ce1rk/uL5OCSHxIvBLL3DmFp68E7+NTwyf2ilsnB0jtqOgBqrZ8 +// KoVfUjsxtwJBAOM46RGxijUhcDEoIZYUE+M+ugWskekdpwq4W9y1sNFULUjdXkwn +// UKeSlwTis8zFCw8mkZDOgNQzRsd0eFjm5HUCQQCt2MipD/TtO7bMsyML++Ahepr5 +// /mCvLCpAKN62BpKQoKQm7pcclXrhqHDfV6D9KboZ4tRzx552KAIdyAqS81vLAkB7 +// CVLy+L7MvDmC9KcTG/YU499YuTQdFahg3qknXt7Kypjmzq+D7vn2cyMBSzxu0feG +// Ea1ayubpgIZ/9CpCgWwNAkEA+f/5Ilnb3Dn9fRhGAuhttUIA9+vec3BH6rKc9oW/ +// JvLtLxuSpfg2D2D74nsZd48OVk+BJ0DqVeMb7sw6zjzH9A== +// -----END RSA PRIVATE KEY-----`) +var publicKey = []byte(`-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrTw3IoR3Q6oRukb6q0LR6dc/GRXVh0ngRPZzRsq87j6817v4HNjfsf2gYkefWK3AcEGyZT7uqji1C0drDfVy82HE68hYjmxgyXQO2YRjNZkYNHNPHD9EO4y5fr4qQsSMCYA1a7fgWqbqZiGPQgCSUvrpbFbJh53QxOWqpkrKoowIDAQAB-----END PUBLIC KEY-----`) +var myPublicKey = []byte(`-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeYUA1i13VEagiHBK9yA9Q/6L0 +MNivlGlz+LyebfHeeoMxYrTngsfdkHruMDvKyDR+ffQGcojtx7kDPj+7ngaicg5r +/7zgDpXfj8Ik5ONg1R5RI/4fZLhl1fL2X7FOgYYDRpavW7Ep9KJWzJM9ll/EMNgk +ZxmzGb2Cu98hJfNxNQIDAQAB +-----END PUBLIC KEY-----`) + +// Head represents the header of the request +type Head struct { + MchtId string `json:"mchtId"` // 商户编号,不参与签名 + Version string `json:"version"` // 固定值“20”,不参与签名 + Biz string `json:"biz"` // 固定值,支付方式,不参与签名 +} + +// Body represents the body of the request +type Body struct { + OrderId string `json:"orderId"` // 订单号 + OrderTime string `json:"orderTime"` // 订单时间yyyyMMddHHmmss + Amount string `json:"amount"` // 总金额,以分为单位 + CurrencyType string `json:"currencyType"` // 货币种类,三位字母代码 + Goods string `json:"goods"` // 商品名称(不要带中文元素) + NotifyUrl string `json:"notifyUrl"` // 接收推送通知的URL + CallBackUrl string `json:"callBackUrl"` // 网页回调地址 + Desc string `json:"desc,omitempty"` // 商品描述,logo地址 + AppId string `json:"appId,omitempty"` // 产品标识 + AppName string `json:"appName,omitempty"` // 产品名称 + Operator string `json:"operator,omitempty"` // 印度UPI支付,用户的VPA账号 + ExpireTime string `json:"expireTime,omitempty"` // 订单超时时间yyyyMMddHHmmss + IP string `json:"ip,omitempty"` // 请求IP地址 + Param string `json:"param,omitempty"` // 保留字段 + UserId string `json:"userId,omitempty"` // 要求:小于32位 + Phone string `json:"phone,omitempty"` // 手机号(币种是BRL,USDT,THB,PHP,USD,此参数可不传) + Name string `json:"name,omitempty"` // 姓名(币种是BRL,USDT,THB,PHP,USD,此参数可不传) + Email string `json:"email,omitempty"` // 邮箱(币种是BRL,USDT,THB,PHP,USD,此参数可不传) +} + +type PayReq struct { + Head Head `json:"head"` + Body Body `json:"body"` + Sign string `json:"sign"` // 签名信息 +} + +type PayResp struct { + Head struct { + RespCode string `json:"respCode"` // 返回Code + RespMsg string `json:"respMsg"` // 返回信息 + } `json:"head"` // 响应报文头 + Body struct { + MchtId string `json:"mchtId"` // 商户ID + OrderId string `json:"orderId"` // 商户订单号 + PayUrl string `json:"payUrl"` // 支付URL地址 + TradeId string `json:"tradeId,omitempty"` // 支付平台返回的交易流水号 + Param string `json:"param,omitempty"` // 保留字段 + } `json:"body,omitempty"` // 响应报文体, 在respCode为"0000"时返回 + Sign string `json:"sign"` // 签名信息 +} + +type PayCallbackReq struct { + Head struct { + RespCode string `json:"respCode"` // 返回Code + RespMsg string `json:"respMsg"` // 返回信息 + } `json:"head"` // 响应报文头 + Body struct { + Amount string `json:"amount"` // 金额 + Biz string `json:"biz"` // 业务类型 + ChargeTime string `json:"chargeTime"` // 充值时间 + MchtId string `json:"mchtId"` // 商户ID + OrderId string `json:"orderId"` // 商户订单号 + Seq string `json:"seq"` // 序号 + Status string `json:"status"` // 状态 + TradeId string `json:"tradeId"` // 交易流水号 + PayType string `json:"payType"` // 支付类型 + } `json:"body"` // 响应报文体 + Sign string `json:"sign"` // 签名信息 +} + +type WithdrawReq struct { + Head Head `json:"head"` + Body string `json:"body"` +} + +type WithdrawBody struct { + BatchOrderNo string `json:"batchOrderNo"` // 商户代付批次号,值唯一 + TotalNum int `json:"totalNum"` // 商户代付笔数,与detail代付明细集合数一致 + TotalAmount string `json:"totalAmount"` // 商户代付总金额,单位:分,为detail代付明细集合中金额总和 + NotifyUrl string `json:"notifyUrl,omitempty"` // 异步通知地址 + Detail []Detail `json:"detail"` // 代付订单明细,Json数组格式 + // AppId string `json:"appId,omitempty"` // 产品Id + CurrencyType string `json:"currencyType"` // 币种BRL +} + +type Detail struct { + Seq string `json:"seq"` // 序号,商户自定义 + Amount string `json:"amount"` // 金额,单位:分 + AccType string `json:"accType"` // 固定值 0 + CertType string `json:"certType"` // PIX账号类型或银行卡代付固定值 + CertId string `json:"certId"` // PIX账号或银行卡相关值 + // BankCardNo string `json:"bankCardNo,omitempty"` // 收款人的CPF或CNPJ(PIX代付必填) + BankCardName string `json:"bankCardName"` // 收款用户姓名 + // BankCode string `json:"bankCode,omitempty"` // 银行编码(银行卡代付时必传) + // Mobile string `json:"mobile,omitempty"` // 银行账户绑定的手机号码 + // Email string `json:"email,omitempty"` // 邮箱 + // BankCardType string `json:"bankCardType,omitempty"` // 银行卡类型,1:借记卡2:信用卡 + // CreditValid string `json:"creditValid,omitempty"` // 信用卡有效期,MMyy(信用卡时必填) + // CreditCvv string `json:"creditCvv,omitempty"` // 卡背面后3位数字(信用卡时必填) + // BankProvince string `json:"bankProvince,omitempty"` // 开户行所属省份 + // BankCity string `json:"bankCity,omitempty"` // 开户行所属市 + // BankLineCode string `json:"bankLineCode,omitempty"` // 联行号 + // BankName string `json:"bankName,omitempty"` // 银行名称 + // Remark string `json:"remark,omitempty"` // 备注 + // FirstName string `json:"firstName,omitempty"` // 收款人名 + // LastName string `json:"lastName,omitempty"` // 收款人姓 + // CardYear string `json:"cardYear,omitempty"` // 卡年份 + // CardMonth string `json:"cardMonth,omitempty"` // 卡月份 +} + +type WithdrawResp struct { + Head struct { + RespCode string `json:"respCode"` // 返回Code + } `json:"head"` // 响应报文头 + Body string `json:"body,omitempty"` // 响应报文体,以字符串表示 + Sign string `json:"sign,omitempty"` // 签名信息,假设存在时 +} + +type WithdrawRespBody struct { + Status string `json:"status"` // 订单受理状态 + TradeId string `json:"tradeId"` // 平台批次号 + BatchOrderNo string `json:"batchOrderNo"` // 商户批次号 + MchtId string `json:"mchtId"` // 商户编号 + Desc string `json:"desc,omitempty"` // 描述, 可选字段 +} + +type WithdrawCallbackReq struct { + Head struct { + RespCode string `json:"respCode"` // 返回Code + RespMsg string `json:"respMsg"` // 返回信息 + } `json:"head"` // 响应报文头 + Body string `json:"body,omitempty"` // 响应报文体,以字符串表示 + Sign string `json:"sign,omitempty"` // 签名信息,假设存在时 +} + +type WithdrawCallbackBody struct { + BatchOrderNo string `json:"batchOrderNo"` // 商户批次号 + TradeId string `json:"tradeId"` // 平台批次号 + TotalNum string `json:"totalNum"` // 代付笔数 + TotalAmount string `json:"totalAmount"` // 代付总金额 + Status string `json:"status"` // 状态 + Desc string `json:"desc,omitempty"` // 结果描述,可选字段 + Detail []WithdrawCallbackDetail `json:"detail"` // 代付明细订单详情 + +} + +type WithdrawCallbackDetail struct { + DetailId string `json:"detailId"` // 平台明细号 + Seq string `json:"seq"` // 序号 + Amount string `json:"amount"` // 金额 单位:分 + Status string `json:"status"` // 状态 + Desc string `json:"desc,omitempty"` // 结果描述 + FinishTime string `json:"finishTime,omitempty"` // 代付完成时间(币种时间)格式: yyyyMMddHHmmss +} + +func Decode(str string, ret interface{}) error { + str2, err := url.QueryUnescape(str) + if err != nil { + log.Error("err:%v", err) + return err + } + res, err := util.NewXRsa(publicKey, privateKeyPkCs8) + if err != nil { + log.Error("err:%v", err) + return err + } + tmp, err := res.PrivateDecrypt(str2) + if err != nil { + log.Error("err:%v", err) + return err + } + err = json.Unmarshal([]byte(tmp), ret) + if err != nil { + log.Error("err:%v", err) + return err + } + return nil +} + +func Encode(str string) string { + res, err := util.NewXRsa(publicKey, privateKeyPkCs8) + if err != nil { + log.Error("err:%v", err) + return "" + } + tmp, err := res.PublicEncrypt(str) + if err != nil { + log.Error("err:%v", err) + return "" + } + return url.QueryEscape(tmp) +} diff --git a/modules/pay/config.go b/modules/pay/config.go new file mode 100644 index 0000000..eeb97dc --- /dev/null +++ b/modules/pay/config.go @@ -0,0 +1,22 @@ +package pay + +import ( + "server/call" + "server/common" + "server/modules/pay/values" + "server/pb" +) + +func loadConfig() error { + c := map[int][]func(*pb.ReloadGameConfig) error{} + c[common.ReloadConfigPayWeight] = []func(*pb.ReloadGameConfig) error{func(rgc *pb.ReloadGameConfig) error { return LoadPayWeight() }} + call.LoadConfigs(c) + return nil +} + +func LoadPayWeight() error { + values.PayWeightLock.Lock() + defer values.PayWeightLock.Unlock() + call.LoadConfigPayChannels() + return nil +} diff --git a/modules/pay/grepay/base.go b/modules/pay/grepay/base.go new file mode 100644 index 0000000..8c81285 --- /dev/null +++ b/modules/pay/grepay/base.go @@ -0,0 +1,152 @@ +package grepay + +import ( + "errors" + "net/http" + "server/common" + "server/config" + "server/modules/pay/base" + "server/modules/pay/values" + "server/pb" + "server/util" + "strings" + + "github.com/gogo/protobuf/proto" + "github.com/liangdas/mqant/log" +) + +func NewSub(b *base.Base) { + sub := &Sub{ + Base: b, + } + b.SignKey = key + b.HttpType = base.HttpTypeForm + b.ShouldSignUpper = true + if b.Opt == 1 { + b.Resp = new(PayResp) + b.ReqURL = payURL + } else if b.Opt == 2 { + b.Resp = new(WithdrawResp) + b.ReqURL = withdrawURL + } else if b.Opt == 3 { + b.SignPassStr = []string{"sign"} + b.CallbackResp.Msg = "SC000000" + b.CallbackReq = new(PayCallbackReq) + } else if b.Opt == 4 { + b.SignPassStr = []string{"sign", "msg"} + b.CallbackResp.Msg = "SC000000" + b.CallbackReq = new(WithdrawCallbackReq) + } + b.Sub = sub +} + +type Sub struct { + Base *base.Base +} + +func (s *Sub) PackHeader(header http.Header) { + header.Set("Merchant-Id", config.GetConfig().Pay.IGeek.MID) +} + +func (s *Sub) PackReq() interface{} { + if s.Base.Opt == 1 { + return s.PackPayReq() + } + return s.PackWithdrawReq() +} + +func (s *Sub) GetResp() (proto.Message, error) { + if s.Base.Opt == 1 { + resp := s.Base.Resp.(*PayResp) + if resp.Code != "000000" || resp.BusContent == "" { + return nil, errors.New("pay fail") + } + return &pb.InnerRechargeResp{APIOrderID: resp.PrdOrdNo, URL: resp.BusContent, Channel: uint32(values.Grepay)}, nil + } + resp := s.Base.Resp.(*WithdrawResp) + if s.Base.Status == 0 && (resp.Code != "000000" || resp.OrdStatus == "08" || resp.OrdStatus == "09") { + return nil, errors.New("withdraw fail") + } + return &pb.InnerWithdrawResp{APIOrderID: resp.CasOrdNo, Channel: uint32(values.Grepay)}, nil +} + +func (s *Sub) PackPayReq() interface{} { + r := s.Base.PayReq + send := &PayReq{ + Version: "2.1", + OrgNo: oid, + CustId: mid, + CustOrderNo: r.OrderID, + TranType: "0512", + ClearType: "01", + PayAmt: int(r.Amount) / 1e6, + BackUrl: values.GetPayCallback(values.Grepay), + FrontUrl: values.GetFrontCallback(), + GoodsName: "shopbuy", + OrderDesc: "pix", + BuyIp: r.IP, + UserName: r.Name, + UserEmail: r.Email, + UserPhone: r.Phone, + CountryCode: "BR", + Currency: "BRL", + } + send.Sign = s.Base.SignMD5(send) + return send +} + +func (s *Sub) PackWithdrawReq() interface{} { + r := s.Base.WithdrawReq + if common.PayType(r.PayType) == common.PayTypeCPF { + r.Number = strings.ReplaceAll(r.Number, "-", "") + r.Number = strings.ReplaceAll(r.Number, ".", "") + } + // bc := strings.ToLower(common.PayType(r.PayType).String()) + send := &WithdrawReq{ + Version: "2.1", + OrgNo: oid, + CustId: mid, + CustOrdNo: r.OrderID, + CasType: "00", + Country: "BR", + Currency: "BRL", + CasAmt: r.Amount / 1e6, + DeductWay: "02", + CallBackUrl: values.GetWithdrawCallback(values.Grepay), + Account: "2406120000279502155", + PayoutType: "PIX", + AccountName: r.Name, + PanNum: r.Number, + WalletId: r.Number, + Phone: r.Phone, + Email: r.Email, + CasDesc: "pix", + } + send.Sign = s.Base.SignMD5(send) + return send +} + +func (s *Sub) CheckSign(str string) bool { + str += "&key=" + key + checkSign := "" + s.Base.CallbackResp.Msg = "success" + if s.Base.Opt == 3 { + req := s.Base.CallbackReq.(*PayCallbackReq) + log.Debug("checkSign pay:%+v", *req) + checkSign = req.Sign + s.Base.CallbackResp.OrderID = req.CustOrderNo + s.Base.CallbackResp.Success = req.OrdStatus == "01" + } else if s.Base.Opt == 4 { + req := s.Base.CallbackReq.(*WithdrawCallbackReq) + log.Debug("checkSign withdraw:%+v", *req) + if req.OrdStatus == "00" || req.OrdStatus == "01" || req.OrdStatus == "06" { + return false + } + checkSign = req.Sign + s.Base.CallbackResp.OrderID = req.CustOrderNo + s.Base.CallbackResp.Success = req.OrdStatus == "07" + s.Base.CallbackResp.APIOrderID = req.PrdOrdNo + s.Base.CallbackResp.FailMessage = req.CasDesc + } + return strings.ToUpper(util.CalculateMD5(str)) == checkSign +} diff --git a/modules/pay/grepay/values.go b/modules/pay/grepay/values.go new file mode 100644 index 0000000..88cef1d --- /dev/null +++ b/modules/pay/grepay/values.go @@ -0,0 +1,116 @@ +package grepay + +const ( + payURL = "https://api.metagopayments.com/cashier/pay.ac" + withdrawURL = "https://paypout.metagopayments.com/cashier/TX0001.ac" + oid = "8240601257" + mid = "24061200002795" + key = "1B5B3E0FE86054BD16F15C29CF4AC0B3" +) + +type PayReq struct { + Version string `json:"version"` // 版本号,固定值:2.1 + OrgNo string `json:"orgNo"` // 机构号,平台下发的机构编号 + CustId string `json:"custId"` // 商户编号,平台提供的商户编号 + CustOrderNo string `json:"custOrderNo"` // 商户订单编号,客户方生成的订单编号,不能重复 + TranType string `json:"tranType"` // 交易类型(传数字编码) 0512 + ClearType string `json:"clearType"` // 清算方式,传数字编码,默认为01 + PayAmt int `json:"payAmt"` // 支付金额, 单位是分(整数) + BackUrl string `json:"backUrl"` // 后台回调地址,支付完成时回调客户方结果接收地址 + FrontUrl string `json:"frontUrl"` // 前台回调地址,支付结果同步通知到前端 + // BankCode string `json:"bankCode"` // 银行编码,可选字段 + GoodsName string `json:"goodsName"` // 商品名称,不可使用中文 + OrderDesc string `json:"orderDesc"` // 订单描述,不可使用中文 + BuyIp string `json:"buyIp"` // 购买者IP,商户提交订单的IP + UserName string `json:"userName"` // 付款人姓名,不可用中国的用户名 + UserEmail string `json:"userEmail"` // 付款人邮箱,不可用中国的邮箱 + UserPhone string `json:"userPhone"` // 付款人手机号,不可用中国的手机号 + // UserCitizenId string `json:"userCitizenId,omitempty"` // 巴西唯一税号(CPF/CNPJ),特殊交易类型必填 + // UserDeviceId string `json:"userDeviceId,omitempty"` // 用户设备ID,可选字段 + CountryCode string `json:"countryCode"` // 国家编码,固定值: BR + Currency string `json:"currency"` // 币种,固定值: BRL + // City string `json:"city,omitempty"` // 商户用户的城市名称,可选字段 + // Street string `json:"street,omitempty"` // 商户用户的街道名称,可选字段 + // HouseNumber string `json:"houseNumber,omitempty"` // 商户用户的门牌号,可选字段 + Sign string `json:"sign"` // 签名,根据规则加签后的结果 +} + +type PayResp struct { + OrgNo string `json:"orgNo"` // 机构编号,平台下发的机构编号 + OrdStatus string `json:"ordStatus,omitempty"` // 订单状态,可选字段 + Code string `json:"code"` // 请求接口状态返回码 + Msg string `json:"msg"` // 返回描述 + CustId string `json:"custId"` // 平台提供的商户编号 + CustOrderNo string `json:"custOrderNo"` // 商户订单编号 + PrdOrdNo string `json:"prdOrdNo,omitempty"` // 平台订单号,可作为查询依据的订单号,订单处理成功时返回 + ContentType string `json:"contentType,omitempty"` // 业务内容类型,可选字段 + BusContent string `json:"busContent,omitempty"` // 订单处理成功时返回的业务内容,如支付页面的URL链接 + OrdDesc string `json:"ordDesc,omitempty"` // 状态描述,订单状态存在时,描述状态代表的含义 + Sign string `json:"sign"` // 签名,根据规则加签后的结果 +} + +type PayCallbackReq struct { + Version string `json:"version"` // 版本号,返回和支付接口上送的一致 + OrgNo string `json:"orgNo"` // 机构编号,平台下发的机构编号 + CustId string `json:"custId"` // 平台提供的商户编号 + CustOrderNo string `json:"custOrderNo"` // 商户订单编号,客户方生成的订单编号, 不能重复 + PrdOrdNo string `json:"prdOrdNo"` // 平台订单号,可作为查询依据 + OrdAmt string `json:"ordAmt"` // 订单金额,单位为分 + OrdTime string `json:"ordTime"` // 订单时间,格式: yyyyMMddHHmmss + PayAmt string `json:"payAmt"` // 支付金额,单位为分 + OrdStatus string `json:"ordStatus"` // 订单状态 + Sign string `json:"sign"` // 签名,根据规则加签以后的结果 +} + +type WithdrawReq struct { + Version string `json:"version"` // 版本号,固定值:2.1 + OrgNo string `json:"orgNo"` // 机构编号,平台下发的机构编号 + CustId string `json:"custId"` // 平台提供的商户编号 + CustOrdNo string `json:"custOrdNo"` // 商户订单编号,客户方生成的订单编号,不能重复 + CasType string `json:"casType"` // 清算类型,固定值00(T0结算) + Country string `json:"country"` // 国家编码,固定值:BR + Currency string `json:"currency"` // 币种编码,固定值:BRL + CasAmt int64 `json:"casAmt"` // 代付金额,单位是分 + DeductWay string `json:"deductWay"` // 手续费扣除方式,固定值02:账户余额中扣除 + CallBackUrl string `json:"callBackUrl"` // 回调地址 + Account string `json:"account"` // 代付子账户名称 + PayoutType string `json:"payoutType"` // 代付类型,可选值:Card, PIX + AccountName string `json:"accountName"` // 收款人姓名 + // PayeeBankCode string `json:"payeeBankCode,omitempty"` // 银行编码,payoutType为Card时必填 + CardType string `json:"cardType"` // 代付类型的收款方式 cpf + // CnapsCode string `json:"cnapsCode,omitempty"` // 巴西通道分行代码 + // CardNo string `json:"cardNo,omitempty"` // 银行卡号,payoutType为Card时必填 + PanNum string `json:"panNum"` // PAN Card 编码,必须传CPF(个人) + WalletId string `json:"walletId"` // 电子钱包收款账号,payoutType为PIX时必填 + // UpiId string `json:"upiId,omitempty"` // UPI 的收款账号,对接巴西渠道时为空 + AccountType string `json:"accountType"` // 账户类型,默认值1:对私 + Phone string `json:"phone"` // 收款人手机号 + Email string `json:"email"` // 收款人邮箱 + CasDesc string `json:"casDesc"` // 代付备注(禁止使用中文) + // AccountDigit string `json:"accountDigit,omitempty"` // 账户校验位,一般为银行卡最后一位校验位 + Sign string `json:"sign"` // 签名,根据规则加签以后的结果 +} + +type WithdrawResp struct { + OrgNo string `json:"orgNo"` // 机构编号,平台下发的机构编号 + OrdStatus string `json:"ordStatus,omitempty"` // 订单状态 + Code string `json:"code"` // 请求接口返回码 + Msg string `json:"msg"` // 返回描述 + CustId string `json:"custId"` // 平台提供的商户编号 + CustOrdNo string `json:"custOrdNo"` // 客户订单编号,原样返回 + CasOrdNo string `json:"casOrdNo,omitempty"` // 平台订单号,可作为查询依据 + CasAmt string `json:"casAmt"` // 代付金额,该字段值为100时则为1雷亚尔 + CasTime string `json:"casTime"` // 代付时间,格式:yyyyMMddHHmmss + Sign string `json:"sign"` // 签名,根据规则加签以后的结果 +} + +type WithdrawCallbackReq struct { + OrgNo string `json:"orgNo"` // 机构编号,平台下发的机构编号 + CustId string `json:"custId"` // 平台提供的商户编号 + CustOrderNo string `json:"custOrderNo"` // 商户订单编号,客户方生成的订单编号,不能重复 + PrdOrdNo string `json:"prdOrdNo"` // 平台订单号,作为查询依据 + PayAmt string `json:"payAmt"` // 代付订单金额,单位是分 + OrdStatus string `json:"ordStatus"` // 代付订单状态 + CasDesc string `json:"casDesc"` // 代付单描述 + Sign string `json:"sign"` // 签名,根据规则加签以后的结果 +} diff --git a/modules/pay/handler.go b/modules/pay/handler.go new file mode 100644 index 0000000..71ffbe8 --- /dev/null +++ b/modules/pay/handler.go @@ -0,0 +1,130 @@ +package pay + +import ( + "errors" + "server/call" + "server/common" + "server/db" + "server/modules/pay/allpay" + "server/modules/pay/base" + "server/modules/pay/values" + "server/pb" + "server/util" + "sync/atomic" + "time" + + "github.com/gogo/protobuf/proto" + "github.com/liangdas/mqant/log" +) + +func Recharge(req *pb.InnerRechargeReq) (ret []byte, err error) { + // 还原amount + // req.Amount /= common.DecimalDigits + payWay := values.PayWay(req.Channel) + log.Debug("recharge req:%+v,channel:%+v", req, payWay) + // 首先判断个卡 + // if req.IsPersonalCard && values.IsPayChannelValid(int(values.IGeekPay), req.Amount) { + // payWay = values.IGeekPay + // } else { + // payWay = values.ChoosePayWay(int(req.UID), int(req.Channel), req.Amount) + // } + if req.Channel == 0 { + for _, v := range call.GetConfigPayChannels() { + if req.Amount <= v.PayUp && req.Amount >= v.PayDown && v.CurrencyType == common.CurrencyBrazil { + req.Channel = uint32(v.ChannelID) + break + } + } + } + base := base.NewRechargeBase(req) + allpay.NewSub(base, int(req.Channel)) + start := time.Now() + if base.Sub != nil { + ret, err = base.Req() + } else { + ret, err = nil, errors.New("inner error") + } + + // 充值返回时间过长 + t := time.Since(start).Milliseconds() + if t >= 8000 { + log.Debug("longpay:%v,time:%v", payWay, t) + db.ES().InsertToESGO(common.ESIndexLongPay, &common.ESLongPay{Channel: int(payWay), Time: start.Unix(), + Cost: t, Date: start.Format("2006-01-02 15:04:05"), UID: int(req.UID), Amount: req.Amount}) + } + if err != nil { + values.PayFail(payWay) + } + return +} + +func Withdraw(req *pb.InnerWithdrawReq) ([]byte, error) { + // 还原amount + // req.Amount /= common.DecimalDigits + var channel *common.ConfigWithdrawChannels + if req.Channel >= 0 { + channel = call.GetConfigWithdrawChannelsByID(int(req.Channel), common.CurrencyBrazil) + } else { + channel = call.GetConfigWithdrawChannelsBest(common.CurrencyBrazil) + } + log.Debug("withdraw req:%+v,channel:%+v", req, channel) + if channel == nil || channel.WithdrawPer <= 0 { + return nil, errors.New("inner error") + } + flag := new(int64) + reqFin := make(chan struct{}, 1) + cid := values.PayWay(channel.ChannelID) + var ret []byte + var err error + base := base.NewWithdrawBase(req) + util.Go(func() { + allpay.NewSub(base, int(req.Channel)) + if base.Sub != nil { + ret, err = base.Req() + } else { + ret, err = nil, errors.New("inner error") + } + if atomic.CompareAndSwapInt64(flag, 0, 1) { + reqFin <- struct{}{} + } else { + // 没抢到则在pay模块更新订单 + time.AfterFunc(5*time.Second, func() { + log.Debug("order:%v,err:%v", req.OrderID, err) + or := &common.WithdrawOrder{OrderID: req.OrderID} + if err != nil { + db.Mysql().Get(or) + log.Debug("order:%+v,err:%v", or) + call.ReturnBackWithdraw(or, common.StatusROrderPay, common.StatusROrderFail) + } else { + data := &pb.InnerWithdrawResp{} + proto.Unmarshal(ret, data) + log.Debug("apipayid:%+v", data.APIOrderID) + if data.APIOrderID != "" { + db.Mysql().Update(or, map[string]interface{}{"apipayid": data.APIOrderID}) + } + } + }) + } + }) + select { + case <-time.After(4 * time.Second): + if atomic.CompareAndSwapInt64(flag, 0, 1) { + data := &pb.InnerWithdrawResp{Channel: uint32(cid)} + retData, _ := proto.Marshal(data) + return retData, nil + } else { + <-reqFin + if err != nil { + data := &pb.InnerWithdrawResp{Channel: uint32(cid)} + ret, _ = proto.Marshal(data) + } + return ret, err + } + case <-reqFin: + if err != nil { + data := &pb.InnerWithdrawResp{Channel: uint32(cid)} + ret, _ = proto.Marshal(data) + } + return ret, err + } +} diff --git a/modules/pay/igeekpay/base.go b/modules/pay/igeekpay/base.go new file mode 100644 index 0000000..9d91e33 --- /dev/null +++ b/modules/pay/igeekpay/base.go @@ -0,0 +1,147 @@ +package igeekpay + +import ( + "errors" + "fmt" + "net/http" + "server/common" + "server/config" + "server/modules/pay/base" + "server/modules/pay/values" + "server/pb" + "server/util" + "strings" + + "github.com/gogo/protobuf/proto" +) + +func NewSub(b *base.Base) { + sub := &Sub{ + Base: b, + } + b.SignKey = config.GetConfig().Pay.IGeek.Key + b.HttpType = base.HttpTypeJson + b.ShouldSignUpper = true + if b.Opt == 1 { + b.Resp = new(PayResp) + b.ReqURL = config.GetConfig().Pay.IGeek.APIURL + payURL + } else if b.Opt == 2 { + b.Resp = new(WithdrawResp) + b.ReqURL = config.GetConfig().Pay.IGeek.APIURL + withdrawURL + } else if b.Opt == 3 { + b.CallbackResp.Msg = "success" + b.CallbackReq = new(PayCallbackReq) + } else if b.Opt == 4 { + b.CallbackResp.Msg = "success" + b.CallbackReq = new(WithdrawCallbackReq) + } + b.Sub = sub +} + +type Sub struct { + Base *base.Base +} + +func (s *Sub) PackHeader(header http.Header) { + header.Set("Merchant-Id", config.GetConfig().Pay.IGeek.MID) +} + +func (s *Sub) PackReq() interface{} { + if s.Base.Opt == 1 { + return s.PackPayReq() + } + return s.PackWithdrawReq() +} + +func (s *Sub) GetResp() (proto.Message, error) { + if s.Base.Opt == 1 { + resp := s.Base.Resp.(*PayResp) + if resp.Status != 200 { + return nil, errors.New("pay fail") + } + return &pb.InnerRechargeResp{APIOrderID: resp.Data.TradeNo, URL: resp.Data.CashierUrl, Channel: uint32(values.IGeekPay)}, nil + } + resp := s.Base.Resp.(*WithdrawResp) + if s.Base.Status == 0 && resp.Status != 200 { + return nil, errors.New("withdraw fail") + } + return &pb.InnerWithdrawResp{APIOrderID: resp.Data.TradeNo, Channel: uint32(values.IGeekPay)}, nil +} + +func (s *Sub) PackPayReq() interface{} { + r := s.Base.PayReq + send := &PayReq{ + Version: "V1", + BizType: "LOCAL", + AppId: config.GetConfig().Pay.IGeek.APPID, + OrderNo: r.OrderID, + ProductCode: "CASHIER", + Currency: "BRL", + Amount: util.Decimal(float64(r.Amount)/common.DecimalDigits, 2), + PaymentDetail: PaymentDetail{ + PayMode: "PIX", + }, + ProductName: fmt.Sprintf("goods%v", r.Amount), + UserDetail: UserDetail{ + ID: fmt.Sprintf("%v", r.UID), + Name: r.Name, + Mobile: r.Phone, + Email: r.Email, + CpfNumber: util.CheckCPF(r.Number), + }, + FrontCallUrl: values.GetFrontCallback(), + BackCallUrl: values.GetPayCallback(values.IGeekPay), + } + send.Sign = s.Base.SignMD5(send) + return send +} + +func (s *Sub) PackWithdrawReq() interface{} { + r := s.Base.WithdrawReq + if common.PayType(r.PayType) == common.PayTypeCPF { + r.Number = strings.ReplaceAll(r.Number, "-", "") + r.Number = strings.ReplaceAll(r.Number, ".", "") + } + send := &WithdrawReq{ + Version: "V1", + BizType: "LOCAL", + AppId: config.GetConfig().Pay.IGeek.APPID, + OrderNo: r.OrderID, + ProductCode: "PAYOUT", + Currency: "BRL", + Amount: util.Decimal(float64(r.Amount)/common.DecimalDigits, 2), + PayMode: "PIX", + PayeeName: r.Name, + IDType: common.PayType(r.PayType).String(), + IDNumber: r.Number, + PayeeMobile: "55" + r.Phone, + UserId: fmt.Sprintf("%v", r.UID), + PayeeEmail: r.Email, + CpfNumber: r.Number, + PayeeAddress: r.Address, + BackCallUrl: values.GetWithdrawCallback(values.IGeekPay), + } + send.Sign = s.Base.SignMD5(send) + return send +} + +func (s *Sub) CheckSign(str string) bool { + str = values.GetSignStrNull(str, "sign") + str += "&key=" + config.GetConfig().Pay.IGeek.Key + checkSign := "" + s.Base.CallbackResp.Msg = "success" + if s.Base.Opt == 3 { + req := s.Base.CallbackReq.(*PayCallbackReq) + checkSign = req.Sign + s.Base.CallbackResp.OrderID = req.OrderNo + s.Base.CallbackResp.Success = req.PayStatus == "Success" + } else if s.Base.Opt == 4 { + req := s.Base.CallbackReq.(*WithdrawCallbackReq) + checkSign = req.Sign + s.Base.CallbackResp.OrderID = req.OrderNo + s.Base.CallbackResp.Success = req.PayStatus == "Success" + s.Base.CallbackResp.APIOrderID = req.TradeNo + s.Base.CallbackResp.FailMessage = req.FailMessage + } + return strings.ToUpper(util.CalculateMD5(str)) == checkSign +} diff --git a/modules/pay/igeekpay/values.go b/modules/pay/igeekpay/values.go new file mode 100644 index 0000000..46d2e55 --- /dev/null +++ b/modules/pay/igeekpay/values.go @@ -0,0 +1,126 @@ +package igeekpay + +const ( + payURL = "/collect/v1/order" + withdrawURL = "/payout/v1/brl" +) + +type PayReq struct { + Version string `json:"version,omitempty"` + BizType string `json:"bizType,omitempty"` + AppId string `json:"appId,omitempty"` + OrderNo string `json:"orderNo,omitempty"` + ProductCode string `json:"productCode,omitempty"` + Currency string `json:"currency,omitempty"` + Amount float64 `json:"amount,omitempty"` + // PaymentDetail PaymentDetail `json:"paymentDetail,omitempty"` + ProductName string `json:"productName,omitempty"` + ProductDesc string `json:"productDesc,omitempty"` + PaymentDetail PaymentDetail `json:"paymentDetail,omitempty"` + UserDetail UserDetail `json:"userDetail,omitempty"` + FrontCallUrl string `json:"frontCallUrl,omitempty"` + BackCallUrl string `json:"backCallUrl,omitempty"` + Lang string `json:"lang,omitempty"` + AttachField string `json:"attachField,omitempty"` + Sign string `json:"sign"` +} + +type PaymentDetail struct { + PayMode string `json:"payMode,omitempty"` // 支付模式 UPI/IMPS/WALLET + // AccountNo string `json:"accountNo,omitempty"` // 支付账户 + // BankIfsc string `json:"bankIfsc,omitempty"` // 银行ifsc编码 +} + +type UserDetail struct { + ID string `json:"id,omitempty"` // 用户uid + Name string `json:"name,omitempty"` // 名字 + Mobile string `json:"mobile,omitempty"` // 手机号 + Email string `json:"email,omitempty"` // 邮箱 + Address string `json:"address,omitempty"` // 地址 + IP string `json:"ip,omitempty"` + DeviceId string `json:"deviceId,omitempty"` + CpfNumber string `json:"cpfNumber,omitempty"` +} + +type PayResp struct { + Status int `json:"status,omitempty"` + Rel bool `json:"rel,omitempty"` + Data struct { + TradeNo string `json:"tradeNo,omitempty"` + OrderNo string `json:"orderNo,omitempty"` + ApplicationId string `json:"applicationId,omitempty"` // 商户appid + CashierUrl string `json:"cashierUrl,omitempty"` // 支付地址 + Sign string `json:"sign,omitempty"` + } `json:"data"` + Message string `json:"message"` +} + +type PayCallbackReq struct { + MerchantId string `json:"merchantId,omitempty"` + ApplicationId string `json:"applicationId,omitempty"` + TradeNo string `json:"tradeNo,omitempty"` + OrderNo string `json:"orderNo,omitempty"` + PayStatus string `json:"payStatus,omitempty"` + Amount float64 `json:"amount,omitempty"` + Currency string `json:"currency,omitempty"` + PayAmount float64 `json:"payAmount,omitempty"` + ServiceFee float64 `json:"serviceFee,omitempty"` + FailMessage string `json:"failMessage,omitempty"` + PayCurrency string `json:"payCurrency,omitempty"` + PayTime string `json:"payTime,omitempty"` + PayTimestamp int64 `json:"payTimestamp,omitempty"` + Attach string `json:"attach,omitempty"` + Sign string `json:"sign"` +} + +type WithdrawReq struct { + Version string `json:"version,omitempty"` + BizType string `json:"bizType,omitempty"` + AppId string `json:"appId,omitempty"` + OrderNo string `json:"orderNo,omitempty"` + ProductCode string `json:"productCode,omitempty"` + Currency string `json:"currency,omitempty"` + Amount float64 `json:"amount,omitempty"` + PayMode string `json:"payMode,omitempty"` // PIX + PayeeName string `json:"payeeName,omitempty"` + IDType string `json:"idType,omitempty"` + IDNumber string `json:"idNumber,omitempty"` + PayeeMobile string `json:"payeeMobile,omitempty"` + UserId string `json:"userId,omitempty"` + PayeeEmail string `json:"payeeEmail,omitempty"` + CpfNumber string `json:"cpfNumber,omitempty"` + PayeeAddress string `json:"payeeAddress,omitempty"` + BackCallUrl string `json:"backCallUrl,omitempty"` + // AttachField string `json:"attachField,omitempty"` + Sign string `json:"sign"` +} + +type WithdrawResp struct { + Status int `json:"status,omitempty"` + Rel bool `json:"rel,omitempty"` + Data struct { + ApplicationId string `json:"applicationId,omitempty"` + TradeNo string `json:"tradeNo,omitempty"` + OrderNo string `json:"orderNo,omitempty"` + Sign string `json:"sign,omitempty"` + } `json:"data,omitempty"` + Message string `json:"message,omitempty"` +} + +type WithdrawCallbackReq struct { + MerchantId string `json:"merchantId,omitempty"` + ApplicationId string `json:"applicationId,omitempty"` + TradeNo string `json:"tradeNo,omitempty"` + OrderNo string `json:"orderNo,omitempty"` + PayStatus string `json:"payStatus,omitempty"` + Amount float64 `json:"amount,omitempty"` + Currency string `json:"currency,omitempty"` + PayAmount float64 `json:"payAmount,omitempty"` + ServiceFee float64 `json:"serviceFee,omitempty"` + FailMessage string `json:"failMessage,omitempty"` + PayCurrency string `json:"payCurrency,omitempty"` + PayTime string `json:"payTime,omitempty"` + PayTimestamp int64 `json:"payTimestamp,omitempty"` + Attach string `json:"attach,omitempty"` + Sign string `json:"sign"` +} diff --git a/modules/pay/luckinpay/base.go b/modules/pay/luckinpay/base.go new file mode 100644 index 0000000..423a713 --- /dev/null +++ b/modules/pay/luckinpay/base.go @@ -0,0 +1,147 @@ +package luckinpay + +import ( + "errors" + "fmt" + "net/http" + "server/common" + "server/config" + "server/modules/pay/base" + "server/modules/pay/values" + "server/pb" + "server/util" + "strings" + "time" + + "github.com/gogo/protobuf/proto" + "github.com/liangdas/mqant/log" +) + +func NewSub(b *base.Base) { + sub := &Sub{ + Base: b, + } + b.SignKey = key + b.HttpType = base.HttpTypeJson + // b.ShouldSignUpper = true + if b.Opt == 1 { + b.Resp = new(PayResp) + b.ReqURL = payURL + } else if b.Opt == 2 { + b.Resp = new(WithdrawResp) + b.ReqURL = withdrawURL + } else if b.Opt == 3 { + // b.SignPassStr = []string{"sign"} + b.CallbackResp.Msg = "success" + b.CallbackReq = new(PayCallbackReq) + } else if b.Opt == 4 { + // b.SignPassStr = []string{"sign", "msg"} + b.CallbackResp.Msg = "success" + b.CallbackReq = new(WithdrawCallbackReq) + } + b.Sub = sub +} + +type Sub struct { + Base *base.Base +} + +func (s *Sub) PackHeader(header http.Header) { + header.Set("Merchant-Id", config.GetConfig().Pay.IGeek.MID) +} + +func (s *Sub) PackReq() interface{} { + if s.Base.Opt == 1 { + return s.PackPayReq() + } + return s.PackWithdrawReq() +} + +func (s *Sub) GetResp() (proto.Message, error) { + if s.Base.Opt == 1 { + resp := s.Base.Resp.(*PayResp) + if resp.Code != "00000" || resp.Data.PayData == "" { + return nil, errors.New("pay fail") + } + return &pb.InnerRechargeResp{APIOrderID: resp.Data.PayOrderNo, URL: resp.Data.PayData, Channel: uint32(values.LuckinPay)}, nil + } + resp := s.Base.Resp.(*WithdrawResp) + if s.Base.Status == 0 && resp.Code != "00000" { + return nil, errors.New("withdraw fail") + } + return &pb.InnerWithdrawResp{APIOrderID: resp.Data.PayOrderNo, Channel: uint32(values.LuckinPay)}, nil +} + +func (s *Sub) PackPayReq() interface{} { + r := s.Base.PayReq + send := &PayReq{ + MchNo: mid, + MchOrderNo: r.OrderID, + Currency: "BRL", + PayAmount: util.RoundFloat(float64(r.Amount)/common.DecimalDigits, 2), + AccountName: r.Name, + AccountEmail: r.Email, + AccountPhone: r.Phone, + CustomerIP: r.IP, + NotifyUrl: values.GetPayCallback(values.LuckinPay), + SuccessPageUrl: values.GetFrontCallback(), + ReqTime: fmt.Sprintf("%d", time.Now().UnixMilli()), + } + send.Sign, _ = base.SignWithSHA256([]byte(base.GetSignStr(send)), string(privateKey)) + return send +} + +func (s *Sub) PackWithdrawReq() interface{} { + r := s.Base.WithdrawReq + if common.PayType(r.PayType) == common.PayTypeCPF { + r.Number = strings.ReplaceAll(r.Number, "-", "") + r.Number = strings.ReplaceAll(r.Number, ".", "") + } + send := &WithdrawReq{ + MchNo: mid, + MchOrderNo: r.OrderID, + Currency: "BRL", + PayAmount: util.RoundFloat(float64(r.Amount)/common.DecimalDigits, 2), + AccountType: common.PayType(r.PayType).String(), + AccountCode: r.Number, + AccountNo: r.Number, + AccountName: r.Name, + AccountEmail: r.Email, + AccountPhone: r.Phone, + CustomerIP: r.IP, + NotifyUrl: values.GetWithdrawCallback(values.LuckinPay), + ReqTime: fmt.Sprintf("%d", time.Now().UnixMilli()), + } + send.Sign, _ = base.SignWithSHA256([]byte(base.GetSignStr(send)), string(privateKey)) + return send +} + +func (s *Sub) CheckSign(str string) bool { + signStr := values.GetSignStr(str, "sign") + checkSign := "" + s.Base.CallbackResp.Msg = "success" + if s.Base.Opt == 3 { + req := s.Base.CallbackReq.(*PayCallbackReq) + log.Debug("checkSign pay:%+v", *req) + checkSign = req.Sign + s.Base.CallbackResp.OrderID = req.MchOrderNo + s.Base.CallbackResp.Success = req.PayState == 2 + } else if s.Base.Opt == 4 { + req := s.Base.CallbackReq.(*WithdrawCallbackReq) + log.Debug("checkSign withdraw:%+v", *req) + if req.PayState == 1 { + return false + } + checkSign = req.Sign + s.Base.CallbackResp.OrderID = req.MchOrderNo + s.Base.CallbackResp.Success = req.PayState == 2 + s.Base.CallbackResp.APIOrderID = req.PayOrderNo + s.Base.CallbackResp.FailMessage = req.ErrMsg + } + pass, err := base.VerifyWithSHA256([]byte(signStr), checkSign, string(publicKey)) + if err != nil { + log.Error("err:%v", err) + return false + } + return pass +} diff --git a/modules/pay/luckinpay/values.go b/modules/pay/luckinpay/values.go new file mode 100644 index 0000000..e111303 --- /dev/null +++ b/modules/pay/luckinpay/values.go @@ -0,0 +1,147 @@ +package luckinpay + +const ( + payURL = "https://api.luckyinpay.online/api/pay/createPayinOrder" + withdrawURL = "https://api.luckyinpay.online/api/pay/createPayoutOrder" + mid = "M1704341111" + key = "O4HWRE7LH3HFDU6YTZJJKW7AGCIEICBR2KT1BR8JJVSNBBVUGLV6RA8ECGR82RMLDAOW8SUZW5YHPARCZQVRQE8MS8GGNWQ3PCIDKNIIIS7MOT8AEKESTMCUTRVZYQW7" +) + +var privateKeyPkCs8 = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCdQYMybS8v6qy9uqTycOH2mRK4aLh4Pzvv530u5eBwY42I+bZO63fUXkXBnecPMgVnlcovPMpPtKa6ocQSAgW2vYmxO+gX0MKPPHcv7UfdmgtC8nUtNoJTfvNOgmElZD64lSJqSGSp2bEE1s6BLH9rLFAr61FzHCHvJFHBlycabXLbByDpLgU/aIDSc0Q8fvSUoS5TacUPSoyAnFZw6k4yX8IAgPZXO3I99CcjsgFjR0b5dXUDaGY4luU13Rg5s7n3iE6mDakiAQ8DJYQNc+I10EgFvoxuviWndiW3zoa1+VLGPsiif7E6X8nfqgOPaexd3E0H7JmUd6JRD0ge8HtXAgMBAAECggEAaI0J4Rjeaokn1+yjhdyvHviejaRyIOBJxTKu8+M52P8XNp5vKwE6ZiNXVWbaHCwxk7Du/4D3MQ72Wtb6OM7HZbuWNBOUN2FAOWMGCwNC6H5mRlhUt36qH0EkGmpslCOV37qnauo+ov5sxr7aBN/Ex0hq9Qg62sE1fn0zLfaEtPhMRqbHHJ9+Jfz7J2Fp69nSxpMfs0h/gOg2Eu2eX+tkgK6G99UJgN+D60XEWqofeUtTu/FVVrKYBLXY1Lzb0nv9XpYXtN4MJa+Xdct2FtHMDUgebCzlVZN/sVlLoSqm+nxdGJD6IbZB0GghW+YZMnimDZ47o+KD7CRXk3fjcvBnYQKBgQDUxvcyfmlcxQdwvOqxXcVW1YvgPSQTFpOc8LEZGwjzs+NjfAbS7tJE7mLImZwon8wHMzuZ2LGwSN+CdpgXiUlTAbcwkyKE9BT4X0GHwp/WD0MPl82EQR6M2bN/71zg+PQc4Sh03JgSC57YGIqVkT55OLlIF3xyriKuApZWMCuVgwKBgQC9M0cOGbJhnOJcRfdl+ZyxslubjocDNRdYH+nSZTddqpR7z04o00/6miCuvzURQ6QorULlh9+WSMB+xOBd4DHn5Zqmw4VN2UujW6dgUvXfXMqtmvldtWK+lsLsge0xbHAq5Ap9fuGnvrYfmvGGbk6m1a1XOnpmUHBVxIXUhBLunQKBgQCZ33Ew4N4NKqdgzh3jOm7VhwTqmwyViUQiwKUyBK0KoFKWxUCiFfeVxddGPmABuN3xbwlxDpYhZ/HLBTyj+LJABwOVazIRd/oaS7i2FvdD9DGI+zyyoe0X6u+2W0GNqDvRDrsVF9oZYrHykHzYAPtu6qiDDAkBXhDSSiiyF4/NRQKBgB59IekqyO0j+/JEsB51wAN+q3aA3E7vAkkIM4TdHLPyZiUhfgXkL5JBvhyK4YFbtht7+DjG0YgFR0fmcAWQuFoXTPmsrlGiP6cegPVryQVqjZq2S5MHRNdTsiussE1znQu8XdhlVvXSLMUhEeTI59HIwzs4SDsuoTuhBLP/aJGdAoGBAJ8ZCNGThSVcQijk1J1Sw0ZelGe2tsgb+2rTl8kiLDOQOgnGKaErGGAyzZBmESqq677vXwbQ+Xn7HWz6HnC+WVoAhxZCLTKXs4YdiMzOiCKeDNWky899OK3qG/6E9CqaO2Pn5F1xlYKVxIUfkwt7aQedFEIcSVN6op2V+JK4fEFX +-----END RSA PRIVATE KEY-----`) +var privateKey = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAnUGDMm0vL+qsvbqk8nDh9pkSuGi4eD877+d9LuXgcGONiPm2 +Tut31F5FwZ3nDzIFZ5XKLzzKT7SmuqHEEgIFtr2JsTvoF9DCjzx3L+1H3ZoLQvJ1 +LTaCU37zToJhJWQ+uJUiakhkqdmxBNbOgSx/ayxQK+tRcxwh7yRRwZcnGm1y2wcg +6S4FP2iA0nNEPH70lKEuU2nFD0qMgJxWcOpOMl/CAID2VztyPfQnI7IBY0dG+XV1 +A2hmOJblNd0YObO594hOpg2pIgEPAyWEDXPiNdBIBb6Mbr4lp3Ylt86GtflSxj7I +on+xOl/J36oDj2nsXdxNB+yZlHeiUQ9IHvB7VwIDAQABAoIBAGiNCeEY3mqJJ9fs +o4Xcrx74no2kciDgScUyrvPjOdj/FzaebysBOmYjV1Vm2hwsMZOw7v+A9zEO9lrW ++jjOx2W7ljQTlDdhQDljBgsDQuh+ZkZYVLd+qh9BJBpqbJQjld+6p2rqPqL+bMa+ +2gTfxMdIavUIOtrBNX59My32hLT4TEamxxyffiX8+ydhaevZ0saTH7NIf4DoNhLt +nl/rZICuhvfVCYDfg+tFxFqqH3lLU7vxVVaymAS12NS829J7/V6WF7TeDCWvl3XL +dhbRzA1IHmws5VWTf7FZS6Eqpvp8XRiQ+iG2QdBoIVvmGTJ4pg2eO6Pig+wkV5N3 +43LwZ2ECgYEA1Mb3Mn5pXMUHcLzqsV3FVtWL4D0kExaTnPCxGRsI87PjY3wG0u7S +RO5iyJmcKJ/MBzM7mdixsEjfgnaYF4lJUwG3MJMihPQU+F9Bh8Kf1g9DD5fNhEEe +jNmzf+9c4Pj0HOEodNyYEgue2BiKlZE+eTi5SBd8cq4irgKWVjArlYMCgYEAvTNH +DhmyYZziXEX3ZfmcsbJbm46HAzUXWB/p0mU3XaqUe89OKNNP+pogrr81EUOkKK1C +5YfflkjAfsTgXeAx5+WapsOFTdlLo1unYFL131zKrZr5XbVivpbC7IHtMWxwKuQK +fX7hp762H5rxhm5OptWtVzp6ZlBwVcSF1IQS7p0CgYEAmd9xMODeDSqnYM4d4zpu +1YcE6psMlYlEIsClMgStCqBSlsVAohX3lcXXRj5gAbjd8W8JcQ6WIWfxywU8o/iy +QAcDlWsyEXf6Gku4thb3Q/QxiPs8sqHtF+rvtltBjag70Q67FRfaGWKx8pB82AD7 +buqogwwJAV4Q0kooshePzUUCgYAefSHpKsjtI/vyRLAedcADfqt2gNxO7wJJCDOE +3Ryz8mYlIX4F5C+SQb4ciuGBW7Ybe/g4xtGIBUdH5nAFkLhaF0z5rK5Roj+nHoD1 +a8kFao2atkuTB0TXU7IrrLBNc50LvF3YZVb10izFIRHkyOfRyMM7OEg7LqE7oQSz +/2iRnQKBgQCfGQjRk4UlXEIo5NSdUsNGXpRntrbIG/tq05fJIiwzkDoJximhKxhg +Ms2QZhEqquu+718G0Pl5+x1s+h5wvllaAIcWQi0yl7OGHYjMzogingzVpMvPfTit +6hv+hPQqmjtj5+RdcZWClcSFH5MLe2kHnRRCHElTeqKdlfiSuHxBVw== +-----END RSA PRIVATE KEY-----`) +var publicKey = []byte(`-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAh/nLyajLky2rGA1rKJD/CYi0BK1onauzcjbDTth9tGeypwZoFLHIlJw3vY1KMIkayBwCVYZK+9X7FpoIcBwcd3mfzFpQA0WIE7gDH+qdtlx/NVKpSyp/6c7l+iKEwxvt8fHbrN/QHMh9xehroS/f8cKxEA8/dC/DAmQQ284ydJ81Ft8pUksWEyL4s3cqbMZSQsbam86U5aU55qWvPvMGqHx/5tfr9dyQ1Pytvr1H9oRPvVXmFRSLofY8GlYapbchSsViyGfAWERs3hYQvAOGB62TEKJfeYTiQyd7/Akn0XCS1S2kS4KllRqq1pIU0XKVjFqXuLf69z05fGZDkytAawIDAQAB +-----END PUBLIC KEY----- +`) +var myPublicKey = []byte(`-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnUGDMm0vL+qsvbqk8nDh9pkSuGi4eD877+d9LuXgcGONiPm2Tut31F5FwZ3nDzIFZ5XKLzzKT7SmuqHEEgIFtr2JsTvoF9DCjzx3L+1H3ZoLQvJ1LTaCU37zToJhJWQ+uJUiakhkqdmxBNbOgSx/ayxQK+tRcxwh7yRRwZcnGm1y2wcg6S4FP2iA0nNEPH70lKEuU2nFD0qMgJxWcOpOMl/CAID2VztyPfQnI7IBY0dG+XV1A2hmOJblNd0YObO594hOpg2pIgEPAyWEDXPiNdBIBb6Mbr4lp3Ylt86GtflSxj7Ion+xOl/J36oDj2nsXdxNB+yZlHeiUQ9IHvB7VwIDAQAB +-----END PUBLIC KEY-----`) + +// var ( +// whiteIPs = []string{"52.67.100.247", "15.228.167.245", "54.207.16.136"} +// ) + +type PayReq struct { + MchNo string `json:"mchNo"` + MchOrderNo string `json:"mchOrderNo"` + Currency string `json:"currency"` + PayAmount string `json:"payAmount"` + AccountName string `json:"accountName"` + AccountEmail string `json:"accountEmail"` + AccountPhone string `json:"accountPhone"` + CustomerIP string `json:"customerIp"` + NotifyUrl string `json:"notifyUrl"` + SuccessPageUrl string `json:"successPageUrl"` + Summary string `json:"summary,omitempty"` + ReqTime string `json:"reqTime"` + Sign string `json:"sign"` +} + +type PayResp struct { + Code string `json:"code"` + Message string `json:"message"` + Sign string `json:"sign"` + Data PaymentData `json:"data"` +} + +// PaymentData 包含在支付响应中的数据体定义 +type PaymentData struct { + PayOrderNo string `json:"payOrderNo"` + MchOrderNo string `json:"mchOrderNo"` + MchNo string `json:"mchNo"` + Currency string `json:"currency"` + PayAmount string `json:"payAmount"` + PayInitiateTime string `json:"payInitiateTime"` + PayData string `json:"payData"` + PayReference string `json:"payReference,omitempty"` + PayState int `json:"payState"` +} + +type PayCallbackReq struct { + PayOrderNo string `json:"payOrderNo"` + MchOrderNo string `json:"mchOrderNo"` + MchNo string `json:"mchNo"` + Currency string `json:"currency"` + PayAmount string `json:"payAmount"` + PayState int `json:"payState"` + PayInitiateTime string `json:"payInitiateTime"` + PayFinishTime string `json:"payFinishTime"` + ErrMsg string `json:"errMsg,omitempty"` + Sign string `json:"sign"` +} + +type WithdrawReq struct { + MchNo string `json:"mchNo"` + MchOrderNo string `json:"mchOrderNo"` + Currency string `json:"currency"` + PayAmount string `json:"payAmount"` + AccountType string `json:"accountType"` + AccountCode string `json:"accountCode"` + AccountNo string `json:"accountNo"` + AccountName string `json:"accountName"` + AccountEmail string `json:"accountEmail"` + AccountPhone string `json:"accountPhone"` + CustomerIP string `json:"customerIp"` + NotifyUrl string `json:"notifyUrl"` + Summary string `json:"summary,omitempty"` + ReqTime string `json:"reqTime"` + Sign string `json:"sign"` +} + +type WithdrawResp struct { + Code string `json:"code"` + Message string `json:"message"` + Sign string `json:"sign"` + Data PayoutData `json:"data"` +} + +type PayoutData struct { + PayOrderNo string `json:"payOrderNo"` + MchOrderNo string `json:"mchOrderNo"` + MchNo string `json:"mchNo"` + Currency string `json:"currency"` + PayAmount string `json:"payAmount"` + PayInitiateTime string `json:"payInitiateTime"` + PayState int `json:"payState"` +} + +type WithdrawCallbackReq struct { + PayOrderNo string `json:"payOrderNo"` + MchOrderNo string `json:"mchOrderNo"` + MchNo string `json:"mchNo"` + Currency string `json:"currency"` + PayAmount string `json:"payAmount"` + PayState int `json:"payState"` + PayInitiateTime string `json:"payInitiateTime"` + PayFinishTime string `json:"payFinishTime"` + ErrMsg string `json:"errMsg,omitempty"` + Sign string `json:"sign"` +} diff --git a/modules/pay/middleware/cross.go b/modules/pay/middleware/cross.go new file mode 100644 index 0000000..19769e8 --- /dev/null +++ b/modules/pay/middleware/cross.go @@ -0,0 +1,50 @@ +package middleware + +import ( + "fmt" + "net/http" + "server/modules/web/app" + + "github.com/gin-gonic/gin" +) + +//跨域访问:cross origin resource share +func CrosHandler() gin.HandlerFunc { + return func(context *gin.Context) { + fmt.Printf("%+v\n", *context.Request) + method := context.Request.Method + origin := context.Request.Header.Get("Origin") //请求头部 + if origin != "" { + //接收客户端发送的origin (重要!) + context.Writer.Header().Set("Access-Control-Allow-Origin", "*") + + // 设置允许访问所有域 + context.Header("Access-Control-Allow-Origin", "*") + + //服务器支持的所有跨域请求的方法 + context.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE") + + //允许跨域设置可以返回其他子段,可以自定义字段 + context.Header("Access-Control-Allow-Headers", "Channel,uuid,version,Authorization, Content-Length, X-CSRF-Token, Token,session,X_Requested_With,Accept, Origin, Host, Connection, Accept-Encoding, Accept-Language,DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Pragma,token,openid,opentoken") + + // 允许浏览器(客户端)可以解析的头部 (重要) + context.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar") + + //设置缓存时间 + context.Header("Access-Control-Max-Age", "172800") + + //允许客户端传递校验信息比如 cookie (重要) + context.Header("Access-Control-Allow-Credentials", "true") + + // 设置返回格式是json + context.Set("content-type", "application/json") + } + + if method == "OPTIONS" { + context.JSON(http.StatusOK, app.R{Code: http.StatusOK, Data: "Options Request!"}) + } + + //处理请求 + context.Next() + } +} diff --git a/modules/pay/module.go b/modules/pay/module.go new file mode 100644 index 0000000..c695f0a --- /dev/null +++ b/modules/pay/module.go @@ -0,0 +1,104 @@ +package pay + +import ( + "context" + "net/http" + "server/call" + "server/config" + "server/db" + edb "server/db/es" + mdb "server/db/mysql" + rdb "server/db/redis" + "server/modules/pay/routers" + "strconv" + "time" + + "github.com/liangdas/mqant/conf" + "github.com/liangdas/mqant/log" + "github.com/liangdas/mqant/module" + basemodule "github.com/liangdas/mqant/module/base" + "github.com/liangdas/mqant/server" +) + +type Pay struct { + basemodule.BaseModule + httpSvr *http.Server +} + +var Module = func() module.Module { + this := new(Pay) + return this +} + +func (p *Pay) GetType() string { + return "pay" +} + +func (p *Pay) Version() string { + //可以在监控时了解代码版本 + return "1.0.0" +} + +func (p *Pay) OnInit(app module.App, settings *conf.ModuleSettings) { + metadata := make(map[string]string) + metadata["workID"] = strconv.Itoa(int(config.GetConfig().WorkID)) + p.BaseModule.OnInit(p, app, settings, + server.RegisterInterval(5*time.Second), + server.RegisterTTL(10*time.Second), + server.Metadata(metadata), + ) + call.NewCaller(p) + db.InitDB(&mdb.MysqlClient{}, &rdb.RedisClient{}, &edb.EsClient{}) + log.Info("[%v]module init finish, config:%+v", p.GetType(), config.GetConfig().Pay) + log.Info("[%v]module init finish, base:%+v", p.GetType(), config.GetBase()) + call.InitReload(p.App.Transport()) + initTimer() + loadConfig() + + p.GetServer().RegisterGO("recharge", Recharge) + p.GetServer().RegisterGO("withdraw", Withdraw) +} + +func (p *Pay) Run(closeSig chan bool) { + log.Info("[%v]module running", p.GetType()) + call.InitTimeWheel(closeSig) + p.startHttpServer() + <-closeSig + log.Info("[%v]module stop", p.GetType()) +} + +func (p *Pay) startHttpServer() { + cfg := config.GetConfig().Pay + router := routers.SetUpRouter() + srv := &http.Server{ + Addr: cfg.Addr, + Handler: router, + } + + go func() { + if config.GetConfig().Pay.TLS { + if err := srv.ListenAndServeTLS(cfg.CertFile, cfg.KeyFile); err != nil { + log.Error("ListenAndServeTLS fail error:%v", err) + } + } else { + if err := srv.ListenAndServe(); err != nil { + log.Error("ListenAndServe fail error:%v", err) + } + } + }() + // returning reference so caller can call Shutdown() + p.httpSvr = srv +} + +func (p *Pay) OnDestroy() { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + if err := p.httpSvr.Shutdown(ctx); err != nil { + log.Error("OnDestroy Shutdown error:%v", err) + } + //一定别忘了继承 + p.BaseModule.OnDestroy() + + log.Info("模块已销毁") +} diff --git a/modules/pay/pluspay/base.go b/modules/pay/pluspay/base.go new file mode 100644 index 0000000..bbace99 --- /dev/null +++ b/modules/pay/pluspay/base.go @@ -0,0 +1,139 @@ +package pluspay + +import ( + "errors" + "fmt" + "net/http" + "server/common" + "server/config" + "server/modules/pay/base" + "server/modules/pay/values" + "server/pb" + "server/util" + "strings" + + "github.com/gogo/protobuf/proto" + "github.com/liangdas/mqant/log" +) + +func NewSub(b *base.Base) { + sub := &Sub{ + Base: b, + } + b.SignKey = key + b.HttpType = base.HttpTypeForm + b.ShouldSignUpper = true + b.WhiteIPs = whiteIPs + if b.Opt == 1 { + b.Resp = new(PayResp) + b.ReqURL = payURL + } else if b.Opt == 2 { + b.Resp = new(WithdrawResp) + b.ReqURL = withdrawURL + } else if b.Opt == 3 { + b.SignPassStr = []string{"sign"} + b.CallbackResp.Msg = "success" + b.CallbackReq = new(PayCallbackReq) + } else if b.Opt == 4 { + b.SignPassStr = []string{"sign", "msg"} + b.CallbackResp.Msg = "success" + b.CallbackReq = new(WithdrawCallbackReq) + } + b.Sub = sub +} + +type Sub struct { + Base *base.Base +} + +func (s *Sub) PackHeader(header http.Header) { + header.Set("Merchant-Id", config.GetConfig().Pay.IGeek.MID) +} + +func (s *Sub) PackReq() interface{} { + if s.Base.Opt == 1 { + return s.PackPayReq() + } + return s.PackWithdrawReq() +} + +func (s *Sub) GetResp() (proto.Message, error) { + if s.Base.Opt == 1 { + resp := s.Base.Resp.(*PayResp) + if resp.RetCode != "SUCCESS" || resp.PayURL == "" { + return nil, errors.New("pay fail") + } + return &pb.InnerRechargeResp{APIOrderID: resp.PlatOrder, URL: resp.PayURL, Channel: uint32(values.PlusPay)}, nil + } + resp := s.Base.Resp.(*WithdrawResp) + if s.Base.Status == 0 && resp.RetCode != "SUCCESS" { + return nil, errors.New("withdraw fail") + } + return &pb.InnerWithdrawResp{APIOrderID: resp.PlatOrder, Channel: uint32(values.PlusPay)}, nil +} + +func (s *Sub) PackPayReq() interface{} { + r := s.Base.PayReq + send := &PayReq{ + MchID: mid, + OrderNo: r.OrderID, + Amount: util.RoundFloat(float64(r.Amount)/common.DecimalDigits, 2), + Product: "baxipix", + BankCode: "all", + Goods: fmt.Sprintf("email:%v/name:%v/phone:%v", r.Email, r.Name, r.Phone), // email:520155@gmail.com/name:tom/phone:7894561230 + // Goods: "testtests", + NotifyURL: values.GetPayCallback(values.PlusPay), + // NotifyURL: "asdasd", + // ReturnURL: "testtest", + ReturnURL: values.GetFrontCallback(), + } + send.Sign = s.Base.SignMD5(send) + return send +} + +func (s *Sub) PackWithdrawReq() interface{} { + r := s.Base.WithdrawReq + if common.PayType(r.PayType) == common.PayTypeCPF { + r.Number = strings.ReplaceAll(r.Number, "-", "") + r.Number = strings.ReplaceAll(r.Number, ".", "") + } + bc := strings.ToLower(common.PayType(r.PayType).String()) + send := &WithdrawReq{ + Type: "api", + MchID: mid, + MchTransNo: r.OrderID, + Amount: util.RoundFloat(float64(r.Amount)/common.DecimalDigits, 2), + NotifyURL: values.GetWithdrawCallback(values.PlusPay), + AccountName: r.Name, + AccountNo: r.Number, + BankCode: bc, + RemarkInfo: fmt.Sprintf("email:%v/phone:%v/mode:pix/%v:%v", r.Email, r.Phone, bc, r.Number), // email:520155@gmail.com/phone:9784561230/mode:pix/cpf:555555555(必须是真实的) + } + send.Sign = s.Base.SignMD5(send) + return send +} + +func (s *Sub) CheckSign(str string) bool { + str += "&key=" + key + checkSign := "" + s.Base.CallbackResp.Msg = "success" + if s.Base.Opt == 3 { + req := s.Base.CallbackReq.(*PayCallbackReq) + log.Debug("checkSign pay:%+v", *req) + checkSign = req.Sign + s.Base.CallbackResp.OrderID = req.OrderNo + s.Base.CallbackResp.Success = req.Status == "2" + } else if s.Base.Opt == 4 { + req := s.Base.CallbackReq.(*WithdrawCallbackReq) + log.Debug("checkSign withdraw:%+v", *req) + if req.Status == "1" { + return false + } + checkSign = req.Sign + s.Base.CallbackResp.OrderID = req.MchTransNo + s.Base.CallbackResp.Success = req.Status == "2" + s.Base.CallbackResp.APIOrderID = req.MchTransNo + s.Base.CallbackResp.FailMessage = req.Msg + } + return strings.ToUpper(util.CalculateMD5(str)) == checkSign +} diff --git a/modules/pay/pluspay/values.go b/modules/pay/pluspay/values.go new file mode 100644 index 0000000..fe1143f --- /dev/null +++ b/modules/pay/pluspay/values.go @@ -0,0 +1,73 @@ +package pluspay + +const ( + payURL = "http://api.fatpag.org/apipay" + withdrawURL = "http://api.fatpag.org/apitrans" + mid = "1686993516524" + key = "O4HWRE7LH3HFDU6YTZJJKW7AGCIEICBR2KT1BR8JJVSNBBVUGLV6RA8ECGR82RMLDAOW8SUZW5YHPARCZQVRQE8MS8GGNWQ3PCIDKNIIIS7MOT8AEKESTMCUTRVZYQW7" +) + +var ( + whiteIPs = []string{"52.67.100.247", "15.228.167.245", "54.207.16.136"} +) + +type PayReq struct { + MchID string `json:"mchId"` // 商户号,必填,填写商户的开户号。巴西 baxipix + OrderNo string `json:"orderNo"` // 订单号,必填,至少6位字符,最多22位。 + Amount string `json:"amount"` // 金额,必填,单位为元,保留两位小数。 + Product string `json:"product"` // 产品号,必填,支付产品说明。 + BankCode string `json:"bankcode"` // 银行代号(小写),必填,没有明确说明填写"all",具体填写见1.3订单说明。 + Goods string `json:"goods"` // 物品说明,必填,本字段是扩展字段,参考后面的说明进行对应格式要求进行字符串拼接。一般情况下提交email、name、phone等参数,格式举例:email:520155@gmail.com/name:tom/phone:7894561230。特殊情况在1.3订单说明中有具体描述。 + NotifyURL string `json:"notifyUrl"` // 异步通知,必填,支持HTTP和HTTPS通知,通知方式为POST。 + ReturnURL string `json:"returnUrl"` // 同步通知,必填,支持HTTP和HTTPS通知,通知方式为POST。 + Sign string `json:"sign"` // 签名,必填,MD5签名,签名顺序是字典排序。 +} + +type PayResp struct { + RetCode string `json:"retCode"` + PayURL string `json:"payUrl"` + OrderNo string `json:"orderNo"` + PlatOrder string `json:"platOrder"` + Code string `json:"code"` +} + +type PayCallbackReq struct { + MchID string `json:"mchId"` // 商户号,商户的开户号。 + OrderNo string `json:"orderNo"` // 订单号,至少6位字符,最多22位。 + Amount string `json:"amount"` // 金额,单位为元,保留两位小数。 + Product string `json:"product"` // 产品号,参考product支付产品说明。 + PaySuccTime string `json:"paySuccTime"` // 支付成功时间。 + Status string `json:"status"` // 成功状态,1:支付中,2:成功,5:失效,-1:失败。 + Sign string `json:"sign"` // 签名,商户返回数据得到签名与返回的签名进行验签名。 +} + +type WithdrawReq struct { + Type string `json:"type"` // 转账类型,必填,必填字符小写固定字符"api"。 + MchID string `json:"mchId"` // 商户号,必填,填写商户的开户号。 + MchTransNo string `json:"mchTransNo"` // 转账订单号,必填,至少6位字符,最多22位。 + Amount string `json:"amount"` // 金额,必填,单位为元,保留两位小数。 + NotifyURL string `json:"notifyUrl"` // 通知地址,必填,支持HTTP和HTTPS通知,通知方式为POST。 + AccountName string `json:"accountName"` // 账户名,必填,持卡人姓名。 + AccountNo string `json:"accountNo"` // 账号,必填,持卡人卡号。 + BankCode string `json:"bankCode"` // 银行代号(驼峰法),必填,见章节五各个国家银行代号。 + RemarkInfo string `json:"remarkInfo"` // 备注,必填,见章节五具体说明。 + Sign string `json:"sign"` // 签名,必填,MD5签名,签名顺序是字典排序。 +} + +type WithdrawResp struct { + RetCode string `json:"retCode"` // 成功 SUCCESS 失败 FAIL + RetMsg string `json:"retMsg"` + MchTransNo string `json:"mchTransNo"` + PlatOrder string `json:"platOrder"` + Status string `json:"status"` +} + +type WithdrawCallbackReq struct { + MchID string `json:"mchId"` // 商户号,商户的开户号。 + MchTransNo string `json:"mchTransNo"` // 转账订单号,至少6位字符,最多22位。 + Amount string `json:"amount"` // 金额,单位为元,保留两位小数。 + Status string `json:"status"` // 状态,1:处理中,2:成功,3:失败。 + TransSuccTime string `json:"transSuccTime"` // 成功时间。 + Sign string `json:"sign"` // 签名,商户返回数据得到签名与返回的签名进行验签名。 + Msg string `json:"msg"` // 信息描述,不参与签名,信息描述。 +} diff --git a/modules/pay/routers/common.go b/modules/pay/routers/common.go new file mode 100644 index 0000000..92b4a92 --- /dev/null +++ b/modules/pay/routers/common.go @@ -0,0 +1,23 @@ +package routers + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +func commonRouter(e *gin.RouterGroup) { + e.GET("front/payCallback", frontPayCallback) + e.GET("front/payCallback/fail", frontPayCallbackFail) +} + +func frontPayCallback(c *gin.Context) { + log.Debug("frontcallback:%v", c.Request.RequestURI) + c.Redirect(http.StatusMovedPermanently, "zypayment://result=true") +} + +func frontPayCallbackFail(c *gin.Context) { + log.Debug("frontcallback:%v", c.Request.RequestURI) + c.Redirect(http.StatusMovedPermanently, "zypayment://result=false") +} diff --git a/modules/pay/routers/routers.go b/modules/pay/routers/routers.go new file mode 100644 index 0000000..1807f5c --- /dev/null +++ b/modules/pay/routers/routers.go @@ -0,0 +1,82 @@ +package routers + +import ( + "reflect" + "server/common" + "server/config" + "server/modules/pay/allpay" + "server/modules/pay/base" + "server/modules/pay/middleware" + "strconv" + "strings" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +func SetUpRouter() *gin.Engine { + if config.GetBase().Release { + gin.SetMode(gin.ReleaseMode) + // 禁用控制台颜色 + gin.DisableConsoleColor() + } else { + gin.SetMode(gin.DebugMode) + } + r := gin.New() + + // 跨域处理 + r.Use(middleware.CrosHandler()) + r.Use(gin.RecoveryWithWriter(log.LogBeego(), nil)) + r.GET("/", common.HealthCheck) + com := r.Group("common") + commonRouter(com) + cb := r.Group("callback") + Callback(cb) + // igeek := r.Group("igeekpay") + // igeekPay(igeek) + // gre := r.Group("grepay") + // grePay(gre) + + return r +} + +func Callback(e *gin.RouterGroup) { + e.POST("pay/*action", PayCallback) + e.POST("withdraw/*action", WithdrawCallback) +} + +func PayCallback(c *gin.Context) { + log.Debug("PayCallback:%+v", c.Request) + path := c.Request.URL.Path + path = strings.ReplaceAll(path, "/callback/pay/", "") + index, err := strconv.Atoi(path) + if err != nil { + log.Error("err:%v", err) + return + } + b := base.NewCallbackBase(3) + allpay.NewSub(b, index) + if b.Sub == nil { + return + } + b.PayCallback(c) +} + +func WithdrawCallback(c *gin.Context) { + log.Debug("WithdrawCallback:%+v", c.Request) + path := c.Request.URL.Path + path = strings.ReplaceAll(path, "/callback/withdraw/", "") + index, err := strconv.Atoi(path) + if err != nil { + log.Error("err:%v", err) + return + } + ref := reflect.ValueOf(allpay.All).Elem().Field(index) + if !ref.IsValid() { + log.Error("invalid index:%v", index) + return + } + b := base.NewCallbackBase(4) + ref.Call([]reflect.Value{reflect.ValueOf(b)}) + b.WithdrawCallback(c) +} diff --git a/modules/pay/timer.go b/modules/pay/timer.go new file mode 100644 index 0000000..c6793d9 --- /dev/null +++ b/modules/pay/timer.go @@ -0,0 +1,302 @@ +package pay + +import ( + "encoding/json" + "errors" + "fmt" + "server/call" + "server/common" + "server/config" + "server/db" + "server/modules/pay/allpay" + "server/modules/pay/base" + "server/modules/pay/values" + "server/pb" + "server/util" + "sort" + "sync" + "time" + + "github.com/gogo/protobuf/proto" + "github.com/liangdas/mqant/log" +) + +var ( + withdrawGroup sync.WaitGroup +) + +func initTimer() { + // StartPayCheckTimer() + WithdrawTimer() +} + +func StartPayCheckTimer() { + values.PayStatus, _ = db.Redis().GetInt(common.RedisKeyPayStatus) + if values.PayStatus != values.PayStatusAuto { + values.PayStatus = values.PayStatusAuto + util.Go(func() { + db.Redis().SetData(common.RedisKeyPayStatus, values.PayStatusAuto) + }) + } + t := config.GetConfig().Pay.PayCheckTime + if t <= 0 { + t = 2 + } + time.AfterFunc(time.Duration(t)*time.Minute, func() { + PayCheck() + StartPayCheckTimer() + }) +} + +var ( + totalAll int64 = 20 // 当前时间段计算的总单数 +) + +// 每隔一段时间检测渠道成功率,切换渠道 +func PayCheck() { + log.Debug("pay check start...... ") + defer func() { + log.Debug("pay check finish...... ") + log.Debug("status:%v", values.PayStatus) + for _, v := range call.ConfigPayChannels { + log.Debug("%+v", *v) + } + }() + log.Debug("limit:%v", config.GetConfig().Pay.CheckLimit) + // t := time.Now().Add(-5 * time.Minute).Format("2006-01-02 15:04:05") + // 当前成功率没有达到预设值,触发重置权重 + // totalAll := getPayCount(t, -1, false) + // if totalAll == 0 { + // return + // } + // success := getPayCount(t, -1, true) + // if success*100/totalAll >= int64(config.GetConfig().Pay.CheckLimit) { + // return + // } + // 检查是否有渠道的成功率达标 + pays := []*common.ConfigPayChannels{} + values.PayWeightLock.RLock() + for _, v := range call.ConfigPayChannels { + if v.PayPer > 0 { + pays = append(pays, v) + } + } + values.PayWeightLock.RUnlock() + // choosePay := -1 + // var bestPer int64 + successPer := map[int]int64{} + successTotalPer := map[int]int64{} + // var totalSuccess int64 + for i, v := range pays { + per := getSuccessPer(v.ChannelID, i) + successPer[v.ChannelID] = per + // if per >= int64(config.GetConfig().Pay.CheckLimit) && per > bestPer { + // choosePay = v.ChannelID + // bestPer = per + // } + tPer := getSuccessPerTotal(v.ChannelID) + successTotalPer[v.ChannelID] = tPer + // totalSuccess += tPer + } + log.Debug("successPer:%v,successTotalPer:%v", successPer, successTotalPer) + // 当前有达标的渠道选择,直接切换渠道权重 + values.PayWeightLock.Lock() + defer values.PayWeightLock.Unlock() + // 全部按成功率重置权重 + for _, v := range pays { + st := successTotalPer[v.ChannelID] + if st == 0 { + st = int64(config.GetConfig().Pay.BaseSuccess) + } + per := successPer[v.ChannelID]*500 + st*100 + 100 + // if totalSuccess > 0 { + // per += successTotalPer[v.ChannelID] * 1000 / totalSuccess + // } + for _, c := range call.ConfigPayChannels { + if c.ChannelID == v.ChannelID { + c.PayPer = int(per) + break + } + } + cid := v.ChannelID + util.Go(func() { + values.SetPayChannel(cid, int(per)) + }) + } + // if choosePay > -1 { + // // 切换为主渠道模式 + // values.PayStatus = values.PayStatusChannel + // util.Go(func() { + // db.Redis().SetData(common.RedisKeyPayStatus, values.PayStatusChannel) + // }) + // } else { + // values.PayStatus = values.PayStatusAuto + // util.Go(func() { + // db.Redis().SetData(common.RedisKeyPayStatus, values.PayStatusAuto) + // }) + // 常驻渠道 + // for _, v := range config.GetConfig().Pay.RootChannel { + // for _, c := range call.ConfigPayChannels { + // if v == c.ChannelID { + // if c.PayPer <= 0 { + // c.PayPer = 500 + // cid := c.ChannelID + // util.Go(func() { + // values.SetPayChannel(cid, 500) + // }) + // } + // break + // } + // } + // } + // } + sort.Slice(call.ConfigPayChannels, func(i, j int) bool { + return call.ConfigPayChannels[i].PayPer > call.ConfigPayChannels[j].PayPer + }) +} + +func getPayCount(start, end int64, channel int, success bool) int64 { + sql := fmt.Sprintf("event = %v and create_time >= %d and create_time < %d and upi >= 0", common.CurrencyEventReCharge, start, end) + if channel >= 0 { + sql += fmt.Sprintf(" and pay_channel = %v", channel) + } + if success { + sql += fmt.Sprintf(" and status = %v", common.StatusROrderPay) + } + return db.Mysql().Count(&common.RechargeOrder{}, sql) +} + +func getSuccessPer(channel, index int) int64 { + now := time.Now() + start := now.Add(-6 * time.Minute).Unix() + end := now.Add(-1 * time.Minute).Unix() + total := getPayCount(start, end, channel, false) + success := getPayCount(start, end, channel, true) + if index == 0 { // 当前最高权重的渠道 + if total < 20 { // 如果最高权重的渠道总单数少于20,降低最低单数判断标准 + totalAll = 10 + } else { + totalAll = 20 + } + } + printTotal := total + if total < totalAll { + total = totalAll + } + per := success * 100 / total + log.Debug("channel:%v,total:%v,totalAll:%v,success:%v,per:%v", channel, printTotal, totalAll, success, per) + return per +} + +func getSuccessPerTotal(channel int) int64 { + now := time.Now() + start := now.Unix() + end := now.AddDate(0, 0, 1).Unix() + total := getPayCount(start, end, channel, false) + if total == 0 { + return 0 + } + success := getPayCount(start, end, channel, true) + if success == 0 && total > 50 { + return 1 + } + per := success * 100 / total + log.Debug("total:channel:%v,total:%v,success:%v,per:%v", channel, total, success, per) + return per +} + +func WithdrawTimer() { + list := []*common.WithdrawOrder{} + // startData := time.Now().AddDate(0, 0, -1).Unix() + db.Mysql().QueryAll(fmt.Sprintf("status = %d and pay_source = %d", + common.StatusROrderWaitting, common.PaySourceModulePay), "", &common.WithdrawOrder{}, &list) + l := len(list) + if l > 0 { + log.Debug("start scan orders:%v", l) + withdrawGroup.Add(l) + for _, v := range list { + // 提交订单 + res, err := db.Mysql().UpdateRes(&common.WithdrawOrder{OrderID: v.OrderID, Status: common.StatusROrderWaitting, + PaySource: common.PaySourceModulePay}, + map[string]interface{}{"status": common.StatusROrderPay}) + if err != nil { + log.Error("err:%v", err) + withdrawGroup.Done() + continue + } + if res == 0 { + withdrawGroup.Done() + continue + } + or := v + log.Debug("trying:%v", or.OrderID) + send := new(common.WithdrawCommon) + if err := json.Unmarshal([]byte(or.PayAccount), &send); err != nil { + log.Error("withdraw unmarshal err %v", err) + call.ReturnBackWithdraw(or, common.StatusROrderPay, common.StatusROrderFail) + return + } + util.Go(func() { + TryWithdraw(or) + withdrawGroup.Done() + }) + } + } + t := config.GetConfig().Pay.WithdrawScanTime + if t <= 0 { + t = 10 + } + time.AfterFunc(time.Duration(t)*time.Second, WithdrawTimer) +} + +func TryWithdraw(or *common.WithdrawOrder) { + send := new(common.WithdrawCommon) + if err := json.Unmarshal([]byte(or.PayAccount), &send); err != nil { + log.Error("withdraw unmarshal err %v", err) + call.ReturnBackWithdraw(or, common.StatusROrderPay, common.StatusROrderFail) + return + } + req := &pb.InnerWithdrawReq{ + OrderID: or.OrderID, + Amount: or.Amount, + Phone: send.Mobile, + Name: send.Name, + Email: send.Email, + PayType: int64(send.PayType), + Number: send.Number, + UID: uint32(or.UID), + Channel: int64(or.UPI), + Address: send.Address, + IP: send.IP, + } + channel := call.GetConfigWithdrawChannelsByID(int(req.Channel), common.CurrencyBrazil) + if channel == nil || channel.WithdrawPer <= 0 { + con := call.GetConfigWithdrawChannelsBest(common.CurrencyBrazil) + if con == nil { + return + } + req.Channel = int64(con.ChannelID) + } + + var ret []byte + var err error + base := base.NewWithdrawBase(req) + allpay.NewSub(base, int(req.Channel)) + if base.Sub != nil { + ret, err = base.Req() + } else { + ret, err = nil, errors.New("inner error") + } + log.Debug("order:%v,err:%v", req.OrderID, err) + if err != nil { + log.Debug("order:%+v,err:%v", or, err) + call.ReturnBackWithdraw(or, common.StatusROrderPay, common.StatusROrderFail, int(req.Channel)) + return + } + data := &pb.InnerWithdrawResp{} + proto.Unmarshal(ret, data) + log.Debug("withdraw resp:%+v", *data) + if data.APIOrderID != "" { + db.Mysql().Update(&common.WithdrawOrder{ID: or.ID}, map[string]interface{}{"apipayid": data.APIOrderID}) + } +} diff --git a/modules/pay/values/timer.go b/modules/pay/values/timer.go new file mode 100644 index 0000000..749ff32 --- /dev/null +++ b/modules/pay/values/timer.go @@ -0,0 +1,22 @@ +package values + +import ( + "time" +) + +const ( + PayTimerInterval = 5 * time.Minute +) + +var ( + PayStatus = PayStatusChannel // 当前支付选择方式 1是按渠道号选择 2按渠道权重动态分配 + // CheckCount = 0 // 按照动态权重配置的检查次数 + // CheckReset = 3 // 检查了预定次数还没有找到合适渠道,重置渠道 + RootChannel = []int{} // 常驻渠道,在渠道重置时,默认加上 +) + +// 1是按渠道号选择 2按渠道权重动态分配 +const ( + PayStatusChannel = iota + 1 + PayStatusAuto +) diff --git a/modules/pay/values/util.go b/modules/pay/values/util.go new file mode 100644 index 0000000..45c901a --- /dev/null +++ b/modules/pay/values/util.go @@ -0,0 +1,39 @@ +package values + +import ( + "fmt" + "math/rand" + "server/pb" + "server/util" +) + +func PackPay(r *pb.InnerRechargeReq) { + if len(r.Name) == 0 { + // r.Name = randName() + r.Name = util.GenerateRandomString(5) + } + r.Phone = util.CheckPhone(r.Phone) + if len(r.Email) == 0 { + r.Email = util.GetSimpleRandomString(5) + "@" + "gmail.com" + } + if r.IP == "" { + r.IP = fmt.Sprintf("%d.%d.%d.%d", rand.Intn(255), rand.Intn(255), rand.Intn(255), rand.Intn(255)) + } +} + +func PackWithdraw(r *pb.InnerWithdrawReq) { + if len(r.Name) == 0 { + r.Name = randName() + } + r.Phone = util.CheckPhone(r.Phone) + if len(r.Email) == 0 { + r.Email = util.GetSimpleRandomString(5) + "@" + "gmail.com" + } + if len(r.Address) == 0 { + r.Address = "SaoPaulo" + } +} + +func randName() string { + return util.GenerateRandomString(5) +} diff --git a/modules/pay/values/values.go b/modules/pay/values/values.go new file mode 100644 index 0000000..1b947ee --- /dev/null +++ b/modules/pay/values/values.go @@ -0,0 +1,432 @@ +package values + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "math/rand" + "net/http" + "server/call" + "server/common" + "server/config" + "server/db" + "server/util" + "sort" + "strings" + "sync" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" + "gorm.io/gorm" +) + +// 支付方式 +type PayWay int + +const ( + InvalidPay PayWay = iota + IGeekPay + PlusPay + LuckinPay + BFPay + Grepay + PayAll +) + +// 错误码 +const ( + CodeOk = iota +) + +var ( + PayWeightLock = new(sync.RWMutex) +) + +// SelectPayWay 特定条件为玩家优先选择渠道 +// func SelectPayWay(uid int, amount int64) PayWay { +// if !config.GetConfig().Pay.SelectPayWay { +// return -1 +// } +// banList := []int{} +// zlist := db.Redis().ZRange(common.GetRedisKeyPlayerPay(uid)) +// for _, v := range zlist { +// banList = append(banList, util.GetInt(v.Member)) +// } +// lastSuccess := &common.RechargeOrder{UID: uid, Event: int(common.CurrencyEventReCharge), Status: common.StatusROrderPay} +// err := db.Mysql().GetLast(lastSuccess) +// if err == nil { // 直接按上次成功的渠道作为首选推荐渠道 +// if !util.SliceContain(banList, lastSuccess.PayChannel) && IsTopChannel(lastSuccess.PayChannel, 3, amount) { +// return PayWay(lastSuccess.PayChannel) +// } +// } +// banAll := GetValidPayChannelsWithBanList(amount, banList) +// if PayStatus == PayStatusChannel { +// for _, v := range banAll { +// return PayWay(v.ChannelID) +// } +// } +// // 尝试当前权重最高的 +// all := GetValidPayChannels(amount) +// if len(all) > 0 { +// if !util.SliceContain(banList, all[0].ChannelID) { +// return PayWay(all[0].ChannelID) +// } +// } +// return GetPayWayWeight(banAll) +// } + +// 手动选择模式获取支付渠道 +func GetPayWayChannel(index int, all []*common.ConfigPayChannels) (w PayWay) { + if len(all) == 0 { + w = -1 + return + } + if index > len(all)-1 || index < 0 { + index = 0 + } + w = PayWay(all[index].ChannelID) + return +} + +// 权重模式获取支付渠道 +func GetPayWayWeight(all []*common.ConfigPayChannels) PayWay { + total := 0 + for _, v := range all { + total += v.PayPer + } + if total > 0 { + ran := rand.Intn(total) + rans := 0 + for i := 0; i < len(all); i++ { + rans += all[i].PayPer + if ran < rans { + return PayWay(all[i].ChannelID) + } + } + } + return -1 +} + +// ChoosePayWay 选择充值渠道 +// func ChoosePayWay(uid, index int, amount int64) PayWay { +// pw := SelectPayWay(uid, amount) +// if pw >= 0 { +// return pw +// } +// if PayStatus == PayStatusChannel { +// return GetPayWayChannel(index, GetValidPayChannels(amount)) +// } +// return GetPayWayWeight(GetValidPayChannels(amount)) +// } + +func PayFail(w PayWay) { + failWeight := config.GetConfig().Pay.PayFailWeight + if failWeight <= 0 { + return + } + PayWeightLock.Lock() + for _, v := range call.ConfigPayChannels { + if v.ChannelID == int(w) { + v.PayPer -= failWeight + break + } + } + sort.Slice(call.ConfigPayChannels, func(i, j int) bool { + return call.ConfigPayChannels[i].PayPer > call.ConfigPayChannels[j].PayPer + }) + PayWeightLock.Unlock() + AddPayChannel(int(w), -failWeight) +} + +func PayCallback(w PayWay) { + successWeight := config.GetConfig().Pay.PaySuccessWeight + if successWeight <= 0 { + return + } + PayWeightLock.Lock() + for _, v := range call.ConfigPayChannels { + if v.ChannelID == int(w) { + v.PayPer += successWeight + break + } + } + sort.Slice(call.ConfigPayChannels, func(i, j int) bool { + return call.ConfigPayChannels[i].PayPer > call.ConfigPayChannels[j].PayPer + }) + PayWeightLock.Unlock() + AddPayChannel(int(w), successWeight) +} + +// 获取支付回调路径 +func GetPayCallback(way PayWay) string { + return config.GetConfig().Pay.CallbackURL + config.GetConfig().Pay.Addr + "/callback/pay/" + fmt.Sprintf("%d", way) +} + +// 获取退出回调路径 +func GetWithdrawCallback(way PayWay) string { + return config.GetConfig().Pay.CallbackURL + config.GetConfig().Pay.Addr + "/callback/withdraw/" + fmt.Sprintf("%d", way) +} + +func GetFrontCallback() string { + return config.GetConfig().Pay.CallbackURL + config.GetConfig().Pay.Addr + "/common/front/payCallback" +} + +func GetFrontCallbackFail() string { + return config.GetConfig().Pay.CallbackURL + config.GetConfig().Pay.Addr + "/common/front/payCallback/fail" +} + +// 根据body里的字段直接拼接出签名字符串 +func GetSignStr(str string, pass ...string) string { + m := map[string]json.RawMessage{} + sortStrs := []string{} + json.Unmarshal([]byte(str), &m) + for i := range m { + sortStrs = append(sortStrs, i) + } + signStr := "" + sort.Strings(sortStrs) + for _, v := range sortStrs { + shouldPass := false + for _, s := range pass { + if v == s { + shouldPass = true + break + } + } + if shouldPass { + continue + } + if len(m[v]) > 1 && m[v][0] == 34 { + m[v] = m[v][1 : len(m[v])-1] + } + if len(m[v]) == 0 { + continue + } + signStr += fmt.Sprintf("%v=%v", v, string(m[v])) + signStr += "&" + } + signStr = signStr[:len(signStr)-1] + log.Debug("signStr:%v", signStr) + return signStr +} + +// 根据body里的字段直接拼接出签名字符串(去除null) +func GetSignStrNull(str string, pass ...string) string { + m := map[string]json.RawMessage{} + sortStrs := []string{} + json.Unmarshal([]byte(str), &m) + for i := range m { + sortStrs = append(sortStrs, i) + } + signStr := "" + sort.Strings(sortStrs) + for _, v := range sortStrs { + shouldPass := false + for _, s := range pass { + if v == s { + shouldPass = true + break + } + } + if shouldPass { + continue + } + if len(m[v]) > 1 && m[v][0] == 34 { + m[v] = m[v][1 : len(m[v])-1] + } + if len(m[v]) == 0 || string(m[v]) == "null" { + continue + } + signStr += fmt.Sprintf("%v=%v", v, string(m[v])) + signStr += "&" + } + signStr = signStr[:len(signStr)-1] + log.Debug("signStr:%v", signStr) + return signStr +} + +// 根据body里的字段直接拼接出签名字符串(包括空字符串) +func GetSignStrAll(str string, pass ...string) string { + m := map[string]json.RawMessage{} + sortStrs := []string{} + json.Unmarshal([]byte(str), &m) + for i := range m { + sortStrs = append(sortStrs, i) + } + signStr := "" + sort.Strings(sortStrs) + for _, v := range sortStrs { + shouldPass := false + for _, s := range pass { + if v == s { + shouldPass = true + break + } + } + if shouldPass { + continue + } + if len(m[v]) > 1 && m[v][0] == 34 { + m[v] = m[v][1 : len(m[v])-1] + } + signStr += fmt.Sprintf("%v=%v", v, string(m[v])) + signStr += "&" + } + signStr = signStr[:len(signStr)-1] + log.Debug("signStr:%v", signStr) + return signStr +} + +// 根据body里的字段直接拼接出签名字符串(包括空字符串,unicode转码) +func GetSignStrAllUnicode(str string, pass ...string) string { + m := map[string]json.RawMessage{} + sortStrs := []string{} + json.Unmarshal([]byte(str), &m) + for i := range m { + sortStrs = append(sortStrs, i) + } + signStr := "" + sort.Strings(sortStrs) + for _, v := range sortStrs { + shouldPass := false + for _, s := range pass { + if v == s { + shouldPass = true + break + } + } + if shouldPass { + continue + } + str := string(m[v]) + if len(str) > 1 && str[0] == 34 { + str = str[1 : len(str)-1] + } + tmp, err := util.ZhToUnicode(str) + if err == nil { + str = tmp + } + str = strings.ReplaceAll(str, `\`, "") + signStr += fmt.Sprintf("%v=%v", v, str) + signStr += "&" + } + signStr = signStr[:len(signStr)-1] + log.Debug("signStr:%v", signStr) + return signStr +} + +func GetSignStrForm(c *gin.Context, pass ...string) string { + sortStrs := []string{} + for k := range c.Request.PostForm { + sortStrs = append(sortStrs, k) + } + signStr := "" + sort.Strings(sortStrs) + for _, v := range sortStrs { + shouldPass := false + for _, s := range pass { + if v == s { + shouldPass = true + break + } + } + if shouldPass { + continue + } + if len(c.PostForm(v)) == 0 { + continue + } + signStr += fmt.Sprintf("%v=%v", v, c.PostForm(v)) + signStr += "&" + } + signStr = signStr[:len(signStr)-1] + log.Debug("signStr:%v", signStr) + return signStr +} + +// status=1时,属于不知道第三方是什么情况的报错,不能当成业务中断 +// status=0时,可以根据返回判断是否是业务错误 +func Post(req *http.Request, ret interface{}) int { + client := &http.Client{} + log.Debug("req:%+v", req) + resp, err := client.Do(req) + if err != nil { + log.Error("http post call err:%v", err) + return 1 + } + defer resp.Body.Close() + body, _ := ioutil.ReadAll(resp.Body) + log.Debug("response Body%v:", string(body)) + if err := json.Unmarshal(body, ret); err != nil { + log.Error("unmarshal fail err:%v", err) + return 1 + } + return 0 +} + +func AddPayChannel(p int, amount int) { + if err := db.Mysql().C().Model(&common.ConfigPayChannels{}).Where("channel_id = ?", p).Updates(map[string]interface{}{"pay_per": gorm.Expr("pay_per + ?", amount)}).Error; err != nil { + log.Error("err:%v", err) + } +} + +func SetPayChannel(p int, amount int) { + if err := db.Mysql().C().Model(&common.ConfigPayChannels{}).Where("channel_id = ?", p).Updates(map[string]interface{}{"pay_per": amount}).Error; err != nil { + log.Error("err:%v", err) + } +} + +func GetValidPayChannels(amount int64) []*common.ConfigPayChannels { + PayWeightLock.RLock() + defer PayWeightLock.RUnlock() + all := []*common.ConfigPayChannels{} + for _, v := range call.ConfigPayChannels { + if v.PayPer > 0 && amount >= v.PayDown && (amount <= v.PayUp || v.PayUp <= 0) { + all = append(all, v) + } + } + return all +} + +func GetValidPayChannelsWithBanList(amount int64, banList []int) []*common.ConfigPayChannels { + all := []*common.ConfigPayChannels{} + PayWeightLock.RLock() + defer PayWeightLock.RUnlock() + for _, v := range call.ConfigPayChannels { + if v.PayPer > 0 && amount >= v.PayDown && (amount <= v.PayUp || v.PayUp <= 0) { + if !util.SliceContain(banList, v.ChannelID) { + all = append(all, v) + } + } + } + return all +} + +func IsPayChannelValid(cid int, amount int64) bool { + PayWeightLock.RLock() + defer PayWeightLock.RUnlock() + for _, v := range call.ConfigPayChannels { + if cid == v.ChannelID { + if v.PayPer > 0 && amount >= v.PayDown && (amount <= v.PayUp || v.PayUp <= 0) { + return true + } + break + } + } + return false +} + +// IsTopChannel 判断渠道是否是排名前几的可用渠道 +func IsTopChannel(cid, top int, amount int64) bool { + PayWeightLock.RLock() + defer PayWeightLock.RUnlock() + for i := 0; i < top; i++ { + v := call.ConfigPayChannels[i] + if cid == v.ChannelID { + if v.PayPer > 0 && amount >= v.PayDown && (amount <= v.PayUp || v.PayUp <= 0) { + return true + } + } + } + return false +} diff --git a/modules/web/app/account.go b/modules/web/app/account.go new file mode 100644 index 0000000..f84c3a0 --- /dev/null +++ b/modules/web/app/account.go @@ -0,0 +1,94 @@ +package app + +import ( + "net" + "server/call" + "server/common" + "server/db" + "server/modules/web/values" + "strings" + + "github.com/go-redis/redis/v8" + "github.com/liangdas/mqant/log" + iptool "github.com/liangdas/mqant/utils/ip" + "gorm.io/gorm" +) + +// VerifyCode 验证短信验证码 +func (g *Gin) VerifyCode(reqContent, reqCode string) bool { + code, err := db.Redis().GetString(common.GetRedisKeyCode(reqContent)) + if err != nil && err != redis.Nil { + log.Error("err:%v", err) + g.Code = values.CodeRetry + return false + } + if code != reqCode { + g.Code = values.CodeCodeError + return false + } + return true +} + +// GetRemoteIP 获取玩家真实IP +func (g *Gin) GetRemoteIP() string { + ip := g.Context.GetHeader("X-Real-IP") + if ip != "" { + if !iptool.IsInnerIp(ip) { + return ip + } + } + ip = iptool.RealIP(g.Context.Request) + if strings.Contains(ip, ":") { + ip, _, _ = net.SplitHostPort(ip) + } + return ip +} + +func (g *Gin) QueryUser(req values.CommonLogin) (user *common.PlayerDBInfo, isNew bool) { + u := &common.PlayerDBInfo{ChannelID: g.Channel} + if len(req.Phone) > 0 { + u.Mobile = req.Phone + } else { + u.Openid = &req.OpenID + } + if err := db.Mysql().Get(u); err != nil && err != gorm.ErrRecordNotFound { + g.Code = values.CodeRetry + log.Error("err:%v", err) + return + } + if req.DeviceID == "" { + req.DeviceID = req.OpenID + } + // if req.Gpsadid == "" { + // req.Gpsadid = req.DeviceID + // } + user = u + if user.Id <= 0 { + user.Nick = req.Nick + user.Avatar = req.Avatar + user.AccountType = req.AccountType + user.ChannelID = g.Channel + user.DeviceId = req.DeviceID + user.ADID = req.Adid + user.GPSADID = req.Gpsadid + user.Openid = &req.OpenID + user.AccountName = req.AccountName + user.Pass = req.Pass + if len(req.Phone) > 0 { + user.Mobile = req.Phone + } + ip := g.GetRemoteIP() + if err := call.NewUser(user, ip, req.Share, req.Fbc, req.Fbp, req.UserAgent); err != nil { + g.Code = values.CodeRetry + if err.Error() == "ip" { + g.Code = values.CodeAccountIPLimit + } + return + } + isNew = true + } else { + user.ADID = req.Adid + user.GPSADID = req.Gpsadid + } + return +} diff --git a/modules/web/app/activity.go b/modules/web/app/activity.go new file mode 100644 index 0000000..a4ad412 --- /dev/null +++ b/modules/web/app/activity.go @@ -0,0 +1,100 @@ +package app + +import ( + "server/call" + "server/common" + "server/db" + "server/modules/web/values" + "sort" + "time" +) + +// 从数据库中读取数据 +func (g *Gin) CheckActivityExpire(id int) (pass bool) { + act := call.GetConfigActivityByID(id) + if act == nil { + pass = false + g.Code = values.CodeActivityExpire + return + } + pass = act.IsValid() + if !pass { + g.Code = values.CodeActivityExpire + } + return +} + +func (a *Gin) GetUserTaskStatus() (ret []*values.OneTask) { + tasks := call.GetConfigTask() + for _, v := range tasks { + one := &values.OneTask{ + ID: v.ID, + TaskID: v.TaskID, + Target: v.Target, + Reward: v.Reward, + Kind: v.Kind, + Type: v.Type, + Icon: v.Icon, + Title: common.GetTaskTitle(v), + Action: v.Action, + } + // 非次数任务,需转换目标数值 + if !common.IsNumTaskType(v.Type) { + one.Target /= common.DecimalDigits + } + ret = append(ret, one) + } + if a.UID <= 0 { + return + } + + task := call.GetUserTaskData(a.UID) + now := time.Now().Unix() + for _, v := range ret { + tag := false + for _, k := range task { + if v.TaskID == k.TaskID { + v.Progess = k.Progress + tag = true + break + } + } + if !tag { + if v.Type == common.TaskTypeRegist { // 已注册且注册任务还没标记完成 + db.Mysql().Create(&common.TaskData{UID: a.UID, TaskID: v.TaskID, Progress: v.Target, Time: now}) + v.Progess = v.Target + } else if v.Type == common.TaskTypeDownload { // 已下载直接标记为完成 + if a.DeviceType == common.DeviceTypeWebview || a.DeviceType == common.DeviceTypePWA { + db.Mysql().Create(&common.TaskData{UID: a.UID, TaskID: v.TaskID, Progress: v.Target, Time: now}) + v.Progess = v.Target + } + } + } + + // 非次数任务,需转换目标数值 + if !common.IsNumTaskType(v.Type) && v.Progess > 0 { + v.Progess /= common.DecimalDigits + } + + if v.Progess < 0 { + v.Progess = v.Target + v.Status = 2 + } else if v.Progess >= v.Target { + v.Progess = v.Target + v.Status = 1 + } + } + sort.SliceStable(ret, func(i, j int) bool { + return ret[i].Status > ret[j].Status + }) + // 完成的任务隐藏 + index := 0 + for i := range ret { + if ret[i].Status != 2 { + index = i + break + } + } + ret = ret[index:] + return +} diff --git a/modules/web/app/balance.go b/modules/web/app/balance.go new file mode 100644 index 0000000..035c2b6 --- /dev/null +++ b/modules/web/app/balance.go @@ -0,0 +1,69 @@ +package app + +import ( + "fmt" + "server/call" + "server/common" + "server/db" + "server/modules/web/values" + "server/util" +) + +// CheckWithdrawCondition 判断是否满足退出的配置条件 +func (g *Gin) CheckWithdrawCondition(amount int64, t common.CurrencyType) (ok bool) { + down, up := call.GetConfigWithdrawLimits() + if amount < down || amount > up { + g.Code = values.CodeWithdrawCondition + g.Msg = fmt.Sprintf("O valor inserido deve estar entre R$ %s e R$ %s.", util.FormatNumberBrazil(float64(down)/common.DecimalDigits), util.FormatNumberBrazil(float64(up)/common.DecimalDigits)) + return + } + // 拉取当前所需下注 + bet := call.GetUserNeedBet(g.UID) + if bet > 0 { + g.Code = values.CodeWithdrawConditionBet + g.Msg = "Ainda não completou as apostas necessárias. É necessário fazê-lo antes de efetuar um levantamento." + return + } + vip := call.GetVIP(g.UID) + if vip.Level == 0 { + var limit int64 = 20 + if con := call.GetConfigVIPByLevel(1); con != nil { + limit = con.Exp / common.DecimalDigits + } + g.Code = values.CodeWithdrawConditionVip + g.Msg = fmt.Sprintf("Nível VIP insuficiente Seu valor de recarga deve chegar a R( $ %d para se tornar um VIP1 e poder sacar seu dinheiro.", limit) + return + } + // con := call.GetVipCon(g.UID) + // if con == nil { + // g.Code = values.CodeWithdrawCondition + // return + // } + // if re.DayWithdraw+amount >= con.WithdrawLimit || re.WithdrawCount+1 >= con.WithdrawCount { + // g.Code = values.CodeWithdrawLimit + // return + // } + + ok = true + return +} + +// CanBuyProduct 判断是否满足可以购买该商品 +func (g *Gin) CanBuyProduct(actID, pid int) (can bool) { + if actID == 0 || actID < common.ProductTypeAll { + return true + } + if !g.CheckActivityExpire(actID) { + return + } + switch actID { + case common.ActivityIDRecharge: + one := &common.RechargeInfo{UID: g.UID} + db.Mysql().Get(one) + if common.GetProductPayCount(one.ProductPayCount, pid) >= 1 { + g.Code = values.CodeBuyLimit + return + } + } + return true +} diff --git a/modules/web/app/provider.go b/modules/web/app/provider.go new file mode 100644 index 0000000..d94e0b6 --- /dev/null +++ b/modules/web/app/provider.go @@ -0,0 +1,46 @@ +package app + +import ( + "fmt" + "reflect" + "server/call" + "server/common" + "server/config" + "server/util" + + "github.com/liangdas/mqant/log" +) + +func ParseUser(name string) (string, string) { + first := name[0] + // uid或者token(token全是大写)判断是哪个服 + if (first >= 48 && first <= 57) || (first >= 65 && first <= 90) { + return "a", name + } + return name[0:1], name[1:] +} + +// ShouldRoute 是否需要转发到其他服 +func (g *Gin) ShouldRoute(req interface{}, fieldName string, apiType int) bool { + ref := reflect.ValueOf(req).Elem().FieldByName(fieldName) + name := ref.String() + flag, userName := ParseUser(name) + log.Debug("flag:%v,userName:%v", flag, userName) + if config.GetBase().ServerFlag == flag { + if name != userName { + ref.SetString(userName) + } + return false + } + server := call.GetConfigServerFlag(flag) + if server == nil { + return false + } + apiURL := fmt.Sprintf("%s%s", server.APIURL, g.Context.Request.URL.Path) + if apiType == common.ProviderAPITypeJson { + util.HttpPost(apiURL, req, g.RetData, nil) + } else { + util.HttpPostForm(apiURL, req, g.RetData, nil) + } + return true +} diff --git a/modules/web/app/response.go b/modules/web/app/response.go new file mode 100644 index 0000000..2a3aa3a --- /dev/null +++ b/modules/web/app/response.go @@ -0,0 +1,201 @@ +package app + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "net/url" + "reflect" + "server/call" + "server/common" + "server/db" + "server/modules/web/values" + "server/util" + "strconv" + "strings" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/liangdas/mqant/log" + "gorm.io/gorm" +) + +type Gin struct { + R + RetData interface{} // 定制返回内容 + TmpData interface{} + TmpData2 interface{} + Context *gin.Context + UID int + Token string + Lang string + Referrer string + // Share int + Channel int + UUID string + WhiteIps []string + Prefix string + DeviceType int +} + +type R struct { + Data interface{} `json:"data"` + Msg string `json:"msg"` + Code int `json:"code"` +} + +func NewApp(c *gin.Context) *Gin { + g := &Gin{Context: c, R: R{Code: values.CodeOK}} + _uid, _ := c.Get("uid") + if _uid != nil { + g.UID = _uid.(int) + } + g.Token = c.GetHeader("token") + g.Lang = common.CheckLang(c.GetHeader("lang")) + g.Referrer = c.GetHeader("referrer") + channel := c.GetHeader("channel") + if len(channel) > 0 { + g.Channel, _ = strconv.Atoi(channel) + } + if g.Channel == 0 { + u, _ := url.Parse(c.Request.Referer()) + if u != nil && len(u.Host) > 0 { + index := strings.Index(u.Host, ".") + if index > 0 { + g.Prefix = u.Host[:index] + } + channel := call.GetChannelByURL(u.Host[index+1:]) + if channel != nil { + g.Channel = channel.ChannelID + } + } + } + g.UUID = c.GetHeader("uuid") + deviceType := c.GetHeader("platform") + g.DeviceType, _ = strconv.Atoi(deviceType) + return g +} + +// Response setting gin.JSON(不加密) +func (g *Gin) ResponseB() { + ret := g.RetData + if ret == nil { + ret = g.R + } + g.Context.JSON(http.StatusOK, ret) +} + +// Response setting gin.JSON +func (g *Gin) Response() { + if g.R.Code == values.CodeRetry { + g.R.Msg = "inner error" + } else if g.R.Code == values.CodeToken { + g.R.Msg = "login expired" + } + jsonData, _ := json.Marshal(g.R) + g.Context.Data(http.StatusOK, "text", util.AesEncrypt(jsonData)) +} + +// S 解析请求(非加密) +func (g *Gin) SB(one interface{}) (pass bool) { + if err := g.Context.ShouldBind(one); err != nil { + log.Error("err:%v", err) + return + } + pass = true + return +} + +// S 解析请求(加密) +func (g *Gin) S(one interface{}) (pass bool) { + data, err := ioutil.ReadAll(g.Context.Request.Body) + if err != nil { + g.Code = values.CodeRetry + log.Error("err:%v", err) + return + } + jsonData, err := util.AesDecrypt(data) + if err != nil { + g.Code = values.CodeRetry + log.Error("err:%v", err) + return + } + err = binding.JSON.BindBody(jsonData, one) + // err = json.Unmarshal(jsonData, one) + if err != nil { + g.Code = values.CodeRetry + log.Error("err:%v", err) + return + } + pass = true + return +} + +// U 统一json解析方法 +func (g *Gin) U(src []byte, tar interface{}) (pass bool) { + err := json.Unmarshal(src, tar) + if err != nil { + log.Error("%v err:%v", reflect.TypeOf(tar), err) + g.R.Code = values.CodeRetry + g.R.Msg = err.Error() + return + } + pass = true + return +} + +// 从数据库中读取活动数据(不存在时创建) +func (g *Gin) MGetActivity(tar interface{}) (pass bool) { + err := db.Mysql().Get(tar) + if err == gorm.ErrRecordNotFound { + db.Mysql().Create(tar) + } else if err != nil { + log.Error("err:%v", err) + g.R.Code = values.CodeRetry + g.R.Msg = err.Error() + return + } + pass = true + return +} + +// 从数据库中读取数据 +func (g *Gin) MGet(tar interface{}) (pass bool) { + err := db.Mysql().Get(tar) + if err != nil && err != gorm.ErrRecordNotFound { + log.Error("err:%v", err) + g.R.Code = values.CodeRetry + g.R.Msg = err.Error() + return + } + pass = true + return +} + +// MCommit 提交事务 +func (g *Gin) MCommit(tx *gorm.DB) { + if g.Code == values.CodeOK { + if err := tx.Commit().Error; err != nil { + tx.Rollback() + return + } + } else { + tx.Rollback() + return + } +} + +// GetUID 可过滤的url带token时获取uid +func (g *Gin) GetUID() { + if g.UID > 0 { + return + } + token := g.Context.GetHeader("token") + if token == "" { + return + } + uid, _ := db.Redis().GetInt(common.GetRedisKeyToken(token)) + if uid > 0 { + g.UID = uid + } +} diff --git a/modules/web/config.go b/modules/web/config.go new file mode 100644 index 0000000..d3c567b --- /dev/null +++ b/modules/web/config.go @@ -0,0 +1,12 @@ +package web + +import ( + "server/call" + "server/pb" +) + +func loadConfig() error { + c := map[int][]func(*pb.ReloadGameConfig) error{} + call.LoadConfigs(c) + return nil +} diff --git a/modules/web/handler/account.go b/modules/web/handler/account.go new file mode 100644 index 0000000..2ebed57 --- /dev/null +++ b/modules/web/handler/account.go @@ -0,0 +1,642 @@ +package handler + +import ( + "server/call" + "server/common" + "server/config" + "server/db" + "server/modules/web/app" + "server/modules/web/values" + "server/util" + "time" + + "github.com/liangdas/mqant/log" + + "github.com/gin-gonic/gin" +) + +func onLogin(user *common.PlayerDBInfo, a *app.Gin, isNew bool) { + if user.Status != common.AccountStatusNormal || call.BlackListAndKick(user.Id, &common.BlackList{DeviceID: user.DeviceId, Phone: user.Mobile}) { + a.Code = values.CodeAccountLimit + log.Debug("uid:%v limit login", user.Id) + return + } + channel := call.GetChannelByID(user.ChannelID) + gateURL := "" + if config.GetBase().Release && channel != nil && channel.URL != "" { + // gateURL = "gate." + channel.URL + config.GetConfig().Gate.WSPort + gateURL = "gate." + channel.URL + } + if gateURL == "" { + next, err := call.GetCaller().GetApp().Options().Selector.Select("gate") + if err != nil { + a.Code = values.CodeRetry + log.Error("err:%v", err) + return + } + n, err := next() + if err != nil { + a.Code = values.CodeRetry + log.Error("err:%v", err) + return + } + gateURL = n.Metadata["addr"] + } + // log.Debug("scheme:%v", a.Context.GetHeader("X-Forwarded-Proto")) + // if a.Context.Request.URL.Scheme == "https" { + gateURL = "wss://" + gateURL + // } else { + // gateURL = "ws://" + gateURL + // } + + uid := user.Id + token := "" + p := db.Redis().GetUserData(uid) + if p == nil { + token = util.RandomToken(uid) + if err := db.Redis().SetToken(uid, token); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + user.Token = token + } else { + token = p.Token + user.Token = p.Token + db.Redis().AddUserExpire(uid, token) + } + user.Platform = a.DeviceType + + if err := db.Redis().UpdateUserData(*user); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + + // ps := new(common.PlayerStatus) + // if err := db.Redis().HGetAll(common.GetRedisKeyPlayerStatus(uid), ps); err != nil && err != redis.Nil { + // log.Error("err:%v", err) + // a.Code = values.CodeRetry + // return + // } + resp := values.LoginResp{GateAddr: gateURL, UID: uid, Token: token} + // cid := user.ChannelID + a.Data = resp + ip := a.GetRemoteIP() + cid := user.ChannelID + birth := user.Birth + deviceType := user.Platform + country := user.Country + util.Go(func() { + update := map[string]interface{}{"ip": ip, "platform": deviceType, "last_login": time.Now().Unix()} + if country == "" { + update["country"] = call.GetCountry(ip) + } + if err := db.Mysql().Update(&common.PlayerDBInfo{Id: uid}, update); err != nil { + log.Error("err:%v", err) + } + call.InsertLoginRecord(uid, cid, ip, birth, deviceType) + + // if db.Mysql().CountTable(common.PlayerRechargeTableName, fmt.Sprintf("uid = %d", uid)) == 0 { + // db.Mysql().C().Table(common.PlayerRechargeTableName).Create(&common.PlayerCurrency{UID: uid, ChannelID: cid}) + // } + }) + log.Debug("resp:%+v", resp) +} + +func GuestLogin(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.GuestLoginReq) + if !a.S(req) { + return + } + log.Debug("data:%+v guest login", *req) + openID := common.GetAccountPre(common.AccountTypeGuest) + a.UUID + user, isNew := a.QueryUser(values.CommonLogin{AccountType: common.AccountTypeGuest, OpenID: openID, Nick: req.Nick, DeviceID: a.UUID, + Adid: req.Adid, Gpsadid: req.GPSAdid, Share: req.Share, + Fbc: a.Context.GetHeader("fbc"), Fbp: a.Context.GetHeader("fbp"), UserAgent: a.Context.GetHeader("User-Agent")}) + if a.Code != values.CodeOK { + return + } + onLogin(user, a, isNew) +} + +func GetPhoneCode(c *gin.Context) { + a := app.NewApp(c) + defer func() { + if a.Code == values.CodePhoneCodeBusy { + a.Msg = "Too many requests,please try later." + } else if a.Code != values.CodeOK { + a.Msg = "OTP error" + } + a.Response() + }() + req := new(values.PhoneCodeReq) + if !a.S(req) { + return + } + cid := a.Channel + if cid == 0 { + a.Code = values.CodeParam + return + } + if call.IsBlackListPlayer(&common.BlackList{Phone: req.Phone}) { + a.Code = values.CodeRetry + return + } + log.Debug("GetPhoneCode:%+v", *req) + code := values.RandomPhoneCode() + if !db.Redis().SetNX(common.GetRedisKeyCodeCD(req.Phone), code, common.RedisExpireCodeCD) { + a.Code = values.CodePhoneCodeBusy + return + } + if err := db.Redis().SetData(common.GetRedisKeyCode(req.Phone), code, common.RedisExpireCode); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + if config.GetBase().Release { + if err := values.APISmsReq(req.Phone, code); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + } else { + a.Data = code + } +} + +func VerifyCode(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.PhoneCodeLoginReq) + if !a.S(req) { + return + } + log.Debug("VerifyCode %+v", *req) + if !a.VerifyCode(req.Phone, req.Code) { + return + } +} + +func PhoneRegist(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.PhoneRegistReq) + if !a.S(req) { + return + } + log.Debug("PhoneRegistReq login %+v", *req) + if a.Channel == 0 { + a.Code = values.CodeParam + return + } + if db.Mysql().Exist(&common.PlayerDBInfo{ChannelID: a.Channel, Mobile: req.Phone}) { + a.Code = values.CodeAccountAlreadyExist + a.Msg = "The phone number is already linked to another customer." + return + } + openID := common.GetAccountPre(common.AccountTypePhone) + req.Phone + user, isNew := a.QueryUser(values.CommonLogin{AccountType: common.AccountTypePhone, OpenID: openID, DeviceID: a.UUID, + Adid: req.Adid, Gpsadid: req.GPSAdid, Phone: req.Phone, Share: req.Share, Pass: req.Pass, + Fbc: a.Context.GetHeader("fbc"), Fbp: a.Context.GetHeader("fbp"), UserAgent: a.Context.GetHeader("User-Agent")}) + if a.Code != values.CodeOK { + return + } + onLogin(user, a, isNew) +} + +func PhoneLogin(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.PhoneLoginReq) + if !a.S(req) { + return + } + log.Debug("PhoneLoginReq login %+v", *req) + if a.Channel == 0 { + a.Code = values.CodeParam + return + } + user := &common.PlayerDBInfo{ChannelID: a.Channel, Mobile: req.Phone} + db.Mysql().Get(user) + if user.Id == 0 { + a.Code = values.CodeAccountNotExist + a.Msg = "The phone number does not exist." + return + } + if req.Pass != user.Pass { + a.Code = values.CodeAccountPass + a.Msg = "Incorrect password." + return + } + onLogin(user, a, false) +} + +func PhoneResetPass(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.PhoneResetPassReq) + if !a.S(req) { + return + } + log.Debug("PhoneResetPassReq %+v", *req) + if !db.Mysql().Exist(&common.PlayerDBInfo{ChannelID: a.Channel, Mobile: req.Phone}) { + a.Code = values.CodeParam + a.Msg = "phone not exist" + return + } + if !a.VerifyCode(req.Phone, req.Code) { + return + } + if a.Channel == 0 { + a.Code = values.CodeParam + return + } + if err := db.Mysql().Update(&common.PlayerDBInfo{ChannelID: a.Channel, Mobile: req.Phone}, map[string]interface{}{"pass": req.NewPass}); err != nil { + a.Code = values.CodeRetry + return + } +} + +func PhoneCodeLogin(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.PhoneCodeLoginReq) + if !a.S(req) { + return + } + log.Debug("phoneCode login %+v", *req) + if !a.VerifyCode(req.Phone, req.Code) { + return + } + if a.Channel == 0 { + a.Code = values.CodeParam + return + } + openID := common.GetAccountPre(common.AccountTypePhone) + req.Phone + user, isNew := a.QueryUser(values.CommonLogin{AccountType: common.AccountTypePhone, OpenID: openID, DeviceID: a.UUID, + Adid: req.Adid, Gpsadid: req.GPSAdid, Phone: req.Phone, Share: req.Share, + Fbc: a.Context.GetHeader("fbc"), Fbp: a.Context.GetHeader("fbp"), UserAgent: a.Context.GetHeader("User-Agent")}) + if a.Code != values.CodeOK { + return + } + onLogin(user, a, isNew) +} + +func BindingAccount(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.PhoneCodeLoginReq) + if !a.S(req) { + return + } + log.Debug("BindingAccount %+v", *req) + if !a.VerifyCode(req.Phone, req.Code) { + return + } + cid := a.Channel + if cid == 0 { + a.Code = values.CodeParam + return + } + p := &common.PlayerDBInfo{Mobile: req.Phone} + db.Mysql().Get(p) + if p.Id > 0 { + a.Code = values.CodePhoneAlreadyBind + return + } + ret, _ := call.GetUserXInfo(a.UID, "mobile") + if ret.Mobile != "" { + a.Code = values.CodeAccountAlreadyBind + return + } + if err := call.UpdateUserXInfo(&common.PlayerDBInfo{Id: a.UID}, map[string]interface{}{"mobile": req.Phone}); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + reward := call.GetConfigPlatform().BindPhoneGift + if reward > 0 { + call.UpdateCurrencyPro(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: a.UID, + Event: common.CurrencyEventBindPhone, + Type: common.CurrencyBrazil, + Value: reward, + NeedBet: call.GetConfigCurrencyResourceNeedBet(common.CurrencyResourceBonus, reward), + }, + }) + } +} + +func GPLogin(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.GPLoginReq) + if !a.S(req) { + return + } + openID := config.GetBase().Google.Prefix + req.ID + user, isNew := a.QueryUser(values.CommonLogin{AccountType: common.AccountTypeGoogleplay, OpenID: openID, DeviceID: a.UUID, + Nick: req.Name, Avatar: req.Avatar, Adid: req.Adid, Gpsadid: req.GPSAdid, Share: req.Share, + Fbc: a.Context.GetHeader("fbc"), Fbp: a.Context.GetHeader("fbp"), UserAgent: a.Context.GetHeader("User-Agent")}) + if a.Code != values.CodeOK { + return + } + onLogin(user, a, isNew) +} + +func FBLogin(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.FBLoginReq) + if !a.S(req) { + return + } + openID := config.GetBase().FaceBook.Prefix + req.ID + user, isNew := a.QueryUser(values.CommonLogin{AccountType: common.AccountTypeFacebook, OpenID: openID, DeviceID: a.UUID, + Nick: req.Name, Avatar: req.Avatar, Adid: req.Adid, Gpsadid: req.GPSAdid, Share: req.Share, + Fbc: a.Context.GetHeader("fbc"), Fbp: a.Context.GetHeader("fbp"), UserAgent: a.Context.GetHeader("User-Agent")}) + if a.Code != values.CodeOK { + return + } + onLogin(user, a, isNew) +} + +func TokenLogin(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.TokenLoginReq) + if !a.S(req) { + return + } + uid, _ := db.Redis().GetInt(common.GetRedisKeyToken(req.Token)) + if uid == 0 { + a.Code = values.CodeToken + return + } + user := new(common.PlayerDBInfo) + user.Id = uid + if err := db.Mysql().Get(user); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + user.ADID = req.Adid + user.GPSADID = req.GPSAdid + onLogin(user, a, false) +} + +func GetEmailCode(c *gin.Context) { + a := app.NewApp(c) + defer func() { + if a.Code == values.CodeReqBusy { + a.Msg = "Too many requests,please try later." + } else if a.Code != values.CodeOK { + a.Msg = "Email code error" + } + a.Response() + }() + req := new(values.EmailCodeReq) + if !a.S(req) { + return + } + cid := a.Channel + if cid == 0 { + a.Code = values.CodeParam + return + } + if call.IsBlackListPlayer(&common.BlackList{Email: req.Email}) { + a.Code = values.CodeRetry + return + } + log.Debug("GetEmailCode:%+v", *req) + code := values.RandomPhoneCode() + if !db.Redis().SetNX(common.GetRedisKeyCodeCD(req.Email), code, common.RedisExpireCodeCD) { + a.Code = values.CodePhoneCodeBusy + return + } + if err := db.Redis().SetData(common.GetRedisKeyCode(req.Email), code, common.RedisExpireCode); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + if !config.GetBase().Release { + a.Data = code + } else { + values.BukaMailRequest(req.Email, code, a.Lang) + } +} + +func EmailRegist(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.EmailRegistReq) + if !a.S(req) { + return + } + log.Debug("emailCode regist %+v", *req) + if !a.VerifyCode(req.Email, req.Code) { + return + } + if a.Channel == 0 { + a.Code = values.CodeParam + return + } + openID := common.GetAccountPre(common.AccountTypeEmail) + req.Email + // if db.Mysql().Exist(&common.PlayerDBInfo{Openid: &openID}) { + // a.Code = values.CodeAccountAlreadyExist + // a.Msg = "email registered" + // return + // } + + user, isNew := a.QueryUser(values.CommonLogin{AccountType: common.AccountTypeEmail, OpenID: openID, DeviceID: a.UUID, + Adid: req.Adid, Gpsadid: req.GPSAdid, AccountName: req.Email, Pass: req.Pass, Share: req.Share, + Fbc: a.Context.GetHeader("fbc"), Fbp: a.Context.GetHeader("fbp"), UserAgent: a.Context.GetHeader("User-Agent")}) + if a.Code != values.CodeOK { + return + } + + if !isNew { + a.Code = values.CodeAccountAlreadyExist + a.Msg = "email registered" + return + } + + onLogin(user, a, isNew) +} + +func EmailLogin(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.EmailLoginReq) + if !a.S(req) { + return + } + log.Debug("emailCode login %+v", *req) + if a.Channel == 0 { + a.Code = values.CodeParam + return + } + user := &common.PlayerDBInfo{ChannelID: a.Channel, AccountName: req.Email} + db.Mysql().Get(user) + if user.Id == 0 { + a.Code = values.CodeAccountNotExist + return + } + if req.Pass != user.Pass { + a.Code = values.CodeAccountPass + return + } + onLogin(user, a, false) +} + +func EmailResetPass(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.EmailResetPassReq) + if !a.S(req) { + return + } + log.Debug("EmailResetPass %+v", *req) + if !db.Mysql().Exist(&common.PlayerDBInfo{ChannelID: a.Channel, AccountName: req.Email}) { + a.Code = values.CodeParam + a.Msg = "Email not exist." + return + } + if !a.VerifyCode(req.Email, req.Code) { + return + } + if a.Channel == 0 { + a.Code = values.CodeParam + return + } + if err := db.Mysql().Update(&common.PlayerDBInfo{ChannelID: a.Channel, AccountName: req.Email}, map[string]interface{}{"pass": req.NewPass}); err != nil { + a.Code = values.CodeRetry + return + } +} + +func AccountRegist(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.AccountRegistReq) + if !a.S(req) { + return + } + log.Debug("AccountRegistReq %+v", *req) + if a.Channel == 0 { + a.Code = values.CodeParam + return + } + openID := common.GetAccountPre(common.AccountTypeAccount) + req.Name + // if db.Mysql().Exist(&common.PlayerDBInfo{Openid: &openID}) { + // a.Code = values.CodeAccountAlreadyExist + // a.Msg = "email registered" + // return + // } + + user, isNew := a.QueryUser(values.CommonLogin{AccountType: common.AccountTypeAccount, OpenID: openID, DeviceID: a.UUID, + AccountName: req.Name, Pass: req.Pass, Share: req.Share, + Fbc: a.Context.GetHeader("fbc"), Fbp: a.Context.GetHeader("fbp"), UserAgent: a.Context.GetHeader("User-Agent")}) + if a.Code != values.CodeOK { + return + } + + if !isNew { + a.Code = values.CodeAccountAlreadyExist + a.Msg = "Account registered." + return + } + + onLogin(user, a, isNew) +} + +func AccountLogin(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.AccountLoginReq) + if !a.S(req) { + return + } + log.Debug("AccountLogin %+v", *req) + if a.Channel == 0 { + a.Code = values.CodeParam + return + } + user := &common.PlayerDBInfo{ChannelID: a.Channel, AccountName: req.Name} + db.Mysql().Get(user) + if user.Id == 0 { + a.Code = values.CodeAccountNotExist + return + } + if req.Pass != user.Pass { + a.Code = values.CodeAccountPass + return + } + onLogin(user, a, false) +} + +func AccountResetPass(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.AccountResetPassReq) + if !a.S(req) { + return + } + log.Debug("EmailResetPass %+v", *req) + p := &common.PlayerDBInfo{ChannelID: a.Channel, AccountName: req.Name} + db.Mysql().Get(p) + if p.Id == 0 { + a.Code = values.CodeParam + a.Msg = "Account not exist." + return + } + if p.Pass != req.OldPass { + a.Code = values.CodeParam + a.Msg = "Password invalid." + return + } + if a.Channel == 0 { + a.Code = values.CodeParam + return + } + if err := db.Mysql().Update(&common.PlayerDBInfo{ChannelID: a.Channel, AccountName: req.Name}, map[string]interface{}{"pass": req.NewPass}); err != nil { + a.Code = values.CodeRetry + return + } +} diff --git a/modules/web/handler/activity.go b/modules/web/handler/activity.go new file mode 100644 index 0000000..c22a28e --- /dev/null +++ b/modules/web/handler/activity.go @@ -0,0 +1,1548 @@ +package handler + +import ( + "fmt" + "math/rand" + "server/call" + "server/common" + "server/config" + "server/db" + "server/modules/web/app" + "server/modules/web/values" + "server/util" + "time" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" + "github.com/olivere/elastic/v7" + "gorm.io/gorm" +) + +func GetPromotions(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + ret := &values.GetPromotionsResp{} + a.Data = ret + a.GetUID() + ret.ActivityList = call.GetConfigActivityActiveAll(a.UID) + ret.TaskList = a.GetUserTaskStatus() +} + +func UploadActivityData(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.UploadActivityReq) + if !a.S(req) { + return + } + call.UploadActivityData(a.UID, req.ActivityID, common.ActivityDataClick, 0) +} + +func GetAllActivity(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + ret := values.GetAllActivityResp{ + List: call.GetConfigActivityActiveAll(a.UID), + } + a.Data = ret +} + +func GetActivityAppSpinInfo(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + if !a.CheckActivityExpire(common.ActivityIDAppSpin) { + return + } + resp := &values.ActivityAppSpinInfoResp{} + a.Data = resp + resp.List = call.GetConfigAppSpin() +} + +func DrawActivityAppSpin(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + if !a.CheckActivityExpire(common.ActivityIDAppSpin) { + return + } + pd := call.GetPlayerData(a.UID) + if pd.LastAppSpinDraw > 0 { + a.Code = values.CodeRetry + return + } + total := 0 + list := call.GetConfigAppSpin() + for _, v := range list { + total += v.Weight + } + weight := rand.Intn(total) + per := 0 + var win *common.ConfigAppSpin + for _, v := range list { + per += v.Weight + if weight < per { + win = v + break + } + } + if win == nil { + a.Code = values.CodeRetry + return + } + rows, err := db.Mysql().UpdateResW(&common.PlayerData{}, map[string]interface{}{"last_app_spin_draw": time.Now().Unix()}, fmt.Sprintf("uid = %v and last_app_spin_draw = 0", a.UID)) + if err != nil || rows == 0 { + log.Error("err:%v", err) + return + } + call.UpdateCurrencyPro(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: a.UID, + ChannelID: a.Channel, + Type: win.CurrencyType, + Value: win.Amount, + Event: common.CurrencyEventActivityAppSpin, + NeedBet: call.GetConfigCurrencyResourceNeedBet(common.CurrencyResourceBonus, win.Amount), + }, + }) + call.UploadActivityData(a.UID, common.ActivityIDAppSpin, common.ActivityDataJoin, win.Amount) + a.Data = values.ActivityAppSpinDrawResp{ID: win.ID} +} + +func ActivityPddInfo(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + if !a.CheckActivityExpire(common.ActivityIDPDD) { + return + } + con := call.GetConfigActivityPdd() + if con == nil { + a.Code = values.CodeRetry + return + } + resp := &values.ActivityPddInfoResp{WithdrawAmount: con.WithdrawAmount, Spin: 1} + a.GetUID() + pddData := &common.PddData{UID: a.UID} + if pddData.UID > 0 { + pddData = call.GetAcitivityPddData(pddData.UID) + if pddData == nil { + a.Code = values.CodeRetry + return + } + resp.Spin = pddData.Spin + resp.Amount = pddData.Amount + channel := call.GetChannelByID(a.Channel) + if channel != nil { + resp.ShareLink = channel.URL + "?code=" + call.GetShareInfo(a.UID).Share + } + resp.Expire = con.Expire*60 + pddData.Time - time.Now().Unix() + resp.NewPddShare = call.HasNewAcitivityPddShare(a.UID) + } + + list := call.GetConfigActivityPddSpinByAmount(pddData.Amount) + for _, v := range list { + resp.Items = append(resp.Items, values.OnePddSpinItem{ + ID: v.ID, + Type: v.Type, + Amount: v.Amount, + Sort: v.Sort, + }) + } + a.Data = resp +} + +func ActivityPddSpin(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + if !a.CheckActivityExpire(common.ActivityIDPDD) { + return + } + con := call.GetConfigActivityPdd() + if con == nil { + a.Code = values.CodeRetry + return + } + pddData := call.GetAcitivityPddData(a.UID) + if pddData == nil { + a.Code = values.CodeRetry + return + } + now := time.Now().Unix() + todaySpin := util.IsSameDayTimeStamp(now, pddData.FreeSpinTime) + if pddData.Spin <= 0 { + a.Code = values.CodeParam + return + } + if pddData.Amount >= con.WithdrawAmount { + a.Code = values.CodeParam + a.Msg = "You can withdraw now." + return + } + items := call.GetConfigActivityPddSpinByAmount(pddData.Amount) + var bingo *common.ConfigActivityPddSpin + var amount int64 + if pddData.Amount == 0 { // 第一次转 + for _, v := range items { + if v.Type == common.ActivityPddItemTypeRandomCash { + bingo = v + break + } + } + + if bingo == nil { + a.Code = values.CodeRetry + return + } + + diff := con.AmountUp - con.AmountDown + if diff > 0 { + amount = rand.Int63n(con.AmountUp-con.AmountDown) + con.AmountDown + } + } else { + totalWeight := 0 + for _, v := range items { + totalWeight += v.Weight + } + if totalWeight == 0 { + a.Code = values.CodeRetry + return + } + + thisWeight := 0 + rans := rand.Intn(totalWeight) + for _, v := range items { + thisWeight += v.Weight + if rans < thisWeight { + bingo = v + break + } + } + if bingo == nil { + a.Code = values.CodeRetry + return + } + + amount = bingo.Amount + if bingo.Type == common.ActivityPddItemTypeRandomCash { + diff := bingo.CashUp - bingo.CashDown + if diff > 0 { + amount = rand.Int63n(diff) + bingo.CashDown + } + } + } + + if amount == 0 { + a.Code = values.CodeRetry + return + } + + update := map[string]interface{}{} + sql := "" + // 今天旋转过则扣除已有旋转次数 + if todaySpin { + update["spin"] = gorm.Expr("spin - 1") + sql = fmt.Sprintf("uid = %d and spin >= 1 and amount = %d", a.UID, pddData.Amount) + } else { + update["free_spin_time"] = now + sql = fmt.Sprintf("uid = %d and free_spin_time = %d ", a.UID, pddData.FreeSpinTime) + } + + // ok + switch bingo.Type { + case common.ActivityPddItemTypeFinish: + update["amount"] = con.WithdrawAmount + case common.ActivityPddItemTypeCash: + if pddData.Amount+amount > con.WithdrawAmount { + update["amount"] = con.WithdrawAmount + } else { + update["amount"] = gorm.Expr("amount + ?", amount) + } + case common.ActivityPddItemTypeRandomCash: + if pddData.Amount+amount > con.WithdrawAmount { + update["amount"] = con.WithdrawAmount + } else { + update["amount"] = gorm.Expr("amount + ?", amount) + } + } + + rows, err := db.Mysql().UpdateResW(&common.PddData{}, update, sql) + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + if rows == 0 { + a.Code = values.CodeRetry + return + } + resp := &values.ActivityPddSpinResp{ + ID: bingo.Sort, + Amount: amount, + } + a.Data = resp +} + +func ActivityPddWithdraw(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + if !a.CheckActivityExpire(common.ActivityIDPDD) { + return + } + con := call.GetConfigActivityPdd() + if con == nil { + a.Code = values.CodeRetry + return + } + pddData := call.GetAcitivityPddData(a.UID) + if pddData == nil { + a.Code = values.CodeRetry + return + } + if pddData.Amount < con.WithdrawAmount { + a.Code = values.CodeParam + return + } + // 转走并重置玩家pdd活动 + spinCount := 1 + if !config.GetBase().Release { + spinCount = 100 + } + rows, err := db.Mysql().UpdateResW(&common.PddData{}, map[string]interface{}{"amount": 0, "spin": spinCount, "time": time.Now().Unix(), "free_spin_time": 0}, + fmt.Sprintf("uid = %d and amount >= %d", a.UID, con.WithdrawAmount)) + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + if rows == 0 { + a.Code = values.CodeRetry + return + } + call.UpdateCurrencyPro(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: a.UID, + Value: con.WithdrawAmount, + Type: common.CurrencyBrazil, + Event: common.CurrencyEventActivityPdd, + NeedBet: call.GetConfigCurrencyResourceNeedBet(common.CurrencyResourceBonus, con.WithdrawAmount), + }, + }) + call.UploadActivityData(a.UID, common.ActivityIDPDD, common.ActivityDataJoin, con.WithdrawAmount) + a.Data = values.ActivityPddWithdrawResp{ExpireTime: con.Expire * 60} +} + +func ActivityPddNewReference(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + if !a.CheckActivityExpire(common.ActivityIDPDD) { + return + } + con := call.GetConfigActivityPdd() + if con == nil { + a.Code = values.CodeRetry + return + } + resp := &values.ActivityPddNewReferenceResp{} + a.Data = resp + pddData := call.GetAcitivityPddData(a.UID) + if pddData == nil { + return + } + list := []common.ESPddRecord{} + q := elastic.NewBoolQuery() + t := pddData.Time + if pddData.NewRecordTime > 0 { + t = pddData.NewRecordTime + } + q.Filter(elastic.NewTermQuery("Referer", a.UID)) + q.Filter(elastic.NewRangeQuery("Time").Gte(t)) + db.ES().QueryList(common.ESIndexBackPddRecord, 0, 5000, q, &list, "Time", false) + for _, v := range list { + resp.List = append(resp.List, values.OnePddRecord{ + UID: v.UID, + Avatar: v.Avatar, + Nick: v.Nick, + Time: v.Time, + SpinCount: 1, + }) + } + resp.NewCount = len(list) + resp.NewSpinCount = len(list) + resp.SpinLeft = pddData.Spin + db.Mysql().Update(&common.PddData{UID: a.UID}, map[string]interface{}{"new_record_time": time.Now().Unix()}) +} + +func ActivityPddReference(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + if !a.CheckActivityExpire(common.ActivityIDPDD) { + return + } + req := new(values.ActivityPddReferenceReq) + if !a.S(req) { + return + } + resp := &values.ActivityPddReferenceResp{} + a.Data = resp + pddData := call.GetAcitivityPddData(a.UID) + if pddData == nil { + return + } + list := []common.ESPddRecord{} + q := elastic.NewBoolQuery() + q.Filter(elastic.NewRangeQuery("Time").Gte(pddData.Time)) + db.ES().QueryList(common.ESIndexBackPddRecord, req.Page, req.Num, q, &list, "Time", false) + for _, v := range list { + resp.List = append(resp.List, values.OnePddRecord{ + UID: v.UID, + Avatar: v.Avatar, + Nick: v.Nick, + Time: v.Time, + SpinCount: 1, + }) + } +} + +func ActivityFreeSpinInfo(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + if !a.CheckActivityExpire(common.ActivityIDFreeSpin) { + return + } + con := call.GetConfigActivityFreeSpin() + if con == nil { + a.Code = values.CodeRetry + return + } + resp := &values.ActivityFreeSpinInfoResp{ + List: con, + NewReward: config.GetConfig().Web.FreeSpinFirst, + } + a.Data = resp + a.GetUID() + if a.UID > 0 { + freeSpin := call.GetUserFreeSpinData(a.UID) + if freeSpin.LastSpin == 0 { + now := time.Now().Unix() + p, _ := call.GetUserXInfo(a.UID, "birth") + data := &common.ActivityFreeSpinData{UID: a.UID} + if !util.IsSameDayTimeStamp(now, p.Birth) { + resp.Count = 1 + } else { + data.LastSpin = now + } + db.Mysql().Create(data) + } else if !util.IsSameDayTimeStamp(time.Now().Unix(), freeSpin.LastSpin) { + resp.Count = 1 + } + } else { + resp.Count = 1 + } +} + +func ActivityFreeSpinDraw(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + if !a.CheckActivityExpire(common.ActivityIDFreeSpin) { + return + } + freeSpin := call.GetUserFreeSpinData(a.UID) + now := time.Now().Unix() + if config.GetBase().Release { + if util.IsSameDayTimeStamp(now, freeSpin.LastSpin) { + a.Code = values.CodeRetry + return + } + } + resp := &values.ActivityFreeSpinDrawResp{} + a.Data = resp + + // 首次旋转,给予固定奖励 + // if freeSpin.LastSpin == 0 { + // items := call.GetConfigActivityFreeSpinByType(common.ActivityFreeSpinItemRandomCash) + // if len(items) == 0 { + // a.Code = values.CodeRetry + // return + // } + // err := db.Mysql().Create(&common.ActivityFreeSpinData{UID: a.UID, LastSpin: time.Now().Unix()}) + // if err != nil { + // a.Code = values.CodeRetry + // return + // } + // resp.Reward = config.GetConfig().Web.FreeSpinFirst + // if resp.Reward == 0 { + // resp.Reward = 5 * common.DecimalDigits + // } + // _, err = call.UpdateCurrencyPro(&common.UpdateCurrency{ + // CurrencyBalance: &common.CurrencyBalance{ + // Type: common.CurrencyBrazil, + // UID: a.UID, + // Event: common.CurrencyEventActivityFreeSpin, + // Value: resp.Reward, + // ChannelID: a.Channel, + // NeedBet: call.GetConfigCurrencyResourceNeedBet(common.CurrencyResourceBonus, resp.Reward), + // }, + // }) + // if err != nil { + // a.Code = values.CodeRetry + // return + // } + // ran := rand.Intn(len(items)) + // resp.ID = items[ran].ID + // call.UploadActivityData(a.UID, common.ActivityIDFreeSpin, common.ActivityDataJoin, resp.Reward) + // return + // } + + con := call.GetConfigActivityFreeSpin() + if con == nil { + a.Code = values.CodeRetry + return + } + total := 0 + for _, v := range con { + total += v.Weight + } + ran := rand.Intn(total) + rans := 0 + for _, v := range con { + rans += v.Weight + if ran < rans { + resp.ID = v.ID + resp.Reward = v.Amount + if v.Type == common.ActivityFreeSpinItemRandomCash { + resp.Reward = rand.Int63n(v.CashUp-v.CashDown) + v.CashDown + } + rows, err := db.Mysql().UpdateRes(&common.ActivityFreeSpinData{UID: a.UID, LastSpin: freeSpin.LastSpin}, map[string]interface{}{"last_spin": time.Now().Unix()}) + if rows == 0 || err != nil { + a.Code = values.CodeRetry + return + } + if v.Type == common.ActivityFreeSpinItemNone { + return + } + _, err = call.UpdateCurrencyPro(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + Type: common.CurrencyBrazil, + UID: a.UID, + Event: common.CurrencyEventActivityFreeSpin, + Value: resp.Reward, + ChannelID: a.Channel, + NeedBet: call.GetConfigCurrencyResourceNeedBet(common.CurrencyResourceBonus, resp.Reward), + }, + }) + if err != nil { + a.Code = values.CodeRetry + return + } + break + } + } + call.UploadActivityData(a.UID, common.ActivityIDFreeSpin, common.ActivityDataJoin, resp.Reward) +} + +func ActivityFirstRechargeBackInfo(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + if !a.CheckActivityExpire(common.ActivityIDFirstRechargeBack) { + return + } + resp := &values.ActivityFirstRechargeBackInfoResp{} + a.Data = resp + data := call.GetUserFirstRechargeBackData(a.UID) + diff := time.Now().Unix() - data.RechargeTime + log.Debug("ActivityFirstRechargeBackInfo:%+v", data) + if data.RechargeTime == 0 { + resp.CanRecharge = true + } else { + if diff > common.ActivityFirstRechargeBackTime*2 { + a.Code = values.CodeActivityExpire + return + } + resp.CanRecharge = diff < common.ActivityFirstRechargeBackTime + } + resp.Recharge = data.Amount + if data.Amount < call.GetConfigActivityFirstRechargeBack().MinRecharge { + return + } + val := data.Amount - call.GetUserCurrencyTotal(a.UID, common.CurrencyBrazil) + if val < 0 { + val = 0 + } + max := call.GetConfigActivityFirstRechargeBack().MaxBack + if max > 0 && val > max { + val = max + } + if val > data.Amount { + val = data.Amount + } + resp.Back = val +} + +func ActivityFirstRechargeBackDraw(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + if !a.CheckActivityExpire(common.ActivityIDFirstRechargeBack) { + return + } + data := call.GetUserFirstRechargeBackData(a.UID) + val := data.Amount - call.GetUserCurrencyTotal(a.UID, common.CurrencyBrazil) + if val <= 0 { + return + } + if time.Now().Unix()-data.RechargeTime < common.ActivityFirstRechargeBackTime { + log.Error("not ActivityFirstRechargeBackDraw time:%+v", data) + a.Code = values.CodeRetry + return + } + if time.Now().Unix()-data.RechargeTime > common.ActivityFirstRechargeBackTime*2 { + a.Code = values.CodeActivityExpire + return + } + rows, err := db.Mysql().UpdateRes(&common.ActivityFirstRechargeBackData{UID: a.UID}, map[string]interface{}{"lost": 0}) + if err != nil || rows == 0 { + a.Code = values.CodeRetry + return + } + max := call.GetConfigActivityFirstRechargeBack().MaxBack + if max > 0 || val > max { + val = max + } + if val > data.Amount { + val = data.Amount + } + _, err = call.UpdateCurrencyPro(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: a.UID, + ChannelID: a.Channel, + Type: common.CurrencyBrazil, + Value: val, + Event: common.CurrencyEventActivityFirstRechargeBack, + }, + }) + if err != nil { + a.Code = values.CodeRetry + return + } + call.UploadActivityData(a.UID, common.ActivityIDFirstRechargeBack, common.ActivityDataJoin, val) +} + +// 幸运码活动 +func ActivityLuckyCodeInfo(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + if !a.CheckActivityExpire(common.ActivityIDLuckyCode) { + return + } + resp := &values.ActivityLuckyCodeInfoResp{ + TelegramChannel: config.GetBase().Server.TelegramChannel, + } + a.Data = resp + con := call.GetConfigAcitivityLuckyCode() + total := call.GetConfigAcitivityLuckyCodeTotalWeight() + for _, v := range con { + resp.List = append(resp.List, values.OneActivityLuckyCodeConfig{ + ID: v.ID, + Reward: v.Reward, + Per: util.FormatFloat(float64(v.Per*100)/float64(total), 2) + "%", + }) + } +} + +func ActivityLuckyCodeDraw(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + + if !a.CheckActivityExpire(common.ActivityIDLuckyCode) { + return + } + + req := new(values.ActivityLuckyCodeDrawReq) + if !a.S(req) { + return + } + now := time.Now() + luckyCode := &common.ActivityLuckyCode{Date: now.Format("20060102")} + if !a.MGet(luckyCode) { + return + } + if req.LuckyCode != luckyCode.Code { + a.Code = values.CodeParam + a.Msg = "O código que você digitou está incorreto" + return + } + t := req.Type + if t == 0 { + t = common.LuckyCodeTypeNormal + } + + resp := &values.ActivityLuckyCodeDrawResp{} + a.Data = resp + + data := &common.ActivityLuckyCodeData{UID: a.UID} + db.Mysql().Get(data) + + if util.IsSameDayTimeStamp(now.Unix(), data.LastDraw) { + a.Code = values.CodeParam + a.Msg = "Esse código já foi usado" + return + } + + // 开始发奖 + con := call.GetConfigAcitivityLuckyCode() + total := call.GetConfigAcitivityLuckyCodeTotalWeight() + if total == 0 { + log.Error("con:%+v invalid,uid:%d", con, a.UID) + a.Code = values.CodeRetry + return + } + + if data.ID == 0 { + err := db.Mysql().Create(&common.ActivityLuckyCodeData{UID: a.UID, LastDraw: now.Unix()}) + if err != nil { + a.Code = values.CodeRetry + return + } + } else { + rows, err := db.Mysql().UpdateRes(&common.ActivityLuckyCodeData{UID: a.UID, LastDraw: data.LastDraw}, + map[string]interface{}{"last_draw": now.Unix()}) + if rows == 0 || err != nil { + a.Code = values.CodeRetry + return + } + } + + ran := rand.Int63n(total) + var rans, reward int64 + id := 0 + for _, v := range con { + rans += v.Per + if ran < rans { + id = v.ID + reward = v.Reward + break + } + } + + _, err := call.UpdateCurrencyPro(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: a.UID, + Type: common.CurrencyBrazil, + Value: reward, + Event: common.CurrencyEventTask, + Exs1: fmt.Sprintf("%d", t), + Exi1: req.LuckyCode, + NeedBet: call.GetConfigCurrencyResourceNeedBet(common.CurrencyResourceBonus, reward), + }, + }) + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + call.UploadActivityData(a.UID, common.ActivityIDLuckyCode, common.ActivityDataJoin, reward) + resp.ID = id +} + +// day 二进制从0位开始,实际签到日期需减1 +func CanSign(sign, day int) bool { + tmp := sign >> (day - 1) + return tmp&1 == 0 +} + +func Sign(day int) int { + ret := 0 + for i := 0; i < day; i++ { + ret <<= 1 + ret |= 1 + } + return ret +} + +// CanSignDays 返回可以签到的天数 +func CanSignDays(sign, day int) (ret []int) { + for i := 0; i < day; i++ { + if sign&1 == 0 { + ret = append(ret, i+1) + } + sign >>= 1 + } + return +} + +func GetSignInfo(uid int) (resp *values.ActivitySignInfoResp) { + if !call.IsActivityValid(common.ActivityIDSign) { + return + } + list := call.GetConfigActivitySign() + if len(list) == 0 { + return + } + resp = &values.ActivitySignInfoResp{List: list} + if uid == 0 { + resp.Day = 1 + resp.CanSign = true + return + } + data := &common.ActivitySignData{UID: uid} + db.Mysql().Get(data) + if data.ID == 0 { + user, _ := call.GetUserXInfo(uid, "birth") + db.Mysql().Create(&common.ActivitySignData{UID: uid, Time: user.Birth}) + data.Time = user.Birth + } + resp.Sign = data.Sign + first := util.GetZeroTime(time.Unix(data.Time, 0)).Unix() + today := util.GetZeroTime(time.Now()).Unix() + day := (today - first) / common.OneDay + resp.Day = int(day) + 1 + + if resp.Day > list[len(list)-1].Day { + resp.Day = list[len(list)-1].Day + return + } + + // 说明已签到过,不需要再判断 + if !CanSign(resp.Sign, resp.Day) { + return + } + resp.CanSign = true + for _, v := range resp.List { + if v.Day == resp.Day { + if v.Recharge > 0 { + re := call.GetRechargeInfo(uid) + if re.DayRecharge < v.Recharge { + resp.CanSign = false + } + } + break + } + } + return +} + +func ActivitySignInfo(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + if !a.CheckActivityExpire(common.ActivityIDSign) { + return + } + a.GetUID() + resp := GetSignInfo(a.UID) + if resp == nil { + a.Code = values.CodeRetry + return + } + a.Data = resp +} + +func ActivitySignDraw(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + if !a.CheckActivityExpire(common.ActivityIDSign) { + return + } + list := call.GetConfigActivitySign() + if len(list) == 0 { + a.Code = values.CodeRetry + return + } + data := &common.ActivitySignData{UID: a.UID} + db.Mysql().Get(data) + if data.ID == 0 { + user, _ := call.GetUserXInfo(a.UID, "birth") + db.Mysql().Create(&common.ActivitySignData{UID: a.UID, Time: user.Birth}) + data.Time = user.Birth + } + first := util.GetZeroTime(time.Unix(data.Time, 0)).Unix() + today := util.GetZeroTime(time.Now()).Unix() + day := int((today-first)/common.OneDay) + 1 + + // 最大签到天数 + if day > list[len(list)-1].Day { + a.Code = values.CodeRetry + return + } + + // if !CanSign(data.Sign, day) { + // a.Code = values.CodeParam + // return + // } + days := CanSignDays(data.Sign, day) + log.Debug("uid:%v,data.Sign:%v,day:%v,days:%v", a.UID, data.Sign, day, days) + if len(days) == 0 { + a.Code = values.CodeParam + a.Msg = "Check-in repetido." + return + } + var reward int64 + for _, v := range list { + if util.SliceContain(days, v.Day) { + reward += v.Reward + if v.Day == day { + if v.Recharge > 0 { + re := call.GetRechargeInfo(a.UID) + if re.DayRecharge < v.Recharge { + a.Code = values.CodeParam + return + } + } + break + } + } + } + if reward == 0 { + a.Code = values.CodeRetry + return + } + // ok + newSign := Sign(day) + rows, err := db.Mysql().UpdateRes(&common.ActivitySignData{UID: a.UID, Sign: data.Sign}, map[string]interface{}{"sign": newSign}) + if rows == 0 || err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + _, err = call.UpdateCurrencyPro(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: a.UID, + Type: common.CurrencyBrazil, + ChannelID: a.Channel, + Value: reward, + Event: common.CurrencyEventActivitySign, + NeedBet: call.GetConfigCurrencyResourceNeedBet(common.CurrencyResourceBonus, reward), + }, + }) + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + a.Data = values.ActivitySignDrawResp{Reward: reward, Day: day, Sign: newSign} + call.UploadActivityData(a.UID, common.ActivityIDSign, common.ActivityDataJoin, reward) +} + +// 破产礼包活动 +func ActivityBreakGiftInfo(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + if !a.CheckActivityExpire(common.ActivityIDBreakGift) { + return + } + resp := &values.ActivityBreakGiftInfoResp{} + limit := config.GetConfig().Web.BreakLimit + if limit <= 0 { + limit = 1 * common.DecimalDigits + } + if call.GetUserCurrency(a.UID, common.CurrencyBrazil) > limit { + return + } + + payData := call.GetPlayerPayData(a.UID) + re := call.GetRechargeInfo(a.UID) + con := call.GetConfigActivityBreakGiftByRecharge(re.TotalRecharge, payData) + log.Debug("con:%+v,total:%v", con, re.TotalRecharge) + if con == nil { + return + } + + if util.SliceContain(payData.SubBreakGift, con.Level) { + return + } + + product := call.GetConfigPayProductByID(con.ProductID) + log.Debug("product:%+v", product) + if product == nil { + a.Code = values.CodeRetry + return + } + resp.Amount = product.Amount + resp.Reward = product.Value + resp.ProductID = con.ProductID + resp.CountDown = con.CountDown + a.Data = resp +} + +func ActivityWeekCardInfo(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + if !a.CheckActivityExpire(common.ActivityIDWeekCard) { + return + } + cons := call.GetConfigActivityWeekCard() + list := call.GetUserWeekCards(a.UID) + resp := &values.ActivityWeekCardInfoResp{} + a.Data = resp + for _, v := range cons { + product := call.GetConfigPayProductByID(v.ProductID) + if product == nil { + continue + } + one := values.OneWeekCard{ + Level: v.Level, + Rebate: fmt.Sprintf("%d", v.Rebate) + "%", + OriginPrice: v.OriginPrice, + Price: product.Amount, + Reward: product.Value, + DayReward: v.DayReward, + DayCount: v.Day, + Discount: fmt.Sprintf("%d", 100-v.Discount), + Next: -1, + ProductID: v.ProductID, + TotalReward: v.DayReward*int64(v.Day) + product.Value, + } + for _, k := range list { + if k.Level == v.Level && k.Day > 0 { + one.DayReward = k.DayReward + one.Day = k.Day + if !util.IsSameDayTimeStamp(k.LastDraw, time.Now().Unix()) { + one.Next = 0 + } else { + one.Next = util.GetZeroTime(time.Now().AddDate(0, 0, 1)).Unix() - k.LastDraw + } + break + } + } + resp.List = append(resp.List, one) + } +} + +func ActivityWeekCardDraw(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + if !a.CheckActivityExpire(common.ActivityIDWeekCard) { + return + } + req := new(values.ActivityWeekCardDrawReq) + if !a.S(req) { + return + } + card := call.GetUserWeekCard(a.UID, req.Level) + if card.ID == 0 { + a.Code = values.CodeRetry + return + } + if card.Day <= 0 { + a.Code = values.CodeRetry + return + } + now := time.Now().Unix() + if util.IsSameDayTimeStamp(now, card.LastDraw) { + a.Code = values.CodeRetry + return + } + rows, err := db.Mysql().UpdateRes(&common.ActivityWeekCardData{UID: a.UID, Level: req.Level, Day: card.Day}, + map[string]interface{}{"day": gorm.Expr("day - 1"), "last_draw": now}) + if rows == 0 || err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + resp := &values.ActivityWeekCardDrawResp{ + Reward: card.DayReward, + } + a.Data = resp + call.UpdateCurrencyPro(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: a.UID, + Type: common.CurrencyBrazil, + Value: card.DayReward, + Event: common.CurrencyEventActivityWeekCard, + ChannelID: a.Channel, + NeedBet: call.GetConfigCurrencyResourceNeedBet(common.CurrencyResourceBonus, card.DayReward), + }, + }) + con := call.GetConfigActivityWeekCardByLevel(card.Level) + if con != nil { + // 最后一天必得一次 + if card.Day == 1 { + call.AddUserDiscountTicket(a.UID, con.Discount) + resp.DiscountTicket = fmt.Sprintf("%d", 100-con.Discount) + } else if card.GetDiscount == 0 { // 未领取过则随机一次 + ran := rand.Intn(card.Day) + if ran == 0 { + db.Mysql().Update(&common.ActivityWeekCardData{UID: a.UID, Level: req.Level}, map[string]interface{}{"get_discount": 1}) + call.AddUserDiscountTicket(a.UID, con.Discount) + resp.DiscountTicket = fmt.Sprintf("%d", 100-con.Discount) + } + } + } +} + +func ActivitySlotsInfo(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + if !a.CheckActivityExpire(common.ActivityIDSlots) { + return + } + resp := &values.ActivitySlotsResp{ActivityID: common.ActivityIDSlots} + a.Data = resp + isSingle := call.IsActivitySingleDay(common.ActivityIDSlots) + for _, v := range values.ActivitySlotsRank { + one := &values.OneActivitySlotsRank{ + UID: v.UID, + Avatar: v.Avatar, + Nick: v.Nick, + } + if v.Avatar == "" || v.Nick == "" { + p, _ := call.GetUserXInfo(v.UID, "mobile", "avatar") + v.Avatar = p.Avatar + v.Nick = p.Mobile + } + one.Nick = "*******" + v.Nick[len(v.Nick)-3:] + if isSingle { + one.Number = v.BestNumber1 + } else { + one.Number = v.BestNumber2 + } + resp.RankList = append(resp.RankList, one) + } + + a.GetUID() + if a.UID == 0 { + return + } + + data := call.GetUserActivitySlotsData(a.UID) + resp.Spin = data.Spin + if isSingle { + if data.Time1 >= util.GetZeroTime(time.Now()).Unix() { + resp.BestNumber = data.BestNumber1 + } + } else { + if data.Time2 >= util.GetZeroTime(time.Now()).Unix() { + resp.BestNumber = data.BestNumber2 + } + } + lastDate := time.Now().AddDate(0, 0, -1).Format("20060102") + record := &common.ActivitySlotsRecord{UID: a.UID, Date: lastDate} + db.Mysql().Get(record) + if record.ID > 0 { + if record.Settle == 0 { + resp.LastReward = &values.OneActivitySlotsLastReward{ + Rank: record.Rank, + Number: record.MyNumber, + Reward: record.Reward, + Draw: record.Settle == 1, + } + } + } else { + // 查询昨天是否参与 + t := data.Time1 + number := data.BestNumber1 + if isSingle { + t = data.Time2 + number = data.BestNumber2 + } + if util.IsSameDayTimeStamp(t, time.Now().AddDate(0, 0, -1).Unix()) { + resp.LastReward = &values.OneActivitySlotsLastReward{ + Number: number, + } + } + } + + // 商品 + product := call.GetConfigPayProductByActivityID(common.ActivityIDSlots) + if len(product) > 0 { + resp.Product = values.OneActivitySlotsProduct{ + ProductID: product[0].ProductID, + OriginAmount: product[0].OriginAmount, + Amount: product[0].Amount, + Reward: product[0].Value, + SpinCount: product[0].Exi, + } + } + +} + +func ActivitySlotsDraw(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + if !a.CheckActivityExpire(common.ActivityIDSlots) { + return + } + data := call.GetUserActivitySlotsData(a.UID) + if data.Spin <= 0 { + a.Code = values.CodeRetry + return + } + resp := &values.ActivitySlotsDrawResp{} + a.Data = resp + /* + 真人玩家若充值大于200则每次抽奖会有75%的概率抽取到900-910之间的数字,25%的概率抽取到850—900以下的数字 + 真人玩家若充值大于50则每次抽奖会有75%的概率抽取到850-900之间的数字,25%的概率抽取到850以下的数字 + 充值小于50的真人玩家抽取到的数字只能小于850(暂定数字),随机选取 + */ + re := call.GetRechargeInfo(a.UID) + ran := rand.Intn(100) + if re.DayRecharge >= 20000000000 { + if ran < 75 { + resp.Number = rand.Intn(11) + 900 + } else { + resp.Number = rand.Intn(50) + 850 + } + } else if re.DayRecharge >= 5000000000 { + if ran < 75 { + resp.Number = rand.Intn(50) + 850 + } else { + resp.Number = rand.Intn(850) + } + } else { + resp.Number = rand.Intn(850) + } + now := time.Now().Unix() + + t := data.Time2 + number := data.BestNumber2 + str := "best_number2" + tstr := "time2" + if call.IsActivitySingleDay(common.ActivityIDSlots) { + t = data.Time1 + number = data.BestNumber1 + str = "best_number1" + tstr = "time1" + } + u := map[string]interface{}{"spin": gorm.Expr("spin - 1"), tstr: now} + + if resp.Number > number || !util.IsSameDayTimeStamp(now, t) { + u[str] = resp.Number + } + rows, err := db.Mysql().UpdateResW(&common.ActivitySlotsData{UID: a.UID}, u, fmt.Sprintf("uid = %d and %s = %d", a.UID, tstr, t)) + if err != nil || rows == 0 { + a.Code = values.CodeRetry + log.Error("err:%v", err) + return + } + call.UploadActivityData(a.UID, common.ActivityIDSlots, 2, 0) +} + +func ActivitySlotsDrawLast(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + lastDate := time.Now().AddDate(0, 0, -1).Format("20060102") + record := &common.ActivitySlotsRecord{UID: a.UID, Date: lastDate} + db.Mysql().Get(record) + resp := &values.ActivitySlotsDrawLastResp{} + a.Data = resp + if record.ID == 0 || record.Settle == 1 { + a.Code = values.CodeRetry + return + } + rows, err := db.Mysql().UpdateResW(&common.ActivitySlotsRecord{}, map[string]interface{}{"settle": 1}, fmt.Sprintf("uid = %d and date = '%s' and settle = 0", a.UID, lastDate)) + if err != nil || rows == 0 { + a.Code = values.CodeRetry + return + } + if record.Reward == 0 { + a.Code = values.CodeRetry + return + } + call.UpdateCurrencyPro(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: a.UID, + Time: time.Now().Unix(), + Value: record.Reward, + Type: common.CurrencyBrazil, + Event: common.CurrencyEventActivitySlots, + NeedBet: call.GetConfigCurrencyResourceNeedBet(common.CurrencyResourceBonus, record.Reward), + }, + }) + resp.Reward = record.Reward + call.UploadActivityData(a.UID, common.ActivityIDSlots, 2, record.Reward) +} + +func ActivityLuckyShopInfo(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + if !a.CheckActivityExpire(common.ActivityIDLuckyShop) { + return + } + resp := &values.ActivityLuckyShopResp{} + a.Data = resp + data := call.GetShowActivityLuckShopData(a.UID) + if data == nil { + return + } + if data.ProductID == 0 { + return + } + product := call.GetConfigPayProductByID(data.ProductID) + if product == nil { + a.Code = values.CodeRetry + return + } + if data.ID == 0 { + db.Mysql().Create(data) + } + resp.Recharge = product.Amount + resp.Value = product.Value + resp.ProductID = product.ProductID + resp.CountDown = common.ActivityLuckyShopExpire + data.Push - time.Now().Unix() +} + +func ActivitySevenDayBoxInfo(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + if !a.CheckActivityExpire(common.ActivityIDSevenDayBox) { + return + } + resp := &values.ActivitySevenDayBoxInfoResp{} + a.Data = resp + re := call.GetRechargeInfo(a.UID) + con := call.GetConfigActivitySevenDayBoxByRechargeAndType(re.TotalRecharge, common.ActivitySevenDayBoxTypeCash) + if len(con) == 0 { + return + } + var least int64 = -1 + var max int64 = -1 + for _, v := range con { + if len(v.SubCashRange) == 2 { + if v.SubCashRange[0] < least || least < 0 { + least = v.SubCashRange[0] + } + if v.SubCashRange[1] > max { + max = v.SubCashRange[1] + } + } + } + resp.ProductID = con[0].ProductID + resp.RewardRange = []int64{least, max} + data := &common.ActivitySevenDayBoxData{UID: a.UID} + db.Mysql().Get(data) + resp.Buy = !util.IsSameDayTimeStamp(data.Time, time.Now().Unix()) + resp.Open = data.Count > 0 + + product := call.GetConfigPayProductByID(resp.ProductID) + if product != nil { + resp.Recharge = product.Amount + } +} + +func ActivitySevenDayBoxDraw(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + resp := &values.ActivitySevenDayBoxDrawResp{} + a.Data = resp + + re := call.GetRechargeInfo(a.UID) + cashCon := call.GetConfigActivitySevenDayBoxByRechargeAndType(re.TotalRecharge, common.ActivitySevenDayBoxTypeCash) + discountCon := call.GetConfigActivitySevenDayBoxByRechargeAndType(re.TotalRecharge, common.ActivitySevenDayBoxTypeDiscountTicket) + + if len(cashCon) == 0 { + a.Code = values.CodeRetry + return + } + + if len(discountCon) == 0 { + a.Code = values.CodeRetry + return + } + + oneCash := cashCon[util.RandomOneEleFromSlice(cashCon, "Per")] + oneDiscount := discountCon[util.RandomOneEleFromSlice(discountCon, "Per")] + + if len(oneCash.SubCashRange) != 2 { + a.Code = values.CodeRetry + return + } + + data := &common.ActivitySevenDayBoxData{UID: a.UID} + db.Mysql().Get(data) + if data.Count <= 0 { + a.Code = values.CodeRetry + return + } + rows, err := db.Mysql().UpdateResW(&common.ActivitySevenDayBoxData{}, map[string]interface{}{"count": gorm.Expr("count - 1")}, + fmt.Sprintf("uid = %d and count = %d", a.UID, data.Count)) + if rows == 0 || err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + + var reward int64 + diff := oneCash.SubCashRange[1] - oneCash.SubCashRange[0] + if diff <= 0 { + reward = oneCash.SubCashRange[0] + } else { + diff /= common.DecimalDigits + reward = rand.Int63n(diff)*common.DecimalDigits + oneCash.SubCashRange[0] + } + if reward > 0 { + call.UpdateCurrencyPro(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: a.UID, + Event: common.CurrencyEventActivityAppSpin, + Type: common.CurrencyBrazil, + Value: reward, + NeedBet: call.GetConfigCurrencyResourceNeedBet(common.CurrencyResourceBonus, reward), + }, + }) + resp.Reward = reward + call.UploadActivityData(a.UID, common.ActivityIDSevenDayBox, common.ActivityDataJoin, reward) + } + if oneDiscount.Discount > 0 { + call.AddUserDiscountTicket(a.UID, oneDiscount.Discount) + resp.Discount = oneDiscount.Discount + } +} + +func ActivitySuperInfo(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + if !a.CheckActivityExpire(common.ActivityIDSuper) { + return + } + resp := &values.ActivitySuperInfoResp{} + a.Data = resp + data := call.GetUserActivitySuperData(a.UID) + con := call.GetConfigActivitySuperByType(data.Type) + resp.Buy = data.CanBuy + if len(con) > 0 { + product := call.GetConfigPayProductByID(con[0].ProductID) + if product != nil { + resp.Recharge = product.Amount + resp.ProductID = con[0].ProductID + } + } + + for i := 1; i <= 3; i++ { + one := values.OneActivitySuperBox{ + Index: i, + } + if data.CanBuy { // 说明未付费 + one.Status = 0 + } else { + tmp := data.Open >> (i - 1) + if tmp&1 == 0 { + one.Status = 1 + } else { + one.Status = 2 + } + } + for _, v := range con { + if v.Index != i { + continue + } + one.Rewards = append(one.Rewards, values.ActivitySuperOneReward{ + RewardType: v.RewardType, + Reward: v.Reward, + }) + } + } +} + +func ActivitySuperDraw(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + if !a.CheckActivityExpire(common.ActivityIDSuper) { + return + } + req := new(values.ActivitySuperDrawReq) + if !a.S(req) { + return + } + if req.Index > 3 || req.Index < 1 { + a.Code = values.CodeRetry + return + } + data := call.GetUserActivitySuperData(a.UID) + if data.Time == 0 { // 未购买 + a.Code = values.CodeRetry + return + } + tmp := data.Open >> (req.Index - 1) + if tmp&1 == 1 { + a.Code = values.CodeParam + a.Msg = "Coleta repetida." + return + } + cons := call.GetConfigActivitySuperByTypeAndIndex(data.Type, req.Index) + index := util.RandomOneEleFromSlice(cons, "Per") + + reward := cons[index] + + open := 1 << (req.Index - 1) & data.Open + rows, err := db.Mysql().UpdateResW(&common.ActivitySuperData{}, map[string]interface{}{"open": open}, fmt.Sprintf("uid = %d and open = %d", a.UID, data.Open)) + if rows == 0 || err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + // 发奖 + if reward.RewardType == 1 { + call.UpdateCurrencyPro(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: a.UID, + Event: common.CurrencyEventActivitySuper, + Value: reward.Reward, + Type: common.CurrencyBrazil, + NeedBet: call.GetConfigCurrencyResourceNeedBet(common.CurrencyResourceBonus, reward.Reward), + }, + }) + call.UploadActivityData(a.UID, common.ActivityIDSuper, 2, reward.Reward) + } else { + call.AddUserDiscountTicket(a.UID, int(reward.Reward)) + } + a.Data = &values.ActivitySuperDrawResp{ + Reward: values.ActivitySuperOneReward{ + RewardType: reward.RewardType, + Reward: reward.Reward, + }, + } +} diff --git a/modules/web/handler/ad.go b/modules/web/handler/ad.go new file mode 100644 index 0000000..519114d --- /dev/null +++ b/modules/web/handler/ad.go @@ -0,0 +1,48 @@ +package handler + +import ( + "server/common" + "server/db" + "server/modules/web/app" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +// UploadFBReq 上报fb数据 +type UploadFBReq struct { + ChannelID int `json:"ChannelID" binding:"required"` + FBC string `json:"FBC" binding:"required"` + FBP string `json:"FBP" binding:"required"` +} + +func UploadFB(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + // req := new(UploadFBReq) + // if !a.S(req) { + // return + // } + ip := a.GetRemoteIP() + ua := c.Request.Header.Get("User-Agent") + fbc := c.Request.Header.Get("fbc") + fbp := c.Request.Header.Get("fbp") + log.Debug("upload FB fbc:%v,fbp:%v,ip:%v,user-agent:%v", fbc, fbp, ip, ua) + if ip == "" { + return + } + + if fbc == "" && fbp == "" { + return + } + + pa := &common.PlayerADData{IP: ip, ChannelID: a.Channel, FBC: fbc, FBP: fbp} + db.Mysql().Get(pa) + if pa.ID > 0 { + return + } + pa.UserAgent = ua + db.Mysql().Create(pa) +} diff --git a/modules/web/handler/balance.go b/modules/web/handler/balance.go new file mode 100644 index 0000000..c7b9d7b --- /dev/null +++ b/modules/web/handler/balance.go @@ -0,0 +1,63 @@ +package handler + +import ( + "server/call" + "server/common" + "server/db" + "server/modules/web/app" + "server/modules/web/values" + "time" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" + "github.com/olivere/elastic/v7" +) + +func BalanceHistory(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.BalanceHisReq) + if !a.S(req) { + return + } + if req.Num > 100 { + log.Error("balance req num:%v", req.Num) + req.Num = 100 + } + // ret := []common.CurrencyBalance{} + // var count int64 + // var err error + // if req.Start != nil && req.End != nil { + // if *req.Start > *req.End { + // a.Code = values.CodeParam + // return + // } + // count, err = db.Mysql().QueryCurrencyHistory(req.Page, req.Num, &common.CurrencyBalance{Uid: a.UID}, &ret, + // fmt.Sprintf("uid = %v and time > %v and time < %v", a.UID, *req.Start, *req.End), "id desc") + // } else { + // count, err = db.Mysql().QueryCurrencyHistory(req.Page, req.Num, &common.CurrencyBalance{Uid: a.UID}, &ret, + // fmt.Sprintf("uid = %v", a.UID), "id desc") + // } + // if err != nil { + // log.Error("err:%v", err) + // a.Code = values.CodeRetry + // return + // } + start := time.Now().Unix() - 30*24*60*60 // 最大30天 + resp := values.BalanceHisResp{} + q := elastic.NewBoolQuery() + // q.MustNot(elastic.NewMatchQuery("event", common.CurrencyEventGameBetReturn), elastic.NewMatchQuery("event", common.CurrencyEventGameBet), + // elastic.NewMatchQuery("event", common.CurrencyEventGameSettleReturn)) + q.Filter(elastic.NewRangeQuery("uid").Gte(a.UID), elastic.NewRangeQuery("uid").Lt(a.UID+1)) + player, _ := call.GetUserXInfo(a.UID, "birth") + if start < player.Birth { + start = player.Birth + } + q.Filter(elastic.NewRangeQuery("time").Gte(start)) + + // q.Must(elastic.NewMatchQuery("uid", a.UID)) + resp.Count, _ = db.ES().QueryList(common.ESIndexBalance, req.Page, req.Num, q, &resp.List, "id", false) + a.Data = resp +} diff --git a/modules/web/handler/firstpage.go b/modules/web/handler/firstpage.go new file mode 100644 index 0000000..f025682 --- /dev/null +++ b/modules/web/handler/firstpage.go @@ -0,0 +1,76 @@ +package handler + +import ( + "fmt" + "server/call" + "server/common" + "server/db" + "server/modules/web/app" + "server/modules/web/values" + "server/util" + "strings" + "time" + + "github.com/gin-gonic/gin" + "github.com/olivere/elastic/v7" +) + +func FirstPage(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.FirstPageReq) + if !a.S(req) { + return + } + uuid := a.UUID + cid := a.Channel + if len(uuid) > 0 { + // 处理一下新增安装 + util.Go(func() { + q := elastic.NewBoolQuery() + q.Filter(elastic.NewTermsQuery("UUID.keyword", uuid)) + q.Filter(elastic.NewTermQuery("Channel", cid)) + if db.ES().Count(common.ESIndexBackOpenRecord, q) > 0 { + return + } + id := fmt.Sprintf("%v_%v", cid, uuid) + db.ES().InsertToESByIDGO(common.ESIndexBackOpenRecord, id, &common.ESOpenRecord{Time: time.Now().Unix(), UUID: uuid, Channel: cid}) + }) + } + + resp := &values.FirstPageResp{} + a.Data = resp + a.GetUID() + resp.Activitys = call.GetConfigBanner(a.UID) + resp.GameTypes = call.GetConfigGameTypes() + resp.GameMarks = call.GetConfigGameMarks() + resp.Providers = call.GetConfigGameProviderAllOpen() + games := call.GetConfigFirstPageGames() + for _, v := range games { + one := values.OneFisrtPageGame{ + ConfigFirstPageGames: v, + } + if v.JumpType == 1 { + one.List = call.GetConfigGameList(req.GameNum, v.JumpID) + } else { + one.List = call.GetConfigGameList(req.GameNum, 0, v.JumpID) + } + resp.Games = append(resp.Games, one) + } + for i := common.CurrencyTypeZero + 1; i < common.CurrencyAll; i++ { + resp.Currencys = append(resp.Currencys, values.OneCurrency{ + ID: i, + Name: strings.ToUpper(i.GetCurrencyName()), + }) + } + list := call.GetConfigGameListByType(common.GameTypeSportBook) + if len(list) > 0 { + resp.Esport = list[0] + } + task := call.GetConfigTaskByTaskType(common.TaskTypeDownload) + if len(task) > 0 { + resp.DownloadAppReward = task[0].Reward + } +} diff --git a/modules/web/handler/ftp.go b/modules/web/handler/ftp.go new file mode 100644 index 0000000..f098084 --- /dev/null +++ b/modules/web/handler/ftp.go @@ -0,0 +1,48 @@ +package handler + +import ( + "fmt" + "os" + "server/modules/backend/app" + "server/modules/backend/values" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +func Hot(c *gin.Context) { + a := app.NewApp(c) + // defer func() { + // a.Response() + // }() + file := c.Request.RequestURI[9:] + // index := strings.LastIndex(c.Request.RequestURI, "/") + // fileName := c.Request.RequestURI[index+1:] + filePath := "hot/" + file + log.Debug("path:%v", filePath) + if _, err := os.Stat(filePath); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + a.Response() + return + } + // c.Header("Content-Disposition", "attachment; filename="+file) + // c.Header("Content-Type", "application/octet-stream") + // c.Header("Content-Disposition", "inline;filename="+file) + c.Header("Connection", "keep-alive") + // c.Header("Content-Transfer-Encoding", "binary") + // c.Header("Cache-Control", "no-cache") + c.File(filePath) +} + +func Privacy(c *gin.Context) { + id := c.Param("id") + filePath := fmt.Sprintf("privacy/PrivacyPolicy%v.html", id) + c.File(filePath) +} + +func TermsofService(c *gin.Context) { + id := c.Param("id") + filePath := fmt.Sprintf("privacy/TERMSOFSERVICE%v.html", id) + c.File(filePath) +} diff --git a/modules/web/handler/game.go b/modules/web/handler/game.go new file mode 100644 index 0000000..8683acb --- /dev/null +++ b/modules/web/handler/game.go @@ -0,0 +1,212 @@ +package handler + +import ( + "fmt" + "server/call" + "server/common" + "server/db" + "server/modules/web/app" + "server/modules/web/providers/all" + "server/modules/web/providers/base" + "server/modules/web/values" + "server/util" + "strings" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" + "github.com/olivere/elastic/v7" +) + +func GameList(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.GameListReq) + if !a.S(req) { + return + } + resp := &values.GameListResp{Provider: req.Provider} + a.Data = resp + var list, tmp []*common.ConfigGameList + tmp = call.GetConfigGameList(0, req.Provider, req.Mark, req.Type) + + if len(req.Name) > 0 { + reqName := strings.ToLower(req.Name) + for _, v := range tmp { + if strings.Contains(strings.ToLower(v.Name), reqName) { + list = append(list, v) + } + } + } else { + list = tmp + } + + start := req.Page * req.Num + end := (req.Page + 1) * req.Num + + if start > len(list)-1 { + return + } + if end > len(list) { + end = len(list) + } + resp.List = list[start:end] + for _, v := range resp.List { + v.Jackpot = call.GetJackpot(common.JackpotTypeGame, v.GameID) + } +} + +func EnterGame(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.EnterGameReq) + if !a.S(req) { + return + } + provider := call.GetConfigGameProvider(req.Provider) + resp := new(values.EnterGameResp) + if provider != nil { + resp.Method = provider.Method + } + a.Data = resp + if req.IsDemo { + log.Debug("player enter demo game %+v", *req) + enter := &base.EnterGameReq{ + ProviderID: req.Provider, + GameID: req.GameID, + Lang: a.Lang, + CurrencyType: req.Currency, + IsDemo: true, + SubID: req.SubID, + IP: a.GetRemoteIP(), + DeviceType: a.DeviceType, + ChannelID: a.Channel, + } + resp.URL = all.EnterGame(enter) + return + } + if !req.Currency.IsValid() { + a.Code = values.CodeParam + return + } + token := c.GetHeader("token") + uid, _ := db.Redis().GetInt(common.GetRedisKeyToken(token)) + if uid == 0 { + a.Code = values.CodeToken + return + } + c.Set("uid", uid) + c.Set("token", token) + util.Go(func() { + db.Redis().AddUserExpire(uid, token) + }) + log.Debug("player %d enter game %+v", uid, *req) + enter := &base.EnterGameReq{ + ProviderID: req.Provider, + GameID: req.GameID, + UID: uid, + Token: token, + Lang: a.Lang, + CurrencyType: req.Currency, + IsDemo: false, + SubID: req.SubID, + IP: a.GetRemoteIP(), + DeviceType: a.DeviceType, + ChannelID: a.Channel, + } + resp.URL = all.EnterGame(enter) + if resp.URL == "" { + a.Code = values.CodeParam + a.Msg = "Under Maintenance,please try later." + } +} + +func GameHistory(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.GameHistoryReq) + if !a.S(req) { + return + } + if req.UID > 0 && req.UID != a.UID { + a.Code = values.CodeParam + return + } + log.Debug("player %d GameHistory %+v", a.UID, *req) + if req.Num > 100 { + return + } + resp := new(values.GameHistoryResp) + a.Data = resp + q := elastic.NewBoolQuery() + if req.UID > 0 { + q.Filter(elastic.NewTermQuery("UID", req.UID)) + } + list := []common.ESGameData{} + db.ES().QueryList(common.ESIndexGameData, req.Page, req.Num, q, &list, "Time", false) + for _, v := range list { + one := values.BetRecord{ + UID: v.UID, + Vip: call.GetVIP(v.UID).Level, + Provider: v.Provider, + GameID: v.GameID, + Time: v.Time, + BetAmount: v.BetAmount, + SettleAmount: v.SettleAmount, + Multiplier: util.FormatFloat(float64(v.SettleAmount)/float64(v.BetAmount), 4), + CurrencyType: v.Type, + } + ga := call.GetConfigGameListByID(v.Provider, v.GameID) + if ga != nil { + one.GameName = ga.Name + } + player, _ := call.GetUserXInfo(v.UID, "nick", "avatar") + one.Avatar = player.Avatar + one.Nick = player.Nick + resp.List = append(resp.List, one) + } +} + +func GameProfile(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + resp := new(values.GameProfileResp) + a.Data = resp + + p, _ := call.GetUserXInfo(a.UID, "birth") + resp.Birth = p.Birth + // 拉取profile + for i := common.CurrencyTypeZero + 1; i < common.CurrencyAll; i++ { + profile := common.PlayerProfile{} + db.Mysql().C().Table(fmt.Sprintf("player_profile_%s", i.GetCurrencyName())).Where("uid = ?", a.UID).Scan(&profile) + resp.Statistics = append(resp.Statistics, profile) + } + + q := elastic.NewBoolQuery() + q.Filter(elastic.NewTermQuery("UID", a.UID)) + q.Filter(elastic.NewRangeQuery("SettleAmount").Gt(0)) + list := []common.ESGameData{} + db.ES().QueryList(common.ESIndexGameData, 0, 10, q, &list, "SettleAmount", false) + for _, v := range list { + one := values.TopWins{ + Provider: v.Provider, + GameID: v.GameID, + Time: v.Time, + BetAmount: v.BetAmount, + SettleAmount: v.SettleAmount, + CurrencyType: v.Type, + } + ga := call.GetConfigGameListByID(v.Provider, v.GameID) + if ga != nil { + one.GameName = ga.Name + } + resp.TopWins = append(resp.TopWins, one) + } +} diff --git a/modules/web/handler/h5.go b/modules/web/handler/h5.go new file mode 100644 index 0000000..a627288 --- /dev/null +++ b/modules/web/handler/h5.go @@ -0,0 +1,100 @@ +package handler + +import ( + "server/call" + "server/common" + "server/db" + "server/modules/web/app" + "server/modules/web/values" + + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +func H5Info(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + resp := values.H5Info{} + con := call.GetConfigH5() + if con != nil { + resp.CollectReward = con.CollectReward + resp.DownloadReward = con.DownloadReward + } + data := &common.PlayerH5Data{UID: a.UID} + err := db.Mysql().Get(data) + if err == gorm.ErrRecordNotFound { + db.Mysql().Create(data) + } + resp.IsCollect = data.Collect == 2 + resp.IsDownload = data.Download == 2 + a.Data = resp +} + +func H5CollectDraw(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + con := call.GetConfigH5() + if con == nil || con.CollectReward <= 0 { + a.Code = values.CodeRetry + return + } + data := &common.PlayerH5Data{UID: a.UID} + db.Mysql().Get(data) + if data.Collect == 2 { + a.Code = values.CodeRetry + return + } + res, err := db.Mysql().UpdateRes(&common.PlayerH5Data{UID: a.UID, Collect: 1}, map[string]interface{}{"collect": 2}) + if err != nil { + a.Code = values.CodeRetry + return + } + if res == 0 { + a.Code = values.CodeRetry + return + } + // call.UpdateCurrencyPro(&common.UpdateCurrency{ + // CurrencyBalance: &common.CurrencyBalance{ + // UID: a.UID, + // Event: common.CurrencyEventH5Collect, + // }, + // }, true, true) +} + +func H5DownloadDraw(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + con := call.GetConfigH5() + if con == nil || con.DownloadReward <= 0 { + a.Code = values.CodeRetry + return + } + data := &common.PlayerH5Data{UID: a.UID} + db.Mysql().Get(data) + if data.Download == 2 { + a.Code = values.CodeRetry + return + } + res, err := db.Mysql().UpdateRes(&common.PlayerH5Data{UID: a.UID, Download: 1}, map[string]interface{}{"download": 2}) + if err != nil { + a.Code = values.CodeRetry + return + } + if res == 0 { + a.Code = values.CodeRetry + return + } + // call.UpdateCurrencyPro(&common.UpdateCurrencyNotify{ + // Pairs: []*common.CurrencyPair{{Type: common.CurrencyTypeBindCash, Value: con.DownloadReward}}, + // CurrencyBalance: &common.CurrencyBalance{ + // UID: a.UID, + // Event: common.CurrencyEventH5Download, + // }, + // }, true, true) +} diff --git a/modules/web/handler/mail.go b/modules/web/handler/mail.go new file mode 100644 index 0000000..8490ef3 --- /dev/null +++ b/modules/web/handler/mail.go @@ -0,0 +1,190 @@ +package handler + +import ( + "server/call" + "server/common" + "server/db" + "server/modules/web/app" + "server/modules/web/values" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +func MailList(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.MailListReq) + if !a.S(req) { + return + } + if req.Num*req.Page > common.MailMaxCount { + a.Code = values.CodeParam + return + } + list := []common.Mail{} + count, err := db.Mysql().QueryMailList(a.UID, req.Page, req.Num, &list, req.Tag) + if err != nil { + a.Code = values.CodeRetry + return + } + // for i := range list { + // list[i].Enclosure = []*pb.CurrencyPair{} + // if len(list[i].Enc) > 0 { + // json.Unmarshal([]byte(list[i].Enc), &list[i].Enclosure) + // } + // } + resp := values.MailListResp{ + List: list, + Count: int(count), + } + a.Data = resp +} + +func ReadMail(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.ReadMailReq) + if !a.S(req) { + return + } + // id, err := strconv.Atoi(req.ID) + // if err != nil { + // log.Error("err:%v", err) + // a.Code = values.CodeRetry + // return + // } + id := req.ID + one := &common.Mail{ID: id} + err := db.Mysql().Get(one) + if err != nil { + a.Code = values.CodeRetry + return + } + count, err := db.Mysql().UpdateRes(&common.Mail{ID: id, Status: common.MailStatusNew}, map[string]interface{}{"status": common.MailStatusRead}) + if count == 0 || err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + call.UpsertRedPointAndNotify(a.UID, -1, call.ModuleMail) + a.Data = values.ReadMailResp{Mail: *one} +} + +// func DrawMail(c *gin.Context) { +// a := app.NewApp(c) +// defer func() { +// a.Response() +// }() +// req := new(values.DrawMailReq) +// if !a.S(req) { +// return +// } +// // id, err := strconv.Atoi(req.ID) +// // if err != nil { +// // log.Error("err:%v", err) +// // a.Code = values.CodeRetry +// // return +// // } + +// id := req.ID +// one := &common.Mail{ID: id} +// err := db.Mysql().Get(one) +// if err != nil { +// a.Code = values.CodeRetry +// return +// } +// if len(one.Enc) == 0 { +// a.Code = values.CodeMailUndrawable +// return +// } +// if one.Status > common.MailStatusRead { +// a.Code = values.CodeMailDrew +// return +// } +// pairs := []*pb.CurrencyPair{} +// err = json.Unmarshal([]byte(one.Enc), &pairs) +// if err != nil { +// log.Error("err:%v", err) +// a.Code = values.CodeRetry +// return +// } +// count, err := db.Mysql().UpdateRes(&common.Mail{ID: id, Status: common.MailStatusRead}, map[string]interface{}{"status": common.MailStatusDraw}) +// if count == 0 || err != nil { +// log.Error("err:%v", err) +// a.Code = values.CodeRetry +// return +// } +// for _, v := range pairs { +// if _, err := call.UpdateCurrencyPro(&common.UpdateCurrency{ +// ShouldNotify: true, +// CurrencyBalance: &common.CurrencyBalance{ +// UID: a.UID, +// Event: common.CurrencyEventMailDraw, +// Type: common.CurrencyType(v.Type), +// Value: v.Value, +// }, +// }); err != nil { +// log.Error("err:%v", err) +// a.Code = values.CodeRetry +// return +// } +// } +// } + +func DeleteMail(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.DeleteMailReq) + if !a.S(req) { + return + } + count := 0 + for _, v := range req.ID { + // id, err := strconv.Atoi(v) + // if err != nil { + // log.Error("err:%v", err) + // a.Code = values.CodeRetry + // return + // } + id := v + one := &common.Mail{ID: id} + err := db.Mysql().Get(one) + if err != nil { + a.Code = values.CodeRetry + return + } + status := one.Status + count, err := db.Mysql().UpdateRes(&common.Mail{ID: id}, map[string]interface{}{"status": common.MailStatusDelete}) + if count == 0 || err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + if status == common.MailStatusNew { + count++ + } + } + if count > 0 { + call.UpsertRedPointAndNotify(a.UID, -count, call.ModuleMail) + } +} + +func DeleteMailAll(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + err := db.Mysql().Update(&common.Mail{Receiver: a.UID}, map[string]interface{}{"status": common.MailStatusDelete}) + if err != nil { + a.Code = values.CodeRetry + return + } + call.UpsertRedPointAndNotify(a.UID, 0, call.ModuleMail) +} diff --git a/modules/web/handler/notice.go b/modules/web/handler/notice.go new file mode 100644 index 0000000..1ca9b79 --- /dev/null +++ b/modules/web/handler/notice.go @@ -0,0 +1,26 @@ +package handler + +import ( + "server/call" + "server/modules/web/app" + "server/modules/web/values" + "time" + + "github.com/gin-gonic/gin" +) + +func NoticeList(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + list := call.GetConfigNotice() + resp := values.NoticeListResp{} + for _, v := range list { + if v.Open == 0 || v.Time > time.Now().Unix() { + continue + } + resp.List = append(resp.List, *v) + } + a.Data = resp +} diff --git a/modules/web/handler/recharge.go b/modules/web/handler/recharge.go new file mode 100644 index 0000000..79893f7 --- /dev/null +++ b/modules/web/handler/recharge.go @@ -0,0 +1,405 @@ +package handler + +import ( + "encoding/json" + "fmt" + "server/call" + "server/config" + "server/db" + "server/modules/web/app" + "server/pb" + "server/util" + "time" + + "server/common" + "server/modules/web/values" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +func CheckRechargeOrder(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.RechargeOrderReq) + if !a.S(req) { + return + } + // log.Debug("req:%+v", *req) + one := &common.RechargeOrder{OrderID: req.OrderID} + db.Mysql().Get(one) + resp := values.RechargeOrderResp{} + resp.Status = int(one.Status) + a.Data = resp + // log.Debug("resp:%+v", resp) +} + +func RechargeInfo(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + list := call.GetConfigPayProduct() + resp := &values.RechargeInfoResp{Tips: call.GetConfigPlatform().PayTips, SelectID: config.GetConfig().Web.SelectID} + a.Data = resp + resp.Channels = call.GetConfigPayChannels() + // payData := call.GetPlayerPayData(a.UID) + a.GetUID() + re := &common.RechargeInfo{} + if a.UID > 0 { + re = call.GetRechargeInfo(a.UID) + } + for _, v := range call.GetConfigFirstPay() { + one := values.OneConfigPayBonus{ + Amount: v.Amount, + } + if re.TotalRecharge > 0 { + one.Per = v.Per + } else { + one.Per = v.FirstPer + } + resp.ConfigPayBonus = append(resp.ConfigPayBonus, one) + } + for _, v := range list { + one := *v + if v.IfSell == 2 { + continue + } + if v.Type == common.CurrencyUSDT { + for _, j := range resp.Channels { + if j.CurrencyType == common.CurrencyUSDT { + if j.PayDown <= v.Amount && j.PayUp >= v.Amount { + one.Channels = append(one.Channels, j.ChannelID) + } + } + } + resp.List = append(resp.List, one) + } else if v.Type == common.CurrencyBrazil { + for _, j := range resp.Channels { + if j.CurrencyType == common.CurrencyBrazil { + if j.PayDown <= v.Amount && j.PayUp >= v.Amount { + one.Channels = append(one.Channels, j.ChannelID) + } + } + } + resp.List = append(resp.List, one) + } + } + + if a.UID > 0 { + // 判断是否有折扣券 + tickets := call.GetUserValidItems(a.UID, common.ItemDiscountTicket) + discount := 0 + for _, v := range tickets { + thisDiscount := v.Exi1 + diff := v.Time + common.ActivityWeekCardTicketExpireTime - time.Now().Unix() + if diff <= 0 { + id := v.ID + util.Go(func() { + db.Mysql().Update(&common.PlayerItems{ID: id}, map[string]interface{}{"status": common.ItemStatusInvalid}) + }) + continue + } + if discount < thisDiscount { + discount = thisDiscount + resp.DiscountTicket = &values.DiscountTicket{ + // Level: con.Level, + Discount: fmt.Sprintf("%d", 100-discount), + TimeLeft: diff, + // Range: , + } + con := call.GetConfigActivityWeekCardByLevel(1) + if con != nil { + resp.DiscountTicket.Range = con.SubRange + } + if discount > 50 { + resp.DiscountTicket.Level = 1 + } else { + resp.DiscountTicket.Level = 2 + } + } + } + } +} + +func RechargeHistory(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.RechargeHistoryReq) + if !a.S(req) { + return + } + ret := []common.RechargeOrder{} + var count int64 + var err error + count, err = db.Mysql().QueryListW(req.Page, req.Num, "create_time desc", + &common.RechargeOrder{UID: a.UID}, &ret, "uid = ? and event = ?", + a.UID, common.CurrencyEventReCharge) + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + resp := values.RechargeHistoryResp{ + Count: count, + List: ret, + } + a.Data = resp +} + +func PlayerRecharge(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + + req := new(values.RechargeReq) + if !a.S(req) { + a.Code = values.CodeParam + return + } + log.Debug("player %v recharge:%+v", a.UID, *req) + + if req.CurrencyType == 0 { + req.CurrencyType = common.CurrencyBrazil + } + if req.ProductID > 0 { + product := call.GetConfigPayProductByID(req.ProductID) + if product == nil { + a.Code = values.CodeRetry + return + } + req.Amount = product.Amount + } else { + req.Amount = common.RoundCurrency(req.CurrencyType, req.Amount) + } + + // product := call.GetConfigPayProductByID(req.ProductID) + // if product == nil { + // log.Error("unknow product:%v", req.ProductID) + // a.Code = values.CodeParam + // a.Msg = "Activity is over" + // return + // } + // if !a.CanBuyProduct(product.ActivityID, product.ProductID) { + // return + // } + + // paySource := req.PaySource + // if paySource >= common.PaySourceAll { + // a.Code = values.CodeParam + // log.Error("paysource err:%v", req.PaySource) + // return + // } + // 判断充值间隔 + if db.Redis().Exist(common.GetRedisKeyPlayerPayInterval(a.UID)) { + a.Code = values.CodeParam + a.Msg = "You have an order to pay,please process first" + return + } + // if req.UserPhone == "" { + // req.UserPhone = util.CheckPhone(req.UserPhone) + // } + payImp := NewRechargeImp(req, a.UID, a.Channel, a.GetRemoteIP()) + if payImp == nil { + a.Code = values.CodeRetry + log.Error("req params err:%v", req) + return + } + + code := payImp.Recharge() + if code != values.CodeOK { + a.Code = code + a.Msg = "channel unavailable" + return + } + + // ok + a.Data = payImp.Data + a.Msg = payImp.Order.OrderID + log.Debug("player %v recharge resp:%v,msg:%v", a.UID, a.Data, a.Msg) + + uid := a.UID + util.Go(func() { + db.Redis().SetData(common.GetRedisKeyPlayerPayInterval(uid), 1, 15*time.Second) + }) + + if !config.GetBase().Release && req.PayChannel == config.GetConfig().Web.TestPay { + util.Go(func() { + call.RechargeCallback(payImp.Order, true, "", "") + }) + } +} + +func NewRechargeImp(req *values.RechargeReq, uid, cid int, ip string) *RechargeImp { + r := new(RechargeImp) + r.Channel = call.GetChannelByID(cid) + if r.Channel == nil { + log.Error("invalid cid:%v", cid) + return nil + } + order := &common.RechargeOrder{ + // ProductID: product.ProductID, Amount: product.Amount, + CreateTime: time.Now().Unix(), + CurrencyType: req.CurrencyType, + Amount: req.Amount, + ChannelID: cid, + UID: uid, Status: common.StatusROrderCreate, Event: int(common.CurrencyEventReCharge), + UPI: req.PayChannel, + ProductID: req.ProductID, + } + // 只有商城购买才能使用优惠券 + ticketCon := call.GetConfigActivityWeekCardByLevel(1) + if req.ProductID == 0 { + // 首先判断折扣券 + if ticketCon != nil && len(ticketCon.SubRange) == 2 { + if req.Amount >= ticketCon.SubRange[0] && req.Amount <= ticketCon.SubRange[1] { + discount := -1 + var ticket *common.PlayerItems + // 判断是否有折扣券 + tickets := call.GetUserValidItems(uid, common.ItemDiscountTicket) + for i, v := range tickets { + thisDiscount := v.Exi1 + diff := v.Time + common.ActivityWeekCardTicketExpireTime - time.Now().Unix() + if diff <= 0 { + id := v.ID + util.Go(func() { + db.Mysql().Update(&common.PlayerItems{ID: id}, map[string]interface{}{"status": common.ItemStatusInvalid}) + }) + continue + } + if discount < 0 || thisDiscount < discount { + discount = thisDiscount + ticket = tickets[i] + } + } + if discount > 0 { + ticketData := common.ActivityRechargeData{ID: common.ItemDiscountTicket, I1: ticket.Exi1, I2: req.Amount} + ticketByte, _ := json.Marshal(ticketData) + order.Extra = string(ticketByte) + req.Amount = common.RoundCurrency(common.CurrencyBrazil, req.Amount*int64(discount)/100) + order.Amount = req.Amount + } + } + } + } + + r.UID = uid + info, _ := call.GetUserXInfo(uid, "mobile") + p := new(PayImp) + p.CurrencyType = req.CurrencyType + p.req = new(pb.InnerRechargeReq) + p.req.Amount = req.Amount + p.req.PlayerChannel = uint32(cid) + if req.CurrencyType == common.CurrencyBrazil { + // 判断黑名单 + if call.BlackListAndKick(uid, &common.BlackList{Phone: info.Mobile}) { + return nil + } + pinfo := &common.PayInfo{UID: uid} + db.Mysql().Get(pinfo) + // if req.UserName == nil { + // log.Error("invalid param:%+v", req) + // return nil + // } + p.req.Name = util.GenerateRandomString(5) + p.req.Phone = pinfo.Mobile + p.req.IP = ip + p.req.Number = pinfo.Number + if info.Mobile != "" { + p.req.Phone = info.Mobile + } + if pinfo.Name != "" { + p.req.Name = pinfo.Name + } + if pinfo.Email != "" { + p.req.Email = pinfo.Email + } + } + r.Order = order + p.base = r + r.RechargeIn = p + r.PayChannel = req.PayChannel + return r +} + +type RechargeIn interface { + Recharge() int +} + +// RechargeImp 新充值对象 +type RechargeImp struct { + Data interface{} + RechargeIn + Order *common.RechargeOrder + // product *common.ConfigPayProduct + // tx *gorm.DB + UID int + Channel *common.Channel + PayChannel int +} + +func (r *RechargeImp) CreateRecharge() error { + if r.Order.OrderID == "" { + r.Order.OrderID = util.NewOrderID(r.UID) + } + // r.Order.UID = r.UID + // r.Order.Status = common.StatusROrderCreate + // r.Order.Event = int(common.CurrencyEventReCharge) + // r.Order.ChannelID = int(r.Channel.ChannelID) + if err := db.Mysql().C().Model(r.Order).Create(r.Order).Error; err != nil { + log.Error("create order err:%v", err) + return err + } + return nil +} + +// pay模块支付 +type PayImp struct { + base *RechargeImp + req *pb.InnerRechargeReq + CurrencyType common.CurrencyType +} + +func (p *PayImp) Recharge() int { + orderID := util.NewOrderID(p.base.UID) + // product := p.base.product + var resp *pb.InnerRechargeResp + var err error + if p.CurrencyType == common.CurrencyBrazil { + req := &pb.InnerRechargeReq{OrderID: orderID, Amount: p.req.Amount, Phone: p.req.Phone, + Name: p.req.Name, UID: uint32(p.base.UID), Channel: uint32(p.base.PayChannel), IsPersonalCard: false, PaySource: common.PaySourceModulePay} + p.base.Order.PaySource = common.PaySourceModulePay + if p.base.PayChannel < 0 { + req.IsPersonalCard = true + } + resp, err = call.Recharge(req) + if err != nil { + log.Error("err:%v", err) + return values.CodeRetry + } + p.base.Order.PayChannel = int(resp.Channel) + } else { + orderID = "USDT" + orderID + req := &pb.InnerRechargeReq{OrderID: orderID, Amount: p.req.Amount, UID: uint32(p.base.UID), Channel: uint32(p.base.PayChannel), PaySource: common.PaySourceBlockPay} + p.base.Order.PaySource = common.PaySourceBlockPay + resp, err = call.Recharge(req) + if err != nil { + log.Error("err:%v", err) + return values.CodeRetry + } + p.base.Order.Extra = resp.URL + } + p.base.Data = values.PayResp{OrderID: orderID, Addr: resp.URL} + p.base.Order.OrderID = orderID + p.base.Order.APIPayID = resp.APIOrderID + err = p.base.CreateRecharge() + if err != nil { + return values.CodeRetry + } + return values.CodeOK +} diff --git a/modules/web/handler/share.go b/modules/web/handler/share.go new file mode 100644 index 0000000..a4367af --- /dev/null +++ b/modules/web/handler/share.go @@ -0,0 +1,381 @@ +package handler + +import ( + "fmt" + "server/call" + "server/common" + "server/db" + "server/modules/web/app" + "server/modules/web/values" + "server/util" + "time" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" + "gorm.io/gorm" +) + +// func ShareInfo(c *gin.Context) { +// a := app.NewApp(c) +// defer func() { +// a.Response() +// }() +// shareInfo := &common.ShareInfo{} +// resp := &values.ShareInfoResp{ +// ShareConfig: call.GetConfigShare(), +// } +// a.Data = resp +// a.GetUID() +// if a.UID > 0 { +// shareInfo = call.GetShareInfo(a.UID) +// resp.Today = values.ShareRecord{ +// Regist: shareInfo.TodayAgents, +// Real: shareInfo.TodayRealAgents, +// Bet: shareInfo.TodayAgentsBet, +// Reward: shareInfo.TodayReward, +// } +// resp.Total = values.ShareRecord{ +// Regist: shareInfo.TotalAgents, +// Real: shareInfo.TotalRealAgents, +// Bet: shareInfo.TotalAgentsBet, +// Reward: shareInfo.TotalReward, +// } +// resp.Rewards = values.RewardRecord{ +// Level: shareInfo.Level, +// TotalWithdraw: shareInfo.TotalReward - shareInfo.AvailableReward, +// Available: shareInfo.AvailableReward, +// } +// } + +// channel := call.GetChannelByID(a.Channel) +// if channel != nil { +// resp.ShareLink = channel.URL + "?code=" + shareInfo.Share +// if a.Prefix != "" { +// resp.ShareLink = a.Prefix + "." + resp.ShareLink +// } else { +// resp.ShareLink = "www." + resp.ShareLink +// } +// if shareInfo.Share == "" { +// resp.ShareLink += "xxxxxx" +// } +// } +// con := call.GetConfigShareSys() +// if con != nil { +// resp.Rewards.WithdrawLimit = con.WithdrawLimit +// } +// } + +func ShareInfo(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + resp := &values.ShareInfoResp{ + InviteRecharge: call.GetConfigShareSys().ShareRecharge, + PlatformInviteReward: values.ShareTotalInviteReward + call.GetConfigShareSys().FakeInviteReward, + PlatformBetReward: values.ShareTotalBetReward + call.GetConfigShareSys().FakeBetReward, + Rank: values.ShareRank, + } + a.Data = resp + resp.PlatformTotalReward = resp.PlatformInviteReward + resp.PlatformBetReward + for _, v := range call.GetConfigShare() { + resp.BetPer = append(resp.BetPer, util.FormatFloat(float64(v.Per)/10, 2)+"%") + } + channel := call.GetChannelByID(a.Channel) + if channel != nil { + resp.ShareLink += channel.URL + "?code=" + "xxxxxx" + } + a.GetUID() + if a.UID <= 0 { + return + } + shareInfo := call.GetShareInfo(a.UID) + resp.InviteReward = shareInfo.InviteReward + resp.BetReward = shareInfo.BetReward + resp.Invites = shareInfo.Invites + resp.InvalidInvites = shareInfo.InvaidInvites + resp.TotalReward = resp.InviteReward + resp.BetReward + resp.AvailableReward = shareInfo.AvailableReward + resp.RechargeCount = call.GetUserShareRecharges(a.UID, 1) + resp.TotalRecharge = call.GetUserShareRechargeAmount(a.UID, 1) + if channel != nil { + resp.ShareLink = channel.URL + "?code=" + shareInfo.Share + if a.Prefix != "" { + resp.ShareLink = a.Prefix + "." + resp.ShareLink + } else { + resp.ShareLink = "www." + resp.ShareLink + } + } +} + +func SharePlatformInfo(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + resp := &values.SharePlatformResp{ + InviteReward: values.ShareTotalInviteReward, + BetReward: values.ShareTotalBetReward, + } + a.Data = resp + + resp.TotalReward = resp.InviteReward + resp.BetReward + resp.Rank = values.ShareRank +} + +func ShareWithdraw(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.ShareWithdrawReq) + if !a.S(req) { + return + } + if req.Opt < 1 || req.Opt > 2 { + a.Code = values.CodeParam + return + } + con := call.GetConfigShareSys() + log.Debug("player %d ShareWithdraw,con:%+v", a.UID, con) + if con == nil { + a.Code = values.CodeRetry + return + } + shareInfo := call.GetShareInfo(a.UID) + if shareInfo.ID <= 0 { + a.Code = values.CodeParam + return + } + availableReward := shareInfo.AvailableReward + if availableReward < con.WithdrawLimit { + a.Code = values.CodeParam + a.Msg = fmt.Sprintf("Minimum withdrawal requirement is %d.", con.WithdrawLimit/common.DecimalDigits) + return + } + if req.Amount > availableReward { + a.Code = values.CodeParam + return + } + + // 直接到余额 + if req.Opt == 1 { + rows, err := db.Mysql().UpdateResW(&common.ShareInfo{}, map[string]interface{}{"available_reward": gorm.Expr("available_reward - ?", req.Amount)}, + fmt.Sprintf("available_reward = %d and uid = %d", availableReward, a.UID)) + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + if rows == 0 { + a.Code = values.CodeRetry + return + } + call.UpdateCurrencyPro(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: a.UID, + Type: common.CurrencyBrazil, + ChannelID: a.Channel, + Value: req.Amount, + Event: common.CurrencyEventShareWithdraw, + }, + }) + return + } + + ip := a.GetRemoteIP() + payInfo, code := NewWithdraw(&values.WithdrawReq{PayAccount: req.PayAccount}, a.UID, ip) + if code != values.CodeOK { + a.Code = code + a.Msg = payInfo + return + } + + if len(payInfo) > 500 { + a.Code = values.CodeParam + a.Msg = "Withdrawal information too long." + return + } + + re := call.GetRechargeInfo(a.UID) + now := time.Now().Unix() + if re.ID == 0 { + re.LastWithdraw = now + if err := db.Mysql().Create(re); err != nil { + a.Code = values.CodeRetry + return + } + } else { + u := map[string]interface{}{"last_withdraw": now} + if !util.IsSameDayTimeStamp(now, re.LastWithdraw) { + u["withdraw_count"] = 0 + u["day_withdraw"] = 0 + } + rows, err := db.Mysql().UpdateRes(&common.RechargeInfo{UID: a.UID, LastWithdraw: re.LastWithdraw}, u) + if err != nil || rows == 0 { + a.Code = values.CodeRetry + return + } + } + + orderID := util.NewOrderID(int(a.UID)) + + vipCon := call.GetVipCon(a.UID) + realAmount := req.Amount // 实际打款 + if con != nil && vipCon.Fee > 0 { + realAmount = common.RoundCurrency(common.CurrencyBrazil, (1000-int64(vipCon.Fee))*realAmount/1000) + } + rows, err := db.Mysql().UpdateResW(&common.ShareInfo{}, map[string]interface{}{"available_reward": gorm.Expr("available_reward - ?", req.Amount)}, + fmt.Sprintf("available_reward = %d and uid = %d", availableReward, a.UID)) + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + if rows == 0 { + a.Code = values.CodeRetry + return + } + + order := &common.WithdrawOrder{ + UID: int(a.UID), + OrderID: orderID, + APIPayID: "", + // ProductID: one.ID, + CreateTime: time.Now().Unix(), + Amount: realAmount, + WithdrawCash: req.Amount, + Status: uint8(common.StatusROrderCreate), + PaySource: common.PaySourceModulePay, + Event: int(common.CurrencyEventWithDraw), + CurrencyType: common.CurrencyBrazil, + PayAccount: payInfo, + ChannelID: a.Channel, + UPI: -1, + OrderType: common.WithdrawOrderTypeShare, + } + if err := db.Mysql().Create(order); err != nil { + log.Error("player %v create withdraw order fail err:%v", a.UID, err) + a.Code = values.CodeRetry + return + } + u := map[string]interface{}{} + u["withdrawing_cash"] = gorm.Expr("withdrawing_cash + ?", req.Amount) + u["withdraw_count"] = gorm.Expr("withdraw_count + ?", 1) + u["total_withdrawing"] = gorm.Expr("total_withdrawing + ?", realAmount) + u["day_withdraw"] = gorm.Expr("day_withdraw + ?", realAmount) + db.Mysql().Update(&common.RechargeInfo{UID: a.UID}, u) + + a.Data = values.ShareWithdrawResp{Available: availableReward - req.Amount} +} + +// func ShareReference(c *gin.Context) { +// a := app.NewApp(c) +// defer func() { +// a.Response() +// }() +// req := new(values.ShareReferenceReq) +// if !a.S(req) { +// return +// } +// if req.Num > 100 { +// req.Num = 100 +// } +// resp := &values.ShareReferenceResp{} +// a.Data = resp + +// a.GetUID() +// if a.UID == 0 { +// return +// } +// q := elastic.NewBoolQuery() +// if req.Start > 0 { +// q.Filter(elastic.NewRangeQuery("Time").Gte(req.Start)) +// } +// if req.End > 0 { +// q.Filter(elastic.NewRangeQuery("Time").Lt(req.End)) +// } +// q.Filter(elastic.NewTermQuery("Up", a.UID)) +// db.ES().QueryList(common.ESIndexShareProfitRecord, req.Page, req.Num, q, &resp.List) +// resp.TotalBet = db.ES().SumByInt64(common.ESIndexShareProfitRecord, "DownBet", q) + db.ES().SumByInt64(common.ESIndexShareProfitRecord, "Bet", q) +// resp.TotalReward = db.ES().SumByInt64(common.ESIndexShareProfitRecord, "Reward", q) +// } + +// func ShareReport(c *gin.Context) { +// a := app.NewApp(c) +// defer func() { +// a.Response() +// }() +// req := new(values.ShareReportReq) +// if !a.S(req) { +// return +// } +// if req.Num > 100 { +// req.Num = 100 +// } +// resp := &values.ShareReportResp{} +// a.Data = resp + +// a.GetUID() +// if a.UID == 0 { +// return +// } +// q := elastic.NewBoolQuery() +// if req.Start > 0 { +// q.Filter(elastic.NewRangeQuery("Time").Gte(req.Start)) +// } +// if req.End > 0 { +// q.Filter(elastic.NewRangeQuery("Time").Lt(req.End)) +// } +// q.Filter(elastic.NewTermQuery("UID", a.UID)) +// db.ES().QueryList(common.ESIndexShareProfitReport, req.Page, req.Num, q, &resp.List, "Time", false) +// today := &common.ShareInfo{UID: a.UID} +// db.Mysql().Get(today) +// if today.TodayAgents > 0 { +// resp.List = append([]common.ESShareProfitReport{{ +// UID: a.UID, +// Regist: today.TodayAgents, +// Bet: today.TodayAgentsBet, +// Level: today.Level, +// Reward: today.TodayReward, +// Date: time.Now().Format("20060102"), +// Time: time.Now().Unix(), +// }}, resp.List...) +// } +// } + +// func ShareTransfer(c *gin.Context) { +// a := app.NewApp(c) +// defer func() { +// a.Response() +// }() +// req := new(values.ShareTransferReq) +// if !a.S(req) { +// return +// } +// if req.Num > 100 { +// req.Num = 100 +// } + +// resp := &values.ShareTransferResp{} +// a.Data = resp + +// a.GetUID() +// if a.UID == 0 { +// return +// } + +// sql := fmt.Sprintf("uid = %d", a.UID) +// // q := elastic.NewBoolQuery() +// if req.Start > 0 { +// // q.Filter(elastic.NewRangeQuery("Time").Gte(req.Start)) +// sql += fmt.Sprintf(" and create_time>=%d", req.Start) +// } +// if req.End > 0 { +// sql += fmt.Sprintf(" and create_time<%d", req.End) +// // q.Filter(elastic.NewRangeQuery("Time").Lt(req.End)) +// } +// // q.Filter(elastic.NewTermQuery("UID", a.UID)) +// db.Mysql().QueryListW(req.Page, req.Num, "create_time desc", &common.ShareOrder{}, &resp.List, sql) +// // db.ES().QueryList(common.ESIndexShareProfitTransfer, req.Page, req.Num, q, &resp.List) +// } diff --git a/modules/web/handler/sys.go b/modules/web/handler/sys.go new file mode 100644 index 0000000..b7e4b52 --- /dev/null +++ b/modules/web/handler/sys.go @@ -0,0 +1,142 @@ +package handler + +import ( + "fmt" + "server/call" + "server/common" + "server/config" + "server/db" + "server/modules/web/app" + "server/modules/web/values" + "server/util" + "strconv" + "time" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" + "github.com/olivere/elastic/v7" +) + +func SysConfig(c *gin.Context) { + a := app.NewApp(c) + resp := &values.SysConfigResp{} + defer func() { + a.Response() + }() + req := new(values.SysConfigReq) + if !a.S(req) { + return + } + ip := a.GetRemoteIP() + uuid := a.UUID + log.Debug("sysconfig ip:%v,req:%+v,uuid:%v", ip, *req, uuid) + if call.CheckChannel(req.Channel, ip) { + a.Code = values.CodeParam + return + } + channel := call.GetChannelByID(req.Channel) + if channel == nil { + log.Error("invalid channel:%v", req.Channel) + a.Code = values.CodeParam + return + } + isExamine := call.CheckExamine(req.Version, ip, uuid, channel) + if !isExamine && !req.IsRecord { + cid := a.Channel + if len(uuid) > 0 { + // 处理一下新增安装 + util.Go(func() { + q := elastic.NewBoolQuery() + q.Must(elastic.NewMatchQuery("UUID.keyword", uuid)) + q.Must(elastic.NewMatchQuery("Channel", cid)) + if db.ES().Count(common.ESIndexBackOpenRecord, q) > 0 { + return + } + id := fmt.Sprintf("%v_%v", cid, uuid) + db.ES().InsertToESByIDGO(common.ESIndexBackOpenRecord, id, &common.ESOpenRecord{Time: time.Now().Unix(), UUID: uuid, Channel: cid}) + }) + } + } + + // ok + url := channel.URL + if url == "" { + url = config.GetBase().Server.GameURL + if isExamine { + url = config.GetBase().Server.ExamineURL + } + } else { + if isExamine { + url = "web." + url + config.GetConfig().Web.Addr + } else { + url = "game." + url + config.GetConfig().Web.Addr + } + } + resp.Is = isExamine + resp.URL = url + resp.IsHot = channel.IsHot == 2 + resp.NewPlayerGift = call.GetConfigPlatform().NewPlayerGift + resp.PhoneBonus = call.GetConfigPlatform().BindPhoneGift + resp.ServiceEmail = call.GetConfigPlatform().Email + resp.ServiceTelegram = call.GetConfigPlatform().Telegram + resp.ServiceWhatsapp = call.GetConfigPlatform().Whatsapp + + ver := c.GetHeader("version") + version, _ := strconv.Atoi(ver) + if call.WhitePass(ip, uuid, "/sys/hotUpdate", version, a.Channel) { + resp.Version = channel.MainVersion + } + log.Debug("resp:%+v", resp) + a.Data = resp +} + +func SysConfig2(c *gin.Context) { + a := app.NewApp(c) + defer a.Response() + req := new(values.SysConfigReq) + if !a.S(req) { + a.Code = values.CodeRetry + return + } + channel := call.GetChannelByID(req.Channel) + if channel == nil { + log.Error("invalid channel:%v", req.Channel) + a.Code = values.CodeParam + return + } + gpsID := req.GPSAdid + isExamine := len(gpsID) == 0 + if channel.IgnoreOrganic == 2 && len(gpsID) > 0 { + isOrganic := false + if util.SliceContain(config.GetConfig().Web.OldChannels, channel.ChannelID) { + isOrganic = call.IsOrganic2(gpsID, channel.AdjustAppToken, channel.ChannelID, 3) + } else { + isOrganic = call.IsOrganic(gpsID, channel.AdjustAppToken, channel.ChannelID, 3) + } + if isOrganic { + count := db.Mysql().Count(&common.PlayerDBInfo{}, fmt.Sprintf("channel_id = %v and deviceid = '%v'", channel.ChannelID, gpsID)) + if count == 0 { + isExamine = true + } + } + } + a.Data = isExamine +} + +func Config(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.ResponseB() + }() + resp := &values.ConfigResp{} + a.Data = resp + channel := call.GetChannelByID(a.Channel) + if channel == nil { + return + } + resp.AdjustAppToken = channel.AdjustAppToken + resp.AdjustEvents = channel.AdjustEventID + resp.FBPixelID = channel.FBPixelID + // resp.FBAccessToken = channel.FBAccessToken + resp.Channel = channel.ChannelID +} diff --git a/modules/web/handler/task.go b/modules/web/handler/task.go new file mode 100644 index 0000000..cd85591 --- /dev/null +++ b/modules/web/handler/task.go @@ -0,0 +1,95 @@ +package handler + +import ( + "server/call" + "server/common" + "server/db" + "server/modules/web/app" + "server/modules/web/values" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +// Record 领奖进度,key为taskid,value为任务进度,小与0时表示已领取 +type TaskInfoResp struct { + TaskList []*values.OneTask +} + +func GetTaskInfo(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + a.GetUID() + resp := &TaskInfoResp{TaskList: a.GetUserTaskStatus()} + a.Data = resp +} + +type DrawTaskReq struct { + TaskID int +} + +func DrawTask(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := &DrawTaskReq{} + if !a.S(req) { + return + } + con := call.GetConfigTaskByTaskID(req.TaskID) + if con == nil { + a.Code = values.CodeRetry + return + } + task := call.GetUserTaskDataByTaskID(a.UID, req.TaskID) + if task.Progress < con.Target { + a.Code = values.CodeParam + a.Msg = "Task not complete." + return + } + + // 判断是否充值 + // re := call.GetRechargeInfo(a.UID) + // if re.TotalRecharge == 0 { + // a.Code = values.CodeRecharge + // a.Msg = "You can get it after linking the payment method." + // return + // } + var rows int64 + var err error + if con.Kind == common.TaskKindOnce { + rows, err = db.Mysql().UpdateRes(&common.TaskData{UID: a.UID, TaskID: req.TaskID, Progress: task.Progress}, map[string]interface{}{"progress": -1}) + } else if con.Kind == common.TaskKindCycle { + rows, err = db.Mysql().UpdateRes(&common.TaskData{UID: a.UID, TaskID: req.TaskID, Progress: task.Progress}, map[string]interface{}{"progress": 0}) + } + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + if rows == 0 { + a.Code = values.CodeRetry + return + } + if con.Reward <= 0 { + return + } + _, err = call.UpdateCurrencyPro(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: a.UID, + Value: con.Reward, + Event: common.CurrencyEventTask, + Type: common.CurrencyBrazil, + Exi1: req.TaskID, + NeedBet: call.GetConfigCurrencyResourceNeedBet(common.CurrencyResourceBonus, con.Reward), + }, + }) + if err != nil { + a.Code = values.CodeRetry + return + } + a.Data = req +} diff --git a/modules/web/handler/telegram.go b/modules/web/handler/telegram.go new file mode 100644 index 0000000..0db23a1 --- /dev/null +++ b/modules/web/handler/telegram.go @@ -0,0 +1,38 @@ +package handler + +import ( + "math/rand" + "server/common" + "server/db" + "server/modules/backend/values" + "server/modules/web/app" + "time" + + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +type LuckyCodeResp struct { + Code int +} + +func GetLuckyCode(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.ResponseB() + }() + resp := &LuckyCodeResp{} + a.Data = resp + date := time.Now().Format("20060102") + data := &common.ActivityLuckyCode{Date: date} + err := db.Mysql().Get(data) + resp.Code = data.Code + if err == gorm.ErrRecordNotFound { + code := rand.Intn(90000) + 10000 + db.Mysql().Create(&common.ActivityLuckyCode{Date: date, Code: code, Type: common.LuckyCodeTypeNormal}) + resp.Code = code + } else if err != nil { + a.Code = values.CodeRetry + return + } +} diff --git a/modules/web/handler/user.go b/modules/web/handler/user.go new file mode 100644 index 0000000..011b698 --- /dev/null +++ b/modules/web/handler/user.go @@ -0,0 +1,220 @@ +package handler + +import ( + "fmt" + "reflect" + "server/call" + "server/common" + "server/db" + "server/modules/web/app" + "server/modules/web/values" + "time" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" + "gorm.io/gorm" +) + +func GetUserInfo(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.GetUserInfoReq) + if !a.S(req) { + return + } + uid := a.UID + if req.UID > 0 { + uid = req.UID + } + pd := &common.PlayerData{UID: uid} + db.Mysql().Get(pd) + ret, err := call.GetUserXInfo(uid, "avatar", "nick", "mobile", "birth") + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + resp := values.UserInfoResp{ + UID: uid, + Nick: ret.Nick, + Avatar: ret.Avatar, + VIPLevel: call.GetVIP(uid).Level, + Currencys: make(map[common.CurrencyType]int64), + Feedback: pd.FeedbackTime > 0, + NewPddShare: call.HasNewAcitivityPddShare(a.UID), + Birth: ret.Birth, + Phone: ret.Mobile, + CurrentVip: &values.OneUserInfoVip{}, + NextVip: &values.OneUserInfoVip{}, + } + resp.Activitys.RechargeBack = call.ShouldShowActivityFirstRechargeBack(uid) + resp.Activitys.DaySign = call.ShouldShowActivitySign(uid) + resp.Activitys.WeekCard = call.ShouldShowActivityWeekCard(uid) + resp.Activitys.LuckyShop = call.ShouldShowActivityLuckShop(uid) + vip := call.GetVipCon(a.UID) + nextVip := call.GetConfigVIPByLevel(vip.Level + 1) + resp.CurrentVip.Bonus = vip.Bonus + resp.CurrentVip.Cashback = vip.Cashback + resp.CurrentVip.WithdrawFee = vip.Fee + // resp.CurrentVip.Cashback = util.FormatFloat(float64(vip.Cashback)/10, 2) + "%" + // resp.CurrentVip.WithdrawFee = util.FormatFloat(float64(vip.Fee)/10, 2) + "%" + if nextVip != nil { + resp.NextVip.Bonus = nextVip.Bonus + resp.NextVip.Cashback = nextVip.Cashback + resp.NextVip.WithdrawFee = nextVip.Fee + // resp.NextVip.Cashback = util.FormatFloat(float64(nextVip.Cashback)/10, 2) + "%" + // resp.NextVip.WithdrawFee = util.FormatFloat(float64(nextVip.Fee)/10, 2) + "%" + } else { + resp.NextVip = resp.CurrentVip + } + + if pd.LastAppSpinDraw == 0 { + resp.AppSpinCount++ + } + // call.GetUserCurrency() + currency := &common.PlayerCurrency{UID: uid} + db.Mysql().Get(currency) + // currencyRe := &common.PlayerCurrency{UID: uid} + // db.Mysql().C().Table(common.PlayerRechargeTableName).Where("uid = ?", uid).Scan(currencyRe) + ref := reflect.ValueOf(currency).Elem() + // refRe := reflect.ValueOf(currencyRe).Elem() + for i := common.CurrencyTypeZero + 1; i < common.CurrencyAll; i++ { + if i == common.CurrencyUSDT { + continue + } + resp.Currencys[i] = ref.Field(int(i) + 1).Int() + } + // 一些参数只发给玩家自己 + // if req.UID <= 0 { + re := &common.RechargeInfo{UID: uid} + db.Mysql().Get(re) + resp.Recharge = re.TotalRecharge + a.Code = values.CodeOK + // } + a.Data = resp +} + +func EditUserInfo(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.EditUserInfoReq) + if !a.S(req) { + return + } + if req.Avatar == nil && req.Nick == nil { + a.Code = values.CodeParam + return + } + tx := db.Mysql().Begin() + uid := a.UID + defer func() { + a.MCommit(tx) + }() + update := map[string]interface{}{} + if req.Nick != nil { + if !call.IsUserNickValid(*req.Nick) { + a.Code = values.CodeNickInvalid + return + } + // pd := &common.PlayerData{UID: uid} + // db.Mysql().Get(pd) + // if pd.LastNickEdit > 0 { + // if time.Now().Unix()-pd.LastNickEdit < 24*60*60 { + // a.Code = values.CodeNickEditBusy + // return + // } + // if _, err := call.UpdateCurrencyAndNotify(&common.UpdateCurrencyNotify{ + // UID: uid, + // Event: common.CurrencyEventChangeNick, + // Pairs: []*common.CurrencyPair{{Type: common.CurrencyTypeCash, Value: -200}}, + // }, true, tx); err != nil { + // log.Error("err:%v", err) + // a.Code = values.CodeRetry + // return + // } + // } + // if err := tx.Model(pd).Where("uid = ? and last_nick_edit = ?", uid, pd.LastNickEdit).Updates(&common.PlayerData{LastNickEdit: time.Now().Unix()}).Error; err != nil { + // log.Error("err:%v", err) + // a.Code = values.CodeRetry + // return + // } + update["nick"] = *req.Nick + } + if req.Avatar != nil { + update["avatar"] = *req.Avatar + } + search := &common.PlayerDBInfo{Id: uid} + if err := tx.Model(search).Where(search).Updates(update).Error; err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + if err := db.Redis().UpdateUserFields(uid, update); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } +} + +func Feedback(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.FeedbackReq) + if !a.S(req) { + return + } + if len(req.List) == 0 { + return + } + // questions := []int{} + // all := []values.OneFeedback{} + for _, v := range req.List { + for _, j := range v.Choose { + if j == 0 && v.Context == "" { + a.Code = values.CodeParam + a.Msg = "context should not be empty." + return + } + } + // if !util.SliceContain(questions, v.Index) { + // questions = append(questions, v.Index) + // all = append(all, v) + // } + } + pd := &common.PlayerData{UID: a.UID} + db.Mysql().Get(pd) + if pd.FeedbackTime > 0 { + a.Code = values.CodeRetry + return + } + now := time.Now().Unix() + rows, err := db.Mysql().UpdateResW(&common.PlayerData{}, map[string]interface{}{"feedback_time": now}, fmt.Sprintf("uid = %d and feedback_time = 0", a.UID)) + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + if rows == 0 { + a.Code = values.CodeRetry + return + } + call.GetAcitivityPddData(a.UID) + db.Mysql().Update(&common.PddData{UID: a.UID}, map[string]interface{}{"spin": gorm.Expr("spin + 1")}) + for _, v := range req.List { + for _, j := range v.Choose { + db.ES().InsertToESGO(common.ESIndexBackFeedback, &common.ESFeedback{ + UID: a.UID, + QuestionIndex: v.Index, + Choose: j, + Context: v.Context, + Time: now, + }) + } + } +} diff --git a/modules/web/handler/vip.go b/modules/web/handler/vip.go new file mode 100644 index 0000000..85c9124 --- /dev/null +++ b/modules/web/handler/vip.go @@ -0,0 +1,261 @@ +package handler + +import ( + "fmt" + "server/call" + "server/common" + "server/config" + "server/db" + "server/modules/web/app" + "server/modules/web/values" + "server/util" + "time" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" + "github.com/olivere/elastic/v7" +) + +func GetVipInfo(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.VipInfoReq) + if !a.S(req) { + return + } + if req.Num > 100 { + req.Num = 100 + } + resp := &values.VipInfoResp{ + List: call.GetConfigVIP(), + } + a.Data = resp + a.GetUID() + if a.UID == 0 { + return + } + + resp.Info = call.GetVIP(a.UID) + con := call.GetConfigVIPByLevel(resp.Info.Level) + if con == nil { + return + } + if resp.Info.Profit < 0 { + resp.Info.Profit = 0 + } + reset := false + now := time.Now() + if config.GetBase().Release { + reset = !util.IsSameDayTimeStamp(now.Unix(), resp.Info.ProfitTime) + if reset { + if util.GetZeroTime(now).Unix()-resp.Info.ProfitTime > 24*3600 { // 超时未领取直接归零 + db.Mysql().UpdateResW(&common.VipData{}, map[string]interface{}{"profit_time": now.Unix(), "cashback": 0, "profit": 0}, + fmt.Sprintf("uid = %v and profit_time = %v", a.UID, resp.Info.ProfitTime)) + resp.Info.Cashback = 0 + resp.Info.Profit = 0 + } else { + cashback := resp.Info.Profit * con.Cashback / 1000 + if cashback < 0 { + cashback = 0 + } + resp.Info.Cashback = cashback + db.Mysql().UpdateResW(&common.VipData{}, map[string]interface{}{"profit_time": now.Unix(), "cashback": cashback, "profit": 0}, + fmt.Sprintf("uid = %v and profit_time = %v", a.UID, resp.Info.ProfitTime)) + } + } + } else { + reset = util.GetNext5MinUnix()-resp.Info.ProfitTime >= 5*60 + if reset { + if util.GetNext5MinUnix()-resp.Info.ProfitTime > 2*5*60 { // 超时未领取直接归零 + db.Mysql().UpdateResW(&common.VipData{}, map[string]interface{}{"profit_time": now.Unix(), "cashback": 0, "profit": 0}, + fmt.Sprintf("uid = %v and profit_time = %v", a.UID, resp.Info.ProfitTime)) + resp.Info.Cashback = 0 + resp.Info.Profit = 0 + } else { + cashback := resp.Info.Profit * con.Cashback / 1000 + if cashback < 0 { + cashback = 0 + } + resp.Info.Cashback = cashback + db.Mysql().UpdateResW(&common.VipData{}, map[string]interface{}{"profit_time": now.Unix(), "cashback": cashback, "profit": 0}, + fmt.Sprintf("uid = %v and profit_time = %v", a.UID, resp.Info.ProfitTime)) + } + } + } + if resp.Info.Cashback == 0 && resp.Info.Level > 0 { + con := call.GetVipCon(a.UID) + resp.Info.Cashback = resp.Info.Profit * con.Cashback / 1000 + if config.GetBase().Release { + resp.NextCashback = util.GetZeroTime(now.AddDate(0, 0, 1)).Unix() - now.Unix() + } else { + resp.NextCashback = util.GetNext5MinUnix() - now.Unix() + } + } + resp.CashbackList = []common.CurrencyBalance{} + q := elastic.NewBoolQuery() + q.Filter(elastic.NewTermQuery("uid", a.UID)) + q.Filter(elastic.NewTermQuery("event", common.CurrencyEventVIPCashback)) + resp.TotalCashback = db.ES().SumByInt64(common.ESIndexBalance, "value", q) + db.ES().QueryList(common.ESIndexBalance, req.Page, req.Num, q, &resp.CashbackList, "time", false) +} + +func DrawVipBonus(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.DrawVipBonusReq) + if !a.S(req) { + return + } + cons := call.GetConfigVIP() + if len(cons) == 0 { + a.Code = values.CodeRetry + return + } + if req.Level < 0 || req.Level > cons[len(cons)-1].Level { + a.Code = values.CodeParam + a.Msg = "VIP level is insufficient" + return + } + vip := call.GetVIP(a.UID) + if vip.Level < req.Level { + a.Code = values.CodeParam + a.Msg = "VIP level is insufficient" + return + } + flag := int64(1 << req.Level) + if vip.Draws&int64(flag) > 0 { + a.Code = values.CodeParam + a.Msg = "reward has been claimed" + return + } + con := call.GetConfigVIPByLevel(req.Level) + if con == nil { + a.Code = values.CodeParam + a.Msg = "VIP level is insufficient" + return + } + bonus := con.Bonus + if bonus <= 0 { + a.Code = values.CodeParam + a.Msg = "reward invalid" + return + } + newDraws := vip.Draws | flag + rows, err := db.Mysql().UpdateResW(&common.VipData{}, map[string]interface{}{"draws": newDraws}, fmt.Sprintf("uid = %d and draws = %v", a.UID, vip.Draws)) + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + if rows == 0 { + a.Code = values.CodeRetry + return + } + _, err = call.UpdateCurrencyPro(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: a.UID, + Event: common.CurrencyEventVIPBonus, + Type: common.CurrencyBrazil, + ChannelID: a.Channel, + Value: bonus, + NeedBet: call.GetConfigCurrencyResourceNeedBet(common.CurrencyResourceBonus, bonus), + }, + }) + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } +} + +func DrawVipCashback(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + vip := call.GetVIP(a.UID) + cashback := vip.Cashback + con := call.GetConfigVIPByLevel(vip.Level) + reset := false + now := time.Now() + update := map[string]interface{}{"cashback": 0} + if config.GetBase().Release { + reset = !util.IsSameDayTimeStamp(now.Unix(), vip.ProfitTime) + if reset { + if util.GetZeroTime(now).Unix()-vip.ProfitTime > 24*3600 { // 超时未领取直接归零 + db.Mysql().UpdateResW(&common.VipData{}, map[string]interface{}{"profit_time": now.Unix(), "cashback": 0, "profit": 0}, + fmt.Sprintf("uid = %v and profit_time = %v", a.UID, vip.ProfitTime)) + a.Code = values.CodeParam + a.Msg = "reward invalid" + return + } else { + cashback = vip.Profit * con.Cashback / 1000 + update["profit"] = 0 + update["profit_time"] = time.Now().Unix() + } + } + } else { + reset = util.GetNext5MinUnix()-vip.ProfitTime >= 5*60 + if reset { + if util.GetNext5MinUnix()-vip.ProfitTime > 2*5*60 { // 超时未领取直接归零 + db.Mysql().UpdateResW(&common.VipData{}, map[string]interface{}{"profit_time": now.Unix(), "cashback": 0, "profit": 0}, + fmt.Sprintf("uid = %v and profit_time = %v", a.UID, vip.ProfitTime)) + a.Code = values.CodeParam + a.Msg = "reward invalid" + return + } else { + cashback = vip.Profit * con.Cashback / 1000 + update["profit"] = 0 + update["profit_time"] = time.Now().Unix() + } + } + } + + if cashback <= 0 { + a.Code = values.CodeParam + a.Msg = "reward invalid" + return + } + + q := elastic.NewBoolQuery() + q.Filter(elastic.NewTermQuery("uid", a.UID)) + q.Filter(elastic.NewTermQuery("event", common.CurrencyEventVIPCashback)) + totalCashback := db.ES().SumByInt64(common.ESIndexBalance, "value", q) + + rows, err := db.Mysql().UpdateResW(&common.VipData{}, update, fmt.Sprintf("uid = %v and cashback = %v", a.UID, vip.Cashback)) + if err != nil || rows == 0 { + log.Error("err:%v", err) + a.Code = values.CodeParam + a.Msg = "reward invalid" + return + } + _, err = call.UpdateCurrencyPro(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: a.UID, + Event: common.CurrencyEventVIPCashback, + Type: common.CurrencyBrazil, + ChannelID: a.Channel, + Value: cashback, + NeedBet: call.GetConfigCurrencyResourceNeedBet(common.CurrencyResourceBonus, cashback), + }, + }) + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + resp := values.DrawCashbackResp{ + TotalCashback: totalCashback + cashback, + Balance: 0, + } + if config.GetBase().Release { + resp.NextCashback = util.GetZeroTime(now.AddDate(0, 0, 1)).Unix() - now.Unix() + } else { + resp.NextCashback = util.GetNext5MinUnix() - now.Unix() + } + a.Data = resp +} diff --git a/modules/web/handler/withdraw.go b/modules/web/handler/withdraw.go new file mode 100644 index 0000000..45eb548 --- /dev/null +++ b/modules/web/handler/withdraw.go @@ -0,0 +1,649 @@ +package handler + +import ( + "encoding/json" + "errors" + "fmt" + "server/call" + "server/common" + "server/config" + "server/db" + "server/modules/web/app" + "server/modules/web/values" + "server/pb" + "server/util" + "time" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" + "github.com/mitchellh/mapstructure" + "gorm.io/gorm" +) + +func WithdrawInfo(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + info := new(common.RechargeInfo) + info.UID = a.UID + if err := db.Mysql().Get(info); err != nil && err != gorm.ErrRecordNotFound { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + resp := values.WithDrawInfoResp{Tips: call.GetConfigPlatform().WithdrawTips} + + if util.IsSameDayTimeStamp(time.Now().Unix(), info.LastWithdraw) { + resp.WithDrawCount = info.WithdrawCount + } else { + resp.WithDrawCount = 0 + } + con := call.GetVipCon(a.UID) + if con != nil { + resp.TotalWithdrawCount = con.WithdrawCount + } + resp.Fees = append(resp.Fees, con.Fee, con.UFee) + resp.Channels = call.GetConfigWithdrawChannels() + list := call.GetConfigWithdrawProduct() + for _, v := range list { + if v.IfSell == 2 { + continue + } + one := *v + if v.Type == common.CurrencyUSDT { + for _, j := range resp.Channels { + if j.CurrencyType == common.CurrencyUSDT { + if j.PayDown <= v.Amount && j.PayUp >= v.Amount { + one.Channels = append(one.Channels, j.ChannelID) + } + } + } + resp.List = append(resp.List, one) + } else if v.Type == common.CurrencyBrazil { + for _, j := range resp.Channels { + if j.CurrencyType == common.CurrencyBrazil { + if j.PayDown <= v.Amount && j.PayUp >= v.Amount { + one.Channels = append(one.Channels, j.ChannelID) + } + } + } + resp.List = append(resp.List, one) + } + } + for i := common.CurrencyTypeZero + 1; i < common.CurrencyAll; i++ { + // bet := call.GetPlayerProfileByCurrency(a.UID, i).TotalBet + info := call.GetPlayerRechargeInfoByCurrency(a.UID, i) + canWithdraw := call.GetUserCurrency(a.UID, i) + if canWithdraw < 0 || info.TotalRecharge == 0 { + canWithdraw = 0 + } + resp.Bets = append(resp.Bets, canWithdraw) + } + + a.Data = resp +} + +func WithdrawHistory(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.WithdrawHistoryReq) + if !a.S(req) { + return + } + if req.Num > 100 { + req.Num = 100 + } + // log.Debug("withdraw history req:%+v", *req) + ret := []common.WithdrawOrder{} + var count int64 + var err error + count, err = db.Mysql().QueryListW(req.Page, req.Num, "create_time desc", + &common.WithdrawOrder{UID: a.UID}, &ret, "uid = ? and event = ?", + a.UID, common.CurrencyEventWithDraw) + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + for i, v := range ret { + if v.Status == common.StatusROrderCreate || v.Status == common.StatusROrderWaitting { + ret[i].Status = common.StatusROrderPay + } else if v.Status == common.StatusROrderRefuse { + ret[i].Status = common.StatusROrderFail + } + } + resp := values.WithdrawHistoryResp{ + Count: count, + List: ret, + } + a.Data = resp +} + +func PlayerWithdrawBlock(c *gin.Context) { + a := app.NewApp(c) + resp := &values.WithdrawResp{} + defer func() { + a.Data = resp + log.Debug("player %v PlayerWithdraw code:%v", a.UID, a.Code) + a.Response() + }() + + req := new(values.WithdrawBlockReq) + if !a.S(req) { + return + } + if req.CurrencyType != common.CurrencyUSDT { + a.Code = values.CodeParam + return + } + uid := a.UID + log.Debug("player %v withdrawblock %+v", uid, *req) + req.Amount = common.RoundCurrency(req.CurrencyType, req.Amount) + + // 退出条件限制 + if !a.CheckWithdrawCondition(req.Amount, req.CurrencyType) { + return + } + + has := call.GetUserCurrency(a.UID, req.CurrencyType) + need := req.Amount + con := call.GetVipCon(uid) + if con != nil && con.UFee > 0 { + need += con.UFee + } + if has < need { + log.Error("err not enough cash:%v,%v", has, need) + a.Code = values.CodeWithdrawNotEnough + return + } + + // 拉取玩家退出信息 + re := call.GetRechargeInfo(a.UID) + now := time.Now().Unix() + if re.ID == 0 { + re.LastWithdraw = now + if err := db.Mysql().Create(re); err != nil { + a.Code = values.CodeRetry + return + } + } else { + u := map[string]interface{}{"last_withdraw": now} + if !util.IsSameDayTimeStamp(now, re.LastWithdraw) { + u["withdraw_count"] = 0 + u["day_withdraw"] = 0 + } + rows, err := db.Mysql().UpdateRes(&common.RechargeInfo{UID: uid, LastWithdraw: re.LastWithdraw}, u) + if err != nil || rows == 0 { + a.Code = values.CodeRetry + return + } + } + + orderID := "USDT" + util.NewOrderID(int(uid)) + // 第一步,先扣钱 + err := call.MineCurrencyProReal(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: a.UID, + Event: common.CurrencyEventWithDraw, + Type: common.CurrencyUSDT, + Value: -need, + Exs1: orderID, + ChannelID: a.Channel, + }, + }).Err + if err != nil { + log.Error("player %v mines cash err:%v", uid, err) + a.Code = values.CodeRetry + return + } + + // 直接发起退出 + shouldAuto := call.CanAutoWithdraw(uid, re.TotalRecharge, re.TotalWithdraw+re.TotalWithdrawing+need) + // 第二步,创建订单 + orderStatus := common.StatusROrderCreate + if shouldAuto { + orderStatus = common.StatusROrderWaitting + } + + order := &common.WithdrawOrder{ + UID: int(uid), + OrderID: orderID, + APIPayID: "", + // ProductID: one.ID, + CreateTime: time.Now().Unix(), + Amount: req.Amount, + WithdrawCash: need, + Status: uint8(orderStatus), + PaySource: common.PaySourceBlockPay, + CurrencyType: req.CurrencyType, + Event: int(common.CurrencyEventWithDraw), + PayAccount: req.Address, + ChannelID: a.Channel, + } + if err := db.Mysql().Create(order); err != nil { + log.Error("player %v create WithdrawBlock order fail err:%v", uid, err) + a.Code = values.CodeRetry + return + } + + u := map[string]interface{}{} + u["withdraw_count"] = gorm.Expr("withdraw_count + ?", 1) + db.Mysql().Update(&common.RechargeInfo{UID: uid}, u) + call.UpdatePlayerRechargeInfoCurrency(uid, req.CurrencyType, map[string]interface{}{"total_withdrawing": gorm.Expr("total_withdrawing + ?", need)}) + // 直接发起退出 + // util.Go(func() { + // call.SendWithdrawMail(uid, call.MailWithdrawType2) + // }) +} + +func PlayerWithdrawCheck(c *gin.Context) { + a := app.NewApp(c) + defer func() { + log.Debug("player %v PlayerWithdrawCheck code:%v,msg:%v", a.UID, a.Code, a.Msg) + a.Response() + }() + req := new(values.WithdrawCheckReq) + if !a.S(req) { + return + } + a.CheckWithdrawCondition(req.Amount, common.CurrencyBrazil) +} + +func PlayerWithdraw(c *gin.Context) { + a := app.NewApp(c) + resp := &values.WithdrawResp{} + a.Data = resp + defer func() { + log.Debug("player %v PlayerWithdraw code:%v", a.UID, a.Code) + a.Response() + }() + + req := new(values.WithdrawReq) + if !a.S(req) { + return + } + if req.CurrencyType != common.CurrencyBrazil { + log.Error("invalid type:%v", req.CurrencyType) + req.CurrencyType = common.CurrencyBrazil + } + uid := a.UID + log.Debug("player %v withdraw %+v", uid, *req) + req.Amount = common.RoundCurrency(req.CurrencyType, req.Amount) + + // 退出条件限制 + if !a.CheckWithdrawCondition(req.Amount, req.CurrencyType) { + return + } + + ip := a.GetRemoteIP() + payInfo, code := NewWithdraw(req, uid, ip) + if code != values.CodeOK { + a.Code = code + a.Msg = payInfo + return + } + + if len(payInfo) > 500 { + a.Code = values.CodeParam + a.Msg = "Withdrawal information too long." + return + } + + user := new(common.PlayerDBInfo) + user.Id = uid + if err := db.Mysql().Get(user); err != nil { + log.Error("err:%v", err) + a.Code = values.CodeRetry + return + } + need := req.Amount + has := call.GetUserCurrency(a.UID, req.CurrencyType) + if need > has { + log.Error("err not enough cash:%v,%v", has, need) + a.Code = values.CodeWithdrawNotEnough + return + } + + re := call.GetRechargeInfo(a.UID) + now := time.Now().Unix() + if re.ID == 0 { + re.LastWithdraw = now + if err := db.Mysql().Create(re); err != nil { + a.Code = values.CodeRetry + return + } + } else { + u := map[string]interface{}{"last_withdraw": now} + if !util.IsSameDayTimeStamp(now, re.LastWithdraw) { + u["withdraw_count"] = 0 + u["day_withdraw"] = 0 + } + rows, err := db.Mysql().UpdateRes(&common.RechargeInfo{UID: uid, LastWithdraw: re.LastWithdraw}, u) + if err != nil || rows == 0 { + a.Code = values.CodeRetry + return + } + } + + orderID := util.NewOrderID(int(uid)) + // 第一步,先扣钱 + pro := call.MineCurrencyProReal(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: uid, + Value: -need, + Event: common.CurrencyEventWithDraw, + Type: req.CurrencyType, + Exs1: orderID, + ChannelID: a.Channel, + }, + }) + if pro.Err != nil { + log.Error("err:%v", pro.Err) + a.Code = values.CodeRetry + return + } + + con := call.GetVipCon(uid) + realAmount := need // 实际打款 + if con != nil && con.Fee > 0 { + realAmount = common.RoundCurrency(req.CurrencyType, (1000-int64(con.Fee))*realAmount/1000) + } + + var shouldAuto = false + // 在总赠送比配置比例小时才判断个人 + if call.GetTotalRechargePer(realAmount) < config.GetConfig().Web.TotalWithdrawPer { + // 直接发起退出 + shouldAuto = call.CanAutoWithdraw(uid, re.TotalRecharge, re.TotalWithdraw+re.TotalWithdrawing+realAmount) + } + // 第二步,创建订单 + withdrawChannel := -1 + if req.ChannelID != nil { + withdrawChannel = *req.ChannelID + } + + orderStatus := common.StatusROrderCreate + if shouldAuto { + orderStatus = common.StatusROrderWaitting + } + + order := &common.WithdrawOrder{ + UID: int(uid), + OrderID: orderID, + APIPayID: "", + // ProductID: one.ID, + CreateTime: time.Now().Unix(), + Amount: realAmount, + WithdrawCash: need, + Status: uint8(orderStatus), + PaySource: common.PaySourceModulePay, + Event: int(common.CurrencyEventWithDraw), + CurrencyType: req.CurrencyType, + PayAccount: payInfo, + ChannelID: a.Channel, + UPI: withdrawChannel, + } + if err := db.Mysql().Create(order); err != nil { + log.Error("player %v create withdraw order fail err:%v", uid, err) + a.Code = values.CodeRetry + return + } + resp.Balance = pro.Balance + resp.WithdrawBalance = has - need + u := map[string]interface{}{} + u["withdrawing_cash"] = gorm.Expr("withdrawing_cash + ?", need) + u["withdraw_count"] = gorm.Expr("withdraw_count + ?", 1) + u["total_withdrawing"] = gorm.Expr("total_withdrawing + ?", realAmount) + u["day_withdraw"] = gorm.Expr("day_withdraw + ?", realAmount) + db.Mysql().Update(&common.RechargeInfo{UID: uid}, u) + // call.UpdatePlayerRechargeInfoCurrency(uid, req.CurrencyType, map[string]interface{}{"total_withdrawing": gorm.Expr("total_withdrawing + ?", need)}) + // 直接发起退出 + // util.Go(func() { + // call.SendWithdrawMail(uid, call.MailWithdrawType2) + // }) +} + +// 返回值在code不为0的时候,代表错误msg +func NewWithdraw(req *values.WithdrawReq, uid int, ip string) (string, int) { + one := common.WithdrawCommon{} + err := mapstructure.Decode(req.PayAccount, &one) + if err != nil || !one.PayType.Isvalid() { + log.Error("NewWithdrawImp err:%v,one:%+v", err, one) + return "", values.CodeParam + } + one.IP = ip + // one.AccountName = strings.TrimSpace(one.AccountName) + // one.BankCardNo = strings.TrimSpace(one.BankCardNo) + // one.BankCode = strings.TrimSpace(one.BankCode) + // if one.DrawType == common.WithdrawTypeBank && len(one.BankCode) != 11 { + // return "The IFSC Code shall be 11 digital letters.", values.CodeParam + // } + // if one.DrawType == common.WithdrawTypeBank && one.BankCardNo == "" && one.AccountName == "" && one.Email == "" { + // log.Error("NewWithdrawImp 银行卡支付,银行卡号不能为空 one:%+v", one) + // return "", values.CodeParam + // } + // if one.DrawType != common.WithdrawTypeUPI && one.DrawType != common.WithdrawTypeBank { + // log.Error("NewWithdrawImp unknown draw type one:%+v", one) + // return "", values.CodeParam + // } + // user, _ := call.GetUserXInfo(uid, "mobile") + // if user.Mobile == "" { + // return "", values.CodeParam + // } + // mapstructure.Decode(req.PayAccount, &one) + // if one.Mobile == "" { + // one.Mobile = user.Mobile + // } + // 判断是否是拉黑用户,拉黑用户不让代付 + // blackData := &common.BlackList{Phone: one.Mobile, PayAccount: one.BankCardNo, Email: one.Email, Name: one.AccountName} + // if one.DrawType == common.WithdrawTypeUPI { + // blackData.PayAccount = one.BankCode + // } + // if call.BlackListAndKick(uid, blackData) { + // return "", values.CodeRetry + // } + // 如果是银行那卡代付,验证ifsc + // if one.DrawType == common.WithdrawTypeBank { + // if !call.CheckIFSC(one.BankCode) { + // return "The IFSC Code is invalid.", values.CodeParam + // } + // } + + // 查询该银行卡是否已经绑定其他账号 + // pi := &common.PayInfo{UID: uid} + // db.Mysql().Get(pi) + // if one.DrawType == common.WithdrawTypeBank { + // if pi.BankCardNo != one.BankCardNo { + // sql := fmt.Sprintf("bank_card_no = '%v'", one.BankCardNo) + // if db.Mysql().Count(&common.PayInfo{}, sql) >= int64(config.GetConfig().Web.MaxBankCardCount) { + // return "", values.CodeBankCardNoLimit + // } + // } + // } else if one.DrawType == common.WithdrawTypeUPI { // UPI + // if pi.BankCode != one.BankCode { + // sql := fmt.Sprintf("bank_code = '%v'", one.BankCode) + // if db.Mysql().Count(&common.PayInfo{}, sql) >= int64(config.GetConfig().Web.MaxBankCardCount) { + // return "", values.CodeBankCardNoLimit + // } + // } + // } + info := &common.PayInfo{ + UID: uid, + Name: one.Name, + Mobile: one.Mobile, + Email: one.Email, + PayType: one.PayType, + Number: one.Number, + } + if _, err := db.Mysql().Upsert(fmt.Sprintf("uid = %v", uid), info); err != nil { + return "", values.CodeParam + } + ret, err := json.Marshal(one) + if err != nil { + log.Error("err:%v", err) + return "", values.CodeParam + } + return string(ret), values.CodeOK +} + +func NewWithdrawImp(order *common.WithdrawOrder) *WithdrawImp { + base := new(WithdrawImp) + // uid := order.UID + cid := order.ChannelID + base.Channel = call.GetChannelByID(cid) + if base.Channel == nil { + log.Error("invalid cid:%v", cid) + return nil + } + base.order = order + one := new(PayWithdraw) + base.SubWithdraw = one + one.base = base + return base +} + +// WithdrawImp 发起退出对象 +type WithdrawImp struct { + order *common.WithdrawOrder + // tx *gorm.DB + SubWithdraw WithdrawInter + Channel *common.Channel + PayChannel int +} + +func (w *WithdrawImp) BaseWithdraw() error { + var err error + // uid := w.order.UID + defer func() { + if err != nil { + call.ReturnBackWithdraw(w.order, common.StatusROrderPay, common.StatusROrderFail, w.PayChannel) + } + }() + res := db.Mysql().C().Model(w.order).Where("status = ?", common.StatusROrderCreate).Updates(map[string]interface{}{"status": common.StatusROrderPay}) + if res.Error != nil { + // w.tx.Rollback() + log.Error("sub withdraw err:%v", err) + return res.Error + } + if res.RowsAffected == 0 { + // w.tx.Rollback() + log.Error("sub withdraw err:%v", err) + return errors.New("invalid order") + } + err = w.SubWithdraw.Withdraw() + if err != nil { + // w.tx.Rollback() + log.Error("sub withdraw err:%v", err) + return err + } + // err = w.tx.Commit().Error + // if err != nil { + // log.Error("commit BaseWithdraw err:%v", err) + // return err + // } + return nil +} + +func (w *WithdrawImp) AutoWithdraw() error { + err := w.SubWithdraw.AutoWithdraw() + if err != nil { + return err + } + return nil +} + +type WithdrawInter interface { + Withdraw() error + AutoWithdraw() error +} + +// PayWithdraw pay模块退出 +type PayWithdraw struct { + base *WithdrawImp +} + +func (p *PayWithdraw) Withdraw() error { + order := p.base.order + req := &pb.InnerWithdrawReq{ + OrderID: order.OrderID, + Amount: order.Amount, + UID: uint32(order.UID), + Channel: int64(order.UPI), + PaySource: uint32(order.PaySource), + } + if req.PaySource == common.PaySourceModulePay { + send := new(common.WithdrawCommon) + err := json.Unmarshal([]byte(order.PayAccount), &send) + if err != nil { + log.Error("withdraw unmarshal err %v", err) + return err + } + req.Phone = send.Mobile + req.Name = send.Name + req.Email = send.Email + req.PayType = int64(send.PayType) + req.Address = send.Address + req.Number = send.Number + } + ret, err := call.Withdraw(req) + if ret != nil { + p.base.PayChannel = int(ret.Channel) + } + if err != nil { + log.Error("err:%v", err) + return err + } + or := &common.RechargeOrder{Status: common.StatusROrderPay, APIPayID: ret.APIOrderID, PaySource: common.PaySourceModulePay, PayChannel: int(ret.Channel)} + or.ID = order.ID + res := db.Mysql().C().Model(order).Updates(or) + err = res.Error + if err != nil { + log.Error("update order err:%v", err) + return err + } + if res.RowsAffected == 0 { + log.Error("update order fail orderid:%v", order.ID) + return errors.New("update order fail") + } + return nil +} + +func (p *PayWithdraw) AutoWithdraw() error { + order := p.base.order + req := &pb.InnerWithdrawReq{ + OrderID: order.OrderID, + Amount: order.Amount, + UID: uint32(order.UID), + Channel: int64(order.UPI), + PaySource: uint32(order.PaySource), + } + if req.PaySource == common.PaySourceModulePay { + send := new(common.WithdrawCommon) + err := json.Unmarshal([]byte(order.PayAccount), &send) + if err != nil { + log.Error("withdraw unmarshal err %v", err) + return err + } + req.Phone = send.Mobile + req.Name = send.Name + req.Email = send.Email + req.PayType = int64(send.PayType) + req.Address = send.Address + req.Number = send.Number + } else { + req.Address = order.PayAccount + } + ret, err := call.Withdraw(req) + if ret != nil { + p.base.PayChannel = int(ret.Channel) + } + if err != nil { + return err + } + p.base.order.APIPayID = ret.APIOrderID + p.base.order.Status = common.StatusROrderPay + p.base.order.PayChannel = int(ret.Channel) + return nil +} diff --git a/modules/web/middleware/cross.go b/modules/web/middleware/cross.go new file mode 100644 index 0000000..057598c --- /dev/null +++ b/modules/web/middleware/cross.go @@ -0,0 +1,50 @@ +package middleware + +import ( + "fmt" + "net/http" + "server/modules/web/app" + + "github.com/gin-gonic/gin" +) + +// 跨域访问:cross origin resource share +func CrosHandler() gin.HandlerFunc { + return func(context *gin.Context) { + fmt.Printf("%+v\n", *context.Request) + method := context.Request.Method + origin := context.Request.Header.Get("Origin") //请求头部 + if origin != "" { + //接收客户端发送的origin (重要!) + context.Writer.Header().Set("Access-Control-Allow-Origin", "*") + + // 设置允许访问所有域 + context.Header("Access-Control-Allow-Origin", "*") + + //服务器支持的所有跨域请求的方法 + context.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE") + + //允许跨域设置可以返回其他子段,可以自定义字段 + context.Header("Access-Control-Allow-Headers", "fbc,fbp,platform,referrer,lang,share,channel,uuid,version,Authorization, Content-Length, X-CSRF-Token, Token,session,X_Requested_With,Accept, Origin, Host, Connection, Accept-Encoding, Accept-Language,DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Pragma,token,openid,opentoken") + + // 允许浏览器(客户端)可以解析的头部 (重要) + context.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar") + + //设置缓存时间 + context.Header("Access-Control-Max-Age", "172800") + + //允许客户端传递校验信息比如 cookie (重要) + context.Header("Access-Control-Allow-Credentials", "true") + + // 设置返回格式是json + context.Set("content-type", "application/json") + } + + if method == "OPTIONS" { + context.JSON(http.StatusOK, app.R{Code: http.StatusOK, Data: "Options Request!"}) + } + + //处理请求 + context.Next() + } +} diff --git a/modules/web/middleware/ratelimit.go b/modules/web/middleware/ratelimit.go new file mode 100644 index 0000000..785ee24 --- /dev/null +++ b/modules/web/middleware/ratelimit.go @@ -0,0 +1,50 @@ +package middleware + +import ( + "server/common" + "server/db" + "server/modules/web/app" + "time" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" + "golang.org/x/time/rate" +) + +const ( + UserLimitPerSec = 10 +) + +func RateLimitMiddleware() gin.HandlerFunc { + l := rate.NewLimiter(5000, 20000) + return func(c *gin.Context) { + if !l.Allow() { + c.Abort() + return + } + c.Next() + } +} + +func UserLimitMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + a := app.NewApp(c) + ip := a.GetRemoteIP() + key := common.GetRedisLockKeyIP(ip) + if db.Redis().Exist(key) { + res, err := db.Redis().Incr(key, 1) + if err != nil { + log.Error("err:%v", err) + c.Abort() + return + } + if res > UserLimitPerSec { + c.Abort() + return + } + } else { + db.Redis().SetData(key, 0, time.Second) + } + c.Next() + } +} diff --git a/modules/web/middleware/token.go b/modules/web/middleware/token.go new file mode 100644 index 0000000..241453d --- /dev/null +++ b/modules/web/middleware/token.go @@ -0,0 +1,123 @@ +package middleware + +import ( + "server/call" + "server/common" + "server/db" + "server/modules/web/app" + "server/modules/web/values" + "server/util" + "strconv" + "strings" + + "github.com/gin-gonic/gin" +) + +var ( + passURLs = map[string]struct{}{ + "/firstpage": {}, + "/game/list": {}, + "/sys/config": {}, + "/account/email/code": {}, + "/account/email/regist": {}, + "/account/email/login": {}, + "/account/email/resetPass": {}, + "/account/guestLogin": {}, + "/account/gpLogin": {}, + "/account/fbLogin": {}, + "/account/tokenLogin": {}, + "/account/phoneCode/get": {}, + "/account/phoneCode/verify": {}, + "/account/phoneCode/regist": {}, + "/account/phoneCode/login": {}, + "/share/upload": {}, + "/share/config": {}, + "/game/enter": {}, + "/activity/appSpin/info": {}, + "/activity/pdd/info": {}, + "/account/phone/regist": {}, + "/account/phone/login": {}, + "/account/phone/resetPass": {}, + "/balance/recharge/info": {}, + "/share/info": {}, + "/vip/info": {}, + "/share/reference": {}, + "/share/report": {}, + "/share/transfer": {}, + "/task/info": {}, + "/activity/freeSpin/info": {}, + "/promotions": {}, + "/tg/luckyCode": {}, + "/activity/sign/info": {}, + "/ad/uploadFB": {}, + "/activity/slots/info": {}, + } +) + +// 进行token校验 +func TokenMiddleWare() gin.HandlerFunc { + return func(c *gin.Context) { + path := c.Request.RequestURI + if PassURL(path) { + c.Next() + return + } + token := c.GetHeader("token") + a := app.NewApp(c) + uid, _ := db.Redis().GetInt(common.GetRedisKeyToken(token)) + if uid == 0 { + a.Code = values.CodeToken + a.Response() + c.Abort() + return + } + c.Set("uid", uid) + c.Set("token", token) + c.Set("referrer", c.GetHeader("referrer")) + util.Go(func() { + db.Redis().AddUserExpire(uid, token) + }) + c.Next() + } +} + +func SetToken(c *gin.Context) { + +} + +// PassURL 过滤url +func PassURL(path string) bool { + index := strings.Index(path, "?") + if index > 0 { + path = path[:index] + } + _, ok := passURLs[path] + if !ok { + // index := strings.LastIndex(path, "/") + // url := path[:index+1] + "*" + // _, ok = passURLs[url] + for k := range passURLs { + if strings.Contains(path, k) { + return true + } + } + } + return ok +} + +// WhiteMiddleWare 白名单验证 +func WhiteMiddleWare() gin.HandlerFunc { + return func(c *gin.Context) { + a := app.NewApp(c) + ver := c.GetHeader("version") + version, _ := strconv.Atoi(ver) + ip := a.GetRemoteIP() + if !call.WhitePass(ip, a.UUID, c.Request.RequestURI, version, a.Channel) { + a.Code = values.CodeServer + a.Response() + c.Abort() + return + } + c.Next() + } +} diff --git a/modules/web/middleware/whiteip.go b/modules/web/middleware/whiteip.go new file mode 100644 index 0000000..9514275 --- /dev/null +++ b/modules/web/middleware/whiteip.go @@ -0,0 +1,73 @@ +package middleware + +import ( + "server/call" + "server/modules/web/app" + "server/modules/web/values" + "strconv" + "strings" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +// 白名单验证 +func WhiteIPs() gin.HandlerFunc { + return func(c *gin.Context) { + a := app.NewApp(c) + ip := a.GetRemoteIP() + uri := c.Request.RequestURI + tmp := strings.ReplaceAll(uri, "/provider", "") + tmpSub := strings.Split(tmp, "/") + if len(tmpSub) < 2 { + c.Next() + return + } + path := tmpSub[1] + provider := call.GetConfigGameProviderByPath(path) + if provider == nil { + c.Next() + return + } + if len(provider.SubIp) == 0 { + c.Next() + return + } + for _, v := range provider.SubIp { + ips := strings.Split(v, "-") + // 说明是固定ip + if len(ips) == 1 { + if ip == v { + c.Next() + return + } + continue + } + // 配置的是ip段的情况 + ip0 := strings.Split(ips[0], ".") + ip1 := strings.Split(ips[1], ".") + thisIP := strings.Split(ip, ".") + same := true + for i := 0; i < len(thisIP)-1; i++ { + if thisIP[i] != ip0[i] { + same = false + break + } + } + if !same { + continue + } + last0, _ := strconv.Atoi(ip0[len(ip0)-1]) + last1, _ := strconv.Atoi(ip1[len(ip1)-1]) + thisLast, _ := strconv.Atoi(thisIP[len(thisIP)-1]) + if thisLast >= last0 && thisLast <= last1 { + c.Next() + return + } + } + log.Debug("ip:%v,provider:%v,path:%v", ip, provider, uri) + a.Code = values.CodeServer + a.Response() + c.Abort() + } +} diff --git a/modules/web/module.go b/modules/web/module.go new file mode 100644 index 0000000..1680093 --- /dev/null +++ b/modules/web/module.go @@ -0,0 +1,142 @@ +package web + +import ( + "context" + "math/rand" + "net/http" + "server/call" + "server/config" + "server/db" + edb "server/db/es" + mdb "server/db/mysql" + rdb "server/db/redis" + "server/modules/web/routers" + "server/util" + "time" + + "github.com/liangdas/mqant/conf" + "github.com/liangdas/mqant/log" + "github.com/liangdas/mqant/module" + basemodule "github.com/liangdas/mqant/module/base" +) + +var Module = func() module.Module { + this := new(Web) + return this +} + +type Web struct { + basemodule.BaseModule + //addr string + + httpSvr *http.Server + // httpSvr2 *http.Server +} + +func (w *Web) GetType() string { + //很关键,需要与配置文件中的Module配置对应 + return "web" +} +func (w *Web) Version() string { + //可以在监控时了解代码版本 + return "1.0.0" +} +func (w *Web) OnInit(app module.App, settings *conf.ModuleSettings) { + w.BaseModule.OnInit(w, app, settings) + + db.InitDB(&rdb.RedisClient{}, &edb.EsClient{}, &mdb.MysqlClient{}) + log.Info("[%v]module init finish, config:%+v", w.GetType(), config.GetConfig().Web) + log.Info("[%v]module init finish, base:%+v", w.GetType(), config.GetBase()) + + // 自动初始化数据库 + // MigrateDB() + + call.NewCaller(w) + + call.NewSnowflake(int64(config.GetConfig().WorkID)) + + // 创建一个随机数生成器 + rand.Seed(time.Now().UnixNano()) + + // 后台改配置 + if err := loadConfig(); err != nil { + log.Error("err:%v", err) + panic(err) + } + + call.InitReload(w.App.Transport()) + if err := call.LoadIpDB(); err != nil { + log.Error("err:%v", err) + panic(err) + } + + // 拉取缓存数据 + util.Go(func() { + FetchDatas() + }) +} + +func (wb *Web) startHttpServer() { + webcfg := config.GetConfig().Web + router := routers.SetUpRouter() + srv := &http.Server{ + Addr: webcfg.Addr, + Handler: router, + } + + go func() { + if config.GetConfig().Web.TLS { + if err := srv.ListenAndServeTLS(webcfg.CertFile, webcfg.KeyFile); err != nil { + log.Error("web ListenAndServeTLS fail error:%v", err) + } + } else { + if err := srv.ListenAndServe(); err != nil { + log.Error("web ListenAndServe fail error:%v", err) + } + } + + }() + // srv2 := &http.Server{ + // Addr: ":80", + // Handler: router, + // } + // go func() { + // if err := srv2.ListenAndServe(); err != nil { + // log.Error("web ListenAndServe fail error:%v", err) + // } + // }() + // returning reference so caller can call Shutdown() + wb.httpSvr = srv + // wb.httpSvr2 = srv2 +} + +func (w *Web) Run(closeSig chan bool) { + log.Info("web: starting HTTP server :%s", config.GetConfig().Web.Addr) + w.startHttpServer() + <-closeSig + log.Info("web: stopping HTTP server") + +} + +func (w *Web) OnDestroy() { + // The context is used to inform the server it has 5 seconds to finish + // the request it is currently handling + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + // now close the server gracefully ("shutdown") + // timeout could be given instead of nil as a https://golang.org/pkg/context/ + if err := w.httpSvr.Shutdown(ctx); err != nil { + log.Error("OnDestroy web Shutdown error:%v", err) + } + // if err := w.httpSvr2.Shutdown(ctx); err != nil { + // log.Error("OnDestroy web Shutdown error:%v", err) + // } + + log.Info("web: done. exiting") + + //一定别忘了继承 + w.BaseModule.OnDestroy() + + log.Info("web 模块已销毁") +} diff --git a/modules/web/providers/all/all.go b/modules/web/providers/all/all.go new file mode 100644 index 0000000..a1847c5 --- /dev/null +++ b/modules/web/providers/all/all.go @@ -0,0 +1,130 @@ +package all + +import ( + "reflect" + "server/call" + "server/common" + "server/db" + "server/modules/web/providers/awc" + "server/modules/web/providers/base" + "server/modules/web/providers/gs" + "server/modules/web/providers/pgsoft" + "server/modules/web/providers/tada" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +type AllProvider struct { + Invalid struct{} + InHouse func(b *base.Base) + Tada func(b *base.Base) + Sexy func(b *base.Base) + PGSoft func(b *base.Base) + EvolutionGaming func(b *base.Base) + AllBet func(b *base.Base) + BigGaming func(b *base.Base) + SAGaming func(b *base.Base) + PragmaticPlay func(b *base.Base) + CQ9 func(b *base.Base) + PlayTech func(b *base.Base) + Joker func(b *base.Base) + DragonSoft func(b *base.Base) + TFGaming func(b *base.Base) + WMCasino func(b *base.Base) + King855 func(b *base.Base) + AMAYA func(b *base.Base) + Habanero func(b *base.Base) + IBC func(b *base.Base) + Reevo func(b *base.Base) + EvoPlay func(b *base.Base) + PlayStar func(b *base.Base) + DreamGaming func(b *base.Base) + Nexus4D func(b *base.Base) + SlotXo func(b *base.Base) + BTI func(b *base.Base) + Ezugi func(b *base.Base) +} + +var All = &AllProvider{} + +func initAll() { + All.Tada = tada.NewSub + All.Sexy = awc.NewSub + pg := call.GetConfigGameProvider(common.ProviderPGSoft) + if pg != nil && pg.Callback == "gs" { + All.PGSoft = gs.NewSub + } else { + All.PGSoft = pgsoft.NewSub + } + All.EvolutionGaming = gs.NewSub + All.AllBet = gs.NewSub + All.BigGaming = gs.NewSub + All.SAGaming = gs.NewSub + All.PragmaticPlay = gs.NewSub + All.CQ9 = gs.NewSub + All.PlayTech = gs.NewSub + All.Joker = gs.NewSub + All.DragonSoft = gs.NewSub + All.TFGaming = gs.NewSub + All.WMCasino = gs.NewSub + All.King855 = gs.NewSub + All.AMAYA = gs.NewSub + All.Habanero = gs.NewSub + All.IBC = gs.NewSub + All.Reevo = gs.NewSub + All.EvoPlay = gs.NewSub + All.PlayStar = gs.NewSub + All.DreamGaming = gs.NewSub + All.Nexus4D = gs.NewSub + All.SlotXo = gs.NewSub + All.BTI = gs.NewSub + All.Ezugi = gs.NewSub +} + +func InitRouter(r *gin.RouterGroup) { + initAll() + pathMap := map[string]struct{}{} + for i := common.ProviderZero + 2; i < common.ProviderAll; i++ { + b := &base.Base{Provider: call.GetConfigGameProvider(i)} + if b.Provider == nil { + continue + } + path := b.Provider.Callback + if path == "" { + path = b.Provider.ProviderName + } + if _, ok := pathMap[path]; ok { + continue + } + pathMap[path] = struct{}{} + NewSub(b, i) + b.Sub.Init() + sub := r.Group(path) + b.SubInitRouter(sub) + } +} + +func EnterGame(req *base.EnterGameReq) string { + provider := call.GetConfigGameProvider(req.ProviderID) + if provider == nil { + return "" + } + if err := db.Redis().SetData(common.GetRedisKeyGameCurrency(req.UID), int(req.CurrencyType), common.RedisExpireGameEnter); err != nil { + log.Error("err:%v", err) + return "" + } + b := &base.Base{Provider: provider} + b.EnterGameReq = req + NewSub(b, req.ProviderID) + return b.Sub.EnterGame() +} + +func NewSub(b *base.Base, index int) { + ref := reflect.ValueOf(All).Elem().Field(index) + if !ref.IsValid() { + log.Error("invalid index:%v", index) + return + } + ref.Call([]reflect.Value{reflect.ValueOf(b)}) +} diff --git a/modules/web/providers/awc/api.go b/modules/web/providers/awc/api.go new file mode 100644 index 0000000..55331ad --- /dev/null +++ b/modules/web/providers/awc/api.go @@ -0,0 +1,62 @@ +package awc + +import ( + "fmt" + "server/call" + "server/common" + "server/config" + "server/util" + "strings" + + "github.com/liangdas/mqant/log" +) + +type CreateMemberReq struct { + Cert string `json:"cert"` + AgentId string `json:"agentId"` + UserId string `json:"userId"` + Currency string `json:"currency"` + BetLimit string `json:"betLimit"` // {"SEXYBCRT":{"LIVE":{"limitId":[110901,110902]}}} + Language string `json:"language"` // en + UserName string `json:"userName"` +} + +func CreateMember(uid int, ct common.CurrencyType) bool { + ctName := strings.ToUpper(ct.GetCurrencyName()) + if ctName == "USDT" { + ctName = "USD" + } + p, _ := call.GetUserXInfo(uid, "nick") + req := &CreateMemberReq{ + Cert: Cert, + AgentId: AgentID, + UserId: fmt.Sprintf("%v%s", uid, ct.GetCurrencyName()), + Currency: ctName, + // BetLimit: , + Language: "en", + UserName: p.Nick, + } + if ct == common.CurrencyBrazil { + if !config.GetBase().Release { + req.BetLimit = `{"SEXYBCRT":{"LIVE":{"limitId":[284901,284902,284903]}}}` + } else { + req.BetLimit = `{"SEXYBCRT":{"LIVE":{"limitId":[154902,154903,154904]}}}` + } + } else { + if !config.GetBase().Release { + req.BetLimit = `{"SEXYBCRT":{"LIVE":{"limitId":[280701,280702,280703]}}}` + } else { + req.BetLimit = `{"SEXYBCRT":{"LIVE":{"limitId":[150704,150705,150703]}}}` + } + } + url := API + CreateAPI + // reqURL := fmt.Sprintf("%s?Token=%s&GameId=%d&Lang=%s&AgentId=%s&Key=%s", url, token, gameid, reqLang, AgentID, GetSignKey(req)) + log.Debug("sexy CreateMember:%+v,url:%s", *req, url) + ret := new(CommonResp) + util.HttpPostForm(url, req, &ret, nil) + log.Debug("sexy CreateMember resp:%+v", ret) + // if str, ok := ret.Data.(string); ok { + // return str + // } + return ret.Status == "0000" +} diff --git a/modules/web/providers/awc/base.go b/modules/web/providers/awc/base.go new file mode 100644 index 0000000..9f30f0c --- /dev/null +++ b/modules/web/providers/awc/base.go @@ -0,0 +1,104 @@ +package awc + +import ( + "fmt" + "server/common" + "server/config" + "server/modules/web/providers/base" + "server/util" + + "github.com/liangdas/mqant/log" +) + +type Sub struct { + Base *base.Base +} + +func NewSub(base *base.Base) { + base.Sub = &Sub{Base: base} + base.SubInitRouter = AWC +} + +func (s *Sub) Init() { + API = APITest + AgentID = AgentIDTest + Cert = CertTest + APIKey = APIKeyTest + BetLimitBrl = BetLimitBrlTest + BetLimitUsdt = BetLimitUsdtTest + if config.GetBase().Release { + API = APIRlease + AgentID = AgentIDRelease + Cert = CertRelease + APIKey = APIKeyRelease + BetLimitBrl = BetLimitBrlRelease + BetLimitUsdt = BetLimitUsdtRelease + } +} + +type EnterGameReq struct { + Cert string `json:"cert"` + AgentId string `json:"agentId"` + UserId string `json:"userId"` + IsMobileLogin string `json:"isMobileLogin"` + ExternalURL string `json:"externalURL"` + Platform string `json:"platform"` // SEXYBCRT + GameType string `json:"gameType"` // LIVE + GameCode string `json:"gameCode"` + Hall string `json:"hall"` + Language string `json:"language"` // en + BetLimit string `json:"betLimit"` // {"SEXYBCRT":{"LIVE":{"limitId":[110901,110902]}}} + AutoBetMode string `json:"autoBetMode"` // 1开启自动下注 0隐藏 + IsLaunchGameTable bool `json:"isLaunchGameTable"` // 进入指定桌子 true + GameTableId string `json:"gameTableId"` // 桌子id +} + +func (s *Sub) EnterGame() string { + uid := s.Base.EnterGameReq.UID + ct := s.Base.EnterGameReq.CurrencyType + gameid := s.Base.EnterGameReq.GameID + subID := s.Base.EnterGameReq.SubID + // lang := s.Base.EnterGameReq.Lang + req := &EnterGameReq{ + Cert: Cert, + AgentId: AgentID, + UserId: fmt.Sprintf("%v%s", uid, ct.GetCurrencyName()), + IsMobileLogin: "true", + ExternalURL: "https://www.riowhale.com", + Platform: "SEXYBCRT", + GameType: "LIVE", + GameCode: fmt.Sprintf("MX-LIVE-%03d", gameid), + Hall: "SEXY", + Language: "en", + // BetLimit: , + AutoBetMode: "1", + // IsLaunchGameTable: true, + // GameTableId: fmt.Sprintf("%d", subID), + } + if subID > 0 { + req.IsLaunchGameTable = true + req.GameTableId = fmt.Sprintf("%d", subID) + } + if ct == common.CurrencyBrazil { + req.BetLimit = BetLimitBrl + } else { + req.BetLimit = BetLimitUsdt + } + url := API + LoginAPI + // reqURL := fmt.Sprintf("%s?Token=%s&GameId=%d&Lang=%s&AgentId=%s&Key=%s", url, token, gameid, reqLang, AgentID, GetSignKey(req)) + log.Debug("sexy EnterGame:%+v,url:%s", *req, url) + ret := new(CommonResp) + util.HttpPostForm(url, req, &ret, nil) + log.Debug("sexy EnterGame resp:%+v", ret) + if ret.Status == "1002" { // 账号不存在,先创建 + if CreateMember(uid, ct) { + return s.EnterGame() // 再调一次进入游戏 + } else { + return "" + } + } + // if str, ok := ret.Data.(string); ok { + // return str + // } + return ret.URL +} diff --git a/modules/web/providers/awc/handler.go b/modules/web/providers/awc/handler.go new file mode 100644 index 0000000..cbf2ff7 --- /dev/null +++ b/modules/web/providers/awc/handler.go @@ -0,0 +1,510 @@ +package awc + +import ( + "encoding/json" + "fmt" + "server/call" + "server/common" + "server/db" + "server/modules/web/app" + "server/modules/web/providers/base" + "server/natsClient" + "server/pb" + "server/util" + "time" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +func AWC(e *gin.RouterGroup) { + e.POST("", Callback) +} + +func Callback(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.ResponseB() + }() + key := c.PostForm("key") + message := c.PostForm("message") + log.Debug("awc callback key:%v,message:%v", key, message) + if key != APIKey { + return + } + // message, err := strconv.Unquote(message) + // if err != nil { + // log.Error("err:%v", err) + // } + a.TmpData = message + + act := new(Action) + json.Unmarshal([]byte(message), act) + + switch act.Action { + case "getBalance": + uid, ct := base.GetUIDAndCurrency(act.UserId) + if uid <= 0 || !ct.IsValid() { + a.RetData = &CommonResp{Status: "1002"} + return + } + a.UID = uid + a.TmpData = ct + getBalance(a) + case "bet": + placeBet(a) + case "cancelBet": + cancelBet(a) + case "voidBet": + cancelBet(a) + case "settle": + settle(a) + case "voidSettle": + viodSettle(a) + case "give": + give(a) + case "resettle": + resettle(a) + default: + a.RetData = &CommonResp{Status: "1036"} + return + } +} + +func getBalance(a *app.Gin) { + resp := &GetBalanceResp{} + a.RetData = resp + // currency, err := db.Redis().GetInt(common.GetRedisKeyGameCurrency(a.UID)) + // if err != nil { + // log.Error("err:%v", err) + // resp.Status = "1036" + // return + // } + resp.Status = "0000" + resp.Balance = call.GetUserCurrencyFloat(a.UID, a.TmpData.(common.CurrencyType), 4) + resp.UserId = fmt.Sprintf("%v", a.UID) + resp.BalanceTs = time.Now().Format(TimeFormatString) +} + +func placeBet(a *app.Gin) { + req := new(PlaceBetReq) + message := a.TmpData.(string) + resp := &PlaceBetResp{} + a.RetData = resp + err := json.Unmarshal([]byte(message), req) + if err != nil { + log.Error("err:%v", err) + resp.Status = "1036" + return + } + // log.Debug("placeBet:%+v", req) + now := time.Now().Unix() + var totalBet int64 + var uid int + var ct common.CurrencyType + myUUID := call.SnowNode().Generate().Int64() + for _, v := range req.Txns { + uid = util.GetIntFromString(v.UserId) + if uid <= 0 { + resp.Status = "1036" + return + } + ctName := v.Currency + if ctName == "USD" { + ctName = "USDT" + } + ct = common.GetCurrencyID(ctName) + if !ct.IsValid() { + log.Debug("unknown ct:%v", ct) + resp.Status = "1036" + return + } + gameID := 0 + var provider *common.ConfigGameProvider + if v.Platform == "SEXYBCRT" { + fmt.Sscanf(v.GameCode, "MX-LIVE-%d", &gameID) + provider = call.GetConfigGameProvider(common.ProviderSexy) + } + var betTime int64 + t, err := time.Parse(TimeFormatString, v.BetTime) + if err != nil { + betTime = now + } else { + betTime = t.Unix() + } + betAmount := common.CashFloat64ToInt64(v.BetAmount) + record := &common.ProviderBetRecord{ + UID: uid, + Provider: provider.ProviderID, + UUID: v.PlatformTxId, + } + db.Mysql().Get(record) + if record.ID > 0 { + resp.Status = "1036" + return + } + if err := db.Mysql().Create(&common.ProviderBetRecord{ + UID: uid, + Provider: provider.ProviderID, + Currency: ct.GetCurrencyName(), + CurrencyType: ct, + GameID: gameID, + UUID: v.PlatformTxId, + MyUUID: myUUID, + Type: common.SessionTypeBet, + Time: now, + ProviderTime: betTime, + Amount: betAmount, + SessionID: v.RoundId, + // TurnOver: req.TurnOver, + // Preserve: req.Preserve, + Esi: base.SessionSuccess, + }); err != nil { + log.Error("err:%v", err) + resp.Status = "1036" + return + } + totalBet += betAmount + // log.Debug("betResp:%+v", betResp) + } + pro := call.MineCurrencyProReal( + &common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: uid, + Type: ct, + Event: common.CurrencyEventGameBet, + Value: -totalBet, + Exs1: fmt.Sprintf("%d", myUUID), + // Exs2: req.BetID, + // Exs3: req.SessionID, + }, + }, + ) + if pro.Err != nil { + log.Error("err:%v", pro.Err) + resp.Status = "1036" + if pro.Err == call.ErrNotEnoughBalance { + resp.Status = "1018" + } + return + } + resp.Status = "0000" + resp.Balance = util.Decimal(float64(pro.Balance)/common.DecimalDigits, 4) + resp.BalanceTs = time.Now().Format(TimeFormatString) +} + +func cancelBet(a *app.Gin) { + req := new(CancelBetReq) + message := a.TmpData.(string) + resp := &CancelBetResp{} + a.RetData = resp + err := json.Unmarshal([]byte(message), req) + if err != nil { + log.Error("err:%v", err) + resp.Status = "1036" + return + } + now := time.Now().Unix() + var betResp base.BetResp + for _, v := range req.Txns { + uid := util.GetIntFromString(v.UserId) + if uid <= 0 { + resp.Status = "1036" + return + } + gameID := 0 + var provider *common.ConfigGameProvider + if v.Platform == "SEXYBCRT" { + fmt.Sscanf(v.GameCode, "MX-LIVE-%d", &gameID) + provider = call.GetConfigGameProvider(common.ProviderSexy) + } + var betTime int64 + t, err := time.Parse(TimeFormatString, v.BetTime) + if err != nil { + betTime = now + } else { + betTime = t.Unix() + } + betResp = base.CancelSessionBet(&base.BetReq{ + UID: uid, + SessionType: common.SessionTypeBet, + GameID: gameID, + Provider: provider, + BetID: v.PlatformTxId, + SessionID: v.RoundId, + Time: betTime, + VoidType: v.VoidType, + }) + } + resp.Status = "0000" + resp.Balance = util.Decimal(float64(betResp.Balance)/common.DecimalDigits, 4) + resp.BalanceTs = time.Now().Format(TimeFormatString) +} + +type SettleUser struct { + UID int + TotalSettle int64 + TotalBet int64 + TotalTurnover int64 +} + +func settle(a *app.Gin) { + req := new(SettleReq) + message := a.TmpData.(string) + resp := &SettleResp{} + a.RetData = resp + err := json.Unmarshal([]byte(message), req) + if err != nil { + log.Error("err:%v", err) + resp.Status = "1036" + return + } + now := time.Now().Unix() + var settleResp base.SettleResp + var uid int + var ct common.CurrencyType + myUUID := call.SnowNode().Generate().Int64() + provider := new(common.ConfigGameProvider) + gameID := 0 + uuid := "" + settles := map[int]SettleUser{} + for _, v := range req.Txns { + uid = util.GetIntFromString(v.UserId) + if uid <= 0 { + resp.Status = "1036" + return + } + if v.Platform == "SEXYBCRT" { + fmt.Sscanf(v.GameCode, "MX-LIVE-%d", &gameID) + provider = call.GetConfigGameProvider(common.ProviderSexy) + } + var settleTime int64 + t, err := time.Parse(TimeFormatString, v.UpdateTime) + if err != nil { + settleTime = now + } else { + settleTime = t.Unix() + } + record := &common.ProviderBetRecord{ + UID: uid, + Provider: provider.ProviderID, + UUID: v.PlatformTxId, + Type: common.SessionTypeBet, + } + db.Mysql().Get(record) + if record.ID == 0 { + resp.Status = "1036" + return + } + // 重复结算,忽略 + if db.Mysql().Exist(&common.ProviderBetRecord{ + UID: uid, + Provider: provider.ProviderID, + UUID: v.PlatformTxId, + Type: common.SessionTypeSettle, + }) { + continue + } + ct = record.CurrencyType + uuid = v.PlatformTxId + settleAmount := common.CashFloat64ToInt64(v.WinAmount) + betAmount := common.CashFloat64ToInt64(v.BetAmount) + turnOver := common.CashFloat64ToInt64(v.TurnOver) + if err := db.Mysql().Create(&common.ProviderBetRecord{ + UID: uid, + Provider: provider.ProviderID, + Currency: ct.GetCurrencyName(), + CurrencyType: ct, + GameID: gameID, + UUID: v.PlatformTxId, + Type: common.SessionTypeSettle, + Time: now, + ProviderTime: settleTime, + Amount: betAmount, + SessionID: v.RoundId, + MyUUID: myUUID, + Settle: settleAmount, + Esi: base.SessionSuccess, + }); err != nil { + log.Error("err:%v", err) + resp.Status = "1036" + return + } + user, ok := settles[uid] + if ok { + user.TotalBet += betAmount + user.TotalSettle += settleAmount + user.TotalTurnover += turnOver + settles[uid] = user + } else { + settles[uid] = SettleUser{ + TotalBet: betAmount, + TotalSettle: settleAmount, + TotalTurnover: turnOver, + } + } + } + for i, v := range settles { + thisUID := i + if v.TotalSettle != 0 { + pro := call.UpdateCurrencyProReal( + &common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: thisUID, + Type: ct, + Event: common.CurrencyEventGameSettle, + Value: v.TotalSettle, + Exs1: fmt.Sprintf("%d", myUUID), + // Exs2: req.BetID, + // Exs3: req.SessionID, + }, + }, + ) + if pro.Err != nil { + log.Error("err:%v", pro.Err) + resp.Status = "1036" + return + } + // balance = pro.Balance + } + + util.IndexTryS(func() error { + return call.Publish(natsClient.TopicInnerAfterSettle, &pb.InnerAfterSettle{ + UID: int64(thisUID), + ProviderID: int64(provider.ProviderID), + GameID: int64(gameID), + UUID: uuid, + CurrencyType: int64(ct), + TotalBet: v.TotalTurnover, + OriginSettle: v.TotalSettle, + FinalSettle: v.TotalSettle, + MyUUID: fmt.Sprintf("%d", myUUID), + }) + }) + } + resp.Status = "0000" + if settleResp.Code != base.CodeOk { + resp.Status = "9999" + } +} + +func viodSettle(a *app.Gin) { + req := new(SettleReq) + message := a.TmpData.(string) + resp := &SettleResp{} + a.RetData = resp + err := json.Unmarshal([]byte(message), req) + if err != nil { + log.Error("err:%v", err) + resp.Status = "1036" + return + } + // var settleResp base.SettleResp + for _, v := range req.Txns { + uid := util.GetIntFromString(v.UserId) + if uid <= 0 { + resp.Status = "1036" + return + } + gameID := 0 + var provider *common.ConfigGameProvider + if v.Platform == "SEXYBCRT" { + fmt.Sscanf(v.GameCode, "MX-LIVE-%d", &gameID) + provider = call.GetConfigGameProvider(common.ProviderSexy) + } + base.VoidSettle(&base.VoidSettleReq{ + UID: uid, + Provider: provider, + BetID: v.PlatformTxId, + }) + } + resp.Status = "0000" +} + +func give(a *app.Gin) { + req := new(GiveReq) + message := a.TmpData.(string) + resp := &GiveResp{} + a.RetData = resp + err := json.Unmarshal([]byte(message), req) + if err != nil { + log.Error("err:%v", err) + resp.Status = "1036" + return + } + now := time.Now().Unix() + // var settleResp base.SettleResp + for _, v := range req.Txns { + uid := util.GetIntFromString(v.UserId) + if uid <= 0 { + resp.Status = "1036" + return + } + ctName := v.Currency + if ctName == "USD" { + ctName = "USDT" + } + ct := common.GetCurrencyID(ctName) + if !ct.IsValid() { + resp.Status = "1036" + return + } + var provider *common.ConfigGameProvider + if v.Platform == "SEXYBCRT" { + provider = call.GetConfigGameProvider(common.ProviderSexy) + } + var txTime int64 + t, err := time.Parse(TimeFormatString, v.TxTime) + if err != nil { + txTime = now + } else { + txTime = t.Unix() + } + base.Adjustment(&base.AdjustmentReq{ + Time: txTime, + UID: uid, + CurrencyType: ct, + Amount: common.CashFloat64ToInt64(v.Amount), + Provider: provider, + BetID: v.PromotionTxId + v.PromotionId, + Ess: v.PromotionTypeId, + Type: common.SessionTypeActivity, + }) + } + resp.Status = "0000" +} + +func resettle(a *app.Gin) { + req := new(SettleReq) + message := a.TmpData.(string) + resp := &SettleResp{} + a.RetData = resp + err := json.Unmarshal([]byte(message), req) + if err != nil { + log.Error("err:%v", err) + resp.Status = "1036" + return + } + var settleResp base.ReSettleResp + for _, v := range req.Txns { + uid := util.GetIntFromString(v.UserId) + if uid <= 0 { + resp.Status = "1036" + return + } + var provider *common.ConfigGameProvider + if v.Platform == "SEXYBCRT" { + provider = call.GetConfigGameProvider(common.ProviderSexy) + } + settleResp = base.Resettle(&base.ReSettleReq{ + UID: uid, + SettleAmount: common.CashFloat64ToInt64(v.WinAmount), + Provider: provider, + BetID: v.PlatformTxId, + }) + } + resp.Status = "0000" + if settleResp.Code != base.CodeOk { + resp.Status = "9999" + } +} diff --git a/modules/web/providers/awc/sign.go b/modules/web/providers/awc/sign.go new file mode 100644 index 0000000..fbe295b --- /dev/null +++ b/modules/web/providers/awc/sign.go @@ -0,0 +1,24 @@ +package awc + +// func GetKeyG() string { +// key := TestKey +// if config.GetBase().Release { +// key = AgentKey +// } +// return util.CalculateMD5(time.Now().UTC().Add(-4*time.Hour).Format("06012") + AgentID + key) +// } + +// func GetSignKey(req interface{}) string { +// keyG := GetKeyG() +// str := "" +// ref := reflect.ValueOf(req) +// reft := reflect.TypeOf(req) +// if reft.Kind() == reflect.Ptr { +// ref = ref.Elem() +// reft = reft.Elem() +// } +// for i := 0; i < ref.NumField(); i++ { +// str += fmt.Sprintf("%s=%v&", reft.Field(i).Name, ref.Field(i).Interface()) +// } +// return util.GenerateRandomString(6) + util.CalculateMD5(fmt.Sprintf("%sAgentId=%s", str, AgentID)+keyG) + util.GenerateRandomString(6) +// } diff --git a/modules/web/providers/awc/values.go b/modules/web/providers/awc/values.go new file mode 100644 index 0000000..97dbd65 --- /dev/null +++ b/modules/web/providers/awc/values.go @@ -0,0 +1,136 @@ +package awc + +const ( + APIRlease = "https://fsaap.velkigames365.cc" + APITest = "https://tttint.onlinegames22.com" + AgentIDTest = "whalegameagt" + AgentIDRelease = "whalegameag" + CertTest = "7bPxMIkZiSI0ljH0j6A" + CertRelease = "OEyCB2Ah41hMwEMXtjN" + APIKeyTest = "SyPUfvf7Q5d$qxGG" + APIKeyRelease = "OEyCB2Ah41hMwEMXtjN" + CreateAPI = "/wallet/createMember" + LoginAPI = "/wallet/doLoginAndLaunchGame" + // LoginAPI = "/wallet/login" + TimeFormatString = "2006-01-02T15:04:05.000-07:00" + + BetLimitBrlTest = `{"SEXYBCRT":{"LIVE":{"limitId":[284901,284902,284903]}}}` + BetLimitUsdtTest = `{"SEXYBCRT":{"LIVE":{"limitId":[280701,280702,280703]}}}` + BetLimitBrlRelease = `{"SEXYBCRT":{"LIVE":{"limitId":[154902,154903,154904]}}}` + BetLimitUsdtRelease = `{"SEXYBCRT":{"LIVE":{"limitId":[150703,150704,150705]}}}` +) + +var ( + API = "" + AgentID = "" + Cert = "" + APIKey = "" + BetLimitBrl = "" + BetLimitUsdt = "" +) + +type CommonResp struct { + Status string `json:"status"` + Desc string `json:"desc"` + URL string `json:"url"` +} + +type Action struct { + Action string `json:"action"` + UserId string `json:"userId"` +} + +type GetBalanceResp struct { + Status string `json:"status"` + UserId string `json:"userId"` + Balance float64 `json:"balance"` + BalanceTs string `json:"balanceTs"` +} + +type PlaceBetReq struct { + Txns []Txn `json:"txns"` +} + +type PlaceBetResp struct { + Status string `json:"status"` + Desc string `json:"desc"` + Balance float64 `json:"balance"` + BalanceTs string `json:"balanceTs"` +} + +type Txn struct { + PlatformTxId string `json:"platformTxId"` + UserId string `json:"userId"` + Currency string `json:"currency"` + Platform string `json:"platform"` + GameType string `json:"gameType"` + GameCode string `json:"gameCode"` + GameName string `json:"gameName"` + BetType string `json:"betType"` + BetAmount float64 `json:"betAmount"` + BetTime string `json:"betTime"` + RoundId string `json:"roundId"` + GameInfo interface{} `json:"gameInfo"` + VoidType int64 `json:"voidType"` +} + +type CancelBetReq struct { + Txns []Txn `json:"txns"` +} + +type CancelBetResp struct { + Status string `json:"status"` + Desc string `json:"desc"` + Balance float64 `json:"balance"` + BalanceTs string `json:"balanceTs"` +} + +type SettleReq struct { + Txns []SettleTxn `json:"txns"` +} + +type SettleResp struct { + Status string `json:"status"` + Desc string `json:"desc"` +} + +type SettleTxn struct { + PlatformTxId string `json:"platformTxId"` + UserId string `json:"userId"` + Currency string `json:"currency"` + Platform string `json:"platform"` + GameType string `json:"gameType"` + GameCode string `json:"gameCode"` + GameName string `json:"gameName"` + BetType string `json:"betType"` + BetAmount float64 `json:"betAmount"` + WinAmount float64 `json:"winAmount"` + TurnOver float64 `json:"turnover"` + BetTime string `json:"betTime"` + UpdateTime string `json:"updateTime"` + RoundId string `json:"roundId"` + GameInfo interface{} `json:"gameInfo"` + TxTime string `json:"txTime"` + SettleType string `json:"settleType"` + RefPlatformTxId string `json:"refPlatformTxId"` +} + +type GiveReq struct { + Txns []GiveTxn `json:"txns"` +} + +type GiveResp struct { + Status string `json:"status"` + Desc string `json:"desc"` +} + +type GiveTxn struct { + TxTime string `json:"txTime"` + Amount float64 `json:"amount"` + Currency string `json:"currency"` + PromotionTxId string `json:"promotionTxId"` + PromotionId string `json:"promotionId"` + PromotionTypeId string `json:"promotionTypeId"` + UserId string `json:"userId"` + Platform string `json:"platform"` +} diff --git a/modules/web/providers/base/base.go b/modules/web/providers/base/base.go new file mode 100644 index 0000000..0774e26 --- /dev/null +++ b/modules/web/providers/base/base.go @@ -0,0 +1,964 @@ +package base + +import ( + "fmt" + "server/call" + "server/common" + "server/db" + "server/natsClient" + "server/pb" + "server/util" + "time" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +const ( + CodeOk = iota + CodeNotEnoughAmount // 余额不足 + CodeAccepted // 重复下注 + CodeSettled // 已结算 + CodeInnerError // 内部错误 + CodeBetNotExist // 结算时,判断下注不存在 +) + +const ( + SessionSuccess = iota + 1 + SessionFail + SessionInvalid +) + +type Base struct { + Provider *common.ConfigGameProvider + SubInitRouter func(r *gin.RouterGroup) + SettleWithoutBet bool // 如果为true,代表结算的时候settle值为玩家实际加减值,不需要扣除下注 + Sub + *EnterGameReq +} + +type Sub interface { + Init() + EnterGame() string +} + +type EnterGameReq struct { + ProviderID int + GameID int + UID int + Token string + Lang string + CurrencyType common.CurrencyType + IsDemo bool + SubID int + IP string + DeviceType int // 设备类型 + ChannelID int +} + +type BetReq struct { + UID int + CurrencyType common.CurrencyType + SettleAmount int64 // 输赢 + BetAmount int64 + TurnOver int64 // 有效下注 + Preserve int64 // 预扣款 + SessionType int // 1下注 2结算 + GameID int + GameName string + Provider *common.ConfigGameProvider + BetID string // 注单唯一id + SessionID string // 轮次id + Time int64 + VoidType int64 // 注单无效原因 + // SettleWithoutBet bool // 如果为true,代表结算的时候settle值为玩家实际加减值,不需要扣除下注 +} + +type BetResp struct { + MyUUID int64 + Balance int64 // 余额 + BeforeBalance int64 // 余额 + Code int +} + +type SettleReq struct { + UID int + CurrencyType common.CurrencyType + SettleAmount int64 // 输赢(玩家实际加减的值) + BetAmount int64 + TurnOver int64 // 有效下注 + Preserve int64 // 预扣款 + GameID int + GameName string + Provider *common.ConfigGameProvider + BetID string // 注单唯一id + SessionID string // 轮次id + Time int64 + VoidType int64 // 注单无效原因 +} + +type SettleResp struct { + MyUUID int64 + Balance int64 // 余额 + Code int +} + +type VoidSettleReq struct { + UID int + Provider *common.ConfigGameProvider + BetID string // 注单唯一id +} + +type VoidSettleResp struct { + MyUUID int64 + Balance int64 // 余额 + BeforeBalance int64 // 余额 + Code int +} + +type ReSettleReq struct { + UID int + SettleAmount int64 // 输赢(玩家实际加减的值) + Provider *common.ConfigGameProvider + BetID string // 注单唯一id +} + +type ReSettleResp struct { + Balance int64 // 余额 + Code int +} + +// type ActivityGiftReq struct { +// UID int +// CurrencyType common.CurrencyType +// Amount int64 // 输赢(玩家实际奖励的值) +// GameID int +// Provider *common.ConfigGameProvider +// BetID string // 注单唯一id +// ActivityID string +// Time int64 +// GiftType int +// } + +// type ActivityGiftResp struct { +// MyUUID int64 +// Balance int64 // 余额 +// BeforeBalance int64 +// Code int +// } + +type AdjustmentReq struct { + UID int + CurrencyType common.CurrencyType + Amount int64 // 玩家实际加减的值 + GameID int + Provider *common.ConfigGameProvider + BetID string // 注单唯一id + GameName string + SessionID string // 回合id + // ActivityID string + Time int64 + Ess string + Type int +} + +type AdjustmentResp struct { + MyUUID int64 + Balance int64 // 余额 + BeforeBalance int64 + Code int +} + +type AdjustBetReq struct { + UID int + Provider *common.ConfigGameProvider + BetID string // 注单唯一id + AdjustAmount int64 +} + +type AdjustBetResp struct { + MyUUID int64 + Balance int64 // 余额 + BeforeBalance int64 + Code int +} + +type PushDetailReq struct { + UID int + CurrencyType common.CurrencyType + SettleAmount int64 // 输赢 + BetAmount int64 + TurnOver int64 // 有效下注 + Preserve int64 // 预扣款 + SessionType int // 1下注 2结算 + GameID int + GameName string + Provider *common.ConfigGameProvider + BetID string // 注单唯一id + SessionID string // 轮次id + Time int64 +} + +type PushDetailResp struct { + MyUUID int64 + Balance int64 // 余额 + Code int +} + +// slot类及时结算玩法,下注即刻结算 +func Bet(req *BetReq) (resp BetResp) { + betAmount := req.BetAmount + uid := req.UID + ct := req.CurrencyType + amount := call.GetUserCurrencyTotal(uid, ct) + if amount < betAmount { + resp.Balance = amount + resp.Code = CodeNotEnoughAmount + return + } + provider := req.Provider + record := &common.ProviderBetRecord{ + UID: uid, + Provider: provider.ProviderID, + UUID: req.BetID, + } + db.Mysql().Get(record) + if record.ID > 0 { + resp.MyUUID = record.MyUUID + resp.Code = CodeAccepted + return + } + settle := req.SettleAmount + uuid := call.SnowNode().Generate().Int64() + if err := db.Mysql().Create(&common.ProviderBetRecord{ + UID: uid, + Provider: provider.ProviderID, + Currency: ct.GetCurrencyName(), + CurrencyType: ct, + Type: common.SessionTypeSettle, + GameID: req.GameID, + GameName: req.GameName, + UUID: req.BetID, + MyUUID: uuid, + Time: time.Now().Unix(), + ProviderTime: req.Time, + Amount: betAmount, + Settle: settle, + Esi: SessionSuccess, + }); err != nil { + log.Error("err:%v", err) + resp.Code = CodeInnerError + return + } + pro := call.MineCurrencyProReal( + &common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: uid, + Type: ct, + Event: common.CurrencyEventGameBet, + Value: -betAmount, + Exi1: req.Provider.ProviderID, + Exi2: req.GameID, + Exs1: fmt.Sprintf("%d", uuid), + Exs2: req.BetID, + }, + }, + ) + if pro.Err != nil { + log.Error("err:%v", pro.Err) + resp.Code = CodeInnerError + if pro.Err == call.ErrNotEnoughBalance { + resp.Code = CodeNotEnoughAmount + } + return + } + if settle > 0 { + pro = call.MineCurrencyProReal( + &common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: uid, + Type: ct, + Event: common.CurrencyEventGameSettle, + Value: settle, + Exi1: req.Provider.ProviderID, + Exi2: req.GameID, + Exs1: fmt.Sprintf("%d", uuid), + Exs2: req.BetID, + }, + }, + ) + } + util.IndexTryS(func() error { + return call.Publish(natsClient.TopicInnerAfterSettle, &pb.InnerAfterSettle{ + UID: int64(uid), + ProviderID: int64(provider.ProviderID), + GameID: int64(req.GameID), + UUID: req.BetID, + CurrencyType: int64(ct), + TotalBet: betAmount, + OriginSettle: settle, + FinalSettle: settle, + MyUUID: fmt.Sprintf("%d", uuid), + }) + }) + resp.MyUUID = uuid + resp.Balance = pro.Balance + return +} + +// 一局下注逻辑(非slot类下注即刻结算) +func SessionBet(req *BetReq) (resp BetResp) { + betAmount := req.BetAmount + req.Preserve + uid := req.UID + ct := req.CurrencyType + provider := req.Provider + record := &common.ProviderBetRecord{ + UID: uid, + Provider: provider.ProviderID, + UUID: req.BetID, + } + db.Mysql().Get(record) + if record.ID > 0 { + resp.MyUUID = record.MyUUID + resp.Code = CodeAccepted + return + } + amount := call.GetUserCurrencyTotal(uid, ct) + if amount < betAmount { + resp.Balance = amount + resp.Code = CodeNotEnoughAmount + return + } + settle := req.SettleAmount + uuid := call.SnowNode().Generate().Int64() + if err := db.Mysql().Create(&common.ProviderBetRecord{ + UID: uid, + Provider: provider.ProviderID, + Currency: req.CurrencyType.GetCurrencyName(), + CurrencyType: req.CurrencyType, + GameID: req.GameID, + GameName: req.GameName, + UUID: req.BetID, + MyUUID: uuid, + Type: req.SessionType, + Time: time.Now().Unix(), + ProviderTime: req.Time, + Amount: betAmount, + Settle: settle, + SessionID: req.SessionID, + TurnOver: req.TurnOver, + Preserve: req.Preserve, + Esi: SessionSuccess, + }); err != nil { + log.Error("err:%v", err) + resp.Code = CodeInnerError + return + } + if betAmount == 0 && req.SessionType == common.SessionTypeBet { + resp.Balance = amount + resp.MyUUID = uuid + return + } + if betAmount > 0 { + pro := call.MineCurrencyProReal( + &common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: uid, + Type: ct, + Event: common.CurrencyEventGameBet, + Value: -betAmount, + Exi1: req.Provider.ProviderID, + Exi2: req.GameID, + Exs1: fmt.Sprintf("%d", uuid), + Exs2: req.BetID, + Exs3: req.SessionID, + }, + }, + ) + if pro.Err != nil { + log.Error("err:%v", pro.Err) + resp.Code = CodeInnerError + if pro.Err == call.ErrNotEnoughBalance { + resp.Code = CodeNotEnoughAmount + } + return + } + if req.SessionType == common.SessionTypeBet { + resp.Balance = pro.Balance + resp.MyUUID = uuid + return + } + } + var pro *call.ProRes + setValue := settle + req.Preserve + if setValue != 0 { + cb := &common.CurrencyBalance{ + UID: uid, + Type: ct, + Event: common.CurrencyEventGameSettle, + Value: setValue, + Exi1: req.Provider.ProviderID, + Exi2: req.GameID, + Exs1: fmt.Sprintf("%d", uuid), + Exs2: req.BetID, + Exs3: req.SessionID, + } + pro = call.MineCurrencyProReal( + &common.UpdateCurrency{ + CurrencyBalance: cb, + }, + ) + if pro.Err != nil { + log.Error("err:%v", pro.Err) + resp.Code = CodeInnerError + return + } + } + util.IndexTryS(func() error { + return call.Publish(natsClient.TopicInnerAfterSettle, &pb.InnerAfterSettle{ + UID: int64(uid), + ProviderID: int64(provider.ProviderID), + GameID: int64(req.GameID), + UUID: req.BetID, + CurrencyType: int64(ct), + TotalBet: req.TurnOver, + OriginSettle: settle, + FinalSettle: settle, + MyUUID: fmt.Sprintf("%d", uuid), + }) + }) + if pro != nil { + resp.Balance = pro.Balance + } else { + resp.Balance = call.GetUserCurrencyTotal(uid, ct) + } + resp.MyUUID = uuid + return +} + +// 一局取消下注逻辑(非slot类下注即刻结算,需对应唯一id去取消,若取消id不同,调用adjust方法回退金额) +func CancelSessionBet(req *BetReq) (resp BetResp) { + uid := req.UID + provider := req.Provider + record := &common.ProviderBetRecord{ + UID: uid, + Provider: provider.ProviderID, + UUID: req.BetID, + Type: common.SessionTypeBet, + } + db.Mysql().Get(record) + ct := record.CurrencyType + if record.Esi == SessionFail { + resp.MyUUID = record.MyUUID + resp.Code = CodeAccepted + resp.Balance = call.GetUserCurrencyTotal(uid, ct) + return + } + betAmount := record.Amount + preserve := record.Preserve + uuid := call.SnowNode().Generate().Int64() + // 说明已下注需要取消 + if record.ID > 0 { + res, err := db.Mysql().UpdateRes(&common.ProviderBetRecord{UID: uid, Provider: provider.ProviderID, UUID: req.BetID, Esi: SessionSuccess, Type: common.SessionTypeBet}, + map[string]interface{}{"esi": SessionFail, "esi1": req.VoidType}) + if err != nil { + log.Error("res:%v,err:%v", res, err) + resp.Code = CodeInnerError + return + } + if res == 0 { + resp.Code = CodeAccepted + return + } + pro := call.UpdateCurrencyProReal(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: uid, + Type: ct, + Event: common.CurrencyEventGameCancelBet, + Value: betAmount + preserve, + Exi1: req.Provider.ProviderID, + Exi2: req.GameID, + Exs1: fmt.Sprintf("%d", uuid), + Exs2: req.BetID, + Exs3: req.SessionID, + }, + }) + if pro.Err != nil { + log.Error("err:%v", pro.Err) + resp.Code = CodeInnerError + } + resp.Balance = pro.Balance + resp.BeforeBalance = pro.Balance - (betAmount + preserve) + resp.MyUUID = uuid + return + } + // 该笔下注还未收到,先创建,后续收到时直接失败 + currency, err := db.Redis().GetInt(common.GetRedisKeyGameCurrency(uid)) + ct = common.CurrencyType(currency) + if err != nil { + lastRecord := &common.ProviderBetRecord{UID: uid} + db.Mysql().GetLast(lastRecord) + ct = lastRecord.CurrencyType + } + if !ct.IsValid() { + ct = common.CurrencyBrazil + } + amount := call.GetUserCurrencyTotal(uid, ct) + if err := db.Mysql().Create(&common.ProviderBetRecord{ + UID: uid, + Provider: provider.ProviderID, + Currency: req.CurrencyType.GetCurrencyName(), + CurrencyType: req.CurrencyType, + GameID: req.GameID, + GameName: req.GameName, + UUID: req.BetID, + Type: req.SessionType, + Time: time.Now().Unix(), + ProviderTime: req.Time, + Amount: betAmount, + SessionID: req.SessionID, + MyUUID: uuid, + Preserve: preserve, + Esi: SessionFail, + }); err != nil { + log.Error("err:%v", err) + resp.Code = CodeInnerError + } + resp.Balance = amount + resp.BeforeBalance = amount + resp.MyUUID = uuid + return +} + +// 结算逻辑 +func Settle(req *SettleReq) (resp SettleResp) { + uid := req.UID + provider := req.Provider + record := &common.ProviderBetRecord{ + UID: uid, + Provider: provider.ProviderID, + SessionID: req.SessionID, + Type: common.SessionTypeBet, + } + db.Mysql().Get(record) + if record.ID == 0 { + resp.Code = CodeBetNotExist + return + } + ct := req.CurrencyType + betAmount := req.BetAmount + preserve := req.Preserve + uuid := call.SnowNode().Generate().Int64() + if err := db.Mysql().Create(&common.ProviderBetRecord{ + UID: uid, + Provider: provider.ProviderID, + Currency: ct.GetCurrencyName(), + CurrencyType: ct, + GameID: req.GameID, + GameName: req.GameName, + UUID: req.BetID, + Type: common.SessionTypeSettle, + Time: time.Now().Unix(), + ProviderTime: req.Time, + Amount: betAmount, + SessionID: req.SessionID, + MyUUID: uuid, + Preserve: preserve, + Settle: req.SettleAmount, + Esi: SessionSuccess, + }); err != nil { + log.Error("err:%v", err) + resp.Code = CodeAccepted + } + var balance int64 + if req.SettleAmount != 0 { + pro := call.UpdateCurrencyProReal( + &common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: uid, + Type: ct, + Event: common.CurrencyEventGameSettle, + Value: req.SettleAmount, + Exi1: req.Provider.ProviderID, + Exi2: req.GameID, + Exs1: fmt.Sprintf("%d", uuid), + Exs2: req.BetID, + Exs3: req.SessionID, + }, + }, + ) + if pro.Err != nil { + log.Error("err:%v", pro.Err) + resp.Code = CodeInnerError + return + } + balance = pro.Balance + } else { + balance = call.GetUserCurrencyTotal(uid, ct) + } + util.IndexTryS(func() error { + return call.Publish(natsClient.TopicInnerAfterSettle, &pb.InnerAfterSettle{ + UID: int64(uid), + ProviderID: int64(provider.ProviderID), + GameID: int64(req.GameID), + UUID: req.BetID, + CurrencyType: int64(ct), + TotalBet: req.TurnOver, + OriginSettle: req.SettleAmount, + FinalSettle: req.SettleAmount, + MyUUID: fmt.Sprintf("%d", uuid), + }) + }) + resp.Balance = balance + resp.MyUUID = uuid + return +} + +// 取消结算(根据id去取消结算,如果id不同,调用adjust回退金额) +func VoidSettle(req *VoidSettleReq) (resp VoidSettleResp) { + uid := req.UID + provider := req.Provider + record := &common.ProviderBetRecord{ + UID: uid, + Provider: provider.ProviderID, + Type: common.SessionTypeSettle, + UUID: req.BetID, + } + db.Mysql().Get(record) + if record.ID == 0 { + resp.MyUUID = record.MyUUID + resp.Code = CodeInnerError + return + } + if record.Esi == SessionInvalid { + resp.Code = CodeSettled + return + } + res, err := db.Mysql().UpdateRes(&common.ProviderBetRecord{UID: uid, Provider: provider.ProviderID, UUID: req.BetID, Esi: SessionSuccess}, + map[string]interface{}{"esi": SessionInvalid}) + if err != nil || res == 0 { + log.Error("res:%v,err:%v", res, err) + resp.Code = CodeInnerError + return + } + pro := call.UpdateCurrencyProReal(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: uid, + Type: record.CurrencyType, + Event: common.CurrencyEventGameVoidSettle, + Value: -(record.Settle - record.Amount), + Exi1: req.Provider.ProviderID, + Exi2: record.GameID, + Exs1: fmt.Sprintf("%d", record.MyUUID), + Exs2: record.UUID, + Exs3: record.SessionID, + }, + }) + if pro.Err != nil { + log.Error("err:%v", pro.Err) + resp.Code = CodeInnerError + } + resp.Balance = pro.Balance + resp.BeforeBalance = pro.Balance + record.Settle - record.Amount + return +} + +// 调整结算(根据id去调整结算,如果id不同,调用adjust回退金额) +func Resettle(req *ReSettleReq) (resp ReSettleResp) { + uid := req.UID + provider := req.Provider + record := &common.ProviderBetRecord{ + UID: uid, + Provider: provider.ProviderID, + Type: common.SessionTypeSettle, + UUID: req.BetID, + } + db.Mysql().Get(record) + if record.ID == 0 { + resp.Code = CodeInnerError + return + } + add := req.SettleAmount - record.Settle + if add == 0 { + return + } + res, err := db.Mysql().UpdateResW(&common.ProviderBetRecord{}, map[string]interface{}{"settle": req.SettleAmount}, + fmt.Sprintf("uid = %v and provider = %v and uuid = '%v' and settle <> %v and type = %d", uid, provider.ProviderID, req.BetID, req.SettleAmount, common.SessionTypeSettle)) + if err != nil || res == 0 { + log.Error("res:%v,err:%v", res, err) + resp.Code = CodeInnerError + return + } + pro := call.UpdateCurrencyProReal(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: uid, + Type: record.CurrencyType, + Event: common.CurrencyEventGameReSettle, + Value: add, + Exi1: req.Provider.ProviderID, + Exi2: record.GameID, + Exs1: fmt.Sprintf("%d", record.MyUUID), + Exs2: record.UUID, + Exs3: record.SessionID, + }, + }) + if pro.Err != nil { + log.Error("err:%v", pro.Err) + resp.Code = CodeInnerError + } + resp.Balance = pro.Balance + return +} + +// 活动奖励(或者是jackpot,bonus,回退) +// func ActivityGive(req *ActivityGiftReq) (resp ActivityGiftResp) { +// uid := req.UID +// provider := req.Provider +// record := &common.ProviderBetRecord{ +// UID: uid, +// Provider: provider.ProviderID, +// Type: req.GiftType, +// UUID: req.BetID, +// } +// db.Mysql().Get(record) +// if record.ID > 0 { +// resp.MyUUID = record.MyUUID +// resp.Code = CodeAccepted +// return +// } +// uuid := call.SnowNode().Generate().Int64() +// if err := db.Mysql().Create(&common.ProviderBetRecord{ +// UID: uid, +// Provider: provider.ProviderID, +// Currency: req.CurrencyType.GetCurrencyName(), +// CurrencyType: req.CurrencyType, +// GameID: req.GameID, +// UUID: req.BetID, +// Type: req.GiftType, +// Settle: req.Amount, +// Time: req.Time, +// MyUUID: uuid, +// Esi: SessionSuccess, +// Ess: req.ActivityID, +// }); err != nil { +// log.Error("err:%v", err) +// resp.Code = CodeInnerError +// } +// uc := &common.UpdateCurrency{ +// CurrencyBalance: &common.CurrencyBalance{ +// UID: uid, +// Type: req.CurrencyType, +// Event: common.CurrencyEventGameActivity, +// Value: req.Amount, +// Exs1: fmt.Sprintf("%d", uuid), +// Exs2: req.BetID, +// }, +// } +// if req.GiftType == common.SessionTypeJackpot { +// uc.Event = common.CurrencyEventGameJackpot +// } else if req.GiftType == common.SessionTypeBonus { +// uc.Event = common.CurrencyEventGameBonus +// } else if req.GiftType == common.SessionTypeBuyIn { +// uc.Event = common.CurrencyEventGameBuyIn +// } else if req.GiftType == common.SessionTypeBuyOut { +// uc.Event = common.CurrencyEventGameBuyOut +// } +// if req.Amount != 0 { +// pro := call.MineCurrencyProReal(uc) +// if pro.Err != nil { +// log.Error("err:%v", pro.Err) +// resp.Code = CodeInnerError +// if pro.Err == call.ErrNotEnoughBalance { +// resp.Code = CodeNotEnoughAmount +// } +// } +// resp.Balance = pro.Balance +// resp.BeforeBalance = pro.Balance - req.Amount +// } else { +// resp.Balance = call.GetUserCurrencyTotal(req.UID, req.CurrencyType) +// resp.BeforeBalance = resp.Balance +// } +// return +// } + +// 直接调整玩家余额,可以是活动奖励(或者是jackpot,bonus,回退) +func Adjustment(req *AdjustmentReq) (resp AdjustmentResp) { + uid := req.UID + if req.Amount < 0 { + amount := call.GetUserCurrencyTotal(uid, req.CurrencyType) + if amount < -req.Amount { + resp.Balance = amount + resp.Code = CodeNotEnoughAmount + return + } + } + provider := req.Provider + record := &common.ProviderBetRecord{ + UID: uid, + Provider: provider.ProviderID, + Type: req.Type, + UUID: req.BetID, + } + db.Mysql().Get(record) + if record.ID > 0 { + resp.MyUUID = record.MyUUID + resp.Code = CodeAccepted + return + } + uuid := call.SnowNode().Generate().Int64() + NewRecord := &common.ProviderBetRecord{ + UID: uid, + Provider: provider.ProviderID, + Currency: req.CurrencyType.GetCurrencyName(), + CurrencyType: req.CurrencyType, + GameID: req.GameID, + GameName: req.GameName, + UUID: req.BetID, + SessionID: req.SessionID, + Type: req.Type, + Time: time.Now().Unix(), + ProviderTime: req.Time, + MyUUID: uuid, + Esi: SessionSuccess, + Ess: req.Ess, + } + if req.Type == common.SessionTypeBuyIn { + NewRecord.Amount = util.Abs(req.Amount) + } else { + NewRecord.Settle = req.Amount + } + if err := db.Mysql().Create(NewRecord); err != nil { + log.Error("err:%v", err) + resp.Code = CodeInnerError + } + + uc := &common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: uid, + Type: req.CurrencyType, + Exi1: provider.ProviderID, + Exi2: req.GameID, + // Event: common.CurrencyEventGameActivity, + Value: req.Amount, + Exs1: fmt.Sprintf("%d", uuid), + Exs2: req.BetID, + }, + } + if req.Type == common.SessionTypeActivity { + uc.Event = common.CurrencyEventGameActivity + } else if req.Type == common.SessionTypeAdjustment { + uc.Event = common.CurrencyEventGameAdjustment + } else if req.Type == common.SessionTypeJackpot { + uc.Event = common.CurrencyEventGameJackpot + } else if req.Type == common.SessionTypeBonus { + uc.Event = common.CurrencyEventGameBonus + } else if req.Type == common.SessionTypeBuyIn { + uc.Event = common.CurrencyEventGameBuyIn + } else if req.Type == common.SessionTypeBuyOut { + uc.Event = common.CurrencyEventGameBuyOut + } + if req.Amount != 0 { + pro := call.MineCurrencyProReal(uc) + if pro.Err != nil { + log.Error("err:%v", pro.Err) + resp.Code = CodeInnerError + if pro.Err == call.ErrNotEnoughBalance { + resp.Code = CodeNotEnoughAmount + } + } + resp.Balance = pro.Balance + resp.BeforeBalance = pro.Balance - req.Amount + } else { + resp.Balance = call.GetUserCurrencyTotal(req.UID, req.CurrencyType) + resp.BeforeBalance = resp.Balance + } + return +} + +// 调整下注(根据id去调整下注,如果id不同,调用adjust回退金额) +func AdjustBet(req *AdjustBetReq) (resp AdjustBetResp) { + uid := req.UID + provider := req.Provider + record := &common.ProviderBetRecord{ + UID: uid, + Provider: provider.ProviderID, + Type: common.SessionTypeBet, + UUID: req.BetID, + } + db.Mysql().Get(record) + if record.ID == 0 { + resp.MyUUID = record.MyUUID + resp.Code = CodeInnerError + return + } + originAmount := record.Amount + if originAmount == req.AdjustAmount { + resp.Code = CodeAccepted + return + } + res, err := db.Mysql().UpdateRes(&common.ProviderBetRecord{UID: uid, Provider: provider.ProviderID, UUID: req.BetID, Type: common.SessionTypeBet, Amount: originAmount}, + map[string]interface{}{"amount": req.AdjustAmount}) + if err != nil { + log.Error("res:%v,err:%v", res, err) + resp.Code = CodeInnerError + return + } + if res == 0 { + resp.Code = CodeAccepted + return + } + pro := call.MineCurrencyProReal(&common.UpdateCurrency{ + CurrencyBalance: &common.CurrencyBalance{ + UID: uid, + Type: record.CurrencyType, + Event: common.CurrencyEventGameAdjustBet, + Value: originAmount - req.AdjustAmount, + Exi1: req.Provider.ProviderID, + Exi2: record.GameID, + Exs1: fmt.Sprintf("%d", record.MyUUID), + Exs2: record.UUID, + Exs3: record.SessionID, + }, + }) + if pro.Err != nil { + log.Error("err:%v", pro.Err) + resp.Code = CodeInnerError + } + resp.Balance = pro.Balance + resp.BeforeBalance = pro.Balance + req.AdjustAmount - originAmount + return +} + +// 推送投注/结算细节 +func PushDetail(req *PushDetailReq) (resp PushDetailResp) { + uid := req.UID + provider := req.Provider + record := &common.ProviderBetRecord{ + UID: uid, + Provider: provider.ProviderID, + UUID: req.BetID, + } + db.Mysql().Get(record) + if record.ID == 0 { + resp.MyUUID = record.MyUUID + resp.Code = CodeInnerError + return + } + + update := map[string]interface{}{ + "session_id": req.SessionID, + "turnover": req.TurnOver, + } + if record.Type == common.SessionTypeBuyIn { + update["type"] = common.SessionTypeBet + update["amount"] = req.BetAmount + } else { + update["type"] = common.SessionTypeSettle + update["settle"] = req.SettleAmount + } + + db.Mysql().UpdateW(&common.ProviderBetRecord{}, update, fmt.Sprintf("uuid = '%s'", req.BetID)) + resp.Balance = call.GetUserCurrencyTotal(uid, req.CurrencyType) + resp.MyUUID = record.MyUUID + return +} diff --git a/modules/web/providers/base/util.go b/modules/web/providers/base/util.go new file mode 100644 index 0000000..db18e81 --- /dev/null +++ b/modules/web/providers/base/util.go @@ -0,0 +1,29 @@ +package base + +import ( + "server/common" + "strconv" + + "github.com/liangdas/mqant/log" +) + +func GetUIDAndCurrency(s string) (uid int, ct common.CurrencyType) { + uids := []rune{} + cts := []rune{} + for _, v := range s { + if v < 48 || v > 57 { + cts = append(cts, v) + continue + } + uids = append(uids, v) + } + tmp := string(uids) + number, err := strconv.Atoi(tmp) + if err != nil { + log.Error("err:%v", err) + return + } + ct = common.GetCurrencyID(string(cts)) + uid = number + return +} diff --git a/modules/web/providers/gs/api.go b/modules/web/providers/gs/api.go new file mode 100644 index 0000000..3e27445 --- /dev/null +++ b/modules/web/providers/gs/api.go @@ -0,0 +1,59 @@ +package gs + +import ( + "server/util" + "time" + + "github.com/liangdas/mqant/log" +) + +type GameListReq struct { + OperatorCode string `json:"OperatorCode"` // Seamless运算符的唯一标识符 + MemberName string `json:"MemberName"` // 操作员的玩家唯一标识符 + DisplayName string `json:"DisplayName,omitempty"` // 玩家的显示名称 (可选) + Password string `json:"Password,omitempty"` // 操作员中的玩家密码 (可选) + GameID string `json:"GameID,omitempty"` // 独特的提供商游戏代码 (可选) + ProductID int `json:"ProductID"` // Seamless产品的唯一标识符 + GameType int `json:"GameType"` // 游戏类型 + LanguageCode int `json:"LanguageCode"` // 语言代码 + Platform int `json:"Platform"` // 平台 + IPAddress string `json:"IPAddress"` // 客户端的 IP 地址 + Sign string `json:"Sign"` // 请求的签名 + RequestTime string `json:"RequestTime"` // Request DateTime 格式为 yyyMDDHHMMSS +} + +type ProviderGame struct { + GameCode string `json:"GameCode"` // 独特的提供商游戏 ID + GameName string `json:"GameName"` // 提供者的游戏名称 + GameType int `json:"GameType,omitempty"` // 游戏类型 (可选) + Category string `json:"Category,omitempty"` // UI 显示的游戏类别 (可选,默认为 "游戏") + ImageURL string `json:"ImageURL,omitempty"` // 获取游戏图像的路径 (可选) + ProviderGameType string `json:"ProviderGameType,omitempty"` // 提供商的分类游戏类型 (可选,默认为 "null") +} + +type GameListResp struct { + ErrorCode int `json:"ErrorCode"` // 响应的状态代码 (Yes) + ErrorMessage string `json:"ErrorMessage"` // 响应消息 (Yes) + ProviderGames []ProviderGame `json:"ProviderGames,omitempty"` // 供应商游戏列表 (No) +} + +func GetGameList(provider int) { + opCode := AgentMap["E386"].OperatorCode + sk := AgentMap["E386"].SecretKey + req := &GameListReq{ + OperatorCode: opCode, + MemberName: "user2144", + ProductID: ProductIDMap[provider], + GameType: 1, + LanguageCode: LangMap["en"], + Platform: 0, + IPAddress: "1.1.1.1", + RequestTime: time.Now().UTC().Format("20060102150405"), + } + req.Sign = util.CalculateMD5(opCode + req.RequestTime + "getgamelist" + sk) + url := API + GetGameListURL + log.Debug("%d GetGameList:%+v,url:%s", provider, *req, url) + ret := new(GameListResp) + util.HttpPost(url, req, &ret, nil) + log.Debug("%d GetGameList resp:%+v", provider, ret) +} diff --git a/modules/web/providers/gs/base.go b/modules/web/providers/gs/base.go new file mode 100644 index 0000000..8f8a309 --- /dev/null +++ b/modules/web/providers/gs/base.go @@ -0,0 +1,116 @@ +package gs + +import ( + "fmt" + "server/call" + "server/common" + "server/config" + "server/db" + "server/modules/web/providers/base" + "server/util" + "time" + + "github.com/liangdas/mqant/log" +) + +type Sub struct { + Base *base.Base +} + +func NewSub(base *base.Base) { + base.Sub = &Sub{Base: base} + base.SubInitRouter = GS +} + +func (s *Sub) Init() { + API = APITest + AgentMap = AgentMapTest + if config.GetBase().Release { + API = APIRlease + AgentMap = AgentMapRelease + } +} + +type EnterGameReq struct { + OperatorCode string `json:"OperatorCode"` // Seamless运算符的唯一标识符 (Yes) + MemberName string `json:"MemberName"` // 操作员的玩家唯一标识符 (Yes) + DisplayName string `json:"DisplayName,omitempty"` // 玩家的显示名称 (No) + Password string `json:"Password,omitempty"` // 操作员中的玩家密码 (No) + GameID string `json:"GameID,omitempty"` // 独特的提供商游戏代码 (No) + ProviderGameType string `json:"ProviderGameType,omitempty"` // 供应商的游戏类型 (No) + ProductID int `json:"ProductID"` // Seamless产品的唯一标识符 (Yes) + GameType int `json:"GameType"` // 游戏类型 (Yes) + LanguageCode int `json:"LanguageCode"` // 语言代码 (Yes) + Platform int `json:"Platform"` // 平台 (Yes) + IPAddress string `json:"IPAddress"` // 客户端的 IP 地址 (Yes) + OperatorLobbyURL string `json:"OperatorLobbyURL,omitempty"` // 运营商主页的URL (No) + Sign string `json:"Sign"` // 请求的签名 (Yes) + RequestTime string `json:"RequestTime"` // DateTime 格式为 yyyMDDHHMMSS (Yes) +} + +type EnterGameResp struct { + ErrorCode int `json:"ErrorCode"` // 响应的状态代码 (Yes) + ErrorMessage string `json:"ErrorMessage"` // 响应消息 (Yes) + Url string `json:"Url,omitempty"` // 游戏网址 (No) +} + +func (s *Sub) EnterGame() string { + uid := s.Base.EnterGameReq.UID + // ct := s.Base.EnterGameReq.CurrencyType + gameID := s.Base.EnterGameReq.GameID + // subID := s.Base.EnterGameReq.SubID + lang := s.Base.EnterGameReq.Lang + providerID := s.Base.EnterGameReq.ProviderID + ip := s.Base.EnterGameReq.IP + cid := s.Base.EnterGameReq.ChannelID + + game := call.GetConfigGameListByID(providerID, gameID) + if game == nil { + return "" + } + currency, err := db.Redis().GetInt(common.GetRedisKeyGameCurrency(uid)) + if err != nil { + log.Error("err:%v", err) + return "" + } + agent := GetAgentByCurrency(common.CurrencyType(currency)) + if agent == nil { + return "" + } + // if !config.GetBase().Release { + // ip = "3.110.154.202" + // } + platForm := 1 + if common.IsPC(s.Base.EnterGameReq.DeviceType) { + platForm = 0 + } + req := &EnterGameReq{ + OperatorCode: agent.OperatorCode, + MemberName: common.GetProviderUserName(fmt.Sprintf("%d", uid)), + // DisplayName: p.Nick, + GameID: game.GameCode, + // ProviderGameType: , + ProductID: ProductIDMap[providerID], + GameType: GameTypeMap[game.GameType], + LanguageCode: LangMap[lang], + Platform: platForm, + IPAddress: ip, + // OperatorLobbyURL: , + RequestTime: time.Now().UTC().Format("20060102150405"), + } + channel := call.GetChannelByID(cid) + if channel != nil && channel.URL != "" { + req.OperatorLobbyURL = "https://" + channel.URL + } + req.Sign = util.CalculateMD5(agent.OperatorCode + req.RequestTime + "launchgame" + agent.SecretKey) + + url := API + LaunchGameURL + log.Debug("%d EnterGame:%+v,url:%s", providerID, *req, url) + ret := new(EnterGameResp) + util.HttpPost(url, req, &ret, nil) + log.Debug("%d EnterGame resp:%+v", providerID, ret) + // if str, ok := ret.Data.(string); ok { + // return str + // } + return util.ReplaceHTTPWithHTTPS(ret.Url) +} diff --git a/modules/web/providers/gs/handler.go b/modules/web/providers/gs/handler.go new file mode 100644 index 0000000..d4bff5a --- /dev/null +++ b/modules/web/providers/gs/handler.go @@ -0,0 +1,778 @@ +package gs + +import ( + "server/call" + "server/common" + "server/db" + "server/modules/web/app" + "server/modules/web/providers/base" + "server/util" + "strconv" + "time" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +func GS(e *gin.RouterGroup) { + e.POST("/Seamless/GetBalance", GetBalance) + e.POST("/Seamless/PlaceBet", PlaceBet) + e.POST("/Seamless/GameResult", GameResult) + e.POST("/Seamless/Rollback", Rollback) + e.POST("/Seamless/CancelBet", CancelBet) + e.POST("/Seamless/Bonus", Bonus) + e.POST("/Seamless/Jackpot", Jackpot) + e.POST("/Seamless/MobileLogin", MobileLogin) + e.POST("/Seamless/BuyIn", BuyIn) + e.POST("/Seamless/BuyOut", BuyOut) + e.POST("/Seamless/PushBet", PushBet) +} + +func VerifySign(operatorCode, reqTime, name, sign string) bool { + agent := GetAgentByCode(operatorCode) + if agent == nil { + return false + } + return util.CalculateMD5(agent.OperatorCode+reqTime+name+agent.SecretKey) == sign +} + +func GetGameID(providerID int, gameCode string) int { + if providerID == common.ProviderEvolutionGaming || providerID == common.ProviderAllBet || providerID == common.ProviderBigGaming || providerID == common.ProviderSAGaming { + return 1 + } + game := call.GetConfigGameListByCode(providerID, gameCode) + if game != nil { + return game.GameID + } + return 0 +} + +func GetBalance(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.ResponseB() + }() + req := &GetBalanceReq{} + resp := &CommonResp2{} + a.RetData = resp + if !a.SB(req) { + resp.ErrorCode = CodeAPIError + return + } + log.Debug("GetBalance:%+v", req) + + if a.ShouldRoute(req, "MemberName", common.ProviderAPITypeJson) { + return + } + + if !VerifySign(req.OperatorCode, req.RequestTime, "getbalance", req.Sign) { + resp.ErrorCode = CodeAPIInvalidSign + return + } + + uid, err := strconv.Atoi(req.MemberName) + if err != nil { + log.Error("err:%v", err) + resp.ErrorCode = CodeAPIError + return + } + + currency, err := db.Redis().GetInt(common.GetRedisKeyGameCurrency(uid)) + if err != nil { + log.Error("err:%v", err) + resp.ErrorCode = CodeInnerError + return + } + + resp.Balance = call.GetUserCurrencyFloat(uid, common.CurrencyType(currency), 4) +} + +func PlaceBet(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.ResponseB() + }() + req := &CommonReq{} + resp := &CommonResp{} + a.RetData = resp + if !a.SB(req) { + resp.ErrorCode = CodeAPIError + return + } + log.Debug("PlaceBet:%+v", req) + + if a.ShouldRoute(req, "MemberName", common.ProviderAPITypeJson) { + return + } + + if !VerifySign(req.OperatorCode, req.RequestTime, "placebet", req.Sign) { + resp.ErrorCode = CodeAPIInvalidSign + return + } + + uid, err := strconv.Atoi(req.MemberName) + if err != nil { + resp.ErrorCode = CodeAPIError + return + } + + now := time.Now().Unix() + for _, v := range req.Transactions { + // if v.TransactionAmount == 0 { + // resp.Balance = call.GetUserCurrencyFloat(uid, ct, 4) + // if resp.BeforeBalance == 0 { + // resp.BeforeBalance = resp.Balance + // } + // continue + // } + + ct := CurrencyIDMap[v.CurrencyID] + if !ct.IsValid() { + currency, err := db.Redis().GetInt(common.GetRedisKeyGameCurrency(uid)) + if err != nil { + log.Error("err:%v", err) + continue + } + ct = common.CurrencyType(currency) + } + providerID := ProviderIDMap[v.ProductID] + provider := call.GetConfigGameProvider(providerID) + if provider == nil { + resp.ErrorCode = CodeInnerError + return + } + betReq := &base.BetReq{ + UID: uid, + CurrencyType: ct, + BetAmount: -common.CashFloat64ToInt64(v.TransactionAmount), + TurnOver: common.CashFloat64ToInt64(v.ValidBetAmount), + SessionType: common.SessionTypeBet, + GameID: GetGameID(providerID, v.GameID), + GameName: v.GameID, + Provider: provider, + BetID: v.TransactionID, + SessionID: v.GameRoundID, + Time: now, + } + betResp := base.SessionBet(betReq) + if betResp.Code != base.CodeOk { + resp.ErrorCode = CodeAPIError + if betResp.Code == base.CodeAccepted { + resp.ErrorCode = CodeAPIDuplicateTransaction + } else if betResp.Code == base.CodeNotEnoughAmount { + resp.ErrorCode = CodeAPIMemberInsufficientBalance + } + return + } + resp.Balance = util.Decimal(float64(betResp.Balance)/common.DecimalDigits, 4) + if resp.BeforeBalance == 0 { + resp.BeforeBalance = util.Decimal(float64(betResp.Balance+betReq.BetAmount)/common.DecimalDigits, 4) + } + } +} + +func GameResult(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.ResponseB() + }() + req := &CommonReq{} + resp := &CommonResp{} + a.RetData = resp + if !a.SB(req) { + resp.ErrorCode = CodeAPIError + return + } + log.Debug("GameResult:%+v", req) + + if a.ShouldRoute(req, "MemberName", common.ProviderAPITypeJson) { + return + } + + if !VerifySign(req.OperatorCode, req.RequestTime, "gameresult", req.Sign) { + resp.ErrorCode = CodeAPIInvalidSign + return + } + + uid, err := strconv.Atoi(req.MemberName) + if err != nil { + resp.ErrorCode = CodeAPIError + return + } + + provider := call.GetConfigGameProvider(ProviderIDMap[int(req.ProductID)]) + if provider == nil { + resp.ErrorCode = CodeAPIError + return + } + + now := time.Now().Unix() + + for _, v := range req.Transactions { + ct := CurrencyIDMap[v.CurrencyID] + if !ct.IsValid() { + currency, err := db.Redis().GetInt(common.GetRedisKeyGameCurrency(uid)) + if err != nil { + log.Error("err:%v", err) + continue + } + ct = common.CurrencyType(currency) + } + settleReq := &base.SettleReq{ + UID: uid, + CurrencyType: ct, + SettleAmount: common.CashFloat64ToInt64(v.TransactionAmount), + BetAmount: common.CashFloat64ToInt64(v.BetAmount), + TurnOver: common.CashFloat64ToInt64(v.ValidBetAmount), + GameID: GetGameID(provider.ProviderID, v.GameID), + GameName: v.GameID, + Provider: provider, + BetID: v.TransactionID, + SessionID: v.GameRoundID, + Time: now, + } + settleResp := base.Settle(settleReq) + if settleResp.Code != base.CodeOk { + resp.ErrorCode = CodeAPIError + if settleResp.Code == base.CodeAccepted { + resp.ErrorCode = CodeAPIDuplicateTransaction + } else if settleResp.Code == base.CodeBetNotExist { + resp.ErrorCode = CodeAPIBetNotExist + } + return + } + resp.Balance = util.Decimal(float64(settleResp.Balance)/common.DecimalDigits, 4) + if resp.BeforeBalance == 0 { + resp.BeforeBalance = util.Decimal(float64(settleResp.Balance-settleReq.SettleAmount)/common.DecimalDigits, 4) + } + } +} + +func Rollback(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.ResponseB() + }() + req := &CommonReq{} + resp := &CommonResp{} + a.RetData = resp + if !a.SB(req) { + resp.ErrorCode = CodeAPIError + return + } + log.Debug("Rollback:%+v", req) + + if a.ShouldRoute(req, "MemberName", common.ProviderAPITypeJson) { + return + } + + if !VerifySign(req.OperatorCode, req.RequestTime, "rollback", req.Sign) { + resp.ErrorCode = CodeAPIInvalidSign + return + } + + uid, err := strconv.Atoi(req.MemberName) + if err != nil { + resp.ErrorCode = CodeAPIError + return + } + provider := call.GetConfigGameProvider(ProviderIDMap[int(req.ProductID)]) + if provider == nil { + resp.ErrorCode = CodeAPIError + return + } + for _, v := range req.Transactions { + ct := CurrencyIDMap[v.CurrencyID] + if !ct.IsValid() { + currency, err := db.Redis().GetInt(common.GetRedisKeyGameCurrency(uid)) + if err != nil { + log.Error("err:%v", err) + continue + } + ct = common.CurrencyType(currency) + } + adjustResp := base.Adjustment(&base.AdjustmentReq{ + UID: uid, + CurrencyType: ct, + Amount: common.CashFloat64ToInt64(v.TransactionAmount), + GameID: GetGameID(provider.ProviderID, v.GameID), + GameName: v.GameID, + Provider: provider, + BetID: v.TransactionID, + Time: time.Now().Unix(), + SessionID: v.GameRoundID, + Type: common.SessionTypeAdjustment, + }) + if adjustResp.Code != base.CodeOk { + resp.ErrorCode = CodeAPIError + if adjustResp.Code == base.CodeAccepted { + resp.ErrorCode = CodeAPIDuplicateTransaction + } + return + } + resp.Balance = util.Decimal(float64(adjustResp.Balance)/common.DecimalDigits, 4) + if resp.BeforeBalance == 0 { + resp.BeforeBalance = util.Decimal(float64(adjustResp.BeforeBalance)/common.DecimalDigits, 4) + } + } +} + +func CancelBet(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.ResponseB() + }() + req := &CommonReq{} + resp := &CommonResp{} + a.RetData = resp + if !a.SB(req) { + resp.ErrorCode = CodeAPIError + return + } + log.Debug("CancelBet:%+v", req) + + if a.ShouldRoute(req, "MemberName", common.ProviderAPITypeJson) { + return + } + + if !VerifySign(req.OperatorCode, req.RequestTime, "cancelbet", req.Sign) { + resp.ErrorCode = CodeAPIInvalidSign + return + } + + uid, err := strconv.Atoi(req.MemberName) + if err != nil { + resp.ErrorCode = CodeAPIError + return + } + provider := call.GetConfigGameProvider(ProviderIDMap[int(req.ProductID)]) + if provider == nil { + resp.ErrorCode = CodeAPIError + return + } + for _, v := range req.Transactions { + ct := CurrencyIDMap[v.CurrencyID] + if !ct.IsValid() { + currency, err := db.Redis().GetInt(common.GetRedisKeyGameCurrency(uid)) + if err != nil { + log.Error("err:%v", err) + continue + } + ct = common.CurrencyType(currency) + } + adjustResp := base.Adjustment(&base.AdjustmentReq{ + UID: uid, + CurrencyType: ct, + Amount: common.CashFloat64ToInt64(v.TransactionAmount), + GameID: GetGameID(provider.ProviderID, v.GameID), + GameName: v.GameID, + Provider: provider, + BetID: v.TransactionID, + SessionID: v.GameRoundID, + Time: time.Now().Unix(), + Type: common.SessionTypeAdjustment, + }) + if adjustResp.Code != base.CodeOk { + resp.ErrorCode = CodeAPIError + if adjustResp.Code == base.CodeAccepted { + resp.ErrorCode = CodeAPIDuplicateTransaction + } + return + } + resp.Balance = util.Decimal(float64(adjustResp.Balance)/common.DecimalDigits, 4) + if resp.BeforeBalance == 0 { + resp.BeforeBalance = util.Decimal(float64(adjustResp.BeforeBalance)/common.DecimalDigits, 4) + } + } +} + +func Bonus(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.ResponseB() + }() + req := &CommonReq{} + resp := &CommonResp{} + a.RetData = resp + if !a.SB(req) { + resp.ErrorCode = CodeAPIError + return + } + log.Debug("Bonus:%+v", req) + + if a.ShouldRoute(req, "MemberName", common.ProviderAPITypeJson) { + return + } + + if !VerifySign(req.OperatorCode, req.RequestTime, "bonus", req.Sign) { + resp.ErrorCode = CodeAPIInvalidSign + return + } + + uid, err := strconv.Atoi(req.MemberName) + if err != nil { + resp.ErrorCode = CodeAPIError + return + } + + provider := call.GetConfigGameProvider(ProviderIDMap[int(req.ProductID)]) + if provider == nil { + resp.ErrorCode = CodeAPIError + return + } + + now := time.Now().Unix() + for _, v := range req.Transactions { + ct := CurrencyIDMap[v.CurrencyID] + if !ct.IsValid() { + currency, err := db.Redis().GetInt(common.GetRedisKeyGameCurrency(uid)) + if err != nil { + log.Error("err:%v", err) + continue + } + ct = common.CurrencyType(currency) + } + giftResp := base.Adjustment(&base.AdjustmentReq{ + UID: uid, + CurrencyType: common.CurrencyType(ct), + Amount: common.CashFloat64ToInt64(v.TransactionAmount), + GameID: GetGameID(provider.ProviderID, v.GameID), + GameName: v.GameID, + Provider: provider, + BetID: v.TransactionID, + SessionID: v.GameRoundID, + Time: now, + Type: common.SessionTypeBonus, + }) + if giftResp.Code != base.CodeOk { + resp.ErrorCode = CodeAPIError + if giftResp.Code == base.CodeAccepted { + resp.ErrorCode = CodeAPIDuplicateTransaction + } + return + } + resp.Balance = util.Decimal(float64(giftResp.Balance)/common.DecimalDigits, 4) + if resp.BeforeBalance == 0 { + resp.BeforeBalance = util.Decimal(float64(giftResp.BeforeBalance)/common.DecimalDigits, 4) + } + } +} + +func Jackpot(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.ResponseB() + }() + req := &CommonReq{} + resp := &CommonResp{} + a.RetData = resp + if !a.SB(req) { + resp.ErrorCode = CodeAPIError + return + } + log.Debug("Jackpot:%+v", req) + + if a.ShouldRoute(req, "MemberName", common.ProviderAPITypeJson) { + return + } + + if !VerifySign(req.OperatorCode, req.RequestTime, "jackpot", req.Sign) { + resp.ErrorCode = CodeAPIInvalidSign + return + } + + uid, err := strconv.Atoi(req.MemberName) + if err != nil { + resp.ErrorCode = CodeAPIError + return + } + + provider := call.GetConfigGameProvider(ProviderIDMap[int(req.ProductID)]) + if provider == nil { + resp.ErrorCode = CodeAPIError + return + } + + now := time.Now().Unix() + for _, v := range req.Transactions { + ct := CurrencyIDMap[v.CurrencyID] + if !ct.IsValid() { + currency, err := db.Redis().GetInt(common.GetRedisKeyGameCurrency(uid)) + if err != nil { + log.Error("err:%v", err) + continue + } + ct = common.CurrencyType(currency) + } + giftResp := base.Adjustment(&base.AdjustmentReq{ + UID: uid, + CurrencyType: common.CurrencyType(ct), + Amount: common.CashFloat64ToInt64(v.TransactionAmount), + GameID: GetGameID(provider.ProviderID, v.GameID), + GameName: v.GameID, + Provider: provider, + BetID: v.TransactionID, + SessionID: v.GameRoundID, + Time: now, + Type: common.SessionTypeJackpot, + }) + if giftResp.Code != base.CodeOk { + resp.ErrorCode = CodeAPIError + if giftResp.Code == base.CodeAccepted { + resp.ErrorCode = CodeAPIDuplicateTransaction + } + return + } + resp.Balance = util.Decimal(float64(giftResp.Balance)/common.DecimalDigits, 4) + if resp.BeforeBalance == 0 { + resp.BeforeBalance = util.Decimal(float64(giftResp.BeforeBalance)/common.DecimalDigits, 4) + } + } +} + +func MobileLogin(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.ResponseB() + }() + req := &GetBalanceReq{} + resp := &CommonResp2{} + a.RetData = resp + if !a.SB(req) { + resp.ErrorCode = CodeAPIError + return + } + log.Debug("MobileLogin:%+v", req) + + if a.ShouldRoute(req, "MemberName", common.ProviderAPITypeJson) { + return + } + + if !VerifySign(req.OperatorCode, req.RequestTime, "mobilelogin", req.Sign) { + resp.ErrorCode = CodeAPIInvalidSign + return + } + + uid, err := strconv.Atoi(req.MemberName) + if err != nil { + resp.ErrorCode = CodeAPIError + return + } + + currency, err := db.Redis().GetInt(common.GetRedisKeyGameCurrency(uid)) + if err != nil { + log.Error("err:%v", err) + resp.ErrorCode = CodeInnerError + return + } + + resp.Balance = call.GetUserCurrencyFloat(uid, common.CurrencyType(currency), 4) +} + +func BuyIn(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.ResponseB() + }() + req := &BuyInReq{} + resp := &CommonResp{} + a.RetData = resp + if !a.SB(req) { + resp.ErrorCode = CodeAPIError + return + } + log.Debug("BuyIn:%+v", req) + + if a.ShouldRoute(req, "MemberName", common.ProviderAPITypeJson) { + return + } + + if !VerifySign(req.OperatorCode, req.RequestTime, "buyin", req.Sign) { + resp.ErrorCode = CodeAPIInvalidSign + return + } + + uid, err := strconv.Atoi(req.MemberName) + if err != nil { + resp.ErrorCode = CodeAPIError + return + } + + ct := CurrencyIDMap[req.Transaction.CurrencyID] + if !ct.IsValid() { + currency, err := db.Redis().GetInt(common.GetRedisKeyGameCurrency(uid)) + if err != nil { + log.Error("err:%v", err) + resp.ErrorCode = CodeInnerError + return + } + ct = common.CurrencyType(currency) + } + provider := call.GetConfigGameProvider(ProviderIDMap[int(req.ProductID)]) + if provider == nil { + resp.ErrorCode = CodeAPIError + return + } + + now := time.Now().Unix() + giftResp := base.Adjustment(&base.AdjustmentReq{ + UID: uid, + CurrencyType: common.CurrencyType(ct), + Amount: common.CashFloat64ToInt64(req.Transaction.TransactionAmount), + GameID: GetGameID(provider.ProviderID, req.Transaction.GameID), + GameName: req.Transaction.GameID, + Provider: provider, + BetID: req.Transaction.TransactionID, + SessionID: req.Transaction.GameRoundID, + Time: now, + Type: common.SessionTypeBuyIn, + }) + if giftResp.Code != base.CodeOk { + resp.ErrorCode = CodeAPIError + if giftResp.Code == base.CodeAccepted { + resp.ErrorCode = CodeAPIDuplicateTransaction + } else if giftResp.Code == base.CodeNotEnoughAmount { + resp.ErrorCode = CodeAPIMemberInsufficientBalance + } + return + } + resp.Balance = util.Decimal(float64(giftResp.Balance)/common.DecimalDigits, 4) + resp.BeforeBalance = util.Decimal(float64(giftResp.BeforeBalance)/common.DecimalDigits, 4) +} + +func BuyOut(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.ResponseB() + }() + req := &BuyOutReq{} + resp := &CommonResp{} + a.RetData = resp + if !a.SB(req) { + resp.ErrorCode = CodeAPIError + return + } + log.Debug("BuyOut:%+v", req) + + if a.ShouldRoute(req, "MemberName", common.ProviderAPITypeJson) { + return + } + + if !VerifySign(req.OperatorCode, req.RequestTime, "buyout", req.Sign) { + resp.ErrorCode = CodeAPIInvalidSign + return + } + + uid, err := strconv.Atoi(req.MemberName) + if err != nil { + resp.ErrorCode = CodeAPIError + return + } + + ct := CurrencyIDMap[req.Transaction.CurrencyID] + if !ct.IsValid() { + currency, err := db.Redis().GetInt(common.GetRedisKeyGameCurrency(uid)) + if err != nil { + log.Error("err:%v", err) + resp.ErrorCode = CodeInnerError + return + } + ct = common.CurrencyType(currency) + } + provider := call.GetConfigGameProvider(ProviderIDMap[int(req.ProductID)]) + if provider == nil { + resp.ErrorCode = CodeAPIError + return + } + + now := time.Now().Unix() + giftResp := base.Adjustment(&base.AdjustmentReq{ + UID: uid, + CurrencyType: common.CurrencyType(ct), + Amount: common.CashFloat64ToInt64(req.Transaction.TransactionAmount), + GameID: GetGameID(provider.ProviderID, req.Transaction.GameID), + GameName: req.Transaction.GameID, + Provider: provider, + BetID: req.Transaction.TransactionID, + SessionID: req.Transaction.GameRoundID, + Time: now, + Type: common.SessionTypeBuyOut, + }) + if giftResp.Code != base.CodeOk { + resp.ErrorCode = CodeAPIError + if giftResp.Code == base.CodeAccepted { + resp.ErrorCode = CodeAPIDuplicateTransaction + } + return + } + resp.Balance = util.Decimal(float64(giftResp.Balance)/common.DecimalDigits, 4) + resp.BeforeBalance = util.Decimal(float64(giftResp.BeforeBalance)/common.DecimalDigits, 4) +} + +func PushBet(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.ResponseB() + }() + req := &CommonReq{} + resp := &CommonResp2{} + a.RetData = resp + if !a.SB(req) { + resp.ErrorCode = CodeAPIError + return + } + log.Debug("PushBet:%+v", req) + + if a.ShouldRoute(req, "MemberName", common.ProviderAPITypeJson) { + return + } + + if !VerifySign(req.OperatorCode, req.RequestTime, "pushbet", req.Sign) { + resp.ErrorCode = CodeAPIInvalidSign + return + } + + uid, err := strconv.Atoi(req.MemberName) + if err != nil { + resp.ErrorCode = CodeAPIError + return + } + + provider := call.GetConfigGameProvider(ProviderIDMap[int(req.ProductID)]) + if provider == nil { + resp.ErrorCode = CodeAPIError + return + } + + now := time.Now().Unix() + for _, v := range req.Transactions { + ct := CurrencyIDMap[v.CurrencyID] + if !ct.IsValid() { + currency, err := db.Redis().GetInt(common.GetRedisKeyGameCurrency(uid)) + if err != nil { + log.Error("err:%v", err) + continue + } + ct = common.CurrencyType(currency) + } + if v.TransactionID != "" { + pushReq := &base.PushDetailReq{ + UID: uid, + CurrencyType: ct, + SettleAmount: common.CashFloat64ToInt64(v.PayoutAmount), + BetAmount: common.CashFloat64ToInt64(v.BetAmount), + TurnOver: common.CashFloat64ToInt64(v.ValidBetAmount), + GameID: GetGameID(provider.ProviderID, v.GameID), + GameName: v.GameID, + Provider: provider, + BetID: v.TransactionID, + SessionID: v.GameRoundID, + Time: now, + } + pushDetailResp := base.PushDetail(pushReq) + resp.Balance = util.Decimal(float64(pushDetailResp.Balance)/common.DecimalDigits, 4) + } else { + resp.Balance = call.GetUserCurrencyFloat(uid, ct, 4) + } + } +} diff --git a/modules/web/providers/gs/sign.go b/modules/web/providers/gs/sign.go new file mode 100644 index 0000000..657363a --- /dev/null +++ b/modules/web/providers/gs/sign.go @@ -0,0 +1,24 @@ +package gs + +// func GetKeyG() string { +// key := TestKey +// if config.GetBase().Release { +// key = AgentKey +// } +// return util.CalculateMD5(time.Now().UTC().Add(-4*time.Hour).Format("06012") + AgentID + key) +// } + +// func GetSignKey(req interface{}) string { +// keyG := GetKeyG() +// str := "" +// ref := reflect.ValueOf(req) +// reft := reflect.TypeOf(req) +// if reft.Kind() == reflect.Ptr { +// ref = ref.Elem() +// reft = reft.Elem() +// } +// for i := 0; i < ref.NumField(); i++ { +// str += fmt.Sprintf("%s=%v&", reft.Field(i).Name, ref.Field(i).Interface()) +// } +// return util.GenerateRandomString(6) + util.CalculateMD5(fmt.Sprintf("%sAgentId=%s", str, AgentID)+keyG) + util.GenerateRandomString(6) +// } diff --git a/modules/web/providers/gs/values.go b/modules/web/providers/gs/values.go new file mode 100644 index 0000000..6ca307c --- /dev/null +++ b/modules/web/providers/gs/values.go @@ -0,0 +1,240 @@ +package gs + +import "server/common" + +const ( + APIRlease = "https://prodmd.9977997.com" + APITest = "https://swmd.6633663.com" + OperatorCode = "E387" + SecretKey = "qqmruv" + LaunchGameURL = "/Seamless/LaunchGame" + GetGameListURL = "/Seamless/GetGameList" +) + +type Agent struct { + OperatorCode string + SecretKey string + Currency common.CurrencyType +} + +// 错误码 +var ( + CodeOk = 0 + CodeFailed = 16 + CodeAPIError = 19 + CodeInnerError = 999 + CodeAPIMemberNotExists = 1000 + CodeAPIMemberInsufficientBalance = 1001 + CodeAPIIncorrectAgentKey = 1002 + CodeAPIDuplicateTransaction = 1003 + CodeAPIInvalidSign = 1004 + CodeAPINotGetGameList = 1005 + CodeAPIBetNotExist = 1006 + CodeAPIProductUnderMainTenance = 2000 +) + +var ( + API = "" + AgentMap = map[string]*Agent{} + GameTypeMap = map[int]int{ + common.GameTypeSlots: 1, + common.GameTypeLive: 2, + common.GameTypeESport: 13, + common.GameTypeSportBook: 3, + } + ProductIDMap = map[int]int{ + common.ProviderPGSoft: 1007, + common.ProviderEvolutionGaming: 1002, + common.ProviderAllBet: 1003, + common.ProviderBigGaming: 1004, + common.ProviderSAGaming: 1005, + common.ProviderPragmaticPlay: 1006, + common.ProviderCQ9: 1009, + common.ProviderPlayTech: 1011, + common.ProviderJoker: 1013, + common.ProviderDragonSoft: 1014, + common.ProviderTFGaming: 1017, + common.ProviderWMCasino: 1020, + common.ProviderKing855: 1038, + common.ProviderAMAYA: 1039, + common.ProviderHabanero: 1041, + common.ProviderIBC: 1046, + common.ProviderReevo: 1048, + common.ProviderEvoPlay: 1049, + common.ProviderPlayStar: 1050, + common.ProviderEzugi: 1051, + common.ProviderDreamGaming: 1052, + common.ProviderNexus4D: 1053, + common.ProviderSlotXo: 1075, + common.ProviderBTI: 1081, + } + ProviderIDMap = map[int]int{ + 1007: common.ProviderPGSoft, + 1002: common.ProviderEvolutionGaming, + 1003: common.ProviderAllBet, + 1004: common.ProviderBigGaming, + 1005: common.ProviderSAGaming, + 1006: common.ProviderPragmaticPlay, + 1009: common.ProviderCQ9, + 1011: common.ProviderPlayTech, + 1013: common.ProviderJoker, + 1014: common.ProviderDragonSoft, + 1017: common.ProviderTFGaming, + 1020: common.ProviderWMCasino, + 1038: common.ProviderKing855, + 1039: common.ProviderAMAYA, + 1041: common.ProviderHabanero, + 1046: common.ProviderIBC, + 1048: common.ProviderReevo, + 1049: common.ProviderEvoPlay, + 1050: common.ProviderPlayStar, + 1051: common.ProviderEzugi, + 1052: common.ProviderDreamGaming, + 1053: common.ProviderNexus4D, + 1075: common.ProviderSlotXo, + 1081: common.ProviderBTI, + } + CurrencyIDMap = map[int]common.CurrencyType{ + 2: common.CurrencyUSDT, + 33: common.CurrencyUSDT, + 36: common.CurrencyBrazil, + } + LangMap = map[string]int{ + "en": 1, + "pt": 28, + } + AgentMapTest = map[string]*Agent{ + "E386": { + OperatorCode: "E386", + SecretKey: "56EcfZ", + Currency: common.CurrencyBrazil, + }, + "E387": { + OperatorCode: "E387", + SecretKey: "qqmruv", + Currency: common.CurrencyUSDT, + }, + } + AgentMapRelease = map[string]*Agent{ + "E386": { + OperatorCode: "E386", + SecretKey: "XF8A4Y", + Currency: common.CurrencyBrazil, + }, + "E387": { + OperatorCode: "E387", + SecretKey: "QclvPN", + Currency: common.CurrencyUSDT, + }, + } +) + +func GetAgentByCode(operatorCode string) *Agent { + return AgentMap[operatorCode] +} + +func GetAgentByCurrency(ct common.CurrencyType) *Agent { + for _, v := range AgentMap { + if v.Currency == ct { + return v + } + } + return nil +} + +const ( + statusPending = iota + 100 + statusSettle + statueVoid +) + +type GetBalanceReq struct { + MemberName string `json:"MemberName"` // 操作员的玩家唯一标识符 (必填) + OperatorCode string `json:"OperatorCode"` // Seamless运算符的唯一标识符 + ProductID int64 `json:"ProductID"` // Seamless产品的唯一标识符 (非必填) + MessageID string `json:"MessageID"` // 当前 API 请求的唯一标识符 (必填) + RequestTime string `json:"RequestTime"` // Request DateTime,格式为 yyyMDDHHMMSS (必填) + Sign string `json:"Sign"` // 请求的签名 +} + +type Transaction struct { + MemberID int64 `json:"MemberID"` // Seamless玩家的唯一标识符 (必填) + OperatorID int64 `json:"OperatorID"` // Seamless运算符的唯一标识符 (必填) + ProductID int `json:"ProductID"` // Seamless产品的唯一标识符 (必填) + ProviderID int64 `json:"ProviderID"` // Seamless提供程序的唯一标识符 (必填) + ProviderLineID int64 `json:"ProviderLineID"` // Seamless对产品线进行配置的唯一标识符 (必填) + WagerID int64 `json:"WagerID,omitempty"` // Seamless工资记录的唯一标识符 (可选) + CurrencyID int `json:"CurrencyID"` // Seamless货币的唯一标识符 (必填) + GameType int `json:"GameType"` // 交易的游戏类型 (必填) + GameID string `json:"GameID,omitempty"` // 提供程序游戏代码 (可选) + GameRoundID string `json:"GameRoundID,omitempty"` // 提供商游戏回合 ID (可选) + ValidBetAmount float64 `json:"ValidBetAmount,omitempty"` // 扣除营业额奖金后的投注金额 (可选) + BetAmount float64 `json:"BetAmount,omitempty"` // 整个投注金额,不扣除营业额奖金 (可选) + TransactionAmount float64 `json:"TransactionAmount"` // 更改需要对玩家钱包执行的操作的金额 (必填) + TransactionID string `json:"TransactionID,omitempty"` // 当前交易的唯一标识符 (可选) + PayoutAmount float64 `json:"PayoutAmount,omitempty"` // 玩家的中奖金额 (可选) + PayoutDetail string `json:"PayoutDetail,omitempty"` // 提供商发送的有关玩家获胜的详细信息 (可选) + BetDetail string `json:"BetDetail,omitempty"` // 有关提供商发送的玩家投注的详细信息 (可选) + CommisionAmount float64 `json:"CommisionAmount,omitempty"` // 佣金金额 (可选) + JackpotAmount float64 `json:"JackpotAmount,omitempty"` // 头奖金额 (可选) + SettlementDate string `json:"SettlementDate,omitempty"` // 最终确定工资记录的日期 (可选) + JPBet float64 `json:"JPBet,omitempty"` // 提供商每次投注的累积奖金 (可选) + Status int `json:"Status"` // 当前交易的状态 (必填) + CreatedOn string `json:"CreatedOn"` // 交易日期 (必填) + ModifiedOn string `json:"ModifiedOn"` // 修改交易的日期 (必填) +} + +type CommonReq struct { + MemberName string `json:"MemberName"` // 操作员的玩家唯一标识符 (必填) + OperatorCode string `json:"OperatorCode"` // Seamless运算符的唯一标识符 + ProductID int64 `json:"ProductID"` // Seamless产品的唯一标识符 (必填) + MessageID string `json:"MessageID"` // 当前 API 请求的唯一标识符 (必填) + RequestTime string `json:"RequestTime"` // Request DateTime,格式为 yyyMDDHHMMSS (必填) + Sign string `json:"Sign"` // 请求的签名 (必填) + Transactions []Transaction `json:"Transactions"` // 交易对象的清单 (必填) +} + +type CommonResp struct { + ErrorCode int `json:"ErrorCode"` // 响应的状态代码 (必填) + ErrorMessage string `json:"ErrorMessage"` // 响应消息 (必填) + Balance float64 `json:"Balance"` // 最终信用余额 (必填,4位小数) + BeforeBalance float64 `json:"BeforeBalance"` // 以前的信用余额 (必填,4位小数) +} + +type CommonResp2 struct { + ErrorCode int `json:"ErrorCode"` // 响应的状态代码 (必填) + ErrorMessage string `json:"ErrorMessage"` // 响应消息 (必填) + Balance float64 `json:"Balance"` // 最终信用余额 (必填,4位小数) +} + +type MobileLoginReq struct { + MemberName string `json:"MemberName"` // 操作员的玩家唯一标识符 (必填) + OperatorCode string `json:"OperatorCode"` // Seamless运算符的唯一标识符 + ProductID int64 `json:"ProductID"` // Seamless产品的唯一标识符 (必填) + MessageID string `json:"MessageID"` // 当前 API 请求的唯一标识符 (必填) + RequestTime string `json:"RequestTime"` // Request DateTime,格式为 yyyMDDHHMMSS (必填) + Sign string `json:"Sign"` // 请求的签名 (必填) + Password string `json:"Password"` // 操作员中的玩家密码 +} + +type BuyInReq struct { + MemberName string `json:"MemberName"` // 操作员的玩家唯一标识符 (必填) + OperatorCode string `json:"OperatorCode"` // Seamless运算符的唯一标识符 + ProductID int64 `json:"ProductID"` // Seamless产品的唯一标识符 (必填) + MessageID string `json:"MessageID"` // 当前 API 请求的唯一标识符 (必填) + RequestTime string `json:"RequestTime"` // Request DateTime,格式为 yyyMDDHHMMSS (必填) + Sign string `json:"Sign"` // 请求的签名 (必填) + Transaction Transaction `json:"Transaction"` // 单一交易对象 + CheckLimit bool `json:"CheckLimit"` // 查看买入限制 +} + +type BuyOutReq struct { + MemberName string `json:"MemberName"` // 操作员的玩家唯一标识符 (必填) + OperatorCode string `json:"OperatorCode"` // Seamless运算符的唯一标识符 + ProductID int64 `json:"ProductID"` // Seamless产品的唯一标识符 (必填) + MessageID string `json:"MessageID"` // 当前 API 请求的唯一标识符 (必填) + RequestTime string `json:"RequestTime"` // Request DateTime,格式为 yyyMDDHHMMSS (必填) + Sign string `json:"Sign"` // 请求的签名 (必填) + Transaction Transaction `json:"Transaction"` // 单一交易对象 + IsAddWager bool `json:"IsAddWager"` // 用于检查在买断期间是否添加了工资记录的标识符 +} diff --git a/modules/web/providers/handler.go b/modules/web/providers/handler.go new file mode 100644 index 0000000..b274a09 --- /dev/null +++ b/modules/web/providers/handler.go @@ -0,0 +1,44 @@ +package providers + +// func (e *EnterGameReq) EnterGame() string { +// providerID := e.ProviderID +// gameID := e.GameID +// uid := e.UID +// token := e.Token +// lang := e.Lang +// ct := e.CurrencyType +// isDemo := e.IsDemo +// subID := e.SubID +// provider := call.GetConfigGameProvider(providerID) +// if provider == nil { +// return "" +// } +// if err := db.Redis().SetData(common.GetRedisKeyGameCurrency(uid), int(ct), common.RedisExpireGameEnter); err != nil { +// log.Error("err:%v", err) +// return "" +// } +// switch provider.ProviderID { +// case common.ProviderTada: +// return tada.EnterGame(gameID, uid, token, lang, isDemo) +// case common.ProviderSexy: +// // 不支持demo +// if isDemo { +// return "" +// } +// return awc.EnterGame(gameID, uid, ct, token, lang, subID) +// case common.ProviderPGSoft: +// // 不支持demo +// if isDemo { +// return "" +// } +// return pgsoft.EnterGame(gameID, uid, token, lang) +// case common.ProviderEvolutionGaming: +// // 不支持demo +// if isDemo { +// return "" +// } +// return gs.EnterGame(gameID, uid, token, lang) +// default: +// return "" +// } +// } diff --git a/modules/web/providers/pgsoft/base.go b/modules/web/providers/pgsoft/base.go new file mode 100644 index 0000000..1ff2e28 --- /dev/null +++ b/modules/web/providers/pgsoft/base.go @@ -0,0 +1,59 @@ +package pgsoft + +import ( + "fmt" + "net/url" + "server/common" + "server/config" + "server/modules/web/providers/base" + + "github.com/liangdas/mqant/log" +) + +type Sub struct { + Base *base.Base +} + +func NewSub(base *base.Base) { + base.Sub = &Sub{Base: base} + base.SubInitRouter = PGSoft + base.SettleWithoutBet = true +} + +func (s *Sub) Init() { + if config.GetBase().Release { + API = APIURL + OperatorToken = OperatorTokenRelease + SecretKey = SecretKeyRelease + Salt = SaltRelease + } else { + API = APITest + OperatorToken = OperatorTokenTest + SecretKey = SecretKeyTest + Salt = SaltTest + } +} + +func (s *Sub) EnterGame() string { + gameid := s.Base.EnterGameReq.GameID + token := common.GetProviderUserToken(s.Base.EnterGameReq.Token) + lang := s.Base.EnterGameReq.Lang + // reqURL := fmt.Sprintf("https://m.pgr-nmga.com/%d/index.html?btt=1&ot=%s&ops=%s&l=%s&oc=1", gameid, OperatorToken, token, lang) + // return reqURL + reqURL := API + "/external-game-launcher/api/v1/GetLaunchURLHTML" + log.Debug("enter pgsoft url:%s", reqURL) + + send := url.Values{} + send.Add("operator_token", OperatorToken) + send.Add("path", fmt.Sprintf("/%d/index.html", gameid)) + send.Add("url_type", "game-entry") + send.Add("client_ip", s.Base.IP) + send.Add("extra_args", fmt.Sprintf("btt=1&ops=%s&l=%s", token, lang)) + ret, err := HttpPostForm(reqURL, send) + if err != nil { + log.Error("err:%v", err) + return "" + } + log.Debug("pgsoft resp:%+v", len(ret)) + return ret +} diff --git a/modules/web/providers/pgsoft/handler.go b/modules/web/providers/pgsoft/handler.go new file mode 100644 index 0000000..7c711d5 --- /dev/null +++ b/modules/web/providers/pgsoft/handler.go @@ -0,0 +1,324 @@ +package pgsoft + +import ( + "fmt" + "server/call" + "server/common" + "server/db" + "server/modules/web/app" + "server/modules/web/providers/base" + "server/util" + "strconv" + "strings" + "time" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +func PGSoft(e *gin.RouterGroup) { + e.POST("/VerifySession", verifySession) + e.POST("/Cash/Get", cashGet) + e.POST("/Cash/TransferInOut", transInOut) + e.POST("/Cash/Adjustment", adjsutment) +} + +func verifySession(c *gin.Context) { + a := app.NewApp(c) + resp := &CommonResp{} + a.RetData = resp + thisRet := new(verifySessionResp) + errCode := "" + flag := false + defer func() { + if flag { + a.ResponseB() + return + } + if errCode != "" { + resp.Error = &Error{ + Code: errCode, + } + } else { + resp.Data = thisRet + } + a.ResponseB() + }() + req := new(verifySessionReq) + c.Request.ParseForm() + util.ParseFormReq(c.Request.PostForm, req) + log.Debug("pgsoft VerifySession req:%+v,traceID:%v", c.Request.Form, c.Query("trace_id")) + if req.OperatorToken != OperatorToken || req.SecretKey != SecretKey { + errCode = "1034" + return + } + if a.ShouldRoute(req, "OperatorPlayerSession", common.ProviderAPITypePostform) { + flag = true + return + } + // 验证token + uid, _ := db.Redis().GetInt(common.GetRedisKeyToken(req.OperatorPlayerSession)) + if uid == 0 { + errCode = "1034" + return + } + currency, err := db.Redis().GetInt(common.GetRedisKeyGameCurrency(uid)) + if err != nil { + log.Error("err:%v", err) + errCode = "1034" + return + } + p, _ := call.GetUserXInfo(uid, "nick") + thisRet.PlayerName = common.GetProviderUserName(fmt.Sprintf("%v", uid)) + thisRet.Currency = strings.ToUpper(common.CurrencyType(currency).GetCurrencyName()) + thisRet.Nickname = p.Nick + log.Debug("thisRet:%+v", thisRet) +} + +func cashGet(c *gin.Context) { + a := app.NewApp(c) + resp := &CommonResp{} + a.RetData = resp + thisRet := new(CashGetResp) + errCode := "" + flag := false + defer func() { + if flag { + a.ResponseB() + return + } + if errCode != "" { + resp.Error = &Error{ + Code: errCode, + } + } else { + resp.Data = thisRet + } + a.ResponseB() + }() + req := new(CashGetReq) + c.Request.ParseForm() + util.ParseFormReq(c.Request.PostForm, req) + log.Debug("pgsoft cashGet req:%+v,traceID:%v", req, c.Query("trace_id")) + if req.OperatorToken != OperatorToken || req.SecretKey != SecretKey { + errCode = "1034" + return + } + if a.ShouldRoute(req, "OperatorPlayerSession", common.ProviderAPITypePostform) { + flag = true + return + } + // 验证token + uid, _ := db.Redis().GetInt(common.GetRedisKeyToken(req.OperatorPlayerSession)) + if uid == 0 { + errCode = "1034" + return + } + // reqUID, err := strconv.Atoi(req.PlayerName) + // if err != nil || reqUID != uid { + // errCode = "1034" + // return + // } + currency, err := db.Redis().GetInt(common.GetRedisKeyGameCurrency(uid)) + if err != nil { + log.Error("err:%v", err) + errCode = "1034" + return + } + ct := common.CurrencyType(currency) + thisRet.CurrencyCode = strings.ToUpper(ct.GetCurrencyName()) + thisRet.BalanceAmount = call.GetUserCurrencyFloat(uid, ct, 2) + thisRet.UpdatedTime = time.Now().Unix() +} + +func transInOut(c *gin.Context) { + a := app.NewApp(c) + resp := &CommonResp{} + a.RetData = resp + thisRet := new(TransInOutResp) + errCode := "" + flag := false + defer func() { + if flag { + a.ResponseB() + return + } + if errCode != "" { + resp.Error = &Error{ + Code: errCode, + } + } else { + resp.Data = thisRet + } + a.ResponseB() + }() + req := new(TransInOutReq) + c.Request.ParseForm() + util.ParseFormReq(c.Request.PostForm, req) + log.Debug("pgsoft transInOut req:%+v,traceID:%v", req, c.Query("trace_id")) + if req.OperatorToken != OperatorToken || req.SecretKey != SecretKey { + errCode = "1034" + return + } + // 验证token + // uid, _ := db.Redis().GetInt(common.GetRedisKeyToken(req.OperatorPlayerSession)) + // if uid == 0 { + // errCode = "1034" + // return + // } + if a.ShouldRoute(req, "PlayerName", common.ProviderAPITypePostform) { + flag = true + return + } + uid, err := strconv.Atoi(req.PlayerName) + if err != nil { + errCode = "1034" + return + } + if !db.Mysql().Exist(&common.PlayerDBInfo{Id: uid}) { + errCode = "1034" + return + } + currency := common.GetCurrencyID(req.CurrencyCode) + if !currency.IsValid() { + log.Error("unknown currency:%v", currency) + errCode = "1034" + return + } + ct := common.CurrencyType(currency) + st := common.SessionTypeBet + if !req.IsWager { + st = common.SessionTypeSettle + } + provider := call.GetConfigGameProvider(common.ProviderPGSoft) + if provider == nil { + errCode = "1034" + return + } + if req.BetAmount == 0 && req.TransferAmount == 0 { + thisRet.CurrencyCode = strings.ToUpper(ct.GetCurrencyName()) + thisRet.BalanceAmount = call.GetUserCurrencyFloat(uid, ct, 2) + thisRet.UpdatedTime = req.UpdatedTime + return + } + betResp := base.SessionBet(&base.BetReq{ + UID: uid, + CurrencyType: ct, + SettleAmount: common.CashFloat64ToInt64(req.WinAmount), + BetAmount: common.CashFloat64ToInt64(req.BetAmount), + TurnOver: common.CashFloat64ToInt64(req.BetAmount), + SessionType: st, + GameID: req.GameID, + Provider: provider, + BetID: req.TansactionID, + SessionID: req.ParentBetID, + Time: req.UpdatedTime, + // SettleWithoutBet: true, + }) + log.Debug("betResp:%+v", betResp) + if betResp.Code != 0 { + if betResp.Code == base.CodeAccepted { + thisRet.BalanceAmount = call.GetUserCurrencyFloat(uid, ct, 2) + thisRet.CurrencyCode = strings.ToUpper(ct.GetCurrencyName()) + thisRet.UpdatedTime = req.UpdatedTime + } else { + errCode = "1034" + if betResp.Code == base.CodeNotEnoughAmount { + errCode = "3202" + } + } + return + } + + thisRet.CurrencyCode = strings.ToUpper(ct.GetCurrencyName()) + thisRet.BalanceAmount = util.Decimal(float64(betResp.Balance)/common.DecimalDigits, 2) + thisRet.UpdatedTime = req.UpdatedTime +} + +func adjsutment(c *gin.Context) { + a := app.NewApp(c) + resp := &CommonResp{} + a.RetData = resp + thisRet := new(AdjustmentResp) + errCode := "" + flag := false + defer func() { + if flag { + a.ResponseB() + return + } + if errCode != "" { + resp.Error = &Error{ + Code: errCode, + } + } else { + resp.Data = thisRet + } + a.ResponseB() + }() + req := new(AdjustmentReq) + c.Request.ParseForm() + util.ParseFormReq(c.Request.PostForm, req) + log.Debug("pgsoft adjsutment req:%+v,traceID:%v", req, c.Query("trace_id")) + if req.OperatorToken != OperatorToken || req.SecretKey != SecretKey { + errCode = "1034" + return + } + if a.ShouldRoute(req, "PlayerName", common.ProviderAPITypePostform) { + flag = true + return + } + uid, err := strconv.Atoi(req.PlayerName) + if err != nil { + log.Error("err:%v", err) + errCode = "1034" + return + } + if !db.Mysql().Exist(&common.PlayerDBInfo{Id: uid}) { + errCode = "1034" + return + } + + ct := common.GetCurrencyID(req.CurrencyCode) + if !ct.IsValid() { + log.Error("unknown currency:%v", ct) + errCode = "1034" + return + } + + provider := call.GetConfigGameProvider(common.ProviderPGSoft) + if provider == nil { + errCode = "1034" + return + } + if req.TransferAmount != 0 { + adjustResp := base.Adjustment(&base.AdjustmentReq{ + UID: uid, + CurrencyType: ct, + Amount: common.CashFloat64ToInt64(req.TransferAmount), + Provider: provider, + BetID: req.AdjustmentTransaction, + Time: req.AdjustmentTime / 1000, + Ess: req.TransactionType, + Type: common.SessionTypeAdjustment, + }) + if adjustResp.Code == base.CodeAccepted { + thisRet.BalanceAfter = call.GetUserCurrencyFloat(uid, ct, 2) + thisRet.BalanceBefore = thisRet.BalanceAfter - req.TransferAmount + } else if adjustResp.Code != base.CodeOk { + errCode = "1034" + if adjustResp.Code == base.CodeNotEnoughAmount { + errCode = "3202" + } + return + } else { + thisRet.BalanceBefore = util.Decimal(float64(adjustResp.BeforeBalance)/common.DecimalDigits, 2) + thisRet.BalanceAfter = util.Decimal(float64(adjustResp.Balance)/common.DecimalDigits, 2) + } + } else { + thisRet.BalanceBefore = call.GetUserCurrencyFloat(uid, ct, 2) + thisRet.BalanceAfter = thisRet.BalanceBefore + } + thisRet.AdjustAmount = req.TransferAmount + thisRet.UpdatedTime = time.Now().Unix() +} diff --git a/modules/web/providers/pgsoft/values.go b/modules/web/providers/pgsoft/values.go new file mode 100644 index 0000000..7829fb2 --- /dev/null +++ b/modules/web/providers/pgsoft/values.go @@ -0,0 +1,221 @@ +package pgsoft + +import ( + "io" + "net/http" + "net/url" + "strings" + "time" + + "github.com/liangdas/mqant/log" +) + +const ( + APIURL = "https://api.pg-bo.co" + APITest = "https://api.pg-bo.me" + // APIURL = "https://m.pgr-nmga.com" + // APITest = "https://m.pg-redirect.net" + OperatorTokenTest = "164a855bf5129a8448dffc31ff9ec86c" + SecretKeyTest = "c441ef6f2297b8390e2d2dad353599fa" + SaltTest = "d6b791314cd04dc6d82e372a11bfd7bd" + OperatorTokenRelease = "4735860F-03A6-5C31-EDD3-3484B0B8E8A0" + SecretKeyRelease = "687CCAFA0D12C0081F48E7D307FD18E4" + SaltRelease = "85238F6B2158D393F447D5F03CA047FB" +) + +var ( + OperatorToken = "" + SecretKey = "" + Salt = "" +) + +var ( + API = "" +) + +type Error struct { + Code string + Message string +} + +type CommonResp struct { + Data interface{} `json:"data"` + Error *Error `json:"error"` +} + +type verifySessionReq struct { + OperatorToken string `json:"operator_token"` + SecretKey string `json:"secret_key"` + OperatorPlayerSession string `json:"operator_player_session"` + GameID int `json:"game_id"` + IP string `json:"ip"` + CustomParameter string `json:"custom_parameter"` + BetType int `json:"bet_type"` +} + +type verifySessionResp struct { + PlayerName string `json:"player_name"` + Nickname string `json:"nickname"` + Currency string `json:"currency"` +} + +type CashGetReq struct { + OperatorToken string `json:"operator_token"` + SecretKey string `json:"secret_key"` + PlayerName string `json:"player_name"` + OperatorPlayerSession string `json:"operator_player_session"` + GameID int `json:"game_id"` +} + +type CashGetResp struct { + CurrencyCode string `json:"currency_code"` + BalanceAmount float64 `json:"balance_amount"` + UpdatedTime int64 `json:"updated_time"` +} + +type TransInOutReq struct { + OperatorToken string `json:"operator_token"` + SecretKey string `json:"secret_key"` + OperatorPlayerSession string `json:"operator_player_session"` + PlayerName string `json:"player_name"` + GameID int `json:"game_id"` + ParentBetID string `json:"parent_bet_id"` + BetID string `json:"bet_id"` + CurrencyCode string `json:"currency_code"` + BetAmount float64 `json:"bet_amount"` + WinAmount float64 `json:"win_amount"` + TransferAmount float64 `json:"transfer_amount"` + TansactionID string `json:"transaction_id"` + WalletType string `json:"wallet_type"` + BetType int `json:"bet_type"` + Platform string `json:"platform"` + CreateTime int64 `json:"create_time"` + UpdatedTime int64 `json:"updated_time"` + IsValidateBet bool `json:"is_validate_bet"` // 表示该请求是否是为进行验证而重新发送的交易 True: 重新发送的交易 False: 正常交易 + IsAdjustment bool `json:"is_adjustment"` // 表示该请求是否是待处理投注的调整或正常交易 True: 调整 False: 正常交易 + IsParentZeroStake bool `json:"is_parent_zero_stake"` // 表示该请求在第一轮投注中的投注金 额是否为0 True: 在第一轮投注中的投注金额为0(针对至尊百家乐的飞牌操作)False: 在第一轮投注中的投注金额大于 0 + IsFeature bool `json:"is_feature"` // 表示旋转类型 True: 特色旋转 False: 普通旋转 + IsFeatureBuy bool `json:"is_feature_buy"` // 表示购买奖金游戏 注:仅适用于具有购买奖金游戏功能的游戏 + IsWager bool `json:"is_wager"` // 表示该交易是否为投注 + IsEndRound bool `json:"is_end_round"` // 表示当前游戏投注是否已结束 + FreeGameTransactionID string `json:"free_game_transaction_id"` // 免费游戏的唯一标识符 只有在免费游戏分配给玩家时才会出现。注:仅适用于以外部应用编程接口API创建的免费游戏 + FreeGameName string `json:"free_game_name"` // 免费游戏名称只有在免费游戏分配给玩家时才会出现。 + FreeGameID int `json:"free_game_id"` // 免费游戏的唯一标识符 只有在免费游戏分配给玩家时才会出现。 + IsMinusCount bool `json:"is_minus_count"` // 免费游戏中的旋转类型 True: 普通旋转(扣除免费游戏次数) False: 免费旋转 + BonusTransactionID string `json:"bonus_transaction_id"` // 红利游戏的唯一标识符 只有在红利分配给玩家时才会出现。注:仅适用于以外部应用编程接口API创建的红利游戏 + BonusName string `json:"bonus_name"` // 红利游戏名称 只有在红利分配给玩家时才会出现。 + BonusID int `json:"bonus_id"` // 红利游戏的唯一标识符 只有在红利分配给玩家时才会出现。 + BonusBalanceAmount float64 `json:"bonus_balance_amount"` // 红利钱包的红利总金额 只有在玩家选择用现金完成红利时才会出现。 + BonusRatioAmount float64 `json:"bonus_ratio_amount"` // 红利游戏中玩家需要达到的流水金额 只有在玩家选择用现金完成红利时才会出现。 +} + +type TransInOutResp struct { + CurrencyCode string `json:"currency_code"` + BalanceAmount float64 `json:"balance_amount"` + UpdatedTime int64 `json:"updated_time"` +} + +type AdjustmentReq struct { + OperatorToken string `json:"operator_token"` + SecretKey string `json:"secret_key"` + PlayerName string `json:"player_name"` + CurrencyCode string `json:"currency_code"` + BetType int `json:"bet_type"` + TransferAmount float64 `json:"transfer_amount"` + AdjustmentTime int64 `json:"adjustment_time"` + AdjustmentID string `json:"adjustment_id"` + AdjustmentTransaction string `json:"adjustment_transaction_id"` + TransactionType string +} + +type AdjustmentResp struct { + AdjustAmount float64 `json:"adjust_amount"` + BalanceBefore float64 `json:"balance_before"` + BalanceAfter float64 `json:"balance_after"` + UpdatedTime int64 `json:"updated_time"` +} + +// BetReq 请求下注 +type BetReq struct { + ReqID string `json:"reqId"` // 请求识别码 + Token string `json:"token"` // token + Currency string `json:"Currency"` + Game int `json:"game"` // 游戏代码 + Round int64 `json:"round"` // 注单唯一识别值 + WagersTime int64 `json:"wagersTime"` // 注单结账时间戳 + BetAmount float64 `json:"betAmount"` // 押注金额 + WinLoseAmount float64 `json:"winloseAmount"` // 发奖金额 + IsFreeRound bool `json:"isFreeRound"` // 为true时表示离线开奖 + UserId string `json:"userId"` // 玩家账号唯一值 + TransactionID int64 `json:"transactionId"` // 离线开奖触发局局号 + Platform string `json:"platform"` // 玩家装置信息 +} + +type BetResp struct { + ErrorCode int `json:"errorCode"` // 0成功 + Message string `json:"message"` // 信息 + UserName string `json:"username"` // 账号唯一识别名称 + Currency string `json:"currency"` // 身上持有币种 + Balance float64 `json:"balance"` // 余额 + TxID int64 `json:"txId"` // 营运商注单号 + Token string `json:"token"` // token +} + +// SessionBetReq 请求下注 +type SessionBetReq struct { + ReqID string `json:"reqId"` // 请求识别码 + Token string `json:"token"` // token + Currency string `json:"Currency"` + Game int `json:"game"` // 游戏代码 + Round int64 `json:"round"` // 注单唯一识别值 + WagersTime int64 `json:"wagersTime"` // 注单结账时间戳 + BetAmount float64 `json:"betAmount"` // 押注金额 + WinLoseAmount float64 `json:"winloseAmount"` // 发奖金额 + SessionID int64 `json:"sessionId"` // 牌局唯一值 + Type int `json:"type"` // 注单类型 1下注 2结算 + UserId string `json:"userId"` // 玩家账号唯一值 + TurnOver float64 `json:"turnover"` // 有效投注 + Preserve float64 `json:"preserve"` // 有效投注 + Platform string `json:"platform"` // 玩家装置信息 + SessionTotalBet float64 `json:"sessionTotalBet"` // 整一局总投注 +} + +// HttpPostForm post请求 +func HttpPostForm(target string, send url.Values) (string, error) { + log.Debug("send:%+v", send.Encode()) + // ul += fmt.Sprintf("%v=%v", "sign", util.CalculateMD5(oid+strconv.Itoa(apiReq.AdvChannel)+apiReq.ClientDate+apiReq.Money+values.ZYAppKey)) + + req, err := http.NewRequest("POST", target, strings.NewReader(send.Encode())) + if err != nil { + log.Error("err:%v", err) + return "", err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("Cache-Control", "no-cache, no-store, must-revalidate") + // req.Header.Set("Accept", "application/problem+json") + // req.Header.Set("Authorization", "api-key "+config.GetConfig().Web.OrangePay.APISecret) + // req.Header.Set("Authorization", "api-key "+OrangeAPISecret) + + client := &http.Client{ + Timeout: 5 * time.Second, + } + log.Debug("req:%+v", req) + resp, err := client.Do(req) + if err != nil { + log.Error("http post call err:%v", err) + return "", err + } + // if resp.StatusCode != http.StatusOK { + // log.Error("req fail err code:%v", resp.StatusCode) + // return errors.New("http req fail") + // } + defer resp.Body.Close() + body, _ := io.ReadAll(resp.Body) + res := string(body) + log.Debug("response Body%v:", res) + // if err := json.Unmarshal(body, ret); err != nil { + // log.Error("unmarshal fail err:%v", err) + // return err + // } + return res, nil +} diff --git a/modules/web/providers/tada/base.go b/modules/web/providers/tada/base.go new file mode 100644 index 0000000..6ed6ea3 --- /dev/null +++ b/modules/web/providers/tada/base.go @@ -0,0 +1,82 @@ +package tada + +import ( + "fmt" + "server/call" + "server/common" + "server/config" + "server/modules/web/providers/base" + "server/util" + + "github.com/liangdas/mqant/log" +) + +type Sub struct { + Base *base.Base +} + +func NewSub(base *base.Base) { + base.Sub = &Sub{Base: base} + base.SubInitRouter = Tada +} + +func (s *Sub) Init() { +} + +type CommonResp struct { + ErrorCode int + Message string + Data interface{} +} + +type EnterGameReq struct { + Token string + GameId int `json:"GameId"` + Lang string +} + +func (s *Sub) EnterGame() string { + lang := s.Base.EnterGameReq.Lang + isDemo := s.Base.EnterGameReq.IsDemo + gameid := s.Base.EnterGameReq.GameID + token := common.GetProviderUserToken(s.Base.EnterGameReq.Token) + + reqLang := "en-US" + if lang == "pt" { + reqLang = "pt-BR" + } + channel := call.GetChannelByID(s.Base.ChannelID) + homeURL := "" + if channel != nil && channel.URL != "" { + homeURL = "HomeUrl=https://www." + channel.URL + } + if isDemo { + ret := "https" + fmt.Sprintf("%s/%d/%s", DemoURL, gameid, reqLang) + if homeURL != "" { + ret += "?" + homeURL + } + return ret + } + req := &EnterGameReq{ + Token: token, + GameId: gameid, + Lang: reqLang, + } + url := APITest + if config.GetBase().Release { + url = APIURL + } + url += LoginAPI + reqURL := fmt.Sprintf("%s?Token=%s&GameId=%d&Lang=%s&AgentId=%s&Key=%s", url, token, gameid, reqLang, AgentID, GetSignKey(req)) + if homeURL != "" { + reqURL += "&" + homeURL + } + log.Debug("enter tada:%+v,url:%s", *req, reqURL) + ret := new(CommonResp) + util.HttpGet(reqURL, &ret) + log.Debug("tada resp:%+v", ret) + if str, ok := ret.Data.(string); ok { + return str + } + return "" +} diff --git a/modules/web/providers/tada/handler.go b/modules/web/providers/tada/handler.go new file mode 100644 index 0000000..1800176 --- /dev/null +++ b/modules/web/providers/tada/handler.go @@ -0,0 +1,293 @@ +package tada + +import ( + "fmt" + "server/call" + "server/common" + "server/db" + "server/modules/web/app" + "server/modules/web/providers/base" + "server/util" + "strconv" + "strings" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +func Tada(e *gin.RouterGroup) { + e.POST("/auth", auth) + e.POST("/bet", bet) + e.POST("/sessionBet", sessionBet) + e.POST("/cancelSessionBet", cancelSessionBet) +} + +func auth(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.ResponseB() + }() + resp := new(AuthResp) + req := new(AuthReq) + if !a.SB(req) { + resp.ErrorCode = ErrorCodeInvalidParameter + return + } + log.Debug("tada auth req:%+v", req) + a.RetData = resp + + if a.ShouldRoute(req, "Token", common.ProviderAPITypeJson) { + return + } + + // 验证token + uid, _ := db.Redis().GetInt(common.GetRedisKeyToken(req.Token)) + if uid == 0 { + resp.ErrorCode = ErrorCodeTokenExpired + return + } + currency, err := db.Redis().GetInt(common.GetRedisKeyGameCurrency(uid)) + if err != nil { + log.Error("err:%v", err) + resp.ErrorCode = ErrorCodeOther + return + } + resp.UserName = common.GetProviderUserName(fmt.Sprintf("%v", uid)) + resp.Currency = strings.ToUpper(common.CurrencyType(currency).GetCurrencyName()) + resp.Balance = call.GetUserCurrencyFloat(uid, common.CurrencyType(currency), 4) + resp.Token = common.GetProviderUserToken(req.Token) + log.Debug("tada auth resp:%+v", resp) +} + +func bet(c *gin.Context) { + a := app.NewApp(c) + resp := new(BetResp) + defer func() { + log.Debug("tada bet resp:%+v", *resp) + a.ResponseB() + }() + req := new(BetReq) + if !a.SB(req) { + resp.ErrorCode = ErrorCodeInvalidParameter + return + } + log.Debug("tada bet req:%+v", req) + a.RetData = resp + + if a.ShouldRoute(req, "Token", common.ProviderAPITypeJson) { + return + } + + ct := common.GetCurrencyID(req.Currency) + if !ct.IsValid() { + resp.ErrorCode = ErrorCodeInvalidParameter + return + } + provider := call.GetConfigGameProvider(common.ProviderTada) + if provider == nil { + resp.ErrorCode = ErrorCodeOther + return + } + // defer func() { + // // 更新游戏次数 + // if resp.ErrorCode == ErrorCodeSuccess { + // call.UpdateGameSort(provider.ProviderID, req.Game) + // } + // }() + // 验证token + uid, _ := db.Redis().GetInt(common.GetRedisKeyToken(req.Token)) + if uid == 0 { + var err error + uid, err = strconv.Atoi(req.UserId) + if err != nil || uid == 0 { + log.Error("err:%v", err) + resp.ErrorCode = ErrorCodeTokenExpired + return + } + } + + betResp := base.Bet(&base.BetReq{ + UID: uid, + CurrencyType: ct, + SettleAmount: common.CashFloat64ToInt64(req.WinLoseAmount), + BetAmount: common.CashFloat64ToInt64(req.BetAmount), + GameID: req.Game, + Provider: provider, + BetID: fmt.Sprintf("%d", req.Round), + // SessionID: fmt.Sprintf("%d", req.SessionID), + Time: req.WagersTime, + }) + if betResp.Code == base.CodeInnerError { + resp.ErrorCode = ErrorCodeOther + return + } + if betResp.Code == base.CodeNotEnoughAmount { + resp.ErrorCode = ErrorCodeNotEnoughBalance + return + } + if betResp.Code == base.CodeAccepted { + resp.TxID = betResp.MyUUID + resp.ErrorCode = ErrorCodeAccepted + return + } + + resp.UserName = common.GetProviderUserName(fmt.Sprintf("%v", uid)) + resp.Currency = req.Currency + resp.Balance = util.Decimal(float64(betResp.Balance)/common.DecimalDigits, 4) + resp.TxID = betResp.MyUUID + resp.Token = common.GetProviderUserToken(req.Token) +} + +func sessionBet(c *gin.Context) { + a := app.NewApp(c) + resp := new(BetResp) + defer func() { + log.Debug("tada sessionBet resp:%+v", *resp) + a.ResponseB() + }() + req := new(SessionBetReq) + if !a.SB(req) { + resp.ErrorCode = ErrorCodeInvalidParameter + return + } + log.Debug("tada sessionBet req:%+v", req) + a.RetData = resp + + if a.ShouldRoute(req, "Token", common.ProviderAPITypeJson) { + return + } + + ct := common.GetCurrencyID(req.Currency) + if !ct.IsValid() { + resp.ErrorCode = ErrorCodeInvalidParameter + return + } + provider := call.GetConfigGameProvider(common.ProviderTada) + if provider == nil { + resp.ErrorCode = ErrorCodeOther + return + } + // 验证token + uid, _ := db.Redis().GetInt(common.GetRedisKeyToken(req.Token)) + if uid == 0 { + var err error + uid, err = strconv.Atoi(req.UserId) + if err != nil || uid == 0 { + log.Error("err:%v", err) + resp.ErrorCode = ErrorCodeTokenExpired + return + } + } + betResp := base.SessionBet(&base.BetReq{ + UID: uid, + CurrencyType: ct, + SettleAmount: common.CashFloat64ToInt64(req.WinLoseAmount), + BetAmount: common.CashFloat64ToInt64(req.BetAmount), + TurnOver: common.CashFloat64ToInt64(req.TurnOver), + Preserve: common.CashFloat64ToInt64(req.Preserve), + SessionType: req.Type, + GameID: req.Game, + Provider: provider, + BetID: fmt.Sprintf("%d", req.Round), + SessionID: fmt.Sprintf("%d", req.SessionID), + Time: req.WagersTime, + }) + + if betResp.Code == base.CodeInnerError { + resp.ErrorCode = ErrorCodeOther + return + } + if betResp.Code == base.CodeNotEnoughAmount { + resp.ErrorCode = ErrorCodeNotEnoughBalance + return + } + if betResp.Code == base.CodeAccepted { + resp.TxID = betResp.MyUUID + resp.ErrorCode = ErrorCodeAccepted + return + } + + // defer func() { + // // 更新游戏次数 + // if resp.ErrorCode == ErrorCodeSuccess { + // call.UpdateGameSort(provider.ProviderID, req.Game) + // } + // }() + + resp.UserName = common.GetProviderUserName(fmt.Sprintf("%v", uid)) + resp.Currency = req.Currency + resp.Balance = util.Decimal(float64(betResp.Balance)/common.DecimalDigits, 4) + resp.TxID = betResp.MyUUID + resp.Token = common.GetProviderUserToken(req.Token) +} + +func cancelSessionBet(c *gin.Context) { + a := app.NewApp(c) + resp := new(BetResp) + defer func() { + log.Debug("tada cancelSessionBet resp:%+v", *resp) + a.ResponseB() + }() + req := new(SessionBetReq) + if !a.SB(req) { + resp.ErrorCode = ErrorCodeInvalidParameter + return + } + log.Debug("tada sessionBet req:%+v", req) + a.RetData = resp + + if a.ShouldRoute(req, "Token", common.ProviderAPITypeJson) { + return + } + + ct := common.GetCurrencyID(req.Currency) + if !ct.IsValid() { + resp.ErrorCode = ErrorCodeInvalidParameter + return + } + provider := call.GetConfigGameProvider(common.ProviderTada) + if provider == nil { + resp.ErrorCode = ErrorCodeOther + return + } + // 验证token + uid, _ := db.Redis().GetInt(common.GetRedisKeyToken(req.Token)) + if uid == 0 { + var err error + uid, err = strconv.Atoi(req.UserId) + if err != nil || uid == 0 { + log.Error("err:%v", err) + resp.ErrorCode = ErrorCodeTokenExpired + return + } + } + betResp := base.CancelSessionBet(&base.BetReq{ + UID: uid, + CurrencyType: ct, + BetAmount: common.CashFloat64ToInt64(req.BetAmount), + Preserve: common.CashFloat64ToInt64(req.Preserve), + BetID: fmt.Sprintf("%d", req.Round), + SessionID: fmt.Sprintf("%d", req.SessionID), + GameID: req.Game, + Provider: provider, + Time: req.WagersTime, + SessionType: req.Type, + }) + + if betResp.Code == base.CodeInnerError { + resp.ErrorCode = ErrorCodeOther + return + } + + if betResp.Code == base.CodeAccepted { + resp.TxID = betResp.MyUUID + resp.ErrorCode = ErrorCodeAccepted + return + } + + resp.UserName = common.GetProviderUserName(fmt.Sprintf("%v", uid)) + resp.Currency = req.Currency + resp.Balance = util.Decimal(float64(betResp.Balance)/common.DecimalDigits, 4) + resp.TxID = betResp.MyUUID + resp.Token = common.GetProviderUserToken(req.Token) +} diff --git a/modules/web/providers/tada/sign.go b/modules/web/providers/tada/sign.go new file mode 100644 index 0000000..e181777 --- /dev/null +++ b/modules/web/providers/tada/sign.go @@ -0,0 +1,32 @@ +package tada + +import ( + "fmt" + "reflect" + "server/config" + "server/util" + "time" +) + +func GetKeyG() string { + key := TestKey + if config.GetBase().Release { + key = AgentKey + } + return util.CalculateMD5(time.Now().UTC().Add(-4*time.Hour).Format("06012") + AgentID + key) +} + +func GetSignKey(req interface{}) string { + keyG := GetKeyG() + str := "" + ref := reflect.ValueOf(req) + reft := reflect.TypeOf(req) + if reft.Kind() == reflect.Ptr { + ref = ref.Elem() + reft = reft.Elem() + } + for i := 0; i < ref.NumField(); i++ { + str += fmt.Sprintf("%s=%v&", reft.Field(i).Name, ref.Field(i).Interface()) + } + return util.GenerateRandomString(6) + util.CalculateMD5(fmt.Sprintf("%sAgentId=%s", str, AgentID)+keyG) + util.GenerateRandomString(6) +} diff --git a/modules/web/providers/tada/values.go b/modules/web/providers/tada/values.go new file mode 100644 index 0000000..d906a35 --- /dev/null +++ b/modules/web/providers/tada/values.go @@ -0,0 +1,80 @@ +package tada + +const ( + DemoURL = "://tadagaming.com/plusplayer/PlusTrial" + APIURL = "https://wb-api.tadagaming.com/api1" + APITest = "https://uat-wb-api.tadagaming.com/api1" + AgentID = "whale_Seamless" + TestKey = "ffe04e1978eeb112d958ddb6139ac47b9b121dc8" + AgentKey = "3a22424c73cb9b2c14a288da480db5b8492ff7b1" + + LoginAPI = "/singleWallet/LoginWithoutRedirect" +) + +const ( + ErrorCodeSuccess = iota + ErrorCodeAccepted // 注单已承认 + ErrorCodeNotEnoughBalance // 余额不足 + ErrorCodeInvalidParameter // 参数无效 + ErrorCodeTokenExpired // token过期 + ErrorCodeOther // 其他错误 +) + +type AuthReq struct { + ReqID string `json:"reqId"` // 请求识别码 + Token string `json:"token"` // token +} + +type AuthResp struct { + ErrorCode int `json:"errorCode"` // 0成功 + Message string `json:"message"` // 信息 + UserName string `json:"username"` // 账号唯一识别名称 + Currency string `json:"currency"` // 身上持有币种 + Balance float64 `json:"balance"` // 余额 + Token string `json:"token"` // token +} + +// BetReq 请求下注 +type BetReq struct { + ReqID string `json:"reqId"` // 请求识别码 + Token string `json:"token"` // token + Currency string `json:"Currency"` + Game int `json:"game"` // 游戏代码 + Round int64 `json:"round"` // 注单唯一识别值 + WagersTime int64 `json:"wagersTime"` // 注单结账时间戳 + BetAmount float64 `json:"betAmount"` // 押注金额 + WinLoseAmount float64 `json:"winloseAmount"` // 发奖金额 + IsFreeRound bool `json:"isFreeRound"` // 为true时表示离线开奖 + UserId string `json:"userId"` // 玩家账号唯一值 + TransactionID int64 `json:"transactionId"` // 离线开奖触发局局号 + Platform string `json:"platform"` // 玩家装置信息 +} + +type BetResp struct { + ErrorCode int `json:"errorCode"` // 0成功 + Message string `json:"message"` // 信息 + UserName string `json:"username"` // 账号唯一识别名称 + Currency string `json:"currency"` // 身上持有币种 + Balance float64 `json:"balance"` // 余额 + TxID int64 `json:"txId"` // 营运商注单号 + Token string `json:"token"` // token +} + +// SessionBetReq 请求下注 +type SessionBetReq struct { + ReqID string `json:"reqId"` // 请求识别码 + Token string `json:"token"` // token + Currency string `json:"Currency"` + Game int `json:"game"` // 游戏代码 + Round int64 `json:"round"` // 注单唯一识别值 + WagersTime int64 `json:"wagersTime"` // 注单结账时间戳 + BetAmount float64 `json:"betAmount"` // 押注金额 + WinLoseAmount float64 `json:"winloseAmount"` // 发奖金额 + SessionID int64 `json:"sessionId"` // 牌局唯一值 + Type int `json:"type"` // 注单类型 1下注 2结算 + UserId string `json:"userId"` // 玩家账号唯一值 + TurnOver float64 `json:"turnover"` // 有效投注 + Preserve float64 `json:"preserve"` // 有效投注 + Platform string `json:"platform"` // 玩家装置信息 + SessionTotalBet float64 `json:"sessionTotalBet"` // 整一局总投注 +} diff --git a/modules/web/routers/routers.go b/modules/web/routers/routers.go new file mode 100644 index 0000000..f8255d6 --- /dev/null +++ b/modules/web/routers/routers.go @@ -0,0 +1,71 @@ +package routers + +import ( + "runtime" + "server/common" + "server/config" + "server/modules/web/middleware" + "server/modules/web/providers/all" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" +) + +func SetUpRouter() *gin.Engine { + if config.GetBase().Release { + gin.SetMode(gin.ReleaseMode) + // 禁用控制台颜色 + gin.DisableConsoleColor() + } else { + gin.SetMode(gin.DebugMode) + } + r := gin.New() + + // 跨域处理 + r.Use(middleware.CrosHandler()) + // r.Use(middleware.RateLimitMiddleware()) + r.Use(gin.RecoveryWithWriter(log.LogBeego(), func(c *gin.Context, err interface{}) { + buf := make([]byte, 1024) + runtime.Stack(buf, false) + log.Error("panic(%+v), stack:\n%s", err, string(buf)) + })) + // r.Use(middleware.UserLimitMiddleware()) + + r.GET("/", common.HealthCheck) + auth := r.Group("") + auth.Use(middleware.TokenMiddleWare()) + auth.Use(middleware.WhiteMiddleWare()) + // auth.Use(middleware.RateLimitMiddleware()) + { + sys(auth) + account(auth) + user(auth) + balance(auth) + activity(auth) + notice(auth) + mail(auth) + ftp(auth) + game(auth) + h5(auth) + firstpage(auth) + vip(auth) + share(auth) + task(auth) + telegram(auth) + ad(auth) + } + provider := r.Group("provider/") + if config.GetBase().Release { + provider.Use(middleware.WhiteIPs()) + } + all.InitRouter(provider) + + // td := provider.Group("tada") + // tada.Tada(td) + // ac := provider.Group("awc") + // awc.AWC(ac) + // pg := provider.Group("pg") + // pgsoft.PGSoft(pg) + + return r +} diff --git a/modules/web/routers/routers_account.go b/modules/web/routers/routers_account.go new file mode 100644 index 0000000..2172453 --- /dev/null +++ b/modules/web/routers/routers_account.go @@ -0,0 +1,30 @@ +package routers + +import ( + "server/modules/web/handler" + + "github.com/gin-gonic/gin" +) + +func account(e *gin.RouterGroup) { + e.POST("/account/email/code", handler.GetEmailCode) + e.POST("/account/email/regist", handler.EmailRegist) + e.POST("/account/email/login", handler.EmailLogin) + e.POST("/account/email/resetPass", handler.EmailResetPass) + + e.POST("/account/guestLogin", handler.GuestLogin) + e.POST("/account/gpLogin", handler.GPLogin) + e.POST("/account/fbLogin", handler.FBLogin) + e.POST("/account/tokenLogin", handler.TokenLogin) + e.POST("/account/phoneCode/get", handler.GetPhoneCode) + e.POST("/account/phoneCode/verify", handler.VerifyCode) + e.POST("/account/phoneCode/login", handler.PhoneCodeLogin) + e.POST("/account/phoneCode/bind", handler.BindingAccount) + e.POST("/account/phone/regist", handler.PhoneRegist) + e.POST("/account/phone/login", handler.PhoneLogin) + e.POST("/account/phone/resetPass", handler.PhoneResetPass) + + e.POST("/account/regist", handler.AccountRegist) + e.POST("/account/login", handler.AccountLogin) + e.POST("/account/resetPass", handler.AccountResetPass) +} diff --git a/modules/web/routers/routers_activity.go b/modules/web/routers/routers_activity.go new file mode 100644 index 0000000..6109d5a --- /dev/null +++ b/modules/web/routers/routers_activity.go @@ -0,0 +1,39 @@ +package routers + +import ( + "server/modules/web/handler" + + "github.com/gin-gonic/gin" +) + +func activity(e *gin.RouterGroup) { + e.POST("/promotions", handler.GetPromotions) + e.POST("/activity/upload", handler.UploadActivityData) + e.POST("/activity/all", handler.GetAllActivity) + e.POST("/activity/appSpin/info", handler.GetActivityAppSpinInfo) + e.POST("/activity/appSpin/draw", handler.DrawActivityAppSpin) + e.POST("/activity/pdd/info", handler.ActivityPddInfo) + e.POST("/activity/pdd/spin", handler.ActivityPddSpin) + e.POST("/activity/pdd/withdraw", handler.ActivityPddWithdraw) + e.POST("/activity/pdd/newReference", handler.ActivityPddNewReference) + e.POST("/activity/pdd/reference", handler.ActivityPddReference) + e.POST("/activity/freeSpin/info", handler.ActivityFreeSpinInfo) + e.POST("/activity/freeSpin/draw", handler.ActivityFreeSpinDraw) + e.POST("/activity/firstRechargeBack/info", handler.ActivityFirstRechargeBackInfo) + e.POST("/activity/firstRechargeBack/draw", handler.ActivityFirstRechargeBackDraw) + e.POST("/activity/luckyCode/info", handler.ActivityLuckyCodeInfo) + e.POST("/activity/luckyCode/draw", handler.ActivityLuckyCodeDraw) + e.POST("/activity/sign/info", handler.ActivitySignInfo) + e.POST("/activity/sign/draw", handler.ActivitySignDraw) + e.POST("/activity/breakGift/info", handler.ActivityBreakGiftInfo) + e.POST("/activity/weekCard/info", handler.ActivityWeekCardInfo) + e.POST("/activity/weekCard/draw", handler.ActivityWeekCardDraw) + e.POST("/activity/slots/info", handler.ActivitySlotsInfo) + e.POST("/activity/slots/draw", handler.ActivitySlotsDraw) + e.POST("/activity/slots/drawLast", handler.ActivitySlotsDrawLast) + e.POST("/activity/luckyShop/info", handler.ActivityLuckyShopInfo) + e.POST("/activity/sevenDayBox/info", handler.ActivitySevenDayBoxInfo) + e.POST("/activity/sevenDayBox/draw", handler.ActivitySevenDayBoxDraw) + e.POST("/activity/super/info", handler.ActivitySuperInfo) + e.POST("/activity/super/draw", handler.ActivitySuperDraw) +} diff --git a/modules/web/routers/routers_ad.go b/modules/web/routers/routers_ad.go new file mode 100644 index 0000000..b8a4f3f --- /dev/null +++ b/modules/web/routers/routers_ad.go @@ -0,0 +1,11 @@ +package routers + +import ( + "server/modules/web/handler" + + "github.com/gin-gonic/gin" +) + +func ad(e *gin.RouterGroup) { + e.POST("/ad/uploadFB", handler.UploadFB) +} diff --git a/modules/web/routers/routers_balance.go b/modules/web/routers/routers_balance.go new file mode 100644 index 0000000..801f2a3 --- /dev/null +++ b/modules/web/routers/routers_balance.go @@ -0,0 +1,20 @@ +package routers + +import ( + "server/modules/web/handler" + + "github.com/gin-gonic/gin" +) + +func balance(e *gin.RouterGroup) { + e.POST("/balance/recharge/order", handler.CheckRechargeOrder) + e.POST("/balance/recharge/info", handler.RechargeInfo) + e.POST("/balance/recharge/do", handler.PlayerRecharge) + e.POST("/balance/recharge/history", handler.RechargeHistory) + e.POST("/balance/withdraw/info", handler.WithdrawInfo) + e.POST("/balance/withdraw/check", handler.PlayerWithdrawCheck) + e.POST("/balance/withdraw/do", handler.PlayerWithdraw) + e.POST("/balance/withdraw/block/do", handler.PlayerWithdrawBlock) + e.POST("/balance/withdraw/history", handler.WithdrawHistory) + e.POST("/balance/history", handler.BalanceHistory) +} diff --git a/modules/web/routers/routers_firstpage.go b/modules/web/routers/routers_firstpage.go new file mode 100644 index 0000000..e474461 --- /dev/null +++ b/modules/web/routers/routers_firstpage.go @@ -0,0 +1,11 @@ +package routers + +import ( + "server/modules/web/handler" + + "github.com/gin-gonic/gin" +) + +func firstpage(e *gin.RouterGroup) { + e.POST("/firstpage", handler.FirstPage) +} diff --git a/modules/web/routers/routers_ftp.go b/modules/web/routers/routers_ftp.go new file mode 100644 index 0000000..9af9d89 --- /dev/null +++ b/modules/web/routers/routers_ftp.go @@ -0,0 +1,12 @@ +package routers + +import ( + "server/modules/web/handler" + + "github.com/gin-gonic/gin" +) + +func ftp(e *gin.RouterGroup) { + e.GET("/ftp/privacy/:id", handler.Privacy) + e.GET("/ftp/termsofservice/:id", handler.TermsofService) +} diff --git a/modules/web/routers/routers_game.go b/modules/web/routers/routers_game.go new file mode 100644 index 0000000..c54dd61 --- /dev/null +++ b/modules/web/routers/routers_game.go @@ -0,0 +1,14 @@ +package routers + +import ( + "server/modules/web/handler" + + "github.com/gin-gonic/gin" +) + +func game(e *gin.RouterGroup) { + e.POST("/game/list", handler.GameList) + e.POST("/game/enter", handler.EnterGame) + e.POST("/game/history", handler.GameHistory) + e.POST("/game/profile", handler.GameProfile) +} diff --git a/modules/web/routers/routers_h5.go b/modules/web/routers/routers_h5.go new file mode 100644 index 0000000..905f58b --- /dev/null +++ b/modules/web/routers/routers_h5.go @@ -0,0 +1,13 @@ +package routers + +import ( + "server/modules/web/handler" + + "github.com/gin-gonic/gin" +) + +func h5(e *gin.RouterGroup) { + e.GET("/h5/info/get", handler.H5Info) + e.GET("/h5/collect/draw", handler.H5CollectDraw) + e.GET("/h5/download/draw", handler.H5DownloadDraw) +} diff --git a/modules/web/routers/routers_mail.go b/modules/web/routers/routers_mail.go new file mode 100644 index 0000000..ee60c0d --- /dev/null +++ b/modules/web/routers/routers_mail.go @@ -0,0 +1,15 @@ +package routers + +import ( + "server/modules/web/handler" + + "github.com/gin-gonic/gin" +) + +func mail(e *gin.RouterGroup) { + e.POST("/mail/list", handler.MailList) + e.POST("/mail/read", handler.ReadMail) + // e.POST("/mail/draw", handler.DrawMail) + e.POST("/mail/delete", handler.DeleteMail) + e.POST("/mail/deleteAll", handler.DeleteMailAll) +} diff --git a/modules/web/routers/routers_notice.go b/modules/web/routers/routers_notice.go new file mode 100644 index 0000000..d6cd392 --- /dev/null +++ b/modules/web/routers/routers_notice.go @@ -0,0 +1,11 @@ +package routers + +import ( + "server/modules/web/handler" + + "github.com/gin-gonic/gin" +) + +func notice(e *gin.RouterGroup) { + e.GET("/notice/list", handler.NoticeList) +} diff --git a/modules/web/routers/routers_share.go b/modules/web/routers/routers_share.go new file mode 100644 index 0000000..728bb05 --- /dev/null +++ b/modules/web/routers/routers_share.go @@ -0,0 +1,16 @@ +package routers + +import ( + "server/modules/web/handler" + + "github.com/gin-gonic/gin" +) + +func share(e *gin.RouterGroup) { + e.POST("/share/info", handler.ShareInfo) + e.POST("/share/platform", handler.SharePlatformInfo) + e.POST("/share/withdraw", handler.ShareWithdraw) + // e.POST("/share/reference", handler.ShareReference) + // e.POST("/share/report", handler.ShareReport) + // e.POST("/share/transfer", handler.ShareTransfer) +} diff --git a/modules/web/routers/routers_sys.go b/modules/web/routers/routers_sys.go new file mode 100644 index 0000000..1d18ca0 --- /dev/null +++ b/modules/web/routers/routers_sys.go @@ -0,0 +1,14 @@ +package routers + +import ( + "server/modules/web/handler" + + "github.com/gin-gonic/gin" +) + +func sys(e *gin.RouterGroup) { + // e.POST("/sys/config", handler.SysConfig) + // e.POST("/sys/config2", handler.SysConfig2) + // e.GET("/sys/gameList", handler.GameList) + e.POST("/sys/config", handler.Config) +} diff --git a/modules/web/routers/routers_task.go b/modules/web/routers/routers_task.go new file mode 100644 index 0000000..7a02730 --- /dev/null +++ b/modules/web/routers/routers_task.go @@ -0,0 +1,12 @@ +package routers + +import ( + "server/modules/web/handler" + + "github.com/gin-gonic/gin" +) + +func task(e *gin.RouterGroup) { + e.POST("/task/info", handler.GetTaskInfo) + e.POST("/task/draw", handler.DrawTask) +} diff --git a/modules/web/routers/routers_telegram.go b/modules/web/routers/routers_telegram.go new file mode 100644 index 0000000..9716b6c --- /dev/null +++ b/modules/web/routers/routers_telegram.go @@ -0,0 +1,11 @@ +package routers + +import ( + "server/modules/web/handler" + + "github.com/gin-gonic/gin" +) + +func telegram(e *gin.RouterGroup) { + e.GET("/tg/luckyCode", handler.GetLuckyCode) +} diff --git a/modules/web/routers/routers_user.go b/modules/web/routers/routers_user.go new file mode 100644 index 0000000..7532b38 --- /dev/null +++ b/modules/web/routers/routers_user.go @@ -0,0 +1,13 @@ +package routers + +import ( + "server/modules/web/handler" + + "github.com/gin-gonic/gin" +) + +func user(e *gin.RouterGroup) { + e.POST("/user/info/get", handler.GetUserInfo) + e.POST("/user/info/edit", handler.EditUserInfo) + e.POST("/user/feedback", handler.Feedback) +} diff --git a/modules/web/routers/routers_vip.go b/modules/web/routers/routers_vip.go new file mode 100644 index 0000000..9ed60f7 --- /dev/null +++ b/modules/web/routers/routers_vip.go @@ -0,0 +1,13 @@ +package routers + +import ( + "server/modules/web/handler" + + "github.com/gin-gonic/gin" +) + +func vip(e *gin.RouterGroup) { + e.POST("/vip/info", handler.GetVipInfo) + e.POST("/vip/drawBonus", handler.DrawVipBonus) + e.POST("/vip/drawCashback", handler.DrawVipCashback) +} diff --git a/modules/web/timer.go b/modules/web/timer.go new file mode 100644 index 0000000..67d301f --- /dev/null +++ b/modules/web/timer.go @@ -0,0 +1,75 @@ +package web + +import ( + "fmt" + "server/call" + "server/common" + "server/config" + "server/db" + "server/modules/web/values" + "server/util" + "time" + + "github.com/liangdas/mqant/log" +) + +func FetchDatas() { + GetShareData() + GetActivitySlotsRank() +} + +var ( + FetchInterval = 5 * 60 + ShareDataRankNum = 8 // 排行榜显示8个 + ActivitySlotsRankNum = 10 // slots活动排行榜数目 +) + +// 分享数据 +func GetShareData() { + values.ShareTotalInviteReward = db.Mysql().Sum(&common.ShareInfo{}, "", "invite_reward") + values.ShareTotalBetReward = db.Mysql().Sum(&common.ShareInfo{}, "", "bet_reward") + + ranks := []*values.OneShareRank{} + err := db.Mysql().C().Raw(fmt.Sprintf("select uid,invite_reward + bet_reward as reward from share_info order by reward desc limit %d", ShareDataRankNum)).Scan(&ranks).Error + if err != nil { + log.Error("err:%v", err) + } + for _, v := range ranks { + robot := call.GetConfigShareRobotByID(v.UID) + if robot != nil { + v.Avatar = robot.Avatar + v.Nick = robot.Nick + } else { + p, _ := call.GetUserXInfo(v.UID, "mobile", "avatar") + v.Avatar = p.Avatar + v.Nick = p.Mobile + } + v.Nick = "*******" + v.Nick[len(v.Nick)-3:] + } + values.ShareRank = ranks + + time.AfterFunc(time.Duration(FetchInterval)*time.Second, GetShareData) +} + +// slots活动排行榜 +func GetActivitySlotsRank() { + list := []*common.ActivitySlotsData{} + str := "time2" + numberStr := "best_number2" + order := "best_number2 desc,time2 asc" + if call.IsActivitySingleDay(common.ActivityIDSlots) { + order = "best_number1 desc,time1 asc" + str = "time1" + numberStr = "best_number1" + } + db.Mysql().QueryList(0, 10, fmt.Sprintf("%s >= %d and %s > 0", str, util.GetZeroTime(time.Now()).Unix(), numberStr), order, &common.ActivitySlotsData{}, &list) + values.ActivitySlotsRank = list + interval := FetchInterval + if !config.GetBase().Release { + interval = int(util.GetNext5MinUnix() - time.Now().Unix()) + } + time.AfterFunc(time.Duration(interval)*time.Second, GetActivitySlotsRank) + for _, v := range values.ActivitySlotsRank { + log.Debug("ActivitySlotsRank:%+v", v) + } +} diff --git a/modules/web/values/account.go b/modules/web/values/account.go new file mode 100644 index 0000000..634df84 --- /dev/null +++ b/modules/web/values/account.go @@ -0,0 +1,160 @@ +package values + +// PhoneRegistReq 手机注册请求 +type PhoneRegistReq struct { + Phone string `json:"Phone" binding:"required"` // 手机号 + Pass string `json:"Pass" binding:"required"` + Adid string `json:"Adid"` + GPSAdid string `json:"GPSAdid"` + Share string `json:"Share"` +} + +type PhoneLoginReq struct { + Phone string `json:"Phone" binding:"required"` + Pass string `json:"Pass" binding:"required"` +} + +// NewPass 新密码 +// Code 验证码 +type PhoneResetPassReq struct { + Phone string `json:"Phone" binding:"required"` + NewPass string `json:"NewPass" binding:"required"` + Code string `json:"Code" binding:"required"` +} + +// PhoneCodeReq 请求手机验证码 +type PhoneCodeReq struct { + Phone string `json:"Phone" binding:"required"` // 手机号 + // Opt int `json:"Opt" binding:"required"` // 操作码,1是登录获取验证码,2是注册获取验证码,3是绑定手机获取验证码 +} + +// swagger:parameters PhoneCodeLoginReq +// PhoneCodeLoginReq 手机登录 +type PhoneCodeLoginReq struct { + Phone string `json:"Phone" binding:"required"` // 手机号 + Code string `json:"Code" binding:"required"` // 验证码 + Adid string `json:"Adid"` + GPSAdid string `json:"GPSAdid"` + Share string `json:"Share"` +} + +// GuestLoginReq 游客登录请求 +// Share 邀请码 +type GuestLoginReq struct { + Nick string `json:"nick"` + Adid string `json:"Adid"` + GPSAdid string `json:"GPSAdid"` + Share string `json:"Share"` +} + +// LoginResp 登录返回 +type LoginResp struct { + GateAddr string `json:"GateAddr"` + UID int `json:"UID"` + Token string `json:"Token"` + // GameID int `json:"GameID"` +} + +// GPLoginReq googleplay login +type GPLoginReq struct { + ID string `json:"ID" binding:"required"` + Name string `json:"Name"` + ChannelID int `json:"ChannelID"` + UUID string `json:"UUID"` + Avatar string `json:"Avatar"` + Adid string `json:"Adid"` + GPSAdid string `json:"GPSAdid"` + Share string `json:"Share"` +} + +// FBLoginReq facebook login +type FBLoginReq struct { + ID string `json:"ID" binding:"required"` + Name string `json:"Name"` + ChannelID int `json:"ChannelID"` + UUID string `json:"UUID"` + Avatar string `json:"Avatar"` + Adid string `json:"Adid"` + GPSAdid string `json:"GPSAdid"` + Share string `json:"Share"` +} + +// CommonLogin 登录通用信息结构 +type CommonLogin struct { + OpenID string + DeviceID string + Nick string + Avatar string + Adid string + Gpsadid string + AccountType int + AccountName string + Pass string + Phone string + Share string // 邀请码 + Fbc string + Fbp string + UserAgent string +} + +// TokenLoginReq +type TokenLoginReq struct { + Token string `json:"Token" binding:"required"` + Adid string `json:"Adid"` + GPSAdid string `json:"GPSAdid"` +} + +// Email 邮箱 +type EmailCodeReq struct { + Email string `json:"Email" binding:"required"` // 邮箱 +} + +// Email 邮箱 +// Pass 密码 +// Code 验证码 +type EmailRegistReq struct { + Email string `json:"Email" binding:"required"` + Pass string `json:"Pass" binding:"required"` + Code string `json:"Code" binding:"required"` + Adid string `json:"Adid"` + GPSAdid string `json:"GPSAdid"` + Share string `json:"Share"` +} + +// Email 邮箱 +// Pass 密码 +type EmailLoginReq struct { + Email string `json:"Email" binding:"required"` + Pass string `json:"Pass" binding:"required"` +} + +// Email 邮箱 +// NewPass 新密码 +// Code 验证码 +type EmailResetPassReq struct { + Email string `json:"Email" binding:"required"` + NewPass string `json:"NewPass" binding:"required"` + Code string `json:"Code" binding:"required"` +} + +// Name 用户名 +// Pass 密码 +// Share 分享码 +type AccountRegistReq struct { + Name string `json:"Name" binding:"required"` + Pass string `json:"Pass" binding:"required"` + Share string `json:"Share"` +} + +// Pass 密码 +type AccountLoginReq struct { + Name string `json:"Name" binding:"required"` + Pass string `json:"Pass" binding:"required"` +} + +// NewPass 新密码 +type AccountResetPassReq struct { + Name string `json:"Name" binding:"required"` + OldPass string `json:"OldPass" binding:"required"` + NewPass string `json:"NewPass" binding:"required"` +} diff --git a/modules/web/values/activity.go b/modules/web/values/activity.go new file mode 100644 index 0000000..7db43d5 --- /dev/null +++ b/modules/web/values/activity.go @@ -0,0 +1,249 @@ +package values + +import "server/common" + +// GetPromotionsResp 促销中心 +type GetPromotionsResp struct { + ActivityList []*common.ConfigActivity + TaskList []*OneTask +} + +// UploadActivityReq 上传活动点击数据 +type UploadActivityReq struct { + ActivityID int +} + +// GetAllActivityResp 获取所有活动信息 +type GetAllActivityResp struct { + List []*common.ConfigActivity +} + +type ActivityAppSpinInfoResp struct { + List []*common.ConfigAppSpin +} + +// ID 转到的物品id +type ActivityAppSpinDrawResp struct { + ID int +} + +// Count 可转动次数 +type ActivityFreeSpinInfoResp struct { + Count int + List []*common.ConfigActivityFreeSpin + NewReward int64 +} + +// ID 转到的物品id +// Reward 实际获得的金额 +type ActivityFreeSpinDrawResp struct { + ID int + Reward int64 +} + +// Recharge 充值总额 +// Back 可领取的总额 +type ActivityFirstRechargeBackInfoResp struct { + Recharge int64 + Back int64 + CanRecharge bool +} + +// ID 物品id +// Reward 奖励 +// Per 概率 +type OneActivityLuckyCodeConfig struct { + ID int + Reward int64 + Per string +} + +type ActivityLuckyCodeInfoResp struct { + List []OneActivityLuckyCodeConfig + TelegramChannel string +} + +// Type 类型,预留用于后续其他活动兑换码 +type ActivityLuckyCodeDrawReq struct { + Type int + LuckyCode int `json:"LuckyCode" binding:"required"` +} + +// ID 奖品id +type ActivityLuckyCodeDrawResp struct { + ID int +} + +// Sign 玩家签到情况数据 二进制 如第一天签了,第二天没签,第三天签了,第四天签了 就是1101 +// Day 当前天数 +// CanSign 能否签到 +type ActivitySignInfoResp struct { + List []*common.ConfigActivitySign + Sign int + Day int + CanSign bool +} + +type ActivitySignDrawResp struct { + Reward int64 + Day int + Sign int +} + +type ActivityBreakGiftInfoResp struct { + Amount int64 + Reward int64 + ProductID int + CountDown int +} + +// Level 周卡等级 +// Rebate 返利率 +// OriginPrice 显示的原价 +// Price 购买价格 +// Reward 购买后立即获得的金额 +// DayReward 每天可领取的金额 +// DayCount 总共可领取天数 +// Discount 折扣券折扣 +// Day 剩余可领取天数 +// Next 下次可领取倒计时,单位秒,如果为0则可领取,为-1时候表示未购买 +type OneWeekCard struct { + Level int + Rebate string + OriginPrice int64 + Price int64 + Reward int64 + DayReward int64 + DayCount int + Discount string + Day int + Next int64 + ProductID int + TotalReward int64 +} + +type ActivityWeekCardInfoResp struct { + List []OneWeekCard +} + +// Level 领取的周卡的等级 +type ActivityWeekCardDrawReq struct { + Level int +} + +// Reward 获得的奖励 +// DiscountTicket 获得的折扣券折扣 +type ActivityWeekCardDrawResp struct { + Reward int64 + DiscountTicket string +} + +// Spin 可转次数 +// BestNumber 抽到的最大数字 +// LastReward 昨日中奖情况 +type ActivitySlotsResp struct { + ActivityID int + Spin int + BestNumber int + RankList []*OneActivitySlotsRank + LastReward *OneActivitySlotsLastReward + Product OneActivitySlotsProduct +} + +type OneActivitySlotsProduct struct { + ProductID int + OriginAmount int64 + Amount int64 + Reward int64 + SpinCount int +} + +// Draw 是否已领取奖励 +type OneActivitySlotsLastReward struct { + Rank int + Number int + Reward int64 + Draw bool +} + +type OneActivitySlotsRank struct { + UID int + Nick string + Avatar string + Number int +} + +// Number 抽中的数字 +type ActivitySlotsDrawResp struct { + Number int +} + +type ActivitySlotsDrawLastResp struct { + Reward int64 +} + +// Recharge 充值金额 +// Value 可获得的金额 +// ProductID 商品id +// CountDown 倒计时 +type ActivityLuckyShopResp struct { + Recharge int64 + Value int64 + ProductID int + CountDown int64 +} + +// ProductID 商品id +// RewardRange 奖励金币范围 +// Open 是否可开启宝箱 +// Buy 是否可购买 +type ActivitySevenDayBoxInfoResp struct { + ProductID int + RewardRange []int64 + Open bool + Buy bool + Recharge int64 +} + +// Reward 获得的金币 +// Discount 获得的折扣券比例,为0时就是没获得 +type ActivitySevenDayBoxDrawResp struct { + Reward int64 + Discount int +} + +// RewardPool 奖池,数组 +// Recharge 需要充值的额度 +// ProductID 商品id +// Buy 能否购买 +type ActivitySuperInfoResp struct { + RewardPool []OneActivitySuperBox + Recharge int64 + ProductID int + Buy bool +} + +// Index 奖池序号 +// Status 该奖池状态 0锁 1可开启 2已开启 +// Rewards 改奖池列表 +// Type 物品类型 1为金额 2为折扣券 +// Value 物品数量 Type为2时代表折扣券数额 +type OneActivitySuperBox struct { + Index int + Status int + Rewards []ActivitySuperOneReward +} + +type ActivitySuperOneReward struct { + RewardType int + Reward int64 +} + +// Index 领取的奖池序号 +type ActivitySuperDrawReq struct { + Index int +} + +type ActivitySuperDrawResp struct { + Reward ActivitySuperOneReward +} diff --git a/modules/web/values/errorCode.go b/modules/web/values/errorCode.go new file mode 100644 index 0000000..a812c8e --- /dev/null +++ b/modules/web/values/errorCode.go @@ -0,0 +1,35 @@ +package values + +// 错误码 +const ( + CodeOK = iota + CodeRetry // 内部错误 + CodeToken // token过期 + CodeParam // 参数有误 + CodeServer // 服务器维护中 +) + +// 账号错误码 +const ( + CodeReqBusy = iota + 100 // 请求过于频繁 + CodeCodeError // 验证码错误 + CodeAccountNotExist // 账号不存在 + CodeAccountAlreadyExist // 账号已经存在 + CodeAccountPass // 密码错误 + CodeAccountIPLimit // ip限制 + CodeAccountAlreadyBind // 账号已绑定 + CodePhoneAlreadyBind // 手机号已绑定 + CodePhoneCodeBusy // 请求过于频繁 + CodeWithdrawNotEnough // 退出钱不够 + CodeWithdrawLimit // 退出次数达上限 + CodeWithdrawCondition // 未到达退出条件 + CodeActivityExpire // 不在活动时间内 + CodeBuyLimit // 超出购买限制 + CodeNickInvalid // 修改的昵称不合法 + CodeMailUndrawable // 邮件没有附件可领取 + CodeMailDrew // 邮件已领取 + CodeAccountLimit // 账号已封禁 + CodeRecharge // 需要充值 + CodeWithdrawConditionVip // 未到达vip退出条件 + CodeWithdrawConditionBet // 未到达打码退出条件 +) diff --git a/modules/web/values/firstpage.go b/modules/web/values/firstpage.go new file mode 100644 index 0000000..446eefc --- /dev/null +++ b/modules/web/values/firstpage.go @@ -0,0 +1,34 @@ +package values + +import "server/common" + +// GameNum 子项游戏请求个数 +type FirstPageReq struct { + GameNum int +} + +type FirstPageResp struct { + Currencys []OneCurrency + Activitys []*common.ConfigBanner + GameTypes []*common.ConfigGameType + GameMarks []*common.ConfigGameMark + Providers []*common.ConfigGameProvider + Games []OneFisrtPageGame + Esport *common.ConfigGameList + DownloadAppReward int64 +} + +type OneCurrency struct { + ID common.CurrencyType + Name string +} + +// Icon 游戏标题图标 +// Name 游戏标题名 +// JumpID 跳转查询id +// JumpType 跳转类型 1type 2mark +// Sort 排序 +type OneFisrtPageGame struct { + *common.ConfigFirstPageGames + List []*common.ConfigGameList +} diff --git a/modules/web/values/game.go b/modules/web/values/game.go new file mode 100644 index 0000000..62a148a --- /dev/null +++ b/modules/web/values/game.go @@ -0,0 +1,98 @@ +package values + +import "server/common" + +// GameListReq 请求gamelist +// Provider 游戏供应商,为0时代表请求所有 +// Name 游戏名 +// Type 游戏类别 +// Mark 标签 +// Page 页码 +// Num 单页个数 +type GameListReq struct { + Provider int // 游戏提供商 + Name string // 游戏名 + Page int + Num int + Type int + Mark int +} + +// Provider 游戏提供商 +// List 列表 +type GameListResp struct { + Provider int // 游戏提供商 + List []*common.ConfigGameList +} + +// EnterGameReq 进入游戏 +// Provider int 游戏提供商 +// GameID int 游戏id +// Currency int 选择的币种 +// IsDemo bool 是否是免费模式 +// SubID int 游戏子id +type EnterGameReq struct { + Provider int // 游戏提供商 + GameID int // 游戏id + Currency common.CurrencyType // 选择的币种 + IsDemo bool // 免费模式 + SubID int // 游戏子id +} + +// EnterGameResp 进入游戏 +// Method 进入方式 1url 2html +type EnterGameResp struct { + URL string // 跳转网址 + Method int +} + +// GameHistoryReq 请求历史 +// Page 页码 +// Num 单页个数 +type GameHistoryReq struct { + UID int + Page int + Num int +} + +type GameHistoryResp struct { + List []BetRecord +} + +// BetRecord 下注记录 +// GameName 游戏名 +// Vip vip等级 +// BetAmount 下注额 +// SettleAmount 发奖 +// Multiplier 倍率 +type BetRecord struct { + UID int + Provider int + GameID int + GameName string + Time int64 + Nick string + Avatar string + Vip int + BetAmount int64 + SettleAmount int64 + Multiplier string + CurrencyType common.CurrencyType +} + +// GameProfileReq 请求生涯数据返回 +type GameProfileResp struct { + Birth int64 + Statistics []common.PlayerProfile + TopWins []TopWins +} + +type TopWins struct { + Provider int + GameID int + GameName string + Time int64 + BetAmount int64 + SettleAmount int64 + CurrencyType common.CurrencyType +} diff --git a/modules/web/values/h5.go b/modules/web/values/h5.go new file mode 100644 index 0000000..c0c032e --- /dev/null +++ b/modules/web/values/h5.go @@ -0,0 +1,9 @@ +package values + +// H5Info h5的一些信息 +type H5Info struct { + CollectReward int64 + IsCollect bool + DownloadReward int64 + IsDownload bool +} diff --git a/modules/web/values/mail.go b/modules/web/values/mail.go new file mode 100644 index 0000000..8162043 --- /dev/null +++ b/modules/web/values/mail.go @@ -0,0 +1,85 @@ +package values + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "server/config" + "server/util" + "strconv" + "time" + + "github.com/liangdas/mqant/log" +) + +// =====================================不卡email +type BukaEmailCodeReq struct { + AppID string `json:"appId"` // appId + FromEmailAddress string `json:"fromEmailAddress"` // 发送的源邮箱 + ToAddress string `json:"toAddress"` // 发送的目标邮箱 + TemplateID string `json:"templateID"` // 模板id + TemplateData string `json:"templateData"` // 模板数据 + Language string `json:"language"` // 语言 +} + +type BukaEmailCodeResp struct { + Status string `json:"status"` // 0成功 + Reason string `json:"reason"` // 失败原因 + Success string `json:"success"` // 成功数 + Fail string `json:"fail"` // 失败数 + EmailState string `json:"emailState"` // 提交状态:失败、成功 + Remark string `json:"remark"` // 成功/失败描述 + EmailId string `json:"emailId"` // 提交邮件到平台emailId + ToAddress string `json:"toAddress"` // 提交收件人地址(邮箱地址) +} + +func BukaMailRequest(toAddr, code, lan string) error { + req := BukaEmailCodeReq{ + AppID: config.GetConfig().Web.Mail.BukaEmailAppID, + FromEmailAddress: config.GetConfig().Web.Mail.BukaEmailAddr, + ToAddress: toAddr, + TemplateID: config.GetConfig().Web.Mail.BukaTemplateID, + Language: lan, + } + tmp := map[string]interface{}{"code": code} + templateData, _ := json.Marshal(tmp) + req.TemplateData = string(templateData) + + url := config.GetConfig().Web.Mail.BukaEmailUrl + reqStr, _ := json.Marshal(req) + log.Debug("Post to:%v,req:%v", url, string(reqStr)) + request, err := http.NewRequest("POST", url, bytes.NewBuffer(reqStr)) + if err != nil { + log.Error("err:%v", err) + return err + } + now := strconv.FormatInt(time.Now().Unix(), 10) + request.Header.Set("Content-Type", "application/json;charset=UTF-8") + request.Header.Set("Api-Key", config.GetConfig().Web.Mail.BukaAPIKey) + request.Header.Set("Timestamp", now) + request.Header.Set("Sign", util.CalculateMD5(config.GetConfig().Web.Mail.BukaAPIKey+config.GetConfig().Web.Mail.BukaAPISecret+now)) + + client := &http.Client{ + Timeout: 5 * time.Second, + } + log.Debug("request:%+v", request) + resp, err := client.Do(request) + if err != nil { + log.Error("http post call err:%v", err) + return err + } + defer resp.Body.Close() + body, _ := ioutil.ReadAll(resp.Body) + log.Debug("response Body%v:", string(body)) + ret := BukaEmailCodeResp{} + if err := json.Unmarshal(body, &ret); err != nil { + log.Error("unmarshal fail err:%v", err) + return err + } + if ret.Status != "0" { + return fmt.Errorf("%v", ret.Reason) + } + return nil +} diff --git a/modules/web/values/pay.go b/modules/web/values/pay.go new file mode 100644 index 0000000..192d6ce --- /dev/null +++ b/modules/web/values/pay.go @@ -0,0 +1,151 @@ +package values + +import "server/common" + +// RechargeOrderReq 充值订单查询 +type RechargeOrderReq struct { + OrderID string +} + +type RechargeOrderResp struct { + Status int +} + +// Amount 充值金额 +// Per 赠送比例 +type OneConfigPayBonus struct { + Amount int64 + Per int64 +} + +// RechargeInfoResp 充值信息返回 +type RechargeInfoResp struct { + List []common.ConfigPayProduct + Channels []*common.ConfigPayChannels // 支付渠道数目 + ConfigPayBonus []OneConfigPayBonus + Tips string + SelectID int + DiscountTicket *DiscountTicket +} + +type DiscountTicket struct { + Level int + Discount string + TimeLeft int64 + Range []int64 +} + +// RechargeReq 充值请求 地址格式为:{"city":"Mumbai","street":"sarang street","houseNumber":"-54/a"} +type RechargeReq struct { + CurrencyType common.CurrencyType `json:"CurrencyType"` + Amount int64 `json:"Amount"` + PayChannel int + ProductID int +} + +// RechargeHistoryReq 请求充值记录 +type RechargeHistoryReq struct { + Page int `json:"Page"` // 页码 + Num int `json:"Num" binding:"required"` // 一页的数目 + // Start *string `json:"Start"` // 开始时间 + // End *string `json:"End"` // 结束时间 +} + +// RechargeHistoryResp 请求充值记录返回 +type RechargeHistoryResp struct { + Count int64 `json:"Count"` + List []common.RechargeOrder `json:"List"` +} + +// CommonWithdrawReq请求 +// type CommonWithdrawReq struct { +// // 收款信息 +// AccountName string `json:"accountName"` // 收款人姓名 +// Mobile string `json:"mobile"` // 收款人手机号码 +// Email string `json:"email"` // 收款人邮箱 +// BankCardNo string `json:"bankCardNo"` // 收款银行卡号,银行卡代付方式必填 +// DrawType int64 `json:"drawType"` // 收款方式 1:UPI 2:银行卡 +// BankCode string `json:"bankCode"` // 银行编码或者钱包类型 UPI代付的VPA账号,3至50个字符。支持的字符:a-z,A-Z,0-9,.,-和一个@,虚拟付款地址,例如,dfumar@exampleupi +// Address string `json:"address"` // 收款人地址 +// } + +type WithdrawPay struct { + common.WithdrawCommon + Address Address `json:"address"` +} + +// Address 用户地址 用户的城市名称 用户的街道名称 用户的门牌号 +type Address struct { + City string `json:"city"` + Street string `json:"street"` + HouseNumber string `json:"houseNumber"` +} + +// WithdrawCheckReq 赠送确认请求 +type WithdrawCheckReq struct { + Amount int64 `json:"Amount"` + // CurrencyType common.CurrencyType `json:"CurrencyType"` +} + +// WithdrawReq 退出请求 +type WithdrawReq struct { + Amount int64 `json:"Amount"` + ChannelID *int `json:"ChannelID"` // 代付渠道选择 + CurrencyType common.CurrencyType `json:"CurrencyType"` + // 可能对应多种结构 + PayAccount map[string]interface{} `json:"PayAccount" binding:"required"` +} + +// WithdrawBlockReq 退出请求 +type WithdrawBlockReq struct { + Amount int64 `json:"Amount"` + CurrencyType common.CurrencyType `json:"CurrencyType"` + Address string `json:"Address"` +} + +// WithdrawResp 退出请求返回 +// RechargeLimit 需要充值的金额 +type WithdrawResp struct { + RechargeLimit int + Balance int64 + WithdrawBalance int64 +} + +// WithDrawInfoResp 获取退出信息 +// List 退出列表 +// WithDrawCount 今日退出次数 +// TotalWithdrawCount 今日可代付总次数 +// Tips 提示语 +// WithdrawChannels 退出渠道 +type WithDrawInfoResp struct { + List []common.ConfigWithdrawProduct + Channels []*common.ConfigWithdrawChannels + WithDrawCount int + TotalWithdrawCount int + Tips string + Fees []int64 + Bets []int64 +} + +// WithdrawHistoryReq 请求退出记录 +type WithdrawHistoryReq struct { + Page int `json:"Page"` // 页码 + Num int `json:"Num" binding:"required"` // 一页的数目 +} + +// WithdrawHistoryResp 请求退出记录返回 +type WithdrawHistoryResp struct { + Count int64 `json:"Count"` + List []common.WithdrawOrder `json:"List"` +} + +// WithdrawCancelReq 取消退出请求 +type WithdrawCancelReq struct { + ID uint `json:"ID" binding:"required"` +} + +// PayResp 区块支付返回 +type PayResp struct { + Addr string `json:"Addr"` + OrderID string `json:"OrderID"` +} diff --git a/modules/web/values/phoneCode.go b/modules/web/values/phoneCode.go new file mode 100644 index 0000000..9305c9c --- /dev/null +++ b/modules/web/values/phoneCode.go @@ -0,0 +1,189 @@ +package values + +import ( + "bytes" + "encoding/json" + "encoding/xml" + "errors" + "fmt" + "io/ioutil" + "math/rand" + "net/http" + "net/url" + "server/call" + "server/config" + "server/util" + "strconv" + "time" + + "github.com/liangdas/mqant/log" +) + +const ( + SmsChannelAntgst = iota + 1 + SmsChannelBuka +) + +func APISmsReq(phone, code string) error { + switch call.GetConfigPlatform().SmsChannel { + case SmsChannelAntgst: + return AntgstRequest(phone, code) + case SmsChannelBuka: + return BukaRequest(phone, code) + default: + return errors.New("unknown channel") + } +} + +type AntgstPhoneCodeReq struct { + Numbers string `json:"number"` // 手机号 + Sms string `json:"sms"` // 正文内容 + AuthSecret string `json:"authSecret"` // 秘钥 +} + +// AntgstResp 短信验证返回 +type AntgstResp struct { + Result struct { + SmsID string `json:"smsId"` + Number string `json:"number"` + Code int `json:"code"` + Result string `json:"result"` + } + Msg string `json:"msg"` + Code int `json:"code"` +} + +// RandomPhoneCode 随机6位验证码 +func RandomPhoneCode() string { + return strconv.Itoa(rand.Intn(900000) + 100000) +} + +func AntgstRequest(phone string, code string) error { + s := fmt.Sprintf(config.GetConfig().Web.OTP.AntgstModel, code) + // s = `"` + s + `"` + req := AntgstPhoneCodeReq{ + Numbers: "0091" + phone, // 加上区号 + // Sms: fmt.Sprintf(config.GetConfig().Web.OTP.AntgstModel, `"`+code+`"`), + Sms: s, + AuthSecret: config.GetConfig().Web.OTP.AntgstAccessKey + config.GetConfig().Web.OTP.AntgstAccessSecret, + } + resp := AntgstResp{} + err := util.HttpPost(config.GetConfig().Web.OTP.AntgstSmsReqURL, &req, &resp, nil) + if err != nil { + log.Error("err:%v", err) + return err + } + if resp.Code != 200 { + log.Error("req err:%v", resp) + return errors.New(resp.Msg) + } + return nil +} + +// AliSmsResp ali短信验证返回 +type AliSmsResp struct { + ReturnStatus string `xml:"returnstatus"` + Message string `xml:"message"` +} + +func AliRequest(phone string, code string) error { + // t := int64(common.RedisExpirePhoneCode / (60 * time.Second)) + s := fmt.Sprintf(config.GetConfig().Web.OTP.AliSmsModel, code) + u := url.Values{} + u.Set("userid", "1") + u.Set("account", config.GetConfig().Web.OTP.AliSmsAccount) + u.Set("password", config.GetConfig().Web.OTP.AliSmsPass) + u.Set("mobile", "91"+phone) + u.Set("content", s) + u.Set("checkcontent", "0") + url := config.GetConfig().Web.OTP.AliSmsReqURL + "&" + u.Encode() + log.Debug("AliRequest:%v", url) + req, err := http.NewRequest("POST", url, nil) + if err != nil { + log.Error("err:%v", err) + return err + } + req.Header.Set("Content-Type", "application/json") + client := &http.Client{ + Timeout: 5 * time.Second, + } + log.Debug("req:%+v", req) + resp, err := client.Do(req) + if err != nil { + log.Error("http post call err:%v", err) + return err + } + ret := AliSmsResp{} + defer resp.Body.Close() + body, _ := ioutil.ReadAll(resp.Body) + log.Debug("response Body%v:", string(body)) + if err := xml.Unmarshal(body, &ret); err != nil { + log.Error("unmarshal fail err:%v", err) + return err + } + if ret.ReturnStatus != "Success" { + log.Error("err:%v", ret.Message) + return errors.New(ret.Message) + } + return nil +} + +// =====================================不卡短信 +type BukaPhoneCodeReq struct { + AppID string `json:"appId"` // appId + Numbers string `json:"numbers"` // 手机号 + Content string `json:"content"` // 正文内容 +} + +type BukaPhoneCodeResp struct { + Status string `json:"status"` // 0成功 + Reason string `json:"reason"` // 失败原因 + Success string `json:"success"` // 成功数 + Fail string `json:"fail"` // 失败数 + MsgID string `json:"msgId"` // 提交号码对应平台msgId + Number string `json:"number"` // 提交号码 +} + +func BukaRequest(phone string, code string) error { + s := fmt.Sprintf(config.GetConfig().Web.OTP.BukaModel, code) + req := BukaPhoneCodeReq{ + AppID: config.GetConfig().Web.OTP.BukaAppID, + Numbers: phone, // 加上区号 + Content: s, + } + url := config.GetConfig().Web.OTP.BukaUrl + reqStr, _ := json.Marshal(req) + log.Debug("Post to:%v,req:%v", url, string(reqStr)) + request, err := http.NewRequest("POST", url, bytes.NewBuffer(reqStr)) + if err != nil { + log.Error("err:%v", err) + return err + } + now := strconv.FormatInt(time.Now().Unix(), 10) + request.Header.Set("Content-Type", "application/json;charset=UTF-8") + request.Header.Set("Api-Key", config.GetConfig().Web.OTP.BukaAPIKey) + request.Header.Set("Timestamp", now) + request.Header.Set("Sign", util.CalculateMD5(config.GetConfig().Web.OTP.BukaAPIKey+config.GetConfig().Web.OTP.BukaAPISecret+now)) + + client := &http.Client{ + Timeout: 5 * time.Second, + } + log.Debug("request:%+v", request) + resp, err := client.Do(request) + if err != nil { + log.Error("http post call err:%v", err) + return err + } + defer resp.Body.Close() + body, _ := ioutil.ReadAll(resp.Body) + log.Debug("response Body%v:", string(body)) + ret := BukaPhoneCodeResp{} + if err := json.Unmarshal(body, &ret); err != nil { + log.Error("unmarshal fail err:%v", err) + return err + } + if ret.Status != "0" { + return fmt.Errorf("%v", ret.Reason) + } + return nil +} diff --git a/modules/web/values/protocol.go b/modules/web/values/protocol.go new file mode 100644 index 0000000..8aacaa5 --- /dev/null +++ b/modules/web/values/protocol.go @@ -0,0 +1,154 @@ +package values + +import ( + "server/common" +) + +// SysConfigReq 请求系统配置 +// Channel: 渠道号 +// Version: 版本号 +// IsRecord 该设备是否已记录 +type SysConfigReq struct { + Channel int + Version int + GPSAdid string `json:"GPSAdid"` + IsRecord bool +} + +// SysConfigResp 请求系统配置返回 Is:是否是审核服 URL:访问的地址 +// IsHot 是否热更 +// Games 游戏开关,游戏idU对应状态值,1是开启 +// NewPlayerGift 初始赠送 +// PhoneBonus 手机登录/绑定手机赠送 +type SysConfigResp struct { + Is bool + URL string + IsHot bool + Version int + NewPlayerGift int64 + PhoneBonus int64 + ServiceTelegram string + ServiceWhatsapp string + ServiceEmail string +} + +type OneUserInfoVip struct { + Cashback int64 + Bonus int64 + WithdrawFee int64 +} + +// Recharge 充值总额 +// VIPLevel vip等级 +// AppSpinCount app下载奖励转盘剩余次数 +// Feedback 是否反馈过 +// NewPddShare 是否有新的pdd邀请 +// RechargeBack 是否能参与充值返还活动 +type UserInfoResp struct { + UID int + Nick string + Avatar string + Recharge int64 + VIPLevel int + Currencys map[common.CurrencyType]int64 + AppSpinCount int + Feedback bool + NewPddShare bool + Birth int64 + Phone string + CurrentVip *OneUserInfoVip + NextVip *OneUserInfoVip + Activitys struct { + RechargeBack bool + DaySign bool + WeekCard bool // 是否弹出周卡 + LuckyShop bool // 是否弹出幸运商店 + } +} + +// BalanceHisReq 请求金币流水记录 +type BalanceHisReq struct { + Page int `json:"Page"` // 页码 + Num int `json:"Num" binding:"required"` // 一页的数目 + Start *int64 `json:"Start"` // 开始时间戳 + End *int64 `json:"End"` // 结束时间戳 +} + +// BalanceHisResp 金币流水记录返回 +type BalanceHisResp struct { + Count int64 `json:"Count"` + List []common.CurrencyBalance `json:"List"` +} + +// NoticeListResp +type NoticeListResp struct { + List []common.ConfigNotice +} + +// GetUserInfoReq 请求用户的信息,通用于房间内的查看用户详情请求 +// 如果是查看自己的可以不传uid +type GetUserInfoReq struct { + UID int `json:"UID"` +} + +// EditUserInfoReq 修改用户信息请求,修改的字段就发,不修改则不发 +type EditUserInfoReq struct { + Nick *string `json:"Nick"` + Avatar *string `json:"Avatar"` +} + +// MailListReq 获取邮件列表 +// Page 页码 +// Num 个数 +// Tag 请求类型 +type MailListReq struct { + Page int `json:"Page"` + Num int `json:"Num" binding:"required"` + Tag int `json:"Tag"` +} + +// MailListResp 返回邮件列表 +// List 邮件列表 +// Count 总数 +type MailListResp struct { + List []common.Mail + Count int +} + +// ReadMailReq 读取一封邮件 +// ID 邮件id +type ReadMailReq struct { + ID int `json:"ID" binding:"required"` +} + +// ReadMailResp 读取一封邮件 +// Mail 邮件 +type ReadMailResp struct { + Mail common.Mail +} + +// DrawMailReq 领取一封邮件 +// ID 邮件id +type DrawMailReq struct { + ID int `json:"ID" binding:"required"` +} + +// DeleteMailReq 删除一封邮件 +// ID 邮件id +type DeleteMailReq struct { + ID []int `json:"ID" binding:"required"` +} + +// FeedbackReq 意见反馈 +type FeedbackReq struct { + List []OneFeedback +} + +// Index 问题序号 +// Choose 选择的序号 如果选择其他,序号为0,需手动输入Context +// Context 选择其他的时候的描述 +type OneFeedback struct { + Index int + Choose []int + Context string +} diff --git a/modules/web/values/share.go b/modules/web/values/share.go new file mode 100644 index 0000000..3f31238 --- /dev/null +++ b/modules/web/values/share.go @@ -0,0 +1,195 @@ +package values + +import "server/common" + +// InviteReward 邀请奖励 +// BetReward 下注奖励 +// Invites 邀请玩家数 +// InvalidInvites 有效邀请玩家数 +// TotalReward 总奖励 +// AvailableReward 可领取奖励 +// InviteRecharge 有效邀请充值额度 +// BetPer 下注返利比例 +// ShareLink 分享链接 +type ShareInfoResp struct { + InviteReward int64 + BetReward int64 + Invites int + InvalidInvites int + TotalReward int64 + AvailableReward int64 + InviteRecharge int64 + BetPer []string + ShareLink string + RechargeCount int64 + TotalRecharge int64 + + PlatformInviteReward int64 + PlatformBetReward int64 + PlatformTotalReward int64 + Rank []*OneShareRank +} + +type SharePlatformResp struct { + InviteReward int64 + BetReward int64 + TotalReward int64 + Rank []*OneShareRank +} + +type OneShareRank struct { + UID int + Nick string + Avatar string + Reward int64 +} + +// Opt 1转到余额 2直接赠送 +// PayAccount opt为2时必填 +type ShareWithdrawReq struct { + Opt int `json:"Opt" binding:"required"` + Amount int64 `json:"Amount" binding:"required"` + // 可能对应多种结构 + PayAccount map[string]interface{} `json:"PayAccount"` +} + +// Available 可用余额 +type ShareWithdrawResp struct { + Available int64 +} + +// Today 今天 +// Total 总数 +// Rewards 佣金数据 +// ShareLink 分享链接 +// type ShareInfoResp struct { +// Today ShareRecord +// Total ShareRecord +// Rewards RewardRecord +// ShareLink string +// ShareConfig []*common.ConfigShare +// } + +// Regist 注册数 +// Real 有效玩家数 +// Bet 有效投注 +// Reward 佣金 +type ShareRecord struct { + Regist int64 + Real int64 + Bet int64 + Reward int64 +} + +// Level 等级 +// TotalWithdraw 总支付 +// Available 可用余额 +// WithdrawLimit 最低限额 +type RewardRecord struct { + Level int + TotalWithdraw int64 + Available int64 + WithdrawLimit int64 +} + +type ShareReferenceReq struct { + Start int64 + End int64 + Page int + Num int +} + +// TotalBet 团队有效总投注 +// TotalReward 总佣金 +type ShareReferenceResp struct { + List []common.ESShareProfitRecord + TotalBet int64 + TotalReward int64 +} + +type ShareReportReq struct { + Start int64 + End int64 + Page int + Num int +} + +type ShareReportResp struct { + List []common.ESShareProfitReport +} + +type ShareTransferReq struct { + Start int64 + End int64 + Page int + Num int +} + +type ShareTransferResp struct { + List []common.ShareOrder +} + +// Spin 剩余旋转次数 +// Amount 当前数额 +// Expire 过期倒计时,单位秒 +// items 转盘物品 +// WithdrawAmount 达到条件额度 +// NewPddShare 是否有新邀请 +type ActivityPddInfoResp struct { + Spin int + Amount int64 + Expire int64 + Items []OnePddSpinItem + WithdrawAmount int64 + ShareLink string + NewPddShare bool +} + +// ID 物品序号 +// Type 物品类型 1直接补齐差额 2固定金额 3随机金额 +// Amount 物品数量 +// Sort 排序 +type OnePddSpinItem struct { + ID int + Type int + Amount int64 + Sort int +} + +// ID 转中的物品id +// Amount 获得的物品数量 +type ActivityPddSpinResp struct { + ID int + Amount int64 +} + +// NewCount 新邀请人数 +// NewSpinCount 新获得的旋转次数 +// SpinLeft 剩余次数 +type ActivityPddNewReferenceResp struct { + NewCount int + NewSpinCount int + SpinLeft int + List []OnePddRecord +} + +type ActivityPddReferenceReq struct { + Page int + Num int +} + +type ActivityPddReferenceResp struct { + List []OnePddRecord +} + +type OnePddRecord struct { + UID int // 被分享人 + Time int64 + Nick string + Avatar string + SpinCount int +} + +type ActivityPddWithdrawResp struct { + ExpireTime int64 +} diff --git a/modules/web/values/sys.go b/modules/web/values/sys.go new file mode 100644 index 0000000..3dc19f6 --- /dev/null +++ b/modules/web/values/sys.go @@ -0,0 +1,9 @@ +package values + +type ConfigResp struct { + AdjustAppToken string + AdjustEvents string + FBPixelID string + // FBAccessToken string + Channel int +} diff --git a/modules/web/values/task.go b/modules/web/values/task.go new file mode 100644 index 0000000..6a67c33 --- /dev/null +++ b/modules/web/values/task.go @@ -0,0 +1,19 @@ +package values + +// Kind 1单次 2循环 +// Type 1注册 2下载 3累充 4单次充 +// Progess 进度 +// Status 0未完成 1已完成 2已领取 +type OneTask struct { + ID int + TaskID int + Target int64 + Reward int64 + Kind int + Type int + Progess int64 + Status int + Title string + Icon string + Action int +} diff --git a/modules/web/values/timer.go b/modules/web/values/timer.go new file mode 100644 index 0000000..8391b72 --- /dev/null +++ b/modules/web/values/timer.go @@ -0,0 +1,10 @@ +package values + +import "server/common" + +var ( + ShareTotalInviteReward int64 + ShareTotalBetReward int64 + ShareRank []*OneShareRank + ActivitySlotsRank []*common.ActivitySlotsData +) diff --git a/modules/web/values/vip.go b/modules/web/values/vip.go new file mode 100644 index 0000000..5caaa25 --- /dev/null +++ b/modules/web/values/vip.go @@ -0,0 +1,32 @@ +package values + +import ( + "server/common" +) + +type VipInfoReq struct { + Page int + Num int +} + +// List vip列表 +// NextCashback 距离下次可领取cashback的时间(秒) +// TotalCashback 总返利 +type VipInfoResp struct { + Info *common.VipData + List []*common.ConfigVIP + NextCashback int64 + TotalCashback int64 + CashbackList []common.CurrencyBalance +} + +// Level 领取的等级 +type DrawVipBonusReq struct { + Level int `json:"Level" binding:"required"` +} + +type DrawCashbackResp struct { + TotalCashback int64 + Balance int64 + NextCashback int64 +} diff --git a/natsClient/base.go b/natsClient/base.go new file mode 100644 index 0000000..1336f97 --- /dev/null +++ b/natsClient/base.go @@ -0,0 +1,97 @@ +package natsClient + +import ( + "runtime" + "server/util" + "time" + + "github.com/liangdas/mqant/log" + "github.com/nats-io/nats.go" +) + +type NatsImp interface { + // 收到消息后的回调 + OnMsgCallBack(*nats.Msg) +} + +type NatsClient struct { + NatsImp NatsImp + *nats.Conn + done chan bool + Topic string + Queue string + isClose bool + GO bool +} + +func NewNatsClient() *NatsClient { + n := new(NatsClient) + n.done = make(chan bool) + return n +} + +func (nc *NatsClient) OnRequestHandle() error { + defer func() { + if r := recover(); r != nil { + var rn = "" + switch r := r.(type) { + case string: + rn = r + case error: + rn = r.Error() + } + buf := make([]byte, 1024) + l := runtime.Stack(buf, false) + errstr := string(buf[:l]) + log.Error("%s\n ----Stack----\n%s", rn, errstr) + } + }() + var subs *nats.Subscription + var err error + if len(nc.Queue) == 0 { + subs, err = nc.SubscribeSync(nc.Topic) + } else { + subs, err = nc.QueueSubscribeSync(nc.Topic, nc.Queue) + } + if err != nil { + return err + } + util.Go(func() { + //服务关闭 + <-nc.done + subs.Unsubscribe() + }) + for !nc.isClose { + m, err := subs.NextMsg(time.Minute) + if err != nil && err == nats.ErrTimeout { + if !subs.IsValid() { + //订阅已关闭,需要重新订阅 + subs, err = nc.SubscribeSync(nc.Topic) + if err != nil { + log.Error("NatsClient SubscribeSync[1] error with '%v'", err) + continue + } + } + continue + } else if err != nil { + if !subs.IsValid() { + //订阅已关闭,需要重新订阅 + subs, err = nc.SubscribeSync(nc.Topic) + if err != nil { + log.Error("NatsClient SubscribeSync[1] error with '%v'", err) + continue + } + } + log.Warning("NatsServer error with '%v'", err) + continue + } + if nc.GO { + util.Go(func() { + nc.NatsImp.OnMsgCallBack(m) + }) + } else { + nc.NatsImp.OnMsgCallBack(m) + } + } + return nil +} diff --git a/natsClient/imp.go b/natsClient/imp.go new file mode 100644 index 0000000..6bee265 --- /dev/null +++ b/natsClient/imp.go @@ -0,0 +1,79 @@ +package natsClient + +import ( + "errors" + "runtime" + "server/pb" + "time" + + "github.com/gogo/protobuf/proto" + "github.com/liangdas/mqant/log" + "github.com/nats-io/nats.go" +) + +// CommonNatsImp 通用回调natsClient +type CommonNatsImp struct { + f func(data []byte) +} + +// g 是否用协程处理 +func NewCommonNatsImp(conn *nats.Conn, topic string, f func(data []byte), queue ...string) { + base := NewNatsClient() + base.Topic = topic + r := &CommonNatsImp{f: f} + base.NatsImp = r + base.Conn = conn + base.GO = true + if len(queue) > 0 { + base.Queue = queue[0] + } + go func() { + err := base.OnRequestHandle() + if err != nil { + log.Error("newClientDissconnectNatsImp OnRequestHandle error:%v", err) + } + }() +} + +func (r *CommonNatsImp) OnMsgCallBack(m *nats.Msg) { + r.f(m.Data) +} + +// NewReplyNatsImp 回复监听 +func NewReplyNatsImp(conn *nats.Conn, topic string) error { + defer func() { + if r := recover(); r != nil { + var rn = "" + switch r := r.(type) { + case string: + rn = r + case error: + rn = r.Error() + } + buf := make([]byte, 1024) + l := runtime.Stack(buf, false) + errstr := string(buf[:l]) + log.Error("%s\n ----Stack----\n%s", rn, errstr) + } + }() + subs, err := conn.SubscribeSync(topic) + if err != nil { + log.Error("err:%v", err) + return err + } + defer subs.Unsubscribe() + m, err := subs.NextMsg(5 * time.Second) + if err != nil { + log.Error("err:%v", err) + return err + } + one := &pb.InnerReply{} + if err := proto.Unmarshal(m.Data, one); err != nil { + log.Error("err:%v", err) + return err + } + if one.Code == 0 { + return nil + } + return errors.New(one.Msg) +} diff --git a/natsClient/reload.go b/natsClient/reload.go new file mode 100644 index 0000000..9733b51 --- /dev/null +++ b/natsClient/reload.go @@ -0,0 +1,39 @@ +package natsClient + +import ( + "server/pb" + + "github.com/gogo/protobuf/proto" + "github.com/liangdas/mqant/log" + "github.com/nats-io/nats.go" +) + +type ReloadNatsImp struct { + f func(*pb.ReloadGameConfig) +} + +// 新建一个reload监听 +func NewReloadNats(conn *nats.Conn, f func(*pb.ReloadGameConfig)) { + base := NewNatsClient() + base.Topic = TopicReloadConfig + r := &ReloadNatsImp{f: f} + base.NatsImp = r + base.Conn = conn + go func() { + err := base.OnRequestHandle() + if err != nil { + log.Error("newClientDissconnectNatsImp OnRequestHandle error:%v", err) + } + }() +} + +func (r *ReloadNatsImp) OnMsgCallBack(m *nats.Msg) { + req := &pb.ReloadGameConfig{} + err := proto.Unmarshal(m.Data, req) + if err != nil { + log.Error("ReloadNatsImp OnMsgCallBack error %v", err) + return + } + log.Debug("ReloadNatsImp:%+v", req) + r.f(req) +} diff --git a/natsClient/values.go b/natsClient/values.go new file mode 100644 index 0000000..154ed0d --- /dev/null +++ b/natsClient/values.go @@ -0,0 +1,31 @@ +package natsClient + +const ( + TopicNewPlayer = "TopicNewPlayer" + TopicClientDisconnect = "TopicClientDisconnect" + TopicReloadConfig = "TopicReloadConfig" // 重置配置 + TopicBroadcast = "TopicBroadcast" // 广播 + TopicBroadcastReq = "TopicBroadcastReq" // 广播 + TopicCancelMatch = "TopicCancelMatch" + TopicInnerRefreshGold = "TopicInnerRefreshGold" // 通知游戏服刷新金币 + // TopicInnerStatistics = "TopicInnerStatistics" // 通知common服写入玩家统计数据 + // TopicInnerOnline = "TopicInnerOnline" // 通知common服写入玩家在线统计数据 + // TopicInnerPushRed = "TopicInnerPushRed" // 通知common服推送玩家红点 + // TopicInnerUpdateRed = "TopicInnerUpdateRed" // 通知common服更新并推送玩家红点 + // TopicInnerESPlayerData = "TopicInnerESPlayerData" // 通知common服更新玩家数据 + // TopicInnerWarn = "TopicInnerWarn" // 通知common服预警 + TopicInnerUpdateWarn = "TopicInnerUpdateWarn" // 通知common服更新预警参数 + TopicInnerOptPlayer = "TopicInnerOptPlayer" // 通知全服对玩家进行一些操作 + TopicInnerPlayerWithdraw = "TopicInnerPlayerWithdraw" // 通知游戏服某个玩家退出回调了 + TopicInnerAfterSettle = "TopicInnerAfterSettle" // 玩家游戏场结算后的一些处理逻辑 + TopicBroadcastAll = "TopicBroadcastAll" // 任意协议进行广播操作 + TopicCustomerMsg = "TopicCustomerMsg" // 广播玩家客服消息 +) + +const ( + TopicBackRefreshMail = "TopicBackRefreshMail" // 后台通知游戏服刷新玩家邮件 +) + +const ( + QueueAfterSettle = "QueueAfterSettle" +) diff --git a/pb/proto/blockpay.ptoto b/pb/proto/blockpay.ptoto new file mode 100644 index 0000000..f26492c --- /dev/null +++ b/pb/proto/blockpay.ptoto @@ -0,0 +1,67 @@ +syntax = "proto3"; + +package pb; + +option go_package = "../../pb"; + +message BlockPayQueryOrderReq{ + string OrderID = 1; // 游戏订单 + string TxID = 2; // 交易号 +} + +message CommonMsg{ +} + +message CommonResp{ + int64 Code = 1; // 0查询成功 其余为失败 + string Msg = 2; // 消息 +} + + +message BlockPayQueryLocalAddrResp{ + repeated OneBlockAddr Addrs = 1; +} + +message OneBlockAddr { + string Addrres = 1; // 地址 + string TRX = 2; + string USDT = 3; +} + +message BlockPayAddLocalAddrReq{ + string Address = 1; // 新增的地址 + string Private = 2; // 秘钥 +} + +message BlockPayTransferReq{ + string FromAddr = 1; // 付款地址 + string ToAddr = 2; // 收款地址 + int64 Type = 3; // 类型 1TRX 2USDT + int64 Amount = 4; // 额度 +} + +message BlockPayTransferResp{ + int64 Code = 1; // 0查询成功 其余为失败 + string Msg = 2; // 消息 + string TxID = 3; // 交易号 +} + +message BlockPayGetFeeResp{ + int64 Code = 1; // 0查询成功 其余为失败 + string Msg = 2; // 消息 + int64 Fee = 3; // 费用 +} + +// 扫描玩家钱包余额并转回主钱包 +message BlockPayScanPlayerWalletReq{ + int64 Down = 1; // 扫描u下限 + int64 Up = 2; // 扫描u上限 +} + + +message BlockPayScanPlayerWalletResp{ + int64 Code = 1; // 0查询成功 其余为失败 + string Msg = 2; // 消息 + int64 Success = 3; // 成功转出数目 + int64 Fail = 4; // 失败转出数目 +} diff --git a/pb/proto/common.proto b/pb/proto/common.proto new file mode 100644 index 0000000..d66abe0 --- /dev/null +++ b/pb/proto/common.proto @@ -0,0 +1,96 @@ +syntax = "proto3"; + +package pb; + +import "platform.proto"; + +option go_package = "../../pb"; + +enum ErrNo { + SUCCESS = 0; // 成功 + Server_Except = 1; // 服务器异常;server except + TokenError = 2; // token错误;token error + TokenExpire = 3; // token已过期;token expired + Account_NotExists = 4; // 账户不存在或密码错误;account not exists or password error + Duplicate_Login = 5; // 重复登录;duplicate login + + Server_Maintain = 8; // 服务器维护;server maintaining + + NickName_Exists = 10; // 昵称已存在;nick name already exists + NickName_TooShort = 11; // 昵称太短;nick name too short + NickName_TooLong = 12; // 昵称太长;nick name too long + Password_TooShort = 13; // 密码太短;password too short + Password_TooLong = 14; // 密码太长;password too long + AccountBind = 15; // 账号已绑定;account bound Already + + Charge_Fail = 20; // 拉取订单失败;charge fail + Lower_Age = 21; // 充值年龄不足;not old enough + Cash_NotEnough = 22; // 退出金额不足;cash not enough + Withdrawing = 23; // 已在退出中,不能退出;withdraw in progress + + + + RequestParam_Error = 50; // 请求参数错误;required param error + Server_ConfigError = 51; // 服务器配置信息错误;server configure error + Reward_IDNotExists = 52; // 奖励ID不存在;reward id not found + Reward_AlreadyGet = 53; // 该奖励已经领取;that reward already get + Reward_ConditionUnCompelete = 54; // 奖励条件未达成;reward condition uncompelete + + SeasonWheel_NotExists = 55; // 奖券不存在;season lottery ticket not exist + SeasonWheel_Expire = 56; // 奖券已过期;season lottery ticket alearay expired + + ReplayRecord_NotExist = 60; // 回放记录不存在;replay record not exist + + + // 游戏相关错误码从100开始 + CompetitionID_NotExists = 100; // 比赛ID不存在;match id not found + + Competition_Over = 101; // 比赛已结束;compete already over + Competition_PriceNotEnough = 102; // 入场费不足;Insufficient entry fee + Competition_Matching = 103; // 已经在匹配队列中;already in match queue + Competition_Gaming = 104; // 已经在比赛中;already in game + Competition_OtherGame = 105; // 已经在其他游戏中;in other game + Competition_NotFound = 106; // 比赛未找到, 比赛ID错误或者游戏已结束;match not found or compete already over + Competition_NotInCmp = 107; // 不在比赛中;not in game + Competition_GameOver = 108; // 个人副本游戏已结束;gameover + Competition_Pause = 109; // 游戏已暂停;game pause + Competition_ParamError = 110; // 请求参数错误;game required param error + MatchID_NotExist = 111; // 匹配ID不存在;match id not found + Match_Timeout = 112; // 匹配超时;match timeout + Competition_AuthFail = 113; // 比赛结果不合法;match result auth fail + Competition_Busy = 114; // 比赛繁忙;competition busy + + HTTP_SUCCESS = 200; // Web使用 + + // 活动 + ActivityInvalid = 201; // 不在活动时间;activity invalid + ActivityDraw = 202; // 活动奖励已领取;activity reward draw + ActivityNotFinished = 203; // 活动未完成;activity not finished + DiamondNotEnough = 204; // 钻石不足;gems not enough +} + +// 用户数据 +message UserData { + uint32 Uid = 1; // uid + string Nick = 2; // 昵称 + string Avatar = 3; // 头像 + int64 Cash = 4; // 现金 + int64 BindCash = 5; // 绑定现金 + int64 Tickets = 6; // 门票 + int32 Sex = 7; // 性别,0保密, 1男, 2女 + string AreaCode = 8; // 区域编号 + string Signature = 9; //签名 + int64 Birth = 10; // 生日 + int32 Dan = 11; // 段位 +} + +// 玩家货币变化通知 +message PlayerCurrencyChangeNotify { + repeated CurrencyPair list = 1; + int32 Event = 2; // 事件 +} + +message DuplicateLoginResp { + ErrNo Err = 1; + string Message = 2; +} \ No newline at end of file diff --git a/pb/proto/crash.proto b/pb/proto/crash.proto new file mode 100644 index 0000000..4e58596 --- /dev/null +++ b/pb/proto/crash.proto @@ -0,0 +1,55 @@ +syntax = "proto3"; + +package pb; + +import "game.proto"; + +option go_package = "../../pb"; + +enum CrashProtocol{ + CrashInvalid = 0; + // 小火箭协议 + CrashCashOutReq = 1001; // 退出请求 + CrashCashOutResp = 1002; // 退出返回 + CrashFlyingStartResp = 1004; // 游戏阶段转换协议/开始飞行 + CrashOtherCashOutResp = 1006; // 其他人退出广播 CrashMsgOtherCashOutResp + CrashAutoCashoutReq = 1007; // 自动下车请求 CrashMsgAutoCashoutReq + CrashAutoCashoutResp = 1008; // 自动下车返回 GameCommonResp +} + +message CrashMsgTableInfoResp{ + GameCommonTableInfo TableInfo = 1; + int64 AutoCashOut = 2; // 玩家设置的自动退出倍率(倍率扩大100倍) + int64 Multiple = 3; // 如果是飞行状态,会有当前倍率 +} + + +// 游戏开始 +message CrashMsgGameStartResp{ + int64 TimeLeft = 1; // 剩余时间 +} + +// 飞行通知 +message CrashMsgGameFlyResp{ +} + + +// 游戏结算 +message CrashMsgGameSettleResp{ + int64 TimeLeft = 1; // 剩余时间 + int64 Result = 2; // 本局结果 + int64 SettleAmount = 3; // 玩家自己输赢 +} + +// 退出返回 +message CrashMsgOtherCashOutResp{ + uint32 UID = 1; + int64 ODD = 2; // 退出时的倍率(扩大了100倍) + string Nick = 3; + int64 Amount = 4; // 其他人退出时的收益 +} + +// CrashMsgAutoCashoutReq 请求自动下车 +message CrashMsgAutoCashoutReq{ + int64 ODD = 1; // 设置下车的倍数(扩大100倍) +} diff --git a/pb/proto/game.proto b/pb/proto/game.proto new file mode 100644 index 0000000..396a44b --- /dev/null +++ b/pb/proto/game.proto @@ -0,0 +1,138 @@ +syntax = "proto3"; + +package pb; + + +option go_package = "../../pb"; + +// 游戏通用协议 +enum GameProtocol{ + Invalid = 0; + EnterGameReq = 1; // 进入游戏请求 + EnterGameResp = 2; // 进入游戏请求 + TableInfoReq = 3; // 请求获取游戏数据 GameCommonReq + TableInfoResp = 4; // 每个游戏不一样 + + GameStartResp = 6; // 游戏阶段转换协议/开始下注 + + GameSettleResp = 8; // 游戏阶段转换协议/停止下注 + LeaveReq = 9; // 退出房间请求 GameCommonReq + LeaveResp = 10; // 退出房间返回 GameCommonResp + BetReq = 11; // 请求下注 + BetResp = 12; // 请求下注返回 + + BetBroadcast = 14; // 其他人下注广播 + HistoryReq = 15; // 请求历史记录 GameCommonReq + HistoryResp = 16; // 历史记录返回 + BetListReq = 17; // 请求玩家下注列表 GameCommonReq + BetListResp = 18; // 玩家下注列表返回 GameMsgBetListResp +} + +// 定义游戏阶段 +enum GameStatus { + GameStatusInvalid = 0; // 无效 + GameStatusNormal = 1; // 空闲 + GameStatusPlaying = 2; // 游戏中 + GameStatusSettle = 3; // 结算中 + GameStatusSpecial = 4; // 子游戏特殊阶段 +} + +// 定义玩家状态 +enum PlayerStatus { + PlayerStatusInvalid = 0; // 无效 + PlayerStatusNormal = 1; // 空闲 + PlayerStatusPlaying = 2; // 游戏中 + PlayerStatusSettle = 3; // 已结算 + PlayerStatusLeave = 4; // 已退出 +} + + +message GameUser{ + int64 UID = 1; + string Nick = 2; + string Avatar = 3; + int64 CurrencyType = 4; + int64 Balance = 5; + int64 Bet = 6; + PlayerStatus Status = 7; +} + +// 通用客户端请求 +message GameCommonReq{ +} + +// 通用客户端返回 +message GameCommonResp{ + int64 Result = 1; // 0:正常 +} + +// 通用客户端请求 +message GameMsgEnterGameReq{ + int64 SubID = 1; // 房间子id + int64 CurrencyType = 2; +} + +// 通用客户端请求 +message GameCommonTableInfo{ + GameUser User = 1; + repeated int64 History = 2; + repeated BetPlayers List = 3; + GameStatus Status = 4; + int64 TimeLeft = 5; + repeated int64 BetLimit = 6; +} + +message GameMsgBetListResp{ + repeated BetPlayers List = 1; +} + +message BetPlayers{ + int64 UID = 1; // 玩家uid + string Avatar = 2; // 头像 + string Nick = 3; // 昵称 + int64 CurrencyType = 4; + int64 BetAmount = 5; // 下注金额 +} + +message ArrayInt64{ + repeated int64 Element = 1; +} + +// 通用离开返回 +message GameLeaveResp{ + int64 Result = 1; // 0:正常 + int64 Reason = 2; // 0: 主动离开 1:强制离开 +} + +// 下注请求 +message GameMsgBetReq{ + int64 Area = 1; + int64 Amount = 2; // 下注金额 +} + +// 下注返回 +message GameMsgBetResp{ + int64 Code = 1; // 错误码 + int64 CurrencyType = 2; + int64 Amount = 3; // 下注金额 + int64 Balance = 4; // 下注后余额 + int64 Area = 5; +} + +// 其他人下注广播 +message GameMsgBetBroadcastResp{ + int64 UID = 1; // 下注人uid + int64 CurrencyType = 2; + int64 Amount = 3; // 下注金额 + int64 Area = 4; +} + +// 游戏开始 +message GameMsgGameStartResp{ + int64 TimeLeft = 1; // 剩余时间 +} + +// 历史记录返回 +message GameMsgHistoryResp{ + repeated int64 Results = 1; // 历史记录 +} diff --git a/pb/proto/gener.sh b/pb/proto/gener.sh new file mode 100644 index 0000000..494eaf2 --- /dev/null +++ b/pb/proto/gener.sh @@ -0,0 +1,14 @@ +#/bin/bash +for f in `ls` +do + if [[ $f = *.sh ]]; + then + continue + fi + echo $f + protoc --gogofaster_out=. $f +done +#protoc -I=. --gogofaster_out=. *.proto +# protoc --go_out=. *.proto +#sed -i 's/,omitempty//g' ../common.pb.go # web.proro文件只用和客户端的http json格式消息传输 强行去掉json的omitempty参数, 不然客户端收到的数据无法正常解析 + diff --git a/pb/proto/gm.proto b/pb/proto/gm.proto new file mode 100644 index 0000000..4ecdc02 --- /dev/null +++ b/pb/proto/gm.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +package pb; + +option go_package = "../../pb"; + +message GMSetCard{ + repeated uint32 Cards = 1; + uint32 GameID = 2; +} \ No newline at end of file diff --git a/pb/proto/hall.proto b/pb/proto/hall.proto new file mode 100644 index 0000000..847877a --- /dev/null +++ b/pb/proto/hall.proto @@ -0,0 +1,48 @@ +syntax = "proto3"; + +package pb; + +import "common.proto"; + +option go_package = "../../pb"; + +message RegisterUserReq { + string NickName = 1; // 昵称 + string Password = 2; // 密码 +} + +message RegisterUserResp { + ErrNo err = 1; +} + +// 请求登录 +message LoginReq { + string Name = 1; + string Token = 2; +} + +// 登录结果返回 +message LoginResp { + ErrNo err = 1; + UserData data = 2; + string Token = 3; // token +} + +message FacebookLoginReq { + string UID = 1; + string Token = 2; + string UUID = 3; + int32 ChannelID = 4; +} + +message GooglePlayLoginReq { + string Token = 1; + string UUID = 2; + int32 ChannelID = 3; +} + +message GuestLoginReq { + string Token = 1; + string Name = 2; + int32 ChannelID = 3; +} \ No newline at end of file diff --git a/pb/proto/inner.proto b/pb/proto/inner.proto new file mode 100644 index 0000000..e03c836 --- /dev/null +++ b/pb/proto/inner.proto @@ -0,0 +1,155 @@ +syntax = "proto3"; + +package pb; + +import "platform.proto"; + +option go_package = "../../pb"; + +// 服务器内部协议 +message ClientDisConnectNotify { + uint32 UserID = 1; + string SessionId = 2; +} + +message ReloadGameConfig { + int32 Type = 1; + bytes Data = 2; +} + +// 新玩家登录发布消息 +message NewPlayerLogin { + int32 uid = 1; + string GateServerID = 2; + string HallServerID = 3; +} + +message InnerBroadcast { + int32 ID = 1; + string Content = 2; + int32 Priority = 3; + int32 Frequency = 4; + int32 Interval = 5; +} + +message InnerBroadcastAll { + uint32 ProtocolType = 1; // 主协议号 + uint32 ProtocolID = 2; // 子协议号 + bytes Data = 3; // 数据 +} + +message InnerRefreshGold { + uint32 UID = 1; + repeated CurrencyPair Pair = 2; + uint32 Event = 3; + int64 Amount = 4; // 如果是充值,代表充值金额 + string Desc = 5; +} + +message InnerRefreshMail { + repeated uint32 UIDs = 1; +} + +// InnerStatisticsData 内部玩家数据统计 +message InnerStatisticsData { + uint32 UID = 1; + uint32 Channel = 2; + bytes Data = 3; +} + +// InnerOnlineData 内部玩家在线统计 +message InnerOnlineData { + bytes Data = 1; + string Field = 2; + int64 Time = 3; +} + +// InnerPushRed 内部玩家红点推送 +message InnerPushRed { + uint32 UID = 1; +} + +// InnerUpdateRed 内部更新玩家红点推送 +message InnerUpdateRed { + uint32 UID = 1; + uint32 Num = 2; + string Module = 3; +} + +// InnerPlayerData 内部更新玩家数据 +message InnerPlayerData { + uint32 UID = 1; + bytes Update = 2; +} + +// InnerTableFee 内部更新每日台费 +message InnerTableFee { + uint32 Channel = 1; + bytes Data = 2; +} + +// InnerReply 内部请求回复结构 +message InnerReply { + uint32 Code = 1; + string Msg = 2; +} + +// InnerWarn 内部预警请求结构 +message InnerWarn { + string ID = 1; // 预警ID + uint32 Opt = 2; // 操作类型 1新增 2修改 3删除 + bytes Content = 3; // 内容 +} + +// InnerUpdateWarn 内部预警更新参数/确认是否触发预警请求结构 +message InnerUpdateWarn { + uint32 Type = 1; // 预警类型 + uint32 Opt = 2; // 操作类型 1更新 2确认 + repeated int64 Amount = 3; // 参数 +} + +// InnerOptPlayer 内部通知对玩家做出某些操作 +message InnerOptPlayer { + uint32 UID = 1; // uid + uint32 Opt = 2; // 操作类型 1踢出玩家 +} + +// InnerWhiteSwitch 内部白名单更新 +message InnerWhiteSwitch { + uint32 Channel = 1; // + uint32 Opt = 2; // 操作类型 1开启 2关闭 +} + +// InnerWaterConfig 内部通知水位配置改动 +message InnerWaterConfig { + uint32 GameID = 1; // 游戏id + uint32 RoomID = 2; // 房间id +} + +// InnerWithdrawCallback 通知某个玩家退出回调了 +message InnerWithdrawCallback { + uint32 UID = 1; // 玩家id +} + +// InnerAfterSettle 玩家结算后逻辑 +message InnerAfterSettle { + int64 UID = 1; // 玩家id + int64 ProviderID = 2; + int64 GameID = 3; + int64 TotalBet = 4; // 有效投注 + int64 OriginSettle = 5; + int64 FinalSettle = 6; + string Phone = 7; + string UUID = 8; + string Nick = 9; + string MyUUID = 10; + int64 CurrencyType = 11; + bool IsNew = 12; + int64 SubID = 13; + int64 PlayTime = 14; +} + +// 客服消息通知 +message InnerCustomerMsgNotify { + uint32 UID = 1; // 玩家id +} diff --git a/pb/proto/pay.proto b/pb/proto/pay.proto new file mode 100644 index 0000000..f696058 --- /dev/null +++ b/pb/proto/pay.proto @@ -0,0 +1,47 @@ +syntax = "proto3"; + +package pb; + +option go_package = "../../pb"; + +// 服务器内部协议 +message InnerRechargeReq { + string OrderID = 1; // 游戏订单 + int64 Amount = 2; // 订单金额 + string Phone = 3; // 手机号 + string Name = 4; // 玩家名字 + uint32 UID = 5; // 玩家id + string Email = 6; // email + uint32 Channel = 7; // 支付渠道 + string IP = 8; // ip + bool IsPersonalCard = 9; // 是否是走个卡 + uint32 PaySource = 10; // 支付方式 + string Number = 11; // 付款信息 + uint32 playerChannel = 12; // 玩家渠道 +} + +message InnerRechargeResp { + string APIOrderID = 1; // 第三方订单号 + string URL = 2; // 支付地址 + uint32 Channel = 3; // 支付渠道 +} + +message InnerWithdrawReq { + string OrderID = 1; // 游戏订单 + int64 Amount = 2; // 订单金额 + string Phone = 3; // 手机号 + string Name = 4; // 玩家名字 + string Email = 5; // 邮件 + int64 PayType = 6; // 方式 + string Number = 7; // 卡号 + uint32 UID = 8; // uid + int64 Channel = 9; // 代付选择的渠道 + string Address = 10; + uint32 PaySource = 11; // 支付方式 + string IP = 12; // ip +} + +message InnerWithdrawResp { + string APIOrderID = 1; // 第三方订单号 + uint32 Channel = 2; // 支付渠道 +} diff --git a/pb/proto/platform.proto b/pb/proto/platform.proto new file mode 100644 index 0000000..9369484 --- /dev/null +++ b/pb/proto/platform.proto @@ -0,0 +1,132 @@ +syntax = "proto3"; + +package pb; + +option go_package = "../../pb"; + +// 通用消息 +message CommonResponse{ + uint32 result = 1; // 0:成功 +} + +/*静态服务定义:这里定义的静态服务,如果是具体游戏,则是动态值*/ +enum ServerType +{ + ServerTypeInvalid = 0; // 无效 + ServerTypeGate = 1000; // 网关服务 + ServerTypeCommon = 1100; // 通用服务 + ServerTypeCrash = 3000; // 小火箭游戏 +} + +/***************************************网关消息开始*******************************************/ +enum ServerGateReq{ + GateInvalidReq = 0; // 无效网关消息 + GateLoginReq = 1; // 登录请求:LoginRequest + GateLogoutReq = 2; // 登录请求:LoginRequest + GatePingReq = 3; // 请求心跳 +} + +enum ServerGateResp{ + GateInvalidResp = 0; // 无效网关消息 + GateLoginResp = 1; // 登录返回:LoginResponse + GateLogoutResp = 2; // 返回注销 + GateRepeatResp = 3; // 返回重复登录 + GatePingResp = 4; // 返回心跳 +} + +// 登录请求 :这个登录请求的command可能有多个,但是返回取都是一样的 +message LoginRequest{ + uint32 user_id = 1; // UID + string token = 2; // Token +} + +// 登录返回 +message LoginResponse{ + uint32 result = 1; // 0 成功;1 userId错误(用户不存在) + uint32 user_id = 2; // UID:这个地方必定和请求中的uid一致,但是为了方便客户端异步处理,把UID返回了 +} + +// 红点 +message RedPoint{ + uint32 Mail = 1; +} + +enum ConfigChangeType{ + ConfigINVALID = 0; // 无效 + ConfigPay = 1; // 充值配置变动 + ConfigActivity = 2; // 活动配置变动 + ConfigWithdraw = 3; // 代付配置变动 + ConfigGame = 4; // 游戏开关相关配置变动 + ConfigVipLevel = 5; // vip等级变动 + ConfigExcel = 6; // 配置表变动 +} + +// 配置变动 +message ConfigChangeResp{ + ConfigChangeType Type = 1; // 变动配置的类型 +} + +// 活动获得物品 +message ActivityResp{ + int64 ActivityID = 1; // 活动id + repeated ActivityItem ActivityItems = 2; +} + +message ActivityItem{ + uint32 Type = 1; // 物品类型 + uint32 Num = 2; // 物品数量 +} + +/****************************************网关消息结束************************************************/ + + +/****************************************通用平台类消息开始**********************************************/ +enum ServerCommonReq{ + CommonInvalidReq = 0; + CommonPlayerBalanceReq = 1; // 返回用户余额 PlayerBalanceReq +} + +enum ServerCommonResp{ + CommonInvalidResp = 0; + CommonPlayerBalanceResp = 1; // 返回用户余额 PlayerBalanceResp + CommonRedPointResp = 2; // 红点推送 RedPoint + CommonConfigChangeResp = 3; // 配置变动通知 ConfigChangeResp + CommonActivityItemResp = 4; // 活动获得物品通知 ActivityResp + CommonVipResp = 5; // 活动获得物品通知 ActivityResp + CommonBroadcastResp = 6; // 广播 +} + +// 获取用户余额 +message PlayerBalanceReq{ + int64 Type = 1; // 货币类型 +} + +// 获取用户余额返回 +message PlayerBalanceResp{ + int64 Type = 1; // 货币类型 + int64 Balance = 2; // 余额 + int64 Event = 3; // 事件 + int64 Value = 4; // 变化的值 + string Exs1 = 5; // 产生金币变化的描述 + string Exs2 = 6; // 充值的时候代表订单号 +} + +message BroadcastMsg{ + string Content = 1; + uint32 Priority = 2; + int64 Loop = 3; // 重复次数 + int64 Interval = 4; // 时间间隔 +} + +// 物品键值对 +message CurrencyPair { + int64 Type = 1; + int64 Value = 2; +} + +// 商品 +message Product { + uint32 ProductID = 1; + repeated CurrencyPair Rewards = 2; // 充值获得的物品 + int64 Amount = 3; // 充值的金额 +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..13a17ef --- /dev/null +++ b/readme.md @@ -0,0 +1,25 @@ +* bin 各模块配置文件 +* call 各个模块可以调用的通用组件 +* common 通用的一些变量,与游戏业务相关的通用逻辑 +* config 读取配置文件 +* db 数据库操作 +* docs 接口文档文件 +* modules 所有模块 + - matching 匹配模块 + - teenpatti 游戏模块 + - rummy 游戏模块 + - gate 网关模块 + - hall 大厅逻辑 + - web web模块 + - backend 后台模块 + +* natsClient 实现nats订阅的相关逻辑 +* pb proto协议 +* tools 工具类 +* util 通用的方法(与游戏逻辑不相关) +- build.sh 打包编译脚本 +- reboot.sh 部署脚本 +- start.sh 运行所有进程脚本,可传进程参数启动单个进程 +- stop.sh 停止所有进程脚本 +- swagger.sh 生成接口文档 +- fieldalignment.sh 自动内存对齐检查修复脚本 \ No newline at end of file diff --git a/reboot.sh b/reboot.sh new file mode 100644 index 0000000..a1ee4bc --- /dev/null +++ b/reboot.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +source ./stop.sh + +mv arenaserver arenaserver.bak_`(date +%Y_%m_%d_%H:%M:%S)` +mv main arenaserver +chmod +x arenaserver + +source ./start.sh +echo 'finish ...' diff --git a/start.sh b/start.sh new file mode 100644 index 0000000..efb21b6 --- /dev/null +++ b/start.sh @@ -0,0 +1,101 @@ +#!/bin/bash + +#nohup ./gameserver-blitz21 -module=conf/modules.json -log=logs/ -conf=conf/config.json -rule=conf/rule.toml >/dev/null 2>&1 & + +#nohup ./gameserver -module=conf/blitz21/modules.json -log=logs/blitz21 -conf=conf/blitz21/config.json -rule=conf/blitz21/rule.toml >/dev/null 2>&1 & + +#source ./stop.sh + +echo "start server..." + +if [[ $# -gt 0 ]] +then + echo "stop $1..." + for f in `ls conf/$1` + do + if [[ $f =~ ^gameserver ]] + then + echo "stop $f" + tmp=$(ps -ef | grep $f | grep -v "grep" | awk '{print $2}') + #echo $tmp + if [ "$tmp" = "" ];then + echo "$f not running" + else + kill $tmp + fi + break + fi + done + + cp gameserver gameserver-$1 + mv gameserver-$1 conf/$1 + echo "start $1..." + tmp="-log=../../logs/$1 -baseConf=../baseConf.toml" + for c in `ls conf/$1` + do + if [[ $c =~ ^gameserver ]] + then continue + fi + + array=(`echo $c | tr '.' ' '` ) + #echo $array + if [[ ${#array[@]} -ne 2 ]] || [[ $c =~ ^ip ]] + then continue + fi + + tmp="$tmp -$array=$c" + #echo $tmp + done + echo $tmp + cd conf/$1 + nohup ./gameserver-$1 $tmp >/dev/null 2>&1 & + exit 0 +fi + +source ./stop.sh +#cd conf/ +for f in `ls conf` +do + #echo "start $f..." + if [[ $f =~ ^_ ]] || [[ $f = *.toml ]] || [[ -f $f ]] || [[ $f = *.db ]] + then + echo "pass $f..." + continue + fi + echo "start $f..." + cp gameserver gameserver-$f + mv gameserver-$f conf/$f + + tmp="-log=../../logs/$f -baseConf=../baseConf.toml" + for c in `ls conf/$f` + do + if [[ $c =~ ^gameserver|bi ]] + then continue + fi + array=(`echo $c | tr '.' ' '` ) + #echo $array + if [[ ${#array[@]} -ne 2 ]] || [[ $c =~ ^ip ]] + then continue + fi + tmp="$tmp -$array=$c" + #echo $tmp + done + #echo $tmp + #tmp="-module=conf/$f/modules.json -log=logs/$f -conf=conf/$f/config.json -rule=conf/$f/rule.toml" + #mv gameserver gameserver1 + #nohup ./gameserver -module=conf/$f/modules.json -log=logs/$f -conf=conf/$f/config.json -rule=conf/$f/rule.toml >/dev/null 2>&1 & + cd conf/$f + nohup ./gameserver-$f $tmp >/dev/null 2>&1 & + cd ../../ +done + +#for f in `ls` +#do +# if [[ $f =~ ^arena.*(.exe)?$ && -x $f ]] +# then +# echo "start $f" +# nohup ./$f -module=conf/modules.json -log=logs/ -conf=conf/config.json -rule=conf/rule.toml >/dev/null 2>&1 & +# fi +#done + +echo 'finish ...' diff --git a/stop.sh b/stop.sh new file mode 100644 index 0000000..7232300 --- /dev/null +++ b/stop.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +echo 'stop server...' + +if [[ $# -gt 0 ]] +then + echo "stop $1..." + for f in `ls conf/$1` + do + if [[ $f =~ ^gameserver ]] + then + echo "stop $f" + tmp=$(ps -ef | grep $f | grep -v "grep" | awk '{print $2}') + #echo $tmp + if [ "$tmp" = "" ];then + echo "$f not running" + else + kill $tmp + fi + break + fi + done + exit 0 +fi + +for f in `ls` +do + if [[ $f =~ ^.*robot(.exe)?|gameserver.*(.exe)?$ && -x $f ]] + then + echo "stop $f" + tmp=$(ps -ef | grep $f | grep -v "grep" | awk '{print $2}') + #echo $tmp + if [ "$tmp" = "" ];then + echo "$f not running" + else + kill $tmp + fi + fi +done + +#ps -ef | grep "gateway_server\ -port" | grep -v "grep" | awk '{print $2}' | xargs kill -9 + +echo 'stop ok' diff --git a/swagger.sh b/swagger.sh new file mode 100644 index 0000000..d09698d --- /dev/null +++ b/swagger.sh @@ -0,0 +1,12 @@ +#!/bin/bash +if [[ $# -gt 0 ]] +then + cd docs/$1 + swagger generate spec -o swagger.json + exit 0 +fi + +cd docs/web +swagger generate spec -o swagger.json +cd ../backend +swagger generate spec -o swagger.json \ No newline at end of file diff --git a/tools/errcode.toml b/tools/errcode.toml new file mode 100644 index 0000000..f172b25 --- /dev/null +++ b/tools/errcode.toml @@ -0,0 +1,90 @@ +[cn] +1 = "服务器异常" +2 = "token错误" +3 = "token已过期" +4 = "账户不存在或密码错误" +5 = "重复登录" +8 = "服务器维护" +10 = "昵称已存在" +11 = "昵称太短" +12 = "昵称太长" +13 = "密码太短" +14 = "密码太长" +15 = "账号已绑定" +20 = "拉取订单失败" +21 = "充值年龄不足" +22 = "退出金额不足" +23 = "已在退出中,不能退出" +50 = "请求参数错误" +51 = "服务器配置信息错误" +52 = "奖励ID不存在" +53 = "该奖励已经领取" +54 = "奖励条件未达成" +55 = "奖券不存在" +56 = "奖券已过期" +60 = "回放记录不存在" +100 = "比赛ID不存在" +101 = "比赛已结束" +102 = "入场费不足" +103 = "已经在匹配队列中" +104 = "已经在比赛中" +105 = "已经在其他游戏中" +106 = "比赛未找到, 比赛ID错误或者游戏已结束" +107 = "不在比赛中" +108 = "个人副本游戏已结束" +109 = "游戏已暂停" +110 = "请求参数错误" +111 = "匹配ID不存在" +112 = "匹配超时" +113 = "比赛结果不合法" +114 = "比赛繁忙" +201 = "不在活动时间" +202 = "活动奖励已领取" +203 = "活动未完成" +204 = "钻石不足" + +[en] +1 = "server except" +2 = "token error" +3 = "token expired" +4 = "account not exists or password error" +5 = "duplicate login" +8 = "server maintaining" +10 = "nick name already exists" +11 = "nick name too short" +12 = "nick name too long" +13 = "password too short" +14 = "password too long" +15 = "account bound Already" +20 = "charge fail" +21 = "not old enough" +22 = "cash not enough" +23 = "withdraw in progress" +50 = "required param error" +51 = "server configure error" +52 = "reward id not found" +53 = "that reward already get" +54 = "reward condition uncompelete" +55 = "season lottery ticket not exist" +56 = "season lottery ticket alearay expired" +60 = "replay record not exist" +100 = "match id not found" +101 = "compete already over" +102 = "Insufficient entry fee" +103 = "already in match queue" +104 = "already in game" +105 = "in other game" +106 = "match not found or compete already over" +107 = "not in game" +108 = "gameover" +109 = "game pause" +110 = "game required param error" +111 = "match id not found" +112 = "match timeout" +113 = "match result auth fail" +114 = "competition busy" +201 = "activity invalid" +202 = "activity reward draw" +203 = "activity not finished" +204 = "gems not enough" + diff --git a/tools/generated.go b/tools/generated.go new file mode 100644 index 0000000..3ad01d9 --- /dev/null +++ b/tools/generated.go @@ -0,0 +1,10 @@ +//go:generate go run generated.go +package main + +import ( + "server/config" +) + +func main() { + config.BuildErrCode() +} diff --git a/tools/robot/main.go b/tools/robot/main.go new file mode 100644 index 0000000..c148a4a --- /dev/null +++ b/tools/robot/main.go @@ -0,0 +1,76 @@ +package main + +import ( + "flag" + "fmt" + "math/rand" + "os" + "os/signal" + task2 "server/tools/robot/task" + "server/util" + "sync/atomic" + "time" + + "github.com/liangdas/armyant/task" + timewheel "github.com/liangdas/mqant/module/modules/timer" +) + +func main() { + // addr := flag.String("addr", "http://149.129.136.204:7615", "addr") + addr := flag.String("addr", "http://149.129.136.204:7615", "addr") + C := flag.Int("C", 1, "task count") + game := 0 + start := flag.Int("start", 0, "start number") + flag.Parse() + + if *start < 0 { + *start = 0 + } + + fmt.Println("addr: ", *addr) + fmt.Println("task count = ", *C) + + rand.Seed(time.Now().UnixNano()) + timewheel.SetTimeWheel(timewheel.New(100*time.Millisecond, 36)) + closeSig := make(chan bool) + util.Go(func() { + timewheel.GetTimeWheel().Start(closeSig) + }) + + /** + 每一次请求都会调用该函数,在该函数内实现具体请求操作 + + task:=task.Task{ + N:1000, //一共请求次数,会被平均分配给每一个并发协程 + C:100, //并发数 + //QPS:10, //每一个并发平均每秒请求次数(限流) 不填代表不限流 + } + + N/C 可计算出每一个Work(协程) RunWorker将要调用的次数 + */ + _task := task.LoopTask{ + C: *C, + } + manager := task2.NewManager(&_task, *addr, *start, game) + task2.CurrentConnect = new(int32) + task2.StopConnect = new(int32) + // PrintConns() + fmt.Println("开始测试") + _task.Run(manager) + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + <-c + _task.Stop() + + fmt.Println("exit...") + os.Exit(1) +} + +func PrintConns() { + c := atomic.LoadInt32(task2.CurrentConnect) + s := atomic.LoadInt32(task2.StopConnect) + fmt.Println("**********************connecting:", c, "**********************stop:", s) + timewheel.GetTimeWheel().AddTimer(5*time.Second, nil, func(arge interface{}) { + PrintConns() + }) +} diff --git a/tools/robot/task/manager.go b/tools/robot/task/manager.go new file mode 100644 index 0000000..67ea67d --- /dev/null +++ b/tools/robot/task/manager.go @@ -0,0 +1,40 @@ +package task + +import ( + "io" + "os" + + "github.com/liangdas/armyant/task" +) + +type Manager struct { + // Writer is where results will be written. If nil, results are written to stdout. + Writer io.Writer + Addr string + Number int + GameKind int +} + +func (this *Manager) writer() io.Writer { + if this.Writer == nil { + return os.Stdout + } + return this.Writer +} +func (this *Manager) Finish(task task.Task) { +} + +func (this *Manager) CreateWork() task.Work { + return NewWork(this) +} + +// Run makes all the requests, prints the summary. It blocks until +// all work is done. +func NewManager(t task.Task, addr string, start int, game int) task.WorkManager { + // append hey's user agent + this := new(Manager) + this.Addr = addr + this.Number = start + this.GameKind = game + return this +} diff --git a/tools/robot/task/work.go b/tools/robot/task/work.go new file mode 100644 index 0000000..2554fb9 --- /dev/null +++ b/tools/robot/task/work.go @@ -0,0 +1,74 @@ +package task + +import ( + "fmt" + "server/modules/web/values" + "server/pb" + "server/util" + "time" + + "github.com/liangdas/armyant/task" + "github.com/liangdas/mqant/log" +) + +func NewWork(manager *Manager) *Work { + this := new(Work) + // this.manager = manager + this.Number = manager.Number + this.WebAddr = manager.Addr + this.GameKind = manager.GameKind + manager.Number++ + return this +} + +func (this *Work) Init(t task.Task) { + this.WsWork = new(WsWork) + this.WsWork.register = map[int]map[int]func(msg []byte){} + this.WsWork.Work = this + + this.RegistFunc(int(pb.ServerType_SERVER_TYPE_GATEWAY), int(pb.ServerGateResp_GateLoginResp), this.LoginResp) + this.RegistFunc(int(pb.ServerType_SERVER_TYPE_COMMON), int(pb.ServerCommonResp_CommonPlayerBalanceResp), this.BroadcastResp) +} + +/* +* +Work 代表一个协程内具体执行任务工作者 +*/ +type Work struct { + *WsWork + WebAddr string + Number int + UID int + Nick string + Token string + GameKind int +} + +func (this *Work) RunWorker(t task.Task) { + fmt.Printf("robot %v start\n", this.Number) + req := &values.GuestLoginReq{} + ret := struct { + Code int + Msg string + Data values.LoginResp + }{} + this.Wait() + for { + err := util.HttpPost(this.WebAddr+"/account/guestLogin", req, &ret, map[string]string{"uuid": fmt.Sprintf("press_robot_%v", this.Number), "channel": "1"}) + if err == nil { + break + } + + log.Error("err:%v", err) + time.Sleep(5 * time.Second) + } + this.addr = ret.Data.GateAddr + this.Token = ret.Data.Token + this.UID = ret.Data.UID + // this.Wait() + this.Connect() +} + +func (this *Work) Close(t task.Task) { + this.conn.Close() +} diff --git a/tools/robot/task/wsreq.go b/tools/robot/task/wsreq.go new file mode 100644 index 0000000..2d7b6e9 --- /dev/null +++ b/tools/robot/task/wsreq.go @@ -0,0 +1,13 @@ +package task + +import ( + "server/pb" +) + +func (w *WsWork) Login() { + req := &pb.LoginRequest{ + UserId: uint32(w.UID), + Token: w.Token, + } + w.Write(int(pb.ServerType_SERVER_TYPE_GATEWAY), int(pb.ServerGateReq_GateLoginReq), req) +} diff --git a/tools/robot/task/wsresp.go b/tools/robot/task/wsresp.go new file mode 100644 index 0000000..c8aa067 --- /dev/null +++ b/tools/robot/task/wsresp.go @@ -0,0 +1,37 @@ +package task + +import ( + "server/pb" + + "github.com/gogo/protobuf/proto" + "github.com/liangdas/mqant/log" +) + +func U(msg []byte, resp proto.Message) bool { + if err := proto.Unmarshal(msg, resp); err != nil { + log.Error("%T err:%v", resp, err) + return false + } + return true +} + +func (w *WsWork) LoginResp(msg []byte) { + resp := new(pb.LoginResponse) + if !U(msg, resp) { + return + } + log.Debug("LoginResp resp:%+v", resp) + // return + if resp.Result != 0 { + return + } + // w.Wait() +} + +func (w *WsWork) BroadcastResp(msg []byte) { + resp := new(pb.BroadcastMsg) + if !U(msg, resp) { + return + } + // log.Debug("BroadcastResp resp:%+v", resp) +} diff --git a/tools/robot/task/wswork.go b/tools/robot/task/wswork.go new file mode 100644 index 0000000..de9594d --- /dev/null +++ b/tools/robot/task/wswork.go @@ -0,0 +1,279 @@ +package task + +import ( + "bufio" + "errors" + "fmt" + "io" + "math/rand" + "server/call" + "server/pb" + "server/util" + "sync/atomic" + "time" + + "github.com/gogo/protobuf/proto" + "github.com/liangdas/mqant/log" + timewheel "github.com/liangdas/mqant/module/modules/timer" + + "golang.org/x/net/websocket" +) + +const ( + bufsize = 4096 +) + +var ( + CurrentConnect *int32 + StopConnect *int32 +) + +type WsWork struct { + // lock *sync.Mutex + addr string + conn *websocket.Conn + r *bufio.Reader + register map[int]map[int]func(msg []byte) + workID int + tableID int + seat uint32 + disconnect bool + *Work +} + +func (w *WsWork) Connect() { + addr := "ws://" + w.addr + origin := "http://localhost/" //客户端地址 + ws, err := websocket.Dial(addr, "", origin) //第二个参数是websocket子协议,可以为空 + // fail := 0 + // t := 10 + for { + if err == nil { + break + } + // if fail >= 5 { + // return + // } + log.Error("Connect err:%v", err) + w.Wait(time.Duration(10) * time.Second) + ws, err = websocket.Dial(addr, "", origin) + // fail++ + // t += 5 + } + atomic.AddInt32(CurrentConnect, 1) + w.conn = ws + w.r = bufio.NewReaderSize(ws, bufsize) + w.StartHeartbeat() + w.StartWork() + w.ReadLoop() + w.Close() + // var buf = make([]byte, 1000) + // for { + // n, err := ws.Read(buf) + // if err != nil { + // log.Fatal(err) + // } + // fmt.Println("receive: ", string(buf[:n])) + // } +} + +func (w *WsWork) RegistFunc(mid, pid int, f func(msg []byte)) error { + if _, ok := w.register[mid]; !ok { + w.register[mid] = map[int]func(msg []byte){} + } + if _, ok := w.register[mid][pid]; !ok { + w.register[mid][pid] = f + } + return nil +} + +func (w *WsWork) StartHeartbeat() { + if w.disconnect { + return + } + timewheel.GetTimeWheel().AddTimer(10*time.Second, nil, func(arge interface{}) { + w.StartHeartbeat() + }) + if err := w.Write(int(pb.ServerType_SERVER_TYPE_GATEWAY), int(pb.ServerGateReq_GatePingReq), nil); err != nil { + log.Error("err:%v", err) + } +} + +func (w *WsWork) StartWork() { + w.Login() +} + +func (w *WsWork) Write(mID, pID int, msg proto.Message) error { + // log.Debug("write to agent:%v,%v", mID, pID) + if w.conn == nil { + return errors.New("Client nil") + } + body := []byte{} + var err error + if msg != nil { + body, err = proto.Marshal(msg) + if err != nil { + log.Error("err:%v", err) + // fmt.Println(err) + return err + } + } + send := []byte{} + length, err := util.IntToBytes(len(body)+10, 2) + if err != nil { + log.Error("length err:%v", err) + return err + } + send = append(send, length...) + module, err := util.IntToBytes(mID, 2) + if err != nil { + log.Error("module err:%v", err) + return err + } + protocol, _ := util.IntToBytes(pID, 2) + send = append(send, module...) + send = append(send, protocol...) + send = append(send, []byte{0, 0, 0, 0}...) + send = append(send, body...) + w.conn.Write(send) + return nil +} + +func (w *WsWork) readInt(n int) (ret int, err error) { + // tmp := []byte{} + tmp, err := w.readByte(n) + // fmt.Println("readint:", tmp) + if err != nil { + log.Error("err:%v", err) + return + } + ret, err = util.BytesToInt(tmp) + return +} + +func (w *WsWork) readByte(n int) (data []byte, err error) { + for i := 0; i < n; i++ { + var tmp byte + tmp, err = w.r.ReadByte() + // fmt.Println("readbyte:", tmp) + if err != nil { + log.Error("err:%v", err) + return + } + data = append(data, tmp) + } + // fmt.Println("protobuf-bytes:", data) + return +} + +func (w *WsWork) ReadLoop() { + for { + // 第一步拿到数据包长度 + length, err := w.readInt(2) + if err != nil { + log.Error("read err:%v", err) + return + } + if length > bufsize { + log.Error("max bufSize limit:%v,length:%v", bufsize, length) + return + } + // 第二步拿到模块协议类型 + moduleType, err := w.readInt(2) + if err != nil { + log.Error("err:%v", err) + return + } + if moduleType == 0 { + log.Error("invalid moduleType") + return + } + if moduleType >= 3000 { + moduleType = call.GetGameOriginID(moduleType) + } + + // 第三步拿到协议类型 + protocolType, err := w.readInt(2) + if err != nil { + log.Error("err:%v", err) + return + } + if protocolType == 0 { + log.Error("invalid protocolType") + return + } + + // 第四步拿到uid + _, err = w.readInt(4) + if err != nil { + log.Error("err:%v", err) + return + } + // log.Debug("uid:%v", uid) + + // 路由 + // moduleName := pb.ModuleType_name[int32(moduleType)] + // if moduleName == "" { + // log.Error("unknow moduleType:%v", moduleType) + // return + // } + + // fmt.Println("==========================", moduleType, moduleName, protocolType) + // var protocolName string + // pr := call.GetProtocolType(moduleType) + // protocolName = pr[int32(protocolType)] + // if protocolName == "" { + // log.Error("invalid protocolType:%v", protocolType) + // return + // } + + request := []byte{} + if length > 10 { + // 第五步拿到协议数据 + request = make([]byte, length-10) + // l, err := w.r.Read(request) + l, err := io.ReadFull(w.r, request) + if err != nil { + log.Error("err:%v", err) + return + } + if l != length-10 { + log.Error("pack len:%v,read len:%v,module:%v,protocol:%v", length-10, l, moduleType, protocolType) + return + } + } + + // 心跳包 + if moduleType == 1000 && protocolType == int(pb.ServerGateResp_GatePingResp) { + // fmt.Println("ping from server") + continue + } + + if r, ok1 := w.register[moduleType]; ok1 { + if sr, ok2 := r[protocolType]; ok2 { + sr(request) + continue + } + } + // fmt.Printf("unknow proto:%v,%v\n", moduleType, protocolType) + } +} + +func (this *WsWork) Wait(t ...time.Duration) { + var t1 time.Duration + if t == nil { + s := rand.Intn(3000) + 1000 + t1 = time.Duration(s) * time.Millisecond + } else { + t1 = t[0] + } + time.Sleep(t1) +} + +func (this *WsWork) Close() { + fmt.Printf("robot %v stop", this.Number) + atomic.AddInt32(CurrentConnect, -1) + atomic.AddInt32(StopConnect, 1) + this.disconnect = true + this.conn.Close() +} diff --git a/util/bytes.go b/util/bytes.go new file mode 100644 index 0000000..3593621 --- /dev/null +++ b/util/bytes.go @@ -0,0 +1,51 @@ +package util + +import ( + "bytes" + "encoding/binary" + "fmt" +) + +func BytesToInt(b []byte) (int, error) { + if len(b) == 3 { + b = append([]byte{0}, b...) + } + bytesBuffer := bytes.NewBuffer(b) + switch len(b) { + case 1: + var tmp uint8 + err := binary.Read(bytesBuffer, binary.LittleEndian, &tmp) + return int(tmp), err + case 2: + var tmp uint16 + err := binary.Read(bytesBuffer, binary.LittleEndian, &tmp) + return int(tmp), err + case 4: + var tmp uint32 + err := binary.Read(bytesBuffer, binary.LittleEndian, &tmp) + return int(tmp), err + default: + return 0, fmt.Errorf("%s", "BytesToInt bytes lenth is invaild!") + } +} + +func IntToBytes(n int, b byte) ([]byte, error) { + switch b { + case 1: + tmp := uint8(n) + bytesBuffer := bytes.NewBuffer([]byte{}) + binary.Write(bytesBuffer, binary.LittleEndian, &tmp) + return bytesBuffer.Bytes(), nil + case 2: + tmp := uint16(n) + bytesBuffer := bytes.NewBuffer([]byte{}) + binary.Write(bytesBuffer, binary.LittleEndian, &tmp) + return bytesBuffer.Bytes(), nil + case 3, 4: + tmp := uint32(n) + bytesBuffer := bytes.NewBuffer([]byte{}) + binary.Write(bytesBuffer, binary.LittleEndian, &tmp) + return bytesBuffer.Bytes(), nil + } + return nil, fmt.Errorf("IntToBytes b param is invaild") +} diff --git a/util/copy.go b/util/copy.go new file mode 100644 index 0000000..36a1cfd --- /dev/null +++ b/util/copy.go @@ -0,0 +1,14 @@ +package util + +import ( + "bytes" + "encoding/gob" +) + +func DeepCopy(dst, src interface{}) error { + var buf bytes.Buffer + if err := gob.NewEncoder(&buf).Encode(src); err != nil { + return err + } + return gob.NewDecoder(bytes.NewBuffer(buf.Bytes())).Decode(dst) +} diff --git a/util/encrypt.go b/util/encrypt.go new file mode 100644 index 0000000..f423c0b --- /dev/null +++ b/util/encrypt.go @@ -0,0 +1,265 @@ +package util + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "errors" + "io" +) + +const ( + Key = "iBszkjbOOZdaJbEp" + CurrentMode = 2 + blocksize = 16 +) + +// AesEncrypt aes加密用不同的模式 +func AesEncrypt(data []byte) []byte { + switch CurrentMode { + case 1: + return AesEncryptCBC(data) + case 2: + return AesEncryptECB(data) + case 3: + return AesEncryptCFB(data) + default: + return nil + } +} +func AesDecrypt(data []byte) (decrypted []byte, err error) { + switch CurrentMode { + case 1: + return AesDecryptCBC(data) + case 2: + return AesDecryptECB(data) + case 3: + return AesDecryptCFB(data) + default: + return nil, errors.New("unknow") + } +} + +// AesEncryptCBC aes加密算法,采用cbc模式 +func AesEncryptCBC(data []byte) []byte { + key := []byte(Key) + block, _ := aes.NewCipher(key) + blockSize := block.BlockSize() // 获取秘钥块的长度 + data = Pkcs5Padding(data, blockSize) // 补全码 + blockMode := cipher.NewCBCEncrypter(block, key[:blockSize]) // 加密模式 + encrypted := make([]byte, len(data)) // 创建数组 + blockMode.CryptBlocks(encrypted, data) // 加密 + // hex.EncodeToString(encrypted) + return []byte(base64.StdEncoding.EncodeToString(encrypted)) + // return encrypted +} + +// AesDecryptCBC 解密 +func AesDecryptCBC(data []byte) (decrypted []byte, err error) { + data, err = base64.StdEncoding.DecodeString(string(data)) + if err != nil { + decrypted = nil + return + } + key := []byte(Key) + block, _ := aes.NewCipher(key) // 分组秘钥 + blockSize := block.BlockSize() // 获取秘钥块的长度 + blockMode := cipher.NewCBCDecrypter(block, key[:blockSize]) // 加密模式 + decrypted = make([]byte, len(data)) // 创建数组 + blockMode.CryptBlocks(decrypted, data) // 解密 + decrypted = pkcs5UnPadding(decrypted) // 去除补全码 + return +} + +// AesDecrypt aes加密算法,采用ecb模式 +func AesDecryptECB(origData []byte) (decrypted []byte, err error) { + + // key := []byte(Key) + // tmp := "" + // tmp, err = url.QueryUnescape(string(origData)) + // if err != nil { + // return + // } + origData, err = base64.StdEncoding.DecodeString(string(origData)) + if err != nil { + return + } + + cipher, _ := aes.NewCipher(generateKey()) + decrypted = make([]byte, len(origData)) + + for bs, be := 0, cipher.BlockSize(); bs < len(origData); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() { + cipher.Decrypt(decrypted[bs:be], origData[bs:be]) + } + + // trim := 0 + // if len(decrypted) > 0 { + // trim = len(decrypted) - int(decrypted[len(decrypted)-1]) + // } + + // decrypted = decrypted[:trim] + decrypted = pkcs5UnPadding(decrypted) + + // decrypted, err = base64.StdEncoding.DecodeString(string(decrypted)) + // if err != nil { + // decrypted = nil + // return + // } + return +} + +func AesDecryptECB1(origData []byte) (decrypted []byte, err error) { + origData, err = base64.StdEncoding.DecodeString(string(origData)) + if err != nil { + return + } + + cipher, _ := aes.NewCipher(generateKey()) + decrypted = make([]byte, len(origData)) + + for bs, be := 0, cipher.BlockSize(); bs < len(origData); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() { + cipher.Decrypt(decrypted[bs:be], origData[bs:be]) + } + + trim := 0 + if len(decrypted) > 0 { + trim = len(decrypted) - int(decrypted[len(decrypted)-1]) + } + + decrypted = decrypted[:trim] + + decrypted, err = base64.StdEncoding.DecodeString(string(decrypted)) + if err != nil { + decrypted = nil + return + } + return +} + +func generateKey() (genKey []byte) { + key := []byte(Key) + genKey = make([]byte, 16) + copy(genKey, key) + for i := 16; i < len(key); { + for j := 0; j < 16 && i < len(key); j, i = j+1, i+1 { + genKey[j] ^= key[i] + } + } + return genKey +} + +func AesEncryptECB(data []byte) []byte { + key := []byte(Key) + block, _ := aes.NewCipher(key) + // data = []byte(base64.StdEncoding.EncodeToString(data)) + data = Pkcs5Padding(data, block.BlockSize()) + decrypted := make([]byte, len(data)) + size := block.BlockSize() + + for bs, be := 0, size; bs < len(data); bs, be = bs+size, be+size { + block.Encrypt(decrypted[bs:be], data[bs:be]) + } + + return []byte(base64.StdEncoding.EncodeToString(decrypted)) +} + +func AesEncryptECB1(data []byte) []byte { + key := []byte(Key) + block, _ := aes.NewCipher(key) + data = []byte(base64.StdEncoding.EncodeToString(data)) + // data = pkcs5Padding(data, block.BlockSize()) + data = noPadding(data) + decrypted := make([]byte, len(data)) + size := block.BlockSize() + + for bs, be := 0, size; bs < len(data); bs, be = bs+size, be+size { + block.Encrypt(decrypted[bs:be], data[bs:be]) + } + + return []byte(base64.StdEncoding.EncodeToString(decrypted)) +} + +// AesEncryptCFB aes加密cfb模式 +func AesEncryptCFB(origData []byte) (encrypted []byte) { + key := []byte(Key) + block, err := aes.NewCipher(key) + if err != nil { + panic(err) + } + encrypted = make([]byte, blocksize+len(origData)) + iv := encrypted[:blocksize] + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + panic(err) + } + stream := cipher.NewCFBEncrypter(block, iv) + stream.XORKeyStream(encrypted[blocksize:], origData) + encrypted = []byte(base64.StdEncoding.EncodeToString(encrypted)) + return +} + +func AesDecryptCFB(data []byte) (decrypted []byte, err error) { + key := []byte(Key) + block, _ := aes.NewCipher(key) + if len(data) < blocksize { + decrypted = nil + err = errors.New("ciphertext too short") + return + } + encrypted, err := base64.StdEncoding.DecodeString(string(data)) + if err != nil { + decrypted = nil + return + } + iv := encrypted[:blocksize] + encrypted = encrypted[blocksize:] + + stream := cipher.NewCFBDecrypter(block, iv) + stream.XORKeyStream(encrypted, encrypted) + return +} + +func ZeroPadding(ciphertext []byte, blockSize int) []byte { + padding := blockSize - len(ciphertext)%blockSize + padtext := bytes.Repeat([]byte{0}, padding) //用0去填充 + return append(ciphertext, padtext...) +} + +func ZeroUnPadding(origData []byte) []byte { + return bytes.TrimFunc(origData, + func(r rune) bool { + return r == rune(0) + }) +} + +func Pkcs5Padding(ciphertext []byte, blockSize int) []byte { + padding := blockSize - len(ciphertext)%blockSize + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + return append(ciphertext, padtext...) +} + +func pkcs5UnPadding(origData []byte) []byte { + length := len(origData) + unpadding := int(origData[length-1]) + return origData[:(length - unpadding)] +} + +// nopadding模式 +func noPadding(src []byte) []byte { + count := blocksize - len(src)%blocksize + if len(src)%blocksize == 0 { + return src + } else { + return append(src, bytes.Repeat([]byte{byte(0)}, count)...) + } +} + +// nopadding模式 +func unNoPadding(src []byte) []byte { + for i := len(src) - 1; ; i-- { + if src[i] != 0 { + return src[:i+1] + } + } +} diff --git a/util/http.go b/util/http.go new file mode 100644 index 0000000..294d723 --- /dev/null +++ b/util/http.go @@ -0,0 +1,264 @@ +package util + +import ( + "bytes" + "crypto/md5" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "reflect" + "regexp" + "strconv" + "strings" + "time" + + "github.com/liangdas/mqant/log" +) + +const ( + key = "HXSQnFRhs1v1cEty" +) + +// UnPackMD5 统一的解析方法 +func UnPackMD5(body []byte) string { + pkg := rawPack{} + if err := json.Unmarshal(body, &pkg); err != nil { + log.Error("umarshal msg fail %v", err) + return "" + } + sign := pkg.Sign + data := pkg.Data + if CalculateMD5(data) != sign { + log.Error("check sign fail") + return "" + } + return data +} + +type rawPack struct { + Sign string + Data string +} + +// CalculateMD5 calculate md5 +func CalculateMD5(data string) string { + h := md5.New() + h.Write([]byte(data)) + return hex.EncodeToString(h.Sum(nil)) +} + +func CalculateSHA256(input string) string { + hasher := sha256.New() + hasher.Write([]byte(input)) + hashBytes := hasher.Sum(nil) + + // Convert the hash bytes to a hexadecimal string + hashString := hex.EncodeToString(hashBytes) + + return hashString +} + +// HttpUseProxy 使用代理请求 +func HttpUseProxy(proxyURL, target string, ret interface{}) error { + uri, err := url.Parse(proxyURL) + if err != nil { + log.Error("parse url error: ", err) + return err + } + client := http.Client{ + Transport: &http.Transport{ + // 设置代理 + Proxy: http.ProxyURL(uri), + }, + Timeout: 5 * time.Second, + } + log.Debug("reqURL:%v", target) + resp, err := client.Get(target) + if err != nil { + log.Error("get err:%v", err) + return err + } + // log.Debug("ret:%v", resp) + if resp.StatusCode != http.StatusOK { + log.Error("req fail err code:%v", resp.StatusCode) + return errors.New("http req fail") + } + defer resp.Body.Close() + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Error("read err %v", err) + return err + } + if err := json.Unmarshal(data, ret); err != nil { + return err + } + return nil +} + +// HttpGet Get请求 +func HttpGet(target string, ret interface{}) error { + client := http.Client{ + Timeout: 5 * time.Second, + } + resp, err := client.Get(target) + if err != nil { + log.Error("get err:%v", err) + return err + } + if resp.StatusCode != http.StatusOK { + log.Error("req fail err code:%v", resp.StatusCode) + return errors.New("http req fail") + } + defer resp.Body.Close() + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Error("read err %v", err) + return err + } + log.Debug("resp:%v", string(data)) + if err := json.Unmarshal(data, ret); err != nil { + log.Error("Unmarshal err %v", err) + return err + } + return nil +} + +// HttpPost post请求 +func HttpPost(target string, send, ret interface{}, header map[string]string) error { + reqStr, _ := json.Marshal(send) + log.Debug("Post to:%v,req:%v", target, string(reqStr)) + req, err := http.NewRequest("POST", target, bytes.NewBuffer(reqStr)) + if err != nil { + log.Error("err:%v", err) + return err + } + req.Header.Set("Content-Type", "application/json") + for k, v := range header { + req.Header.Set(k, v) + } + // req.Header.Set("Accept", "application/problem+json") + // req.Header.Set("Authorization", "api-key "+config.GetConfig().Web.OrangePay.APISecret) + // req.Header.Set("Authorization", "api-key "+OrangeAPISecret) + + client := &http.Client{ + Timeout: 20 * time.Second, + } + log.Debug("req:%+v", req) + resp, err := client.Do(req) + if err != nil { + log.Error("http post call err:%v", err) + return err + } + // if resp.StatusCode != http.StatusOK { + // log.Error("req fail err code:%v", resp.StatusCode) + // return errors.New("http req fail") + // } + defer resp.Body.Close() + body, _ := ioutil.ReadAll(resp.Body) + log.Debug("response Body:%v", string(body)) + if err := json.Unmarshal(body, ret); err != nil { + log.Error("unmarshal fail err:%v", err) + return err + } + return nil +} + +// HttpPostForm post请求 +func HttpPostForm(target string, send, ret interface{}, header map[string]string) error { + log.Debug("send:%+v", send) + uv := url.Values{} + typ := reflect.TypeOf(send).Elem() + val := reflect.ValueOf(send).Elem() + for i := 0; i < typ.NumField(); i++ { + if val.Field(i).IsZero() { + continue + } + uv.Add(typ.Field(i).Tag.Get("json"), fmt.Sprintf("%v", val.Field(i).Interface())) + } + log.Debug("uv:%v", uv) + // ul += fmt.Sprintf("%v=%v", "sign", util.CalculateMD5(oid+strconv.Itoa(apiReq.AdvChannel)+apiReq.ClientDate+apiReq.Money+values.ZYAppKey)) + + req, err := http.NewRequest("POST", target, strings.NewReader(uv.Encode())) + if err != nil { + log.Error("err:%v", err) + return err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + // req.Header.Set("Accept", "application/problem+json") + // req.Header.Set("Authorization", "api-key "+config.GetConfig().Web.OrangePay.APISecret) + // req.Header.Set("Authorization", "api-key "+OrangeAPISecret) + + client := &http.Client{ + Timeout: 5 * time.Second, + } + log.Debug("req:%+v", req) + resp, err := client.Do(req) + if err != nil { + log.Error("http post call err:%v", err) + return err + } + // if resp.StatusCode != http.StatusOK { + // log.Error("req fail err code:%v", resp.StatusCode) + // return errors.New("http req fail") + // } + defer resp.Body.Close() + body, _ := ioutil.ReadAll(resp.Body) + log.Debug("response Body%v:", string(body)) + if err := json.Unmarshal(body, ret); err != nil { + log.Error("unmarshal fail err:%v", err) + return err + } + return nil +} + +func ParseFormReq(va url.Values, req interface{}) { + typ := reflect.TypeOf(req).Elem() + val := reflect.ValueOf(req).Elem() + for i := 0; i < typ.NumField(); i++ { + one := val.Field(i) + value := va.Get(typ.Field(i).Tag.Get("json")) + if value == "" { + continue + } + if one.Kind() == reflect.Int || one.Kind() == reflect.Int64 { + this, err := strconv.Atoi(value) + if err != nil { + log.Error("err:%v", err) + } + one.SetInt(int64(this)) + } else if one.Kind() == reflect.Float32 || one.Kind() == reflect.Float64 { + this, err := strconv.ParseFloat(value, 64) + if err != nil { + log.Error("err:%v", err) + } + one.SetFloat(this) + } else if one.Kind() == reflect.Bool { + this, err := strconv.ParseBool(value) + if err != nil { + log.Error("err:%v", err) + } + one.SetBool(this) + } else { + one.SetString(value) + } + } +} + +// 将http的请求头替换为https +func ReplaceHTTPWithHTTPS(text string) string { + // 创建正则表达式,用于匹配 HTTP 链接 + httpPattern := regexp.MustCompile(`http://[^\s]+`) + + // 将 HTTP 替换为 HTTPS + httpsText := httpPattern.ReplaceAllStringFunc(text, func(match string) string { + httpsURL := "https" + strings.TrimPrefix(match, "http") + return httpsURL + }) + + return httpsText +} diff --git a/util/recover.go b/util/recover.go new file mode 100644 index 0000000..3058441 --- /dev/null +++ b/util/recover.go @@ -0,0 +1,96 @@ +package util + +import ( + "bytes" + "fmt" + "io/ioutil" + "runtime" + "strings" + + "github.com/liangdas/mqant/log" +) + +// 捕获异常并打日志 +// Usage: defer Recover() +func Recover() { + if err := recover(); err != nil { + buf := make([]byte, 1024) + runtime.Stack(buf, false) + log.Error("panic(%+v), stack:\n%s", err, string(buf)) + } +} + +var ( + dunno = []byte("???") + centerDot = []byte("·") + dot = []byte(".") + slash = []byte("/") +) + +// 和Recover差不多, 但是会舍弃最近两个没用的栈 +func Catch(desc string) { + if err := recover(); err != nil { + log.Error("[%s] -- panic(%+v), stack:\n%s", desc, err, stack(3)) + } +} + +// stack returns a nicely formated stack frame, skipping skip frames +func stack(skip int) []byte { + buf := new(bytes.Buffer) // the returned data + // As we loop, we open files and read them. These variables record the currently + // loaded file. + var lines [][]byte + var lastFile string + for i := skip; ; i = i + 1 { // Skip the expected number of frames + pc, file, line, ok := runtime.Caller(i) + if !ok || strings.HasSuffix(file, ".s") { + break + } + // Print this much at least. If we can't find the source, it won't show. + fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc) + if file != lastFile { + data, err := ioutil.ReadFile(file) + if err != nil { + continue + } + lines = bytes.Split(data, []byte{'\n'}) + lastFile = file + } + fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line)) + } + return buf.Bytes() +} + +// source returns a space-trimmed slice of the n'th line. +func source(lines [][]byte, n int) []byte { + n = n - 1 // in stack trace, lines are 1-indexed but our array is 0-indexed + if n < 0 || n >= len(lines) { + return dunno + } + return bytes.TrimSpace(lines[n]) +} + +// function returns, if possible, the name of the function containing the PC. +func function(pc uintptr) []byte { + fn := runtime.FuncForPC(pc) + if fn == nil { + return dunno + } + name := []byte(fn.Name()) + // The name includes the path name to the package, which is unnecessary + // since the file name is already included. Plus, it has center dots. + // That is, we see + // runtime/debug.*T·ptrmethod + // and want + // *T.ptrmethod + // Also the package path might contains dot (e.g. code.google.com/...), + // so first eliminate the path prefix + if lastslash := bytes.LastIndex(name, slash); lastslash >= 0 { + name = name[lastslash+1:] + } + if period := bytes.Index(name, dot); period >= 0 { + name = name[period+1:] + } + name = bytes.Replace(name, centerDot, dot, -1) + return name +} diff --git a/util/share.go b/util/share.go new file mode 100644 index 0000000..61d1829 --- /dev/null +++ b/util/share.go @@ -0,0 +1,56 @@ +package util + +import ( + "strconv" + + "github.com/liangdas/mqant/log" +) + +var ( + ShareCodeMap = map[string]string{ + "1": "K", + "2": "C", + "3": "R", + "4": "T", + "5": "U", + "6": "W", + "7": "B", + "8": "E", + "9": "A", + "0": "F", + } + ShareUIDMap = map[string]string{ + "K": "1", + "C": "2", + "R": "3", + "T": "4", + "U": "5", + "W": "6", + "B": "7", + "E": "8", + "A": "9", + "F": "0", + } +) + +func GetShareCode(uid int) string { + code := "" + str := strconv.Itoa(uid) + for _, v := range str { + code += ShareCodeMap[string(v)] + } + return code +} + +func GetShareUID(code string) int { + str := "" + for _, v := range code { + str += ShareUIDMap[string(v)] + } + ret, err := strconv.Atoi(str) + if err != nil { + log.Error("%v", err) + return 0 + } + return ret +} diff --git a/util/sign.go b/util/sign.go new file mode 100644 index 0000000..263183a --- /dev/null +++ b/util/sign.go @@ -0,0 +1,31 @@ +package util + +import ( + "crypto/hmac" + "crypto/sha1" + "encoding/base64" + "fmt" +) + +// sha1加密 +func HMACSHA1(keyStr, value string) string { + + key := []byte(keyStr) + mac := hmac.New(sha1.New, key) + mac.Write([]byte(value)) + //进行base64编码 + res := base64.StdEncoding.EncodeToString(mac.Sum(nil)) + + return res +} + +// HMACSHA1X 先转16进制 +func HMACSHA1X(keyStr, value string) string { + key := []byte(keyStr) + mac := hmac.New(sha1.New, key) + mac.Write([]byte(value)) + //进行base64编码 + tmp := fmt.Sprintf("%x", mac.Sum(nil)) + res := base64.StdEncoding.EncodeToString([]byte(tmp)) + return res +} diff --git a/util/snowflake.go b/util/snowflake.go new file mode 100644 index 0000000..e43cbf4 --- /dev/null +++ b/util/snowflake.go @@ -0,0 +1,76 @@ +package util + +import ( + "strconv" + "sync" + "time" +) + +var IdWorker *Worker + +const ( + workerBits uint8 = 10 // 每台机器(节点)的ID位数 10位最大可以有2^10=1024个节点 + numberBits uint8 = 12 // 表示每个集群下的每个节点,1毫秒内可生成的id序号的二进制位数 即每毫秒可生成 2^12-1=4096个唯一ID + // 这里求最大值使用了位运算,-1 的二进制表示为 1 的补码,感兴趣的同学可以自己算算试试 -1 ^ (-1 << nodeBits) 这里是不是等于 1023 + workerMax int64 = -1 ^ (-1 << workerBits) // 节点ID的最大值,用于防止溢出 + numberMax int64 = -1 ^ (-1 << numberBits) // 同上,用来表示生成id序号的最大值 + timeShift uint8 = workerBits + numberBits // 时间戳向左的偏移量 + workerShift uint8 = numberBits // 节点ID向左的偏移量 + // 41位字节作为时间戳数值的话 大约68年就会用完 + // 假如你2010年1月1日开始开发系统 如果不减去2010年1月1日的时间戳 那么白白浪费40年的时间戳啊! + // 这个一旦定义且开始生成ID后千万不要改了 不然可能会生成相同的ID + epoch int64 = 1525705533000 // 这个是我在写epoch这个变量时的时间戳(毫秒) +) + +// 定义一个woker工作节点所需要的基本参数 +type Worker struct { + mu sync.Mutex // 添加互斥锁 确保并发安全 + timestamp int64 // 记录时间戳 + workerId int64 // 该节点的ID + number int64 // 当前毫秒已经生成的id序列号(从0开始累加) 1毫秒内最多生成4096个ID +} + +// 实例化一个工作节点 +func NewWorker(workerId int64) { + // 要先检测workerId是否在上面定义的范围内 + if workerId < 0 || workerId > workerMax { + return + } + // 生成一个新节点 + IdWorker = &Worker{ + timestamp: 0, + workerId: workerId, + number: 0, + } +} + +// 接下来我们开始生成id +// 生成方法一定要挂载在某个woker下,这样逻辑会比较清晰 指定某个节点生成id +func (w *Worker) GetId() string { + // 获取id最关键的一点 加锁 加锁 加锁 + w.mu.Lock() + defer w.mu.Unlock() // 生成完成后记得 解锁 解锁 解锁 + + // 获取生成时的时间戳 + now := time.Now().UnixNano() / 1e6 // 纳秒转毫秒 + if w.timestamp == now { + w.number++ + + // 这里要判断,当前工作节点是否在1毫秒内已经生成numberMax个ID + if w.number > numberMax { + // 如果当前工作节点在1毫秒内生成的ID已经超过上限 需要等待1毫秒再继续生成 + for now <= w.timestamp { + now = time.Now().UnixNano() / 1e6 + } + } + } else { + // 如果当前时间与工作节点上一次生成ID的时间不一致 则需要重置工作节点生成ID的序号 + w.number = 0 + w.timestamp = now // 将机器上一次生成ID的时间更新为当前时间 + } + + // 第一段 now - epoch 为该算法目前已经奔跑了xxx毫秒 + // 如果在程序跑了一段时间修改了epoch这个值 可能会导致生成相同的ID + ID := int64((now-epoch)< 7*24*6060 { + return false + } + ta := time.Unix(a, 0) + tb := time.Unix(b, 0) + taw := ta.Weekday() + tbw := tb.Weekday() + // if taw == time.Sunday { + // taw = 7 + // } + // if tbw == time.Sunday { + // tbw = 7 + // } + if ta.After(tb) { + return taw >= tbw + } + return tbw >= taw +} + +func IsSameMonth(a, b int64) bool { + ta := time.Unix(a, 0) + tb := time.Unix(b, 0) + return GetFirstDateOfMonth(ta) == GetFirstDateOfMonth(tb) +} + +func GetMonthDay(t time.Time) int { + y, m, _ := t.Date() + if m == 2 { + if ((y%4) == 0 && (y%100) != 0) || (y%400) == 0 { + return 29 + } else { + return 28 + } + } + if m == 4 || m == 6 || m == 9 || m == 11 { + return 30 + } + return 31 +} + +// 获取本周的开始时间和结束时间 +func GetStartAndEndOfWeek(day int) (string, string) { + // 获取当前时间 + t := time.Now() + weekday := t.Weekday() + startOfWeek := t.AddDate(0, 0, -int(weekday)+1-day) + endOfWeek := startOfWeek.AddDate(0, 0, 6) + return startOfWeek.Format("2006-01-02"), endOfWeek.Format("2006-01-02") +} + +// 根据星期数判断今天是否是单数天 +func IsSingleDay() bool { + day := time.Now().Weekday() + if day == time.Sunday { + day = 7 + } + return day%2 == 1 +} diff --git a/util/util.go b/util/util.go new file mode 100644 index 0000000..a4a2db1 --- /dev/null +++ b/util/util.go @@ -0,0 +1,846 @@ +package util + +import ( + "bufio" + "container/list" + "fmt" + "io" + "math" + "math/rand" + "net/http" + "os" + "reflect" + "regexp" + "strconv" + "strings" + "time" + + "github.com/liangdas/mqant/log" + "github.com/mitchellh/mapstructure" +) + +// FormatFloat 取小数点后n位小数,四舍五入 +func FormatFloat(num float64, decimal int) string { + if math.Trunc(num) == num || decimal == 0 { + return fmt.Sprintf("%.f", math.Trunc(num)) + } + format := "%." + strconv.Itoa(decimal) + "f" + return fmt.Sprintf(format, num) +} + +// RoundFloat 取小数点后n位非零小数 +func RoundFloat(num float64, decimal int) string { + // 默认乘1 + d := float64(1) + if decimal > 0 { + // 10的N次方 + d = math.Pow10(decimal) + } + // math.trunc作用就是返回浮点数的整数部分 + // 再除回去,小数点后无效的0也就不存在了 + return strconv.FormatFloat(math.Trunc(num*d)/d, 'f', -1, 64) +} + +// Decimal 保留x位小数,返回float64 +func Decimal(value float64, deci int) float64 { + value, _ = strconv.ParseFloat(fmt.Sprintf("%."+strconv.Itoa(deci)+"f", value), 64) + return value +} + +func GetInt(data interface{}) int { + switch data := data.(type) { + case int: + return data + case int64: + return int(data) + case float32: + return int(data) + case float64: + return int(data) + case string: + ret, _ := strconv.Atoi(data) + return ret + default: + return 0 + } +} + +func GetInt64(data interface{}) int64 { + switch data := data.(type) { + case int: + return int64(data) + case int64: + return data + case float32: + return int64(data) + case float64: + return int64(data) + case string: + ret, _ := strconv.ParseInt(data, 10, 64) + return ret + default: + return 0 + } +} + +// CheckFileIsExist 判断文件是否存在 +func CheckFileIsExist(filename string) bool { + var exist = true + if _, err := os.Stat(filename); os.IsNotExist(err) { + exist = false + } + return exist +} + +func StructToMap(obj interface{}, tag string) map[string]interface{} { + t := reflect.TypeOf(obj) + v := reflect.ValueOf(obj) + if v.Kind() == reflect.Ptr { + t = t.Elem() + v = v.Elem() + } + + var result = make(map[string]interface{}) + for i := 0; i < t.NumField(); i++ { + tagName := t.Field(i).Tag.Get(tag) + if tagName != "" && tagName != "-" { + result[tagName] = v.Field(i).Interface() + } + } + + return result +} + +func StructToMapJson(obj interface{}) map[string]interface{} { + t := reflect.TypeOf(obj) + v := reflect.ValueOf(obj) + if t.Kind() == reflect.Ptr { + t = t.Elem() + v = v.Elem() + } + + var result = make(map[string]interface{}) + for i := 0; i < t.NumField(); i++ { + if v.Field(i).Kind() == reflect.Ptr && v.Field(i).IsNil() { + continue + } + tagName := t.Field(i).Tag.Get("json") + if tagName != "" && tagName != "-" && !v.Field(i).IsZero() { + result[tagName] = v.Field(i).Interface() + } + } + + return result +} + +// StructToMapJsonAll 非空字段也包括 +func StructToMapJsonAll(obj interface{}) map[string]interface{} { + t := reflect.TypeOf(obj) + v := reflect.ValueOf(obj) + if t.Kind() == reflect.Ptr { + t = t.Elem() + v = v.Elem() + } + + var result = make(map[string]interface{}) + for i := 0; i < t.NumField(); i++ { + if v.Field(i).Kind() == reflect.Ptr && v.Field(i).IsNil() { + continue + } + tagName := t.Field(i).Tag.Get("json") + if tagName != "" && tagName != "-" { + result[tagName] = v.Field(i).Interface() + } + } + + return result +} + +func Map2Struct(obj map[string]interface{}, result interface{}) error { + return mapstructure.Decode(obj, result) +} + +// Go 用协程处理f +func Go(f func()) { + go func() { + defer Recover() + f() + }() +} + +// RandomLetters 随机26位不重复的字符串 +func RandomLetters() string { + a := rand.Perm(26) + s := []byte{} + for _, v := range a { + s = append(s, byte(97+v)) + } + return string(s) +} + +var ( + characters52 = [52]byte{ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + } +) + +func GenerateRandomString(size int) string { + raw := make([]byte, size) + for i := 0; i < size; i++ { + raw[i] = characters52[rand.Intn(52)] + } + return string(raw) +} + +// StrFirstToUpper 首字母大写 +func StrFirstToUpper(str string) string { + if len(str) < 1 { + return "" + } + strArry := []byte(str) + if strArry[0] >= 97 && strArry[0] <= 122 { + strArry[0] -= 32 + } + return string(strArry) +} + +// DecodeRedisMap 解析map到目标t中,目前只实现了部分方法,如果需要解析更复杂结构需实现 +func DecodeRedisMap(m map[string]string, t interface{}) (err error) { + val := reflect.ValueOf(t).Elem() + for i := 0; i < val.NumField(); i++ { + f := val.Type().Field(i) + fi := val.Field(i) + name := f.Name + if s, ok := m[name]; ok { + switch f.Type.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + ret, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return err + } + fi.SetInt(ret) + case reflect.Float32, reflect.Float64: + ret, err := strconv.ParseFloat(s, 64) + if err != nil { + return err + } + fi.SetFloat(ret) + case reflect.String: + fi.SetString(s) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + ret, err := strconv.ParseUint(s, 10, 64) + if err != nil { + return err + } + fi.SetUint(ret) + case reflect.Bool: + ret, err := strconv.ParseBool(s) + if err != nil { + return err + } + fi.SetBool(ret) + default: + fmt.Println(t) + continue + } + } + } + return nil +} + +/* +判断文件或文件夹是否存在 +如果返回的错误为nil,说明文件或文件夹存在 +如果返回的错误类型使用os.IsNotExist()判断为true,说明文件或文件夹不存在 +如果返回的错误为其它类型,则不确定是否在存在 +*/ +func PathExists(path string) (bool, error) { + _, err := os.Stat(path) + if err == nil { + return true, nil + } + if os.IsNotExist(err) { + return false, nil + } + return false, err +} + +// CheckPath 判断目录是否存在,不存在自动创建 +func CheckPath(path string) error { + b, err := PathExists(path) + if err != nil { + return err + } + if b { + return fmt.Errorf("path %s 存在\n", path) + } + err = os.Mkdir(path, os.ModePerm) + if err != nil { + return err + } + return nil +} + +// SavePic 下载并保存图片 +func SavePic(imgPath, imgUrl, fileName string) error { + res, err := http.Get(imgUrl) + if err != nil { + log.Error("err:%v", err) + return err + } + defer res.Body.Close() + // 获得get请求响应的reader对象 + reader := bufio.NewReaderSize(res.Body, 32*1024) + + path := imgPath + string(os.PathSeparator) + fileName + ".png" + if ok, _ := PathExists(path); ok { + if err := os.Remove(path); err != nil { + log.Error("err:%v", err) + return err + } + } + + file, err := os.Create(path) + if err != nil { + log.Error("err:%v", err) + return err + } + defer file.Close() + // 获得文件的writer对象 + writer := bufio.NewWriter(file) + + if _, err := io.Copy(writer, reader); err != nil { + log.Error("err:%v", err) + return err + } + return nil +} + +// GetSimpleRandomString 获取纯英文大写的随机n位字符串 +func GetSimpleRandomString(n int) string { + ret := []byte{} + for i := 0; i < n; i++ { + tmp := rand.Intn(26) + ret = append(ret, byte(tmp+65)) + } + return string(ret) +} + +var tokenMap = map[string]string{ + "1": "G", + "2": "J", + "3": "M", + "4": "S", + "5": "Q", + "6": "B", + "7": "E", + "8": "F", + "9": "I", + "0": "Z", +} + +// RandomToken 生成token +func RandomToken(uid int) string { + ran := GetSimpleRandomString(10) + str := strconv.Itoa(uid) + for _, v := range str { + ran += tokenMap[string(v)] + } + return ran +} + +// NewOrderID 新生成一个订单id +func NewOrderID(uid int) string { + orderID := "" + str := strconv.Itoa(uid) + for _, v := range str { + orderID += tokenMap[string(v)] + } + return orderID + strconv.FormatInt(time.Now().Unix(), 10) +} + +// SliceInt2Int32 数组转换 +func SliceInt2Int32(c []int) []uint32 { + ret := []uint32{} + for _, v := range c { + ret = append(ret, uint32(v)) + } + return ret +} + +// SliceInt322Int 数组转换 +func SliceInt322Int(c []uint32) []int { + ret := []int{} + for _, v := range c { + ret = append(ret, int(v)) + } + return ret +} + +// SliceInt2Int64 数组转换 +func SliceInt2Int64(c []int) []int64 { + ret := []int64{} + for _, v := range c { + ret = append(ret, int64(v)) + } + return ret +} + +// RemoveSlice 根据b中的元素,从a中剔除,相当于两个slice做差 +func RemoveSlice(a, b []int) []int { + ret := []int{} + dm := map[int]int{} + for _, v := range b { + dm[v]++ + } + for _, v := range a { + if num, ok := dm[v]; ok { + num-- + if num == 0 { + delete(dm, v) + } else { + dm[v] = num + } + continue + } else { + ret = append(ret, v) + } + } + return ret +} + +// RemoveOne 从a中剔除b +func RemoveOne(a []int, b int) []int { + ret := []int{} + count := 0 + for _, v := range a { + if v == b && count == 0 { + count++ + continue + } + ret = append(ret, v) + } + return ret +} + +// SliceContain a中是否包含b +func SliceContain(a []int, b int) bool { + for _, v := range a { + if v == b { + return true + } + } + return false +} + +// SliceContainInt64 a中是否包含b +func SliceContainInt64(a []int64, b int64) bool { + for _, v := range a { + if v == b { + return true + } + } + return false +} + +func TrimHiddenCharacter(originStr string) string { + srcRunes := []rune(originStr) + dstRunes := make([]rune, 0, len(srcRunes)) + for _, c := range srcRunes { + if c >= 0 && c <= 31 { + continue + } + if c == 127 { + continue + } + dstRunes = append(dstRunes, c) + } + return string(dstRunes) +} + +func IsEqualSlice(a, b []int) bool { + dm := map[int]int{} + for _, v := range a { + dm[v]++ + } + for _, v := range b { + dm[v]-- + } + for _, v := range dm { + if v != 0 { + return false + } + } + return true +} + +// GetRandSection 获取区间随机值 +func GetRandSection(a, b int) int { + if a >= b { + return a + } + return a + rand.Intn(b-a) +} + +// CopySameStruct 将b中的非空字段复制到相同的结构体a中 +func CopySameStruct(a, b interface{}) bool { + typa := reflect.TypeOf(a) + typb := reflect.TypeOf(b) + if typa != typb { + return false + } + if typa.Kind() != reflect.Ptr { + return false + } + vala := reflect.ValueOf(a).Elem() + valb := reflect.ValueOf(b).Elem() + // typaE := typa.Elem() + // typbE := typb.Elem() + for i := 0; i < vala.NumField(); i++ { + if valb.Field(i).IsZero() { + continue + } + vala.Field(i).Set(valb.Field(i)) + } + return true +} + +// 字符串去重 +func RemoveDuplication(arr []string) []string { + set := make(map[string]struct{}, len(arr)) + j := 0 + for _, v := range arr { + _, ok := set[v] + if ok { + continue + } + set[v] = struct{}{} + arr[j] = v + j++ + } + + return arr[:j] +} + +// Abs 取绝对值 +func Abs(a int64) int64 { + if a < 0 { + a = -a + } + return a +} + +func CleanList(l *list.List) { + e := l.Front() + if e == nil { + return + } + for { + next := e.Next() + l.Remove(e) + if next == nil { + break + } + e = next + } +} + +func CheckPhone(phone string) string { + ok, _ := regexp.MatchString("[1-9]{2}[2-9][0-9]{7,8}", phone) + if !ok { + phone = "" + for i := 0; i < 2; i++ { + phone += fmt.Sprintf("%d", rand.Intn(9)+1) + } + phone += fmt.Sprintf("%d", rand.Intn(8)+2) + tmp := rand.Intn(2) + count := 7 + if tmp == 0 { + count = 8 + } + for i := 0; i < count; i++ { + phone += fmt.Sprintf("%d", rand.Intn(10)) + } + } + + return phone +} + +func CheckCPF(cpf string) string { + if IsCPF(cpf) { + return cpf + } + num := [11]int{} + for i := 0; i < 9; i++ { + num[i] = rand.Intn(9) + 1 + } + s1 := 0 + for i := 0; i < 9; i++ { + s1 += (10 - i) * num[i] + } + m1 := s1 % 11 + if m1 < 2 { + num[9] = 0 + } else { + num[9] = 11 - m1 + } + s2 := 0 + for i := 0; i < 10; i++ { + s2 += num[i] * (11 - i) + } + m2 := s2 % 11 + if m2 < 2 { + num[10] = 0 + } else { + num[10] = 11 - m2 + } + s := "" + for _, v := range num { + s += fmt.Sprintf("%d", v) + } + return s +} + +func IsCPF(cpf string) bool { + cpf = strings.ReplaceAll(cpf, ".", "") + cpf = strings.ReplaceAll(cpf, "-", "") + num := StringToInt(cpf) + if len(num) != 11 { + return false + } + s1 := 0 + for i := 0; i < len(num)-2; i++ { + s1 += (10 - i) * num[i] + } + m1 := s1 % 11 + y := num[9] + if m1 < 2 && y != 0 { + return false + } + if m1 >= 2 && y != (11-m1) { + return false + } + z := num[10] + s2 := 0 + for i := 0; i < len(num)-1; i++ { + s2 += num[i] * (11 - i) + } + m2 := s2 % 11 + if m2 < 2 && z != 0 { + return false + } + if m2 >= 2 && z != 11-m2 { + return false + } + return true +} + +func IndexTryCallback(f func() error, cb func()) { + Go(func() { + for i := 0; i <= 6; i++ { + if i > 0 { + next := time.Duration(i*i) * time.Minute + time.Sleep(next) + } + err := f() + if err == nil { + break + } + log.Error("err:%v next:%v", err, i) + if i == 6 { + cb() + } + } + }) +} + +func IndexTry(f func() error) { + Go(func() { + for i := 0; i <= 6; i++ { + if i > 0 { + next := time.Duration(i*i) * time.Minute + time.Sleep(next) + } + err := f() + if err == nil { + break + } + log.Error("err:%v next:%v", err, i) + } + }) +} + +func IndexTryS(f func() error) { + Go(func() { + for i := 0; i <= 6; i++ { + if i > 0 { + next := time.Duration(i*i) * time.Second + time.Sleep(next) + } + err := f() + if err == nil { + break + } + log.Error("err:%v next:%v", err, i) + } + }) +} + +// 获取下一个整点5分钟时间戳 +func GetNext5MinUnix() int64 { + now := time.Now() + zero := GetZeroTime(now) + h, m, _ := now.Clock() + m -= m % 5 + last := zero.Add(time.Duration(h) * time.Hour).Add(time.Duration(m) * time.Minute) + return last.Add(5 * time.Minute).Unix() +} + +// stringsToInt 数字字符串转数字数组 +func StringToInt(s string) []int { + b := []byte(s) + ret := []int{} + for _, v := range b { + if v < 48 || v > 57 { + return nil + } + ret = append(ret, int(v)-48) + } + return ret +} + +func ZhToUnicode(raw string) (string, error) { + str, err := strconv.Unquote(strings.Replace(strconv.Quote(raw), `\\u`, `\u`, -1)) + if err != nil { + return "", err + } + return str, nil +} + +// IsSingle 判断数字是不是单数 +func IsSingle(n int) bool { + return n/2*2 != n +} + +// GetIntFromString 从字符串s中提取出所有的int +func GetIntFromString(s string) int { + ret := []rune{} + for _, v := range s { + if v < 48 || v > 57 { + continue + } + ret = append(ret, v) + } + tmp := string(ret) + number, err := strconv.Atoi(tmp) + if err != nil { + log.Error("err:%v", err) + return 0 + } + return number +} + +func FormatUserName(name string) (fn string, ln string) { + if len(name) == 0 { + return + } + name1 := strings.Split(strings.TrimSpace(name), " ") + name2 := []string{} + for _, v := range name1 { + if len(v) == 0 { + continue + } + name2 = append(name2, v) + } + if len(name2) > 1 { + for i, v := range name2 { + if i == len(name2)-1 { + break + } + fn += v + } + ln = name2[len(name2)-1] + } else if len(name2) == 1 { + fn = name2[0] + } + return +} + +// FormatNumberBrazil 格式化数字为巴西格式(例如:1234.56 -> "1.234,56"),小数点后全为零时去除小数部分 +func FormatNumberBrazil(number float64) string { + // 首先,使用fmt.Sprintf格式化数字,这里使用"%.2f"保证即使是整数也会带有".00" + usFormatted := fmt.Sprintf("%.2f", number) + + // 如果小数部分为.00,则去除它 + // if strings.HasSuffix(usFormatted, ".00") { + // usFormatted = usFormatted[:len(usFormatted)-3] + // } + usFormatted = strings.TrimSuffix(usFormatted, ".00") + + // 将点替换为逗号,作为小数点 + step1 := strings.ReplaceAll(usFormatted, ".", ",") + + // 分割字符串为整数部分和可能的小数部分 + parts := strings.Split(step1, ",") + + // 替换整数部分中的千位分隔符 + integerPart := parts[0] + var sb strings.Builder + for i, r := range integerPart { + if i > 0 && (len(integerPart)-i)%3 == 0 { + sb.WriteRune('.') + } + sb.WriteRune(r) + } + + // 如果有小数部分,且不是"00",将其添加回去 + if len(parts) > 1 && parts[1] != "00" { + sb.WriteRune(',') + sb.WriteString(parts[1]) + } + + return sb.String() +} + +func CountDecimals(value float64) int { + // 将浮点数转换为字符串 + text := fmt.Sprintf("%v", value) + // 查找小数点位置 + pointIndex := strings.Index(text, ".") + if pointIndex == -1 { + // 没有找到小数点,说明没有小数部分 + return 0 + } + // 去除小数点后面尾随的0 + trimmed := strings.TrimRight(text[pointIndex+1:], "0") + // 返回小数部分的长度 + return len(trimmed) +} + +// 根据权重,从数组中随机一个元素 +// name 权重字段名(该字段值必须为整型) +func RandomOneEleFromSlice(slice interface{}, name string) int { + ref := reflect.ValueOf(slice) + var total int64 + for i := 0; i < ref.Len(); i++ { + tmp := ref.Index(i) + if tmp.Kind() == reflect.Pointer { + tmp = tmp.Elem() + } + total += tmp.FieldByName(name).Int() + } + ran := rand.Int63n(total) + var rans int64 + for i := 0; i < ref.Len(); i++ { + tmp := ref.Index(i) + if tmp.Kind() == reflect.Pointer { + tmp = tmp.Elem() + } + rans += tmp.FieldByName(name).Int() + if ran < rans { + return i + } + } + return 0 +} diff --git a/util/xrsa.go b/util/xrsa.go new file mode 100644 index 0000000..a1d15ea --- /dev/null +++ b/util/xrsa.go @@ -0,0 +1,156 @@ +package util + +import ( + "bytes" + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/asn1" + "encoding/base64" + "encoding/pem" + "errors" + "io" +) + +const ( + CHAR_SET = "UTF-8" + BASE_64_FORMAT = "UrlSafeNoPadding" + RSA_ALGORITHM_KEY_TYPE = "PKCS8" + RSA_ALGORITHM_SIGN = crypto.SHA256 +) + +type XRsa struct { + publicKey *rsa.PublicKey + privateKey *rsa.PrivateKey +} + +// 生成密钥对 +func CreateKeys(publicKeyWriter, privateKeyWriter io.Writer, keyLength int) error { + // 生成私钥文件 + privateKey, err := rsa.GenerateKey(rand.Reader, keyLength) + if err != nil { + return err + } + derStream := MarshalPKCS8PrivateKey(privateKey) + block := &pem.Block{ + Type: "PRIVATE KEY", + Bytes: derStream, + } + err = pem.Encode(privateKeyWriter, block) + if err != nil { + return err + } + + // 生成公钥文件 + publicKey := &privateKey.PublicKey + derPkix, err := x509.MarshalPKIXPublicKey(publicKey) + if err != nil { + return err + } + block = &pem.Block{ + Type: "PUBLIC KEY", + Bytes: derPkix, + } + err = pem.Encode(publicKeyWriter, block) + if err != nil { + return err + } + + return nil +} + +// 验证公私钥是否可用和构造XRsa结构体返回 +func NewXRsa(publicKey []byte, privateKey []byte) (*XRsa, error) { + block, _ := pem.Decode(publicKey) + if block == nil { + return nil, errors.New("public key error") + } + pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return nil, err + } + pub := pubInterface.(*rsa.PublicKey) + + block, _ = pem.Decode(privateKey) + if block == nil { + return nil, errors.New("private key error!") + } + priv, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + + pri, ok := priv.(*rsa.PrivateKey) + if ok { + return &XRsa{ + publicKey: pub, + privateKey: pri, + }, nil + } else { + return nil, errors.New("private key not supported") + } +} + +// 公钥加密 +func (r *XRsa) PublicEncrypt(data string) (string, error) { + partLen := r.publicKey.N.BitLen()/8 - 11 + chunks := split([]byte(data), partLen) + + buffer := bytes.NewBufferString("") + for _, chunk := range chunks { + bytes, err := rsa.EncryptPKCS1v15(rand.Reader, r.publicKey, chunk) + if err != nil { + return "", err + } + buffer.Write(bytes) + } + + return base64.StdEncoding.EncodeToString(buffer.Bytes()), nil +} + +// 私钥解密 +func (r *XRsa) PrivateDecrypt(encrypted string) (string, error) { + partLen := r.publicKey.N.BitLen() / 8 + raw, err := base64.StdEncoding.DecodeString(encrypted) + chunks := split([]byte(raw), partLen) + + buffer := bytes.NewBufferString("") + for _, chunk := range chunks { + decrypted, err := rsa.DecryptPKCS1v15(rand.Reader, r.privateKey, chunk) + if err != nil { + return "", err + } + buffer.Write(decrypted) + } + + return buffer.String(), err +} + +func MarshalPKCS8PrivateKey(key *rsa.PrivateKey) []byte { + info := struct { + Version int + PrivateKeyAlgorithm []asn1.ObjectIdentifier + PrivateKey []byte + }{} + info.Version = 0 + info.PrivateKeyAlgorithm = make([]asn1.ObjectIdentifier, 1) + info.PrivateKeyAlgorithm[0] = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1} + info.PrivateKey = x509.MarshalPKCS1PrivateKey(key) + + k, _ := asn1.Marshal(info) + return k +} + +func split(buf []byte, lim int) [][]byte { + var chunk []byte + chunks := make([][]byte, 0, len(buf)/lim+1) + for len(buf) >= lim { + chunk, buf = buf[:lim], buf[lim:] + chunks = append(chunks, chunk) + } + if len(buf) > 0 { + chunks = append(chunks, buf[:len(buf)]) + } + return chunks +}