| | | 1 | | using Hangfire; |
| | | 2 | | using Hangfire.Storage; |
| | | 3 | | using Microsoft.AspNetCore.Authorization; |
| | | 4 | | using Microsoft.AspNetCore.Mvc; |
| | | 5 | | using OzonOrdersWeb.Areas.Studio2.ViewModels.EmailRouting; |
| | | 6 | | using Services.EmailRoutingServices; |
| | | 7 | | using Services.HangfireService; |
| | | 8 | | |
| | | 9 | | namespace OzonOrdersWeb.Areas.Studio2.Controllers; |
| | | 10 | | |
| | | 11 | | [Area("Studio2")] |
| | | 12 | | [Authorize(Roles = "Admin")] |
| | | 13 | | public class EmailRoutingSchedulerController : Controller |
| | | 14 | | { |
| | | 15 | | private readonly IEmailRoutingSchedulerStore _schedulerStore; |
| | | 16 | | private readonly IEmailRoutingJobRunService _jobRunService; |
| | | 17 | | |
| | 0 | 18 | | public EmailRoutingSchedulerController( |
| | 0 | 19 | | IEmailRoutingSchedulerStore schedulerStore, |
| | 0 | 20 | | IEmailRoutingJobRunService jobRunService) |
| | | 21 | | { |
| | 0 | 22 | | _schedulerStore = schedulerStore; |
| | 0 | 23 | | _jobRunService = jobRunService; |
| | 0 | 24 | | } |
| | | 25 | | |
| | | 26 | | [HttpGet] |
| | | 27 | | public async Task<IActionResult> Index() |
| | | 28 | | { |
| | 0 | 29 | | var vm = BuildModel(); |
| | 0 | 30 | | vm.RecentExecutions = await _jobRunService.GetRecentAsync(15); |
| | 0 | 31 | | return View(vm); |
| | 0 | 32 | | } |
| | | 33 | | |
| | | 34 | | [HttpPost] |
| | | 35 | | [ValidateAntiForgeryToken] |
| | | 36 | | public async Task<IActionResult> Save([Bind(Prefix = "Settings")] EmailRoutingSchedulerSettingsInput model) |
| | | 37 | | { |
| | 0 | 38 | | if (!ModelState.IsValid) |
| | | 39 | | { |
| | 0 | 40 | | var vm = BuildModel(model); |
| | 0 | 41 | | vm.RecentExecutions = await _jobRunService.GetRecentAsync(15); |
| | 0 | 42 | | return View(nameof(Index), vm); |
| | | 43 | | } |
| | | 44 | | |
| | 0 | 45 | | var settings = MapSettings(model, recurringJobEnabled: true); |
| | 0 | 46 | | await _schedulerStore.SaveSettingsAsync(settings); |
| | 0 | 47 | | EmailRoutingRecurringJobRegistrar.RegisterIfEnabled(ToExecutionOptions(settings)); |
| | | 48 | | |
| | 0 | 49 | | TempData["TransactionResult"] = |
| | 0 | 50 | | $"Настройки сохранены. Расписание: каждые {settings.IntervalMinutes} мин (MSK), " + |
| | 0 | 51 | | $"до {settings.MaxBatchesPerRun * settings.FetchLimit} писем за запуск."; |
| | 0 | 52 | | return RedirectToAction(nameof(Index)); |
| | 0 | 53 | | } |
| | | 54 | | |
| | | 55 | | [HttpGet] |
| | | 56 | | public IActionResult Save() |
| | | 57 | | { |
| | 0 | 58 | | TempData["TransactionResult"] = "Откройте страницу настроек и сохраните изменения через форму."; |
| | 0 | 59 | | return RedirectToAction(nameof(Index)); |
| | | 60 | | } |
| | | 61 | | |
| | | 62 | | [HttpPost] |
| | | 63 | | [ValidateAntiForgeryToken] |
| | | 64 | | public IActionResult RunNow() |
| | | 65 | | { |
| | 0 | 66 | | HangfireJobClientExtensions.EnqueueMain<EmailAttachmentRoutingJob>(x => x.ExecuteManualAsync()); |
| | 0 | 67 | | TempData["TransactionResult"] = "Задача поставлена в очередь на немедленное выполнение."; |
| | 0 | 68 | | return RedirectToAction(nameof(Index)); |
| | | 69 | | } |
| | | 70 | | |
| | | 71 | | [HttpPost] |
| | | 72 | | [ValidateAntiForgeryToken] |
| | | 73 | | public async Task<IActionResult> Disable() |
| | | 74 | | { |
| | 0 | 75 | | await _schedulerStore.DisableAsync(); |
| | 0 | 76 | | RecurringJob.RemoveIfExists(EmailAttachmentRoutingJob.JobId); |
| | 0 | 77 | | TempData["TransactionResult"] = "Периодическая задача отключена."; |
| | 0 | 78 | | return RedirectToAction(nameof(Index)); |
| | 0 | 79 | | } |
| | | 80 | | |
| | | 81 | | private EmailRoutingSchedulerViewModel BuildModel(EmailRoutingSchedulerSettingsInput? submitted = null) |
| | | 82 | | { |
| | 0 | 83 | | var current = _schedulerStore.GetCurrent(); |
| | 0 | 84 | | using var connection = JobStorage.Current.GetConnection(); |
| | 0 | 85 | | var recurring = connection.GetRecurringJobs() |
| | 0 | 86 | | .FirstOrDefault(x => x.Id == EmailAttachmentRoutingJob.JobId); |
| | | 87 | | |
| | 0 | 88 | | var settings = submitted ?? new EmailRoutingSchedulerSettingsInput |
| | 0 | 89 | | { |
| | 0 | 90 | | IntervalMinutes = current.IntervalMinutes, |
| | 0 | 91 | | MaxBatchesPerRun = current.MaxBatchesPerRun, |
| | 0 | 92 | | AllowMultiMatch = current.AllowMultiMatch, |
| | 0 | 93 | | FetchLimit = current.FetchLimit, |
| | 0 | 94 | | FetchScanLimit = current.FetchScanLimit, |
| | 0 | 95 | | FetchWindowDays = current.FetchWindowDays |
| | 0 | 96 | | }; |
| | | 97 | | |
| | 0 | 98 | | if (recurring == null) |
| | | 99 | | { |
| | 0 | 100 | | return new EmailRoutingSchedulerViewModel |
| | 0 | 101 | | { |
| | 0 | 102 | | IsScheduled = false, |
| | 0 | 103 | | RecurringJobEnabled = current.RecurringJobEnabled, |
| | 0 | 104 | | RecurringJobId = EmailAttachmentRoutingJob.JobId, |
| | 0 | 105 | | Settings = settings |
| | 0 | 106 | | }; |
| | | 107 | | } |
| | | 108 | | |
| | 0 | 109 | | var cronMinutes = ParseMinutesFromCron(recurring.Cron); |
| | 0 | 110 | | if (cronMinutes.HasValue) |
| | 0 | 111 | | settings.IntervalMinutes = cronMinutes.Value; |
| | | 112 | | |
| | 0 | 113 | | return new EmailRoutingSchedulerViewModel |
| | 0 | 114 | | { |
| | 0 | 115 | | IsScheduled = current.RecurringJobEnabled, |
| | 0 | 116 | | RecurringJobEnabled = current.RecurringJobEnabled, |
| | 0 | 117 | | RecurringJobId = recurring.Id, |
| | 0 | 118 | | CronExpression = recurring.Cron, |
| | 0 | 119 | | LastExecution = recurring.LastExecution, |
| | 0 | 120 | | NextExecution = recurring.NextExecution, |
| | 0 | 121 | | LastJobState = recurring.LastJobState, |
| | 0 | 122 | | Settings = settings |
| | 0 | 123 | | }; |
| | 0 | 124 | | } |
| | | 125 | | |
| | | 126 | | private static EmailRoutingSchedulerSettings MapSettings( |
| | | 127 | | EmailRoutingSchedulerSettingsInput model, |
| | | 128 | | bool recurringJobEnabled) => |
| | 0 | 129 | | new() |
| | 0 | 130 | | { |
| | 0 | 131 | | IntervalMinutes = model.IntervalMinutes, |
| | 0 | 132 | | MaxBatchesPerRun = model.MaxBatchesPerRun, |
| | 0 | 133 | | AllowMultiMatch = model.AllowMultiMatch, |
| | 0 | 134 | | RecurringJobEnabled = recurringJobEnabled, |
| | 0 | 135 | | FetchLimit = model.FetchLimit, |
| | 0 | 136 | | FetchScanLimit = model.FetchScanLimit, |
| | 0 | 137 | | FetchWindowDays = model.FetchWindowDays |
| | 0 | 138 | | }; |
| | | 139 | | |
| | | 140 | | private static EmailRoutingExecutionOptions ToExecutionOptions(EmailRoutingSchedulerSettings settings) => |
| | 0 | 141 | | new() |
| | 0 | 142 | | { |
| | 0 | 143 | | AllowMultiMatch = settings.AllowMultiMatch, |
| | 0 | 144 | | RecurringJobEnabled = settings.RecurringJobEnabled, |
| | 0 | 145 | | IntervalMinutes = settings.IntervalMinutes, |
| | 0 | 146 | | MaxBatchesPerRun = settings.MaxBatchesPerRun |
| | 0 | 147 | | }; |
| | | 148 | | |
| | | 149 | | private static int? ParseMinutesFromCron(string? cron) |
| | | 150 | | { |
| | 0 | 151 | | if (string.IsNullOrWhiteSpace(cron) || !cron.StartsWith("*/", StringComparison.Ordinal)) |
| | 0 | 152 | | return null; |
| | | 153 | | |
| | 0 | 154 | | var spaceIdx = cron.IndexOf(' '); |
| | 0 | 155 | | if (spaceIdx < 0) |
| | 0 | 156 | | return null; |
| | | 157 | | |
| | 0 | 158 | | var minutePart = cron.Substring(2, spaceIdx - 2); |
| | 0 | 159 | | return int.TryParse(minutePart, out var value) ? value : null; |
| | | 160 | | } |
| | | 161 | | } |