HomeServer/Server/AllServer/GameServer/AccountCenter.lua

902 lines
36 KiB
Lua
Raw Permalink Normal View History

2024-11-20 15:41:09 +08:00
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