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. MyBatis if test 传入一个数字进行比较报错 There is no getter for property named 'userState' in 'class java.lang.Integer'

    在写MyBatis映射文件中,我要传入一个 int 类型的参数,在映射文件中用 'test' 中进行比较,我花了很长时间百度,发现都是不靠谱的方法,有把数字在比较时转成字符串用 equals 比较的. ...

  2. HTTP 协议的基本知识,包括请求流程、请求方法等

    一.什么是http协议? HTTP是Hyper Text Transfer Protocol(超文本传输协议)的缩写.它的发展是万维网协会(World Wide Web Consortium)和Int ...

  3. 会编程的 AI + 会修 Bug 的 AI,等于什么 ?

    2017-02-25 Python开发者 (点击上方公众号,可快速关注) 关于人工智能未来的畅想,除了家庭服务机器人,快递无人机,医用机器人等等,Lucas Carlson 认为人工智能在另外一个领域 ...

  4. k8s集群搭建(三)

    Dashboard安装 Kubernetes Dashboard是k8s提供基于Web的监控和操作界面,可以通过UI来显示集群的所有工作负载,除了查看资源,还是创建.编辑.更新.删除资源. 根据Kub ...

  5. CSRF之POST

    最近重温<白帽子讲web安全>一书,看到第4章CSRF的时候,发现有个错误的地方,第116页底部的代码中有个坑,那段代码是运行不了的.原因是在form表单中有个<input type ...

  6. Swift iOS实现把PCM语音转成MP3格式

    最近折腾了swift的语音录制识别和转码,这块还是比较坑的,由于语音识别的准确度实测大概也就80%左右,所以还是需要上传录音文件啊.首先是用讯飞语音SDK实现语音录制和识别(语音听写),第一个坑是讯飞 ...

  7. 微软发布MS MARCO数据集,提高计算机阅读理解能力

    ​ MARCO数据集,提高计算机阅读理解能力" title="微软发布MS MARCO数据集,提高计算机阅读理解能力"> ​ 本文译自:Microsoft data ...

  8. 线程sleep,wait,notify,join,yield方法解析

    线程的五种状态 线程从创建到销毁一般分为五种状态,如下图: 1) 新建 当用new关键字创建一个线程时,就是新建状态. 2) 就绪 调用了 start 方法之后,线程就进入了就绪阶段.此时,线程不会立 ...

  9. Grafana+Prometheus监控mysql性能

    #cmd /usr/local 今天讲一下如何监控服务器中的mysql数据库的性能 一.数据库操作 1.mysql启动 #service mysqld start #启动数据库 #service my ...

  10. 30s源码刨析系列之函数篇

    前言 由浅入深.逐个击破 30SecondsOfCode 中函数系列所有源码片段,带你领略源码之美. 本系列是对名库 30SecondsOfCode 的深入刨析. 本篇是其中的函数篇,可以在极短的时间 ...