模型注册

This commit is contained in:
chfei committed 2026-09-18 19:17:11 +08:00
1 parent 75d0e85218
commit 0e1620c1f9
11 files changed
+399 -41

No files matched your search

+89
View File
@@ -0,0 +1,89 @@
using Hangfire;
using Hangfire.MySql;
using HangfireBasicAuthenticationFilter;
using MAX.Core.Interfaces;
using MAX.Framework.Interfaces;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using MySql.Data.MySqlClient;
using Store.Modules.CloudProduct.Services;
namespace Store.Modules.CloudProduct
{
/// <summary>
/// CloudProduct 模块的模块化入口(<see cref="IModule"/>):Hangfire 后台任务(商品分词同步)+ Hangfire 看板。
/// 与 <see cref="Startup"/>(只做 IStartup 的 Initialize/Register)分开,是因为模块注册需要配置和 app:
/// <list type="bullet">
/// <item><c>builder.AddStoreHub()</c> 执行 <see cref="AddModule"/>(注册 Hangfire 服务,此时只能拿到容器和配置);</item>
/// <item><c>app.UseStoreHubModules()</c> 执行 <see cref="UseModule"/>(挂看板中间件 + 注册定时任务,此时才有 app)。</item>
/// </list>
/// </summary>
public class CloudProductModule : ModuleBase
{
public override int Priority => IntPriority.Normal;
/// <summary>
/// 服务注册阶段(builder.Build() 之前):这里只能拿到容器和配置,碰不到 app
/// </summary>
public override void AddModule(ModuleContext context)
{
IServiceCollection services = context.Services;
IConfiguration configuration = context.Configuration;
// ===== Hangfire 后台定时任务(商品分词同步) =====
var hangfireConnection = configuration.GetConnectionString("DefaultConnection")
?? throw new InvalidOperationException("缺少配置 ConnectionStrings:DefaultConnection,CloudProduct 模块的 Hangfire 无法启动。");
// AllowUserVariables=true:Hangfire.MySqlStorage 的建表/查询语句依赖用户变量
var connectionStringBuilder = new MySqlConnectionStringBuilder(hangfireConnection)
{
AllowUserVariables = true
};
services.AddHangfire(config => config
.SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UseStorage(new MySqlStorage(connectionStringBuilder.ConnectionString, new MySqlStorageOptions() { TablesPrefix = "Hangfire_" })));
services.AddHangfireServer();
services.AddTransient<GoodsIndexJob>();
}
/// <summary>
/// 管道装配阶段(builder.Build() 之后、app.Run() 之前):这里可以挂中间件、解析容器里的服务
/// </summary>
public override void UseModule(WebApplication app)
{
try
{
// Hangfire 后台任务看板(默认地址 /hangfire)
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
Authorization = new[]
{
new HangfireCustomBasicAuthenticationFilter(){
User = app.Configuration.GetValue<string>("Hangfire:Dashboard:User") ?? "admin",
Pass = app.Configuration.GetValue<string>("Hangfire:Dashboard:Pass") ?? "1234560"
}
}
});
// 注册定时任务:把未同步(IndexStatus=0)的商品写入 OpenSearch 分词索引
var cron = app.Configuration.GetValue<string>("Indexing:Cron") ?? "*/5 * * * *";
// 用容器里的 IRecurringJobManager,而不是静态 RecurringJob:
// AddHangfire 只把 JobStorage 注册进容器(没有设置 JobStorage.Current),静态 API 在 Build() 之后调用会直接抛异常。
app.Services.GetRequiredService<IRecurringJobManager>().AddOrUpdate<GoodsIndexJob>(
"goods-index-pending",
job => job.ProcessPendingAsync(),
cron);
}
catch (Exception ex)
{
// 数据库不可用/表未初始化时避免阻断应用启动(看板与定时任务都会连库),恢复后重启即可重新装配
Console.WriteLine("[Hangfire] 看板/定时任务装配失败: " + ex.Message);
}
}
}
}
@@ -0,0 +1,32 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Store.Modules.CloudProduct.Services
{
/// <summary>
/// Hangfire 定时任务:把数据库中未同步(IndexStatus=0)的商品写入 OpenSearch 分词索引。
/// </summary>
public class GoodsIndexJob
{
private readonly IServiceScopeFactory _scopeFactory;
private readonly IConfiguration _configuration;
private readonly ILogger<GoodsIndexJob> _logger;
public GoodsIndexJob(IServiceScopeFactory scopeFactory, IConfiguration configuration,ILogger<GoodsIndexJob> logger)
{
_scopeFactory = scopeFactory;
_configuration = configuration;
_logger = logger;
}
public async Task ProcessPendingAsync()
{
var batchSize = _configuration.GetValue<int?>("Indexing:BatchSize") ?? 1000;
using var scope = _scopeFactory.CreateScope();
var service = scope.ServiceProvider.GetRequiredService<GoodsService>();
var count = await service.IndexPendingGoodsAsync(batchSize);
_logger.LogInformation($"[Hangfire] 商品分词定时任务完成,本次同步 {count} 条");
}
}
}
+7 -12
View File
@@ -1,28 +1,23 @@
using MAX.Core.DependencyInjection;
using MAX.Core.DependencyInjection;
using MAX.Core.Interfaces;
using MAX.Framework.Interfaces;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Store.Modules.CloudProduct
{
public class Startup : IStartup, IModule
/// <summary>
/// CloudProduct 模块的启动器(<see cref="IStartup"/>):由 AddStoreHub() 自动发现,
/// 按 <see cref="Priority"/> 降序执行 <see cref="Initialize"/> + <see cref="Register"/>。
/// 需要配置或管道装配(app)的部分不在这里,见 <see cref="CloudProductModule"/>。
/// </summary>
public class Startup : IStartup
{
public int Priority => IntPriority.Normal;
public void AddModule(IServiceCollection services)
{
services.AddControllers();
}
public void Initialize()
{
}
public void Register(IServiceRegistrar registrar)
{
}
}
}
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
@@ -12,6 +12,9 @@
<ProjectReference Include="..\..\Store.Framework\Store.Framework.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.25" />
<PackageReference Include="Hangfire.Dashboard.Basic.Authentication" Version="8.0.0" />
<PackageReference Include="Hangfire.MySqlStorage" Version="2.0.3" />
<PackageReference Include="OpenSearch.Client" Version="2.1.0" />
</ItemGroup>