using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Options; using System.Security.Cryptography; using System.Text.Json; using TakeoutSaaS.Application.Identity.Abstractions; using TakeoutSaaS.Application.Identity.Models; using TakeoutSaaS.Infrastructure.Identity.Options; namespace TakeoutSaaS.Infrastructure.Identity.Services; /// /// Redis 刷新令牌存储。 /// public sealed class RedisRefreshTokenStore(IDistributedCache cache, IOptions options) : IRefreshTokenStore { private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web); private readonly RefreshTokenStoreOptions _options = options.Value; public async Task IssueAsync(long userId, DateTime expiresAt, CancellationToken cancellationToken = default) { var token = Convert.ToBase64String(RandomNumberGenerator.GetBytes(48)); var descriptor = new RefreshTokenDescriptor(token, userId, expiresAt, false); var key = BuildKey(token); var entryOptions = new DistributedCacheEntryOptions { AbsoluteExpiration = expiresAt }; await cache.SetStringAsync(key, JsonSerializer.Serialize(descriptor, JsonOptions), entryOptions, cancellationToken); return descriptor; } public async Task GetAsync(string refreshToken, CancellationToken cancellationToken = default) { var json = await cache.GetStringAsync(BuildKey(refreshToken), cancellationToken); return string.IsNullOrWhiteSpace(json) ? null : JsonSerializer.Deserialize(json, JsonOptions); } public async Task RevokeAsync(string refreshToken, CancellationToken cancellationToken = default) { var descriptor = await GetAsync(refreshToken, cancellationToken); if (descriptor == null) { return; } var updated = descriptor with { Revoked = true }; var entryOptions = new DistributedCacheEntryOptions { AbsoluteExpiration = updated.ExpiresAt }; await cache.SetStringAsync(BuildKey(refreshToken), JsonSerializer.Serialize(updated, JsonOptions), entryOptions, cancellationToken); } private string BuildKey(string token) => $"{_options.Prefix}{token}"; }