--节日排行榜服务 local skynet = require "skynet" require "skynet.manager" local util = require "util" local logger = require "logger" local common_fun = require "model.common_fun" local stringify = require "stringify" local pipeline = require "pipeline" local redisdriver = require "skynet.db.redis" local asset = require "model.asset" local redis local usercenter local activitytime local mailbox local KEY_STR = "act_ranking:list:%d" local KEY_ACT_INFO = "act_ranking:act_info" local THIS_OPEN = 1 -- 开启 local THIS_SYN = 2 -- 最后一次同步数据 local THIS_AWARD = 3 -- 发奖 local THIS_CLOSE = 4 -- 结束 local cjson = require "cjson" local cjson_decode = cjson.decode -- 解码 local cjson_encode = cjson.encode -- 编码 local MAX_RANK = 300 local SHOW_RANK = 100 local compare = function(a, b) if a.num > b.num then return true end if a.num == b.num and a.num2 and b.num2 then return a.num2 > b.num2 end return false end local function inserting(t, n, f) assert(type(t) == "table") local f = f or compare local size = #t local n = n or size + 1 for i = 2, size do local num = math.min(i, n) if i > n and f(t[i], t[num]) then t[i], t[num] = t[num], t[i] end local temp = t[num] local j = num -1 while j >= 1 and f(temp, t[j]) do t[j+1] = t[j] j = j - 1 end t[j+1] = temp end -- if n < size then -- t[n+1] = nil -- end for i = n + 1, size do t[i] = nil end return t end local CMD = {} local ranklist = { } local showlist = { } -- 活动状态表 local act_list = { --[[ [id] = { state = 开启, 发奖, 结束 list = { -- 参与人员 [uid] = 1 } } ]] } local function find_uid(list, uid) for k, info in ipairs(list or {})do if info.role.uid == uid then return k, info end end end local function init_act_info(id, state) return { id = id, state = state or THIS_OPEN, list = {} } end local function get_act_conf(id) return asset.ActivityConfig_proto[id] end local function save_act_info(id) if act_list[id] then redis:hset(KEY_ACT_INFO, id, cjson_encode(act_list[id])) else redis:hdel(KEY_ACT_INFO, id) end end local function send_award(id) local conf = assert(get_act_conf(id), id) local act_type = conf.type mailbox = mailbox or skynet.localname(".mailbox") local send_list = {-- 已发送奖励的玩家 --[[ [uid] = 1 ]] } local join_list = (act_list[id] or {}).list or {} local rank_list = showlist[id] or {} local mail_type_1_100 local mail_type_100_n local source = { module = "act_ranking", brief = "排行活动奖励", } if act_type == MODULE_ID_HERO_RANK then source.brief = "英雄排行奖励" mail_type_1_100 = 11 mail_type_100_n = 15 elseif act_type == MODULE_ID_SIMPLE_BATTLE_RANK then source.brief = "闯关排行奖励" mail_type_1_100 = 12 mail_type_100_n = 16 elseif act_type == MODULE_ID_POWER_RANK then source.brief = "战力排行奖励" mail_type_1_100 = 13 mail_type_100_n = 17 elseif act_type == MODULE_ID_ELITE_BATTLE_RANK then source.brief = "精英关卡排行奖励" mail_type_1_100 = 14 mail_type_100_n = 18 else assert(false, act_type) end for i=1, #conf.reward do local r1 = assert(conf.parameter1[i*2-1], i) local r2 = assert(conf.parameter1[i*2], i) local award_id = assert(conf.reward[i], i) local _, award_list = common_fun.get_award(award_id, "NewawardConfig_proto") for rank = r1, r2 do local rankinfo = rank_list[rank] if not rankinfo then break end if rank <= SHOW_RANK then if rankinfo.role and rankinfo.role.uid then local uid = rankinfo.role.uid source.context = string.format("第%s名奖励", rank) send_list[uid] = 1 skynet.call(mailbox, "lua", "off_line_mail", {uid}, mail_type_1_100, rank, award_list, source) else logger.error("错误的玩家数据,%s,%s",id, rank) end else if rank_list[SHOW_RANK] then local uids = {} source.context = string.format("第%s名奖励", rank) for uid in pairs(join_list) do if not send_list[uid] then table.insert(uids, uid) end end if next(uids) then skynet.call(mailbox, "lua", "off_line_mail", uids, mail_type_100_n, "", award_list, source) end end break end end end end local function update() for act_id, list in pairs(ranklist) do inserting(list, MAX_RANK) showlist[act_id] = {} local key = string.format(KEY_STR, act_id) redis:del(key) local batch = pipeline(100) for rank, data in ipairs(list) do batch.add("hset", key, data.role.uid, cjson_encode(data)) showlist[act_id][rank] = common_fun.scopy(data) end batch.execute(redis,{}) end end local function check_over() local now = os.time() for id, data in pairs(act_list) do if data.state ~= THIS_CLOSE then activitytime = activitytime or skynet.localname(".activitytime") usercenter = usercenter or skynet.localname(".usercenter") local info = skynet.call(activitytime, "lua", "get_sid_info", id) local conf = get_act_conf(id) if info and conf then if data.state == THIS_OPEN then if now > info.endtime - conf.parameter3[1]*HOUR_SEC then data.state = THIS_SYN -- 通知在线玩家同步 skynet.call(usercenter, "lua", "command", "syn_act_rank", conf.type) save_act_info(id) end elseif data.state == THIS_SYN then if now > info.endtime - conf.parameter3[1]*HOUR_SEC + 10 * MIN_SEC then -- 发奖 local ok, msg = xpcall(send_award, debug.traceback, id) data.state = THIS_AWARD save_act_info(id) if not ok then logger.errno(msg) end end elseif data.state == THIS_AWARD then if now > info.endtime then data.state = THIS_CLOSE -- 清空数据 save_act_info(id) end end else logger.error("活动排行,错误的数据info:%s, conf%s, id:%d", type(info), type(conf), id) end end end end local function init() local conf = assert(option.redis) redis = redisdriver.connect(conf) redis:select(12) local retnuma = redis:hgetall(KEY_ACT_INFO) for k = 1, #retnuma, 2 do local key = tonumber(retnuma[k]) local data = cjson_decode(retnuma[k+1]) if key and data then act_list[key] = data end end for act_id, info in pairs(act_list) do if info.state ~= THIS_CLOSE then ranklist[act_id] = {} local key = string.format(KEY_STR, act_id) local retnuma = redis:hgetall(key) for k = 1, #retnuma, 2 do local data = cjson_decode(retnuma[k+1]) ranklist[act_id][#ranklist[act_id]+1] = data end end end end local function watchtime() skynet.fork(function() local interval = 60*2 -- 更新间隔为2分钟 local up_time = os.time() while(true) do local now = os.time() if now > up_time then up_time = now + interval update() check_over() end skynet.sleep(300) end end) end function CMD.player_rename(uid, name) for _, list in pairs(ranklist) do local _, info = find_uid(list, uid) if info then info.role.nickname = name end end end function CMD.upload(act_id, data) if not act_list[act_id] then activitytime = activitytime or skynet.localname(".activitytime") local info = skynet.call(activitytime, "lua", "get_sid_info", act_id) if not info then logger.error("活动排行 未找到活动数据:%s", act_id) return end if info.state == STATE_OPEN then act_list[act_id] = init_act_info(act_id) else act_list[act_id] = init_act_info(act_id, THIS_CLOSE) end save_act_info(act_id) end if act_list[act_id].state ~= THIS_OPEN and act_list[act_id].state ~= THIS_SYN then return end act_list[act_id].list = act_list[act_id].list or {} if not act_list[act_id].list[data.role.uid] then act_list[act_id].list[data.role.uid] = 1 save_act_info(act_id) end local key = string.format(KEY_STR, act_id) ranklist[act_id] = ranklist[act_id] or {} local list = ranklist[act_id] local k = find_uid(list, data.role.uid) if k then list[k] = data redis:hset(key, data.role.uid, cjson_encode(data)) return end local show = showlist[act_id] or {} if show[MAX_RANK] and show[MAX_RANK].num > data.num then return end list[#list+1] = data redis:hset(key, data.role.uid, cjson_encode(data)) end function CMD.get_ranking(act_id) logger.trace("showlist"..stringify(showlist)) return showlist[act_id] or {} end -- 从排行榜中删除玩家 function CMD.del(uid) for act_id, list in pairs(showlist) do local act_info = act_list[act_id] if not act_info or (act_info.state == THIS_AWARD or act_info.state == THIS_SYN) then local k = find_uid(list, uid) if k then table.remove(list, k) end end end for act_id, list in pairs(ranklist) do local act_info = act_list[act_id] if not act_info or (act_info.state == THIS_AWARD or act_info.state == THIS_SYN) then local key = string.format(KEY_STR, act_id) local k = find_uid(list, uid) if k then table.remove(list, k) redis:hdel(key, uid) end end end end function CMD.start() init() watchtime() end skynet.init(function() skynet.register(".act_rankinglist") end) skynet.start(function() skynet.dispatch("lua", function(session, _, cmd, ...) local f = assert(CMD[cmd]) if 0==session then f(...) else skynet.retpack(f(...)) end end) end)