#!/usr/bin/python
# -*- coding: utf-8 -*-
#-------------------------------------------------------------------------------
import sys
import datetime
import traceback
import urllib2
import urllib
import json
import os
import logging
import gettext
import sys
import ConfigParser
Encoding = "utf-8"
FinalDataSign = "_FinalData"
def encode(s):
if isinstance(s, unicode):
return s.encode(Encoding)
elif isinstance(s, int) or isinstance(s, long):
return s
elif not isinstance(s, str):
return str(s)
return s
def toGBK(s):
return s.decode(Encoding).encode("gbk")
def encodePych(pyChStr):
## 转化py中文
return unicode(pyChStr, Encoding).encode(Encoding)
def setdefaultencoding(encoding=None):
reload(sys)
sys.setdefaultencoding(encoding if encoding else Encoding)
return
def parse_args():
## 解析运行参数
#print "sys.argv: ", len(sys.argv), sys.argv
argvDict = {}
for argvInfo in sys.argv[1:]:
kvList = argvInfo.split("=")
if not kvList:
continue
value = kvList[1] if len(kvList) > 1 else ""
# 中文参数超过4个字会有问题,但是用符号包起来就没问题,暂未知原因,先这样处理,参数固定用[]包起来
if value.startswith("[") and value.endswith("]"):
value = value[1:-1]
argvDict[kvList[0]] = value
#print "parse_args: ", argvDict
return argvDict
def toInt(inputValue, defValue=0):
try:
result = int(inputValue)
return result
except:
return defValue
def checkDate(startDate, endDate, checkDate):
## 检查对应日期是否在指定日期范围内,参数都是 datetime 类型
if not checkDate:
return False
if startDate and checkDate < startDate:
return False
if endDate and checkDate > endDate:
return False
return True
def strToDatetime(timeStr, strFormat="%Y-%m-%d"):
try:
return datetime.datetime.strptime(timeStr, strFormat)
except:
return
def getDiffDaysDate(diffDays, dateObj=None):
## 日期加减运算
if dateObj == None:
dateObj = datetime.datetime.today()
return dateObj + datetime.timedelta(days=diffDays)
def getCurrentTime():
return datetime.datetime.today()
def getCurrentDateStr():
curTime = datetime.datetime.today()
return "%d-%02d-%02d" % (curTime.year, curTime.month, curTime.day)
def getServerID(accID):
infoList = accID.split("@")
return 0 if len(infoList) < 3 else int(infoList[-1][1:])
def coinToY(coinValue, rate=100):
## coin转为元
toValue = coinValue / float(rate)
if str(toValue).endswith(".0"):
toValue = int(toValue)
return toValue
def getCoinRate(cfg, spid):
## 获取coin的转化比例,默认100
if cfg.has_option("ExChange", "ExChangeScale_%s" % spid):
return int(cfg.get("ExChange", "ExChangeScale_%s" % spid))
if cfg.has_option("ExChange", "ExChangeScale"):
return int(cfg.get("ExChange", "ExChangeScale"))
return 100
def roundInt(value, ndigits=2):
## 保留x位小数,如果刚好x.0则取整
value = round(value, ndigits)
if str(value).endswith(".0"):
value = int(value)
return value
def matchDr(drDict, matchSet={}, filterSet={}):
''' 检查流向数据是否是需要满足匹配条件的数据
@param matchSet: 匹配设定 {匹配key:[匹配值列表], ...}, 即只会匹配对应key为指定值列表的内容
@param filterSet: 过滤设定, {过滤key:[过滤值列表], ...}, 即会过滤掉对应key为某些值的内容
@return: 是否是需要统计的流向数据
'''
# 优先检查被过滤掉的
for filterKey, filterList in filterSet.items():
if filterKey not in drDict:
continue
if drDict[filterKey] in filterList:
return False
# 检查精确匹配的
for matchKey, matchList in matchSet.items():
if matchKey not in drDict:
return False
if drDict[matchKey] not in matchList:
return False
return True
def parseMixServerIDInfo(mixServerIDInfo, useAllIDList=False):
## 解析合服服务器ID组合信息 支持范围: 服务器ID~服务器ID 或 单服:服务器ID; 多组
''' 解析合服服务器ID组合信息
支持格式:
范围: 服务器ID~服务器ID
单服:服务器ID
组合格式:按支持的格式使用英文逗号分割,如 1~10,20,30 代表 1至10服 及 20服 及 30服
'''
serverIDList = []
if not mixServerIDInfo:
return serverIDList
splitList = mixServerIDInfo.replace(" ", "").split(",")
for idInfo in splitList:
if idInfo.isdigit():
serverIDList.append(int(idInfo))
elif "~" in idInfo:
rangeIDList = idInfo.split("~")
if useAllIDList:
serverIDList.extend(range(int(rangeIDList[0]), int(rangeIDList[1]) + 1))
else:
serverIDList.append([int(rangeIDList[0]), int(rangeIDList[1])])
return serverIDList
def checkIsBackupServerFolder(queryServerIDList, checkServerInfo):
''' 检查是否满足需要查询的区服备份
@param queryServerIDList: 需要查询的目标服务器组合列表,由 parseMixServerIDInfo 解析得到
@param checkServerInfo: 需要检查是否满足条件的区分ID信息串,可由 parseMixServerIDInfo 解析的格式
@return: 是否满足
'''
checkServerIDList = parseMixServerIDInfo(checkServerInfo)
if not checkServerIDList:
return False
for checkServerIDInfo in checkServerIDList:
if isinstance(checkServerIDInfo, int):
checkID = checkServerIDInfo
if checkID in queryServerIDList:
return True
for queryServerIDInfo in queryServerIDList:
if isinstance(queryServerIDInfo, int):
continue
queryIDMin, queryIDMax = queryServerIDInfo
if queryIDMin <= checkID <= queryIDMax:
return True
else:
checkIDMin, checkIDMax = checkServerIDInfo
for queryServerIDInfo in queryServerIDList:
if isinstance(queryServerIDInfo, int):
if checkIDMin <= queryServerIDInfo <= checkIDMax:
return True
else:
queryIDMin, queryIDMax = queryServerIDInfo
if checkIDMin <= queryIDMin <= checkIDMax or checkIDMin <= queryIDMax <= checkIDMax \
or queryIDMin <= checkIDMin <= queryIDMax or queryIDMin <= checkIDMin <= queryIDMax:
return True
return False
def queryBackupCenterDR(cfg, postData={}):
## 查询中心合服备份流向数据
# @return: 中心返回数据,None代表失败,需用None判断,因为可能实际数据就是空的列表
url = cfg.get("ServerInfo", "EventDataBackupToolUrl")
postData["queryCenterbak"] = 1
req = urllib2.Request(url=url, data=urllib.urlencode(postData))
res_data = urllib2.urlopen(req, timeout=120)
resMsg = res_data.read()
if not resMsg:
print "error: query backup center's eventdata no message return!
"
return
if resMsg.startswith("error:") or "Traceback" in resMsg:
print resMsg
return
return json.loads(resMsg)
def queryBackupCenterError(errorMsg):
print "error: %s" % errorMsg
return
def queryBackupCenterOK(retData):
## 成功默认返回json格式数据,支持普通kv字典,或类实例转化json
#logging.info("retData:%s", retData)
print json.dumps(retData, ensure_ascii=False, default=lambda obj: obj.__dict__)
return
def printExceptionError():
## 显示报错等异常信息
error = traceback.format_exc()
error = error.replace("<", "<").replace(">", ">").replace("\r", "
").replace("\n", "
")
print "error:
:", error
def loopMainServerDR(cfg, startDate, endDate, argvDict, checkDrFileNeedParseFunc, parseLineFunc, *parseArgs, **kv):
''' 循环遍历游戏主服流向信息,并解析满足时间范围流向
@param cfg: InterfaceConfig.php 配置对象
@param startDate: 起始日期
@param endDate: 结束日期
@param argvDict: post到中心的参数
@param checkDrFileNeedParseFunc: 额外检查流向文件是否需要解析的函数,传入参数(流向文件名 drFileName, *parseArgs, **kv),返回自定义信息提供 parseLineFunc 用
@param parseLineFunc: 解析流向每行内容的逻辑函数,传入参数(流向名drName, 所属dateStr, checkDrFileNeedParseFunc 返回的结果信息 checkNeedParseRetInfo, 流向行内容line, *parseArgs, **kv)
@param *parseArgs: 自定义参数
@return: needQueryCenterbak 是否需要查询中心备份流向数据
'''
startDatetime = strToDatetime(startDate)
endDatetime = strToDatetime(endDate)
minDatetime = None
logging.info("loopMainServerDR %s" % str(argvDict))
# 流向路径
FolderPath = os.path.join(cfg.get("ServerInfo", "ServerPath"), "EventServer\EventData")
for parent, _, fileList in os.walk(FolderPath):
parentList = parent.split("\\")
lastStr = parentList[-1]
fileDatetime = strToDatetime(lastStr)
if fileDatetime and (not minDatetime or fileDatetime < minDatetime):
minDatetime = fileDatetime
if not checkDate(startDatetime, endDatetime, fileDatetime):
continue
# 指定遍历流向文件及顺序
if "drNameList" in kv:
loopFileList = ["%s_%s.txt" % (drName, lastStr) for drName in kv["drNameList"]]
else:
loopFileList = fileList
for drFileName in loopFileList:
fullPath = os.path.join(parent, drFileName)
if not os.path.isfile(fullPath):
continue
dateStr = lastStr # 流向日期
isNeed, checkNeedParseRetInfo = checkDrFileNeedParseFunc(drFileName, *parseArgs, **kv)
if not isNeed:
continue
# 以下为已经确定需要处理的流向文件逻辑,可进行open、readlines、eval等操作
drName = drFileName[:drFileName.rindex("_")] # 流向名
f = open(fullPath, "r")
lines = f.readlines()
f.close()
for line in lines:
parseLineFunc(drName, dateStr, checkNeedParseRetInfo, line, *parseArgs, **kv)
## 查询所有数据(含备份在中心服务器的本次合服前的数据)
needQueryCenterbak = argvDict.get("queryAllData") and (not startDatetime or not minDatetime or startDatetime <= minDatetime)
if needQueryCenterbak:
argvDict["queryCenterbak"] = 1 # 设置查询中心备份数据 与 isQueryCenterbak 对应
logging.info("End")
return needQueryCenterbak
def isQueryCenterbak(argvDict):
## 是否查询中心备份的
return argvDict.get("queryCenterbak") != None
def loopCenterbakRarDR(cfg, startDate, endDate, argvDict, checkDrFileNeedParseFunc, parseLineFunc, *parseArgs, **kv):
''' 循环遍历中心服务器备份流向信息,并解析满足时间范围及区服条件的流向
@note: Rar专用
@param cfg: InterfaceConfig.php 配置对象
@param argvDict: post到中心的参数
@param checkDrFileNeedParseFunc: 额外检查流向文件是否需要解析的函数,传入参数(流向文件名 drFileName, *parseArgs, **kv),返回自定义信息提供 parseLineFunc 用
@param parseLineFunc: 解析流向每行内容的逻辑函数,传入参数(流向名drName, 所属dateStr, checkDrFileNeedParseFunc 返回的结果信息 checkNeedParseRetInfo, 流向行内容line, *parseArgs, **kv)
@param *parseArgs: 解析函数所需的参数
@return: 是否成功
'''
logging.info("loopCenterbakRarDR %s" % str(argvDict))
allServer = argvDict.get("allServer", 0)
mixServerIDInfo = argvDict.get("mixServerIDInfo", "")
if not allServer and not mixServerIDInfo:
queryBackupCenterError("can not found mixServerIDInfo!")
return
spID = argvDict.get("spID", "")
backupPath = os.path.join(cfg.get("ServerInfo", "EventDataBackupPath"), spID, "EventData")
isUseRar = cfg.getint("ServerInfo", "EventDataBackupUseRar") == 1 # 是否使用压缩包
logging.info("isUseRar=%s,spID=%s,backupPath=%s" % (isUseRar, spID, backupPath))
if not os.path.exists(backupPath):
queryBackupCenterError("can not found backup path: %s" % backupPath)
return
startDatetime = strToDatetime(startDate)
endDatetime = strToDatetime(endDate)
OnlyServerID = int(argvDict.get("OnlyServerID", 0))
serverIDList = [OnlyServerID] if OnlyServerID else parseMixServerIDInfo(mixServerIDInfo)
spfileFormat = "Event_%s-s" % spID
logging.info("allServer=%s,serverIDList=%s,spfileFormat=%s" % (allServer, serverIDList, spfileFormat))
logloop = "logloop" in kv
printloop = "printloop" in kv
# 备份日期文件夹
for backupDateFile in os.listdir(backupPath):
if logloop:
logging.info("backupDateFile:%s" % backupDateFile)
if printloop:
print "backupDateFile:%s" % backupDateFile
backupDateStr = backupDateFile[len("Event_"):]
backupDatetime = strToDatetime(backupDateStr)
if not checkDate(startDatetime, "", backupDatetime):
continue
if not isUseRar:
# 备份分区文件夹
for backupServer in os.listdir(os.path.join(backupPath, backupDateFile)):
if backupServer.endswith(".rar"):
# rar文件的不处理
continue
if not backupServer.startswith(spfileFormat):
continue
serverInfo = backupServer[len(spfileFormat):]
if not allServer and not checkIsBackupServerFolder(serverIDList, serverInfo):
continue
logging.info("loop %s" % os.path.join(backupPath, backupDateFile, backupServer))
for parent, _, fileList in os.walk(os.path.join(backupPath, backupDateFile, backupServer)):
if not fileList:
continue
parentList = parent.split("\\")
lastStr = parentList[-1]
fileDatetime = strToDatetime(lastStr)
if not checkDate(startDatetime, endDatetime, fileDatetime):
continue
# 指定遍历流向文件及顺序
if "drNameList" in kv:
loopFileList = ["%s_%s.txt" % (drName, lastStr) for drName in kv["drNameList"]]
else:
loopFileList = fileList
for drFileName in loopFileList:
fullPath = os.path.join(parent, drFileName)
if not os.path.isfile(fullPath):
continue
dateStr = lastStr # 流向日期
checkNeedParseRetInfo = []
if checkDrFileNeedParseFunc:
isNeed, checkNeedParseRetInfo = checkDrFileNeedParseFunc(drFileName, *parseArgs, **kv)
if not isNeed:
continue
# 以下为已经确定需要处理的流向文件逻辑,可进行open、readlines、eval等操作
drName = drFileName[:drFileName.rindex("_")] # 流向名
f = open(fullPath, "r")
lines = f.readlines()
f.close()
for line in lines:
parseLineFunc(drName, dateStr, checkNeedParseRetInfo, line, *parseArgs, **kv)
else:
from unrar import rarfile # 放在函数内,子服用不到此模块,这样就不需要安装此依赖了
# 备份分区文件夹
for backupServer in os.listdir(os.path.join(backupPath, backupDateFile)):
if logloop:
logging.info(" %s/%s" % (backupDateFile, backupServer))
if printloop:
print " %s/%s" % (backupDateFile, backupServer)
if not backupServer.endswith(".rar"):
# 非rar文件的不处理
continue
if not backupServer.startswith(spfileFormat):
continue
serverInfo = backupServer[len(spfileFormat):-4]
if not allServer and not checkIsBackupServerFolder(serverIDList, serverInfo):
continue
#logging.info("backupServer:%s" % backupServer)
#rarfile.namelist() # 列表,rar文件中所有子文件的path(相对于rar文件包而言的)
#rar解析只能遍历rar内所有文件,无法直接定位,会相对文件夹遍历慢
# drPath is like: server\EventServer\EventData\Group_0\Server_0\2021-01-04\OpenPackCount_2021-01-04.txt
drRarFile = rarfile.RarFile(os.path.join(backupPath, backupDateFile, backupServer))
drFileNameList = drRarFile.namelist()
loopFileList = []
if "drNameList" in kv:
loopDateInfo = {}
for drPath in drFileNameList[::-1]:
if "." in drPath:
continue
rspList = drPath.split("\\")
dateStr = rspList[-1] # 日期字符串
fileDatetime = strToDatetime(dateStr)
if not fileDatetime:
continue
if not checkDate(startDatetime, endDatetime, fileDatetime):
continue
drDatePath = drPath
loopDateInfo[dateStr] = drDatePath
loopDateList = loopDateInfo.keys()
loopDateList.sort()
# 需要遍历的日期文件夹路径列表
for dateStr in loopDateList:
drDatePath = loopDateInfo[dateStr]
for drName in kv["drNameList"]:
loopDrPath = os.path.join(drDatePath, "%s_%s.txt" % (drName, dateStr))
if loopDrPath in drFileNameList:
loopFileList.append(os.path.join(drDatePath, "%s_%s.txt" % (drName, dateStr)))
else:
loopFileList = drFileNameList
for drPath in loopFileList:
if "." not in drPath:
continue
#logging.info("loop: %s", str(drPath))
rspList = drPath.split("\\")
if len(rspList) < 2:
continue
dateStr = rspList[-2] # 日期字符串
fileDatetime = strToDatetime(dateStr)
if not checkDate(startDatetime, endDatetime, fileDatetime):
continue
drFileName = rspList[-1] # 流向文件名 xxxx_日期.txt
checkNeedParseRetInfo = []
if checkDrFileNeedParseFunc:
isNeed, checkNeedParseRetInfo = checkDrFileNeedParseFunc(drFileName, *parseArgs, **kv)
if not isNeed:
continue
drName = drFileName[:drFileName.rindex("_")] # 流向名
rarOpenObj = drRarFile.open(drPath)
for line in rarOpenObj.readlines():
parseLineFunc(drName, dateStr, checkNeedParseRetInfo, line, *parseArgs, **kv)
logging.info("loopEnd")
return True
def editTableHtml(dataList, tdList, rowNumTitle="", styleInfo={}, printCount=True):
''' 编辑表格html
@param dataList: 数据列表,元素支持 类实例 或 字典
@param tdList: 表格属性字段key顺序列表
@param rowNumTitle: 行编号标题,如果空表示不显示行编号,非空则传入行编号标题名,行编号默认为 index + 1
@param styleInfo: 属性字段样式信息
'''
tablHtml = ""
if printCount:
tablHtml += _(u"数据条数") + ": %s
" % len(dataList)
tablHtml += "
%s | " % (rowNumTitle) for tdKey in tdList: style = styleInfo.get(tdKey, {}) tablHtml += "%s | " % (style.get("title", tdKey)) tablHtml += "
%s | " % (i + 1) for tdKey in tdList: # 仅支持字典及类 tdValue = "" if isinstance(dataObj, dict): tdValue = dataObj.get(tdKey, "") elif hasattr(dataObj, tdKey): tdValue = getattr(dataObj, tdKey) if isinstance(tdValue, unicode): tdValue = tdValue.encode(Encoding) elif not isinstance(tdValue, str): tdValue = str(tdValue) style = styleInfo.get(tdKey, {}) tablHtml += "%s | " % (style.get("align", "center"), tdValue) tablHtml += "