diff --git a/Backend/ApiControllers/DeviceApiController.cs b/Backend/ApiControllers/DeviceApiController.cs index cf3547f..c72b574 100644 --- a/Backend/ApiControllers/DeviceApiController.cs +++ b/Backend/ApiControllers/DeviceApiController.cs @@ -61,10 +61,10 @@ namespace Backend.ApiControllers { { "@user_guid", myUser.userinfo_guid }, { "@operation_type", 1 }, //1:名稱修改 - { "@building_guid", GetOne.Building_guid }, - { "@main_system_guid", GetOne.Main_system_guid }, - { "@sub_system_guid", GetOne.Sub_system_guid }, - { "@floor_guid", GetOne.Floor_guid }, + { "@building_tag", GetOne.device_building_tag }, + { "@main_system_tag", GetOne.device_system_tag }, + { "@sub_system_tag", GetOne.device_name_tag }, + { "@floor_tag", GetOne.device_floor_tag }, { "@device_guid", GetOne.Device_guid }, { "@action_name", "修改名稱" }, { "@parameter", JsonConvert.SerializeObject(change) }, diff --git a/Backend/Backend.csproj b/Backend/Backend.csproj index ae412bd..daebb6f 100644 --- a/Backend/Backend.csproj +++ b/Backend/Backend.csproj @@ -23,8 +23,10 @@ + + diff --git a/Backend/Controllers/DeviceManageController.cs b/Backend/Controllers/DeviceManageController.cs index c810839..ceb04f3 100644 --- a/Backend/Controllers/DeviceManageController.cs +++ b/Backend/Controllers/DeviceManageController.cs @@ -1662,7 +1662,7 @@ namespace Backend.Controllers try { - string sWhere = "deleted = 0 AND device_name_tag = @Sub_system_tag and device_building_tag = @device_building_tag"; + string sWhere = "deleted = 0 AND device_name_tag = @Sub_system_tag and device_building_tag = @device_building_tag and is_link = 1 and is_controll = 1"; object param = new { Sub_system_tag = input.sub_system_tag, device_building_tag = input.building_tag }; diff --git a/Backend/Controllers/SystemCategoryController.cs b/Backend/Controllers/SystemCategoryController.cs index 641391b..9246230 100644 --- a/Backend/Controllers/SystemCategoryController.cs +++ b/Backend/Controllers/SystemCategoryController.cs @@ -501,26 +501,37 @@ namespace Backend.Controllers try { + var main_tag = await backendRepository.GetOneAsync($@"SELECT system_value FROM variable WHERE id = @id", new { id = device_Item.device_system_tag }); + var sub_tag = await backendRepository.GetOneAsync($@"SELECT system_value FROM variable WHERE id = @id", new { id = device_Item.device_name_tag }); //檢查是否有未刪除的區域選單 - if(device_Item.is_show_riserDiagram == 1) + if (device_Item.is_show_riserDiagram == 1) { - var sql_show_riserDiagram = $@"SELECT * FROM device_item di - WHERE di.id = @id AND di.deleted = 0 AND is_show_riserDiagram = 1"; + var sql_show_riserDiagram = $@"SELECT di.id FROM device_item di + WHERE di.id != @id AND di.deleted = 0 AND is_show_riserDiagram = 1 and device_system_tag = @device_system_tag + and device_name_tag = @device_name_tag"; - var is_show_riserDiagram = await backendRepository.GetAllAsync(sql_show_riserDiagram, new { id = device_Item.id }); + var deviceItemId = await backendRepository.GetAllAsync(sql_show_riserDiagram, + new { id = device_Item.id, device_system_tag = main_tag, device_name_tag = sub_tag }); - if (is_show_riserDiagram.Count() > 0) + if (deviceItemId.Count() > 0) { - apiResult.Code = "9998"; - apiResult.Msg = "請先取消已選擇顯示於昇位圖點位。"; - return apiResult; + foreach (var id in deviceItemId) + { + Dictionary Device_itemDic = new Dictionary() + { + { "@is_show_riserDiagram", false}, + { "@updated_by", myUserInfo.Userinfo_guid}, + { "@updated_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}, + }; + + await backendRepository.UpdateOneByCustomTable(Device_itemDic, "device_item", "id='" + id + "'"); + } } } if (device_Item.id == 0) { - var main_tag = await backendRepository.GetOneAsync($@"SELECT system_value FROM variable WHERE id = @id", new { id = device_Item.device_system_tag }); - var sub_tag = await backendRepository.GetOneAsync($@"SELECT system_value FROM variable WHERE id = @id", new { id = device_Item.device_name_tag }); + //新增 Dictionary Device_itemDic = new Dictionary() { @@ -586,7 +597,7 @@ namespace Backend.Controllers FROM device_item di JOIN variable sv ON di.device_name_tag = sv.system_value JOIN variable mv ON sv.system_parent_id = mv.id AND di.device_system_tag = mv.system_value - WHERE sv.id = @id AND di.deleted = @Deleted and di.device_building_tag = @building_tag"; + WHERE sv.id = @id AND di.deleted = @Deleted and di.device_building_tag = @building_tag and di.is_link = 1"; object param = new { Deleted = 0, id = id, building_tag = building_tag.Split("/")[1] }; diff --git a/Backend/Models/Device.cs b/Backend/Models/Device.cs index c611d80..898d9ca 100644 --- a/Backend/Models/Device.cs +++ b/Backend/Models/Device.cs @@ -54,13 +54,13 @@ namespace Backend.Models public string Device_guid { get; set; } public byte Deleted { get; set; } public byte Status { get; set; } - public string Building_guid { get; set; } + public string device_building_tag { get; set; } public string Building_full_name { get; set; } public string Main_system_guid { get; set; } public string Main_system_full_name { get; set; } public string Sub_system_guid { get; set; } public string Sub_system_full_name { get; set; } - public string Floor_guid { get; set; } + public string device_floor_tag { get; set; } public string Floor_full_name { get; set; } public string Device_coordinate { get; set; } public string Device_full_name { get; set; } @@ -76,7 +76,8 @@ namespace Backend.Models public string Device_flashing { get; set; } public string Device_ip { get; set; } public string Device_port { get; set; } - public string Device_name_tag { get; set; } + public string device_name_tag { get; set; } + public string device_system_tag { get; set; } public List Device_disasters { get; set; } //防災類型 public List Device_nodes { get; set; } //設備子節點 } diff --git a/Backend/Quartz/JobSchedule.cs b/Backend/Quartz/JobSchedule.cs new file mode 100644 index 0000000..7d62e67 --- /dev/null +++ b/Backend/Quartz/JobSchedule.cs @@ -0,0 +1,198 @@ +using Microsoft.Extensions.Logging; +using Repository.BackendRepository.Implement; +using Repository.BackendRepository.Interface; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Text; +using System.Threading.Tasks; +using NCrontab; +using static NCrontab.CrontabSchedule; + +namespace Backend.Quartz +{ + /// + /// Job調度中間對象 + /// + public class JobSchedule + { + public JobSchedule(Type jobType, string cronExpression) + { + this.JobType = jobType ?? throw new ArgumentNullException(nameof(jobType)); + CronExpression = cronExpression ?? throw new ArgumentNullException(nameof(cronExpression)); + } + /// + /// Job類型 + /// + public Type JobType { get; private set; } + /// + /// Cron表達式 + /// + public string CronExpression { get; private set; } + /// + /// Job狀態 + /// + public JobStatus JobStatu { get; set; } = JobStatus.Init; + } + /// + /// Job運行狀態 + /// + public enum JobStatus : byte + { + [Description("初始化")] + Init = 0, + [Description("運行中")] + Running = 1, + [Description("調度中")] + Scheduling = 2, + [Description("已停止")] + Stopped = 3, + } + + public class Task_Detail + { + private readonly IBackendRepository backendRepository; + private readonly ILogger logger; + public Task_Detail(ILogger logger, IBackendRepository backendRepository) + { + this.logger = logger; + this.backendRepository = backendRepository; + } + /// + /// 取得時間規則 + /// + /// + /// + /// + private async Task GetWorkRule(string task,string task_item) + { + string Times = null; + try + { + var sql = $@"select b.system_value from task_detail a + join variable b on a.variable_id = b.id + where a.task = '{task}' and a.task_item = '{task_item}'"; + Times = await backendRepository.GetOneAsync(sql); + } + catch(Exception exception) + { + logger.LogError("【Task_Detail】【任務時間獲取失敗】"); + logger.LogError("【Task_Detail】【任務時間獲取失敗】[Exception]:{0},Task:{1},task_item:{2}", exception.ToString(),task,task_item); + } + return Times; + } + /// + /// 是否執行任務 + /// + /// + /// + /// + public async Task GetNeedWorkTask(string task, string task_item) + { + try + { + var sql = $@"select a.lastwork_time from task_detail a + where a.task = '{task}' and a.task_item = '{task_item}'"; + + var lastworkTime = await backendRepository.GetOneAsync(sql); + + + + DateTime dateTime = lastworkTime != null ? Convert.ToDateTime(lastworkTime) : Convert.ToDateTime("1970-01-01 00:00:01"); + + var nextTime = CrontabSchedule.Parse(await GetWorkRule(task, task_item), new ParseOptions { IncludingSeconds = true } ).GetNextOccurrence(dateTime); + if (DateTime.Now >= nextTime) + { + return true; + } + } + catch(Exception exception) + { + logger.LogError("【Task_Detail】【時間解析失敗】[Exception]:{0},Task:{1},task_item:{2}", exception.ToString(), task, task_item); + } + return false; + } + /// + /// 紀錄任務開始執行時間 + /// + /// + /// + /// + public async Task InsertWorkTime(string task, string task_item ,string LoggerWord = null) + { + try + { + Dictionary worktime = new Dictionary() + { + { "@lastwork_time", DateTime.Now}, + { "@success", 2}, + { "@updated_at", DateTime.Now}, + }; + await backendRepository.UpdateOneByCustomTable(worktime, "task_detail", $" task = '{task}' and task_item = '{task_item}'"); + + if(LoggerWord == null) + { + logger.LogInformation($"【Task_Detail】【開始{task},{task_item}任務】"); + } + logger.LogInformation($"【Task_Detail】【{LoggerWord}】"); + } + catch (Exception exception) + { + logger.LogError("【Task_Detail】【任務輸入開始時間】[Exception]:{0},Task:{1},task_item:{2}", exception.ToString(), task, task_item); + } + } + /// + /// 紀錄任務結束時間 + /// + /// + /// + /// + public async Task InsertWorkTime_End(string task, string task_item,string LoggerWord = null) + { + try + { + Dictionary worktime = new Dictionary() + { + { "@success", 0}, + { "@lastwork_end_time", DateTime.Now}, + { "@updated_at", DateTime.Now}, + }; + await backendRepository.UpdateOneByCustomTable(worktime, "task_detail", $" task = '{task}' and task_item = '{task_item}'"); + if (LoggerWord == null) + { + logger.LogInformation($"【Task_Detail】【結束{task},{task_item}任務】"); + } + logger.LogInformation($"【Task_Detail】【{LoggerWord}】"); + } + catch (Exception exception) + { + logger.LogError("【Task_Detail】【任務輸入結束時間】[Exception]:{0},Task:{1},task_item:{2}", exception.ToString(), task, task_item); + } + } + /// + /// 執行失敗紀錄 + /// + /// + /// + /// + public async Task WorkFail(string task, string task_item,string reason = "") + { + try + { + Dictionary worktime = new Dictionary() + { + { "@success", 1}, + { "@updated_at", DateTime.Now}, + }; + await backendRepository.UpdateOneByCustomTable(worktime, "task_detail", $" task = '{task}' and task_item = '{task_item}'"); + logger.LogError("【Task_Detail】【任務執行失敗】[Exception]:{0},Task:{1},task_item:{2}", reason, task, task_item); + } + catch (Exception exception) + { + logger.LogError("【Task_Detail】【任務執行失敗】[Exception]:{0},Task:{1},task_item:{2}", exception.ToString(), task, task_item); + } + } + + } + +} diff --git a/Backend/Quartz/Jobs/ArchiveElectricMeterDayJob.cs b/Backend/Quartz/Jobs/ArchiveElectricMeterDayJob.cs new file mode 100644 index 0000000..49244b1 --- /dev/null +++ b/Backend/Quartz/Jobs/ArchiveElectricMeterDayJob.cs @@ -0,0 +1,1461 @@ +using Backend.Models; +using BackendWorkerService.Services.Implement; +using Dapper; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Quartz; +using Repository.BackendRepository.Interface; +using Repository.Helper; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using System.Xml; + + +namespace Backend.Quartz.Jobs +{ + [DisallowConcurrentExecution] + class ArchiveElectricMeterDayJob : IJob + { + private readonly ILogger logger; + private readonly IBackgroundServiceRepository backgroundServiceRepository; + private readonly IBackgroundServiceMsSqlRepository backgroundServiceMsSqlRepository; + protected readonly IDatabaseHelper _databaseHelper; + private readonly ILogger loggers; + + + public ArchiveElectricMeterDayJob( + ILogger logger, + IBackgroundServiceRepository backgroundServiceRepository, + IBackgroundServiceMsSqlRepository backgroundServiceMySqlRepository, + IDatabaseHelper databaseHelper, ILogger loggers, IBackendRepository backendRepository) + { + this.logger = logger; + this.backgroundServiceRepository = backgroundServiceRepository; + this.backgroundServiceMsSqlRepository = backgroundServiceMySqlRepository; + this._databaseHelper = databaseHelper; + this.loggers = loggers; + } + + public async Task Execute(IJobExecutionContext context) + { + Task_Detail task_Detail = new Task_Detail(loggers, backgroundServiceRepository); + try + { + if(await task_Detail.GetNeedWorkTask("ArchiveElectricMeterDayJob", "All")) + { + await task_Detail.InsertWorkTime("ArchiveElectricMeterDayJob", "All", "水電表開始任務開始"); + EDFunction ed = new EDFunction(); + XmlDocument xmlDocument = new XmlDocument(); + + var sqlArchive = $@"SELECT system_value as Value, system_key as Name FROM variable WHERE deleted = 0 AND system_type = 'archiveConfig'"; + var saveToMSDB = await backgroundServiceRepository.GetOneAsync("select system_value from variable where system_type = 'save_to_ms_db' and deleted = 0"); + + var variableArchive = await backgroundServiceRepository.GetAllAsync(sqlArchive); + var electricMeterGuid = variableArchive.Where(x => x.Name == "ElectricMeterGuid").Select(x => x.Value).FirstOrDefault(); + var waterMeterGuid = variableArchive.Where(x => x.Name == "WaterMeterGuid").Select(x => x.Value).FirstOrDefault(); + + #region 找出所有電錶設備 + var sWhere = "deleted = 0 AND device_name_tag = @sub_system_guid"; + var electricMeters = await backgroundServiceRepository.GetAllAsync("device", sWhere, new { sub_system_guid = electricMeterGuid }); + var waterMeters = await backgroundServiceRepository.GetAllAsync("device", sWhere, new { sub_system_guid = waterMeterGuid }); + #endregion 找出所有電錶設備 + + #region 找出所有電錶系統的點位 + var sPointWhere = "deleted = 0 AND device_name_tag = @sub_system_guid"; + var electricPoints = await backgroundServiceRepository.GetAllAsync("device_item", sPointWhere, new { sub_system_guid = electricMeterGuid }); + var waterPoints = await backgroundServiceRepository.GetAllAsync("device_item", sPointWhere, new { sub_system_guid = waterMeterGuid }); + #endregion 找出所有電錶系統的點位 + + #region 組合出所有電錶設備點位 + List electricDeviceNumberPoints = new List(); + foreach (var electricMeter in electricMeters) + { + foreach (var point in electricPoints) + { + DeviceNumberPoint deviceNumberPoint = new DeviceNumberPoint(); + deviceNumberPoint.DeviceNumber = electricMeter.Device_number; + deviceNumberPoint.Point = point.points; + deviceNumberPoint.FullDeviceNumberPoint = string.Format("{0}_{1}", electricMeter.Device_number, point.points); + + electricDeviceNumberPoints.Add(deviceNumberPoint); + } + } + #endregion 組合出所有電錶設備點位 + #region 組合出所有水錶設備點位 + List waterDeviceNumberPoints = new List(); + foreach (var waterMeter in waterMeters) + { + foreach (var point in waterPoints) + { + DeviceNumberPoint deviceNumberPoint = new DeviceNumberPoint(); + deviceNumberPoint.DeviceNumber = waterMeter.Device_number; + deviceNumberPoint.Point = point.points; + deviceNumberPoint.FullDeviceNumberPoint = string.Format("{0}_{1}", waterMeter.Device_number, point.points); + + waterDeviceNumberPoints.Add(deviceNumberPoint); + } + } + #endregion 組合出所有電錶設備點位 + + #region 取得obix 設定 + var obixApiConfig = new ObixApiConfig(); + + var sqlObix = $@"SELECT system_value as Value, system_key as Name FROM variable WHERE deleted = 0 AND system_type = 'obixConfig'"; + + var variableObix = await backgroundServiceRepository.GetAllAsync(sqlObix); + obixApiConfig.ApiBase = variableObix.Where(x => x.Name == "ApiBase").Select(x => x.Value).FirstOrDefault(); + obixApiConfig.UserName = ed.AESDecrypt(variableObix.Where(x => x.Name == "UserName").Select(x => x.Value).FirstOrDefault()); + obixApiConfig.Password = ed.AESDecrypt(variableObix.Where(x => x.Name == "Password").Select(x => x.Value).FirstOrDefault()); + + String encoded = System.Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(obixApiConfig.UserName + ":" + obixApiConfig.Password)); + #endregion 取得obix 設定 + + var now = DateTime.Now; + + #region 天歸檔 + if (await task_Detail.GetNeedWorkTask("ArchiveElectricMeterDayJob", "Day")) + { + try + { + await task_Detail.InsertWorkTime("ArchiveElectricMeterDayJob", "Day", "水電表天任務開始"); + var preDay = now.AddDays(-1); //取得前一天 + var dbDateName = preDay.Year.ToString().PadLeft(4, '0') + preDay.Month.ToString().PadLeft(2, '0'); + + var startTimestamp = string.Format("{0}T00:00:00.000+08:00", preDay.ToString("yyyy-MM-dd")); + var endTimestamp = string.Format("{0}T23:59:59.000+08:00", preDay.ToString("yyyy-MM-dd")); + + var historyQueryFilter = $@" + + + + "; + + //Stopwatch stopWatch = new Stopwatch(); + //stopWatch.Start(); + + //抓取每個設備的資料 + List> electericArchiveDayRawDatas = new List>(); + List> waterArchiveDayRawDatas = new List>(); + foreach (var deviceNumberPoint in electricDeviceNumberPoints) + { + + HttpWebRequest archiveDayRequest = (HttpWebRequest)WebRequest.Create($"{obixApiConfig.ApiBase}obix/histories/FIC_Center/{deviceNumberPoint.FullDeviceNumberPoint}/~historyRollup/"); + //HttpWebRequest archiveDayRequest = (HttpWebRequest)WebRequest.Create($"{obixApiConfig.ApiBase}obix/histories/FIC_Center/H_E1_B1F_MVCB_MVCBH_V1/~historyRollup/"); + archiveDayRequest.Method = "POST"; + archiveDayRequest.Headers.Add("Authorization", "Basic " + encoded); + archiveDayRequest.PreAuthenticate = true; + + byte[] byteArray = Encoding.UTF8.GetBytes(historyQueryFilter); + using (Stream reqStream = archiveDayRequest.GetRequestStream()) + { + reqStream.Write(byteArray, 0, byteArray.Length); + } + + HttpWebResponse archiveDayResponse = (HttpWebResponse)archiveDayRequest.GetResponse(); + var archiveDayResponseContent = new StreamReader(archiveDayResponse.GetResponseStream()).ReadToEnd(); + + xmlDocument.LoadXml(archiveDayResponseContent); + string archiveDayJson = JsonConvert.SerializeXmlNode(xmlDocument); + JObject archiveDayJsonResult = (JObject)JsonConvert.DeserializeObject(archiveDayJson); + + if (archiveDayJsonResult.ContainsKey("err")) //抓取錯誤 + { + //logger.LogError("【ArchiveElectricMeterDayJob】【天歸檔】【取得資料失敗】"); + //logger.LogError("【ArchiveElectricMeterDayJob】【天歸檔】【取得資料失敗】[錯誤內容]:{0}", archiveDayJsonResult); + + Dictionary archiveDayRawData = new Dictionary(); + archiveDayRawData.Add("@device_number", deviceNumberPoint.DeviceNumber); + archiveDayRawData.Add("@point", deviceNumberPoint.Point); + archiveDayRawData.Add("@start_timestamp", startTimestamp.Replace("T", " ").Substring(0, 19)); + archiveDayRawData.Add("@end_timestamp", endTimestamp.Replace("T", " ").Substring(0, 19)); + archiveDayRawData.Add("@is_complete", 0); + archiveDayRawData.Add("@repeat_times", 0); + archiveDayRawData.Add("@fail_reason", archiveDayJson); + + archiveDayRawData.Add("@count_rawdata", 0); + archiveDayRawData.Add("@min_rawdata", 0); + archiveDayRawData.Add("@max_rawdata", 0); + archiveDayRawData.Add("@avg_rawdata", 0); + archiveDayRawData.Add("@sum_rawdata", 0); + archiveDayRawData.Add("@updated_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); + + electericArchiveDayRawDatas.Add(archiveDayRawData); + } + + if (archiveDayJsonResult.ContainsKey("obj")) //表示可以讀取到內容 + { + var ArrangeRawDatas = ArrangeRawData(deviceNumberPoint, archiveDayJsonResult); + if (ArrangeRawDatas != null && ArrangeRawDatas.Count() > 0) + { + electericArchiveDayRawDatas.AddRange(ArrangeRawDatas); + } + } + } + foreach (var deviceNumberPoint in waterDeviceNumberPoints) + { + + HttpWebRequest archiveDayRequest = (HttpWebRequest)WebRequest.Create($"{obixApiConfig.ApiBase}obix/histories/FIC_Center/{deviceNumberPoint.FullDeviceNumberPoint}/~historyRollup/"); + //HttpWebRequest archiveDayRequest = (HttpWebRequest)WebRequest.Create($"{obixApiConfig.ApiBase}obix/histories/FIC_Center/H_E1_B1F_MVCB_MVCBH_V1/~historyRollup/"); + archiveDayRequest.Method = "POST"; + archiveDayRequest.Headers.Add("Authorization", "Basic " + encoded); + archiveDayRequest.PreAuthenticate = true; + + byte[] byteArray = Encoding.UTF8.GetBytes(historyQueryFilter); + using (Stream reqStream = archiveDayRequest.GetRequestStream()) + { + reqStream.Write(byteArray, 0, byteArray.Length); + } + + HttpWebResponse archiveDayResponse = (HttpWebResponse)archiveDayRequest.GetResponse(); + var archiveDayResponseContent = new StreamReader(archiveDayResponse.GetResponseStream()).ReadToEnd(); + + xmlDocument.LoadXml(archiveDayResponseContent); + string archiveDayJson = JsonConvert.SerializeXmlNode(xmlDocument); + JObject archiveDayJsonResult = (JObject)JsonConvert.DeserializeObject(archiveDayJson); + + if (archiveDayJsonResult.ContainsKey("err")) //抓取錯誤 + { + //logger.LogError("【ArchiveElectricMeterDayJob】【天歸檔】【取得資料失敗】"); + //logger.LogError("【ArchiveElectricMeterDayJob】【天歸檔】【取得資料失敗】[錯誤內容]:{0}", archiveDayJsonResult); + + Dictionary archiveDayRawData = new Dictionary(); + archiveDayRawData.Add("@device_number", deviceNumberPoint.DeviceNumber); + archiveDayRawData.Add("@point", deviceNumberPoint.Point); + archiveDayRawData.Add("@start_timestamp", startTimestamp.Replace("T", " ").Substring(0, 19)); + archiveDayRawData.Add("@end_timestamp", endTimestamp.Replace("T", " ").Substring(0, 19)); + archiveDayRawData.Add("@is_complete", 0); + archiveDayRawData.Add("@repeat_times", 0); + archiveDayRawData.Add("@fail_reason", archiveDayJson); + + archiveDayRawData.Add("@count_rawdata", 0); + archiveDayRawData.Add("@min_rawdata", 0); + archiveDayRawData.Add("@max_rawdata", 0); + archiveDayRawData.Add("@avg_rawdata", 0); + archiveDayRawData.Add("@sum_rawdata", 0); + archiveDayRawData.Add("@updated_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); + + waterArchiveDayRawDatas.Add(archiveDayRawData); + } + + if (archiveDayJsonResult.ContainsKey("obj")) //表示可以讀取到內容 + { + var ArrangeRawDatas = ArrangeRawData(deviceNumberPoint, archiveDayJsonResult); + if (ArrangeRawDatas != null && ArrangeRawDatas.Count() > 0) + { + waterArchiveDayRawDatas.AddRange(ArrangeRawDatas); + } + } + } + + //stopWatch.Stop(); + //logger.LogInformation("【ArchiveElectricMeterDayJob】【天歸檔】【效能檢驗】[取得資料花費時間]{0} 毫秒", stopWatch.ElapsedMilliseconds); + + if (electericArchiveDayRawDatas.Count() > 0) + { + var sql = $@"CREATE TABLE IF NOT EXISTS `archive_electric_meter_day_{dbDateName}` ( + `device_number` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `point` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `start_timestamp` datetime(6) NOT NULL, + `end_timestamp` datetime(6) NULL DEFAULT NULL, + `count_rawdata` int(11) NULL DEFAULT NULL, + `min_rawdata` decimal(15, 3) NULL DEFAULT NULL, + `max_rawdata` decimal(15, 3) NULL DEFAULT NULL, + `avg_rawdata` decimal(15, 3) NULL DEFAULT NULL, + `sum_rawdata` decimal(15, 3) NULL DEFAULT NULL, + `is_complete` tinyint(3) UNSIGNED NULL DEFAULT NULL COMMENT '是否完成,0:未完成 1:完成', + `repeat_times` int(11) NULL DEFAULT 0 COMMENT '重複次數', + `fail_reason` varchar(4000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '失敗原因', + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` datetime(6) NULL DEFAULT NULL, + PRIMARY KEY (`device_number`, `point`, `start_timestamp`) USING BTREE + ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; + + SET FOREIGN_KEY_CHECKS = 1; + UPDATE archive_electric_meter_day_{dbDateName} SET + count_rawdata = @count_rawdata, + min_rawdata = @min_rawdata, + max_rawdata = @max_rawdata, + avg_rawdata = @avg_rawdata, + sum_rawdata = @sum_rawdata, + is_complete = @is_complete, + repeat_times = @repeat_times, + fail_reason = @fail_reason, + updated_at = @updated_at + WHERE device_number = @device_number + AND point = @point + AND start_timestamp = @start_timestamp; + + INSERT INTO archive_electric_meter_day_{dbDateName} ( + device_number, + point, + start_timestamp, + end_timestamp, + count_rawdata, + min_rawdata, + max_rawdata, + avg_rawdata, + sum_rawdata, + is_complete, + repeat_times, + fail_reason) + SELECT + @device_number, + @point, + @start_timestamp, + @end_timestamp, + @count_rawdata, + @min_rawdata, + @max_rawdata, + @avg_rawdata, + @sum_rawdata, + @is_complete, + @repeat_times, + @fail_reason + WHERE ROW_COUNT() = 0;"; + + var mySql = $@"BEGIN TRANSACTION; + IF OBJECT_ID(N'dbo.archive_electric_meter_day_{dbDateName}', N'U') is null + BEGIN + CREATE TABLE [dbo].[archive_electric_meter_day_{dbDateName}]( + [device_number] [varchar](50) NOT NULL, + [point] [varchar](20) NOT NULL, + [start_timestamp] [datetime] NOT NULL, + [end_timestamp] [datetime] NULL, + [count_rawdata] [int] NULL, + [min_rawdata] [decimal](15, 3) NULL, + [max_rawdata] [decimal](15, 3) NULL, + [avg_rawdata] [decimal](15, 3) NULL, + [sum_rawdata] [decimal](15, 3) NULL, + [is_complete] [tinyint] NULL, + [repeat_times] [int] NULL, + [fail_reason] [nvarchar](max) NULL, + [created_at] [datetime] NULL, + [updated_at] [datetime] NULL, + CONSTRAINT [PK_archive_electric_meter_day_{dbDateName}] PRIMARY KEY CLUSTERED + ( + [device_number] ASC, + [point] ASC, + [start_timestamp] ASC + )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY] + ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] + + ALTER TABLE [dbo].[archive_electric_meter_day_{dbDateName}] ADD CONSTRAINT [DF_archive_electric_meter_day_{dbDateName}_repeat_times] DEFAULT ((0)) FOR [repeat_times] + + ALTER TABLE [dbo].[archive_electric_meter_day_{dbDateName}] ADD CONSTRAINT [DF_archive_electric_meter_day_{dbDateName}_created_at] DEFAULT (getdate()) FOR [created_at] + + ALTER TABLE [dbo].[archive_electric_meter_day_{dbDateName}] ADD CONSTRAINT [DF_archive_electric_meter_day_{dbDateName}_updated_at] DEFAULT (NULL) FOR [updated_at] + + EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'是否完成,0:未完成 1:完成' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'archive_electric_meter_day_{dbDateName}', @level2type=N'COLUMN',@level2name=N'is_complete' + + EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'重複次數' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'archive_electric_meter_day_{dbDateName}', @level2type=N'COLUMN',@level2name=N'repeat_times' + + EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'失敗原因' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'archive_electric_meter_day_{dbDateName}', @level2type=N'COLUMN',@level2name=N'fail_reason' + END + + UPDATE archive_electric_meter_day_{dbDateName} SET + count_rawdata = @count_rawdata, + min_rawdata = @min_rawdata, + max_rawdata = @max_rawdata, + avg_rawdata = @avg_rawdata, + sum_rawdata = @sum_rawdata, + is_complete = @is_complete, + repeat_times = @repeat_times, + fail_reason = @fail_reason, + updated_at = @updated_at + WHERE device_number = @device_number + AND point = @point + AND start_timestamp = @start_timestamp; + + IF @@ROWCOUNT = 0 + BEGIN + INSERT INTO archive_electric_meter_day_{dbDateName} ( + device_number, + point, + start_timestamp, + end_timestamp, + count_rawdata, + min_rawdata, + max_rawdata, + avg_rawdata, + sum_rawdata, + is_complete, + repeat_times, + fail_reason) + VALUES ( + @device_number, + @point, + @start_timestamp, + @end_timestamp, + @count_rawdata, + @min_rawdata, + @max_rawdata, + @avg_rawdata, + @sum_rawdata, + @is_complete, + @repeat_times, + @fail_reason) + END + + COMMIT TRANSACTION;"; + await backgroundServiceRepository.ExecuteSql(sql, electericArchiveDayRawDatas); + if (!string.IsNullOrEmpty(saveToMSDB) && saveToMSDB == "1") + { + await backgroundServiceMsSqlRepository.ExecuteSql(mySql, electericArchiveDayRawDatas); + } + } + if (waterArchiveDayRawDatas.Count() > 0) + { + var sql = $@" + CREATE TABLE IF NOT EXISTS `archive_water_meter_day_{dbDateName}` ( + `device_number` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `point` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `start_timestamp` datetime(6) NOT NULL, + `end_timestamp` datetime(6) NULL DEFAULT NULL, + `count_rawdata` int(11) NULL DEFAULT NULL, + `min_rawdata` decimal(15, 3) NULL DEFAULT NULL, + `max_rawdata` decimal(15, 3) NULL DEFAULT NULL, + `avg_rawdata` decimal(15, 3) NULL DEFAULT NULL, + `sum_rawdata` decimal(15, 3) NULL DEFAULT NULL, + `is_complete` tinyint(3) UNSIGNED NULL DEFAULT NULL COMMENT '是否完成,0:未完成 1:完成', + `repeat_times` int(11) NULL DEFAULT 0 COMMENT '重複次數', + `fail_reason` varchar(4000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '失敗原因', + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` datetime(6) NULL DEFAULT NULL, + PRIMARY KEY (`device_number`, `point`, `start_timestamp`) USING BTREE + ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; + + UPDATE archive_water_meter_day_{dbDateName} SET + count_rawdata = @count_rawdata, + min_rawdata = @min_rawdata, + max_rawdata = @max_rawdata, + avg_rawdata = @avg_rawdata, + sum_rawdata = @sum_rawdata, + is_complete = @is_complete, + repeat_times = @repeat_times, + fail_reason = @fail_reason, + updated_at = @updated_at + WHERE device_number = @device_number + AND point = @point + AND start_timestamp = @start_timestamp; + + + INSERT INTO archive_water_meter_day_{dbDateName} ( + device_number, + point, + start_timestamp, + end_timestamp, + count_rawdata, + min_rawdata, + max_rawdata, + avg_rawdata, + sum_rawdata, + is_complete, + repeat_times, + fail_reason) + SELECT + @device_number, + @point, + @start_timestamp, + @end_timestamp, + @count_rawdata, + @min_rawdata, + @max_rawdata, + @avg_rawdata, + @sum_rawdata, + @is_complete, + @repeat_times, + @fail_reason + WHERE ROW_COUNT() = 0;"; + + var mySql = $@"BEGIN TRANSACTION; + IF OBJECT_ID(N'dbo.archive_water_meter_day_{dbDateName}', N'U') is null + BEGIN + CREATE TABLE [dbo].[archive_water_meter_day_{dbDateName}]( + [device_number] [varchar](50) NOT NULL, + [point] [varchar](20) NOT NULL, + [start_timestamp] [datetime] NOT NULL, + [end_timestamp] [datetime] NULL, + [count_rawdata] [int] NULL, + [min_rawdata] [decimal](15, 3) NULL, + [max_rawdata] [decimal](15, 3) NULL, + [avg_rawdata] [decimal](15, 3) NULL, + [sum_rawdata] [decimal](15, 3) NULL, + [is_complete] [tinyint] NULL, + [repeat_times] [int] NULL, + [fail_reason] [nvarchar](max) NULL, + [created_at] [datetime] NULL, + [updated_at] [datetime] NULL, + CONSTRAINT [PK_archive_water_meter_day_{dbDateName}] PRIMARY KEY CLUSTERED + ( + [device_number] ASC, + [point] ASC, + [start_timestamp] ASC + )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY] + ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] + + ALTER TABLE [dbo].[archive_water_meter_day_{dbDateName}] ADD CONSTRAINT [DF_archive_water_meter_day_{dbDateName}_repeat_times] DEFAULT ((0)) FOR [repeat_times] + + ALTER TABLE [dbo].[archive_water_meter_day_{dbDateName}] ADD CONSTRAINT [DF_archive_water_meter_day_{dbDateName}_created_at] DEFAULT (getdate()) FOR [created_at] + + ALTER TABLE [dbo].[archive_water_meter_day_{dbDateName}] ADD CONSTRAINT [DF_archive_water_meter_day_{dbDateName}_updated_at] DEFAULT (NULL) FOR [updated_at] + + EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'是否完成,0:未完成 1:完成' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'archive_water_meter_day_{dbDateName}', @level2type=N'COLUMN',@level2name=N'is_complete' + + EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'重複次數' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'archive_water_meter_day_{dbDateName}', @level2type=N'COLUMN',@level2name=N'repeat_times' + + EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'失敗原因' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'archive_water_meter_day_{dbDateName}', @level2type=N'COLUMN',@level2name=N'fail_reason' + END + + UPDATE archive_water_meter_day_{dbDateName} SET + count_rawdata = @count_rawdata, + min_rawdata = @min_rawdata, + max_rawdata = @max_rawdata, + avg_rawdata = @avg_rawdata, + sum_rawdata = @sum_rawdata, + is_complete = @is_complete, + repeat_times = @repeat_times, + fail_reason = @fail_reason, + updated_at = @updated_at + WHERE device_number = @device_number + AND point = @point + AND start_timestamp = @start_timestamp; + + IF @@ROWCOUNT = 0 + BEGIN + INSERT INTO archive_water_meter_day_{dbDateName} ( + device_number, + point, + start_timestamp, + end_timestamp, + count_rawdata, + min_rawdata, + max_rawdata, + avg_rawdata, + sum_rawdata, + is_complete, + repeat_times, + fail_reason) + VALUES ( + @device_number, + @point, + @start_timestamp, + @end_timestamp, + @count_rawdata, + @min_rawdata, + @max_rawdata, + @avg_rawdata, + @sum_rawdata, + @is_complete, + @repeat_times, + @fail_reason) + END + + COMMIT TRANSACTION;"; + await backgroundServiceRepository.ExecuteSql(sql, waterArchiveDayRawDatas); + if (!string.IsNullOrEmpty(saveToMSDB) && saveToMSDB == "1") + { + await backgroundServiceMsSqlRepository.ExecuteSql(mySql, waterArchiveDayRawDatas); + } + } + await task_Detail.InsertWorkTime_End("ArchiveElectricMeterDayJob", "Day", "任務完成"); + } + catch (Exception exception) + { + await task_Detail.WorkFail("ArchiveElectricMeterDayJob", "Day", exception.ToString()); + logger.LogError("【ArchiveElectricMeterDayJob】【天歸檔】【任務失敗】"); + logger.LogError("【ArchiveElectricMeterDayJob】【天歸檔】【任務失敗】[Exception]:{0}", exception.ToString()); + } + } + #endregion 天歸檔 + + #region 週歸檔 + if (await task_Detail.GetNeedWorkTask("ArchiveElectricMeterDayJob", "Week")) + { + try + { + await task_Detail.InsertWorkTime("ArchiveElectricMeterDayJob", "Week", "水電表周任務開始"); + int week = Convert.ToInt32(now.DayOfWeek); + week = week == 0 ? 7 : week; + + var startTimestamp = string.Format("{0}T00:00:00.000+08:00", now.AddDays(1 - week).ToString("yyyy-MM-dd")); + var endTimestamp = string.Format("{0}T23:59:59.000+08:00", now.AddDays(7 - week).ToString("yyyy-MM-dd")); + + var historyQueryFilter = $@" + + + + "; + + //Stopwatch stopWatch = new Stopwatch(); + //stopWatch.Start(); + + //抓取每個設備的資料 + List> electricArchiveWeekRawDatas = new List>(); + List> waterArchiveWeekRawDatas = new List>(); + foreach (var deviceNumberPoint in electricDeviceNumberPoints) + { + HttpWebRequest archiveWeekRequest = (HttpWebRequest)WebRequest.Create($"{obixApiConfig.ApiBase}obix/histories/FIC_Center/{deviceNumberPoint.FullDeviceNumberPoint}/~historyRollup/"); + //HttpWebRequest archiveWeekRequest = (HttpWebRequest)WebRequest.Create($"{obixApiConfig.ApiBase}obix/histories/FIC_Center/H_E1_B1F_MVCB_MVCBH_V1/~historyRollup/"); + archiveWeekRequest.Method = "POST"; + archiveWeekRequest.Headers.Add("Authorization", "Basic " + encoded); + archiveWeekRequest.PreAuthenticate = true; + + byte[] byteArray = Encoding.UTF8.GetBytes(historyQueryFilter); + using (Stream reqStream = archiveWeekRequest.GetRequestStream()) + { + reqStream.Write(byteArray, 0, byteArray.Length); + } + + HttpWebResponse archiveWeekResponse = (HttpWebResponse)archiveWeekRequest.GetResponse(); + var archiveWeekResponseContent = new StreamReader(archiveWeekResponse.GetResponseStream()).ReadToEnd(); + + xmlDocument.LoadXml(archiveWeekResponseContent); + string archiveWeekJson = JsonConvert.SerializeXmlNode(xmlDocument); + JObject archiveWeekJsonResult = (JObject)JsonConvert.DeserializeObject(archiveWeekJson); + + if (archiveWeekJsonResult.ContainsKey("err")) //抓取錯誤 + { + //logger.LogError("【ArchiveElectricMeterDayJob】【週歸檔】【取得資料失敗】"); + //logger.LogError("【ArchiveElectricMeterDayJob】【週歸檔】【取得資料失敗】[錯誤內容]:{0}", archiveWeekJsonResult); + + Dictionary archiveWeekRawData = new Dictionary(); + archiveWeekRawData.Add("@device_number", deviceNumberPoint.DeviceNumber); + archiveWeekRawData.Add("@point", deviceNumberPoint.Point); + archiveWeekRawData.Add("@start_timestamp", startTimestamp.Replace("T", " ").Substring(0, 19)); + archiveWeekRawData.Add("@end_timestamp", endTimestamp.Replace("T", " ").Substring(0, 19)); + archiveWeekRawData.Add("@is_complete", 0); + archiveWeekRawData.Add("@repeat_times", 0); + archiveWeekRawData.Add("@fail_reason", archiveWeekJson); + + archiveWeekRawData.Add("@count_rawdata", 0); + archiveWeekRawData.Add("@min_rawdata", 0); + archiveWeekRawData.Add("@max_rawdata", 0); + archiveWeekRawData.Add("@avg_rawdata", 0); + archiveWeekRawData.Add("@sum_rawdata", 0); + archiveWeekRawData.Add("@updated_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); + + electricArchiveWeekRawDatas.Add(archiveWeekRawData); + } + + if (archiveWeekJsonResult.ContainsKey("obj")) //表示可以讀取到內容 + { + var ArrangeRawDatas = ArrangeRawData(deviceNumberPoint, archiveWeekJsonResult); + if (ArrangeRawDatas != null && ArrangeRawDatas.Count() > 0) + { + electricArchiveWeekRawDatas.AddRange(ArrangeRawDatas); + } + } + } + foreach (var deviceNumberPoint in waterDeviceNumberPoints) + { + HttpWebRequest archiveWeekRequest = (HttpWebRequest)WebRequest.Create($"{obixApiConfig.ApiBase}obix/histories/FIC_Center/{deviceNumberPoint.FullDeviceNumberPoint}/~historyRollup/"); + //HttpWebRequest archiveWeekRequest = (HttpWebRequest)WebRequest.Create($"{obixApiConfig.ApiBase}obix/histories/FIC_Center/H_E1_B1F_MVCB_MVCBH_V1/~historyRollup/"); + archiveWeekRequest.Method = "POST"; + archiveWeekRequest.Headers.Add("Authorization", "Basic " + encoded); + archiveWeekRequest.PreAuthenticate = true; + + byte[] byteArray = Encoding.UTF8.GetBytes(historyQueryFilter); + using (Stream reqStream = archiveWeekRequest.GetRequestStream()) + { + reqStream.Write(byteArray, 0, byteArray.Length); + } + + HttpWebResponse archiveWeekResponse = (HttpWebResponse)archiveWeekRequest.GetResponse(); + var archiveWeekResponseContent = new StreamReader(archiveWeekResponse.GetResponseStream()).ReadToEnd(); + + xmlDocument.LoadXml(archiveWeekResponseContent); + string archiveWeekJson = JsonConvert.SerializeXmlNode(xmlDocument); + JObject archiveWeekJsonResult = (JObject)JsonConvert.DeserializeObject(archiveWeekJson); + + if (archiveWeekJsonResult.ContainsKey("err")) //抓取錯誤 + { + //logger.LogError("【ArchiveElectricMeterDayJob】【週歸檔】【取得資料失敗】"); + //logger.LogError("【ArchiveElectricMeterDayJob】【週歸檔】【取得資料失敗】[錯誤內容]:{0}", archiveWeekJsonResult); + + Dictionary archiveWeekRawData = new Dictionary(); + archiveWeekRawData.Add("@device_number", deviceNumberPoint.DeviceNumber); + archiveWeekRawData.Add("@point", deviceNumberPoint.Point); + archiveWeekRawData.Add("@start_timestamp", startTimestamp.Replace("T", " ").Substring(0, 19)); + archiveWeekRawData.Add("@end_timestamp", endTimestamp.Replace("T", " ").Substring(0, 19)); + archiveWeekRawData.Add("@is_complete", 0); + archiveWeekRawData.Add("@repeat_times", 0); + archiveWeekRawData.Add("@fail_reason", archiveWeekJson); + + archiveWeekRawData.Add("@count_rawdata", 0); + archiveWeekRawData.Add("@min_rawdata", 0); + archiveWeekRawData.Add("@max_rawdata", 0); + archiveWeekRawData.Add("@avg_rawdata", 0); + archiveWeekRawData.Add("@sum_rawdata", 0); + archiveWeekRawData.Add("@updated_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); + + waterArchiveWeekRawDatas.Add(archiveWeekRawData); + } + + if (archiveWeekJsonResult.ContainsKey("obj")) //表示可以讀取到內容 + { + var ArrangeRawDatas = ArrangeRawData(deviceNumberPoint, archiveWeekJsonResult); + if (ArrangeRawDatas != null && ArrangeRawDatas.Count() > 0) + { + waterArchiveWeekRawDatas.AddRange(ArrangeRawDatas); + } + } + } + + //stopWatch.Stop(); + //logger.LogInformation("【ArchiveElectricMeterDayJob】【週歸檔】【效能檢驗】[取得資料花費時間]{0} 毫秒", stopWatch.ElapsedMilliseconds); + + if (electricArchiveWeekRawDatas.Count() > 0) + { + var sql = $@" + + UPDATE archive_electric_meter_week SET + count_rawdata = @count_rawdata, + min_rawdata = @min_rawdata, + max_rawdata = @max_rawdata, + avg_rawdata = @avg_rawdata, + sum_rawdata = @sum_rawdata, + is_complete = @is_complete, + repeat_times = @repeat_times, + fail_reason = @fail_reason, + updated_at = @updated_at + WHERE device_number = @device_number + AND point = @point + AND start_timestamp = @start_timestamp; + + + INSERT INTO archive_electric_meter_week ( + device_number, + point, + start_timestamp, + end_timestamp, + count_rawdata, + min_rawdata, + max_rawdata, + avg_rawdata, + sum_rawdata, + is_complete, + repeat_times, + fail_reason) + SELECT + @device_number, + @point, + @start_timestamp, + @end_timestamp, + @count_rawdata, + @min_rawdata, + @max_rawdata, + @avg_rawdata, + @sum_rawdata, + @is_complete, + @repeat_times, + @fail_reason + WHERE ROW_COUNT() = 0; + "; + + var mySql = $@"BEGIN TRANSACTION; + + UPDATE archive_electric_meter_week SET + count_rawdata = @count_rawdata, + min_rawdata = @min_rawdata, + max_rawdata = @max_rawdata, + avg_rawdata = @avg_rawdata, + sum_rawdata = @sum_rawdata, + is_complete = @is_complete, + repeat_times = @repeat_times, + fail_reason = @fail_reason, + updated_at = @updated_at + WHERE device_number = @device_number + AND point = @point + AND start_timestamp = @start_timestamp; + + IF @@ROWCOUNT = 0 + BEGIN + INSERT INTO archive_electric_meter_week ( + device_number, + point, + start_timestamp, + end_timestamp, + count_rawdata, + min_rawdata, + max_rawdata, + avg_rawdata, + sum_rawdata, + is_complete, + repeat_times, + fail_reason) + VALUES ( + @device_number, + @point, + @start_timestamp, + @end_timestamp, + @count_rawdata, + @min_rawdata, + @max_rawdata, + @avg_rawdata, + @sum_rawdata, + @is_complete, + @repeat_times, + @fail_reason) + END + + COMMIT TRANSACTION;"; + await backgroundServiceRepository.ExecuteSql(sql, electricArchiveWeekRawDatas); + if (!string.IsNullOrEmpty(saveToMSDB) && saveToMSDB == "1") + { + await backgroundServiceMsSqlRepository.ExecuteSql(mySql, electricArchiveWeekRawDatas); + } + + } + if (waterArchiveWeekRawDatas.Count() > 0) + { + var sql = $@" + + UPDATE archive_water_meter_week SET + count_rawdata = @count_rawdata, + min_rawdata = @min_rawdata, + max_rawdata = @max_rawdata, + avg_rawdata = @avg_rawdata, + sum_rawdata = @sum_rawdata, + is_complete = @is_complete, + repeat_times = @repeat_times, + fail_reason = @fail_reason, + updated_at = @updated_at + WHERE device_number = @device_number + AND point = @point + AND start_timestamp = @start_timestamp; + + + INSERT INTO archive_water_meter_week ( + device_number, + point, + start_timestamp, + end_timestamp, + count_rawdata, + min_rawdata, + max_rawdata, + avg_rawdata, + sum_rawdata, + is_complete, + repeat_times, + fail_reason) + SELECT + @device_number, + @point, + @start_timestamp, + @end_timestamp, + @count_rawdata, + @min_rawdata, + @max_rawdata, + @avg_rawdata, + @sum_rawdata, + @is_complete, + @repeat_times, + @fail_reason + WHERE ROW_COUNT() = 0; + "; + + var mySql = $@"BEGIN TRANSACTION; + + UPDATE archive_water_meter_week SET + count_rawdata = @count_rawdata, + min_rawdata = @min_rawdata, + max_rawdata = @max_rawdata, + avg_rawdata = @avg_rawdata, + sum_rawdata = @sum_rawdata, + is_complete = @is_complete, + repeat_times = @repeat_times, + fail_reason = @fail_reason, + updated_at = @updated_at + WHERE device_number = @device_number + AND point = @point + AND start_timestamp = @start_timestamp; + + IF @@ROWCOUNT = 0 + BEGIN + INSERT INTO archive_water_meter_week ( + device_number, + point, + start_timestamp, + end_timestamp, + count_rawdata, + min_rawdata, + max_rawdata, + avg_rawdata, + sum_rawdata, + is_complete, + repeat_times, + fail_reason) + VALUES ( + @device_number, + @point, + @start_timestamp, + @end_timestamp, + @count_rawdata, + @min_rawdata, + @max_rawdata, + @avg_rawdata, + @sum_rawdata, + @is_complete, + @repeat_times, + @fail_reason) + END + + COMMIT TRANSACTION;"; + await backgroundServiceRepository.ExecuteSql(sql, waterArchiveWeekRawDatas); + if (!string.IsNullOrEmpty(saveToMSDB) && saveToMSDB == "1") + { + await backgroundServiceMsSqlRepository.ExecuteSql(mySql, waterArchiveWeekRawDatas); + } + } + await task_Detail.InsertWorkTime_End("ArchiveElectricMeterDayJob", "Week", "任務完成"); + } + catch (Exception exception) + { + await task_Detail.WorkFail("ArchiveElectricMeterDayJob", "Week", exception.ToString()); + logger.LogError("【ArchiveElectricMeterDayJob】【週歸檔】【任務失敗】"); + logger.LogError("【ArchiveElectricMeterDayJob】【週歸檔】【任務失敗】[Exception]:{0}", exception.ToString()); + } + } + #endregion 週歸檔 + + #region 月歸檔 + if (await task_Detail.GetNeedWorkTask("ArchiveElectricMeterDayJob", "Month")) + { + try + { + await task_Detail.InsertWorkTime("ArchiveElectricMeterDayJob", "Month", "水電表月任務開始"); + var FirstDay = now.AddDays(-now.Day + 1); + var LastDay = now.AddMonths(1).AddDays(-now.AddMonths(1).Day); + + var dayInMonth = DateTime.DaysInMonth(now.Year, now.Month); + + var startTimestamp = string.Format("{0}T00:00:00.000+08:00", FirstDay.ToString("yyyy-MM-dd")); + var endTimestamp = string.Format("{0}T23:59:59.000+08:00", LastDay.ToString("yyyy-MM-dd")); + + var historyQueryFilter = $@" + + + + "; + + //Stopwatch stopWatch = new Stopwatch(); + //stopWatch.Start(); + + //抓取每個設備的資料 + List> electricArchiveMonthRawDatas = new List>(); + List> waterArchiveMonthRawDatas = new List>(); + foreach (var deviceNumberPoint in electricDeviceNumberPoints) + { + HttpWebRequest archiveMonthRequest = (HttpWebRequest)WebRequest.Create($"{obixApiConfig.ApiBase}obix/histories/FIC_Center/{deviceNumberPoint.FullDeviceNumberPoint}/~historyRollup/"); + //HttpWebRequest archiveMonthRequest = (HttpWebRequest)WebRequest.Create($"{obixApiConfig.ApiBase}obix/histories/FIC_Center/H_E1_B1F_MVCB_MVCBH_V1/~historyRollup/"); + archiveMonthRequest.Method = "POST"; + archiveMonthRequest.Headers.Add("Authorization", "Basic " + encoded); + archiveMonthRequest.PreAuthenticate = true; + + byte[] byteArray = Encoding.UTF8.GetBytes(historyQueryFilter); + using (Stream reqStream = archiveMonthRequest.GetRequestStream()) + { + reqStream.Write(byteArray, 0, byteArray.Length); + } + + HttpWebResponse archiveMonthResponse = (HttpWebResponse)archiveMonthRequest.GetResponse(); + var archiveMonthResponseContent = new StreamReader(archiveMonthResponse.GetResponseStream()).ReadToEnd(); + + xmlDocument.LoadXml(archiveMonthResponseContent); + string archiveMonthJson = JsonConvert.SerializeXmlNode(xmlDocument); + JObject archiveMonthJsonResult = (JObject)JsonConvert.DeserializeObject(archiveMonthJson); + + if (archiveMonthJsonResult.ContainsKey("err")) //抓取錯誤 + { + //logger.LogError("【ArchiveElectricMeterDayJob】【月歸檔】【取得資料失敗】"); + //logger.LogError("【ArchiveElectricMeterDayJob】【月歸檔】【取得資料失敗】[錯誤內容]:{0}", archiveMonthJsonResult); + + Dictionary archiveMonthRawData = new Dictionary(); + archiveMonthRawData.Add("@device_number", deviceNumberPoint.DeviceNumber); + archiveMonthRawData.Add("@point", deviceNumberPoint.Point); + archiveMonthRawData.Add("@start_timestamp", startTimestamp.Replace("T", " ").Substring(0, 19)); + archiveMonthRawData.Add("@end_timestamp", endTimestamp.Replace("T", " ").Substring(0, 19)); + archiveMonthRawData.Add("@is_complete", 0); + archiveMonthRawData.Add("@repeat_times", 0); + archiveMonthRawData.Add("@fail_reason", archiveMonthJson); + + archiveMonthRawData.Add("@count_rawdata", 0); + archiveMonthRawData.Add("@min_rawdata", 0); + archiveMonthRawData.Add("@max_rawdata", 0); + archiveMonthRawData.Add("@avg_rawdata", 0); + archiveMonthRawData.Add("@sum_rawdata", 0); + archiveMonthRawData.Add("@updated_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); + + electricArchiveMonthRawDatas.Add(archiveMonthRawData); + } + + if (archiveMonthJsonResult.ContainsKey("obj")) //表示可以讀取到內容 + { + var ArrangeRawDatas = ArrangeRawData(deviceNumberPoint, archiveMonthJsonResult); + if (ArrangeRawDatas != null && ArrangeRawDatas.Count() > 0) + { + electricArchiveMonthRawDatas.AddRange(ArrangeRawDatas); + } + } + } + foreach (var deviceNumberPoint in waterDeviceNumberPoints) + { + HttpWebRequest archiveMonthRequest = (HttpWebRequest)WebRequest.Create($"{obixApiConfig.ApiBase}obix/histories/FIC_Center/{deviceNumberPoint.FullDeviceNumberPoint}/~historyRollup/"); + //HttpWebRequest archiveMonthRequest = (HttpWebRequest)WebRequest.Create($"{obixApiConfig.ApiBase}obix/histories/FIC_Center/H_E1_B1F_MVCB_MVCBH_V1/~historyRollup/"); + archiveMonthRequest.Method = "POST"; + archiveMonthRequest.Headers.Add("Authorization", "Basic " + encoded); + archiveMonthRequest.PreAuthenticate = true; + + byte[] byteArray = Encoding.UTF8.GetBytes(historyQueryFilter); + using (Stream reqStream = archiveMonthRequest.GetRequestStream()) + { + reqStream.Write(byteArray, 0, byteArray.Length); + } + + HttpWebResponse archiveMonthResponse = (HttpWebResponse)archiveMonthRequest.GetResponse(); + var archiveMonthResponseContent = new StreamReader(archiveMonthResponse.GetResponseStream()).ReadToEnd(); + + xmlDocument.LoadXml(archiveMonthResponseContent); + string archiveMonthJson = JsonConvert.SerializeXmlNode(xmlDocument); + JObject archiveMonthJsonResult = (JObject)JsonConvert.DeserializeObject(archiveMonthJson); + + if (archiveMonthJsonResult.ContainsKey("err")) //抓取錯誤 + { + //logger.LogError("【ArchiveElectricMeterDayJob】【月歸檔】【取得資料失敗】"); + //logger.LogError("【ArchiveElectricMeterDayJob】【月歸檔】【取得資料失敗】[錯誤內容]:{0}", archiveMonthJsonResult); + + Dictionary archiveMonthRawData = new Dictionary(); + archiveMonthRawData.Add("@device_number", deviceNumberPoint.DeviceNumber); + archiveMonthRawData.Add("@point", deviceNumberPoint.Point); + archiveMonthRawData.Add("@start_timestamp", startTimestamp.Replace("T", " ").Substring(0, 19)); + archiveMonthRawData.Add("@end_timestamp", endTimestamp.Replace("T", " ").Substring(0, 19)); + archiveMonthRawData.Add("@is_complete", 0); + archiveMonthRawData.Add("@repeat_times", 0); + archiveMonthRawData.Add("@fail_reason", archiveMonthJson); + + archiveMonthRawData.Add("@count_rawdata", 0); + archiveMonthRawData.Add("@min_rawdata", 0); + archiveMonthRawData.Add("@max_rawdata", 0); + archiveMonthRawData.Add("@avg_rawdata", 0); + archiveMonthRawData.Add("@sum_rawdata", 0); + archiveMonthRawData.Add("@updated_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); + + waterArchiveMonthRawDatas.Add(archiveMonthRawData); + } + + if (archiveMonthJsonResult.ContainsKey("obj")) //表示可以讀取到內容 + { + var ArrangeRawDatas = ArrangeRawData(deviceNumberPoint, archiveMonthJsonResult); + if (ArrangeRawDatas != null && ArrangeRawDatas.Count() > 0) + { + waterArchiveMonthRawDatas.AddRange(ArrangeRawDatas); + } + } + } + + //stopWatch.Stop(); + //logger.LogInformation("【ArchiveElectricMeterDayJob】【月歸檔效能檢驗】[取得資料花費時間]{0} 毫秒", stopWatch.ElapsedMilliseconds); + + if (electricArchiveMonthRawDatas.Count() > 0) + { + var sql = $@" + UPDATE archive_electric_meter_month SET + count_rawdata = @count_rawdata, + min_rawdata = @min_rawdata, + max_rawdata = @max_rawdata, + avg_rawdata = @avg_rawdata, + sum_rawdata = @sum_rawdata, + is_complete = @is_complete, + repeat_times = @repeat_times, + fail_reason = @fail_reason, + updated_at = @updated_at + WHERE device_number = @device_number + AND point = @point + AND start_timestamp = @start_timestamp; + + INSERT INTO archive_electric_meter_month ( + device_number, + point, + start_timestamp, + end_timestamp, + count_rawdata, + min_rawdata, + max_rawdata, + avg_rawdata, + sum_rawdata, + is_complete, + repeat_times, + fail_reason) + SELECT + @device_number, + @point, + @start_timestamp, + @end_timestamp, + @count_rawdata, + @min_rawdata, + @max_rawdata, + @avg_rawdata, + @sum_rawdata, + @is_complete, + @repeat_times, + @fail_reason + WHERE ROW_COUNT() = 0;"; + + var mySql = $@"BEGIN TRANSACTION; + + UPDATE archive_electric_meter_month SET + count_rawdata = @count_rawdata, + min_rawdata = @min_rawdata, + max_rawdata = @max_rawdata, + avg_rawdata = @avg_rawdata, + sum_rawdata = @sum_rawdata, + is_complete = @is_complete, + repeat_times = @repeat_times, + fail_reason = @fail_reason, + updated_at = @updated_at + WHERE device_number = @device_number + AND point = @point + AND start_timestamp = @start_timestamp; + + IF @@ROWCOUNT = 0 + BEGIN + INSERT INTO archive_electric_meter_month ( + device_number, + point, + start_timestamp, + end_timestamp, + count_rawdata, + min_rawdata, + max_rawdata, + avg_rawdata, + sum_rawdata, + is_complete, + repeat_times, + fail_reason) + VALUES ( + @device_number, + @point, + @start_timestamp, + @end_timestamp, + @count_rawdata, + @min_rawdata, + @max_rawdata, + @avg_rawdata, + @sum_rawdata, + @is_complete, + @repeat_times, + @fail_reason) + END + + COMMIT TRANSACTION;"; + await backgroundServiceRepository.ExecuteSql(sql, electricArchiveMonthRawDatas); + if (!string.IsNullOrEmpty(saveToMSDB) && saveToMSDB == "1") + { + await backgroundServiceMsSqlRepository.ExecuteSql(mySql, electricArchiveMonthRawDatas); + } + } + if (waterArchiveMonthRawDatas.Count() > 0) + { + var sql = $@" + UPDATE archive_water_meter_month SET + count_rawdata = @count_rawdata, + min_rawdata = @min_rawdata, + max_rawdata = @max_rawdata, + avg_rawdata = @avg_rawdata, + sum_rawdata = @sum_rawdata, + is_complete = @is_complete, + repeat_times = @repeat_times, + fail_reason = @fail_reason, + updated_at = @updated_at + WHERE device_number = @device_number + AND point = @point + AND start_timestamp = @start_timestamp; + + INSERT INTO archive_water_meter_month ( + device_number, + point, + start_timestamp, + end_timestamp, + count_rawdata, + min_rawdata, + max_rawdata, + avg_rawdata, + sum_rawdata, + is_complete, + repeat_times, + fail_reason) + SELECT + @device_number, + @point, + @start_timestamp, + @end_timestamp, + @count_rawdata, + @min_rawdata, + @max_rawdata, + @avg_rawdata, + @sum_rawdata, + @is_complete, + @repeat_times, + @fail_reason + WHERE ROW_COUNT() = 0;"; + + var mySql = $@"BEGIN TRANSACTION; + + UPDATE archive_water_meter_month SET + count_rawdata = @count_rawdata, + min_rawdata = @min_rawdata, + max_rawdata = @max_rawdata, + avg_rawdata = @avg_rawdata, + sum_rawdata = @sum_rawdata, + is_complete = @is_complete, + repeat_times = @repeat_times, + fail_reason = @fail_reason, + updated_at = @updated_at + WHERE device_number = @device_number + AND point = @point + AND start_timestamp = @start_timestamp; + + IF @@ROWCOUNT = 0 + BEGIN + INSERT INTO archive_water_meter_month ( + device_number, + point, + start_timestamp, + end_timestamp, + count_rawdata, + min_rawdata, + max_rawdata, + avg_rawdata, + sum_rawdata, + is_complete, + repeat_times, + fail_reason) + VALUES ( + @device_number, + @point, + @start_timestamp, + @end_timestamp, + @count_rawdata, + @min_rawdata, + @max_rawdata, + @avg_rawdata, + @sum_rawdata, + @is_complete, + @repeat_times, + @fail_reason) + END + + COMMIT TRANSACTION;"; + await backgroundServiceRepository.ExecuteSql(sql, waterArchiveMonthRawDatas); + if (!string.IsNullOrEmpty(saveToMSDB) && saveToMSDB == "1") + { + await backgroundServiceMsSqlRepository.ExecuteSql(mySql, waterArchiveMonthRawDatas); + } + } + await task_Detail.InsertWorkTime_End("ArchiveElectricMeterDayJob", "Month", "任務完成"); + } + catch (Exception exception) + { + await task_Detail.WorkFail("ArchiveElectricMeterDayJob", "Month", exception.ToString()); + logger.LogError("【ArchiveElectricMeterDayJob】【月歸檔】【任務失敗】"); + logger.LogError("【ArchiveElectricMeterDayJob】【月歸檔】【任務失敗】[Exception]:{0}", exception.ToString()); + } + } + #endregion 月歸檔 + + #region 補償機制 + //取得連線字串 + if (await task_Detail.GetNeedWorkTask("ArchiveElectricMeterDayJob", "Compensate")) + { + try + { + await task_Detail.InsertWorkTime("ArchiveElectricMeterDayJob", "Compensate", "水電表補償任務開始"); + ProcEletricMeterService procEletricMeterService = new ProcEletricMeterService(backgroundServiceRepository, backgroundServiceMsSqlRepository); + await procEletricMeterService.ArchiveData(); + await task_Detail.InsertWorkTime_End("ArchiveElectricMeterDayJob", "Compensate", "任務完成"); + } + catch(Exception ex) + { + await task_Detail.WorkFail("ArchiveElectricMeterDayJob", "Compensate", ex.ToString()); + logger.LogError("【ArchiveElectricMeterDayJob】【補償機制】【任務失敗】"); + logger.LogError("【ArchiveElectricMeterDayJob】【補償機制】【任務失敗】[Exception]:{0}", ex.ToString()); + } + } + #endregion 補償機制 + + await task_Detail.InsertWorkTime_End("ArchiveElectricMeterDayJob", "All","任務完成"); + } + } + catch (Exception exception) + { + await task_Detail.WorkFail("ArchiveElectricMeterDayJob", "All", exception.ToString()); + logger.LogError("【ArchiveElectricMeterDayJob】【任務失敗】"); + logger.LogError("【ArchiveElectricMeterDayJob】【任務失敗】[Exception]:{0}", exception.ToString()); + } + } + + private List> ArrangeRawData(DeviceNumberPoint deviceNumberPoint, JObject jsonResult) + { + List> arrangeRawDatas = new List>(); + var histories = jsonResult["obj"]["list"]; + var rawdateCount = Convert.ToInt32(jsonResult["obj"]["int"]["@val"].ToString()); + + if(rawdateCount == 0) + { + return null; + } + + if (histories != null && histories.HasValues) + { + if (rawdateCount > 1) + { //多筆資料 + foreach (var history in histories) + { + Dictionary arrangeRawData = new Dictionary(); + arrangeRawData.Add("@device_number", deviceNumberPoint.DeviceNumber); + arrangeRawData.Add("@point", deviceNumberPoint.Point); + + //時間 + if (history["abstime"] != null && history["abstime"].HasValues) + { + foreach (var abstime in history["abstime"]) + { + var name = abstime["@name"].ToString(); + switch (name) + { + case "start": + var startTimstamp = Convert.ToDateTime(abstime["@val"].ToString()).ToString("yyyy-MM-dd HH:mm:ss"); + arrangeRawData.Add("@start_timestamp", startTimstamp); + break; + case "end": + var endTimstamp = Convert.ToDateTime(abstime["@val"].ToString()).ToString("yyyy-MM-dd HH:mm:ss"); + arrangeRawData.Add("@end_timestamp", endTimstamp); + break; + } + } + } + + //區間內資料筆數 + if (history["int"] != null && history["int"].HasValues) + { + var count = Convert.ToInt32(histories["obj"]["int"]["@val"].ToString()); + arrangeRawData.Add("@count_rawdata", count); + } + + //整合數值(最大、最小、平均、總和) + if (history["real"] != null && history["real"].HasValues) + { + foreach (var real in history["real"]) + { + var name = real["@name"].ToString(); + switch (name) + { + case "min": + var min = Convert.ToDecimal(real["@val"].ToString()); + arrangeRawData.Add("@min_rawdata", min); + break; + case "max": + var max = Convert.ToDecimal(real["@val"].ToString()); + arrangeRawData.Add("@max_rawdata", max); + break; + case "avg": + var avg = Convert.ToDecimal(real["@val"].ToString()); + arrangeRawData.Add("@avg_rawdata", avg); + break; + case "sum": + var sum = Decimal.Parse(real["@val"].ToString(), System.Globalization.NumberStyles.Float); + arrangeRawData.Add("@sum_rawdata", sum); + break; + } + } + } + arrangeRawData.Add("@is_complete", 1); + arrangeRawData.Add("@repeat_times", 0); + arrangeRawData.Add("@fail_reason", null); + arrangeRawData.Add("@updated_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); + + arrangeRawDatas.Add(arrangeRawData); + } + } + else + { //單筆資料 + Dictionary arrangeRawData = new Dictionary(); + arrangeRawData.Add("@device_number", deviceNumberPoint.DeviceNumber); + arrangeRawData.Add("@point", deviceNumberPoint.Point); + + //時間 + if (histories["obj"]["abstime"] != null && histories["obj"]["abstime"].HasValues) + { + foreach (var abstime in histories["obj"]["abstime"]) + { + var name = abstime["@name"].ToString(); + switch (name) + { + case "start": + var startTimstamp = Convert.ToDateTime(abstime["@val"].ToString()).ToString("yyyy-MM-dd HH:mm:ss"); + arrangeRawData.Add("@start_timestamp", startTimstamp); + break; + case "end": + var endTimstamp = Convert.ToDateTime(abstime["@val"].ToString()).ToString("yyyy-MM-dd HH:mm:ss"); + arrangeRawData.Add("@end_timestamp", endTimstamp); + break; + } + } + } + + //區間內資料筆數 + if (histories["obj"]["int"] != null && histories["obj"]["int"].HasValues) + { + var count = Convert.ToInt32(histories["obj"]["int"]["@val"].ToString()); + arrangeRawData.Add("@count_rawdata", count); + } + + //整合數值(最大、最小、平均、總和) + if (histories["obj"]["real"] != null && histories["obj"]["real"].HasValues) + { + foreach (var real in histories["obj"]["real"]) + { + var name = real["@name"].ToString(); + switch (name) + { + case "min": + var min = Convert.ToDecimal(real["@val"].ToString()); + arrangeRawData.Add("@min_rawdata", min); + break; + case "max": + var max = Convert.ToDecimal(real["@val"].ToString()); + arrangeRawData.Add("@max_rawdata", max); + break; + case "avg": + var avg = Convert.ToDecimal(real["@val"].ToString()); + arrangeRawData.Add("@avg_rawdata", avg); + break; + case "sum": + var sum = Decimal.Parse(real["@val"].ToString(), System.Globalization.NumberStyles.Float); + arrangeRawData.Add("@sum_rawdata", sum); + break; + } + } + } + arrangeRawData.Add("@is_complete", 1); + arrangeRawData.Add("@repeat_times", 0); + arrangeRawData.Add("@fail_reason", null); + arrangeRawData.Add("@updated_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); + + arrangeRawDatas.Add(arrangeRawData); + } + } + + return arrangeRawDatas; + } + } +} diff --git a/Backend/Quartz/Jobs/ArchiveElectricMeterHourJob.cs b/Backend/Quartz/Jobs/ArchiveElectricMeterHourJob.cs new file mode 100644 index 0000000..dda8ae2 --- /dev/null +++ b/Backend/Quartz/Jobs/ArchiveElectricMeterHourJob.cs @@ -0,0 +1,335 @@ +using Backend.Models; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Quartz; +using Repository.BackendRepository.Interface; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using System.Xml; + +namespace Backend.Quartz.Jobs +{ + /// + /// 電錶歸檔,每小時執行,只執行小時歸檔 + /// + [DisallowConcurrentExecution] + class ArchiveElectricMeterHourJob : IJob + { + private readonly ILogger logger; + private readonly IBackgroundServiceRepository backgroundServiceRepository; + private readonly ILogger loggers; + + public ArchiveElectricMeterHourJob( + ILogger logger, + IBackgroundServiceRepository backgroundServiceRepository, ILogger loggers) + { + this.logger = logger; + this.backgroundServiceRepository = backgroundServiceRepository; + this.loggers = loggers; + } + + public async Task Execute(IJobExecutionContext context) + { + Task_Detail task_Detail = new Task_Detail(loggers, backgroundServiceRepository); + try + { + if(await task_Detail.GetNeedWorkTask("ArchiveElectricMeterHourJob", "All")) + { + await task_Detail.InsertWorkTime("ArchiveElectricMeterHourJob", "All", "任務開始"); + EDFunction ed = new EDFunction(); + XmlDocument xmlDocument = new XmlDocument(); + + var sqlArchive = $@"SELECT system_value as Value, system_key as Name FROM variable WHERE deleted = 0 AND system_type = 'archiveConfig'"; + + var variableArchive = await backgroundServiceRepository.GetAllAsync(sqlArchive); + var electricMeterGuid = variableArchive.Where(x => x.Name == "ElectricMeterGuid").Select(x => x.Value).FirstOrDefault(); + + #region 找出所有電錶設備 + var sWhere = "deleted = 0 AND sub_system_guid = @sub_system_guid"; + var electricMeters = await backgroundServiceRepository.GetAllAsync("device", sWhere, new { sub_system_guid = electricMeterGuid }); + #endregion 找出所有電錶設備 + + #region 找出所有電錶系統的點位 + var sPointWhere = "deleted = 0 AND sub_system_guid = @sub_system_guid"; + var points = await backgroundServiceRepository.GetAllAsync("device_item", sPointWhere, new { sub_system_guid = electricMeterGuid }); + #endregion 找出所有電錶系統的點位 + + #region 組合出所有電錶設備點位 + List deviceNumberPoints = new List(); + foreach (var electricMeter in electricMeters) + { + foreach (var point in points) + { + DeviceNumberPoint deviceNumberPoint = new DeviceNumberPoint(); + deviceNumberPoint.DeviceNumber = electricMeter.Device_number; + deviceNumberPoint.Point = point.points; + deviceNumberPoint.FullDeviceNumberPoint = string.Format("{0}_{1}", electricMeter.Device_number, point.points); + + deviceNumberPoints.Add(deviceNumberPoint); + } + } + #endregion 組合出所有電錶設備點位 + + #region 取得obix 設定 + var obixApiConfig = new ObixApiConfig(); + + var sqlObix = $@"SELECT system_value as Value, system_key as Name FROM variable WHERE deleted = 0 AND system_type = 'obixConfig'"; + + var variableObix = await backgroundServiceRepository.GetAllAsync(sqlObix); + obixApiConfig.ApiBase = variableObix.Where(x => x.Name == "ApiBase").Select(x => x.Value).FirstOrDefault(); + obixApiConfig.UserName = ed.AESDecrypt(variableObix.Where(x => x.Name == "UserName").Select(x => x.Value).FirstOrDefault()); + obixApiConfig.Password = ed.AESDecrypt(variableObix.Where(x => x.Name == "Password").Select(x => x.Value).FirstOrDefault()); + + String encoded = System.Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(obixApiConfig.UserName + ":" + obixApiConfig.Password)); + #endregion 取得obix 設定 + + var now = DateTime.Now; + + #region 小時歸檔 + try + { + var preHour = now.AddHours(-1); //取得前一小時 + + var tempHour = string.Empty; + if (preHour.Hour < 10) + { + tempHour = "0" + preHour.Hour.ToString(); + } + else + { + tempHour = preHour.Hour.ToString(); + } + + var startTimestamp = string.Format("{0}T{1}:00:00.000+08:00", preHour.ToString("yyyy-MM-dd"), tempHour); + var endTimestamp = string.Format("{0}T{1}:59:59.000+08:00", preHour.ToString("yyyy-MM-dd"), tempHour); + + var historyQueryFilter = $@" + + + + "; + + //Stopwatch stopWatch = new Stopwatch(); + //stopWatch.Start(); + + //抓取每個設備的資料 + List> archiveHourRawDatas = new List>(); + foreach (var deviceNumberPoint in deviceNumberPoints) + { + + HttpWebRequest archiveHourRequest = (HttpWebRequest)WebRequest.Create($"{obixApiConfig.ApiBase}obix/histories/FIC_Center/{deviceNumberPoint.FullDeviceNumberPoint}/~historyRollup/"); + //HttpWebRequest archiveHourRequest = (HttpWebRequest)WebRequest.Create($"{obixApiConfig.ApiBase}obix/histories/FIC_Center/H_E1_B1F_MVCB_MVCBH_V1/~historyRollup/"); + archiveHourRequest.Method = "POST"; + archiveHourRequest.Headers.Add("Authorization", "Basic " + encoded); + archiveHourRequest.PreAuthenticate = true; + + byte[] byteArray = Encoding.UTF8.GetBytes(historyQueryFilter); + using (Stream reqStream = archiveHourRequest.GetRequestStream()) + { + reqStream.Write(byteArray, 0, byteArray.Length); + } + + HttpWebResponse archiveHourResponse = (HttpWebResponse)archiveHourRequest.GetResponse(); + var archiveHourResponseContent = new StreamReader(archiveHourResponse.GetResponseStream()).ReadToEnd(); + + xmlDocument.LoadXml(archiveHourResponseContent); + string archiveHourJson = JsonConvert.SerializeXmlNode(xmlDocument); + JObject archiveHourJsonResult = (JObject)JsonConvert.DeserializeObject(archiveHourJson); + + if (archiveHourJsonResult.ContainsKey("err")) //抓取錯誤 + { + //logger.LogError("【ArchiveElectricMeterHourJob】【小時歸檔】【取得資料失敗】"); + //logger.LogError("【ArchiveElectricMeterHourJob】【小時歸檔】【取得資料失敗】[錯誤內容]:{0}", archiveHourJsonResult); + + Dictionary archiveHourRawData = new Dictionary(); + archiveHourRawData.Add("@device_number", deviceNumberPoint.DeviceNumber); + archiveHourRawData.Add("@point", deviceNumberPoint.Point); + archiveHourRawData.Add("@start_timestamp", startTimestamp.Replace("T", " ").Substring(0, 19)); + archiveHourRawData.Add("@end_timestamp", endTimestamp.Replace("T", " ").Substring(0, 19)); + archiveHourRawData.Add("@is_complete", 0); + archiveHourRawData.Add("@repeat_times", 0); + archiveHourRawData.Add("@fail_reason", archiveHourJson); + + archiveHourRawDatas.Add(archiveHourRawData); + } + + if (archiveHourJsonResult.ContainsKey("obj")) //表示可以讀取到內容 + { + var histories = archiveHourJsonResult["obj"]["list"]; + var rawdateCount = Convert.ToInt32(archiveHourJsonResult["obj"]["int"]["@val"].ToString()); + if (histories != null && histories.HasValues) + { + if (rawdateCount > 1) + { //多筆資料 + foreach (var history in histories) + { + Dictionary archiveHourRawData = new Dictionary(); + archiveHourRawData.Add("@device_number", deviceNumberPoint.DeviceNumber); + archiveHourRawData.Add("@point", deviceNumberPoint.Point); + + //時間 + if (history["abstime"] != null && history["abstime"].HasValues) + { + foreach (var abstime in history["abstime"]) + { + var name = abstime["@name"].ToString(); + switch (name) + { + case "start": + var startTimstamp = Convert.ToDateTime(abstime["@val"].ToString()).ToString("yyyy-MM-dd HH:mm:ss"); + archiveHourRawData.Add("@start_timestamp", startTimstamp); + break; + case "end": + var endTimstamp = Convert.ToDateTime(abstime["@val"].ToString()).ToString("yyyy-MM-dd HH:mm:ss"); + archiveHourRawData.Add("@end_timestamp", endTimstamp); + break; + } + } + } + + //區間內資料筆數 + if (history["int"] != null && history["int"].HasValues) + { + var count = Convert.ToInt32(histories["obj"]["int"]["@val"].ToString()); + archiveHourRawData.Add("@count_rawdata", count); + } + + //整合數值(最大、最小、平均、總和) + if (history["real"] != null && history["real"].HasValues) + { + foreach (var real in history["real"]) + { + var name = real["@name"].ToString(); + switch (name) + { + case "min": + var min = Convert.ToDecimal(real["@val"].ToString()); + archiveHourRawData.Add("@min_rawdata", min); + break; + case "max": + var max = Convert.ToDecimal(real["@val"].ToString()); + archiveHourRawData.Add("@max_rawdata", max); + break; + case "avg": + var avg = Convert.ToDecimal(real["@val"].ToString()); + archiveHourRawData.Add("@avg_rawdata", avg); + break; + case "sum": + var sum = Convert.ToDecimal(real["@val"].ToString()); + archiveHourRawData.Add("@sum_rawdata", sum); + break; + } + } + } + archiveHourRawData.Add("@is_complete", 1); + + archiveHourRawDatas.Add(archiveHourRawData); + } + } + else + { //單筆資料 + Dictionary archiveHourRawData = new Dictionary(); + archiveHourRawData.Add("@device_number", deviceNumberPoint.DeviceNumber); + archiveHourRawData.Add("@point", deviceNumberPoint.Point); + + //時間 + if (histories["obj"]["abstime"] != null && histories["obj"]["abstime"].HasValues) + { + foreach (var abstime in histories["obj"]["abstime"]) + { + var name = abstime["@name"].ToString(); + switch (name) + { + case "start": + var startTimstamp = Convert.ToDateTime(abstime["@val"].ToString()).ToString("yyyy-MM-dd HH:mm:ss"); + archiveHourRawData.Add("@start_timestamp", startTimstamp); + break; + case "end": + var endTimstamp = Convert.ToDateTime(abstime["@val"].ToString()).ToString("yyyy-MM-dd HH:mm:ss"); + archiveHourRawData.Add("@end_timestamp", endTimstamp); + break; + } + } + } + + //區間內資料筆數 + if (histories["obj"]["int"] != null && histories["obj"]["int"].HasValues) + { + var count = Convert.ToInt32(histories["obj"]["int"]["@val"].ToString()); + archiveHourRawData.Add("@count_rawdata", count); + } + + //整合數值(最大、最小、平均、總和) + if (histories["obj"]["real"] != null && histories["obj"]["real"].HasValues) + { + foreach (var real in histories["obj"]["real"]) + { + var name = real["@name"].ToString(); + switch (name) + { + case "min": + var min = Convert.ToDecimal(real["@val"].ToString()); + archiveHourRawData.Add("@min_rawdata", min); + break; + case "max": + var max = Convert.ToDecimal(real["@val"].ToString()); + archiveHourRawData.Add("@max_rawdata", max); + break; + case "avg": + var avg = Convert.ToDecimal(real["@val"].ToString()); + archiveHourRawData.Add("@avg_rawdata", avg); + break; + case "sum": + var sum = Convert.ToDecimal(real["@val"].ToString()); + archiveHourRawData.Add("@sum_rawdata", sum); + break; + } + } + } + archiveHourRawData.Add("@is_complete", 1); + + archiveHourRawDatas.Add(archiveHourRawData); + } + } + } + } + + //stopWatch.Stop(); + //logger.LogInformation("【ArchiveElectricMeterHourJob】【小時歸檔】【效能檢驗】[取得資料花費時間]{0} 毫秒", stopWatch.ElapsedMilliseconds); + + if (archiveHourRawDatas.Count() > 0) + { + await backgroundServiceRepository.AddMutiByCustomTable(archiveHourRawDatas, "archive_electric_meter_hour"); + } + + + await task_Detail.InsertWorkTime_End("ArchiveElectricMeterHourJob", "All", "任務完成"); + } + catch (Exception exception) + { + await task_Detail.WorkFail("ArchiveElectricMeterHourJob", "All", exception.ToString()); + logger.LogError("【ArchiveElectricMeterHourJob】【小時歸檔】【任務失敗】"); + logger.LogError("【ArchiveElectricMeterHourJob】【小時歸檔】【任務失敗】[Exception]:{0}", exception.ToString()); + } + #endregion 小時歸檔 + + + } + + } + catch (Exception exception) + { + await task_Detail.WorkFail("ArchiveElectricMeterHourJob", "All", exception.ToString()); + logger.LogError("【ArchiveElectricMeterHourJob】【任務失敗】"); + logger.LogError("【ArchiveElectricMeterHourJob】【任務失敗】[Exception]:{0}", exception.ToString()); + } + } + } +} diff --git a/Backend/Quartz/Jobs/DataDeliveryJob.cs b/Backend/Quartz/Jobs/DataDeliveryJob.cs new file mode 100644 index 0000000..7a2b7b9 --- /dev/null +++ b/Backend/Quartz/Jobs/DataDeliveryJob.cs @@ -0,0 +1,269 @@ +using Backend.Models; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; +using Quartz; +using Repository.BackendRepository.Interface; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; + +namespace Backend.Quartz.Jobs +{ + [DisallowConcurrentExecution] + class DataDeliveryJob : IJob + { + private readonly ILogger logger; + private readonly IBackgroundServiceRepository backgroundServiceRepository; + + public DataDeliveryJob( + ILogger logger, + IBackgroundServiceRepository backgroundServiceRepository) + { + this.logger = logger; + this.backgroundServiceRepository = backgroundServiceRepository; + } + + public async Task Execute(IJobExecutionContext context) + { + Dictionary insertLog = new Dictionary() + { + { "@task_id", 0 }, + { "@log_level", "" }, + { "@log_content", "" } + }; + + try + { + logger.LogInformation("【DataDeliveryJob】【任務開始】"); + insertLog["@log_level"] = $@"INFO"; + insertLog["@log_content"] = $@"【DataDeliveryJob】任務開始"; + await backgroundServiceRepository.AddOneByCustomTable(insertLog, "background_service_task_log"); + + //找出所有要派送的資料 + string sWhere = @"is_complete = 0 AND task_type = @Task_type AND repeat_times < 10"; + var backgroundServiceTasks = await backgroundServiceRepository.GetAllAsync("background_service_task", sWhere, new { Task_type = BackgroundServiceTaskType.data_delivery }); + + if (backgroundServiceTasks.Count == 0) + { + logger.LogInformation("【DataDeliveryJob】【查無任務列表】"); + + insertLog["@log_level"] = $@"INFO"; + insertLog["@log_content"] = $@"【DataDeliveryJob】查無任務列表"; + await backgroundServiceRepository.AddOneByCustomTable(insertLog, "background_service_task_log"); + } + else + { + List> updateObjs = new List>(); + foreach (var task in backgroundServiceTasks) + { + var DateTimeNow = DateTime.Now; + Dictionary updateObj = new Dictionary() + { + { "Id", task.Id }, + { "@repeat_times", 0 }, + { "@is_complete", 0 }, + { "@fail_reason", null }, + { "@complete_at", null }, + { "@updated_at", DateTimeNow } + }; + + insertLog["@task_id"] = task.Id; + + //var parameters = JsonSerializer.Deserialize>(task.Target_data); + try + { + logger.LogInformation("【DataDeliveryJob】【開始派送】[棟別IP]:{0}", task.Target_ip); + insertLog["@log_level"] = $@"INFO"; + insertLog["@log_content"] = $@"開始派送"; + await backgroundServiceRepository.AddOneByCustomTable(insertLog, "background_service_task_log"); + + var boundary = "----------------------------" + DateTime.Now.Ticks.ToString("x"); + + var boundaryBytes = Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n"); + var endBoundaryBytes = Encoding.ASCII.GetBytes("\r\n--" + boundary + "--"); + + HttpWebRequest Postrequest = (HttpWebRequest)WebRequest.Create($"http://{task.Target_ip}/api/ReceiveDataDelivery/GetData"); + Postrequest.ContentType = "multipart/form-data; boundary=" + boundary; + Postrequest.Method = "POST"; + + if (!string.IsNullOrEmpty(task.Target_table)) + { + using (Stream requestStream = Postrequest.GetRequestStream()) + { + //Id + requestStream.Write(boundaryBytes, 0, boundaryBytes.Length); + string task_id = "Content-Disposition: form-data; name=\"" + "Id" + "\"\r\n\r\n" + task.Id; + byte[] task_id_bytes = System.Text.Encoding.UTF8.GetBytes(task_id); + requestStream.Write(task_id_bytes, 0, task_id_bytes.Length); + + //Target Table + requestStream.Write(boundaryBytes, 0, boundaryBytes.Length); + string target_table = "Content-Disposition: form-data; name=\"" + "TargetTable" + "\"\r\n\r\n" + task.Target_table; + byte[] target_table_bytes = System.Text.Encoding.UTF8.GetBytes(target_table); + requestStream.Write(target_table_bytes, 0, target_table_bytes.Length); + + //mode + requestStream.Write(boundaryBytes, 0, boundaryBytes.Length); + string target_mode = "Content-Disposition: form-data; name=\"" + "mode" + "\"\r\n\r\n" + task.Mode; + byte[] target_mode_bytes = System.Text.Encoding.UTF8.GetBytes(target_mode); + requestStream.Write(target_mode_bytes, 0, target_mode_bytes.Length); + + //Target data + requestStream.Write(boundaryBytes, 0, boundaryBytes.Length); + string target_data = "Content-Disposition: form-data; name=\"" + "TargetData" + "\"\r\n\r\n" + task.Target_data; + byte[] target_data_bytes = System.Text.Encoding.UTF8.GetBytes(target_data); + requestStream.Write(target_data_bytes, 0, target_data_bytes.Length); + + //解析Files + if (task.Target_files != null) + { + var target_files = JsonSerializer.Deserialize>(task.Target_files); + var file_index = 0; + + foreach (var file in target_files) + { + //file Folder + requestStream.Write(boundaryBytes, 0, boundaryBytes.Length); + string file_folder = "Content-Disposition: form-data; name=\"" + $"FileInfos[{file_index}].Folder" + "\"\r\n\r\n" + file.Folder; + byte[] file_folder_bytes = System.Text.Encoding.UTF8.GetBytes(file_folder); + requestStream.Write(file_folder_bytes, 0, file_folder_bytes.Length); + + //file OriginalFileName + requestStream.Write(boundaryBytes, 0, boundaryBytes.Length); + string orig_file_name = "Content-Disposition: form-data; name=\"" + $"FileInfos[{file_index}].OriginalFileName" + "\"\r\n\r\n" + file.OriginalFileName; + byte[] orig_file_name_bytes = System.Text.Encoding.UTF8.GetBytes(orig_file_name); + requestStream.Write(orig_file_name_bytes, 0, orig_file_name_bytes.Length); + + //file FileName + requestStream.Write(boundaryBytes, 0, boundaryBytes.Length); + string file_name = "Content-Disposition: form-data; name=\"" + $"FileInfos[{file_index}].FileName" + "\"\r\n\r\n" + file.FileName; + byte[] file_name_bytes = System.Text.Encoding.UTF8.GetBytes(file_name); + requestStream.Write(file_name_bytes, 0, file_name_bytes.Length); + + //取得Content-Type + var content_type = string.Empty; + string ext = Path.GetExtension(file.File); + using (Microsoft.Win32.RegistryKey registryKey = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(ext)) + { + if (registryKey != null) + { + var value = registryKey.GetValue("Content Type"); + if (value != null) + { + content_type = value.ToString(); + } + } + } + + if (file.File != null) + { + string file_header = "Content-Disposition: form-data; name=\"" + $"FileInfos[{file_index}].File" + "\"; filename=\"" + file.FileName + "\"\r\nContent-Type: " + content_type + "\r\n\r\n"; + byte[] file_header_bytes = System.Text.Encoding.UTF8.GetBytes(file_header); + + requestStream.Write(boundaryBytes, 0, boundaryBytes.Length); + requestStream.Write(file_header_bytes, 0, file_header_bytes.Length); + byte[] buffer = new byte[32768]; + int bytesRead; + + // upload from file + using (FileStream fileStream = File.OpenRead(file.File)) + { + while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0) + requestStream.Write(buffer, 0, bytesRead); + fileStream.Close(); + } + } + } + } + + requestStream.Write(endBoundaryBytes, 0, endBoundaryBytes.Length); + requestStream.Close(); + } + } + + HttpWebResponse response = (HttpWebResponse)Postrequest.GetResponse(); + var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd(); + var statusNumber = (int)response.StatusCode; + if (statusNumber != 200) + { + logger.LogError("【DataDeliveryJob】【派送失敗】[棟別IP]:{0}", task.Target_ip); + logger.LogError("【DataDeliveryJob】【派送失敗】[response]:{0}", responseString); + updateObj["@repeat_times"] = task.Repeat_times + 1; + updateObj["@fail_reason"] = responseString; + + insertLog["@log_level"] = $@"ERR"; + insertLog["@log_content"] = $@"派送失敗 - [失敗原因]:{responseString}"; + await backgroundServiceRepository.AddOneByCustomTable(insertLog, "background_service_task_log"); + } + else + { + //解析回傳內容 + var final = JObject.Parse(responseString); + var code = final["code"].ToString(); + if (code == "0000") + { + logger.LogInformation("【DataDeliveryJob】【派送成功】[棟別IP]:{0}", task.Target_ip); + updateObj["@repeat_times"] = task.Repeat_times; + updateObj["@is_complete"] = 1; + updateObj["@complete_at"] = DateTime.Now; + + insertLog["@log_level"] = $@"INFO"; + insertLog["@log_content"] = $@"派送成功"; + await backgroundServiceRepository.AddOneByCustomTable(insertLog, "background_service_task_log"); + } + else + { + logger.LogError("【DataDeliveryJob】【派送失敗】[棟別IP]:{0}", task.Target_ip); + logger.LogError("【DataDeliveryJob】【派送失敗】[response]:{0}", responseString); + updateObj["@repeat_times"] = task.Repeat_times + 1; + updateObj["@fail_reason"] = responseString; + + insertLog["@log_level"] = $@"ERR"; + insertLog["@log_content"] = $@"派送失敗 - [失敗原因]:{responseString}"; + await backgroundServiceRepository.AddOneByCustomTable(insertLog, "background_service_task_log"); + } + } + } + catch (Exception exception) + { + logger.LogError("【DataDeliveryJob】【派送失敗】[棟別IP]:{0}", task.Target_ip); + logger.LogError("【DataDeliveryJob】【派送失敗】[Exception]:{0}", exception.ToString()); + updateObj["@repeat_times"] = task.Repeat_times + 1; + updateObj["@fail_reason"] = exception.ToString(); + + insertLog["@log_level"] = $@"ERR"; + insertLog["@log_content"] = $@"派送失敗 - [失敗原因(Exception)]:{exception.ToString()}"; + await backgroundServiceRepository.AddOneByCustomTable(insertLog, "background_service_task_log"); + } + + updateObjs.Add(updateObj); + } + + await backgroundServiceRepository.UpdateListByCustomTable(updateObjs, "background_service_task", "id = @Id"); + logger.LogInformation("【DataDeliveryJob】【任務完成】"); + + insertLog["@task_id"] = 0; + insertLog["@log_level"] = $@"INFO"; + insertLog["@log_content"] = $@"任務完成"; + await backgroundServiceRepository.AddOneByCustomTable(insertLog, "background_service_task_log"); + } + } + catch (Exception exception) + { + logger.LogError("【DataDeliveryJob】【任務失敗】"); + logger.LogError("【DataDeliveryJob】【任務失敗】[Exception]:{0}", exception.ToString()); + + insertLog["@task_id"] = 0; + insertLog["@log_level"] = $@"ERR"; + insertLog["@log_content"] = $@"任務失敗"; + await backgroundServiceRepository.AddOneByCustomTable(insertLog, "background_service_task_log"); + } + + } + } +} diff --git a/Backend/Quartz/Jobs/ExecutionBackgroundServicePlanJob.cs b/Backend/Quartz/Jobs/ExecutionBackgroundServicePlanJob.cs new file mode 100644 index 0000000..fae06f2 --- /dev/null +++ b/Backend/Quartz/Jobs/ExecutionBackgroundServicePlanJob.cs @@ -0,0 +1,113 @@ +using Backend.Models; +using Microsoft.Extensions.Logging; +using Quartz; +using Repository.BackendRepository.Interface; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace Backend.Quartz.Jobs +{ + [DisallowConcurrentExecution] + public class ExecutionBackgroundServicePlanJob : IJob + { + private readonly ILogger logger; + private readonly IBackgroundServiceRepository backgroundServiceRepository; + + public ExecutionBackgroundServicePlanJob( + ILogger logger, + IBackgroundServiceRepository backgroundServiceRepository) + { + this.logger = logger; + this.backgroundServiceRepository = backgroundServiceRepository; + } + + public async Task Execute(IJobExecutionContext context) + { + try + { + logger.LogInformation("【ExecutionBackgroundServicePlanJob】【任務開始】"); + + // 找出當前在起始與結束時間所有計畫 + var DateTimeNow = DateTime.Now; + var sPlanWhere = @"deleted = 0 + AND + ( + @DateTimeNow Between start_time AND end_time + OR (end_time IS NULL AND @DateTimeNow > start_time) + ) + "; + var backgroundServicePlans = await backgroundServiceRepository.GetAllAsync("background_service_plan", sPlanWhere, new { DateTimeNow = DateTimeNow.ToString("yyyy-MM-dd HH:mm:ss") }); + + foreach (var plan in backgroundServicePlans) + { + //logger.LogInformation("【ExecutionBackgroundServicePlanJob】【計畫編號:{0},計畫名稱:{1}】 - 開始生成下次任務項目", plan.Id, plan.Plane_name); + #region 紀錄最後生成任務的時間 + try + { + var lastCreateTime = Convert.ToDateTime(plan.Last_create_time); + if (lastCreateTime == default(DateTime)) + { + lastCreateTime = Convert.ToDateTime(plan.Start_time); + } + + DateTime nextCreateTime; //下次待生成的時間 + nextCreateTime = plan.Execution_type switch + { + (byte)ExecutionTypeEnum.Min => Convert.ToDateTime(lastCreateTime).AddMinutes(plan.Execution_time), + (byte)ExecutionTypeEnum.Hour => Convert.ToDateTime(lastCreateTime).AddHours(plan.Execution_time), + (byte)ExecutionTypeEnum.Day => Convert.ToDateTime(lastCreateTime).AddDays(plan.Execution_time), + (byte)ExecutionTypeEnum.Week => Convert.ToDateTime(lastCreateTime).AddDays(plan.Execution_time * 7), + (byte)ExecutionTypeEnum.Month => Convert.ToDateTime(lastCreateTime).AddMonths(plan.Execution_time), + _ => default(DateTime) + }; + + if (nextCreateTime != default(DateTime) && nextCreateTime < DateTimeNow) + { + Dictionary servicePlanDic = new Dictionary() + { + { "@last_create_time", nextCreateTime}, + { "@updated_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}, + }; + + await backgroundServiceRepository.UpdateOneByCustomTable(servicePlanDic, "background_service_plan", "id=" + plan.Id + ""); + + #region 建立任務 + try + { + BackgroundServiceTask backgroundServiceTask = new BackgroundServiceTask() + { + + }; + } + catch(Exception exception) + { + + } + #endregion 建立任務 + } + + } + catch (Exception exception) + { + //logger.LogError("【ExecutionBackgroundServicePlanJob】【計畫編號:{0},計畫名稱:{1}】 - 產生下次任務時間失敗", plan.Id, plan.Plane_name); + //logger.LogError("【ExecutionBackgroundServicePlanJob】【計畫編號:{0},計畫名稱:{1}】 - 產生下次任務時間失敗[Exception]- {2}", plan.Id, plan.Plane_name, exception.Message); ; + } + #endregion 紀錄最後該生成任務的時間 + + } + + + + logger.LogInformation("【ExecutionBackgroundServicePlanJob】【任務完成】"); + } + catch (Exception exception) + { + logger.LogError("【ExecutionBackgroundServicePlanJob】【任務失敗】"); + logger.LogError("【ExecutionBackgroundServicePlanJob】【任務失敗】[Exception]:{0}", exception.Message); ; + } + + } + } +} diff --git a/Backend/Quartz/Jobs/MessageNotificationJob.cs b/Backend/Quartz/Jobs/MessageNotificationJob.cs new file mode 100644 index 0000000..50e6100 --- /dev/null +++ b/Backend/Quartz/Jobs/MessageNotificationJob.cs @@ -0,0 +1,163 @@ +using Backend.Models; +using BackendWorkerService.Services.Interface; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Quartz; +using Repository.BackendRepository.Interface; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace Backend.Quartz.Jobs +{ + [DisallowConcurrentExecution] + class MessageNotificationJob : IJob + { + private readonly ILogger logger; + private readonly IBackgroundServiceRepository backgroundServiceRepository; + + private readonly ISendEmailService sendEmailService; + private readonly ISendSMSService sendSMSService; + private readonly ISendLineNotifyService sendLineNotifyService; + + + public MessageNotificationJob( + ILogger logger, + IBackgroundServiceRepository backgroundServiceRepository, + ISendEmailService sendEmailService, + ISendSMSService sendSMSService, + ISendLineNotifyService sendLineNotifyService) + { + this.logger = logger; + this.backgroundServiceRepository = backgroundServiceRepository; + this.sendEmailService = sendEmailService; + this.sendSMSService = sendSMSService; + this.sendLineNotifyService = sendLineNotifyService; + } + + public async Task Execute(IJobExecutionContext context) + { + Dictionary insertLog = new Dictionary() + { + { "@task_id", 0 }, + { "@log_level", "" }, + { "@log_content", "" } + }; + + try + { + logger.LogInformation("【MessageNotificationJob】【任務開始】"); + insertLog["@log_level"] = $@"INFO"; + insertLog["@log_content"] = $@"任務開始"; + await backgroundServiceRepository.AddOneByCustomTable(insertLog, "background_service_message_notification_task_log"); + + //找出所有要通知的清單 + string sWhere = @"is_complete = 0"; + var messageNotificationTasks = await backgroundServiceRepository.GetAllAsync("background_service_message_notification_task", sWhere); + + if(messageNotificationTasks.Count == 0) + { + logger.LogInformation("【MessageNotificationJob】【查無任務列表】"); + + insertLog["@log_level"] = $@"INFO"; + insertLog["@log_content"] = $@"查無任務列表"; + await backgroundServiceRepository.AddOneByCustomTable(insertLog, "background_service_message_notification_task_log"); + } + else + { + List> updateObjs = new List>(); + foreach (var task in messageNotificationTasks) + { + var DateTimeNow = DateTime.Now; + Dictionary updateObj = new Dictionary() + { + { "Id", task.Id }, + { "@updated_at", DateTimeNow } + }; + + try + { + switch (task.Task_type) + { + case (byte)MessageNotificationTaskType.email: + List recipientEmails = new List() + { + task.Recipient_email + }; + + if (sendEmailService.Send(task.Id, recipientEmails, task.Email_subject, task.Message_content)) + { + updateObj.Add("@is_complete", 1); + updateObj.Add("@complete_at", DateTimeNow); + } + else + { + updateObj.Add("@repeat_times", task.Repeat_times + 1); + } + + updateObjs.Add(updateObj); + break; + case (byte)MessageNotificationTaskType.sms: + if (sendSMSService.Send(task.Id, task.Recipient_phone, task.Message_content)) + { + updateObj.Add("@is_complete", 1); + updateObj.Add("@complete_at", DateTimeNow); + } + else + { + updateObj.Add("@repeat_times", task.Repeat_times + 1); + } + + updateObjs.Add(updateObj); + break; + case (byte)MessageNotificationTaskType.line_notify: + if (sendLineNotifyService.Send(task.Id, task.Line_token, task.Message_content)) + { + updateObj.Add("@is_complete", 1); + updateObj.Add("@complete_at", DateTimeNow); + } + else + { + updateObj.Add("@repeat_times", task.Repeat_times + 1); + } + + updateObjs.Add(updateObj); + break; + } + } + catch(Exception exception) + { + logger.LogError("【MessageNotificationJob】【通知失敗】[任務id]:{0}", task.Id); + logger.LogError("【MessageNotificationJob】【通知失敗】[Exception]:{0}", exception.ToString()); + + insertLog["@log_level"] = $@"ERR"; + insertLog["@log_content"] = $@"通知失敗 - [失敗原因(Exception)]:{exception.ToString()}"; + await backgroundServiceRepository.AddOneByCustomTable(insertLog, "background_service_message_notification_task_log"); + } + + } + + await backgroundServiceRepository.UpdateListByCustomTable(updateObjs, "background_service_message_notification_task", "id = @Id"); + logger.LogInformation("【MessageNotificationJob】【任務完成】"); + + insertLog["@task_id"] = 0; + insertLog["@log_level"] = $@"INFO"; + insertLog["@log_content"] = $@"任務完成"; + await backgroundServiceRepository.AddOneByCustomTable(insertLog, "background_service_message_notification_task_log"); + } + } + catch (Exception exception) + { + logger.LogError("【MessageNotificationJob】【任務失敗】"); + logger.LogError("【MessageNotificationJob】【任務失敗】[Exception]:{0}", exception.ToString()); + + insertLog["@task_id"] = 0; + insertLog["@log_level"] = $@"ERR"; + insertLog["@log_content"] = $@"任務失敗"; + await backgroundServiceRepository.AddOneByCustomTable(insertLog, "background_service_message_notification_task_log"); + } + + } + } +} diff --git a/Backend/Quartz/Jobs/ParkingJob.cs b/Backend/Quartz/Jobs/ParkingJob.cs new file mode 100644 index 0000000..2486bf4 --- /dev/null +++ b/Backend/Quartz/Jobs/ParkingJob.cs @@ -0,0 +1,324 @@ +using Backend.Models; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Quartz; +using Repository.BackendRepository.Interface; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using System.Xml; + +namespace Backend.Quartz.Jobs +{ + /// + /// 停車場管理 + /// + [DisallowConcurrentExecution] + class ParkingJob : IJob + { + private readonly ILogger logger; + private readonly IBackgroundServiceRepository backgroundServiceRepository; + private readonly IBackendRepository backendRepository; + private readonly ILogger loggers; + + public ParkingJob( + ILogger logger, + IBackgroundServiceRepository backgroundServiceRepository, ILogger loggers, IBackendRepository backendRepository) + { + this.logger = logger; + this.backgroundServiceRepository = backgroundServiceRepository; + this.backendRepository = backendRepository; + this.loggers = loggers; + } + + public async Task Execute(IJobExecutionContext context) + { + Task_Detail task_Detail = new Task_Detail(loggers, backendRepository); + try + { + if(await task_Detail.GetNeedWorkTask("ParkingJob", "All")) + { + await task_Detail.InsertWorkTime("ParkingJob", "All", "任務開始"); + EDFunction ed = new EDFunction(); + var parkingConfig = new ParkingConfig(); + + var sqlParking = $@"SELECT system_value as Value, system_key as Name FROM variable WHERE deleted = 0 AND system_type = 'parkingConfig'"; + + var variable = await backgroundServiceRepository.GetAllAsync(sqlParking); + parkingConfig.Host = variable.Where(x => x.Name == "Host").Select(x => x.Value).FirstOrDefault(); + parkingConfig.Prefix = variable.Where(x => x.Name == "Prefix").Select(x => x.Value).FirstOrDefault(); + parkingConfig.ApiBase = variable.Where(x => x.Name == "ApiBase").Select(x => x.Value).FirstOrDefault(); + parkingConfig.UserName = ed.AESDecrypt(variable.Where(x => x.Name == "UserName").Select(x => x.Value).FirstOrDefault()); + parkingConfig.Password = ed.AESDecrypt(variable.Where(x => x.Name == "Password").Select(x => x.Value).FirstOrDefault()); + + String encoded = System.Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(parkingConfig.UserName + ":" + parkingConfig.Password)); + + #region 取得停車場資訊 + if (await task_Detail.GetNeedWorkTask("ParkingJob", "Parking")) + { + try + { + await task_Detail.InsertWorkTime("ParkingJob", "Parking", "開始執行停車場剩餘車位Job"); + HttpWebRequest spaceRequest = (HttpWebRequest)WebRequest.Create($"{parkingConfig.Host}{parkingConfig.Prefix}/api/space/details"); + spaceRequest.Method = "GET"; + //request.Headers.Add("Authorization", "Basic " + encoded); + spaceRequest.PreAuthenticate = true; + + //Stopwatch stopWatch = new Stopwatch(); + //stopWatch.Start(); + + HttpWebResponse spaceResponse = (HttpWebResponse)spaceRequest.GetResponse(); + var spaceResponseContent = new StreamReader(spaceResponse.GetResponseStream()).ReadToEnd(); + //logger.LogInformation("【ParkingJob】【取得成功停車場車位資訊】"); + //stopWatch.Stop(); + //logger.LogInformation("【ParkingJob】【取得停車場車位資訊】【效能檢驗】[取得資料花費時間]{0} 毫秒", stopWatch.ElapsedMilliseconds); + + var spaceResponseResult = JsonConvert.DeserializeObject(spaceResponseContent); + + //取得停車場車位對應表 + var sqlSapceMapping = $@"SELECT * FROM variable WHERE deleted = 0 AND system_type = 'parkingSapceMapping'"; + var parkingSapceMapping = await backgroundServiceRepository.GetAllAsync(sqlSapceMapping); + + if (spaceResponseResult != null && spaceResponseResult.Code == "20000") + { + foreach (var area in spaceResponseResult.Payload.Areas) + { + //找出對定的設備代碼 + var selectedMapping = parkingSapceMapping.Where(x => x.System_key == area.Name).FirstOrDefault(); + if (selectedMapping != null) + { + var tagName = selectedMapping.system_value; + var apiFormat = @"{0}obix/config/Arena/{1}/{2}/{3}/{4}/{5}/CV/set"; + + var tagNameSplit = tagName.Split("_"); + + var parames = new List(); + parames.Add(parkingConfig.ApiBase); + for (var i = 0; i < tagNameSplit.Length; i++) + { + if (i != tagNameSplit.Length - 1) + { + parames.Add(tagNameSplit[i]); + } + else + { + parames.Add(tagName); + } + } + + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(string.Format(apiFormat, parames.ToArray())); + request.Method = "POST"; + request.Headers.Add("Authorization", "Basic " + encoded); + request.PreAuthenticate = true; + + var real = $@""; + byte[] realByteArray = Encoding.UTF8.GetBytes(real); + using (Stream reqStream = request.GetRequestStream()) + { + reqStream.Write(realByteArray, 0, realByteArray.Length); + } + + HttpWebResponse response = (HttpWebResponse)request.GetResponse(); + var responseContent = new StreamReader(response.GetResponseStream()).ReadToEnd(); + + XmlDocument xmlDocument = new XmlDocument(); + xmlDocument.LoadXml(responseContent); + string json = JsonConvert.SerializeXmlNode(xmlDocument); + JObject jsonResult = (JObject)JsonConvert.DeserializeObject(json); + + if (jsonResult.ContainsKey("err")) //抓取錯誤 + { + logger.LogError("【ParkingJob】【停車場剩餘車位資訊】"); + logger.LogError("【ParkingJob】【停車場剩餘車位資訊】[錯誤內容]:{0}", json); + } + else + { + if (jsonResult.ContainsKey("real")) //表示可以讀取到內容 + { + List> ontimeRawDatas = new List>(); + + var realList = jsonResult["real"]; + var display = realList["@display"]; + if (display != null) + { + var tempStrSplit = display.ToString().Split(" "); + if (tempStrSplit[0] != area.Remain.ToString()) + { + logger.LogError("【ParkingJob】【停車場剩餘車位資訊】[修改失敗]:{0}", display.ToString()); + } + } + } + } + } + else + { + logger.LogWarning("【ParkingJob】【停車場剩餘車位資訊】[查無該名稱對應表]:{0}", area.Name); + } + } + + } + else + { + logger.LogWarning("【ParkingJob】【停車場剩餘車位資訊】 - [查無資料]"); + } + await task_Detail.InsertWorkTime_End("ParkingJob", "Parking", "執行成功停車場剩餘車位Job"); + //logger.LogInformation("【ParkingJob】【執行成功停車場剩餘車位Job】"); + } + catch (Exception exception) + { + await task_Detail.WorkFail("ParkingJob", "Parking", exception.ToString()); + logger.LogInformation("【ParkingJob】【執行失敗停車場剩餘車位Job】"); + logger.LogInformation("【ParkingJob】【執行失敗停車場剩餘車位Job】[Exception]:{0}", exception.ToString()); + } + } + #endregion + + #region 取得設備資訊 + if (await task_Detail.GetNeedWorkTask("ParkingJob", "Device")) + { + try + { + await task_Detail.InsertWorkTime("ParkingJob", "Device", "開始執行設備資訊Job"); + //logger.LogInformation("【ParkingJob】【開始執行設備資訊Job】"); + + HttpWebRequest equipmentRequest = (HttpWebRequest)WebRequest.Create($"{parkingConfig.Host}{parkingConfig.Prefix}/api/equipment/state"); + equipmentRequest.Method = "GET"; + //request.Headers.Add("Authorization", "Basic " + encoded); + equipmentRequest.PreAuthenticate = true; + + HttpWebResponse equipmentResponse = (HttpWebResponse)equipmentRequest.GetResponse(); + var equipmentResponseContent = new StreamReader(equipmentResponse.GetResponseStream()).ReadToEnd(); + + var equipmentResponseResult = JsonConvert.DeserializeObject(equipmentResponseContent); + + //取得設備對應表 + var sqlEquipmentMapping = $@"SELECT * FROM variable WHERE deleted = 0 AND system_type = 'parkingEquipmentMapping'"; + var parkingEquipmentMapping = await backgroundServiceRepository.GetAllAsync(sqlEquipmentMapping); + + //List parkingEquipmentMapping = new List(); + //VariableInfo variableInfo_1 = new VariableInfo(); + //variableInfo_1.System_key = "APS-000"; + //variableInfo_1.system_value = "D3_P_B4F_CAPS_B413"; + + //parkingEquipmentMapping.Add(variableInfo_1); + + //VariableInfo variableInfo_2 = new VariableInfo(); + //variableInfo_2.System_key = "APS-001"; + //variableInfo_2.system_value = "D3_P_B4F_CAPS_B414"; + //parkingEquipmentMapping.Add(variableInfo_2); + + if (equipmentResponseResult != null && equipmentResponseResult.Code == "20000") + { + foreach (var equipment in equipmentResponseResult.Payload) + { + //找出對定的設備代碼 + var selectedMapping = parkingEquipmentMapping.Where(x => x.System_key == equipment.Name).FirstOrDefault(); + if (selectedMapping != null) + { + + var tagName = selectedMapping.system_value; + var apiFormat = @"{0}obix/config/Arena/{1}/{2}/{3}/{4}/{5}/ST/set"; + + var tagNameSplit = tagName.Split("_"); + + var parames = new List(); + parames.Add(parkingConfig.ApiBase); + for (var i = 0; i < tagNameSplit.Length; i++) + { + if (i != tagNameSplit.Length - 1) + { + parames.Add(tagNameSplit[i]); + } + else + { + parames.Add(tagName); + } + } + + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(string.Format(apiFormat, parames.ToArray())); + request.Method = "POST"; + request.Headers.Add("Authorization", "Basic " + encoded); + request.PreAuthenticate = true; + + var real = $@""; + byte[] realByteArray = Encoding.UTF8.GetBytes(real); + using (Stream reqStream = request.GetRequestStream()) + { + reqStream.Write(realByteArray, 0, realByteArray.Length); + } + + HttpWebResponse response = (HttpWebResponse)request.GetResponse(); + var responseContent = new StreamReader(response.GetResponseStream()).ReadToEnd(); + + XmlDocument xmlDocument = new XmlDocument(); + xmlDocument.LoadXml(responseContent); + string json = JsonConvert.SerializeXmlNode(xmlDocument); + JObject jsonResult = (JObject)JsonConvert.DeserializeObject(json); + + if (jsonResult.ContainsKey("err")) //抓取錯誤 + { + logger.LogError("【ParkingJob】【設備資訊】"); + logger.LogError("【ParkingJob】【設備資訊】[錯誤內容]:{0}", json); + } + else + { + if (jsonResult.ContainsKey("bool")) //表示可以讀取到內容 + { + List> ontimeRawDatas = new List>(); + + var realList = jsonResult["bool"]; + var val = realList["@val"]; + if (val != null) + { + var tempStrSplit = val.ToString(); + if (tempStrSplit != equipment.Alive.ToString().ToLower()) + { + logger.LogError("【ParkingJob】【設備資訊】[修改失敗]:{0}", val.ToString()); + } + } + } + } + } + else + { + logger.LogWarning("【ParkingJob】【設備資訊】[查無該名稱對應表]:{0}", equipment.Name); + } + } + } + else + { + logger.LogWarning("【ParkingJob】【設備資訊】 - [查無資料]"); + } + await task_Detail.InsertWorkTime_End("ParkingJob", "Device", "執行成功設備資訊Job"); + //logger.LogInformation("【ParkingJob】【執行成功設備資訊Job】"); + } + catch (Exception exception) + { + await task_Detail.WorkFail("ParkingJob", "Device", exception.ToString()); + logger.LogInformation("【ParkingJob】【執行失敗設備資訊Job】"); + logger.LogInformation("【ParkingJob】【執行失敗設備資訊Job】[Exception]:{0}", exception.ToString()); + } + } + #endregion + + await task_Detail.InsertWorkTime_End("ParkingJob", "All", "任務完成"); + } + + + } + catch (Exception exception) + { + await task_Detail.WorkFail("ParkingJob", "All", exception.ToString()); + logger.LogError("【ParkingJob】【任務失敗】"); + logger.LogError("【ParkingJob】【任務失敗】[Exception]:{0}", exception.ToString()); + } + + } + } +} diff --git a/Backend/Quartz/Jobs/RegularUpdateDBTableJob.cs b/Backend/Quartz/Jobs/RegularUpdateDBTableJob.cs new file mode 100644 index 0000000..dc9d577 --- /dev/null +++ b/Backend/Quartz/Jobs/RegularUpdateDBTableJob.cs @@ -0,0 +1,77 @@ +using Backend.Services.Implement; +using Microsoft.Extensions.Logging; +using Quartz; +using Repository.BackendRepository.Interface; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Backend.Quartz.Jobs +{ + /// + /// 定時將資料表加入派送任務 + /// + [DisallowConcurrentExecution] + class RegularUpdateDBTableJob: IJob + { + private readonly ILogger logger; + private readonly IBackgroundServiceRepository backgroundServiceRepository; + private readonly IBackendRepository backendRepository; + + public RegularUpdateDBTableJob( + ILogger logger, + IBackgroundServiceRepository backgroundServiceRepository, + IBackendRepository backendRepository) + { + this.logger = logger; + this.backgroundServiceRepository = backgroundServiceRepository; + this.backendRepository = backendRepository; + } + + public async Task Execute(IJobExecutionContext context) + { + try + { + logger.LogInformation("【RegularUpdateDBTableJob】【任務開始】"); + + //要派送的資料表 + List db_tables = new List() { "variable" }; + + foreach (var db_table in db_tables) + { + try + { + logger.LogInformation("【RegularUpdateDBTableJob】【新增資料表 [{0}] 至派送任務】", db_table); + //取得資料表所有資料 + var temp_datas = (await backgroundServiceRepository.GetAllAsync(db_table, "")); + + List> dicts = new List>(); + foreach (var temp_data in temp_datas) + { + var dict = new Dictionary((IDictionary)temp_data); + + dicts.Add(dict); + } + + await backendRepository.ManualInsertBackgroundServiceTask("", "", db_table, "purge_all_insert", dicts); + + logger.LogError("【RegularUpdateDBTableJob】【新增成功 資料表 [{0}] 至派送任務】", db_table); + } + catch(Exception exception) + { + logger.LogError("【RegularUpdateDBTableJob】【新增失敗 資料表 [{0}] 至派送任務】", db_table); + logger.LogError("【RegularUpdateDBTableJob】【新增失敗 資料表 [{0}] 至派送任務】[Exception]:{1}", db_table, exception.ToString()); + } + } + } + catch (Exception exception) + { + logger.LogError("【RegularUpdateDBTableJob】【任務失敗】"); + logger.LogError("【RegularUpdateDBTableJob】【任務失敗】[Exception]:{0}", exception.ToString()); + } + + } + } +} diff --git a/Backend/Quartz/Jobs/WeatherAPIJob.cs b/Backend/Quartz/Jobs/WeatherAPIJob.cs new file mode 100644 index 0000000..77adc5f --- /dev/null +++ b/Backend/Quartz/Jobs/WeatherAPIJob.cs @@ -0,0 +1,559 @@ +using Backend.Models; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Quartz; +using Repository.BackendRepository.Implement; +using Repository.BackendRepository.Interface; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Serialization; +using System.Linq; +using NCrontab; +using BackendWorkerService.Services.Implement; + +namespace Backend.Quartz.Jobs +{ + [DisallowConcurrentExecution] + class WeatherAPIJob : IJob + { + private readonly ILogger logger; + private readonly IBackgroundServiceRepository backgroundServiceRepository; + private readonly IBackendRepository backendRepository; + private readonly ILogger loggers; + public WeatherAPIJob(ILogger logger, + IBackgroundServiceRepository backgroundServiceRepository, IBackendRepository backendRepository, ILogger loggers) + { + this.logger = logger; + this.backgroundServiceRepository = backgroundServiceRepository; + this.backendRepository = backendRepository; + this.loggers = loggers; + } + public string Fetch_PostWithJSONFormat(string url, string paramter) + { + try + { + string username = "stanGG"; + string password = "St12345678"; + string encoded = Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(username + ":" + password)); + HttpWebRequest Postrequest = (HttpWebRequest)WebRequest.Create(url); + Postrequest.Method = "POST"; + Postrequest.Headers.Add("Authorization", "Basic " + encoded); + Postrequest.PreAuthenticate = true; + + using (var streamWriter = new StreamWriter(Postrequest.GetRequestStream())) + { + if (paramter != "NULL") + { + string json = ""; + streamWriter.Write(json); + } + else + { + string json = ""; + streamWriter.Write(json); + } + } + HttpWebResponse response = (HttpWebResponse)Postrequest.GetResponse(); + var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd(); + XmlDocument xmlDoc = new XmlDocument(); + xmlDoc.LoadXml(responseString); + string jsonText = JsonConvert.SerializeXmlNode(xmlDoc); + //JObject resultVal = (JObject)JsonConvert.DeserializeObject(jsonText); + + return jsonText; + + } + catch (Exception ex) + { + logger.LogError("【WeatherAPIJob】" + "Fetch_PostWithJSONFormat:" + ex.ToString()); + throw ex; + } + } + + + public async Task Execute(IJobExecutionContext context) + { + Task_Detail task_Detail = new Task_Detail(loggers, backendRepository); + try + { + var obixApiConfig = new ObixApiConfig(); + string encoded = string.Empty; + #region 取得obix 設定 + var sqlObix = $@"SELECT system_value as Value, system_key as Name FROM variable WHERE deleted = 0 AND system_type = 'obixConfig'"; + + var variableObix = await backgroundServiceRepository.GetAllAsync(sqlObix); + obixApiConfig.ApiBase = variableObix.Where(x => x.Name == "ApiBase").Select(x => x.Value).FirstOrDefault(); + obixApiConfig.UserName = variableObix.Where(x => x.Name == "UserName").Select(x => x.Value).FirstOrDefault(); + obixApiConfig.Password = variableObix.Where(x => x.Name == "Password").Select(x => x.Value).FirstOrDefault(); + + encoded = System.Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(obixApiConfig.UserName + ":" + obixApiConfig.Password)); + #endregion 取得obix 設定 + //取得氣象預報 + if (await task_Detail.GetNeedWorkTask("WeatherAPI", "api_weateher")) + { + try + { + await task_Detail.InsertWorkTime("WeatherAPI", "api_weateher"); + var client = new HttpClient(); + var DataNO = "F-D0047-061"; + var UVUri = "https://opendata.cwb.gov.tw/api/v1/rest/datastore/F-D0047-061?Authorization=CWB-EA24220B-DDCC-4188-84E5-AD37A0E03F80&format=JSON&locationName=%E4%BF%A1%E7%BE%A9%E5%8D%80&elementName=Wx,PoP12h,T,RH"; + HttpResponseMessage response = client.GetAsync(UVUri).Result; + String jsonUVs = response.Content.ReadAsStringAsync().Result.ToString(); + + var observation = JsonConvert.DeserializeObject(jsonUVs); + logger.LogInformation("【WeatherAPIJob】【取得成功氣象預報】"); + + if (observation.Success != "true") + { + logger.LogInformation("【WeatherAPIJob】【取得氣象預報資料不正確】"); + } + else + { + logger.LogInformation("【WeatherAPIJob】【開始存入氣象預報到資料庫】"); + List> WeatherAPIdbS = new List>(); + var Type_ALL = observation.Records.Locations[0].Location[0].WeatherElement; + + foreach (var a in Type_ALL) + { + + foreach (var b in a.Time) + { + if (a.ElementName == "PoP12h" || a.ElementName == "Wx") + { + if (Convert.ToDateTime(b.StartTime) > DateTime.Now.AddDays(1)) + { + break; + } + } + else + { + if (Convert.ToDateTime(b.DataTime) > DateTime.Now.AddDays(1)) + { + break; + } + } + Dictionary WeatherAPIdb = new Dictionary() + { + { "@weather_type", a.ElementName}, + { "@data_no", DataNO}, + { "@get_value", b.ElementValue[0].Value}, + { "@measures", b.ElementValue[0].Measures}, + { "@created_by", "system"}, + { "@created_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}, + }; + if (a.ElementName == "PoP12h" || a.ElementName == "Wx") + { + WeatherAPIdb.Add("@start_time", b.StartTime); + WeatherAPIdb.Add("@end_time", b.EndTime); + } + else + { + WeatherAPIdb.Add("@start_time", b.DataTime); + WeatherAPIdb.Add("@end_time", null); + } + WeatherAPIdbS.Add(WeatherAPIdb); + + if (a.ElementName == "Wx") + { + Dictionary TWeatherAPIdb = new Dictionary() + { + { "@weather_type", "WxV"}, + { "@data_no", DataNO}, + { "@get_value", b.ElementValue[1].Value}, + { "@measures", b.ElementValue[1].Measures}, + { "@start_time", b.StartTime}, + { "@end_time", b.EndTime}, + { "@created_by", "system"}, + { "@created_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}, + }; + WeatherAPIdbS.Add(TWeatherAPIdb); + } + + } + } + await backendRepository.AddMutiByCustomTable(WeatherAPIdbS, "api_weateher"); + } + + await task_Detail.InsertWorkTime_End("WeatherAPI", "api_weateher"); + } + catch (Exception ex) + { + await task_Detail.WorkFail("WeatherAPI", "api_weateher", ex.Message.ToString()); + } + } + + if (await task_Detail.GetNeedWorkTask("WeatherAPI", "api_rain")) + { + try + { + await task_Detail.InsertWorkTime("WeatherAPI", "api_rain"); + WebClient mywebClient = new WebClient(); + mywebClient.DownloadFile("https://opendata.cwb.gov.tw/fileapi/v1/opendataapi/W-C0033-003?Authorization=CWB-EA24220B-DDCC-4188-84E5-AD37A0E03F80&downloadType=WEB&format=CAP", @"root/PowerfulRain.xml"); + XmlDocument doc = new XmlDocument(); + doc.Load("root/PowerfulRain.xml"); + var json = JsonConvert.SerializeXmlNode(doc); + var haveinfo = json.Split("info"); + if (haveinfo.Length > 2) + { + var observation = RainApi.Welcome.FromJson(json); + var area = observation.Alert.Info.Area.Where(a => a.Geocode.Value == "63").Select(a => a.AreaDesc).FirstOrDefault(); + var sql = $"select id from api_rain where msgType = '{observation.Alert.MsgType}' and onset = '{observation.Alert.Info.Onset.ToString("yyyy-MM-dd HH:mm:ss")}' and expires = '{observation.Alert.Info.Expires.ToString("yyyy-MM-dd HH:mm:ss")}'"; + var NeedCallApi = await backendRepository.GetOneAsync(sql); + + Dictionary RainAPIdb = new Dictionary() + { + { "@msgType", observation.Alert.MsgType}, + { "@headline", observation.Alert.Info.Headline}, + { "@areaDesc", area}, + { "@onset", observation.Alert.Info.Onset}, + { "@expires", observation.Alert.Info.Expires}, + { "@created_by", "system"}, + { "@created_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}, + }; + var id = await backendRepository.AddOneByCustomTableReturnId(RainAPIdb, "api_rain"); + if (NeedCallApi != 0) + { + var val = RainValue(observation.Alert.MsgType, observation.Alert.Info.Headline); + if (val < 5) + { + var ReStr = Fetch_PostWithJSONFormat($@"{obixApiConfig.ApiBase}obix/config/Arena/D2/CWB/L110/CAP/D2_CWB_L110_CAP_MET1/SeverityLEVL_RAIN/set", val.ToString()); + UpdatedNiagara("api_rain", ReStr, id); + } + } + + FolderFunction folderFunction = new FolderFunction(); + folderFunction.DeleteFile("root/PowerfulRain.xml"); + await task_Detail.InsertWorkTime_End("WeatherAPI", "api_rain"); + } + else + { + var observation = RainApi.Welcome.FromJson(json); + var area = observation.Alert.Info.Area.Where(a => a.Geocode.Value == "63").Select(a => a.AreaDesc).FirstOrDefault(); + var sql = $"select id from api_rain where msgType = '{observation.Alert.MsgType}' and onset = '{observation.Alert.Info.Onset.ToString("yyyy-MM-dd HH:mm:ss")}' and expires = '{observation.Alert.Info.Expires.ToString("yyyy-MM-dd HH:mm:ss")}'"; + var NeedCallApi = await backendRepository.GetOneAsync(sql); + + Dictionary RainAPIdb = new Dictionary() + { + { "@msgType", observation.Alert.MsgType}, + { "@headline", observation.Alert.Info.Headline}, + { "@areaDesc", area}, + { "@onset", observation.Alert.Info.Onset.ToString("yyyy-MM-dd HH:mm:ss")}, + { "@expires", observation.Alert.Info.Expires.ToString("yyyy-MM-dd HH:mm:ss")}, + { "@created_by", "system"}, + { "@created_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}, + }; + var id = await backendRepository.AddOneByCustomTableReturnId(RainAPIdb, "api_rain"); + if (NeedCallApi != 0) + { + var val = RainValue(observation.Alert.MsgType, observation.Alert.Info.Headline); + if (val < 5) + { + var ReStr = Fetch_PostWithJSONFormat($@"{obixApiConfig.ApiBase}obix/config/Arena/D2/CWB/L110/CAP/D2_CWB_L110_CAP_MET1/SeverityLEVL_RAIN/set", val.ToString()); + UpdatedNiagara("api_rain", ReStr, id); + } + } + + FolderFunction folderFunction = new FolderFunction(); + folderFunction.DeleteFile("root/PowerfulRain.xml"); + await task_Detail.InsertWorkTime_End("WeatherAPI", "api_rain"); + + } + } + catch (Exception ex) + { + await task_Detail.WorkFail("WeatherAPI", "api_rain", ex.Message.ToString()); + } + } + + if (await task_Detail.GetNeedWorkTask("WeatherAPI", "api_typhoon")) + { + try + { + await task_Detail.InsertWorkTime("WeatherAPI", "api_typhoon"); + WebClient mywebClient = new WebClient(); + mywebClient.DownloadFile("https://opendata.cwb.gov.tw/fileapi/v1/opendataapi/W-C0034-001?Authorization=CWB-EA24220B-DDCC-4188-84E5-AD37A0E03F80&downloadType=WEB&format=CAP", @"root/Typhoon.xml"); + XmlDocument doc = new XmlDocument(); + doc.Load("root/Typhoon.xml"); + var json = JsonConvert.SerializeXmlNode(doc); + var haveinfo = json.Split("info"); + + if (haveinfo.Length > 2) + { + var observation = TyphoonApi.Welcome.FromJson(json); + var area = observation.Alert.Info.Area.Where(a => a.Geocode.Value == "63").Select(a => a.AreaDesc).FirstOrDefault(); + var sql = $"select id from api_typhoon where msgType = '{observation.Alert.MsgType}' and onset = '{observation.Alert.Info.Onset.ToString("yyyy-MM-dd HH:mm:ss")}' and expires = '{observation.Alert.Info.Expires.ToString("yyyy-MM-dd HH:mm:ss")}'"; + var NeedCallApi = await backendRepository.GetOneAsync(sql); + Dictionary EarthquakeAPIdb = new Dictionary() + { + { "@msgType", observation.Alert.MsgType}, + { "@headline", observation.Alert.Info.Headline}, + { "@areaDesc", area}, + { "@urgency",observation.Alert.Info.Urgency}, + { "@severity",observation.Alert.Info.Severity}, + { "@onset", observation.Alert.Info.Onset}, + { "@expires", observation.Alert.Info.Expires}, + { "@created_by", "system"}, + { "@created_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}, + }; + var id = await backendRepository.AddOneByCustomTableReturnId(EarthquakeAPIdb, "api_typhoon"); + if (NeedCallApi != 0) + { + if (observation.Alert.Info.Urgency != null && observation.Alert.Info.Urgency != "Expected") + { + + var ReStr = Fetch_PostWithJSONFormat($@"{obixApiConfig.ApiBase}obix/config/Arena/D2/CWB/L110/CAP/D2_CWB_L110_CAP_MET2/SeverityLEVL_Typhoon/set", observation.Alert.Info.Urgency); + UpdatedNiagara("api_typhoon", ReStr, id); + } + } + FolderFunction folderFunction = new FolderFunction(); + folderFunction.DeleteFile("root/Typhoon.xml"); + await task_Detail.InsertWorkTime_End("WeatherAPI", "api_typhoon"); + } + else + { + var observation = TyphoonApi.Welcome.FromJson(json); + //var area = observation.Alert.Info.Area.Where(a => a.Geocode.Value == "63").Select(a => a.AreaDesc).FirstOrDefault(); + var sql = $"select id from api_typhoon where msgType = '{observation.Alert.MsgType}' and onset = '{observation.Alert.Info.Onset.ToString("yyyy-MM-dd HH:mm:ss")}' and expires = '{observation.Alert.Info.Expires.ToString("yyyy-MM-dd HH:mm:ss")}'"; + var NeedCallApi = await backendRepository.GetOneAsync(sql); + Dictionary EarthquakeAPIdb = new Dictionary() + { + { "@msgType", observation.Alert.MsgType}, + { "@headline", observation.Alert.Info.Headline}, + //{ "@areaDesc", area}, + { "@urgency",observation.Alert.Info.Urgency}, + { "@severity",observation.Alert.Info.Severity}, + { "@onset", observation.Alert.Info.Onset.ToString("yyyy-MM-dd HH:mm:ss")}, + { "@expires", observation.Alert.Info.Expires.ToString("yyyy-MM-dd HH:mm:ss")}, + { "@created_by", "system"}, + { "@created_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}, + }; + var id = await backendRepository.AddOneByCustomTableReturnId(EarthquakeAPIdb, "api_typhoon"); + if (NeedCallApi != 0) + { + if (observation.Alert.Info.Urgency != null && observation.Alert.Info.Urgency != "Expected") + { + + var ReStr = Fetch_PostWithJSONFormat($@"{obixApiConfig.ApiBase}obix/config/Arena/D2/CWB/L110/CAP/D2_CWB_L110_CAP_MET2/SeverityLEVL_Typhoon/set", observation.Alert.Info.Urgency); + UpdatedNiagara("api_typhoon", ReStr, id); + } + } + FolderFunction folderFunction = new FolderFunction(); + folderFunction.DeleteFile("root/Typhoon.xml"); + await task_Detail.InsertWorkTime_End("WeatherAPI", "api_typhoon"); + + } + + } + catch (Exception ex) + { + await task_Detail.WorkFail("WeatherAPI", "api_typhoon", ex.Message.ToString()); + } + } + if (await task_Detail.GetNeedWorkTask("WeatherAPI", "api_earthquake")) + { + try + { + await task_Detail.InsertWorkTime("WeatherAPI", "api_earthquake"); + var client = new HttpClient(); + var UVUri = "https://opendata.cwb.gov.tw/api/v1/rest/datastore/E-A0015-001?Authorization=CWB-EA24220B-DDCC-4188-84E5-AD37A0E03F80&format=JSON&areaName=%E8%87%BA%E5%8C%97%E5%B8%82"; + HttpResponseMessage response = client.GetAsync(UVUri).Result; + String jsonUVs = response.Content.ReadAsStringAsync().Result.ToString(); + var observation = QuickType.Welcome.FromJson(jsonUVs); + if (!observation.Success) + { + logger.LogInformation("【WeatherAPIJob】【取得地震觀測資料不正確】"); + } + else + { + logger.LogInformation("【WeatherAPIJob】【開始存入地震觀測到資料庫】"); + List> EarthquakeAPIdbS = new List>(); + var nowNo = await backendRepository.GetOneAsync("select if( Max(earthquakeNo) is null ,0,Max(earthquakeNo)) earthquakeNo from api_earthquake where newEarthquake = 1"); + foreach (var a in observation.Records.Earthquake) + { + if (a.EarthquakeNo <= nowNo) + { + Dictionary EarthquakeAPIdb = new Dictionary() + { + { "@earthquakeNo", a.EarthquakeNo}, + { "@newEarthquake", 0}, + { "@originTime", DateTime.Parse(a.EarthquakeInfo.OriginTime.ToString(), System.Globalization.CultureInfo.CurrentCulture)}, + { "@magnitudeValue", a.EarthquakeInfo.EarthquakeMagnitude.MagnitudeValue}, + { "@areaName", null}, + { "@areaIntensity", null}, + { "@created_by", "system"}, + { "@created_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}, + }; + EarthquakeAPIdbS.Add(EarthquakeAPIdb); + break; + } + else + { + if (a.Intensity.ShakingArea.Length > 0) + { + Dictionary EarthquakeAPIdb = new Dictionary() + { + { "@earthquakeNo", a.EarthquakeNo}, + { "@newEarthquake", 1}, + { "@originTime", DateTime.Parse(a.EarthquakeInfo.OriginTime.ToString(), System.Globalization.CultureInfo.CurrentCulture)}, + { "@magnitudeValue", a.EarthquakeInfo.EarthquakeMagnitude.MagnitudeValue}, + { "@areaName", a.Intensity.ShakingArea[0].CountyName}, + { "@areaIntensity", a.Intensity.ShakingArea[0].AreaIntensity}, + { "@created_by", "system"}, + { "@created_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}, + }; + + + var Nag = Fetch_PostWithJSONFormat($@"{obixApiConfig.ApiBase}obix/config/Arena/D2/CWB/L110/CAP/D2_CWB_L110_CAP_GEO/SeverityLEVL_LMI/set", a.Intensity.ShakingArea[0].AreaIntensity.ToString()); + + if (Nag.Contains("err")) + { + EarthquakeAPIdb.Add("@niagara", Nag); + } + else + { + EarthquakeAPIdb.Add("@niagara", "success"); + } + + + EarthquakeAPIdbS.Add(EarthquakeAPIdb); + } + else + { + Dictionary EarthquakeAPIdb = new Dictionary() + { + { "@earthquakeNo", a.EarthquakeNo}, + { "@newEarthquake", 1}, + { "@originTime", DateTime.Parse(a.EarthquakeInfo.OriginTime.ToString(), System.Globalization.CultureInfo.CurrentCulture)}, + { "@magnitudeValue", a.EarthquakeInfo.EarthquakeMagnitude.MagnitudeValue}, + { "@areaName", null}, + { "@areaIntensity", null}, + { "@created_by", "system"}, + { "@created_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}, + }; + + var Nag = Fetch_PostWithJSONFormat($@"{obixApiConfig.ApiBase}obix/config/Arena/D2/CWB/L110/CAP/D2_CWB_L110_CAP_GEO/SeverityLEVL_LMI/set", "NULL"); + if (Nag.Contains("err")) + { + EarthquakeAPIdb.Add("@niagara", Nag); + } + else + { + EarthquakeAPIdb.Add("@niagara", "success"); + } + EarthquakeAPIdbS.Add(EarthquakeAPIdb); + } + } + } + await backendRepository.AddMutiByCustomTable(EarthquakeAPIdbS, "api_earthquake"); + } + await task_Detail.InsertWorkTime_End("WeatherAPI", "api_earthquake"); + } + catch (Exception ex) + { + await task_Detail.WorkFail("WeatherAPI", "api_earthquake", ex.Message.ToString()); + } + } + if (await task_Detail.GetNeedWorkTask("WeatherAPI", "set_weather")) + { + try + { + await task_Detail.InsertWorkTime("WeatherAPI", "set_weather"); + var sql = @$"SELECT + id, + weather_type, + get_value + FROM api_weateher + where id in (select MAX(id) from api_weateher where start_time < NOW() group by weather_type) + order by start_time desc"; + var types = await backendRepository.GetAllAsync(sql); + var T = types.Where(a => a.weather_type == "T").FirstOrDefault(); + var RbT = Fetch_PostWithJSONFormat($@"{obixApiConfig.ApiBase}obix/config/Arena/D2/CWB/L110/OPD/D2_CWB_L110_OPD_element/T/set", T.get_value); + UpdatedNiagara("api_weateher", RbT, T.id); + + var RH = types.Where(a => a.weather_type == "RH").FirstOrDefault(); + var RHT = Fetch_PostWithJSONFormat($@"{obixApiConfig.ApiBase}obix/config/Arena/D2/CWB/L110/OPD/D2_CWB_L110_OPD_element/RH/set", RH.get_value); + UpdatedNiagara("api_weateher", RHT, RH.id); + + var PoP12h = types.Where(a => a.weather_type == "PoP12h").FirstOrDefault(); + var PoP12hT = Fetch_PostWithJSONFormat($@"{obixApiConfig.ApiBase}obix/config/Arena/D2/CWB/L110/OPD/D2_CWB_L110_OPD_element/PoP6h/set", PoP12h.get_value); + UpdatedNiagara("api_weateher", PoP12hT, PoP12h.id); + + var Wx = types.Where(a => a.weather_type == "Wx").FirstOrDefault(); + var WxT = Fetch_PostWithJSONFormat($@"{obixApiConfig.ApiBase}obix/config/Arena/D2/CWB/L110/OPD/D2_CWB_L110_OPD_element/Wx/set", Wx.get_value); + UpdatedNiagara("api_weateher", WxT, Wx.id); + await task_Detail.InsertWorkTime_End("WeatherAPI", "set_weather"); + } + catch (Exception ex) + { + await task_Detail.WorkFail("WeatherAPI", "set_weather", ex.Message.ToString()); + } + } + } + catch (Exception exception) + { + logger.LogError("【WeatherAPIJob】【任務失敗】"); + logger.LogError("【WeatherAPIJob】【任務失敗】[Exception]:{0}", exception.ToString()); + } + } + + public int RainValue(string Ty, string Headline) + { + var rint = 5; + if (Ty == "") + { + return 0; + + } + else + { + if (Headline.Contains("大雨")) + { + rint = 1; + } + else if (Headline.Contains("豪雨")) + { + rint = 2; + } + else if (Headline.Contains("大豪雨")) + { + rint = 3; + } + else if (Headline.Contains("超大豪雨")) + { + rint = 4; + } + } + return rint; + } + + public async void UpdatedNiagara(string DBTableName, string ResponseStr, int CheckNumId) + { + try + { + var Su = "success"; + + if (ResponseStr.Contains("err")) + { + Su = ResponseStr; + } + Dictionary RainAPIdb = new Dictionary() + { + { "@niagara", Su} + }; + await backendRepository.UpdateOneByCustomTable(RainAPIdb, DBTableName, " id = " + CheckNumId); + } + catch (Exception ex) + { + logger.LogError("【WeatherAPIJob】" + "UpdatedNiagara:" + ex.ToString()); + throw ex; + } + } + } +} diff --git a/Backend/Quartz/QuartzHostedService.cs b/Backend/Quartz/QuartzHostedService.cs new file mode 100644 index 0000000..b111409 --- /dev/null +++ b/Backend/Quartz/QuartzHostedService.cs @@ -0,0 +1,69 @@ +using Microsoft.Extensions.Hosting; +using Quartz; +using Quartz.Spi; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Backend.Quartz +{ + public class QuartzHostedService : IHostedService + { + private readonly ISchedulerFactory _schedulerFactory; + private readonly IJobFactory _jobFactory; + private readonly IEnumerable _jobSchedules; + public QuartzHostedService(ISchedulerFactory schedulerFactory, IJobFactory jobFactory, IEnumerable jobSchedules) + { + _schedulerFactory = schedulerFactory ?? throw new ArgumentNullException(nameof(schedulerFactory)); + _jobFactory = jobFactory ?? throw new ArgumentNullException(nameof(jobFactory)); + _jobSchedules = jobSchedules ?? throw new ArgumentNullException(nameof(jobSchedules)); + } + public IScheduler Scheduler { get; set; } + public async Task StartAsync(CancellationToken cancellationToken) + { + Scheduler = await _schedulerFactory.GetScheduler(cancellationToken); + Scheduler.JobFactory = _jobFactory; + foreach (var jobSchedule in _jobSchedules) + { + var job = CreateJob(jobSchedule); + var trigger = CreateTrigger(jobSchedule); + await Scheduler.ScheduleJob(job, trigger, cancellationToken); + jobSchedule.JobStatu = JobStatus.Scheduling; + } + await Scheduler.Start(cancellationToken); + foreach (var jobSchedule in _jobSchedules) + { + jobSchedule.JobStatu = JobStatus.Running; + } + } + public async Task StopAsync(CancellationToken cancellationToken) + { + await Scheduler?.Shutdown(cancellationToken); + foreach (var jobSchedule in _jobSchedules) + { + + jobSchedule.JobStatu = JobStatus.Stopped; + } + } + private static IJobDetail CreateJob(JobSchedule schedule) + { + var jobType = schedule.JobType; + return JobBuilder + .Create(jobType) + .WithIdentity(jobType.FullName) + .WithDescription(jobType.Name) + .Build(); + } + private static ITrigger CreateTrigger(JobSchedule schedule) + { + return TriggerBuilder + .Create() + .WithIdentity($"{schedule.JobType.FullName}.trigger") + .WithCronSchedule(schedule.CronExpression) + .WithDescription(schedule.CronExpression) + .Build(); + } + } +} diff --git a/Backend/Quartz/SingletonJobFactory.cs b/Backend/Quartz/SingletonJobFactory.cs new file mode 100644 index 0000000..c925edd --- /dev/null +++ b/Backend/Quartz/SingletonJobFactory.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.DependencyInjection; +using Quartz; +using Quartz.Spi; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Backend.Quartz +{ + public class SingletonJobFactory : IJobFactory + { + private readonly IServiceProvider _serviceProvider; + public SingletonJobFactory(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + } + public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler) + { + return _serviceProvider.GetRequiredService(bundle.JobDetail.JobType) as IJob; + } + public void ReturnJob(IJob job) + { + + } + } +} diff --git a/Backend/Services/Implement/ProcEletricMeterService.cs b/Backend/Services/Implement/ProcEletricMeterService.cs new file mode 100644 index 0000000..8478c30 --- /dev/null +++ b/Backend/Services/Implement/ProcEletricMeterService.cs @@ -0,0 +1,935 @@ +using Backend.Models; +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Data.SqlClient; +using System.Text; +using Dapper; +using System.Linq; +using Newtonsoft.Json.Linq; +using System.Xml; +using System.Net; +using System.IO; +using Newtonsoft.Json; +using Repository.Helper; +using Repository.BackendRepository.Interface; +using Repository.BackendRepository.Implement; +using System.Threading.Tasks; + +namespace BackendWorkerService.Services.Implement +{ + /// + /// 電錶補償歸檔 + /// + public class ProcEletricMeterService + { + private readonly IBackgroundServiceRepository backgroundServiceRepository; + private readonly IBackgroundServiceMsSqlRepository backgroundServiceMsSqlRepository; + + public ProcEletricMeterService(IBackgroundServiceRepository backgroundServiceRepository, + IBackgroundServiceMsSqlRepository backgroundServiceMySqlRepository) + { + this.backgroundServiceRepository = backgroundServiceRepository; + this.backgroundServiceMsSqlRepository = backgroundServiceMySqlRepository; + } + + public async Task ArchiveData() + { + bool result = false; + int repeatTimes = 0; + string targetTable = string.Empty; + + EDFunction ed = new EDFunction(); + XmlDocument xmlDocument = new XmlDocument(); + var obixApiConfig = new ObixApiConfig(); + string encoded = string.Empty; + + try + { + //取得可錯誤次數 + var sqlArchive = $@"SELECT system_value as Value, system_key as Name FROM variable WHERE deleted = 0 AND system_type = 'archiveConfig'"; + + var variableArchive = await backgroundServiceRepository.GetAllAsync(sqlArchive); + repeatTimes = Convert.ToInt32(variableArchive.Where(x => x.Name == "RepeatTimes").Select(x => x.Value).FirstOrDefault()); + var saveToMSDB = await backgroundServiceRepository.GetOneAsync("select system_value from variable where system_type = 'save_to_ms_db' and deleted = 0"); + + #region 取得obix 設定 + var sqlObix = $@"SELECT system_value as Value, system_key as Name FROM variable WHERE deleted = 0 AND system_type = 'obixConfig'"; + + var variableObix = await backgroundServiceRepository.GetAllAsync(sqlObix); + obixApiConfig.ApiBase = variableObix.Where(x => x.Name == "ApiBase").Select(x => x.Value).FirstOrDefault(); + obixApiConfig.UserName = variableObix.Where(x => x.Name == "UserName").Select(x => x.Value).FirstOrDefault(); + obixApiConfig.Password = variableObix.Where(x => x.Name == "Password").Select(x => x.Value).FirstOrDefault(); + + encoded = System.Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(obixApiConfig.UserName + ":" + obixApiConfig.Password)); + #endregion 取得obix 設定 + + //取得錯誤的設備sql format + var sql_error_format = @"SELECT * FROM {0} WHERE is_complete = 0 AND repeat_times < @RepeatTimes"; + + //MY 新增/修改sql format + var MYsql_update_format = @" + UPDATE {0} SET + count_rawdata = @count_rawdata, + min_rawdata = @min_rawdata, + max_rawdata = @max_rawdata, + avg_rawdata = @avg_rawdata, + sum_rawdata = @sum_rawdata, + is_complete = @is_complete, + repeat_times = @repeat_times, + fail_reason = @fail_reason, + updated_at = @updated_at + WHERE device_number = @device_number + AND point = @point + AND start_timestamp = @start_timestamp; + + INSERT INTO {0} ( + device_number, + point, + start_timestamp, + end_timestamp, + count_rawdata, + min_rawdata, + max_rawdata, + avg_rawdata, + sum_rawdata, + is_complete, + repeat_times, + fail_reason) + SELECT + @device_number, + @point, + @start_timestamp, + @end_timestamp, + @count_rawdata, + @min_rawdata, + @max_rawdata, + @avg_rawdata, + @sum_rawdata, + @is_complete, + @repeat_times, + @fail_reason + WHERE ROW_COUNT() = 0;"; + //新增/修改sql format + var sql_update_format = @"BEGIN TRANSACTION; + + UPDATE {0} SET + count_rawdata = @count_rawdata, + min_rawdata = @min_rawdata, + max_rawdata = @max_rawdata, + avg_rawdata = @avg_rawdata, + sum_rawdata = @sum_rawdata, + is_complete = @is_complete, + repeat_times = @repeat_times, + fail_reason = @fail_reason, + updated_at = @updated_at + WHERE device_number = @device_number + AND point = @point + AND start_timestamp = @start_timestamp; + + IF @@ROWCOUNT = 0 + BEGIN + INSERT INTO {0} ( + device_number, + point, + start_timestamp, + end_timestamp, + count_rawdata, + min_rawdata, + max_rawdata, + avg_rawdata, + sum_rawdata, + is_complete, + repeat_times, + fail_reason) + VALUES ( + @device_number, + @point, + @start_timestamp, + @end_timestamp, + @count_rawdata, + @min_rawdata, + @max_rawdata, + @avg_rawdata, + @sum_rawdata, + @is_complete, + @repeat_times, + @fail_reason) + END + + COMMIT TRANSACTION;"; + + #region 時歸檔補償 + //using (IDbConnection conn = new SqlConnection(Connection1)) + //{ + // //取得所有須補償的設備資訊 + // targetTable = "archive_electric_meter_hour"; + // var sql_error_hour = string.Format(sql_error_format, targetTable); + // var error_hours = conn.Query(sql_error_hour, new { RepeatTimes = repeatTimes }).ToList(); + + // List> archiveHourRawDatas = new List>(); + // if (error_hours.Count() > 0) + // { + // foreach (var error_hour in error_hours) + // { + // DeviceNumberPoint deviceNumberPoint = new DeviceNumberPoint(); + // deviceNumberPoint.DeviceNumber = error_hour.Device_number; + // deviceNumberPoint.Point = error_hour.Point; + // deviceNumberPoint.FullDeviceNumberPoint = string.Format("{0}_{1}", error_hour.Device_number, error_hour.Point); + + // var startTimestamp = string.Format("{0}+08:00", error_hour.Start_timestamp.Replace(" ", "T")); + // var endTimestamp = string.Format("{0}+08:00", error_hour.End_timestamp.Replace(" ", "T")); + + // var historyQueryFilter = $@" + // + // + // + // "; + + // HttpWebRequest archiveHourRequest = (HttpWebRequest)WebRequest.Create($"{obixApiConfig.ApiBase}obix/histories/FIC_Center/{deviceNumberPoint.FullDeviceNumberPoint}/~historyRollup/"); + // //HttpWebRequest archiveHourRequest = (HttpWebRequest)WebRequest.Create($"{obixApiConfig.ApiBase}obix/histories/FIC_Center/H_E1_B1F_MVCB_MVCBH_V1/~historyRollup/"); + // archiveHourRequest.Method = "POST"; + // archiveHourRequest.Headers.Add("Authorization", "Basic " + encoded); + // archiveHourRequest.PreAuthenticate = true; + + // byte[] byteArray = Encoding.UTF8.GetBytes(historyQueryFilter); + // using (Stream reqStream = archiveHourRequest.GetRequestStream()) + // { + // reqStream.Write(byteArray, 0, byteArray.Length); + // } + + // HttpWebResponse archiveHourResponse = (HttpWebResponse)archiveHourRequest.GetResponse(); + // var archiveHourResponseContent = new StreamReader(archiveHourResponse.GetResponseStream()).ReadToEnd(); + + // xmlDocument.LoadXml(archiveHourResponseContent); + // string archiveHourJson = JsonConvert.SerializeXmlNode(xmlDocument); + // JObject archiveHourJsonResult = (JObject)JsonConvert.DeserializeObject(archiveHourJson); + + // if (archiveHourJsonResult.ContainsKey("err")) //抓取錯誤 + // { + // Dictionary archiveDayRawData = new Dictionary(); + // archiveDayRawData.Add("@device_number", error_hour.Device_number); + // archiveDayRawData.Add("@point", error_hour.Point); + // archiveDayRawData.Add("@start_timestamp", error_hour.Start_timestamp); + // archiveDayRawData.Add("@end_timestamp", error_hour.End_timestamp); + // archiveDayRawData.Add("@is_complete", 0); + // archiveDayRawData.Add("@repeat_times", ++error_hour.Repeat_times); + // archiveDayRawData.Add("@fail_reason", archiveHourJson); + + // archiveDayRawData.Add("@count_rawdata", 0); + // archiveDayRawData.Add("@min_rawdata", 0); + // archiveDayRawData.Add("@max_rawdata", 0); + // archiveDayRawData.Add("@avg_rawdata", 0); + // archiveDayRawData.Add("@sum_rawdata", 0); + // archiveDayRawData.Add("@updated_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); + + // archiveHourRawDatas.Add(archiveDayRawData); + // } + + // if (archiveHourJsonResult.ContainsKey("obj")) //表示可以讀取到內容 + // { + // var ArrangeRawDatas = ArrangeRawData(deviceNumberPoint, archiveHourJsonResult); + // if (ArrangeRawDatas != null && ArrangeRawDatas.Count() > 0) + // { + // archiveHourRawDatas.AddRange(ArrangeRawDatas); + // } + // } + // } + + // if (archiveHourRawDatas.Count() > 0) + // { + // var sql_error_update = string.Format(sql_update_format, targetTable); + // conn.Execute(sql_error_update, archiveHourRawDatas); + // } + // } + // conn.Close(); + //} + #endregion 時歸檔補償 + + #region 天歸檔補償 + //取得所有須補償的設備資訊 + targetTable = "archive_electric_meter_day"; + var sql_error_day = string.Format(sql_error_format, targetTable); + var electric_error_days = await backgroundServiceRepository.GetAllAsync(sql_error_day, new { RepeatTimes = repeatTimes }); + List> electricArchiveDayRawDatas = new List>(); + if (electric_error_days.Count() > 0) + { + foreach (var error_day in electric_error_days) + { + DeviceNumberPoint deviceNumberPoint = new DeviceNumberPoint(); + deviceNumberPoint.DeviceNumber = error_day.Device_number; + deviceNumberPoint.Point = error_day.Point; + deviceNumberPoint.FullDeviceNumberPoint = string.Format("{0}_{1}", error_day.Device_number, error_day.Point); + + var startTimestamp = string.Format("{0}+08:00", error_day.Start_timestamp.Replace(" ", "T")); + var endTimestamp = string.Format("{0}+08:00", error_day.End_timestamp.Replace(" ", "T")); + + var historyQueryFilter = $@" + + + + "; + + HttpWebRequest archiveDayRequest = (HttpWebRequest)WebRequest.Create($"{obixApiConfig.ApiBase}obix/histories/FIC_Center/{deviceNumberPoint.FullDeviceNumberPoint}/~historyRollup/"); + //HttpWebRequest archiveDayRequest = (HttpWebRequest)WebRequest.Create($"{obixApiConfig.ApiBase}obix/histories/FIC_Center/H_E1_B1F_MVCB_MVCBH_V1/~historyRollup/"); + archiveDayRequest.Method = "POST"; + archiveDayRequest.Headers.Add("Authorization", "Basic " + encoded); + archiveDayRequest.PreAuthenticate = true; + + byte[] byteArray = Encoding.UTF8.GetBytes(historyQueryFilter); + using (Stream reqStream = archiveDayRequest.GetRequestStream()) + { + reqStream.Write(byteArray, 0, byteArray.Length); + } + + HttpWebResponse archiveDayResponse = (HttpWebResponse)archiveDayRequest.GetResponse(); + var archiveDayResponseContent = new StreamReader(archiveDayResponse.GetResponseStream()).ReadToEnd(); + + xmlDocument.LoadXml(archiveDayResponseContent); + string archiveDayJson = JsonConvert.SerializeXmlNode(xmlDocument); + JObject archiveDayJsonResult = (JObject)JsonConvert.DeserializeObject(archiveDayJson); + + if (archiveDayJsonResult.ContainsKey("err")) //抓取錯誤 + { + Dictionary archiveDayRawData = new Dictionary(); + archiveDayRawData.Add("@device_number", error_day.Device_number); + archiveDayRawData.Add("@point", error_day.Point); + archiveDayRawData.Add("@start_timestamp", DateTime.Parse(error_day.Start_timestamp, System.Globalization.CultureInfo.CurrentCulture)); + archiveDayRawData.Add("@end_timestamp", DateTime.Parse(error_day.End_timestamp, System.Globalization.CultureInfo.CurrentCulture)); + archiveDayRawData.Add("@is_complete", 0); + archiveDayRawData.Add("@repeat_times", ++error_day.Repeat_times); + archiveDayRawData.Add("@fail_reason", archiveDayJson); + + archiveDayRawData.Add("@count_rawdata", 0); + archiveDayRawData.Add("@min_rawdata", 0); + archiveDayRawData.Add("@max_rawdata", 0); + archiveDayRawData.Add("@avg_rawdata", 0); + archiveDayRawData.Add("@sum_rawdata", 0); + archiveDayRawData.Add("@updated_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); + + electricArchiveDayRawDatas.Add(archiveDayRawData); + } + + if (archiveDayJsonResult.ContainsKey("obj")) //表示可以讀取到內容 + { + var ArrangeRawDatas = ArrangeRawData(deviceNumberPoint, archiveDayJsonResult); + if (ArrangeRawDatas != null && ArrangeRawDatas.Count() > 0) + { + electricArchiveDayRawDatas.AddRange(ArrangeRawDatas); + } + } + } + + if (electricArchiveDayRawDatas.Count() > 0) + { + var Mysql_error_update = string.Format(MYsql_update_format, targetTable); + var sql_error_update = string.Format(sql_update_format, targetTable); + if (!string.IsNullOrEmpty(saveToMSDB) && saveToMSDB == "1") + { + await backgroundServiceMsSqlRepository.ExecuteSql(sql_error_update, electricArchiveDayRawDatas); + } + await backgroundServiceRepository.ExecuteSql(Mysql_error_update, electricArchiveDayRawDatas); + } + } + + targetTable = "archive_water_meter_day"; + sql_error_day = string.Format(sql_error_format, targetTable); + var water_error_days = await backgroundServiceRepository.GetAllAsync(sql_error_day, new { RepeatTimes = repeatTimes }); + List> waterArchiveDayRawDatas = new List>(); + if (water_error_days.Count() > 0) + { + foreach (var error_day in water_error_days) + { + DeviceNumberPoint deviceNumberPoint = new DeviceNumberPoint(); + deviceNumberPoint.DeviceNumber = error_day.Device_number; + deviceNumberPoint.Point = error_day.Point; + deviceNumberPoint.FullDeviceNumberPoint = string.Format("{0}_{1}", error_day.Device_number, error_day.Point); + + var startTimestamp = string.Format("{0}+08:00", error_day.Start_timestamp.Replace(" ", "T")); + var endTimestamp = string.Format("{0}+08:00", error_day.End_timestamp.Replace(" ", "T")); + + var historyQueryFilter = $@" + + + + "; + + HttpWebRequest archiveDayRequest = (HttpWebRequest)WebRequest.Create($"{obixApiConfig.ApiBase}obix/histories/FIC_Center/{deviceNumberPoint.FullDeviceNumberPoint}/~historyRollup/"); + //HttpWebRequest archiveDayRequest = (HttpWebRequest)WebRequest.Create($"{obixApiConfig.ApiBase}obix/histories/FIC_Center/H_E1_B1F_MVCB_MVCBH_V1/~historyRollup/"); + archiveDayRequest.Method = "POST"; + archiveDayRequest.Headers.Add("Authorization", "Basic " + encoded); + archiveDayRequest.PreAuthenticate = true; + + byte[] byteArray = Encoding.UTF8.GetBytes(historyQueryFilter); + using (Stream reqStream = archiveDayRequest.GetRequestStream()) + { + reqStream.Write(byteArray, 0, byteArray.Length); + } + + HttpWebResponse archiveDayResponse = (HttpWebResponse)archiveDayRequest.GetResponse(); + var archiveDayResponseContent = new StreamReader(archiveDayResponse.GetResponseStream()).ReadToEnd(); + + xmlDocument.LoadXml(archiveDayResponseContent); + string archiveDayJson = JsonConvert.SerializeXmlNode(xmlDocument); + JObject archiveDayJsonResult = (JObject)JsonConvert.DeserializeObject(archiveDayJson); + + if (archiveDayJsonResult.ContainsKey("err")) //抓取錯誤 + { + Dictionary archiveDayRawData = new Dictionary(); + archiveDayRawData.Add("@device_number", error_day.Device_number); + archiveDayRawData.Add("@point", error_day.Point); + archiveDayRawData.Add("@start_timestamp", DateTime.Parse(error_day.Start_timestamp, System.Globalization.CultureInfo.CurrentCulture)); + archiveDayRawData.Add("@end_timestamp", DateTime.Parse(error_day.End_timestamp, System.Globalization.CultureInfo.CurrentCulture)); + archiveDayRawData.Add("@is_complete", 0); + archiveDayRawData.Add("@repeat_times", ++error_day.Repeat_times); + archiveDayRawData.Add("@fail_reason", archiveDayJson); + + archiveDayRawData.Add("@count_rawdata", 0); + archiveDayRawData.Add("@min_rawdata", 0); + archiveDayRawData.Add("@max_rawdata", 0); + archiveDayRawData.Add("@avg_rawdata", 0); + archiveDayRawData.Add("@sum_rawdata", 0); + archiveDayRawData.Add("@updated_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); + + waterArchiveDayRawDatas.Add(archiveDayRawData); + } + + if (archiveDayJsonResult.ContainsKey("obj")) //表示可以讀取到內容 + { + var ArrangeRawDatas = ArrangeRawData(deviceNumberPoint, archiveDayJsonResult); + if (ArrangeRawDatas != null && ArrangeRawDatas.Count() > 0) + { + waterArchiveDayRawDatas.AddRange(ArrangeRawDatas); + } + } + } + + if (waterArchiveDayRawDatas.Count() > 0) + { + var Mysql_error_update = string.Format(MYsql_update_format, targetTable); + var sql_error_update = string.Format(sql_update_format, targetTable); + if (!string.IsNullOrEmpty(saveToMSDB) && saveToMSDB == "1") + { + await backgroundServiceMsSqlRepository.ExecuteSql(sql_error_update, waterArchiveDayRawDatas); + } + await backgroundServiceRepository.ExecuteSql(Mysql_error_update, waterArchiveDayRawDatas); + } + } + #endregion 天歸檔補償 + + #region 週歸檔補償 + //取得所有須補償的設備資訊 + targetTable = "archive_electric_meter_week"; + var sql_error_week = string.Format(sql_error_format, targetTable); + var eletric_error_weeks = await backgroundServiceRepository.GetAllAsync(sql_error_week, new { RepeatTimes = repeatTimes }); + List> electricArchiveWeekRawDatas = new List>(); + if (eletric_error_weeks.Count() > 0) + { + foreach (var error_week in eletric_error_weeks) + { + DeviceNumberPoint deviceNumberPoint = new DeviceNumberPoint(); + deviceNumberPoint.DeviceNumber = error_week.Device_number; + deviceNumberPoint.Point = error_week.Point; + deviceNumberPoint.FullDeviceNumberPoint = string.Format("{0}_{1}", error_week.Device_number, error_week.Point); + + var startTimestamp = string.Format("{0}+08:00", error_week.Start_timestamp.Replace(" ", "T")); + var endTimestamp = string.Format("{0}+08:00", error_week.End_timestamp.Replace(" ", "T")); + + var historyQueryFilter = $@" + + + + "; + + HttpWebRequest archiveWeekRequest = (HttpWebRequest)WebRequest.Create($"{obixApiConfig.ApiBase}obix/histories/FIC_Center/{deviceNumberPoint.FullDeviceNumberPoint}/~historyRollup/"); + //HttpWebRequest archiveWeekRequest = (HttpWebRequest)WebRequest.Create($"{obixApiConfig.ApiBase}obix/histories/FIC_Center/H_E1_B1F_MVCB_MVCBH_V1/~historyRollup/"); + archiveWeekRequest.Method = "POST"; + archiveWeekRequest.Headers.Add("Authorization", "Basic " + encoded); + archiveWeekRequest.PreAuthenticate = true; + + byte[] byteArray = Encoding.UTF8.GetBytes(historyQueryFilter); + using (Stream reqStream = archiveWeekRequest.GetRequestStream()) + { + reqStream.Write(byteArray, 0, byteArray.Length); + } + + HttpWebResponse archiveWeekResponse = (HttpWebResponse)archiveWeekRequest.GetResponse(); + var archiveWeekResponseContent = new StreamReader(archiveWeekResponse.GetResponseStream()).ReadToEnd(); + + xmlDocument.LoadXml(archiveWeekResponseContent); + string archiveWeekJson = JsonConvert.SerializeXmlNode(xmlDocument); + JObject archiveWeekJsonResult = (JObject)JsonConvert.DeserializeObject(archiveWeekJson); + + if (archiveWeekJsonResult.ContainsKey("err")) //抓取錯誤 + { + Dictionary archiveWeekRawData = new Dictionary(); + archiveWeekRawData.Add("@device_number", error_week.Device_number); + archiveWeekRawData.Add("@point", error_week.Point); + archiveWeekRawData.Add("@start_timestamp", DateTime.Parse(error_week.Start_timestamp, System.Globalization.CultureInfo.CurrentCulture)); + archiveWeekRawData.Add("@end_timestamp", DateTime.Parse(error_week.End_timestamp, System.Globalization.CultureInfo.CurrentCulture)); + archiveWeekRawData.Add("@is_complete", 0); + archiveWeekRawData.Add("@repeat_times", ++error_week.Repeat_times); + archiveWeekRawData.Add("@fail_reason", archiveWeekJson); + + archiveWeekRawData.Add("@count_rawdata", 0); + archiveWeekRawData.Add("@min_rawdata", 0); + archiveWeekRawData.Add("@max_rawdata", 0); + archiveWeekRawData.Add("@avg_rawdata", 0); + archiveWeekRawData.Add("@sum_rawdata", 0); + archiveWeekRawData.Add("@updated_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); + + electricArchiveWeekRawDatas.Add(archiveWeekRawData); + } + + if (archiveWeekJsonResult.ContainsKey("obj")) //表示可以讀取到內容 + { + var ArrangeRawDatas = ArrangeRawData(deviceNumberPoint, archiveWeekJsonResult); + if (ArrangeRawDatas != null && ArrangeRawDatas.Count() > 0) + { + electricArchiveWeekRawDatas.AddRange(ArrangeRawDatas); + } + } + } + + if (electricArchiveWeekRawDatas.Count() > 0) + { + var Mysql_error_update = string.Format(MYsql_update_format, targetTable); + var sql_error_update = string.Format(sql_update_format, targetTable); + if (!string.IsNullOrEmpty(saveToMSDB) && saveToMSDB == "1") + { + await backgroundServiceMsSqlRepository.ExecuteSql(sql_error_update, electricArchiveWeekRawDatas); + } + await backgroundServiceRepository.ExecuteSql(Mysql_error_update, electricArchiveWeekRawDatas); + } + } + + targetTable = "archive_water_meter_week"; + sql_error_week = string.Format(sql_error_format, targetTable); + var water_error_weeks = await backgroundServiceRepository.GetAllAsync(sql_error_week, new { RepeatTimes = repeatTimes }); + List> waterArchiveWeekRawDatas = new List>(); + if (water_error_weeks.Count() > 0) + { + foreach (var error_week in water_error_weeks) + { + DeviceNumberPoint deviceNumberPoint = new DeviceNumberPoint(); + deviceNumberPoint.DeviceNumber = error_week.Device_number; + deviceNumberPoint.Point = error_week.Point; + deviceNumberPoint.FullDeviceNumberPoint = string.Format("{0}_{1}", error_week.Device_number, error_week.Point); + + var startTimestamp = string.Format("{0}+08:00", error_week.Start_timestamp.Replace(" ", "T")); + var endTimestamp = string.Format("{0}+08:00", error_week.End_timestamp.Replace(" ", "T")); + + var historyQueryFilter = $@" + + + + "; + + HttpWebRequest archiveWeekRequest = (HttpWebRequest)WebRequest.Create($"{obixApiConfig.ApiBase}obix/histories/FIC_Center/{deviceNumberPoint.FullDeviceNumberPoint}/~historyRollup/"); + //HttpWebRequest archiveWeekRequest = (HttpWebRequest)WebRequest.Create($"{obixApiConfig.ApiBase}obix/histories/FIC_Center/H_E1_B1F_MVCB_MVCBH_V1/~historyRollup/"); + archiveWeekRequest.Method = "POST"; + archiveWeekRequest.Headers.Add("Authorization", "Basic " + encoded); + archiveWeekRequest.PreAuthenticate = true; + + byte[] byteArray = Encoding.UTF8.GetBytes(historyQueryFilter); + using (Stream reqStream = archiveWeekRequest.GetRequestStream()) + { + reqStream.Write(byteArray, 0, byteArray.Length); + } + + HttpWebResponse archiveWeekResponse = (HttpWebResponse)archiveWeekRequest.GetResponse(); + var archiveWeekResponseContent = new StreamReader(archiveWeekResponse.GetResponseStream()).ReadToEnd(); + + xmlDocument.LoadXml(archiveWeekResponseContent); + string archiveWeekJson = JsonConvert.SerializeXmlNode(xmlDocument); + JObject archiveWeekJsonResult = (JObject)JsonConvert.DeserializeObject(archiveWeekJson); + + if (archiveWeekJsonResult.ContainsKey("err")) //抓取錯誤 + { + Dictionary archiveWeekRawData = new Dictionary(); + archiveWeekRawData.Add("@device_number", error_week.Device_number); + archiveWeekRawData.Add("@point", error_week.Point); + archiveWeekRawData.Add("@start_timestamp", DateTime.Parse(error_week.Start_timestamp, System.Globalization.CultureInfo.CurrentCulture)); + archiveWeekRawData.Add("@end_timestamp", DateTime.Parse(error_week.End_timestamp, System.Globalization.CultureInfo.CurrentCulture)); + archiveWeekRawData.Add("@is_complete", 0); + archiveWeekRawData.Add("@repeat_times", ++error_week.Repeat_times); + archiveWeekRawData.Add("@fail_reason", archiveWeekJson); + + archiveWeekRawData.Add("@count_rawdata", 0); + archiveWeekRawData.Add("@min_rawdata", 0); + archiveWeekRawData.Add("@max_rawdata", 0); + archiveWeekRawData.Add("@avg_rawdata", 0); + archiveWeekRawData.Add("@sum_rawdata", 0); + archiveWeekRawData.Add("@updated_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); + + waterArchiveWeekRawDatas.Add(archiveWeekRawData); + } + + if (archiveWeekJsonResult.ContainsKey("obj")) //表示可以讀取到內容 + { + var ArrangeRawDatas = ArrangeRawData(deviceNumberPoint, archiveWeekJsonResult); + if (ArrangeRawDatas != null && ArrangeRawDatas.Count() > 0) + { + waterArchiveWeekRawDatas.AddRange(ArrangeRawDatas); + } + } + } + + if (waterArchiveWeekRawDatas.Count() > 0) + { + var Mysql_error_update = string.Format(MYsql_update_format, targetTable); + var sql_error_update = string.Format(sql_update_format, targetTable); + if (!string.IsNullOrEmpty(saveToMSDB) && saveToMSDB == "1") + { + await backgroundServiceMsSqlRepository.ExecuteSql(sql_error_update, waterArchiveWeekRawDatas); + } + await backgroundServiceRepository.ExecuteSql(Mysql_error_update, waterArchiveWeekRawDatas); + } + } + #endregion 週歸檔補償 + + #region 月歸檔補償 + //取得所有須補償的設備資訊 + targetTable = "archive_electric_meter_month"; + var sql_error_month = string.Format(sql_error_format, targetTable); + var electric_error_months = await backgroundServiceRepository.GetAllAsync(sql_error_month, new { RepeatTimes = repeatTimes }); + List> electricArchiveMonthRawDatas = new List>(); + if (electric_error_months.Count() > 0) + { + foreach (var error_month in electric_error_months) + { + DeviceNumberPoint deviceNumberPoint = new DeviceNumberPoint(); + deviceNumberPoint.DeviceNumber = error_month.Device_number; + deviceNumberPoint.Point = error_month.Point; + deviceNumberPoint.FullDeviceNumberPoint = string.Format("{0}_{1}", error_month.Device_number, error_month.Point); + + var startTimestamp = string.Format("{0}+08:00", error_month.Start_timestamp.Replace(" ", "T")); + var endTimestamp = string.Format("{0}+08:00", error_month.End_timestamp.Replace(" ", "T")); + + var startDateTime = Convert.ToDateTime(error_month.Start_timestamp); + var dayInMonth = DateTime.DaysInMonth(startDateTime.Year, startDateTime.Month); + + var historyQueryFilter = $@" + + + + "; + + HttpWebRequest archiveMonthRequest = (HttpWebRequest)WebRequest.Create($"{obixApiConfig.ApiBase}obix/histories/FIC_Center/{deviceNumberPoint.FullDeviceNumberPoint}/~historyRollup/"); + //HttpWebRequest archiveMonthRequest = (HttpWebRequest)WebRequest.Create($"{obixApiConfig.ApiBase}obix/histories/FIC_Center/H_E1_B1F_MVCB_MVCBH_V1/~historyRollup/"); + archiveMonthRequest.Method = "POST"; + archiveMonthRequest.Headers.Add("Authorization", "Basic " + encoded); + archiveMonthRequest.PreAuthenticate = true; + + byte[] byteArray = Encoding.UTF8.GetBytes(historyQueryFilter); + using (Stream reqStream = archiveMonthRequest.GetRequestStream()) + { + reqStream.Write(byteArray, 0, byteArray.Length); + } + + HttpWebResponse archiveMonthResponse = (HttpWebResponse)archiveMonthRequest.GetResponse(); + var archiveMonthResponseContent = new StreamReader(archiveMonthResponse.GetResponseStream()).ReadToEnd(); + + xmlDocument.LoadXml(archiveMonthResponseContent); + string archiveMonthJson = JsonConvert.SerializeXmlNode(xmlDocument); + JObject archiveMonthJsonResult = (JObject)JsonConvert.DeserializeObject(archiveMonthJson); + + if (archiveMonthJsonResult.ContainsKey("err")) //抓取錯誤 + { + Dictionary archiveMonthRawData = new Dictionary(); + archiveMonthRawData.Add("@device_number", error_month.Device_number); + archiveMonthRawData.Add("@point", error_month.Point); + archiveMonthRawData.Add("@start_timestamp", DateTime.Parse(error_month.Start_timestamp, System.Globalization.CultureInfo.CurrentCulture)); + archiveMonthRawData.Add("@end_timestamp", DateTime.Parse(error_month.End_timestamp, System.Globalization.CultureInfo.CurrentCulture)); + archiveMonthRawData.Add("@is_complete", 0); + archiveMonthRawData.Add("@repeat_times", ++error_month.Repeat_times); + archiveMonthRawData.Add("@fail_reason", archiveMonthJson); + + archiveMonthRawData.Add("@count_rawdata", 0); + archiveMonthRawData.Add("@min_rawdata", 0); + archiveMonthRawData.Add("@max_rawdata", 0); + archiveMonthRawData.Add("@avg_rawdata", 0); + archiveMonthRawData.Add("@sum_rawdata", 0); + archiveMonthRawData.Add("@updated_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); + + electricArchiveMonthRawDatas.Add(archiveMonthRawData); + } + + if (archiveMonthJsonResult.ContainsKey("obj")) //表示可以讀取到內容 + { + var ArrangeRawDatas = ArrangeRawData(deviceNumberPoint, archiveMonthJsonResult); + if (ArrangeRawDatas != null && ArrangeRawDatas.Count() > 0) + { + electricArchiveMonthRawDatas.AddRange(ArrangeRawDatas); + } + } + } + + if (electricArchiveMonthRawDatas.Count() > 0) + { + var Mysql_error_update = string.Format(MYsql_update_format, targetTable); + var sql_error_update = string.Format(sql_update_format, targetTable); + if (!string.IsNullOrEmpty(saveToMSDB) && saveToMSDB == "1") + { + await backgroundServiceMsSqlRepository.ExecuteSql(sql_error_update, electricArchiveMonthRawDatas); + } + await backgroundServiceRepository.ExecuteSql(MYsql_update_format, electricArchiveMonthRawDatas); + } + } + + targetTable = "archive_electric_meter_month"; + sql_error_month = string.Format(sql_error_format, targetTable); + var water_error_months = await backgroundServiceRepository.GetAllAsync(sql_error_month, new { RepeatTimes = repeatTimes }); + List> waterArchiveMonthRawDatas = new List>(); + if (water_error_months.Count() > 0) + { + foreach (var error_month in water_error_months) + { + DeviceNumberPoint deviceNumberPoint = new DeviceNumberPoint(); + deviceNumberPoint.DeviceNumber = error_month.Device_number; + deviceNumberPoint.Point = error_month.Point; + deviceNumberPoint.FullDeviceNumberPoint = string.Format("{0}_{1}", error_month.Device_number, error_month.Point); + + var startTimestamp = string.Format("{0}+08:00", error_month.Start_timestamp.Replace(" ", "T")); + var endTimestamp = string.Format("{0}+08:00", error_month.End_timestamp.Replace(" ", "T")); + + var startDateTime = Convert.ToDateTime(error_month.Start_timestamp); + var dayInMonth = DateTime.DaysInMonth(startDateTime.Year, startDateTime.Month); + + var historyQueryFilter = $@" + + + + "; + + HttpWebRequest archiveMonthRequest = (HttpWebRequest)WebRequest.Create($"{obixApiConfig.ApiBase}obix/histories/FIC_Center/{deviceNumberPoint.FullDeviceNumberPoint}/~historyRollup/"); + //HttpWebRequest archiveMonthRequest = (HttpWebRequest)WebRequest.Create($"{obixApiConfig.ApiBase}obix/histories/FIC_Center/H_E1_B1F_MVCB_MVCBH_V1/~historyRollup/"); + archiveMonthRequest.Method = "POST"; + archiveMonthRequest.Headers.Add("Authorization", "Basic " + encoded); + archiveMonthRequest.PreAuthenticate = true; + + byte[] byteArray = Encoding.UTF8.GetBytes(historyQueryFilter); + using (Stream reqStream = archiveMonthRequest.GetRequestStream()) + { + reqStream.Write(byteArray, 0, byteArray.Length); + } + + HttpWebResponse archiveMonthResponse = (HttpWebResponse)archiveMonthRequest.GetResponse(); + var archiveMonthResponseContent = new StreamReader(archiveMonthResponse.GetResponseStream()).ReadToEnd(); + + xmlDocument.LoadXml(archiveMonthResponseContent); + string archiveMonthJson = JsonConvert.SerializeXmlNode(xmlDocument); + JObject archiveMonthJsonResult = (JObject)JsonConvert.DeserializeObject(archiveMonthJson); + + if (archiveMonthJsonResult.ContainsKey("err")) //抓取錯誤 + { + Dictionary archiveMonthRawData = new Dictionary(); + archiveMonthRawData.Add("@device_number", error_month.Device_number); + archiveMonthRawData.Add("@point", error_month.Point); + archiveMonthRawData.Add("@start_timestamp", DateTime.Parse(error_month.Start_timestamp, System.Globalization.CultureInfo.CurrentCulture)); + archiveMonthRawData.Add("@end_timestamp", DateTime.Parse(error_month.End_timestamp, System.Globalization.CultureInfo.CurrentCulture)); + archiveMonthRawData.Add("@is_complete", 0); + archiveMonthRawData.Add("@repeat_times", ++error_month.Repeat_times); + archiveMonthRawData.Add("@fail_reason", archiveMonthJson); + + archiveMonthRawData.Add("@count_rawdata", 0); + archiveMonthRawData.Add("@min_rawdata", 0); + archiveMonthRawData.Add("@max_rawdata", 0); + archiveMonthRawData.Add("@avg_rawdata", 0); + archiveMonthRawData.Add("@sum_rawdata", 0); + archiveMonthRawData.Add("@updated_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); + + waterArchiveMonthRawDatas.Add(archiveMonthRawData); + } + + if (archiveMonthJsonResult.ContainsKey("obj")) //表示可以讀取到內容 + { + var ArrangeRawDatas = ArrangeRawData(deviceNumberPoint, archiveMonthJsonResult); + if (ArrangeRawDatas != null && ArrangeRawDatas.Count() > 0) + { + waterArchiveMonthRawDatas.AddRange(ArrangeRawDatas); + } + } + } + + if (waterArchiveMonthRawDatas.Count() > 0) + { + var Mysql_error_update = string.Format(MYsql_update_format, targetTable); + var sql_error_update = string.Format(sql_update_format, targetTable); + if (!string.IsNullOrEmpty(saveToMSDB) && saveToMSDB == "1") + { + await backgroundServiceMsSqlRepository.ExecuteSql(sql_error_update, waterArchiveMonthRawDatas); + } + await backgroundServiceRepository.ExecuteSql(MYsql_update_format, waterArchiveMonthRawDatas); + } + } + #endregion 月歸檔補償 + + result = true; + } + catch (Exception exception) + { + throw exception; + } + + return result; + } + + private List> ArrangeRawData(DeviceNumberPoint deviceNumberPoint, JObject jsonResult) + { + List> arrangeRawDatas = new List>(); + var histories = jsonResult["obj"]["list"]; + var rawdateCount = Convert.ToInt32(jsonResult["obj"]["int"]["@val"].ToString()); + + if (rawdateCount == 0) + { + return null; + } + + if (histories != null && histories.HasValues) + { + if (rawdateCount > 1) + { //多筆資料 + foreach (var history in histories) + { + Dictionary arrangeRawData = new Dictionary(); + arrangeRawData.Add("@device_number", deviceNumberPoint.DeviceNumber); + arrangeRawData.Add("@point", deviceNumberPoint.Point); + + //時間 + if (history["abstime"] != null && history["abstime"].HasValues) + { + foreach (var abstime in history["abstime"]) + { + var name = abstime["@name"].ToString(); + switch (name) + { + case "start": + var startTimstamp = Convert.ToDateTime(abstime["@val"].ToString()).ToString("yyyy-MM-dd HH:mm:ss"); + arrangeRawData.Add("@start_timestamp", startTimstamp); + break; + case "end": + var endTimstamp = Convert.ToDateTime(abstime["@val"].ToString()).ToString("yyyy-MM-dd HH:mm:ss"); + arrangeRawData.Add("@end_timestamp", endTimstamp); + break; + } + } + } + + //區間內資料筆數 + if (history["int"] != null && history["int"].HasValues) + { + var count = Convert.ToInt32(histories["obj"]["int"]["@val"].ToString()); + arrangeRawData.Add("@count_rawdata", count); + } + + //整合數值(最大、最小、平均、總和) + if (history["real"] != null && history["real"].HasValues) + { + foreach (var real in history["real"]) + { + var name = real["@name"].ToString(); + switch (name) + { + case "min": + var min = Convert.ToDecimal(real["@val"].ToString()); + arrangeRawData.Add("@min_rawdata", min); + break; + case "max": + var max = Convert.ToDecimal(real["@val"].ToString()); + arrangeRawData.Add("@max_rawdata", max); + break; + case "avg": + var avg = Convert.ToDecimal(real["@val"].ToString()); + arrangeRawData.Add("@avg_rawdata", avg); + break; + case "sum": + var sum = Decimal.Parse(real["@val"].ToString(), System.Globalization.NumberStyles.Float); + arrangeRawData.Add("@sum_rawdata", sum); + break; + } + } + } + arrangeRawData.Add("@is_complete", 1); + arrangeRawData.Add("@repeat_times", 0); + arrangeRawData.Add("@fail_reason", null); + arrangeRawData.Add("@updated_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); + + arrangeRawDatas.Add(arrangeRawData); + } + } + else + { //單筆資料 + Dictionary arrangeRawData = new Dictionary(); + arrangeRawData.Add("@device_number", deviceNumberPoint.DeviceNumber); + arrangeRawData.Add("@point", deviceNumberPoint.Point); + + //時間 + if (histories["obj"]["abstime"] != null && histories["obj"]["abstime"].HasValues) + { + foreach (var abstime in histories["obj"]["abstime"]) + { + var name = abstime["@name"].ToString(); + switch (name) + { + case "start": + var startTimstamp = Convert.ToDateTime(abstime["@val"].ToString()).ToString("yyyy-MM-dd HH:mm:ss"); + arrangeRawData.Add("@start_timestamp", startTimstamp); + break; + case "end": + var endTimstamp = Convert.ToDateTime(abstime["@val"].ToString()).ToString("yyyy-MM-dd HH:mm:ss"); + arrangeRawData.Add("@end_timestamp", endTimstamp); + break; + } + } + } + + //區間內資料筆數 + if (histories["obj"]["int"] != null && histories["obj"]["int"].HasValues) + { + var count = Convert.ToInt32(histories["obj"]["int"]["@val"].ToString()); + arrangeRawData.Add("@count_rawdata", count); + } + + //整合數值(最大、最小、平均、總和) + if (histories["obj"]["real"] != null && histories["obj"]["real"].HasValues) + { + foreach (var real in histories["obj"]["real"]) + { + var name = real["@name"].ToString(); + switch (name) + { + case "min": + var min = Convert.ToDecimal(real["@val"].ToString()); + arrangeRawData.Add("@min_rawdata", min); + break; + case "max": + var max = Convert.ToDecimal(real["@val"].ToString()); + arrangeRawData.Add("@max_rawdata", max); + break; + case "avg": + var avg = Convert.ToDecimal(real["@val"].ToString()); + arrangeRawData.Add("@avg_rawdata", avg); + break; + case "sum": + var sum = Decimal.Parse(real["@val"].ToString(), System.Globalization.NumberStyles.Float); + arrangeRawData.Add("@sum_rawdata", sum); + break; + } + } + } + + arrangeRawData.Add("@is_complete", 1); + arrangeRawData.Add("@repeat_times", 0); + arrangeRawData.Add("@fail_reason", null); + arrangeRawData.Add("@updated_at", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); + + arrangeRawDatas.Add(arrangeRawData); + } + } + + return arrangeRawDatas; + } + } +} diff --git a/Backend/Services/Implement/Quicktype.cs b/Backend/Services/Implement/Quicktype.cs new file mode 100644 index 0000000..35dab94 --- /dev/null +++ b/Backend/Services/Implement/Quicktype.cs @@ -0,0 +1,640 @@ +using System; +using System.Collections.Generic; +using System.Text; + +// +// +// To parse this JSON data, add NuGet 'Newtonsoft.Json' then do: +// +// using QuickType; +// +// var welcome = Welcome.FromJson(jsonString); + +namespace QuickType +{ + using System; + using System.Collections.Generic; + + using System.Globalization; + using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + + public partial class Welcome + { + [JsonProperty("success")] + [JsonConverter(typeof(ParseStringConverter))] + public bool Success { get; set; } + + [JsonProperty("result")] + public Result Result { get; set; } + + [JsonProperty("records")] + public Records Records { get; set; } + } + + public partial class Records + { + [JsonProperty("datasetDescription")] + public DatasetDescription DatasetDescription { get; set; } + + [JsonProperty("Earthquake")] + public Earthquake[] Earthquake { get; set; } + } + + public partial class Earthquake + { + [JsonProperty("EarthquakeNo")] + public long EarthquakeNo { get; set; } + + [JsonProperty("ReportType")] + public DatasetDescription ReportType { get; set; } + + [JsonProperty("ReportColor")] + public ReportColor ReportColor { get; set; } + + [JsonProperty("ReportContent")] + public string ReportContent { get; set; } + + [JsonProperty("ReportImageURI")] + public Uri ReportImageUri { get; set; } + + [JsonProperty("ReportRemark")] + public ReportRemark ReportRemark { get; set; } + + [JsonProperty("Web")] + public Uri Web { get; set; } + + [JsonProperty("ShakemapImageURI")] + public Uri ShakemapImageUri { get; set; } + + [JsonProperty("EarthquakeInfo")] + public EarthquakeInfo EarthquakeInfo { get; set; } + + [JsonProperty("Intensity")] + public Intensity Intensity { get; set; } + } + + public partial class EarthquakeInfo + { + [JsonProperty("OriginTime")] + public DateTimeOffset OriginTime { get; set; } + + [JsonProperty("Source")] + public Source Source { get; set; } + + [JsonProperty("FocalDepth")] + public double FocalDepth { get; set; } + + [JsonProperty("Epicenter")] + public Epicenter Epicenter { get; set; } + + [JsonProperty("EarthquakeMagnitude")] + public EarthquakeMagnitude EarthquakeMagnitude { get; set; } + } + + public partial class EarthquakeMagnitude + { + [JsonProperty("MagnitudeType")] + public MagnitudeType MagnitudeType { get; set; } + + [JsonProperty("MagnitudeValue")] + public double MagnitudeValue { get; set; } + } + + public partial class Epicenter + { + [JsonProperty("Location")] + public string Location { get; set; } + + [JsonProperty("EpicenterLatitude")] + public double EpicenterLatitude { get; set; } + + [JsonProperty("EpicenterLongitude")] + public double EpicenterLongitude { get; set; } + } + + public partial class Intensity + { + [JsonProperty("ShakingArea")] + public ShakingArea[] ShakingArea { get; set; } + } + + public partial class ShakingArea + { + [JsonProperty("AreaDesc")] + public string AreaDesc { get; set; } + + [JsonProperty("CountyName")] + public string CountyName { get; set; } + + [JsonProperty("InfoStatus", NullValueHandling = NullValueHandling.Ignore)] + public InfoStatus? InfoStatus { get; set; } + + [JsonProperty("AreaIntensity")] + public AreaIntensityEnum AreaIntensity { get; set; } + + [JsonProperty("EqStation")] + public EqStation[] EqStation { get; set; } + } + + public partial class EqStation + { + [JsonProperty("pga", NullValueHandling = NullValueHandling.Ignore)] + public Pga Pga { get; set; } + + [JsonProperty("pgv", NullValueHandling = NullValueHandling.Ignore)] + public Pga Pgv { get; set; } + + [JsonProperty("StationName")] + public string StationName { get; set; } + + [JsonProperty("StationID")] + public string StationId { get; set; } + + [JsonProperty("InfoStatus", NullValueHandling = NullValueHandling.Ignore)] + public InfoStatus? InfoStatus { get; set; } + + [JsonProperty("BackAzimuth")] + public double BackAzimuth { get; set; } + + [JsonProperty("EpicenterDistance")] + public double EpicenterDistance { get; set; } + + [JsonProperty("SeismicIntensity")] + public AreaIntensityEnum SeismicIntensity { get; set; } + + [JsonProperty("StationLatitude")] + public double StationLatitude { get; set; } + + [JsonProperty("StationLongitude")] + public double StationLongitude { get; set; } + + [JsonProperty("WaveImageURI", NullValueHandling = NullValueHandling.Ignore)] + public Uri WaveImageUri { get; set; } + } + + public partial class Pga + { + [JsonProperty("unit")] + public Unit Unit { get; set; } + + [JsonProperty("EWComponent")] + public double EwComponent { get; set; } + + [JsonProperty("NSComponent")] + public double NsComponent { get; set; } + + [JsonProperty("VComponent")] + public double VComponent { get; set; } + + [JsonProperty("IntScaleValue")] + public double IntScaleValue { get; set; } + } + + public partial class Result + { + [JsonProperty("resource_id")] + public string ResourceId { get; set; } + + [JsonProperty("fields")] + public Field[] Fields { get; set; } + } + + public partial class Field + { + [JsonProperty("id")] + public string Id { get; set; } + + [JsonProperty("type")] + public TypeEnum Type { get; set; } + } + + public enum DatasetDescription { 地震報告 }; + + public enum MagnitudeType { 芮氏規模 }; + + public enum Source { 中央氣象局 }; + + public enum AreaIntensityEnum { The1級, The2級, The3級, The4級 }; + + public enum InfoStatus { Observe }; + + public enum Unit { Gal, Kine }; + + public enum ReportColor { 綠色 }; + + public enum ReportRemark { 本報告係中央氣象局地震觀測網即時地震資料地震速報之結果 }; + + public enum TypeEnum { Float, Integer, String, Timestamp }; + + public partial class Welcome + { + public static Welcome FromJson(string json) => JsonConvert.DeserializeObject(json, QuickType.Converter.Settings); + } + + public static class Serialize + { + public static string ToJson(this Welcome self) => JsonConvert.SerializeObject(self, QuickType.Converter.Settings); + } + + internal static class Converter + { + public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings + { + MetadataPropertyHandling = MetadataPropertyHandling.Ignore, + DateParseHandling = DateParseHandling.None, + Converters = + { + MagnitudeTypeConverter.Singleton, + SourceConverter.Singleton, + AreaIntensityEnumConverter.Singleton, + InfoStatusConverter.Singleton, + UnitConverter.Singleton, + ReportColorConverter.Singleton, + ReportRemarkConverter.Singleton, + DatasetDescriptionConverter.Singleton, + TypeEnumConverter.Singleton, + new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal } + }, + }; + } + + internal class MagnitudeTypeConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(MagnitudeType) || t == typeof(MagnitudeType?); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + var value = serializer.Deserialize(reader); + if (value == "芮氏規模") + { + return MagnitudeType.芮氏規模; + } + throw new Exception("Cannot unmarshal type MagnitudeType"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + if (untypedValue == null) + { + serializer.Serialize(writer, null); + return; + } + var value = (MagnitudeType)untypedValue; + if (value == MagnitudeType.芮氏規模) + { + serializer.Serialize(writer, "芮氏規模"); + return; + } + throw new Exception("Cannot marshal type MagnitudeType"); + } + + public static readonly MagnitudeTypeConverter Singleton = new MagnitudeTypeConverter(); + } + + internal class SourceConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(Source) || t == typeof(Source?); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + var value = serializer.Deserialize(reader); + if (value == "中央氣象局") + { + return Source.中央氣象局; + } + throw new Exception("Cannot unmarshal type Source"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + if (untypedValue == null) + { + serializer.Serialize(writer, null); + return; + } + var value = (Source)untypedValue; + if (value == Source.中央氣象局) + { + serializer.Serialize(writer, "中央氣象局"); + return; + } + throw new Exception("Cannot marshal type Source"); + } + + public static readonly SourceConverter Singleton = new SourceConverter(); + } + + internal class AreaIntensityEnumConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(AreaIntensityEnum) || t == typeof(AreaIntensityEnum?); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + var value = serializer.Deserialize(reader); + switch (value) + { + case "1級": + return AreaIntensityEnum.The1級; + case "2級": + return AreaIntensityEnum.The2級; + case "3級": + return AreaIntensityEnum.The3級; + case "4級": + return AreaIntensityEnum.The4級; + } + throw new Exception("Cannot unmarshal type AreaIntensityEnum"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + if (untypedValue == null) + { + serializer.Serialize(writer, null); + return; + } + var value = (AreaIntensityEnum)untypedValue; + switch (value) + { + case AreaIntensityEnum.The1級: + serializer.Serialize(writer, "1級"); + return; + case AreaIntensityEnum.The2級: + serializer.Serialize(writer, "2級"); + return; + case AreaIntensityEnum.The3級: + serializer.Serialize(writer, "3級"); + return; + case AreaIntensityEnum.The4級: + serializer.Serialize(writer, "4級"); + return; + } + throw new Exception("Cannot marshal type AreaIntensityEnum"); + } + + public static readonly AreaIntensityEnumConverter Singleton = new AreaIntensityEnumConverter(); + } + + internal class InfoStatusConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(InfoStatus) || t == typeof(InfoStatus?); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + var value = serializer.Deserialize(reader); + if (value == "observe") + { + return InfoStatus.Observe; + } + throw new Exception("Cannot unmarshal type InfoStatus"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + if (untypedValue == null) + { + serializer.Serialize(writer, null); + return; + } + var value = (InfoStatus)untypedValue; + if (value == InfoStatus.Observe) + { + serializer.Serialize(writer, "observe"); + return; + } + throw new Exception("Cannot marshal type InfoStatus"); + } + + public static readonly InfoStatusConverter Singleton = new InfoStatusConverter(); + } + + internal class UnitConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(Unit) || t == typeof(Unit?); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + var value = serializer.Deserialize(reader); + switch (value) + { + case "gal": + return Unit.Gal; + case "kine": + return Unit.Kine; + } + throw new Exception("Cannot unmarshal type Unit"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + if (untypedValue == null) + { + serializer.Serialize(writer, null); + return; + } + var value = (Unit)untypedValue; + switch (value) + { + case Unit.Gal: + serializer.Serialize(writer, "gal"); + return; + case Unit.Kine: + serializer.Serialize(writer, "kine"); + return; + } + throw new Exception("Cannot marshal type Unit"); + } + + public static readonly UnitConverter Singleton = new UnitConverter(); + } + + internal class ReportColorConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(ReportColor) || t == typeof(ReportColor?); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + var value = serializer.Deserialize(reader); + if (value == "綠色") + { + return ReportColor.綠色; + } + throw new Exception("Cannot unmarshal type ReportColor"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + if (untypedValue == null) + { + serializer.Serialize(writer, null); + return; + } + var value = (ReportColor)untypedValue; + if (value == ReportColor.綠色) + { + serializer.Serialize(writer, "綠色"); + return; + } + throw new Exception("Cannot marshal type ReportColor"); + } + + public static readonly ReportColorConverter Singleton = new ReportColorConverter(); + } + + internal class ReportRemarkConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(ReportRemark) || t == typeof(ReportRemark?); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + var value = serializer.Deserialize(reader); + if (value == "本報告係中央氣象局地震觀測網即時地震資料地震速報之結果。") + { + return ReportRemark.本報告係中央氣象局地震觀測網即時地震資料地震速報之結果; + } + throw new Exception("Cannot unmarshal type ReportRemark"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + if (untypedValue == null) + { + serializer.Serialize(writer, null); + return; + } + var value = (ReportRemark)untypedValue; + if (value == ReportRemark.本報告係中央氣象局地震觀測網即時地震資料地震速報之結果) + { + serializer.Serialize(writer, "本報告係中央氣象局地震觀測網即時地震資料地震速報之結果。"); + return; + } + throw new Exception("Cannot marshal type ReportRemark"); + } + + public static readonly ReportRemarkConverter Singleton = new ReportRemarkConverter(); + } + + internal class DatasetDescriptionConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(DatasetDescription) || t == typeof(DatasetDescription?); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + var value = serializer.Deserialize(reader); + if (value == "地震報告") + { + return DatasetDescription.地震報告; + } + throw new Exception("Cannot unmarshal type DatasetDescription"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + if (untypedValue == null) + { + serializer.Serialize(writer, null); + return; + } + var value = (DatasetDescription)untypedValue; + if (value == DatasetDescription.地震報告) + { + serializer.Serialize(writer, "地震報告"); + return; + } + throw new Exception("Cannot marshal type DatasetDescription"); + } + + public static readonly DatasetDescriptionConverter Singleton = new DatasetDescriptionConverter(); + } + + internal class TypeEnumConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(TypeEnum) || t == typeof(TypeEnum?); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + var value = serializer.Deserialize(reader); + switch (value) + { + case "Float": + return TypeEnum.Float; + case "Integer": + return TypeEnum.Integer; + case "String": + return TypeEnum.String; + case "Timestamp": + return TypeEnum.Timestamp; + } + throw new Exception("Cannot unmarshal type TypeEnum"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + if (untypedValue == null) + { + serializer.Serialize(writer, null); + return; + } + var value = (TypeEnum)untypedValue; + switch (value) + { + case TypeEnum.Float: + serializer.Serialize(writer, "Float"); + return; + case TypeEnum.Integer: + serializer.Serialize(writer, "Integer"); + return; + case TypeEnum.String: + serializer.Serialize(writer, "String"); + return; + case TypeEnum.Timestamp: + serializer.Serialize(writer, "Timestamp"); + return; + } + throw new Exception("Cannot marshal type TypeEnum"); + } + + public static readonly TypeEnumConverter Singleton = new TypeEnumConverter(); + } + + internal class ParseStringConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(bool) || t == typeof(bool?); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + var value = serializer.Deserialize(reader); + bool b; + if (Boolean.TryParse(value, out b)) + { + return b; + } + throw new Exception("Cannot unmarshal type bool"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + if (untypedValue == null) + { + serializer.Serialize(writer, null); + return; + } + var value = (bool)untypedValue; + var boolString = value ? "true" : "false"; + serializer.Serialize(writer, boolString); + return; + } + + public static readonly ParseStringConverter Singleton = new ParseStringConverter(); + } +} diff --git a/Backend/Services/Implement/RainApi.cs b/Backend/Services/Implement/RainApi.cs new file mode 100644 index 0000000..2757671 --- /dev/null +++ b/Backend/Services/Implement/RainApi.cs @@ -0,0 +1,213 @@ +using System; +using System.Collections.Generic; +using System.Text; + +// +// +// To parse this JSON data, add NuGet 'Newtonsoft.Json' then do: +// +// using QuickType; +// +// var welcome = Welcome.FromJson(jsonString); + +namespace RainApi +{ + using System; + using System.Collections.Generic; + + using System.Globalization; + using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + + public partial class Welcome + { + [JsonProperty("?xml")] + public Xml Xml { get; set; } + + [JsonProperty("alert")] + public Alert Alert { get; set; } + } + + public partial class Alert + { + [JsonProperty("@xmlns")] + public string Xmlns { get; set; } + + [JsonProperty("identifier")] + public string Identifier { get; set; } + + [JsonProperty("sender")] + public string Sender { get; set; } + + [JsonProperty("sent")] + public DateTimeOffset Sent { get; set; } + + [JsonProperty("status")] + public string Status { get; set; } + + [JsonProperty("msgType")] + public string MsgType { get; set; } + + [JsonProperty("scope")] + public string Scope { get; set; } + + [JsonProperty("references")] + public string References { get; set; } + + [JsonProperty("info")] + public Info Info { get; set; } + } + + public partial class Info + { + [JsonProperty("language")] + public string Language { get; set; } + + [JsonProperty("category")] + public string Category { get; set; } + + [JsonProperty("event")] + public string Event { get; set; } + + [JsonProperty("urgency")] + public string Urgency { get; set; } + + [JsonProperty("severity")] + public string Severity { get; set; } + + [JsonProperty("certainty")] + public string Certainty { get; set; } + + [JsonProperty("eventCode")] + public EventCode EventCode { get; set; } + + [JsonProperty("effective")] + public DateTimeOffset Effective { get; set; } + + [JsonProperty("onset")] + public DateTimeOffset Onset { get; set; } + + [JsonProperty("expires")] + public DateTimeOffset Expires { get; set; } + + [JsonProperty("senderName")] + public string SenderName { get; set; } + + [JsonProperty("headline")] + public string Headline { get; set; } + + [JsonProperty("description")] + public string Description { get; set; } + + [JsonProperty("instruction")] + public string Instruction { get; set; } + + [JsonProperty("web")] + public Uri Web { get; set; } + + [JsonProperty("parameter")] + public EventCode Parameter { get; set; } + + [JsonProperty("area")] + public List Area { get; set; } + } + + public partial class Area + { + [JsonProperty("areaDesc")] + public string AreaDesc { get; set; } + + [JsonProperty("geocode")] + public EventCode Geocode { get; set; } + } + + public partial class EventCode + { + [JsonProperty("valueName")] + public string ValueName { get; set; } + + [JsonProperty("value")] + public string Value { get; set; } + } + + public partial class Xml + { + [JsonProperty("@version")] + public string Version { get; set; } + + [JsonProperty("@encoding")] + public string Encoding { get; set; } + } + + public enum ValueName { AlertTitle, ProfileCapTwpEvent10, TaiwanGeocode103 }; + + public partial class Welcome + { + public static Welcome FromJson(string json) => JsonConvert.DeserializeObject(json, QuickType.Converter.Settings); + } + + public static class Serialize + { + public static string ToJson(this Welcome self) => JsonConvert.SerializeObject(self, QuickType.Converter.Settings); + } + + internal static class Converter + { + public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings + { + MetadataPropertyHandling = MetadataPropertyHandling.Ignore, + DateParseHandling = DateParseHandling.None, + Converters = + { + ValueNameConverter.Singleton, + new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal } + }, + }; + } + + internal class ValueNameConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(ValueName) || t == typeof(ValueName?); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + var value = serializer.Deserialize(reader); + switch (value) + { + case "Taiwan_Geocode_103": + return ValueName.TaiwanGeocode103; + case "alert_title": + return ValueName.AlertTitle; + case "profile:CAP-TWP:Event:1.0": + return ValueName.ProfileCapTwpEvent10; + } + throw new Exception("Cannot unmarshal type ValueName"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + if (untypedValue == null) + { + serializer.Serialize(writer, null); + return; + } + var value = (ValueName)untypedValue; + switch (value) + { + case ValueName.TaiwanGeocode103: + serializer.Serialize(writer, "Taiwan_Geocode_103"); + return; + case ValueName.AlertTitle: + serializer.Serialize(writer, "alert_title"); + return; + case ValueName.ProfileCapTwpEvent10: + serializer.Serialize(writer, "profile:CAP-TWP:Event:1.0"); + return; + } + throw new Exception("Cannot marshal type ValueName"); + } + + public static readonly ValueNameConverter Singleton = new ValueNameConverter(); + } +} diff --git a/Backend/Services/Implement/SendEmailService.cs b/Backend/Services/Implement/SendEmailService.cs new file mode 100644 index 0000000..b72b5ee --- /dev/null +++ b/Backend/Services/Implement/SendEmailService.cs @@ -0,0 +1,81 @@ +using Microsoft.Extensions.Options; +using Backend.Models; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Mail; +using System.Threading.Tasks; +using BackendWorkerService.Services.Interface; +using Microsoft.Extensions.Logging; + +namespace BackendWorkerService.Services.Implement +{ + public class SendEmailService : ISendEmailService + { + private readonly ILogger logger; + private readonly IOptions _options; + + private SMTPConfig smtp; + + public SendEmailService(ILogger logger, IOptions options) + { + this.logger = logger; + smtp = options.Value; + } + + public bool Send(int id, List recipientEmails, string subject, string content) + { + try + { + logger.LogInformation("【SendEmailSMSService】【Email開始寄送】[任務編號]:{0}", id); + + MailMessage MyMail = new MailMessage(); + + MyMail.SubjectEncoding = System.Text.Encoding.UTF8;//郵件標題編碼 + MyMail.BodyEncoding = System.Text.Encoding.UTF8; //郵件內容編碼 + MyMail.IsBodyHtml = true; //是否使用html格式 + + var mailFrom = $"FIC IBMS管理系統通知 <{smtp.UserName}>"; + + MyMail.From = new System.Net.Mail.MailAddress(mailFrom); //寄件人 + foreach (var email in recipientEmails) + { + MyMail.To.Add(email); //設定收件者Email + } + + MyMail.Subject = subject; //主題 + MyMail.Body = content; //設定信件內容 + + //讀取 SMTP Config + SmtpClient MySMTP = new SmtpClient(smtp.Host, smtp.Port); + MySMTP.EnableSsl = smtp.EnableSsl; + MySMTP.Credentials = new System.Net.NetworkCredential(smtp.UserName, smtp.Password); + + try + { + MySMTP.Send(MyMail); + MySMTP.Dispose(); + MyMail.Dispose(); //釋放資源 + + logger.LogInformation("【SendEmailSMSService】【Email寄送成功】[任務編號]:{0}", id); + return true; + } + catch(Exception exception) + { + logger.LogError("【SendEmailSMSService】【Email寄送失敗】[任務編號]:{0}", id); + logger.LogError("【SendEmailSMSService】【Email寄送失敗】[Exception]:{0}", exception.ToString()); + return false; + } + } + catch (Exception exception) + { + logger.LogError("【SendEmailSMSService】【Email寄送失敗】[任務編號]:{0}", id); + logger.LogError("【SendEmailSMSService】【Email寄送失敗】[Exception]:{0}", exception.ToString()); + throw; + //return false; + } + + } + } +} diff --git a/Backend/Services/Implement/SendLineNotifyService.cs b/Backend/Services/Implement/SendLineNotifyService.cs new file mode 100644 index 0000000..b291d6a --- /dev/null +++ b/Backend/Services/Implement/SendLineNotifyService.cs @@ -0,0 +1,54 @@ +using BackendWorkerService.Services.Interface; +using Microsoft. Extensions.Logging; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; + +namespace BackendWorkerService.Services.Implement +{ + class SendLineNotifyService: ISendLineNotifyService + { + + private readonly ILogger logger; + + public SendLineNotifyService(ILogger logger) + { + this.logger = logger; + } + + public bool Send(int id, string lineToken, string message) + { + try + { + logger.LogInformation("【SendLineNotifyService】【Line Notify開始發送】[任務編號]:{0}", id); + HttpWebRequest Postrequest = (HttpWebRequest)WebRequest.Create("https://notify-api.line.me/api/notify?message=" + message); + Postrequest.Method = "POST"; + Postrequest.Headers.Add("Authorization", "Bearer " + lineToken); + Postrequest.PreAuthenticate = true; + HttpWebResponse response = (HttpWebResponse)Postrequest.GetResponse(); + var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd(); + var final = JObject.Parse(responseString); + var get = final["status"].ToString(); + if (get != "200") + { + logger.LogError("【SendLineNotifyService】【Line Notify發送失敗】[任務編號]:{0}", id); + logger.LogError("【SendLineNotifyService】【Line Notify發送失敗】[失敗內容]:{0}", responseString); + return false; + } + + logger.LogInformation("【SendLineNotifyService】【Line Notify發送成功】[任務編號]:{0}", id); + return true; + } + catch (Exception exception) + { + logger.LogError("【SendLineNotifyService】【Line Notify發送失敗】[任務編號]:{0}", id); + logger.LogError("【SendLineNotifyService】【Line Notify發送失敗】[Exception]:{0}", exception.ToString()); + throw; + //return false; + } + } + } +} diff --git a/Backend/Services/Implement/SendSMSService.cs b/Backend/Services/Implement/SendSMSService.cs new file mode 100644 index 0000000..97e4259 --- /dev/null +++ b/Backend/Services/Implement/SendSMSService.cs @@ -0,0 +1,84 @@ +using Backend.Models; +using BackendWorkerService.Services.Interface; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; +using System.Xml; + +namespace BackendWorkerService.Services.Implement +{ + class SendSMSService : ISendSMSService + { + private readonly ILogger logger; + private readonly IOptions _options; + + private SMSConfig sms; + + public SendSMSService(ILogger logger, IOptions options) + { + this.logger = logger; + sms = options.Value; + sms.Encoded = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(sms.UserName + ":" + sms.Password)); + } + + public bool Send(int id, string phone, string content) + { + try + { + logger.LogError("【SendSMSService】【SMS開始寄送】[任務編號]:{0}", id); + //設定傳送的Request + HttpWebRequest Postrequest = (HttpWebRequest)WebRequest.Create(sms.Api); + Postrequest.Method = "POST"; + Postrequest.Headers.Add("Authorization", "Basic " + sms.Encoded); + Postrequest.PreAuthenticate = true; + using (var streamWriter = new StreamWriter(Postrequest.GetRequestStream())) + { + string json = string.Format("", phone, content); + + streamWriter.Write(json); + } + + //設定回傳的Request + HttpWebResponse response = (HttpWebResponse)Postrequest.GetResponse(); + var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd(); + XmlDocument xmlDoc = new XmlDocument(); + xmlDoc.LoadXml(responseString); + string jsonText = JsonConvert.SerializeXmlNode(xmlDoc); + JObject resultVal = (JObject)JsonConvert.DeserializeObject(jsonText); + + if (resultVal.ContainsKey("obj")) + { + var display_split = resultVal["obj"]["@display"].ToString().Split(' '); + var responseStatus = display_split[display_split.Length - 1]; + + if (responseStatus == "{ok}") + { + logger.LogInformation("【SendSMSService】【SMS寄送成功】[任務編號]:{0}", id); + return true; + } + } + else + { + logger.LogError("【SendSMSService】【SMS寄送失敗】[任務編號]:{0}", id); + logger.LogError("【SendSMSService】【SMS寄送失敗】[失敗內容]:{0}", resultVal); + return false; + } + + return false; + } + catch (Exception exception) + { + logger.LogError("【SendSMSService】【SMS寄送失敗】[任務編號]:{0}", id); + logger.LogError("【SendSMSService】【SMS寄送失敗】[Exception]:{0}", exception.ToString()); + throw; + //return false; + } + } + } +} diff --git a/Backend/Services/Implement/TyphoonApi.cs b/Backend/Services/Implement/TyphoonApi.cs new file mode 100644 index 0000000..87a2e6d --- /dev/null +++ b/Backend/Services/Implement/TyphoonApi.cs @@ -0,0 +1,213 @@ +using System; +using System.Collections.Generic; +using System.Text; + +// +// +// To parse this JSON data, add NuGet 'Newtonsoft.Json' then do: +// +// using QuickType; +// +// var welcome = Welcome.FromJson(jsonString); + +namespace TyphoonApi +{ + using System; + using System.Collections.Generic; + + using System.Globalization; + using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + + public partial class Welcome + { + [JsonProperty("?xml")] + public Xml Xml { get; set; } + + [JsonProperty("alert")] + public Alert Alert { get; set; } + } + + public partial class Alert + { + [JsonProperty("@xmlns")] + public string Xmlns { get; set; } + + [JsonProperty("identifier")] + public string Identifier { get; set; } + + [JsonProperty("sender")] + public string Sender { get; set; } + + [JsonProperty("sent")] + public DateTimeOffset Sent { get; set; } + + [JsonProperty("status")] + public string Status { get; set; } + + [JsonProperty("msgType")] + public string MsgType { get; set; } + + [JsonProperty("scope")] + public string Scope { get; set; } + + [JsonProperty("references")] + public string References { get; set; } + + [JsonProperty("info")] + public Info Info { get; set; } + } + + public partial class Info + { + [JsonProperty("language")] + public string Language { get; set; } + + [JsonProperty("category")] + public string Category { get; set; } + + [JsonProperty("event")] + public string Event { get; set; } + + [JsonProperty("urgency")] + public string Urgency { get; set; } + + [JsonProperty("severity")] + public string Severity { get; set; } + + [JsonProperty("certainty")] + public string Certainty { get; set; } + + [JsonProperty("eventCode")] + public EventCode EventCode { get; set; } + + [JsonProperty("effective")] + public DateTimeOffset Effective { get; set; } + + [JsonProperty("onset")] + public DateTimeOffset Onset { get; set; } + + [JsonProperty("expires")] + public DateTimeOffset Expires { get; set; } + + [JsonProperty("senderName")] + public string SenderName { get; set; } + + [JsonProperty("headline")] + public string Headline { get; set; } + + [JsonProperty("description")] + public string Description { get; set; } + + [JsonProperty("instruction")] + public string Instruction { get; set; } + + [JsonProperty("web")] + public Uri Web { get; set; } + + [JsonProperty("parameter")] + public EventCode Parameter { get; set; } + + [JsonProperty("area")] + public List Area { get; set; } + } + + public partial class Area + { + [JsonProperty("areaDesc")] + public string AreaDesc { get; set; } + + [JsonProperty("geocode")] + public EventCode Geocode { get; set; } + } + + public partial class EventCode + { + [JsonProperty("valueName")] + public string ValueName { get; set; } + + [JsonProperty("value")] + public string Value { get; set; } + } + + public partial class Xml + { + [JsonProperty("@version")] + public string Version { get; set; } + + [JsonProperty("@encoding")] + public string Encoding { get; set; } + } + + public enum ValueName { AlertTitle, ProfileCapTwpEvent10, TaiwanGeocode103 }; + + public partial class Welcome + { + public static Welcome FromJson(string json) => JsonConvert.DeserializeObject(json, QuickType.Converter.Settings); + } + + public static class Serialize + { + public static string ToJson(this Welcome self) => JsonConvert.SerializeObject(self, QuickType.Converter.Settings); + } + + internal static class Converter + { + public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings + { + MetadataPropertyHandling = MetadataPropertyHandling.Ignore, + DateParseHandling = DateParseHandling.None, + Converters = + { + ValueNameConverter.Singleton, + new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal } + }, + }; + } + + internal class ValueNameConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(ValueName) || t == typeof(ValueName?); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + var value = serializer.Deserialize(reader); + switch (value) + { + case "Taiwan_Geocode_103": + return ValueName.TaiwanGeocode103; + case "alert_title": + return ValueName.AlertTitle; + case "profile:CAP-TWP:Event:1.0": + return ValueName.ProfileCapTwpEvent10; + } + throw new Exception("Cannot unmarshal type ValueName"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + if (untypedValue == null) + { + serializer.Serialize(writer, null); + return; + } + var value = (ValueName)untypedValue; + switch (value) + { + case ValueName.TaiwanGeocode103: + serializer.Serialize(writer, "Taiwan_Geocode_103"); + return; + case ValueName.AlertTitle: + serializer.Serialize(writer, "alert_title"); + return; + case ValueName.ProfileCapTwpEvent10: + serializer.Serialize(writer, "profile:CAP-TWP:Event:1.0"); + return; + } + throw new Exception("Cannot marshal type ValueName"); + } + + public static readonly ValueNameConverter Singleton = new ValueNameConverter(); + } +} diff --git a/Backend/Services/Interface/ISendEmailService.cs b/Backend/Services/Interface/ISendEmailService.cs new file mode 100644 index 0000000..a349ed7 --- /dev/null +++ b/Backend/Services/Interface/ISendEmailService.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace BackendWorkerService.Services.Interface +{ + public interface ISendEmailService + { + bool Send(int id, List recipientEmails, string subject, string content); + } +} diff --git a/Backend/Services/Interface/ISendLineNotifyService.cs b/Backend/Services/Interface/ISendLineNotifyService.cs new file mode 100644 index 0000000..295dd8c --- /dev/null +++ b/Backend/Services/Interface/ISendLineNotifyService.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace BackendWorkerService.Services.Interface +{ + public interface ISendLineNotifyService + { + bool Send(int id, string lineToken, string message); + } +} diff --git a/Backend/Services/Interface/ISendSMSService.cs b/Backend/Services/Interface/ISendSMSService.cs new file mode 100644 index 0000000..c414a93 --- /dev/null +++ b/Backend/Services/Interface/ISendSMSService.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace BackendWorkerService.Services.Interface +{ + public interface ISendSMSService + { + bool Send(int id, string phone, string message); + } +} diff --git a/Backend/Startup.cs b/Backend/Startup.cs index 37e1ba6..a85040b 100644 --- a/Backend/Startup.cs +++ b/Backend/Startup.cs @@ -1,6 +1,5 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.HttpsPolicy; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -16,15 +15,18 @@ using Repository.FrontendRepository.Interface; using Repository.Models; using System; -using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; using Backend.Jwt; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using System.Text; using System.IdentityModel.Tokens.Jwt; using Microsoft.AspNetCore.Http; +using Backend.Quartz.Jobs; +using Backend.Quartz; +using Quartz.Impl; +using Quartz.Spi; +using Quartz; namespace Backend { @@ -115,8 +117,16 @@ namespace Backend services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); + services.AddTransient(); #endregion Repository `J + #region K[QuartzA + services.AddTransient(); + services.AddTransient(); + services.AddHostedService(); + #endregion + #region JWT `J services.AddTransient(); services @@ -153,6 +163,20 @@ namespace Backend }); #endregion JWT `J + #region qk(]wC @) + services.AddSingleton(); + services.AddSingleton( + new JobSchedule(jobType: typeof(ArchiveElectricMeterDayJob), cronExpression: Configuration.GetValue("BackgroundServiceCron:ArchiveElectricMeterDayJob")) + ); + #endregion + + #region wɨoHAPI + services.AddSingleton(); + services.AddSingleton( + new JobSchedule(jobType: typeof(WeatherAPIJob), cronExpression: Configuration.GetValue("BackgroundServiceCron:WeatherAPIJob")) + ); + #endregion + double loginExpireMinute = this.Configuration.GetValue("LoginExpireMinute"); services.AddSession(options => { diff --git a/Backend/Views/UserInfo/Index.cshtml b/Backend/Views/UserInfo/Index.cshtml index 40cd35c..e894251 100644 --- a/Backend/Views/UserInfo/Index.cshtml +++ b/Backend/Views/UserInfo/Index.cshtml @@ -814,7 +814,7 @@ if (rel.data.length > 0) { $('#select_building').empty(); $.each(rel.data, function (index, val) { - $("#select_building").append($("