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 Microsoft.AspNetCore.Authorization;
using DramaLing.Api.Utils; using DramaLing.Api.Utils;
using DramaLing.Api.Services; using DramaLing.Api.Services;
using DramaLing.Api.Services.Storage;
using DramaLing.Api.Data; using DramaLing.Api.Data;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -18,17 +19,33 @@ public class FlashcardsController : BaseController
private readonly IFlashcardRepository _flashcardRepository; private readonly IFlashcardRepository _flashcardRepository;
private readonly IReviewService _reviewService; private readonly IReviewService _reviewService;
private readonly DramaLingDbContext _context; private readonly DramaLingDbContext _context;
private readonly IImageStorageService _imageStorageService;
public FlashcardsController( public FlashcardsController(
IFlashcardRepository flashcardRepository, IFlashcardRepository flashcardRepository,
IReviewService reviewService, IReviewService reviewService,
DramaLingDbContext context, DramaLingDbContext context,
IImageStorageService imageStorageService,
IAuthService authService, IAuthService authService,
ILogger<FlashcardsController> logger) : base(logger, authService) ILogger<FlashcardsController> logger) : base(logger, authService)
{ {
_flashcardRepository = flashcardRepository; _flashcardRepository = flashcardRepository;
_reviewService = reviewService; _reviewService = reviewService;
_context = context; _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] [HttpGet]
@ -47,11 +64,22 @@ public class FlashcardsController : BaseController
.Where(fr => fr.UserId == userId && flashcardIds.Contains(fr.FlashcardId)) .Where(fr => fr.UserId == userId && flashcardIds.Contains(fr.FlashcardId))
.ToDictionaryAsync(fr => fr.FlashcardId); .ToDictionaryAsync(fr => fr.FlashcardId);
var flashcardData = new // 重構為 foreach 迴圈,支援異步 URL 處理
var flashcardList = new List<object>();
foreach (var f in flashcards)
{ {
Flashcards = flashcards.Select(f => {
reviews.TryGetValue(f.Id, out var review); reviews.TryGetValue(f.Id, out var review);
return new
// 取得主要圖片的相對路徑並轉換為完整 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.Id,
f.Word, f.Word,
@ -73,12 +101,13 @@ public class FlashcardsController : BaseController
MasteryLevel = review?.SuccessCount ?? 0, MasteryLevel = review?.SuccessCount ?? 0,
// 添加圖片相關屬性 // 添加圖片相關屬性
HasExampleImage = f.FlashcardExampleImages.Any(), HasExampleImage = f.FlashcardExampleImages.Any(),
PrimaryImageUrl = f.FlashcardExampleImages PrimaryImageUrl = primaryImageUrl
.Where(fei => fei.IsPrimary) });
.Select(fei => $"/images/examples/{fei.ExampleImage.RelativePath}") }
.FirstOrDefault()
}; var flashcardData = new
}), {
Flashcards = flashcardList,
Count = flashcards.Count() Count = flashcards.Count()
}; };
@ -181,10 +210,10 @@ public class FlashcardsController : BaseController
MasteryLevel = review?.SuccessCount ?? 0, MasteryLevel = review?.SuccessCount ?? 0,
// 添加圖片相關屬性 // 添加圖片相關屬性
HasExampleImage = flashcard.FlashcardExampleImages.Any(), HasExampleImage = flashcard.FlashcardExampleImages.Any(),
PrimaryImageUrl = flashcard.FlashcardExampleImages PrimaryImageUrl = await GetImageUrlAsync(flashcard.FlashcardExampleImages
.Where(fei => fei.IsPrimary) .Where(fei => fei.IsPrimary)
.Select(fei => $"/images/examples/{fei.ExampleImage.RelativePath}") .Select(fei => fei.ExampleImage.RelativePath)
.FirstOrDefault(), .FirstOrDefault()),
// 保留完整的圖片關聯數據供前端使用 // 保留完整的圖片關聯數據供前端使用
FlashcardExampleImages = flashcard.FlashcardExampleImages FlashcardExampleImages = flashcard.FlashcardExampleImages
}; };