RDIFramework.NET框架基於Quartz.Net實現任務調度詳解及效果展示


在上一篇Quartz.Net實現作業定時調度詳解,我們通過實例代碼詳細講解與演示了基於Quartz.NET開發的詳細方法。本篇我們主要講述基於RDIFramework.NET框架整合Quartz.NET,以實現任務調度,並對任務持久化操作的全過程。本文主要通過以下幾個方面講解:

  • 1、任務調度概述
  • 2 任務調度管理
    • 2.1、Cron表達式
    • 2.2、創建用戶過程調度任務
    • 2.3、創建程序集任務

1、任務調度概述

任務調度在各種應用中都會存在,在業務系統中我們為了調度一些自動執行的任務或從隊列中消費一些消息,所以基本上都會涉及到后台服務的開發。在了解任務調度之前,我們先了解一下實現任務調度的Quartz.NET框架。

Quartz.NET是一個開源的作業調度框架,是OpenSymphony 的 Quartz API的.NET移植,它用C#寫成,可用於winform和asp.net應用中。它提供了巨大的靈活性而不犧牲簡單性。你能夠用它來為執行一個作業而創建簡單的或復雜的調度。它有很多特征,如:數據庫支持,集群,插件,支持cron-like表達式等等。你曾經需要應用執行一個任務嗎?這個任務每天或每周星期二晚上11:30,或許僅僅每個月的最后一天執行。一個自動執行而無須干預的任務在執行過程中如果發生一個嚴重錯誤,應用能夠知到其執行失敗並嘗試重新執行嗎?你和你的團隊是用.NET編程嗎?如果這些問題中任何一個你回答是,那么你應該使用Quartz.NET調度器。 Quartz.NET允許開發人員根據時間間隔(或天)來調度作業。它實現了作業和觸發器的多對多關系,還能把多個作業與不同的觸發器關聯。整合了 Quartz.NET的應用程序可以重用來自不同事件的作業,還可以為一個事件組合多個作業。

總的來說就是Quartz.NET是一個開源的作業調度框架,非常適合在平時的工作中,定時輪詢數據庫同步,定時郵件通知,定時處理數據等。 Quartz.NET允許開發人員根據時間間隔(或天)來調度作業。它實現了作業和觸發器的多對多關系,還能把多個作業與不同的觸發器關聯,配置靈活方便。相當於數據庫中的 Job、Windows 的計划任務、Unix/Linux 下的 Cron,但 Quartz 可以把排程控制的更精細,對任務調度的領域問題進行了高度的抽象,實現作業的靈活調度。

我們框架的任務調度就是基於Quartz.NET框架的整合使用,並對任務做了持久化的操作。

2、任務調度管理

“任務列表”管理模塊是放在“系統配置”->“任務調度”下的“任務列表”,任務列表如下圖所示。任務列表主界面左側顯示的是已經創建的任務,右側為當前選中任務的執行情況列表。在左側的任務列表最左邊的操作欄,可以通過操作按鈕對當前任務做刪除、暫停、啟動、刪除任務日志的操作。

在任務列表主界面頂部的工具欄中的按鈕可用來創建任務,創建的任務分為兩種類型:

  1. 創建用戶過程調度任務。

  2. 創建程序集任務。

每種類型下的任務又可以分為簡單任務與復雜任務。簡單任務類似於定時器每隔特定的間隔時間觸發,復雜任務主要是主要是借助CronTrigger表達式來實現類似數據庫中的計划任務類型的工作。使用CronTrigger你可以指定諸如“每個周五中午”,或者“每個工作日的9:30”或者“從每個周一、周三、周五的上午9:00到上午10:00之間每隔五分鍾”這樣日程安排來觸發。甚至像SimpleTrigger(簡單任務)一樣,CronTrigger也有一個StartTime以指定日程從什么時候開始,也有一個(可選的)EndTime以指定何時日程不再繼續。
下面的章節我們分別對這兩種任務類型做介紹。在介紹之前先了解學習一下Cron 表達式。

2.1、Quartz的cron表達式

Cron表達式是一個字符串,字符串以5或6個空格隔開,分為6或7個域,每一個域代表一個含義,Cron有如下兩種語法格式:

(1) Seconds Minutes Hours DayofMonth Month DayofWeek Year

(2)Seconds Minutes Hours DayofMonth Month DayofWeek

一、結構

  corn從左到右(用空格隔開):秒 分 小時 月份中的日期 月份 星期中的日期 年份

二、各字段的含義

Cron各字段的含義

  注意:每一個域都使用數字,但還可以出現如下特殊字符,它們的含義是:

  (1):表示匹配該域的任意值。假如在Minutes域使用, 即表示每分鍾都會觸發事件。

  (2)?:只能用在DayofMonth和DayofWeek兩個域。它也匹配域的任意值,但實際不會。因為DayofMonth和DayofWeek會相互影響。例如想在每月的20日觸發調度,不管20日到底是星期幾,則只能使用如下寫法: 13 13 15 20 * ?, 其中最后一位只能用?,而不能使用,如果使用表示不管星期幾都會觸發,實際上並不是這樣。

  (3)-:表示范圍。例如在Minutes域使用5-20,表示從5分到20分鍾每分鍾觸發一次

  (4)/:表示起始時間開始觸發,然后每隔固定時間觸發一次。例如在Minutes域使用5/20,則意味着5分鍾觸發一次,而25,45等分別觸發一次.

  (5),:表示列出枚舉值。例如:在Minutes域使用5,20,則意味着在5和20分每分鍾觸發一次。

  (6)L:表示最后,只能出現在DayofWeek和DayofMonth域。如果在DayofWeek域使用5L,意味着在最后的一個星期四觸發。

  (7)W:表示有效工作日(周一到周五),只能出現在DayofMonth域,系統將在離指定日期的最近的有效工作日觸發事件。例如:在 DayofMonth使用5W,如果5日是星期六,則將在最近的工作日:星期五,即4日觸發。如果5日是星期天,則在6日(周一)觸發;如果5日在星期一到星期五中的一天,則就在5日觸發。另外一點,W的最近尋找不會跨過月份 。

  (8)LW:這兩個字符可以連用,表示在某個月最后一個工作日,即最后一個星期五。

  (9)#:用於確定每個月第幾個星期幾,只能出現在DayofMonth域。例如在4#2,表示某月的第二個星期三。

三、常用表達式例子

  (1)0 0 2 1 * ? * 表示在每月的1日的凌晨2點調整任務

  (2)0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15執行作業

  (3)0 15 10 ? 6L 2002-2006 表示2002-2006年的每個月的最后一個星期五上午10:15執行作

  (4)0 0 10,14,16 * * ? 每天上午10點,下午2點,4點

  (5)0 0/30 9-17 * * ? 朝九晚五工作時間內每半小時

  (6)0 0 12 ? * WED 表示每個星期三中午12點

  (7)0 0 12 * * ? 每天中午12點觸發

  (8)0 15 10 ? * * 每天上午10:15觸發

  (9)0 15 10 * * ? 每天上午10:15觸發

  (10)0 15 10 * * ? * 每天上午10:15觸發

  (11)0 15 10 * * ? 2005 2005年的每天上午10:15觸發

  (12)0 * 14 * * ? 在每天下午2點到下午2:59期間的每1分鍾觸發

  (13)0 0/5 14 * * ? 在每天下午2點到下午2:55期間的每5分鍾觸發

  (14)0 0/5 14,18 * * ? 在每天下午2點到2:55期間和下午6點到6:55期間的每5分鍾觸發

  (15)0 0-5 14 * * ? 在每天下午2點到下午2:05期間的每1分鍾觸發

  (16)0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44觸發

  (17)0 15 10 ? * MON-FRI 周一至周五的上午10:15觸發

  (18)0 15 10 15 * ? 每月15日上午10:15觸發

  (19)0 15 10 L * ? 每月最后一日的上午10:15觸發

  (20)0 15 10 ? * 6L 每月的最后一個星期五上午10:15觸發

  (21)0 15 10 ? * 6L 2002-2005 2002年至2005年的每月的最后一個星期五上午10:15觸發

  (22)0 15 10 ? * 6#3 每月的第三個星期五上午10:15觸發

注:

  (1)有些子表達式能包含一些范圍或列表

  例如:

  子表達式(天(星期))可以為 “MON-FRI”,“MON,WED,FRI”,“MON-WED,SAT”

  “*”字符代表所有可能的值,

  因此,“”在子表達式(月)里表示每個月的含義,“”在子表達式(天(星期))表示星期的每一天

  “/”字符用來指定數值的增量

  例如:在子表達式(分鍾)里的“0/15”表示從第0分鍾開始,每15分鍾

  在子表達式(分鍾)里的“3/20”表示從第3分鍾開始,每20分鍾(它和“3,23,43”)的含義一樣

  “?”字符僅被用於天(月)和天(星期)兩個子表達式,表示不指定值

  當2個子表達式其中之一被指定了值以后,為了避免沖突,需要將另一個子表達式的值設為“?”

  “L” 字符僅被用於天(月)和天(星期)兩個子表達式,它是單詞“last”的縮寫

  但是它在兩個子表達式里的含義是不同的。

  在天(月)子表達式中,“L”表示一個月的最后一天

  在天(星期)自表達式中,“L”表示一個星期的最后一天,也就是SAT

  如果在“L”前有具體的內容,它就具有其他的含義了

  例如:“6L”表示這個月的倒數第6天,“FRIL”表示這個月的最一個星期五

  注意:在使用“L”參數時,不要指定列表或范圍,因為這會導致問題

四、表達式生成器

有很多的cron表達式在線生成器,這里給大家推薦幾款

http://www.pdtools.net/tools/becron.jsp

或者

http://cron.qqe2.com/

2.2、創建用戶過程調度任務

過程調度任務簡單的理解就是可以執行SQL語句或存儲過程等。創建用戶過程調度任務如下圖所示。

在創建用戶過程調試界面,“過程SQL”就是執行的SQL語句或存儲過程或函數等。過程參數就是過程SQL中的參數列表對應的參數值。創建的任務默認是簡單任務,如上圖我們創建了一個每1分鍾執行一次的簡單過程任務,其實“無限次”選中就表示不限次數,否則可以指定執行的次數。要創建復雜任務可以單擊“復雜任務”選項卡,如下圖所示。

復雜任務中的各時間項的配置就是Cron表達式,每單擊一個配置項,右側都對該配置項進行了詳細的設置說明。設置好后可以單擊“檢查表達式”來驗證Cron表達式的正確性,如下圖所示。

單擊確認按鈕即可成功創建任務。要刪除、暫停、重啟、刪除任務日志,只需選中任務后單擊當前任務左側的操作按鈕區域對應的操作按鈕即可,如下圖所示。

2.3、創建程序集任務

程序集任務簡單的理解就是創建一個自動執行的C#方法,程序集任務與用戶過程調度任務類似,也分簡單的任務與復雜的任務,創建程序集任務如下圖所示。

我們在微信公眾號開發系列-玩轉微信開發-目錄匯總系列文章中對微信開發進行了詳細的講解,我們知道微信提供的API大多都是以微信分配給我們的一個access_token為基礎,Access Token相當於打開這些服務的鑰匙,正常情況下會在7200秒內失效。對於access_tokenr的詳細介紹可參考我們的:微信公眾號開發系列-4、獲取接口調用憑證

前面的開發我們都是失效后各自應用去重新獲取access_token。雖然這樣操作可行但不是理想的操作方式,理想的操作方式應該是后台定時自動刷新我們得到的access_token。我們可以使用任務調度來實現access_token的獲取。

在上圖中我們創建了一個每30分鍾自動更新微信公眾號開發中的access_token,配置項的中程序集名稱格式為:命名空間+類,具體的開發方法可以參考我們任務調度中的Job事例。編寫的job只需要繼承我們的基類:ITaskJob,並實現以下方法即可。

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";
                            //方式二,直接調用微信的接口方法
                            //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";
                            //方式二,直接調用微信的接口方法
                            //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,"運行");
        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的數據!";
        }

        return null;
    }


    public string CloseJob(JobEntity jobModel)
    {
        Log.Write("CloseJob", jobModel.taskName,"關閉");
        TaskJob.UpdateState(jobModel.id, 3, "掛起");
        return "關閉獲取Access_Token任務";
    }
}

3、相關文章參考


一路走來數個年頭,感謝RDIFramework.NET框架的支持者與使用者,大家可以通過下面的地址了解詳情。

RDIFramework.NET官方網站:http://www.rdiframework.net/

RDIFramework.NET官方博客:http://blog.rdiframework.net/

同時需要說明的,以后的所有技術文章以官方網站為准,歡迎大家收藏!

RDIFramework.NET框架由專業團隊長期打造、一直在更新、一直在升級,請放心使用!

歡迎關注RDIFramework.net框架官方公眾微信(微信號:guosisoft),及時了解最新動態。

掃描二維碼立即關注

微信號:guosisoft


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM