本篇隨筆介紹在Web微信應用中使用博客園RSS以及Quartz.NET實現博客文章內容的定期推送功能,首先對Quartz.NET進行一個簡單的介紹和代碼分析,掌握對作業調度的處理,然后對博客園RSS內容的處理如何獲取,並結合微信消息的群發接口進行內容的發送,從而構建了一個在Web應用中利用作業調度來進行消息發送的業務模型。
Quartz.NET是一個開源的作業調度框架,非常適合在平時的工作中,定時輪詢數據庫同步,定時郵件通知,定時處理數據等。 Quartz.NET允許開發人員根據時間間隔(或天)來調度作業。它實現了作業和觸發器的多對多關系,還能把多個作業與不同的觸發器關聯。整合了 Quartz.NET的應用程序可以重用來自不同事件的作業,還可以為一個事件組合多個作業。
1、Quartz.NET的使用
Quartz框架的一些基礎概念解釋:
Scheduler 作業調度器。
IJob 作業接口,繼承並實現Execute, 編寫執行的具體作業邏輯。
JobBuilder 根據設置,生成一個詳細作業信息(JobDetail)。
TriggerBuilder 根據規則,生產對應的Trigger
官方的使用案例代碼如下所示
private void button1_Click(object sender, EventArgs e) { try { Common.Logging.LogManager.Adapter = new Common.Logging.Simple.ConsoleOutLoggerFactoryAdapter { Level = Common.Logging.LogLevel.Info }; // Grab the Scheduler instance from the Factory IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler(); // and start it off scheduler.Start(); // define the job and tie it to our HelloJob class IJobDetail job = JobBuilder.Create<HelloJob>() .WithIdentity("job1", "group1") .Build(); // Trigger the job to run now, and then repeat every 10 seconds ITrigger trigger = TriggerBuilder.Create() .WithIdentity("trigger1", "group1") .StartNow() .WithSimpleSchedule(x => x .WithIntervalInSeconds(10) .RepeatForever()) .Build(); // Tell quartz to schedule the job using our trigger scheduler.ScheduleJob(job, trigger); // some sleep to show what's happening Thread.Sleep(TimeSpan.FromSeconds(60)); // and last shut down the scheduler when you are ready to close your program scheduler.Shutdown(); } catch (SchedulerException se) { Console.WriteLine(se); } Console.WriteLine("Finished"); }
啟動定義一個HelloJOb的對象,如下代碼所示
public class HelloJob : IJob { public void Execute(IJobExecutionContext context) { Console.WriteLine("Greetings from HelloJob!"); } }
2、Quartz的cron表達式
cron expressions 整體上還是非常容易理解的,只有一點需要注意:"?"號的用法,看下文可以知道“?”可以用在 day of month 和 day of week中,他主要是為了解決如下場景,如:每月的1號的每小時的31分鍾,正確的表達式是:* 31 * 1 * ?,而不能是:* 31 * 1 * *,因為這樣代表每周的任意一天。
由7段構成:秒 分 時 日 月 星期 年(可選)
"-" :表示范圍 MON-WED表示星期一到星期三
"," :表示列舉 MON,WEB表示星期一和星期三
"*" :表是“每”,每月,每天,每周,每年等
"/" :表示增量:0/15(處於分鍾段里面) 每15分鍾,在0分以后開始,3/20 每20分鍾,從3分鍾以后開始
"?" :只能出現在日,星期段里面,表示不指定具體的值
"L" :只能出現在日,星期段里面,是Last的縮寫,一個月的最后一天,一個星期的最后一天(星期六)
"W" :表示工作日,距離給定值最近的工作日
"#" :表示一個月的第幾個星期幾,例如:"6#3"表示每個月的第三個星期五(1=SUN...6=FRI,7=SAT)
官方cron表達式實例
表達式 | 代表意義 |
---|---|
0 0 12 * * ? | 每天中午12點觸發 |
0 15 10 ? * * | 每天上午10:15觸發 |
0 15 10 * * ? | 每天上午10:15觸發 |
0 15 10 * * ? * | 每天上午10:15觸發 |
0 15 10 * * ? 2005 | 2005年的每天上午10:15觸發 |
0 * 14 * * ? | 在每天下午2點到下午2:59期間的每1分鍾觸發 |
0 0/5 14 * * ? | 在每天下午2點到下午2:55期間的每5分鍾觸發 |
0 0/5 14,18 * * ? | 在每天下午2點到2:55期間和下午6點到6:55期間的每5分鍾觸發 |
0 0-5 14 * * ? | 在每天下午2點到下午2:05期間的每1分鍾觸發 |
0 10,44 14 ? 3 WED | 每年三月的星期三的下午2:10和2:44觸發 |
0 15 10 ? * MON-FRI | 周一至周五的上午10:15觸發 |
0 15 10 15 * ? | 每月15日上午10:15觸發 |
0 15 10 L * ? | 每月最后一日的上午10:15觸發 |
0 15 10 L-2 * ? | Fire at 10:15am on the 2nd-to-last last day of every month |
0 15 10 ? * 6L | 每月的最后一個星期五上午10:15觸發 |
0 15 10 ? * 6L | Fire at 10:15am on the last Friday of every month |
0 15 10 ? * 6L 2002-2005 | 2002年至2005年的每月的最后一個星期五上午10:15觸發 |
0 15 10 ? * 6#3 | 每月的第三個星期五上午10:15觸發 |
0 0 12 1/5 * ? | Fire at 12pm (noon) every 5 days every month, starting on the first day of the month. |
0 11 11 11 11 ? | Fire every November 11th at 11:11am. |
3、Quartz.NET的應用案例
我曾經在統一接口的Web API后台,使用了這個Quartz.NET來實現站場信息的同步處理,這樣可以把其他供應商提供的接口數據,同步到本地,可以加快數據的檢索和處理效率。
具體代碼如下所示。
首先是在Global.asax的后台代碼里面進行同步代碼處理。
public class WebApiApplication : System.Web.HttpApplication { IScheduler scheduler = null; protected void Application_Start() { GlobalConfiguration.Configuration.EnableCors(); GlobalConfiguration.Configure(WebApiConfig.Register); //創建執行同步的處理 ISchedulerFactory sf = new StdSchedulerFactory(); scheduler = sf.GetScheduler(); CalendarTask(); CreateOnceJob(); //啟動所有的任務 scheduler.Start(); } protected void Application_End(object sender, EventArgs e) { if(scheduler != null) { scheduler.Shutdown(true); } } /// <summary> /// 創建同步任務 /// </summary> private void CalendarTask() { IJobDetail job = JobBuilder.Create<StationSyncJob>() .WithIdentity("StationSyncJob", "group1") .Build(); //每天凌晨1點執行一次:0 0 1 * * ? ICronTrigger trigger = (ICronTrigger)TriggerBuilder.Create() .WithIdentity("trigger1", "group1") //"0 34,36,38,40 * * * ?" .WithCronSchedule("0 0 1 * * ?")//"0 0 1 * * ?" .Build(); DateTimeOffset ft = scheduler.ScheduleJob(job, trigger); LogTextHelper.Info(string.Format("您在 {0} 時候創建了Quartz任務", DateTime.Now)); } private void CreateOnceJob() { IJobDetail onceJob = JobBuilder.Create<StationSyncJob>() .WithIdentity("onceJob", "group1") .Build(); //啟動的時候運行一次 DateTimeOffset startTime = DateBuilder.NextGivenSecondDate(null, 30); ISimpleTrigger simpleTrigger = (ISimpleTrigger)TriggerBuilder.Create() .WithIdentity("simpleOnce", "group1") .StartAt(startTime) .Build(); DateTimeOffset ft = scheduler.ScheduleJob(onceJob, simpleTrigger); } }
其中同步站場信息的Job實現如下所示(這里是通過調用第三方接口獲取數據,然后把它們保存到本地,這個定時服務設定在每天的一個是時間點上執行,如凌晨1點時刻)。
/// <summary> /// 同步站場信息 /// </summary> public class StationSyncJob : IJob { public void Execute(IJobExecutionContext context) { LogTextHelper.Info(string.Format("您在 {0} 時候調用【同步站場信息】一次", DateTime.Now)); StationDetailResult result = new StationDetailResult(); try { QueryStationJson json = new QueryStationJson();//空查詢,一次性查詢所有 BaseDataAgent agent = new BaseDataAgent(); result = agent.QueryStationDetail(json); if(result != null && result.success) { foreach(StationDetailJson detail in result.data) { StationInfo info = detail.ConvertInfo(); try { BLLFactory<Station>.Instance.InsertIfNew(info); } catch(Exception ex) { LogTextHelper.Error(ex); LogTextHelper.Info(info.ToJson()); } } } } catch (Exception ex) { result.errmsg = ex.Message; result.success = false; LogTextHelper.Error(ex); } } }
4、博客的RSS
原則上我們可以利用任何RSS來源來獲取響應的博客內容,這里我以自己博客園的RSS源進行介紹使用,我們每個博客園的賬號都有一個如下的連接,提供我們最新的博客列表信息。
打開連接,可以看到它的內容就是最新顯示的博客內容,如下所示
處理RSS的內容,我們使用內置的SyndicationFeed對象來處理即可,非常方便。
string url = "http://feed.cnblogs.com/blog/u/12391/rss"; XmlReader reader = XmlReader.Create(url); SyndicationFeed feed = SyndicationFeed.Load(reader); reader.Close();
上面代碼就是獲取到對應的RSS內容,然后把它們轉換為XMLReader進行解析即可。
然后可以通過一個遍歷的處理就可以獲取到其中各個的XML節點內容了,非常方便。
foreach (SyndicationItem item in feed.Items) { var id = item.Id; string subject = item.Title.Text; string summary = item.Summary.Text; }
5、在微信應用中發送博客內容
通過上面的RSS讀取操作,我們可以獲得對應的博客內容,如果我們需要每周給客戶發送一些內容,那么這些就可以通過上面RSS源進行處理發送了。
關於發送文本消息的處理,可以參考我的隨筆文章《C#開發微信門戶及應用(3)--文本消息和圖文消息的應答》
這里我就直接應用上面的接口對內容進行處理發送,具體接口的邏輯就不再羅列。
/// <summary> /// 獲取博客園文章(RSS)並發送文本給指定的用戶 /// </summary> private void GetCnblogsArticles() { string url = "http://feed.cnblogs.com/blog/u/12391/rss"; XmlReader reader = XmlReader.Create(url); SyndicationFeed feed = SyndicationFeed.Load(reader); reader.Close(); ICustomerApi api = new CustomerApi(); foreach (SyndicationItem item in feed.Items) { Console.WriteLine(item.ToJson()); var id = item.Id; string subject = item.Title.Text; string summary = item.Summary.Text; var content = string.Format("<a href='{0}'>{1}</a>", id, subject); CommonResult result = api.SendText(token, openId, content); Console.WriteLine("發送內容:" + (result.Success ? "成功" : "失敗:" + result.ErrorMessage)); } }
得到的界面效果如下所示。
但是這樣的效果還是有點差強人意,我們知道微信里面有圖文消息的接口,可以利用圖文消息的接口進行發送,則更加美觀一些。
調整后的代碼如下所示。
/// <summary> /// 發送博客圖文消息給指定用戶 /// </summary> private void SendBlogsNews() { List<ArticleEntity> list = new List<ArticleEntity>(); string url = "http://feed.cnblogs.com/blog/u/12391/rss"; XmlReader reader = XmlReader.Create(url); SyndicationFeed feed = SyndicationFeed.Load(reader); reader.Close(); int i = 0; foreach (SyndicationItem item in feed.Items) { list.Add( new ArticleEntity { Title = item.Title.Text, Description = item.Summary.Text, PicUrl = i == 0 ? "http://www.iqidi.com/Content/Images/cnblogs_whc.png" : "http://www.iqidi.com/Content/Images/frame_web.png", Url = item.Id }); if(i >= 8) { break; } i++; } ICustomerApi customerApi = new CustomerApi(); var result = customerApi.SendNews(token, openId, list); }
這樣就是發送圖文消息的代碼,需要重新構建一個實體類集合進行發送,得到發送的效果如下所示。
整體的界面效果就是我們需要的效果了,不過如果我們需要使用批量發送給訂閱用戶的話,那么我們需要使用消息的群發接口,群發的消息接口封裝如需了解,可以參考文章《C#開發微信門戶及應用(30)--消息的群發處理和預覽功能》。
整個群發消息的邏輯代碼如下所示,主要邏輯就是獲取博客文章,並上傳文章的圖片,接着上傳需要群發的圖文消息資源,最后調用群發接口進行消息的發送即可。
private void BatchSendBlogNews() { List<NewsUploadJson> list = new List<NewsUploadJson>(); string url = "http://feed.cnblogs.com/blog/u/12391/rss"; XmlReader reader = XmlReader.Create(url); SyndicationFeed feed = SyndicationFeed.Load(reader); reader.Close(); //上傳圖片獲取MediaId IMediaApi mediaApi = new MediaApi(); var result1 = mediaApi.UploadTempMedia(token, UploadMediaFileType.image, @"E:\我的網站資料\iqidiSoftware\content\images\cnblogs_whc.png");//"http://www.iqidi.com/Content/Images/cnblogs_whc.png"); var result2 = mediaApi.UploadTempMedia(token, UploadMediaFileType.image, @"E:\我的網站資料\iqidiSoftware\content\images\frame_web.png");//"http://www.iqidi.com/Content/Images/frame_web.png"); if (result1 != null && result2 != null) { int i = 0; foreach (SyndicationItem item in feed.Items) { list.Add( new NewsUploadJson { author = "伍華聰", title = item.Title.Text, content = item.Summary.Text, //digest = item.Summary.Text, thumb_media_id = i == 0 ? result1.media_id : result2.media_id, content_source_url = item.Id, }); if (i >= 8) { break; } i++; } } if (list.Count > 0) { UploadJsonResult resultNews = mediaApi.UploadNews(token, list); if (resultNews != null) { IMassSendApi massApi = new MassSendApi(); var result = massApi.SendByGroup(token, MassMessageType.mpnews, resultNews.media_id, "0", true); } else { Console.WriteLine("上傳圖文消息失敗"); } } }
群發的消息在微信上看到內容和前面的差不多,不過點擊並不會直接跳轉鏈接,而是進去到一個詳細內容的頁面里面,只有單擊閱讀原文才進行跳轉URL,如下所示。
6.結合Quartz.NET實現博客文章內容的定期推送功能
在Web微信應用中使用博客RSS以及Quartz.NET實現文章內容的定期推送功能,我們需要結合Quartz.NET的作業調度處理、微信接口的內容發送,以及博文RSS內容的獲取處理,三者整合進行實現整個功能。
首先我們根據上面的代碼,設計好調度的Job內容,如下所示。
然后,在Web應用的Global.asa的后台代碼里面,編寫代碼啟動作業調度即可。
而根據前面Corn表達式的說明,我們要每周定時發送一次的的規則,如下所示。
每周星期天凌晨1點實行一次: 0 0 1 ? * L
這樣我們最終的Globa.asa后台代碼如下所示。
public class Global : HttpApplication { private IScheduler scheduler = null; void Application_Start(object sender, EventArgs e) { // 在應用程序啟動時運行的代碼 AreaRegistration.RegisterAllAreas(); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); //構造調度對象,並創建對應的調度任務 scheduler = StdSchedulerFactory.GetDefaultScheduler(); CalendarTask(); //啟動所有的任務 scheduler.Start(); } protected void Application_End(object sender, EventArgs e) { if (scheduler != null) { scheduler.Shutdown(true); } } private void CalendarTask() { IJobDetail job = JobBuilder.Create<BlogArticleSendJob>() .WithIdentity("BlogArticleSendJob", "group1") .Build(); //每周星期天凌晨1點實行一次:0 0 1 ? * L ICronTrigger trigger = (ICronTrigger)TriggerBuilder.Create() .WithIdentity("trigger1", "group1") .WithCronSchedule("0 0 1 ? * L")//0 0 1 ? * L .Build(); DateTimeOffset ft = scheduler.ScheduleJob(job, trigger); LogTextHelper.Info(string.Format("您在 {0} 時候創建了Quartz任務", DateTime.Now)); }
綜合上面的思路,我們可以利用Quartz.NET做成更多的數據同步任務調度,另外在微信應用中,我們也可以整合很多組件或者控件,來實現更加彈性化的業務支持,如消息群發、訪客匯總,內容同步等處理。
以上就是我的一些組件代碼的應用思路,其實我們只要涉獵更廣一些,很多東西可以使用拿來主義,經過自己的整合優化,可以為我們服務的更好。