using System.Diagnostics; using System.Net; using DotNetCore.CAP; using DotNetCore.CAP.Internal; using DotNetCore.CAP.Messages; using Fake; using Fake.EventBus.RabbitMQ; using Fake.Modularity; using Fake.ObjectMapping.AutoMapper; using Fake.ObjectStorage; using Fake.RabbitMQ; using FreeRedis; using Hangfire; using Hangfire.Redis.StackExchange; using InterfaceForward.Domain.Shared.Helpers; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using RabbitMQ.Client; using Savorboard.CAP.InMemoryMessageQueue; using TrackCenter.Application.Kafka; using TrackCenter.Application.CAP; using TrackCenter.Application.HostServices; using TrackCenter.Application.Options; using TrackCenter.Application.Rabbit; namespace TrackCenter.Application; [DependsOn(typeof(FakeRabbitMqModule))] [DependsOn(typeof(FakeObjectMappingAutoMapperModule))] [DependsOn(typeof(FakeEventBusRabbitMqModule))] [DependsOn(typeof(FakeObjectStorageTencentCosModule))] [DependsOn(typeof(TrackCenterDomainModule))] public class TrackCenterApplicationModule : FakeModule { public override void ConfigureServices(ServiceConfigurationContext context) { var configuration = context.Services.GetConfiguration(); context.Services.Configure(options => { options.ScanProfiles(); }); var rabbitOptions = configuration.GetSection("RabbitMQ").Get()!.Default; #region CAP context.Services.AddSingleton(); if (!Debugger.IsAttached) { context.Services.AddCap(options => { options.UseMySql(configuration["ConnectionStrings:Default"]!); options.UseRabbitMQ(configure => { configure.HostName = rabbitOptions.HostName; configure.UserName = rabbitOptions.UserName; configure.Password = rabbitOptions.Password; configure.Port = rabbitOptions.Port; configure.BasicQosOptions = new RabbitMQOptions.BasicQos(configuration.GetValue("CAP:PrefetchCount")); }); options.FailedRetryInterval = configuration.GetValue("CAP:FailedRetryInterval"); options.FailedRetryCount = configuration.GetValue("CAP:FailedRetryCount"); options.SucceedMessageExpiredAfter = configuration.GetValue("CAP:SucceedMessageExpiredAfter"); options.FallbackWindowLookbackSeconds = configuration.GetValue("CAP:FallbackWindowLookbackSeconds"); options.UseDashboard(dashboardOptions => { dashboardOptions.PathBase = configuration["ASPNETCORE_PATHBASE"]; }); options.FailedThresholdCallback = faileCalback => { try { Log.Error($@" |-CAP失败回调事件: |-处理类型:{(faileCalback.MessageType == MessageType.Subscribe ? "消费消息达到最大重试次数,需要开发人员介入" : "生产消息推送失败")} |-头部信息:{faileCalback.Message.Headers.ToJson()}"); } catch (Exception ex) { Log.Error("【严重】CAP失败回调事件发生未处理异常" + ex); } }; }).AddSubscribeFilter(); } else { //是本地调试环境,用内存存储和内存队列 context.Services.AddCap(capOptions => { capOptions.UseInMemoryStorage(); capOptions.UseInMemoryMessageQueue(); capOptions.UseDashboard(); }).AddSubscribeFilter(); context.Services.GetInitLogger().LogInformation("-- 当前为本地环境,用内存存储和内存队列"); } #endregion var redisOptions = configuration.GetSection("Redis").Get(); if (redisOptions == null) throw new FakeException("未找到Redis相关配置,请检查配置文件中是否包含名为Redis的节点"); RedisHelper.Init(redisOptions, showLogInfo: Debugger.IsAttached); context.Services.AddHostedService(); context.Services.Configure(options => { options.Declarations = new List>() { o => o.ExchangeDeclare(GlobalConst.TrackChangeExchange, ExchangeType.Topic, true), o => o.ExchangeDeclare(GlobalConst.TrackPushExchange, ExchangeType.Topic, true) }; options.Consumes = new List { new() { Queue = GlobalConst.TrackRefreshSubscribe, FetchCount = configuration.GetValue("TrackApp:TrackRefreshFetchCount"), FailedRequeue = false, Declaration = o => { o.ExchangeDeclare(GlobalConst.InterfaceRelayExchange, ExchangeType.Direct, true); o.QueueDeclare(GlobalConst.TrackRefreshSubscribe, true); o.QueueBind(GlobalConst.TrackRefreshSubscribe, GlobalConst.InterfaceRelayExchange, GlobalConst.TrackRefreshSubscribe); } }, new() { Queue = GlobalConst.TrackPushCompletedSubscribe, FetchCount = configuration.GetValue("TrackApp:TrackRefreshFetchCount"), FailedRequeue = false, Declaration = o => { o.ExchangeDeclare(GlobalConst.InterfaceRelayExchange, ExchangeType.Direct, true); o.QueueDeclare(GlobalConst.TrackPushCompletedSubscribe, true); o.QueueBind(GlobalConst.TrackPushCompletedSubscribe, GlobalConst.InterfaceRelayExchange, GlobalConst.TrackPushCompletedSubscribe); } } }; }); context.Services.Configure(configuration.GetSection("TrackApp")); context.Services.Configure( configuration.GetSection("InterfaceRelayParamsConfig")); var connectionString = $"{configuration["Redis:Host"]}" + $",password={configuration["Redis:Password"]}" + $",defaultDatabase={configuration["Redis:Database"]}" + $",abortConnect=false"; // 注册预警计算服务 context.Services.AddWarningCalcServices(configuration); // 注册预警任务后台服务 context.Services.AddHostedService(); // 注册飞书服务(用于预警通知) context.Services.AddFeishuNetSdk(o => { o.AppId = configuration.GetValue("FeiShu:AppId")!; o.AppSecret = configuration.GetValue("FeiShu:AppSecret")!; o.EnableLogging = configuration.GetValue("FeiShu:EnableLogging"); }); // 注册HangFire服务 context.Services.Configure(configuration.GetSection("WarningTask")); context.Services.AddHangfire(o => { var options = new RedisStorageOptions { Prefix = $"{GlobalConst.TrackHangfireKeyPrefix}:", Db = int.Parse(configuration["Redis:Database"]!), }; o.UseRedisStorage(connectionString, options) .WithJobExpirationTimeout(TimeSpan.FromHours(1)); // 终止态任务只保留1小时 }); var hangfireWorkerCount = configuration.GetValue("TrackApp:HangfireWorkerCount"); if (hangfireWorkerCount <= 0) { hangfireWorkerCount = Math.Max(4, Environment.ProcessorCount); } context.Services.AddHangfireServer(o => { // 索引越小优先级越高 o.Queues = [ GlobalConst.WarningJobQueueLevel0, GlobalConst.WarningJobQueueLevel1, GlobalConst.WarningJobQueueLevel2, "default" ]; o.ServerName = Dns.GetHostName(); o.ShutdownTimeout = TimeSpan.FromSeconds(10); o.WorkerCount = hangfireWorkerCount; }); // 注册轨迹业务定时任务服务 context.Services.AddHostedService(); context.Services.AddHttpClient("itfx", client => { client.Timeout = TimeSpan.FromSeconds(20); client.BaseAddress = new Uri(configuration["TrackApp:ItfxBaseAddress"]!); }); } }