cache/auth 不强依赖Redis

This commit is contained in:
2026-08-07 03:45:19 +08:00
parent bf98792f96
commit c7a4395000
5 changed files with 155 additions and 45 deletions
+1
View File
@@ -437,3 +437,4 @@ MigrationBackup/
# BitFun snapshot data - auto managed # BitFun snapshot data - auto managed
.bitfun/ .bitfun/
/.bitfun/search/flashgrep-index/
+6
View File
@@ -17,6 +17,12 @@ public class SimApiOptions
/// </summary> /// </summary>
public bool EnableSimApiAuth { get; set; } public bool EnableSimApiAuth { get; set; }
/// <summary>
/// 启用Cache功能
/// </summary>
public bool EnableSimApiCache { get; set; } = true;
/// <summary> /// <summary>
/// 启用SimApi网关授权, 基于上层网关透传的身份令牌验证 /// 启用SimApi网关授权, 基于上层网关透传的身份令牌验证
/// </summary> /// </summary>
+107 -42
View File
@@ -1,19 +1,32 @@
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
using SimApi.Communications; using SimApi.Communications;
using StackExchange.Redis; using StackExchange.Redis;
namespace SimApi.Helpers; namespace SimApi.Helpers;
/// <summary> /// <summary>
/// 认证助手 /// 认证助手(支持 Redis 和 InMemory 两种模式)
/// </summary> /// </summary>
public class SimApiAuth(IDistributedCache cache, IConnectionMultiplexer redis) public class SimApiAuth
{ {
private const string TokenCacheKey = "SimApi:Auth:Token:{token}"; private const string TokenCacheKey = "SimApi:Auth:Token:{token}";
private const string TokenSetCacheKey = "SimApi:Auth:User:{userId}"; 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<string, ConcurrentDictionary<string, byte>> _userTokens = new();
public SimApiAuth(IDistributedCache cache, IServiceProvider sp)
{
_cache = cache;
_redisDb = sp.GetService<IConnectionMultiplexer>()?.GetDatabase();
}
/// <summary> /// <summary>
/// 登录信息 /// 登录信息
@@ -28,13 +41,20 @@ public class SimApiAuth(IDistributedCache cache, IConnectionMultiplexer redis)
token ??= Guid.NewGuid().ToString(); token ??= Guid.NewGuid().ToString();
var cacheKey = TokenCacheKey.Replace("{token}", token); var cacheKey = TokenCacheKey.Replace("{token}", token);
var setCacheKey = TokenSetCacheKey.Replace("{userId}", loginItem.Id); var setCacheKey = TokenSetCacheKey.Replace("{userId}", loginItem.Id);
_redisDb.SetAdd(setCacheKey, token);
cache.SetString(cacheKey, SimApiUtil.Json(loginItem), _cache.SetString(cacheKey, SimApiUtil.Json(loginItem),
new DistributedCacheEntryOptions new DistributedCacheEntryOptions { SlidingExpiration = expireTime });
{
SlidingExpiration = expireTime if (_redisDb != null)
}); {
_redisDb.KeyExpire(setCacheKey, expireTime.Value); _redisDb.SetAdd(setCacheKey, token);
_redisDb.KeyExpire(setCacheKey, expireTime.Value);
}
else
{
var tokens = _userTokens.GetOrAdd(loginItem.Id, _ => new ConcurrentDictionary<string, byte>());
tokens.TryAdd(token, 0);
}
return token; return token;
} }
@@ -48,10 +68,15 @@ public class SimApiAuth(IDistributedCache cache, IConnectionMultiplexer redis)
public string Update(SimApiLoginItem loginItem, string token) public string Update(SimApiLoginItem loginItem, string token)
{ {
var cacheKey = TokenCacheKey.Replace("{token}", token); var cacheKey = TokenCacheKey.Replace("{token}", token);
cache.SetString(cacheKey, SimApiUtil.Json(loginItem)); _cache.SetString(cacheKey, SimApiUtil.Json(loginItem));
var ttl = _redisDb.KeyTimeToLive(cacheKey);
var setCacheKey = TokenSetCacheKey.Replace("{userId}", loginItem.Id); if (_redisDb != null)
_redisDb.KeyExpire(setCacheKey, ttl); {
var ttl = _redisDb.KeyTimeToLive(cacheKey);
var setCacheKey = TokenSetCacheKey.Replace("{userId}", loginItem.Id);
_redisDb.KeyExpire(setCacheKey, ttl);
}
return token; return token;
} }
@@ -64,9 +89,10 @@ public class SimApiAuth(IDistributedCache cache, IConnectionMultiplexer redis)
public SimApiLoginItem? GetLogin(string token) public SimApiLoginItem? GetLogin(string token)
{ {
var cacheKey = TokenCacheKey.Replace("{token}", token); var cacheKey = TokenCacheKey.Replace("{token}", token);
var login = cache.GetString(cacheKey); var login = _cache.GetString(cacheKey);
var resp = login != null ? SimApiUtil.FromJson<SimApiLoginItem>(login) : null; var resp = login != null ? SimApiUtil.FromJson<SimApiLoginItem>(login) : null;
if (resp != null)
if (resp != null && _redisDb != null)
{ {
var ttl = _redisDb.KeyTimeToLive(cacheKey); var ttl = _redisDb.KeyTimeToLive(cacheKey);
var setCacheKey = TokenSetCacheKey.Replace("{userId}", resp.Id); var setCacheKey = TokenSetCacheKey.Replace("{userId}", resp.Id);
@@ -83,26 +109,42 @@ public class SimApiAuth(IDistributedCache cache, IConnectionMultiplexer redis)
/// <returns></returns> /// <returns></returns>
public SimApiLoginItem[] GetAllLogins(string userId) public SimApiLoginItem[] GetAllLogins(string userId)
{ {
var setCacheKey = TokenSetCacheKey.Replace("{userId}", userId); if (_redisDb != null)
var allLogins = _redisDb.SetMembers(setCacheKey).ToStringArray();
var resp = new List<SimApiLoginItem>();
foreach (var login in allLogins)
{ {
if (login != null) var setCacheKey = TokenSetCacheKey.Replace("{userId}", userId);
var allLogins = _redisDb.SetMembers(setCacheKey).ToStringArray();
var resp = new List<SimApiLoginItem>();
foreach (var login in allLogins)
{ {
var item = GetLogin(login); if (login != null)
if (item != null)
{ {
resp.Add(item); var item = GetLogin(login);
} if (item != null)
else resp.Add(item);
{ else
_redisDb.SetRemove(setCacheKey, login); _redisDb.SetRemove(setCacheKey, login);
} }
} }
}
return resp.ToArray(); return resp.ToArray();
}
else
{
if (!_userTokens.TryGetValue(userId, out var tokens))
return [];
var resp = new List<SimApiLoginItem>();
foreach (var token in tokens.Keys)
{
var item = GetLogin(token);
if (item != null)
resp.Add(item);
else
tokens.TryRemove(token, out _);
}
return resp.ToArray();
}
} }
/// <summary> /// <summary>
@@ -111,18 +153,32 @@ public class SimApiAuth(IDistributedCache cache, IConnectionMultiplexer redis)
/// <param name="userId"></param> /// <param name="userId"></param>
public void LogoutAll(string userId) public void LogoutAll(string userId)
{ {
var setCacheKey = TokenSetCacheKey.Replace("{userId}", userId); if (_redisDb != null)
var allLogins = _redisDb.SetMembers(setCacheKey).ToStringArray();
foreach (var login in allLogins)
{ {
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); if (login != null)
cache.Remove(cacheKey); {
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);
} }
/// <summary> /// <summary>
@@ -132,13 +188,22 @@ public class SimApiAuth(IDistributedCache cache, IConnectionMultiplexer redis)
public void Logout(string token) public void Logout(string token)
{ {
var item = GetLogin(token); var item = GetLogin(token);
if (item != null)
if (_redisDb != null)
{ {
var setCacheKey = TokenSetCacheKey.Replace("{userId}", item.Id); if (item != null)
_redisDb.SetRemove(setCacheKey, token); {
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); var cacheKey = TokenCacheKey.Replace("{token}", token);
cache.Remove(cacheKey); _cache.Remove(cacheKey);
} }
} }
+26 -2
View File
@@ -13,6 +13,7 @@ ASP.NET Core API 基础框架库,提供统一异常拦截、响应封装、Tok
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSimApi(options => builder.Services.AddSimApi(options =>
{ {
// RedisConfiguration 可选:配置则使用 Redis,不配则自动使用 InMemory
options.RedisConfiguration = "localhost:6379"; options.RedisConfiguration = "localhost:6379";
options.EnableSimApiAuth = true; options.EnableSimApiAuth = true;
options.EnableSimApiDoc = true; options.EnableSimApiDoc = true;
@@ -191,6 +192,26 @@ public class SimApiLoginItem {
**Token 传参**: Header `Token: <value>` **Token 传参**: Header `Token: <value>`
### 存储模式
`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 ### 认证后处理 Hook — ISimApiAuthChecker
```csharp ```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 ```csharp
void Set(string key, object value, DistributedCacheEntryOptions? options = null); void Set(string key, object value, DistributedCacheEntryOptions? options = null);
@@ -617,6 +640,7 @@ builder.Services.AddSimApi(options =>
// 功能开关 // 功能开关
options.EnableSimApiAuth = false; // Token 认证 options.EnableSimApiAuth = false; // Token 认证
options.EnableSimApiCache = true; // 缓存(Redis 或 InMemory
options.EnableSimApiAuthGate = false; // Auth Center 网关鉴权 options.EnableSimApiAuthGate = false; // Auth Center 网关鉴权
options.EnableSimApiDoc = false; // Swagger 文档 options.EnableSimApiDoc = false; // Swagger 文档
options.EnableSimApiStorage = false; // S3 存储 options.EnableSimApiStorage = false; // S3 存储
+14
View File
@@ -36,11 +36,20 @@ public static class SimApiExtensions
{ {
var simApiOptions = new SimApiOptions(); var simApiOptions = new SimApiOptions();
options?.Invoke(simApiOptions); options?.Invoke(simApiOptions);
if (simApiOptions.RedisConfiguration != null) if (simApiOptions.RedisConfiguration != null)
{ {
builder.AddStackExchangeRedisCache(x => x.Configuration = simApiOptions.RedisConfiguration); builder.AddStackExchangeRedisCache(x => x.Configuration = simApiOptions.RedisConfiguration);
builder.AddSingleton<IConnectionMultiplexer>(_ => builder.AddSingleton<IConnectionMultiplexer>(_ =>
ConnectionMultiplexer.Connect(simApiOptions.RedisConfiguration)); ConnectionMultiplexer.Connect(simApiOptions.RedisConfiguration));
}
else if (simApiOptions.EnableSimApiAuth || simApiOptions.EnableSimApiCache)
{
builder.AddDistributedMemoryCache();
}
if (simApiOptions.EnableSimApiCache)
{
builder.AddSingleton<SimApiCache>(); builder.AddSingleton<SimApiCache>();
} }
@@ -360,6 +369,11 @@ public static class SimApiExtensions
logger.LogInformation("开始配置 RedisCache ..."); logger.LogInformation("开始配置 RedisCache ...");
} }
if (options.EnableSimApiCache)
{
logger.LogInformation("开始配置SimApiCache...");
}
//请求一下检测存储错误 //请求一下检测存储错误
if (options.EnableSimApiStorage) if (options.EnableSimApiStorage)
{ {