feat: finalize core modules and gateway

This commit is contained in:
2025-11-23 18:53:12 +08:00
parent 429d4fb747
commit ae273e510a
115 changed files with 4695 additions and 223 deletions

View File

@@ -0,0 +1,15 @@
using System.Threading;
using System.Threading.Tasks;
namespace TakeoutSaaS.Module.Scheduler.Abstractions;
/// <summary>
/// 周期性任务注册抽象。
/// </summary>
public interface IRecurringJobRegistrar
{
/// <summary>
/// 注册所有预设的周期性任务。
/// </summary>
Task RegisterAsync(CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,68 @@
using Hangfire;
using Hangfire.PostgreSql;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using TakeoutSaaS.Module.Scheduler.Abstractions;
using TakeoutSaaS.Module.Scheduler.HostedServices;
using TakeoutSaaS.Module.Scheduler.Jobs;
using TakeoutSaaS.Module.Scheduler.Options;
using TakeoutSaaS.Module.Scheduler.Services;
namespace TakeoutSaaS.Module.Scheduler.Extensions;
/// <summary>
/// 调度模块注册扩展(默认 Hangfire
/// </summary>
public static class SchedulerServiceCollectionExtensions
{
/// <summary>
/// 注册调度模块。
/// </summary>
public static IServiceCollection AddSchedulerModule(this IServiceCollection services, IConfiguration configuration)
{
services.AddOptions<SchedulerOptions>()
.Bind(configuration.GetSection("Scheduler"))
.ValidateDataAnnotations()
.ValidateOnStart();
services.AddHangfire((serviceProvider, config) =>
{
var options = serviceProvider.GetRequiredService<IOptionsMonitor<SchedulerOptions>>().CurrentValue;
config
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UsePostgreSqlStorage(options.ConnectionString);
});
services.AddHangfireServer((serviceProvider, options) =>
{
var scheduler = serviceProvider.GetRequiredService<IOptionsMonitor<SchedulerOptions>>().CurrentValue;
options.WorkerCount = scheduler.WorkerCount ?? options.WorkerCount;
});
services.AddSingleton<IRecurringJobRegistrar, RecurringJobRegistrar>();
services.AddHostedService<RecurringJobHostedService>();
services.AddScoped<OrderTimeoutJob>();
services.AddScoped<CouponExpireJob>();
services.AddScoped<LogCleanupJob>();
return services;
}
/// <summary>
/// 启用 Hangfire Dashboard默认关闭可通过配置开启
/// </summary>
public static IApplicationBuilder UseSchedulerDashboard(this IApplicationBuilder app, IConfiguration configuration)
{
var options = configuration.GetSection("Scheduler").Get<SchedulerOptions>();
if (options is { DashboardEnabled: true })
{
app.UseHangfireDashboard(options.DashboardPath);
}
return app;
}
}

View File

@@ -0,0 +1,21 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using TakeoutSaaS.Module.Scheduler.Abstractions;
namespace TakeoutSaaS.Module.Scheduler.HostedServices;
/// <summary>
/// 启动时注册周期性任务的宿主服务。
/// </summary>
public sealed class RecurringJobHostedService(IRecurringJobRegistrar registrar, ILogger<RecurringJobHostedService> logger) : IHostedService
{
/// <inheritdoc />
public async Task StartAsync(CancellationToken cancellationToken)
{
await registrar.RegisterAsync(cancellationToken).ConfigureAwait(false);
logger.LogInformation("调度任务已注册");
}
/// <inheritdoc />
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}

View File

@@ -0,0 +1,18 @@
using Microsoft.Extensions.Logging;
namespace TakeoutSaaS.Module.Scheduler.Jobs;
/// <summary>
/// 优惠券过期处理任务(占位实现)。
/// </summary>
public sealed class CouponExpireJob(ILogger<CouponExpireJob> logger)
{
/// <summary>
/// 执行优惠券过期清理。
/// </summary>
public Task ExecuteAsync()
{
logger.LogInformation("定时任务:处理已过期优惠券(占位实现)");
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,18 @@
using Microsoft.Extensions.Logging;
namespace TakeoutSaaS.Module.Scheduler.Jobs;
/// <summary>
/// 日志清理任务(占位实现)。
/// </summary>
public sealed class LogCleanupJob(ILogger<LogCleanupJob> logger)
{
/// <summary>
/// 执行日志清理。
/// </summary>
public Task ExecuteAsync()
{
logger.LogInformation("定时任务:清理历史日志(占位实现)");
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,18 @@
using Microsoft.Extensions.Logging;
namespace TakeoutSaaS.Module.Scheduler.Jobs;
/// <summary>
/// 订单超时取消任务(占位,后续接入订单服务)。
/// </summary>
public sealed class OrderTimeoutJob(ILogger<OrderTimeoutJob> logger)
{
/// <summary>
/// 执行超时订单检查。
/// </summary>
public Task ExecuteAsync()
{
logger.LogInformation("定时任务:检查超时未支付订单并取消(占位实现)");
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,31 @@
using System.ComponentModel.DataAnnotations;
namespace TakeoutSaaS.Module.Scheduler.Options;
/// <summary>
/// 调度模块配置。
/// </summary>
public sealed class SchedulerOptions
{
/// <summary>
/// Hangfire 存储使用的连接字符串。
/// </summary>
[Required]
public string ConnectionString { get; set; } = string.Empty;
/// <summary>
/// 工作线程数,默认根据 CPU 计算。
/// </summary>
[Range(1, 100)]
public int? WorkerCount { get; set; }
/// <summary>
/// 是否启用 Dashboard默认 false待 AdminUI 接入)。
/// </summary>
public bool DashboardEnabled { get; set; }
/// <summary>
/// Dashboard 路径。
/// </summary>
public string DashboardPath { get; set; } = "/hangfire";
}

View File

@@ -0,0 +1,37 @@
using Hangfire;
using TakeoutSaaS.Module.Scheduler.Abstractions;
using TakeoutSaaS.Module.Scheduler.Jobs;
namespace TakeoutSaaS.Module.Scheduler.Services;
/// <summary>
/// 周期性任务注册器。
/// </summary>
public sealed class RecurringJobRegistrar : IRecurringJobRegistrar
{
private readonly OrderTimeoutJob _orderTimeoutJob;
private readonly CouponExpireJob _couponExpireJob;
private readonly LogCleanupJob _logCleanupJob;
/// <summary>
/// 初始化注册器。
/// </summary>
public RecurringJobRegistrar(
OrderTimeoutJob orderTimeoutJob,
CouponExpireJob couponExpireJob,
LogCleanupJob logCleanupJob)
{
_orderTimeoutJob = orderTimeoutJob;
_couponExpireJob = couponExpireJob;
_logCleanupJob = logCleanupJob;
}
/// <inheritdoc />
public Task RegisterAsync(CancellationToken cancellationToken = default)
{
RecurringJob.AddOrUpdate("orders.timeout-cancel", () => _orderTimeoutJob.ExecuteAsync(), "*/5 * * * *");
RecurringJob.AddOrUpdate("coupons.expire", () => _couponExpireJob.ExecuteAsync(), "0 */1 * * *");
RecurringJob.AddOrUpdate("logs.cleanup", () => _logCleanupJob.ExecuteAsync(), "0 3 * * *");
return Task.CompletedTask;
}
}

View File

@@ -4,8 +4,16 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.14" />
<PackageReference Include="Hangfire.PostgreSql" Version="1.20.12" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.DataAnnotations" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Core\TakeoutSaaS.Shared.Abstractions\TakeoutSaaS.Shared.Abstractions.csproj" />
</ItemGroup>
</Project>