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/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