关于使用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" />
<