回到目錄html
在大叔框架裏總以爲缺點什麼,在最近的項目開發中,終於知道缺什麼了,分佈式文件存儲組件,就是缺它,呵呵,對於分佈式文件存儲來講,業界比較公認的是FastDFS組件,它本身自己就是集羣機制,有本身的路由選擇和文件存儲兩個部分,咱們經過FastDFS的客戶端進行上傳後,它會返回一個在FastDFS上存儲的路徑,這固然是IO路徑,咱們只要在服務器上開個Http服務器,就能夠以Http的方法訪問你的文件了。前端
前端上傳控件(表單方式,swf方式,js方法都可)將文件流傳給咱們的FastDFS客戶端,經過客戶端與服務端創建Socket鏈接,將數據包發給FastDFS服務端並等待返回,上傳成功後返回路徑,咱們能夠對路徑進行HTTP的處理,並存入數據庫nginx
原圖像地址: http://www.fastdfs.com/demo/pictruename.jpg數據庫
指定尺寸的圖像地址:http://www.fastdfs.com/demo/pictruename_100x100.jpg服務器
1 一個接口,定義三種上傳規格,普通文件,圖像文件和視頻文件(通常須要對它進行截力)網絡
public interface IFileUploader { /// <summary> /// 上傳視頻文件 /// </summary> /// <param name="param"></param> /// <returns></returns> VideoUploadResult UploadVideo(VideoUploadParameter param); /// <summary> /// 上傳普通文件 /// </summary> /// <param name="filePath"></param> /// <returns></returns> FileUploadResult UploadFile(FileUploadParameter param); /// <summary> /// 上傳圖片 /// </summary> /// <param name="param"></param> /// <returns></returns> /// <remarks>Update:cyr(Ben) 20150317</remarks> ImageUploadResult UploadImage(ImageUploadParameter param); }
2 一批方法參數,包括了文件,圖像和視頻等框架
/// <summary> /// 文件上傳參數基類 /// </summary> public abstract class UploadParameterBase { public UploadParameterBase() { MaxSize = 1024 * 1024 * 8; } /// <summary> /// 前一次上傳時生成的服務器端文件名,若是須要斷點續傳,需傳入此文件名 /// </summary> public string ServiceFileName { get; set; } /// <summary> /// 文件流 /// </summary> public Stream Stream { get; set; } /// <summary> /// 文件名 /// </summary> public string FileName { get; set; } /// <summary> /// 文件大小限制(單位bit 默認1M) /// </summary> public int MaxSize { get; protected set; } /// <summary> /// 上傳文件類型限制 /// </summary> public string[] FilenameExtension { get; set; } }
/// <summary> /// 圖片上傳參數對象 /// </summary> public class ImageUploadParameter : UploadParameterBase { /// <summary> /// 構造方法 /// </summary> /// <param name="stream"></param> /// <param name="fileName"></param> /// <param name="filenameExtension">默認支持經常使用圖片格式</param> /// <param name="maxSize"></param> public ImageUploadParameter(Stream stream, string fileName, string[] filenameExtension = null, int maxSize = 3) { base.Stream = stream; base.FileName = fileName; base.MaxSize = maxSize; base.FilenameExtension = filenameExtension ?? new string[] { ".jpeg", ".jpg", ".gif", ".png" }; ; } /// <summary> /// 構造方法 /// </summary> /// <param name="stream"></param> /// <param name="fileName"></param> /// <param name="maxSize">單位爲M</param> public ImageUploadParameter(Stream stream, string fileName, int maxSize) : this(stream, fileName, null, maxSize) { } }
3 一批返回類型,包括對文件,圖像和視頻等方法的返回數據的定義分佈式
/// <summary> /// 上傳文件返回對象基類 /// </summary> public abstract class UploadResultBase { /// <summary> /// 返回文件地址 /// </summary> public string FilePath { get; set; } /// <summary> /// 錯誤消息列表 /// </summary> public string ErrorMessage { get; set; } /// <summary> /// 是否上傳成功 /// </summary> public bool IsValid { get { return string.IsNullOrWhiteSpace(ErrorMessage); } } }
/// <summary> /// 視頻上傳返回對象 /// </summary> public class VideoUploadResult : UploadResultBase { /// <summary> /// 上傳的視頻截圖地址 /// </summary> public List<string> ScreenshotPaths { get; set; } /// <summary> /// 上傳狀態 /// </summary> public UploadStatus UploadStatus { get; set; } public VideoUploadResult() { ScreenshotPaths = new List<string>(); } /// <summary> /// 把VideoPath和ScreenshotPaths拼起來 以豎線(|)隔開 /// </summary> /// <returns></returns> public override string ToString() { StringBuilder sb = new StringBuilder(); sb.Append(FilePath); foreach (var item in ScreenshotPaths) { sb.Append("|" + item); } return sb.ToString(); } }
4 一個使用FastDFS實現的文件上傳實現類ide
/// <summary> /// 使用fastDFS完成文件上傳 /// </summary> internal class FastDFSUploader : IFileUploader { /// <summary> /// 目錄名,須要提早在fastDFS上創建 /// </summary> public string DFSGroupName { get { return "tsingda"; } } /// <summary> /// FastDFS結點 /// </summary> public StorageNode Node { get; private set; } /// <summary> /// 服務器地址 /// </summary> public string Host { get; private set; } /// <summary> /// 失敗次數 /// </summary> protected int FaildCount { get; set; } public int MaxFaildCount { get; set; } public FastDFSUploader() { InitStorageNode(); MaxFaildCount = 3; } #region Private Methods private void InitStorageNode() { Node = FastDFSClient.GetStorageNode(DFSGroupName); Host = Node.EndPoint.Address.ToString(); } private List<string> CreateImagePath(string fileName) { List<string> pathList = new List<string>(); string snapshotPath = ""; //視頻截圖 List<string> localList = new VideoSnapshoter().GetVideoSnapshots(fileName, out snapshotPath); foreach (var item in localList) { string aImage = SmallFileUpload(item); pathList.Add(aImage); } //清除本地多餘的圖片,有的視頻截取的圖片多,有的視頻截取的圖片少 string[] strArr = Directory.GetFiles(snapshotPath); try { foreach (var strpath in strArr) { File.Delete(strpath); } Directory.Delete(snapshotPath); } catch (Exception ex) { Logger.Core.LoggerFactory.Instance.Logger_Info("刪除圖片截圖異常" + ex.Message); } return pathList; } private string SmallFileUpload(string filePath) { if (string.IsNullOrEmpty(filePath)) throw new ArgumentNullException("filePath 參數不能爲空"); if (!File.Exists(filePath)) throw new Exception("上傳的文件不存在"); byte[] content; using (FileStream streamUpload = new FileStream(filePath, FileMode.Open, FileAccess.Read)) { using (BinaryReader reader = new BinaryReader(streamUpload)) { content = reader.ReadBytes((int)streamUpload.Length); } } string shortName = FastDFSClient.UploadFile(Node, content, "png"); return GetFormatUrl(shortName); } /// <summary> /// 文件分塊上傳,適合大文件 /// </summary> /// <param name="file"></param> /// <returns></returns> private string MultipartUpload(UploadParameterBase param) { Stream stream = param.Stream; if (stream == null) throw new ArgumentNullException("stream參數不能爲空"); int size = 1024 * 1024; byte[] content = new byte[size]; Stream streamUpload = stream; // 第一個數據包上傳或獲取已上傳的位置 string ext = param.FileName.Substring(param.FileName.LastIndexOf('.') + 1); streamUpload.Read(content, 0, size); string shortName = FastDFSClient.UploadAppenderFile(Node, content, ext); BeginUploadPart(stream, shortName); return CompleteUpload(stream, shortName); } /// <summary> /// 斷點續傳 /// </summary> /// <param name="stream"></param> /// <param name="serverShortName"></param> private void ContinueUploadPart(Stream stream, string serverShortName) { var serviceFile = FastDFSClient.GetFileInfo(Node, serverShortName); stream.Seek(serviceFile.FileSize, SeekOrigin.Begin); BeginUploadPart(stream, serverShortName); } /// <summary> /// 從指定位置開始上傳文件 /// </summary> /// <param name="stream"></param> /// <param name="beginOffset"></param> /// <param name="serverShortName"></param> private void BeginUploadPart(Stream stream, string serverShortName) { try { int size = 1024 * 1024; byte[] content = new byte[size]; while (stream.Position < stream.Length) { stream.Read(content, 0, size); var result = FastDFSClient.AppendFile(DFSGroupName, serverShortName, content); if (result.Length == 0) { FaildCount = 0; continue; } } } catch (Exception ex) { Logger.Core.LoggerFactory.Instance.Logger_Info("上傳文件中斷!" + ex.Message); if (NetCheck()) { //重試 if (FaildCount < MaxFaildCount) { FaildCount++; InitStorageNode(); ContinueUploadPart(stream, serverShortName); } else { Logger.Core.LoggerFactory.Instance.Logger_Info("已達到失敗重試次數仍沒有上傳成功"); ; throw ex; } } else { Logger.Core.LoggerFactory.Instance.Logger_Info("當前網絡不可用"); throw ex; } } } /// <summary> /// 網絡可用爲True,不然爲False /// </summary> /// <returns></returns> private bool NetCheck() { return NetworkInterface.GetIsNetworkAvailable(); } /// <summary> /// 拼接Url /// </summary> /// <param name="shortName"></param> /// <returns></returns> private string GetFormatUrl(string shortName) { return string.Format("http://{0}/{1}/{2}", Host, DFSGroupName, shortName); } private string CompleteUpload(Stream stream, string shortName) { stream.Close(); return GetFormatUrl(shortName); } private string GetShortNameFromUrl(string url) { if (string.IsNullOrEmpty(url)) return string.Empty; Uri uri = new Uri(url); string urlFirstPart = string.Format("http://{0}/{1}/", Host, DFSGroupName); if (!url.StartsWith(urlFirstPart)) return string.Empty; return url.Substring(urlFirstPart.Length); } #endregion #region IFileUploader 成員 /// <summary> /// 上傳視頻 /// </summary> /// <param name="param"></param> /// <returns></returns> public VideoUploadResult UploadVideo(VideoUploadParameter param) { VideoUploadResult result = new VideoUploadResult(); string fileName = MultipartUpload(param); if (param.IsScreenshot) { result.ScreenshotPaths = CreateImagePath(fileName); } result.FilePath = fileName; return result; } /// <summary> /// 上傳普通文件 /// </summary> /// <param name="param"></param> /// <returns></returns> public FileUploadResult UploadFile(FileUploadParameter param) { var result = new FileUploadResult(); try { string fileName = MultipartUpload(param); result.FilePath = fileName; } catch (Exception ex) { result.ErrorMessage = ex.Message; } return result; } /// <summary> /// 上傳圖片 /// </summary> /// <param name="param"></param> /// <param name="message"></param> /// <returns></returns> public ImageUploadResult UploadImage(ImageUploadParameter param) { byte[] content; string shortName = ""; string ext = System.IO.Path.GetExtension(param.FileName).ToLower(); if (param.FilenameExtension != null && param.FilenameExtension.Contains(ext)) { if (param.Stream.Length > param.MaxSize) { return new ImageUploadResult { ErrorMessage = "圖片大小超過指定大小" + param.MaxSize / 1048576 + "M,請從新選擇", FilePath = shortName }; } else { using (BinaryReader reader = new BinaryReader(param.Stream)) { content = reader.ReadBytes((int)param.Stream.Length); } shortName = FastDFSClient.UploadFile(Node, content, ext.Contains('.') ? ext.Substring(1) : ext); } } else { return new ImageUploadResult { ErrorMessage = "文件類型不匹配", FilePath = shortName }; } return new ImageUploadResult { FilePath = CompleteUpload(param.Stream, shortName), }; } #endregion }
5 一個文件上傳的生產者,經典的單例模式的體現ui
/// <summary> /// 文件上傳生產者 /// </summary> public class FileUploaderFactory { /// <summary> /// 上傳實例 /// </summary> public readonly static IFileUploader Instance; private static object lockObj = new object(); static FileUploaderFactory() { if (Instance == null) { lock (lockObj) { Instance = new FastDFSUploader(); } } } }
6 前臺的文件上傳控件,你能夠隨便選擇了,它與前臺是解耦的,沒有什麼關係,用哪一種方法實現均可以,呵呵
[HttpPost] public ActionResult Index(FormCollection form) { var file = Request.Files[0]; var result = Project.FileUpload.FileUploaderFactory.Instance.UploadFile(new Project.FileUpload.Parameters.FileUploadParameter { FileName = file.FileName, Stream = file.InputStream, }); ViewBag.Path = result.FilePath; return View(); }
最後咱們看一下個人Project.FileUpload的完整結構
它隸屬於大叔的Project.Frameworks集合,咱們在這裏,對Project.FileUpload說一聲:Hello FileUpload,We wait for you for a long time...