ConfirmWaitingMessageSerevice.cs 9.7 KB

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