我心中的核心组件(可插拔的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...

 本文转自博客园张占岭(仓储大叔)的博客,原文链接:我心中的核心组件(可插拔的AOP)~分布式文件上传组件~基于FastDFS,如需转载请自行联系原博主。

目录
相关文章
|
设计模式 安全 Java
【分布式技术专题】「Tomcat技术专题」 探索Tomcat技术架构设计模式的奥秘(Server和Service组件原理分析)
【分布式技术专题】「Tomcat技术专题」 探索Tomcat技术架构设计模式的奥秘(Server和Service组件原理分析)
326 0
|
10月前
|
NoSQL Java Redis
分布式锁—6.Redisson的同步器组件
Redisson提供了多种分布式同步工具,包括分布式锁、Semaphore和CountDownLatch。分布式锁包括可重入锁、公平锁、联锁、红锁和读写锁,适用于不同的并发控制场景。Semaphore允许多个线程同时获取锁,适用于资源池管理。CountDownLatch则用于线程间的同步,确保一组线程完成操作后再继续执行。Redisson通过Redis实现这些同步机制,提供了高可用性和高性能的分布式同步解决方案。源码剖析部分详细介绍了这些组件的初始化和操作流程,展示了Redisson如何利用Redis命令和
|
SpringCloudAlibaba JavaScript 前端开发
谷粒商城笔记+踩坑(2)——分布式组件、前端基础,nacos+feign+gateway+ES6+vue脚手架
分布式组件、nacos注册配置中心、openfegin远程调用、网关gateway、ES6脚本语言规范、vue、elementUI
谷粒商城笔记+踩坑(2)——分布式组件、前端基础,nacos+feign+gateway+ES6+vue脚手架
|
Java Spring
Spring的AOP组件详解
该文章主要介绍了Spring AOP(面向切面编程)组件的实现原理,包括Spring AOP的基础概念、动态代理模式、AOP组件的实现以及Spring选择JDK动态代理或CGLIB动态代理的依据。
Spring的AOP组件详解
|
监控 负载均衡 Java
(九)漫谈分布式之微服务组件篇:探索分布式环境下各核心组件的必要性!
本文将深入探讨微服务中各个组件的必要性,以此帮助各位更好地加深对分布式系统的掌握度。
1181 1
|
NoSQL 前端开发 Java
技术笔记:springboot分布式锁组件spring
技术笔记:springboot分布式锁组件spring
337 1
|
Windows
Windows系统下安装分布式事务组件Seata
Windows系统下安装分布式事务组件Seata
802 0
|
6月前
|
XML 安全 Java
使用 Spring 的 @Aspect 和 @Pointcut 注解简化面向方面的编程 (AOP)
面向方面编程(AOP)通过分离横切关注点,如日志、安全和事务,提升代码模块化与可维护性。Spring 提供了对 AOP 的强大支持,核心注解 `@Aspect` 和 `@Pointcut` 使得定义切面与切入点变得简洁直观。`@Aspect` 标记切面类,集中处理通用逻辑;`@Pointcut` 则通过表达式定义通知的应用位置,提高代码可读性与复用性。二者结合,使开发者能清晰划分业务逻辑与辅助功能,简化维护并提升系统灵活性。Spring AOP 借助代理机制实现运行时织入,与 Spring 容器无缝集成,支持依赖注入与声明式配置,是构建清晰、高内聚应用的理想选择。
673 0
|
5月前
|
监控 Java Spring
AOP 切面编程
AOP(面向切面编程)通过动态代理在不修改源码的前提下,对方法进行增强。核心概念包括连接点、通知、切入点、切面和目标对象。常用于日志记录、权限校验、性能监控等场景,结合Spring AOP与@Aspect、@Pointcut等注解,实现灵活的横切逻辑管理。
1444 6
AOP 切面编程
|
5月前
|
XML Java 数据格式
《深入理解Spring》:AOP面向切面编程深度解析
Spring AOP通过代理模式实现面向切面编程,将日志、事务等横切关注点与业务逻辑分离。支持注解、XML和编程式配置,提供五种通知类型及丰富切点表达式,助力构建高内聚、低耦合的可维护系统。