開篇先來聊一聊縮略圖吧,其經典應用場景就是商品列表、詳情以及查看大圖時返回不同尺寸的圖片。好處,額,閉上眼睛自己體會。我看到過一些做法是在圖片保存時就生成三套縮略圖提供給前端訪問,這樣其實是可以滿足基本需求的,只是局限比較大,譬如下面這幾種情況:
1、系統初期是基於pc做的開發,生成的縮略圖也是提供給pc端使用。某個月黑風高的夜晚,領導突然拉着你的小手說我們明天開始做移動端,並且移動端的需要的圖片尺寸和pc端不一樣。
2、換領導了,新來的領導說列表頁為什么加載這么慢,把寬320px的圖片換成319px的,以前的數據全部要換~換~換~。
關於生成縮略圖的思考
為了技(yi)術(lao)創(yong)新(yi),我們重新設計了縮略圖的生成方式,與其生成固定不可更改的縮略圖,不如根據前端的需(bian)求(hua)來生成縮略圖,這樣無論前端是需要什么尺寸我們都可以輕松應對。至於安全性,我們可以在后端配置允許訪問的尺寸集合,遇到非法的請求,直接給他一張小黃圖就好。於是我們的圖片訪問流程大概變成了這個樣子:
1、用戶發起圖片請求,服務端拿到請求,檢查圖片是否存在,不存在則返回小黃圖。
2、驗證圖片請求是否帶有尺寸參數,沒有則返回原圖,尺寸超過原圖也直接返回原圖。
3、如果帶有尺寸參數,驗證是否在服務器允許的參數范圍內,不在則返回小黃圖。
4、判斷該尺寸的縮略圖是否存在,不存在則生成縮略圖,存在即直接返回縮略圖。
縮略圖只會在第一次訪問時生成,總體來說不會太影響圖片訪問速度。思路大概就是這個樣子,接下來就只需要從服務端拿到用戶的圖片請求並處理。
關於圖片請求url的思考
平時我們訪問圖片的方式大概是這個樣子,http://xx.com/xx.png,那么帶參數的圖片請求會是個什么樣子呢,我想最好像這樣吧:http://xx.com/xx.png?width=320。
后端如何拿到這個請求的參數並控制其返回的內容。我能想到的有兩個辦法:
1、將圖片訪問集中到一個webapi接口,將圖片路徑和尺寸當做參數傳過來統一處理,webapi接口以流的形式返回圖片。
2、是否可以將特定的圖片請求使用一個自定義的IHttpHandler來處理。
其實仔細一想,第一個辦法實現起來並不是那么優雅,並且也不能達到我們http://xx.com/xx.png?width=320的需求,甚至圖片返回的速度也會變慢不少,那么我們來分析第二個辦法的可行性。我的圖片都是存儲在/upload/image/這個目錄之下,那么前端的圖片訪問也必然是http://file.com/upload/image/xx.png,我們只需要將帶/upload/image/的請求映射到我們自定義的IHttpHandler來處理即可。
解決方案
1、自定義HttpHandler:
using System.Drawing; using System.Web; using cczcrv.Web.File.Uploader.Image; using System.IO; using System.Linq; using System; namespace cczcrv.Web.File.Filters { public class GetImageHandler : IHttpHandler { private int[] _imageWidthLimits = new[] { 100, 255, 320 }; public void ProcessRequest(HttpContext context) { //防盜鏈 //if (context.Request.UrlReferrer == null || !context.Request.UrlReferrer.Host.Contains("cczcrv.com")) //{ // CreateNotFoundResponse(context); // return; //} DateTime lastCacheTime; if (DateTime.TryParse(context.Request.Headers["If-Modified-Since"], out lastCacheTime)) { if ((DateTime.Now - lastCacheTime).TotalMinutes < 20) { CreateCacheResponse(context); return; } } //圖片不存在,返回默認圖片 var path = context.Server.MapPath(context.Request.Url.AbsolutePath); if (!System.IO.File.Exists(path)) { CreateNotFoundResponse(context); return; } int width = 0; var strWidth = context.Request.Params["width"]; if (!string.IsNullOrWhiteSpace(strWidth) && int.TryParse(strWidth, out width)) { //驗證請求的圖片的尺寸是否在允許的范圍內 if (!_imageWidthLimits.Contains(width)) { CreateNotFoundResponse(context); return; } var index = path.LastIndexOf('\\'); //縮略圖目錄不存在,創建目錄 var thumbnailDirectory = $"{path.Substring(0, index)}/thumb_{width}"; if (!Directory.Exists(thumbnailDirectory)) { Directory.CreateDirectory(thumbnailDirectory); } var thumbnailPath = $"{thumbnailDirectory}/{path.Substring(index + 1)}"; //縮略圖不存在,生成縮略圖 if (!System.IO.File.Exists(thumbnailPath)) { var image = Image.FromFile(path); //width大於圖片本身寬度,則返回原圖 if (width >= image.Width) { CreateImageResponse(context, path); return; } ThumbnailHelper.MakeThumbnail(image, thumbnailPath, width, 100, ThumbnailModel.W); } CreateImageResponse(context, thumbnailPath); return; } CreateImageResponse(context, path); } public bool IsReusable { get { return false; } } #region 私有方法 /// <summary> /// 返回圖片 /// </summary> /// <param name="context">當前上下文</param> /// <param name="filePath">圖片路徑</param> private void CreateImageResponse(HttpContext context, string filePath) { context.Response.Cache.SetLastModified(DateTime.Now); context.Response.ContentType = "image/JPEG"; context.Response.WriteFile(filePath); context.Response.End(); } /// <summary> /// 返回默認圖片 /// </summary> /// <param name="context">當前上下文</param> private void CreateNotFoundResponse(HttpContext context) { var path = context.Server.MapPath("/upload/image/404.png"); CreateImageResponse(context, path); } /// <summary> /// 返回緩存的內容,HttpCode等於304 /// </summary> /// <param name="context"></param> private void CreateCacheResponse(HttpContext context) { context.Response.StatusCode = 304; context.Response.End(); } #endregion } }
2、在webconfig中添加handler處理配置:
<add name="getImage" path="/upload/image/*" verb="GET" type="cczcrv.Web.File.Filters.GetImageHandler" />
運行效果可以通過下面兩個鏈接查看:
原圖:http://file.cczcrv.com/upload/image/201612/15/w_1903359727.png
縮略圖:http://file.cczcrv.com/upload/image/201612/15/w_1903359727.png?width=100
唉,受園友啟發,不能偷懶,加了width參數驗證,合法的參數包括[100,255,320]。
這個Handler中還可以做很多事情,比如圖片防盜鏈,ip黑名單等等,不過說到底原理只是一個IHttpHandler的應用而已。