技術棧確認
ASP.NET MVC + Entity Framework + Razor 完全可以構建生產級圖片管理系統。核心決策點在於圖片存儲方式與架構分層。
存儲方案對比與選擇
| 方案 | 優點 | 缺點 | 適用場景 |
| 文件系統(推薦) | 性能高、成本低、易備份、CDN 友好 | 需處理文件名衝突、權限管理 | 中小型項目、高併發讀取 |
| 資料庫 BLOB | 事務一致性、單點備份、權限集中 | 資料庫膨脹、讀取性能差、備份慢 | 極小文件、強一致性需求 |
| XML 存儲元數據 | 人類可讀、無資料庫依賴 | 查詢慢、併發差、不擴展 | 配置文件、極小數據量 |
| 雲存儲 (OSS/S3) | 無限擴展、內置 CDN、運維免責 | 成本、供應商鎖定、網絡延遲 | 生產級、多地域、大規模 |
推薦架構:文件系統 + EF 元數據管理
領域模型
public class ImageEntity {
public Guid Id { get; set; } // 唯一標識
public string OriginalFileName { get; set; } // 原始文件名
public string StoredFileName { get; set; } // 存儲文件名 (Guid + 擴展名)
public string ContentType { get; set; } // MIME 類型
public long FileSize { get; set; } // 字節大小
public int Width { get; set; } // 圖片寬度
public int Height { get; set; } // 圖片高度
public string Tags { get; set; } // 標籤 JSON
public DateTime UploadTime { get; set; }
public string UploadedBy { get; set; } // 用戶標識
public bool IsDeleted { get; set; } // 軟刪除
}
存儲路徑策略
// 年/月/日 分級目錄,避免單目錄文件過多
string relativePath = $"{DateTime.Now:yyyy/MM/dd}/{Guid.NewGuid()}{extension}";
// 物理路徑
string physicalPath = Path.Combine(_storageRoot, relativePath);
// URL 路徑
string urlPath = $"/images/{relativePath}";
核心服務接口
public interface IImageService {
Task UploadAsync(Stream fileStream, string fileName, string contentType, string userId);
Task GetByIdAsync(Guid id);
Task> GetPagedAsync(int page, int size, string tags = null);
Task DeleteAsync(Guid id); // 軟刪除
Task GetThumbnailAsync(Guid id, int width, int height);
}
上傳實現要點
[HttpPost]
public async Task Upload(IFormFile file) {
// 1. 驗證
if (file == null || file.Length == 0) return BadRequest("文件為空");
if (!IsImageContentType(file.ContentType)) return BadRequest("非圖片文件");
if (file.Length > 10 * 1024 * 1024) return BadRequest("超過 10MB 限制");
// 2. 處理
using (var stream = file.OpenReadStream()) {
var entity = await _imageService.UploadAsync(stream, file.FileName, file.ContentType, UserId);
return Ok(new { id = entity.Id, url = entity.Url });
}
}
新手學習路徑
- 官方教程:完成 ASP.NET Core MVC 入門
- 實戰樣本:深入研究 eShopOnWeb 架構
- 核心技能:
- Razor 頁面佈局與分部視圖
- EF Core Code First 遷移
- 依賴注入與服務層模式
- 異步編程
async/await
- jQuery AJAX / Fetch API 前後端交互
- 進階主題:ImageSharp 圖片處理、SignalR 實時進度、Azure Blob / AWS S3 集成
前端交互建議
- 上傳:
dropzone.js 或原生 <input type="file" multiple> + FormData
- 列表:分頁 + 無限滾動,縮圖懶加載
loading="lazy"
- 預覽:Lightbox2 / Fancybox 模態框查看原圖
- 編輯:標籤輸入
select2、裁剪 Cropper.js
避坑指南
| 坑點 | 後果 | 預防 |
| 直接用用戶文件名存儲 | 衝突、覆蓋、路徑遍歷攻擊 | Guid 重命名 + 擴展名白名單 |
| 同步讀取大圖生成縮圖 | 請求超時、線程阻塞 | 異步任務隊列 (Hangfire/Quartz) |
| 無權限校驗直接刪除 | 數據洩露、誤刪 | 基於用戶/角色的授權策略 |
| 資料庫存 Base64 字符串 | 性能災難、體積膨脹 33% | 堅持文件系統 + 元數據入庫 |