关于缩略图的生成与访问策略的一些经验分享

      之前在一家做图片分享的互联网公司上班,由于一系列的因素公司业务关停,我们不得不另谋出路。不过还是很感谢公司为我们提供了一个相对宽松的工作环境,同时也让我们体会到了一个创业公司的激情与困惑。

      今天就跟大家说说缩略图的那些事儿,以此来做为一个总结和纪念。

      对于项目来说,不管大小一般都或多或少的存在一些图片,有时候我们需要用代码对图片进行一些处理,比如加个水印、生成个缩略图等,这些都是比较普通的需求了。为了让大家对接下来的需求有一个直观的了解,我们还是先来看一些截图

     

                          (1)  照片上传页

 

                         (图2)照片列表页

 

                        (图3)照片单张页

 

      分析代码之前首先介绍一下项目所使用到的一些框架及主要技术点。项目采用MVC3+EF4.1框架搭建,所涉及到的技术主要有GDIIO流、缓存、正则表达式等,下面是项目截图。

一个很简单的需求,下面我们就来一步步分析如何实现。这里我们讨论的重点不是缩略图本身生成的技术,我们将要讨论的话题主要包括以下几方面:

 

1.照片信息建模。这是基于EFCode First开发方式的第一步。

OriginalImage.cs (原始照片信息)

View Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

using System.ComponentModel.DataAnnotations;
namespace ImgService.EF.Domain
{
    public class OriginalImage
    {
        public OriginalImage()
        {
            ThumbnailImage = new List<ThumbnailImage>();
            Created = DateTime.Now;
        }

        [Key]
        public string OriginalID { get; set; }

        public string Ext { get; set; }

        public string Path { get; set; }

        public int Width { get; set; }

        public int Height { get; set; }

        public string Description { get; set; }

        public DateTime Created { get; set; }

        /// <summary>
        /// 缩略图信息
        /// </summary>
        public virtual List<ThumbnailImage> ThumbnailImage
        {
            get;
            set;
        }
    }
}

ThumbnailImage.cs (缩略照片信息)

View Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

using System.ComponentModel.DataAnnotations;

namespace ImgService.EF.Domain
{
    public class ThumbnailImage
    {
        public ThumbnailImage()
        {
            Created = DateTime.Now;
        }

        [Key]
        public string ID { get; set; }

        public string Ext { get; set; }

        public string Path { get; set; }

        public int Width { get; set; }

        public int Height { get; set; }

        public string Description { get; set; }

        public DateTime Created { get; set; }

        [Required]
        public string OriginalID { get; set; }

        public virtual OriginalImage OriginalImage { get; set; }
    }
}

2.缩略图的基本生成原理。这里不做详细讲解,都是写网上类似的代码,不过应该还有可以优化的地方。

ImageHelper.cs

View Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.IO;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;

namespace ImgService.Helpers
{
    public class ImageHelper
    {

        public static Size GetSize(Stream stream)
        {
            System.Drawing.Image imageObj = System.Drawing.Image.FromStream(stream);
            Size size = imageObj.Size;
            imageObj.Dispose();
            return size;
        }

        public static byte[] GetImageBytes(string physicPath)
        {
            if (!File.Exists(physicPath)) { return null; }

           FileStream fs = new FileStream(physicPath, FileMode.Open);
           int fileLen = (int)fs.Length;
           byte[] bytes = new byte[fileLen];
           fs.Read(bytes, 0, fileLen);

           fs.Close();
           fs.Dispose();
           return bytes;
        }

        public static void SaveImage(Bitmap img,string physicPath)
        {
            img.Save(physicPath);
            img.Dispose();
        }

        /// <summary>
        /// 计算新尺寸
        /// </summary>
        /// <param name="width">原始宽度</param>
        /// <param name="height">原始高度</param>
        /// <param name="maxWidth">最大新宽度</param>
        /// <param name="maxHeight">最大新高度</param>
        /// <returns></returns>
        private static Size Resize(int width, int height, int maxWidth, int maxHeight)
        {
            decimal MAX_WIDTH = (decimal)maxWidth;
            decimal MAX_HEIGHT = (decimal)maxHeight;
            decimal ASPECT_RATIO = MAX_WIDTH / MAX_HEIGHT;

            int newWidth, newHeight;
            decimal originalWidth = (decimal)width;
            decimal originalHeight = (decimal)height;

            if (originalWidth > MAX_WIDTH || originalHeight > MAX_HEIGHT)
            {
                decimal factor;
                // determine the largest factor 
                if (originalWidth / originalHeight > ASPECT_RATIO)
                {
                    factor = originalWidth / MAX_WIDTH;
                    newWidth = Convert.ToInt32(originalWidth / factor);
                    newHeight = Convert.ToInt32(originalHeight / factor);
                }
                else
                {
                    factor = originalHeight / MAX_HEIGHT;
                    newWidth = Convert.ToInt32(originalWidth / factor);
                    newHeight = Convert.ToInt32(originalHeight / factor);
                }
            }
            else
            {
                newWidth = width;
                newHeight = height;
            }
            return new Size(newWidth, newHeight);
        }


        /// <summary>
        /// 生成宽高相等的缩略图,当原图比例严重失衡时缩略图将只截取原图的一部分
        /// </summary>
        /// <param name="bitmapOriginal"></param>
        /// <param name="newSize"></param>
        /// <returns></returns>
        public static Bitmap Resize(Bitmap bitmapOriginal, int newSize)
        {
            int originalWidth = bitmapOriginal.Width;
            int originalHeight = bitmapOriginal.Height;


            double standard = Convert.ToDouble(newSize) / Convert.ToDouble(newSize);
            double actualRate = Convert.ToDouble(originalWidth) / Convert.ToDouble(originalHeight);

            int imgNewWidth = newSize;
            int imgNewHeight = newSize;

            int x = 0;
            int y = 0;

            if (actualRate > standard)
            {
                imgNewWidth = originalWidth * newSize / originalHeight;
                x = (imgNewWidth - newSize) / 2;
            }
            else
            {
                imgNewHeight = originalHeight * newSize / originalWidth;
                y = (imgNewHeight - newSize) / 2;
            }

            Bitmap bitmap = new Bitmap(imgNewWidth, imgNewHeight);
            Graphics graphics = Graphics.FromImage(bitmap);
            graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
            graphics.SmoothingMode = SmoothingMode.HighQuality;
            graphics.CompositingQuality = CompositingQuality.HighQuality;
            graphics.Clear(Color.Transparent);
            graphics.DrawImage(bitmapOriginal, new Rectangle(0, 0, imgNewWidth, imgNewHeight));

            Bitmap bitmap_cut = new Bitmap(newSize, newSize);
            graphics = Graphics.FromImage(bitmap_cut);
            graphics.DrawImage(bitmap, new System.Drawing.Rectangle(0, 0, newSize, newSize), new System.Drawing.Rectangle(x, y, newSize, newSize), System.Drawing.GraphicsUnit.Pixel);

            graphics.Dispose();
            graphics = null;

            bitmap.Dispose();
            bitmap = null;

            //bitmapOriginal.Dispose();

            return bitmap_cut;
        }

        /// <summary>
        /// 生成指定宽高的缩略图,当原图比例严重失衡时缩略图将只截取原图的一部分
        /// </summary>
        /// <param name="bitmapOriginal"></param>
        /// <param name="width"></param>
        /// <param name="height"></param>
        /// <returns></returns>
        public static Bitmap Resize(Bitmap bitmapOriginal, int width, int height)
        {
            int originalWidth = bitmapOriginal.Width;
            int originalHeight = bitmapOriginal.Height;


            double Standard = Convert.ToDouble(width) / Convert.ToDouble(height);
            double ActualRate = Convert.ToDouble(originalWidth) / Convert.ToDouble(originalHeight);

            int imgNewWidth = width;
            int imgNewHeight = height;

            int x = 0;
            int y = 0;

            if (ActualRate > Standard)
            {
                imgNewWidth = originalWidth * height / originalHeight;
                x = (imgNewWidth - width) / 2;
            }
            else
            {
                imgNewHeight = originalHeight * width / originalWidth;
                y = (imgNewHeight - height) / 2;
            }

            Bitmap bitmap = new Bitmap(imgNewWidth, imgNewHeight);
            Graphics graphics = Graphics.FromImage(bitmap);
            graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
            graphics.SmoothingMode = SmoothingMode.HighQuality;
            graphics.CompositingQuality = CompositingQuality.HighQuality;
            graphics.Clear(Color.Transparent);
            graphics.DrawImage(bitmapOriginal, new Rectangle(0, 0, imgNewWidth, imgNewHeight));

            Bitmap bitmap_cut = new Bitmap(width, height);
            graphics = Graphics.FromImage(bitmap_cut);
            graphics.DrawImage(bitmap, new System.Drawing.Rectangle(0, 0, width, height), new System.Drawing.Rectangle(x, y, width, height), System.Drawing.GraphicsUnit.Pixel);

            graphics.Dispose();
            graphics = null;
            bitmap.Dispose();
            bitmap = null;
            //bitmapOriginal.Dispose();

            return bitmap_cut;
        }
        
        /// <summary>
        /// 生成指定宽度的缩略图,高度将等比缩放
        /// </summary>
        /// <param name="bitmapOriginal"></param>
        /// <param name="width"></param>
        /// <returns></returns>
        public static Bitmap ResizeW(Bitmap bitmapOriginal, int width)
        {
            int originalWidth = bitmapOriginal.Width;
            int originalHeight = bitmapOriginal.Height;

            int height = 0;

            if (originalWidth < width)
            {
                width = originalWidth;
                height = originalHeight;
            }
            else
            {
                height = originalHeight * width / originalWidth;
            }

            Bitmap bitmap = new Bitmap(width, height);
            Graphics graphics = Graphics.FromImage(bitmap);
            graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
            graphics.SmoothingMode = SmoothingMode.HighQuality;
            graphics.CompositingQuality = CompositingQuality.HighQuality;
            graphics.Clear(Color.Transparent);
            graphics.DrawImage(bitmapOriginal, new Rectangle(0, 0, width, height));

            graphics.Dispose();
            graphics = null;

            return bitmap;
        }

        /// <summary>
        /// 生成指定高度的缩略图,宽度将等比缩放
        /// </summary>
        /// <param name="bitmapOriginal"></param>
        /// <param name="height"></param>
        /// <returns></returns>
        public static Bitmap ResizeH(Bitmap bitmapOriginal, int height)
        {
            int originalWidth = bitmapOriginal.Width;
            int originalHeight = bitmapOriginal.Height;

            int width = 0;

            if (originalHeight < height)
            {
                width = originalWidth;
                height = originalHeight;
            }
            else
            {
                width = originalWidth * height / originalHeight;
            }

            Bitmap bitmap = new Bitmap(width, height);
            Graphics graphics = Graphics.FromImage(bitmap);
            graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
            graphics.SmoothingMode = SmoothingMode.HighQuality;
            graphics.CompositingQuality = CompositingQuality.HighQuality;
            graphics.Clear(Color.Transparent);
            graphics.DrawImage(bitmapOriginal, new Rectangle(0, 0, width, height));

            graphics.Dispose();
            graphics = null;

            return bitmap;
        }

        /// <summary>
        /// 裁切原图指定区域
        /// </summary>
        /// <param name="bitmapOriginal"></param>
        /// <param name="width"></param>
        /// <param name="height"></param>
        /// <param name="left"></param>
        /// <param name="top"></param>
        /// <returns></returns>
        public static Bitmap Crop(Bitmap bitmapOriginal, int width, int height, int left, int top)
        {
            int originalWidth = bitmapOriginal.Width;
            int originalHeight = bitmapOriginal.Height;

            double Standard = Convert.ToDouble(width) / Convert.ToDouble(height);
            double ActualRate = Convert.ToDouble(originalWidth) / Convert.ToDouble(originalHeight);

            int imgNewWidth = width;
            int imgNewHeight = height;

            int x = 0;
            int y = 0;

            if (ActualRate > Standard)
            {
                imgNewWidth = originalWidth * height / originalHeight;
                if (left == -1)
                {
                    x = (imgNewWidth - width) / 2;
                }
                else
                {
                    if (left > (imgNewWidth - width) / 2)
                    {
                        x = (imgNewWidth - width) / 2;
                    }
                    else
                    {
                        x = left;
                    }
                }
            }
            else
            {
                imgNewHeight = originalHeight * width / originalWidth;
                if (top == -1)
                {
                    y = (imgNewHeight - height) / 2;
                }
                else
                {
                    if (top > (imgNewHeight - height) / 2)
                    {
                        y = (imgNewHeight - height) / 2;
                    }
                    else
                    {
                        y = top;
                    }
                }
            }

            Bitmap bitmap = new Bitmap(imgNewWidth, imgNewHeight);
            Graphics graphics = Graphics.FromImage(bitmap);

            graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
            graphics.SmoothingMode = SmoothingMode.HighQuality;
            graphics.CompositingQuality = CompositingQuality.HighQuality;
            graphics.Clear(Color.Transparent);
            graphics.DrawImage(bitmapOriginal, new Rectangle(0, 0, imgNewWidth, imgNewHeight));


            Bitmap bitmap_cut = new Bitmap(width, height);
            graphics = Graphics.FromImage(bitmap_cut);
            graphics.DrawImage(bitmap, new System.Drawing.Rectangle(0, 0, width, height), new System.Drawing.Rectangle(x, y, width, height), System.Drawing.GraphicsUnit.Pixel);

            graphics.Dispose();
            graphics = null;

            bitmap.Dispose();
            bitmap = null;


            //bitmapOriginal.Dispose();

            return bitmap_cut;
        }

    }
}

3.缩略图的存取策略

      原图在上传的同时会生成150 * 150200 * 200800 * X三种不同规格的缩略图文件并存储在项目的Images目录之下,为了提高图片的读取速度,三张缩略图在生成的同时就被放入了缓存当中。这里是把图片放入本地缓存,当然如果实际项目图片较多占用的内存较大,可考虑memcache等第三方分布式缓存策略。以下是核心代码

ManageController.cs

          //要生成缩略图的保存路径,默认生成800 * x,150 * 150,200 * 200 的三种缩略图
            string w800_ImgName = string.Format("{0}_w_800{1}", upLoadFileInfo.FileID, upLoadFileInfo.FileExt);
            string size150_ImgName = string.Format("{0}_s_150{1}", upLoadFileInfo.FileID, upLoadFileInfo.FileExt);
            string size200_ImgName = string.Format("{0}_s_200{1}", upLoadFileInfo.FileID, upLoadFileInfo.FileExt);

            string image800_SavePath = string.Format("~/images/{0}", w800_ImgName);
            string image150_SavePath = string.Format("~/images/{0}", size150_ImgName);
            string image200_SavePath = string.Format("~/images/{0}", size200_ImgName);

            string image800_SavePhysicPath = Server.MapPath(image800_SavePath);
            string image150_SavePhysicPath = Server.MapPath(image150_SavePath);
            string image200_SavePhysicPath = Server.MapPath(image200_SavePath);

            //生成宽为800的缩略图并保存
            Bitmap bitmap_800 = ImageHelper.ResizeW(new Bitmap(upLoadFileInfo.PostFile.InputStream), 800);
            DB.ThumbnailImages.Add(new ThumbnailImage {
                OriginalID = upLoadFileInfo.FileID,
                ID = w800_ImgName,
                Ext = upLoadFileInfo.FileExt,
                Path = image800_SavePath,
                Height = bitmap_800.Height,
                Width = bitmap_800.Width,
                Description = Description
            });
            ImageHelper.SaveImage(bitmap_800, image800_SavePhysicPath);
            //将图片存入缓存
            ImageCacheHelper.SetImageCahce(w800_ImgName, ImageHelper.GetImageBytes(image800_SavePhysicPath));


            //生成宽高为150的缩略图并保存
            Bitmap bitmap_150 = ImageHelper.Resize(new Bitmap(upLoadFileInfo.PostFile.InputStream), 150);
            DB.ThumbnailImages.Add(new ThumbnailImage
            {
                OriginalID = upLoadFileInfo.FileID,
                ID = size150_ImgName,
                Ext = upLoadFileInfo.FileExt,
                Path = image150_SavePath,
                Height = bitmap_150.Height,
                Width = bitmap_150.Width,
                Description = Description
            });
            ImageHelper.SaveImage(bitmap_150, image150_SavePhysicPath);
            //将图片存入缓存
            ImageCacheHelper.SetImageCahce(size150_ImgName, ImageHelper.GetImageBytes(image150_SavePhysicPath));
            

            //生成宽高为200的缩略图并保存
            Bitmap bitmap_200 = ImageHelper.Resize(new Bitmap(upLoadFileInfo.PostFile.InputStream), 200);
            DB.ThumbnailImages.Add(new ThumbnailImage
            {
                OriginalID = upLoadFileInfo.FileID,
                ID = size200_ImgName,
                Ext = upLoadFileInfo.FileExt,
                Path = image200_SavePath,
                Height = bitmap_200.Height,
                Width = bitmap_200.Width,
                Description = Description
            });
            DB.SaveChanges();
            ImageHelper.SaveImage(bitmap_200, image200_SavePhysicPath);
            //将图片存入缓存
            ImageCacheHelper.SetImageCahce(size200_ImgName, ImageHelper.GetImageBytes(image200_SavePhysicPath));

4.缩略图访问服务。来看下面几个地址

http://www.caidian.com/Thumbnail/d92fe65928bc48a8b9ac47d771dad56e_s_200.jpg

http://www.caidian.com/Thumbnail/d92fe65928bc48a8b9ac47d771dad56e_w_800.jpg

想必大家都见到过类似的图片地址,其实地址中隐含了图片的生成规则和尺寸。d92fe65928bc48a8b9ac47d771dad56e_s_200.jpg里面的s表示生成规则,200表示要生成的尺寸大小。我这里的实现方式是,当用户访问类似地址的时候其实是访问了ServiceController控制器中名为Thumbnail的Action,而d92fe65928bc48a8b9ac47d771dad56e_s_200.jpg则是传递给Action的参数,这样还有一个好处就是图片访问有了一个统一的处理入口,如果你访问的图片被删除或者地址错误我们可以返回一个默认图片,再也不用担心会出一个红叉了,具体还是来看实现代码。

ServiceController.cs

View Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

using ImgService.Helpers;
using System.Text.RegularExpressions;
using ImgService.EF;
using ImgService.EF.Domain;
using System.Drawing;
using System.IO;

namespace ImgService.Controllers
{
    public class ServiceController : Controller
    {
        private ImgServiceContext DB = new ImgServiceContext();
  
        public FileResult Thumbnail(string key = "")
        { 
          //1.从缓存提取图片
          //2.缓存没有,读取缩略图文件
          //3.缩略图不存在,读取图片源文件。生成缩略图,加入缓存
            key = key.ToLower();
            if (string.IsNullOrEmpty(key)) { return null; }
            byte[] bytes = null;
          
            //正则分析Key的内容,可匹配的格式如 43sdfwerw345343ff_s_150.jpg 或 43sdfwerw345343ff_w_800.jpg 等
            string rule = @"(?<p1>([a-z0-9]+)_(s|w)_([\d]+)\.(jpg|png|bmp|tif|tiff))";

            Match match = Regex.Match(key, rule, RegexOptions.IgnoreCase);
            if (match.Success)
            {
                string type = ""; //s或w
                int imgSize = 200;
                Bitmap originalBitmap = null;
                Bitmap thumbnaiBitmap = null;
                MemoryStream ms = null;

                type = match.Groups[2].Value.Trim().ToLower();
                int.TryParse(match.Groups[3].Value, out imgSize);

                //确认请求的缩略图是否在系统生成范围之内,验证合法性
                ThumbnailImage thumbnailData = GetThumbnail(key);
                if (thumbnailData != null)
                {
                    //查询缓存
                    bytes = ImageCacheHelper.GetImageCache(key);
                    if (bytes != null && bytes.Length > 0)
                    {
                        return File(bytes, "image/jpeg");
                    }

                    //缓存不存在,从文件读取缩略图
                    bytes = ImageHelper.GetImageBytes(Server.MapPath(thumbnailData.Path));
                    if (bytes != null && bytes.Length > 0) //缩略图文件存在
                    {
                        ImageCacheHelper.SetImageCahce(key, bytes);
                        return File(bytes, "image/jpeg");
                    }
                    else  //缩略图不存在,读原图生成缩略图
                    {
                        bytes = ImageHelper.GetImageBytes(Server.MapPath( thumbnailData.OriginalImage.Path ));
                        if (bytes != null && bytes.Length > 0) //原图存在
                        {
                            ms = new MemoryStream(bytes);
                            originalBitmap = new Bitmap(ms);

                            if (type == "s")
                            {
                                thumbnaiBitmap = ImageHelper.Resize(originalBitmap, imgSize);//生成缩略图
                            }

                            if (type == "w")
                            {
                                thumbnaiBitmap = ImageHelper.ResizeW(originalBitmap, imgSize);//生成缩略图
                            }

                            thumbnaiBitmap.Save(Server.MapPath(thumbnailData.Path)); //保存缩略图
                            bytes = ImageHelper.GetImageBytes(Server.MapPath(thumbnailData.Path));
                            ImageCacheHelper.SetImageCahce(key, bytes);//把缩略图放入缓存

                            ms.Dispose();
                            originalBitmap.Dispose();
                            thumbnaiBitmap.Dispose();
                            return File(bytes, "image/jpeg");

                        }
                        else  //原图不存在
                        {
                            return File(Server.MapPath("~/404.jpg"), "image/jpeg");
                        }
                    }
                }
                else
                {
                    return File(Server.MapPath("~/404.jpg"), "image/jpeg");
                }
            }
            else
            {
                return File(Server.MapPath("~/404.jpg"), "image/jpeg");
            }
        }

        private ThumbnailImage GetThumbnail(string key)
        {
            var q = from t in DB.ThumbnailImages
                    where
                        t.ID == key
                    select t;
            var imgList = q.ToList();
            if (imgList == null || imgList.Count == 0)
            {
                return null;
            }
            return imgList.First();
        }
    }
}

5.自定义路由规则,让缩略图地址更友好

如果没有自定义路由规则的话,那么缩略图的访问地址应该类似这样。http://www.caidian.com/Thumbnail?key=d92fe65928bc48a8b9ac47d771dad56e_s_200.jpg

这样的地址给人的第一印象会让人觉着不是一个纯粹的图片地址,所以我对它进行了路由规则的重写,来看看代码

Global.asax.cs

public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            //为缩略图的访问配置一个比较友好的地址,访问的地址一般如下
            //http://localhost:1087/Thumbnail/d92fe65928bc48a8b9ac47d771dad56e_s_200.jpg
            //http://localhost:1087/Thumbnail/d92fe65928bc48a8b9ac47d771dad56e_w_800.jpg
            routes.MapRoute(
                "ImgService", // Route name
                "thumbnail/{key}", // URL with parameters
                new { controller = "Service", action = "Thumbnail", key = "" } // Parameter defaults
            );

            //照片列表,访问的地址一般如下
            //http://localhost:1087/photos
            routes.MapRoute(
                "Client-photos", // Route name
                "photos", // URL with parameters
                new { controller = "Client", action = "Index"} // Parameter defaults
            );

            //单张照片,访问的地址一般如下
            //http://localhost:1087/photo/61e69dc5a92940ad86d9d419c496324d
            routes.MapRoute(
                "Client-photo", // Route name
                "photo/{key}", // URL with parameters
                new { controller = "Client", action = "Photo", key = "" } // Parameter defaults
            );

            //这里请注意,默认的路由配置放在自定义配置后面,否则不认自定义路由规则
            routes.MapRoute(
                "Default", // Route name
                "{controller}/{action}/{id}", // URL with parameters
                new { controller = "Manage", action = "Index", id = UrlParameter.Optional } // Parameter defaults
            ); 
        }

6.关于如何运行程序的注意事项

下载源码,然后修改web.config中的数据库连接字符串,依你个人的环境而定。

<connectionStrings>
    <add name="ImgServiceDB" connectionString="server=.;uid=sa;pwd=123456;database=ImgServiceDB" providerName="System.Data.SqlClient"/>
  </connectionStrings>

接下来就是运行程序了,会自动的跳转到照片上传页面,由于里面没有任何照片数据所以你必须先进行上传,接下来就是看照片了... ...很简单

当然本项目只是一个很简单的范例程序,真正要开发一套大数据量,高并发的图片分享网站光有这点知识是远远不够的,我在这里也只是算抛砖引玉吧。

最后,感谢你了浏览到这里,如果你觉得有点收获的话不妨点击推荐。

原文地址:https://www.cnblogs.com/huangzelin/p/2667500.html