feat: replace Redis with PostgreSQL for caching, rate limiting, SMS codes, and token blacklist
- Add 4 PG entities: VerificationCode, RateLimitEntry, TokenBlacklistEntry, CacheEntry - Add 4 services: VerificationService, RateLimitService, TokenBlacklistService, CacheService - Add CleanupBackgroundService for periodic expired data cleanup - Add MigrationHelper for safe schema migration without data loss - Update AuthController: real SMS code generation, rate limiting, logout endpoint with JWT blacklist - Update JwtProvider: add JTI claim for token revocation - Update Program.cs: register new services, JWT blacklist validation, DB migration - Remove StackExchange.Redis NuGet package and all Redis config references - Update start-dev.bat: 6→5 services, remove Redis startup - Update docs: remove Redis references from all documentation - Fix: logout button spacing on profile page - Fix: .gitignore data/→/data/ to not ignore Infrastructure/Data/
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
using System.Text.Json;
|
||||
using HealthManager.Domain.Entities;
|
||||
using HealthManager.Infrastructure.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace HealthManager.Application.Services;
|
||||
|
||||
public class CacheService(AppDbContext db)
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNameCaseInsensitive = true };
|
||||
|
||||
public async Task<T?> GetAsync<T>(string key) where T : class
|
||||
{
|
||||
var entry = await db.CacheEntries
|
||||
.FirstOrDefaultAsync(c => c.Key == key && c.ExpiresAt > DateTime.UtcNow);
|
||||
if (entry == null) return default;
|
||||
return JsonSerializer.Deserialize<T>(entry.Value.RootElement.GetRawText(), JsonOptions);
|
||||
}
|
||||
|
||||
public async Task SetAsync<T>(string key, T value, TimeSpan ttl)
|
||||
{
|
||||
var json = JsonSerializer.SerializeToDocument(value);
|
||||
var existing = await db.CacheEntries.FirstOrDefaultAsync(c => c.Key == key);
|
||||
if (existing != null)
|
||||
{
|
||||
existing.Value = json;
|
||||
existing.ExpiresAt = DateTime.UtcNow.Add(ttl);
|
||||
}
|
||||
else
|
||||
{
|
||||
db.CacheEntries.Add(new CacheEntry
|
||||
{
|
||||
Key = key,
|
||||
Value = json,
|
||||
ExpiresAt = DateTime.UtcNow.Add(ttl),
|
||||
});
|
||||
}
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<T> GetOrSetAsync<T>(string key, Func<Task<T>> factory, TimeSpan ttl) where T : class
|
||||
{
|
||||
var cached = await GetAsync<T>(key);
|
||||
if (cached != null) return cached;
|
||||
var value = await factory();
|
||||
await SetAsync(key, value, ttl);
|
||||
return value;
|
||||
}
|
||||
|
||||
public async Task RemoveAsync(string key)
|
||||
{
|
||||
await db.CacheEntries.Where(c => c.Key == key).ExecuteDeleteAsync();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user