1.TopShelf

TopShelf是一个开源的跨平台的宿主服务框架。可通过.Net Core/.Net Framwork控制台应用程序快速开发windows服务,更加便于服务调试。

本文基于.Net Core2.2快速开发windows服务

首先,我们创建一个控制台应用程序

然后添加Topshelf Nuget程序包  版本4.2.1

通过Topshelf集成的Log4net管理日志,所以我们这里添加了Topshelf.LogNet4 Nuget程序包

添加log4net.config日志配置文件(需手动新建config文件,复制以下内容即可),一般默认配置就可以,主要是改一下日志路径和控制台日志输出级别

 <?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
<log4net>
<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<!--日志路径-->
<param name= "File" value= "D:\log\"/>
<!--是否是向文件中追加日志-->
<param name= "AppendToFile" value= "true"/>
<!--备份文件的最大切分数量-->
<param name= "MaxSizeRollBackups" value= ""/>
<!-- 每个文件的大小限制 -->
<param name="MaximumFileSize" value="10MB" />
<!-- RollingStyle Composite 综合 Size 按大小 Date 按时间 -->
<param name="RollingStyle" value="Composite" />
<!--最小锁定模式,允许多个进程写入同一个文件-->
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<!--日志文件名是否是固定不变的-->
<param name= "StaticLogFileName" value= "false"/>
<!--日志文件名格式为:--.log-->
<datePattern value="yyyy-MM-dd\\&quot;Log4Net&quot;'.log'" />
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n" />
</layout>
</appender>
<!-- 控制台前台显示日志 -->
<appender name="ColoredConsoleAppender" type="log4net.Appender.ColoredConsoleAppender">
<mapping>
<level value="ERROR" />
<foreColor value="Red, HighIntensity" />
</mapping>
<mapping>
<level value="Info" />
<foreColor value="Green" />
</mapping>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%d [%-5level] %m%n" />
</layout> <filter type="log4net.Filter.LevelRangeFilter">
<param name="LevelMin" value="Info" />
<param name="LevelMax" value="Fatal" />
</filter>
</appender> <root>
<!--(高) OFF > FATAL > ERROR > WARN > INFO > DEBUG > ALL (低) -->
<level value="INFO" />
<appender-ref ref="ColoredConsoleAppender"/>
<appender-ref ref="RollingLogFileAppender"/>
</root>
</log4net>
</configuration>

Topshelf关键点:下面我们开始修改Program文件中程序主函数,如下

         //使用Log4进行日志管理
static ILog log = LogManager.GetLogger(typeof(Program));
public static void Main(string[] args)
{
try
{
//Console.WriteLine("Hello World!");
HostFactory.Run(x =>
{
x.UseLog4Net("log4net.config", true);//使用配置文件
//指定服务
x.Service<MyService>(y =>
{
y.ConstructUsing<MyService>(service => new MyService());//实际业务逻辑处理的地方
y.WhenStarted((tc, th) => tc.Start(th));//自定义服务类启动动作,其中th表示自定义服务类实现的ServiceControl接口方法中的入参
y.WhenStopped((ts, th) => ts.Stop(th));//自定义服务类结束动作
}); x.RunAsLocalSystem(); // 服务描述信息
x.SetDescription(ConfigurationManager.AppSettings["ServiceDescription"]);
// 服务显示名称
x.SetDisplayName(ConfigurationManager.AppSettings["ServiceDisplayName"]);
// 服务名称
x.SetServiceName(ConfigurationManager.AppSettings["ServiceName"]);
});
}
catch (Exception ex)
{
log.Error("服务异常:" + ex.Message);
}
}

其中服务的一些基本信息(服务名称,描述信息等)我们通过App.config配置文件进行统一管理。添加App.config文件

 <?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<!--服务名称-->
<add key="ServiceName" value="QuartzService"/>
<!--服务显示名称-->
<add key="ServiceDisplayName" value="Quartz"/>
<!--服务描述-->
<add key="ServiceDescription" value="Quartz定时服务"/>
<!--操作人-->
<add key="OpUser" value="Quartz"/>
</appSettings>
<connectionStrings>
<!--数据库连接字符串>-->
</connectionStrings>
</configuration>

主函数中我们指定了MyService自定义功能类,它是实际处理业务逻辑的地方,也是程序功能处理的入口。自定义类实现了ServiceControl接口,该接口一共包含两个需实现的方法:

bool Start(HostControl hostControl);
bool Stop(HostControl hostControl);

 public class MyService: ServiceControl
{
/// <summary>
/// 程序开始入口
/// </summary>
/// <param name="hostControl"></param>
/// <returns></returns>
public bool Start(HostControl hostControl)
{
//do something you want to do here
return true;
} /// <summary>
/// 程序结束
/// </summary>
/// <param name="hostControl"></param>
/// <returns></returns>
public bool Stop(HostControl hostControl)
{
return true;
}
}

至此,我们通过Topshelf快速开发windows服务的功能已基本完成,同学只需要在Start()方法中添加自己的服务处理功能即可。

但是,我们这里想介绍下如何结合Quart任务调度框架使用。

首先简单介绍下Quartz:

Quartz 是一个开源作业调度框架,允许程序开发人员根据时间的间隔来调度作业,实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联。

我们需要明白 Quartz 的几个核心概念,这样理解起 Quartz 的原理就会变得简单了。

  1. Job 表示一个工作,要执行的具体内容。此接口中只有一个方法,如下:(Quartz3.0版本  方法返类型是Task,老版本为void)

    Task Execute(IJobExecutionContext context);
  2. JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。
  3. Trigger 代表一个调度参数的配置,什么时候去调。
  4. Scheduler 代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。

好了,了解了上面几个概念之后,我们准备开始入手。本文以配置文件的方式进行作业调度管理。

第一步,添加Quartz3.0 Nuget程序包

第二步,修改我们上面添加的自定义服务类,如下:

主要做如下调整:

1.声明并初始化Quartz调度程序实例,其中scheduler初始化方式和老版本的有点不同,Quartz3.0版本的返回值是Task,我们通过方式1和方式2都可以实现(实际使用中,选择一种即可)

2.程序开始和结束方法,调用scheduler的Start()和Shutdown()

         private IScheduler scheduler;//声明Quartz调度程序实例,用与管理Job
/// <summary>
/// 构造函数初始化IScheduler实例
/// </summary>
public MyService()
{
//Quartz3.0版本初始化方式1
scheduler = StdSchedulerFactory.GetDefaultScheduler().GetAwaiter().GetResult();
//Quartz3.0版本初始化方式2
//scheduler = StdSchedulerFactory.GetDefaultScheduler().Result;
} /// <summary>
/// 程序开始入口
/// </summary>
/// <param name="hostControl"></param>
/// <returns></returns>
public bool Start(HostControl hostControl)
{
scheduler.Start();
return true;
} /// <summary>
/// 程序结束
/// </summary>
/// <param name="hostControl"></param>
/// <returns></returns>
public bool Stop(HostControl hostControl)
{
scheduler.Shutdown();
return true;
}

第三步,我们需要添加工作任务job,这里是实际干活的任务。新建一个功能类,实现接口IJob

  /// <summary>
/// 自定义job,实际功能处理单元,需实现IJob
/// </summary>
public class TestJob : IJob
{
log4net.ILog _logger = log4net.LogManager.GetLogger(typeof(TestJob));
public Task Execute(IJobExecutionContext context)
{
_logger.InfoFormat("TestJob测试");
//return Task.FromResult("TestJob测试");
return Task.Factory.StartNew(() => Console.WriteLine($"工作任务测试:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}"));
}
}

最后,也是比较重要的地方。我们需添加配置文件,来关联我们上面创建的schedule和job。

添加配置文件有个注意的地方:文件属性要选择始终复制,便于发布版本时可用。

添加quartz.config

默认配置就可以,里面指定的调度程序实例,线程池数量,配置文件路径名称等

 # You can configure your scheduler in either <quartz> configuration section
# or in quartz properties file
# Configuration section has precedence quartz.scheduler.instanceName = ServerScheduler # configure thread pool info
quartz.threadPool.type = Quartz.Simpl.SimpleThreadPool, Quartz
quartz.threadPool.threadCount = # job initialization plugin handles our xml reading, without it defaults are used
quartz.plugin.xml.type = Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz.Plugins
quartz.plugin.xml.fileNames = ~/quartz_jobs.xml # export this server to remoting context
quartz.scheduler.exporter.type = Quartz.Simpl.RemotingSchedulerExporter, Quartz
quartz.scheduler.exporter.port =
quartz.scheduler.exporter.bindName = QuartzScheduler
quartz.scheduler.exporter.channelType = tcp
quartz.scheduler.exporter.channelName = httpQuartz

添加quartz_jobs.xml

该配置文件才是体现精华的地方。这里面真正实现了schedule,job和trigger三者的联系。也是好多同学上面有疑问的地方(为什么schedule.start()之后会自动调用我创建的job)

配置注意点:

1.job->job-type,配置job类和job类所在的命名空间

2.trigger->job-name/job-group 一定要和你上面创建的job的name/group一样

 <?xml version="1.0" encoding="UTF-8"?>

 <!-- This file contains job definitions in schema version 2.0 format -->

 <job-scheduling-data xmlns="http://quartznet.sourceforge.net/JobSchedulingData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0">

   <processing-directives>
<overwrite-existing-data>true</overwrite-existing-data>
</processing-directives> <schedule> <job>
<name>sampleJob</name>
<group>sampleGroup</group>
<description>Sample job for Quartz Server</description>
<job-type>QuartzServer.TestJob, QuartzServer</job-type>
<durable>true</durable>
<recover>false</recover>
<!--<job-data-map>
<entry>
<key>key1</key>
<value>value1</value>
</entry>
<entry>
<key>key2</key>
<value>value2</value>
</entry>
</job-data-map>-->
</job>
<!--当仅需触发一次或者以固定时间间隔周期执行,SimpleTrigger是最适合的选择-->
<!--<trigger>
<simple>
<name>sampleSimpleTrigger</name>
<group>sampleSimpleGroup</group>
<description>Simple trigger to simply fire sample job</description>
<job-name>sampleJob</job-name>
<job-group>sampleGroup</job-group>
<misfire-instruction>SmartPolicy</misfire-instruction>
<repeat-count>-</repeat-count>
<repeat-interval></repeat-interval>
</simple>
</trigger>-->
<!--通过Cron表达式定义出各种复杂时间规则的调度方案:如每早晨9:00执行,周一、周三、周五下午5:00执行等-->
<trigger>
<cron>
<name>sampleCronTrigger</name>
<group>sampleCronGroup</group>
<description>Cron trigger to simply fire sample job</description>
<job-name>sampleJob</job-name>
<job-group>sampleGroup</job-group>
<misfire-instruction>SmartPolicy</misfire-instruction>
<cron-expression>/ * * * * ?</cron-expression>
</cron>
</trigger>
<!--quartz.Calendar它是一些日历特定时间点的集合,一个Trigger可以和多个Calendar关联,以便排除或包含某些时间点。
假设,我们安排每周星期一早上10:00执行任务,但是如果碰到法定的节日,任务则不执行,这时就需要在Trigger触发机制的基础上使用Calendar进行定点排除-->
<!--<trigger>
<calendar-interval>
<name>sampleCalendarIntervalTrigger</name>
<group>sampleCalendarIntervalGroup</group>
<description>Calendar interval trigger to simply fire sample job</description>
<job-name>sampleJob</job-name>
<job-group>sampleGroup</job-group>
<misfire-instruction>SmartPolicy</misfire-instruction>
<repeat-interval></repeat-interval>
<repeat-interval-unit>Second</repeat-interval-unit>
</calendar-interval>
</trigger>-->
</schedule>
</job-scheduling-data>

常用的cron-trigger表达式配置说明

 cron expressions 整体上还是非常容易理解的,只有一点需要注意:"?"号的用法,看下文可以知道“?”可以用在 day of month 和 day of week中,他主要是为了解决如下场景,如:每月的1号的每小时的31分钟,正确的表达式是:*  *  * ?,而不能是:*  *  * *,因为这样代表每周的任意一天。

 由7段构成:秒 分 时 日 月 星期 年(可选)
"-" :表示范围 MON-WED表示星期一到星期三
"," :表示列举 MON,WEB表示星期一和星期三
"*" :表是“每”,每月,每天,每周,每年等
"/" :表示增量:/(处于分钟段里面) 每15分钟,在0分以后开始,/ 每20分钟,从3分钟以后开始
"?" :只能出现在日,星期段里面,表示不指定具体的值
"L" :只能出现在日,星期段里面,是Last的缩写,一个月的最后一天,一个星期的最后一天(星期六)
"W" :表示工作日,距离给定值最近的工作日
"#" :表示一个月的第几个星期几,例如:"6#3"表示每个月的第三个星期五(=SUN...=FRI,=SAT) 官方实例
Expression Meaning
* * ? 每天中午12点触发
? * * 每天上午10:15触发
* * ? 每天上午10:15触发
* * ? * 每天上午10:15触发
* * ? 2005年的每天上午10:15触发
* * * ? 在每天下午2点到下午2:59期间的每1分钟触发
/ * * ? 在每天下午2点到下午2:55期间的每5分钟触发
/ , * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
- * * ? 在每天下午2点到下午2:05期间的每1分钟触发
, ? WED 每年三月的星期三的下午2:10和2:44触发
? * MON-FRI 周一至周五的上午10:15触发
* ? 每月15日上午10:15触发
L * ? 每月最后一日的上午10:15触发
L- * ? Fire at :15am on the 2nd-to-last last day of every month
? * 6L 每月的最后一个星期五上午10:15触发
? * 6L Fire at :15am on the last Friday of every month
? * 6L - 2002年至2005年的每月的最后一个星期五上午10:15触发
? * # 每月的第三个星期五上午10:15触发
/ * ? Fire at 12pm (noon) every days every month, starting on the first day of the month.
? Fire every November 11th at :11am.

好了,大功告成,我们只需要发布部署即可。

安装:TopshelfDemo.exe install
启动:TopshelfDemo.exe start
卸载:TopshelfDemo.exe uninstall
 
安装程序(指定项目发布文件地址 进行Install)

在服务列表中可看到我们刚才安装的windows服务

好了,本文关于介绍topshelf框架快速开发windows服务以及通过Quartz框架调度管理服务的开发已介绍完毕,文中有不足之处,请各位看官多多指正!

Topshelf+Quartz3.0基于控制台应用程序快速开发可调度windows服务的更多相关文章

  1. 基于SpringBoot+AntDesign的快速开发平台,JeecgBoot 2.0.2 版本发布

    Jeecg-Boot 是一款基于SpringBoot+代码生成器的快速开发平台! 采用前后端分离架构:SpringBoot,Ant-Design-Vue,Mybatis,Shiro,JWT. 强大的代 ...

  2. 微信小程序快速开发

    微信小程序快速开发 一.注册小程序账号,下载IDE 1.官网注册https://mp.weixin.qq.com/,并下载IDE. 2.官方文档一向都是最好的学习资料. 注意:1)注册账号之后会有一个 ...

  3. 在线Online表单来了!JeecgBoot 2.1 版本发布——基于SpringBoot+AntDesign的快速开发平台

    项目介绍 Jeecg-Boot 是一款基于SpringBoot+代码生成器的快速开发平台! 采用前后端分离架构:SpringBoot,Ant-Design-Vue,Mybatis,Shiro,JWT. ...

  4. JEECG 4.0 版本发布,JAVA快速开发平台

    JEECG 4.0 版本发布,系统全面优化升级,更快,更稳定!         导读                               ⊙平台性能优化,系统更稳定,速度闪电般提升      ...

  5. web程序快速开发

    关于web程序快速开发个人见解以及经历 由于在之前公司业务的发展,需要在基于核心业务的基础上开发其他较为独立的业务系统,所以就有了这个基于Dapper,DDD概念的基础框架,由于个人基于这个框架已经经 ...

  6. vue+uni-app商城实战 | 第一篇:【有来小店】微信小程序快速开发接入Spring Cloud OAuth2认证中心完成授权登录

    一. 前言 本篇通过实战来讲述如何使用uni-app快速进行商城微信小程序的开发以及小程序如何接入后台Spring Cloud微服务. 有来商城 youlai-mall 项目是一套全栈商城系统,技术栈 ...

  7. 微信小程序快速开发上手

    微信小程序快速开发上手 介绍: 从实战开发角度,完整系统地介绍了小程序的开发环境.小程序的结构.小程序的组件与小程序的API,并提供了多个开发实例帮助读者快速掌握小程序的开发技能,并能自己动手开发出小 ...

  8. 【技术博客】基于vue的前端快速开发(工具篇)

    一.Vue教程 vue.js是一套构建用户界面的渐进式框架.vue采用自底向上增量开发的设计.vue的核心库只关心视图层,非常容易学习,非常容易与其它库和已有项目整合.vue完全有能力驱动采用单文件组 ...

  9. 关于web程序快速开发个人见解以及经历

    由于在之前公司业务的发展,需要在基于核心业务的基础上开发其他较为独立的业务系统,所以就有了这个基于Dapper,DDD概念的基础框架,由于个人基于这个框架已经经历过两个系统的开发,也因为其他项目团队需 ...

随机推荐

  1. 模型压缩之Channel Pruning

    论文地址 channel pruning是指给定一个CNN模型,去掉卷积层的某几个输入channel以及相应的卷积核, 并最小化裁剪channel后与原始输出的误差. 可以分两步来解决: channe ...

  2. 语言发展与python

    编程语言的发展史(机械语言.汇编语言.高级语言) 机械语言:直接使用二进制与计算机沟通,直接操作硬件,执行效率高,开发效率低. 汇编语言:用简单的英文代替二进制,直接操作硬件,执行效率较机械语言低,开 ...

  3. Python实现链表倒序(带头指针)

    class ListNode(object): def __init__(self, x): self.val = x self.next = None def reverseList(self, h ...

  4. nexus7入手

    平板一直关注了很久了,关键是不知道平板对我来说,拿它来做什么用.所以,一直也就是关注,也没有决心买了. 终于这次出手了,N7,到货了! 照片是原生的android系统,不习惯,不习惯,直接用刷机精灵, ...

  5. Unity中使用C#的null条件运算符?.的注意事项

    Introduction: 在C#6及以上版本中,加入了一项特别好用的运算符:Null条件运算符?.和?[]可以用来方便的执行判空操作,当运算符左侧操作数不为null时才会进行访问操作,否则直接返回n ...

  6. 参考C# 使用 System.Web.Script.Serialization 解析 JSON

    参考C# 使用 System.Web.Script.Serialization 解析 JSON 使用json需要引用到System.Web.Script.Serialization.习惯在解决方案右键 ...

  7. 【WPF学习】第五十四章 关键帧动画

    到目前为止,看到的所有动画都使用线性插值从起点到终点.但如果需要创建具有多个分段的动画和不规则移动的动画.例如,可能希望创建一个动画,快速地将一个元素滑入到视图中,然后慢慢地将它移到正确位置.可通过创 ...

  8. HTML5中form的新增属性或元素

    1.新增的表单元素 1.1 progress表示任务的完成情况,常用于进度条. max 定义进度元素所要求的任务的工作量,默认值为1 value 定义已经完成的工作量,如果max值为1,该值必须是介于 ...

  9. mongoose-面向对象操作mongodb的Nodejs框架

    介绍 无论是mysql还是mongodb,传统的与数据库交互的方式都是按照他们提供的API来写代码.它们提供的API往往不是很容易理解,而且难以记忆,如果传错了参数,写错一个符号都要查文档. ORM( ...

  10. C# 存储相同键多个值的Dictionary

    涉及到两个问题: 一.访问磁盘中文件夹.文件夹下面的文件夹 先看一下磁盘文件夹结构 C盘下面有个根文件夹SaveFile,SaveFIle下面有两个子文件夹分别为,2018.2019, 子文件下201 ...