Files
StoreWeb/StoreHub.Framework/Security/OnlineUserStore.cs
T
2026-08-14 18:00:58 +08:00

81 lines
2.0 KiB
C#

using Microsoft.Extensions.Caching.Memory;
using System.Collections.Concurrent;
namespace StoreHub.Framework.Security;
/// <summary>
/// 基于 <see cref="IMemoryCache"/> 的在线用户会话存储。
/// </summary>
public class OnlineUserStore : IOnlineUserStore
{
private const string CacheKeyPrefix = "online:user:";
private readonly IMemoryCache _cache;
// 在线人员索引:IMemoryCache 不提供公共枚举 API,这里用字典维护便于查询在线列表
private readonly ConcurrentDictionary<int, OnlineUserInfo> _onlineIndex = new();
public OnlineUserStore(IMemoryCache cache)
{
_cache = cache;
}
/// <inheritdoc />
public void SignIn(OnlineUserInfo info)
{
ArgumentNullException.ThrowIfNull(info);
// 以 userId 为键覆盖写入:旧会话(jti)被顶替,实现单点登录
_cache.Set(GetKey(info.UserId), info, new MemoryCacheEntryOptions
{
// 缓存条目与令牌同时过期,过期后自动失效
AbsoluteExpiration = info.ExpiresAt,
});
_onlineIndex[info.UserId] = info;
}
/// <inheritdoc />
public bool IsTokenActive(int userId, string jti)
{
if (string.IsNullOrWhiteSpace(jti))
{
return false;
}
return _cache.TryGetValue(GetKey(userId), out OnlineUserInfo? info)
&& info is not null
&& string.Equals(info.Jti, jti, StringComparison.Ordinal);
}
/// <inheritdoc />
public void SignOut(int userId)
{
_cache.Remove(GetKey(userId));
_onlineIndex.TryRemove(userId, out _);
}
/// <inheritdoc />
public IReadOnlyList<OnlineUserInfo> GetOnlineUsers()
{
DateTime now = DateTime.Now;
List<OnlineUserInfo> result = [];
foreach (KeyValuePair<int, OnlineUserInfo> pair in _onlineIndex)
{
if (pair.Value.ExpiresAt > now)
{
result.Add(pair.Value);
}
else
{
_onlineIndex.TryRemove(pair.Key, out _);
}
}
return result;
}
/// <inheritdoc />
public int Count => GetOnlineUsers().Count;
private static string GetKey(int userId) => $"{CacheKeyPrefix}{userId}";
}