You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
544 lines
17 KiB
544 lines
17 KiB
package call |
|
|
|
import ( |
|
"context" |
|
"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(30 * time.Minute) |
|
case "2": |
|
awardAt = util.GetWeekZeroTime(now).AddDate(0, 0, 7).Add(45 * time.Minute) |
|
case "3": |
|
awardAt = util.GetFirstDateOfMonth(now).AddDate(0, 1, 0).Add(60 * time.Minute) |
|
} |
|
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 |
|
} |
|
if rankData.IsRobot == 1 { |
|
continue |
|
} |
|
if userAwardCount > 0 { |
|
_, err = UpdateCurrencyPro(&common.UpdateCurrency{ |
|
CurrencyBalance: &common.CurrencyBalance{ |
|
UID: rankData.UID, |
|
Type: common.CurrencyINR, |
|
Value: userAwardCount, |
|
Event: common.CurrencyEventRankAward, |
|
NeedBet: GetConfigCurrencyResourceNeedBet(common.CurrencyResourceBonus, userAwardCount), |
|
}, |
|
}) |
|
if err != nil { |
|
log.Error("rank award err, %s", err.Error()) |
|
} |
|
} |
|
|
|
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), |
|
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) |
|
if lessJackpot < 0 { |
|
lessJackpot = 0 |
|
} |
|
err = db.Redis().GetRedis().Set(context.Background(), preJackpotLessKey, lessJackpot, 0).Err() |
|
if err != nil { |
|
log.Error("share rankAward, %s set less-jackpot err, %s", preJackpotLessKey, err.Error()) |
|
} |
|
var expire int64 |
|
switch ticker.rankCycle { |
|
case "1": |
|
expire = 24 * 60 * 60 * 2 |
|
case "2": |
|
expire = 24 * 60 * 60 * 7 * 2 |
|
case "3": |
|
expire = 24 * 60 * 60 * 30 * 2 |
|
} |
|
db.Redis().Expire(preJackpotLessKey, time.Duration(expire)*time.Second) |
|
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) { |
|
if isRobot == 1 { |
|
UpdateShare(uid, 1, updateValue, "isRobot") |
|
} |
|
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, |
|
TotalBet: updateValue, |
|
RankValue: updateValueReal, |
|
UpdatedAt: now.Unix(), |
|
} |
|
|
|
updates := map[string]interface{}{ |
|
"total_bet": gorm.Expr(fmt.Sprintf("total_bet + %d", rankData.TotalBet)), |
|
"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 |
|
} |
|
cfg := GetConfigRankByCycle(rankType, rankCycle) |
|
for _, rankUser := range rankUsers { |
|
if rankUser.TotalBet == 0 { |
|
rankUser.RankValue = rankUser.RankValue * 10000 / int64(cfg.FreeRatesMap[rankCycle]) |
|
} else { |
|
rankUser.RankValue = rankUser.TotalBet |
|
} |
|
} |
|
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 |
|
}
|
|
|