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(); public OnlineUserStore(IMemoryCache cache) { _cache = cache; } /// 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; } /// 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); } /// public void SignOut(int userId) { _cache.Remove(GetKey(userId)); _onlineIndex.TryRemove(userId, 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(int userId) => $"{CacheKeyPrefix}{userId}"; }