MainDbService.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. using EVCB_OCPP.Domain;
  2. using EVCB_OCPP.Domain.Models.Database;
  3. using EVCB_OCPP.Packet.Messages.Core;
  4. using EVCB_OCPP.WSServer.Helper;
  5. using Microsoft.EntityFrameworkCore;
  6. using Microsoft.Extensions.Configuration;
  7. using Microsoft.Extensions.DependencyInjection;
  8. using Microsoft.Extensions.Logging;
  9. using Newtonsoft.Json;
  10. using OCPPPackage.Profiles;
  11. using System;
  12. using System.Collections.Generic;
  13. using System.Linq;
  14. using System.Text;
  15. using System.Threading;
  16. using System.Threading.Tasks;
  17. namespace EVCB_OCPP.WSServer.Service;
  18. public interface IMainDbService
  19. {
  20. Task<string> GetMachineAuthorizationKey(string ChargeBoxId);
  21. Task<string> GetMachineConfiguration(string ChargeBoxId, string configName);
  22. Task<string> GetMachineHeartbeatInterval(string ChargeBoxId);
  23. Task<MachineAndCustomerInfo> GetMachineIdAndCustomerInfo(string ChargeBoxId);
  24. Task<string> GetMachineSecurityProfile(string ChargeBoxId);
  25. Task UpdateMachineBasicInfo(string ChargeBoxId, Machine machine);
  26. Task AddOCMF(OCMF oCMF);
  27. Task<ConnectorStatus> GetConnectorStatus(string ChargeBoxId, int ConnectorId);
  28. Task UpdateConnectorStatus(string Id, ConnectorStatus connectorStatus);
  29. Task AddServerMessage(ServerMessage message);
  30. Task AddServerMessage(string ChargeBoxId, string OutAction, object OutRequest, string CreatedBy = "", DateTime? CreatedOn = null, string SerialNo = "", string InMessage = "");
  31. }
  32. public class MainDbService : IMainDbService
  33. {
  34. public MainDbService(IDbContextFactory<MainDBContext> contextFactory, IConfiguration configuration, ILoggerFactory loggerFactory)
  35. {
  36. this.contextFactory = contextFactory;
  37. this.loggerFactory = loggerFactory;
  38. var startupLimit = GetStartupLimit(configuration);
  39. this.startupSemaphore = new (startupLimit);
  40. var opLimit = GetOpLimit(configuration);
  41. this.opSemaphore = new SemaphoreSlim(opLimit);
  42. InitUpdateConnectorStatusHandler();
  43. InitUpdateMachineBasicInfoHandler();
  44. InitAddServerMessageHandler();
  45. }
  46. private readonly IDbContextFactory<MainDBContext> contextFactory;
  47. private readonly ILoggerFactory loggerFactory;
  48. private readonly QueueSemaphore startupSemaphore;
  49. private readonly SemaphoreSlim opSemaphore;
  50. private GroupSingleHandler<StatusNotificationParam> statusNotificationHandler;
  51. private GroupSingleHandler<UpdateMachineBasicInfoParam> updateMachineBasicInfoHandler;
  52. private GroupSingleHandler<ServerMessage> addServerMessageHandler;
  53. public async Task<MachineAndCustomerInfo> GetMachineIdAndCustomerInfo(string ChargeBoxId)
  54. {
  55. using var semaphoreWrapper = await startupSemaphore.GetToken();
  56. using var db = contextFactory.CreateDbContext();
  57. var machine = await db.Machine.Where(x => x.ChargeBoxId == ChargeBoxId && x.IsDelete == false).Select(x => new { x.CustomerId, x.Id }).AsNoTracking().FirstOrDefaultAsync();
  58. if (machine == null)
  59. {
  60. return new MachineAndCustomerInfo(string.Empty, Guid.Empty, "Unknown");
  61. }
  62. var customerName = await db.Customer.Where(x => x.Id == machine.CustomerId).Select(x => x.Name).FirstOrDefaultAsync();
  63. return new MachineAndCustomerInfo(machine.Id, machine.CustomerId, customerName);
  64. }
  65. public async Task<string> GetMachineConfiguration(string ChargeBoxId, string configName)
  66. {
  67. using var semaphoreWrapper = await startupSemaphore.GetToken();
  68. using var db = contextFactory.CreateDbContext();
  69. return await db.MachineConfigurations
  70. .Where(x => x.ChargeBoxId == ChargeBoxId && x.ConfigureName == configName)
  71. .Select(x => x.ConfigureSetting).FirstOrDefaultAsync();
  72. }
  73. public async Task<string> GetMachineSecurityProfile(string ChargeBoxId)
  74. {
  75. return await GetMachineConfiguration(ChargeBoxId, StandardConfiguration.SecurityProfile);
  76. }
  77. public async Task<string> GetMachineAuthorizationKey(string ChargeBoxId)
  78. {
  79. return await GetMachineConfiguration(ChargeBoxId, StandardConfiguration.AuthorizationKey);
  80. }
  81. public async Task<string> GetMachineHeartbeatInterval(string ChargeBoxId)
  82. {
  83. return await GetMachineConfiguration(ChargeBoxId, StandardConfiguration.HeartbeatInterval);
  84. }
  85. public async Task UpdateMachineBasicInfo(string ChargeBoxId, Machine machine)
  86. {
  87. //using var semaphoreWrapper = await startupSemaphore.GetToken();
  88. //using var db = await contextFactory.CreateDbContextAsync();
  89. //var _machine = db.Machine.FirstOrDefault(x => x.ChargeBoxId == ChargeBoxId);
  90. //_machine.ChargeBoxSerialNumber = machine.ChargeBoxSerialNumber;
  91. //_machine.ChargePointSerialNumber = machine.ChargePointSerialNumber;
  92. //_machine.ChargePointModel = machine.ChargePointModel;
  93. //_machine.ChargePointVendor = machine.ChargePointVendor;
  94. //_machine.FW_CurrentVersion = machine.FW_CurrentVersion;
  95. //_machine.Iccid = DateTime.UtcNow.ToString("yy-MM-dd HH:mm");
  96. //_machine.Imsi = machine.Imsi;
  97. //_machine.MeterSerialNumber = machine.MeterSerialNumber;
  98. //_machine.MeterType = machine.MeterType;
  99. //await db.SaveChangesAsync();
  100. //using var semaphoreWrapper = await startupSemaphore.GetToken();
  101. await updateMachineBasicInfoHandler.HandleAsync(new UpdateMachineBasicInfoParam(ChargeBoxId, machine));
  102. }
  103. public async Task AddOCMF(OCMF oCMF)
  104. {
  105. using var db = contextFactory.CreateDbContext();
  106. db.OCMF.Add(oCMF);
  107. await db.SaveChangesAsync();
  108. }
  109. public async Task<ConnectorStatus> GetConnectorStatus(string ChargeBoxId, int ConnectorId)
  110. {
  111. using var db = contextFactory.CreateDbContext();
  112. return await db.ConnectorStatus.Where(x => x.ChargeBoxId == ChargeBoxId
  113. && x.ConnectorId == ConnectorId).AsNoTracking().FirstOrDefaultAsync();
  114. }
  115. public async Task UpdateConnectorStatus(string Id, ConnectorStatus Status)
  116. {
  117. //using var db = await contextFactory.CreateDbContextAsync();
  118. //ConnectorStatus status = new() { Id = Id };
  119. //db.ChangeTracker.AutoDetectChangesEnabled = false;
  120. //db.ConnectorStatus.Attach(status);
  121. //status.CreatedOn = Status.CreatedOn;
  122. //status.Status = Status.Status;
  123. //status.ChargePointErrorCodeId = Status.ChargePointErrorCodeId;
  124. //status.ErrorInfo = Status.ErrorInfo;
  125. //status.VendorId = Status.VendorId;
  126. //status.VendorErrorCode = Status.VendorErrorCode;
  127. //db.Entry(status).Property(x => x.CreatedOn).IsModified = true;
  128. //db.Entry(status).Property(x => x.Status).IsModified = true;
  129. //db.Entry(status).Property(x => x.ChargePointErrorCodeId).IsModified = true;
  130. //db.Entry(status).Property(x => x.ErrorInfo).IsModified = true;
  131. //db.Entry(status).Property(x => x.VendorId).IsModified = true;
  132. //db.Entry(status).Property(x => x.VendorErrorCode).IsModified = true;
  133. //await db.SaveChangesAsync();
  134. await statusNotificationHandler.HandleAsync(new StatusNotificationParam(Id, Status));
  135. return;
  136. }
  137. public Task AddServerMessage(string ChargeBoxId, string OutAction, object OutRequest, string CreatedBy, DateTime? CreatedOn = null, string SerialNo = "", string InMessage = "")
  138. {
  139. if (string.IsNullOrEmpty(CreatedBy))
  140. {
  141. CreatedBy = "Server";
  142. }
  143. if (string.IsNullOrEmpty(SerialNo))
  144. {
  145. SerialNo = Guid.NewGuid().ToString();
  146. }
  147. var _CreatedOn = CreatedOn ?? DateTime.UtcNow;
  148. string _OutRequest = "";
  149. if (OutRequest is not null)
  150. {
  151. _OutRequest = JsonConvert.SerializeObject(
  152. OutRequest,
  153. new JsonSerializerSettings()
  154. {
  155. NullValueHandling = NullValueHandling.Ignore,
  156. Formatting = Formatting.None
  157. });
  158. }
  159. return AddServerMessage(new ServerMessage()
  160. {
  161. ChargeBoxId = ChargeBoxId,
  162. CreatedBy = CreatedBy,
  163. CreatedOn = _CreatedOn,
  164. OutAction = OutAction,
  165. OutRequest = _OutRequest,
  166. SerialNo = SerialNo,
  167. InMessage = InMessage
  168. });
  169. }
  170. public Task AddServerMessage(ServerMessage message)
  171. {
  172. return addServerMessageHandler.HandleAsync(message);
  173. }
  174. private void InitUpdateMachineBasicInfoHandler()
  175. {
  176. if (updateMachineBasicInfoHandler is not null)
  177. {
  178. throw new Exception($"{nameof(InitUpdateMachineBasicInfoHandler)} should only called once");
  179. }
  180. updateMachineBasicInfoHandler = new GroupSingleHandler<UpdateMachineBasicInfoParam>(
  181. handleFunc: BundelUpdateMachineBasicInfo,
  182. logger: loggerFactory.CreateLogger("UpdateMachineBasicInfoHandler"),
  183. workerCnt: 10);
  184. }
  185. private async Task BundelUpdateMachineBasicInfo(IEnumerable<UpdateMachineBasicInfoParam> pams)
  186. {
  187. using var db = await contextFactory.CreateDbContextAsync();
  188. using var trans = await db.Database.BeginTransactionAsync();
  189. pams = pams.DistinctBy(x => x.ChargeBoxId);
  190. foreach (var pam in pams)
  191. {
  192. var _machine = db.Machine.FirstOrDefault(x => x.ChargeBoxId == pam.ChargeBoxId);
  193. _machine.ChargeBoxSerialNumber = pam.machine.ChargeBoxSerialNumber;
  194. _machine.ChargePointSerialNumber = pam.machine.ChargePointSerialNumber;
  195. _machine.ChargePointModel = pam.machine.ChargePointModel;
  196. _machine.ChargePointVendor = pam.machine.ChargePointVendor;
  197. _machine.FW_CurrentVersion = pam.machine.FW_CurrentVersion;
  198. _machine.Iccid = DateTime.UtcNow.ToString("yy-MM-dd HH:mm");
  199. _machine.Imsi = pam.machine.Imsi;
  200. _machine.MeterSerialNumber = pam.machine.MeterSerialNumber;
  201. _machine.MeterType = pam.machine.MeterType;
  202. }
  203. db.SaveChanges();
  204. trans.Commit();
  205. }
  206. private void InitUpdateConnectorStatusHandler()
  207. {
  208. if (statusNotificationHandler is not null)
  209. {
  210. throw new Exception($"{nameof(InitUpdateConnectorStatusHandler)} should only called once");
  211. }
  212. statusNotificationHandler = new GroupSingleHandler<StatusNotificationParam>(
  213. handleFunc: BundleUpdateConnectorStatus,
  214. logger: loggerFactory.CreateLogger("StatusNotificationHandler"),
  215. workerCnt: 10);
  216. }
  217. private async Task BundleUpdateConnectorStatus(IEnumerable<StatusNotificationParam> statusNotifications)
  218. {
  219. using var db = await contextFactory.CreateDbContextAsync();
  220. using var trans = await db.Database.BeginTransactionAsync();
  221. statusNotifications = statusNotifications.OrderBy(x => x.Status.CreatedOn).DistinctBy(x => x.Id);
  222. foreach (var param in statusNotifications)
  223. {
  224. ConnectorStatus status = new() { Id = param.Id };
  225. //db.ChangeTracker.AutoDetectChangesEnabled = false;
  226. db.ConnectorStatus.Attach(status);
  227. status.CreatedOn = param.Status.CreatedOn;
  228. status.Status = param.Status.Status;
  229. status.ChargePointErrorCodeId = param.Status.ChargePointErrorCodeId;
  230. status.ErrorInfo = param.Status.ErrorInfo;
  231. status.VendorId = param.Status.VendorId;
  232. status.VendorErrorCode = param.Status.VendorErrorCode;
  233. db.Entry(status).Property(x => x.CreatedOn).IsModified = true;
  234. db.Entry(status).Property(x => x.Status).IsModified = true;
  235. db.Entry(status).Property(x => x.ChargePointErrorCodeId).IsModified = true;
  236. db.Entry(status).Property(x => x.ErrorInfo).IsModified = true;
  237. db.Entry(status).Property(x => x.VendorId).IsModified = true;
  238. db.Entry(status).Property(x => x.VendorErrorCode).IsModified = true;
  239. //db.SaveChanges();
  240. }
  241. db.SaveChanges();
  242. trans.Commit();
  243. db.ChangeTracker.Clear();
  244. }
  245. private void InitAddServerMessageHandler()
  246. {
  247. if (addServerMessageHandler is not null)
  248. {
  249. throw new Exception($"{nameof(InitAddServerMessageHandler)} should only called once");
  250. }
  251. addServerMessageHandler = new GroupSingleHandler<ServerMessage>(
  252. handleFunc: BundleAddServerMessage,
  253. logger: loggerFactory.CreateLogger("AddServerMessageHandler"));
  254. }
  255. private async Task BundleAddServerMessage(IEnumerable<ServerMessage> messages)
  256. {
  257. using var db = await contextFactory.CreateDbContextAsync();
  258. using var trans = await db.Database.BeginTransactionAsync();
  259. foreach (var message in messages)
  260. {
  261. db.ServerMessage.Add(message);
  262. }
  263. db.SaveChanges();
  264. trans.Commit();
  265. db.ChangeTracker.Clear();
  266. }
  267. private int GetStartupLimit(IConfiguration configuration)
  268. {
  269. var limitConfig = configuration["MainDbStartupLimit"];
  270. int limit = 5;
  271. if (limitConfig != default)
  272. {
  273. int.TryParse(limitConfig, out limit);
  274. }
  275. return limit;
  276. }
  277. private int GetOpLimit(IConfiguration configuration)
  278. {
  279. var limitConfig = configuration["MainDbOpLimit"];
  280. int limit = 500;
  281. if (limitConfig != default)
  282. {
  283. int.TryParse(limitConfig, out limit);
  284. }
  285. return limit;
  286. }
  287. }
  288. public record MachineAndCustomerInfo (string MachineId, Guid CustomerId, string CustomerName);
  289. public record StatusNotificationParam(string Id, ConnectorStatus Status);
  290. public record UpdateMachineBasicInfoParam(string ChargeBoxId, Machine machine);