Add Redis support for SignalR backplane and caching

- Introduced Redis configuration in appsettings.json to enable SignalR backplane functionality.
- Updated Program.cs to conditionally configure SignalR with Redis if a connection string is provided.
- Added Redis connection service registration in ApiBootstrap for distributed scenarios.
- Included necessary package references for StackExchange.Redis and Microsoft.Extensions.Caching.StackExchangeRedis in project files.
- Implemented password masking for Redis connection strings to enhance security.
This commit is contained in:
2026-01-07 16:59:10 +07:00
parent bc4725ca19
commit 7108907e0e
10 changed files with 847 additions and 1 deletions

View File

@@ -510,9 +510,56 @@ public static class ApiBootstrap
services.AddTransient<ICacheService, CacheService>();
services.AddSingleton<ITaskCache, TaskCache>();
// Redis (for SignalR backplane and other distributed scenarios)
services.AddRedis(configuration);
return services;
}
private static IServiceCollection AddRedis(this IServiceCollection services, IConfiguration configuration)
{
// Check if Redis is configured
// Priority: ConnectionStrings:Redis (can be set via ConnectionStrings__Redis env var) > REDIS_URL env var
var redisConnectionString = configuration.GetConnectionString("Redis")
?? configuration["REDIS_URL"];
if (!string.IsNullOrWhiteSpace(redisConnectionString))
{
Console.WriteLine($"✅ Redis configured: {MaskRedisPassword(redisConnectionString)}");
// Register generic Redis connection service for various use cases
// (SignalR backplane, distributed caching, pub/sub, etc.)
services.AddSingleton<IRedisConnectionService, RedisConnectionService>();
}
else
{
Console.WriteLine(" Redis not configured - running in single-instance mode");
// Register a no-op Redis service that returns null connections
services.AddSingleton<IRedisConnectionService>(sp =>
new RedisConnectionService(configuration, sp.GetRequiredService<ILogger<RedisConnectionService>>()));
}
return services;
}
private static string MaskRedisPassword(string connectionString)
{
if (connectionString.Contains("password=", StringComparison.OrdinalIgnoreCase))
{
var parts = connectionString.Split(',');
for (int i = 0; i < parts.Length; i++)
{
if (parts[i].Trim().StartsWith("password=", StringComparison.OrdinalIgnoreCase))
{
parts[i] = "password=***";
}
}
return string.Join(",", parts);
}
return connectionString;
}
private static IServiceCollection AddWorkers(this IServiceCollection services, IConfiguration configuration)
{
if (configuration.GetValue<bool>("WorkerNotifyBundleBacktest", false))