换个姿式访问图片

开篇先来聊一聊缩略图吧,其经典应用场景就是商品列表、详情以及查看大图时返回不一样尺寸的图片。好处,额,闭上眼睛本身体会。我看到过一些作法是在图片保存时就生成三套缩略图提供给前端访问,这样实际上是能够知足基本需求的,只是局限比较大,譬以下面这几种状况:
一、系统初期是基于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的应用而已。缓存

相关文章
相关标签/搜索