ServerPython/ZoneServerGroup/map1_8G/MapServer/MapServerData/Script/DB/DBDataMgr.py
@@ -15,126 +15,180 @@
#"""Version = 2025-05-09 12:20"""
#-------------------------------------------------------------------------------
import CommFunc
import DBStruct
import ChConfig
import GameWorld
import PyGameData
import PyMongoMain
import DBPlayerViewCache
import DBEventTrig
import DBBillboard
import DBFuncTeam
import DBGameRec
import DBFamily
import DBMail
import binascii
import time
import zlib
import os
BakRoot = "C:\ServerRealTimeBackup"
BakFileType = ".bak"
BakFileType = ".rtb"
BackupCopyMax = 5 # 保留历史备档文件数
BackupInterval = 30 # 备档频率,分钟,暂定每30分钟
#临时变量,之后优化
g_loadErr = False
Map2ServerID = {
                10010:87,
                10090:89,
                }
def OnServerStart():
    ##启动服务器数据处理
    GameWorld.DebugLog("地图服务器启动")
    LoadServerDataBackup()
    if not LoadGameWorldDataFromBackup():
        LoadGameWorldDataFromDB()
    return
def OnServerClose():
    ##关服服务器数据处理
    SaveGameWorldDataToDB()
    return
def OnMinute(curTime):
    curMinute = curTime.minute
    ServerDataBackup()
    DBFamily.OnMinute(curMinute)
    return
def OnDayEx():
    if PyGameData.g_serverClosing:
        return
    DBFamily.OnMinute()
    DBBillboard.OnMinute()
    #备档、入库放最后,等数据都处理完后再保存,每天 [[时,分], ...],尽量岔开整点、5分
    if [curTime.hour, curTime.minute] in [[3, 6]]:
        BackupGameWorldData(True)
    else:
        BackupGameWorldData(False)
    return
#------------------------------------------- 备档 ---------------------------------------------------
def GetBakFileSortList(bakPath):
    ## 按备档时间倒序排序返回 [[bakTime, filePath], ...]
    ## 按备档时间倒序排序返回 [[bakTime, 数据版本号, filePath], ...]
    bakFileList = []
    for parent, _, filenames in os.walk(bakPath):
        for filename in filenames:
            if not filename.endswith(BakFileType):
                continue
            fullPath = os.path.join(parent, filename)
            bakTime = GameWorld.ToIntDef(filename[:filename.index(".")].split("_")[1])
            bakFileList.append([bakTime, fullPath])
            bakInfo = filename[:filename.index(".")].split("_")
            if len(bakInfo) != 3:
                continue
            dataVersionNO = GameWorld.ToIntDef(bakInfo[1]) # 数据版本号
            bakTime = GameWorld.ToIntDef(bakInfo[2]) # yyyyMMddhhmmss
            bakFileList.append([bakTime, dataVersionNO, fullPath])
    bakFileList.sort(reverse=True)
    return bakFileList
def LoadServerDataBackup():
    ## 服务器公共数据备档加载
    global g_loadErr
    mapID = GameWorld.GetMap().GetMapID()
    if mapID not in Map2ServerID:
        return
    serverName = "S%s" % Map2ServerID[mapID]
def LoadGameWorldDataFromBackup():
    ## 备档加载服务器公共数据
    # @return: 是否存在备档成功加载
    serverID = GameWorld.GetGameWorld().GetServerID()
    serverName = "S%s" % serverID
    BakDir = os.path.join(BakRoot, serverName)
    GameWorld.Log("加载备档: %s" % BakDir)
    bakFileList = GetBakFileSortList(BakDir)
    if not bakFileList:
        GameWorld.Log("不存在备档!")
        return
    bakFilePath = bakFileList[0][1] # 取第一个为最近的一次备档
    finalBakInfo = bakFileList[0] # 排序后的第一个为最新备档数据
    bakVersionNO = finalBakInfo[1]
    bakFilePath = finalBakInfo[2]
    if bakVersionNO != DBStruct.GAMEWORLD_DATA_VERSION_NO:
        GameWorld.ErrLog("备档数据版本号校验失败,请使用正确版本服务器启动重新导入数据! bakVersionNO=%s, GAMEWORLD_DATA_VERSION_NO=%s"
                         % (bakVersionNO, DBStruct.GAMEWORLD_DATA_VERSION_NO))
        raise
    GameWorld.Log("读取备档: %s" % bakFilePath)
    
    f = open(bakFilePath, 'rb')
    compressed_data = f.read().strip()
    compressed_data = f.read()
    f.close()
    
    try:
        decompressed_data = zlib.decompress(compressed_data)
        bakData = binascii.a2b_hex(decompressed_data)
    except:
        g_loadErr = True
        raise
    saveData = CommFunc.Decompress(compressed_data)
    if not saveData:
        raise
    
    LoadPyGameData(bakData, 0)
    return
    LoadPyGameData(saveData, 0)
    SaveGameWorldDataToDB(saveData) # 如果是从备档读取的则直接入库
    return True
def ServerDataBackup():
def BackupGameWorldData(saveToDB=False):
    ## 服务器公共数据定时备档,暂时直接存盘
    if g_loadErr:
        GameWorld.ErrLog("加载备档已异常,暂时不在存储备档")
    # @param saveToDB: 是否附带入库,为确保备档数据存在时一定优于db库数据,故常规入库时必须附带先备档一次
    if PyGameData.g_serverClosing:
        return
    mapID = GameWorld.GetMap().GetMapID()
    if mapID not in Map2ServerID:
    if not PyGameData.g_loadDataOK:
        GameWorld.Log("加载数据未完成,不存储备档")
        return
    serverName = "S%s" % Map2ServerID[mapID]
    curTime = int(time.time())
    if saveToDB:
        if curTime - PyGameData.g_lastRTBTime < BackupInterval * 60:
            GameWorld.Log("备档冷却中!")
            return
    serverID = GameWorld.GetGameWorld().GetServerID()
    serverName = "S%s" % serverID
    BakDir = os.path.join(BakRoot, serverName)
    BackupCopyMax = 3
    GameWorld.Log("服务器备档: %s" % serverName)
    
    if not os.path.exists(BakDir):
        os.makedirs(BakDir)
        
    curTime = int(time.time())
    bakFilePath = os.path.join(BakDir, "%s_%s%s" % (serverName, curTime, BakFileType))
    bakData = GetSavePyData()
    GameWorld.Log("Bak:%s, len=%s, %s" % (serverName, len(bakData), bakFilePath))
    dataVersionNO = DBStruct.GAMEWORLD_DATA_VERSION_NO
    bakTiemStr = GameWorld.ChangeTimeNumToStr(curTime, ChConfig.TYPE_Time_Format_YmdHMS)
    bakFilePath = os.path.join(BakDir, "%s_%s_%s%s" % (serverName, dataVersionNO, bakTiemStr, BakFileType))
    saveData = GetSavePyData()
    GameWorld.Log("Bak:%s, len=%s, %s" % (serverName, len(saveData), bakFilePath))
    compressed_data = CommFunc.Compress(saveData) # 备档数据才进行压缩
    if not compressed_data:
        GameWorld.SendGameError("ServerDataBackupError")
        return
    GameWorld.Log("compress len=%s" % len(compressed_data))
    try:
        compressed_data = zlib.compress(bakData, 9)    #最大压缩
        GameWorld.Log("compress len=%s" % len(compressed_data))
        fp = open(bakFilePath, "wb")
        fp.write(compressed_data)
        fp.close()
    except:
        return
    
    PyGameData.g_lastRTBTime = curTime
    bakFileList = GetBakFileSortList(BakDir)
    # 删除多余备档
    for _, filePath in bakFileList[BackupCopyMax:]:
    for bakInfo in bakFileList[BackupCopyMax:]:
        filePath = bakInfo[-1]
        os.remove(filePath)
        GameWorld.Log("删除多余备档文件: %s" % filePath)
        
    if saveToDB:
        SaveGameWorldDataToDB(saveData)
    return
def ClearBackupFile():
    ## 清空备档文件
    serverID = GameWorld.GetGameWorld().GetServerID()
    serverName = "S%s" % serverID
    BakDir = os.path.join(BakRoot, serverName)
    GameWorld.Log("清空备档: %s" % BakDir)
    CommFunc.DelFolder(BakDir, True)
    return
#--------------------------------------------------------------------------------------------------
def LoadGameWorldDataFromDB():
    ## 直接从db读取公共数据
    GameWorld.Log("没有备档数据,直接从db加载数据!")
    serverData = PyMongoMain.GetUserCtrlDB().readGameWorldData()
    LoadPyGameData(serverData, 0)
    return
def SaveGameWorldDataToDB(saveData=""):
    ## 公共数据入库
    if not saveData:
        saveData = GetSavePyData()
    PyMongoMain.GetUserCtrlDB().saveGameWorldData(saveData)
    return
def GetSavePyData():
    
@@ -144,12 +198,7 @@
    pyGameDataMgr = GetDBDataMgr()
    result = pyGameDataMgr.GetSaveData()
    GameWorld.Log("GetSavePyData!! id = %s-%s"%(id(pyGameDataMgr), len(result)))
    result = binascii.b2a_hex(result)
    #GameWorld.DebugLog("GetSavePyData!! result = %s-%s"%(result, len(result)))
    # 字节码在C++转化会发生错误must be string without null bytes, not str,但是可以正常保存,错误会在下次调用便宜接口才会触发
    # 暂时改成字符串返回,暂无解决方案
    return result
#    return str(len(result)) + "|" + result
def LoadPyGameData(gameBuffer, pos):
    pyGameDataMgr = GetDBDataMgr()
@@ -159,27 +208,42 @@
    #加载数据后,一些功能转化为功能业务数据
    #...
    
    #放最后
    PyGameData.g_loadDataOK = True
    return pos
class PyGameDataManager(object):
    def __init__(self):
        self.EventTrigMgr = DBEventTrig.EventTrigMgr()
        self.PlayerViewCacheMgr = DBPlayerViewCache.PlayerViewCacheMgr()
        self.FamilyMgr = DBFamily.FamilyMgr()
        self.BillboardMgr = DBBillboard.BillboardMgr()
        self.MailMgr = DBMail.MailMgr()
        self.FamilyMgr = DBFamily.FamilyMgr()
        self.GameRecMgr = DBGameRec.GameRecMgr()
        self.FuncTeamMgr = DBFuncTeam.FuncTeamMgr()
        return
    
    def GetSaveData(self):
        buff = ""
        buff += self.EventTrigMgr.GetSaveData()
        buff += self.PlayerViewCacheMgr.GetSaveData()
        buff += self.FamilyMgr.GetSaveData()
        buff += self.BillboardMgr.GetSaveData()
        buff += self.MailMgr.GetSaveData()
        buff += self.FamilyMgr.GetSaveData()
        buff += self.GameRecMgr.GetSaveData()
        buff += self.FuncTeamMgr.GetSaveData()
        return buff
    
    def LoadGameData(self, gameBuffer, pos):
        dataslen = len(gameBuffer)
        pos = self.EventTrigMgr.LoadPyGameData(gameBuffer, pos, dataslen)
        pos = self.PlayerViewCacheMgr.LoadPyGameData(gameBuffer, pos, dataslen)
        pos = self.FamilyMgr.LoadPyGameData(gameBuffer, pos, dataslen)
        pos = self.BillboardMgr.LoadPyGameData(gameBuffer, pos, dataslen)
        pos = self.MailMgr.LoadPyGameData(gameBuffer, pos, dataslen)
        pos = self.FamilyMgr.LoadPyGameData(gameBuffer, pos, dataslen)
        pos = self.GameRecMgr.LoadPyGameData(gameBuffer, pos, dataslen)
        pos = self.FuncTeamMgr.LoadPyGameData(gameBuffer, pos, dataslen)
        return pos
    
def GetDBDataMgr():
@@ -189,6 +253,11 @@
        pyGameDataMgr = PyGameDataManager()
        PyGameData.g_pyGameDataManager = pyGameDataMgr
    return pyGameDataMgr
def GetEventTrigMgr():
    ## 事件值管理器
    dbDataMgr = GetDBDataMgr()
    return dbDataMgr.EventTrigMgr
def GetPlayerViewCacheMgr():
    ## 玩家查看缓存数据管理器
@@ -209,3 +278,17 @@
    dbDataMgr = GetDBDataMgr()
    return dbDataMgr.MailMgr
def GetBillboardMgr():
    ## 排行榜数据管理器
    dbDataMgr = GetDBDataMgr()
    return dbDataMgr.BillboardMgr
def GetGameRecMgr():
    ## 通用记录数据管理器
    dbDataMgr = GetDBDataMgr()
    return dbDataMgr.GameRecMgr
def GetFuncTeamMgr():
    ## 功能队伍数据管理器
    dbDataMgr = GetDBDataMgr()
    return dbDataMgr.FuncTeamMgr