Windows服务调用Quartz.net 实现消息调度
Quartz.NET是一个开源的作业调度框架,是OpenSymphony 的 Quartz API的.NET移植,它用C#写成,可用于winform和asp.net应用中。它提供了巨大的灵活性而不牺牲简单性。你能够用它来为执行一个作业而创建简单的或复杂的调度。它有很多特征,如:数据库支持,集群,插件,支持cron-like表达式等等
以上介绍是从博客园张善友(http://www.cnblogs.com/shanyou/archive/2007/08/25/quartznettutorial.html)的博客摘录,可登录他博客具体了解quartz.net。
我在这里只讲具体在项目中的实现:
通过Windows服务调用Quartz.net,然后Quartz.net 调用WinForm消息窗口,实现计划任务消息推送。
【Windows服务】-->【Quartz.net】 --> 【Winform .exe】 --> 【程序打包】
项目解决方案,如图所示:
一、创建Windows服务
1、打开vs2010--新建项目--选择"Windows服务",我这里命名为"QuartzService". 创建好后首先映入我们眼帘的是QuartzService.cs[设计]视图,右键点设计视图选择"添加安装程序",如下图:
注释:(创建一个Windows服务,仅用InstallUtil程序去安装这个服务是不够的。你必须还要把一个服务安装程序添加到你的Windows服务当中,这样便于InstallUtil或是任何别的安装程序知道应用你服务的是怎样的配置设置)
2、切换到刚被添加的ProjectInstaller的设计视图, 设置serviceInstaller1组件的属性: StartType = Automatic;ServiceName = QuartzService;
设置serviceProcessInstaller1组件的属性 Account = LocalSystem; 如下图:
3、QuartzService中添加Quartz.dll ,log4net.dll引用,在QuartzService.cs文件中引用命名空间 Quartz;Quartz.Impl; log4net;
右键点击QuartzService项目,属性-目标框架 ,选择.net Framwork 4,如下图:
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".txt"" />
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>
二、创建Quartz.Net.JobLibrary并配置Job
1、在解决方案中添加新项-选择"类库",我这里命名为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 }
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 }
2、在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:30,11:30,12:30触发的表达式。
95 "0 30 10-13 ? * WED,FRI" CronTrigger
96 例4 – 在每个月的5号,20号的8点和10点之间每隔半个小时触发一次且不包括10点,只是8:30,9: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>
三、创建Quartz.Net.WinForm消息窗口
1、在解决方案中添加新项-选择"windows 应用程序",我这里命名为Quartz.Net.WinForm,具体实现从右下角逐渐显示气泡式窗口如图所示:
点击【确定】从右下角逐渐消失
通过JobTest1调用消息窗口,
string path = System.Windows.Forms.Application.StartupPath;
Interop.CreateProcess("Quartz.Net.WinForm.exe", path + "\\");
实现例如每一个小时检测是否有计划任务,一旦有任务,消息窗口弹出提醒!
四、注册Windows服务
1、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服务名称
这样注册就完了。
2、打开服务:
运行 下输入services.msc打开服务管理。如图:
刷新,可以查看到QuartzService.如下图:
3、 启动QuartzService,回到windows服务 应用程序所在目录
以上四部分大体实现了类似一些新闻消息推送,下一篇讲讲具体程序打包、安装等实现过程。希望大家多多推荐一下
作者:Raymond-Yang
出处:http://www.cnblogs.com/Raymond-YYC/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。