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