[WebApi]緊急應變事件查詢頁面修改,只要發生過就會有紀錄

[BGService]新增緊急應變事件紀錄定期存DB排程
This commit is contained in:
張家睿 2024-06-12 16:29:20 +08:00
parent 877055440c
commit c147f79e96
12 changed files with 1412 additions and 77 deletions

View File

@ -156,6 +156,13 @@ namespace BackendWorkerService
); );
#endregion #endregion
#region
services.AddSingleton<EmergencyRecordJob>();
services.AddSingleton(
new JobSchedule(jobType: typeof(EmergencyRecordJob), cronExpression: configuration.GetValue<string>("BackgroundServiceCron:EmergencyRecordJob "))
);
#endregion
}).ConfigureLogging((hostContext, logFactory) => { }).ConfigureLogging((hostContext, logFactory) => {
IConfiguration configuration = hostContext.Configuration; IConfiguration configuration = hostContext.Configuration;

View File

@ -0,0 +1,156 @@
using FrontendWebApi.Models;
using Microsoft.Extensions.Logging;
using Quartz;
using Repository.BackendRepository.Interface;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace BackendWorkerService.Quartz.Jobs
{
[DisallowConcurrentExecution]
class EmergencyRecordJob : IJob
{
private readonly ILogger<EmergencyRecordJob> logger;
private readonly IBackendRepository backendRepository;
private readonly ILogger<Task_Detail> loggers;
public EmergencyRecordJob(ILogger<EmergencyRecordJob> logger, IBackendRepository backendRepository, ILogger<Task_Detail> loggers)
{
this.logger = logger;
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("EmergencyRecordJob", "EmergencyRecord"))
{
try
{
await task_Detail.InsertWorkTime("EmergencyRecordJob", "EmergencyRecord");
var sqlString = $@"SELECT system_value FROM variable WHERE system_type = 'obixConfig' AND system_key = 'ApiBase' AND deleted = 0";
string baseApiUrl = await backendRepository.GetOneAsync<string>(sqlString);
sqlString = $@"SELECT system_value FROM variable WHERE system_type = 'emergencyConfig' AND system_key = 'getNiagaraAlarmMoveDayNumStart' AND deleted = 0";
int moveDayNum = int.Parse((await backendRepository.GetOneAsync<string>(sqlString)) ?? "-1");
string apiUrl = Path.Combine(baseApiUrl, "obix/config/Services/AlarmService/~alarmQuery/");
var device_disaster = await backendRepository.GetAllAsync<DeviceDisaster>("device_disaster","");
using (HttpClient client = new HttpClient())
{
string username = "stanGG";
string password = "St12345678";
string encoded = Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(username + ":" + password));
client.DefaultRequestHeaders.Add("Authorization", "Basic " + encoded);
// 建構 XML 數據
string xmlData = @$"<obj href='obix:AlarmFilter'>
<abstime name='start' val='{DateTime.Now.AddDays(moveDayNum).ToString("yyyy-MM-ddTHH:mm:ss.fff")}+08:00' />
<abstime name='end' val='{DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.fff")}+08:00'/>
</obj>";
HttpContent content = new StringContent(xmlData, Encoding.UTF8, "application/xml");
var response = await client.PostAsync(apiUrl, content);
var resString = (await response.Content.ReadAsStringAsync()).ToString();
XDocument xmlDoc = XDocument.Parse(resString);
var list = xmlDoc.Descendants().Where(d => d.Name?.LocalName == "list").FirstOrDefault();
var objs = list.Descendants().Where(d => d.Name?.LocalName == "obj" && (d.Attribute("is")?.Value?.Contains("obix:Alarm") ?? false)).ToList();
var dictionaryList = new List<Dictionary<string, object>>();
List<string> filterList = device_disaster.Select(x => x.device_number).ToList();
var filteredObjs = objs.Where(obj =>
{
var deviceNumber = obj.Descendants().Where(d => d.Name.LocalName == "str" && d.Attribute("name").Value == "sourceName")
.Select(d => d.Attribute("val").Value)
.Select(d => string.Join("_", d.Split("_").Take(5)))
.FirstOrDefault();
return filterList.Contains(deviceNumber);
}).ToList();
foreach (var obj in filteredObjs)
{
var newguid = Guid.NewGuid();
var alarm_timestamp = obj.Descendants().Where(d => d.Name.LocalName == "abstime" && d.Attribute("name").Value == "timestamp")
.Select(d =>
{
DateTime valid;
if (DateTime.TryParse(d.Attribute("val").Value, out valid))
{
return DateTime.Parse(d.Attribute("val").Value).ToString("yyyy-MM-dd HH:mm:ss");
}
else
{
return null;
}
}).FirstOrDefault();
var device_number = obj.Descendants().Where(d => d.Name.LocalName == "str" && d.Attribute("name").Value == "sourceName")
.Select(d => d.Attribute("val").Value)
.Select(d => string.Join("_", d.Split("_").Take(5)))
.FirstOrDefault();
var filterDevice = device_disaster.Where(x => x.device_number == device_number).FirstOrDefault();
var dictionary = new Dictionary<string, object>()
{
{"@emergency_event_guid",newguid},
{"@disaster",filterDevice.device_system_value},
{"@building_tag", filterDevice.device_building_tag},
{"@device_guid",filterDevice.device_guid },
{"@type",0},
{"@alarm_time",alarm_timestamp}
};
dictionaryList.Add(dictionary);
}
var mergeSql = @"
INSERT INTO emergency_event
(emergency_event_guid, disaster, device_guid, building_tag, type, alarm_time)
VALUES
(@emergency_event_guid, @disaster, @device_guid, @building_tag, @type, @alarm_time)
ON DUPLICATE KEY UPDATE
disaster = VALUES(disaster),
building_tag = VALUES(building_tag),
type = VALUES(type)";
await backendRepository.ExecuteSql(mergeSql, dictionaryList);
}
await task_Detail.InsertWorkTime_End("EmergencyRecordJob", "EmergencyRecord");
}
catch (Exception ex)
{
await task_Detail.WorkFail("EmergencyRecordJob", "EmergencyRecord", ex.Message.ToString());
}
}
}
catch (Exception exception)
{
logger.LogError("【EmergencyRecordJob】【任務失敗】");
logger.LogError("【EmergencyRecordJob】【任務失敗】[Exception]{0}", exception.ToString());
}
}
}
}

View File

@ -16,6 +16,7 @@
"ArchiveElectricMeterHourJob": "0 0 2 * * ?", "ArchiveElectricMeterHourJob": "0 0 2 * * ?",
"ArchiveElectricMeterDayJob": "0/5 * * * * ?", "ArchiveElectricMeterDayJob": "0/5 * * * * ?",
"LightScheduleJob ": "0/5 * * * * ?", "LightScheduleJob ": "0/5 * * * * ?",
"EmergencyRecordJob ": "0/5 * * * * ?",
"WeatherAPIJob": "0/5 * * * * ?" "WeatherAPIJob": "0/5 * * * * ?"
}, },
"DBConfig": { "DBConfig": {

View File

@ -17,6 +17,7 @@
"ArchiveElectricMeterHourJob": "0 0 2 * * ?", "ArchiveElectricMeterHourJob": "0 0 2 * * ?",
"ArchiveElectricMeterDayJob": "0/5 * * * * ?", "ArchiveElectricMeterDayJob": "0/5 * * * * ?",
"LightScheduleJob ": "0 0/1 * * * ?", "LightScheduleJob ": "0 0/1 * * * ?",
"EmergencyRecordJob ": "0 0/1 * * * ?",
"WeatherAPIJob": "0 0 2 * * ?" "WeatherAPIJob": "0 0 2 * * ?"
}, },
"DBConfig": { "DBConfig": {

View File

@ -121,7 +121,7 @@ namespace FrontendWebApi.ApiControllers
DateTime valid; DateTime valid;
if (DateTime.TryParse(d.Attribute("val").Value, out valid)) if (DateTime.TryParse(d.Attribute("val").Value, out valid))
{ {
return DateTime.Parse(d.Attribute("val").Value).ToString("yyyy-MM-dd HH:mm:ss.fff"); return DateTime.Parse(d.Attribute("val").Value).ToString("yyyy-MM-dd HH:mm:ss");
} }
else else
{ {

View File

@ -6,6 +6,7 @@ using Newtonsoft.Json;
using Repository.BackendRepository.Interface; using Repository.BackendRepository.Interface;
using Repository.FrontendRepository.Interface; using Repository.FrontendRepository.Interface;
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data.SqlTypes; using System.Data.SqlTypes;
using System.Diagnostics; using System.Diagnostics;
@ -256,6 +257,30 @@ namespace FrontendWebApi.ApiControllers
} }
try try
{ {
if (eventpost.emergency_event_guid != null)
{
var dictionary = new Dictionary<string, object>()
{
{"@finish_time",DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}
};
await backendRepository.UpdateOneByCustomTable(dictionary, "emergency_event", $"emergency_event_guid = '{eventpost.emergency_event_guid}'");
apiResult.Code = "0000";
apiResult.Msg = "更新完成時間";
}
else
{
// 檢查是否有該紀錄
var sql = "SELECT * FROM emergency_event WHERE alarm_time = @alarmTime AND device_guid = @deviceGuid";
var count = await backendRepository.GetOneAsync<EmergencyRecordEvent>(sql, new { alarmTime = eventpost.alarm_time, deviceGuid = eventpost.device });
if (count != null)
{
apiResult.Code = "0000";
apiResult.Msg = "已有該筆紀錄";
apiResult.Data = count.emergency_event_guid;
return Ok(apiResult);
}
var newguid = Guid.NewGuid(); var newguid = Guid.NewGuid();
var dictionary = new Dictionary<string, object>() var dictionary = new Dictionary<string, object>()
{ {
@ -263,12 +288,14 @@ namespace FrontendWebApi.ApiControllers
{"@disaster",eventpost.disaster}, {"@disaster",eventpost.disaster},
{"@building_tag", eventpost.build}, {"@building_tag", eventpost.build},
{"@device_guid",eventpost.device }, {"@device_guid",eventpost.device },
{"@type",eventpost.type} {"@type",eventpost.type},
{"@alarm_time",eventpost.alarm_time}
}; };
await backendRepository.AddOneByCustomTable(dictionary, "emergency_event"); await backendRepository.AddOneByCustomTable(dictionary, "emergency_event");
apiResult.Data = newguid.ToString(); apiResult.Data = newguid.ToString();
apiResult.Code = "0000"; apiResult.Code = "0000";
} }
}
catch (Exception exception) catch (Exception exception)
{ {
apiResult.Code = "9999"; apiResult.Code = "9999";

View File

@ -71,7 +71,7 @@ namespace FrontendWebApi.ApiControllers
} }
EmergencyRecordEvent = await backendRepository.GetAllAsync<EmergencyRecordEventTable>($@" EmergencyRecordEvent = await backendRepository.GetAllAsync<EmergencyRecordEventTable>($@"
select d.device_number device_name,v.system_key disaster_name,ee.*,b.full_name building_name from emergency_event ee select d.device_number device_name,v.system_key disaster_name,ee.*,b.full_name building_name,ee.alarm_time,ee.finish_time from emergency_event ee
left join (select * from variable v where v.system_type = 'disaster') v on v.system_value = ee.disaster left join (select * from variable v where v.system_type = 'disaster') v on v.system_value = ee.disaster
left join device d on d.device_guid = ee.device_guid left join device d on d.device_guid = ee.device_guid
left join building b on b.building_tag = ee.building_tag left join building b on b.building_tag = ee.building_tag

View File

@ -113,10 +113,13 @@ namespace FrontendWebApi.Models
public class Eventpost public class Eventpost
{ {
public string emergency_event_guid { get; set; }
public int disaster { get; set; } public int disaster { get; set; }
public string build { get; set; } public string build { get; set; }
public byte type { get; set; } public byte type { get; set; }
public string device { get; set; } public string device { get; set; }
public string alarm_time { get; set; } = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
} }
public class EmergencySetting public class EmergencySetting

View File

@ -19,11 +19,15 @@ namespace FrontendWebApi.Models
public class EmergencyRecordEvent : Actor public class EmergencyRecordEvent : Actor
{ {
private string _alarm_time;
private string _finish_time;
public string emergency_event_guid { get; set; } public string emergency_event_guid { get; set; }
public int disaster { get; set; } public int disaster { get; set; }
public string device_guid { get; set; } public string device_guid { get; set; }
public string building_guid { get; set; } public string building_guid { get; set; }
public byte type { get; set; } public byte type { get; set; }
public string alarm_time { get { return Convert.ToDateTime(_alarm_time).ToString("yyyy-MM-dd HH:mm:ss"); } set { _alarm_time = value; } }
public string finish_time { get { return Convert.ToDateTime(_finish_time).ToString("yyyy-MM-dd HH:mm:ss"); } set { _finish_time = value; } }
} }
public class EmergencyRecordEventTable : EmergencyRecordEvent public class EmergencyRecordEventTable : EmergencyRecordEvent

View File

@ -1,12 +1,12 @@
@{ @{
ViewData["MainNum"] = "4"; ViewData["MainNum"] = "4";
ViewData["SubNum"] = "1"; ViewData["SubNum"] = "1";
ViewData["Title"] = "設備總覽"; ViewData["Title"] = "事件清單";
} }
<ol class="breadcrumb page-breadcrumb"> <ol class="breadcrumb page-breadcrumb">
<li class="breadcrumb-item"><a href="javascript:void(0);">首頁</a></li> <li class="breadcrumb-item"><a href="javascript:void(0);">首頁</a></li>
<li class="breadcrumb-item active">設備總覽</li> <li class="breadcrumb-item active">事件清單</li>
<li class="position-absolute pos-top pos-right d-none d-sm-block"><span class="js-get-date"></span></li> <li class="position-absolute pos-top pos-right d-none d-sm-block"><span class="js-get-date"></span></li>
</ol> </ol>
@ -94,7 +94,10 @@
@*<button type="button" class="close p-sm-2 p-md-4 text-dark fs-xxl position-absolute pos-right mr-sm-2 mt-sm-1 z-index-space" data-dismiss="modal" aria-label="Close"> @*<button type="button" class="close p-sm-2 p-md-4 text-dark fs-xxl position-absolute pos-right mr-sm-2 mt-sm-1 z-index-space" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true"><i class="fal fa-times"></i></span> <span aria-hidden="true"><i class="fal fa-times"></i></span>
</button>*@ </button>*@
<button type="button" class=" position-absolute pos-right mr-sm-3 mt-sm-3 z-index-space btn btn-danger waves-effect waves-themed" id="closemodal" onclick="Closemodal()">關閉流程</button> <div class="position-absolute pos-right mr-sm-3 mt-sm-3 z-index-space">
@*<button type="button" class="btn btn-danger waves-effect waves-themed mr-2" id="Tcp" onclick="SendMessageAsync('D002')">取消上牆</button>*@
<button type="button" class="btn btn-danger waves-effect waves-themed" id="closemodal" onclick="Closemodal()">關閉流程</button>
</div>
<div class="modal-body bg-white"> <div class="modal-body bg-white">
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
@ -621,6 +624,7 @@
'createdRow': function (row, data, dataIndex) { 'createdRow': function (row, data, dataIndex) {
$(row).attr('data-guid', data.device_guid); $(row).attr('data-guid', data.device_guid);
$(row).attr('data-disaster', data.disaster); $(row).attr('data-disaster', data.disaster);
$(row).attr('data-time', data.alarm_timestamp);
}, },
"ajax": { "ajax": {
"url": "/api/EmergencyDevice", "url": "/api/EmergencyDevice",
@ -651,7 +655,7 @@
enable_alarm_timer = true; enable_alarm_timer = true;
is_need_reload = false; is_need_reload = false;
// 如果是告警頁面則會進行設備清單跟alarm設備的比對
if (show_mode == 'alarm') { if (show_mode == 'alarm') {
backfill_building_alarm_device_amount = []; backfill_building_alarm_device_amount = [];
backfill_layer2_alarm_device_amount = []; backfill_layer2_alarm_device_amount = [];
@ -748,9 +752,10 @@
selected_device_guid = $(this).parents('tr').attr('data-guid'); selected_device_guid = $(this).parents('tr').attr('data-guid');
selected_device_disaster = $(this).parents('tr').attr('data-disaster'); selected_device_disaster = $(this).parents('tr').attr('data-disaster');
selected_device_time = $(this).parents('tr').attr('data-time');
OnSimulationExercise(selected_device_guid, selected_device_disaster, 0); OnSimulationExercise(selected_device_guid, selected_device_disaster, 0, selected_device_time);
// SendMessageAsync("D001");
}); });
//#endregion //#endregion
@ -1389,7 +1394,7 @@
//#endregion //#endregion
//#region 模擬演練 / SOP 開啟畫面 //#region 模擬演練 / SOP 開啟畫面
function OnSimulationExercise(guid, disaster, type) { function OnSimulationExercise(guid, disaster, type ,time) {
if (type === 1 && SelectedFilter.disasters.length > 1) { if (type === 1 && SelectedFilter.disasters.length > 1) {
toast_warning("防災類別只能單選,請修改防災類別"); toast_warning("防災類別只能單選,請修改防災類別");
@ -1415,6 +1420,7 @@
build: SelectedFilter.building_tag[0] ?? null, build: SelectedFilter.building_tag[0] ?? null,
disaster: disaster, disaster: disaster,
type: type, type: type,
alarm_time: time,
device: guid device: guid
}; };
$.post(url, send_data, function (rel) { $.post(url, send_data, function (rel) {
@ -1433,6 +1439,49 @@
} }
//#endregion //#endregion
//#region 更新事件完成時間
function UpdateRecordFinishTime() {
var url = "/api/EmergencyDevice/SaveAndOpenSimulationExercise";
var send_data = {
emergency_event_guid: SimulationExerciseGuid
};
$.post(url, send_data, function (rel) {
if (rel.code != "0000") {
if (rel.code == "9999") {
toast_error(rel.msg);
}
return;
}
else {
return;
}
}, 'json');
}
//#endregion
//#region 發送電視牆訊號
function SendMessageAsync(message) {
var url = "/api/Send";
var send_data = {
message: message
};
$.post(url, send_data, function (rel) {
if (rel.code != "0000") {
if (rel.code == "9999") {
toast_error("電視牆送訊號" + message + "失敗");
}
return;
}
else {
return;
}
}, 'json');
}
//#endregion
//#region 模擬演練 / SOP modal 取得組別 //#region 模擬演練 / SOP modal 取得組別
function GetbuttonList(selected_disaster) { function GetbuttonList(selected_disaster) {
var url = "/api/EmergencyDevice/GetGroupingList"; var url = "/api/EmergencyDevice/GetGroupingList";
@ -1805,7 +1854,7 @@
if (tablist == undefined) { if (tablist == undefined) {
$('#button_' + EmerNowsetting).find('button').attr('disabled', true); $('#button_' + EmerNowsetting).find('button').attr('disabled', true);
CloseVerify = true; // CloseVerify = true;
//$('#closemodal').attr('disabled', false); //$('#closemodal').attr('disabled', false);
} else { } else {
$('#' + tablist).trigger('click'); $('#' + tablist).trigger('click');
@ -1903,6 +1952,9 @@
CloseVerify = true; CloseVerify = true;
$('#emergency-verify-modal').modal('hide'); $('#emergency-verify-modal').modal('hide');
$('#SimulationExercisemodal').modal('hide'); $('#SimulationExercisemodal').modal('hide');
// SendMessageAsync("D002");
UpdateRecordFinishTime()
CloseVerify = false; // 每次關閉流程都要驗證一次
} else { } else {
toast_warning('密碼驗證失敗,請重新驗證'); toast_warning('密碼驗證失敗,請重新驗證');
} }

View File

@ -359,56 +359,56 @@
}) })
//#region 定時取得報警設備(3s) //#region 定時取得報警設備(3s)
interval_id = setInterval(function () { // interval_id = setInterval(function () {
if (!enable_alarm_timer) { // if (!enable_alarm_timer) {
return; // return;
} // }
var url = "/api/Device/Getalarm"; // var url = "/api/Device/Getalarm";
$.post(url, null, function (rel) { // $.post(url, null, function (rel) {
if (rel.code != "0000") { // if (rel.code != "0000") {
if (rel.code == "9999") { // if (rel.code == "9999") {
toast_error(rel.msg); // toast_error(rel.msg);
} // }
else { // else {
toast_warning(rel.msg); // toast_warning(rel.msg);
} // }
return; // return;
} // }
else { // else {
var is_diff = false; // var is_diff = false;
if (Object.keys(temp_alarm_device).length == Object.keys(rel.data.alarmorion).length) { // if (Object.keys(temp_alarm_device).length == Object.keys(rel.data.alarmorion).length) {
for (var i = 0; i < Object.keys(rel.data.alarmorion).length; i++) { // for (var i = 0; i < Object.keys(rel.data.alarmorion).length; i++) {
var index = temp_alarm_device.findIndex(x => x.alarm_timestamp == rel.data.alarmorion[i].alarm_timestamp // var index = temp_alarm_device.findIndex(x => x.alarm_timestamp == rel.data.alarmorion[i].alarm_timestamp
&& x.device_number == rel.data.alarmorion[i].device_number) // && x.device_number == rel.data.alarmorion[i].device_number)
if (index > -1) { // if (index > -1) {
is_diff = false; // is_diff = false;
} else { // } else {
is_diff = true; // is_diff = true;
} // }
} // }
} else { // } else {
is_diff = true; // is_diff = true;
} // }
temp_alarm_device = rel.data.alarmorion; // temp_alarm_device = rel.data.alarmorion;
if (show_mode == "alarm" && (is_need_reload || is_diff)) { // if (show_mode == "alarm" && (is_need_reload || is_diff)) {
enable_alarm_timer = false; //關閉查詢異常設備,避免重複呼叫 // enable_alarm_timer = false; 關閉查詢異常設備,避免重複呼叫
$("#building").find(".building_device_amount").html(0); // $("#building").find(".building_device_amount").html(0);
rel.data.buildingAlarmDeviceAmount.forEach(function (item) { // rel.data.buildingAlarmDeviceAmount.forEach(function (item) {
$(`#${item.building_tag}_device_amount`).html(item.device_amount); // $(`#${item.building_tag}_device_amount`).html(item.device_amount);
}); // });
ResetDeviceTable(); // ResetDeviceTable();
} // }
} // }
}, 'json'); // }, 'json');
}, 3000); // }, 3000);
//#endregion //#endregion
//#region 模擬演練 / SOP modal 聯絡清單Table //#region 模擬演練 / SOP modal 聯絡清單Table

File diff suppressed because it is too large Load Diff