App.cs 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. using AutoMapper;
  2. using Edge.Core.Database;
  3. using Edge.Core.Database.Models;
  4. using Edge.Core.IndustryStandardInterface.PhotoVoltaicInverter;
  5. using Edge.Core.Processor;
  6. using Edge.Core.Processor.Dispatcher.Attributes;
  7. using Edge.Core.UniversalApi;
  8. using Microsoft.EntityFrameworkCore;
  9. using Microsoft.Extensions.DependencyInjection;
  10. using Microsoft.Extensions.Logging;
  11. using Microsoft.Extensions.Logging.Abstractions;
  12. using System;
  13. using System.Collections.Generic;
  14. using System.Linq;
  15. using System.Net.Http;
  16. using System.Text;
  17. using System.Text.Json;
  18. using System.Text.Json.Serialization;
  19. using System.Threading.Tasks;
  20. namespace SuZhou_SIPAC_Client
  21. {
  22. [MetaPartsDescriptor(
  23. "SIPAC 苏州工业园区碳达峰平台",
  24. "用于配置 SIPAC 苏州工业园区 光伏发电数据接入园区的碳达峰平台 接入app的参数",
  25. new[] { "lang-zh-cn:光伏lang-en-us:PhotoVoltaic" })]
  26. public class App : IAppProcessor
  27. {
  28. private bool isStarted = false;
  29. private AppConfigV1 appConfig;
  30. private IServiceProvider services;
  31. private IEnumerable<IPhotoVoltaicInverterHandler> inverterHandlers;
  32. private System.Timers.Timer httpPostTimer;
  33. private ILogger logger = NullLogger.Instance;
  34. public string MetaConfigName { get; set; }
  35. private HttpClient httpClient = new HttpClient();
  36. public class AppConfigV1
  37. {
  38. /// <summary>
  39. /// 1. 光伏接口(单个插入)
  40. /// </summary>
  41. public string HttpPostUrl_ForSingleRecord { get; set; }
  42. public string HttpPostTestUrl { get; set; } = "http://jjdn-yqfk.sipac.gov.cn/api/jjdn/photovoltaics/test";
  43. public int HttpPostIntervalBySecond { get; set; } = 60 * 5;
  44. /// <summary>
  45. /// 2. 光伏接口(批量插入)
  46. /// </summary>
  47. //public string HttpPostUrl_ForBatchRecords { get; set; }
  48. public string Id_唯一标识符 { get; set; }
  49. public string gffdqyhh_光伏发电企业户号 { get; set; }
  50. public string qymc_企业名称 { get; set; }
  51. public string tyshxydm_统一社会信用代码 { get; set; }
  52. public string jsdd_建设地点 { get; set; }
  53. public string jsddjwd_建设地点经纬度 { get; set; }
  54. public string xmmc_项目名称 { get; set; }
  55. public string bwtysj_并网投运时间 { get; set; }
  56. }
  57. [ParamsJsonSchemas("appCtorParamsJsonSchema")]
  58. public App(AppConfigV1 appConfig, IServiceProvider services)
  59. {
  60. this.appConfig = appConfig;
  61. var loggerFactory = services.GetRequiredService<ILoggerFactory>();
  62. this.logger = loggerFactory.CreateLogger("DynamicPrivate_SuZhou_SIPAC_Client");
  63. this.services = services;
  64. this.httpPostTimer = new System.Timers.Timer();
  65. this.httpPostTimer.Interval = this.appConfig.HttpPostIntervalBySecond * 1000;
  66. this.httpPostTimer.Elapsed += async (s, a) =>
  67. {
  68. await ReadAndPersistDeviceRealTimeDataAsync();
  69. };
  70. }
  71. private async Task ReadAndPersistDeviceRealTimeDataAsync()
  72. {
  73. if (this.inverterHandlers == null || !this.inverterHandlers.Any())
  74. {
  75. //var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
  76. //await universalApiHub.FirePersistGenericAlarmIfNotExists(this,
  77. // new GenericAlarm()
  78. // {
  79. // Title = $"地址为{targetDevice.SlaveAddress}的光伏逆变器未配置",
  80. // Category = $"光伏逆变器",
  81. // Detail = $"地址为{targetDevice.SlaveAddress}的光伏逆变器:{targetDevice.Description ?? ""} 于 {DateTime.Now} 成功连接,但本地未对其进行配置,请打开FCC配置页面添加此设备",
  82. // Severity = GenericAlarmSeverity.Warning
  83. // }, ga => ga.Detail,
  84. // ga => ga.Detail);
  85. return;
  86. }
  87. var realTimeDataInfos = new List<Tuple<InverterDevice, InverterDeviceRealTimeData>>();
  88. foreach (var h in this.inverterHandlers)
  89. {
  90. var devices = h.GetDevices();
  91. foreach (var d in devices)
  92. {
  93. try
  94. {
  95. this.logger.LogDebug($"Reading realtime data for inverter({d.Name ?? ""} - {(d.Description ?? "")} with slaveAddr: {d.SlaveAddress})...");
  96. var data = await h.ReadRealTimeDataAsync(d.SlaveAddress);
  97. this.logger.LogDebug($" read realtime data for inverter(slaveAddr: {d.SlaveAddress}): {data.ToLogString()}");
  98. realTimeDataInfos.Add(new Tuple<InverterDevice, InverterDeviceRealTimeData>(d, data));
  99. }
  100. catch (Exception eee)
  101. {
  102. /*the inverter device always shutdown itself automatically at midnight, so ommit below to avoid produce too many alarms */
  103. this.logger.LogInformation($" read realtime data for inverter(slaveAddr: {d.SlaveAddress}) got exception, will skip this reading loop: {eee}");
  104. //var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
  105. //await universalApiHub.FirePersistGenericAlarmIfNotExists(this,
  106. // new GenericAlarm()
  107. // {
  108. // Title = $"读取地址为{d.SlaveAddress}的光伏逆变器发电数据失败",
  109. // Category = $"光伏逆变器",
  110. // Detail = $"地址为{d.SlaveAddress}的光伏逆变器:{d.Description ?? ""} 于 {DateTime.Now} 进行实时发电数据读取时出错.",
  111. // Severity = GenericAlarmSeverity.Warning
  112. // }, ga => ga.Detail,
  113. // ga => ga.Detail);
  114. //return;
  115. }
  116. }
  117. }
  118. await this.PersistRealTimeDataAsync(realTimeDataInfos);
  119. }
  120. public void Init(IEnumerable<IProcessor> processors)
  121. {
  122. var inverterHandlers = processors.WithHandlerOrApp<IPhotoVoltaicInverterHandler>().SelectHandlerOrAppThenCast<IPhotoVoltaicInverterHandler>();
  123. this.inverterHandlers = inverterHandlers;
  124. this.logger.LogInformation($"Found {this.inverterHandlers?.Count() ?? -1} inverter group handlers, and total {this.inverterHandlers?.SelectMany(ih => ih.GetDevices())?.Count() ?? -1} devices.");
  125. }
  126. private async Task PersistRealTimeDataAsync(IEnumerable<Tuple<InverterDevice, InverterDeviceRealTimeData>> realTimeDataBatchInfos)
  127. {
  128. //Guid readingBatchNo = Guid.NewGuid();
  129. var realTimeDataModels = realTimeDataBatchInfos.Select(d => new InverterRealTimeDataModel()
  130. {
  131. ReadTimeStamp = DateTime.Now,
  132. DeviceName = d.Item1.Name,
  133. DeviceDescription = d.Item1.Description,
  134. DeviceSlaveAddress = d.Item1.SlaveAddress,
  135. Raw_当日有功发电量 = d.Item2.日有功发电量,
  136. Raw_当日无功发电量 = d.Item2.日无功发电量,
  137. }).ToList();
  138. using (var dbContext = new AppDbContext())
  139. {
  140. try
  141. {
  142. var lastAccumulatedDataModel = await dbContext.InverterAccumulatedDatas.Include(d => d.SourceRealTimeDatas).OrderByDescending(d => d.Id).FirstOrDefaultAsync();
  143. decimal 当日有功发电量_incremental = 0;
  144. decimal 当日无功发电量_incremental = 0;
  145. if (lastAccumulatedDataModel == null)
  146. {
  147. 当日有功发电量_incremental = realTimeDataModels.Sum(d => d.Raw_当日有功发电量);
  148. 当日无功发电量_incremental = realTimeDataModels.Sum(d => d.Raw_当日无功发电量);
  149. }
  150. else
  151. {
  152. if (realTimeDataModels.Max(n => n.ReadTimeStamp).Year == lastAccumulatedDataModel.CreatedTimeStamp.Year
  153. && realTimeDataModels.Max(n => n.ReadTimeStamp).DayOfYear == lastAccumulatedDataModel.CreatedTimeStamp.DayOfYear)
  154. {
  155. /*当日有功发电量 and 当日无功发电量 are directly retrieved from device, and device already accumlated them,
  156. * so here need caculate the incremental and later add to 当月 and 当年
  157. */
  158. 当日有功发电量_incremental = realTimeDataModels.Sum(d => d.Raw_当日有功发电量) - lastAccumulatedDataModel.当日有功发电量;
  159. 当日无功发电量_incremental = realTimeDataModels.Sum(d => d.Raw_当日无功发电量) - lastAccumulatedDataModel.当日无功发电量;
  160. if (当日有功发电量_incremental < 0 || 当日无功发电量_incremental < 0)
  161. {
  162. this.logger.LogInformation($"PersistRealTimeDataAsync, see at least one negative value for 当日有功发电量_incremental: {当日有功发电量_incremental} OR 当日无功发电量_incremental: {当日无功发电量_incremental}, will reset today's accumulation as it likes the device side reset its accum.");
  163. /*this indicates the device side reset day accum, for what reason?*/
  164. 当日有功发电量_incremental = realTimeDataModels.Sum(d => d.Raw_当日有功发电量);
  165. 当日无功发电量_incremental = realTimeDataModels.Sum(d => d.Raw_当日无功发电量);
  166. }
  167. }
  168. else
  169. {
  170. this.logger.LogDebug($"PersistRealTimeDataAsync, see a day flip, so set 当日 incremental to this round of realTime reading data.");
  171. /*this indicates a day flip, the device side should have cleared the day accumluation and start a new round from zero for day*/
  172. 当日有功发电量_incremental = realTimeDataModels.Sum(d => d.Raw_当日有功发电量);
  173. 当日无功发电量_incremental = realTimeDataModels.Sum(d => d.Raw_当日无功发电量);
  174. }
  175. }
  176. this.logger.LogDebug($"PersistRealTimeDataAsync, 当日有功发电量_incremental: {当日有功发电量_incremental}, 当日无功发电量_incremental: {当日无功发电量_incremental}");
  177. var newAccumulatedDataModel = new InverterAccumulatedDataModel();
  178. newAccumulatedDataModel.CreatedTimeStamp = DateTime.Now;
  179. newAccumulatedDataModel.SourceRealTimeDatas = realTimeDataModels;
  180. newAccumulatedDataModel.当日有功发电量 = realTimeDataModels.Sum(d => d.Raw_当日有功发电量);
  181. newAccumulatedDataModel.当日无功发电量 = realTimeDataModels.Sum(d => d.Raw_当日无功发电量);
  182. if (lastAccumulatedDataModel == null)
  183. {
  184. newAccumulatedDataModel.当月有功发电量 = realTimeDataModels.Sum(d => d.Raw_当日有功发电量);
  185. newAccumulatedDataModel.当月无功发电量 = realTimeDataModels.Sum(d => d.Raw_当日无功发电量);
  186. newAccumulatedDataModel.当年有功发电量 = realTimeDataModels.Sum(d => d.Raw_当日有功发电量);
  187. newAccumulatedDataModel.当年无功发电量 = realTimeDataModels.Sum(d => d.Raw_当日无功发电量);
  188. }
  189. else
  190. {
  191. var lastSourceTimeStamp = lastAccumulatedDataModel.SourceRealTimeDatas.Max(d => d.ReadTimeStamp);
  192. if (lastSourceTimeStamp.Year != realTimeDataModels.First().ReadTimeStamp.Year)
  193. {
  194. this.logger.LogDebug($"PersistRealTimeDataAsync, see a year flip, so reset 当月 and 当年");
  195. /*this indicates a year flip*/
  196. newAccumulatedDataModel.当月有功发电量 = realTimeDataModels.Sum(d => d.Raw_当日有功发电量);
  197. newAccumulatedDataModel.当月无功发电量 = realTimeDataModels.Sum(d => d.Raw_当日无功发电量);
  198. newAccumulatedDataModel.当年有功发电量 = realTimeDataModels.Sum(d => d.Raw_当日有功发电量);
  199. newAccumulatedDataModel.当年无功发电量 = realTimeDataModels.Sum(d => d.Raw_当日无功发电量);
  200. }
  201. else
  202. {
  203. newAccumulatedDataModel.当年有功发电量 = lastAccumulatedDataModel.当年有功发电量 + 当日有功发电量_incremental;
  204. newAccumulatedDataModel.当年无功发电量 = lastAccumulatedDataModel.当年无功发电量 + 当日无功发电量_incremental;
  205. if (lastSourceTimeStamp.Month == realTimeDataModels.First().ReadTimeStamp.Month)
  206. {
  207. newAccumulatedDataModel.当月有功发电量 = lastAccumulatedDataModel.当月有功发电量 + 当日有功发电量_incremental;
  208. newAccumulatedDataModel.当月无功发电量 = lastAccumulatedDataModel.当月无功发电量 + 当日无功发电量_incremental;
  209. }
  210. else
  211. {
  212. this.logger.LogDebug($"PersistRealTimeDataAsync, see a month flip, so reset 当月");
  213. /*this indicates a month flip*/
  214. newAccumulatedDataModel.当月有功发电量 = realTimeDataModels.Sum(d => d.Raw_当日有功发电量);
  215. newAccumulatedDataModel.当月无功发电量 = realTimeDataModels.Sum(d => d.Raw_当日无功发电量);
  216. }
  217. }
  218. }
  219. await dbContext.AddRangeAsync(realTimeDataModels);
  220. await dbContext.AddAsync(newAccumulatedDataModel);
  221. await dbContext.SaveChangesAsync();
  222. }
  223. catch (Exception ex)
  224. {
  225. logger.LogError($"Exception In db SaveChangesAsync for new realTimeDataModels and newAccumulatedDataModel, exception: {ex}");
  226. var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
  227. await universalApiHub.FirePersistGenericAlarmIfNotExists(this,
  228. new GenericAlarm()
  229. {
  230. Title = $"存储光伏逆变器待上传数据失败",
  231. Category = $"存储待上传数据",
  232. Detail = $"Exception In db SaveChangesAsync for new realTimeDataModels and newAccumulatedDataModel, exception: {ex}",
  233. Severity = GenericAlarmSeverity.Warning
  234. }, ga => ga.Detail,
  235. ga => ga.Detail);
  236. return;
  237. }
  238. }
  239. }
  240. public async Task<bool> Start()
  241. {
  242. #region Migrating database
  243. this.logger.LogInformation("Migrating database...");
  244. var migrateDbContext = new AppDbContext();
  245. try
  246. {
  247. migrateDbContext.Database.Migrate();
  248. }
  249. catch (Exception exx)
  250. {
  251. string migrationsStr = "";
  252. string pendingMigrationsStr = "";
  253. string appliedMigrationsStr = "";
  254. try
  255. {
  256. migrationsStr = migrateDbContext.Database?.GetMigrations()?.Aggregate("", (acc, n) => acc + ", " + n) ?? "";
  257. pendingMigrationsStr = migrateDbContext.Database?.GetPendingMigrations()?.Aggregate("", (acc, n) => acc + ", " + n) ?? "";
  258. appliedMigrationsStr = migrateDbContext.Database?.GetAppliedMigrations()?.Aggregate("", (acc, n) => acc + ", " + n) ?? "";
  259. }
  260. catch
  261. {
  262. }
  263. this.logger.LogError("SuZhou_SIPAC_Client App Exceptioned when Migrating the database, detail: " +
  264. exx + System.Environment.NewLine +
  265. "migrations are: " + migrationsStr + System.Environment.NewLine +
  266. ", pendingMigrations are: " + pendingMigrationsStr + System.Environment.NewLine +
  267. ", appliedMigrations are: " + appliedMigrationsStr);
  268. throw new InvalidOperationException("failed for migrating the database");
  269. }
  270. this.logger.LogInformation(" Migrate database finished.");
  271. #endregion
  272. this.httpPostTimer.Start();
  273. this.isStarted = true;
  274. var __ = Task.Run(async () => { await this.StartHttpPostLoopAsync(); });
  275. return true;
  276. }
  277. private async Task AddTestData()
  278. {
  279. var realTimeDatas = Enumerable.Range(0, 10).Select(i => new Tuple<InverterDevice, InverterDeviceRealTimeData>(
  280. new InverterDevice("device" + i, (byte)i) { Description = $"I'm the dummy device{i}" },
  281. new InverterDeviceRealTimeData()
  282. {
  283. 日有功发电量 = 10 + i,
  284. 日无功发电量 = 5 + i,
  285. }
  286. ));
  287. await this.PersistRealTimeDataAsync(realTimeDatas);
  288. //var inverterAccumulatedDataModels = Enumerable.Range(0, 9).Select(i => new InverterAccumulatedDataModel()
  289. //{
  290. // CreatedTimeStamp = DateTime.Now.Subtract(new TimeSpan(10 - i, 0, 0)),
  291. // TriggeredReadingBatchNo = Guid.NewGuid(),
  292. // 当日有功发电量 = 10 + i * 10,
  293. // 当日无功发电量 = 5 + i * 10,
  294. // 当月有功发电量 = 100 + i * 10,
  295. // 当月无功发电量 = 50 + i * 10,
  296. // 当年有功发电量 = 1000 + i * 10,
  297. // 当年无功发电量 = 500 + i * 10,
  298. //});
  299. //using (var dbContext = new AppDbContext())
  300. //{
  301. // try
  302. // {
  303. // await dbContext.InverterAccumulatedDatas.AddRangeAsync(inverterAccumulatedDataModels);
  304. // await dbContext.SaveChangesAsync();
  305. // }
  306. // catch (Exception ex)
  307. // {
  308. // logger.LogError($"Exception In AddTestData, exception: {ex}");
  309. // return;
  310. // }
  311. //}
  312. }
  313. public Task<bool> Stop()
  314. {
  315. this.isStarted = false;
  316. this.httpPostTimer?.Stop();
  317. return Task.FromResult(true);
  318. }
  319. public async Task Test(params object[] parameters)
  320. {
  321. if (string.IsNullOrEmpty(this.appConfig.HttpPostTestUrl))
  322. throw new NotImplementedException("用于测试的 HTTP URL 未配置, 无法进行测试");
  323. var postContent = JsonSerializer.Serialize(new
  324. {
  325. id = this.appConfig.Id_唯一标识符,
  326. gffdqyhh = this.appConfig.gffdqyhh_光伏发电企业户号,
  327. qymc = this.appConfig.qymc_企业名称,
  328. tyshxydm = this.appConfig.tyshxydm_统一社会信用代码,
  329. jsdd = this.appConfig.jsdd_建设地点,
  330. jsddjwd = this.appConfig.jsddjwd_建设地点经纬度,
  331. xmmc = this.appConfig.xmmc_项目名称,
  332. bwtysj = this.appConfig.bwtysj_并网投运时间,
  333. //当日有功发电量 KWh
  334. drygfdl = 150,
  335. //当日无功发电量 Kvarh
  336. drwgfdl = 10,
  337. //当月有功发电量 KWh
  338. dyygfdl = 1500,
  339. //当月无功发电量 KWh
  340. dywgfdl = 100,
  341. //当年有功发电量 KWh
  342. dnygfdl = 15000,
  343. //当年无功发电量 KWh
  344. dnwgfdl = 1000,
  345. cjrq = DateTime.Now,
  346. }, new JsonSerializerOptions()
  347. {
  348. WriteIndented = false,
  349. AllowTrailingCommas = false,
  350. Converters = { new DateTimeConverter() }
  351. });
  352. var response = await this.httpClient.PostAsync(
  353. this.appConfig.HttpPostTestUrl,
  354. new StringContent(postContent, Encoding.UTF8, "application/json")).ConfigureAwait(false);
  355. if (response != null && response.IsSuccessStatusCode)
  356. {
  357. /* sample success response:
  358. {
  359. "success": true,
  360. "message": "成功",
  361. "code": 200,
  362. "data": null,
  363. "timestamp": 1653873404980
  364. }
  365. */
  366. try
  367. {
  368. var content = await response.Content.ReadAsStringAsync();
  369. JsonDocument jd = JsonDocument.Parse(content);
  370. if (jd.RootElement.GetProperty("code").GetInt32() == 200)
  371. {
  372. return;
  373. }
  374. else
  375. {
  376. throw new InvalidOperationException($"post failed with response json data has an inner error code: {jd.RootElement.GetProperty("code").GetInt32()}, full json: {content}");
  377. }
  378. }
  379. catch (Exception eee)
  380. {
  381. throw new InvalidOperationException($"post failed with parsing response to json failure: {eee}");
  382. }
  383. }
  384. else
  385. {
  386. throw new InvalidOperationException($"post failed with http level failure response: {response?.ReasonPhrase ?? ""}");
  387. }
  388. }
  389. public class DateTimeConverter : JsonConverter<DateTime>
  390. {
  391. public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
  392. {
  393. return DateTime.Parse(reader.GetString());
  394. }
  395. public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
  396. {
  397. writer.WriteStringValue(value.ToString("yyyy-MM-dd HH:mm:ss"));
  398. }
  399. }
  400. private async Task StartHttpPostLoopAsync()
  401. {
  402. // there're may have many un-posted records, so here only look back in some days.
  403. var maxLookBackDays = 1;
  404. var lookBackStartDateTime = DateTime.Now.Subtract(new TimeSpan(maxLookBackDays, 0, 0, 0));
  405. // by ms
  406. var loopIntervalTime = 5000;
  407. int currentLoopTimes = 0;
  408. while (this.isStarted)
  409. {
  410. try
  411. {
  412. currentLoopTimes++;
  413. using (var dbContext = new AppDbContext())
  414. {
  415. var postingRecord = await dbContext.InverterAccumulatedDatas.Include(d => d.SourceRealTimeDatas).OrderBy(d => d.Id)
  416. .Where(d => d.HttpPostResponseReceivedTime == null && d.CreatedTimeStamp >= lookBackStartDateTime).FirstOrDefaultAsync();
  417. if (postingRecord == null)
  418. {
  419. //this.logger.LogDebug($"Nothing for Posting, delay a while for re-check...");
  420. await Task.Delay(loopIntervalTime);
  421. continue;
  422. }
  423. this.logger.LogDebug($"Posting Record is selected: {postingRecord.ToLogString()}");
  424. var postContent = JsonSerializer.Serialize(new
  425. {
  426. id = this.appConfig.Id_唯一标识符,
  427. gffdqyhh = this.appConfig.gffdqyhh_光伏发电企业户号,
  428. qymc = this.appConfig.qymc_企业名称,
  429. tyshxydm = this.appConfig.tyshxydm_统一社会信用代码,
  430. jsdd = this.appConfig.jsdd_建设地点,
  431. jsddjwd = this.appConfig.jsddjwd_建设地点经纬度,
  432. xmmc = this.appConfig.xmmc_项目名称,
  433. bwtysj = this.appConfig.bwtysj_并网投运时间,
  434. //当日有功发电量 KWh
  435. drygfdl = postingRecord.当日有功发电量,
  436. //当日无功发电量 Kvarh
  437. drwgfdl = postingRecord.当日无功发电量,
  438. //当月有功发电量 KWh
  439. dyygfdl = postingRecord.当月有功发电量,
  440. //当月无功发电量 KWh
  441. dywgfdl = postingRecord.当月无功发电量,
  442. //当年有功发电量 KWh
  443. dnygfdl = postingRecord.当年有功发电量,
  444. //当年无功发电量 KWh
  445. dnwgfdl = postingRecord.当年无功发电量,
  446. cjrq = postingRecord.SourceRealTimeDatas.Max(d => d.ReadTimeStamp)
  447. }, new JsonSerializerOptions()
  448. {
  449. Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
  450. WriteIndented = false,
  451. AllowTrailingCommas = false,
  452. Converters =
  453. {
  454. new DateTimeConverter()
  455. }
  456. });
  457. this.logger.LogDebug($" posting content:{Environment.NewLine} {postContent}");
  458. var response = await this.httpClient.PostAsync(
  459. this.appConfig.HttpPostUrl_ForSingleRecord,
  460. new StringContent(postContent, Encoding.UTF8, "application/json")).ConfigureAwait(false);
  461. if (response != null && response.IsSuccessStatusCode)
  462. {
  463. /* sample success response:
  464. {
  465. "success": true,
  466. "message": "成功",
  467. "code": 200,
  468. "data": null,
  469. "timestamp": 1653873404980
  470. }
  471. */
  472. try
  473. {
  474. var content = await response.Content.ReadAsStringAsync();
  475. JsonDocument jd = JsonDocument.Parse(content);
  476. if (jd.RootElement.GetProperty("code").GetInt32() == 200)
  477. {
  478. this.logger.LogDebug($" post successfully with response: {content ?? ""}");
  479. postingRecord.HttpPostResponseReceivedTime = DateTime.Now;
  480. postingRecord.HttpPostResponse = content ?? "";
  481. await dbContext.SaveChangesAsync();
  482. this.logger.LogDebug($" post progress saved into database");
  483. }
  484. else
  485. {
  486. this.logger.LogInformation($" post failed with response json data has an inner error code: {jd.RootElement.GetProperty("code").GetInt32()}, full json: {content}");
  487. var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
  488. await universalApiHub.ClearAndFirePersistGenericAlarm(this,
  489. new GenericAlarm()
  490. {
  491. Title = $"上传光伏数据到SIPAC平台失败",
  492. Category = $"光伏逆变器",
  493. Detail = $"上传光伏数据到SIPAC平台失败, 原因: post failed with response json data has an inner error code: {jd.RootElement.GetProperty("code").GetInt32()}, full json: {content}",
  494. Severity = GenericAlarmSeverity.Warning
  495. }, ga => ga.Detail,
  496. ga => ga.Detail);
  497. }
  498. }
  499. catch (Exception eee)
  500. {
  501. this.logger.LogInformation($" post failed with parsing response to json failure: {eee}");
  502. var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
  503. await universalApiHub.ClearAndFirePersistGenericAlarm(this,
  504. new GenericAlarm()
  505. {
  506. Title = $"上传光伏数据到SIPAC平台失败",
  507. Category = $"光伏逆变器",
  508. Detail = $"上传光伏数据到SIPAC平台失败, 原因: post failed with parsing response to json failure: {eee}",
  509. Severity = GenericAlarmSeverity.Warning
  510. }, ga => ga.Detail,
  511. ga => ga.Detail);
  512. }
  513. }
  514. else
  515. {
  516. var failedReason = (response == null ? "HttpStatusCode: null" : ("HttpStatusCode: " + response.StatusCode.ToString())) + "-" + response?.ReasonPhrase ?? "";
  517. this.logger.LogInformation($" post failed with http level failure response: {failedReason}");
  518. var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
  519. await universalApiHub.ClearAndFirePersistGenericAlarm(this,
  520. new GenericAlarm()
  521. {
  522. Title = $"上传光伏数据到SIPAC平台失败",
  523. Category = $"光伏逆变器",
  524. Detail = $"上传光伏数据到SIPAC平台失败, 原因: post failed with http level failure response: {failedReason}",
  525. Severity = GenericAlarmSeverity.Warning
  526. }, ga => ga.Detail,
  527. ga => ga.Detail);
  528. }
  529. }
  530. }
  531. catch (Exception exxx)
  532. {
  533. this.logger.LogInformation($"HttpPostLoop, see an exception: {exxx}");
  534. }
  535. await Task.Delay(loopIntervalTime);
  536. }
  537. }
  538. }
  539. }