package call
import (
"context"
"encoding/json"
"fmt"
"github.com/liangdas/mqant/log"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"math/rand"
"server/common"
"server/db"
"server/util"
"sort"
"time"
)
var rankTimerMap map [ string ] * rankTicker
type rankTicker struct {
timer * time . Ticker
ctx context . Context
ctxCancel context . CancelFunc
rankType int
rankCycle string
activityId int
}
func getRankTimerKey ( rankType int , rankCycle string ) string {
return fmt . Sprintf ( "rank|%d|%s" , rankType , rankCycle )
}
func getRankJackpotKey ( rankType int , rankCycle string ) ( rankAt , awardAt time . Time , expired int64 , key string ) {
now := time . Now ( )
key = fmt . Sprintf ( "rank_jackpot|%d|%s" , rankType , rankCycle )
switch rankCycle {
case "1" :
rankAt = util . GetZeroTime ( now )
awardAt = rankAt . AddDate ( 0 , 0 , 1 )
expired = int64 ( rankAt . AddDate ( 0 , 0 , 2 ) . Sub ( now ) . Seconds ( ) )
case "2" :
rankAt = util . GetWeekZeroTime ( now )
awardAt = rankAt . AddDate ( 0 , 0 , 7 )
expired = int64 ( rankAt . AddDate ( 0 , 0 , 14 ) . Sub ( now ) . Seconds ( ) )
case "3" :
rankAt = util . GetFirstDateOfMonth ( now )
awardAt = rankAt . AddDate ( 0 , 1 , 0 )
expired = int64 ( rankAt . AddDate ( 0 , 2 , 0 ) . Sub ( now ) . Seconds ( ) )
}
key += fmt . Sprintf ( "|%s" , rankAt . Format ( "20060102" ) )
return
}
func getRankJackpotPreKey ( rankType int , rankCycle string ) ( lastRankAt time . Time , key string ) {
now := time . Now ( )
key = fmt . Sprintf ( "rank_jackpot|%d|%s" , rankType , rankCycle )
switch rankCycle {
case "1" :
lastRankAt = util . GetZeroTime ( now ) . AddDate ( 0 , 0 , - 1 )
case "2" :
lastRankAt = util . GetWeekZeroTime ( now ) . AddDate ( 0 , 0 , - 7 )
case "3" :
lastRankAt = util . GetFirstDateOfMonth ( now ) . AddDate ( 0 , - 1 , 0 )
}
key += fmt . Sprintf ( "|%s" , lastRankAt . Format ( "20060102" ) )
return
}
// RankHandler 加载排行榜发奖程序
func RankHandler ( ) {
configRanks := GetConfigRank ( 0 )
if len ( configRanks ) == 0 {
return
}
now := time . Now ( )
if rankTimerMap == nil {
rankTimerMap = make ( map [ string ] * rankTicker )
}
for _ , rank := range configRanks {
var (
rankType = rank . RankType
awardAt time . Time
)
for rankCycle := range rank . RankCycleMap {
switch rankCycle {
case "1" :
awardAt = util . GetZeroTime ( now . AddDate ( 0 , 0 , 1 ) ) . Add ( time . Hour )
case "2" :
awardAt = util . GetWeekZeroTime ( now ) . AddDate ( 0 , 0 , 7 ) . Add ( 2 * time . Hour )
case "3" :
awardAt = util . GetFirstDateOfMonth ( now ) . AddDate ( 0 , 1 , 0 ) . Add ( 2 * time . Hour )
}
if awardAt . IsZero ( ) {
log . Error ( "get award time err, %+v, %s" , * rank , rankCycle )
continue
}
timerKey := getRankTimerKey ( rankType , rankCycle )
rankTimer , ok := rankTimerMap [ timerKey ]
if ok {
// notice: 覆盖之前的
rankTimer . ctxCancel ( )
}
ctx , cancel := context . WithCancel ( context . Background ( ) )
rankTimerMap [ timerKey ] = & rankTicker {
timer : time . NewTicker ( awardAt . Sub ( time . Now ( ) ) ) ,
ctx : ctx ,
ctxCancel : cancel ,
rankType : rankType ,
rankCycle : rankCycle ,
activityId : rank . ID ,
}
go func ( ticker * rankTicker ) {
defer util . Recover ( )
for {
select {
case <- ticker . timer . C :
rankAward ( ticker )
case <- ticker . ctx . Done ( ) :
ticker . timer . Stop ( )
return
}
}
} ( rankTimerMap [ timerKey ] )
}
}
}
// rankAward 发奖逻辑
func rankAward ( ticker * rankTicker ) {
now := time . Now ( )
rankConfig := GetConfigRankById ( ticker . activityId )
if rankConfig == nil {
log . Error ( "rankAward, get rankConfig wrong, %d" , ticker . activityId )
return
}
_ , ok := rankConfig . RankCycleMap [ ticker . rankCycle ]
if ! ok {
log . Error ( "rankAward, get rankConfig by cycle wrong, %s, %+v" , ticker . rankCycle , * rankConfig )
return
}
// 重刷排行榜
rankUserRefresh ( ticker . rankType , true )
// 获取上一期排行榜时间
rankAt , jackpotKey := getRankJackpotPreKey ( rankConfig . RankType , ticker . rankCycle )
// 获取上一期排行榜奖池
jackpot := RankJackpotPreGet ( rankConfig . RankType , ticker . rankCycle )
// 获取上一期排行榜用户排名
rankUsers , err := rankAwardUsersGet ( rankConfig . RankType , ticker . rankCycle , rankAt )
if err != nil {
log . Error ( "rankAward, %s get rank award users err, %s, %s, rank:%+v" , jackpotKey , err . Error ( ) , ticker . rankCycle , * rankConfig )
return
}
rankAwards := make ( map [ int ] * common . RankAward )
lessJackpot := jackpot
log . Debug ( "rankAward, %s jackpot:%d" , jackpotKey , jackpot )
// 用户发奖
for _ , v := range rankConfig . RankAwardsMap [ ticker . rankCycle ] {
for index := v . SmallRank ; index <= v . LargeRank ; index ++ {
rankAwards [ index ] = & v
}
}
for index , rankData := range rankUsers {
if rankData . Rank != 0 {
// todo 兼容发奖错误,重新发奖
continue
}
var (
rank = index + 1
userAwardCount int64
userAward int
)
award , exist := rankAwards [ rank ]
if exist {
userAward = award . AwardRate
userAwardCount = int64 ( userAward ) * jackpot / 10000
lessJackpot -= userAwardCount
}
if userAwardCount < 0 {
userAwardCount = 0
}
err = db . Mysql ( ) . C ( ) . Model ( & common . RankData { } ) .
Where ( "id = ?" , rankData . ID ) .
Updates ( map [ string ] interface { } {
"user_award" : userAward ,
"user_award_count" : userAwardCount ,
"rank" : rank ,
"updated_at" : time . Now ( ) . Unix ( ) ,
} ) . Error
if err != nil {
log . Error ( "rankAward, %s update rankData err, %s" , jackpotKey , err . Error ( ) )
continue
}
if userAwardCount == 0 {
continue
}
awardData , _ := json . Marshal ( common . CurrencyPair {
Type : common . CurrencyINR ,
Value : userAwardCount ,
} )
var rankCycle string
switch ticker . rankCycle {
case "1" :
rankCycle = "Day"
case "2" :
rankCycle = "Week"
case "3" :
rankCycle = "Month"
}
// todo 给玩家邮件发奖
mail := common . Mail {
DraftID : "" ,
Sender : "system" ,
Receiver : rankData . UID ,
Type : 1 ,
Title : fmt . Sprintf ( "%s Rank Rankings settlement(%s)" , rankCycle , rankAt . Format ( "20060102" ) ) ,
Content : fmt . Sprintf ( "Nice work! You ranked %dth on the betting leaderboard and earned a reward. Go claim it now!" , rank ) ,
Enc : string ( awardData ) ,
SendMethod : 1 ,
Status : common . MailStatusNew ,
Time : now . Unix ( ) ,
}
err = db . Mysql ( ) . C ( ) . Model ( & common . Mail { } ) . Create ( & mail ) . Error
if err != nil {
log . Error ( "create mail err, %s, %+v" , err . Error ( ) , mail )
}
}
// 重置上一期奖池数量
preJackpotLessKey := jackpotKey + "|less"
log . Debug ( "rankAward, %s, %d, less jackpot:%d" , jackpotKey , jackpot , lessJackpot )
err = db . Redis ( ) . GetRedis ( ) . Set ( context . Background ( ) , preJackpotLessKey , lessJackpot , time . Hour * 24 * 30 * 2 ) . Err ( )
if err != nil {
log . Error ( "share rankAward, %s set less-jackpot err, %s" , preJackpotLessKey , err . Error ( ) )
}
if lessJackpot > 0 {
_ , _ , _ , jackpotKeyNow := getRankJackpotKey ( rankConfig . RankType , ticker . rankCycle )
_ , err = db . Redis ( ) . Incr ( jackpotKeyNow , lessJackpot )
if err != nil {
log . Error ( "rankAward, %s:%s incr less-jackpot err, %s" , jackpotKey , jackpotKeyNow , err . Error ( ) )
}
}
}
// UpdateRankValue 更新玩家数值(mode:1打码,2充值)
// todo 接入打码
func UpdateRankValue ( mode int , uid int , updateValue int64 , isRobot int ) {
configRanks := GetConfigRank ( mode )
if len ( configRanks ) == 0 {
return
}
now := time . Now ( )
for _ , rank := range configRanks {
for rankCycle := range rank . RankCycleMap {
freeRate , ok := rank . FreeRatesMap [ rankCycle ]
if ! ok {
continue
}
updateValueReal := updateValue * int64 ( freeRate ) / 10000
rankAt , _ , expired , rankKey := getRankJackpotKey ( rank . RankType , rankCycle )
rankData := common . RankData {
UID : uid ,
RankType : rank . RankType ,
RankCycle : util . ToInt ( rankCycle ) ,
RankAt : rankAt . Unix ( ) ,
IsRobot : isRobot ,
RankValue : updateValueReal ,
UpdatedAt : now . Unix ( ) ,
}
updates := map [ string ] interface { } {
"rank_value" : gorm . Expr ( fmt . Sprintf ( "rank_value + %d" , rankData . RankValue ) ) ,
"updated_at" : rankData . UpdatedAt ,
}
_ , err := db . Redis ( ) . Incr ( rankKey , updateValueReal )
if err != nil {
log . Error ( "update rank jackpot err, %s:%s" , rankKey , err . Error ( ) )
continue
}
db . Redis ( ) . Expire ( rankKey , time . Duration ( expired ) * time . Second )
err = db . Mysql ( ) . C ( ) . Model ( & common . RankData { } ) . Clauses ( clause . OnConflict {
Columns : [ ] clause . Column { { Name : "rank_type" } , { Name : "rank_cycle" } , { Name : "rank_at" } , { Name : "uid" } } ,
DoUpdates : clause . Assignments ( updates ) ,
} ) . Create ( & rankData ) . Error
if err != nil {
log . Error ( "update rank value err, %s, %+v" , err . Error ( ) , rankData )
_ , err = db . Redis ( ) . Incr ( rankKey , updateValueReal * - 1 )
continue
}
}
}
}
// RankJackpotGet 获取当期奖池信息
func RankJackpotGet ( rankType int , rankCycle string ) ( jackpot , rankLess int64 ) {
_ , awardAt , _ , rankKey := getRankJackpotKey ( rankType , rankCycle )
jackpot = db . Redis ( ) . GetInt64 ( rankKey )
rankLess = int64 ( awardAt . Sub ( time . Now ( ) ) . Seconds ( ) )
return
}
// RankJackpotPreGet 获取上一期奖池
func RankJackpotPreGet ( rankType int , rankCycle string ) ( jackpot int64 ) {
_ , rankKey := getRankJackpotPreKey ( rankType , rankCycle )
return db . Redis ( ) . GetInt64 ( rankKey )
}
func RankJackpotPreLessGet ( rankType int , rankCycle string ) ( jackpot int64 ) {
_ , rankKey := getRankJackpotPreKey ( rankType , rankCycle )
return db . Redis ( ) . GetInt64 ( rankKey + "less" )
}
// rankUserRefresh 刷新用户数据
func rankUserRefresh ( rankType int , must bool ) {
// todo must为false可以使用缓存
var err error
rankList := GetConfigRank ( rankType )
for _ , rankCycle := range [ ] string { "3" , "2" , "1" } {
for _ , rankCfg := range rankList {
if _ , ok := rankCfg . RankCycleMap [ rankCycle ] ; ! ok {
continue
}
if rankCfg . RobotCfgValue == nil {
continue
}
if rankCfg . RobotCfgValue . ChangeType == 2 {
continue
}
robotCfg := rankCfg . RobotCfgValue
rankAt , _ , _ , _ := getRankJackpotKey ( rankType , rankCycle )
var rankRobots [ ] common . RankData
err = db . Mysql ( ) . C ( ) . Model ( & common . RankData { } ) . Where ( "rank_type = ? and rank_cycle = ? and rank_at = ? and is_robot = 1" , rankType , util . ToInt ( rankCycle ) , rankAt . Unix ( ) ) .
Order ( "rank_value desc" ) . Find ( & rankRobots ) . Error
if err != nil {
log . Error ( "get robot data err, %s" , err . Error ( ) )
continue
}
if needRobotCount := len ( robotCfg . RobotRankMap ) - len ( rankRobots ) ; needRobotCount > 0 {
var robots [ ] common . PlayerDBInfo
err = db . Mysql ( ) . C ( ) . Model ( & common . PlayerDBInfo { } ) . Where ( "openid like 'robot%'" ) . Order ( "RAND()" ) . Limit ( needRobotCount ) . Find ( & robots ) . Error
if err != nil {
log . Error ( "get robot info err, %s" , err . Error ( ) )
continue
}
if len ( robots ) == 0 {
log . Error ( "get robot wrong" )
continue
}
for _ , robot := range robots {
rankRobots = append ( rankRobots , common . RankData {
UID : robot . Id ,
RankType : rankType ,
RankCycle : util . ToInt ( rankCycle ) ,
RankAt : rankAt . Unix ( ) ,
RankValue : 0 ,
IsRobot : 1 ,
UpdatedAt : time . Now ( ) . Unix ( ) ,
} )
}
}
var rankUsers [ ] * common . RankData
maxRank := robotCfg . RobotRankArray [ len ( robotCfg . RobotRankArray ) - 1 ]
err = db . Mysql ( ) . C ( ) . Model ( & common . RankData { } ) . Where ( "rank_type = ? and rank_cycle = ? and rank_at = ?" ,
rankType , util . ToInt ( rankCycle ) , rankAt . Unix ( ) ) . Order ( "rank_value desc" ) .
Limit ( maxRank ) . Find ( & rankUsers ) . Error
if err != nil {
log . Error ( "get rank user err, %s" , err . Error ( ) )
continue
}
freeRate := rankCfg . FreeRatesMap [ rankCycle ]
if freeRate == 0 {
log . Error ( "freeRate is nil, %d:%s" , rankType , rankCycle )
continue
}
rankRobotsMap := make ( map [ int ] struct { } )
rankUserMap := make ( map [ int ] * common . RankData )
for _ , v := range rankRobots {
rankRobotsMap [ v . UID ] = struct { } { }
}
for _ , v := range rankUsers {
rankUserMap [ v . UID ] = v
}
if len ( rankUsers ) == 0 { // 没有玩家上榜,随机一个值上去
for _ , rankRobot := range rankRobots {
addValue := rand . Intn ( 100 ) + 1
updateValue := addValue * 10000 / freeRate
if rankRobot . RankValue == 0 {
UpdateRankValue ( rankType , rankRobot . UID , int64 ( updateValue ) , 1 )
}
}
} else {
for _ , rank := range robotCfg . RobotRankArray {
rankUser := rankUsers [ rank - 1 ]
if rankUser . IsRobot == 1 {
delete ( rankRobotsMap , rankUser . UID )
continue
}
rankValue := rankUser . RankValue // 当前排位的值
for _ , robot := range rankRobots { // 从大到小找一个没重置过值的机器人
if _ , ok := rankRobotsMap [ robot . UID ] ; ! ok {
continue
}
addFloat := rand . Intn ( robotCfg . FloatMax - robotCfg . FloatMin ) + robotCfg . FloatMin
rankValueFinal := rankValue + rankValue * int64 ( addFloat ) / 100
addValue := rankValueFinal - robot . RankValue
updateValue := addValue * 10000 / int64 ( freeRate )
if updateValue > 0 {
UpdateRankValue ( rankType , robot . UID , updateValue , 1 )
delete ( rankRobotsMap , rankUser . UID )
}
_ , ok := rankUserMap [ robot . UID ]
if ! ok { // 不存在新增
robot . RankValue = rankValueFinal
rankUserMap [ robot . UID ] = & robot
rankUsers = append ( rankUsers , & robot )
} else { // 存在则更新数值
rankUserMap [ robot . UID ] . RankValue = rankValueFinal
}
sort . Slice ( rankUsers , func ( i , j int ) bool {
return rankUsers [ i ] . RankValue > rankUsers [ j ] . RankValue
} )
}
}
}
}
}
}
// rankUsersGet 获取排行用户
func rankUsersGet ( rankType int , rankCycle string , rankAt time . Time , page , pageSize int ) ( rankUsers [ ] * common . RankDataWithUser , count int64 , err error ) {
log . Debug ( "rankCycle:%s, rankAt:%d" , rankCycle , rankAt . Unix ( ) )
// 获取总数
countQuery := db . Mysql ( ) . C ( ) .
Model ( & common . RankData { } ) .
Where ( "rank_type = ? and rank_cycle = ? and rank_at = ?" ,
rankType , util . ToInt ( rankCycle ) , rankAt . Unix ( ) )
err = countQuery . Count ( & count ) . Error
if err != nil {
log . Error ( "get rank user count err, %s" , err . Error ( ) )
return
}
query := db . Mysql ( ) . C ( ) .
Table ( "rank_data rd" ) .
Select ( "rd.*, u.*" ) .
Joins ( "LEFT JOIN users u ON rd.uid = u.id" ) .
Where ( "rd.rank_type = ? and rd.rank_cycle = ? and rd.rank_at = ?" ,
rankType , util . ToInt ( rankCycle ) , rankAt . Unix ( ) ) .
Order ( "rd.rank_value desc, rd.updated_at" ) .
Offset ( ( page - 1 ) * pageSize ) .
Limit ( pageSize )
err = query . Find ( & rankUsers ) . Error
if err != nil {
log . Error ( "get rank user with info err, %s" , err . Error ( ) )
return
}
return
}
// RankUsersGet 获取当前排行榜用户
func RankUsersGet ( rankType int , rankCycle string , page , pageSize int ) ( rankUsers [ ] * common . RankDataWithUser , count int64 , err error ) {
rankAt , _ , _ , _ := getRankJackpotKey ( rankType , rankCycle )
if time . Now ( ) . Sub ( rankAt ) . Minutes ( ) > 5 {
rankUserRefresh ( rankType , false )
}
return rankUsersGet ( rankType , rankCycle , rankAt , page , pageSize )
}
// RankUsersPreGet 获取上一期排行榜用户
func RankUsersPreGet ( rankType int , rankCycle string , page , pageSize int ) ( rankUsers [ ] * common . RankDataWithUser , count int64 , err error ) {
rankAt , _ := getRankJackpotPreKey ( rankType , rankCycle )
return rankUsersGet ( rankType , rankCycle , rankAt , page , pageSize )
}
// rankAwardUsersGet
func rankAwardUsersGet ( rankType int , rankCycle string , rankAt time . Time ) ( rankData [ ] * common . RankData , err error ) {
log . Debug ( "rankCycle:%s, rankAt:%d" , rankCycle , rankAt . Unix ( ) )
mdb := db . Mysql ( ) . C ( ) .
Model ( & common . RankData { } ) .
Where ( "rank_type = ? and rank_cycle = ? and rank_at = ?" ,
rankType , util . ToInt ( rankCycle ) , rankAt . Unix ( ) )
err = mdb . Order ( "rank_value desc,updated_at" ) . Find ( & rankData ) . Error
if err != nil {
log . Error ( "get rank user with info err, %s" , err . Error ( ) )
return
}
return
}
// UserRankAwardRecord 玩家排行榜记录
func UserRankAwardRecord ( uid , page , pageSize int ) ( rankUsers [ ] common . RankData , count int64 , err error ) {
mdb := db . Mysql ( ) . C ( ) . Model ( & common . RankData { } ) . Where ( "uid = ? and `rank` != 0" , uid )
err = mdb . Count ( & count ) . Error
if err != nil {
log . Error ( "get rank award count err, %s" , err . Error ( ) )
return
}
err = mdb . Order ( "rank_at desc" ) . Offset ( ( page - 1 ) * pageSize ) . Limit ( pageSize ) . Find ( & rankUsers ) . Error
if err != nil {
log . Error ( "get rank award err, %s" , err . Error ( ) )
return
}
return
}