zoukankan      html  css  js  c++  java
  • 我心中的核心组件(可插拔的AOP)~分布式文件上传组件~基于FastDFS

    回到目录

    一些概念

    在大叔框架里总觉得缺点什么,在最近的项目开发中,终于知道缺什么了,分布式文件存储组件,就是缺它,呵呵,对于分布式文件存储来说,业界比较公认的是FastDFS组件,它自己本身就是集群机制,有自己的路由选择和文件存储两个部分,我们通过FastDFS的客户端进行上传后,它会返回一个在FastDFS上存储的路径,这当然是IO路径,我们只要在服务器上开个Http服务器,就可以以Http的方法访问你的文件了。

    我的组件实现方式

    前端上传控件(表单方式,swf方式,js方法均可)将文件流传给我们的FastDFS客户端,通过客户端与服务端建立Socket连接,将数据包发给FastDFS服务端并等待返回,上传成功后返回路径,我们可以对路径进行HTTP的处理,并存入数据库

    fastDFS配合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实现的文件上传实现类

        /// <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 一个文件上传的生产者,经典的单例模式的体现

     /// <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...

     回到目录

  • 相关阅读:
    SEO优化---学会建立高转化率的网站关键词库
    从一个程序员的角度看——微信小应用
    当AngularJS POST方法碰上PHP
    angularJS(6)
    彻底解决显示Opencv中Mat图像到Mfc窗口问题
    数据结构与算法基础总结
    java类别问题
    java基础知识
    逻辑地址、线性地址、物理地址和虚拟地址的区别
    TCP协议中的三次握手和四次挥手(图解)
  • 原文地址:https://www.cnblogs.com/lori/p/4742269.html
Copyright © 2011-2022 走看看