【X23】 04 定时任务(Hangfire)

前言

知识点:
Hangfire官网
目的: 利用 Hangfire 生成可视化启停定时任务

1 简单介绍

在 .NET 和 .NET Core 应用程序中执行后台处理的简单方法。无需 Windows 服务或单独的进程。由持久存储支持。开放且免费用于商业用途。更多介绍...
Hangfire

2 如何完成一个用户可视化的定时任务看板

简单的定时任务通过 Hangfire 执行根据官网即可实现。基本使用方法暂不描述,官网已经十分十分详细了!在此主要解决怎么处理生成一个可视化的启停定时任务。当然,Hangfire自带看板,但那应该主要面对于开发等相关人员,用户往往只需要对一个任务看板进行任务的查看、启停以及让他按照什么样的规律去处理任务。
通过翻阅官网发现,执行周期任务只需要

RecurringJob.AddOrUpdate(() => Console.Write("Easy!"), Cron.Daily);

此时发现,如果我有很多定时任务则需要写很多 RecurringJob.AddOrUpdate 方法,导致很多重复性的代码,比如更改cron表达式,启停任务等。
设想,我们只写主要的定时任务所需要的 处理方法 ,对该方法标识为定时任务,则可以通过反射得到本程序的所有 定时任务。结合用户已定义的Cron表达式和启停操作,最终构造出用户可视化的定时任务看板。

3 用户可视化的定时任务看板实现

3.1 构建JobAttribute,标识方法为定时任务方法

/// <summary>
/// 定时任务
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class JobAttribute : Attribute
{
    /// <summary>
    /// 定时任务
    /// </summary>
    /// <param name="key">关键字</param>
    /// <param name="methtod">方法名称</param>
    /// <param name="cron">Cron表达式</param>
    /// <param name="jobName">任务名称</param>
    /// <param name="cronName">Cron表达式名称</param>
    public JobAttribute(string key, string methtod, string cron, string jobName, string cronName)
    {
        Key = key;
        Methtod = methtod;
        Cron = cron;
        JobName = jobName;
        CronName = cronName;
    }
    // ...省略
}

3.2 获取所有的定时任务

首先通过JobAttribute可以获取到所有的定时任务。此处采用静态字典存储。

internal static class StaticDic
{
    /// <summary>
    /// 全局静态变量
    /// </summary>
    internal static Dictionary<string, JobAttributeAndTypeDto> JobStaticDic = new();

    /// <summary>
    /// 初始化注册JobAttribute特性
    /// </summary>
    /// <param name="assemblies"></param>
    internal static void IniJobStaticDic(params Assembly[] assemblies)
    {
        var jobAttributeAndTypeDtoList = new List<JobAttributeAndTypeDto>();
        assemblies.ToList().ForEach(item =>
        {
            var attributes = item.DefinedTypes.SelectMany(x => x.DeclaredMethods
                    .Where(x => x.GetCustomAttribute(typeof(JobAttribute), false) != null))
                    .Select(x => new JobAttributeAndTypeDto
                    {
                        JobAttribute = x.GetCustomAttribute(typeof(JobAttribute), false) as JobAttribute,
                        ClassType = x.DeclaringType
                    })
                    .ToList();
            jobAttributeAndTypeDtoList.AddRange(attributes);
        });
        JobStaticDic = jobAttributeAndTypeDtoList.ToDictionary(
            x => x.JobAttribute.Key,
            y => new JobAttributeAndTypeDto
            {
                ClassType = y.ClassType,
                JobAttribute = y.JobAttribute
            });
    }
}

其次,通过Hangfire存储的Set以及Hash表可以查询关联得到定时任务的状态

select * from `Set`;
select * from `Hash`;

-- 查询Hangfire定时任务
SELECT tSet.`Key`,tHash.`Json`
FROM (SELECT `Value` AS `Key`,CONCAT("recurring-job",':',`Value`) AS `SetKey` FROM `Set`) tSet
LEFT JOIN
(
SELECT `Key` AS `HashKey`,CONCAT('{',GROUP_CONCAT('"',`Field`,'":"',`Value`,'"'),'}') AS `Json`
FROM `Hash`
WHERE `Field` IN ('CreatedAt','NextExecution','LastExecution')
GROUP BY `Key`
) `tHash`
ON tSet.SetKey = tHash.HashKey
定时任务关键字 任务描述Json(创建时间、上次执行时间、下次执行时间)
TestJobKey

此时,即可关联得到任务看板!

/// <summary>
/// 获取所有的job
/// </summary>
/// <returns></returns>
public List<AllJobDto> GetAllJob()
{
    var hanfireHashKeySetList = _dbConnection.Query<HanfireHashKeySet>(
                @"
                SELECT tSet.`Key`,tHash.`Json`
                FROM (SELECT `Value` AS `Key`,CONCAT(""recurring-job"",':',`Value`) AS `SetKey` FROM `Set`) tSet
                LEFT JOIN
                (
                SELECT `Key` AS `HashKey`, CONCAT('{', GROUP_CONCAT('""',`Field`, '"":""',`Value`, '""'), '}') AS `Json`
                FROM `Hash`
                WHERE `Field` IN('CreatedAt', 'NextExecution', 'LastExecution')
                GROUP BY `Key`
                ) `tHash`
                ON tSet.SetKey = tHash.HashKey
                "
        ).ToList();

    var dic = StaticDic.JobStaticDic.Select(x => x.Value.JobAttribute).OrderBy(x => x.Key).ToList();

    var result = from a in dic
                 join b in hanfireHashKeySetList
                 on a.Key equals b.Key
                 into temp
                 from tt in temp.DefaultIfEmpty()
                 select new AllJobDto
                 {
                     Key = a.Key,
                     Cron = a.Cron,
                     CronName = a.CronName,
                     JobName = a.JobName,
                     Methtod = a.Methtod,
                     JobStatus = tt == null ? JobStatus.No : JobStatus.Executing,
                     Json = tt == null ? "{}" : tt.Json
                 };
    return result.ToList();
}

3.3 启停定时任务

停止任务很简单,通过Hangfire自带Api RecurringJob.Trigger(key); 即可。那开始运行定时任务怎么处理呢?
前面我们用 JobStaticDic 去存储了所有的任务方法、key、Cron表达式等。故而实现入下:

/// <summary>
/// 运行周期任务
/// </summary>
/// <param name="key">指定标识符</param>
/// <returns></returns>
public bool RunJob(string key)
{
    RecurringJob.AddOrUpdate(key, () => DoWork(key), StaticDic.JobStaticDic[key].JobAttribute.Cron);

    return true;
}
/// <summary>
/// 运行周期任务
/// </summary>
/// <param name="key">指定标识符</param>
/// <returns></returns>
public async Task<bool> DoWork(string key)
{
    var jobAttributeDto = StaticDic.JobStaticDic[key];

    //C#反射调用外部Dll,执行其中异步函数并取返回值
    //https://www.cnblogs.com/cteng-common/p/9011920.html
    var obj = ActivatorUtilities.GetServiceOrCreateInstance(_serviceProvider, jobAttributeDto.ClassType);

    var data = jobAttributeDto.ClassType.GetMethod(jobAttributeDto.JobAttribute.Methtod).Invoke(obj, null);

    if (data is Task)
    {
        var task = data as Task;
        await task;
    }
    return true;
}

4 大功告成!源码下载

posted @ 2022-05-22 17:30  Boring246  阅读(668)  评论(0)    收藏  举报