From 231e9982771cc711fd33a40484095358bb3fe216 Mon Sep 17 00:00:00 2001 From: ky_yusj <2655568377@qq.com> Date: Thu, 15 Jul 2021 15:53:11 +0800 Subject: [PATCH] =?UTF-8?q?update=20=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E8=AE=A1=E5=88=92=E7=9B=B8=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Api/Ewide.Core/Const/CommonConst.cs | 10 + Api/Ewide.Core/Entity/SysTimer.cs | 35 +- Api/Ewide.Core/Enum/RequestTypeEnum.cs | 39 ++ Api/Ewide.Core/Ewide.Core.csproj | 3 + Api/Ewide.Core/Ewide.Core.xml | 510 +++++++++++------- .../Service/Cache/ISysCacheService.cs | 1 + .../Service/Cache/SysCacheService.cs | 11 + Api/Ewide.Core/Service/Timer/Dto/JobInput.cs | 213 ++++++-- Api/Ewide.Core/Service/Timer/Dto/JobOutput.cs | 158 ++++-- .../Service/Timer/ISysTimerService.cs | 13 +- .../Service/Timer/SysTimerService.cs | 238 ++++++-- Api/Ewide.Core/applicationconfig.json | 6 + web-react/src/pages/system/timers/form.jsx | 243 +++++++++ web-react/src/pages/system/timers/index.jsx | 318 +++++++++++ 14 files changed, 1452 insertions(+), 346 deletions(-) create mode 100644 Api/Ewide.Core/Enum/RequestTypeEnum.cs create mode 100644 web-react/src/pages/system/timers/form.jsx create mode 100644 web-react/src/pages/system/timers/index.jsx diff --git a/Api/Ewide.Core/Const/CommonConst.cs b/Api/Ewide.Core/Const/CommonConst.cs index afa39b1..5f50e37 100644 --- a/Api/Ewide.Core/Const/CommonConst.cs +++ b/Api/Ewide.Core/Const/CommonConst.cs @@ -35,5 +35,15 @@ /// 区域缓存 /// public const string CACHE_AREA_CODE = "areaCode"; + + /// + /// 所有缓存关键字集合 + /// + public const string CACHE_KEY_ALL = "allkey"; + + /// + /// 定时任务缓存 + /// + public const string CACHE_KEY_TIMER_JOB = "timerjob"; } } diff --git a/Api/Ewide.Core/Entity/SysTimer.cs b/Api/Ewide.Core/Entity/SysTimer.cs index 471986d..54941bf 100644 --- a/Api/Ewide.Core/Entity/SysTimer.cs +++ b/Api/Ewide.Core/Entity/SysTimer.cs @@ -1,4 +1,4 @@ -using Ewide.Core.Service; +using Furion.TaskScheduler; using Microsoft.EntityFrameworkCore; using System; using System.ComponentModel.DataAnnotations.Schema; @@ -19,12 +19,31 @@ namespace Ewide.Core [Comment("任务名称")] public string JobName { get; set; } + + /// + /// 只执行一次 + /// + [Comment("只执行一次")] + public bool DoOnce { get; set; } = false; + /// /// 任务分组 /// /// ewide - [Comment("任务分组")] - public string JobGroup { get; set; } + //[Comment("任务分组")] + //public string JobGroup { get; set; } + + /// + /// 立即执行(默认等待启动) + /// + [Comment("立即执行")] + public bool StartNow { get; set; } = false; + + // + /// 执行类型(并行、列队) + /// + [Comment("执行类型")] + public SpareTimeExecuteTypes ExecuteType { get; set; } = SpareTimeExecuteTypes.Parallel; /// /// 开始时间 @@ -63,8 +82,14 @@ namespace Ewide.Core /// /// 触发器类型 /// - [Comment("触发器类型")] - public TriggerTypeEnum TriggerType { get; set; } = TriggerTypeEnum.Simple; + //[Comment("触发器类型")] + //public TriggerTypeEnum TriggerType { get; set; } = TriggerTypeEnum.Simple; + + /// + /// 定时器类型 + /// + [Comment("定时器类型")] + public SpareTimeTypes TimerType { get; set; } = SpareTimeTypes.Interval; /// /// 请求url diff --git a/Api/Ewide.Core/Enum/RequestTypeEnum.cs b/Api/Ewide.Core/Enum/RequestTypeEnum.cs new file mode 100644 index 0000000..a0fdfba --- /dev/null +++ b/Api/Ewide.Core/Enum/RequestTypeEnum.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Core +{ + /// + /// http请求类型 + /// + public enum RequestTypeEnum + { + /// + /// 执行内部方法 + /// + Run = 0, + + /// + /// GET请求 + /// + Get = 1, + + /// + /// POST请求 + /// + Post = 2, + + /// + /// PUT请求 + /// + Put = 3, + + /// + /// DELETE请求 + /// + Delete = 4 + } +} diff --git a/Api/Ewide.Core/Ewide.Core.csproj b/Api/Ewide.Core/Ewide.Core.csproj index 6ab3b8e..367616d 100644 --- a/Api/Ewide.Core/Ewide.Core.csproj +++ b/Api/Ewide.Core/Ewide.Core.csproj @@ -16,6 +16,9 @@ + + + diff --git a/Api/Ewide.Core/Ewide.Core.xml b/Api/Ewide.Core/Ewide.Core.xml index 4411fa2..2ea8b12 100644 --- a/Api/Ewide.Core/Ewide.Core.xml +++ b/Api/Ewide.Core/Ewide.Core.xml @@ -308,6 +308,16 @@ 区域缓存 + + + 所有缓存关键字集合 + + + + + 定时任务缓存 + + 区域代码相关服务 @@ -1617,12 +1627,17 @@ ewide - + - 任务分组 + 只执行一次 - ewide + + + 立即执行(默认等待启动) + + + 开始时间 @@ -1652,9 +1667,9 @@ 5 - + - 触发器类型 + 定时器类型 @@ -2537,6 +2552,36 @@ 结合于 LIKE %Param + + + http请求类型 + + + + + 执行内部方法 + + + + + GET请求 + + + + + POST请求 + + + + + PUT请求 + + + + + DELETE请求 + + 菜单激活类型 @@ -3475,6 +3520,13 @@ + + + 检查给定 key 是否存在 + + 键 + + 代码生成详细配置服务 @@ -6397,92 +6449,198 @@ - + 任务调度参数 - + 任务名称 - ewide - + - 任务分组 - - ewide - - - - 开始时间 + 只执行一次 - + - 结束时间 + 立即执行(默认等待启动) - null - + - Cron表达式 + 执行类型(并行、列队) - - + - 执行次数(默认无限循环) - - 10 - - - - 执行间隔时间,单位秒(如果有Cron,则IntervalSecond失效) + 执行间隔时间(单位秒) 5 - + - 触发器类型 + Cron表达式 - + + + 定时器类型 + + + 请求url - + 请求参数(Post,Put请求用) - + Headers(可以包含如:Authorization授权认证) 格式:{"Authorization":"userpassword.."} - + + + 请求类型 + + + + + 备注 + + + + + 任务名称 + + + + + 只执行一次 + + + + + 立即执行(默认等待启动) + + + + + 执行类型(并行、列队) + + + + + 执行间隔时间(单位秒) + + 5 + + + + Cron表达式 + + + + + 定时器类型 + + + + + 请求url + + + + + 请求参数(Post,Put请求用) + + + + + Headers(可以包含如:Authorization授权认证) + 格式:{"Authorization":"userpassword.."} + + + + + 请求类型 + + + + + 备注 + + + + + 任务名称 + + dilon + + + + 只执行一次 + + + + + 立即执行(默认等待启动) + + + + + 执行类型(并行、列队) + + + + + 执行间隔时间(单位秒) + + 5 + + + + Cron表达式 + + + + + + 定时器类型 + + + + + 请求url + + + + + 请求参数(Post,Put请求用) + + + + + Headers(可以包含如:Authorization授权认证) + 格式:{"Authorization":"userpassword.."} + + + 请求类型 2 - + - 描述 - - - - - 任务状态 - - - - - 任务Id + 备注 @@ -6490,226 +6648,141 @@ 任务信息---任务详情 + + + Id + + + + + 已执行次数 + + + + + 定时器状态 + + + + + 异常信息 + + 任务名称 - + - 任务组名 + 只执行一次 - + - 下次执行时间 + 立即执行(默认等待启动) - + - 上次执行时间 - - - - - 开始时间 - - - - - 结束时间 - - - - - 上次执行的异常信息 - - - - - 任务状态 - - - - - 描述 - - - - - 显示状态 + 执行类型(并行、列队) - 时间间隔 + 执行间隔时间(单位秒) + + + + + Cron表达式 + + + + + 定时器类型 - 请求地址 + 请求url 请求类型 + 2 - + - 已经执行的次数 + 备注 - + - 任务调度中心 + 任务方法信息 - + - 开启调度器 - - - - - - 停止调度器 + 方法名 - + - 添加一个工作任务 - - - - - - - 暂停任务 - - - - - - - 删除任务 - - - - - - - 恢复运行暂停的任务 - - - - - - - 查询任务 - - - - - - - 立即执行 - - - - - - - 获取任务日志 - - - - - - - 获取任务运行次数 - - - - - - - 获取所有任务详情 - - - - - - 从数据库里面获取所有任务并初始化 + 方法所属类的Type对象 - + - 创建类型Simple的触发器 - - - - - - - 创建类型Cron的触发器 - - - - - - - 任务调度相关常量 + 任务名称 - + - 请求url RequestUrl + 只执行一次 - + - 请求参数 RequestParameters + 立即执行(默认等待启动) - + - Headers(可以包含:Authorization授权认证) + 执行类型(并行、列队) - + - 请求类型 RequestType + 执行间隔时间(单位秒) - + - 日志 LogList + Cron表达式 - + - 异常 Exception + 定时器类型 - + - 执行次数 + 请求url - + - 任务结束时间 + 请求类型 + 2 - + - http请求类型 - - - - - 触发器类型 + 备注 @@ -6717,28 +6790,34 @@ 任务调度服务 - + 分页获取任务列表 - + + + 获取所有本地任务 + + + + 增加任务 - + 删除任务 - + 修改任务 @@ -6752,20 +6831,37 @@ - + 停止任务 - + 启动任务 + + + 新增定时任务 + + + + + + 启动自启动任务 + + + + + 获取所有本地任务 + + + AuthToken参数 diff --git a/Api/Ewide.Core/Service/Cache/ISysCacheService.cs b/Api/Ewide.Core/Service/Cache/ISysCacheService.cs index deeec91..035a441 100644 --- a/Api/Ewide.Core/Service/Cache/ISysCacheService.cs +++ b/Api/Ewide.Core/Service/Cache/ISysCacheService.cs @@ -19,5 +19,6 @@ namespace Ewide.Core.Service Task SetAsync(string key, object value); Task GetAsync(string key); Task GetAsync(string key); + bool Exists(string cacheKey); } } \ No newline at end of file diff --git a/Api/Ewide.Core/Service/Cache/SysCacheService.cs b/Api/Ewide.Core/Service/Cache/SysCacheService.cs index d1d8880..80fdb33 100644 --- a/Api/Ewide.Core/Service/Cache/SysCacheService.cs +++ b/Api/Ewide.Core/Service/Cache/SysCacheService.cs @@ -174,5 +174,16 @@ namespace Ewide.Core.Service { await _cache.SetAsync(CommonConst.CACHE_AREA_CODE, areaCodes); } + + /// + /// 检查给定 key 是否存在 + /// + /// 键 + /// + [NonAction] + public bool Exists(string cacheKey) + { + return _cache.Equals(cacheKey); + } } } diff --git a/Api/Ewide.Core/Service/Timer/Dto/JobInput.cs b/Api/Ewide.Core/Service/Timer/Dto/JobInput.cs index 2e82c3c..be9a27e 100644 --- a/Api/Ewide.Core/Service/Timer/Dto/JobInput.cs +++ b/Api/Ewide.Core/Service/Timer/Dto/JobInput.cs @@ -1,4 +1,5 @@ using Furion.DataValidation; +using Furion.TaskScheduler; using System; using System.ComponentModel.DataAnnotations; @@ -7,53 +8,44 @@ namespace Ewide.Core.Service /// /// 任务调度参数 /// - public class JobInput : PageInputBase + public class JobPageInput : PageInputBase { /// /// 任务名称 /// - /// ewide + public string JobName { get; set; } /// - /// 任务分组 + /// 只执行一次 /// - /// ewide - public string JobGroup { get; set; } + public bool DoOnce { get; set; } = false; /// - /// 开始时间 + /// 立即执行(默认等待启动) /// - public DateTime BeginTime { get; set; } = DateTime.Now; + public bool StartNow { get; set; } = false; /// - /// 结束时间 + /// 执行类型(并行、列队) /// - /// null - public DateTime? EndTime { get; set; } + public SpareTimeExecuteTypes ExecuteType { get; set; } + + /// + /// 执行间隔时间(单位秒) + /// + /// 5 + public int Interval { get; set; } /// /// Cron表达式 /// - /// public string Cron { get; set; } /// - /// 执行次数(默认无限循环) + /// 定时器类型 /// - /// 10 - public int? RunNumber { get; set; } - - /// - /// 执行间隔时间,单位秒(如果有Cron,则IntervalSecond失效) - /// - /// 5 - public int? Interval { get; set; } - - /// - /// 触发器类型 - /// - public TriggerTypeEnum TriggerType { get; set; } = TriggerTypeEnum.Simple; + public SpareTimeTypes TimerType { get; set; } /// /// 请求url @@ -71,6 +63,152 @@ namespace Ewide.Core.Service /// public string Headers { get; set; } + /// + /// 请求类型 + /// + public RequestTypeEnum RequestType { get; set; } + + /// + /// 备注 + /// + public string Remark { get; set; } + } + + public class AddJobInput + { + /// + /// 任务名称 + /// + + public string JobName { get; set; } + + /// + /// 只执行一次 + /// + public bool DoOnce { get; set; } = false; + + /// + /// 立即执行(默认等待启动) + /// + public bool StartNow { get; set; } = false; + + /// + /// 执行类型(并行、列队) + /// + public SpareTimeExecuteTypes ExecuteType { get; set; } + + /// + /// 执行间隔时间(单位秒) + /// + /// 5 + public int Interval { get; set; } + + /// + /// Cron表达式 + /// + public string Cron { get; set; } + + /// + /// 定时器类型 + /// + public SpareTimeTypes TimerType { get; set; } + + /// + /// 请求url + /// + public string RequestUrl { get; set; } + + /// + /// 请求参数(Post,Put请求用) + /// + public string RequestParameters { get; set; } + + /// + /// Headers(可以包含如:Authorization授权认证) + /// 格式:{"Authorization":"userpassword.."} + /// + public string Headers { get; set; } + + /// + /// 请求类型 + /// + public RequestTypeEnum RequestType { get; set; } + + /// + /// 备注 + /// + public string Remark { get; set; } + } + + public class StopJobInput + { + public string JobName { get; set; } + } + + public class DeleteJobInput : QueryJobInput + { + + } + + public class UpdateJobInput : QueryJobInput + { + /// + /// 任务名称 + /// + /// dilon + [Required, MaxLength(20)] + public string JobName { get; set; } + + /// + /// 只执行一次 + /// + public bool DoOnce { get; set; } = false; + + /// + /// 立即执行(默认等待启动) + /// + public bool StartNow { get; set; } = false; + + /// + /// 执行类型(并行、列队) + /// + public SpareTimeExecuteTypes ExecuteType { get; set; } = SpareTimeExecuteTypes.Parallel; + + /// + /// 执行间隔时间(单位秒) + /// + /// 5 + public int? Interval { get; set; } = 5; + + /// + /// Cron表达式 + /// + /// + [MaxLength(20)] + public string Cron { get; set; } + + /// + /// 定时器类型 + /// + public SpareTimeTypes TimerType { get; set; } = SpareTimeTypes.Interval; + + /// + /// 请求url + /// + [MaxLength(200)] + public string RequestUrl { get; set; } + + /// + /// 请求参数(Post,Put请求用) + /// + public string RequestParameters { get; set; } + + /// + /// Headers(可以包含如:Authorization授权认证) + /// 格式:{"Authorization":"userpassword.."} + /// + public string Headers { get; set; } + /// /// 请求类型 /// @@ -78,32 +216,15 @@ namespace Ewide.Core.Service public RequestTypeEnum RequestType { get; set; } = RequestTypeEnum.Post; /// - /// 描述 + /// 备注 /// + [MaxLength(100)] public string Remark { get; set; } - - /// - /// 任务状态 - /// - public string DisplayState { get; set; } } - public class DeleteJobInput : JobInput + public class QueryJobInput { - /// - /// 任务Id - /// - [Required(ErrorMessage = "任务Id不能为空")] + [Required(ErrorMessage = "Id不能为空")] public string Id { get; set; } } - - public class UpdateJobInput : DeleteJobInput - { - - } - - public class QueryJobInput : DeleteJobInput - { - - } } diff --git a/Api/Ewide.Core/Service/Timer/Dto/JobOutput.cs b/Api/Ewide.Core/Service/Timer/Dto/JobOutput.cs index da3fce1..a161b84 100644 --- a/Api/Ewide.Core/Service/Timer/Dto/JobOutput.cs +++ b/Api/Ewide.Core/Service/Timer/Dto/JobOutput.cs @@ -1,4 +1,5 @@ -using Quartz; +using Furion.TaskScheduler; +using Quartz; using System; namespace Ewide.Core.Service @@ -8,89 +9,142 @@ namespace Ewide.Core.Service /// public class JobOutput { + /// + /// Id + /// + public string Id { get; set; } + + /// + /// 已执行次数 + /// + public long? RunNumber { get; set; } + + /// + /// 定时器状态 + /// + public SpareTimeStatus TimerStatus { get; set; } = SpareTimeStatus.Stopped; + + /// + /// 异常信息 + /// + public string Exception { get; set; } + /// /// 任务名称 /// public string JobName { get; set; } /// - /// 任务组名 + /// 只执行一次 /// - public string JobGroup { get; set; } + public bool DoOnce { get; set; } = false; /// - /// 下次执行时间 + /// 立即执行(默认等待启动) /// - public DateTime? NextFireTime { get; set; } + public bool StartNow { get; set; } = false; /// - /// 上次执行时间 + /// 执行类型(并行、列队) /// - public DateTime? PreviousFireTime { get; set; } + public SpareTimeExecuteTypes ExecuteType { get; set; } /// - /// 开始时间 + /// 执行间隔时间(单位秒) /// - public DateTime BeginTime { get; set; } + public int Interval { get; set; } /// - /// 结束时间 + /// Cron表达式 /// - public DateTime? EndTime { get; set; } + public string Cron { get; set; } /// - /// 上次执行的异常信息 + /// 定时器类型 /// - public string LastErrMsg { get; set; } + public SpareTimeTypes TimerType { get; set; } /// - /// 任务状态 - /// - public TriggerState TriggerState { get; set; } - - /// - /// 描述 - /// - public string Remark { get; set; } - - /// - /// 显示状态 - /// - public string DisplayState - { - get - { - return TriggerState switch - { - TriggerState.Normal => "正常", - TriggerState.Paused => "暂停", - TriggerState.Complete => "完成", - TriggerState.Error => "异常", - TriggerState.Blocked => "阻塞", - TriggerState.None => "不存在", - _ => "未知", - }; - } - } - - /// - /// 时间间隔 - /// - public string Interval { get; set; } - - /// - /// 请求地址 + /// 请求url /// public string RequestUrl { get; set; } /// /// 请求类型 /// - public string RequestType { get; set; } + /// 2 + public RequestTypeEnum RequestType { get; set; } /// - /// 已经执行的次数 + /// 备注 /// - public string RunNumber { get; set; } + public string Remark { get; set; } + } + + /// + /// 任务方法信息 + /// + public class TaskMethodInfo + { + /// + /// 方法名 + /// + public string MethodName { get; set; } + + /// + /// 方法所属类的Type对象 + /// + public Type DeclaringType { get; set; } + + /// + /// 任务名称 + /// + public string JobName { get; set; } + + /// + /// 只执行一次 + /// + public bool DoOnce { get; set; } = false; + + /// + /// 立即执行(默认等待启动) + /// + public bool StartNow { get; set; } = false; + + /// + /// 执行类型(并行、列队) + /// + public SpareTimeExecuteTypes ExecuteType { get; set; } + + /// + /// 执行间隔时间(单位秒) + /// + public int Interval { get; set; } + + /// + /// Cron表达式 + /// + public string Cron { get; set; } + + /// + /// 定时器类型 + /// + public SpareTimeTypes TimerType { get; set; } + + /// + /// 请求url + /// + public string RequestUrl { get; set; } + + /// + /// 请求类型 + /// + /// 2 + public RequestTypeEnum RequestType { get; set; } + + /// + /// 备注 + /// + public string Remark { get; set; } } } diff --git a/Api/Ewide.Core/Service/Timer/ISysTimerService.cs b/Api/Ewide.Core/Service/Timer/ISysTimerService.cs index 7aa7ef7..f71c66d 100644 --- a/Api/Ewide.Core/Service/Timer/ISysTimerService.cs +++ b/Api/Ewide.Core/Service/Timer/ISysTimerService.cs @@ -1,16 +1,17 @@ using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; using System.Threading.Tasks; namespace Ewide.Core.Service { public interface ISysTimerService { - Task AddJob(JobInput input); - Task DeleteJob(DeleteJobInput input); - Task GetJobPageList([FromQuery] JobInput input); + Task GetTimerPageList([FromQuery] JobPageInput input); + Task GetLocalJobList(); + Task AddTimer(AddJobInput input); + Task DeleteTimer(DeleteJobInput input); + Task UpdateTimber(UpdateJobInput input); Task GetTimer([FromQuery] QueryJobInput input); - Task StopScheduleJobAsync(JobInput input); - Task TriggerJobAsync(JobInput input); - Task UpdateJob(UpdateJobInput input); + Task> GetTaskMethods(); } } \ No newline at end of file diff --git a/Api/Ewide.Core/Service/Timer/SysTimerService.cs b/Api/Ewide.Core/Service/Timer/SysTimerService.cs index 96f57d4..835716a 100644 --- a/Api/Ewide.Core/Service/Timer/SysTimerService.cs +++ b/Api/Ewide.Core/Service/Timer/SysTimerService.cs @@ -1,12 +1,20 @@ -using Furion.DatabaseAccessor; +using Ewide.Core.Extension; +using Furion; +using Furion.DatabaseAccessor; using Furion.DatabaseAccessor.Extensions; using Furion.DependencyInjection; using Furion.DynamicApiController; using Furion.FriendlyException; +using Furion.JsonSerialization; +using Furion.RemoteRequest.Extensions; +using Furion.TaskScheduler; using Mapster; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Threading.Tasks; namespace Ewide.Core.Service @@ -18,12 +26,14 @@ namespace Ewide.Core.Service public class SysTimerService : ISysTimerService, IDynamicApiController, IScoped { private readonly IRepository _sysTimerRep; // 任务表仓储 - private readonly SchedulerCenter _schedulerCenter; + private readonly ISysCacheService _cache; - public SysTimerService(IRepository sysTimerRep, SchedulerCenter schedulerCenter) + //private readonly SchedulerCenter _schedulerCenter; //随框架更新已弃用 + + public SysTimerService(IRepository sysTimerRep, ISysCacheService cache) { _sysTimerRep = sysTimerRep; - _schedulerCenter = schedulerCenter; + _cache = cache; } /// @@ -32,21 +42,37 @@ namespace Ewide.Core.Service /// /// [HttpPost("/sysTimers/page")] - public async Task GetJobPageList([FromBody] JobInput input) + public async Task GetTimerPageList([FromBody] JobPageInput input) { - var jobList = await _schedulerCenter.GetJobList(); + var workers = SpareTime.GetWorkers().ToList(); - var jobName = !string.IsNullOrEmpty(input.JobName?.Trim()); var timers = await _sysTimerRep.DetachedEntities - .Where((jobName, u => EF.Functions.Like(u.JobName, $"%{input.JobName.Trim()}%"))) - .Select(u => u.Adapt()) - .ToPagedListAsync(input.PageIndex, input.PageSize); + .Where(!string.IsNullOrEmpty(input.JobName?.Trim()), u => EF.Functions.Like(u.JobName, $"%{input.JobName.Trim()}%")) + .Select(u => u.Adapt()) + .ToPagedListAsync(input.PageIndex, input.PageSize); timers.Items.ToList().ForEach(u => { - u.DisplayState = jobList.Find(m => m.JobName == u.JobName)?.DisplayState; + var timer = workers.FirstOrDefault(m => m.WorkerName == u.JobName); + if (timer != null) + { + u.TimerStatus = timer.Status; + u.RunNumber = timer.Tally; + u.Exception = Newtonsoft.Json.JsonConvert.SerializeObject(timer.Exception); + } }); - return PageDataResult.PageResult(timers); + return PageDataResult.PageResult(timers); + } + + /// + /// 获取所有本地任务 + /// + /// + [HttpGet("/sysTimers/localJobList")] + public async Task GetLocalJobList() + { + // 获取本地所有任务方法 + return await GetTaskMethods(); } /// @@ -55,7 +81,7 @@ namespace Ewide.Core.Service /// /// [HttpPost("/sysTimers/add")] - public async Task AddJob(JobInput input) + public async Task AddTimer(AddJobInput input) { var isExist = await _sysTimerRep.AnyAsync(u => u.JobName == input.JobName, false); if (isExist) @@ -64,8 +90,8 @@ namespace Ewide.Core.Service var timer = input.Adapt(); await _sysTimerRep.InsertAsync(timer); - // 添加到调度 - await _schedulerCenter.AddScheduleJobAsync(input); + // 添加到任务调度里 + AddTimerJob(input); } /// @@ -74,7 +100,7 @@ namespace Ewide.Core.Service /// /// [HttpPost("/sysTimers/delete")] - public async Task DeleteJob(DeleteJobInput input) + public async Task DeleteTimer(DeleteJobInput input) { var timer = await _sysTimerRep.FirstOrDefaultAsync(u => u.Id == input.Id); if (timer == null) @@ -82,8 +108,8 @@ namespace Ewide.Core.Service await timer.DeleteAsync(); - // 从调度器里删除 - await _schedulerCenter.DeleteScheduleJobAsync(input); + // 从调度器里取消 + SpareTime.Cancel(timer.JobName); } /// @@ -92,21 +118,21 @@ namespace Ewide.Core.Service /// /// [HttpPost("/sysTimers/edit")] - public async Task UpdateJob(UpdateJobInput input) + public async Task UpdateTimber(UpdateJobInput input) { // 排除自己并且判断与其他是否相同 var isExist = await _sysTimerRep.AnyAsync(u => u.JobName == input.JobName && u.Id != input.Id, false); if (isExist) throw Oops.Oh(ErrorCode.D1100); + // 先从调度器里取消 + var oldTimer = await _sysTimerRep.FirstOrDefaultAsync(u => u.Id == input.Id, false); + SpareTime.Cancel(oldTimer.JobName); + var timer = input.Adapt(); await timer.UpdateAsync(ignoreNullValues: true); - - // 先从调度器里删除 - var oldTimer = await _sysTimerRep.FirstOrDefaultAsync(u => u.Id == input.Id, false); - await _schedulerCenter.DeleteScheduleJobAsync(oldTimer.Adapt()); - - // 再加到调度里 - await _schedulerCenter.AddScheduleJobAsync(timer.Adapt()); + var addJobInput = input.Adapt(); + // 再添加到任务调度里 + AddTimerJob(addJobInput); } /// @@ -126,9 +152,9 @@ namespace Ewide.Core.Service /// /// [HttpPost("/sysTimers/stop")] - public async Task StopScheduleJobAsync(JobInput input) + public void StopTimerJob(StopJobInput input) { - await _schedulerCenter.StopScheduleJobAsync(input); + SpareTime.Stop(input.JobName); } /// @@ -137,9 +163,161 @@ namespace Ewide.Core.Service /// /// [HttpPost("/sysTimers/start")] - public async Task TriggerJobAsync(JobInput input) + public void StartTimerJob(AddJobInput input) { - await _schedulerCenter.TriggerJobAsync(input); + var timer = SpareTime.GetWorkers().ToList().Find(u => u.WorkerName == input.JobName); + if (timer == null) + //AddTimerJob(input); + throw Oops.Oh("启动失败:任务不存在"); + // 如果 StartNow 为 flase , 执行 AddTimerJob 并不会启动任务 + SpareTime.Start(input.JobName); + } + + /// + /// 新增定时任务 + /// + /// + [NonAction] + public void AddTimerJob(AddJobInput 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) return; + + // 缓存任务配置参数,以供任务运行时读取 + 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.DelAsync(jobParametersName); + else if (!requestParametersIsNull) + _cache.SetAsync(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.DetachedEntities.Where(t => t.StartNow).Select(u => u.Adapt()).ToList(); + sysTimerList.ForEach(AddTimerJob); + } + + /// + /// 获取所有本地任务 + /// + /// + [NonAction] + public async Task> GetTaskMethods() + { + // 有缓存就返回缓存 + var taskMethods = await _cache.GetAsync>(CommonConst.CACHE_KEY_TIMER_JOB); + 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(CommonConst.CACHE_KEY_TIMER_JOB, taskMethods); + return taskMethods; } } } diff --git a/Api/Ewide.Core/applicationconfig.json b/Api/Ewide.Core/applicationconfig.json index 1aaef5a..70149cf 100644 --- a/Api/Ewide.Core/applicationconfig.json +++ b/Api/Ewide.Core/applicationconfig.json @@ -8,6 +8,12 @@ "Title": "Admin.NET通用权限管理平台", "Description": "前后端分离架构,开箱即用,紧随前沿技术。
后台.NET5平台基于Furion框架,前端基于XiaoNuo生态技术框架的vue版本。
Furion框架,让 .NET 开发更简单,更通用,更流行
XiaoNuo前端框架采用Vue2.x + AntDesign Vue pro1.x + Axios", "Version": "1.0.0" + }, + { + "Group": "HouseBusiness", + "Title": "城镇房屋业务接口", + "Description": "城镇房屋业务接口", + "Version": "1.0.1" } ] }, diff --git a/web-react/src/pages/system/timers/form.jsx b/web-react/src/pages/system/timers/form.jsx new file mode 100644 index 0000000..1c567ea --- /dev/null +++ b/web-react/src/pages/system/timers/form.jsx @@ -0,0 +1,243 @@ +import React, { Component } from 'react' +import { Row, Col, Form, Input, Spin, Radio, Switch } from 'antd' +import { AntIcon } from 'components' +import { cloneDeep } from 'lodash' +import { api } from 'common/api' + +const initialValues = { requestType: 2, startNow: 0, doOnce: 1, timerType: 1, executeType: 1 } + +export default class form extends Component { + state = { + // 加载状态 + loading: true, + timerType: 1, + requestType: 2, + } + // 表单实例 + form = React.createRef() + + // 初始化数据 + record = {} + + /** + * mount后回调 + */ + componentDidMount() { + this.props.created && this.props.created(this) + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + if (params.id) { + this.record = (await api.sysTimersDetail({ id: params.id })).data + } + this.record = { + ...this.record, + } + //#region 从后端转换成前段所需格式 + //#endregion + this.form.current && this.form.current.setFieldsValue(this.record) + + this.setState({ + loading: false, + }) + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + if (this.record) { + postData.id = this.record.id + } + //#region 从前段转换后端所需格式 + //#endregion + return postData + } + } + + render() { + const { requestType, timerType } = this.state + return ( + }> +
+
+ + + + + + + + + + + + + + this.setState({ requestType: e.target.value })} + > + {/* + Run + */} + + Get + + + Post + + + Put + + + Delete + + + + {requestType != 0 && ( + + + + )} + + + + + + + + this.setState({ timerType: e.target.value })} + > + + 间隔方式 + + + Cron表达式 + + + + + + {timerType == 1 && ( + + + + )} + {timerType == 2 && ( + + + 参考 + + } + /> + + )} + + + + + + + + + + + + + + + + + + this.onHouseTypeChange(e)} + > + + 并行方式 + + + 串行方式 + + + + + + + + + + +
+
+
+ ) + } +} diff --git a/web-react/src/pages/system/timers/index.jsx b/web-react/src/pages/system/timers/index.jsx new file mode 100644 index 0000000..210818d --- /dev/null +++ b/web-react/src/pages/system/timers/index.jsx @@ -0,0 +1,318 @@ +import React, { Component } from 'react' +import { Button, Card, Form, Input, message as Message, Popconfirm } from 'antd' +import { AntIcon, Auth, Container, ModalForm, QueryTable, QueryTableActions } from 'components' +import { api } from 'common/api' +import auth from 'components/authorized/handler' +import { isEqual } from 'lodash' +import getDictData from 'util/dic' +import { toCamelCase } from 'util/format' +import FormBody from './form' + +/** + * 配置页面所需接口函数 + */ +const apiAction = { + page: api.sysTimersPage, + add: api.sysTimersAdd, + edit: api.sysTimersEdit, + delete: api.sysTimersDelete, + start: api.sysTimersStart, + stop: api.sysTimersStop, +} + +const name = '任务计划' +const authName = 'sysTimers' +export default class index extends Component { + state = { + codes: { + requestType: [ + { code: '0', value: 'Run' }, + { code: '1', value: 'Get' }, + { code: '2', value: 'Post' }, + { code: '3', value: 'Put' }, + { code: '4', value: 'Delete' }, + ], + timerStatus: [ + { code: '0', value: '运行中' }, + { code: '1', value: '已停止' }, + { code: '2', value: '执行失败' }, + { code: '3', value: '已取消' }, + ], + }, + } + // 表格实例 + table = React.createRef() + + // 新增窗口实例 + addForm = React.createRef() + // 编辑窗口实例 + editForm = React.createRef() + + columns = [ + { + title: '任务名称', + width: 80, + dataIndex: 'jobName', + }, + { + title: '请求地址', + width: 300, + dataIndex: 'requestUrl', + }, + { + title: '请求类型', + width: 80, + dataIndex: 'requestType', + render: text => <>{this.bindCodeValue(text, 'request_type')}, + }, + { + title: '请求参数', + width: 300, + dataIndex: 'requestParameters', + }, + { + title: '间隔时间', + width: 80, + dataIndex: 'interval', + }, + { + title: 'Cron', + width: 300, + dataIndex: 'cron', + }, + { + title: '执行次数', + width: 80, + dataIndex: 'runNumber', + }, + { + title: '状态', + width: 80, + dataIndex: 'timerStatus', + render: (text, record) => ( + <> + {(text == 0 || text == 1 || text == 2) && ( + + text == 0 + ? this.onStop(record.jobName) + : this.onStart(record.jobName) + } + > + {this.bindCodeValue(text, 'timer_status')} + + )} + {text == 3 && this.bindCodeValue(text, 'timer_status')} + + ), + }, + { + title: '备注', + width: 120, + dataIndex: 'remark', + }, + ] + + /** + * 构造函数,在渲染前动态添加操作字段等 + * @param {*} props + */ + constructor(props) { + super(props) + + const flag = auth({ [authName]: [['edit'], ['delete']] }) + + if (flag) { + this.columns.push({ + title: '操作', + width: 150, + dataIndex: 'actions', + render: (text, { id }) => ( + + + this.onOpen(this.editForm, id)}>编辑 + + + this.onDelete(id)} + > + 删除 + + + + ), + }) + } + } + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 加载字典数据,之后开始加载表格数据 + * 如果必须要加载字典数据,可直接对表格设置autoLoad=true + */ + componentDidMount() { + const { onLoading, onLoadData } = this.table.current + onLoading() + onLoadData() + // getDictData('house_company_type').then(codes => { + // this.setState({ codes }, () => { + // onLoadData() + // }) + // }) + } + + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + const { data } = await apiAction.page({ + ...params, + ...query, + }) + return data + } + + /** + * 绑定字典数据 + * @param {*} code + * @param {*} name + * @returns + */ + bindCodeValue(code, name) { + name = toCamelCase(name) + const codes = this.state.codes[name] + if (codes) { + const c = codes.find(p => p.code == code) + if (c) { + return c.value + } + } + return null + } + + /** + * 打开新增/编辑弹窗 + * @param {*} modal + * @param {*} id + */ + onOpen(modal, id) { + modal.current.open({ id }) + } + + /** + * 对表格上的操作进行统一处理 + * [异步] + * @param {*} action + * @param {*} successMessage + */ + async onAction(action, successMessage) { + const { onLoading, onLoaded, onReloadData } = this.table.current + onLoading() + try { + if (action) { + await action + } + if (successMessage) { + Message.success(successMessage) + } + onReloadData() + } catch { + onLoaded() + } + } + + /** + * 删除 + * @param {*} id + */ + onDelete(id) { + this.onAction(apiAction.delete({ id }), '删除成功') + } + + onStart(jobName) { + this.onAction(apiAction.start({ jobName }), '启动成功') + } + onStop(jobName) { + this.onAction(apiAction.stop({ jobName }), '停止成功') + } + + render() { + return ( + +
+ + + + + + + } + operator={ + + + + } + /> + + + + this.table.current.onReloadData()} + > + + + + + + this.table.current.onReloadData()} + > + + + +
+ ) + } +}