138 lines
4.8 KiB
C#
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
|
|
} |