換個姿式訪問圖片

開篇先來聊一聊縮略圖吧,其經典應用場景就是商品列表、詳情以及查看大圖時返回不一樣尺寸的圖片。好處,額,閉上眼睛本身體會。我看到過一些作法是在圖片保存時就生成三套縮略圖提供給前端訪問,這樣實際上是能夠知足基本需求的,只是侷限比較大,譬以下面這幾種狀況:
一、系統初期是基於pc作的開發,生成的縮略圖也是提供給pc端使用。某個月黑風高的夜晚,領導忽然拉着你的小手說咱們明天開始作移動端,而且移動端的須要的圖片尺寸和pc端不同。
二、換領導了,新來的領導說列表頁爲何加載這麼慢,把寬320px的圖片換成319px的,之前的數據所有要換~換~換~。
 
關於生成縮略圖的思考
爲了技(yi)術(lao)創(yong)新(yi),咱們從新設計了縮略圖的生成方式,與其生成固定不可更改的縮略圖,不如根據前端的需(bian)求(hua)來生成縮略圖,這樣不管前端是須要什麼尺寸咱們均可以輕鬆應對。至於安全性,咱們能夠在後端配置容許訪問的尺寸集合,遇到非法的請求,直接給他一張小黃圖就好。因而咱們的圖片訪問流程大概變成了這個樣子:
一、用戶發起圖片請求,服務端拿到請求,檢查圖片是否存在,不存在則返回小黃圖。
二、驗證圖片請求是否帶有尺寸參數,沒有則返回原圖,尺寸超過原圖也直接返回原圖。
三、若是帶有尺寸參數,驗證是否在服務器容許的參數範圍內,不在則返回小黃圖。
四、判斷該尺寸的縮略圖是否存在,不存在則生成縮略圖,存在即直接返回縮略圖。
 
縮略圖只會在第一次訪問時生成,整體來講不會太影響圖片訪問速度。思路大概就是這個樣子,接下來就只須要從服務端拿到用戶的圖片請求並處理。
 
關於圖片請求url的思考
平時咱們訪問圖片的方式大概是這個樣子,http://xx.com/xx.png,那麼帶參數的圖片請求會是個什麼樣子呢,我想最好像這樣吧:http://xx.com/xx.png?width=320。
 
後端如何拿到這個請求的參數並控制其返回的內容。我能想到的有兩個辦法:
一、將圖片訪問集中到一個webapi接口,將圖片路徑和尺寸當作參數傳過來統一處理,webapi接口以流的形式返回圖片。
二、是否能夠將特定的圖片請求使用一個自定義的IHttpHandler來處理。
 
其實仔細一想,第一個辦法實現起來並非那麼優雅,而且也不能達到咱們http://xx.com/xx.png?width=320的需求,甚至圖片返回的速度也會變慢很多,那麼咱們來分析第二個辦法的可行性。個人圖片都是存儲在/upload/image/這個目錄之下,那麼前端的圖片訪問也必然是http://file.com/upload/image/xx.png,咱們只須要將帶/upload/image/的請求映射到咱們自定義的IHttpHandler來處理便可。
 
解決方案
一、自定義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

    }
}

 

 
二、在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.pngweb

縮略圖:http://file.cczcrv.com/upload/image/201612/15/w_1903359727.png?width=100後端

唉,受園友啓發,不能偷懶,加了width參數驗證,合法的參數包括[100,255,320]。api

 

這個Handler中還能夠作不少事情,好比圖片防盜鏈,ip黑名單等等,不過說到底原理只是一個IHttpHandler的應用而已。緩存

相關文章
相關標籤/搜索