Furion定时任务UI管理界面怎么玩?/myjob路径配置与动态任务增删改查实战
Furion定时任务UI管理界面实战指南从配置到动态任务管理在.NET生态系统中定时任务管理一直是开发者需要面对的基础设施挑战之一。传统方式下我们往往需要依赖Windows任务计划程序或第三方服务不仅部署复杂还缺乏实时监控能力。Furion框架提供的ScheduleUI功能模块彻底改变了这一局面它将定时任务的管理和监控集成到了Web界面中让开发者能够像管理普通页面一样管理后台任务。1. 环境准备与基础配置让我们从最基础的配置开始逐步构建一个完整的定时任务管理系统。首先确保你已经创建了一个基于.NET 6的Web项目并安装了Furion.Pure 4.8.8.48或更高版本。在Program.cs中我们需要进行以下基础配置var builder WebApplication.CreateBuilder(args).Inject(); builder.Services.AddControllers().AddInject(); builder.Services.AddSchedule(); // 添加定时任务服务 var app builder.Build(); // 配置ScheduleUI界面 app.UseScheduleUI(options { options.RequestPath /myjob; // 自定义访问路径 options.DisableOnProduction false; // 生产环境也启用 }); app.UseInject(string.Empty); app.MapControllers(); app.Run();这段代码做了几件重要的事情通过AddSchedule()添加了定时任务服务使用UseScheduleUI()启用了UI管理界面将UI界面的访问路径设置为/myjob你可以更改为任何你喜欢的路径关键配置参数说明参数名类型默认值说明RequestPathstring/scheduleUI界面的访问路径DisableOnProductionbooltrue是否在生产环境禁用UIDisplayNamestring定时任务UI界面显示的名称Themestringdefault界面主题风格配置完成后启动项目并访问/myjob路径你将看到一个简洁的任务管理界面。虽然此时还没有任何任务显示但这个界面已经具备了监控和管理任务的所有基础功能。2. 创建与配置定时任务有了基础环境后我们来创建第一个定时任务。Furion中的定时任务是通过实现IJob接口来定义的。using Furion.Schedule; public class LogCleanJob : IJob { private readonly ILoggerLogCleanJob _logger; public LogCleanJob(ILoggerLogCleanJob logger) { _logger logger; } public Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken) { _logger.LogInformation($正在执行日志清理任务任务ID{context.JobDetail.JobId}); // 实际的日志清理逻辑放在这里 return Task.CompletedTask; } }这个简单的LogCleanJob定义了一个日志清理任务。接下来我们需要在服务配置中注册这个任务builder.Services.AddSchedule(options { options.AddJobLogCleanJob(log_clean, Triggers.Daily(hour: 2)); // 每天凌晨2点执行 });Furion提供了多种触发器类型可以满足不同场景的需求立即执行Triggers.Now()周期性执行Triggers.PeriodSeconds(30)每30秒每日执行Triggers.Daily(hour: 14, minute: 30)每天14:30每周执行Triggers.Weekly(DayOfWeek.Monday, hour: 9)每周一9点CRON表达式Triggers.Cron(0 0 12 * * ?)每天中午12点在UI界面中你可以看到任务的详细信息任务名称和描述上次执行时间下次执行时间执行状态运行中、等待、暂停等执行次数统计错误次数统计3. 动态任务管理实战静态配置的任务虽然简单但在实际应用中我们经常需要根据业务需求动态地添加、修改或删除任务。Furion通过ISchedulerFactory接口提供了完整的动态任务管理能力。首先我们创建一个控制器来处理动态任务操作[DynamicApiController] public class JobController { private readonly ISchedulerFactory _schedulerFactory; public JobController(ISchedulerFactory schedulerFactory) { _schedulerFactory schedulerFactory; } // 添加动态任务 [HttpPost] public string AddDynamicJob([FromBody] DynamicJobRequest request) { var jobId $dynamic_{Guid.NewGuid():N}; // 根据请求参数创建不同类型的触发器 ITrigger trigger request.TriggerType switch { interval Triggers.PeriodSeconds(request.Interval), daily Triggers.Daily(hour: request.Hour, minute: request.Minute), cron Triggers.Cron(request.CronExpression), _ throw new ArgumentException(不支持的触发器类型) }; _schedulerFactory.AddJobDynamicJob(jobId, trigger); return jobId; } // 暂停任务 [HttpPost({jobId}/pause)] public bool PauseJob(string jobId) { return _schedulerFactory.TryPauseJob(jobId); } // 恢复任务 [HttpPost({jobId}/resume)] public bool ResumeJob(string jobId) { return _schedulerFactory.TryResumeJob(jobId); } // 删除任务 [HttpDelete({jobId})] public bool RemoveJob(string jobId) { return _schedulerFactory.TryRemoveJob(jobId); } // 获取所有任务 [HttpGet] public IEnumerableJobInfo GetAllJobs() { return _schedulerFactory.GetJobs() .Select(j new JobInfo { JobId j.JobId, Status j.Status.ToString(), NextRunTime j.NextRunTime, LastRunTime j.LastRunTime }); } } public class DynamicJobRequest { public string TriggerType { get; set; } public int Interval { get; set; } public int Hour { get; set; } public int Minute { get; set; } public string CronExpression { get; set; } } public class JobInfo { public string JobId { get; set; } public string Status { get; set; } public DateTime? NextRunTime { get; set; } public DateTime? LastRunTime { get; set; } }这个控制器提供了完整的任务生命周期管理功能添加任务通过POST请求可以动态创建各种类型的定时任务暂停/恢复任务无需删除任务可以临时暂停后再恢复删除任务彻底移除不再需要的任务查询任务获取当前所有任务的状态信息提示动态任务的修改操作通常通过删除新增组合实现因为直接修改现有任务的触发器可能会带来意想不到的副作用。4. 任务持久化与高可用设计在生产环境中应用可能会重启或需要多实例部署这就要求定时任务的状态能够持久化并在多个实例之间保持一致性。Furion通过IJobPersistence接口提供了任务持久化的扩展点。首先我们需要定义数据库模型来存储任务信息[SugarTable(Jobs)] public class JobModel { [SugarColumn(IsPrimaryKey true)] public string JobId { get; set; } public string GroupName { get; set; } public string JobType { get; set; } public string AssemblyName { get; set; } public string Description { get; set; } public bool Concurrent { get; set; } public bool IncludeAnnotations { get; set; } public string Properties { get; set; } public DateTime UpdatedTime { get; set; } public bool IsDelete { get; set; } } [SugarTable(JobTriggers)] public class JobTriggerModel { [SugarColumn(IsPrimaryKey true)] public string TriggerId { get; set; } public string JobId { get; set; } public string TriggerType { get; set; } public string AssemblyName { get; set; } public string Args { get; set; } public string Description { get; set; } public TriggerStatus Status { get; set; } public DateTime? StartTime { get; set; } public DateTime? EndTime { get; set; } public DateTime? LastRunTime { get; set; } public DateTime? NextRunTime { get; set; } public long NumberOfRuns { get; set; } public long MaxNumberOfRuns { get; set; } public int NumberOfErrors { get; set; } public int MaxNumberOfErrors { get; set; } public DateTime UpdatedTime { get; set; } public bool IsDelete { get; set; } }接下来实现IJobPersistence接口public class DatabaseJobPersistence : IJobPersistence { private readonly ISqlSugarClient _db; public DatabaseJobPersistence(ISqlSugarClient db) { _db db; } public void OnChanged(PersistenceContext context) { var job JsonConvert.DeserializeObjectJobModel(context.ConvertToJSON()); job.UpdatedTime DateTime.Now; switch (context.Behavior) { case PersistenceBehavior.Appended: case PersistenceBehavior.Updated: job.IsDelete false; _db.Storageable(job).ExecuteCommand(); break; case PersistenceBehavior.Removed: job.IsDelete true; _db.Updateable(job).ExecuteCommand(); break; } } public SchedulerBuilder OnLoading(SchedulerBuilder builder) { return builder; } public void OnTriggerChanged(PersistenceTriggerContext context) { var trigger JsonConvert.DeserializeObjectJobTriggerModel(context.ConvertToJSON()); trigger.UpdatedTime DateTime.Now; switch (context.Behavior) { case PersistenceBehavior.Appended: case PersistenceBehavior.Updated: trigger.IsDelete false; _db.Storageable(trigger).ExecuteCommand(); break; case PersistenceBehavior.Removed: trigger.IsDelete true; _db.Updateable(trigger).ExecuteCommand(); break; } } public IEnumerableSchedulerBuilder Preload() { var allJobs new ListSchedulerBuilder(); // 加载静态任务 var staticJobs App.EffectiveTypes.ScanToBuilders(); allJobs.AddRange(staticJobs); // 加载数据库中的动态任务 var dbJobs _db.QueryableJobModel() .Where(j !j.IsDelete) .ToList(); var dbTriggers _db.QueryableJobTriggerModel() .Where(t !t.IsDelete) .ToList(); foreach (var job in dbJobs) { // 跳过已经在静态任务中定义的 if (staticJobs.Any(s s.GetJobBuilder().JobId job.JobId)) continue; var jobBuilder JobBuilder.Create(job.JobId).LoadFrom(job); var triggers dbTriggers .Where(t t.JobId job.JobId) .Select(t TriggerBuilder.Create(t.TriggerId).LoadFrom(t)) .ToArray(); allJobs.Add(SchedulerBuilder.Create(jobBuilder, triggers)); } return allJobs; } }最后在Program.cs中注册持久化服务// 配置数据库连接 var db new SqlSugarClient(new ConnectionConfig { ConnectionString 你的数据库连接字符串, DbType DbType.SqlServer, IsAutoCloseConnection true }); // 初始化数据库表 db.CodeFirst.InitTablesJobModel, JobTriggerModel(); builder.Services.AddSingletonISqlSugarClient(db); builder.Services.AddSchedule(options { options.AddPersistenceDatabaseJobPersistence(); });这种持久化方案带来了几个重要优势任务状态持久化即使应用重启所有任务状态都能恢复多实例支持多个应用实例可以共享同一个任务状态历史记录可以追踪任务的执行历史和状态变化动态任务持久化动态创建的任务也会被保存不会丢失5. 高级功能与最佳实践掌握了基础功能后让我们来看一些高级用法和最佳实践这些技巧可以帮助你构建更健壮、更易维护的定时任务系统。5.1 任务异常处理与重试机制定时任务在执行过程中可能会遇到各种异常情况良好的错误处理机制至关重要。public class RobustJob : IJob { public Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken) { try { // 可能会失败的业务逻辑 ProcessData(); } catch (Exception ex) { context.JobDetail.JobLogger?.LogError(ex, 任务执行失败); // 决定是否重试 if (context.Trigger.NumberOfErrors context.Trigger.MaxNumberOfErrors) { context.SetRetry(); // 标记为需要重试 } // 将异常信息保存到任务结果中 context.Result $失败{ex.Message}; context.ElapsedTime context.Stopwatch.ElapsedMilliseconds; throw; // 重新抛出异常以触发重试机制 } return Task.CompletedTask; } }在任务配置时可以设置重试参数builder.Services.AddSchedule(options { options.AddJobRobustJob(robust_job, Triggers.PeriodSeconds(30) .SetNumRetries(3) // 最大重试次数 .SetRetryTimeout(5000)); // 重试间隔(毫秒) });5.2 任务依赖与顺序执行有时我们需要确保某些任务按特定顺序执行或者一个任务依赖于另一个任务的结果。Furion虽然没有内置的依赖管理但我们可以通过任务状态来实现类似功能。public class DependentJob : IJob { private readonly ISchedulerFactory _scheduler; public DependentJob(ISchedulerFactory scheduler) { _scheduler scheduler; } public Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken) { // 检查前置任务是否完成 var prerequisiteJob _scheduler.GetJob(prerequisite_job); if (prerequisiteJob?.Status ! JobStatus.COMPLETED) { context.Result 等待前置任务完成; return Task.CompletedTask; // 本次不执行实际逻辑 } // 实际业务逻辑 ExecuteBusinessLogic(); // 触发后续任务 _scheduler.ResumeJob(next_job); return Task.CompletedTask; } }5.3 任务性能监控对于关键任务监控其执行性能和资源消耗非常重要。我们可以通过自定义拦截器来实现public class MonitoringJobInterceptor : IJobInterceptor { private readonly ILoggerMonitoringJobInterceptor _logger; public MonitoringJobInterceptor(ILoggerMonitoringJobInterceptor logger) { _logger logger; } public Task OnExecutiongAsync(JobExecutingContext context, CancellationToken stoppingToken) { _logger.LogInformation($任务[{context.JobDetail.JobId}]开始执行); context.Properties[StartTime] DateTime.Now; context.Properties[MemoryBefore] GC.GetTotalMemory(false); return Task.CompletedTask; } public Task OnExecutedAsync(JobExecutedContext context, CancellationToken stoppingToken) { var startTime (DateTime)context.Properties[StartTime]; var memoryBefore (long)context.Properties[MemoryBefore]; var memoryAfter GC.GetTotalMemory(false); _logger.LogInformation($任务[{context.JobDetail.JobId}]执行完成耗时{(DateTime.Now - startTime).TotalMilliseconds}ms内存变化{memoryAfter - memoryBefore}bytes); // 记录性能指标到数据库或监控系统 RecordPerformanceMetrics( context.JobDetail.JobId, DateTime.Now - startTime, memoryAfter - memoryBefore, context.Exception ! null); return Task.CompletedTask; } }注册拦截器builder.Services.AddSchedule(options { options.AddJobCriticalJob(critical_job, Triggers.Hourly()) .AddInterceptorMonitoringJobInterceptor(); });5.4 UI界面自定义Furion的ScheduleUI界面虽然开箱即用但有时我们需要根据业务需求进行一些自定义。app.UseScheduleUI(options { options.RequestPath /admin/jobs; options.DisplayName 后台任务管理; options.Theme dark; // 添加自定义CSS和JS options.AddCustomResource(/css/custom-schedule.css); options.AddCustomResource(/js/custom-schedule.js); // 控制列的显示/隐藏 options.ConfigureColumnVisibility(visible: [jobId, status, nextRunTime], hidden: [assemblyName, description]); // 添加自定义操作按钮 options.AddCustomAction(手动执行, jobId $executeJob({jobId})); });通过这些高级功能你可以构建出适应复杂业务场景的定时任务系统同时保持良好的可维护性和可观测性。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2568897.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!