MailManager.lua 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. --邮件管理
  2. local LuaMongo = _G.lua_mongo
  3. local Log = require("common.Log")
  4. local DB = require("common.DB")
  5. local Util = require("common.Util")
  6. local Msg = require("core.Msg")
  7. local ObjHuman = require("core.ObjHuman")
  8. local RoleSystemLogic = require("roleSystem.RoleSystemLogic")
  9. local RoleSystemDefine = require("roleSystem.RoleSystemDefine")
  10. local RoleDBLogic = require("role.RoleDBLogic")
  11. local MailExcel = require("excel.mail")
  12. local CommonDB = require("common.CommonDB")
  13. SYSTEM = 1 -- 系统邮件
  14. GONGGAO = 2 -- 公告
  15. MAIL_MAX_CNT = 100 -- 邮件最大值
  16. local FIELD_ID = {_id = nil}
  17. local FIELD_RECEIVER = {uuid = nil,type = nil}
  18. --后面如果fbAccount有值应该把区服id一起
  19. function add(type,receiverUuid,title,content,items,senderName,sender,time,fbTime,fbContent,fbAccount,expireTime)
  20. if _G.is_middle == true or type == GONGGAO then
  21. return
  22. end
  23. local mail = {}
  24. mail.type = type
  25. mail.receiverUuid = receiverUuid
  26. mail.title = title
  27. mail.content = content
  28. mail.read = nil
  29. mail.senderName = title
  30. mail.head = 0
  31. mail.fbTime = fbTime
  32. mail.fbContent = fbContent
  33. --后面用到
  34. local fields = {head = 1,lv = 1}
  35. if fbAccount ~= nil then
  36. local db = RoleDBLogic.getDbByAccount(fbAccount,fields)
  37. mail.fbHead = db.head
  38. mail.fbLv = db.lv
  39. end
  40. if sender and sender.db and sender.db.identity then
  41. mail.identity = sender.db.identity
  42. end
  43. if sender and sender.db and sender.db.head then
  44. mail.head = sender.db.head
  45. end
  46. if time == nil then
  47. mail.time = os.time()
  48. else
  49. mail.time = time
  50. end
  51. if expireTime then
  52. mail.expireTime = expireTime
  53. end
  54. if items then
  55. mail.items = {}
  56. for _,item in ipairs(items) do
  57. mail.items[#mail.items + 1] = {item[1],item[2]}
  58. end
  59. end
  60. if items == nil then
  61. mail.get = 1
  62. mail.flag = nil
  63. else
  64. mail.get = nil
  65. mail.flag = 1
  66. end
  67. LuaMongo.insert(DB.db_mail, mail)
  68. -- 检测红点会调用getMails(), 短时间插入多封邮件加上玩家邮件数量过多,会发生段错误
  69. -- RoleSystemLogic.onDotByUuid(receiverUuid, RoleSystemDefine.ROLE_SYS_ID_204)
  70. return mail
  71. end
  72. -- condition = {startLv = 1, endLv = 100, lastLoginTime = xxx}
  73. local MailLvQuery = {lv = {["$gte"] = 0, ["$lte"] = 0}}
  74. local MailLvField = {lastLoginTime = 1, lastLogoutTime = 1}
  75. function sendMailByCondition(condition, title, content, gridList,fbTime,fbContent,fbAccount,expireTime)
  76. local startLv = condition.startLv and tonumber(condition.startLv)
  77. local endLv = condition.endLv and tonumber(condition.endLv)
  78. local lastLoginTime = condition.lastLoginTime and tonumber(condition.lastLoginTime) or 0
  79. if startLv == nil or endLv == nil or lastLoginTime == nil then
  80. return
  81. end
  82. local successCount = 0
  83. local failCount = 0
  84. --Log.write(Log.LOGID_ERR_PCALL, "mail sendMailByCondition" , "")
  85. -- 在线的只要等级符合直接发
  86. for uuid, oHuman in pairs(ObjHuman.onlineUuid) do
  87. if oHuman.db.lv >= startLv and
  88. oHuman.db.lv <= endLv then
  89. p_ret, err = pcall(add, SYSTEM, oHuman.db._id, title, content, gridList, condition.senderName, nil, nil,fbTime,fbContent,fbAccount,expireTime)
  90. if p_ret then
  91. successCount = successCount + 1
  92. --Log.write(Log.LOGID_ERR_PCALL, "mail rolename=" .. oHuman.db.name, err)
  93. else
  94. failCount = failCount + 1
  95. --Log.write(Log.LOGID_ERR_PCALL, "mail rolename=" .. oHuman.db.name, err)
  96. end
  97. end
  98. end
  99. -- 不在线的,还需要验证x天内登录
  100. MailLvQuery.lv["$gte"] = startLv
  101. MailLvQuery.lv["$lte"] = endLv
  102. LuaMongo.find(DB.db_char, MailLvQuery, MailLvField)
  103. while true do
  104. local data = {}
  105. if not LuaMongo.next(data) then
  106. break
  107. end
  108. if not ObjHuman.onlineUuid[data._id] and
  109. (lastLoginTime == 0 or
  110. (data.lastLoginTime and data.lastLoginTime >= lastLoginTime) or
  111. (data.lastLogoutTime and data.lastLogoutTime >= lastLoginTime)
  112. ) then
  113. p_ret, err = pcall(add, SYSTEM, data._id, title, content, gridList, condition.senderName, nil, nil,fbTime,fbContent,fbAccount)
  114. if p_ret then
  115. successCount = successCount + 1
  116. else
  117. failCount = failCount + 1
  118. Log.write(Log.LOGID_ERR_PCALL, "mail role uuid=" .. data._id, err)
  119. end
  120. end
  121. end
  122. return successCount, failCount
  123. end
  124. function del(mailUuid)
  125. FIELD_ID._id = mailUuid
  126. LuaMongo.remove(DB.db_mail, FIELD_ID)
  127. end
  128. function getMail(mailUuid)
  129. FIELD_ID._id = mailUuid
  130. LuaMongo.find(DB.db_mail,FIELD_ID)
  131. local mail = {}
  132. if not LuaMongo.next(mail) then
  133. return nil
  134. end
  135. return mail
  136. end
  137. local function cmpMail(a, b)
  138. return a.time > b.time
  139. end
  140. local mails = {}
  141. -- function getMails(receiverUuid,mailType)
  142. -- for key in ipairs(mails) do
  143. -- mails[key] = nil
  144. -- end
  145. -- FIELD_RECEIVER.receiverUuid = receiverUuid
  146. -- FIELD_RECEIVER.type = mailType
  147. -- LuaMongo.find(DB.db_mail,{["$query"]=FIELD_RECEIVER})
  148. -- local lastTime = os.time() - 7 * 86400
  149. -- local mailCnt = 0
  150. -- while true do
  151. -- local mail = {}
  152. -- -- if not LuaMongo.next(mail) then
  153. -- -- break
  154. -- -- end
  155. -- local res, err = pcall(function ()
  156. -- return LuaMongo.next(mail)
  157. -- end)
  158. -- if not res then
  159. -- Log.write(Log.LOGID_DEBUG, "MailManager.getMails err = ".. err)
  160. -- break
  161. -- end
  162. -- if not err then
  163. -- break
  164. -- end
  165. -- if mail.expireTime then -- 有指定过期时间
  166. -- if mail.time > os.time() - mail.expireTime then
  167. -- mailCnt = mailCnt + 1
  168. -- mails[mailCnt] = mail
  169. -- end
  170. -- elseif mail.time > lastTime then -- 没有就用默认过期时间
  171. -- mailCnt = mailCnt + 1
  172. -- mails[mailCnt] = mail
  173. -- end
  174. -- end
  175. -- if mailCnt > MAIL_MAX_CNT then
  176. -- table.sort(mails, cmpMail)
  177. -- for i = MAIL_MAX_CNT + 1, mailCnt do
  178. -- mails[i] = nil
  179. -- end
  180. -- end
  181. -- return mails
  182. -- end
  183. -- 替换原有的 getMails 函数
  184. function getMails(receiverUuid, mailType)
  185. -- 1. 清空缓存表
  186. for i = 1, #mails do mails[i] = nil end
  187. local now = os.time()
  188. local mailCnt = 0
  189. local tempMails = {}
  190. local SCAN_LIMIT = 2000 -- 单次分段查询安全上限
  191. local foundEnough = false
  192. -- 2.将时间划分为多个小窗口,避免单次 find 触发底层 OOM
  193. -- 窗口设计:最近7天 -> 7~14天 -> 14~30天 -> 30~60天
  194. local timeWindows = {
  195. {now - 7*86400, now},
  196. {now - 14*86400, now - 7*86400},
  197. {now - 30*86400, now - 14*86400},
  198. {now - 60*86400, now - 30*86400}
  199. }
  200. -- 60天 -> 开服
  201. local serverOpenTime = CommonDB.getServerOpenTime()
  202. timeWindows[#timeWindows+1] = { serverOpenTime, now - 60*86400 }
  203. for _, window in ipairs(timeWindows) do
  204. if foundEnough then break end
  205. -- 使用最简平铺查询,彻底避开 C 扩展解析缺陷
  206. local query = {
  207. receiverUuid = receiverUuid,
  208. type = mailType,
  209. time = { ["$gte"] = window[1], ["$lte"] = window[2] }
  210. }
  211. LuaMongo.find(DB.db_mail, query)
  212. local mail = {}
  213. local scanCnt = 0
  214. while true do
  215. local has_next = LuaMongo.next(mail)
  216. if not has_next then break end
  217. scanCnt = scanCnt + 1
  218. if scanCnt > SCAN_LIMIT then
  219. Log.write(Log.LOGID_DEBUG, string.format("Mail segment limit hit! uuid=%s window=%d~%d",
  220. receiverUuid, window[1], window[2]))
  221. break
  222. end
  223. -- 浅拷贝隔离 C 驱动内部 Buffer
  224. local m = {}
  225. for k, v in pairs(mail) do m[k] = v end
  226. tempMails[#tempMails + 1] = m
  227. -- 清空复用表
  228. for k in pairs(mail) do mail[k] = nil end
  229. end
  230. -- 如果已收集足够多数据(留足过滤冗余),提前终止后续窗口查询
  231. if #tempMails >= 500 then
  232. foundEnough = true
  233. break
  234. end
  235. end
  236. -- 3.按时间倒序排序,确保后续取到的是“最新”的
  237. table.sort(tempMails, cmpMail)
  238. -- 4. 过滤过期邮件,精准截取最新 100 条
  239. for _, m in ipairs(tempMails) do
  240. if type(m.time) ~= "number" then m.time = 0 end
  241. local isExpired = false
  242. local expireTime = m.expireTime
  243. if expireTime and type(expireTime) == "number" then
  244. if m.time <= now - expireTime then isExpired = true end
  245. elseif m.time <= now - 7 * 86400 then
  246. isExpired = true
  247. end
  248. if not isExpired then
  249. mailCnt = mailCnt + 1
  250. mails[mailCnt] = m
  251. if mailCnt >= MAIL_MAX_CNT then break end -- 达到 100 条立即退出
  252. end
  253. end
  254. return mails
  255. end
  256. function saveMail(mail)
  257. FIELD_ID._id = mail._id
  258. LuaMongo.update(DB.db_mail, FIELD_ID, mail)
  259. end
  260. function delAll(uuid,mailType)
  261. local QueryMailByUuid = {get = {["$exists"] = 0}, read = {["$exists"] = 1}}
  262. QueryMailByUuid.receiverUuid = uuid
  263. QueryMailByUuid.type = mailType
  264. QueryMailByUuid.get["$exists"] = 1
  265. LuaMongo.remove(DB.db_mail, QueryMailByUuid)
  266. end