dramaling-vocab-learning/backend/DramaLing.Api/Services/AnalysisCacheService.cs

204 lines
6.6 KiB
C#

using Microsoft.EntityFrameworkCore;
using DramaLing.Api.Data;
using DramaLing.Api.Models.Entities;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
namespace DramaLing.Api.Services;
public interface IAnalysisCacheService
{
Task<SentenceAnalysisCache?> GetCachedAnalysisAsync(string inputText);
Task<string> SetCachedAnalysisAsync(string inputText, object analysisResult, TimeSpan ttl);
Task<bool> InvalidateCacheAsync(string textHash);
Task<int> GetCacheHitCountAsync();
Task CleanExpiredCacheAsync();
}
public class AnalysisCacheService : IAnalysisCacheService
{
private readonly DramaLingDbContext _context;
private readonly ILogger<AnalysisCacheService> _logger;
public AnalysisCacheService(DramaLingDbContext context, ILogger<AnalysisCacheService> logger)
{
_context = context;
_logger = logger;
}
/// <summary>
/// 獲取快取的分析結果
/// </summary>
public async Task<SentenceAnalysisCache?> GetCachedAnalysisAsync(string inputText)
{
try
{
var textHash = GenerateTextHash(inputText);
var cached = await _context.SentenceAnalysisCache
.FirstOrDefaultAsync(c => c.InputTextHash == textHash && c.ExpiresAt > DateTime.UtcNow);
if (cached != null)
{
// 更新訪問統計
cached.AccessCount++;
cached.LastAccessedAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
_logger.LogInformation("Cache hit for text hash: {TextHash}", textHash);
return cached;
}
_logger.LogInformation("Cache miss for text hash: {TextHash}", textHash);
return null;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting cached analysis for text: {InputText}", inputText);
return null;
}
}
/// <summary>
/// 設定快取分析結果
/// </summary>
public async Task<string> SetCachedAnalysisAsync(string inputText, object analysisResult, TimeSpan ttl)
{
try
{
var textHash = GenerateTextHash(inputText);
var expiresAt = DateTime.UtcNow.Add(ttl);
// 檢查是否已存在
var existing = await _context.SentenceAnalysisCache
.FirstOrDefaultAsync(c => c.InputTextHash == textHash);
if (existing != null)
{
// 更新現有快取 - 使用一致的命名策略
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
existing.AnalysisResult = JsonSerializer.Serialize(analysisResult, options);
existing.ExpiresAt = expiresAt;
existing.AccessCount++;
existing.LastAccessedAt = DateTime.UtcNow;
}
else
{
// 創建新快取項目
var cacheItem = new SentenceAnalysisCache
{
Id = Guid.NewGuid(),
InputTextHash = textHash,
InputText = inputText,
AnalysisResult = JsonSerializer.Serialize(analysisResult, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
}),
CreatedAt = DateTime.UtcNow,
ExpiresAt = expiresAt,
AccessCount = 1,
LastAccessedAt = DateTime.UtcNow
};
_context.SentenceAnalysisCache.Add(cacheItem);
}
await _context.SaveChangesAsync();
_logger.LogInformation("Cached analysis for text hash: {TextHash}, expires at: {ExpiresAt}",
textHash, expiresAt);
return textHash;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error setting cached analysis for text: {InputText}", inputText);
throw;
}
}
/// <summary>
/// 使快取失效
/// </summary>
public async Task<bool> InvalidateCacheAsync(string textHash)
{
try
{
var cached = await _context.SentenceAnalysisCache
.FirstOrDefaultAsync(c => c.InputTextHash == textHash);
if (cached != null)
{
_context.SentenceAnalysisCache.Remove(cached);
await _context.SaveChangesAsync();
_logger.LogInformation("Invalidated cache for text hash: {TextHash}", textHash);
return true;
}
return false;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error invalidating cache for text hash: {TextHash}", textHash);
return false;
}
}
/// <summary>
/// 獲取快取命中次數
/// </summary>
public async Task<int> GetCacheHitCountAsync()
{
try
{
return await _context.SentenceAnalysisCache
.Where(c => c.ExpiresAt > DateTime.UtcNow)
.SumAsync(c => c.AccessCount);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting cache hit count");
return 0;
}
}
/// <summary>
/// 清理過期的快取
/// </summary>
public async Task CleanExpiredCacheAsync()
{
try
{
var expiredItems = await _context.SentenceAnalysisCache
.Where(c => c.ExpiresAt <= DateTime.UtcNow)
.ToListAsync();
if (expiredItems.Any())
{
_context.SentenceAnalysisCache.RemoveRange(expiredItems);
await _context.SaveChangesAsync();
_logger.LogInformation("Cleaned {Count} expired cache items", expiredItems.Count);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error cleaning expired cache");
}
}
/// <summary>
/// 生成文本哈希值
/// </summary>
private string GenerateTextHash(string inputText)
{
using var sha256 = SHA256.Create();
var bytes = Encoding.UTF8.GetBytes(inputText.Trim().ToLower());
var hash = sha256.ComputeHash(bytes);
return Convert.ToHexString(hash).ToLower();
}
}