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 }