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 Microsoft.Extensions.Hosting; using MySql.Data.MySqlClient; using Store.Modules.CloudProduct.Services; namespace Store.Modules.CloudProduct { public class CloudProductModule : IModule { /// /// 本模块自己的配置文件 /// public const string ConfigurationFileName = "appsettings.CloudProduct.json"; public int Priority => IntPriority.Normal; /// /// 服务注册阶段(builder.Build() 之前):这里只能拿到容器和配置,碰不到 app /// public void AddModule(IHostApplicationBuilder host) { IServiceCollection services = host.Services; IConfigurationManager configuration = host.Configuration; if (!string.IsNullOrWhiteSpace(ConfigurationFileName)) { string path = Path.IsPathRooted(ConfigurationFileName)? ConfigurationFileName: Path.Combine(AppContext.BaseDirectory, ConfigurationFileName); configuration.AddJsonFile(path, optional: false, reloadOnChange: true); } //// 本模块的配置节(Search / RawData / Indexing / Delete)都在模块自己的配置文件里, //// 关键项缺失直接报错,避免 OpenSearch 地址为空、原始数据写到意外目录之类的隐性故障 RequireConfiguration(configuration, "Search:Url"); RequireConfiguration(configuration, "Search:IndexName"); RequireConfiguration(configuration, "Search:FavoritesIndexName"); RequireConfiguration(configuration, "RawData:Path"); // ===== Hangfire 后台定时任务(商品分词同步) ===== var hangfireConnection = configuration.GetValue("CloudProductConnection") ?? 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(); } /// /// 管道装配阶段(builder.Build() 之后、app.Run() 之前):这里可以挂中间件、解析容器里的服务 /// public void UseModule(IApplicationBuilder app) { try { var Configuration = app.ApplicationServices.GetService()!; // Hangfire 后台任务看板(默认地址 /hangfire) app.UseHangfireDashboard("/hangfire", new DashboardOptions { Authorization = new[] { new HangfireCustomBasicAuthenticationFilter(){ User = Configuration.GetValue("Hangfire:Dashboard:User") ?? "admin", Pass = Configuration.GetValue("Hangfire:Dashboard:Pass") ?? "1234560" } } }); // 注册定时任务:把未同步(IndexStatus=0)的商品写入 OpenSearch 分词索引 var cron = Configuration.GetValue("Indexing:Cron") ?? "*/5 * * * *"; // 用容器里的 IRecurringJobManager,而不是静态 RecurringJob: // AddHangfire 只把 JobStorage 注册进容器(没有设置 JobStorage.Current),静态 API 在 Build() 之后调用会直接抛异常。 app.ApplicationServices.GetRequiredService().AddOrUpdate( "goods-index-pending", job => job.ProcessPendingAsync(), cron); } catch (Exception ex) { // 数据库不可用/表未初始化时避免阻断应用启动(看板与定时任务都会连库),恢复后重启即可重新装配 Console.WriteLine("[Hangfire] 看板/定时任务装配失败: " + ex.Message); } } /// /// 校验模块配置项存在(值来自模块自己的 ) /// private static void RequireConfiguration(IConfiguration configuration, string key) { if (string.IsNullOrWhiteSpace(configuration.GetValue(key))) { throw new InvalidOperationException($"缺少配置 {key},请检查模块配置文件 {ConfigurationFileName}。"); } } } }