Skip to content

任务调度

添加任务调度

在 Program.cs 文件中添加任务调度

cs
using Cronos;
using FreeScheduler;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using ZhonTai.Admin.Core;
using ZhonTai.Admin.Core.Consts;
using ZhonTai.Admin.Core.Db;
using ZhonTai.Admin.Core.Startup;
using ZhonTai.Admin.Domain;
using ZhonTai.Admin.Tools.TaskScheduler;
using ZhonTai.Common.Extensions;
using TaskStatus = FreeScheduler.TaskStatus;

static void ConfigureScheduler(IFreeSql fsql)
{
    fsql.CodeFirst
    .ConfigEntity<TaskInfo>(a =>
    {
        a.Name("app_task");
    })
    .ConfigEntity<TaskLog>(a =>
    {
        a.Name("app_task_log");
    })
    .ConfigEntity<TaskInfoExt>(a =>
    {
        a.Name("app_task_ext");
    });
}

new HostApp(new HostAppOptions
{
    //配置FreeSql
    ConfigureFreeSql = (freeSql, dbConfig) =>
    {
        if (dbConfig.Key == DbKeys.TaskDb)
        {
            freeSql.SyncSchedulerStructure(dbConfig, ConfigureScheduler);
        }
    },

	//配置后置服务
	ConfigurePostServices = context =>
	{ 
        //添加任务调度
        context.Services.AddTaskScheduler(DbKeys.TaskDb, options =>
        {
            options.ConfigureFreeSql = ConfigureScheduler;
        });
    }
}).Run(args);

#if DEBUG
public partial class Program { }
#endif

更多任务表和任务日志表配置

cs
freeSql.CodeFirst
//配置任务表
.ConfigEntity<TaskInfo>(a =>
{
    a.Name("app_task");
    a.Property((TaskInfo b) => b.Id).IsPrimary(value: true);
    a.Property((TaskInfo b) => b.Body).StringLength(-1);
    a.Property((TaskInfo b) => b.Interval).MapType(typeof(int));
    a.Property((TaskInfo b) => b.IntervalArgument).StringLength(1024);
    a.Property((TaskInfo b) => b.Status).MapType(typeof(int));
    a.Property((TaskInfo b) => b.CreateTime).ServerTime(DateTimeKind.Local);
    a.Property((TaskInfo b) => b.LastRunTime).ServerTime(DateTimeKind.Local);
})
//配置任务日志表
.ConfigEntity<TaskLog>(a =>
{
    a.Name("app_task_log");
    a.Property((TaskLog b) => b.Exception).StringLength(-1);
    a.Property((TaskLog b) => b.Remark).StringLength(-1);
    a.Property((TaskLog b) => b.CreateTime).ServerTime(DateTimeKind.Local);
});

解析 Cron 表达式

1、安装解析Cron表达式包

cs
<PackageReference Include="Cronos" Version="0.8.0" />

2、解析Cron表达式

cs
new HostApp(new HostAppOptions
{
	//配置后置服务
	ConfigurePostServices = context =>
	{ 
        //添加任务调度
        context.Services.AddTaskScheduler(DbKeys.TaskDb, options =>
        {
            //配置任务调度
            options.ConfigureFreeSchedulerBuilder = freeSchedulerBuilder =>
            {
                freeSchedulerBuilder
                .UseCustomInterval(task =>
                {
                    //利用 cron 功能库解析 task.IntervalArgument 得到下一次执行时间
                    //与当前时间相减,得到 TimeSpan,若返回 null 则任务完成
                    var expression = CronExpression.Parse(task.IntervalArgument, CronFormat.IncludeSeconds);
                    var next = expression.GetNextOccurrence(DateTimeOffset.Now, TimeZoneInfo.Local);
                    var nextLocalTime = next?.DateTime;

                    return nextLocalTime == null ? null : nextLocalTime - DateTime.Now;
                });
            };
        });
    }
}).Run(args);

添加任务常量

在 MyCompanyName.MyProjectName.Api\Core\Consts 目录下添加任务常量类TaskNames.cs

cs
namespace MyCompanyName.MyProjectName.Api.Core.Consts;

/// <summary>
/// 任务常量
/// </summary>
public static partial class TaskNames
{
    /// <summary>
    /// 任务名称
    /// </summary>
    public const string ModuleTaskName = "ModuleTaskName";
}

添加 Shell 任务

下载 grpcurl 应用程序

https://github.com/fullstorydev/grpcurl/releases/tag/v1.8.7

建议使用 grpcurl v1.8.7 版本,v1.8.8+ 版本存在调用 grpc 方法异常的问题

添加或修改任务参数

json
{
  "desc": "任务说明",
  "fileName": "/app/bin/grpcurl",
  "arguments": "-plaintext -d \"{ \\\"id\\\": 1 }\" no-protocol-host:port YourNamespace.YourGrpcService/YourMethod"
}
json
{
  "desc": "任务说明",
  "fileName": "C:/grpcurl_1.8.7/grpcurl",
  "arguments": "-plaintext -d \"{ \\\"id\\\": 1 }\" no-protocol-host:port YourNamespace.YourGrpcService/YourMethod"
}

执行 Shell 任务

添加ShellArgs类

cs
namespace ZhonTai.Admin.Tools.TaskScheduler;

public class ShellArgs
{
    /// <summary>
    /// 执行应用
    /// </summary>
    public string FileName { get; set; }

    /// <summary>
    /// 执行参数
    /// </summary>
    public string Arguments { get; set; }

    /// <summary>
    /// 命令应用工作目录
    /// </summary>
    public string WorkingDirectory { get; set; }
}

执行 Shell 任务

cs
new HostApp(new HostAppOptions
{
	//配置后置服务
	ConfigurePostServices = context =>
	{ 
        //添加任务调度
        context.Services.AddTaskScheduler(DbKeys.TaskDb, options =>
        {
            //配置任务调度
            options.ConfigureFreeSchedulerBuilder = freeSchedulerBuilder =>
            {
                freeSchedulerBuilder
                .OnExecuting(task =>
                {
                    switch (task.Topic)
                    {
                        case "[system]shell":
                            var jsonArgs = JToken.Parse(task.Body);
                            var shellArgs = jsonArgs.Adapt<ShellArgs>();

                            var startInfo = new ProcessStartInfo
                            {
                                FileName = shellArgs.FileName,
                                Arguments = shellArgs.Arguments,
                                UseShellExecute = false,
                                RedirectStandardOutput = true,
                                RedirectStandardError = true,
                                WorkingDirectory = shellArgs.WorkingDirectory
                            };

                            var response = string.Empty;
                            var error = string.Empty;
                            using (var process = Process.Start(startInfo))
                            {
                                response = process.StandardOutput.ReadToEnd();
                                error = process.StandardError.ReadToEnd();

                                //if (response.NotNull())
                                //{
                                //    Console.WriteLine("Response:");
                                //    Console.WriteLine(response);
                                //}
                                
                                //if (error.NotNull())
                                //{
                                //    Console.WriteLine("Error:");
                                //    Console.WriteLine(error);
                                //}
                            
                                process.WaitForExit();
                            }

                            if(response.NotNull())
                                task.Remark(response);

                            if (error.NotNull())
                                throw new Exception(error);

                            break;
                    }
                });
            };
        });
    }
}).Run(args);

执行 Service 任务

cs
new HostApp(new HostAppOptions
{
	//配置后置服务
	ConfigurePostServices = context =>
	{ 
        //添加任务调度
        context.Services.AddTaskScheduler(DbKeys.TaskDb, options =>
        {
            //配置任务调度
            options.ConfigureFreeSchedulerBuilder = freeSchedulerBuilder =>
            {
                freeSchedulerBuilder
                .OnExecuting(task =>
                {
                    //匹配任务
                    switch (task.Topic)
                    {
                        //模块任务
                        case TaskNames.ModuleTaskName:
                            Task.Run(async () => {
                                using var scope = AppInfo.ServiceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope();
                                var moduleService = scope.ServiceProvider.GetRequiredService<IModuleService>();
                                var dics = JsonConvert.DeserializeObject<Dictionary<string, string>>(task.Body);
                                var moduleId = dics["moduleId"];
                                var result = await moduleService.GetAsync(moduleId.ToLong());
                                if (result.Success)
                                {
                                    //完成并结束任务
                                    task.Status = TaskStatus.Completed;
                                }
                            }).Wait();
                            break;
                    }
                });
            };
        });
    }
}).Run(args);

发送告警邮件

cs
new HostApp(new HostAppOptions
{
	//配置后置服务
	ConfigurePostServices = context =>
	{ 
        //添加任务调度
        context.Services.AddTaskScheduler(DbKeys.TaskDb, options =>
        {
            //配置任务调度
            options.ConfigureFreeSchedulerBuilder = freeSchedulerBuilder =>
            {
                freeSchedulerBuilder
                .OnExecuted((task, taskLog) =>
                {
                    if (!taskLog.Success)
                    {
                        //发送告警邮件
                        var taskService = AppInfo.GetRequiredService<TaskService>();
                        var emailService = AppInfo.GetRequiredService<EmailService>();
                        var alerEmail = taskService.GetAlerEmailAsync(task.Id).Result;
                        var topic = task.Topic;
                        if (alerEmail.NotNull())
                        {
                            var jsonArgs = JToken.Parse(task.Body);
                            var desc = jsonArgs["desc"]?.ToString();
                            if (desc.NotNull())
                                topic = desc;
                        }
                        alerEmail?.Split(',')?.ToList()?.ForEach(async address =>
                        {
                            await emailService.SingleSendAsync(new EamilSingleSendEvent
                            {
                                ToEmail = new EamilSingleSendEvent.Models.EmailModel
                                {
                                    Address = address,
                                    Name = address
                                },
                                Subject = "【任务调度中心】监控报警",
                                Body = $@"<p>任务名称:{topic}</p>
<p>任务编号:{task.Id}</p>
<p>告警类型:调度失败</p>
<p>告警内容:<br/>{taskLog.Exception}</p>"
                            });
                        });
                    }
                });
            };
        });
    }
}).Run(args);

添加任务

在模块服务中添加任务

cs
namespace MyCompanyName.MyProjectName.Api.Services.Module;

/// <summary>
/// 模块服务
/// </summary>
[DynamicApi(Area = ApiConsts.AreaName)]
public class ModuleService : BaseService, IModuleService, IDynamicApi
{
    private readonly Lazy<Scheduler> _scheduler;

    public TaskService(Lazy<Scheduler> scheduler)
    {
        _scheduler = scheduler;
    }

    /// <summary>
    /// 执行任务
    /// </summary>
    /// <returns></returns>
    public void ExecuteTask()
    {
        var scheduler = _scheduler.Value;

        //方式1:添加任务组,第一组每次间隔15秒,第二组每次间隔2分钟
        scheduler.AddTask(TaskNames.ModuleTaskName, JsonConvert.SerializeObject(new
        {
            moduleId = 1
        }), new int[] { 15, 15, 120, 120 });

        //方式2:添加任务,每次间隔15秒
        scheduler.AddTask(TaskNames.ModuleTaskName, JsonConvert.SerializeObject(new
        {
            moduleId = 1
        }), 2, 15);

        //方式3:无限循环任务,每次间隔10分钟
        scheduler.AddTask(TaskNames.ModuleTaskName, JsonConvert.SerializeObject(new
        {
            moduleId = 1
        }), -1, 600);

        //方式4:每天凌晨执行一次
        scheduler.AddTaskRunOnDay(TaskNames.ModuleTaskName, JsonConvert.SerializeObject(new
        {
            moduleId = 1
        }), 1, "0:00:00");

        //方式5:每周一晚上11点半执行一次,0为周日,1-6为周一至周六
        scheduler.AddTaskRunOnWeek(TaskNames.ModuleTaskName, JsonConvert.SerializeObject(new
        {
            moduleId = 1
        }), 1, "1:23:30:00");

        //方式6:每个月1号下午4点执行1次, -1为每月最后一日
        scheduler.AddTaskRunOnMonth(TaskNames.ModuleTaskName, JsonConvert.SerializeObject(new
        {
            moduleId = 1
        }), 1, "1:16:00:00");

        //方式7:自定义cron表达式,从0秒开始每10秒执行一次
        scheduler.AddTaskCustom(TaskNames.ModuleTaskName, JsonConvert.SerializeObject(new
        {
            moduleId = 1
        }), "0/10 * * * * ?");
    }
}