using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using Sog; using ProtoCSStruct; namespace World { public class WaitAckDiamondHolderOneObj { public WaitAckDiamondHolderOne Data; public long LastSendTime; } public class WaitAckMutexOrderID { public string OrderID; public long CreateTime;//保护10秒 } /// /// 一定需要等待回应的消息发送器 /// 邮件之类需要严格处理逻辑的可以使用 /// public class WaitAckDiamondHolderReqSender : Singleton { private const int TimeoutDeleteTimeSecond = 24*3600;//默认24小时超时,会强制删除 private int m_sendCountThisTick = 0; //频率控制 private long m_lastSaveTime = 0; private bool m_bDirty; //uid 互斥列表 private Dictionary m_mutexMap = new Dictionary(); private Dictionary m_sendAllPacket = new Dictionary(); public void Init() { ReadFromFile(); } public int CheckMutex(PlayerInfoWorld player) { if(m_mutexMap.ContainsKey(player.UserID)) { string orderId = m_mutexMap[player.UserID].OrderID; TraceLog.Error("WaitAckDiamondHolderReqSender.CheckMutex uid {0} orderId {1}", player.UserID, orderId); return -1; } return 0; } public void AddToMutex(long uid, string orderId) { if(m_mutexMap.ContainsKey(uid)) { return; } WaitAckMutexOrderID mutexOrderID = new WaitAckMutexOrderID(); mutexOrderID.OrderID = orderId; mutexOrderID.CreateTime = WorldServerUtils.GetTimeSecond(); m_mutexMap.Add(uid, mutexOrderID); } public void RemoveMutexOrderId(long uid, string orderId) { if (m_mutexMap.ContainsKey(uid)) { string mutexOrderId = m_mutexMap[uid].OrderID; if (mutexOrderId == orderId) { m_mutexMap.Remove(uid); TraceLog.Debug("WaitAckDiamondHolderReqSender.RemoveMutexOrderId orderid {0} uid {1} success" , orderId, uid); } } } public void BeginSendAdd(ref SSPayDiamondHolderAddReq req, PlayerInfoWorld player) { TraceLog.Debug("WaitAckDiamondHolderReqSender.BeginSendAdd orderId {0} uid {1} add {2}" , req.OrderId, req.Uid, req.Add); WaitAckDiamondHolderOneObj obj = new WaitAckDiamondHolderOneObj(); obj.Data.Uid = player.UserID; obj.Data.RealmId = player.RealmID; obj.Data.Account = req.Account; obj.Data.Qqpayinfo = req.Qqpayinfo; obj.Data.OrderId = req.OrderId; obj.Data.ReqType = (int)SSGameMsgID.PayDiamondHolderAddReq; obj.Data.Value = req.Add; obj.Data.Mutex = req.Mutex; obj.Data.ReasonId = req.ReasonId; string orderId = req.OrderId.GetString(); m_sendAllPacket.Add(orderId, obj); if(req.Mutex) { AddToMutex(player.UserID, orderId); TraceLog.Debug("WaitAckDiamondHolderReqSender.BeginSendAdd orderId {0} add mutex uid {1}" , orderId, player.UserID); } SendReq(obj); m_bDirty = true; } public void OnReceiveSuccess(long uid, string orderId) { if(m_sendAllPacket.ContainsKey(orderId)) { RemoveByOrderId(orderId); TraceLog.Debug("WaitAckDiamondHolderReqSender.OnReceiveSuccess orderId {0} , removed" , orderId); } else { TraceLog.Debug("WaitAckDiamondHolderReqSender.OnReceiveSuccess no orderId {0}", orderId); } //有可能orderId不在m_sendAllPacket里,只在m_mutexMap里,这是有可能的 RemoveMutexOrderId(uid, orderId); } public void Tick(long nowSecond) { TickSender(nowSecond); TickMutex(nowSecond); TickSave(nowSecond); } private void TickSender(long nowSecond) { m_sendCountThisTick = 0; List deleteList = null; foreach (var pair in m_sendAllPacket) { var one = pair.Value; //timeout delete if (nowSecond - one.Data.CreateTime > TimeoutDeleteTimeSecond) { if (deleteList == null) { deleteList = new List(); } deleteList.Add(one.Data.OrderId.GetString()); //需要记录bill日志,表示超时失败 TraceLog.Error("WaitAckDiamondHolderReqSender.TickSender timeout, Deleted, uid {0} orderId {1} type {2} value {3}" , one.Data.Uid , one.Data.OrderId , one.Data.ReqType , one.Data.Value); continue; } //动态计算重试时间 //前5分钟每分钟尝试一次,如果还失败,每5分钟尝试一次 int resenderTimeOut = 60; if(one.Data.Mutex) { resenderTimeOut = 30; //互斥的快点处理 } if (nowSecond - one.Data.CreateTime > 300) { resenderTimeOut = 300; } //失败了重新尝试发送 if (nowSecond - one.LastSendTime > resenderTimeOut) { one.LastSendTime = nowSecond; //发送 SendReq(one); TraceLog.Error("WaitAckDiamondHolderReqSender orderid {0} MsgID {1} uid {2} timeout ,resend it" , one.Data.OrderId, one.Data.ReqType, one.Data.Uid); m_sendCountThisTick++; //控制1秒最多100个请求,防止雪崩 if (m_sendCountThisTick > 100) { break; } } } if (deleteList != null) { foreach (var key in deleteList) { RemoveByOrderId(key); } m_bDirty = true; } } private void TickMutex(long nowSecond) { List deleteList = null; foreach(var pair in m_mutexMap) { if(nowSecond - pair.Value.CreateTime >= 30) { if(deleteList == null) { deleteList = new List(); } deleteList.Add(pair.Key); } } if (deleteList != null) { foreach (var key in deleteList) { m_mutexMap.Remove(key); } } } private void RemoveByOrderId(string orderId) { WaitAckDiamondHolderOneObj obj; if (m_sendAllPacket.TryGetValue(orderId, out obj)) { m_sendAllPacket.Remove(orderId); if(obj.Data.Mutex) { RemoveMutexOrderId(obj.Data.Uid, orderId); } } } private void SendReq(WaitAckDiamondHolderOneObj one) { if(one.Data.ReqType == (int)SSGameMsgID.PayDiamondHolderAddReq) { SSPayDiamondHolderAddReq req = new SSPayDiamondHolderAddReq(); req.RealmId = one.Data.RealmId; req.OrderId = one.Data.OrderId; req.Uid = one.Data.Uid; req.Account = one.Data.Account; req.Qqpayinfo = one.Data.Qqpayinfo; req.Add = one.Data.Value; req.Mutex = one.Data.Mutex; req.ReasonId = one.Data.ReasonId; //先实现托管给数据库,没问题了再实现托管给米大师 uint dbServerID = DBServerIDUtils.GetGameDBServerID(req.Uid); WorldServerUtils.GetPacketSender().SendToServerByID(dbServerID, (int)SSGameMsgID.PayDiamondHolderAddReq, ref req, req.Uid); } } private void TickSave(long nowSecond) { if(m_bDirty == false) { return; } //1分钟尝试保存一下 if (nowSecond - m_lastSaveTime < 60) { return; } m_lastSaveTime = nowSecond; SaveToFile(); m_bDirty = false; } public string GetFileDBName() { return "../bin/" + "world_waitackdiamondhandler.fdb"; } public void SaveToFile() { string filepath = GetFileDBName(); try { byte[] dataByte = new byte[1024]; FileStream fileStream = File.OpenWrite(filepath); //清空文件 fileStream.SetLength(0); BinaryWriter sw = new BinaryWriter(fileStream); foreach (var onereq in m_sendAllPacket) { int size = onereq.Value.Data.CalculateSize(); CodedOutputStream ouput = new CodedOutputStream(dataByte); onereq.Value.Data.WriteTo(ouput); sw.Write(size); sw.Write(dataByte,0,size); } sw.Flush(); sw.Dispose(); fileStream.Dispose(); } catch (Exception ex) { TraceLog.Error("WaitAckDiamondHolderReqSender.SaveToFile write file failed! {0}", filepath); TraceLog.Exception(ex); } } private void ReadFromFile() { string filepath = GetFileDBName(); if (File.Exists(filepath) == false) { //这个不能算错误 TraceLog.Debug("WaitAckDiamondHolderReqSender.ReadFromFile file not exist {0}", filepath); return ; } try { byte[] data = File.ReadAllBytes(filepath); if(data.Length < 4) { //TraceLog.Error("WaitAckDiamondHolderReqSender.ReadFromFile length invalid {0}", filepath); return; } int startIndex = 0; while(startIndex < data.Length - 10) { int oneDataLength = BitConverter.ToInt32(data, startIndex); //orderid挺长的,其实肯定不止10个长度 if (oneDataLength < 10) { break; } startIndex += 4; ProtoCSStruct.CodedInputStream input = new ProtoCSStruct.CodedInputStream(data, startIndex, oneDataLength); startIndex += oneDataLength; WaitAckDiamondHolderOneObj one = new WaitAckDiamondHolderOneObj(); one.Data.ReadFrom(input); string orderId = one.Data.OrderId.GetString(); if(m_sendAllPacket.ContainsKey(orderId) == false) { m_sendAllPacket.Add(orderId, one); } } TraceLog.Debug("WaitAckDiamondHolderReqSender.ReadFromFile read WaitAckDiamondHolderOne count {0}", m_sendAllPacket.Count); return ; } catch (Exception ex) { TraceLog.Error("WaitAckDiamondHolderReqSender.ReadFromFile parse message failed! {0}", filepath); TraceLog.Exception(ex); return ; } } } }