#!/usr/bin/python
|
# -*- coding: utf-8 -*-
|
# @todo: 区服总览(当前服生态)
|
|
import CommFunc
|
import DBOperate
|
import ConfigParser
|
import CTGOKSort
|
|
import operator
|
import datetime
|
import time
|
import os
|
import copy
|
import mylog
|
import logging
|
|
cfg = ConfigParser.ConfigParser()
|
cfg.read("../../InterfaceConfig.php")
|
ServerPath = cfg.get("ServerInfo", "ServerPath")
|
|
class CrossPlayer():
|
|
def __init__(self):
|
self.playerLV = 0
|
self.fightPower = 0
|
return
|
|
def CrossServerViewInfo(argvDict):
|
'''
|
按跨服功能分区查询:
|
1、活跃玩家人数(选取时间内上线过)总合
|
2、活跃玩家人数的等级分布(1~299,之后按每50级一个跨段)
|
'''
|
|
crossMapDict = {(32010, 32011, 32012, 32013, 32014, 32015, 32016, 32017, 32018, 32019):_(u"跨服天梯"),
|
(32020, 32021, 32022, 32023, 32024, 32025, 32026, 32027, 32028, 32029):_(u"蓬莱仙境"),
|
(32080, 32081, 32082, 32083, 32084, 32085, 32086, 32087, 32088, 32089):_(u"魔化之地"),
|
(32070, 32071, 32072, 32073, 32074, 32075, 32076, 32077, 32078, 32079, 33000, 33001, 33002, 33003, 33004, 33005, 33006, 33007, 33008, 33009):_(u"跨服排位赛"),
|
(32060, 32061, 32062, 32063, 32064, 32065, 32066, 32067, 32068, 32069):_(u"古神战场"),
|
}
|
loginZonePlayerDict = {k:{} for k in crossMapDict.keys()}
|
|
startDate = argvDict.get("startDate", "")
|
endDate = argvDict.get("endDate", "")
|
CommFunc.loopMainServerDR(cfg, startDate, endDate, argvDict, checkDrFileNeedParseFunc_CrossServerViewInfo,
|
parseLineFunc_CrossServerViewInfo, loginZonePlayerDict)
|
|
spaceStr = " ";
|
printStr = "<font style=\"text-indent:2em;font-size:12px;\">"
|
|
printStr += "%s:%s%s" % (_(u"开始日期"), spaceStr, startDate);
|
printStr += "%s~%s" % (spaceStr, spaceStr)
|
if endDate:
|
printStr += "%s" % (endDate)
|
else:
|
printStr += _(u"今天")
|
printStr += "<br/>"
|
for mapKey, zonePlayerDict in loginZonePlayerDict.items():
|
mapName = crossMapDict.get(mapKey, str(mapKey))
|
printStr += __PrintCrossFuncViewInfo(zonePlayerDict, mapName, spaceStr)
|
|
printStr += "</font>"
|
print printStr
|
return
|
|
def __PrintCrossFuncViewInfo(pkZonePlayerDict, funcName, spaceStr):
|
printStr = ""
|
printStr += "<br/>【%s】<br/>" % funcName
|
|
dataList = []
|
tdList = ["zoneID", "accIDCount"]
|
|
topFPCount = 10 # 前10战力
|
lvRangeList = [300] + range(350, 1000, 50)
|
lvTitleList = []
|
accIDTotal = 0
|
lvTotalCountDict = {}
|
topFPPlayerList = []
|
for zoneID, playerDict in pkZonePlayerDict.items():
|
playerList = playerDict.values()
|
playerList.sort(key=operator.attrgetter("fightPower"), reverse=True)
|
|
accIDCount = len(playerDict) # 总人数
|
|
lvCountDict = {} # 等级分布人数
|
fightPowerList = []
|
for player in playerList:
|
playerLV = player.playerLV
|
for lv in lvRangeList:
|
if playerLV < lv:
|
lvCountDict[lv] = lvCountDict.get(lv, 0) + 1
|
break
|
|
if len(fightPowerList) < topFPCount:
|
fightPowerList.append(player)
|
|
topFPPlayerList.extend(fightPowerList)
|
for lv in lvRangeList:
|
if lv in lvCountDict:
|
continue
|
if lv > max(lvCountDict):
|
break
|
lvCountDict[lv] = 0
|
|
for lv, count in lvCountDict.items():
|
if lv not in lvTitleList:
|
lvTitleList.append(lv)
|
lvTotalCountDict[lv] = lvTotalCountDict.get(lv, 0) + count
|
|
accIDTotal += accIDCount
|
tableInfo = {"zoneID":zoneID, "accIDCount":accIDCount, "topFightPower":getSortDataStr(fightPowerList, "fightPower", spaceStr)}
|
tableInfo.update(lvCountDict)
|
dataList.append(tableInfo)
|
|
lvTitleList.sort()
|
tdList.extend(lvTitleList)
|
tdList.extend(["topFightPower"])
|
|
# 最后一行汇总信息
|
topFPPlayerList.sort(key=operator.attrgetter("fightPower"), reverse=True)
|
totalInfo = {"zoneID":_(u"总"), "accIDCount":accIDTotal, "topFightPower":getSortDataStr(topFPPlayerList[:topFPCount], "fightPower", spaceStr)}
|
totalInfo.update(lvTotalCountDict)
|
dataList.append(totalInfo)
|
|
styleInfo = {"zoneID":{"title":_(u"分区ID")},
|
"accIDCount":{"title":_(u"总活跃")},
|
"topFightPower":{"align":"left", "title":_(u"战力榜")},
|
}
|
for lv in lvTitleList:
|
styleInfo[lv] = {"title":"LV < %s" % lv}
|
printStr += CommFunc.editTableHtml(dataList, tdList, "", styleInfo, printCount=False)
|
|
# printStr += "</p>"
|
return printStr
|
|
def checkDrFileNeedParseFunc_CrossServerViewInfo(drFileName, *parseArgs, **kv):
|
''' 检查流向是否需要处理
|
@param drFileName: 流向文件名 xxx_日期.txt
|
@param *parseArgs: 自定义参数
|
@return: isNeed, checkNeedParseRetInfo
|
'''
|
isNeed = drFileName.startswith("LogInOut_")
|
return isNeed, None
|
|
def parseLineFunc_CrossServerViewInfo(drName, dateStr, checkNeedParseRetInfo, line, *parseArgs, **kv):
|
''' 解析流向行内容
|
@param drName: 流向名
|
@param dateStr: 对应日期字符串
|
@param checkNeedParseRetInfo: checkDrFileNeedParseFunc 返回的内容
|
@param line: 本行内容
|
@param *parseArgs: 自定义参数
|
'''
|
|
loginZonePlayerDict = parseArgs[0]
|
|
drDict = eval(line)
|
|
# 统计累计登录
|
if drName == "LogInOut":
|
if drDict["Type"] != "login":
|
return
|
if "RegCrossZoneID" not in drDict:
|
return
|
accID = drDict["AccID"]
|
mapID = drDict["mapID"]
|
|
zonePlayerDict = None
|
for mapKey in loginZonePlayerDict.keys():
|
if mapID in mapKey:
|
zonePlayerDict = loginZonePlayerDict[mapKey]
|
break
|
if zonePlayerDict == None:
|
mapKey = (mapID,)
|
loginZonePlayerDict[mapKey] = {}
|
zonePlayerDict = loginZonePlayerDict[mapKey]
|
|
#playerName = drDict["Name"]
|
playerLV = drDict["PlayerLV"]
|
fightPower = drDict["fightPower"]
|
zoneID = drDict["RegCrossZoneID"]
|
|
if zoneID not in zonePlayerDict:
|
zonePlayerDict[zoneID] = {}
|
if accID not in zonePlayerDict[zoneID]:
|
player = CrossPlayer()
|
zonePlayerDict[zoneID][accID] = player
|
player = zonePlayerDict[zoneID][accID]
|
player.playerLV = playerLV
|
player.fightPower = fightPower
|
|
return
|
|
def getServerViewInfo(argvDict):
|
## 非跨服服务器总览
|
|
onlineMaxInfo = {}
|
loginAccIDList = []
|
orderTypeCTGOKInfo = {}
|
createRoleInfo = [0, 0] # [创角人数, 首登人数]
|
newCTGAccIDInfo = {}
|
|
startDate = argvDict.get("startDate", "")
|
endDate = argvDict.get("endDate", "")
|
CommFunc.loopMainServerDR(cfg, startDate, endDate, argvDict, checkDrFileNeedParseFunc_ServerViewInfo,
|
parseLineFunc_ServerViewInfo, onlineMaxInfo, loginAccIDList, orderTypeCTGOKInfo, createRoleInfo, newCTGAccIDInfo)
|
|
# 以下榜单数据有一定延迟,非实时排行数据,存档时间5分钟
|
# 合并数据 - 战力榜
|
topOrderCount = CommFunc.toInt(argvDict.get("topOrderCount"), 3)
|
dboper = DBOperate.DBOper(ServerPath)
|
col = dboper.db["tagDBPlayer"]
|
spec = {}
|
fields = {'_id':0, "FightPower":1, "FightPowerEx":1}
|
fightPowerRet = col.find(spec, fields).sort([("FightPowerEx", -1), ("FightPower", -1)]).limit(topOrderCount)
|
|
# 合并数据 - 等级榜
|
fields = {'_id':0, "LV":1}
|
LVRet = col.find(spec, fields).sort([("LV", -1)]).limit(topOrderCount)
|
|
# 实际排行 - 战力
|
col = dboper.db["tagDBBillboard"]
|
spec = {"Type":0}
|
fields = {'_id':0, "CmpValue":1, "CmpValue2":1}
|
billboard_FightPowerRet = col.find(spec, fields).sort([("CmpValue", -1), ("CmpValue2", -1)]).limit(topOrderCount)
|
isFightPowerExVer = False # 战力是否超过20E的版本
|
for fpData in copy.deepcopy(billboard_FightPowerRet):
|
if fpData["CmpValue2"] > 0: # 理论上只要有数据中比较值2大于0就是超过20E的版本,比较值2为求余亿的部分
|
isFightPowerExVer = True
|
break
|
|
# 实际排行 - 等级
|
spec = {"Type":4}
|
fields = {'_id':0, "CmpValue":1}
|
billboard_LVRet = col.find(spec, fields).sort([("CmpValue", -1)]).limit(topOrderCount)
|
|
# 服务器其他信息
|
openServerTimeStr, worldLV, serverDay, isMixServer, mixServerDay = "", 0, 0, 0, 0
|
openServerTiemServerDay = 0
|
zoneID = 0
|
col = dboper.db["tagDBGameServerEventTrig"]
|
spec = {"EventID":{"$in":["InitOpenServerTime", "WorldAverageLv", "ServerDay", "IsMixServer", "MixServerDay", "CrossPKZoneID"]}}
|
#fields = {}
|
eventRet = col.find(spec)
|
for eventInfo in eventRet:
|
eventName = eventInfo["EventID"]
|
eventValue = eventInfo["IsEvent"]
|
if eventName == "InitOpenServerTime":
|
openServerTime = eventValue
|
openServerTimeStr = "" if not openServerTime else time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(openServerTime))
|
if openServerTimeStr:
|
openServerDatetime = datetime.datetime.strptime(openServerTimeStr.split(" ")[0], "%Y-%m-%d")
|
openServerTiemServerDay = (datetime.datetime.today() - openServerDatetime).days + 1
|
|
elif eventName == "WorldAverageLv":
|
worldLV = eventValue
|
elif eventName == "ServerDay":
|
serverDay = eventValue + 1
|
elif eventName == "IsMixServer":
|
isMixServer = eventValue
|
if not isMixServer:
|
mixServerDay = 0
|
elif eventName == "MixServerDay":
|
if isMixServer:
|
mixServerDay = eventValue + 1
|
elif eventName == "CrossPKZoneID":
|
zoneID = eventValue
|
|
# 关闭
|
dboper.close()
|
|
spaceStr = " ";
|
printStr = "<font style=\"text-indent:2em;font-size:12px;\">"
|
|
printStr += "%s:%s%s" % (_(u"开始日期"), spaceStr, startDate);
|
printStr += "%s~%s" % (spaceStr, spaceStr)
|
if endDate:
|
printStr += "%s" % (endDate)
|
else:
|
printStr += _(u"今天")
|
printStr += "<br/>"
|
|
printStr += "%s:%s%s%s人;%s %s:%s%s%s人;%s <br/>" \
|
% (_(u"创角人数"), spaceStr, createRoleInfo[0], spaceStr, spaceStr * 2,
|
_(u"首登人数"), spaceStr, createRoleInfo[1], spaceStr, spaceStr * 2
|
)
|
|
printStr += "%s:%s%s%s人;%s %s:%s%s%s人;%s %s:%s%s<br/>" \
|
% (_(u"最高在线"), spaceStr, onlineMaxInfo.get("OnLinePlayerCount", 0) - onlineMaxInfo.get("tjgOnlineCnt", 0), spaceStr, spaceStr * 2,
|
_(u"额外脱机"), spaceStr, onlineMaxInfo.get("tjgOnlineCnt", 0), spaceStr, spaceStr * 2,
|
_(u"时间"), spaceStr, onlineMaxInfo.get("time", ""))
|
|
allCtgAccIDList = []
|
printStr += "%s:%s%s%s人<br/>" % (_(u"累计登录"), spaceStr, len(loginAccIDList), spaceStr)
|
for payOrderType, ctgOKInfo in orderTypeCTGOKInfo.items():
|
orderTypeName = CommFunc.getPayOrderTypeName(payOrderType)
|
ctgOKTotal, ctgAccIDList = ctgOKInfo
|
if payOrderType not in CTGOKSort.PayByMoneyOrderTypeList:
|
ctgOKTotal = CommFunc.coinToY(ctgOKTotal, CommFunc.getPayOrderCoinRate(payOrderType))
|
ctgPlayerCount = len(ctgAccIDList)
|
ctgAvgValue = "%.2f" % (ctgOKTotal / ctgPlayerCount) if ctgPlayerCount else 0
|
printStr += "%s:%s%s%s%s;%s %s:%s%s%s;%s %s:%s%s%s%s<br/>" \
|
% (_(u"累计充值"), spaceStr, ctgOKTotal, spaceStr, orderTypeName, spaceStr * 2,
|
_(u"人数"), spaceStr, ctgPlayerCount, spaceStr, spaceStr * 2,
|
_(u"人均"), spaceStr, ctgAvgValue, spaceStr, orderTypeName)
|
for accID in ctgAccIDList:
|
if accID not in allCtgAccIDList:
|
allCtgAccIDList.append(accID)
|
if not orderTypeCTGOKInfo:
|
printStr += "%s:%s 0 <br/>" % (_(u"累计充值"), spaceStr)
|
elif len(orderTypeCTGOKInfo) > 1:
|
printStr += "%s:%s%s<br/>" % (_(u"累计充值人数"), spaceStr, len(allCtgAccIDList))
|
|
printStr += "%s:" % (_(u"新增充值人数"))
|
newCTGDateList = newCTGAccIDInfo.keys()
|
newCTGDateList.sort()
|
newCTGDateList = newCTGDateList[-7:]
|
for dateStr in newCTGDateList:
|
newCTGCount = len(newCTGAccIDInfo[dateStr])
|
printStr += "%s%s:%s" % (spaceStr, dateStr, newCTGCount)
|
if not newCTGDateList:
|
printStr += "%s0" % spaceStr
|
printStr += "<br/>"
|
|
# 战力
|
printStr += "%s<br/>" % _(u"以下排行榜非实时数据")
|
printStr += "%s:%s%s<br/>" % (_(u"所有角色战力排行"), spaceStr, getSortDataStr(fightPowerRet, "FightPower", spaceStr, "FightPowerEx"))
|
if isFightPowerExVer:
|
printStr += "%s:%s%s<br/>" % (_(u"活跃角色战力排行"), spaceStr, getSortDataStr(billboard_FightPowerRet, "CmpValue2", spaceStr, "CmpValue"))
|
else:
|
printStr += "%s:%s%s<br/>" % (_(u"活跃角色战力排行"), spaceStr, getSortDataStr(billboard_FightPowerRet, "CmpValue", spaceStr))
|
printStr += "%s:%s%s<br/>" % (_(u"所有角色等级排行"), spaceStr, getSortDataStr(LVRet, "LV", spaceStr))
|
printStr += "%s:%s%s<br/>" % (_(u"活跃角色等级排行"), spaceStr, getSortDataStr(billboard_LVRet, "CmpValue", spaceStr))
|
printStr += "%s:%s%s%s %s:%s%s %s:%s%s %s:%s%s %s:%s%s %s(%s)%s" % \
|
(_(u"开服时间"), spaceStr, openServerTimeStr, spaceStr * 2,
|
_(u"开服天"), serverDay, spaceStr,
|
_(u"是否合服"), isMixServer, spaceStr,
|
_(u"合服天"), mixServerDay, spaceStr,
|
_(u"世界等级"), worldLV, spaceStr,
|
spaceStr * 2, _(u"凌晨0~5点查询可能误差1天"), spaceStr * 2
|
)
|
|
# 跨服PK分区显示样式,不同分区禁止规划合服合在一起
|
if zoneID:
|
crossPKZoneStyleList = [["#ff0000", "#ffffff"], ["#00ff00", "#ffffff"], ["#ffff00", "#515a6e"], ["#2d8cf0", "#ffffff"], ["#00ffff", "#ffffff"]]
|
bgColor, zoneIDColor = ["", ""] if zoneID > len(crossPKZoneStyleList) else crossPKZoneStyleList[zoneID - 1]
|
printStr += "<font style='background:%s'>%s%s<font color='%s'><strong>%s</strong></font>%s</font>" \
|
% (bgColor, _(u"跨服PK分区"), spaceStr, zoneIDColor, zoneID, spaceStr)
|
|
printStr += "<br/>"
|
printStr += "</font>"
|
print printStr
|
return
|
|
def getSortDataStr(dataList, showDataKey, spaceStr, showDataKeyEx=None):
|
showStr = ""
|
for i, dataInfo in enumerate(dataList):
|
if i != 0:
|
showStr += ",%s" % (spaceStr)
|
if isinstance(dataInfo, dict):
|
value = dataInfo[showDataKey]
|
if showDataKeyEx != None and showDataKeyEx in dataInfo:
|
value += dataInfo[showDataKeyEx] * 100000000
|
else:
|
value = getattr(dataInfo, showDataKey)
|
if showDataKeyEx != None and hasattr(dataInfo, showDataKeyEx):
|
value += getattr(dataInfo, showDataKeyEx) * 100000000
|
showStr += CommFunc.transformNum(value)
|
return showStr
|
|
def checkDrFileNeedParseFunc_ServerViewInfo(drFileName, *parseArgs, **kv):
|
''' 检查流向是否需要处理
|
@param drFileName: 流向文件名 xxx_日期.txt
|
@param *parseArgs: 自定义参数
|
@return: isNeed, checkNeedParseRetInfo
|
'''
|
isNeed = (drFileName.startswith("OnLinePlayerCount_") or drFileName.startswith("LogInOut_") or drFileName.startswith("CTGOK_") \
|
or drFileName.startswith("FirstLogin_") or drFileName.startswith("tagDBCreateRole_"))
|
return isNeed, None
|
|
def parseLineFunc_ServerViewInfo(drName, dateStr, checkNeedParseRetInfo, line, *parseArgs, **kv):
|
''' 解析流向行内容
|
@param drName: 流向名
|
@param dateStr: 对应日期字符串
|
@param checkNeedParseRetInfo: checkDrFileNeedParseFunc 返回的内容
|
@param line: 本行内容
|
@param *parseArgs: 自定义参数
|
'''
|
|
onlineMaxInfo, loginAccIDList, orderTypeCTGOKInfo, createRoleInfo, newCTGAccIDInfo = parseArgs
|
|
# 统计最高在线
|
if drName == "OnLinePlayerCount":
|
drDict = eval(line)
|
#{'OnLinePlayerCount': 2, 'tjgOnlineCnt': 28, 'platformOLDict': 'qkbtgame', 'time': '2021-01-01 00:00:46'} 按平台记录
|
# ...
|
#{'OnLinePlayerCount': 30, 'tjgOnlineCnt': 28, 'platformOLDict': {}, 'time': '2021-01-01 00:00:46'} 最后一条汇总
|
if drDict["platformOLDict"] != {}:
|
# 暂只处理汇总的信息
|
return
|
nowMax = onlineMaxInfo.get("OnLinePlayerCount", 0) - onlineMaxInfo.get("tjgOnlineCnt", 0)
|
drOnlineCount = drDict["OnLinePlayerCount"]
|
drTjCount = drDict["tjgOnlineCnt"]
|
realOnlineCount = drOnlineCount - drTjCount # 实际在线
|
if realOnlineCount < nowMax:
|
return
|
if realOnlineCount == nowMax and drDict["tjgOnlineCnt"] < onlineMaxInfo.get("tjgOnlineCnt", 0):
|
return
|
onlineMaxInfo.update(drDict)
|
|
# 统计累计登录
|
elif drName == "LogInOut":
|
if "logout" in line:
|
return
|
drDict = eval(line)
|
if drDict["Type"] != "login":
|
return
|
accID = drDict["AccID"]
|
if accID in loginAccIDList:
|
return
|
loginAccIDList.append(accID)
|
|
# 统计充值
|
elif drName == "CTGOK":
|
drDict = eval(line)
|
payOrderType = drDict.get("payOrderType", CTGOKSort.DefaultOrderType)
|
ctgOKTotal, ctgAccIDList = orderTypeCTGOKInfo.get(payOrderType, [0, []])
|
if payOrderType in CTGOKSort.PayByMoneyOrderTypeList:
|
orderValue = drDict["orderMoneyValue"]
|
else:
|
if not CTGOKSort.IsRealCTG(drDict):
|
return
|
orderValue = drDict["orderCoin"]
|
ctgOKTotal += orderValue
|
accID = drDict["AccID"]
|
if accID not in ctgAccIDList:
|
ctgAccIDList.append(accID)
|
orderTypeCTGOKInfo[payOrderType] = [ctgOKTotal, ctgAccIDList]
|
|
ctgRealFirstTimeStr = drDict.get("ctgRealFirstTime", "")
|
if ctgRealFirstTimeStr.startswith(dateStr):
|
newCTGAccIDList = newCTGAccIDInfo.get(dateStr, [])
|
if accID not in newCTGAccIDList:
|
newCTGAccIDList.append(accID)
|
newCTGAccIDInfo[dateStr] = newCTGAccIDList
|
# 创角
|
elif drName == "tagDBCreateRole":
|
createRoleInfo[0] += 1
|
|
# 首登
|
elif drName == "FirstLogin":
|
createRoleInfo[1] += 1
|
|
return
|
|
def main():
|
CommFunc.setdefaultencoding()
|
argvDict = CommFunc.parse_args()
|
mylog.InitMyLog(argvDict.get("eventType", ""))
|
CommFunc.gettextInstall(argvDict.get("lang", ""))
|
dbIni = ConfigParser.ConfigParser()
|
dbIni.read(os.path.join(ServerPath, "db\PyMongoDataServer\PyMongoDataServer.ini"))
|
isCrossServer = dbIni.has_option("Merge", "IsMergeServer") and dbIni.getint("Merge", "IsMergeServer") == 1
|
if isCrossServer:
|
CrossServerViewInfo(argvDict)
|
else:
|
getServerViewInfo(argvDict)
|
return
|
|
if __name__ == "__main__":
|
try:
|
main()
|
except:
|
CommFunc.printExceptionError()
|