--- title: ASP.NET Core date: 2021-03-23 10:30:31 author: 文永达 top_img: https://gcore.jsdelivr.net/gh/volantis-x/cdn-wallpaper/abstract/67239FBB-E15D-4F4F-8EE8-0F1C9F3C4E7C.jpeg --- # Web API 项目初始化搭建 首先打开Visual Studio 2022,然后选择创建新项目 之后筛选下拉框选择如红框标注 ![image-20230301104542343](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20230301104542343.png) 起一个项目名称及选择项目位置,下一步 ![image-20230301104644030](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20230301104644030.png) 框架选择.Net 6.0(长期支持) 选择启用Docker,为了之后可以部署到Docker容器 启用OpenAPI支持是为了可以输出Swagger接口文档,但如果使用Furion框架的话,需要勾掉 顶级语句是无需在Program.cs中显式包含Main方法,可以使用顶级语句功能最大程度地减少必须编写的代码 ![image-20230301104855227](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20230301104855227.png) 点击创建即可 ![image-20230301105745182](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20230301105745182.png) # 集成Furion框架 在NuGet包管理器中搜索 `Furion` ![image-20230301110105535](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20230301110105535.png) 选择安装的项目,然后安装即可 `Program.cs`配置 ```c# var builder = WebApplication.CreateBuilder(args).Inject(); builder.Services.AddControllers().AddInject(); app.UseInject(); ``` # 可能遇到的问题 ## 包降级 ![image-20230301110232202](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20230301110232202.png) 将提示的NuGet包升级到 前者的版本即可,比如图内的 Swashbuckle.AspNetCore 原有的版本是 6.2.3 那么升级到 6.5.0即可 # 部署到Docker ## 安装.Net SDK 6.0环境 ```shell sudo rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm sudo yum install dotnet-sdk-6.0 dotnet --info ``` ## Visual Studio添加Docker支持 ![image-20221121144928205](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20221121144928205.png) ## Linux下构建Docker镜像 ```shell docker image build -f ./XiaodaERP/Dockerfile -t aspnetcore . docker images ``` ## 运行Docker镜像 ```shell docker run --name=aspnetcore -p 9001:80 -d aspnetcore docker ps ``` ```shell cd /usr/local/jenkins_home/workspace/XiaodaERP_NetCore echo $PWD docker image build -f ./XiaodaERP/Dockerfile -t xiaodaerp/netcore . docker images docker run --name xiaodaerp/netcore -p 7274:80 -d xiaodaerp/netcore ``` # 顶级语句配置 `Program.cs` ## 取消默认JSON首字母小写命名 ```c# builder.Services.AddControllers().AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNamingPolicy = null; }); ``` ## Json序列化时忽略属性为null的值 ```c# builder.Services.AddControllers().AddJsonOptions(options => { options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; }); ``` ## Json序列化时日期类型格式化输出 ```c# builder.Services.AddControllers().AddJsonOptions(options => { options.JsonSerializerOptions.Converters.Add(new SystemTextJsonDateTimeJsonConverter("yyyy-MM-dd HH:mm:ss")); }); ``` ## 使用Autofac自动注入Service 通过NuGet包管理器 安装NuGet包 ![image-20221130161234399](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20221130161234399.png) ![image-20221130161319595](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20221130161319595.png) Autofac Autofac.Extensions.DependencyInjection Autofac.Extras.DynamicProxy 新建 `ServiceAutofac.cs`类 ```c# using System.Reflection; namespace XiaodaERP { public class ServiceAutofac { /// /// 获取程序集名称 /// /// public static string GetAssemblyName() { return Assembly.GetExecutingAssembly().GetName().Name; } } } ``` `Program.cs`配置 ```c# builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); builder.Host.ConfigureContainer(builder => { Assembly assembly = Assembly.Load(ServiceAutofac.GetAssemblyName());//注入Service程序集 可以是其他程序集 builder.RegisterAssemblyTypes(assembly) .AsImplementedInterfaces() .InstancePerDependency(); }); ``` ## 注入Entity Framework Core 6 DbContext上下文 ```c# builder.Services.AddDbContext(options => options.UseOracle(builder.Configuration.GetConnectionString("OracleDbContext"))); builder.Services.AddDbContext(options => options.UseSqlServer(builder.Configuration.GetConnectionString("SqlServerDbContext"))); ``` ## 使用JWT进行授权与认证 安装NuGet包 `Microsoft.AspNetCore.Authentication.JwtBearer` ![image-20221206130808039](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20221206130808039.png) `appsettings.json`配置文件中配置 ```json "Authentication": { "SecretKey": "nadjhfgkadshgoihfkajhkjdhsfaidkuahfhdksjaghidshyaukfhdjks", "Issuer": "www.xiaoda", "Audience": "www.xiaoda" } ``` `Program.cs`顶级语句配置 ```c# // 使用Autofac自动注入Service builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); builder.Host.ConfigureContainer(builder => { Assembly assembly = Assembly.Load(ServiceAutofac.GetAssemblyName());//注入Service程序集 可以是其他程序集 builder.RegisterAssemblyTypes(assembly) .AsImplementedInterfaces() .InstancePerDependency(); // 在IOC容器中注入 // 用于Jwt的各种操作 builder.RegisterType().InstancePerLifetimeScope(); // 支持泛型存入Jwt builder.RegisterType().InstancePerLifetimeScope(); }); //JWT认证 builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options => { //取出私钥 var secretByte = Encoding.UTF8.GetBytes(builder.Configuration["Authentication:SecretKey"]); options.TokenValidationParameters = new TokenValidationParameters() { //验证发布者 ValidateIssuer = true, ValidIssuer = builder.Configuration["Authentication:Issuer"], //验证接受者 ValidateAudience = true, ValidAudience = builder.Configuration["Authentication:Audience"], //验证是否过期 ValidateLifetime = true, //验证私钥 IssuerSigningKey = new SymmetricSecurityKey(secretByte) }; }); // 顺序不能颠倒 // 你是谁 授权 app.UseAuthentication(); // 你可以干什么 验证 app.UseAuthorization(); ``` 新建 `TokenHelper.cs`工具类 ```c# using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Reflection; using System.Security.Claims; using System.Text; using XiaodaERP.Models; namespace XiaodaERP.Utils { public class TokenHelper { private readonly IConfiguration _configuration; private readonly JwtSecurityTokenHandler _jwtSecurityTokenHandler; public TokenHelper(IConfiguration configuration, JwtSecurityTokenHandler jwtSecurityTokenHandler) { this._configuration = configuration; this._jwtSecurityTokenHandler = jwtSecurityTokenHandler; } public static string? Token { get; set; } // 生成Token public string CreateJwtToken(T user) { // 生成JWT // Header,选择签名算法 var signingAlogorithm = SecurityAlgorithms.HmacSha256; // Payload,存放用户信息,放用户ID,用户名 var claimList = this.CreateClaimList(user); //Signature //取出私钥并以utf8编码字节输出 var secretByte = Encoding.UTF8.GetBytes(_configuration["Authentication:SecretKey"]); //使用非对称算法对私钥进行加密 var signingKey = new SymmetricSecurityKey(secretByte); //使用HmacSha256来验证加密后的私钥生成数字签名 var signingCredentials = new SigningCredentials(signingKey, signingAlogorithm); //生成Token var Token = new JwtSecurityToken( issuer: _configuration["Authentication:Issuer"], //发布者 audience: _configuration["Authentication:Audience"], //接收者 claims: claimList, //存放的用户信息 notBefore: DateTime.UtcNow, //发布时间 expires: DateTime.UtcNow.AddMinutes(30), //有效期设置为1天 signingCredentials //数字签名 ); //生成字符串token var TokenStr = new JwtSecurityTokenHandler().WriteToken(Token); return TokenStr; } // 获取Token Payload信息 public T GetToken(string token) { Type t = typeof(T); object obj = Activator.CreateInstance(t); var b = _jwtSecurityTokenHandler.ReadJwtToken(token); foreach (var item in b.Claims) { PropertyInfo propertyInfo = t.GetProperty(item.Type); if (propertyInfo != null && propertyInfo.CanRead) { propertyInfo.SetValue(obj, item.Value, null); } } return (T)obj; } // 根据类生成Token 断言列表 private List CreateClaimList(T authUser) { var Class = typeof(T); List claimList = new(); foreach (var item in Class.GetProperties()) { // 不将PassWord放入Token中 if (item.Name == "PassWord") { continue; } // 将UserName属性名重命名为username存入Token中 if (item.Name == "UserName") { claimList.Add(new Claim("username", Convert.ToString(item.GetValue(authUser)))); continue; } claimList.Add(new Claim(item.Name, Convert.ToString(item.GetValue(authUser)))); } return claimList; } } } ``` 在登录方法中加入 ```c# public ViewUser Login(string UserName, string PassWord) { var res = _sqlServerDbContext.Users.Include(user => user.Role).FirstOrDefault(x => x.UserName == UserName); if (res != null) { if (res.PassWord == Md5Encoding(PassWord)) { // 生成JWT var TokenStr = _tokenHelper.CreateJwtToken(res); var config = new MapperConfiguration(cfg => cfg.CreateMap() .ForMember(d => d.username, opt => opt.MapFrom(src => src.UserName)) .AfterMap((src, des) => des.Roles = new Role[1] { src.Role }) .AfterMap((src, des) => des.Token = "bearer " + TokenStr) // 需要加上bearer .AfterMap((src, des) => des.HomePath = "/dashboard/analysis") .AfterMap((src, des) => des.password = null)); var mapper = config.CreateMapper(); return mapper.Map(res); } } return null; } ``` WebAPI 需要认证的加上 `[Authorize]`注解,注意登录不能加 ```c# [AuthFilter] [HttpPost(Name = "login")] public ResultUtil Login(ViewUser viewUser) => ResultUtil.ok(_userService.Login(viewUser.username, viewUser.password)); // 需要认证的API [Authorize] [AuthFilter] [HttpGet(Name = "getUserInfo")] public ResultUtil GetUserInfo() { Token = HttpContext.Request.Headers["Authorization"]; Token = Token.Split(" ")[1]; TokenHelper.Token = Token; ViewUser us = _tokenHelper.GetToken(Token); return ResultUtil.ok(us); } ``` 访问登录接口 ![image-20221206131847333](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20221206131847333.png) 访问需要认证的接口,需要把Token放在请求头中,如果不携带Token,访问则报401 ![image-20221206132042533](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20221206132042533.png) 请求头Key 为 Authorization 访问成功 ![image-20221206132124913](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20221206132124913.png) # 三大拦截器 认证拦截器 `AuthorizeAttribute` 方法拦截器 `ActionFilterAttribute` ```c# using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Filters; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System.IdentityModel.Tokens.Jwt; using Castle.Core.Internal; using XiaodaERP.Models; using XiaodaERP.Utils; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; namespace XiaodaERP.Attributes { public class AuthFilter : ActionFilterAttribute { //private readonly TokenHelper _tokenHelper; //public AuthFilter(TokenHelper tokenHelper) //{ // this._tokenHelper = tokenHelper; //} private readonly SqlServerDbContext _sqlServerDbContext; public AuthFilter(SqlServerDbContext sqlServerDbContext) { this._sqlServerDbContext = sqlServerDbContext; } private SysActionLog sysActionLog = new() { ActionId = Guid.NewGuid().ToString().Replace("-", "").ToUpper() }; public override void OnActionExecuting(ActionExecutingContext context) { var descriptor = context.ActionDescriptor as ControllerActionDescriptor; string param = string.Empty; string globalParam = string.Empty; var jsonSetting = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }; foreach (var arg in context.ActionArguments) { string value = Newtonsoft.Json.JsonConvert.SerializeObject(arg.Value, Formatting.None, jsonSetting); param += $"{arg.Key} : {value} \r\n"; globalParam += value; } // 方法名 Console.WriteLine(descriptor.ActionName); // 参数值拼接 Console.WriteLine(globalParam); // 参数名 与 值 Console.WriteLine(param); sysActionLog.ActionName = descriptor.ActionName; sysActionLog.RequestParams = param; } public override void OnActionExecuted(ActionExecutedContext context) { // 获取请求Host Console.WriteLine(context.HttpContext.Request.Host); sysActionLog.RequestHost = context.HttpContext.Request.Host.ToString(); // 获取请求方法 Console.WriteLine(context.HttpContext.Request.Method); sysActionLog.RequestMethod = context.HttpContext.Request.Method; // 获取请求Url Console.WriteLine(context.HttpContext.Request.Path); sysActionLog.RequestPath = context.HttpContext.Request.Path.ToString(); // 获取应答返回状态码 Console.WriteLine(context.HttpContext.Response.StatusCode); if (context.HttpContext.Request.Path.Equals("/api/User/login")) { sysActionLog.ActionTime = DateTime.Now; } else { string Token = context.HttpContext.Request.Headers["Authorization"]; // Token失效 if (Token.IsNullOrEmpty()) { } else { Token = Token.Split(" ")[1]; TokenHelper.Token = Token; ViewUser us = new TokenHelper(new JwtSecurityTokenHandler()).GetToken(Token); Console.WriteLine(us.UserId); sysActionLog.ActionUserId = us.UserId; Console.WriteLine(us.username); sysActionLog.ActionUserName = us.username; } sysActionLog.ActionTime = DateTime.Now; } _sqlServerDbContext.SysActionLogs.Add(sysActionLog); _sqlServerDbContext.SaveChanges(); } } } ``` 接口上使用 ```c# //[AuthFilter] [TypeFilter(typeof(AuthFilter))] [HttpPost(Name = "login")] public ResultUtil Login(ViewUser viewUser) => ResultUtil.ok(_userService.Login(viewUser.username, viewUser.password)); [Authorize] //[AuthFilter] // 注解为拦截器类名 [TypeFilter(typeof(AuthFilter))] // 因为主键中使用了构造器依赖注入,所以需要使用TypeFilter,并需要在顶级语句中注入 AuthFilter [HttpGet(Name = "getUserInfo")] public ResultUtil GetUserInfo() { Token = HttpContext.Request.Headers["Authorization"]; Token = Token.Split(" ")[1]; TokenHelper.Token = Token; ViewUser us = _tokenHelper.GetToken(Token); return ResultUtil.ok(us); } ``` 顶级语句中注入 ```c# builder.Services.AddScoped(); ``` 异常拦截器 `ExceptionFilterAttribute` # AspNetCoreRateLimit 速率限制 ## 介绍 [**AspNetCoreRateLimit**](https://github.com/stefanprodan/AspNetCoreRateLimit/)是一个ASP.NET Core速率限制的解决方案,旨在控制客户端根据IP地址或客户端ID向Web API或MVC应用发出的请求的速率。AspNetCoreRateLimit包含一个**IpRateLimitMiddleware**和**ClientRateLimitMiddleware**,每个中间件可以根据不同的场景配置限制允许IP或客户端,自定义这些限制策略,也可以将限制策略应用在每个API URL或具体的HTTP Method上。 ## 使用 由上面介绍可知AspNetCoreRateLimit支持了两种方式:基于**客户端IP(\**IpRateLimitMiddleware)\**和客户端ID(\**ClientRateLimitMiddleware\**)速率限制** 接下来就分别说明使用方式 添加Nuget包引用: ```shell Install-Package AspNetCoreRateLimit ``` ### 基于客户端IP速率限制 新建 `IPRateExtension.cs` ```c# public static class IPRateExtension { public static void AddIPRate(this IServiceCollection services, IConfiguration configuration) { if (services == null) throw new ArgumentNullException(nameof(services)); //从appsettings.json中加载常规配置,IpRateLimiting与配置文件中节点对应 services.Configure(configuration.GetSection("IpRateLimiting")); //从appsettings.json中加载Ip规则 services.Configure(configuration.GetSection("IpRateLimitPolicies")); //注入计数器和规则存储 //分布式部署时,需要将速率限制计算器和ip规则存储到分布式缓存中如Redis services.AddSingleton(); services.AddSingleton(); // services.AddSingleton(); services.AddSingleton(); // services.AddSingleton(); //配置(解析器、计数器密钥生成器) services.AddSingleton(); services.AddSingleton(); } } ``` `Program.cs` ```c# builder.Services.AddSingleton(); //初始化限流器 builder.Services.AddIPRate(builder.Configuration); //启用客户端IP限制速率 app.UseIpRateLimiting(); ``` **在appsettings.json中添加通用配置项节点:** ```json "IpRateLimiting": { //false,则全局将应用限制,并且仅应用具有作为端点的规则*。例如,如果您设置每秒5次调用的限制,则对任何端点的任何HTTP调用都将计入该限制 //true, 则限制将应用于每个端点,如{HTTP_Verb}{PATH}。例如,如果您为*:/api/values客户端设置每秒5个呼叫的限制, "EnableEndpointRateLimiting": false, //false,拒绝的API调用不会添加到调用次数计数器上;如 客户端每秒发出3个请求并且您设置了每秒一个调用的限制,则每分钟或每天计数器等其他限制将仅记录第一个调用,即成功的API调用。如果您希望被拒绝的API调用计入其他时间的显示(分钟,小时等) //,则必须设置StackBlockedRequests为true。 "StackBlockedRequests": false, //Kestrel 服务器背后是一个反向代理,如果你的代理服务器使用不同的页眉然后提取客户端IP X-Real-IP使用此选项来设置 "RealIpHeader": "X-Real-IP", //取白名单的客户端ID。如果此标头中存在客户端ID并且与ClientWhitelist中指定的值匹配,则不应用速率限制。 "ClientIdHeader": "X-ClientId", //限制状态码 "HttpStatusCode": 429, ////IP白名单:支持Ip v4和v6 //"IpWhitelist": [ "127.0.0.1", "::1/10", "192.168.0.0/24" ], ////端点白名单 //"EndpointWhitelist": [ "get:/api/license", "*:/api/status" ], ////客户端白名单 //"ClientWhitelist": [ "dev-id-1", "dev-id-2" ], //通用规则 "GeneralRules": [ { //端点路径 "Endpoint": "*", //时间段,格式:{数字}{单位};可使用单位:s, m, h, d "Period": "1s", //限制 "Limit": 2 },   //15分钟只能调用100次 {"Endpoint": "*","Period": "15m","Limit": 100},   //12H只能调用1000 {"Endpoint": "*","Period": "12h","Limit": 1000},   //7天只能调用10000次 {"Endpoint": "*","Period": "7d","Limit": 10000} ] } ``` 配置节点已添加相应注释信息。 规则设置格式:    **端点格式:**`{HTTP_Verb}:{PATH}`,您可以使用asterix符号来定位任何HTTP谓词。 **期间格式:**`{INT}{PERIOD_TYPE}`,您可以使用以下期间类型之一:`s, m, h, d`。 **限制格式:**`{LONG}` **特点Ip限制规则设置,在\**appsettings.json中添加 IP规则配置节点\**** ```json "IpRateLimitPolicies": { //ip规则 "IpRules": [ { //IP "Ip": "84.247.85.224", //规则内容 "Rules": [ //1s请求10次 {"Endpoint": "*","Period": "1s","Limit": 10}, //15分钟请求200次 {"Endpoint": "*","Period": "15m","Limit": 200} ] }, { //ip支持设置多个 "Ip": "192.168.3.22/25", "Rules": [ //1秒请求5次 {"Endpoint": "*","Period": "1s","Limit": 5}, //15分钟请求150次 {"Endpoint": "*","Period": "15m","Limit": 150}, //12小时请求500次 {"Endpoint": "*","Period": "12h","Limit": 500} ] } ] } ``` 为使特点Ip限制规则生效,需初始化 IP 限制策略 `Program.cs` ```c# using (var serviceScope = app.Services.CreateScope()) { var services = serviceScope.ServiceProvider; // get the IpPolicyStore instance var ipPolicyStore = services.GetRequiredService(); // seed IP data from appsettings ipPolicyStore.SeedAsync().GetAwaiter().GetResult(); var clientPolicyStore = services.GetRequiredService(); clientPolicyStore.SeedAsync().GetAwaiter().GetResult(); } ``` ### 运行时更新速率限制 添加 `IpRateLimitController`控制器 ```c# /// /// IP限制控制器 /// [Route("api/[controller]")] [ApiController] public class IpRateLimitController : ControllerBase { private readonly IpRateLimitOptions _options; private readonly IIpPolicyStore _ipPolicyStore; public IpRateLimitController(IOptions optionsAccessor, IIpPolicyStore ipPolicyStore) { _options = optionsAccessor.Value; _ipPolicyStore = ipPolicyStore; } [HttpGet] public IpRateLimitPolicies Get() { return _ipPolicyStore.Get(_options.IpPolicyPrefix); } [HttpPost] public void Post() { var pol = _ipPolicyStore.Get(_options.IpPolicyPrefix); pol.IpRules.Add(new IpRateLimitPolicy { Ip = "8.8.4.4", Rules = new List(new RateLimitRule[] { new RateLimitRule { Endpoint = "*:/api/testupdate", Limit = 100, Period = "1d" } }) }); _ipPolicyStore.Set(_options.IpPolicyPrefix, pol); } } ``` ### 自定义 `IpRateLimitMiddleware`中间件 新建 `CustomIpRateLimitMiddleware`类并继承 `IpRateLimitMiddleware` ```c# public class CustomIpRateLimitMiddleware : IpRateLimitMiddleware { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private readonly IIpRateLimitLogService _ipRateLimitLogService; public CustomIpRateLimitMiddleware(RequestDelegate next, IProcessingStrategy processingStrategy, IOptions options, IIpPolicyStore policyStore, IRateLimitConfiguration config, ILogger logger, IIpRateLimitLogService ipRateLimitLogService) : base(next, processingStrategy, options, policyStore, config, logger) { _ipRateLimitLogService = ipRateLimitLogService; } // 重写 用于记录被阻止的请求的日志 protected override void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule) { // base.LogBlockedRequest(httpContext, identity, counter, rule); var nowDate = DateTime.Now; var ipRateLimitLog = new IpRateLimitLog { HttpVerb = identity.HttpVerb, Path = identity.Path, ClientIp = identity.ClientIp, Limit = rule.Limit, Period = rule.Period, Exceeded = counter.Count - rule.Limit, Endpoint = rule.Endpoint, CreateTime = nowDate }; var logStr = $"请求 {ipRateLimitLog.HttpVerb}:{ipRateLimitLog.Path} 来自 IP {ipRateLimitLog.ClientIp} 已被阻止, " + $"配额 {ipRateLimitLog.Limit}/{ipRateLimitLog.Period} 超出次数 {ipRateLimitLog.Exceeded}. " + $"被规则 {ipRateLimitLog.Endpoint} 阻止. 时间: {ipRateLimitLog.CreateTime}"; Logger.Info(logStr); _ipRateLimitLogService.InsertIpRateLimitLogAsync(ipRateLimitLog); } } ``` # Quartz.Net 定时任务 ## 介绍 在项目的开发过程中,难免会遇见后需要后台处理的任务,例如定时发送邮件通知、后台处理耗时的数据处理等,这个时候你就需要`Quartz.Net`了。 `Quartz.Net`是纯净的,它是一个.Net程序集,是非常流行的Java作业调度系统Quartz的C#实现。 `Quartz.Net`一款功能齐全的任务调度系统,从小型应用到大型企业级系统都能适用。功能齐全体现在触发器的多样性上面,即支持简单的定时器,也支持Cron表达式;即能执行重复的作业任务,也支持指定例外的日历;任务也可以是多样性的,只要继承IJob接口即可。 对于小型应用,`Quartz.Net`可以集成到你的系统中,对于企业级系统,它提供了Routing支持,提供了Group来组织和管理任务 ## 使用 添加Quartz.Net的引用 ```shell Install-Package Quartz -Version 3.7.0 ``` 添加引用以后,来创建一个Job类`HelloQuartzJob` ```c# public class HelloQuartzJob : IJob { public Task Execute(IJobExecutionContext context) { return Task.Factory.StartNew(() => { Console.WriteLine("Hello Quartz.Net"); }); } } ``` 这是个非常简单的Job类,它在执行时输出文本`Hello Quartz.Net`。 接下来,我们在程序启动时创建调度器(Scheduler),并添加HelloQuartzJob的调度: ```c# var schedulerFactory = new StdSchedulerFactory(); var scheduler = await schedulerFactory.GetScheduler(); await scheduler.Start(); Console.WriteLine($"任务调度器已启动"); //创建作业和触发器 var jobDetail = JobBuilder.Create().Build(); var trigger = TriggerBuilder.Create() .WithSimpleSchedule(m => { m.WithRepeatCount(3).WithIntervalInSeconds(1); }) .Build(); //添加调度 await scheduler.ScheduleJob(jobDetail, trigger); ``` 然后运行程序 ```shell 任务调度器已启动 Hello Quartz.Net Hello Quartz.Net Hello Quartz.Net Hello Quartz.Net ``` 通过演示可以看出,要执行一个定时任务,一般需要四步: 1. 创建任务调度器。调度器通常在应用程序启动时创建,一个应用程序实例通常只需要一个调度器即可。 2. 创建Job和JobDetail。Job是作业的类型,描述了作业是如何执行的,这个类是由我们定义的;JobDetail是Quartz对作业的封装,它包含Job类型,以及Job在执行时用到的数据,还包括是否要持久化、是否覆盖已存在的作业等选项。 3. 创建触发器。触发器描述了在何时执行作业。 4. 添加调度。当完成以上三步以后,就可以对作业进行调度了。 # NLog 日志记录 ## 介绍 ## 使用