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,11 +64,22 @@ public class FlashcardsController : BaseController
.Where(fr => fr.UserId == userId && flashcardIds.Contains(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);
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.Word,
@ -73,12 +101,13 @@ public class FlashcardsController : BaseController
MasteryLevel = review?.SuccessCount ?? 0,
// 添加圖片相關屬性
HasExampleImage = f.FlashcardExampleImages.Any(),
PrimaryImageUrl = f.FlashcardExampleImages
.Where(fei => fei.IsPrimary)
.Select(fei => $"/images/examples/{fei.ExampleImage.RelativePath}")
.FirstOrDefault()
};
}),
PrimaryImageUrl = primaryImageUrl
});
}
var flashcardData = new
{
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
};