RDIFramework.NET框架基于Quartz.Net實(shí)現(xiàn)任務(wù)調(diào)度詳解及效果展示

在上一篇Quartz.Net實(shí)現(xiàn)作業(yè)定時(shí)調(diào)度詳解,我們通過實(shí)例代碼詳細(xì)講解與演示了基于Quartz.NET開發(fā)的詳細(xì)方法。本篇我們主要講述基于RDIFramework.NET框架整合Quartz.NET,以實(shí)現(xiàn)任務(wù)調(diào)度,并對(duì)任務(wù)持久化操作的全過程。本文主要通過以下幾個(gè)方面講解:
- 1、任務(wù)調(diào)度概述
- 2 任務(wù)調(diào)度管理
- 2.1、Cron表達(dá)式
- 2.2、創(chuàng)建用戶過程調(diào)度任務(wù)
- 2.3、創(chuàng)建程序集任務(wù)
1、任務(wù)調(diào)度概述
任務(wù)調(diào)度在各種應(yīng)用中都會(huì)存在,在業(yè)務(wù)系統(tǒng)中我們?yōu)榱苏{(diào)度一些自動(dòng)執(zhí)行的任務(wù)或從隊(duì)列中消費(fèi)一些消息,所以基本上都會(huì)涉及到后臺(tái)服務(wù)的開發(fā)。在了解任務(wù)調(diào)度之前,我們先了解一下實(shí)現(xiàn)任務(wù)調(diào)度的Quartz.NET框架。
Quartz.NET是一個(gè)開源的作業(yè)調(diào)度框架,是OpenSymphony 的 Quartz API的.NET移植,它用C#寫成,可用于winform和asp.net應(yīng)用中。它提供了巨大的靈活性而不犧牲簡單性。你能夠用它來為執(zhí)行一個(gè)作業(yè)而創(chuàng)建簡單的或復(fù)雜的調(diào)度。它有很多特征,如:數(shù)據(jù)庫支持,集群,插件,支持cron-like表達(dá)式等等。你曾經(jīng)需要應(yīng)用執(zhí)行一個(gè)任務(wù)嗎?這個(gè)任務(wù)每天或每周星期二晚上11:30,或許僅僅每個(gè)月的最后一天執(zhí)行。一個(gè)自動(dòng)執(zhí)行而無須干預(yù)的任務(wù)在執(zhí)行過程中如果發(fā)生一個(gè)嚴(yán)重錯(cuò)誤,應(yīng)用能夠知到其執(zhí)行失敗并嘗試重新執(zhí)行嗎?你和你的團(tuán)隊(duì)是用.NET編程嗎?如果這些問題中任何一個(gè)你回答是,那么你應(yīng)該使用Quartz.NET調(diào)度器。 Quartz.NET允許開發(fā)人員根據(jù)時(shí)間間隔(或天)來調(diào)度作業(yè)。它實(shí)現(xiàn)了作業(yè)和觸發(fā)器的多對(duì)多關(guān)系,還能把多個(gè)作業(yè)與不同的觸發(fā)器關(guān)聯(lián)。整合了 Quartz.NET的應(yīng)用程序可以重用來自不同事件的作業(yè),還可以為一個(gè)事件組合多個(gè)作業(yè)。
總的來說就是Quartz.NET是一個(gè)開源的作業(yè)調(diào)度框架,非常適合在平時(shí)的工作中,定時(shí)輪詢數(shù)據(jù)庫同步,定時(shí)郵件通知,定時(shí)處理數(shù)據(jù)等。 Quartz.NET允許開發(fā)人員根據(jù)時(shí)間間隔(或天)來調(diào)度作業(yè)。它實(shí)現(xiàn)了作業(yè)和觸發(fā)器的多對(duì)多關(guān)系,還能把多個(gè)作業(yè)與不同的觸發(fā)器關(guān)聯(lián),配置靈活方便。相當(dāng)于數(shù)據(jù)庫中的 Job、Windows 的計(jì)劃任務(wù)、Unix/Linux 下的 Cron,但 Quartz 可以把排程控制的更精細(xì),對(duì)任務(wù)調(diào)度的領(lǐng)域問題進(jìn)行了高度的抽象,實(shí)現(xiàn)作業(yè)的靈活調(diào)度。

我們框架的任務(wù)調(diào)度就是基于Quartz.NET框架的整合使用,并對(duì)任務(wù)做了持久化的操作。
2、任務(wù)調(diào)度管理
“任務(wù)列表”管理模塊是放在“系統(tǒng)配置”->“任務(wù)調(diào)度”下的“任務(wù)列表”,任務(wù)列表如下圖所示。任務(wù)列表主界面左側(cè)顯示的是已經(jīng)創(chuàng)建的任務(wù),右側(cè)為當(dāng)前選中任務(wù)的執(zhí)行情況列表。在左側(cè)的任務(wù)列表最左邊的操作欄,可以通過操作按鈕對(duì)當(dāng)前任務(wù)做刪除、暫停、啟動(dòng)、刪除任務(wù)日志的操作。

在任務(wù)列表主界面頂部的工具欄中的按鈕可用來創(chuàng)建任務(wù),創(chuàng)建的任務(wù)分為兩種類型:
-
創(chuàng)建用戶過程調(diào)度任務(wù)。
-
創(chuàng)建程序集任務(wù)。
每種類型下的任務(wù)又可以分為簡單任務(wù)與復(fù)雜任務(wù)。簡單任務(wù)類似于定時(shí)器每隔特定的間隔時(shí)間觸發(fā),復(fù)雜任務(wù)主要是主要是借助CronTrigger表達(dá)式來實(shí)現(xiàn)類似數(shù)據(jù)庫中的計(jì)劃任務(wù)類型的工作。使用CronTrigger你可以指定諸如“每個(gè)周五中午”,或者“每個(gè)工作日的9:30”或者“從每個(gè)周一、周三、周五的上午9:00到上午10:00之間每隔五分鐘”這樣日程安排來觸發(fā)。甚至像SimpleTrigger(簡單任務(wù))一樣,CronTrigger也有一個(gè)StartTime以指定日程從什么時(shí)候開始,也有一個(gè)(可選的)EndTime以指定何時(shí)日程不再繼續(xù)。
下面的章節(jié)我們分別對(duì)這兩種任務(wù)類型做介紹。在介紹之前先了解學(xué)習(xí)一下Cron 表達(dá)式。
2.1、Quartz的cron表達(dá)式
Cron表達(dá)式是一個(gè)字符串,字符串以5或6個(gè)空格隔開,分為6或7個(gè)域,每一個(gè)域代表一個(gè)含義,Cron有如下兩種語法格式:
(1) Seconds Minutes Hours DayofMonth Month DayofWeek Year
(2)Seconds Minutes Hours DayofMonth Month DayofWeek
一、結(jié)構(gòu)
corn從左到右(用空格隔開):秒 分 小時(shí) 月份中的日期 月份 星期中的日期 年份
二、各字段的含義

注意:每一個(gè)域都使用數(shù)字,但還可以出現(xiàn)如下特殊字符,它們的含義是:
(1):表示匹配該域的任意值。假如在Minutes域使用, 即表示每分鐘都會(huì)觸發(fā)事件。
?。?)?:只能用在DayofMonth和DayofWeek兩個(gè)域。它也匹配域的任意值,但實(shí)際不會(huì)。因?yàn)镈ayofMonth和DayofWeek會(huì)相互影響。例如想在每月的20日觸發(fā)調(diào)度,不管20日到底是星期幾,則只能使用如下寫法: 13 13 15 20 * ?, 其中最后一位只能用?,而不能使用,如果使用表示不管星期幾都會(huì)觸發(fā),實(shí)際上并不是這樣。
?。?)-:表示范圍。例如在Minutes域使用5-20,表示從5分到20分鐘每分鐘觸發(fā)一次
(4)/:表示起始時(shí)間開始觸發(fā),然后每隔固定時(shí)間觸發(fā)一次。例如在Minutes域使用5/20,則意味著5分鐘觸發(fā)一次,而25,45等分別觸發(fā)一次.
(5),:表示列出枚舉值。例如:在Minutes域使用5,20,則意味著在5和20分每分鐘觸發(fā)一次。
?。?)L:表示最后,只能出現(xiàn)在DayofWeek和DayofMonth域。如果在DayofWeek域使用5L,意味著在最后的一個(gè)星期四觸發(fā)。
(7)W:表示有效工作日(周一到周五),只能出現(xiàn)在DayofMonth域,系統(tǒng)將在離指定日期的最近的有效工作日觸發(fā)事件。例如:在 DayofMonth使用5W,如果5日是星期六,則將在最近的工作日:星期五,即4日觸發(fā)。如果5日是星期天,則在6日(周一)觸發(fā);如果5日在星期一到星期五中的一天,則就在5日觸發(fā)。另外一點(diǎn),W的最近尋找不會(huì)跨過月份 。
(8)LW:這兩個(gè)字符可以連用,表示在某個(gè)月最后一個(gè)工作日,即最后一個(gè)星期五。
?。?)#:用于確定每個(gè)月第幾個(gè)星期幾,只能出現(xiàn)在DayofMonth域。例如在4#2,表示某月的第二個(gè)星期三。
三、常用表達(dá)式例子
?。?)0 0 2 1 * ? * 表示在每月的1日的凌晨2點(diǎn)調(diào)整任務(wù)
?。?)0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15執(zhí)行作業(yè)
?。?)0 15 10 ? 6L 2002-2006 表示2002-2006年的每個(gè)月的最后一個(gè)星期五上午10:15執(zhí)行作
?。?)0 0 10,14,16 * * ? 每天上午10點(diǎn),下午2點(diǎn),4點(diǎn)
?。?)0 0/30 9-17 * * ? 朝九晚五工作時(shí)間內(nèi)每半小時(shí)
?。?)0 0 12 ? * WED 表示每個(gè)星期三中午12點(diǎn)
?。?)0 0 12 * * ? 每天中午12點(diǎn)觸發(fā)
(8)0 15 10 ? * * 每天上午10:15觸發(fā)
?。?)0 15 10 * * ? 每天上午10:15觸發(fā)
(10)0 15 10 * * ? * 每天上午10:15觸發(fā)
(11)0 15 10 * * ? 2005 2005年的每天上午10:15觸發(fā)
(12)0 * 14 * * ? 在每天下午2點(diǎn)到下午2:59期間的每1分鐘觸發(fā)
?。?3)0 0/5 14 * * ? 在每天下午2點(diǎn)到下午2:55期間的每5分鐘觸發(fā)
?。?4)0 0/5 14,18 * * ? 在每天下午2點(diǎn)到2:55期間和下午6點(diǎn)到6:55期間的每5分鐘觸發(fā)
(15)0 0-5 14 * * ? 在每天下午2點(diǎn)到下午2:05期間的每1分鐘觸發(fā)
(16)0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44觸發(fā)
?。?7)0 15 10 ? * MON-FRI 周一至周五的上午10:15觸發(fā)
?。?8)0 15 10 15 * ? 每月15日上午10:15觸發(fā)
(19)0 15 10 L * ? 每月最后一日的上午10:15觸發(fā)
?。?0)0 15 10 ? * 6L 每月的最后一個(gè)星期五上午10:15觸發(fā)
(21)0 15 10 ? * 6L 2002-2005 2002年至2005年的每月的最后一個(gè)星期五上午10:15觸發(fā)
?。?2)0 15 10 ? * 6#3 每月的第三個(gè)星期五上午10:15觸發(fā)
注:
?。?)有些子表達(dá)式能包含一些范圍或列表
例如:
子表達(dá)式(天(星期))可以為 “MON-FRI”,“MON,WED,F(xiàn)RI”,“MON-WED,SAT”
“*”字符代表所有可能的值,
因此,“”在子表達(dá)式(月)里表示每個(gè)月的含義,“”在子表達(dá)式(天(星期))表示星期的每一天
“/”字符用來指定數(shù)值的增量
例如:在子表達(dá)式(分鐘)里的“0/15”表示從第0分鐘開始,每15分鐘
在子表達(dá)式(分鐘)里的“3/20”表示從第3分鐘開始,每20分鐘(它和“3,23,43”)的含義一樣
“?”字符僅被用于天(月)和天(星期)兩個(gè)子表達(dá)式,表示不指定值
當(dāng)2個(gè)子表達(dá)式其中之一被指定了值以后,為了避免沖突,需要將另一個(gè)子表達(dá)式的值設(shè)為“?”
“L” 字符僅被用于天(月)和天(星期)兩個(gè)子表達(dá)式,它是單詞“l(fā)ast”的縮寫
但是它在兩個(gè)子表達(dá)式里的含義是不同的。
在天(月)子表達(dá)式中,“L”表示一個(gè)月的最后一天
在天(星期)自表達(dá)式中,“L”表示一個(gè)星期的最后一天,也就是SAT
如果在“L”前有具體的內(nèi)容,它就具有其他的含義了
例如:“6L”表示這個(gè)月的倒數(shù)第6天,“FRIL”表示這個(gè)月的最一個(gè)星期五
注意:在使用“L”參數(shù)時(shí),不要指定列表或范圍,因?yàn)檫@會(huì)導(dǎo)致問題
四、表達(dá)式生成器
有很多的cron表達(dá)式在線生成器,這里給大家推薦幾款
http://www.pdtools.net/tools/becron.jsp
或者
2.2、創(chuàng)建用戶過程調(diào)度任務(wù)
過程調(diào)度任務(wù)簡單的理解就是可以執(zhí)行SQL語句或存儲(chǔ)過程等。創(chuàng)建用戶過程調(diào)度任務(wù)如下圖所示。

在創(chuàng)建用戶過程調(diào)試界面,“過程SQL”就是執(zhí)行的SQL語句或存儲(chǔ)過程或函數(shù)等。過程參數(shù)就是過程SQL中的參數(shù)列表對(duì)應(yīng)的參數(shù)值。創(chuàng)建的任務(wù)默認(rèn)是簡單任務(wù),如上圖我們創(chuàng)建了一個(gè)每1分鐘執(zhí)行一次的簡單過程任務(wù),其實(shí)“無限次”選中就表示不限次數(shù),否則可以指定執(zhí)行的次數(shù)。要?jiǎng)?chuàng)建復(fù)雜任務(wù)可以單擊“復(fù)雜任務(wù)”選項(xiàng)卡,如下圖所示。

復(fù)雜任務(wù)中的各時(shí)間項(xiàng)的配置就是Cron表達(dá)式,每單擊一個(gè)配置項(xiàng),右側(cè)都對(duì)該配置項(xiàng)進(jìn)行了詳細(xì)的設(shè)置說明。設(shè)置好后可以單擊“檢查表達(dá)式”來驗(yàn)證Cron表達(dá)式的正確性,如下圖所示。

單擊確認(rèn)按鈕即可成功創(chuàng)建任務(wù)。要?jiǎng)h除、暫停、重啟、刪除任務(wù)日志,只需選中任務(wù)后單擊當(dāng)前任務(wù)左側(cè)的操作按鈕區(qū)域?qū)?yīng)的操作按鈕即可,如下圖所示。

2.3、創(chuàng)建程序集任務(wù)
程序集任務(wù)簡單的理解就是創(chuàng)建一個(gè)自動(dòng)執(zhí)行的C#方法,程序集任務(wù)與用戶過程調(diào)度任務(wù)類似,也分簡單的任務(wù)與復(fù)雜的任務(wù),創(chuàng)建程序集任務(wù)如下圖所示。

我們?cè)?a target="_blank" rel="noopener nofollow">微信公眾號(hào)開發(fā)系列-玩轉(zhuǎn)微信開發(fā)-目錄匯總系列文章中對(duì)微信開發(fā)進(jìn)行了詳細(xì)的講解,我們知道微信提供的API大多都是以微信分配給我們的一個(gè)access_token為基礎(chǔ),Access Token相當(dāng)于打開這些服務(wù)的鑰匙,正常情況下會(huì)在7200秒內(nèi)失效。對(duì)于access_tokenr的詳細(xì)介紹可參考我們的:微信公眾號(hào)開發(fā)系列-4、獲取接口調(diào)用憑證
前面的開發(fā)我們都是失效后各自應(yīng)用去重新獲取access_token。雖然這樣操作可行但不是理想的操作方式,理想的操作方式應(yīng)該是后臺(tái)定時(shí)自動(dòng)刷新我們得到的access_token。我們可以使用任務(wù)調(diào)度來實(shí)現(xiàn)access_token的獲取。
在上圖中我們創(chuàng)建了一個(gè)每30分鐘自動(dòng)更新微信公眾號(hào)開發(fā)中的access_token,配置項(xiàng)的中程序集名稱格式為:命名空間+類,具體的開發(fā)方法可以參考我們?nèi)蝿?wù)調(diào)度中的Job事例。編寫的job只需要繼承我們的基類:ITaskJob,并實(shí)現(xiàn)以下方法即可。
1、public string RunJob(ref JobDataMap dataMap, string jobName, string id, string taskName)
2、public string RunJobBefore(JobEntity jobModel)
3、public string CloseJob(JobEntity jobModel)
參考代碼如下:
public class WeChatGetTokenJob : ITaskJob
{
public string RunJob(ref JobDataMap dataMap, string jobName, string id, string taskName)
{
int returnValue = 0;
List<KeyValuePair<string, object>> parmeters = new List<KeyValuePair<string, object>>
{
new KeyValuePair<string, object>(WeixinOfficialAccountTable.FieldDeleteMark, 0)
};
var listOfficialAccount = BaseEntity.GetList<WeixinOfficialAccountEntity>(RDIFrameworkService.Instance.WeixinBasicService.GetOfficialAccountDTByValues(parmeters));
if (listOfficialAccount != null && listOfficialAccount.Count() > 0)
{
foreach (WeixinOfficialAccountEntity entity in listOfficialAccount)
{
try
{
if (entity.Category == (int)WeChatSubscriberEnum.EnterpriseSubscriber)
{
if (!string.IsNullOrEmpty(entity.AppId) && !string.IsNullOrEmpty(entity.AppSecret))
{
//方法一:使用Senparc.WeiXin SDK的方法
entity.AccessToken = Senparc.Weixin.QY.CommonAPIs.CommonApi.GetToken(entity.AppId, entity.AppSecret).access_token;
entity.ModifiedBy = "job_rdiframework";
//方式二,直接調(diào)用微信的接口方法
//var url = string.Format("https://api.weixin.qq.com/cgi-bin/token?grant_type={0}&appid={1}&secret={2}", "client_credential".AsUrlData(), entity.AppId.AsUrlData(), entity.AppSecret.AsUrlData());
//AccessTokenResult result = Get.GetJson<AccessTokenResult>(url);
//entity.AccessToken = result.access_token;
entity.ModifiedOn = DateTime.Now;
returnValue += RDIFrameworkService.Instance.WeixinBasicService.UpdateOfficialAccount(entity);
}
}
else
{
if (!string.IsNullOrEmpty(entity.AppId) && !string.IsNullOrEmpty(entity.AppSecret))
{
//方法一:使用Senparc.WeiXin SDK的方法
entity.AccessToken = Senparc.Weixin.MP.CommonAPIs.CommonApi.GetToken(entity.AppId, entity.AppSecret).access_token;
entity.ModifiedBy = "job_rdiframework";
//方式二,直接調(diào)用微信的接口方法
//var url = string.Format("https://api.weixin.qq.com/cgi-bin/token?grant_type={0}&appid={1}&secret={2}", "client_credential".AsUrlData(), entity.AppId.AsUrlData(), entity.AppSecret.AsUrlData());
//AccessTokenResult result = Get.GetJson<AccessTokenResult>(url);
//entity.AccessToken = result.access_token;
entity.ModifiedOn = DateTime.Now;
returnValue += RDIFrameworkService.Instance.WeixinBasicService.UpdateOfficialAccount(entity);
}
}
}
catch (Exception ex)
{
LogHelper.WriteException(ex);
}
}
}
if (returnValue > 0)
{
TaskJob.UpdateState(jobName, 1, "成功");
}
return "批量更新Access_Token!";
}
public string RunJobBefore(JobEntity jobModel)
{
Log.Write("RunJobBefor", jobModel.taskName,"運(yùn)行");
List<KeyValuePair<string, object>> parmeters = new List<KeyValuePair<string, object>>
{
new KeyValuePair<string, object>(WeixinOfficialAccountTable.FieldDeleteMark, 0)
};
var listOfficialAccount = BaseEntity.GetList<WeixinOfficialAccountEntity>(RDIFrameworkService.Instance.WeixinBasicService.GetOfficialAccountDTByValues(parmeters));
if (listOfficialAccount == null || listOfficialAccount.Count() <= 0)
{
return "沒有符合獲取Access_Token的數(shù)據(jù)!";
}
return null;
}
public string CloseJob(JobEntity jobModel)
{
Log.Write("CloseJob", jobModel.taskName,"關(guān)閉");
TaskJob.UpdateState(jobModel.id, 3, "掛起");
return "關(guān)閉獲取Access_Token任務(wù)";
}
}
3、相關(guān)文章參考
一路走來數(shù)個(gè)年頭,感謝RDIFramework.NET框架的支持者與使用者,大家可以通過下面的地址了解詳情。
RDIFramework.NET官方網(wǎng)站:http://www.rdiframework.net/
RDIFramework.NET官方博客:http://blog.rdiframework.net/
同時(shí)需要說明的,以后的所有技術(shù)文章以官方網(wǎng)站為準(zhǔn),歡迎大家收藏!
RDIFramework.NET框架由專業(yè)團(tuán)隊(duì)長期打造、一直在更新、一直在升級(jí),請(qǐng)放心使用!
歡迎關(guān)注RDIFramework.net框架官方公眾微信(微信號(hào):guosisoft),及時(shí)了解最新動(dòng)態(tài)。
掃描二維碼立即關(guān)注

作者:
RDIF
出處:
http://www.rzrgm.cn/huyong/
Email:
406590790@qq.com
QQ:
406590790
微信:
13005007127(同手機(jī)號(hào))
框架官網(wǎng):
http://www.guosisoft.com/
http://www.rdiframework.net/
框架其他博客:
http://blog.csdn.net/chinahuyong
http://www.rzrgm.cn/huyong
國思RDIF開發(fā)框架
,
給用戶和開發(fā)者最佳的.Net框架平臺(tái)方案,為企業(yè)快速構(gòu)建跨平臺(tái)、企業(yè)級(jí)的應(yīng)用提供強(qiáng)大支持。
關(guān)于作者:系統(tǒng)架構(gòu)師、信息系統(tǒng)項(xiàng)目管理師、DBA。專注于微軟平臺(tái)項(xiàng)目架構(gòu)、管理和企業(yè)解決方案,多年項(xiàng)目開發(fā)與管理經(jīng)驗(yàn),曾多次組織并開發(fā)多個(gè)大型項(xiàng)目,在面向?qū)ο蟆⒚嫦蚍?wù)以及數(shù)據(jù)庫領(lǐng)域有一定的造詣。現(xiàn)主要從事基于
RDIF
框架的技術(shù)開發(fā)、咨詢工作,主要服務(wù)于金融、醫(yī)療衛(wèi)生、鐵路、電信、物流、物聯(lián)網(wǎng)、制造、零售等行業(yè)。
如有問題或建議,請(qǐng)多多賜教!
本文版權(quán)歸作者和CNBLOGS博客共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,如有問題,可以通過微信、郵箱、QQ等聯(lián)系我,非常感謝。

浙公網(wǎng)安備 33010602011771號(hào)