From a953509ba876cb171e0ecae72c2462b3bd79a8ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=84=AD=E6=B2=9B=E8=BB=92?= Date: Wed, 8 Oct 2025 23:52:44 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=BE=A9=E5=9C=96=E7=89=87=20U?= =?UTF-8?q?RL=20=E7=94=9F=E6=88=90=E9=82=8F=E8=BC=AF=EF=BC=8C=E7=A2=BA?= =?UTF-8?q?=E4=BF=9D=E8=BF=94=E5=9B=9E=E5=AE=8C=E6=95=B4=E7=9A=84=20Google?= =?UTF-8?q?=20Cloud=20Storage=20URLs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 注入 IImageStorageService 到 FlashcardsController - 添加 GetImageUrlAsync 方法統一處理圖片 URL 生成 - 重構 GetFlashcards 從 LINQ 改為 foreach 迴圈支援異步操作 - 修復 GetFlashcard 方法的圖片 URL 處理邏輯 - 確保前端接收到完整的 GCS URLs 而非相對路徑 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../Controllers/FlashcardsController.cs | 97 ++++++++++++------- 1 file changed, 63 insertions(+), 34 deletions(-) diff --git a/backend/DramaLing.Api/Controllers/FlashcardsController.cs b/backend/DramaLing.Api/Controllers/FlashcardsController.cs index 4bbc287..2f93bbb 100644 --- a/backend/DramaLing.Api/Controllers/FlashcardsController.cs +++ b/backend/DramaLing.Api/Controllers/FlashcardsController.cs @@ -6,6 +6,7 @@ using DramaLing.Api.Contracts.Services.Review; using Microsoft.AspNetCore.Authorization; using DramaLing.Api.Utils; using DramaLing.Api.Services; +using DramaLing.Api.Services.Storage; using DramaLing.Api.Data; using Microsoft.EntityFrameworkCore; @@ -18,17 +19,33 @@ public class FlashcardsController : BaseController private readonly IFlashcardRepository _flashcardRepository; private readonly IReviewService _reviewService; private readonly DramaLingDbContext _context; + private readonly IImageStorageService _imageStorageService; public FlashcardsController( IFlashcardRepository flashcardRepository, IReviewService reviewService, DramaLingDbContext context, + IImageStorageService imageStorageService, IAuthService authService, ILogger logger) : base(logger, authService) { _flashcardRepository = flashcardRepository; _reviewService = reviewService; _context = context; + _imageStorageService = imageStorageService; + } + + private async Task GetImageUrlAsync(string? relativePath) + { + if (string.IsNullOrEmpty(relativePath)) + return null; + + // 確保路徑包含 examples/ 前綴 + var fullPath = relativePath.StartsWith("examples/") + ? relativePath + : $"examples/{relativePath}"; + + return await _imageStorageService.GetImageUrlAsync(fullPath); } [HttpGet] @@ -47,38 +64,50 @@ public class FlashcardsController : BaseController .Where(fr => fr.UserId == userId && flashcardIds.Contains(fr.FlashcardId)) .ToDictionaryAsync(fr => fr.FlashcardId); + // 重構為 foreach 迴圈,支援異步 URL 處理 + var flashcardList = new List(); + + foreach (var f in flashcards) + { + reviews.TryGetValue(f.Id, out var review); + + // 取得主要圖片的相對路徑並轉換為完整 URL + var primaryImageRelativePath = f.FlashcardExampleImages + .Where(fei => fei.IsPrimary) + .Select(fei => fei.ExampleImage.RelativePath) + .FirstOrDefault(); + + var primaryImageUrl = await GetImageUrlAsync(primaryImageRelativePath); + + flashcardList.Add(new + { + f.Id, + f.Word, + f.Translation, + f.Definition, + f.PartOfSpeech, + f.Pronunciation, + f.Example, + f.ExampleTranslation, + f.IsFavorite, + f.Synonyms, + DifficultyLevelNumeric = f.DifficultyLevelNumeric, + CEFR = CEFRHelper.ToString(f.DifficultyLevelNumeric), + f.CreatedAt, + f.UpdatedAt, + // 添加複習相關屬性 + NextReviewDate = review?.NextReviewDate ?? DateTime.UtcNow.AddDays(1), + TimesReviewed = review?.TotalCorrectCount + review?.TotalWrongCount + review?.TotalSkipCount ?? 0, + MasteryLevel = review?.SuccessCount ?? 0, + // 添加圖片相關屬性 + HasExampleImage = f.FlashcardExampleImages.Any(), + PrimaryImageUrl = primaryImageUrl + }); + } + var flashcardData = new { - Flashcards = flashcards.Select(f => { - reviews.TryGetValue(f.Id, out var review); - return new - { - f.Id, - f.Word, - f.Translation, - f.Definition, - f.PartOfSpeech, - f.Pronunciation, - f.Example, - f.ExampleTranslation, - f.IsFavorite, - f.Synonyms, - DifficultyLevelNumeric = f.DifficultyLevelNumeric, - CEFR = CEFRHelper.ToString(f.DifficultyLevelNumeric), - f.CreatedAt, - f.UpdatedAt, - // 添加複習相關屬性 - NextReviewDate = review?.NextReviewDate ?? DateTime.UtcNow.AddDays(1), - TimesReviewed = review?.TotalCorrectCount + review?.TotalWrongCount + review?.TotalSkipCount ?? 0, - MasteryLevel = review?.SuccessCount ?? 0, - // 添加圖片相關屬性 - HasExampleImage = f.FlashcardExampleImages.Any(), - PrimaryImageUrl = f.FlashcardExampleImages - .Where(fei => fei.IsPrimary) - .Select(fei => $"/images/examples/{fei.ExampleImage.RelativePath}") - .FirstOrDefault() - }; - }), + Flashcards = flashcardList, Count = flashcards.Count() }; @@ -181,10 +210,10 @@ public class FlashcardsController : BaseController MasteryLevel = review?.SuccessCount ?? 0, // 添加圖片相關屬性 HasExampleImage = flashcard.FlashcardExampleImages.Any(), - PrimaryImageUrl = flashcard.FlashcardExampleImages + PrimaryImageUrl = await GetImageUrlAsync(flashcard.FlashcardExampleImages .Where(fei => fei.IsPrimary) - .Select(fei => $"/images/examples/{fei.ExampleImage.RelativePath}") - .FirstOrDefault(), + .Select(fei => fei.ExampleImage.RelativePath) + .FirstOrDefault()), // 保留完整的圖片關聯數據供前端使用 FlashcardExampleImages = flashcard.FlashcardExampleImages }; @@ -407,4 +436,4 @@ public class CreateFlashcardRequest public string? ExampleTranslation { get; set; } public string? Synonyms { get; set; } // AI 生成的同義詞 (JSON 字串) public string? CEFR { get; set; } = string.Empty; -} \ No newline at end of file +}