using Furion; using Furion.DependencyInjection; using Furion.DynamicApiController; using Furion.FriendlyException; using Furion.JsonSerialization; using Furion.RemoteRequest.Extensions; using Furion.TaskScheduler; using Myshipping.Core.Entity; using Mapster; using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading.Tasks; namespace Myshipping.Core.Service; /// /// 任务调度服务 /// [ApiDescriptionSettings(Name = "Timer", Order = 100)] public class SysTimerService : ISysTimerService, IDynamicApiController, IScoped { private readonly SqlSugarRepository _sysTimerRep; // 任务表仓储 private readonly ISysCacheService _cache; public SysTimerService(SqlSugarRepository sysTimerRep, ISysCacheService cache) { _sysTimerRep = sysTimerRep; _cache = cache; } /// /// 分页获取任务列表 /// /// /// [HttpGet("/sysTimers/page")] public async Task GetTimerPageList([FromQuery] JobInput input) { var workers = SpareTime.GetWorkers().ToList(); var timers = await _sysTimerRep.AsQueryable() .WhereIF(!string.IsNullOrWhiteSpace(input.JobName), u => u.JobName.Contains(input.JobName.Trim())) .Select() .ToPagedListAsync(input.PageNo, input.PageSize); timers.Items.ToList().ForEach(u => { var timer = workers.FirstOrDefault(m => m.WorkerName == u.JobName); if (timer != null) { u.TimerStatus = timer.Status; u.RunNumber = timer.Tally; u.Exception = ""; // JSON.Serialize(timer.Exception); } }); return timers.XnPagedResult(); } /// /// 获取所有本地任务 /// /// [HttpGet("/sysTimers/localJobList")] public async Task GetLocalJobList() { // 获取本地所有任务方法 var LocalJobs = await GetTaskMethods(); // TaskMethodInfo继承自LocalJobOutput,直接强转为LocalJobOutput再返回 return LocalJobs.Select(t => (LocalJobOutput)t); } /// /// 增加任务 /// /// /// [HttpPost("/sysTimers/add")] public async Task AddTimer(JobInput input) { var isExist = await _sysTimerRep.AnyAsync(u => u.JobName == input.JobName); if (isExist) throw Oops.Oh(ErrorCode.D1100); var timer = input.Adapt(); await _sysTimerRep.InsertAsync(timer); // 添加到任务调度里 AddTimerJob(input); } /// /// 删除任务 /// /// /// [HttpPost("/sysTimers/delete")] public async Task DeleteTimer(DeleteJobInput input) { var timer = await _sysTimerRep.FirstOrDefaultAsync(u => u.Id == input.Id); if (timer == null) throw Oops.Oh(ErrorCode.D1101); await _sysTimerRep.DeleteAsync(timer); // 从调度器里取消 SpareTime.Cancel(timer.JobName); } /// /// 修改任务 /// /// /// [HttpPost("/sysTimers/edit")] public async Task UpdateTimber(UpdateJobInput input) { // 排除自己并且判断与其他是否相同 var isExist = await _sysTimerRep.AnyAsync(u => u.JobName == input.JobName && u.Id != input.Id); if (isExist) throw Oops.Oh(ErrorCode.D1100); // 先从调度器里取消 var oldTimer = await _sysTimerRep.FirstOrDefaultAsync(u => u.Id == input.Id); SpareTime.Cancel(oldTimer.JobName); var timer = input.Adapt(); await _sysTimerRep.AsUpdateable(timer).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync(); // 再添加到任务调度里 AddTimerJob(input); } /// /// 查看任务 /// /// /// [HttpGet("/sysTimers/detail")] public async Task GetTimer([FromQuery] QueryJobInput input) { return await _sysTimerRep.FirstOrDefaultAsync(u => u.Id == input.Id); } /// /// 停止任务 /// /// /// [HttpPost("/sysTimers/stop")] public void StopTimerJob(JobInput input) { var timer = _sysTimerRep.FirstOrDefault(m => m.JobName == input.JobName); if (timer == null) throw Oops.Oh(ErrorCode.D1002); timer.StartNow = false; _sysTimerRep.AsUpdateable(timer).ExecuteCommand(); SpareTime.Stop(input.JobName); } /// /// 启动任务 /// /// /// [HttpPost("/sysTimers/start")] public void StartTimerJob(JobInput input) { var dbTimer = _sysTimerRep.FirstOrDefault(m => m.JobName == input.JobName); if (dbTimer == null) throw Oops.Oh(ErrorCode.D1002); dbTimer.StartNow = true; _sysTimerRep.AsUpdateable(dbTimer).ExecuteCommand(); var timer = SpareTime.GetWorkers().ToList().Find(u => u.WorkerName == input.JobName); if (timer == null) AddTimerJob(input); // 如果 StartNow 为 flase , 执行 AddTimerJob 并不会启动任务 SpareTime.Start(input.JobName); } /// /// 新增定时任务 /// /// [NonAction] public void AddTimerJob(JobInput input) { Action action = null; switch (input.RequestType) { // 创建本地方法委托 case RequestTypeEnum.Run: { // 查询符合条件的任务方法 var taskMethod = GetTaskMethods().Result.FirstOrDefault(m => m.RequestUrl == input.RequestUrl); if (taskMethod == null) break; // 创建任务对象 var typeInstance = Activator.CreateInstance(taskMethod.DeclaringType); // 创建委托 action = (Action)Delegate.CreateDelegate(typeof(Action), typeInstance, taskMethod.MethodName); break; } // 创建网络任务委托 default: { action = async (_, _) => { var requestUrl = input.RequestUrl.Trim(); requestUrl = requestUrl?.IndexOf("http") == 0 ? requestUrl : "http://" + requestUrl; var requestParameters = input.RequestParameters; var headersString = input.Headers; var headers = string.IsNullOrEmpty(headersString) ? null : JSON.Deserialize>(headersString); switch (input.RequestType) { case RequestTypeEnum.Get: await requestUrl.SetHeaders(headers).GetAsync(); break; case RequestTypeEnum.Post: await requestUrl.SetHeaders(headers).SetQueries(requestParameters).PostAsync(); break; case RequestTypeEnum.Put: await requestUrl.SetHeaders(headers).SetQueries(requestParameters).PutAsync(); break; case RequestTypeEnum.Delete: await requestUrl.SetHeaders(headers).DeleteAsync(); break; } }; break; } } if (action == null) throw Oops.Oh($"定时任务委托创建失败!JobName:{input.JobName}"); // 缓存任务配置参数,以供任务运行时读取 if (input.RequestType == RequestTypeEnum.Run) { var jobParametersName = $"{input.JobName}_Parameters"; var jobParameters = _cache.Exists(jobParametersName); var requestParametersIsNull = string.IsNullOrEmpty(input.RequestParameters); // 如果没有任务配置却又存在缓存,则删除缓存 if (requestParametersIsNull && jobParameters) _cache.Del(jobParametersName); else if (!requestParametersIsNull) _cache.Set(jobParametersName, JSON.Deserialize>(input.RequestParameters)); } // 创建定时任务 switch (input.TimerType) { case SpareTimeTypes.Interval: if (input.DoOnce) SpareTime.DoOnce(input.Interval * 1000, action, input.JobName, input.Remark, input.StartNow, executeType: input.ExecuteType); else SpareTime.Do(input.Interval * 1000, action, input.JobName, input.Remark, input.StartNow, executeType: input.ExecuteType); break; case SpareTimeTypes.Cron: SpareTime.Do(input.Cron, action, input.JobName, input.Remark, input.StartNow, executeType: input.ExecuteType); break; } } /// /// 启动自启动任务 /// [NonAction] public void StartTimerJob() { var sysTimerList = _sysTimerRep.Where(t => t.StartNow).Select().ToList(); sysTimerList.ForEach(AddTimerJob); } /// /// 获取所有本地任务 /// /// [NonAction] public async Task> GetTaskMethods() { // 有缓存就返回缓存 var taskMethods = await _cache.GetAsync>("TaskMethodInfos"); if (taskMethods != null) return taskMethods; // 获取所有本地任务方法,必须有spareTimeAttribute特性 taskMethods = App.EffectiveTypes .Where(u => u.IsClass && !u.IsInterface && !u.IsAbstract && typeof(ISpareTimeWorker).IsAssignableFrom(u)) .SelectMany(u => u.GetMethods(BindingFlags.Public | BindingFlags.Instance) .Where(m => m.IsDefined(typeof(SpareTimeAttribute), false) && m.GetParameters().Length == 2 && m.GetParameters()[0].ParameterType == typeof(SpareTimer) && m.GetParameters()[1].ParameterType == typeof(long) && m.ReturnType == typeof(void)) .Select(m => { // 默认获取第一条任务特性 var spareTimeAttribute = m.GetCustomAttribute(); return new TaskMethodInfo { JobName = spareTimeAttribute.WorkerName, RequestUrl = $"{m.DeclaringType.Name}/{m.Name}", Cron = spareTimeAttribute.CronExpression, DoOnce = spareTimeAttribute.DoOnce, ExecuteType = spareTimeAttribute.ExecuteType, Interval = (int)spareTimeAttribute.Interval / 1000, StartNow = spareTimeAttribute.StartNow, RequestType = RequestTypeEnum.Run, Remark = spareTimeAttribute.Description, TimerType = string.IsNullOrEmpty(spareTimeAttribute.CronExpression) ? SpareTimeTypes.Interval : SpareTimeTypes.Cron, MethodName = m.Name, DeclaringType = m.DeclaringType }; })); await _cache.SetAsync("TaskMethodInfos", taskMethods); return taskMethods; } //[HttpGet("/sysTimers/test")] //public async Task Test(long timerId) //{ // var model = await _sysTimerRep.Where(t => t.Id == timerId).Select().FirstAsync(); // if (model == null) return false; // model.StartNow = true; // model.Interval = 1; // AddTimerJob(model); // return true; //} }