ConfirmWaitingMessageSerevice.cs 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. using Azure.Core;
  2. using Azure;
  3. using EVCB_OCPP.Domain;
  4. using EVCB_OCPP.Domain.Models.MainDb;
  5. using EVCB_OCPP.Packet.Features;
  6. using EVCB_OCPP.WSServer.Message;
  7. using Microsoft.EntityFrameworkCore;
  8. using Microsoft.EntityFrameworkCore.Query.Internal;
  9. using Microsoft.Extensions.Logging;
  10. using Microsoft.Identity.Client;
  11. using Newtonsoft.Json;
  12. using System;
  13. using System.Collections.Generic;
  14. using System.Linq;
  15. using System.Text;
  16. using System.Threading.Tasks;
  17. namespace EVCB_OCPP.WSServer.Service
  18. {
  19. public enum ConfirmWaitingMessageRemoveReason
  20. {
  21. HitRetryLimit,
  22. Confirmed,
  23. }
  24. public class ConfirmWaitingMessageSerevice
  25. {
  26. private static readonly List<string> needConfirmActions = new List<string>()
  27. {
  28. "GetConfiguration",
  29. "ChangeConfiguration",
  30. "RemoteStartTransaction",
  31. "RemoteStopTransaction",
  32. "ChangeAvailability",
  33. "ClearCache",
  34. "DataTransfer",
  35. "Reset",
  36. "UnlockConnector",
  37. "TriggerMessage",
  38. "GetDiagnostics",
  39. "UpdateFirmware",
  40. "GetLocalListVersion",
  41. "SendLocalList",
  42. "SetChargingProfile",
  43. "ClearChargingProfile",
  44. "GetCompositeSchedule",
  45. "ReserveNow",
  46. "CancelReservation",
  47. "ExtendedTriggerMessage"
  48. };
  49. public static bool IsNeedConfirm(string action)
  50. {
  51. return needConfirmActions.Contains(action);
  52. }
  53. public ConfirmWaitingMessageSerevice(
  54. IDbContextFactory<MainDBContext> maindbContextFactory
  55. ,ILogger<ConfirmWaitingMessageSerevice> logger)
  56. {
  57. this.maindbContextFactory = maindbContextFactory;
  58. this.logger = logger;
  59. }
  60. public event EventHandler<ConfirmWaitingMessageRemoveReason> OnMessageRemoved;
  61. private readonly Object _lockConfirmPacketList = new object();
  62. private readonly IDbContextFactory<MainDBContext> maindbContextFactory;
  63. private readonly ILogger<ConfirmWaitingMessageSerevice> logger;
  64. private readonly List<NeedConfirmMessage> needConfirmPacketList = new();
  65. private readonly Dictionary<string, MessageResultWaitObject> asyncWaitingTasks = new();
  66. internal async Task Add(string chargePointSerialNumber, int table_id, string requestId, string action, string msg_id, string createdBy, string sendMessage)
  67. {
  68. NeedConfirmMessage _needConfirmMsg = new NeedConfirmMessage
  69. {
  70. Id = table_id,
  71. SentAction = action,
  72. SentOn = DateTime.UtcNow,
  73. SentTimes = 4,
  74. ChargePointSerialNumber = chargePointSerialNumber,
  75. RequestId = requestId,
  76. SentUniqueId = msg_id,
  77. CreatedBy = createdBy,
  78. SentMessage = sendMessage
  79. };
  80. if (needConfirmActions.Contains(action))
  81. {
  82. lock (_lockConfirmPacketList)
  83. {
  84. needConfirmPacketList.Add(_needConfirmMsg);
  85. }
  86. }
  87. #region 更新資料表單一欄位
  88. var dateTimeNow = DateTime.UtcNow;
  89. var _UpdatedItem = new ServerMessage() { Id = table_id, UpdatedOn = dateTimeNow };
  90. //db.Configuration.AutoDetectChangesEnabled = false;//自動呼叫DetectChanges()比對所有的entry集合的每一個屬性Properties的新舊值
  91. //db.Configuration.ValidateOnSaveEnabled = false;// 因為Entity有些欄位必填,若不避開會有Validate錯誤
  92. // var _UpdatedItem = db.ServerMessage.Where(x => x.Id == item.Id).FirstOrDefault();
  93. using (var db = await maindbContextFactory.CreateDbContextAsync())
  94. {
  95. db.ServerMessage.Attach(_UpdatedItem);
  96. _UpdatedItem.UpdatedOn = dateTimeNow;
  97. db.Entry(_UpdatedItem).Property(x => x.UpdatedOn).IsModified = true;// 可以直接使用這方式強制某欄位要更新,只是查詢集合耗效能而己
  98. await db.SaveChangesAsync();
  99. db.ChangeTracker.Clear();
  100. }
  101. #endregion
  102. }
  103. internal List<NeedConfirmMessage> GetPendingMessages()
  104. {
  105. List<NeedConfirmMessage> sendMessages = new List<NeedConfirmMessage>();
  106. lock (_lockConfirmPacketList)
  107. {
  108. sendMessages = needConfirmPacketList.Where(x => x.SentTimes > 1 && x.CreatedBy == "Server").ToList();
  109. }
  110. return sendMessages;
  111. }
  112. internal async Task<bool> TryConfirmMessage(MessageResult analysisResult)
  113. {
  114. bool confirmed = false;
  115. //if (needConfirmActions.Contains(analysisResult.Action))
  116. if (!IsNeedConfirm(analysisResult.Action))
  117. {
  118. return confirmed;
  119. }
  120. NeedConfirmMessage foundRequest = null;
  121. lock (_lockConfirmPacketList)
  122. {
  123. foundRequest = needConfirmPacketList.Where(x => x.SentUniqueId == analysisResult.UUID).FirstOrDefault();
  124. }
  125. if (foundRequest != null && foundRequest.Id > 0)
  126. {
  127. RemoveWaitingMsg(foundRequest, ConfirmWaitingMessageRemoveReason.Confirmed, analysisResult);
  128. foundRequest.SentTimes = 0;
  129. foundRequest.SentInterval = 0;
  130. analysisResult.RequestId = foundRequest.RequestId;
  131. using (var db = await maindbContextFactory.CreateDbContextAsync())
  132. {
  133. var sc = await db.ServerMessage.Where(x => x.Id == foundRequest.Id).FirstOrDefaultAsync();
  134. sc.InMessage = JsonConvert.SerializeObject(analysisResult.Message, Formatting.None);
  135. sc.ReceivedOn = DateTime.UtcNow;
  136. await db.SaveChangesAsync();
  137. // Console.WriteLine(string.Format("Now:{0} ServerMessage Id:{1} ", DateTime.UtcNow.ToString("yyyy/MM/dd HH:mm:ss"), foundRequest.Id));
  138. }
  139. confirmed = true;
  140. }
  141. else if (analysisResult.Action == Actions.TriggerMessage.ToString())
  142. {
  143. confirmed = true;
  144. }
  145. else
  146. {
  147. logger.LogError(string.Format("Received no record Action:{0} MessageId:{1} ", analysisResult.Action, analysisResult.UUID));
  148. }
  149. return confirmed;
  150. }
  151. internal void SignalMessageSended(NeedConfirmMessage resendItem)
  152. {
  153. var dateTimeNow = DateTime.UtcNow;
  154. resendItem.SentTimes--;
  155. resendItem.SentOn = dateTimeNow;
  156. }
  157. internal void RemoveExpiredConfirmMessage()
  158. {
  159. var before10Mins = DateTime.UtcNow.AddMinutes(-10);
  160. var removeList = needConfirmPacketList.Where(x => x.SentTimes == 0 || x.SentOn < before10Mins).ToList();
  161. foreach (var item in removeList)
  162. {
  163. RemoveWaitingMsg(item, ConfirmWaitingMessageRemoveReason.HitRetryLimit);
  164. }
  165. }
  166. internal async Task<MessageResult> SendAndWaitUntilResultAsync(Func<Task<string>> startSendTaskFunc, CancellationToken token = default)
  167. {
  168. MessageResult response;
  169. do
  170. {
  171. var requestId = await startSendTaskFunc();
  172. response = await WaitResultAsync(requestId, token: token);
  173. }
  174. while (response == null && !token.IsCancellationRequested);
  175. return response;
  176. }
  177. internal async Task<MessageResult> WaitResultAsync(string msgId, int maxWaitSec = 65000, CancellationToken token = default)
  178. {
  179. var waiObj = new MessageResultWaitObject();
  180. asyncWaitingTasks.Add(msgId, waiObj);
  181. var task = waiObj.Lock.WaitAsync(token);
  182. var completedTask = await Task.WhenAny(task, Task.Delay(650_000));
  183. return waiObj.Result;
  184. }
  185. private void RemoveWaitingMsg(
  186. NeedConfirmMessage msg,
  187. ConfirmWaitingMessageRemoveReason reason,
  188. MessageResult response = null)
  189. {
  190. lock (_lockConfirmPacketList)
  191. {
  192. needConfirmPacketList.Remove(msg);
  193. }
  194. if (asyncWaitingTasks.Keys.Contains(msg.RequestId))
  195. {
  196. asyncWaitingTasks[msg.RequestId].Result = response;
  197. asyncWaitingTasks[msg.RequestId].Lock.Release();
  198. }
  199. OnMessageRemoved?.Invoke(this, reason);
  200. }
  201. }
  202. internal class MessageResultWaitObject
  203. {
  204. public MessageResult Result { get; set; } = null;
  205. public SemaphoreSlim Lock { get; set; } = new SemaphoreSlim(0);
  206. }
  207. }