windows服务
Windows服务是一种在Windows操作系统中运行的后台程序,用于在系统启动时启动并在系统关闭时关闭。这些服务可以是Microsoft自己的服务,也可以是第三方软件的服务。它们在后台运行,通常不需要交互式用户界面。 Windows服务通常用于在计算机上提供系统级别的功能和服务,例如打印服务、数据库服务、网络服务、系统安全服务等。 通常,Windows服务可以在服务控制管理器(SCM)中进行配置和管理。其中,SCM是一种Windows组件,用于管理Windows服务和设备驱动程序。
.NET8
在全盘扫描是怎么实现的介绍了下.NET5的架构。目前.NET已经升级到.NET8。Blazor maui AOT更加成熟。微软在不遗余力的布局全平台,提升C#的性能.。ChatGPT最大的BOSS也是微软,只能说微软是真的强,虽错过了移动互联网,但凭借云计算和人工智能的布局,我想会再一次登顶科技圈。不得不说,科技是第一生产力。 .NET8新增功能在这里。
创建windows服务
本文以打卡程序为例来实践下整个服务的创建过程。
- dotnet new worker --name “SmartSign2”
- dotnet add package Microsoft.Extensions.Hosting.WindowsServices
- 更新项目文件
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>App.WindowsService</RootNamespace>
<OutputType>exe</OutputType>
<PublishSingleFile Condition="'$(Configuration)' == 'Release'">true</PublishSingleFile>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PlatformTarget>x64</PlatformTarget>
<UserSecretsId>dotnet-smart_sign-ef6ac77d-c996-482b-8f81-5d63d0c287a5</UserSecretsId>
</PropertyGroup>
- 创建服务
要爬去网站或者模拟登陆的话,请修改相应的账号密码以及URL
public sealed class JokeService
{
private string name = "xxx";
private string password = "xxx";
public string GetKqMessage()
{
String Result = String.Empty;
IWebDriver wd = null;
try
{
wd = new EdgeDriver();
wd.Navigate().GoToUrl("http://xxx/#/");
IWindow window = wd.Manage().Window;
Thread.Sleep(2000);
//处理登陆
wd.FindElement(By.Name("account")).SendKeys(name);
wd.FindElement(By.Name("password")).SendKeys(password);
wd.FindElement(By.CssSelector("body > div > div > div > form > button > span")).Click();
Thread.Sleep(10000);
//打卡记录
var record = "body > div.theme_panasonic > div > section > section > div > main > div.el-col.el-col-4.el-col-offset-2 > div > div:nth-child(4) > div:nth-child(5) > div > div > div:nth-child(2) > span";
wd.FindElement(By.CssSelector(record)).Click();
Thread.Sleep(5000);
//每日考勤
var attendence = "body > div > div > section > section > div > main > div.el-col.el-col-24 > div:nth-child(1) > div.EasyNormalTable > div.el-card.box-card.is-always-shadow > div.el-card__header > div > div > button:nth-child(3) > span";
wd.FindElement(By.CssSelector(attendence)).Click();
Thread.Sleep(2000);
var attendence_time = "body > div > div > section > section > div > main > div.el-col.el-col-24 > div:nth-child(1) > div > div.el-card.box-card.is-always-shadow > div.el-card__body > div.filter-container > span.el-tag.el-tag--success.el-tag--light";
var total_time = wd.FindElement(By.CssSelector(attendence_time)).Text;
var tbody = wd.FindElement(By.TagName("tbody"));
var detail_message = tbody.Text;
var childrens = tbody.FindElements(By.TagName("tr"));
// 第一条打卡记录
var first_record_message = childrens.First().Text;
String last_record_message = String.Empty;
if (childrens.Count >= 2)
{
last_record_message = childrens[childrens.Count - 2].Text;
}
else
{
last_record_message = childrens[childrens.Count - 1].Text;
}
var record_message = first_record_message + "," + last_record_message;
Thread.Sleep(2000);
wd.Navigate().GoToUrl("http://burning.live:3000/counter");
Thread.Sleep(3000);
wd.FindElement(By.CssSelector("body > div.page > main > article > div > textarea")).SendKeys("首末次打卡情况:" + record_message + " \r\n" + total_time + "\r\n" + detail_message);
var btn_send = wd.FindElement(By.CssSelector("#mail"));
btn_send.Click();
Thread.Sleep(3000);
Result = record_message;
}
catch (Exception e)
{
Console.WriteLine(e);
Result = e.StackTrace;
}
finally
{
if (wd != null)
{
wd.Close();
wd.Quit();
}
}
return Result;
}
public string GetJoke()
{
Joke joke = _jokes.ElementAt(
Random.Shared.Next(_jokes.Count));
return $"{joke.Setup}{Environment.NewLine}{joke.Punchline}";
}
// Programming jokes borrowed from:
// https://github.com/eklavyadev/karljoke/blob/main/source/jokes.json
private readonly HashSet<Joke> _jokes = new()
{
new Joke("What's the best thing about a Boolean?", "Even if you're wrong, you're only off by a bit."),
new Joke("What's the object-oriented way to become wealthy?", "Inheritance"),
new Joke("Why did the programmer quit their job?", "Because they didn't get arrays."),
new Joke("Why do programmers always mix up Halloween and Christmas?", "Because Oct 31 == Dec 25"),
new Joke("How many programmers does it take to change a lightbulb?", "None that's a hardware problem"),
new Joke("If you put a million monkeys at a million keyboards, one of them will eventually write a Java program", "the rest of them will write Perl"),
new Joke("['hip', 'hip']", "(hip hip array)"),
new Joke("To understand what recursion is...", "You must first understand what recursion is"),
new Joke("There are 10 types of people in this world...", "Those who understand binary and those who don't"),
new Joke("Which song would an exception sing?", "Can't catch me - Avicii"),
new Joke("Why do Java programmers wear glasses?", "Because they don't C#"),
new Joke("How do you check if a webpage is HTML5?", "Try it out on Internet Explorer"),
new Joke("A user interface is like a joke.", "If you have to explain it then it is not that good."),
new Joke("I was gonna tell you a joke about UDP...", "...but you might not get it."),
new Joke("The punchline often arrives before the set-up.", "Do you know the problem with UDP jokes?"),
new Joke("Why do C# and Java developers keep breaking their keyboards?", "Because they use a strongly typed language."),
new Joke("Knock-knock.", "A race condition. Who is there?"),
new Joke("What's the best part about TCP jokes?", "I get to keep telling them until you get them."),
new Joke("A programmer puts two glasses on their bedside table before going to sleep.", "A full one, in case they gets thirsty, and an empty one, in case they don’t."),
new Joke("There are 10 kinds of people in this world.", "Those who understand binary, those who don't, and those who weren't expecting a base 3 joke."),
new Joke("What did the router say to the doctor?", "It hurts when IP."),
new Joke("An IPv6 packet is walking out of the house.", "He goes nowhere."),
new Joke("3 SQL statements walk into a NoSQL bar. Soon, they walk out", "They couldn't find a table.")
};
}
readonly record struct Joke(string Setup, string Punchline);
- 重写worker类
可通过事件查看器来查看日志。在指定的时间会调用GetKqMessage,其他时间调用GetJoke
namespace App.WindowsService;
public sealed class WindowsBackgroundService : BackgroundService
{
private readonly JokeService _jokeService;
private readonly ILogger<WindowsBackgroundService> _logger;
private int interval = 0;
public WindowsBackgroundService(
JokeService jokeService,
ILogger<WindowsBackgroundService> logger) =>
(_jokeService, _logger) = (jokeService, logger);
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
try
{
while (!stoppingToken.IsCancellationRequested)
{
var isNeedTime = (DateTimeOffset.Now.Hour == 20 && DateTimeOffset.Now.Minute == 00) || (DateTimeOffset.Now.Hour == 18 && DateTimeOffset.Now.Minute == 00)
|| (DateTimeOffset.Now.Hour == 15 && DateTimeOffset.Now.Minute == 00) || (DateTimeOffset.Now.Hour == 10 && DateTimeOffset.Now.Minute == 00);
if (isNeedTime)
{
string kqMessage = _jokeService.GetKqMessage();
_logger.LogWarning("打卡结束 {kqMessage}", kqMessage);
}
else
{
if (interval % 4 == 0)
{
string joke = _jokeService.GetJoke();
_logger.LogWarning("interval is {interval}, {Joke}, 当前时间是: {time}", interval, joke, DateTimeOffset.Now);
}
}
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
interval++;
if (interval == 30)
{
Console.Clear();
interval = 0;
}
}
}
catch (OperationCanceledException)
{
interval = 0;
// When the stopping token is canceled, for example, a call made from services.msc,
// we shouldn't exit with a non-zero exit code. In other words, this is expected...
}
catch (Exception ex)
{
interval = 0;
_logger.LogError(ex, "{Message}", ex.Message);
// Terminates this process and returns an exit code to the operating system.
// This is required to avoid the 'BackgroundServiceExceptionBehavior', which
// performs one of two scenarios:
// 1. When set to "Ignore": will do nothing at all, errors cause zombie services.
// 2. When set to "StopHost": will cleanly stop the host, and log errors.
//
// In order for the Windows Service Management system to leverage configured
// recovery options, we need to terminate the process with a non-zero exit code.
Environment.Exit(1);
}
}
}
- 重写Program.cs
using App.WindowsService;
using Microsoft.Extensions.Logging.Configuration;
using Microsoft.Extensions.Logging.EventLog;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddWindowsService(options =>
{
options.ServiceName = "SmartSign2";
});
LoggerProviderOptions.RegisterProviderOptions<
EventLogSettings, EventLogLoggerProvider>(builder.Services);
builder.Services.AddSingleton<JokeService>();
builder.Services.AddHostedService<WindowsBackgroundService>();
IHost host = builder.Build();
host.Run();
- 发布应用
- 创建 Windows 服务
sc.exe create “SmartSign2” binpath=“C:\Users\yy\source\Python_Kq (1)\smart\smart_sign\bin\Release\net8.0\win-x64\publish\smart_sign.exe” - 配置 Windows 服务
sc qfailure “SmartSign2” - 启动 Windows 服务
sc.exe start “SmartSign2” - 停止 Windows 服务
sc.exe stop “SmartSign2” - 删除 Windows 服务
sc.exe delete “SmartSign2”
参考
使用 BackgroundService 创建 Windows 服务
更多内容,欢迎关注我的微信公众号:半夏之夜的无情剑客。