--活动时间服务 local skynet = require "skynet" require "skynet.manager" local redisdriver = require "skynet.db.redis" local logger = require "logger" local common_fun = require "model.common_fun" local util = require "util" local asset = require "model.asset" local cjson = require "cjson" local stringify = require "stringify" local queue = require "skynet.queue" local trace = logger.trace --local trace = function(...) end local string_format = string.format local skynet_retpack = skynet.retpack local synchronized = queue() cjson.encode_sparse_array(true, 1) local STATE_OPEN = 0 -- 活动开启 local STATE_CLOSE = 1 -- 活动结束 local CMD = {} local activity = {} local THIS = {} local finishtime = { --记录已经开启活动,或者即将开启的活动 --[[ [activ_tyep] = { -- 活动信息 } ]] } local recent_finishtime = { -- 开启/待开启/结束 (先筛选开启中 - 筛选待开启 - 筛选以结束) } local all_activity_time = {} -- 所有活动的数据 [sid] = {} local usercenter --玩家服务 local redis --数据库 local KEY_FORMAT = "activity:%s" local globaltime --开服时间 local TEN_YEAR -- 开服十年后的时间节点 local cache_acti = { -- 内存活动数据 配置表转发而来 --[[ [id] = { 活动信息 } ]] } local IMM_SEND = false -- 立即推送活动数据 local calculate = {} -- TODO: 活动时间类型 1 --[[ opentype = 1 永久活动 直接开启十年 ]] calculate[1] = function(aconfig, t_type, act_info) act_info[1] = STATE_OPEN act_info[2] = globaltime act_info[3] = TEN_YEAR end -- TODO: 活动类型 2 --[[ opentype = 2 限时活动 ]] calculate[2] = function(aconfig, t_type, act_info, now) local opentime = common_fun.split(aconfig.starttime) local endtime = common_fun.split(aconfig.endtime) if now >= opentime and now < endtime then act_info[1] = STATE_OPEN else act_info[1] = STATE_CLOSE end act_info[2] = opentime -- 活动开启时间 act_info[3] = endtime -- 活动结束时间 end function THIS.split_unknown(str) local ret = {} for t in string.gmatch(str, "(%d+)%/?") do table.insert(ret, 1, tonumber(t)) end return ret end local function math_type6(tab) return tab[1]+ (tab[2] or 0)*MIN_SEC + (tab[3] or 0) * HOUR_SEC + ((tab[4] or 0)-1) *DAY_SEC end -- TODO: 活动类型 3 --[[ opentype = 3 开服活动 ]] calculate[3] = function(aconfig, t_type, act_info, now) local list1 = THIS.split_unknown(aconfig.starttime) -- 活动开启配置 local list2 = THIS.split_unknown(aconfig.endtime) -- 活动结束配置 local opentime = globaltime + math_type6(list1) local endtime = globaltime + math_type6(list2) if now >= opentime and now < endtime then act_info[1] = STATE_OPEN else act_info[1] = STATE_CLOSE end act_info[2] = opentime -- 活动开启时间 act_info[3] = endtime -- 活动结束时间 end -- TODO: 计算活动开启时间, 及活动现在状态 local function calculate_time(aconfig) local t_type = aconfig.opentype -- 活动开启类型 -- globaltime -- 开服时间 local now = os.time() local act_info = { [1] = STATE_CLOSE, -- 活动状态 [2] = 0, -- 活动开启时间 [3] = 0, -- 活动结束时间 } calculate[t_type](aconfig, t_type, act_info, now) logger.trace(" id = %s, type = %s, opentype = %s, state = %s, open = %s, end = %s", aconfig.ID, aconfig.type, t_type, act_info[1], os.date("%Y-%m-%d %H:%M:%S",tonumber(act_info[2])), os.date("%Y-%m-%d %H:%M:%S",tonumber(act_info[3])) ) return table.unpack(act_info) end -- TODO: 活动状态发生变化,推送活动数据 function activity.send_active_info() --通知在线的玩家有活动的状态发生了变更 skynet.send(usercenter, "lua", "command", "update", finishtime)--向服务器推送 trace("限时活动时间new_finishtime:%s",stringify(finishtime)) end -- TODO: 热更接口 function CMD.hotfix(list) if list.ActivityConfig_proto then synchronized(function() logger.trace(" ========== 热更活动配置表") -- 读取配置,活动时间数据 local temp = activity.load_conf() cache_acti = temp IMM_SEND = true end) end end -- TODO: 检查有活动状态, 并返回已经开启的活动 及 是否有活动状态变化 function THIS.check_active_state() local open_act = {} -- 开启中得活动 local finsi_act = {} -- 结束了得活动 local recent_act = {} -- 待开启得活动 local now = os.time() local nstate local change = false for sid, ainfo in pairs(cache_acti) do if ainfo.opentime <= now and ainfo.endtime > now then nstate = STATE_OPEN if open_act[ainfo.activity_type] then if ainfo.sid < open_act[ainfo.activity_type].sid then open_act[ainfo.activity_type] = ainfo change = true end else open_act[ainfo.activity_type] = ainfo end else nstate = STATE_CLOSE end if now < ainfo.opentime then -- 待开启活动 if recent_act[ainfo.activity_type] then if recent_act[ainfo.activity_type].opentime > ainfo.opentime then -- 取时间小得 recent_act[ainfo.activity_type] = ainfo end else recent_act[ainfo.activity_type] = ainfo end end if now >= ainfo.endtime then -- 已经结束得活动 if finsi_act[ainfo.activity_type] then if finsi_act[ainfo.activity_type].endtime < ainfo.endtime then -- 取时间大得 finsi_act[ainfo.activity_type] = ainfo end else finsi_act[ainfo.activity_type] = ainfo end end -- 检查状态是否变化 if nstate ~= ainfo.state then logger.trace(" -- 活动状态改变 sid = %s, ainfo = %s", sid, stringify(ainfo)) change = true ainfo.state = nstate end end return open_act, finsi_act, recent_act, change end --创建检查活动配置时间的协程 function activity.detection() skynet.fork(function() local change, finsi_act, recent_act, recent_temp -- 处理 最近活动数据 local dis_recent = function(tt, act) for sid, v in pairs(act) do if not tt[sid] then tt[sid] = v end end end while true do synchronized(function() finishtime, finsi_act, recent_act, change = THIS.check_active_state() -- 有活动发生了变化 -- logger.trace(" ===============finishtime = %s", stringify(finishtime)) recent_temp = {} dis_recent(recent_temp, finishtime) dis_recent(recent_temp, finsi_act) dis_recent(recent_temp, recent_act) recent_finishtime = recent_temp if change or IMM_SEND then activity.send_active_info() IMM_SEND = false end redis:set("activity:activity_time", cjson.encode(finishtime)) end) skynet.sleep(100) end end) end --获得限时活动指定的时间 function CMD.getassigntime(name) return synchronized(function() return finishtime[name]--state0是关闭1是开启 end) end --玩家上线的时候获取一次所有限时活动的时间 function CMD.getactivitytime() return synchronized(function() return finishtime end) end -- 获取对接活动数据 function CMD.get_recent_activitytime() return synchronized(function() return recent_finishtime end) end -- 获取对接活动数据 function CMD.get_recent_assigntime(name) return synchronized(function() return recent_finishtime[name] end) end function CMD.get_sid_info(sid) return all_activity_time[sid] end -- TODO: 准备-加载数据库数据 function activity.prepare() local key = string_format(KEY_FORMAT, "global") local res = redis:hget(key, "starttime") if res then globaltime = util.today(tonumber(res)+HOUR_SEC) -- 防止冬夏令时导致的时间异常 else res = util.today() globaltime = res redis:hmset(key, "starttime", res) end end function CMD.query() return globaltime end function CMD.open_day() return math.max(1, (util.today() - globaltime)/DAY_SEC + 1) end ----------------------------------------------------------------------- -- TODO: 筛选本服得活动 function activity.filtrate_activity() local ret = {} local act_conf = asset.ActivityConfig_proto for sid, info in pairs(act_conf) do ret[sid] = common_fun.scopy(info) end return ret end -- TODO: 加载活动配置数据,并生成时间节点 及活动状态 function activity.load_conf() local act_conf = activity.filtrate_activity() local temp_act = {} -- 配置表转发活动数据结构, 临时存储 for sid, aconfig in pairs(act_conf) do local astate, aopne_tm, aend_tm = calculate_time(aconfig) temp_act[sid] = { sid = sid, -- 活动sid state = astate, -- 活动状态 switch = aconfig.switch, -- 临时关闭标记 activity_type = aconfig.type, -- 活动类型 name = aconfig.name, -- 活动名字 opentime = aopne_tm, endtime = aend_tm, sort = aconfig.sort, icon = aconfig.icon -- 活动icon } all_activity_time[sid] = temp_act[sid] end return temp_act end function CMD.start() usercenter = usercenter or skynet.localname(".usercenter") local conf = assert(option.redis) redis = redisdriver.connect(conf) redis:select(1)--切换到数据库db1 activity.prepare() TEN_YEAR = globaltime + DAY_SEC*365*10 -- 读取配置,活动时间数据 cache_acti = activity.load_conf() IMM_SEND = true activity.detection()--检查时间 -- 冲关 -- skynet.fork(function() -- skynet.sleep(300) -- CMD.hotfix() -- end) end skynet.info_func(function() return { open_activie = finishtime, -- 已经开启的活动关 cache_acti = cache_acti, -- 所有活动情况 } end) skynet.init(function() skynet.register(".activitytime") end) skynet.start(function() skynet.dispatch("lua", function(session, _, cmd, ...) local f = assert(CMD[cmd]) if session == 0 then f(...) else skynet_retpack(f(...)) end end) end)