Asp.Net DDD架构浅谈——图片上传、缩略裁剪
本系列目录
Asp.Net DDD架构浅谈——整体框架说明
Asp.Net DDD架构浅谈——领域划分、仓储应用、Services层定义
Asp.Net DDD架构浅谈——图片上传、缩略裁剪
Asp.Net DDD架构浅谈——依赖注入Autofac
Asp.Net DDD架构浅谈——网站配置
任何一个网站都离不开图片上传、缩略裁剪,可以想到的场景就很多:
- 用户头像
- 文章封面图,富文本图片
- 广告图片,幻灯片图片
等等,能用到的实在是太多了,所以一个好的图片处理流程是很重要的,它适用于几乎所有的网站。
图片上传
图片上传的插件已经很成熟,能找到的就很多,比如说webuploader、uploadify、fileupload等。我们要对这些插件进行筛选,就要先确定我们的要求:
- 支持Html5,支持移动端,因此不能是Flash。
- 有上传进度回调函数,这样可以显示进度条,用户友好度提高。
- 免费
综合考虑,我在项目中选中了fileupload。看下项目中的代码:
@using System.Web.Mvc
@using Steven.Domain.Enums
@using Steven.Web.Framework.Extensions
@helper SingleUpload(WebViewPage wvp, TableSource src, string btnId, string imgId, string hdId, long imgAttaId = 0, int width = 100, int height = 100)
{
<div class="row" id="row_upload_@btnId">
<input type="hidden" name="@hdId" id="@hdId" value="@imgAttaId" />
<div class="col-md-4">
<span class="btn btn-success fileinput-button">
<span>上传图片</span>
<input type="file" accept="image/*" name="Filedata" id="@btnId">
</span>
</div>
<div class="col-md-6">
<div class="text-center">
<span class="thumbnail">
<img id="@imgId" src="@wvp.Url.ThumbUrl(imgAttaId,width,height,"/Res/shop/img/img_icon.png")" width="@width" height="@height" alt="" />
</span>
</div>
<div class="progress mt10" @(imgAttaId == 0 ? "" : "hidden")>
<div style="width: 00%" aria-valuemax="100" aria-valuemin="0" aria-valuenow="0" role="progressbar" class="progress-bar">
</div>
</div>
</div>
</div>
<script>
$(function () {
var $row = $('#row_upload_@btnId');
$('#@btnId').fileupload({
url: '@wvp.Html.Raw(wvp.Url.Action("BatchUpload", "Attachment", new { src, width, height }))',
dataType: 'json',
add: function (e, data) {
//判断文件类型
var acceptFileTypes = /^image\/(gif|jpe?g|png)$/i;
//文件类型判断
if (data.files[0]['type'].length && !acceptFileTypes.test(data.files[0]['type'])) {
showErrorMsg('请上传图片!');
return;
}
$row.find('.progress-bar').css('width', '0%');
$row.find('.progress').show();
//预览
if (data.files && data.files[0]) {
var reader = new FileReader();
reader.onload = function (e) {
$('#@imgId').attr('src', e.target.result);
}
reader.readAsDataURL(data.files[0]);
}
data.submit();
},
progress: function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
$row.find('.progress-bar').css('width', progress + '%');
},
done: function (e, data) {
var json = data.result;
$('#@imgId').attr('src', json.imgPath);
$('#@hdId').val(json.id);
$row.find('.progress').hide();
}
});
});
</script>
}
在add函数里面,我们添加了预览,并且把进度条设为0.
在progress函数里面,我们实时更新上传进度。
在done函数里面,更新图片链接,以及附件的id值,并且隐藏进度条,看下实际效果:
上传图片.gif
图片裁剪
在项目中,在UI设计师设计好的页面中,会对图片的大小尺寸有要求,而后台编辑人员上传图片的时候,不可能先按规定裁剪好再上传图片,这样会导致大量的工作量,所以裁剪的过程就需要程序来处理。稍微总结了下优点如下:
- 图片统一尺寸,保持页面整齐。
- 控制图片大小,图片太大时网页加载会很慢。
图片裁剪有很多种形式,最常用的是一下三种:
- Crop,根据缩略图的尺寸截取原图
- Fit,按比例缩放,自动调整尺寸
-
Pad,按比例缩放,保持尺寸,不足部分填白
假设我们要上传一张图片,尺寸是1920x640的图片,如下图所示:
少年营.jpg
现在有一个业务是需要在前台展示500x500的图片,那么三种裁剪方式就不一样了,而具体要那种效果,需要和设计师讨论下。
少年营-500x500-Crop
少年营-500x167-Fit
少年营-500x500-Pad
图片链接 & 生成缩略图时机
一开始我是这么处理的,把图片的物理路径做Base64处理,然后加上参数:裁剪方式,大小等,生成一个图片路劲,如:
http://img.hielites.com//Thumbnail/ZGVmYXVsdFwyMDE3XzA0XDE4XDE2MTYzMTMwNzU3NDM3LmpwZw2/Crop/1920x640
其中ZGVmYXVsdFwyMDE3XzA0XDE4XDE2MTYzMTMwNzU3NDM3LmpwZw2是图片路劲的Base64编码,Crop是裁剪方式,1920x640是缩略图的尺寸。
然后在用户打开网页,访问这个链接的时候,会判断,是否已经存在缩略图,如果不存在,则创建缩略图;如果已经存在,则返回图片,代码如下:
[OutputCache(CacheProfile = "FileCache")]
public ActionResult Thumb(string size, ThumbnailMethod mode, int q, string fn, WaterMarkingPosition? position)
{
Response.Cache.SetOmitVaryStar(true);
//Base64解码
var filePath = StringUtility.XBase64Decode(fn);
var arrSize = size.ToLower().Split('x');
var dirRoot = _attSvc.GetFullPath($@"thumb\{mode}-{size}-{position}");
//拼接缩略图路径
var filePathAbs = Path.Combine(dirRoot, filePath);
var dirPath = Path.GetDirectoryName(filePathAbs);
if (string.IsNullOrEmpty(dirPath))
{
return HttpNotFound("-File Not Found-");
}
if (!Directory.Exists(dirPath))
{
Directory.CreateDirectory(dirPath);
}
var fileExt = Path.GetExtension(filePathAbs);
//判断缩略图片是否存在
if (!System.IO.File.Exists(filePathAbs))
{
var iThumb = new ImgThumbUtillity()
{
QualityLevel = q
};
var filePathSrc = _attSvc.GetFullPath(filePath);
iThumb.MakeThumbnail(filePathSrc, filePathAbs, fileExt, StringUtility.ConvertToInt(arrSize[0]), StringUtility.ConvertToInt(arrSize[1]), mode, position, _configRep.WaterMarkingPath);
}
var fileContentType = _attSvc.GetFileContentType(fileExt);
return File(filePathAbs, fileContentType);
}
再用了一段时间后,发现会出一些问题:
- 图片链接并不是以.jpg/.png等图片扩展名结尾的,对浏览器来说并不友好。
- 用户访问时再去生成缩略图,第一个打开的用户会很慢。
- 图片是放在OutputCache缓存中的,导致服务器的图片站点占用内存偏大。
为了解决这些问题,改进流程,做了如下处理
- 先生成缩略图,再给链接,解决了第一个用户打开慢的问题。
- 直接给静态图片路劲,解决了图片地址不以.jpg/.png结尾,以及图片站内存占用偏大的问题。
改进后的图片路径变为http://www.beilinsoft.com/UploadFiles/Thumb/Crop_540x360/default/2017_09/6/1607471540151200.jpg
其中,Crop表示裁剪方式,540x360表示缩略图尺寸。
代码如下:
public string GetPicUrl(long attaId, int width = 100, int height = 100, ThumMode mode = ThumMode.Crop, int quality = 100, WaterMarkingPosition? waterPosition = null)
{
var atta = AttachmentRepository.Get(attaId);
if (atta == null)
{
return null;
}
//图片路径
var thumbPath = Path.Combine(SysConfigRepository.UploadThumbDirectory,
$"{mode}_{width}x{height}",
atta.FilePath);
var thumbFullPath = GetFullPath(thumbPath);
//判断图片是否存在
if (!File.Exists(thumbFullPath))
{
var originalFullPath = GetFullPath(atta.FilePath);
if (!File.Exists(originalFullPath))
{
return null;
}
using (var originalImage = new ImageFactory(true))
{
ResizeMode resizeMode = GetRezieMode(mode);
var resizeLayer = new ResizeLayer(new Size(width, height), resizeMode);
originalImage.Load(originalFullPath)
.Resize(resizeLayer);
AddWatermark(waterPosition, originalImage);
originalImage.BackgroundColor(Color.White)
.Quality(quality)
.Save(thumbFullPath);
}
}
return GetFileUrl(Path.Combine(SysConfigRepository.UploadRootDirectory, thumbPath));
}
还有一个需要补充的是,需要在图片站创建一个虚拟目录,指向图片实际存储的地址:
虚拟目录
虚拟目录编辑
这样我们可以建立多个图片站,来分撒服务器压力。