1. 加入Quartz

2. 地圖總覽
This commit is contained in:
Kai 2021-07-01 09:51:33 +08:00
parent f7e2e9c123
commit 18e4fa4b57
12 changed files with 530 additions and 183 deletions

View File

@ -0,0 +1,25 @@
using Microsoft.AspNetCore.Mvc;
using SolarPower.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace SolarPower.Controllers
{
public class MapOverviewController : Controller
{
public IActionResult Index()
{
return View();
}
//public async Task<ApiResult<MapOverview>> GetAllDate()
//{
// ApiResult<MapOverview> apiResult = new ApiResult<MapOverview>();
//}
}
}

View File

@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace SolarPower.Models
{
public class MapOverview
{
}
}

View File

@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
namespace SolarPower.Quartz
{
/// <summary>
/// Job調度中間對象
/// </summary>
public class JobSchedule
{
public JobSchedule(Type jobType, string cronExpression)
{
this.JobType = jobType ?? throw new ArgumentNullException(nameof(jobType));
CronExpression = cronExpression ?? throw new ArgumentNullException(nameof(cronExpression));
}
/// <summary>
/// Job類型
/// </summary>
public Type JobType { get; private set; }
/// <summary>
/// Cron表達式
/// </summary>
public string CronExpression { get; private set; }
/// <summary>
/// Job狀態
/// </summary>
public JobStatus JobStatu { get; set; } = JobStatus.Init;
}
/// <summary>
/// Job運行狀態
/// </summary>
public enum JobStatus : byte
{
[Description("初始化")]
Init = 0,
[Description("運行中")]
Running = 1,
[Description("調度中")]
Scheduling = 2,
[Description("已停止")]
Stopped = 3,
}
}

View File

@ -0,0 +1,148 @@
using Microsoft.Extensions.Logging;
using Quartz;
using SolarPower.Models;
using SolarPower.Repository.Interface;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace SolarPower.Quartz.Jobs
{
[DisallowConcurrentExecution]
public class OperationScheduleJob : IJob
{
private readonly ILogger<OperationScheduleJob> logger;
private IOperationRepository operationRepository;
public OperationScheduleJob(ILogger<OperationScheduleJob> logger, IOperationRepository operationRepository)
{
this.logger = logger;
this.operationRepository = operationRepository;
}
public async Task Execute(IJobExecutionContext context)
{
try
{
var getTime = await operationRepository.GetOperationSchedules();
foreach (var a in getTime)
{
DateTime Updatedtime;
if (a.ScheduleType == 0)//日
{
Updatedtime = Convert.ToDateTime(a.StartTime).AddDays(a.ScheduleNum);
}
else if (a.ScheduleType == 1)//周
{
Updatedtime = Convert.ToDateTime(a.StartTime).AddDays(a.ScheduleNum * 7);
}
else if (a.ScheduleType == 2)//月
{
Updatedtime = Convert.ToDateTime(a.StartTime).AddMonths(a.ScheduleNum);
}
else if (a.ScheduleType == 3)//季
{
Updatedtime = Convert.ToDateTime(a.StartTime).AddMonths(a.ScheduleNum * 3);
}
else // 年
{
Updatedtime = Convert.ToDateTime(a.StartTime).AddYears(a.ScheduleNum);
}
if (Updatedtime < DateTime.Now)
{
var now = DateTime.Now.ToString("yyyy-MM-dd");
var finalid = await operationRepository.GetCurrentSerialNumber("operation_plan_create", $"PowerStationId = {a.PowerStationId} AND CreatedAt LIKE '%{now}%'");
var newSerialNumber = GetLastSerialNumber(finalid);
var OperationPlan = new OperationCreatePlan()
{
EmailType = a.EmailType,
ScheduleNum = a.ScheduleNum,
Description = a.Description,
WorkDay = a.WorkDay,
ScheduleType = a.ScheduleType,
SerialNumber = newSerialNumber,
StartTime = Updatedtime.ToString("yyyy-MM-dd hh:mm:ss"),
PowerStationId = a.PowerStationId,
Type = a.Type,
PlanId = DateTime.Now.ToString("yyyyMMdd") + newSerialNumber,
CreatedBy = a.CreatedBy
};
List<string> properties = new List<string>()
{
"EmailType",
"ScheduleNum",
"Description",
"WorkDay",
"ScheduleType",
"SerialNumber",
"StartTime",
"PowerStationId",
"Type",
"PlanId",
"CreatedBy"
};
await operationRepository.AddOperationPlan(OperationPlan, properties);
var record = new PlanToRecord()
{
WorkType = a.Type,
PowerStationId = a.PowerStationId,
StartTime = Updatedtime.ToString("yyyy-MM-dd hh:mm:ss"),
CreatedBy = a.CreatedBy,
EndTime = Updatedtime.AddDays(a.WorkDay).ToString("yyyy-MM-dd hh:mm:ss")
};
List<string> properties2 = new List<string>()
{
"WorkType",
"PowerStationId",
"StartTime",
"CreatedBy",
"EndTime"
};
await operationRepository.AddToRecord(record, properties2);
var operation = await operationRepository.GetOneOperation(a.Id);
await operationRepository.DeleteOneByIdWithCustomTable(a.Id, "operation_plan_create");
}
}
}
catch (Exception exception)
{
logger.LogError("【{0}】{1}", nameof(logger), exception.Message);
}
}
/// <summary>
/// 取得最新的流水號
/// </summary>
/// <param name="current">當前的</param>
/// <param name="pad"></param>
/// <param name="direction">0: PadLeft1: PadRight</param>
/// <returns></returns>
public string GetLastSerialNumber(string current = "", int pad = 4, byte direction = 0)
{
var tempSerialNumber = 0;
if (!string.IsNullOrEmpty(current))
{
tempSerialNumber = Convert.ToInt32(current) + 1;
}
else
{
tempSerialNumber = 1;
}
if (direction == 0)
{
return tempSerialNumber.ToString().Trim().PadLeft(pad, '0');
}
else
{
return tempSerialNumber.ToString().Trim().PadRight(pad, '0');
}
}
}
}

View File

@ -0,0 +1,27 @@
using Microsoft.Extensions.DependencyInjection;
using Quartz;
using Quartz.Spi;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace SolarPower.Quartz
{
public class SingletonJobFactory: IJobFactory
{
private readonly IServiceProvider _serviceProvider;
public SingletonJobFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
return _serviceProvider.GetRequiredService(bundle.JobDetail.JobType) as IJob;
}
public void ReturnJob(IJob job)
{
}
}
}

View File

@ -1,179 +0,0 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using SolarPower.Models;
using SolarPower.Repository.Interface;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace SolarPower.Services.Implement
{
public class OperationScheduleBackgroundService : IHostedService, IDisposable
{
static Timer _timer;
private int time_interval = 30; //查詢間隔(秒)
private readonly ILogger<OperationScheduleBackgroundService> _log;
private int execCount = 0;
private readonly IOperationRepository operationRepository;
public OperationScheduleBackgroundService(ILogger<OperationScheduleBackgroundService> log,
IOperationRepository operationRepository
)
{
this.operationRepository = operationRepository;
this._log = log;
}
public void Dispose()
{
_timer?.Dispose();
}
public Task StartAsync(CancellationToken cancellationToken)
{
_timer = new Timer(DoWork, null,
TimeSpan.Zero,
TimeSpan.FromSeconds(time_interval));
return Task.CompletedTask;
}
public async void DoWork(object state)
{
//利用 Interlocked 計數防止重複執行
if (execCount <= 0)
{
Interlocked.Increment(ref execCount);
}
if (execCount == 1)
{
try
{
var getTime = await operationRepository.GetOperationSchedules();
foreach (OperationCreatePlanModal a in getTime)
{
DateTime Updatedtime;
if (a.ScheduleType == 0)//日
{
Updatedtime = Convert.ToDateTime(a.StartTime).AddDays(a.ScheduleNum);
}
else if (a.ScheduleType == 1)//周
{
Updatedtime = Convert.ToDateTime(a.StartTime).AddDays(a.ScheduleNum * 7);
}
else if (a.ScheduleType == 2)//月
{
Updatedtime = Convert.ToDateTime(a.StartTime).AddMonths(a.ScheduleNum);
}
else if (a.ScheduleType == 3)//季
{
Updatedtime = Convert.ToDateTime(a.StartTime).AddMonths(a.ScheduleNum * 3);
}
else // 年
{
Updatedtime = Convert.ToDateTime(a.StartTime).AddYears(a.ScheduleNum);
}
if (Updatedtime < DateTime.Now)
{
var now = DateTime.Now.ToString("yyyy-MM-dd");
var finalid = await operationRepository.GetCurrentSerialNumber("operation_plan_create", $"PowerStationId = {a.PowerStationId} AND CreatedAt LIKE '%{now}%'");
var newSerialNumber = GetLastSerialNumber(finalid);
var OperationPlan = new OperationCreatePlan()
{
EmailType = a.EmailType,
ScheduleNum = a.ScheduleNum,
Description = a.Description,
WorkDay = a.WorkDay,
ScheduleType = a.ScheduleType,
SerialNumber = newSerialNumber,
StartTime = Updatedtime.ToString("yyyy-MM-dd hh:mm:ss"),
PowerStationId = a.PowerStationId,
Type = a.Type,
PlanId = DateTime.Now.ToString("yyyyMMdd") + newSerialNumber,
CreatedBy = a.CreatedBy
};
List<string> properties = new List<string>()
{
"EmailType",
"ScheduleNum",
"Description",
"WorkDay",
"ScheduleType",
"SerialNumber",
"StartTime",
"PowerStationId",
"Type",
"PlanId",
"CreatedBy"
};
await operationRepository.AddOperationPlan(OperationPlan, properties);
var record = new PlanToRecord()
{
WorkType = a.Type,
PowerStationId = a.PowerStationId,
StartTime = Updatedtime.ToString("yyyy-MM-dd hh:mm:ss"),
CreatedBy = a.CreatedBy,
EndTime = Updatedtime.AddDays(a.WorkDay).ToString("yyyy-MM-dd hh:mm:ss")
};
List<string> properties2 = new List<string>()
{
"WorkType",
"PowerStationId",
"StartTime",
"CreatedBy",
"EndTime"
};
await operationRepository.AddToRecord(record, properties2);
var operation = await operationRepository.GetOneOperation(a.Id);
await operationRepository.DeleteOneByIdWithCustomTable(a.Id, "operation_plan_create");
}
}
}
catch (Exception ex)
{
_log.LogError("【OperationScheduleService】 - " + ex.Message);
}
finally
{
Interlocked.Decrement(ref execCount);
}
}
}
public string GetLastSerialNumber(string current = "", int pad = 4, byte direction = 0)
{
var tempSerialNumber = 0;
if (!string.IsNullOrEmpty(current))
{
tempSerialNumber = Convert.ToInt32(current) + 1;
}
else
{
tempSerialNumber = 1;
}
if (direction == 0)
{
return tempSerialNumber.ToString().Trim().PadLeft(pad, '0');
}
else
{
return tempSerialNumber.ToString().Trim().PadRight(pad, '0');
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
//調整Timer為永不觸發停用定期排程
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
}
}

View File

@ -0,0 +1,71 @@
using Microsoft.Extensions.Hosting;
using Quartz;
using Quartz.Spi;
using SolarPower.Models;
using SolarPower.Quartz;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace SolarPower.Services.Implement
{
public class QuartzHostedService : IHostedService
{
private readonly ISchedulerFactory _schedulerFactory;
private readonly IJobFactory _jobFactory;
private readonly IEnumerable<JobSchedule> _jobSchedules;
public QuartzHostedService(ISchedulerFactory schedulerFactory, IJobFactory jobFactory, IEnumerable<JobSchedule> jobSchedules)
{
_schedulerFactory = schedulerFactory ?? throw new ArgumentNullException(nameof(schedulerFactory));
_jobFactory = jobFactory ?? throw new ArgumentNullException(nameof(jobFactory));
_jobSchedules = jobSchedules ?? throw new ArgumentNullException(nameof(jobSchedules));
}
public IScheduler Scheduler { get; set; }
public async Task StartAsync(CancellationToken cancellationToken)
{
Scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
Scheduler.JobFactory = _jobFactory;
foreach (var jobSchedule in _jobSchedules)
{
var job = CreateJob(jobSchedule);
var trigger = CreateTrigger(jobSchedule);
await Scheduler.ScheduleJob(job, trigger, cancellationToken);
jobSchedule.JobStatu = JobStatus.Scheduling;
}
await Scheduler.Start(cancellationToken);
foreach (var jobSchedule in _jobSchedules)
{
jobSchedule.JobStatu = JobStatus.Running;
}
}
public async Task StopAsync(CancellationToken cancellationToken)
{
await Scheduler?.Shutdown(cancellationToken);
foreach (var jobSchedule in _jobSchedules)
{
jobSchedule.JobStatu = JobStatus.Stopped;
}
}
private static IJobDetail CreateJob(JobSchedule schedule)
{
var jobType = schedule.JobType;
return JobBuilder
.Create(jobType)
.WithIdentity(jobType.FullName)
.WithDescription(jobType.Name)
.Build();
}
private static ITrigger CreateTrigger(JobSchedule schedule)
{
return TriggerBuilder
.Create()
.WithIdentity($"{schedule.JobType.FullName}.trigger")
.WithCronSchedule(schedule.CronExpression)
.WithDescription(schedule.CronExpression)
.Build();
}
}
}

View File

@ -1,4 +1,5 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -36,7 +37,7 @@ namespace SolarPower.Services.Implement
Interlocked.Increment(ref execCount); Interlocked.Increment(ref execCount);
} }
if (execCount == 1 && send_complete) if (execCount == 1)
{ {
try try
{ {
@ -58,5 +59,10 @@ namespace SolarPower.Services.Implement
_timer?.Change(Timeout.Infinite, 0); _timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask; return Task.CompletedTask;
} }
public void Dispose()
{
throw new NotImplementedException();
}
} }
} }

View File

@ -27,6 +27,7 @@
<PackageReference Include="Dapper" Version="2.0.78" /> <PackageReference Include="Dapper" Version="2.0.78" />
<PackageReference Include="MySql.Data" Version="8.0.24" /> <PackageReference Include="MySql.Data" Version="8.0.24" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Quartz" Version="3.3.2" />
<PackageReference Include="Serilog.Extensions.Logging.File" Version="2.0.0" /> <PackageReference Include="Serilog.Extensions.Logging.File" Version="2.0.0" />
<PackageReference Include="System.Data.SqlClient" Version="4.8.2" /> <PackageReference Include="System.Data.SqlClient" Version="4.8.2" />
</ItemGroup> </ItemGroup>

View File

@ -8,9 +8,13 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using MySql.Data.MySqlClient; using MySql.Data.MySqlClient;
using Quartz;
using Quartz.Impl;
using Quartz.Spi;
using SolarPower.Helper; using SolarPower.Helper;
using SolarPower.Models; using SolarPower.Models;
using SolarPower.Quartz;
using SolarPower.Quartz.Jobs;
using SolarPower.Repository.Implement; using SolarPower.Repository.Implement;
using SolarPower.Repository.Interface; using SolarPower.Repository.Interface;
using SolarPower.Services; using SolarPower.Services;
@ -81,6 +85,16 @@ namespace SolarPower
#region #region
//services.AddHostedService<OperationScheduleBackgroundService>(); //services.AddHostedService<OperationScheduleBackgroundService>();
//²K¥[QuartzªA°È
services.AddSingleton<IJobFactory, SingletonJobFactory>();
services.AddSingleton<ISchedulerFactory, StdSchedulerFactory>();
//²K¥[§Ú­ÌªºJob
services.AddSingleton<OperationScheduleJob>();
services.AddSingleton(
new JobSchedule(jobType: typeof(OperationScheduleJob), cronExpression: "0/5 * * * * ?")
);
services.AddHostedService<QuartzHostedService>();
#endregion #endregion
} }

View File

@ -0,0 +1,177 @@
@{
ViewData["MainNum"] = "1";
ViewData["SubNum"] = "1";
ViewData["Title"] = "地圖總覽";
}
<ol class="breadcrumb page-breadcrumb">
<li class="breadcrumb-item"><a href="javascript:void(0);">總覽</a></li>
<li class="breadcrumb-item active">@ViewData["Title"]</li>
<li class="position-absolute pos-top pos-right d-none d-sm-block"><span class="js-get-date"></span></li>
</ol>
<div class="row">
<div class="col-xl-12">
<div id="map"></div>
</div>
</div>
@section Scripts{
<script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>
<script src="https://unpkg.com/@googlemaps/markerclustererplus/dist/index.min.js"></script>
<script>
var companyTable; var companyAuthTable;
$(function () {
//#region 公司管理列表 DataTable
companyTable = $("#company_table").DataTable({
"paging": true,
"lengthChange": false,
"searching": false,
"ordering": true,
"info": true,
"autoWidth": false,
"responsive": true,
"order": [[8, "desc"]],
"columns": [{
"data": "id"
}, {
"data": "logo"
}, {
"data": "name"
}, {
"data": "taxIDNumber"
}, {
"data": "phone"
}, {
"data": "address"
}, {
"data": "registerRatio"
}, {
"data": "spStationAmount"
}, {
"data": "createdAt"
}, {
"data": "function",
}],
"columnDefs": [{
'targets': 1,
'searchable': false,
'orderable': false,
'className': 'dt-body-center',
'render': function (data, type, full, meta) {
return '<img src="' + data + '?v=' + Date.now() + '" class="img-fluid">';
}
}],
"language": {
"emptyTable": "無資料...",
"processing": "處理中...",
"loadingRecords": "載入中...",
"lengthMenu": "顯示 _MENU_ 項結果",
"zeroRecords": "沒有符合的結果",
"info": "顯示第 _START_ 至 _END_ 項結果,共 _TOTAL_ 項",
"infoEmpty": "顯示第 0 至 0 項結果,共 0 項",
"infoFiltered": "(從 _MAX_ 項結果中過濾)",
"infoPostFix": "",
"search": "搜尋:",
"paginate": {
"first": "第一頁",
"previous": "上一頁",
"next": "下一頁",
"last": "最後一頁"
},
"aria": {
"sortAscending": ": 升冪排列",
"sortDescending": ": 降冪排列"
}
},
'createdRow': function (row, data, dataIndex) {
$(row).attr('data-id', data.id);
},
"ajax": {
"url": "/Company/CompanyList",
"type": "POST",
"data": function (d) {
d.SelectedCompanyId = $('#system_admin_account').val();
d.Name = $('#company_name').val();
d.Phone = $('#company_phone').val();
d.TaxIDNumber = $('#company_taxIDNumber').val();
},
"dataSrc": function (rel) {
if (rel.data.code == "9999") {
toast_error(rel.data.msg);
return;
}
data = rel.data.data;
if (data == null || data.length == 0) {
this.data = [];
}
return data;
}
}
});
//#endregion
//#region google map
initMap();
//#endregion
});
function initMap() {
const map = new google.maps.Map(document.getElementById("map"), {
zoom: 7,
center: { lat: 23.5, lng: 123 },
});
// Create an array of alphabetical characters used to label the markers.
const labels = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
// Add some markers to the map.
// Note: The code uses the JavaScript Array.prototype.map() method to
// create an array of markers based on a given "locations" array.
// The map() method here has nothing to do with the Google Maps API.
const markers = locations.map((location, i) => {
return new google.maps.Marker({
position: location,
label: labels[i % labels.length],
map: map
});
});
// Add a marker clusterer to manage the markers.
new MarkerClusterer(map, markers, {
imagePath:
"https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m",
});
}
const locations = [
{ lat: -31.56391, lng: 147.154312 },
{ lat: -33.718234, lng: 150.363181 },
{ lat: -33.727111, lng: 150.371124 },
{ lat: -33.848588, lng: 151.209834 },
{ lat: -33.851702, lng: 151.216968 },
{ lat: -34.671264, lng: 150.863657 },
{ lat: -35.304724, lng: 148.662905 },
{ lat: -36.817685, lng: 175.699196 },
{ lat: -36.828611, lng: 175.790222 },
{ lat: -37.75, lng: 145.116667 },
{ lat: -37.759859, lng: 145.128708 },
{ lat: -37.765015, lng: 145.133858 },
{ lat: -37.770104, lng: 145.143299 },
{ lat: -37.7737, lng: 145.145187 },
{ lat: -37.774785, lng: 145.137978 },
{ lat: -37.819616, lng: 144.968119 },
{ lat: -38.330766, lng: 144.695692 },
{ lat: -39.927193, lng: 175.053218 },
{ lat: -41.330162, lng: 174.865694 },
{ lat: -42.734358, lng: 147.439506 },
{ lat: -42.734358, lng: 147.501315 },
{ lat: -42.735258, lng: 147.438 },
{ lat: -43.999792, lng: 170.463352 },
];
}
</script>
}

View File

@ -89,7 +89,7 @@
</li> </li>
<li class="nav-title">Navigation Title</li>--> <li class="nav-title">Navigation Title</li>-->
<li class="@(ViewData["MainNum"] == "1" ? "active open" : "")"> <li class="@(ViewData["MainNum"] == "1" ? "active open" : "")">
<a href="#" title="Category" data-filter-tags="category"> <a asp-controller="MapOverview" asp-action="Index" title="Category" data-filter-tags="category">
<i class="fal fa-file"></i> <i class="fal fa-file"></i>
<span class="nav-link-text" data-i18n="nav.category">總覽</span> <span class="nav-link-text" data-i18n="nav.category">總覽</span>
</a> </a>