dramaling-vocab-learning/backend/DramaLing.Api/Services/AI/Analysis/AnalysisService.cs

138 lines
4.8 KiB
C#

using DramaLing.Api.Models.DTOs;
using DramaLing.Api.Services.Caching;
using System.Security.Cryptography;
using System.Text;
using System.Diagnostics;
namespace DramaLing.Api.Services;
/// <summary>
/// 分析服務實作,整合快取和 AI 服務
/// </summary>
public class AnalysisService : IAnalysisService
{
private readonly IGeminiService _geminiService;
private readonly ICacheService _cacheService;
private readonly ILogger<AnalysisService> _logger;
private static readonly AnalysisStats _stats = new();
public AnalysisService(
IGeminiService geminiService,
ICacheService cacheService,
ILogger<AnalysisService> logger)
{
_geminiService = geminiService ?? throw new ArgumentNullException(nameof(geminiService));
_cacheService = cacheService ?? throw new ArgumentNullException(nameof(cacheService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task<SentenceAnalysisData> AnalyzeSentenceAsync(string inputText, AnalysisOptions options)
{
var stopwatch = Stopwatch.StartNew();
var cacheKey = GenerateCacheKey(inputText, options);
try
{
_logger.LogInformation("Starting analysis for text: {Text} (cache key: {CacheKey})",
inputText.Substring(0, Math.Min(50, inputText.Length)), cacheKey);
// 1. 快取檢查
var cachedResult = await _cacheService.GetAsync<SentenceAnalysisData>(cacheKey);
if (cachedResult != null)
{
stopwatch.Stop();
_stats.CachedAnalyses++;
_stats.TotalAnalyses++;
_logger.LogInformation("Cache hit for analysis in {ElapsedMs}ms", stopwatch.ElapsedMilliseconds);
return cachedResult;
}
// 2. 快取未命中,執行 AI 分析
_logger.LogInformation("Cache miss, calling AI service");
var analysisResult = await _geminiService.AnalyzeSentenceAsync(inputText, options);
// 3. 存入快取 (三層快取會自動同步到資料庫)
await _cacheService.SetAsync(cacheKey, analysisResult, TimeSpan.FromHours(2));
// 4. 更新統計
stopwatch.Stop();
_stats.TotalAnalyses++;
_stats.LastAnalysisAt = DateTime.UtcNow;
UpdateAverageResponseTime((int)stopwatch.ElapsedMilliseconds);
_logger.LogInformation("AI analysis completed and cached in {ElapsedMs}ms", stopwatch.ElapsedMilliseconds);
return analysisResult;
}
catch (Exception ex)
{
stopwatch.Stop();
_logger.LogError(ex, "Error in analysis service for text: {Text}", inputText);
throw;
}
}
public async Task<bool> HasCachedAnalysisAsync(string inputText, AnalysisOptions options)
{
try
{
var cacheKey = GenerateCacheKey(inputText, options);
return await _cacheService.ExistsAsync(cacheKey);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error checking cache existence");
return false;
}
}
public async Task<bool> ClearAnalysisCacheAsync(string inputText, AnalysisOptions options)
{
try
{
var cacheKey = GenerateCacheKey(inputText, options);
return await _cacheService.RemoveAsync(cacheKey);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error clearing analysis cache");
return false;
}
}
public Task<AnalysisStats> GetAnalysisStatsAsync()
{
_stats.ProviderUsageStats["Gemini"] = _stats.TotalAnalyses - _stats.CachedAnalyses;
return Task.FromResult(_stats);
}
#region
private string GenerateCacheKey(string inputText, AnalysisOptions options)
{
// 根據輸入和選項生成穩定的快取鍵
var optionsString = $"{options.IncludeGrammarCheck}_{options.IncludeVocabularyAnalysis}_{options.IncludeIdiomDetection}";
var combinedInput = $"{inputText}_{optionsString}";
using var sha256 = SHA256.Create();
var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(combinedInput));
var hash = Convert.ToHexString(hashBytes)[..16];
return $"analysis:{hash}";
}
private void UpdateAverageResponseTime(int responseTimeMs)
{
if (_stats.AverageResponseTimeMs == 0)
{
_stats.AverageResponseTimeMs = responseTimeMs;
}
else
{
_stats.AverageResponseTimeMs = (_stats.AverageResponseTimeMs + responseTimeMs) / 2;
}
}
#endregion
}