fix: 修復圖片 URL 生成邏輯,確保返回完整的 Google Cloud Storage URLs

- 注入 IImageStorageService 到 FlashcardsController
- 添加 GetImageUrlAsync 方法統一處理圖片 URL 生成
- 重構 GetFlashcards 從 LINQ 改為 foreach 迴圈支援異步操作
- 修復 GetFlashcard 方法的圖片 URL 處理邏輯
- 確保前端接收到完整的 GCS URLs 而非相對路徑

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
鄭沛軒 2025-10-08 23:52:44 +08:00
parent 1a20a562d2
commit a953509ba8
1 changed files with 63 additions and 34 deletions

View File

@ -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<FlashcardsController> logger) : base(logger, authService)
{
_flashcardRepository = flashcardRepository;
_reviewService = reviewService;
_context = context;
_imageStorageService = imageStorageService;
}
private async Task<string?> 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<object>();
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;
}
}