local skynet = require "skynet" local oo = require "Class" local gameCmd = require "GameCmd" local json =require "json" local log = require "Log" local sqlUrl = require "SqlUrl" local defense = require "Defense" local playerFields = require "PlayerFields" local errorInfo = require "ErrorInfo" local serverId = tonumber(skynet.getenv "serverId") local redisKeyUrl = require "RedisKeyUrl" local pb = require "pb" local dbData = require "DBData" local player = require "Player" local dataType = require "DataType" local AccountServer = oo.class() AccountServer.Platform_local = "Local" AccountServer.Platform_WeChat = "WeChat" AccountServer.PerSaveMaxCount = 10 --每次保存最多个数 --初始化 function AccountServer:Init() self.platformType = {} table.insert( self.platformType , "Apple") --初始化默认DB索引(千成不能删除,非常重要 开始)!!!!!!!!!!!!!!! --[[ local key = string.format(redisKeyUrl.AccountServerCurDBIndex ) if not skynet.server.redis:exists( key) then skynet.server.redis:set( key , 0 ) end for i = 1, skynet.server.gameConfig.DBInfoConfig.playerTableCount, 1 do key = string.format(redisKeyUrl.AccountServerUserDBID , i ) if not skynet.server.redis:exists( key) then skynet.server.redis:set( key , 10000000 * i ) end end local key = string.format(redisKeyUrl.GameServerActivityID ) if not skynet.server.redis:exists( key) then skynet.server.redis:set( key , 0 ) end ]] --!!!!!!!!!!!!重要结束!!!!!!!!!!!!!!!!!!!!! end --跨天 function AccountServer:OnNewDay() end --1秒Timer function AccountServer:On1SecTimer() if not self:IsAccountServer( serverId ) then return end end --5秒Timer function AccountServer:On5SecTimer() self:CheckOfflineUser() --self:IntervalSaveUser() end --间隔保存用户信息 function AccountServer:IntervalSaveUser() local playerList = skynet.server.playerCenter:GetPlayerList() local saveCount = 0 for userId, value in pairs( playerList ) do if saveCount >= 5 then break end if value.isIntervalSave and skynet.GetTime() - value.lastIntervalSaveTime >= 10 then log.info("玩家" ,userId, "五分钟保存一次数据") value.isIntervalSave = false if self:SavePlayerToDB( value.player , true ) then saveCount = saveCount +1 end end end end --检查玩家是否超时未游戏 function AccountServer:CheckOfflineUser() local playerList = skynet.server.playerCenter:GetPlayerList() local saveCount = 0 for userId, value in pairs( playerList ) do if saveCount >= self.PerSaveMaxCount then break end --玩家ping超时,就设为掉线状态 if 0 == skynet.AddTime and skynet.server.playerCenter.Status_Playing == value.status and 0 ~= value.pingTime and skynet.GetTime() - value.pingTime >= skynet.server.gameServer.curServerConfig.CheckPingTimeout then log.info(string.format("玩家 %d Ping超时 %d 强制让玩家掉线 " , userId , skynet.GetTime() - value.pingTime)) skynet.server.playerCenter:UserOffline( userId ) skynet.server.playerCenter:CloseSocket( value.socketId ) end if skynet.server.playerCenter.Status_Offline == value.status and 0 ~= value.offlineTime and skynet.GetTime() - value.offlineTime >= skynet.server.gameServer.curServerConfig.CheckOfflineUserTime then log.info("玩家" ,userId, "超时未游戏,已被服务器下线") if self:SavePlayerToDB( value.player , false ) then saveCount = saveCount +1 end end end end --用户登陆游戏 function AccountServer:UserLoginGame( c2sData , s2cData ) local platform = c2sData.data.platform local account = c2sData.data.loginAccount local t1 = skynet.GetTime() --检查是创建还是登陆 local redisKey = string.format(redisKeyUrl.AccountServerUserList , platform , account ) if not skynet.server.redis:exists( redisKey) then --不存在帐号,创建帐号 self:UserCreateToDB( c2sData , s2cData ) else --这里肯定是存在玩家帐号的 local dbIndex = skynet.server.redis:hget( redisKey , "DBIndex") local userId = tonumber(skynet.server.redis:hget( redisKey , "UserID")) --从REDIS中取出来的数据会变成字符串 log.info("玩家登陆游戏" , dbIndex , userId , platform , account ) --根据玩家是否在线来决定从哪里去取数据 local isExistOnlinePlayer = skynet.server.playerCenter:IsExistPlayer( platform , account ) if isExistOnlinePlayer then --直接从内存中加载数据 self:LoadUserFromMem( account , userId , s2cData ) else --玩家数据不在内存中,从DB中加载 self:LoadUserFromDB( c2sData , s2cData , dbIndex , userId ) end end if errorInfo.Suc == s2cData.code then --将该玩家的ID放入在线玩家列表中 log.info("将玩家UserID 加入到在线列表成功 ",account ) redisKey = string.format(redisKeyUrl.AccountServerOnlinePlayerList , c2sData.data.platform , serverId ) skynet.server.redis:sadd( redisKey , account ) s2cData.data.playerData = json:encode(s2cData.data.playerData) else log.info("将玩家UserID 加入到在线列表失败 ",account , s2cData.code) end end --创建用户数据到DB中 function AccountServer:UserCreateToDB( c2sData , s2cData ) local platform = c2sData.data.platform local account = c2sData.data.loginAccount local version = c2sData.data.version s2cData.code = errorInfo.Suc s2cData.data = {} --检查服务器是否能注册 if not skynet.server.gameServer.curServerConfig.IsCanReg then s2cData.code = errorInfo.ErrorCode.ForbidUserReg return s2cData end local rand = 0 local token = "" local sql = nil local queryData = nil local curId = nil --分配DB索引 local redisKey = string.format(redisKeyUrl.AccountServerCurDBIndex ) local dbIndex = skynet.server.redis:incr(redisKey) if dbIndex > skynet.server.gameConfig.DBInfoConfig.playerTableCount then skynet.server.redis:set(redisKey , 1) dbIndex = 1 end --获取最新DBID redisKey = string.format(redisKeyUrl.AccountServerUserDBID , dbIndex ) local curId = skynet.server.redis:incr(redisKey) --所有玩家列表到redis local keyUserList = string.format(redisKeyUrl.AccountServerUserList , platform , account ) skynet.server.redis:hset( keyUserList , "DBIndex" ,dbIndex ) skynet.server.redis:hset( keyUserList , "UserID" ,curId ) --所有玩家列表到DB sql = string.format(sqlUrl.insertUserDBIndexToUser , curId , platform , account , dbIndex ) queryData = skynet.server.db:Query( "account" , sql ) if 1 ~= queryData.affected_rows then log.info("插入DBIndex失败",skynet.server.common:TableToString(queryData) ,sql) s2cData.code = errorInfo.ErrorCode.AlreadyCreateAccount log.info("创建user数据失败,删除redis中的key ",keyUserList) skynet.server.redis:del(keyUserList) return s2cData end --组装玩家的游戏数据 local newPlayer = {} newPlayer.basicInfo = {} dbData:Traversal( newPlayer.basicInfo , playerFields.InitBasicInfo ) newPlayer.basicInfo.userID = tonumber(curId) newPlayer.basicInfo.platform = platform newPlayer.basicInfo.accountName = account newPlayer.basicInfo.appVersion = c2sData.data.version newPlayer.basicInfo.channel = c2sData.data.channel newPlayer.basicInfo.regTime = skynet.GetTime() newPlayer.basicInfo.regIP = c2sData.addr newPlayer.basicInfo.lastLoginIP = c2sData.addr newPlayer.basicInfo.lastLoginTime = skynet.GetTime() newPlayer.basicInfo.lastExitTime = 0 --游戏数据 newPlayer.gameData = {} --非存档用户可以初始化线上数据 dbData:Traversal( newPlayer.gameData , playerFields.InitGameData ) --存档数据 newPlayer.archiveData = "{}" --在player表中创建玩家的数据 sql = string.format(sqlUrl.insertAccountToPlayer ,curId , account , platform , json:encode( newPlayer.basicInfo ) , json:encode( newPlayer.gameData ) , newPlayer.archiveData ) queryData = skynet.server.db:QueryPlayer( dbIndex , sql ) if 1 ~= queryData.affected_rows then log.info("未成功创建用户ID信息 ",account) log.info("失败SQL ",sql) log.info("创建player数据失败,删除user中的玩家ID",curId) sql = string.format(sqlUrl.deleteUserDBIndexToUser , curId) skynet.server.db:Query( "account" , sql ) log.info("创建player数据失败,删除redis中的key",keyUserList) skynet.server.redis:del(keyUserList) s2cData.code = errorInfo.ErrorCode.AlreadyCreateAccount return s2cData end --创建后再进入游戏 log.info("创建帐号并登陆成功 用户ID",curId,account) local userData = {} userData.userId = curId userData.account = account userData.playerData = newPlayer userData.announce = "" s2cData.data = userData end --从内存中加载玩家数据 function AccountServer:LoadUserFromMem( account , userId , s2cData ) local player = skynet.server.playerCenter:GetPlayer( userId ) if not player then log.info(string.format("玩家 %d 从内存中加载失败",userId )) s2cData.code = errorInfo.ErrorCode.NoExistUser return end local playerData = {} playerData.basicInfo = player.basicInfo playerData.gameData = player.gameData playerData.archiveData = player.archiveData --用户数据 local userData = {} userData.userId = userId userData.account = account userData.playerData = playerData userData.announce = "" s2cData.data = userData log.info(string.format("玩家 %d 从内存中加载数据",userId)) end --从DB中加载玩家数据 function AccountServer:LoadUserFromDB( c2sData , s2cData , dbIndex , userId ) local platform = c2sData.data.platform local account = c2sData.data.loginAccount --从玩家表中获取数据 local sql = string.format(sqlUrl.queryAccountFromPlayer , userId ) local queryData = skynet.server.db:QueryPlayer( dbIndex , sql ) queryData = queryData[1] local basicInfo = json:decode(queryData.BasicInfo) local gameData = json:decode(queryData.GameData) local archiveData = queryData.ArchiveData log.info(string.format("玩家 %s 平台 %s BasicInfo %s GameData %s", account , platform , queryData.BasicInfo , queryData.GameData)) if defense.AccountStatus_Black == basicInfo.Status then log.info("玩家",userId ,"已经被列入黑名单,无法登陆游戏") s2cData.code =errorInfo.ErrorCode.BlackList return s2cData end --检查是否有新字段,有新字段进行处理 dbData:CheckNewFields( basicInfo , playerFields.InitBasicInfo) dbData:CheckNewFields( gameData , playerFields.InitGameData) local playerData = {} playerData.basicInfo = basicInfo playerData.gameData = gameData playerData.archiveData = archiveData --处理一下邮件的转义问题 for k ,v in pairs(playerData.gameData.mail.mailList) do v.content = string.gsub(v.content,"\n","\\n") v.content = string.gsub(v.content,"\r","\\r") end --用户数据 local userData = {} userData.userId = userId userData.account = account userData.playerData = playerData userData.announce = "" s2cData.data = userData log.info(string.format("玩家 %d 从DB中加载数据",userId)) end --同步玩家数据到Redis function AccountServer:SyncPlayerDataToRedis( playerData , redisKey ) local basickey = "" if not redisKey then basickey = string.format(redisKeyUrl.AccountServerOnlinePlayer , playerData.basicInfo.platform , playerData.basicInfo.userID ) else basickey = string.format("%s" , redisKey) end for k, v in pairs( playerData ) do if "table" == type( v ) then self:SyncPlayerDataToRedis( v , string.format("%s:%s" , basickey , k )) else skynet.server.redis:hset(basickey,k,v) end end end --从Redis获取玩家数据 function AccountServer:GetPlayerFromRedis( id , redisKeyUrl , initData ,data ) if -1 == id then return end for k, v in pairs( initData ) do if "table" == type(v[2]) then redisKeyUrl = string.format("%s:%s" , redisKeyUrl , v[1] ) data[ v[1] ] = {} self:GetPlayerFromRedis( id , redisKeyUrl , v[2] , data[ v[1] ] ) else data[ v[1] ] = skynet.server.redis:hget(redisKeyUrl, v[1] ) end end end --从Redis中删除玩家数据 function AccountServer:DeletePlayerDataFromRedis( userId , redisKeyUrl , initData ) for k, v in pairs( initData ) do if "table" == type(v[2]) then redisKeyUrl = string.format("%s:%s" , redisKeyUrl , v[1] ) self:DeletePlayerDataFromRedis( userId , redisKeyUrl , v[2] ) else local fields = skynet.server.redis:hkeys(redisKeyUrl) for k, v in pairs(fields) do skynet.server.redis:hdel(redisKeyUrl , v) end end end end --保存玩家数据到DB function AccountServer:SavePlayerToDB( player , isIntervalSave ) function Do() local platform = player.basicInfo.platform local account = player.basicInfo.accountName local userId = player.basicInfo.userID local basicInfo = player.basicInfo local gameData = player.gameData local archiveData =player.archiveData --计算在线时间 basicInfo.allGameTime = basicInfo.allGameTime + (os.time() - player.tmpData.intoTime) --获取数据库索引 local redisKey = string.format(redisKeyUrl.AccountServerUserList , platform , account ) local dbIndex = skynet.server.redis:hget( redisKey , "DBIndex") --更新玩家所有数据到数据库 local sql = string.format(sqlUrl.saveAccountToPlayer , json:encode(basicInfo) , json:encode(gameData) , archiveData , userId ) skynet.server.db:QueryPlayer( dbIndex , sql ) log.info(string.format("玩家 %d 成功保存数据到DB 数据库索引 %d ", userId , dbIndex )) --不是间隔保存才会剔除玩家 if not isIntervalSave then --让玩家从在线列表中删除 redisKey = string.format(redisKeyUrl.AccountServerOnlinePlayerList , platform , serverId ) skynet.server.redis:srem( redisKey , account ) skynet.server.playerCenter:UserExit( userId ) end end local ret,err = pcall(Do) if not ret then log.info("内部错误信息 保存当前玩家失败",player.basicInfo.userID ,ret ,err) return false end return true end skynet.server.accountServer = AccountServer return AccountServer