基于 abp vNext 和 .NET Core 开发博客项目 - 集成Hangfire实现定时任务处理
上一篇文章(https://www.cnblogs.com/meowv/p/12956696.html)成功使用了Redis缓存数据,大大提高博客的响应性能。
接下来,将完成一个任务调度中心,关于定时任务有多种处理方式,如果你的需求比较简单,比如就是单纯的过多少时间循环执行某个操作,可以直接使用.net core中内置的实现方式,新建一个类继承BackgroundService
,实现ExecuteAsync()
既可。
看一个例子,我们每过一秒输出一句HelloWorld,并写入日志中。
在.BackgroundJobs
中新建一个Jobs文件夹,添加HelloWorldJob.cs
,并且继承自BackgroundService
。
//HelloWorldJob.cs
using log4net;
using Microsoft.Extensions.Hosting;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Meowv.Blog.BackgroundJobs.Jobs
{
public class HelloWorldJob : BackgroundService
{
private readonly ILog _log;
public HelloWorldJob()
{
_log = LogManager.GetLogger(typeof(HelloWorldJob));
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var msg = $"CurrentTime:{ DateTime.Now}, Hello World!";
Console.WriteLine(msg);
_log.Info(msg);
await Task.Delay(1000, stoppingToken);
}
}
}
}
然后在.HttpApi.Hosting
层模块类中的ConfigureServices()
注入context.Services.AddTransient<IHostedService, HelloWorldJob>();
使用,运行一下看看效果。
可以看到已经成功输出了,你可以在ExecuteAsync()
中做你的事件处理逻辑。这应该是最简单后台定时任务处理了,比较单一。
在abp框架中,官方给我们提供了许多后台工作的集成方式,有兴趣的可以自行研究一下,文档地址:https://docs.abp.io/zh-Hans/abp/latest/Background-Jobs
在本项目中,我将使用 Hangfire 来完成定时任务处理,为什么选择它呢?因为简单,开箱即用。下面进入正题,可以先将 HelloWorldJob
停掉。
在.BackgroundJobs
中添加nuget包:Volo.Abp.BackgroundJobs.HangFire
、Hangfire.MySql.Core
、Hangfire.Dashboard.BasicAuthorization
、Volo.Abp.AspNetCore
,然后添加项目引用:.Domain
。
在根目录新建模块类:MeowvBlogBackgroundJobsModule.cs
,继承AbpModule
,依赖AbpBackgroundJobsHangfireModule
。
//MeowvBlogBackgroundJobsModule.cs
using Hangfire;
using Hangfire.MySql.Core;
using Meowv.Blog.Domain.Configurations;
using Meowv.Blog.Domain.Shared;
using Volo.Abp;
using Volo.Abp.BackgroundJobs.Hangfire;
using Volo.Abp.Modularity;
namespace Meowv.Blog.BackgroundJobs
{
[DependsOn(typeof(AbpBackgroundJobsHangfireModule))]
public class MeowvBlogBackgroundJobsModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddHangfire(config =>
{
config.UseStorage(
new MySqlStorage(AppSettings.ConnectionStrings,
new MySqlStorageOptions
{
TablePrefix = MeowvBlogConsts.DbTablePrefix + "hangfire"
}));
});
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
app.UseHangfireServer();
app.UseHangfireDashboard();
}
}
}
在ConfigureServices()
中添加配置,因为之前选用了MySQL,所以这里引用了Hangfire.MySql.Core
这个包,相对于的其它数据库可以在nuget上寻找。
在new MySqlStorage()
中配置连接字符串,new MySqlStorageOptions()
中配置表前缀,Hangfire会在第一次运行时,自动为我们创建表。
然后在OnApplicationInitialization()中
进行使用,app.UseHangfireServer()
必须调用,如果你不需要界面显示可以不用app.UseHangfireDashboard();
最后不要忘记,在.HttpApi.Hosting
层模块类中依赖定时任务模块MeowvBlogBackgroundJobsModule
。
现在运行一下项目,打开地址:.../hangfire 看看。
数据库默认已经为我们创建了hangfire所需的表。
有一个地方要注意,就是在连接字符串中需要开启用户变量,修改一下appsettings.json
中的连接字符串,在末尾添加:Allow User Variables=True
。
同时在app.UseHangfireDashboard()
中,还支持很多配置项,现在我们这个定时任务是公开的,如果我们不想要外人访问,可以开启BasicAuth。
现在配置文件中配置Hangfire的登录账号和密码。
...
"Hangfire": {
"Login": "meowv",
"Password": "123456"
}
...
...
/// <summary>
/// Hangfire
/// </summary>
public static class Hangfire
{
public static string Login => _config["Hangfire:Login"];
public static string Password => _config["Hangfire:Password"];
}
...
开启方式也很简单,之前已经引用了Hangfire.Dashboard.BasicAuthorization
这个包,直接看代码。
app.UseHangfireDashboard(options: new DashboardOptions
{
Authorization = new[]
{
new BasicAuthAuthorizationFilter(new BasicAuthAuthorizationFilterOptions
{
RequireSsl = false,
SslRedirect = false,
LoginCaseSensitive = true,
Users = new []
{
new BasicAuthAuthorizationUser
{
Login = AppSettings.Hangfire.Login,
PasswordClear = AppSettings.Hangfire.Password
}
}
})
},
DashboardTitle = "任务调度中心"
});
app.UseHangfireDashboard()
中可以自定义访问路径,我们这里没有传,就是用默认值。自定义界面的标题Title等等。更多参数可以自己看DashboardOptions
,结合情况来使用,编译运行看看效果。
现在就需要输入我们配置的账号密码才可以进入Hangfire界面了。
这样我们就集成好了Hangfire,并且还有了一个可视化的界面,接下来我们同样实现一个简单的定时任务看看效果。
在Jobs文件夹添加一个接口:IBackgroundJob
,让他继承ITransientDependency
,实现依赖注入,同时定义一个方法ExecuteAsync()
。
//IBackgroundJob.cs
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace Meowv.Blog.BackgroundJobs.Jobs
{
public interface IBackgroundJob : ITransientDependency
{
/// <summary>
/// 执行任务
/// </summary>
/// <returns></returns>
Task ExecuteAsync();
}
}
在Jobs文件夹新建文件夹Hangfire,添加HangfireTestJob.cs
,继承IBackgroundJob
实现ExecuteAsync()
方法。
//HangfireTestJob.cs
using System;
using System.Threading.Tasks;
namespace Meowv.Blog.BackgroundJobs.Jobs.Hangfire
{
public class HangfireTestJob : IBackgroundJob
{
public async Task ExecuteAsync()
{
Console.WriteLine("定时任务测试");
await Task.CompletedTask;
}
}
}
这样就完成了定时任务的逻辑,我们怎么来调用呢?新建一个扩展方法MeowvBlogBackgroundJobsExtensions.cs
。
//MeowvBlogBackgroundJobsExtensions.cs
using Hangfire;
using Meowv.Blog.BackgroundJobs.Jobs.Hangfire;
using Microsoft.Extensions.DependencyInjection;
using System;
namespace Meowv.Blog.BackgroundJobs
{
public static class MeowvBlogBackgroundJobsExtensions
{
public static void UseHangfireTest(this IServiceProvider service)
{
var job = service.GetService<HangfireTestJob>();
RecurringJob.AddOrUpdate("定时任务测试", () => job.ExecuteAsync(), CronType.Minute());
}
}
}
这里使用IServiceProvider
解析服务,获取到我们的实列,所以我们可以在模块类中的OnApplicationInitialization(...)
中直接调用此扩展方法。
RecurringJob.AddOrUpdate()
是定期作业按指定的计划触发任务,同时还有Enqueue
、Schedule
、ContinueJobWith
等等,可以看一下Hangfire官方文档:https://docs.hangfire.io/en/latest/
CronType是自定义的一个静态类,他帮我们自动生成了Cron表达式,这里表示一分钟执行一次,关于不懂Cron的同学,可以去自学一下,也许看看下面代码就懂了,也有许多Cron表达式在线生成的工具。
# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
*/30 * * * * /bin/python /qix/spider/spider.py #每30分钟执行一次
直接在根目录添加MeowvBlogCronType.cs
。
//MeowvBlogCronType.cs
using Hangfire;
using System;
namespace Meowv.Blog.BackgroundJobs
{
/// <summary>
/// Cron类型
/// </summary>
public static class CronType
{
/// <summary>
/// 周期性为分钟的任务
/// </summary>
/// <param name="interval">执行周期的间隔,默认为每分钟一次</param>
/// <returns></returns>
public static string Minute(int interval = 1)
{
return "1 0/" + interval.ToString() + " * * * ? ";
}
/// <summary>
/// 周期性为小时的任务
/// </summary>
/// <param name="minute">第几分钟开始,默认为第一分钟</param>
/// <param name="interval">执行周期的间隔,默认为每小时一次</param>
/// <returns></returns>
public static string Hour(int minute = 1, int interval = 1)
{
return "1 " + minute + " 0/" + interval.ToString() + " * * ? ";
}
/// <summary>
/// 周期性为天的任务
/// </summary>
/// <param name="hour">第几小时开始,默认从1点开始</param>
/// <param name="minute">第几分钟开始,默认从第1分钟开始</param>
/// <param name="interval">执行周期的间隔,默认为每天一次</param>
/// <returns></returns>
public static string Day(int hour = 1, int minute = 1, int interval = 1)
{
return "1 " + minute.ToString() + " " + hour.ToString() + " 1/" + interval.ToString() + " * ? ";
}
/// <summary>
/// 周期性为周的任务
/// </summary>
/// <param name="dayOfWeek">星期几开始,默认从星期一点开始</param>
/// <param name="hour">第几小时开始,默认从1点开始</param>
/// <param name="minute">第几分钟开始,默认从第1分钟开始</param>
/// <returns></returns>
public static string Week(DayOfWeek dayOfWeek = DayOfWeek.Monday, int hour = 1, int minute = 1)
{
return Cron.Weekly(dayOfWeek, hour, minute);
}
/// <summary>
/// 周期性为月的任务
/// </summary>
/// <param name="day">几号开始,默认从一号开始</param>
/// <param name="hour">第几小时开始,默认从1点开始</param>
/// <param name="minute">第几分钟开始,默认从第1分钟开始</param>
/// <returns></returns>
public static string Month(int day = 1, int hour = 1, int minute = 1)
{
return Cron.Monthly(day, hour, minute);
}
/// <summary>
/// 周期性为年的任务
/// </summary>
/// <param name="month">几月开始,默认从一月开始</param>
/// <param name="day">几号开始,默认从一号开始</param>
/// <param name="hour">第几小时开始,默认从1点开始</param>
/// <param name="minute">第几分钟开始,默认从第1分钟开始</param>
/// <returns></returns>
public static string Year(int month = 1, int day = 1, int hour = 1, int minute = 1)
{
return Cron.Yearly(month, day, hour, minute);
}
}
}
接着就可以调用定时任务了。
//MeowvBlogBackgroundJobsModule.cs
...
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
...
var service = context.ServiceProvider;
service.UseHangfireTest();
}
...
通过context.ServiceProvider
可以获取到IServiceProvider
,然后直接调用扩展方法,是不是超级简单,现在编译运行项目看效果。
可以看到已经有一个周期性的任务躺在那,每过一分钟都将执行一次,执行完成后如下图,可以很清楚的知道我们的任务当前状态。
关于任务是否真的运行成功,我们可以从输出看出。
完美,本篇完成了Hangfire的集成,并实现了一个定时任务计划,有没有发现很简单,你学会了吗?
基于 abp vNext 和 .NET Core 开发博客项目 - 集成Hangfire实现定时任务处理的更多相关文章
- 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(一)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(二)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(三)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(四)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(五)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(一)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(二)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(三)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(四)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
随机推荐
- 数学--数论--Alice and Bob (CodeForces - 346A )推导
It is so boring in the summer holiday, isn't it? So Alice and Bob have invented a new game to play. ...
- java基于socket的网络通信,实现一个服务端多个客户端的群聊,传输文件功能,界面使用Swing
最近在复习java的io流及网络编程.但复习写那些样板程序总是乏味的.便准备写个项目来巩固.想来想去还是聊天项目比较好玩.如果日后完成的比较好自己也可以用(哈哈哈).并且自己后面也要继续巩固java多 ...
- unittest 管理接口用例(数据分离-读取excel)
1.公共模块 ---> login.xls """ common (package) ---> ReadFile.py """ ...
- IIS搭建网站(二)
win+IIS+ASP+ACCESS第二种搭建方式 安装 控制面板”,依次选“添加/删除程序”, 添加/删除Windows组件 在应用程序服务器前打钩.点击详细信息 将“Internet信息服务(II ...
- python 中open文件路径的选择
一.问题描述 python 中使用open打开某个文件写入时,往往会发现需要写入的文件不在同级目录下.这样就需要根据文件的路径来找到并打开. 但往往有时绝对路径和相对路径,写入不正确就会打开失败. 二 ...
- Java基础-数据类型的拓展
整数拓展:进制 二进制 0b开头 十进制 八进制 0开头 十六进制 0x开头 0~9 A~F 16 int i = 10; int i1 = 010;//八进制 0开头 int i2 = 0x10;/ ...
- 错误:Several ports (8005, 8080, 8009) required by Tomcat v7.0 Server at localhost are already in use.
Several ports (8005, 8080, 8009) required by Tomcat v7.0 Server at localhost are already in use. The ...
- mysql中的读锁和写锁
当mysql为一个表开启读锁的时候,其他进程包括进程本身没有权利去修改这张表的内容,也没有权利更新,但是可以读取表里面的内容 如下例子 给表上读锁 接着更新一下,显示被锁了 释放锁并插入数据 写锁 查 ...
- 【Kafka】自定义分区策略
自定义分区策略 思路 Command+Option+shift+N 调出查询页面,找到producer包的Partitioner接口 Partitioner下有一个DefaultPartitioner ...
- [hdu3631]背包或中途相遇法
暴力的背包: #pragma comment(linker, "/STACK:10240000,10240000") #include <iostream> #incl ...