--邮件管理 local LuaMongo = _G.lua_mongo local Log = require("common.Log") local DB = require("common.DB") local Util = require("common.Util") local Msg = require("core.Msg") local ObjHuman = require("core.ObjHuman") local RoleSystemLogic = require("roleSystem.RoleSystemLogic") local RoleSystemDefine = require("roleSystem.RoleSystemDefine") local RoleDBLogic = require("role.RoleDBLogic") local MailExcel = require("excel.mail") local CommonDB = require("common.CommonDB") SYSTEM = 1 -- 系统邮件 GONGGAO = 2 -- 公告 MAIL_MAX_CNT = 100 -- 邮件最大值 MAIL_LOOKBACK_SEC = 90 * 86400 -- 查询回溯上限(覆盖较长 expireTime) MAIL_SCAN_LIMIT = 2000 -- 单时间窗游标扫描上限 MAIL_SORT_DESC = {time = -1} -- find 第4参: 按时间倒序,优先拿到最新邮件 local FIELD_ID = {_id = nil} local FIELD_RECEIVER = {uuid = nil,type = nil} --后面如果fbAccount有值应该把区服id一起 function add(type,receiverUuid,title,content,items,senderName,sender,time,fbTime,fbContent,fbAccount,expireTime) if _G.is_middle == true or type == GONGGAO then return end local mail = {} mail.type = type mail.receiverUuid = receiverUuid mail.title = title mail.content = content mail.read = nil mail.senderName = title mail.head = 0 mail.fbTime = fbTime mail.fbContent = fbContent --后面用到 local fields = {head = 1,lv = 1} if fbAccount ~= nil then local db = RoleDBLogic.getDbByAccount(fbAccount,fields) mail.fbHead = db.head mail.fbLv = db.lv end if sender and sender.db and sender.db.identity then mail.identity = sender.db.identity end if sender and sender.db and sender.db.head then mail.head = sender.db.head end if time == nil then mail.time = os.time() else mail.time = time end if expireTime then mail.expireTime = expireTime end if items then mail.items = {} for _,item in ipairs(items) do mail.items[#mail.items + 1] = {item[1],item[2]} end end if items == nil then mail.get = 1 mail.flag = nil else mail.get = nil mail.flag = 1 end LuaMongo.insert(DB.db_mail, mail) -- 检测红点会调用getMails(), 短时间插入多封邮件加上玩家邮件数量过多,会发生段错误 -- RoleSystemLogic.onDotByUuid(receiverUuid, RoleSystemDefine.ROLE_SYS_ID_204) return mail end -- condition = {startLv = 1, endLv = 100, lastLoginTime = xxx} local MailLvQuery = {lv = {["$gte"] = 0, ["$lte"] = 0}} local MailLvField = {lastLoginTime = 1, lastLogoutTime = 1} function sendMailByCondition(condition, title, content, gridList,fbTime,fbContent,fbAccount,expireTime) local startLv = condition.startLv and tonumber(condition.startLv) local endLv = condition.endLv and tonumber(condition.endLv) local lastLoginTime = condition.lastLoginTime and tonumber(condition.lastLoginTime) or 0 if startLv == nil or endLv == nil or lastLoginTime == nil then return end local successCount = 0 local failCount = 0 --Log.write(Log.LOGID_ERR_PCALL, "mail sendMailByCondition" , "") -- 在线的只要等级符合直接发 for uuid, oHuman in pairs(ObjHuman.onlineUuid) do if oHuman.db.lv >= startLv and oHuman.db.lv <= endLv then p_ret, err = pcall(add, SYSTEM, oHuman.db._id, title, content, gridList, condition.senderName, nil, nil,fbTime,fbContent,fbAccount,expireTime) if p_ret then successCount = successCount + 1 --Log.write(Log.LOGID_ERR_PCALL, "mail rolename=" .. oHuman.db.name, err) else failCount = failCount + 1 --Log.write(Log.LOGID_ERR_PCALL, "mail rolename=" .. oHuman.db.name, err) end end end -- 不在线的,还需要验证x天内登录 MailLvQuery.lv["$gte"] = startLv MailLvQuery.lv["$lte"] = endLv LuaMongo.find(DB.db_char, MailLvQuery, MailLvField) while true do local data = {} if not LuaMongo.next(data) then break end if not ObjHuman.onlineUuid[data._id] and (lastLoginTime == 0 or (data.lastLoginTime and data.lastLoginTime >= lastLoginTime) or (data.lastLogoutTime and data.lastLogoutTime >= lastLoginTime) ) then p_ret, err = pcall(add, SYSTEM, data._id, title, content, gridList, condition.senderName, nil, nil,fbTime,fbContent,fbAccount) if p_ret then successCount = successCount + 1 else failCount = failCount + 1 Log.write(Log.LOGID_ERR_PCALL, "mail role uuid=" .. data._id, err) end end end return successCount, failCount end function del(mailUuid) FIELD_ID._id = mailUuid LuaMongo.remove(DB.db_mail, FIELD_ID) end function getMail(mailUuid) FIELD_ID._id = mailUuid LuaMongo.find(DB.db_mail,FIELD_ID) local mail = {} if not LuaMongo.next(mail) then return nil end return mail end local function cmpMail(a, b) return a.time > b.time end local mails = {} local seenMailIds = {} local function isMailExpired(m, now) if type(m.time) ~= "number" then m.time = 0 end local expireTime = m.expireTime if expireTime and type(expireTime) == "number" then return m.time <= now - expireTime end return m.time <= now - 7 * 86400 end local function countValidThreshold(sortedMails, now) local cnt = 0 for _, m in ipairs(sortedMails) do if not isMailExpired(m, now) then cnt = cnt + 1 if cnt >= MAIL_MAX_CNT then return cnt, m.time end end end return cnt, nil end -- 缓存过大时只保留最新 MAIL_MAX_CNT 条未过期,降低内存与排序成本 local function trimTempMails(tempMails, now) if #tempMails <= MAIL_MAX_CNT * 2 then return end table.sort(tempMails, cmpMail) local writeIdx = 0 for _, m in ipairs(tempMails) do if not isMailExpired(m, now) then writeIdx = writeIdx + 1 tempMails[writeIdx] = m if writeIdx >= MAIL_MAX_CNT then break end end end for i = writeIdx + 1, #tempMails do tempMails[i] = nil end end local function buildMailQuery(receiverUuid, mailType, startTime, endTime, useLte) local query = {receiverUuid = receiverUuid} if mailType ~= nil then query.type = mailType end local timeCond = {["$gte"] = startTime} if useLte then timeCond["$lte"] = endTime else timeCond["$lt"] = endTime end query.time = timeCond return query end -- function getMails(receiverUuid,mailType) -- for key in ipairs(mails) do -- mails[key] = nil -- end -- FIELD_RECEIVER.receiverUuid = receiverUuid -- FIELD_RECEIVER.type = mailType -- LuaMongo.find(DB.db_mail,{["$query"]=FIELD_RECEIVER}) -- local lastTime = os.time() - 7 * 86400 -- local mailCnt = 0 -- while true do -- local mail = {} -- -- if not LuaMongo.next(mail) then -- -- break -- -- end -- local res, err = pcall(function () -- return LuaMongo.next(mail) -- end) -- if not res then -- Log.write(Log.LOGID_DEBUG, "MailManager.getMails err = ".. err) -- break -- end -- if not err then -- break -- end -- if mail.expireTime then -- 有指定过期时间 -- if mail.time > os.time() - mail.expireTime then -- mailCnt = mailCnt + 1 -- mails[mailCnt] = mail -- end -- elseif mail.time > lastTime then -- 没有就用默认过期时间 -- mailCnt = mailCnt + 1 -- mails[mailCnt] = mail -- end -- end -- if mailCnt > MAIL_MAX_CNT then -- table.sort(mails, cmpMail) -- for i = MAIL_MAX_CNT + 1, mailCnt do -- mails[i] = nil -- end -- end -- return mails -- end -- 分段 + 倒序游标:兼顾 C 驱动稳定性与“取最新 100 封未过期”语义 function getMails(receiverUuid, mailType) for i = 1, #mails do mails[i] = nil end for k in pairs(seenMailIds) do seenMailIds[k] = nil end local now = os.time() local mailCnt = 0 local tempMails = {} local thresholdTime = nil local lookbackStart = now - MAIL_LOOKBACK_SEC -- 半开区间 [start, end);首窗含 now 用 lte local timeWindows = { {now - 7 * 86400, now, true}, {now - 14 * 86400, now - 7 * 86400, false}, {now - 30 * 86400, now - 14 * 86400, false}, {now - 60 * 86400, now - 30 * 86400, false}, {now - MAIL_LOOKBACK_SEC, now - 60 * 86400, false}, } local serverOpenTime = CommonDB.getServerOpenTime() if serverOpenTime < lookbackStart then timeWindows[#timeWindows + 1] = {serverOpenTime, lookbackStart, false} end for _, window in ipairs(timeWindows) do local startTime, endTime, useLte = window[1], window[2], window[3] if startTime >= endTime then goto continue_window end if thresholdTime then local windowMaxTime = useLte and endTime or (endTime - 1) if windowMaxTime < thresholdTime then break end end local query = buildMailQuery(receiverUuid, mailType, startTime, endTime, useLte) LuaMongo.find(DB.db_mail, query, nil, MAIL_SORT_DESC) local mail = {} local scanCnt = 0 while true do local ok, hasNext = pcall(function() return LuaMongo.next(mail) end) if not ok then Log.write(Log.LOGID_DEBUG, "MailManager.getMails next err: " .. tostring(hasNext)) break end if not hasNext then break end scanCnt = scanCnt + 1 if scanCnt > MAIL_SCAN_LIMIT then Log.write(Log.LOGID_DEBUG, string.format( "Mail segment limit hit! uuid=%s window=%d~%d", receiverUuid, startTime, endTime)) break end if thresholdTime and type(mail.time) == "number" and mail.time < thresholdTime then break end local mailId = mail._id if not mailId or not seenMailIds[mailId] then if mailId then seenMailIds[mailId] = true end local m = {} for k, v in pairs(mail) do m[k] = v end tempMails[#tempMails + 1] = m trimTempMails(tempMails, now) local validCnt, th = countValidThreshold(tempMails, now) if th then thresholdTime = th end end for k in pairs(mail) do mail[k] = nil end end ::continue_window:: end table.sort(tempMails, cmpMail) for _, m in ipairs(tempMails) do if not isMailExpired(m, now) then mailCnt = mailCnt + 1 mails[mailCnt] = m if mailCnt >= MAIL_MAX_CNT then break end end end return mails end function saveMail(mail) FIELD_ID._id = mail._id LuaMongo.update(DB.db_mail, FIELD_ID, mail) end function delAll(uuid,mailType) local QueryMailByUuid = {get = {["$exists"] = 0}, read = {["$exists"] = 1}} QueryMailByUuid.receiverUuid = uuid QueryMailByUuid.type = mailType QueryMailByUuid.get["$exists"] = 1 LuaMongo.remove(DB.db_mail, QueryMailByUuid) end