소스 검색

😁优化调整:SignalR增加集群配置 !799

zuohuaijun 2 년 전
부모
커밋
c9109c1a3a

+ 0 - 3
Admin.NET/Admin.NET.Application/Admin.NET.Application.csproj

@@ -36,9 +36,6 @@
 	  <None Update="Configuration\App.json">
 		  <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
 	  </None>
-	  <None Update="Configuration\Cluster.json">
-	    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-	  </None>
 	  <None Update="Configuration\Captcha.json">
 	    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
 	  </None>

+ 10 - 1
Admin.NET/Admin.NET.Application/Configuration/Cache.json

@@ -3,10 +3,19 @@
 
   "Cache": {
     "Prefix": "adminnet_", // 全局缓存前缀
-    "CacheType": "Redis", // Memory、Redis
+    "CacheType": "Memory", // Memory、Redis
     "Redis": {
       "Configuration": "server=127.0.0.1:6379;password=;db=5;", // Redis连接字符串
       "Prefix": "adminnet_" // Redis前缀(目前没用)
     }
+  },
+  "Cluster": { // 集群配置
+    "Enabled": true, // 启用集群:Cache.json使用Redis方式,OSS.json不能使用本地方式保存文件
+    "ServerId": "adminnet", // 服务器标识
+    "ServerIp": "", // 服务器IP
+    "SignalR": {
+      "RedisConfiguration": "127.0.0.1:6379,ssl=false,password=,defaultDatabase=5",
+      "ChannelPrefix": "signalrPrefix_"
+    }
   }
 }

+ 0 - 12
Admin.NET/Admin.NET.Application/Configuration/Cluster.json

@@ -1,12 +0,0 @@
-{
-  "$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
-  "Cluster": { // 集群配置
-    "EnableCluster": true, //启用了集群,应该把cache.json也使用redis方式,oss.json中不能使用本地方式保存文件
-    "ServerId": "Cluster1", //服务器标识,有些接口可能需要记录一下从哪个节点执行的
-    "ServerIp": "", //服务器IP,有些接口可能需要记录一下从哪个IP执行的,然后调用其它的服务(预留)
-    "SignalR": { //配置Redis保存Signal的连接信息
-      "RedisConfiguration": "127.0.0.1:6379,ssl=false,password=,defaultDatabase=5",
-      "ChannelPrefix": "signalrPrefix_"
-    }
-  }
-}

+ 42 - 0
Admin.NET/Admin.NET.Core/Option/CacheOptions.cs

@@ -40,4 +40,46 @@ public sealed class CacheOptions : IConfigurableOptions<CacheOptions>
 /// </summary>
 public sealed class RedisOption : RedisOptions
 {
+}
+
+/// <summary>
+/// 集群配置选项
+/// </summary>
+public sealed class ClusterOptions : IConfigurableOptions
+{
+    /// <summary>
+    /// 是否启用
+    /// </summary>
+    public bool Enabled { get; set; }
+
+    /// <summary>
+    /// 服务器标识
+    /// </summary>
+    public string ServerId { get; set; }
+
+    /// <summary>
+    /// 服务器IP
+    /// </summary>
+    public string ServerIp { get; set; }
+
+    /// <summary>
+    /// SignalR配置
+    /// </summary>
+    public ClusterSignalR SignalR { get; set; }
+}
+
+/// <summary>
+/// 集群SignalR配置
+/// </summary>
+public sealed class ClusterSignalR
+{
+    /// <summary>
+    /// Redis连接字符串
+    /// </summary>
+    public string RedisConfiguration { get; set; }
+
+    /// <summary>
+    /// 缓存前缀
+    /// </summary>
+    public string ChannelPrefix { get; set; }
 }

+ 242 - 251
Admin.NET/Admin.NET.Web.Core/Startup.cs

@@ -1,260 +1,251 @@
-// 麻省理工学院许可证
-//
-// 版权所有 (c) 2021-2023 zuohuaijun,大名科技(天津)有限公司  联系电话/微信:18020030720  QQ:515096995
-//
-// 特此免费授予获得本软件的任何人以处理本软件的权利,但须遵守以下条件:在所有副本或重要部分的软件中必须包括上述版权声明和本许可声明。
-//
-// 软件按“原样”提供,不提供任何形式的明示或暗示的保证,包括但不限于对适销性、适用性和非侵权的保证。
-// 在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任负责,无论是因合同、侵权或其他方式引起的,与软件或其使用或其他交易有关。
-
-using Admin.NET.Core;
-using Admin.NET.Core.Service;
-using AspNetCoreRateLimit;
-using Furion;
-using Furion.SpecificationDocument;
-using Furion.VirtualFileServer;
-using IGeekFan.AspNetCore.Knife4jUI;
-using Microsoft.AspNetCore.Authentication;
-using Microsoft.AspNetCore.Authentication.Cookies;
-using Microsoft.AspNetCore.Authentication.JwtBearer;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.AspNetCore.HttpOverrides;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Hosting;
-using Newtonsoft.Json;
-using OnceMi.AspNetCore.OSS;
-using System;
-using System.Net;
-using System.Net.Mail;
-
-namespace Admin.NET.Web.Core;
-
-public class Startup : AppStartup
-{
-    public void ConfigureServices(IServiceCollection services)
-    {
-        // 配置选项
-        services.AddProjectOptions();
-
-        // 缓存注册
-        services.AddCache();
-        // SqlSugar
-        services.AddSqlSugar();
-        // JWT
-        services.AddJwt<JwtHandler>(enableGlobalAuthorize: true);
-        // 允许跨域
-        services.AddCorsAccessor();
-        // 远程请求
-        services.AddRemoteRequest();
-        // 任务队列
-        services.AddTaskQueue();
-        // 任务调度
-        services.AddSchedule(options =>
-        {
-            options.AddPersistence<DbJobPersistence>(); // 添加作业持久化器
-        });
-        // 脱敏检测
-        services.AddSensitiveDetection();
-
-        // Json序列化设置
-        static void SetNewtonsoftJsonSetting(JsonSerializerSettings setting)
-        {
-            setting.DateFormatHandling = DateFormatHandling.IsoDateFormat;
-            setting.DateTimeZoneHandling = DateTimeZoneHandling.Local;
-            setting.DateFormatString = "yyyy-MM-dd HH:mm:ss"; // 时间格式化
-            setting.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; // 忽略循环引用
-            // setting.ContractResolver = new CamelCasePropertyNamesContractResolver(); // 解决动态对象属性名大写
-            // setting.NullValueHandling = NullValueHandling.Ignore; // 忽略空值
-            // setting.Converters.AddLongTypeConverters(); // long转string(防止js精度溢出) 超过16位开启
-            // setting.MetadataPropertyHandling = MetadataPropertyHandling.Ignore; // 解决DateTimeOffset异常
-            // setting.DateParseHandling = DateParseHandling.None; // 解决DateTimeOffset异常
-            // setting.Converters.Add(new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }); // 解决DateTimeOffset异常
-        };
-
-        services.AddControllersWithViews()
-            .AddAppLocalization()
-            .AddNewtonsoftJson(options => SetNewtonsoftJsonSetting(options.SerializerSettings))
-            //.AddXmlSerializerFormatters()
-            //.AddXmlDataContractSerializerFormatters()
-            .AddInjectWithUnifyResult<AdminResultProvider>();
-
-        // 第三方授权登录
-        var authOpt = App.GetOptions<OAuthOptions>();
-        services.AddAuthentication(options =>
-            {
-                options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
-                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
-            })
-            .AddCookie(options =>
-            {
-                options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.None;
-                options.Cookie.SecurePolicy = Microsoft.AspNetCore.Http.CookieSecurePolicy.Always;
-            })
-            .AddWeixin(options =>
-            {
-                options.ClientId = authOpt.Weixin?.ClientId;
-                options.ClientSecret = authOpt.Weixin?.ClientSecret;
-            })
-            .AddGitee(options =>
-            {
-                options.ClientId = authOpt.Gitee?.ClientId;
-                options.ClientSecret = authOpt.Gitee?.ClientSecret;
-
-                options.ClaimActions.MapJsonKey(OAuthClaim.GiteeAvatarUrl, "avatar_url");
-            });
-
-        // ElasticSearch
-        services.AddElasticSearch();
-
-        // 配置Nginx转发获取客户端真实IP
-        // 注1:如果负载均衡不是在本机通过 Loopback 地址转发请求的,一定要加上options.KnownNetworks.Clear()和options.KnownProxies.Clear()
-        // 注2:如果设置环境变量 ASPNETCORE_FORWARDEDHEADERS_ENABLED 为 True,则不需要下面的配置代码
-        services.Configure<ForwardedHeadersOptions>(options =>
-        {
-            options.ForwardedHeaders = ForwardedHeaders.All;
-            options.KnownNetworks.Clear();
-            options.KnownProxies.Clear();
-        });
-
-        // 限流服务
-        services.AddInMemoryRateLimiting();
-        services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
-
-        // 事件总线
-        services.AddEventBus(options =>
-        {
-            options.UseUtcTimestamp = false;
-            // 不启用事件日志
-            options.LogEnabled = false;
-            // 事件执行器(失败重试)
-            options.AddExecutor<RetryEventHandlerExecutor>();
-            //// 替换事件源存储器
-            //options.ReplaceStorer(serviceProvider =>
-            //{
-            //    var redisCache = serviceProvider.GetService<ICache>();
-            //    // 创建默认内存通道事件源对象,可自定义队列路由key,比如这里是 eventbus
-            //    return new RedisEventSourceStorer(redisCache, "eventbus", 3000);
-            //});
-        });
-
-        // OSS对象存储
-        var ossOpt = App.GetOptions<OSSProviderOptions>();
-        services.AddOSSService(Enum.GetName(ossOpt.Provider), "OSSProvider");
-
-        // 电子邮件
-        var emailOpt = App.GetOptions<EmailOptions>();
-        services.AddFluentEmail(emailOpt.DefaultFromEmail, emailOpt.DefaultFromName)
-            .AddSmtpSender(new SmtpClient(emailOpt.Host, emailOpt.Port)
-            {
-                EnableSsl = emailOpt.EnableSsl,
-                UseDefaultCredentials = emailOpt.UseDefaultCredentials,
-                Credentials = new NetworkCredential(emailOpt.UserName, emailOpt.Password)
-            });
-
-        // 模板引擎
-        services.AddViewEngine();
-
+// 麻省理工学院许可证
+//
+// 版权所有 (c) 2021-2023 zuohuaijun,大名科技(天津)有限公司  联系电话/微信:18020030720  QQ:515096995
+//
+// 特此免费授予获得本软件的任何人以处理本软件的权利,但须遵守以下条件:在所有副本或重要部分的软件中必须包括上述版权声明和本许可声明。
+//
+// 软件按“原样”提供,不提供任何形式的明示或暗示的保证,包括但不限于对适销性、适用性和非侵权的保证。
+// 在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任负责,无论是因合同、侵权或其他方式引起的,与软件或其使用或其他交易有关。
+
+using Admin.NET.Core;
+using Admin.NET.Core.Service;
+using AspNetCoreRateLimit;
+using Furion;
+using Furion.SpecificationDocument;
+using Furion.VirtualFileServer;
+using IGeekFan.AspNetCore.Knife4jUI;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authentication.Cookies;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.HttpOverrides;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Newtonsoft.Json;
+using OnceMi.AspNetCore.OSS;
+using System;
+using System.Net;
+using System.Net.Mail;
+
+namespace Admin.NET.Web.Core;
+
+public class Startup : AppStartup
+{
+    public void ConfigureServices(IServiceCollection services)
+    {
+        // 配置选项
+        services.AddProjectOptions();
+
+        // 缓存注册
+        services.AddCache();
+        // SqlSugar
+        services.AddSqlSugar();
+        // JWT
+        services.AddJwt<JwtHandler>(enableGlobalAuthorize: true);
+        // 允许跨域
+        services.AddCorsAccessor();
+        // 远程请求
+        services.AddRemoteRequest();
+        // 任务队列
+        services.AddTaskQueue();
+        // 任务调度
+        services.AddSchedule(options =>
+        {
+            options.AddPersistence<DbJobPersistence>(); // 添加作业持久化器
+        });
+        // 脱敏检测
+        services.AddSensitiveDetection();
+
+        // Json序列化设置
+        static void SetNewtonsoftJsonSetting(JsonSerializerSettings setting)
+        {
+            setting.DateFormatHandling = DateFormatHandling.IsoDateFormat;
+            setting.DateTimeZoneHandling = DateTimeZoneHandling.Local;
+            setting.DateFormatString = "yyyy-MM-dd HH:mm:ss"; // 时间格式化
+            setting.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; // 忽略循环引用
+            // setting.ContractResolver = new CamelCasePropertyNamesContractResolver(); // 解决动态对象属性名大写
+            // setting.NullValueHandling = NullValueHandling.Ignore; // 忽略空值
+            // setting.Converters.AddLongTypeConverters(); // long转string(防止js精度溢出) 超过16位开启
+            // setting.MetadataPropertyHandling = MetadataPropertyHandling.Ignore; // 解决DateTimeOffset异常
+            // setting.DateParseHandling = DateParseHandling.None; // 解决DateTimeOffset异常
+            // setting.Converters.Add(new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }); // 解决DateTimeOffset异常
+        };
+
+        services.AddControllersWithViews()
+            .AddAppLocalization()
+            .AddNewtonsoftJson(options => SetNewtonsoftJsonSetting(options.SerializerSettings))
+            //.AddXmlSerializerFormatters()
+            //.AddXmlDataContractSerializerFormatters()
+            .AddInjectWithUnifyResult<AdminResultProvider>();
+
+        // 第三方授权登录
+        var authOpt = App.GetOptions<OAuthOptions>();
+        services.AddAuthentication(options =>
+            {
+                options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
+                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
+            })
+            .AddCookie(options =>
+            {
+                options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.None;
+                options.Cookie.SecurePolicy = Microsoft.AspNetCore.Http.CookieSecurePolicy.Always;
+            })
+            .AddWeixin(options =>
+            {
+                options.ClientId = authOpt.Weixin?.ClientId;
+                options.ClientSecret = authOpt.Weixin?.ClientSecret;
+            })
+            .AddGitee(options =>
+            {
+                options.ClientId = authOpt.Gitee?.ClientId;
+                options.ClientSecret = authOpt.Gitee?.ClientSecret;
+
+                options.ClaimActions.MapJsonKey(OAuthClaim.GiteeAvatarUrl, "avatar_url");
+            });
+
+        // ElasticSearch
+        services.AddElasticSearch();
+
+        // 配置Nginx转发获取客户端真实IP
+        // 注1:如果负载均衡不是在本机通过 Loopback 地址转发请求的,一定要加上options.KnownNetworks.Clear()和options.KnownProxies.Clear()
+        // 注2:如果设置环境变量 ASPNETCORE_FORWARDEDHEADERS_ENABLED 为 True,则不需要下面的配置代码
+        services.Configure<ForwardedHeadersOptions>(options =>
+        {
+            options.ForwardedHeaders = ForwardedHeaders.All;
+            options.KnownNetworks.Clear();
+            options.KnownProxies.Clear();
+        });
+
+        // 限流服务
+        services.AddInMemoryRateLimiting();
+        services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
+
+        // 事件总线
+        services.AddEventBus(options =>
+        {
+            options.UseUtcTimestamp = false;
+            // 不启用事件日志
+            options.LogEnabled = false;
+            // 事件执行器(失败重试)
+            options.AddExecutor<RetryEventHandlerExecutor>();
+            //// 替换事件源存储器
+            //options.ReplaceStorer(serviceProvider =>
+            //{
+            //    var redisCache = serviceProvider.GetService<ICache>();
+            //    // 创建默认内存通道事件源对象,可自定义队列路由key,比如这里是 eventbus
+            //    return new RedisEventSourceStorer(redisCache, "eventbus", 3000);
+            //});
+        });
+
+        // OSS对象存储
+        var ossOpt = App.GetOptions<OSSProviderOptions>();
+        services.AddOSSService(Enum.GetName(ossOpt.Provider), "OSSProvider");
+
+        // 电子邮件
+        var emailOpt = App.GetOptions<EmailOptions>();
+        services.AddFluentEmail(emailOpt.DefaultFromEmail, emailOpt.DefaultFromName)
+            .AddSmtpSender(new SmtpClient(emailOpt.Host, emailOpt.Port)
+            {
+                EnableSsl = emailOpt.EnableSsl,
+                UseDefaultCredentials = emailOpt.UseDefaultCredentials,
+                Credentials = new NetworkCredential(emailOpt.UserName, emailOpt.Password)
+            });
+
+        // 模板引擎
+        services.AddViewEngine();
+
         // 即时通讯
-        // 如果支持集群,把SignalR配置地为支持集群的模式
-        if (App.GetConfig<bool>("Cluster:EnableCluster"))
+        var signalRBuilder = services.AddSignalR(options =>
+            {
+                options.KeepAliveInterval = TimeSpan.FromSeconds(5);
+            })
+            .AddNewtonsoftJsonProtocol(options => SetNewtonsoftJsonSetting(options.PayloadSerializerSettings));
+        // 若已开启集群配置,则把SignalR配置为支持集群模式
+        var clusterOpt = App.GetOptions<ClusterOptions>();
+        if (clusterOpt.Enabled)
         {
-            string redisConnectionString = App.GetConfig<string>("Cluster:SignalR:RedisConfiguration");
-            string channelPrefix = App.GetConfig<string>("Cluster:SignalR:ChannelPrefix");
-            services.AddSignalR(options =>
+            signalRBuilder.AddStackExchangeRedis(clusterOpt.SignalR.RedisConfiguration, options =>
                 {
-                    options.KeepAliveInterval = TimeSpan.FromSeconds(5);
-                })
-                .AddNewtonsoftJsonProtocol(options => SetNewtonsoftJsonSetting(options.PayloadSerializerSettings))
-                .AddStackExchangeRedis(redisConnectionString, options =>
-                {
-                    options.Configuration.ChannelPrefix = channelPrefix;
+                    options.Configuration.ChannelPrefix = clusterOpt.SignalR.ChannelPrefix;
                 });
+        }
 
+        // 系统日志
+        services.AddLoggingSetup();
+
+        // 验证码
+        services.AddCaptcha();
+
+        // 控制台logo
+        services.AddConsoleLogo();
+    }
+
+    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
+    {
+        if (env.IsDevelopment())
+        {
+            app.UseDeveloperExceptionPage();
+            app.UseForwardedHeaders();
         }
         else
         {
-            services.AddSignalR(options =>
-                {
-                    options.KeepAliveInterval = TimeSpan.FromSeconds(5);
-                })
-                .AddNewtonsoftJsonProtocol(options => SetNewtonsoftJsonSetting(options.PayloadSerializerSettings));
-        }
-        // 系统日志
-        services.AddLoggingSetup();
-
-        // 验证码
-        services.AddCaptcha();
-
-        // 控制台logo
-        services.AddConsoleLogo();
-    }
-
-    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
-    {
-        if (env.IsDevelopment())
-        {
-            app.UseDeveloperExceptionPage();
-            app.UseForwardedHeaders();
-        }
-        else
-        {
-            app.UseExceptionHandler("/Home/Error");
-            app.UseForwardedHeaders();
-            app.UseHsts();
-        }
-
-        // 添加状态码拦截中间件
-        app.UseUnifyResultStatusCodes();
-
-        // 配置多语言
-        app.UseAppLocalization();
-
-        //// 启用HTTPS
-        //app.UseHttpsRedirection();
-
-        // 特定文件类型(文件后缀)处理
-        var contentTypeProvider = FS.GetFileExtensionContentTypeProvider();
-        // contentTypeProvider.Mappings[".文件后缀"] = "MIME 类型";
-        app.UseStaticFiles(new StaticFileOptions
-        {
-            ContentTypeProvider = contentTypeProvider
-        });
-
-        app.UseRouting();
-
-        app.UseCorsAccessor();
-
-        // 限流组件(在跨域之后)
-        app.UseIpRateLimiting();
-        app.UseClientRateLimiting();
-
-        app.UseAuthentication();
-        app.UseAuthorization();
-
-        // 任务调度看板
-        app.UseScheduleUI();
-
-        // 配置Swagger-Knife4UI(路由前缀一致代表独立,不同则代表共存)
-        app.UseKnife4UI(options =>
-        {
-            options.RoutePrefix = "kapi";
-            foreach (var groupInfo in SpecificationDocumentBuilder.GetOpenApiGroups())
-            {
-                options.SwaggerEndpoint("/" + groupInfo.RouteTemplate, groupInfo.Title);
-            }
-        });
-
-        app.UseInject(string.Empty);
-
-        app.UseEndpoints(endpoints =>
-        {
-            // 注册集线器
-            endpoints.MapHubs();
-
-            endpoints.MapControllerRoute(
-                name: "default",
-                pattern: "{controller=Home}/{action=Index}/{id?}");
-        });
-    }
+            app.UseExceptionHandler("/Home/Error");
+            app.UseForwardedHeaders();
+            app.UseHsts();
+        }
+
+        // 添加状态码拦截中间件
+        app.UseUnifyResultStatusCodes();
+
+        // 配置多语言
+        app.UseAppLocalization();
+
+        //// 启用HTTPS
+        //app.UseHttpsRedirection();
+
+        // 特定文件类型(文件后缀)处理
+        var contentTypeProvider = FS.GetFileExtensionContentTypeProvider();
+        // contentTypeProvider.Mappings[".文件后缀"] = "MIME 类型";
+        app.UseStaticFiles(new StaticFileOptions
+        {
+            ContentTypeProvider = contentTypeProvider
+        });
+
+        app.UseRouting();
+
+        app.UseCorsAccessor();
+
+        // 限流组件(在跨域之后)
+        app.UseIpRateLimiting();
+        app.UseClientRateLimiting();
+
+        app.UseAuthentication();
+        app.UseAuthorization();
+
+        // 任务调度看板
+        app.UseScheduleUI();
+
+        // 配置Swagger-Knife4UI(路由前缀一致代表独立,不同则代表共存)
+        app.UseKnife4UI(options =>
+        {
+            options.RoutePrefix = "kapi";
+            foreach (var groupInfo in SpecificationDocumentBuilder.GetOpenApiGroups())
+            {
+                options.SwaggerEndpoint("/" + groupInfo.RouteTemplate, groupInfo.Title);
+            }
+        });
+
+        app.UseInject(string.Empty);
+
+        app.UseEndpoints(endpoints =>
+        {
+            // 注册集线器
+            endpoints.MapHubs();
+
+            endpoints.MapControllerRoute(
+                name: "default",
+                pattern: "{controller=Home}/{action=Index}/{id?}");
+        });
+    }
 }