using Cronos; using NetAdmin.Application.Repositories; using NetAdmin.Application.Services; using NetAdmin.Domain.DbMaps.Sys; using NetAdmin.Domain.Dto.Dependency; using NetAdmin.Domain.Dto.Sys; using NetAdmin.Domain.Dto.Sys.Job; using NetAdmin.Domain.Dto.Sys.JobRecord; using NetAdmin.Domain.Enums.Sys; using NetAdmin.SysComponent.Application.Services.Sys.Dependency; using DataType = FreeSql.DataType; namespace NetAdmin.SysComponent.Application.Services.Sys; /// public sealed class JobService(DefaultRepository rpo, IJobRecordService jobRecordService) // : RepositoryService(rpo), IJobService { /// public async Task BulkDeleteAsync(BulkReq req) { req.ThrowIfInvalid(); var ret = 0; // ReSharper disable once LoopCanBeConvertedToQuery foreach (var item in req.Items) { ret += await DeleteAsync(item).ConfigureAwait(false); } return ret; } /// public Task CountAsync(QueryReq req) { req.ThrowIfInvalid(); return QueryInternal(req).CountAsync(); } /// public async Task CreateAsync(CreateJobReq req) { req.ThrowIfInvalid(); var nextExecTime = GetNextExecTime(req.ExecutionCron); var ret = await Rpo.InsertAsync(req with { NextExecTime = nextExecTime , NextTimeId = nextExecTime?.TimeUnixUtc() , RequestHeader = req.RequestHeaders?.Json() }) .ConfigureAwait(false); return ret.Adapt(); } /// public async Task DeleteAsync(DelReq req) { req.ThrowIfInvalid(); var ret = await Rpo.DeleteCascadeByDatabaseAsync(a => a.Id == req.Id).ConfigureAwait(false); return ret.Count; } /// public async Task EditAsync(UpdateJobReq req) { req.ThrowIfInvalid(); var ret = await Rpo.UpdateDiy.Set(a => a.ExecutionCron == req.ExecutionCron) .Set(a => a.HttpMethod == req.HttpMethod) .Set(a => a.JobName == req.JobName) .SetIf(req.RequestHeaders == null, a => a.RequestHeader, null) .SetIf(req.RequestHeaders != null, a => a.RequestHeader, req.RequestHeaders.Json()) .Set(a => a.RequestBody == req.RequestBody) .Set(a => a.RequestUrl == req.RequestUrl) .Set(a => a.UserId == req.UserId) .Where(a => a.Id == req.Id) .ExecuteUpdatedAsync() .ConfigureAwait(false); return ret[0].Adapt(); } /// public Task ExistAsync(QueryReq req) { req.ThrowIfInvalid(); return QueryInternal(req).AnyAsync(); } /// public async Task FinishJobAsync(UpdateJobReq req) { req.ThrowIfInvalid(); var nextExecTime = GetNextExecTime(req.ExecutionCron); _ = await UpdateAsync(req with { Status = JobStatues.Idle , NextExecTime = nextExecTime , NextTimeId = nextExecTime?.TimeUnixUtc() }) .ConfigureAwait(false); } /// public async Task GetAsync(QueryJobReq req) { req.ThrowIfInvalid(); var ret = await QueryInternal(new QueryReq { Filter = req }).ToOneAsync().ConfigureAwait(false); return ret.Adapt(); } /// public async Task GetNextJobAsync() { var df = new DynamicFilterInfo { Filters = [ new DynamicFilterInfo { Field = nameof(QueryJobReq.NextExecTime) , Value = DateTime.Now , Operator = DynamicFilterOperators.LessThan } , new DynamicFilterInfo { Field = nameof(QueryJobReq.Status) , Value = JobStatues.Idle , Operator = DynamicFilterOperators.Eq } , new DynamicFilterInfo { Field = nameof(QueryJobReq.Enabled) , Value = true , Operator = DynamicFilterOperators.Eq } ] }; var job = await QueryInternal(new QueryReq { DynamicFilter = df, Order = Orders.Random }) .Take(1) .Where(a => !Rpo.Orm.Select() .As("b") .Where(b => b.JobId == a.Id && b.TimeId == a.NextTimeId) .Any()) .ToOneAsync() .ConfigureAwait(false); return job == null ? null : await UpdateAsync(job.Adapt() with { Status = JobStatues.Running , LastExecTime = DateTime.Now }) .ConfigureAwait(false); } /// public Task> GetRecordBarChartAsync(QueryReq req) { req.ThrowIfInvalid(); return jobRecordService.GetBarChartAsync(req); } /// public Task> GetRecordPieChartByHttpStatusCodeAsync( QueryReq req) { req.ThrowIfInvalid(); return jobRecordService.GetPieChartByHttpStatusCodeAsync(req); } /// public Task> GetRecordPieChartByNameAsync(QueryReq req) { req.ThrowIfInvalid(); return jobRecordService.GetPieChartByNameAsync(req); } /// public async Task> PagedQueryAsync(PagedQueryReq req) { req.ThrowIfInvalid(); var list = await QueryInternal(req) .Page(req.Page, req.PageSize) .Count(out var total) .ToListAsync() .ConfigureAwait(false); return new PagedQueryRsp(req.Page, req.PageSize, total, list.Adapt>()); } /// public async Task> QueryAsync(QueryReq req) { req.ThrowIfInvalid(); var ret = await QueryInternal(req).Take(req.Count).ToListAsync().ConfigureAwait(false); return ret.Adapt>(); } /// public Task RecordGetAsync(QueryJobRecordReq req) { req.ThrowIfInvalid(); return jobRecordService.GetAsync(req); } /// public Task> RecordPagedQueryAsync(PagedQueryReq req) { req.ThrowIfInvalid(); return jobRecordService.PagedQueryAsync(req); } /// public Task ReleaseStuckTaskAsync() { return Rpo.UpdateDiy.Set(a => a.Status == JobStatues.Idle) .Where(a => a.Status == JobStatues.Running && a.LastExecTime < DateTime.Now.AddSeconds(-Numbers.SECS_TIMEOUT_JOB)) .ExecuteAffrowsAsync(); } /// public Task SetEnabledAsync(UpdateJobReq req) { req.ThrowIfInvalid(); return Rpo.UpdateDiy.Set(a => a.Enabled == req.Enabled).Where(a => a.Id == req.Id).ExecuteAffrowsAsync(); } /// public async Task UpdateAsync(UpdateJobReq req) { req.ThrowIfInvalid(); if (Rpo.Orm.Ado.DataType == DataType.Sqlite) { return (await UpdateForSqliteAsync(req).ConfigureAwait(false)).Adapt(); } _ = await Rpo.UpdateAsync(req).ConfigureAwait(false); return req.Adapt(); } /// protected override async Task UpdateForSqliteAsync(Sys_Job req) { _ = await Rpo.UpdateAsync(req).ConfigureAwait(false); return req; } private static DateTime? GetNextExecTime(string cron) { return CronExpression.Parse(cron, CronFormat.IncludeSeconds) .GetNextOccurrence(DateTime.UtcNow, TimeZoneInfo.Local) ?.ToLocalTime(); } private ISelect QueryInternal(QueryReq req) { var ret = Rpo.Select.Include(a => a.User) .WhereDynamicFilter(req.DynamicFilter) .WhereDynamic(req.Filter) .WhereIf( // req.Keywords?.Length > 0 , a => a.Id == req.Keywords.Int64Try(0) || a.JobName.Contains(req.Keywords)); switch (req.Order) { case Orders.None: return ret; case Orders.Random: return ret.OrderByRandom(); } ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending); if (!req.Prop?.Equals(nameof(req.Filter.LastExecTime), StringComparison.OrdinalIgnoreCase) ?? true) { ret = ret.OrderByDescending(a => a.LastExecTime); } return ret; } }