namespace Admin.NET.Core.Service; /// /// 系统作业任务服务 /// [ApiDescriptionSettings(Order = 320)] public class SysJobService : IDynamicApiController, ITransient { private readonly SqlSugarRepository _sysJobDetailRep; private readonly SqlSugarRepository _sysJobTriggerRep; private readonly SqlSugarRepository _sysJobClusterRep; private readonly ISchedulerFactory _schedulerFactory; private readonly DynamicJobCompiler _dynamicJobCompiler; public SysJobService(SqlSugarRepository sysJobDetailRep, SqlSugarRepository sysJobTriggerRep, SqlSugarRepository sysJobClusterRep, ISchedulerFactory schedulerFactory, DynamicJobCompiler dynamicJobCompiler) { _sysJobDetailRep = sysJobDetailRep; _sysJobTriggerRep = sysJobTriggerRep; _sysJobClusterRep = sysJobClusterRep; _schedulerFactory = schedulerFactory; _dynamicJobCompiler = dynamicJobCompiler; } /// /// 获取作业分页列表 /// [DisplayName("获取作业分页列表")] public async Task> PageJobDetail(PageJobInput input) { var jobDetails = await _sysJobDetailRep.AsQueryable() .WhereIF(!string.IsNullOrWhiteSpace(input.JobId), u => u.JobId.Contains(input.JobId)) .WhereIF(!string.IsNullOrWhiteSpace(input.Description), u => u.Description.Contains(input.Description)) .Select(d => new JobOutput { JobDetail = d, }).ToPagedListAsync(input.Page, input.PageSize); await _sysJobDetailRep.AsSugarClient().ThenMapperAsync(jobDetails.Items, async u => { u.JobTriggers = await _sysJobTriggerRep.GetListAsync(t => t.JobId == u.JobDetail.JobId); }); // 提取中括号里面的参数值 var rgx = new Regex(@"(?i)(?<=\[)(.*)(?=\])"); foreach (var job in jobDetails.Items) { foreach (var jobTrigger in job.JobTriggers) { jobTrigger.Args = rgx.Match(jobTrigger.Args ?? "").Value; } } return jobDetails; } /// /// 添加作业 /// /// [ApiDescriptionSettings(Name = "AddJobDetail"), HttpPost] [DisplayName("添加作业")] public async Task AddJobDetail(AddJobDetailInput input) { var isExist = await _sysJobDetailRep.IsAnyAsync(u => u.JobId == input.JobId && u.Id != input.Id); if (isExist) throw Oops.Oh(ErrorCodeEnum.D1006); // 动态创建作业 Type jobType; switch (input.CreateType) { case JobCreateTypeEnum.Script when string.IsNullOrEmpty(input.ScriptCode): throw Oops.Oh(ErrorCodeEnum.D1701); case JobCreateTypeEnum.Script: { jobType = _dynamicJobCompiler.BuildJob(input.ScriptCode); if (jobType.GetCustomAttributes(typeof(JobDetailAttribute)).FirstOrDefault() is not JobDetailAttribute jobDetailAttribute) throw Oops.Oh(ErrorCodeEnum.D1702); if (jobDetailAttribute.JobId != input.JobId) throw Oops.Oh(ErrorCodeEnum.D1703); break; } case JobCreateTypeEnum.Http: jobType = typeof(HttpJob); break; default: throw new NotSupportedException(); } _schedulerFactory.AddJob( JobBuilder.Create(jobType) .LoadFrom(input.Adapt()).SetJobType(jobType)); // 延迟一下等待持久化写入,再执行其他字段的更新 await Task.Delay(500); await _sysJobDetailRep.AsUpdateable() .SetColumns(u => new SysJobDetail { CreateType = input.CreateType, ScriptCode = input.ScriptCode }) .Where(u => u.JobId == input.JobId).ExecuteCommandAsync(); } /// /// 更新作业 /// /// [ApiDescriptionSettings(Name = "UpdateJobDetail"), HttpPost] [DisplayName("更新作业")] public async Task UpdateJobDetail(UpdateJobDetailInput input) { var isExist = await _sysJobDetailRep.IsAnyAsync(u => u.JobId == input.JobId && u.Id != input.Id); if (isExist) throw Oops.Oh(ErrorCodeEnum.D1006); var sysJobDetail = await _sysJobDetailRep.GetFirstAsync(u => u.Id == input.Id); if (sysJobDetail.JobId != input.JobId) throw Oops.Oh(ErrorCodeEnum.D1704); var scheduler = _schedulerFactory.GetJob(sysJobDetail.JobId); var oldScriptCode = sysJobDetail.ScriptCode;//旧脚本代码 input.Adapt(sysJobDetail); if (input.CreateType == JobCreateTypeEnum.Script) { if (string.IsNullOrEmpty(input.ScriptCode)) throw Oops.Oh(ErrorCodeEnum.D1701); if (input.ScriptCode != oldScriptCode) { // 动态创建作业 var jobType = _dynamicJobCompiler.BuildJob(input.ScriptCode); if (jobType.GetCustomAttributes(typeof(JobDetailAttribute)).FirstOrDefault() is not JobDetailAttribute jobDetailAttribute) throw Oops.Oh(ErrorCodeEnum.D1702); if (jobDetailAttribute.JobId != input.JobId) throw Oops.Oh(ErrorCodeEnum.D1703); scheduler?.UpdateDetail(JobBuilder.Create(jobType).LoadFrom(sysJobDetail).SetJobType(jobType)); } } else { scheduler?.UpdateDetail(scheduler.GetJobBuilder().LoadFrom(sysJobDetail)); } // Tip: 假如这次更新有变更了 JobId,变更 JobId 后触发的持久化更新执行,会由于找不到 JobId 而更新不到数据 // 延迟一下等待持久化写入,再执行其他字段的更新 await Task.Delay(500); await _sysJobDetailRep.UpdateAsync(sysJobDetail); } /// /// 删除作业 /// /// [ApiDescriptionSettings(Name = "DeleteJobDetail"), HttpPost] [DisplayName("删除作业")] public async Task DeleteJobDetail(DeleteJobDetailInput input) { _schedulerFactory.RemoveJob(input.JobId); // 如果 _schedulerFactory 中不存在 JodId,则无法触发持久化,下面的代码确保作业和触发器能被删除 await _sysJobDetailRep.DeleteAsync(u => u.JobId == input.JobId); await _sysJobTriggerRep.DeleteAsync(u => u.JobId == input.JobId); } /// /// 获取触发器列表 /// [DisplayName("获取触发器列表")] public async Task> GetJobTriggerList([FromQuery] JobDetailInput input) { return await _sysJobTriggerRep.AsQueryable() .WhereIF(!string.IsNullOrWhiteSpace(input.JobId), u => u.JobId.Contains(input.JobId)) .ToListAsync(); } /// /// 添加触发器 /// /// [ApiDescriptionSettings(Name = "AddJobTrigger"), HttpPost] [DisplayName("添加触发器")] public async Task AddJobTrigger(AddJobTriggerInput input) { var isExist = await _sysJobTriggerRep.IsAnyAsync(u => u.TriggerId == input.TriggerId && u.Id != input.Id); if (isExist) throw Oops.Oh(ErrorCodeEnum.D1006); var jobTrigger = input.Adapt(); jobTrigger.Args = "[" + jobTrigger.Args + "]"; var scheduler = _schedulerFactory.GetJob(input.JobId); scheduler?.AddTrigger(Triggers.Create(input.AssemblyName, input.TriggerType).LoadFrom(jobTrigger)); } /// /// 更新触发器 /// /// [ApiDescriptionSettings(Name = "UpdateJobTrigger"), HttpPost] [DisplayName("更新触发器")] public async Task UpdateJobTrigger(UpdateJobTriggerInput input) { var isExist = await _sysJobTriggerRep.IsAnyAsync(u => u.TriggerId == input.TriggerId && u.Id != input.Id); if (isExist) throw Oops.Oh(ErrorCodeEnum.D1006); var jobTrigger = input.Adapt(); jobTrigger.Args = "[" + jobTrigger.Args + "]"; var scheduler = _schedulerFactory.GetJob(input.JobId); scheduler?.UpdateTrigger(Triggers.Create(input.AssemblyName, input.TriggerType).LoadFrom(jobTrigger)); } /// /// 删除触发器 /// /// [ApiDescriptionSettings(Name = "DeleteJobTrigger"), HttpPost] [DisplayName("删除触发器")] public async Task DeleteJobTrigger(DeleteJobTriggerInput input) { var scheduler = _schedulerFactory.GetJob(input.JobId); scheduler?.RemoveTrigger(input.TriggerId); // 如果 _schedulerFactory 中不存在 JodId,则无法触发持久化,下行代码确保触发器能被删除 await _sysJobTriggerRep.DeleteAsync(u => u.JobId == input.JobId && u.TriggerId == input.TriggerId); } /// /// 暂停所有作业 /// /// [DisplayName("暂停所有作业")] public void PauseAllJob() { _schedulerFactory.PauseAll(); } /// /// 启动所有作业 /// /// [DisplayName("启动所有作业")] public void StartAllJob() { _schedulerFactory.StartAll(); } /// /// 暂停作业 /// [DisplayName("暂停作业")] public void PauseJob(JobDetailInput input) { var scheduler = _schedulerFactory.GetJob(input.JobId); scheduler?.Pause(); } /// /// 启动作业 /// [DisplayName("启动作业")] public void StartJob(JobDetailInput input) { var scheduler = _schedulerFactory.GetJob(input.JobId); scheduler?.Start(); } /// /// 暂停触发器 /// [DisplayName("暂停触发器")] public void PauseTrigger(JobTriggerInput input) { var scheduler = _schedulerFactory.GetJob(input.JobId); scheduler?.PauseTrigger(input.TriggerId); } /// /// 启动触发器 /// [DisplayName("启动触发器")] public void StartTrigger(JobTriggerInput input) { var scheduler = _schedulerFactory.GetJob(input.JobId); scheduler?.StartTrigger(input.TriggerId); } /// /// 强制唤醒作业调度器 /// [DisplayName("强制唤醒作业调度器")] public void CancelSleep() { _schedulerFactory.CancelSleep(); } /// /// 强制触发所有作业持久化 /// [DisplayName("强制触发所有作业持久化")] public void PersistAll() { _schedulerFactory.PersistAll(); } /// /// 获取集群列表 /// [DisplayName("获取集群列表")] public async Task> GetJobClusterList() { return await _sysJobClusterRep.GetListAsync(); } }