<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      Hangfire Redis 實(shí)現(xiàn)秒級(jí)定時(shí)任務(wù)、使用 CQRS 實(shí)現(xiàn)動(dòng)態(tài)執(zhí)行代碼

      定時(shí)任務(wù)需求

      本文示例項(xiàng)目倉(cāng)庫(kù):https://github.com/whuanle/HangfireDemo


      主要有兩個(gè)核心需求:

      • 需要實(shí)現(xiàn)秒級(jí)定時(shí)任務(wù);
      • 開發(fā)者使用定時(shí)任務(wù)要簡(jiǎn)單,不要弄復(fù)雜了;

      在微服務(wù)架構(gòu)中中,定時(shí)任務(wù)是最常用的基礎(chǔ)設(shè)施組件之一,社區(qū)中有很多定時(shí)任務(wù)類庫(kù)或平臺(tái),例如 Quartz.NET、xxx-job,使用方法差異很大,比如 xxx-job 的核心是 http 請(qǐng)求,配置定時(shí)任務(wù)實(shí)現(xiàn) http 請(qǐng)求具體的接口,不過用起來還是比較復(fù)雜的。

      在微服務(wù)中,使用的組件太多了,如果每個(gè)組件的集成都搞得很麻煩,那么服務(wù)的代碼很可能會(huì)大量膨脹,并且容易出現(xiàn)各種 bug。以 xxx-job 為例,如果項(xiàng)目中有 N 個(gè)定時(shí)任務(wù),設(shè)計(jì) N 個(gè) http 接口被 xxx-job 回調(diào)觸發(fā),除了 http 接口數(shù)量龐大,在各個(gè)環(huán)節(jié)中還容易出現(xiàn) bug。


      在近期項(xiàng)目需求中,剛好要用到定時(shí)任務(wù),結(jié)合 C# 語言的特性,筆者的方法是利用 Hangfire 框架和語言特性,封裝一些方法,使得開發(fā)者可以無感使用定時(shí)任務(wù),大大簡(jiǎn)化鏈路和使用難度。

      使用示例,結(jié)合 MediatR 框架定義 CQRS ,該 Command 將會(huì)被定時(shí)任務(wù)觸發(fā)執(zhí)行:

      public class MyTestRequest : HangfireRequest, IRequest<ExecteTasResult>
      {
      }
      
      /// <summary>
      /// 要被定時(shí)任務(wù)執(zhí)行的代碼.
      /// </summary>
      public class MyTestHandler : IRequestHandler<MyTestRequest, ExecteTasResult>
      {
          public async Task<ExecteTasResult> Handle(MyTestRequest request, CancellationToken cancellationToken)
          {
              // 邏輯
              
              return new ExecteTasResult
              {
                  CancelTask = false
              };
          }
      }
      

      要啟動(dòng)一個(gè)定時(shí)任務(wù),只需要:

      private readonly SendHangfireService _hangfireService;
      
      public SendTaskController(SendHangfireService hangfireService)
      {
      	_hangfireService = hangfireService;
      }
      
      [HttpGet("aaa")]
      public async Task<string> SendAsync()
      {
      	await _hangfireService.Send(new MyTestRequest
      	{
      		CreateTime = DateTimeOffset.Now,
      		CronExpression = "* * * * * *",
      		TaskId = Guid.NewGuid().ToString(),
      	});
      
      	return "aaa";
      }
      

      通過這種方式使用定時(shí)任務(wù),開發(fā)者只需要使用很簡(jiǎn)單的代碼即可完成需求,不需要關(guān)注細(xì)節(jié),也不需要定義各種 http 接口,并且猶豫不需要關(guān)注使用的外部定時(shí)任務(wù)框架,所以隨時(shí)可以切換不同的定時(shí)任務(wù)實(shí)現(xiàn)。

      核心邏輯

      本文示例項(xiàng)目倉(cāng)庫(kù):whuanle/HangfireDemo


      示例項(xiàng)目結(jié)構(gòu)如下:

      image-20250418084329362


      HangfireServer 是定時(shí)任務(wù)服務(wù)實(shí)現(xiàn),HangfireServer 服務(wù)只需要暴露兩個(gè)接口 addtask、cancel,分別用于添加定時(shí)任務(wù)和取消定時(shí)任務(wù),無論什么業(yè)務(wù)的服務(wù),都通過 addtask 服務(wù)添加。


      DemoApi 則是業(yè)務(wù)服務(wù),業(yè)務(wù)服務(wù)只需要暴露一個(gè)· execute 接口用于觸發(fā)定時(shí)任務(wù)即可。


      基礎(chǔ)邏輯如下:


      graph LR subgraph DemoApi A[定義 Command] -- 序列化參數(shù)Command --> AA[發(fā)送定時(shí)任務(wù)] E[DemoApi:execute 接口] --> F[DemoApi:執(zhí)行 Command] end subgraph Hangfire B[addtask] --> C[Hangfire:存儲(chǔ)任務(wù)] C --> D[Hangfire:執(zhí)行任務(wù)] D --> DD[發(fā)起請(qǐng)求] end %% 同時(shí)建立必要的連接 AA -- 添加定時(shí)任務(wù) --> B DD -- 請(qǐng)求 --> E

      由于項(xiàng)目中使用的是 MediatR 框架實(shí)現(xiàn) CQRS 模式,因此很容易實(shí)現(xiàn)定時(shí)任務(wù)動(dòng)態(tài)調(diào)用代碼,只需要按照平時(shí)的 CQRS 發(fā)送定時(shí)任務(wù)命令,指定定時(shí)任務(wù)要執(zhí)行的 Command 即可。

      例如,有以下 Command 需要被定時(shí)任務(wù)執(zhí)行:

      ACommand
      BCommand
      CCommand
      


      首先這些命令會(huì)被序列化為 json ,發(fā)送到 HangfireServer 服務(wù),HangfireServer 在恰當(dāng)時(shí)機(jī)將參數(shù)原封不動(dòng)推送到 DemoApi 服務(wù),DemoApi 服務(wù)拿到這些參數(shù)序列化為對(duì)應(yīng)的類型,然后通過 MediatR 發(fā)送命令,即可實(shí)現(xiàn)任意命令的定時(shí)任務(wù)動(dòng)態(tài)調(diào)用。


      下面來分別實(shí)現(xiàn) HangfireServer 、DemoApi 服務(wù)。

      在 Shred 項(xiàng)目中添加以下文件。

      image-20250418093956062


      其中 TaskRequest 內(nèi)容如下,其它文件請(qǐng)參考示例項(xiàng)目。

      public class TaskRequest
      {
          /// <summary>
          /// 任務(wù) id.
          /// </summary>
          public string TaskId { get; set; } = "";
      
          /// <summary>
          /// 定時(shí)任務(wù)要請(qǐng)求的服務(wù)地址或服務(wù)名稱.
          /// </summary>
          public string ServiceName { get; set; } = "";
      
          /// <summary>
          /// 參數(shù)類型名稱.
          /// </summary>
          public string CommandType { get; set; } = "";
      
          /// <summary>
          /// 請(qǐng)求參數(shù)內(nèi)容,json 序列化后的字符串.
          /// </summary>
          public string CommandBody { get; set; } = "";
      
          /// <summary>
          /// Cron 表達(dá)式.
          /// </summary>
          public string CronExpression { get; set; } = "";
      
          /// <summary>
          /// 創(chuàng)建時(shí)間.
          /// </summary>
          public string CreateTime { get; set; } = "";
      }
      

      使用 Redis 實(shí)現(xiàn)秒級(jí)定時(shí)任務(wù)

      Hangfire 本身配置比較復(fù)雜,其分布式實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)性能要求比較高,因此使用 Mysql、Sqlserver 等數(shù)據(jù)庫(kù)存儲(chǔ)數(shù)據(jù)會(huì)帶了很大的壓力,而且要求實(shí)現(xiàn)秒級(jí)定時(shí)任務(wù),NoSql 數(shù)據(jù)庫(kù)可以更加好地實(shí)現(xiàn)這一需求,筆者這里使用 Redis 來存儲(chǔ)任務(wù)數(shù)據(jù)。

      HangfireServer 項(xiàng)目結(jié)構(gòu)如下:

      image-20250418094109409


      對(duì) HangfireServer 的設(shè)計(jì)主要分為幾步:

      • Hangfire 支持容器管理;
      • 配置 Hangfire ;
      • 定義 RecurringJobHandler 執(zhí)行任務(wù)發(fā)起 http 請(qǐng)求到業(yè)務(wù)系統(tǒng);
      • 定義 http 接口,接收定時(shí)任務(wù);


      引入類庫(kù):

      <PackageReference Include="Hangfire.AspNetCore" Version="1.8.18" />
      <PackageReference Include="Hangfire.Redis.StackExchange" Version="1.12.0" />
      

      首先是關(guān)于 Hangfire 本身的配置,現(xiàn)在幾乎都是基于依賴注入的設(shè)計(jì),不搞靜態(tài)類型,所以我們需要實(shí)現(xiàn)定時(shí)任務(wù)執(zhí)行器創(chuàng)建服務(wù)實(shí)例的,以便每次定時(shí)任務(wù)請(qǐng)求時(shí),服務(wù)實(shí)例都是在一個(gè)新的容器,處以一個(gè)新的上下文中。

      第一步

      創(chuàng)建 HangfireJobActivatorScope、HangfireActivator 兩個(gè)文件,實(shí)現(xiàn) Hangfire 支持容器上下文。


      /// <summary>
      /// 任務(wù)容器.
      /// </summary>
      public class HangfireJobActivatorScope : JobActivatorScope
      {
          private readonly IServiceScope _serviceScope;
          private readonly string _jobId;
      
          /// <summary>
          /// Initializes a new instance of the <see cref="HangfireJobActivatorScope"/> class.
          /// </summary>
          /// <param name="serviceScope"></param>
          /// <param name="jobId"></param>
          public HangfireJobActivatorScope([NotNull] IServiceScope serviceScope, string jobId)
          {
              _serviceScope = serviceScope ?? throw new ArgumentNullException(nameof(serviceScope));
              _jobId = jobId;
          }
      
          /// <inheritdoc/>
          public override object Resolve(Type type)
          {
              var res = ActivatorUtilities.GetServiceOrCreateInstance(_serviceScope.ServiceProvider, type);
              return res;
          }
      
          /// <inheritdoc/>
          public override void DisposeScope()
          {
              _serviceScope.Dispose();
          }
      }
      

      /// <summary>
      /// JobActivator.
      /// </summary>
      public class HangfireActivator : JobActivator
      {
          private readonly IServiceScopeFactory _serviceScopeFactory;
      
          /// <summary>
          /// Initializes a new instance of the <see cref="HangfireActivator"/> class.
          /// </summary>
          /// <param name="serviceScopeFactory"></param>
          public HangfireActivator(IServiceScopeFactory serviceScopeFactory)
          {
              _serviceScopeFactory = serviceScopeFactory ?? throw new ArgumentNullException(nameof(serviceScopeFactory));
          }
      
          /// <inheritdoc/>
          public override JobActivatorScope BeginScope(JobActivatorContext context)
          {
              return new HangfireJobActivatorScope(_serviceScopeFactory.CreateScope(), context.BackgroundJob.Id);
          }
      }
      

      第二步

      配置 Hangfire 服務(wù),使其支持 Redis,并且配置一些參數(shù)。

      private void ConfigureHangfire(IServiceCollection services)
      {
      	var options =
      		new RedisStorageOptions
      		{
                  // 配置 redis 前綴,每個(gè)任務(wù)實(shí)例都會(huì)創(chuàng)建一個(gè) key
      			Prefix = "aaa:aaa:hangfire",
      		};
      
      	services.AddHangfire(
      		config =>
      		{
      			config.UseRedisStorage("{redis連接字符串}", options)
      			.SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
      			.UseSimpleAssemblyNameTypeSerializer()
      			.UseRecommendedSerializerSettings();
      			config.UseActivator(new HangfireActivator(services.BuildServiceProvider().GetRequiredService<IServiceScopeFactory>()));
      		});
      
      	services.AddHangfireServer(options =>
      	{
              // 注意,這里必須設(shè)置非常小的間隔
      		options.SchedulePollingInterval = TimeSpan.FromSeconds(1);
              
              // 如果考慮到后續(xù)任務(wù)比較多,則需要調(diào)大此參數(shù)
      		options.WorkerCount = 50;
      	});
      }
      

      1744940846417

      第三步

      實(shí)現(xiàn) RecurringJobHandler 執(zhí)行定時(shí)任務(wù),發(fā)起 http 請(qǐng)求業(yè)務(wù)系統(tǒng)。

      被調(diào)用方要返回 TaskInterfaceResponse 類型,主要考慮如果被調(diào)用方后續(xù)不需要在繼續(xù)此定時(shí)任務(wù),那么返回參數(shù) CancelTask = tre 時(shí),定時(shí)任務(wù)服務(wù)直接取消后續(xù)的任務(wù)即可,不需要被調(diào)用方手動(dòng)調(diào)用接口取消。

      public class RecurringJobHandler
      {
          private readonly IServiceProvider _serviceProvider;
          public RecurringJobHandler(IServiceProvider serviceProvider)
          {
              _serviceProvider = serviceProvider;
          }
      
          /// <summary>
          /// 執(zhí)行任務(wù).
          /// </summary>
          /// <param name="taskRequest"></param>
          /// <returns>Task.</returns>
          public async Task Handler(TaskRequest taskRequest)
          {
              var ioc = _serviceProvider;
      
              var recurringJobManager = ioc.GetRequiredService<IRecurringJobManager>();
              var httpClientFactory = ioc.GetRequiredService<IHttpClientFactory>();
              var logger = ioc.GetRequiredService<ILogger<RecurringJobHandler>>();
              using var httpClient = httpClientFactory.CreateClient(taskRequest.ServiceName);
      
              // 無論是否請(qǐng)求成功,都算完成了本次任務(wù)
              try
              {
                  // 請(qǐng)求子系統(tǒng)的接口
                  var response = await httpClient.PostAsJsonAsync(taskRequest.ServiceName, taskRequest);
      
                  var execteResult = await response.Content.ReadFromJsonAsync<ExecteTasResult>();
      
                  // 被調(diào)用方要求取消任務(wù)
                  if (execteResult != null && execteResult.CancelTask)
                  {
                      recurringJobManager.RemoveIfExists(taskRequest.TaskId);
                  }
              }
              catch (Exception ex)
              {
                  logger.LogError(ex, "Task error.");
              }
          }
      }
      

      第四步

      配置好 Hangfire 后,開始考慮如何接收任務(wù)和發(fā)起請(qǐng)求,首先定義一個(gè) Http 接口或 grpc 接口。

      [ApiController]
      [Route("/execute")]
      public class HangfireController : ControllerBase
      {
          private readonly IRecurringJobManager _recurringJobManager;
      
          public HangfireController(IRecurringJobManager recurringJobManager)
          {
              _recurringJobManager = recurringJobManager;
          }
      
          [HttpPost("addtask")]
          public async Task<TaskResponse> AddTask(TaskRequest value)
          {
              await Task.CompletedTask;
              _recurringJobManager.AddOrUpdate<RecurringJobHandler>(
                  value.TaskId,
                  task => task.Handler(value),
                  cronExpression: value.CronExpression,
                  options: new RecurringJobOptions
                  {
                  });
              return new TaskResponse {  };
          }
      
          [HttpPost("cancel")]
          public async Task<TaskResponse> Cancel(CancelTaskRequest value)
          {
              await Task.CompletedTask;
              _recurringJobManager.RemoveIfExists(value.TaskId);
      
              return new TaskResponse
              {
              };
          }
      }
      
      

      業(yè)務(wù)服務(wù)實(shí)現(xiàn)動(dòng)態(tài)代碼

      業(yè)務(wù)服務(wù)只需要暴露一個(gè) exceute 接口給 HangfireServer 即可,DemoApi 將 Command 序列化包裝為請(qǐng)求參數(shù)給 HangfireServer ,然后 HangfireServer 原封不動(dòng)地將參數(shù)請(qǐng)求到 exceute 接口。

      image-20250418095553964


      對(duì) DemoApi 主要設(shè)計(jì)過程如下:

      • 定義 SendHangfireService 服務(wù),包裝 Command 數(shù)據(jù)和一些定時(shí)任務(wù)參數(shù),通過 http 發(fā)送到 HangfireServer 中;
      • 定義 ExecuteTaskHandler ,當(dāng)接口被觸發(fā)時(shí),實(shí)現(xiàn)反序列化參數(shù)并使用 MediatR 發(fā)送 Command,實(shí)現(xiàn)動(dòng)態(tài)執(zhí)行;
      • 定義 ExecuteController 接口,接收 HangfireServer 請(qǐng)求,并調(diào)用 ExecuteTaskHandler 處理請(qǐng)求;

      DemoApi 引入類庫(kù)如下-:

      <PackageReference Include="Maomi.Core" Version="2.2.0" />
      <PackageReference Include="MediatR" Version="12.5.0" />
      

      Maomi.Core 是一個(gè)模塊化和自動(dòng)服務(wù)注冊(cè)框架。


      第一步

      定義 SendHangfireService 服務(wù),包裝 Command 數(shù)據(jù)和一些定時(shí)任務(wù)參數(shù),通過 http 發(fā)送到 HangfireServer 中。

      接收 HangfireServer 請(qǐng)求時(shí),需要通過字符串查找出 Type,這就需要 DemoApi 啟動(dòng)時(shí),自動(dòng)掃描程序集并將對(duì)應(yīng)的類型緩存起來。

      為了將定時(shí)任務(wù)命令和其它 Command 區(qū)分處理,需要定義一個(gè)統(tǒng)一的抽象,當(dāng)然也可以不這樣做,也可以通過特性注解的方式做處理。

      /// <summary>
      /// 定時(shí)任務(wù)抽象參數(shù).
      /// </summary>
      public abstract class HangfireRequest : IRequest<HangfireResponse>
      {
          /// <summary>
          /// 定時(shí)任務(wù) id.
          /// </summary>
          public string TaskId { get; init; } = string.Empty;
      
          /// <summary>
          /// 該任務(wù)創(chuàng)建時(shí)間.
          /// </summary>
          public DateTimeOffset CreateTime { get; init; }
      }
      

      定義 HangireTypeFactory ,以便能夠通過字符串快速查找 Type。

      /// <summary>
      /// 記錄 CQRS 中的命令類型,以便能夠通過字符串快速查找 Type.
      /// </summary>
      public class HangireTypeFactory
      {
          private readonly ConcurrentDictionary<string, Type> _typeDictionary;
          public HangireTypeFactory()
          {
              _typeDictionary = new ConcurrentDictionary<string, Type>();
          }
      
          public void Add(Type type)
          {
              if (!_typeDictionary.ContainsKey(type.Name))
              {
                  _typeDictionary[type.Name] = type;
              }
          }
      
          public Type? Get(string typeName)
          {
              if (_typeDictionary.TryGetValue(typeName, out var type))
              {
                  return type;
              }
      
              return _typeDictionary.FirstOrDefault(x => x.Value.FullName == typeName).Value;
          }
      }
      

      最后實(shí)現(xiàn) SendHangfireService 服務(wù),能夠包裝參數(shù)發(fā)送到 HangfireServer 中。

      當(dāng)然,可以使用 CQRS 處理。

      /// <summary>
      /// 定時(shí)任務(wù)服務(wù),用于發(fā)送定時(shí)任務(wù)請(qǐng)求.
      /// </summary>
      [InjectOnScoped]
      public class SendHangfireService
      {
          private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions
          {
              AllowTrailingCommas = true,
              PropertyNameCaseInsensitive = true,
              PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
              ReadCommentHandling = JsonCommentHandling.Skip
          };
      
          private readonly IHttpClientFactory _httpClientFactory;
      
          public SendHangfireService(IHttpClientFactory httpClientFactory)
          {
              _httpClientFactory = httpClientFactory;
          }
      
          /// <summary>
          /// 發(fā)送定時(shí)任務(wù)請(qǐng)求.
          /// </summary>
          /// <typeparam name="TCommand"></typeparam>
          /// <param name="request"></param>
          /// <param name="cancellationToken"></param>
          /// <returns></returns>
          /// <exception cref="TypeLoadException"></exception>
          public async Task Send<TCommand>(TCommand request)
              where TCommand : HangfireRequest
          {
              using var httpClient = _httpClientFactory.CreateClient();
      
              var taskRequest = new TaskRequest
              {
                  TaskId = request.TaskId,
                  CommandBody = JsonSerializer.Serialize(request, JsonOptions),
                  ServiceName = "http://127.0.0.1:5000/hangfire/execute",
                  CommandType = typeof(TCommand).Name ?? throw new TypeLoadException(typeof(TCommand).Name),
                  CreateTime = request.CreateTime.ToUnixTimeMilliseconds().ToString(),
                  CronExpression = request.CronExpression,
              };
      
              _ = await httpClient.PostAsJsonAsync("http://127.0.0.1:5001/execute/addtask", taskRequest);
          }
      
          /// <summary>
          /// 取消定時(shí)任務(wù).
          /// </summary>
          /// <param name="taskId"></param>
          /// <returns></returns>
          public async Task Cancel(string taskId)
          {
              using var httpClient = _httpClientFactory.CreateClient();
              _ = await httpClient.PostAsJsonAsync("http://127.0.0.1:5001/hangfire/cancel", new CancelTaskRequest
              {
                  TaskId = taskId
              });
      
          }
      }
      

      第二步

      要實(shí)現(xiàn)通過 Type 動(dòng)態(tài)執(zhí)行某個(gè) Command ,其實(shí)思路比較簡(jiǎn)單,也并不需要表達(dá)式樹等麻煩的方式。

      筆者的實(shí)現(xiàn)思路如下,定義 ExecuteTaskHandler 泛型類,直接以強(qiáng)類型的方式觸發(fā) Command,但是為了屏蔽泛型類型強(qiáng)類型在代碼調(diào)用中的麻煩,需要再抽象一個(gè)接口 IHangfireTaskHandler 屏蔽泛型。

      /// <summary>
      /// 定義執(zhí)行任務(wù)的抽象,便于忽略泛型處理.
      /// </summary>
      public interface IHangfireTaskHandler
      {
          /// <summary>
          /// 執(zhí)行任務(wù).
          /// </summary>
          /// <param name="taskRequest"></param>
          /// <returns></returns>
          Task<ExecteTasResult> Handler(TaskRequest taskRequest);
      }
      
      /// <summary>
      /// 用于反序列化參數(shù)并發(fā)送 Command.
      /// </summary>
      /// <typeparam name="TCommand">命令.</typeparam>
      public class ExecuteTaskHandler<TCommand> : IHangfireTaskHandler
          where TCommand : HangfireRequest, IRequest<ExecteTasResult>
      {
          private readonly IMediator _mediator;
      
          /// <summary>
          /// Initializes a new instance of the <see cref="ExecuteTaskHandler{TCommand}"/> class.
          /// </summary>
          /// <param name="mediator"></param>
          public ExecuteTaskHandler(IMediator mediator)
          {
              _mediator = mediator;
          }
      
          private static readonly JsonSerializerOptions JsonSerializerOptions = new JsonSerializerOptions
          {
              AllowTrailingCommas = true,
              PropertyNameCaseInsensitive = true,
              PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
              ReadCommentHandling = JsonCommentHandling.Skip
          };
      
          /// <inheritdoc/>
          public async Task<ExecteTasResult> Handler(TaskRequest taskRequest)
          {
              var command = JsonSerializer.Deserialize<TCommand>(taskRequest.CommandBody, JsonSerializerOptions)!;
              if (command == null)
              {
                  throw new Exception("解析命令參數(shù)失敗");
              }
      
              // 處理命令的邏輯
              var response = await _mediator.Send(command);
              return response;
          }
      }
      

      第三步

      實(shí)現(xiàn)定時(shí)任務(wù) execute 觸發(fā)接口,然后將參數(shù)轉(zhuǎn)發(fā)到 ExecuteTaskHandler 中,這里通過依賴注入的方式屏蔽和解決強(qiáng)類型的問題。

      /// <summary>
      /// 定時(shí)任務(wù)觸發(fā)入口.
      /// </summary>
      [ApiController]
      [Route("/hangfire")]
      public class ExecuteController : ControllerBase
      {
          private readonly IServiceProvider _serviceProvider;
          private readonly HangireTypeFactory _hangireTypeFactory;
      
          public ExecuteController(IServiceProvider serviceProvider, HangireTypeFactory hangireTypeFactory)
          {
              _serviceProvider = serviceProvider;
              _hangireTypeFactory = hangireTypeFactory;
          }
      
          [HttpPost("execute")]
          public async Task<ExecteTasResult> ExecuteTask([FromBody] TaskRequest request)
          {
              var commandType = _hangireTypeFactory.Get(request.CommandType);
      
              // 找不到該事件類型,取消后續(xù)事件執(zhí)行
              if (commandType == null)
              {
                  return new ExecteTasResult
                  {
                      CancelTask = true
                  };
              }
      
              var commandTypeHandler = typeof(ExecuteTaskHandler<>).MakeGenericType(commandType);
      
              var handler = _serviceProvider.GetService(commandTypeHandler) as IHangfireTaskHandler;
              if(handler == null)
              {
                  return new ExecteTasResult
                  {
                      CancelTask = true
                  };
              }
      
              return await handler.Handler(request);
          }
      }
      

      第四步

      封裝好代碼后,開始最后一個(gè)環(huán)境,配置和注冊(cè)服務(wù),由于筆者使用 Maomi.Core 框架,因此服務(wù)注冊(cè)配置和掃描程序集變得非常簡(jiǎn)單,只需要通過 Maomi.Core 框架提供的接口即可最簡(jiǎn)單地實(shí)現(xiàn)功能。

      public class ApiModule : Maomi.ModuleCore, IModule
      {
          private readonly HangireTypeFactory _hangireTypeFactory;
      
          public ApiModule()
          {
              _hangireTypeFactory = new HangireTypeFactory();
          }
      
          public override void ConfigureServices(ServiceContext context)
          {
              context.Services.AddTransient(typeof(ExecuteTaskHandler<>));
              context.Services.AddSingleton(_hangireTypeFactory);
              context.Services.AddHttpClient();
              context.Services.AddMediatR(o =>
              {
                  o.RegisterServicesFromAssemblies(context.Modules.Select(x => x.Assembly).ToArray());
              });
          }
      
          public override void TypeFilter(Type type)
          {
              if (!type.IsClass || type.IsAbstract)
              {
                  return;
              }
      
              if (type.IsAssignableTo(typeof(HangfireRequest)))
              {
                  _hangireTypeFactory.Add(type);
              }
          }
      }
      

      1744942983410

      第五步

      開發(fā)者可以這樣寫定時(shí)任務(wù) Command 以及執(zhí)行器,然后通過接口觸發(fā)定時(shí)任務(wù)。

      public class MyTestRequest : HangfireRequest, IRequest<ExecteTasResult>
      {
      }
      
      
      /// <summary>
      /// 要被定時(shí)任務(wù)執(zhí)行的代碼.
      /// </summary>
      public class MyTestHandler : IRequestHandler<MyTestRequest, ExecteTasResult>
      {
          private static volatile int _count;
          private static DateTimeOffset _lastTime;
      
          public async Task<ExecteTasResult> Handle(MyTestRequest request, CancellationToken cancellationToken)
          {
              _count++;
              if (_lastTime == default)
              {
                  _lastTime = DateTimeOffset.Now;
              }
      
              Console.WriteLine($"""
                  執(zhí)行時(shí)間:{DateTimeOffset.Now.ToString("HH:mm:ss.ffff")}
                  執(zhí)行頻率(每 10s):{(_count / (DateTimeOffset.Now - _lastTime).TotalSeconds * 10)}
                  """);
      
              return new ExecteTasResult
              {
                  CancelTask = false
              };
          }
      }
      
      [ApiController]
      [Route("/test")]
      public class SendTaskController : ControllerBase
      {
          private readonly SendHangfireService _hangfireService;
      
          public SendTaskController(SendHangfireService hangfireService)
          {
              _hangfireService = hangfireService;
          }
      
          [HttpGet("aaa")]
          public async Task<string> SendAsync()
          {
              await _hangfireService.Send(new MyTestRequest
              {
                  CreateTime = DateTimeOffset.Now,
                  CronExpression = "* * * * * *",
                  TaskId = Guid.NewGuid().ToString(),
              });
      
              return "aaa";
          }
      }
      
      

      最后

      啟動(dòng)項(xiàng)目測(cè)試代碼,記錄執(zhí)行頻率和時(shí)間間隔。

      image-20250418103509714

      動(dòng)畫

      posted @ 2025-04-18 11:08  癡者工良  閱讀(2261)  評(píng)論(5)    收藏  舉報(bào)
      主站蜘蛛池模板: 中文人妻av高清一区二区| 日韩精品三区二区三区| 国产一区二区三区小说| 国内揄拍国内精品对久久| 日韩伦理片| 日韩V欧美V中文在线| 嫩草成人AV影院在线观看| 国产成人一区二区免av| 男女真人国产牲交a做片野外| 欧美视频精品免费覌看| 99精品国产一区二区三区2021| 国产午夜精品福利免费不| 清水河县| 中文字幕少妇人妻精品| 成人国产精品日本在线观看| 国产av一区二区不卡| 中文字幕乱偷无码av先锋蜜桃| 亚洲精品国产aⅴ成拍色拍| 亚洲av无码国产在丝袜线观看| 欧洲女人牲交性开放视频| 国产蜜臀在线一区二区三区| 日韩激情一区二区三区| 老司机亚洲精品一区二区| av中文字幕国产精品| 国产成人午夜福利院| 亚洲天堂av在线免费看| 久久国产精品夜色| 国产精品人成视频免费播放| 成人精品一区二区三区在线观看| 亚欧成人精品一区二区乱| 三门峡市| 亚洲一区成人av在线| 亚洲香蕉av一区二区蜜桃| 免费观看在线A级毛片| 日本高清视频网站www| 国产人妻人伦精品1国产丝袜| 国产老熟女视频一区二区| 大香伊蕉在人线国产最新2005 | 拜城县| 国产国产午夜福利视频| 国产99视频精品免费专区|