local skynet = require "skynet" local oo = require "Class" local log = require "Log" local json =require "json" local sqlUrl = require "SqlUrl" local curServerId = tonumber(skynet.getenv "serverId") local manageCmd = require "ManageCmd" local clusterServer = require "ClusterServer" local errorInfo = require "ErrorInfo" local redisKeyUrl = require "RedisKeyUrl" local ServerManage = oo.class() function ServerManage:Init() end --获取服务器 function ServerManage:GetServer() local server = nil --找出对应的服务器信息 if curServerId == clusterServer.centerServerID then server = skynet.server.centerServer elseif curServerId == clusterServer.multiServerID then server = skynet.server.multiServer elseif curServerId == clusterServer.payServerID then server = skynet.server.payServer elseif curServerId >= clusterServer.routeServerMinID and curServerId <= clusterServer.routeServerMaxID then server = skynet.server.routeServer elseif curServerId >= clusterServer.gameServerMinID and curServerId <= clusterServer.gameServerMaxID then server = skynet.server.gameServer end return server end --执行服务器命令 function ServerManage:DoManageCmd( c2sData , s2cData) local cmd = c2sData.cmd local userId = c2sData.userId or nil local value = c2sData.value or nil s2cData.data = {} s2cData.data.serverId = curServerId s2cData.data.isSuc = true log.info("执行服务器命令" ,cmd ,userId ,value) if cmd > manageCmd.AllServerStart and cmd < manageCmd.AllServerEnd then --所有服务器 local server = self:GetServer() if manageCmd.ReloadClusterConfig == cmd then self:ReloadClusterConfig( server ) elseif manageCmd.Reg == cmd then self:Reg( server ) elseif manageCmd.UnReg == cmd then self:UnReg( server ) elseif manageCmd.ReConnectDB == cmd then self:ReConnectDB( server ) elseif manageCmd.ShutDown == cmd then self:ShutDown( server , c2sData , s2cData ) end elseif curServerId == clusterServer.centerServerID then --中心服 if manageCmd.QueryPlayerDBInfo == cmd then self:QueryPlayerDBInfo( c2sData , s2cData ) elseif manageCmd.QueryPlayerOnlineInfo == cmd then self:QueryPlayerOnlineInfo( c2sData , s2cData ) elseif manageCmd.RestartGameServer == cmd then self:RestartGameServer( c2sData , s2cData ) end elseif curServerId >= clusterServer.routeServerMinID and curServerId <= clusterServer.routeServerMaxID and cmd > manageCmd.RouteServerStart and cmd < manageCmd.RouteServerEnd then --路由服务器 if manageCmd.RouteSyncWhiteList == cmd then self:RouteSyncWhiteList( c2sData , s2cData ) elseif manageCmd.RouteSyncBlackList == cmd then self:RouteSyncBlackList( c2sData , s2cData ) end elseif curServerId >= clusterServer.gameServerMinID and curServerId <= clusterServer.gameServerMaxID and cmd > manageCmd.GameServerStart and cmd < manageCmd.GameServerEnd then --游戏服务器 if manageCmd.NeedPlayerCount == cmd then self:NeedPlayerCount( c2sData , s2cData) elseif manageCmd.SaveUserToDB == cmd then self:SaveUserToDB( c2sData , s2cData) elseif manageCmd.SaveUserToTxt == cmd then self:SaveUserToTxt( c2sData , s2cData ) elseif manageCmd.ForceUserOffline == cmd then self:ForceUserOffline( c2sData , s2cData ) elseif manageCmd.QueryPlayerInServer == cmd then self:QueryPlayerInServer( c2sData , s2cData ) elseif manageCmd.UpdateGameVersion == cmd then self:UpdateGameVersion( s2cData ) elseif manageCmd.UpdateUserData == cmd then self:UpdateUserData( c2sData , s2cData ) elseif manageCmd.SaveAllUserDataToFile == cmd then self:SaveAllUserDataToFile( c2sData , s2cData ) elseif manageCmd.ReStartTimer == cmd then self:ReStartTimer( c2sData , s2cData ) end else s2cData.code = errorInfo.ErrorCode.ErrRequestParam end if errorInfo.Suc ~= s2cData.code then s2cData.data.isSuc = false end return s2cData end --注册服务器 function ServerManage:Reg( server ) skynet.server.clusterServer:Reg() log.info("成功注册服务") end --注销服务器 function ServerManage:UnReg( server ) skynet.server.clusterServer:UnReg() log.info("成功注销服务") end --重连数据库 function ServerManage:ReConnectDB( server ) log.info("开始 重新连接数据库") skynet.InitDB( true ) log.info("结束 重新连接数据库") end --关闭服务器 function ServerManage:ShutDown( server , c2sData , s2cData ) skynet.server.clusterServer:UnReg() if server:StopServer( c2sData , s2cData ) then log.info("强制关闭服务器成功") else log.info("强制关闭服务器失败") end end --关闭服务器 function ServerManage:ReloadClusterConfig( server ) server:ReloadClusterNode() log.info("成功载入集群配置") end --查询玩家DB信息 function ServerManage:QueryPlayerDBInfo( c2sData , s2cData) local account = c2sData.account local userDBInfo = skynet.server.redisCenter:GetRedisDBInfo( account ) s2cData.data.userDBInfo = userDBInfo end --查询玩家在线信息 function ServerManage:QueryPlayerOnlineInfo( c2sData , s2cData) local account = c2sData.account local redisKey = string.format( redisKeyUrl.RouteServerLoginInfoHash , account ) local queryData = skynet.server.redis:hgetall( redisKey ) queryData = redisKeyUrl:CovertTable(queryData) s2cData.data.onlineInfo = queryData end --重启游戏服 function ServerManage:RestartGameServer( c2sData , s2cData) local startServerId = c2sData.startServerId --游戏服起始ID local endServerId = c2sData.endServerId --游戏服结束ID local opTime = c2sData.opTime --操作时间 if nil == endServerId then endServerId = startServerId end if nil == opTime or "" == opTime then s2cData.code = errorInfo.ErrorCode.ErrRequestParam return s2cData end --将时间格式 2022-02-22 22:22:22 转为时间戳 local tmpOpTime = opTime opTime = skynet.server.common:GetTime( opTime ) --将执行计划写入redis local planInfo = {} planInfo.startServerId = startServerId planInfo.endServerId = endServerId planInfo.opTime = opTime for k, v in pairs( skynet.server.clusterServer.clusterInfo ) do if v.serverId >= skynet.server.clusterServer.routeServerMinID and v.serverId <= skynet.server.clusterServer.routeServerMaxID then skynet.server.redis:hset( redisKeyUrl.RestartPlanHSet , v.serverId , json:encode( planInfo ) ) end end log.info(string.format("成功写入重启计划 游戏服ID 起始 %d 结束 %d 开始时间 %s", startServerId , endServerId, tmpOpTime)) end --路由服加入白名单 function ServerManage:RouteSyncWhiteList( c2sData , s2cData ) skynet.server.routeServer:SyncWhiteList() s2cData.data.isSuc = true end --路由服加入黑名单 function ServerManage:RouteSyncBlackList( c2sData , s2cData ) skynet.server.routeServer:SyncBlackList() s2cData.data.isSuc = true end --需要玩家数量 function ServerManage:NeedPlayerCount( c2sData , s2cData) local param = c2sData.param skynet.server.gameServer.serverInfo.needPlayerCount = tonumber( param.needCount ) log.info(string.format("游戏服需要 %d 个玩家", skynet.server.gameServer.serverInfo.needPlayerCount)) end --保存用户数据到DB function ServerManage:SaveUserToDB( c2sData , s2cData) local account = c2sData.account local player = skynet.server.playerCenter:GetPlayerForAccount( account ) if not player then s2cData.code = errorInfo.ErrorCode.NoExistUser else local sucSave = skynet.server.accountCenter:SavePlayerToMysql( player ) if sucSave then log.info(string.format("玩家 %s 强制将玩家数据保存到Mysql成功", account )) else --强制失败,先存玩家的sql,如果无法组装数据,只能打印成文本 s2cData.code = errorInfo.ErrorCode.OpFailed log.info(string.format("玩家 %s 强制将玩家数据保存到Mysql失败", account )) skynet.server.accountCenter:SaveFailedPlayerDataToFile( player ) end end end --保存用户数据到Txt function ServerManage:SaveUserToTxt( c2sData , s2cData ) local account = c2sData.account local param = c2sData.param local player = skynet.server.playerCenter:GetPlayerForAccount( account ) if not player then s2cData.code = errorInfo.ErrorCode.NoExistUser else local basicInfo = player.basicInfo local gameData = player.gameData local file = io.open("UserData.txt", "a+") file:write( account ) if param.isJson then file:write( json:encode( basicInfo )) file:write("\r\n") file:write( json:encode( gameData )) file:write("\r\n") log.info(string.format("玩家 %s 强制将玩家数据以Json格式保存到Txt ", account )) else file:write( skynet.server.common:TableToString( basicInfo )) file:write("\r\n") file:write( skynet.server.common:TableToString( gameData )) file:write("\r\n") log.info(string.format("玩家 %s 强制将玩家数据以文本格式保存到Txt ", account )) end file:close() end end --强制玩家下线 function ServerManage:ForceUserOffline( c2sData , s2cData) local account = c2sData.account local player = skynet.server.playerCenter:GetPlayerForAccount( account ) if not player then s2cData.code = errorInfo.ErrorCode.NoExistUser else local sucSave = skynet.server.accountCenter:SavePlayerToMysql( player ) if sucSave then log.info(string.format("玩家 %s 强制玩家下线并保存到Mysql成功", account )) else --强制失败,先存玩家的sql,如果无法组装数据,只能打印成文本 s2cData.code = errorInfo.ErrorCode.OpFailed log.info(string.format("玩家 %s 强制玩家下线并保存到Mysql失败", account )) skynet.server.accountCenter:SaveFailedPlayerDataToFile( player ) end skynet.server.accountCenter:OfflineProcess( player ) end end --查询玩家在哪个游戏服 function ServerManage:QueryPlayerInServer( c2sData , s2cData ) local playerList = skynet.server.playerCenter:GetPlayerList() for curUseId, value in pairs( playerList ) do if curUseId == userId then s2cData.data.isSuc = true break end end end --更新游戏版本号 function ServerManage:UpdateGameVersion( s2cData ) skynet.server.redis:incr( redisKeyUrl.GameServerVersion ) end --更新玩家数据(玩家可能在线,某种数据,导致无法数据落地,我们将进行修复) function ServerManage:UpdateUserData( c2sData , s2cData ) local playerList = skynet.server.playerCenter:GetPlayerList() for curUseId, v in pairs( playerList ) do if curUseId == userId then local resetKey = c2sData.resetKey local resetValue = c2sData.resetValue local listResetKey = skynet.server.common:Split( resetKey , ".") local gameData = v.player[ c2sData.dataType ] --s2cData.data.isSuc = true break end end end --保存所有玩家数据到文件 function ServerManage:SaveAllUserDataToFile( c2sData , s2cData ) --保存玩家数据的文件列表 local fileList = {} for i = 1, 5, 1 do local fileName = string.format("ForceSaveUserDataToFile_%d_%d.txt", curServerId , i) fileList[ i ] = io.open( fileName , "w+") end local playerList = skynet.server.playerCenter:GetPlayerList() for curUseId, v in pairs( playerList ) do function Do() local basicInfo = v.player.basicInfo local gameData = v.player.gameData local dbIndex = basicInfo.dbIndex local userId = basicInfo.userID local account = basicInfo.accountName local sql = string.format(sqlUrl.saveAccountToPlayer , json:encode(basicInfo) , json:encode(gameData) , userId ) sql = string.format("%s|%d|%s \r\n" , account, userId , sql) fileList[ dbIndex ]:write( sql ) log.info(string.format("玩家 %d 成功保存玩家数据sql到文件", userId )) end local isDo,callData = pcall( Do ) if not isDo then local errorText = "保存所有玩家数据到文件 失败" if curUseId then log.warning(errorText , curUseId , callData ) else log.warning( errorText , callData) end end end for i = 1, 5, 1 do fileList[ i ]:close() end end --重启Timer function ServerManage:ReStartTimer( c2sData , s2cData ) local timerType = c2sData.timerType if 1 == timerType then skynet.fork(skynet.SaveMongoDBTimer,500) --500 elseif 2 == timerType then skynet.fork(skynet.SaveMysqlDBTimer,500) --500 end log.info("重启Timer成功 TimerType" , timerType) end skynet.server.serverManage = ServerManage return ServerManage