197 lines
6.2 KiB
C#
197 lines
6.2 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)
|
|
{
|
|
// 更新現有快取
|
|
existing.AnalysisResult = JsonSerializer.Serialize(analysisResult);
|
|
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),
|
|
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();
|
|
}
|
|
} |