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;
}
}