366 lines
17 KiB
Lua
366 lines
17 KiB
Lua
local skynet = require "skynet"
|
||
local oo = require "Class"
|
||
local log = require "Log"
|
||
local pb = require "pb"
|
||
local dataType = require "DataType"
|
||
local errorInfo = require "ErrorInfo"
|
||
local redisKeyUrl = require "RedisKeyUrl"
|
||
local json = require "json"
|
||
local common = require "Common"
|
||
local Announcement = oo.class()
|
||
|
||
Announcement.AnnouncementOpType_1 = 1 --获取公告列表
|
||
|
||
Announcement.AnnouncementOpStatus_Suc = 1 --操作状态成功
|
||
|
||
Announcement.AnnouncementOpStatus_NoAnnouncementID = 2 --不存在该公告ID
|
||
|
||
Announcement.MaxSendCount = 100 --一次发送最大数量
|
||
|
||
function Announcement:Init()
|
||
self.announcementList = {} --游戏服缓存公告
|
||
self.announcementListCache = {} --已推送的公告
|
||
self.announcementUpdateTime =0 --公告更新时间
|
||
self.announcementDisableTime =0 --失效公告移除时间 服务于客户端申请公告列表时做比对,如果本地有缓存且公告更新时间一致再比对失效公告移除时间和客户端更新时间,如果不一致则将新的公告列表返回
|
||
self:ServerAnnouncementDataSync() --服务器重启丢失缓存,所以在初始化时读取redis中的数据
|
||
end
|
||
|
||
--登陆初始化数据
|
||
function Announcement:LoginInitData( player )
|
||
self:CheckNewAnnouncement( player )
|
||
end
|
||
|
||
--每5秒调一次
|
||
function Announcement:On5SecTimer()
|
||
self:BatchSendTips()
|
||
end
|
||
|
||
--从后台刷新公告列表
|
||
function Announcement:RefreshAnnouncementList()
|
||
--向后端请求
|
||
local param = { backstageId = 1 }
|
||
local web = skynet.server.gameConfig.WebConfig.host .. ":" .. skynet.server.gameConfig.WebConfig.port
|
||
local url = skynet.server.common.getAnnouncementUrl
|
||
local status, body = skynet.server.httpClient:PostJson(web, url, json:encode(param), "http")
|
||
|
||
if 200 == status then
|
||
-- newbody 即后台的 ResultModel
|
||
local newbody = json:decode(body)
|
||
if 200 == newbody.code then
|
||
--先设置所有旧公告状态失效 (可以考虑给游戏服缓存list加锁,防止更新状态时其他地方操作)
|
||
--self.announcementLock = true
|
||
local updateTag = false
|
||
if next(Announcement.announcementList)~=nil then
|
||
for k ,v in pairs(Announcement.announcementList) do
|
||
v.canGet = false
|
||
end
|
||
end
|
||
--添加新的公告
|
||
for k, v in pairs(newbody.data) do
|
||
if not Announcement.announcementList[v.id] then
|
||
local announcementId = v.id
|
||
Announcement.announcementList[announcementId] = {}
|
||
local curAnnounce = Announcement.announcementList[announcementId]
|
||
curAnnounce.announcementId = announcementId
|
||
curAnnounce.status = dataType.AnnouncementStatus_NoGet
|
||
curAnnounce.announcementRemarks = v.remarks
|
||
curAnnounce.announcementWeight = v.weight
|
||
curAnnounce.title = v.announcementTitle
|
||
curAnnounce.content = v.announcementText
|
||
curAnnounce.receiveTime = skynet.GetTime()
|
||
curAnnounce.canGet = true
|
||
curAnnounce.isSendAllOnlineUser = false --是否发送所有在线玩家
|
||
--只显示一张图片
|
||
if v.img then
|
||
--处理下微小图片访问问题
|
||
local data = skynet.server.common:Split(v.img[1],":")
|
||
if data[2] == "//47.96.98.191" then
|
||
data = skynet.server.common:Split(v.img[1],"/")
|
||
curAnnounce.image = "https://web.myftime2024.skydreams.cn/" .. data[#data - 1] .. "/" .. data[#data]
|
||
else
|
||
curAnnounce.image = v.img[1]
|
||
end
|
||
end
|
||
--只有一个外链
|
||
if v.href then
|
||
curAnnounce.href = v.href[1]
|
||
end
|
||
curAnnounce.announcementTypeName = v.announcementType --公告类型名称
|
||
curAnnounce.gameId = v.gameId --游戏cgi
|
||
curAnnounce.useChannel = v.useChannel --使用渠道
|
||
curAnnounce.minVersion = tonumber(v.minVersion) --最小版本号
|
||
curAnnounce.operatingSystem = v.operatingSystem --操作系统
|
||
Announcement.announcementUpdateTime =skynet.GetTime() --刷新公告更新时间
|
||
updateTag = true
|
||
else --仍存在的公告状态设置为未失效
|
||
if v.img then
|
||
Announcement.announcementList[v.id].image = v.img[1]
|
||
end
|
||
Announcement.announcementList[v.id].canGet = true
|
||
end
|
||
end
|
||
--判断公告缓存是否还有效,若无效则移出游戏服缓存中
|
||
if next(Announcement.announcementList)~=nil then
|
||
for k ,v in pairs(Announcement.announcementList) do
|
||
if not v.canGet then
|
||
self.announcementList[k] =nil
|
||
--table.remove(self.announcementList,k)
|
||
if self.announcementListCache[k] then
|
||
self.announcementListCache[k] =nil
|
||
end
|
||
Announcement.announcementDisableTime =skynet.GetTime() --刷新失效公告时间
|
||
updateTag = true
|
||
end
|
||
end
|
||
end
|
||
--self.announcementLock = false --释放锁
|
||
if updateTag then
|
||
self:SaveServerAnnouncementData()
|
||
end
|
||
elseif 802 == newbody.code then
|
||
--s2cData.code = errorInfo.ErrorCode.AlreadyGet
|
||
--没有公告情况下移除缓存池的所有公告
|
||
if next(Announcement.announcementList)~=nil then
|
||
if next(Announcement.announcementList)~=nil then
|
||
Announcement.announcementList = {}
|
||
self.announcementListCache = {}
|
||
Announcement.announcementDisableTime =skynet.GetTime() --刷新失效公告时间
|
||
self:SaveServerAnnouncementData()
|
||
end
|
||
end
|
||
end
|
||
else
|
||
log.info("公告刷新 获取数据失败 status ", status or 0)
|
||
end
|
||
end
|
||
|
||
|
||
--拉取最新公告
|
||
function Announcement:GetLatestAnnouncement(player, c2sData, s2cData)
|
||
--重置外部红点
|
||
skynet.server.msgTips:Reset(player , 101)
|
||
c2sData.data = assert(pb.decode("C2SAnnouncementNew", c2sData.data))
|
||
local data = {}
|
||
data.announcementInfo = {}
|
||
--获取客户端上一次申请公告请求的时间
|
||
local localAnnouncementUpdateTime = c2sData.data.localAnnouncementUpdateTime
|
||
--该时间>公告更新时间&公告失效时间则返回 1 通知客户端使用本地缓存 否则返回 2 并且发送当前缓存列表的公告
|
||
if localAnnouncementUpdateTime and localAnnouncementUpdateTime > Announcement.announcementUpdateTime and localAnnouncementUpdateTime > Announcement.announcementDisableTime and localAnnouncementUpdateTime < skynet.GetTime() then
|
||
data.backType = 1
|
||
s2cData.cmd = pb.enum("MsgType", "CMD_S2C_AnnouncementNew")
|
||
s2cData.data = assert(pb.encode("S2CAnnouncementNew", data))
|
||
else
|
||
data.backType = 2
|
||
data.announcementUpdateTime = skynet.GetTime()
|
||
player.gameData.announcementUpdateTime =Announcement.announcementUpdateTime
|
||
for k , v in pairs(Announcement.announcementList) do
|
||
if player.basicInfo.gameCgi ~= v.gameId then
|
||
goto continue
|
||
end
|
||
--玩家的渠道是否符合
|
||
if player.basicInfo.channel ~= v.useChannel and "all" ~= v.useChannel then
|
||
goto continue
|
||
end
|
||
--玩家的APP版本是否符合
|
||
if player.basicInfo.appVersion < v.minVersion then
|
||
goto continue
|
||
end
|
||
--玩家的系统是否符合
|
||
if player.basicInfo.system ~= v.operatingSystem and "all" ~= v.operatingSystem and "双端" ~= v.operatingSystem then
|
||
goto continue
|
||
end
|
||
local info = skynet.server.common:DeepCopy(v)
|
||
--处理单引号的问题
|
||
info.content = string.gsub(v.content,"'","'")
|
||
info.content = string.gsub(v.content," "," ")
|
||
table.insert(data.announcementInfo,info)
|
||
::continue::
|
||
end
|
||
s2cData.cmd = pb.enum("MsgType", "CMD_S2C_AnnouncementNew")
|
||
s2cData.data = assert(pb.encode("S2CAnnouncementNew", data))
|
||
end
|
||
end
|
||
|
||
--添加公告提醒
|
||
function Announcement:AddAnnouncementNotice(player,announcementInfo)
|
||
--玩家当前游戏的包是否匹配当前公告的cgi
|
||
if player.basicInfo.gameCgi ~= announcementInfo.gameId then
|
||
return false
|
||
end
|
||
--玩家的渠道是否符合
|
||
if player.basicInfo.channel ~= announcementInfo.useChannel and "all" ~= announcementInfo.useChannel then
|
||
return false
|
||
end
|
||
--玩家的APP版本是否符合
|
||
if player.basicInfo.appVersion < announcementInfo.minVersion then
|
||
return false
|
||
end
|
||
--玩家的系统是否符合
|
||
if player.basicInfo.system ~= announcementInfo.operatingSystem and "all" ~= announcementInfo.operatingSystem and "双端" ~= announcementInfo.operatingSystem then
|
||
return false
|
||
end
|
||
--向玩家推送红点
|
||
skynet.server.msgTips:Add(player , 101)
|
||
return true
|
||
end
|
||
|
||
--新的登陆初始化函数,更新时记得解开注释,然后将旧函数注释
|
||
function Announcement:LoginInitData(player)
|
||
--如果玩家登陆时不存在公告更新时间字段或者该字段小于当前游戏服最新的公告更新时间则推送红点提示
|
||
if not player.gameData.announcementUpdateTime or player.gameData.announcementUpdateTime < Announcement.announcementUpdateTime then
|
||
for announceId, v1 in pairs( self.announcementList ) do
|
||
self:AddAnnouncementNotice(player,v1)
|
||
end
|
||
--刷新玩家获取公告时间
|
||
player.gameData.announcementUpdateTime =Announcement.announcementUpdateTime
|
||
end
|
||
end
|
||
|
||
--是否存在公告(待废弃)
|
||
function Announcement:IsExist(player, announcementId)
|
||
for k, v in pairs(player.gameData.announcement.historyAnnouncement) do
|
||
if announcementId == v then
|
||
return true
|
||
end
|
||
end
|
||
return false
|
||
end
|
||
|
||
--批量推送公告更新提示
|
||
function Announcement:BatchSendTips()
|
||
local t1 = skynet.GetTime()
|
||
local playerList = skynet.server.playerCenter:GetPlayerList()
|
||
local sendCount = 0 --此次推送公告数量
|
||
local isSendAnnounce = true --是否继续推送公告
|
||
for announceId, v1 in pairs( self.announcementList ) do
|
||
--在已推送完毕的公告池中进行检索,如果已经被推送过则跳过后续的推送逻辑节省性能(不会存在推送过程中新登陆的玩家收不到新公告推送的问题,登陆的消息初始化中会调用专门的函数查询)
|
||
if self.announcementListCache[announceId] then
|
||
goto continue
|
||
end
|
||
for userId, value in pairs(playerList) do
|
||
if value.status == skynet.server.playerCenter.Status_Playing and value.player.gameData.announcementUpdateTime and value.player.gameData.announcementUpdateTime < Announcement.announcementUpdateTime then --玩家获取过公告,但可能存在新的公告
|
||
--调用推送公告提醒函数
|
||
if self:AddAnnouncementNotice(value.player,v1) then
|
||
--刷新玩家获取公告时间
|
||
value.player.gameData.announcementUpdateTime =Announcement.announcementUpdateTime
|
||
sendCount = sendCount + 1
|
||
if sendCount >= self.MaxSendCount then
|
||
isSendAnnounce = false
|
||
break
|
||
end
|
||
end
|
||
elseif value.status == skynet.server.playerCenter.Status_Playing and not value.player.gameData.announcementUpdateTime then --玩家没有获取过公告(公告系统逻辑迭代,主要作用于不存在该属性的旧版本玩家)
|
||
if self:AddAnnouncementNotice(value.player,v1) then
|
||
value.player.gameData.announcementUpdateTime =Announcement.announcementUpdateTime
|
||
sendCount = sendCount + 1
|
||
if sendCount >= self.MaxSendCount then
|
||
isSendAnnounce = false
|
||
break
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
if sendCount == 0 then
|
||
self.announcementListCache[announceId] = true
|
||
self:SaveServerAnnouncementData()
|
||
log.info(string.format("公告 全服公告 %s 在线已发送结束 ", announceId ))
|
||
else
|
||
log.info(string.format("公告 全服公告 %s 发送 发送数量 %d 时间花费 %d ", announceId , sendCount, skynet.GetTime() - t1))
|
||
end
|
||
|
||
if not isSendAnnounce then
|
||
break
|
||
end
|
||
::continue::
|
||
end
|
||
end
|
||
|
||
--保存游戏服公告的缓存到Redis做备份
|
||
function Announcement:SaveServerAnnouncementData()
|
||
local redisKey = redisKeyUrl.GameServerAnnoucementCache
|
||
local data = {}
|
||
data.announcementList ={}
|
||
for k,v in pairs(self.announcementList) do
|
||
table.insert(data.announcementList,v)
|
||
end
|
||
data.announcementListCache = {}
|
||
for k,v in pairs(self.announcementListCache) do
|
||
table.insert(data.announcementListCache,k)
|
||
end
|
||
data.announcementUpdateTime=self.announcementUpdateTime
|
||
data.announcementDisableTime=self.announcementDisableTime
|
||
skynet.server.redis:set( redisKey , json:encode(data) )
|
||
end
|
||
|
||
--同步Redis中的公告缓存 一般在服务器重启时调用
|
||
function Announcement:ServerAnnouncementDataSync()
|
||
local redisKey = redisKeyUrl.GameServerAnnoucementCache
|
||
if not skynet.server.redis:exists(redisKey) then
|
||
return
|
||
end
|
||
local cache = skynet.server.redis:get(redisKey)
|
||
cache = json:decode(cache)
|
||
if not cache.announcementUpdateTime and cache.announcementUpdateTime > self.announcementUpdateTime then
|
||
for k,v in pairs(cache.announcementList) do
|
||
self.announcementList[v.announcementId] = v
|
||
end
|
||
for k,v in pairs(cache.announcementListCache) do
|
||
self.announcementListCache[v] = true
|
||
end
|
||
self.announcementUpdateTime = cache.announcementUpdateTime
|
||
self.announcementDisableTime = cache.announcementDisableTime
|
||
end
|
||
end
|
||
|
||
--调用服务器的添加公告方法 标题 内容 类型
|
||
function Announcement:AddAnnouncementByBS(title , content , typeId)
|
||
--取消方法
|
||
if true then
|
||
return
|
||
end
|
||
--platform 操作系统 默认双端
|
||
local platform = "双端"
|
||
--useChannel 使用渠道 默认所有渠道
|
||
local channel = "all"
|
||
local playerList = skynet.server.playerCenter:GetPlayerList()
|
||
--gameCgi 游戏id
|
||
local gameCgi = ""
|
||
--version 最小版本号
|
||
local version = 0
|
||
for k ,v in pairs(playerList) do
|
||
--从在线玩家中获取相关数据
|
||
if skynet.server.playerCenter.Status_Playing == v.status and v.player.basicInfo.gameCgi and v.player.basicInfo.appVersion then
|
||
gameCgi = v.player.basicInfo.gameCgi
|
||
version = v.player.basicInfo.appVersion
|
||
break
|
||
end
|
||
end
|
||
if gameCgi == "" and version == 0 then
|
||
return true
|
||
end
|
||
--startTime 生效时间
|
||
local startTime = skynet.server.common:GetAfterSomeDay(0)
|
||
--endTime 失效时间 公告存在一天
|
||
local endTime = startTime + 24 * 60 * 60
|
||
--向后端请求
|
||
startTime = skynet.server.common:GetStrTime(startTime)
|
||
endTime = skynet.server.common:GetStrTime(endTime)
|
||
local param = {gameId = gameCgi , useChannel = channel , minVersion = tostring(version) ,
|
||
effectiveTime = startTime , failureTime = endTime ,
|
||
announcementTitle = title , announcementText = content , operatingSystem = platform}
|
||
local web = skynet.server.gameConfig.WebConfig.host .. ":" .. skynet.server.gameConfig.WebConfig.port
|
||
local url = skynet.server.common.addAnnouncementUrl
|
||
local status, body = skynet.server.httpClient:PostJson(web, url, json:encode(param), "http")
|
||
if 200 == status then
|
||
local newbody = json:decode(body)
|
||
if 200 == newbody.code then
|
||
--添加了公告过后 再获取一遍
|
||
self:RefreshAnnouncementList()
|
||
end
|
||
end
|
||
end
|
||
|
||
--相当于单例
|
||
skynet.server.announcement = Announcement
|
||
return Announcement |