關於使用Topshelf創建服務


志銘-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官方文檔中的最簡的入門示例


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM