-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathkingday.py
More file actions
246 lines (181 loc) · 7.64 KB
/
kingday.py
File metadata and controls
246 lines (181 loc) · 7.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
#! -*- coding:utf-8 -*-
# Author: qianyong
# Created: 2016.06.30 19.43
from freetime.util import log as ftlog
from poker.entity.dao import daobase
from cars.entity import const
from cars.entity import carsconf
from cars.entity.util import UtilFunc
import time
class KingDay(object):
'''水果王每天'''
# 本地保存的前30名排行, 按winchips从大到小排序, 每天重置
# 每个元素的格式 {userId: 132, purl: "http://asdfsaf", name: "zhangsan", winChips: 1234}
_dayRanks = []
# dayRank对应的是哪天的日期
_dateForDayRank = ''
# 上一次检查redis排行榜的时间点
_lastCheckedRanksAt = 0
# 30分钟检查一次redis里的排行榜
CHECK_REDIS_RANKS_PERIOD = 30 * 60
@classmethod
def initialize(cls):
'''初始化, 一段时间后启动周期扫描timer'''
UtilFunc.callLater(cls.CHECK_REDIS_RANKS_PERIOD, cls._loopCheckRedisRanksPeriod)
@classmethod
def addWinChip(cls, userId, deltaChip, now):
'''玩家赢金币时, 更新排行榜, 返回玩家今天总计赢的金币数'''
today = cls._todayDate(now)
# 获取玩家的每日赢取金币, 并更新
dayData = UtilFunc.getDayData(userId, today)
dayData['winChipsForKing'] += deltaChip
UtilFunc.setDayData(userId, dayData, today)
userDayWinChips = dayData['winChipsForKing']
# 比最后一名的今日赢取金币数还低, 不用更新到榜上
dayRanks = cls._getLocalDayRanks(today)
if len(dayRanks) >= cls._maxRanksCount() and dayRanks[-1]['winChips'] >= userDayWinChips:
return
# 更新远程redis里的排行榜数据, 负分保存在redis里
daobase.executeRankCmd('ZADD', cls._dayKey(today), -userDayWinChips, userId)
# 更新本地的排行榜变量
cls._updateLocalDayRanks(userId, userDayWinChips, today)
return userDayWinChips
@classmethod
def getTodayGameRanks(cls, now):
'''获取当天的前20名排行'''
today = cls._todayDate(now)
ranks = cls._getLocalDayRanks(today)
maxCount = carsconf.getKingConf().get('today', {}).get('game_count', 1)
return ranks[:maxCount]
@classmethod
def getYesterdayRanks(cls, now, count=None):
'''获取头一天的前20名. 直接从redis读取'''
yesterday = cls._todayDate(now - const.ONE_DAY_SEC)
if count is None:
count = cls._maxRanksCount()
dayRanks = []
parts = daobase.executeRankCmd('ZRANGE', cls._dayKey(yesterday), 0, count - 1, 'WITHSCORES')
if not parts:
parts = []
for i in xrange(len(parts) / 2):
userId = int(parts[i * 2])
winChips = -int(parts[i * 2 + 1])
user = cls._buildUser(userId, winChips)
dayRanks.append(user)
return dayRanks
@classmethod
def getYesterdayTop(cls, now):
'''获取头一天的头名, 如果没有, 则返回None。 直接从redis读取'''
ranks = cls.getYesterdayRanks(now, 1)
if len(ranks) > 0:
return ranks[0]
else:
return None
@classmethod
def setExpireRemoteRank(cls, now):
'''远程redis里的当日rank数据最长保留往后推延24小时'''
today = cls._todayDate(now)
daobase.executeRankCmd('EXPIRE', cls._dayKey(today), 24 * 3600)
@classmethod
def getUserTodayWinChips(cls, userId, now):
'''从redis获取某个用户今天的winchips'''
today = cls._todayDate(now)
data = UtilFunc.getDayData(userId, today)
return data['winChipsForKing']
@classmethod
def _buildUser(cls, userId, winChips):
'''构建user数据'''
name, purl = UtilFunc.getUserNameAndPurl(userId)
return {
"userId": userId,
"winChips": winChips,
"name": name,
"purl": purl
}
@classmethod
def _dayKey(cls, date):
'''每天排行的key'''
return 'kingrank:%s:%s' % (const.GAME_ID, date)
@classmethod
def _todayDate(cls, now=None):
'''返回当天的日期'''
if now is None:
now = time.time()
return time.strftime("%Y%m%d", time.localtime(now))
@classmethod
def _getLocalDayRanks(cls, today):
'''
获取本地保存的当日的dayRank值
:param today: 今天的日期
:return: dayRank
'''
# 日期不同的话, 从redis刷新数据
if today != cls._dateForDayRank:
del cls._dayRanks[:]
count = cls._maxRanksCount()
parts = daobase.executeRankCmd('ZRANGE', cls._dayKey(today), 0, count - 1, 'WITHSCORES')
if not parts:
parts = []
for i in xrange(len(parts) / 2):
userId = int(parts[i * 2])
winChips = -int(parts[i * 2 + 1])
user = cls._buildUser(userId, winChips)
cls._dayRanks.append(user)
cls._dateForDayRank = today
return cls._dayRanks
@classmethod
def _updateLocalDayRanks(cls, userId, winChips, today):
'''更新本地的dayrank变量'''
count = cls._maxRanksCount()
dayRanks = cls._getLocalDayRanks(today)
# 更新用户的分数
index = cls._findUserInDayRanks(userId, dayRanks)
if index >= 0:
dayRanks[index]['winChips'] = winChips
else:
if len(dayRanks) < count:
dayRanks.append(cls._buildUser(userId, winChips))
else:
dayRanks[-1] = cls._buildUser(userId, winChips)
# 按分数从大到小排序
dayRanks.sort(cmp=lambda a, b: cmp(a['winChips'], b['winChips']), reverse=True)
@classmethod
def _findUserInDayRanks(cls, userId, dayRank):
'''找到用户在dayrank中的索引, 找不到时返回-1'''
for i in xrange(len(dayRank)):
if dayRank[i]['userId'] == userId:
return i
return -1
@classmethod
def _maxRanksCount(cls):
'''排行榜最大存储的数量'''
dayConf = carsconf.getKingConf().get('today', {})
return max(dayConf.get('game_count', 1), dayConf.get('bi_count', 1))
@classmethod
def _loopCheckRedisRanksPeriod(cls):
now = int(time.time())
# 排行榜里成员数量
today = cls._todayDate(now)
countInRedis = daobase.executeRankCmd('ZCOUNT', cls._dayKey(today), '-inf', '+inf')
if not countInRedis:
countInRedis = 0
countInRedis = int(countInRedis)
ftlog.info('_loopCheckRedisRanksPeriod king ranks total length', countInRedis)
# 超出预配置长度时, 要做裁剪
maxCount = cls._maxRanksCount()
if countInRedis > maxCount * 2:
daobase.executeRankCmd('ZREMRANGEBYRANK', cls._dayKey(today), maxCount, -1)
# 跟上次check的时间做比较, 跨越一天时, 输出BI统计需要的日志
if UtilFunc.isAcrossTwoDays(now, cls._lastCheckedRanksAt):
biCount = carsconf.getKingConf().get('today', {}).get('bi_count', 1)
yesterday = cls._todayDate(now - const.ONE_DAY_SEC)
parts = daobase.executeRankCmd('ZRANGE', cls._dayKey(yesterday), 0, biCount, 'WITHSCORES')
if not parts:
parts = []
# BI统计需要的日志
ftlog.info('King.Ranks.Yesterday:', parts)
# 配置过期时间, 最长保留一天
cls.setExpireRemoteRank(now)
cls._lastCheckedRanksAt = now
# 一段时间后, 再次调度自己
UtilFunc.callLater(cls.CHECK_REDIS_RANKS_PERIOD, cls._loopCheckRedisRanksPeriod)