目錄
志銘-2021年12月13日 21:21:36
0. 背景說明
我為什麼要建立windows服務?因為有一個操作需要我定時執行,這個操作可以是定時的資料同步或是週期性的某種操作
之前都是按照以下方式實現的:
-
若是資料庫層面定時任務,則建立一個定時作業
-
其他操作,則建立一個控制檯程式,使用windows服務自帶的任務計劃程式,定時週期性執行該控制檯程式
而目前出現了問題:
-
資料庫作業,因為我使用的資料庫賬號沒有看不到“SQL Server代理”,就是沒有許可權建立作業
-
控制檯程式因為引用其他的dll檔案所以偶爾出現被防毒軟體刪除,需要加入白名單。但也意味著其不夠健壯。
1. 使用Topshelf元件建立Windows服務
使用Topshelf可以便捷的建立一個Windows服務
該元件可以通過建立一個控制檯程式,實現一個windows服務
該元件可以配合Log4net和Quartz.net實現記錄日誌和定時執行
Nuget:
Install-Package Topshelf -Version 4.3.0
Install-Package Topshelf.Log4Net -Version 4.3.0(注:依賴於Log4net)
Install-Package Topshelf.Quartz -Version 0.4.0.1(注:依賴於Quartz)
1.1 依賴Quartz.net實現定時任務
定義任務類,實現Ijob介面
//using log4net;
//using Quartz;
/// <summary>
/// 任務類,定義我們需要排程的任務
/// </summary>
public class MyJob : IJob
{
private readonly ILog _logger = LogManager.GetLogger(typeof(MyJob));
public void Execute(IJobExecutionContext context)
{
//todo:你所期望實現定時執行的操作
//計入日誌
_logger.InfoFormat("任務執行成功");
}
}
簡單的安裝單例模式封裝一個排程器類,
這裡排程規則簡單示例為每隔5s執行一次
//using Quartz;
//using Quartz.Impl;
/// <summary>
/// 任務排程器類,用於建立排程器並配置排程計劃
/// </summary>
public class MyScheduler
{
private static readonly MyScheduler myScheduler = new MyScheduler();
//排程器
public IScheduler scheduler { private set; get; }
//任務
public IJobDetail job { private set; get; }
//觸發器
public ISimpleTrigger trigger { private set; get; }
private MyScheduler()
{
scheduler = StdSchedulerFactory.GetDefaultScheduler();
job = JobBuilder.Create<MyJob>().Build();
trigger = TriggerBuilder.Create()
.StartNow()
.WithSimpleSchedule(x => x.WithIntervalInSeconds(5).RepeatForever())//配置每隔5s執行一次,永久執行下去
.WithIdentity("trigger", "group").Build() as ISimpleTrigger;
scheduler.ScheduleJob(job, trigger);
}
//單例模式,用於返回單例物件
public static MyScheduler GetMyScheduler()
{
return myScheduler;
}
}
1.2 依賴於Topshelf建立服務類
//using Quartz;
//using Topshelf;
/// <summary>
/// 服務類,實現相應介面,定義服務啟動和停止執行的操作
/// </summary>
public class MyServiceRunner : ServiceControl, ServiceSuspend
{
private readonly IScheduler scheduler = MyScheduler.GetMyScheduler().scheduler;
#region ServiceControl介面需要實現的方法
public bool Start(HostControl hostControl)
{
scheduler.Start();
return true;
}
public bool Stop(HostControl hostControl)
{
scheduler.Shutdown(false);
return true;
}
#endregion
#region ServiceSuspen介面需要實現的方法
public bool Continue(HostControl hostControl)
{
scheduler.ResumeAll();
return true;
}
public bool Pause(HostControl hostControl)
{
scheduler.PauseAll();
return true;
}
#endregion
}
1.3 log4net的配置檔案log4net.config
這裡通過配置將日誌資訊寫入到資料庫中
- 日誌表建表語句
CREATE TABLE [dbo].[Log4net] ( [Id] [int] IDENTITY (1, 1) NOT NULL, [Date] [datetime] NOT NULL, [Thread] [varchar] (255) NOT NULL, [Level] [varchar] (50) NOT NULL, [Logger] [varchar] (255) NOT NULL, [Message] [varchar] (4000) NOT NULL, [Exception] [varchar] (2000) NULL )
- 注意資料庫的連線字串需要單獨寫在專案的配置檔案中
- 右鍵log4net.config-->屬性-->複製的輸出目錄:始終複製
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!-- ...................................為Log4Net新增的配置.....開始.................................-->
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
</configSections>
<log4net>
<root>
<level value="ALL"></level>
<appender-ref ref="AdoNetAppender"></appender-ref>
</root>
<appender name="AdoNetAppender" type="log4net.Appender.AdoNetAppender">
<bufferSize value="1" />
<connectionType value="System.Data.SqlClient.SqlConnection,System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<connectionStringName value="ConnectionStringLogging" />
<commandText value="INSERT INTO Log4net ([Date],[Thread],[Level],[Logger],[Message],[Exception])
VALUES (@log_date, @thread, @log_level, @logger, @message, @exception)" />
<parameter>
<parameterName value="@log_date" />
<dbType value="DateTime" />
<layout type="log4net.Layout.RawTimeStampLayout" />
</parameter>
<parameter>
<parameterName value="@thread" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%thread" />
</layout>
</parameter>
<parameter>
<parameterName value="@log_level" />
<dbType value="String" />
<size value="50" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%level" />
</layout>
</parameter>
<parameter>
<parameterName value="@logger" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%logger" />
</layout>
</parameter>
<parameter>
<parameterName value="@message" />
<dbType value="String" />
<size value="4000" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%message" />
</layout>
</parameter>
<parameter>
<parameterName value="@exception" />
<dbType value="String" />
<size value="2000" />
<layout type="log4net.Layout.ExceptionLayout" />
</parameter>
</appender>
</log4net>
<!-- ...................................為Log4Net新增的配置.....結束.................................-->
</configuration>
1.4 主函式中配置服務資訊
static void Main(string[] args)
{
//這裡讀取log4net.config
log4net.Config.XmlConfigurator.ConfigureAndWatch(new FileInfo(AppDomain.CurrentDomainBaseDirectory + "log4net.config"));
HostFactory.Run(x =>
{
x.UseLog4Net();
x.Service<MyServiceRunner>();
x.SetDescription("服務描述");
x.SetDisplayName("服務顯示名稱");
x.SetServiceName("服務名稱");
x.EnablePauseAndContinue();
});
}
1.5 安裝服務
編譯後得到的控制檯程式 xxx.exe,
以管理員的身份執行命令列程式,跳轉到程式所在路徑,執行該命令
xxx.exe install
win+R:services.msc 開啟服務管理器,找到安裝的服務並右鍵啟動
找到自己安裝的服務,可以右鍵屬性設定其他一些功能,如服務失敗後的操作等
解除安裝該服務,則先在停止,在按照安裝的方法,管理員身份開啟命令列,跳轉程式路徑,執行
xxx.exe uninstall
3. Demo原始碼下載及參考文件
若是簡單的週期執行,也可以不使用Quartz .net ,而是使用System.Timers.Timer物件實現間隔一定的時間執行一次任務
而且可以拋棄Log4net,只是簡單的使用 System.IO.StreamWriter在服務本地簡單的記錄一個日誌文字
該功能就是Topshelf官方文件中的最簡的入門示例