用abp vNext快速開發Quartz.NET定時任務管理界面

今天這篇文章我將經過實例代碼帶着你們一步一步經過abp vNext這個asp.net core的快速開發框架來進行Quartz.net定時任務調度的管理界面的開發。大夥最好跟着一塊兒敲一下代碼,固然源碼我會上傳到github上,有興趣的小夥伴能夠在文章底部查看源碼連接。html

做者:依樂祝
原文連接:http://www.javashuo.com/article/p-xsaecxlv-kt.htmlgit

寫在前面

有幾天沒更新博客了,一方面由於比較忙,另外一方面是由於最近在準備組織咱們霸都合肥的.NET技術社區首次非正式的線下聚會,忙着聯繫人啊,這裏歡迎有興趣的小夥伴加我wx:jkingzhu進行詳細的瞭解,固然也歡迎同行加我微信,而後我拉你進入咱們合肥.NET技術社區微信羣跟大夥進行交流。github

概念

開始以前還有必要跟大夥說一下abp vNext以及Quartz.net是什麼,防止有小白。若是對這兩個概念很是熟悉的話能夠直接閱讀下一節。項目最終實現的效果以下圖所示:web

1551252591337

abp vNext是什麼

提及abp vNext就要從另外一個概念開始提及了,那就是大名鼎鼎的ABP了。
ABP 官方的介紹是:ASP.NET Boilerplate 是一個用最佳實踐和流行技術開發現代 WEB 應用程序的新起點,它旨在成爲一個通用的 WEB 應用程序基礎框架和項目模板。基於 DDD 的經典分層架構思想,實現了衆多 DDD 的概念(但沒有實現全部 DDD 的概念)。
而ABPVNext的出現是爲了拋棄掉.net framework 版本下的包袱,從新啓動的 abp 框架,目的是爲了放棄對傳統技術的支持,讓 asp.net core 可以自身作到更加的模塊化,目前這塊的內容還不夠成熟。緣由是缺乏組件信息和內容。
若是你想用於生產環境建議你可使用ABP,若是你勇於嘗試,敢於創新的話能夠直接使用abp vNext進行開發的。
abp vNext官網:https://abp.io/
github:https://github.com/abpframework/abp
文檔:https://abp.io/documentssql

Quartz.NET是什麼

Quartz.NET是一個強大、開源、輕量的做業調度框架,你可以用它來爲執行一個做業而建立簡單的或複雜的做業調度。它有不少特徵,如:數據庫支持,集羣,插件,支持cron-like表達式等等。目前已經正式支持了.NET Core 和async/await。
說白了就是你可使用Quartz.NET能夠很方便的開發定時任務諸如平時的工做中,定時輪詢數據庫同步,定時郵件通知,定時處理數據等。數據庫

實例演練

這一節咱們經過實例進行操做,相信跟着作的你也可以把代碼跑起來。json

ABP vNext代碼

既然咱們這次演練的項目是使用的abp vNext這個asp.net core的快速開發框架來完成的,因此首先在項目開始以前,你須要到ABP vNext的官網上去下載項目代碼。英文站打開慢的話,能夠訪問中文子域名進行訪問:https://cn.abp.io/Templates 。下面給出具體步驟:c#

  1. 打開https://cn.abp.io/Templates 而後如圖填寫對應的項目名稱,這裏我用的Czar.AbpDemo 項目類型選擇ASP.NET Core MVC應用程序,由於這個是帶有UI界面的web項目,數據庫提供程序選擇EFCore這個你們都比較熟悉,而後點擊建立就能夠了。微信

    1551248124416

  2. 下載後,解壓到一個文件夾下面,而後用vs打開解決方案,看到以下圖所示的項目結構架構

    1551248279486

  3. 這裏簡單介紹下,每一個項目的做用,具體的就不過多介紹了,在下面的實戰代碼中慢慢體會吧

    • .Domain 爲領域層.
    • .Application 爲應用層.
    • .Web 爲是表示層.
    • .EntityFrameworkCore 是EF Core集成.

    解決方案還包含配置好的的單元&集成測試項目, 以便與於EF CoreSQLite 數據庫配合使用.

  4. 查看.Web項目下appsettings.json文件中的 鏈接字符串並進行相應的修改,怎麼改不要問我:

    {
      "ConnectionStrings": {
        "Default": "Server=localhost;Database=CzarAbpDemo;Trusted_Connection=True;MultipleActiveResultSets=true"
      }
    }
  5. 右鍵單擊.Web項目並將其設爲啓動項目

    1551248539374

  6. 打開包管理器控制檯(Package Manager Console), 選擇.EntityFrameworkCore項目做爲默認項目並運行Update-Database命令:

    1551248591831

  7. 如今能夠運行應用程序,它將會打開home頁面:

    1551248689114

  8. 點擊「Login」 輸入用戶名admin, 密碼1q2w3E*, 登陸應用程序.

    啓動模板包括 身份管理(identity management) 模塊. 登陸後將提供身份管理菜單,你能夠在其中管理角色,用戶及其權限. 這個不過多講解了,本身去動手操做一番吧

集成Quartz.NET管理功能

這部分咱們將實現Quartz.NET定時任務的管理功能,爲了進行Quartz.NET定時任務的管理,咱們還須要定義一個表來進行Quartz.NET定時任務的信息的承載,並完成這個表的增刪改查功能,這樣咱們在對這個表的數據進行操做的同時來進行Quartz.NET定時任務的操做便可實現咱們的需求。話很少說,開始吧。這部分咱們再分紅兩個小節:JobInfo的增刪改查功能的實現,Quartz.NET調度任務功能的增刪改查的實現。

JobInfo的增刪改查功能的實現

這個部分你將體會到我爲何使用abp vNext框架來進行開發了,就是由於快~~~~

  1. 建立領域實體對象JobInfo,這個在領域層代碼以下:

    1551249480050

  2. 將咱們的JobInfo實體添加到DBContext中,這樣應該在EF層

    1551249406105

  3. 添加新的Migration並更新到數據庫中,這個應該算EFCore的基礎了吧,兩個步驟,一個「Add-Migration」 而後「Update-Database」更新到數據庫便可

    Add-Migration "Add_JobInfo_Entity"
    Update-Database
  4. 應用層建立頁面顯示實體BookDto 用來在 基礎設施層 和 應用層 傳遞數據

    1551249983515

  5. 一樣的你還須要在應用層建立一個用來傳遞增改的Dto對象
    1551250041669

  6. 萬事俱備,只欠服務了,接下來咱們建立一下JobInfo的服務接口以及服務接口的實現了,這裏有個約定,就是全部的服務AppService結尾,就跟控制器都以Controller結尾的概念差很少。

    1551250166378

    服務實現:

    1551250189323

    註釋還算清真,相信你應該能看懂。

  7. 這裏abp vNext框架就會自動爲咱們實現增刪改查的API Controllers接口的實現(能夠經過swagger進行查看),還會自動 爲全部的API接口建立了JavaScript 代理.所以,你能夠像調用 JavaScript function同樣調用任何接口.

    以下圖所示

    1551250400532
    是否是,感受什麼都還沒作,全部接口都已經實現的感受。

  8. 新增一個菜單任務調度的菜單,以下代碼所示:

    1551250546971

  9. 對應的,咱們須要在Pages/JobSchedule 這個路徑下面建立對應的Index.cshtml頁面,以及新增,編輯的頁面。因爲內容太多,這裏就不貼代碼了,只給你們貼下圖:

    Index.cshtml

    1551250659492

    CreateModal.cshtml代碼以下:

    1551250688733

  10. 而後咱們運行起來查看下:

    1551250773502

  11. 點擊,右上角的新增,會彈出新增界面,點擊每一行的操做,會彈出刪除(刪除,這裏只作了一個假功能),編輯的兩個選項。

  12. 到此,JobInfo的增刪改查就作好了,是否是很簡單,這就是abp vNext賦予咱們的高效之處。

Quartz.NET調度任務功能的增刪改的實現

在使用Quartz.NET以前,你須要經過Nuget進行下安裝,而後才能進行調用。這裏我不會給你詳細講解Quartz.NET的使用,由於這將佔用大量的篇幅,並偏離本文的主旨

  1. 安裝Quartz.NET的Nuget包:

    1551251014507

  2. 新建一個ScheduleCenter 的任務調度中心,代碼以下所示:

    /// <summary>
        /// 任務調度中心
        /// </summary>
        public class ScheduleCenter
        {
            private readonly ILogger _logger;
            public ScheduleCenter(ILogger<ScheduleCenter> logger)
            {
                _logger = logger;
            }
    
            /// <summary>
            /// 任務計劃
            /// </summary>
            public IScheduler scheduler = null;
            public  async Task<IScheduler> GetSchedulerAsync()
            {
                if (scheduler != null)
                {
                    return scheduler;
                }
                else
                {
                    // 從Factory中獲取Scheduler實例
                    NameValueCollection props = new NameValueCollection
                    {
                        { "quartz.serializer.type", "binary" },
                        //如下配置須要數據庫表配合使用,表結構sql地址:https://github.com/quartznet/quartznet/tree/master/database/tables
                        //{ "quartz.jobStore.type","Quartz.Impl.AdoJobStore.JobStoreTX, Quartz"},
                        //{ "quartz.jobStore.driverDelegateType","Quartz.Impl.AdoJobStore.StdAdoDelegate, Quartz"},
                        //{ "quartz.jobStore.tablePrefix","QRTZ_"},
                        //{ "quartz.jobStore.dataSource","myDS"},
                        //{ "quartz.dataSource.myDS.connectionString",AppSettingHelper.MysqlConnection},//鏈接字符串
                        //{ "quartz.dataSource.myDS.provider","MySql"},
                        //{ "quartz.jobStore.usePropert ies","true"}
    
                    };
                    StdSchedulerFactory factory = new StdSchedulerFactory(props);
                    return await factory.GetScheduler();
    
                }
            }
    
            /// <summary>
            /// 添加調度任務
            /// </summary>
            /// <param name="jobName">任務名稱</param>
            /// <param name="jobGroup">任務分組</param>
            /// <returns></returns>
            public async Task<bool> AddJobAsync(CreateUpdateJobInfoDto infoDto)
            {
                try
                {
                    if (infoDto!=null)
                    {
                        if (infoDto.StarTime == null)
                        {
                            infoDto.StarTime = DateTime.Now;
                        }
                        DateTimeOffset starRunTime = DateBuilder.NextGivenSecondDate(infoDto.StarTime, 1);
                        if (infoDto.EndTime == null)
                        {
                            infoDto.EndTime = DateTime.MaxValue.AddDays(-1);
                        }
                        DateTimeOffset endRunTime = DateBuilder.NextGivenSecondDate(infoDto.EndTime, 1);
                        scheduler = await GetSchedulerAsync();
                        JobKey jobKey = new JobKey(infoDto.JobName, infoDto.JobGroup);
                        if (await scheduler.CheckExists(jobKey))
                        {
                            await scheduler.PauseJob(jobKey);
                            await scheduler.DeleteJob(jobKey);
                        }
                        IJobDetail job = JobBuilder.Create<LogTestJob>()
                          .WithIdentity(jobKey)
                          .Build();
                        ICronTrigger trigger = (ICronTrigger)TriggerBuilder.Create()
                                                     .StartAt(starRunTime)
                                                     .EndAt(endRunTime)
                                                     .WithIdentity(infoDto.JobName, infoDto.JobGroup)
                                                     .WithCronSchedule(infoDto.CronExpress)
                                                     .Build();
                        await scheduler.ScheduleJob(job, trigger);
                        await scheduler.Start();
                        return true;
                    }
    
                    return false;//JobInfo爲空
                }
                catch (Exception ex)
                {
                    _logger.LogException(ex);
                    return false;//出現異常
                }
            }
    
            /// <summary>
            /// 暫停指定任務計劃
            /// </summary>
            /// <param name="jobName">任務名</param>
            /// <param name="jobGroup">任務分組</param>
            /// <returns></returns>
            public async Task<bool> StopJobAsync(string jobName, string jobGroup)
            {
                try
                {
                    JobKey jobKey = new JobKey(jobName, jobGroup);
                    scheduler = await GetSchedulerAsync();
                    if (await scheduler.CheckExists(jobKey))
                    {
                        await scheduler.PauseJob(new JobKey(jobName, jobGroup));
                        return true;
                    }
                    else
                    {
                        return false;//任務不存在
                    }
                }
                catch (Exception ex)
                {
                    _logger.LogException(ex);
                    return false;//出現異常
                }
            }
    
            /// <summary>
            /// 恢復指定的任務計劃,若是是程序奔潰後 或者是進程殺死後的恢復,此方法無效
            /// </summary>
            /// <param name="jobName">任務名稱</param>
            /// <param name="jobGroup">任務組</param>
            /// <returns></returns>
            public async Task<bool> ResumeJobAsync(string jobName, string jobGroup)
            {
                try
                {
                    JobKey jobKey = new JobKey(jobName, jobGroup);
                    scheduler = await GetSchedulerAsync();
                    if (await scheduler.CheckExists(jobKey))
                    {
                        //resumejob 恢復
                        await scheduler.ResumeJob(new JobKey(jobName, jobGroup));
                        return true;
                    }
                    else
                    {
                        return false;//不存在任務
                    }
    
                }
                catch (Exception ex)
                {
                    _logger.LogException(ex);
                    return false;//出現異常
                }
            }
    
            /// <summary>
            /// 恢復指定的任務計劃,若是是程序奔潰後 或者是進程殺死後的恢復,此方法無效
            /// </summary>
            /// <param name="jobName">任務名稱</param>
            /// <param name="jobGroup">任務組</param>
            /// <returns></returns>
            public async Task<bool> DeleteJobAsync(string jobName, string jobGroup)
            {
                try
                {
                    JobKey jobKey = new JobKey(jobName, jobGroup);
                    scheduler = await GetSchedulerAsync();
                    if (await scheduler.CheckExists(jobKey))
                    {
                        //DeleteJob 恢復
                        await scheduler.DeleteJob(jobKey);
                        return true;
                    }
                    else
                    {
                        return false;//不存在任務
                    }
    
                }
                catch (Exception ex)
                {
                    _logger.LogException(ex);
                    return false;//出現異常
                }
            }
        }
  3. 新建一個LogTestJob 的計劃任務,代碼以下所示,須要繼承IJob接口:

    1551251169229

  4. 至此Quartz.NET調度任務功能完成

集成

這裏咱們按照以前的思路對JobInfo跟Quartz.NET任務進行集成

  1. 新增時,啓動任務:

    1551251315532

  2. 編輯時,更新任務

    1551251351318

  3. 這裏細心的網友,可能注意到任務的刪除是在編輯裏面進行實現的。而列表頁面的刪除功能並無實現真正意義的功能的刪除。

功能演示

上面咱們演示的任務是一個每5秒寫入當前時間的一個任務,並實現了對這個任務的新增,刪除,編輯的功能,這裏大夥能夠自行實現進行測試,也能夠下載個人代碼進行嘗試。效果圖以下所示:

1551251560062

功能擴展

目前只能對既定義好任務進行調度,後期能夠根據任務的名稱,如咱們實例中的測試任務LogTestJob 的名字找到這個任務,而後動態的進行處理。這樣就能夠在界面實現對多個任務進行調度了!固然還有其餘的擴展,本文只是做爲引子。

源碼地址

GitHub:https://github.com/yilezhu/AbpQuzatzDemo

總結

本文只是簡單的利用abp vNext框架進行Quartz.NET任務調度進行UI的管理,實現的功能也比較簡單,你們徹底能夠在此基礎上進行擴展完善,最後感謝大夥的閱讀。

相關文章
相關標籤/搜索