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 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 queue = require "Queue" local AccountCenter = oo.class() AccountCenter.Platform_local = "Local" AccountCenter.Platform_WeChat = "WeChat" AccountCenter.PerSaveMaxCount = 50 --每次保存最多个数 AccountCenter.SaveMysqlDBFailedMaxCount = 3 --保存mysql失败最大次数 --处理老IOS用户 function AccountCenter:IosOldPlayer( mobile ) if skynet.GetTime() >= 1719849600 then --超过这个时间 2024-07-02 00:00:00 不再处理 return false , "" , 0 , 0 end local redisKey = redisKeyUrl.AccountServerOldIOSPlayerSet if not skynet.server.redis:sismember( redisKey , mobile ) then return false , "" , 0 , 0 end local sql = string.format(sqlUrl.queryAccountFromOldIosPlayer , mobile ) local queryData = skynet.server.db:Query("account" , sql ) if queryData and queryData[1] then --从redis删除标记 skynet.server.redis:srem( redisKey , mobile ) --数据库里更新状态 sql = string.format(sqlUrl.updateAccountToOldIosPlayer , 1 , mobile ) skynet.server.db:Query("account" , sql ) return true, queryData[1].ArchiveData , queryData[1].PayCount , queryData[1].InviteCount --获取存档数据 end return false , "" , 0 , 0 end --初始化 function AccountCenter:Init() --初始化默认DB索引(千成不能删除,非常重要 开始)!!!!!!!!!!!!!!! --[[ local key = nil 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 --设置游戏服的版本号 if not skynet.server.redis:exists( key) then skynet.server.redis:set( redisKeyUrl.GameServerVersion , 1 ) end ]] --!!!!!!!!!!!!重要结束!!!!!!!!!!!!!!!!!!!!! end --跨天 function AccountCenter:OnNewDay() end --1秒Timer function AccountCenter:On1SecTimer() if not self:IsAccountCenter( serverId ) then return end end --检查玩家是否符合普通保存策略 function AccountCenter:CheckNormalToMongo() local playerList = skynet.server.playerCenter:GetPlayerList() local saveCount = 0 local saveUserMaxTime = 600 --玩家数据有变动最长3分钟保存1次 180 local saveUserMaxCount = 15 --一次保存10个 local nowTime = skynet.GetTime() for userId, value in pairs( playerList ) do if saveCount >= saveUserMaxCount then --一次保存200个 break end --游戏进行中 时间超过十分钟 if dataType.SaveLevel_Normal == value.player.tmpData.saveLevel and nowTime - value.lastSaveTime >= saveUserMaxTime then value.lastSaveTime = nowTime --保存了,刷新一下间隔保存时间 if self:SavePlayerToMongo( value.player ) then saveCount = saveCount +1 end end end if saveCount > 0 then log.info(string.format("间隔保存 一般策略 保存数量 %d 耗时 %d" , saveCount , skynet.GetTime() - nowTime )) end end --检查玩家是否有充值 function AccountCenter:CheckPayToMongo() local playerList = skynet.server.playerCenter:GetPlayerList() local saveCount = 0 --落地玩家数量 local nowTime = skynet.GetTime() for userId, value in pairs( playerList ) do --玩家充过值,优先数据落地 if dataType.SaveLevel_Pay == value.player.tmpData.saveLevel then value.lastSaveTime = nowTime --优先保存了,刷新一下间隔保存时间 if self:SavePlayerToMongo( value.player ) then saveCount = saveCount +1 end end if saveCount >= 10 then break end end if saveCount > 0 then log.info(string.format("间隔保存 充值优先 保存数量 %d 耗时 %d" , saveCount , skynet.GetTime() - nowTime )) end end --检查关键数据变化保存到mongo function AccountCenter:CheckKeyDataChangeToMongo() local playerList = skynet.server.playerCenter:GetPlayerList() local saveCount = 0 local saveUserMaxTime =60 --背包变化最长1分钟保存1次 local saveUserMaxCount = 20 --一次保存20个 local nowTime = skynet.GetTime() for userId, value in pairs( playerList ) do if saveCount >= saveUserMaxCount then --一次保存20个 break end --游戏进行中 时间超过十分钟 if dataType.SaveLevel_KeyDataChange == value.player.tmpData.saveLevel and nowTime - value.lastSaveTime >= saveUserMaxTime then value.lastSaveTime = nowTime --优先保存了,刷新一下间隔保存时间 if self:SavePlayerToMongo( value.player ) then saveCount = saveCount +1 end end end if saveCount > 0 then log.info(string.format("间隔保存 关键数据变化 保存数量 %d 耗时 %d" , saveCount , skynet.GetTime() - nowTime )) end end --间隔保存用户信息 function AccountCenter:CheckSaveMongoDB() --[[ if skynet.server.gameServer.isStopServerSaveAccount then log.info("AccountCenter:CheckSaveMongoDB 停服保存数据中,暂时不执行该Timer") return end ]] --优先保存充值用户数据 self:CheckPayToMongo() --优先关键数据变化保存 self:CheckKeyDataChangeToMongo() --一般策略保存 self:CheckNormalToMongo() log.info("成功执行 CheckSaveMongoDB") end --检查玩家是否超时未游戏 function AccountCenter:CheckSaveMysqlDB() function Do() --[[ if skynet.server.gameServer.isStopServerSaveAccount then log.info("AccountCenter:CheckSaveMysqlDB 停服保存数据中,暂时不执行该Timer") return end ]] local playerList = skynet.server.playerCenter:GetPlayerList() local offlineCount = 0 --下线玩家数量 local saveCount = 0 --落地玩家数量 local curTime = skynet.GetTime() local checkPingTimeout = 180 --正常情况下3分钟断线 local checkOfflineUserTime = 1200 --下线后20分钟未登陆就数据落地 self.PerSaveMaxCount = 50 --每次保存10个 --测试数据,测试时可以打开 --checkPingTimeout = 30 --checkOfflineUserTime = 10 --如果服务器停止了,就尽快让玩家数据落地 if skynet.server.clusterServer and skynet.server.clusterServer.isForceDisconnectCenter then checkPingTimeout = 60 --服务器停止,ping超时时间设为60秒 checkOfflineUserTime = 90 --服务器停止,离线时间设为90秒 self.PerSaveMaxCount = 20 --服务器停止,大量服务器都会开始保存数据,一次最多写2个玩家数据,以免把数据库搞崩 end for userId, value in pairs( playerList ) do if value then local dbIndex = value.player.basicInfo.dbIndex local account = value.player.basicInfo.accountName --玩家游戏状态中,已经ping超时,就设为掉线状态 if 0 == skynet.AddTime and skynet.server.playerCenter.Status_Playing == value.status and curTime - value.pingTime >= checkPingTimeout then skynet.server.playerCenter:UserOffline( userId ) skynet.server.playerCenter:CloseSocket( value.netType , value.socketId , value.agentId ) offlineCount = offlineCount + 1 log.info(string.format("玩家 %d netType %d Ping超时 %d 秒 强制让玩家掉线" , userId , value.netType , curTime - value.pingTime)) end --玩家离线状态中,掉线超过指定时间,就进行数据落地 if skynet.server.playerCenter.Status_Offline == value.status and curTime - value.offlineTime >= checkOfflineUserTime then if self:SavePlayerToMysql( value.player ) then saveCount = saveCount +1 self:OfflineProcess( value.player ) log.info(string.format("玩家 %d 数据库索引 %d 保存数据到MysqlDB成功", userId , dbIndex )) else value.saveMysqlCount = value.saveMysqlCount + 1 value.offlineTime = value.offlineTime + 300 --离线时间延长5分钟,后面再次尝试保存数据 log.info(string.format("玩家 %d 数据库索引 %d 保存数据到MysqlDB失败%d次", userId , dbIndex , value.saveMysqlCount )) --失败保存次数达到最大次数,将玩家数据强制写到文件当中 if value.saveMysqlCount >=self.SaveMysqlDBFailedMaxCount then local basicInfo = value.player.basicInfo local gameData = value.player.gameData --组装下玩家数据,写到文件中,方便恢复 self:SaveFailedPlayerDataToFile( value.player ) self:OfflineProcess( value.player ) local errorText = string.format("玩家 %d 数据库索引 %d 保存数据到MysqlDB失败%d次 已达最大次数5次 ", userId , dbIndex , value.saveMysqlCount or 0 ) skynet.server.clusterServer:SendErrorInfoToCenter( account , errorInfo.ErrorCode.SaveUserDataFailed , errorText ) end end end end if saveCount >= self.PerSaveMaxCount then break end end log.info(string.format("成功执行 CheckSaveMysqlDB 下线数量 %d 落地保存数量 %d" , offlineCount , saveCount )) return true end local ret,err = pcall(Do) if not ret or not err then log.info("内部错误信息 AccountCenter:CheckSaveMysqlDB 检查玩家是否超时未游戏",ret ,err) return false end end --下线后一些处理 function AccountCenter:OfflineProcess( player ) function Do() local account = player.basicInfo.accountName local userId = player.basicInfo.userID --让玩家从在线列表中删除 local redisKey = string.format( redisKeyUrl.RouteServerLoginInfoHash , account ) skynet.server.redis:del( redisKey ) --触发离线订阅 skynet.server.subscribe:UpdateOfflineTime( player ,1) skynet.server.playerCenter:UserExit( userId ) return true end local ret,err = pcall(Do) if not ret or not err then --保存玩家数据报错了,有可能是数据有问题,无法转成json,所以直接打到文件中,方便查看 log.info("内部错误信息 AccountCenter:OfflineProcess 下线后一些处理失败", ret ,err) return false end return true end --用户登陆游戏 function AccountCenter:UserLoginGame( c2sData , s2cData ) local platform = c2sData.data.platform local account = c2sData.data.loginAccount --迁移redis相关 skynet.server.migrateData:AccountData( account ) --检查是创建还是登陆 local userDBInfo = skynet.server.redisCenter:GetRedisDBInfo( account ) local redisUserListKey = string.format( redisKeyUrl.AccountServerUserList , account ) if not userDBInfo and not skynet.server.redis:exists( redisUserListKey ) then --不存在帐号,创建帐号 self:UserCreateToDB( c2sData , s2cData ) else --这里肯定是存在玩家帐号的 local dbIndex = userDBInfo.DBIndex local userId = userDBInfo.UserID --从REDIS中取出来的数据会变成字符串 log.info("玩家登陆游戏" , dbIndex , userId , platform , account ) --根据玩家是否在线来决定从哪里去取数据 local isExistOnlinePlayer = skynet.server.playerCenter:IsExistPlayer( account ) if isExistOnlinePlayer then --直接从内存中加载数据 self:LoadUserFromMem( account , userId , s2cData ) else --玩家数据不在内存中,从DB中加载 self:LoadUserFromDB( c2sData , s2cData , userDBInfo ) end end if errorInfo.Suc == s2cData.code then --更新下登陆IP地址 s2cData.data.playerData.basicInfo.lastLoginIP = c2sData.addr --将该玩家的ID放入在线玩家列表中 local redisKey = string.format( redisKeyUrl.RouteServerLoginInfoHash , account ) skynet.server.redis:hset( redisKey , "Status" , dataType.LoginStatus_IntoGame ) --修改登录状态 skynet.server.redis:persist( redisKey ) --不删除玩家的登录信息 s2cData.data.playerData = json:encode(s2cData.data.playerData) log.info("将玩家UserID 加入到在线列表成功 ",account ) else log.info("将玩家UserID 加入到在线列表失败 ",account , s2cData.code) end end --创建用户数据到DB中 function AccountCenter:UserCreateToDB( c2sData , s2cData ) local platform = c2sData.data.platform local account = c2sData.data.loginAccount local version = c2sData.data.version local configType = c2sData.data.configType local mobile = c2sData.data.mobile 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数据 skynet.server.redisCenter:SetRedisDBInfo( account , "DBIndex" , dbIndex , "UserID" , curId ) local cacheCount = 0 --skynet.server.gameServer.curServerConfig.PerSaveCacheCreateAccount or 0 local cacheAccountInfo = {} --缓存帐号信息 if 0 == cacheCount then --所有玩家列表到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 local keyUserList = string.format(redisKeyUrl.AccountServerUserList , account ) log.info("创建user数据失败,删除redis中的key ",keyUserList) skynet.server.redis:del(keyUserList) return s2cData end else --将创建的帐号信息进入队列 cacheAccountInfo.curId = curId cacheAccountInfo.platform = platform end --组装玩家的游戏数据 local newPlayer = {} newPlayer.basicInfo = {} dbData:Traversal( newPlayer.basicInfo , playerFields.InitBasicInfo ) newPlayer.basicInfo.dbIndex = dbIndex 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.lastExitTime = 0 newPlayer.basicInfo.mobile = mobile --根据XX来定配置 newPlayer.basicInfo.configType = skynet.server.gameConfig:GetConfigType( configType , 1 ) --游戏数据 newPlayer.gameData = {} --非存档用户可以初始化线上数据 dbData:Traversal( newPlayer.gameData , playerFields.InitGameData ) local isExistUser , oldData , oldPayCount , inviteCount = self:IosOldPlayer( mobile ) if isExistUser then --把老苹果用户的存档数据保存在该玩家身上 newPlayer.basicInfo.isOldIOS = true newPlayer.gameData.oldData = json:decode(oldData) newPlayer.gameData.oldPayCount = oldPayCount newPlayer.gameData.inviteCount = inviteCount end --在player表中创建玩家的数据 sql = string.format(sqlUrl.insertAccountToPlayer ,curId , account , platform , json:encode( newPlayer.basicInfo ) , json:encode( newPlayer.gameData )) 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 ) local keyUserList = string.format(redisKeyUrl.AccountServerUserList , account ) log.info("创建player数据失败,删除redis中的key",keyUserList) skynet.server.redis:del(keyUserList) s2cData.code = errorInfo.ErrorCode.AlreadyCreateAccount return s2cData end --处理邀请注册信息 if c2sData.data.invitePartnerId ~= nil and c2sData.data.invitePartnerId ~= "" then skynet.server.gameClubTask:BeInviteInfoHandle(newPlayer,c2sData.data.invitePartnerId) end --创建后再进入游戏 log.info("创建帐号并登陆成功 用户ID",curId,account ) local userData = {} userData.userId = curId userData.account = account userData.playerData = newPlayer userData.announce = "" s2cData.data = userData end --从内存中加载玩家数据 function AccountCenter: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 --用户数据 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 AccountCenter:LoadUserFromDB( c2sData , s2cData , userDBInfo ) local dbIndex = userDBInfo.DBIndex local userId = userDBInfo.UserID local saveMySqlTime = userDBInfo.saveMySqlTime or 0 local saveMongoTime = userDBInfo.saveMongoTime or 0 local platform = c2sData.data.platform local account = c2sData.data.loginAccount local queryData = nil local basicInfo = nil local gameData = nil log.info(string.format("玩家 %d DB相关信息 MysqlTime %d MongoTime %d" , userId , saveMySqlTime , saveMongoTime )) if ( 0 == saveMySqlTime and 0 == saveMongoTime ) or saveMySqlTime > saveMongoTime then --mysql保存时间大于mongo保存时间,则从mysql中加载数据 local sql = string.format(sqlUrl.queryAccountFromPlayer , userId ) queryData = skynet.server.db:QueryPlayer( dbIndex , sql ) if not queryData then --这种报错一般是因为mysql的连接失败 s2cData.code = errorInfo.ErrorCode.NoGetUserDataFromDB log.info(string.format("玩家 %d 从MysqlDB中加载数据失败",userId)) return s2cData end queryData = queryData[1] log.info(string.format("玩家 %d 从MysqlDB中加载数据成功",userId)) else --从MongoDB中加载数据 function Do() queryData = skynet.server.mongo[ "player".. dbIndex]:findOne({ _id = userId }) return queryData end local isDo, res = pcall( Do ) if not isDo then --这种报错一般是因为mongo的连接失败 s2cData.code = errorInfo.ErrorCode.NoGetUserDataFromDB log.info(string.format("玩家 %d 从MongoDB中加载数据失败",userId)) return s2cData end queryData = res log.info(string.format("玩家 %d 从MongoDB中加载数据成功",userId)) end --解析数据 basicInfo = json:decode(queryData.BasicInfo) gameData = json:decode(queryData.GameData) --检查是否有新字段,有新字段进行处理 dbData:CheckNewFields( basicInfo , playerFields.InitBasicInfo) dbData:CheckNewFields( gameData , playerFields.InitGameData) if not userDBInfo.PartnerId then --由于之前未到账号下保存好友ID,这里做个检测,如果没有就加入进来 skynet.server.redisCenter:SetRedisDBInfo( account , "PartnerId" , gameData.partner.id ) end local playerData = {} playerData.basicInfo = basicInfo playerData.gameData = gameData --处理一下邮件的转义问题 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") v.content = string.gsub(v.content,"\t","\\t") v.content = string.gsub(v.content,"\"","") v.content = string.gsub(v.content,'"',"") 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 AccountCenter: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 AccountCenter: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 AccountCenter: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 --玩家关键数据保存到MongoDB(暂时不用) function AccountCenter:SaveKeyDataChangeToMongo( player ) function Do() local account = player.basicInfo.accountName local userId = player.userId local dbIndex = player.basicInfo.dbIndex local bag = json:encode(player.gameData.bag) local nowTime = skynet.GetTime() local ok , err , r = skynet.server.mongo[ "player".. dbIndex ]:safe_update({ _id = userId } , { BasicInfo = "nimei" } , true) if not ok then log.info("Mongo执行异常 SaveKeyDataChangeToMongo", userId , err , skynet.server.common:TableToString(r)) return false end return true end local ret,err = pcall(Do) if not ret or not err then local account = player.basicInfo.accountName local userId = player.userId log.info("内部错误信息 SaveKeyDataChangeToMongo 保存当前玩家失败", account ,userId ,ret ,err ) return false end return true end --玩家数据保存到MongoDB function AccountCenter:SavePlayerToMongo( player ) function Do() player:AddSaveLevel( dataType.SaveLevel_No , true ) local account = player.basicInfo.accountName local userId = player.userId local dbIndex = player.basicInfo.dbIndex local basicInfo = json:encode(player.basicInfo) local gameData = json:encode(player.gameData) local nowTime = skynet.GetTime() local ok , err , r = skynet.server.mongo[ "player".. dbIndex ]:safe_update({ _id = userId} , { BasicInfo = basicInfo , GameData = gameData , SaveTime = nowTime } , true) if not ok then log.info("Mongo执行异常 SavePlayerToMongo", userId , err , skynet.server.common:TableToString(r)) return false end skynet.server.redisCenter:SetRedisDBInfo( account , "saveMongoTime" , nowTime ) log.info(string.format("玩家 %d 保存数据到MongoDB成功", userId )) return true end local ret,err = pcall(Do) if not ret or not err then local account = player.basicInfo.accountName local userId = player.userId log.info("内部错误信息 SavePlayerToMongo 保存当前玩家失败", account ,userId ,ret ,err ) return false end return true end --保存玩家数据到DB isIntervalSave非间隔保存 function AccountCenter:SavePlayerToMysql( player ) 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 nowTime = skynet.GetTime() --计算在线时间 player:CalcGameTime( nowTime ) --如果是同一天 则增加当天登录时间 if skynet.server.common:IsSameDay(player.basicInfo.lastGameTime , nowTime ) then --判断是否可以触发相关礼包 local cfgSValue = skynet.server.gameConfig:GetPlayerAllCfg( player , "SValue") local packInfo = cfgSValue.triggerUpgradePack --如果本日登录时长到达并且等级没有变化 则触发相关礼包 if player.gameData.todayGain.todayGameTime >= packInfo[1]*60*60 and nowTime - player.gameData.upTime >= packInfo[1]*60*60 and player.gameData.level < player.maxLevelLimt and player.gameData.level >= cfgSValue.storeUnlockLvl then skynet.server.store:TriggerPack(player , skynet.server.store.TriggerPack_UpGrade) end end --获取数据库索引 local dbIndex = basicInfo.dbIndex --更新玩家所有数据到数据库 local sql = string.format(sqlUrl.saveAccountToPlayer , json:encode(basicInfo) , json:encode(gameData) , userId ) local queryData = skynet.server.db:QueryPlayer( dbIndex , sql ) if queryData and queryData.errno then log.info("DB执行异常 SavePlayerToDB", dbIndex , skynet.server.common:TableToString(queryData)) return false end player:AddSaveLevel( dataType.SaveLevel_No , true ) skynet.server.redisCenter:SetRedisDBInfo( account , "saveMySqlTime" , skynet.GetTime() ) return true end local ret,err = pcall(Do) if not ret or not err then local account = player.basicInfo.accountName local userId = player.userId --保存玩家数据报错了,有可能是数据有问题,无法转成json,所以直接打到文件中,方便查看 log.info("内部错误信息 SavePlayerToDB 保存当前玩家失败", account , userId ,ret ,err) return false end return true end --保存Mysql数据库失败的玩家数据写文件 function AccountCenter:SaveFailedPlayerDataToFile( player ) local basicInfo = player.basicInfo local gameData = player.gameData local dbIndex = basicInfo.dbIndex local userId = basicInfo.userID local account = basicInfo.accountName local fileName = string.format("SaveFailedPlayerDataToFile_%d_%d.txt", serverId , dbIndex ) local saveFile = io.open( fileName , "a+") local nowTime = skynet.GetTime() --有可能玩家JSON数据有问题,无法转,所以这里打印成文本,方便排查问题 function Do1() log.info("玩家数据写入失败" , account , userId , skynet.server.common:TableToString(basicInfo) , skynet.server.common:TableToString(gameData)) end local isDo1,callData1 = pcall( Do1 ) if not isDo1 then local errorText = "保存玩家数据到文件失败 , 只有打印相关数据,方便排查问题1" log.warning( errorText , callData1) end --转成sql,方便直接恢复玩家数据 function Do2() local sql = string.format(sqlUrl.saveAccountToPlayer , json:encode(basicInfo) , json:encode(gameData) , userId ) sql = string.format("%s|%s|%d|%s \r\n" , skynet.server.common:GetStrTime( nowTime ) , account, userId , sql) saveFile:write( sql ) end local isDo2,callData2 = pcall( Do2 ) if not isDo2 then local errorText = "保存玩家数据到文件失败 , 只有打印相关数据,方便排查问题2" if userId then log.warning(errorText , userId , callData2 , skynet.server.common:TableToString(basicInfo) , skynet.server.common:TableToString(gameData) ) else log.warning( errorText , callData2 , skynet.server.common:TableToString(basicInfo) , skynet.server.common:TableToString(gameData)) end end saveFile:close() end --检查参数是否有效 function AccountCenter:CheckParam( key , value) if nil == key or nil == value then return false end local suc = true local paramLen = 0 --参数长度,字符串会取长度,其余参数为0 if "string" == type(value) then paramLen = #value end if "platform" == key and paramLen > 32 then suc = false elseif "loginAccount" == key and paramLen > 20 then suc = false elseif "loginToken" == key and 8 ~= paramLen then suc = false elseif "hardwareCode" == key and paramLen > 64 then suc = false elseif "channel" == key and paramLen > 32 then suc = false elseif "gameCgi" == key and paramLen > 32 then suc = false elseif "configType" == key and ( paramLen > 16 or nil == skynet.server.gameConfig:GetConfigType( value , 1 ) ) then suc = false end if not suc then log.warning(string.format("参数 %s 值 %s 异常 长度 %d" , key , value, paramLen )) end return suc end --保存缓存账号到DB(废弃) function AccountCenter:SaveCacheCreateAccountToDB() local userPararm = nil local playerPararm = nil local cacheCount = skynet.server.gameServer.curServerConfig.PerSaveCacheCreateAccount or 0 if 0 == cacheCount then return false end for dbIndex = 1, skynet.server.gameConfig.DBInfoConfig.playerTableCount, 1 do local sqlUser = sqlUrl.insertUserDBIndexToUser1 local sqlPlayer = sqlUrl.insertAccountToPlayer1 local saveCount = 0 --当前保存的数量 local account = nil local accountInfo = nil local queueCount = self.queueAccount[ dbIndex ].Count --当前队列中的缓存数量 local curSaveList = {} log.info(string.format("目前缓存账号信息 DB %d 数量 %d" , dbIndex , queueCount )) function Do() for i = 1, queueCount , 1 do --从队列中取出数据 local item = self.queueAccount[ dbIndex ]:DeQueue() if not item or not item.key or "" == item.key or not item.value then break end account = item.key accountInfo = item.value saveCount = saveCount + 1 table.insert( curSaveList , account ) --组装SQL userPararm = string.format(sqlUrl.insertUserDBIndexToUser2 , accountInfo.curId , accountInfo.platform , account , dbIndex ) sqlUser = sqlUser .. userPararm playerPararm = string.format(sqlUrl.insertAccountToPlayer2 , accountInfo.curId , account , accountInfo.platform , json:encode( accountInfo.basicInfo ) , json:encode( accountInfo.gameData ) ) sqlPlayer = sqlPlayer .. playerPararm if saveCount >= cacheCount then break end end if saveCount >0 then --超过最大保存次数 local curTime = skynet.GetTime() sqlUser = string.sub(sqlUser , 1, -2 ) --去掉最后一个逗号 local queryData = skynet.server.db:Query( "account" , sqlUser ) if saveCount ~= queryData.affected_rows then log.info("user 插入数据失败", sqlUser , skynet.server.common:TableToString(queryData)) skynet.server.clusterServer:SendErrorInfoToCenter( account , errorInfo.ErrorCode.CacheCreateAccountError , string.format("user 插入数据失败 %d %d", saveCount or 0 , queryData.affected_rows or 0 )) end log.info(string.format("user 保存缓存创建玩家数据 数据库索引 %d 应保存人数 %d 实际保存人数 %d 保存用时 %d 秒", dbIndex , saveCount , queryData.affected_rows , curTime - skynet.GetTime())) sqlPlayer = string.sub(sqlPlayer , 1, -2 ) --去掉最后一个逗号 queryData = skynet.server.db:QueryPlayer( dbIndex , sqlPlayer ) if saveCount ~= queryData.affected_rows then log.info("player 插入数据失败", sqlUser , skynet.server.common:TableToString(queryData)) skynet.server.clusterServer:SendErrorInfoToCenter( account , errorInfo.ErrorCode.CacheCreateAccountError , string.format("player 插入数据失败 %d %d", saveCount or 0 , queryData.affected_rows or 0 )) end log.info(string.format("player 保存缓存创建玩家数据 数据库索引 %d 应保存人数 %d 实际保存人数 %d 保存用时 %d 秒", dbIndex , saveCount , queryData.affected_rows , curTime - skynet.GetTime() )) end end local ret,err = pcall(Do) if not ret then log.info("内部错误信息 缓存保存当前玩家失败1", ret ,err , account , sqlUser , sqlPlayer) log.info("内部错误信息 缓存保存当前玩家失败2", skynet.server.common:TableToString(curSaveList)) return end end return true end skynet.server.accountCenter = AccountCenter return AccountCenter