package call import ( "context" "fmt" "github.com/liangdas/mqant/log" "gorm.io/gorm" "gorm.io/gorm/clause" "server/common" "server/db" "server/util" "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) { 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() // 获取上一期排行榜时间 rankAt, jackpotKey := getRankJackpotPreKey(rankConfig.RankType, rankConfig.RankCycle) // 获取上一期排行榜奖池 jackpot := RankJackpotPreGet(rankConfig.RankType, rankConfig.RankCycle) // 获取上一期排行榜用户排名 rankUsers, err := rankAwardUsersGet(rankConfig.RankType, rankConfig.RankCycle, rankAt) if err != nil { log.Error("rankAward, %s get rank award users err, %s, %s, rank:%+v", jackpotKey, err.Error(), rankConfig.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 } // todo 给玩家邮件发奖 // 重置上一期奖池数量 if lessJackpot > 0 { log.Debug("rankAward, %s less jackpot:%d", jackpotKey, lessJackpot) err = db.Redis().GetRedis().Set(context.Background(), jackpotKey, lessJackpot, 0).Err() if err != nil { log.Error("rankAward, %s set less-jackpot err, %s", jackpotKey, err.Error()) } _, _, _, jackpotKeyNow := getRankJackpotKey(rankConfig.RankType, rankConfig.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充值) func UpdateRankValue(mode int, uid int, updateValue int64) { 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(rank.RankCycle), RankAt: rankAt.Unix(), 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) } // rankUserRefresh 刷新用户数据 func rankUserRefresh() { // todo 根据机器人排行规则排行 } // 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 rankAt.Sub(time.Now()).Minutes() > 5 { rankUserRefresh() } 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 }