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