From c7a43950008a61b8fb2f072d508a44c5678c0dcc Mon Sep 17 00:00:00 2001 From: xRain Date: Fri, 7 Aug 2026 03:45:19 +0800 Subject: [PATCH] =?UTF-8?q?cache/auth=20=E4=B8=8D=E5=BC=BA=E4=BE=9D?= =?UTF-8?q?=E8=B5=96Redis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + Configurations/SimApiOptions.cs | 6 ++ Helpers/SimApiAuth.cs | 151 +++++++++++++++++++++++--------- README.md | 28 +++++- SimApiExtensions.cs | 14 +++ 5 files changed, 155 insertions(+), 45 deletions(-) diff --git a/.gitignore b/.gitignore index 8631db7..568fcee 100644 --- a/.gitignore +++ b/.gitignore @@ -437,3 +437,4 @@ MigrationBackup/ # BitFun snapshot data - auto managed .bitfun/ +/.bitfun/search/flashgrep-index/ diff --git a/Configurations/SimApiOptions.cs b/Configurations/SimApiOptions.cs index c3641bd..cc7136f 100644 --- a/Configurations/SimApiOptions.cs +++ b/Configurations/SimApiOptions.cs @@ -17,6 +17,12 @@ public class SimApiOptions /// public bool EnableSimApiAuth { get; set; } + /// + /// 启用Cache功能 + /// + public bool EnableSimApiCache { get; set; } = true; + + /// /// 启用SimApi网关授权, 基于上层网关透传的身份令牌验证 /// diff --git a/Helpers/SimApiAuth.cs b/Helpers/SimApiAuth.cs index 46a8f9d..dc78ced 100644 --- a/Helpers/SimApiAuth.cs +++ b/Helpers/SimApiAuth.cs @@ -1,19 +1,32 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.DependencyInjection; using SimApi.Communications; using StackExchange.Redis; namespace SimApi.Helpers; /// -/// 认证助手 +/// 认证助手(支持 Redis 和 InMemory 两种模式) /// -public class SimApiAuth(IDistributedCache cache, IConnectionMultiplexer redis) +public class SimApiAuth { private const string TokenCacheKey = "SimApi:Auth:Token:{token}"; private const string TokenSetCacheKey = "SimApi:Auth:User:{userId}"; - private readonly IDatabase _redisDb = redis.GetDatabase(); + + private readonly IDistributedCache _cache; + private readonly IDatabase? _redisDb; + + // InMemory 模式:用户 → Token集合 + private readonly ConcurrentDictionary> _userTokens = new(); + + public SimApiAuth(IDistributedCache cache, IServiceProvider sp) + { + _cache = cache; + _redisDb = sp.GetService()?.GetDatabase(); + } /// /// 登录信息 @@ -28,13 +41,20 @@ public class SimApiAuth(IDistributedCache cache, IConnectionMultiplexer redis) token ??= Guid.NewGuid().ToString(); var cacheKey = TokenCacheKey.Replace("{token}", token); var setCacheKey = TokenSetCacheKey.Replace("{userId}", loginItem.Id); - _redisDb.SetAdd(setCacheKey, token); - cache.SetString(cacheKey, SimApiUtil.Json(loginItem), - new DistributedCacheEntryOptions - { - SlidingExpiration = expireTime - }); - _redisDb.KeyExpire(setCacheKey, expireTime.Value); + + _cache.SetString(cacheKey, SimApiUtil.Json(loginItem), + new DistributedCacheEntryOptions { SlidingExpiration = expireTime }); + + if (_redisDb != null) + { + _redisDb.SetAdd(setCacheKey, token); + _redisDb.KeyExpire(setCacheKey, expireTime.Value); + } + else + { + var tokens = _userTokens.GetOrAdd(loginItem.Id, _ => new ConcurrentDictionary()); + tokens.TryAdd(token, 0); + } return token; } @@ -48,10 +68,15 @@ public class SimApiAuth(IDistributedCache cache, IConnectionMultiplexer redis) public string Update(SimApiLoginItem loginItem, string token) { var cacheKey = TokenCacheKey.Replace("{token}", token); - cache.SetString(cacheKey, SimApiUtil.Json(loginItem)); - var ttl = _redisDb.KeyTimeToLive(cacheKey); - var setCacheKey = TokenSetCacheKey.Replace("{userId}", loginItem.Id); - _redisDb.KeyExpire(setCacheKey, ttl); + _cache.SetString(cacheKey, SimApiUtil.Json(loginItem)); + + if (_redisDb != null) + { + var ttl = _redisDb.KeyTimeToLive(cacheKey); + var setCacheKey = TokenSetCacheKey.Replace("{userId}", loginItem.Id); + _redisDb.KeyExpire(setCacheKey, ttl); + } + return token; } @@ -64,9 +89,10 @@ public class SimApiAuth(IDistributedCache cache, IConnectionMultiplexer redis) public SimApiLoginItem? GetLogin(string token) { var cacheKey = TokenCacheKey.Replace("{token}", token); - var login = cache.GetString(cacheKey); + var login = _cache.GetString(cacheKey); var resp = login != null ? SimApiUtil.FromJson(login) : null; - if (resp != null) + + if (resp != null && _redisDb != null) { var ttl = _redisDb.KeyTimeToLive(cacheKey); var setCacheKey = TokenSetCacheKey.Replace("{userId}", resp.Id); @@ -83,26 +109,42 @@ public class SimApiAuth(IDistributedCache cache, IConnectionMultiplexer redis) /// public SimApiLoginItem[] GetAllLogins(string userId) { - var setCacheKey = TokenSetCacheKey.Replace("{userId}", userId); - var allLogins = _redisDb.SetMembers(setCacheKey).ToStringArray(); - var resp = new List(); - foreach (var login in allLogins) + if (_redisDb != null) { - if (login != null) + var setCacheKey = TokenSetCacheKey.Replace("{userId}", userId); + var allLogins = _redisDb.SetMembers(setCacheKey).ToStringArray(); + var resp = new List(); + foreach (var login in allLogins) { - var item = GetLogin(login); - if (item != null) + if (login != null) { - resp.Add(item); - } - else - { - _redisDb.SetRemove(setCacheKey, login); + var item = GetLogin(login); + if (item != null) + resp.Add(item); + else + _redisDb.SetRemove(setCacheKey, login); } } - } - return resp.ToArray(); + return resp.ToArray(); + } + else + { + if (!_userTokens.TryGetValue(userId, out var tokens)) + return []; + + var resp = new List(); + foreach (var token in tokens.Keys) + { + var item = GetLogin(token); + if (item != null) + resp.Add(item); + else + tokens.TryRemove(token, out _); + } + + return resp.ToArray(); + } } /// @@ -111,18 +153,32 @@ public class SimApiAuth(IDistributedCache cache, IConnectionMultiplexer redis) /// public void LogoutAll(string userId) { - var setCacheKey = TokenSetCacheKey.Replace("{userId}", userId); - var allLogins = _redisDb.SetMembers(setCacheKey).ToStringArray(); - foreach (var login in allLogins) + if (_redisDb != null) { - if (login != null) + var setCacheKey = TokenSetCacheKey.Replace("{userId}", userId); + var allLogins = _redisDb.SetMembers(setCacheKey).ToStringArray(); + foreach (var login in allLogins) { - var cacheKey = TokenCacheKey.Replace("{token}", login); - cache.Remove(cacheKey); + if (login != null) + { + var cacheKey = TokenCacheKey.Replace("{token}", login); + _cache.Remove(cacheKey); + } + } + + _redisDb.KeyDelete(setCacheKey); + } + else + { + if (_userTokens.TryRemove(userId, out var tokens)) + { + foreach (var token in tokens.Keys) + { + var cacheKey = TokenCacheKey.Replace("{token}", token); + _cache.Remove(cacheKey); + } } } - - _redisDb.KeyDelete(setCacheKey); } /// @@ -132,13 +188,22 @@ public class SimApiAuth(IDistributedCache cache, IConnectionMultiplexer redis) public void Logout(string token) { var item = GetLogin(token); - if (item != null) + + if (_redisDb != null) { - var setCacheKey = TokenSetCacheKey.Replace("{userId}", item.Id); - _redisDb.SetRemove(setCacheKey, token); + if (item != null) + { + var setCacheKey = TokenSetCacheKey.Replace("{userId}", item.Id); + _redisDb.SetRemove(setCacheKey, token); + } + } + else + { + if (item != null && _userTokens.TryGetValue(item.Id, out var tokens)) + tokens.TryRemove(token, out _); } var cacheKey = TokenCacheKey.Replace("{token}", token); - cache.Remove(cacheKey); + _cache.Remove(cacheKey); } -} \ No newline at end of file +} diff --git a/README.md b/README.md index 1103ba6..5c87bb7 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ ASP.NET Core API 基础框架库,提供统一异常拦截、响应封装、Tok var builder = WebApplication.CreateBuilder(args); builder.Services.AddSimApi(options => { + // RedisConfiguration 可选:配置则使用 Redis,不配则自动使用 InMemory options.RedisConfiguration = "localhost:6379"; options.EnableSimApiAuth = true; options.EnableSimApiDoc = true; @@ -191,6 +192,26 @@ public class SimApiLoginItem { **Token 传参**: Header `Token: ` +### 存储模式 + +`SimApiAuth` 内部自动判断,无需手动配置: + +| 条件 | 存储后端 | 用户↔Token 映射 | +|------|---------|-----------------| +| 配置了 `RedisConfiguration` | Redis(`IDistributedCache` + Set) | Redis Set | +| 未配置 `RedisConfiguration` | InMemory(`DistributedMemoryCache`) | `ConcurrentDictionary` | + +- **Redis 模式**:支持多实例共享,Token 持久化,适合生产环境 +- **InMemory 模式**:零配置即可启用 `EnableSimApiAuth`,适合开发/测试/单实例场景。注意重启后所有登录态丢失 + +```csharp +// 不配 Redis 也能用 Auth +builder.Services.AddSimApi(options => +{ + options.EnableSimApiAuth = true; // 自动使用 InMemory +}); +``` + ### 认证后处理 Hook — ISimApiAuthChecker ```csharp @@ -406,9 +427,11 @@ IMinioClient Client { get; } // 底层 MinIO 客户端 --- -## 8. Redis 缓存 — SimApiCache +## 8. 缓存 — SimApiCache -依赖 `RedisConfiguration`,Key 自动加前缀 `SimApi:Cache:`。 +通过 `EnableSimApiCache`(默认 `true`)控制。Key 自动加前缀 `SimApi:Cache:`。 + +存储后端与 `SimApiAuth` 一致:配了 `RedisConfiguration` 就用 Redis,否则用 InMemory。 ```csharp void Set(string key, object value, DistributedCacheEntryOptions? options = null); @@ -617,6 +640,7 @@ builder.Services.AddSimApi(options => // 功能开关 options.EnableSimApiAuth = false; // Token 认证 + options.EnableSimApiCache = true; // 缓存(Redis 或 InMemory) options.EnableSimApiAuthGate = false; // Auth Center 网关鉴权 options.EnableSimApiDoc = false; // Swagger 文档 options.EnableSimApiStorage = false; // S3 存储 diff --git a/SimApiExtensions.cs b/SimApiExtensions.cs index 9a220dd..8008ae2 100644 --- a/SimApiExtensions.cs +++ b/SimApiExtensions.cs @@ -36,11 +36,20 @@ public static class SimApiExtensions { var simApiOptions = new SimApiOptions(); options?.Invoke(simApiOptions); + if (simApiOptions.RedisConfiguration != null) { builder.AddStackExchangeRedisCache(x => x.Configuration = simApiOptions.RedisConfiguration); builder.AddSingleton(_ => ConnectionMultiplexer.Connect(simApiOptions.RedisConfiguration)); + } + else if (simApiOptions.EnableSimApiAuth || simApiOptions.EnableSimApiCache) + { + builder.AddDistributedMemoryCache(); + } + + if (simApiOptions.EnableSimApiCache) + { builder.AddSingleton(); } @@ -360,6 +369,11 @@ public static class SimApiExtensions logger.LogInformation("开始配置 RedisCache ..."); } + if (options.EnableSimApiCache) + { + logger.LogInformation("开始配置SimApiCache..."); + } + //请求一下检测存储错误 if (options.EnableSimApiStorage) {