HomeServer/Server/AllServer/GameServer/AccountCenter.lua
2024-11-20 15:41:37 +08:00

902 lines
36 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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