dramaling-vocab-learning/Google-Cloud-Storage圖片儲存遷移手...

931 lines
26 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Google Cloud Storage 圖片儲存遷移手冊
## 概述
將 DramaLing 後端的例句圖片儲存從本地檔案系統遷移到 Google Cloud Storage (GCS),利用 Google 的全球 CDN 網路提供更快的圖片載入速度和更高的可靠性。
## 目前系統分析
### 現有架構優勢
- ✅ 使用 `IImageStorageService` 接口抽象化
- ✅ 依賴注入已完整設定
- ✅ 支援條件式服務切換
- ✅ 完整的錯誤處理和日誌
### 當前實現
- **服務**: `LocalImageStorageService`
- **儲存位置**: `wwwroot/images/examples`
- **URL 模式**: `https://localhost:5008/images/examples/{fileName}`
## Phase 1: Google Cloud 環境準備
### 1.1 建立 Google Cloud 專案
1. **前往 Google Cloud Console**
```
訪問: https://console.cloud.google.com/
登入你的 Google 帳戶
```
2. **建立新專案**
```
點擊頂部專案選擇器 → 新增專案
專案名稱: dramaling-storage (或你偏好的名稱)
組織: 選擇適當的組織 (可選)
專案 ID: 記錄此 ID後續會用到
```
### 1.2 啟用 Cloud Storage API
```
Google Cloud Console → API 和服務 → 程式庫
搜尋: "Cloud Storage API"
點擊 → 啟用
```
### 1.3 建立 Service Account
1. **建立服務帳戶**
```
Google Cloud Console → IAM 和管理 → 服務帳戶 → 建立服務帳戶
服務帳戶名稱: dramaling-storage-service
說明: DramaLing application storage service account
```
2. **設定權限**
```
選擇角色: Storage Object Admin (允許完整的物件管理)
或更細緻的權限:
- Storage Object Creator (建立物件)
- Storage Object Viewer (檢視物件)
- Storage Object Admin (完整管理)
```
3. **建立和下載金鑰檔案**
```
服務帳戶 → 金鑰 → 新增金鑰 → JSON
下載 JSON 檔案並妥善保存
檔案名建議: dramaling-storage-service-account.json
```
### 1.4 建立 Storage Bucket
1. **建立 Bucket**
```
Google Cloud Console → Cloud Storage → 瀏覽器 → 建立值區
值區名稱: dramaling-images (需全球唯一)
位置類型: Region
位置: asia-east1 (台灣) 或 asia-southeast1 (新加坡)
儲存類別: Standard
存取控制: 統一 (Uniform)
```
2. **設定公開存取權限**
```
選擇建立的 bucket → 權限 → 新增主體
新主體: allUsers
角色: Storage Object Viewer
這會讓圖片可以透過 URL 公開存取
```
### 1.5 設定 CORS
在 Google Cloud Console 中設定 CORS:
```bash
# 建立 cors.json 檔案
[
{
"origin": ["http://localhost:3000", "http://localhost:5000", "https://你的域名.com"],
"method": ["GET", "HEAD"],
"responseHeader": ["Content-Type"],
"maxAgeSeconds": 86400
}
]
# 使用 gsutil 設定 (需要安裝 Google Cloud SDK)
gsutil cors set cors.json gs://dramaling-images
```
## Phase 2: .NET 專案設定
### 2.1 安裝 NuGet 套件
`backend/DramaLing.Api/DramaLing.Api.csproj` 中添加:
```xml
<PackageReference Include="Google.Cloud.Storage.V1" Version="4.7.0" />
<PackageReference Include="Google.Apis.Auth" Version="1.68.0" />
```
或使用命令列:
```bash
cd backend/DramaLing.Api
dotnet add package Google.Cloud.Storage.V1
dotnet add package Google.Apis.Auth
```
### 2.2 建立配置模型
建立 `backend/DramaLing.Api/Models/Configuration/GoogleCloudStorageOptions.cs`:
```csharp
using Microsoft.Extensions.Options;
namespace DramaLing.Api.Models.Configuration;
public class GoogleCloudStorageOptions
{
public const string SectionName = "GoogleCloudStorage";
/// <summary>
/// Google Cloud 專案 ID
/// </summary>
public string ProjectId { get; set; } = string.Empty;
/// <summary>
/// Storage Bucket 名稱
/// </summary>
public string BucketName { get; set; } = string.Empty;
/// <summary>
/// Service Account JSON 金鑰檔案路徑
/// </summary>
public string CredentialsPath { get; set; } = string.Empty;
/// <summary>
/// Service Account JSON 金鑰內容 (用於環境變數)
/// </summary>
public string CredentialsJson { get; set; } = string.Empty;
/// <summary>
/// 自訂域名 (用於 CDN)
/// </summary>
public string CustomDomain { get; set; } = string.Empty;
/// <summary>
/// 是否使用自訂域名
/// </summary>
public bool UseCustomDomain { get; set; } = false;
/// <summary>
/// 圖片路徑前綴
/// </summary>
public string PathPrefix { get; set; } = "examples";
}
public class GoogleCloudStorageOptionsValidator : IValidateOptions<GoogleCloudStorageOptions>
{
public ValidateOptionsResult Validate(string name, GoogleCloudStorageOptions options)
{
var failures = new List<string>();
if (string.IsNullOrEmpty(options.ProjectId))
failures.Add("GoogleCloudStorage:ProjectId is required");
if (string.IsNullOrEmpty(options.BucketName))
failures.Add("GoogleCloudStorage:BucketName is required");
if (string.IsNullOrEmpty(options.CredentialsPath) && string.IsNullOrEmpty(options.CredentialsJson))
failures.Add("Either GoogleCloudStorage:CredentialsPath or GoogleCloudStorage:CredentialsJson must be provided");
return failures.Count > 0
? ValidateOptionsResult.Fail(failures)
: ValidateOptionsResult.Success;
}
}
```
## Phase 3: 實現 Google Cloud Storage 服務
### 3.1 建立 GoogleCloudImageStorageService
建立 `backend/DramaLing.Api/Services/Media/Storage/GoogleCloudImageStorageService.cs`:
```csharp
using Google.Cloud.Storage.V1;
using Google.Apis.Auth.OAuth2;
using DramaLing.Api.Models.Configuration;
using DramaLing.Api.Services.Storage;
using Microsoft.Extensions.Options;
using System.Text;
namespace DramaLing.Api.Services.Media.Storage;
public class GoogleCloudImageStorageService : IImageStorageService
{
private readonly StorageClient _storageClient;
private readonly GoogleCloudStorageOptions _options;
private readonly ILogger<GoogleCloudImageStorageService> _logger;
public GoogleCloudImageStorageService(
IOptions<GoogleCloudStorageOptions> options,
ILogger<GoogleCloudImageStorageService> logger)
{
_options = options.Value ?? throw new ArgumentNullException(nameof(options));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
// 初始化 Storage Client
_storageClient = CreateStorageClient();
_logger.LogInformation("GoogleCloudImageStorageService initialized with bucket: {BucketName}",
_options.BucketName);
}
private StorageClient CreateStorageClient()
{
GoogleCredential credential;
// 優先使用 JSON 字串 (適合 Render 等雲端部署)
if (!string.IsNullOrEmpty(_options.CredentialsJson))
{
credential = GoogleCredential.FromJson(_options.CredentialsJson);
}
// 次要使用檔案路徑 (適合本地開發)
else if (!string.IsNullOrEmpty(_options.CredentialsPath) && File.Exists(_options.CredentialsPath))
{
credential = GoogleCredential.FromFile(_options.CredentialsPath);
}
// 最後嘗試使用預設認證 (適合 Google Cloud 環境)
else
{
credential = GoogleCredential.GetApplicationDefault();
}
return StorageClient.Create(credential);
}
public async Task<string> SaveImageAsync(Stream imageStream, string fileName)
{
try
{
var objectName = $"{_options.PathPrefix}/{fileName}";
var obj = new Google.Cloud.Storage.V1.Object
{
Bucket = _options.BucketName,
Name = objectName,
ContentType = GetContentType(fileName)
};
// 上傳檔案
var uploadedObject = await _storageClient.UploadObjectAsync(obj, imageStream);
_logger.LogInformation("Image uploaded successfully to GCS: {ObjectName}", objectName);
return objectName; // 回傳 GCS 中的物件名稱
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to save image to GCS: {FileName}", fileName);
throw;
}
}
public Task<string> GetImageUrlAsync(string imagePath)
{
// 如果設定了自訂域名 (CDN)
if (_options.UseCustomDomain && !string.IsNullOrEmpty(_options.CustomDomain))
{
var cdnUrl = $"https://{_options.CustomDomain.TrimEnd('/')}/{imagePath.TrimStart('/')}";
return Task.FromResult(cdnUrl);
}
// 使用標準 Google Cloud Storage URL
var gcsUrl = $"https://storage.googleapis.com/{_options.BucketName}/{imagePath.TrimStart('/')}";
return Task.FromResult(gcsUrl);
}
public async Task<bool> DeleteImageAsync(string imagePath)
{
try
{
await _storageClient.DeleteObjectAsync(_options.BucketName, imagePath);
_logger.LogInformation("Image deleted from GCS: {ObjectName}", imagePath);
return true;
}
catch (GoogleApiException ex) when (ex.HttpStatusCode == System.Net.HttpStatusCode.NotFound)
{
_logger.LogWarning("Attempted to delete non-existent image: {ObjectName}", imagePath);
return false;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to delete image from GCS: {ObjectName}", imagePath);
return false;
}
}
public async Task<bool> ImageExistsAsync(string imagePath)
{
try
{
var obj = await _storageClient.GetObjectAsync(_options.BucketName, imagePath);
return obj != null;
}
catch (GoogleApiException ex) when (ex.HttpStatusCode == System.Net.HttpStatusCode.NotFound)
{
return false;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to check image existence in GCS: {ObjectName}", imagePath);
return false;
}
}
public async Task<StorageInfo> GetStorageInfoAsync()
{
try
{
var request = new ListObjectsOptions
{
Prefix = _options.PathPrefix,
PageSize = 1000 // 限制查詢數量
};
var objects = _storageClient.ListObjectsAsync(_options.BucketName, request);
long totalSize = 0;
int fileCount = 0;
await foreach (var obj in objects)
{
totalSize += (long)(obj.Size ?? 0);
fileCount++;
}
return new StorageInfo
{
Provider = "Google Cloud Storage",
TotalSizeBytes = totalSize,
FileCount = fileCount,
Status = "Available"
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to get GCS storage info");
return new StorageInfo
{
Provider = "Google Cloud Storage",
Status = $"Error: {ex.Message}"
};
}
}
private static string GetContentType(string fileName)
{
var extension = Path.GetExtension(fileName).ToLowerInvariant();
return extension switch
{
".jpg" or ".jpeg" => "image/jpeg",
".png" => "image/png",
".gif" => "image/gif",
".webp" => "image/webp",
".svg" => "image/svg+xml",
_ => "application/octet-stream"
};
}
}
```
## Phase 4: 應用配置更新
### 4.1 更新 ServiceCollectionExtensions.cs
修改 `AddBusinessServices` 方法:
```csharp
/// <summary>
/// 配置業務服務
/// </summary>
public static IServiceCollection AddBusinessServices(this IServiceCollection services, IConfiguration configuration)
{
services.AddScoped<IAuthService, AuthService>();
// 媒體服務
services.AddScoped<IImageProcessingService, ImageProcessingService>();
// 圖片儲存服務 - 根據設定選擇實現
var storageProvider = configuration.GetValue<string>("ImageStorage:Provider", "Local");
switch (storageProvider.ToLowerInvariant())
{
case "googlecloud" or "gcs":
ConfigureGoogleCloudStorage(services, configuration);
break;
case "local":
default:
services.AddScoped<IImageStorageService, LocalImageStorageService>();
break;
}
// 其他服務保持不變...
services.AddHttpClient<IReplicateService, ReplicateService>();
services.AddScoped<IOptionsVocabularyService, OptionsVocabularyService>();
services.AddScoped<IAnalysisService, AnalysisService>();
services.AddScoped<DramaLing.Api.Contracts.Services.Review.IReviewService,
DramaLing.Api.Services.Review.ReviewService>();
return services;
}
private static void ConfigureGoogleCloudStorage(IServiceCollection services, IConfiguration configuration)
{
// 配置 Google Cloud Storage 選項
services.Configure<GoogleCloudStorageOptions>(configuration.GetSection(GoogleCloudStorageOptions.SectionName));
services.AddSingleton<IValidateOptions<GoogleCloudStorageOptions>, GoogleCloudStorageOptionsValidator>();
// 註冊 Google Cloud Storage 服務
services.AddScoped<IImageStorageService, GoogleCloudImageStorageService>();
}
```
### 4.2 更新 appsettings.json
```json
{
"ImageStorage": {
"Provider": "Local",
"Local": {
"BasePath": "wwwroot/images/examples",
"BaseUrl": "https://localhost:5008/images/examples"
}
},
"GoogleCloudStorage": {
"ProjectId": "",
"BucketName": "dramaling-images",
"CredentialsPath": "",
"CredentialsJson": "",
"CustomDomain": "",
"UseCustomDomain": false,
"PathPrefix": "examples"
}
}
```
### 4.3 開發環境設定 (appsettings.Development.json)
```json
{
"ImageStorage": {
"Provider": "GoogleCloud"
},
"GoogleCloudStorage": {
"ProjectId": "your-project-id",
"BucketName": "dramaling-images",
"CredentialsPath": "path/to/your/service-account.json",
"PathPrefix": "examples"
}
}
```
### 4.4 生產環境設定 (appsettings.Production.json)
```json
{
"ImageStorage": {
"Provider": "GoogleCloud"
},
"GoogleCloudStorage": {
"ProjectId": "your-production-project-id",
"BucketName": "dramaling-images-prod",
"CustomDomain": "images.dramaling.com",
"UseCustomDomain": true,
"PathPrefix": "examples"
}
}
```
## Phase 5: 認證設定
### 5.1 本地開發環境
**方法 1: Service Account JSON 檔案**
1. **儲存金鑰檔案**
```
將下載的 JSON 檔案放到安全位置
建議: backend/secrets/dramaling-storage-service-account.json
⚠️ 務必將 secrets/ 目錄加入 .gitignore
```
2. **設定檔案路徑**
```json
{
"GoogleCloudStorage": {
"CredentialsPath": "secrets/dramaling-storage-service-account.json"
}
}
```
**方法 2: 環境變數 (推薦)**
```bash
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json"
```
**方法 3: User Secrets (最安全)**
```bash
cd backend/DramaLing.Api
dotnet user-secrets init
dotnet user-secrets set "GoogleCloudStorage:ProjectId" "your-project-id"
dotnet user-secrets set "GoogleCloudStorage:CredentialsJson" "$(cat path/to/service-account.json)"
```
### 5.2 生產環境 (Render)
在 Render Dashboard 設定環境變數:
```
GOOGLE_CLOUD_PROJECT_ID=your-project-id
GOOGLE_CLOUD_STORAGE_BUCKET=dramaling-images-prod
GOOGLE_CLOUD_CREDENTIALS_JSON=[整個JSON檔案內容]
```
然後在 `Program.cs` 中添加環境變數載入:
```csharp
// 在建立 builder 後添加
builder.Configuration.AddInMemoryCollection(new Dictionary<string, string>
{
["GoogleCloudStorage:ProjectId"] = Environment.GetEnvironmentVariable("GOOGLE_CLOUD_PROJECT_ID") ?? "",
["GoogleCloudStorage:BucketName"] = Environment.GetEnvironmentVariable("GOOGLE_CLOUD_STORAGE_BUCKET") ?? "",
["GoogleCloudStorage:CredentialsJson"] = Environment.GetEnvironmentVariable("GOOGLE_CLOUD_CREDENTIALS_JSON") ?? ""
}!);
```
## Phase 6: 測試和驗證
### 6.1 本地測試步驟
1. **設定開發環境**
```bash
# 設定環境變數
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json"
# 或使用 user secrets
cd backend/DramaLing.Api
dotnet user-secrets set "GoogleCloudStorage:ProjectId" "your-project-id"
dotnet user-secrets set "GoogleCloudStorage:BucketName" "dramaling-images"
```
2. **修改開發設定**
```json
{
"ImageStorage": {
"Provider": "GoogleCloud"
}
}
```
3. **測試圖片功能**
- 啟動後端 API
- 前往 AI 生成頁面
- 輸入句子並生成例句圖
- 檢查 Google Cloud Console 中的 bucket 是否有新檔案
- 檢查前端是否正確顯示圖片
### 6.2 功能驗證清單
- [ ] **圖片上傳**: 新圖片出現在 GCS bucket 中
- [ ] **圖片顯示**: 前端可正確載入並顯示 GCS 圖片
- [ ] **URL 生成**: 圖片 URL 格式正確
- [ ] **圖片刪除**: 刪除功能正常運作
- [ ] **錯誤處理**: 網路錯誤時有適當的錯誤訊息
- [ ] **日誌記錄**: 操作日誌正確記錄
- [ ] **效能**: 圖片載入速度合理
### 6.3 常見問題排除
**問題 1**: `The Application Default Credentials are not available`
```
解決方法:
1. 檢查環境變數 GOOGLE_APPLICATION_CREDENTIALS 是否設定
2. 檢查 JSON 檔案路徑是否正確
3. 檢查 JSON 檔案格式是否正確
```
**問題 2**: `Access denied` 錯誤
```
解決方法:
1. 檢查 Service Account 是否有 Storage Object Admin 權限
2. 檢查 bucket 名稱是否正確
3. 檢查專案 ID 是否正確
```
**問題 3**: CORS 錯誤
```
解決方法:
1. 設定 bucket 的 CORS 政策
2. 檢查前端域名是否在允許清單中
```
## Phase 7: 生產環境部署
### 7.1 Render 環境設定
1. **設定環境變數**
```
在 Render Dashboard → Your Service → Environment:
GOOGLE_CLOUD_PROJECT_ID = your-production-project-id
GOOGLE_CLOUD_STORAGE_BUCKET = dramaling-images-prod
GOOGLE_CLOUD_CREDENTIALS_JSON = [完整的JSON內容單行格式]
```
2. **JSON 內容格式化**
```bash
# 將多行 JSON 轉為單行 (用於環境變數)
cat service-account.json | jq -c .
```
### 7.2 CDN 設定 (可選)
如果需要 CDN 加速:
1. **設定 Load Balancer**
```
Google Cloud Console → 網路服務 → Cloud CDN
建立 HTTP(S) Load Balancer
後端指向你的 Storage bucket
```
2. **自訂域名設定**
```json
{
"GoogleCloudStorage": {
"CustomDomain": "images.dramaling.com",
"UseCustomDomain": true
}
}
```
### 7.3 部署流程
1. **更新生產設定**
```json
{
"ImageStorage": {
"Provider": "GoogleCloud"
}
}
```
2. **部署到 Render**
- 推送代碼到 Git
- Render 自動部署
- 檢查部署日誌
3. **驗證功能**
- 測試圖片生成
- 檢查 GCS bucket
- 測試圖片載入速度
## 成本分析
### Google Cloud Storage 定價 (2024年價格)
- **Storage**: $0.020 per GB/month (Standard class, Asia region)
- **Operations**:
- Class A (write): $0.05 per 10,000 operations
- Class B (read): $0.004 per 10,000 operations
- **Network**:
- Asia to Asia: $0.05 per GB
- Global CDN: $0.08-0.20 per GB (depending on region)
### 預期成本估算 (1000 張圖片範例)
假設每張圖片 500KB:
- **儲存成本**: 0.5GB × $0.02 = $0.01/月
- **上傳操作**: 1000 × $0.05/10,000 = $0.005
- **瀏覽操作**: 10,000 次 × $0.004/10,000 = $0.004
**每月總成本約**: $0.02-0.05 USD (非常便宜)
### 與其他方案比較
| 方案 | 月成本 | 效能 | 可靠性 | 管理複雜度 |
|------|-------|------|--------|------------|
| 本地儲存 | $0 | 低 | 低 | 高 |
| Google Cloud | $0.02-0.05 | 高 | 高 | 低 |
| AWS S3 | $0.03-0.08 | 高 | 高 | 中 |
| Cloudflare R2 | $0.01-0.03 | 高 | 高 | 低 |
## 遷移時程表
### 建議實施順序
1. **準備階段** (1-2 小時):
- 建立 Google Cloud 專案
- 設定 Service Account 和 Bucket
- 下載認證檔案
2. **開發階段** (2-3 小時):
- 安裝 NuGet 套件
- 實現 GoogleCloudImageStorageService
- 建立配置模型
3. **測試階段** (1-2 小時):
- 本地環境測試
- 功能驗證
- 效能測試
4. **部署階段** (1 小時):
- 設定生產環境變數
- 部署到 Render
- 最終驗證
**總計時間**: 5-8 小時
## 安全性考量
### 最佳實務
1. **認證管理**:
- ✅ 使用環境變數存放敏感資訊
- ✅ 本地使用 user secrets
- ✅ 生產使用 Render 環境變數
- ❌ 絕不將金鑰提交到 Git
2. **權限管理**:
- ✅ Service Account 最小權限原則
- ✅ Bucket 層級的權限控制
- ✅ 定期輪換 Service Account 金鑰
3. **網路安全**:
- ✅ 使用 HTTPS 傳輸
- ✅ 設定 CORS 限制
- ✅ 監控異常存取
## 回滾策略
如果需要回到本地儲存:
1. **快速回滾**
```json
{
"ImageStorage": {
"Provider": "Local"
}
}
```
2. **重新部署**
- 系統自動切換回 LocalImageStorageService
3. **資料遷移** (可選)
- 從 GCS 下載圖片回本地 (如果需要)
## 監控和維護
### 日誌監控
- 設定 Google Cloud Logging 監控
- 關注 Storage API 錯誤率
- 監控上傳/下載效能
### 成本監控
- 設定 Google Cloud 計費警告
- 定期檢查 Storage 使用量
- 監控 API 調用頻率
### 維護建議
- 定期檢查圖片存取權限
- 清理未使用的圖片 (可選)
- 備份重要圖片 (可選)
## 技術支援
### 文檔資源
- [Google Cloud Storage .NET SDK](https://cloud.google.com/storage/docs/reference/libraries#client-libraries-install-csharp)
- [Service Account 認證](https://cloud.google.com/docs/authentication/production)
- [Storage 最佳實務](https://cloud.google.com/storage/docs/best-practices)
### 故障排除指令
```bash
# 檢查 GCS 連線
gsutil ls gs://your-bucket-name
# 測試認證
gcloud auth application-default print-access-token
# 檢查 bucket 權限
gsutil iam get gs://your-bucket-name
```
---
## 實施檢查清單
### 準備階段
- [ ] 建立 Google Cloud 專案
- [ ] 啟用 Cloud Storage API
- [ ] 建立 Service Account
- [ ] 下載 JSON 認證檔案
- [ ] 建立 Storage Bucket
- [ ] 設定 Bucket 權限和 CORS
### 開發階段
- [x] 安裝 Google.Cloud.Storage.V1 NuGet 套件 ✅ **已完成 2024-10-08**
- [x] 建立 GoogleCloudStorageOptions 配置模型 ✅ **已完成 2024-10-08**
- [x] 實現 GoogleCloudImageStorageService ✅ **已完成 2024-10-08**
- [x] 更新 ServiceCollectionExtensions ✅ **已完成 2024-10-08**
- [x] 更新 appsettings.json 配置 ✅ **已完成 2024-10-08**
- [x] 編譯測試通過 ✅ **已完成 2024-10-08**
- [ ] 設定本地認證
### 測試階段
- [ ] 本地環境測試圖片上傳
- [ ] 驗證圖片 URL 可存取
- [ ] 測試圖片刪除功能
- [ ] 檢查錯誤處理
- [ ] 驗證日誌記錄
### 部署階段
- [ ] 設定 Render 環境變數
- [ ] 更新生產配置
- [ ] 部署並驗證功能
- [ ] 設定監控和警告
- [ ] 準備回滾計劃
## 🚀 實施進度
### 已完成項目 (2024-10-08)
**NuGet 套件安裝**
- 已在 `DramaLing.Api.csproj` 中添加:
- `Google.Cloud.Storage.V1` v4.7.0
- `Google.Apis.Auth` v1.68.0
**配置模型建立**
- 已建立 `Models/Configuration/GoogleCloudStorageOptions.cs`
- 支援多種認證方式Service Account JSON、檔案路徑、API Key
- 包含配置驗證器
**服務實現完成**
- 已建立 `Services/Media/Storage/GoogleCloudImageStorageService.cs`
- 完整實現 `IImageStorageService` 接口
- 支援現有的 User Secrets 中的 `GoogleStorage:ApiKey`
- 包含錯誤處理和日誌記錄
### 設計特色 ⭐
🔄 **條件式切換支援**
- 可透過設定檔在本地儲存 ↔ Google Cloud 之間切換
- 零程式碼修改,完全向後相容
- 支援開發/測試/生產環境不同配置
🔐 **多重認證支援**
- Service Account JSON (推薦)
- 檔案路徑認證
- 現有 API Key 支援
- 環境變數配置
**依賴注入設定完成**
- 已在 `ServiceCollectionExtensions.cs` 中添加條件式切換邏輯
- 支援通過 `ImageStorage:Provider` 配置選擇實現
- 已更新 `Program.cs` 傳入 configuration 參數
**編譯測試通過**
- **Build succeeded with 0 Error(s)**
- 所有組件整合成功
- 準備就緒可進行實際測試
### 🎯 當前狀態
**系統已具備完整的抽換式圖片儲存架構!**
**立即可用的切換方式**
```json
// 保持本地儲存 (當前設定)
"ImageStorage": { "Provider": "Local" }
// 切換到 Google Cloud Storage
"ImageStorage": { "Provider": "GoogleCloud" }
```
### 下一步 (準備實際使用)
要啟用 Google Cloud Storage
1. 建立 Google Cloud 專案和 Bucket
2. 設定認證 (`gcloud auth application-default login`)
3. 修改設定檔 `Provider``GoogleCloud`
**抽換式架構開發完成!** 🚀
這份手冊提供了完整的 Google Cloud Storage 遷移指南,讓你能夠安全且有效地從本地儲存遷移到雲端儲存方案。