Windows服務調用Quartz.net 實現消息調度

Quartz.NET是一個開源的做業調度框架,是OpenSymphony 的 Quartz API的.NET移植,它用C#寫成,可用於winform和asp.net應用中。它提供了巨大的靈活性而不犧牲簡單性。你可以用它來爲執行一個做業而建立簡單的或複雜的調度。它有不少特徵,如:數據庫支持,集羣,插件,支持cron-like表達式等等html

以上介紹是從博客園張善友(http://www.cnblogs.com/shanyou/archive/2007/08/25/quartznettutorial.html)的博客摘錄,可登陸他博客具體瞭解quartz.net。數據庫

 我在這裏只講具體在項目中的實現:express

經過Windows服務調用Quartz.net,而後Quartz.net 調用WinForm消息窗口,實現計劃任務消息推送。windows

【Windows服務】-->【Quartz.net】 --> 【Winform .exe】 --> 【程序打包】 api


項目解決方案,如圖所示:session

1、建立Windows服務


 一、打開vs2010--新建項目--選擇"Windows服務",我這裏命名爲"QuartzService". 建立好後首先映入咱們眼簾的是QuartzService.cs[設計]視圖,右鍵點設計視圖選擇"添加安裝程序",以下圖:app

註釋:(建立一個Windows服務,僅用InstallUtil程序去安裝這個服務是不夠的。你必須還要把一個服務安裝程序添加到你的Windows服務當中,這樣便於InstallUtil或是任何別的安裝程序知道應用你服務的是怎樣的配置設置)框架

二、切換到剛被添加的ProjectInstaller的設計視圖, 設置serviceInstaller1組件的屬性: StartType = Automatic;ServiceName = QuartzService;asp.net

設置serviceProcessInstaller1組件的屬性 Account = LocalSystem; 以下圖:ide

三、QuartzService中添加Quartz.dll ,log4net.dll引用,在QuartzService.cs文件中引用命名空間 Quartz;Quartz.Impl; log4net;

右鍵點擊QuartzService項目,屬性-目標框架 ,選擇.net Framwork 4,以下圖:

四、添加app.config配置文件,具體配置以下:

 1 <?xml version="1.0"?>
 2 <configuration>
 3   <configSections>
 4     <section name="quartz" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
 5     <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
 6     <sectionGroup name="common">
 7       <section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging"/>
 8     </sectionGroup>
 9   </configSections>
10   <common>
11     <logging>
12       <factoryAdapter type="Common.Logging.Log4Net.Log4NetLoggerFactoryAdapter, Common.Logging.Log4net">
13         <arg key="configType" value="INLINE"/>
14       </factoryAdapter>
15     </logging>
16   </common>
17   <log4net>
18     <appender name="InfoFileAppender" type="log4net.Appender.RollingFileAppender">
19       <file value="log/" />
20       <appendToFile value="true" />
21       <param name="DatePattern" value="yyyyMMdd&quot;.txt&quot;" />
22       <rollingStyle value="Date" />
23       <maxSizeRollBackups value="100" />
24       <maximumFileSize value="1024KB" />
25       <staticLogFileName value="false" />
26       <Encoding value="UTF-8" />
27       <filter type="log4net.Filter.LevelRangeFilter">
28         <param name="LevelMin" value="INFO" />
29         <param name="LevelMax" value="INFO" />
30       </filter>
31       <layout type="log4net.Layout.PatternLayout">
32         <conversionPattern value="%date %-5level %logger - %message%newline" />
33       </layout>
34     </appender>
35     <appender name="ErrorFileAppender" type="log4net.Appender.RollingFileAppender">
36       <file value="log/error.txt" />
37       <appendToFile value="true" />
38       <rollingStyle value="Size" />
39       <maxSizeRollBackups value="100" />
40       <maximumFileSize value="10240KB" />
41       <staticLogFileName value="true" />
42       <Encoding value="UTF-8" />
43       <filter type="log4net.Filter.LevelRangeFilter">
44         <param name="LevelMin" value="WARN" />
45         <param name="LevelMax" value="FATAL" />
46       </filter>
47       <layout type="log4net.Layout.PatternLayout">
48         <conversionPattern value="%date %-5level %logger - %message%newline" />
49       </layout>
50     </appender>
51     <root>
52       <level value="INFO" />
53       <appender-ref ref="InfoFileAppender" />
54       <appender-ref ref="ErrorFileAppender" />
55     </root>
56   </log4net>
57 
58   <!-- 
59     We use quartz.config for this server, you can always use configuration section if you want to. 60  Configuration section has precedence here. 61   -->
62   <appSettings>
63     <!-- YYC.WebService URL地址 -->
64     <add key="URL" value="http://localhost:43093/WebService.asmx" />
65   </appSettings>
66   <!--
67   <quartz >
68   </quartz>
69   -->
70   <startup>
71     <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
72   </startup>
73 </configuration>
View Code

2、建立Quartz.Net.JobLibrary並配置Job


一、在解決方案中添加新項-選擇"類庫",我這裏命名爲Quartz.Net.JobLibrary,Quartz.Net.JobLibrary用來實現多個"做業"類。Quartz.Net.JobLibrary類庫中添加Quartz.dll ,log4net.dll引用。

Quartz.Net.JobLibrary類庫中添加一個類Interop.cs,這個類是爲了解決在Win7中出現【交互式檢測】彈窗,博客園李敬然(http://www.cnblogs.com/gnielee/archive/2010/04/07/session0-isolation-part1.html)的博客詳細談到 穿透Session 0 隔離,具體代碼以下:

 1 using System;  2 using System.Runtime.InteropServices;  3 
 4 namespace Quartz.Net.JobLibrary  5 {  6     public class Interop  7  {  8         public static IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero;  9         public static void ShowMessageBox(string message, string title)  10  {  11             int resp = 0;  12  WTSSendMessage(  13  WTS_CURRENT_SERVER_HANDLE,  14  WTSGetActiveConsoleSessionId(),  15  title, title.Length,  16  message, message.Length,  17                 0, 0, out resp, false);  18  }  19         [DllImport("kernel32.dll", SetLastError = true)]  20         public static extern int WTSGetActiveConsoleSessionId();  21         [DllImport("wtsapi32.dll", SetLastError = true)]  22         public static extern bool WTSSendMessage(  23  IntPtr hServer,  24             int SessionId,  25  String pTitle,  26             int TitleLength,  27  String pMessage,  28             int MessageLength,  29             int Style,  30             int Timeout,  31             out int pResponse,  32             bool bWait);  33 
 34         public static void CreateProcess(string app, string path)  35  {  36  IntPtr hToken;  37             IntPtr hDupedToken = IntPtr.Zero;  38 
 39             PROCESS_INFORMATION pi = new PROCESS_INFORMATION();  40             SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();  41             sa.Length = Marshal.SizeOf(sa);  42 
 43             STARTUPINFO si = new STARTUPINFO();  44             si.cb = Marshal.SizeOf(si);  45 
 46             int dwSessionID = WTSGetActiveConsoleSessionId();  47             bool result = WTSQueryUserToken(dwSessionID, out hToken);  48 
 49             if (!result)  50  {  51                 ShowMessageBox("WTSQueryUserToken failed", "AlertService Message");  52  }  53 
 54             result = DuplicateTokenEx(  55  hToken,  56  GENERIC_ALL_ACCESS,  57                   ref sa,  58                   (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,  59                   (int)TOKEN_TYPE.TokenPrimary,  60                   ref hDupedToken  61  );  62 
 63             if (!result)  64  {  65                 ShowMessageBox("DuplicateTokenEx failed", "AlertService Message");  66  }  67 
 68  IntPtr lpEnvironment;  69             result = CreateEnvironmentBlock(out lpEnvironment, hDupedToken, false);  70 
 71             if (!result)  72  {  73                 ShowMessageBox("CreateEnvironmentBlock failed", "AlertService Message");  74  }  75 
 76             result = CreateProcessAsUser(  77  hDupedToken,  78                         path + app,  79  String.Empty,  80                         ref sa, ref sa,  81                         false, 0, IntPtr.Zero,  82                         path, ref si, ref pi);  83 
 84             if (!result)  85  {  86                 int error = Marshal.GetLastWin32Error();  87                 string message = String.Format("CreateProcessAsUser Error: {0}", error);  88                 ShowMessageBox(message, "AlertService Message");  89  }  90 
 91             if (pi.hProcess != IntPtr.Zero)  92  CloseHandle(pi.hProcess);  93             if (pi.hThread != IntPtr.Zero)  94  CloseHandle(pi.hThread);  95             if (hDupedToken != IntPtr.Zero)  96  CloseHandle(hDupedToken);  97  }  98 
 99  [StructLayout(LayoutKind.Sequential)] 100         public struct STARTUPINFO 101  { 102             public Int32 cb; 103             public string lpReserved; 104             public string lpDesktop; 105             public string lpTitle; 106             public Int32 dwX; 107             public Int32 dwY; 108             public Int32 dwXSize; 109             public Int32 dwXCountChars; 110             public Int32 dwYCountChars; 111             public Int32 dwFillAttribute; 112             public Int32 dwFlags; 113             public Int16 wShowWindow; 114             public Int16 cbReserved2; 115             public IntPtr lpReserved2; 116             public IntPtr hStdInput; 117             public IntPtr hStdOutput; 118             public IntPtr hStdError; 119  } 120 
121  [StructLayout(LayoutKind.Sequential)] 122         public struct PROCESS_INFORMATION 123  { 124             public IntPtr hProcess; 125             public IntPtr hThread; 126             public Int32 dwProcessID; 127             public Int32 dwThreadID; 128  } 129 
130  [StructLayout(LayoutKind.Sequential)] 131         public struct SECURITY_ATTRIBUTES 132  { 133             public Int32 Length; 134             public IntPtr lpSecurityDescriptor; 135             public bool bInheritHandle; 136  } 137 
138         public enum SECURITY_IMPERSONATION_LEVEL 139  { 140  SecurityAnonymous, 141  SecurityIdentification, 142  SecurityImpersonation, 143  SecurityDelegation 144  } 145 
146         public enum TOKEN_TYPE 147  { 148             TokenPrimary = 1, 149  TokenImpersonation 150  } 151 
152         public const int GENERIC_ALL_ACCESS = 0x10000000; 153 
154         [DllImport("kernel32.dll", SetLastError = true, 155             CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] 156         public static extern bool CloseHandle(IntPtr handle); 157 
158         [DllImport("advapi32.dll", SetLastError = true, 159             CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] 160         public static extern bool CreateProcessAsUser( 161  IntPtr hToken, 162             string lpApplicationName, 163             string lpCommandLine, 164             ref SECURITY_ATTRIBUTES lpProcessAttributes, 165             ref SECURITY_ATTRIBUTES lpThreadAttributes, 166             bool bInheritHandle, 167  Int32 dwCreationFlags, 168  IntPtr lpEnvrionment, 169             string lpCurrentDirectory, 170             ref STARTUPINFO lpStartupInfo, 171             ref PROCESS_INFORMATION lpProcessInformation); 172 
173         [DllImport("advapi32.dll", SetLastError = true)] 174         public static extern bool DuplicateTokenEx( 175  IntPtr hExistingToken, 176  Int32 dwDesiredAccess, 177             ref SECURITY_ATTRIBUTES lpThreadAttributes, 178  Int32 ImpersonationLevel, 179  Int32 dwTokenType, 180             ref IntPtr phNewToken); 181 
182         [DllImport("wtsapi32.dll", SetLastError = true)] 183         public static extern bool WTSQueryUserToken( 184  Int32 sessionId, 185             out IntPtr Token); 186 
187         [DllImport("userenv.dll", SetLastError = true)] 188         static extern bool CreateEnvironmentBlock( 189             out IntPtr lpEnvironment, 190  IntPtr hToken, 191             bool bInherit); 192  } 193 }
View Code

Quartz.Net.JobLibrary類庫中再分別添加兩個"做業"類JobTest1.cs,JobTest2.cs:

代碼以下

JobTest1.cs 

 1 using System;  2 using System.Configuration;  3 using System.Reflection;  4 using Common.Logging;  5 
 6 namespace Quartz.Net.JobLibrary  7 {  8     public class JobTest1 : IJob  9  { 10         //使用Common.Logging.dll日誌接口實現日誌記錄
11         private static readonly ILog logger =
12  LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); 13 
14         private static string URL = ConfigurationManager.AppSettings["URL"]; 15 
16         #region IJob 成員
17 
18         public void Execute(IJobExecutionContext context) 19  { 20             try
21  { 22                 logger.Info("JobTest1 任務開始運行"); 23                 //ShowMsgBox msgBox = new ShowMsgBox(); 24                 //msgBox.ShowMsg(100, "AAAA", "計劃任務提醒"); 25                 //WebServiceSoapClient client = new WebServiceSoapClient(new BasicHttpBinding(), new EndpointAddress(URL)); 26                 //client.Shake(); 27                 //ShowMsg(100, "AAAA", "計劃任務提醒"); 28                 //Interop.ShowMessageBox("This a message from AlertService.", 29                 // "AlertService Message");
30                 string path = System.Windows.Forms.Application.StartupPath; 31                 Interop.CreateProcess("Quartz.Net.WinForm.exe", path + "\\"); 32                 logger.Info("JobTest1 任務運行結束"); 33  } 34             catch (Exception ex) 35  { 36                 logger.Error("JobTest1 運行異常", ex); 37  } 38  } 39 
40         #endregion
41 
42  } 43 }
View Code

二、在Quartz.Net.JobLibrary中,添加配置文件quartz_jobs.xml,配置信息以下:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 
 3 <!-- This file contains job definitions in schema version 2.0 format -->
 4 <job-scheduling-data xmlns="http://quartznet.sourceforge.net/JobSchedulingData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0">
 5   <processing-directives>
 6     <overwrite-existing-data>true</overwrite-existing-data>
 7   </processing-directives>
 8 
 9   <schedule>
 10     <!--定義JobTest1-->
 11     <job>
 12       <name>JobTest1</name>
 13       <group>JobGroup</group>
 14       <description>測試任務1</description>
 15       <job-type>Quartz.Net.JobLibrary.JobTest1,Quartz.Net.JobLibrary</job-type>
 16       <durable>true</durable>
 17       <recover>false</recover>
 18     </job>
 19 
 20     <!--定義示JobTest2-->
 21     <job>
 22       <name>JobTest2</name>
 23       <group>JobGroup</group>
 24       <description>測試任務2</description>
 25       <job-type>Quartz.Net.JobLibrary.JobTest2,Quartz.Net.JobLibrary</job-type>
 26       <durable>true</durable>
 27       <recover>false</recover>
 28     </job>
 29 
 30     <!--定義JobTest1 觸發器 每30秒執行一次任務-->
 31     <trigger>
 32       <cron>
 33         <name>JobTestTrigger1</name>
 34         <group>TriggerGroup</group>
 35         <job-name>JobTest1</job-name>
 36         <job-group>JobGroup</job-group>
 37         <cron-expression>0/30 * * * * ?</cron-expression>
 38       </cron>
 39     </trigger>
 40 
 41     <!--定義JobTest2 觸發器 每分鐘執行一次任務-->
 42     <trigger>
 43       <cron>
 44         <name>JobTestTrigger2</name>
 45         <group>TriggerGroup</group>
 46         <job-name>JobTest2</job-name>
 47         <job-group>JobGroup</job-group>
 48         <cron-expression>0 * * * * ?</cron-expression>
 49       </cron>
 50     </trigger>
 51 
 52     <!--定義JobTest2 觸發器 天天凌晨01:00執行一次任務-->
 53     <trigger>
 54       <cron>
 55         <name>JobTestTrigger3</name>
 56         <group>TriggerGroup</group>
 57         <job-name>JobTest2</job-name>
 58         <job-group>JobGroup</job-group>
 59         <cron-expression>0 0 1 * * ?</cron-expression>
 60       </cron>
 61     </trigger>
 62   </schedule>
 63 
 64 <!-- Cron Expressions——Cron 表達式  65  Cron表達式被用來配置CronTrigger實例。Cron表達式是一個由7個子表達式組成的字符串。每一個子表達式都描述了一個單獨的日程細節。這些子表達式用空格分隔,分別表示:  66       1. Seconds 秒  67       2. Minutes 分鐘  68       3. Hours 小時  69       4. Day-of-Month 月中的天  70       5. Month 月  71       6. Day-of-Week 週中的天  72       7. Year (optional field) 年(可選的域)  73       一個cron表達式的例子字符串爲"0 0 12 ? * WED",這表示「每週三的中午12:00」。  74       單個子表達式能夠包含範圍或者列表。例如:前面例子中的週中的天這個域(這裏是"WED")能夠被替換爲"MON-FRI", "MON, WED, FRI"或者甚至"MON-WED,SAT" 75       通配符('*')能夠被用來表示域中「每一個」可能的值。所以在"Month"域中的*表示每月,而在Day-Of-Week域中的*則表示「週中的每一天」。  76       全部的域中的值都有特定的合法範圍,這些值的合法範圍至關明顯,例如:秒和分域的合法值爲0到59,小時的合法範圍是0到23,Day-of-Month中值得合法凡範圍是0到31,  77  可是須要注意不一樣的月份中的天數不一樣。月份的合法值是0到11。或者用字符串JAN,FEB MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV 及DEC來表示。  78       Days-of-Week能夠用1到7來表示(1=星期日)或者用字符串SUN, MON, TUE, WED, THU, FRI 和SAT來表示.  79       '/'字符用來表示值的增量,例如, 若是分鐘域中放入'0/15',它表示「每隔15分鐘,從0開始」,若是在份中域中使用'3/20',則表示「小時中每隔20分鐘,  80       從第3分鐘開始」或者另外相同的形式就是'3,23,43' 81       '?'字符能夠用在day-of-month及day-of-week域中,它用來表示「沒有指定值」。這對於須要指定一個或者兩個域的值而不須要對其餘域進行設置來講至關有用。  82       'L'字符能夠在day-of-month及day-of-week中使用,這個字符是"last"的簡寫,可是在兩個域中的意義不一樣。例如,在day-of-month域中的"L"表示這個月的最後一天,  83       即,一月的31日,非閏年的二月的28日。若是它用在day-of-week中,則表示"7"或者"SAT"。可是若是在day-of-week域中,這個字符跟在別的值後面,  84       則表示"當月的最後的周XXX"。例如:"6L" 或者 "FRIL"都表示本月的最後一個週五。當使用'L'選項時,最重要的是不要指定列表或者值範圍,不然會致使混亂。  85       'W' 字符用來指定距離給定日最接近的周幾(在day-of-week域中指定)。例如:若是你爲day-of-month域指定爲"15W",則表示「距離月中15號最近的周幾」。  86       '#'表示表示月中的第幾個周幾。例如:day-of-week域中的"6#3" 或者 "FRI#3"表示「月中第三個週五」。  87  下面是一些表達式以及它們的含義。  88  Example Cron Expressions ——Cron表達式的例子  89  CronTrigger  90  例1 – 一個簡單的每隔5分鐘觸發一次的表達式  91       "0 0/5 * * * ?" CronTrigger  92        例2 – 在每分鐘的10秒後每隔5分鐘觸發一次的表達式(例如. 10:00:10 am, 10:05:10等.)。  93       "10 0/5 * * * ?" CronTrigger  94       例3 – 在每一個週三和週五的10:30113012:30觸發的表達式。  95       "0 30 10-13 ? * WED,FRI" CronTrigger  96       例4 – 在每月的5號,20號的8點和10點之間每隔半個小時觸發一次且不包括10點,只是8:309:00和9:30的表達式。  97       "0 0/30 8-9 5,20 * ?" 注意,對於單獨觸發器來講,有些日程需求可能過於複雜而不能用表達式表述,例如:9:00到10:00之間每隔5分鐘觸發一次,  98  下午1:00到10點每隔20分鐘觸發一次。這個解決方案就是建立兩個觸發器,兩個觸發器都運行相同的任務。  99 -->
100 </job-scheduling-data>
View Code

3、建立Quartz.Net.WinForm消息窗口


一、在解決方案中添加新項-選擇"windows 應用程序",我這裏命名爲Quartz.Net.WinForm,具體實現從右下角逐漸顯示氣泡式窗口如圖所示:

 

點擊【肯定】從右下角逐漸消失

經過JobTest1調用消息窗口,

string path = System.Windows.Forms.Application.StartupPath;
Interop.CreateProcess("Quartz.Net.WinForm.exe", path + "\\");

實現例如每個小時檢測是否有計劃任務,一旦有任務,消息窗口彈出提醒!

4、註冊Windows服務


一、cmd打開命令行工具:若是你係統是C盤,通常命令應該以下:

C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe   "你的windows服務路徑"

反註冊以下:

C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe  /u "你的windows服務路徑"

固然也能夠用sc命令

sc create windows服務名稱 binPath= "你的windows服務路徑"

刪除服務

sc delete windows服務名稱

這樣註冊就完了。

二、打開服務:

運行 下輸入services.msc打開服務管理。如圖:

刷新,能夠查看到QuartzService.以下圖:

三、 啓動QuartzService,回到windows服務 應用程序所在目錄

以上四部分大致實現了相似一些新聞消息推送,下一篇講講具體程序打包、安裝等實現過程。但願你們多多推薦一下

相關文章
相關標籤/搜索