Переглянути джерело

!802 集群配置增加数据保护,SignalR支持redis哨兵模式
Merge pull request !802 from 适可而止/next

zuohuaijun 2 роки тому
батько
коміт
006f832568

+ 13 - 0
Admin.NET/Admin.NET.Application/Configuration/Cache.json

@@ -17,5 +17,18 @@
       "RedisConfiguration": "127.0.0.1:6379,ssl=false,password=,defaultDatabase=5",
       "ChannelPrefix": "signalrPrefix_"
     }
+  },
+  "StackExchangeRedis": {
+    "DefaultDb": "4",
+    "EndPoints": [
+      "10.10.0.123:26379",// 哨兵端口
+      "10.10.0.124:26380",
+      "10.10.0.125:26381"
+    ],
+    "MainPrefix": "adminNet:",
+    "Password": "123456",
+    "SentinelPassword": "1qaz",
+    "ServiceName": "mymaster",
+    "SignalRChannelPrefix": "signalR:"
   }
 }

+ 1 - 0
Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj

@@ -40,6 +40,7 @@
     <PackageReference Include="System.Linq.Dynamic.Core" Version="1.3.5" />
     <PackageReference Include="UAParser" Version="3.1.47" />
     <PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
+    <PackageReference Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="6.0.20" />
   </ItemGroup>
 
   <ItemGroup>

+ 39 - 0
Admin.NET/Admin.NET.Core/Option/StackExchangeRedisOptions.cs

@@ -0,0 +1,39 @@
+namespace Admin.NET.Core;
+
+/// <summary>
+/// StackExchangeRedisOptions 配置选项
+/// </summary>
+public sealed class StackExchangeRedisOptions : IConfigurableOptions
+{
+    /// <summary>
+    /// master名称
+    /// </summary>
+    public string ServiceName { get; set; }
+
+    /// <summary>
+    /// master访问密码
+    /// </summary>
+    public string Password { get; set; }
+
+    /// <summary>
+    /// 哨兵访问密码
+    /// </summary>
+    public string SentinelPassword { get; set; }
+
+    /// <summary>
+    /// 
+    /// </summary>
+    public List<string> EndPoints { get; set; }
+
+    public int DefaultDb { get; set; }
+
+    /// <summary>
+    /// 主前缀
+    /// </summary>
+    public string MainPrefix { get; set; }
+
+    /// <summary>
+    ///  SignalR 前缀
+    /// </summary>
+    public string SignalRChannelPrefix { get; set; }
+}

+ 1 - 0
Admin.NET/Admin.NET.Web.Core/ProjectOptions.cs

@@ -47,6 +47,7 @@ public static class ProjectOptions
         services.Configure<IpRateLimitPolicies>(App.Configuration.GetSection("IpRateLimitPolicies"));
         services.Configure<ClientRateLimitOptions>(App.Configuration.GetSection("ClientRateLimiting"));
         services.Configure<ClientRateLimitPolicies>(App.Configuration.GetSection("ClientRateLimitPolicies"));
+        services.AddConfigurableOptions<StackExchangeRedisOptions>();
 
         return services;
     }

+ 54 - 4
Admin.NET/Admin.NET.Web.Core/Startup.cs

@@ -24,9 +24,13 @@ using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Hosting;
 using Newtonsoft.Json;
 using OnceMi.AspNetCore.OSS;
+using StackExchange.Redis;
 using System;
 using System.Net;
 using System.Net.Mail;
+using Microsoft.AspNetCore.DataProtection;
+using Furion.Logging.Extensions;
+using Microsoft.AspNetCore.SignalR;
 
 namespace Admin.NET.Web.Core;
 
@@ -157,17 +161,63 @@ public class Startup : AppStartup
 
         // 即时通讯
         var signalRBuilder = services.AddSignalR(options =>
-            {
-                options.KeepAliveInterval = TimeSpan.FromSeconds(5);
-            })
-            .AddNewtonsoftJsonProtocol(options => SetNewtonsoftJsonSetting(options.PayloadSerializerSettings));
+        {
+            options.EnableDetailedErrors = true;
+            options.ClientTimeoutInterval = TimeSpan.FromMinutes(2);
+            options.KeepAliveInterval = TimeSpan.FromMinutes(1);
+        }).AddNewtonsoftJsonProtocol(options => SetNewtonsoftJsonSetting(options.PayloadSerializerSettings));
+
         // 若已开启集群配置,则把SignalR配置为支持集群模式
         var clusterOpt = App.GetOptions<ClusterOptions>();
         if (clusterOpt.Enabled)
         {
+            // StackExchangeRedis 缓存
+            var redisOptions = App.GetOptions<StackExchangeRedisOptions>();
+
+            // 密钥存储(数据保护)
+            var redisConfig = new ConfigurationOptions
+            {
+                AbortOnConnectFail = false,
+                ServiceName = redisOptions.ServiceName,
+                AllowAdmin = true,
+                DefaultDatabase = redisOptions.DefaultDb,
+                Password = redisOptions.Password
+            };
+            redisOptions.EndPoints.ForEach(o => redisConfig.EndPoints.Add(o));
+            var connection1 = ConnectionMultiplexer.Connect(redisConfig);
+            services.AddDataProtection()
+                .PersistKeysToStackExchangeRedis(connection1, "AdminNet:DataProtection-Keys");
+
             signalRBuilder.AddStackExchangeRedis(clusterOpt.SignalR.RedisConfiguration, options =>
                 {
+                    // 此处设置的ChannelPrefix并不会生效,如果两个不同的项目,且[程序集名+类名]一样,使用同一个redis服务,请注意修改 Hub/OnlineUserHub 的类名。
+                    // 原因请参考下边链接:
+                    // https://github.com/dotnet/aspnetcore/blob/f9121bc3e976ec40a959818451d126d5126ce868/src/SignalR/server/StackExchangeRedis/src/RedisHubLifetimeManager.cs#L74
+                    // https://github.com/dotnet/aspnetcore/blob/f9121bc3e976ec40a959818451d126d5126ce868/src/SignalR/server/StackExchangeRedis/src/Internal/RedisChannels.cs#L33
                     options.Configuration.ChannelPrefix = clusterOpt.SignalR.ChannelPrefix;
+                    options.ConnectionFactory = async writer =>
+                    {
+                        var config = new ConfigurationOptions
+                        {
+                            AbortOnConnectFail = false,
+                            ServiceName = redisOptions.ServiceName,
+                            AllowAdmin = true,
+                            DefaultDatabase = redisOptions.DefaultDb,
+                            Password = redisOptions.Password
+                        };
+                        redisOptions.EndPoints.ForEach(o => config.EndPoints.Add(o));
+                        var connection = await ConnectionMultiplexer.ConnectAsync(config, writer);
+                        connection.ConnectionFailed += (_, e) =>
+                        {
+                            "Connection to Redis failed.".LogError();
+                        };
+
+                        if (!connection.IsConnected)
+                        {
+                            "Did not connect to Redis.".LogError();
+                        }
+                        return connection;
+                    };
                 });
         }
 

+ 1 - 1
Web/src/views/system/onlineUser/signalR.ts

@@ -5,7 +5,7 @@ import { getToken } from '/@/utils/axios-utils';
 // 初始化SignalR对象
 const connection = new SignalR.HubConnectionBuilder()
 	.configureLogging(SignalR.LogLevel.Information)
-	.withUrl(`${import.meta.env.VITE_API_URL}/hubs/onlineUser?access_token=${getToken()}`)
+	.withUrl(`${import.meta.env.VITE_API_URL}/hubs/onlineUser?access_token=${getToken()}`, { transport: SignalR.HttpTransportType.WebSockets, skipNegotiation: true })
 	.withAutomaticReconnect({
 		nextRetryDelayInMilliseconds: () => {
 			return 5000; // 每5秒重连一次