Prepare


本文将使用一个NuGet公开的组件技术来实现一个服务器端的文件管理引擎,提供了一些简单的API,来方便的实现文件引擎来对您自己的软件系统的文件进行管理。

联系作者及加群方式(激活码在群里发放):http://www.hslcommunication.cn/Cooperation

在Visual Studio 中的NuGet管理器中可以下载安装,也可以直接在NuGet控制台输入下面的指令安装:

Install-Package HslCommunication

NuGet安装教程  http://www.cnblogs.com/dathlin/p/7705014.html

Summary


这个文件管理的引擎实现的功能是对所有客户端上传的文件信息进行管理,客户端在上传或是下载的时候允许进度报告。如果我们只是显示一个文件发送到服务器上,服务器接收数据后保存到本地,那么这是非常容易实现的,只要比较熟悉网络通信就可以,但是对于文件服务器引擎需要的逻辑更多,允许上传额外的信息,包括文件的上传人,上传日期,下载次数等等信息,然后允许上传的时候不影响下载,可以同时下载,同时上传,而服务器的硬盘IO不进行阻塞,这样实现起来就相当困难了,但是上述所有的功能在使用本组件实现的时候就非常的方便,当客户端进行上传下载的时候更是调用一个方法就能完成。

本文件引擎的特色是实现了一个文件的读写分离,无锁读写,也就是说,一个文件内容支持同时下载,同时上传,甚至下载的时候,进行上传,原有的下载不会受影响。所有的上传,下载,删除都是线程安全的,无论在哪个线程都是方便调用的。

需求场景:

  • 比如我们要开发一个项目管理系统,如果我们想要实现每个项目允许上传附件,需要支持方便的下载,删除,上传操作。
  • 比如我们开发一个设备资料管理系统,除了设备一些自带属性需要创建关系型数据表外,还要支持附件管理。
  • 比如我们需要实现一个软件的共享文件管理器,在你软件的首页上支持方便的显示。
  • 再比如个人账户的附件管理,个人头像管理等等。

一个基于本组件扩展出来的CS架构的基础模版项目,二次基于此进行方便的二次开发,该项目使用了好几处的文件管理:

https://github.com/dathlin/ClientServerProject

一个C-S模版,该模版由三部分的程序组成,一个服务端运行的程序,一个客户端运行的程序,还有一个公共的组件,实现了基础的账户管理功能,版本控制,软件升级,公告管理,消息群发,共享文件上传下载,批量文件传送功能。具体的操作方法见演示就行。本项目的一个目标是:提供一个基础的中小型系统的C-S框架,客户端有四种模式,无缝集成访问,winform版本,wpf版本,asp.net mvc版本,Android版本。方便企业进行中小型系统的二次开发和个人学习。

Reference


日志组件所有的功能类都在 HslCommunicationHslCommunication.Enthernet 命名空间,所以再使用之前先添加

using HslCommunication;
using HslCommunication.Enthernet;

How to Use


首先先要在服务器端进行搭建服务,基本上只需要两个参数即可,端口号和文件引擎的基础路径。至于日志,可要可不要,看自己的需求,系统会对文件的下载,上传,异常情况进行记录。

我们先上服务器的代码,假设需要日志记录,如果你不需要的话,就注释掉那两行日志相关的代码,系统也可以实现对指定分类的文件数量进行监视,当数量变化的时候进行更新推送等等。ok,接下来先看看一种最简单的方式。

        private UltimateFileServer ultimateFileServer;                                            // 引擎对象

        private void UltimateFileServerInitialization()
{
ultimateFileServer = new UltimateFileServer(); // 实例化对象
ultimateFileServer.FilesDirectoryPath = Application.StartupPath + @"\UltimateFile"; // 所有文件存储的基础路径
ultimateFileServer.ServerStart(34567); // 启动一个端口的引擎
} private void userButton1_Click(object sender, EventArgs e)
{
// 点击了启动服务器端的文件引擎
UltimateFileServerInitialization();
userButton1.Enabled = false;
}

声明一个服务器端的对象,然后写一个初始化方法来实例化数据,然后在一个按钮中调用这个初始化方法即可。服务器端的程序简单版就只有这么多了。

客户端操作:

实例化:

客户端在进行文件的上传,下载之前先进行客户端的实例化,实例化的时候可以指定一些信息,目前测试来说,不需要。

        #region 客户端核心引擎

        private IntegrationFileClient integrationFileClient;                 // 客户端的核心引擎

        private void IntegrationFileClientInitialization()
{
// 定义连接服务器的一些属性,超时时间,IP及端口信息
integrationFileClient = new IntegrationFileClient()
{
ConnectTimeout = 5000,
ServerIpEndPoint = new System.Net.IPEndPoint(System.Net.IPAddress.Parse("127.0.0.1"), 34567),
}; // 创建本地文件存储的路径
string path = Application.StartupPath + @"\Files";
if(!System.IO.Directory.Exists(path))
{
System.IO.Directory.CreateDirectory(path);
}
} #endregion

上传文件:

在讲解上传文件操作之前,先说明下本文件管理引擎的机制,对于每个文件,都有三个分类机制,用于文件的分类,以及标注在服务器端的文件存储路径。当你确认上传一个文件后,需要确认3个分类目录和文件在服务器端真正存储的文件名,一般就是文件自己的名称。接下来我们来上传一个文件吧,该文件来自手动选择:

        #region 上传文件块

        /*************************************************************************************************
*
* 一条指令即可完成文件的上传操作,上传模式有三种
* 1. 指定本地的完整路径的文件名
* 2. 将流(stream)中的数据上传到服务器
* 3. 将bitmap图片数据上传到服务器
*
********************************************************************************************/ private void userButton3_Click(object sender, EventArgs e)
{
// 点击后进行文件选择
using (OpenFileDialog ofd = new OpenFileDialog())
{
if (ofd.ShowDialog() == DialogResult.OK)
{
textBox1.Text = ofd.FileName;
}
} } private void userButton2_Click(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(textBox1.Text))
{
// 点击开始上传,此处按照实际项目需求放到了后台线程处理,事实上这种耗时的操作就应该放到后台线程
System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart((ThreadUploadFile)));
thread.IsBackground = true;
thread.Start(textBox1.Text);
userButton2.Enabled = false;
progressBar1.Value = 0;
}
} private void ThreadUploadFile(object filename)
{
if (filename is string fileName)
{
System.IO.FileInfo fileInfo = new System.IO.FileInfo(fileName);
// 开始正式上传,关于三级分类,下面只是举个例子,上传成功后去服务器端寻找文件就能明白
OperateResult result = integrationFileClient.UploadFile(
fileName, // 需要上传的原文件的完整路径,上传成功还需要个条件,该文件不能被占用
fileInfo.Name, // 在服务器存储的文件名,带后缀,一般设置为原文件的文件名
"Files", // 第一级分类,指示文件存储的类别,对应在服务器端存储的路径不一致
"Personal", // 第二级分类,指示文件存储的类别,对应在服务器端存储的路径不一致
"Admin", // 第三级分类,指示文件存储的类别,对应在服务器端存储的路径不一致
"这个文件非常重要", // 这个文件的额外描述文本,可以为空("")
"张三", // 文件的上传人,当然你也可以不使用
UpdateReportProgress // 文件上传时的进度报告,如果你不需要,指定为NULL就行,一般文件比较大,带宽比较小,都需要进度提示
); // 切换到UI前台显示结果
Invoke(new Action<OperateResult>(operateResult =>
{
userButton2.Enabled = true;
if (result.IsSuccess)
{
MessageBox.Show("文件上传成功!");
}
else
{
// 失败原因多半来自网络异常,还有文件不存在,分类名称填写异常
MessageBox.Show("文件上传失败:" + result.ToMessageShowString());
}
}), result);
}
} /// <summary>
/// 用于更新上传进度的方法,该方法是线程安全的
/// </summary>
/// <param name="sended">已经上传的字节数</param>
/// <param name="totle">总字节数</param>
private void UpdateReportProgress(long sended, long totle)
{
if (progressBar1.InvokeRequired)
{
progressBar1.Invoke(new Action<long, long>(UpdateReportProgress), sended, totle);
return;
} // 此处代码是安全的
int value = (int)(sended * 100L / totle);
progressBar1.Value = value;
} #endregion

在文件上传的时候可以指定一些额外的信息,比如说文件上传人,额外的描述文本。

下载文件:

        #region 文件下载块

        /*************************************************************************************************
*
* 一条指令即可完成文件的下载操作,下载模式有三种
* 1. 指定需要下载的文件名(带后缀)
* 2. 将服务器上的数据下载到流(stream)中
* 3. 将服务器上的数据下载到bitmap图片中
*
********************************************************************************************/ /// <summary>
/// 点击了文件下载触发的事件,如果需要下载一个文件,要传入下载文件的完整名称
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void userButton5_Click(object sender, EventArgs e)
{
// 点击开始下载,此处按照实际项目需求放到了后台线程处理,事实上这种耗时的操作就应该放到后台线程
System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart((ThreadDownloadFile)));
thread.IsBackground = true;
thread.Start(textBox2.Text);
progressBar1.Value = 0;
} private void ThreadDownloadFile(object filename)
{
if (filename is string fileName)
{
OperateResult result = integrationFileClient.DownloadFile(
fileName, // 文件在服务器上保存的名称,举例123.txt
"Files", // 第一级分类,指示文件存储的类别,对应在服务器端存储的路径不一致
"Personal", // 第二级分类,指示文件存储的类别,对应在服务器端存储的路径不一致
"Admin", // 第三级分类,指示文件存储的类别,对应在服务器端存储的路径不一致
DownloadReportProgress, // 文件下载的时候的进度报告,友好的提示下载进度信息
Application.StartupPath + @"\Files\" + filename // 下载后在文本保存的路径,也可以直接下载到 MemoryStream 的数据流中,或是bitmap中
); // 切换到UI前台显示结果
Invoke(new Action<OperateResult>(operateResult =>
{
if (result.IsSuccess)
{
MessageBox.Show("文件下载成功!");
}
else
{
// 失败原因多半来自网络异常,还有文件不存在,分类名称填写异常
MessageBox.Show("文件下载失败:" + result.ToMessageShowString());
}
}), result);
}
} /// <summary>
/// 用于更新文件下载进度的方法,该方法是线程安全的
/// </summary>
/// <param name="receive">已经接收的字节数</param>
/// <param name="totle">总字节数</param>
private void DownloadReportProgress(long receive, long totle)
{
if (progressBar2.InvokeRequired)
{
progressBar2.Invoke(new Action<long, long>(DownloadReportProgress), receive, totle);
return;
} // 此处代码是安全的
int value = (int)(receive * 100L / totle);
progressBar2.Value = value;
} #endregion

文件删除:

        #region 文件的删除操作

        private void userButton1_Click(object sender, EventArgs e)
{
// 文件的删除不需要放在后台线程,前台即可处理,无论多少大的文件,无论该文件是否在下载中,都是很快删除的
OperateResult result = integrationFileClient.DeleteFile("123.txt", "Files", "Personal", "Admin");
if(result.IsSuccess)
{
MessageBox.Show("文件删除成功!");
}
else
{
// 删除失败的原因除了一般的网络问题,还有因为服务器的文件不存在,会在Message里有显示。
MessageBox.Show("文件删除失败,原因:" + result.ToMessageShowString());
}
} #endregion

获取服务器的文件信息:

上面的信息操作,都是指定了三个文件夹路径,上面的示例是:"Files","Personal","Admin"  ,比如我们向这个文件夹里上传了很多文件,想知道文件夹里有什么文件,以及他们的详细信息,可以调用如下的方法:

        private void userButton4_Click(object sender, EventArgs e)
{
// 获取服务器指定目录的所有文件
OperateResult result = integrationFileClient.DownloadPathFileNames(out GroupFileItem[] files, "Files", "Personal", "Admin");
if(result.IsSuccess)
{
treeView1.Nodes[0].Nodes.Clear();
foreach(var file in files)
{
TreeNode node = new TreeNode(file.FileName);
node.Tag = file;
treeView1.Nodes[0].Nodes.Add(node);
}
treeView1.Nodes[0].Expand();
}
else
{
// 获取文件名失败
MessageBox.Show(result.ToMessageShowString());
}
}

获取了文件信息,并存储在了数组files中,当我们点击了这个树节点的时候,就显示了文件的详细信息:

        private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
{
TreeNode node = e.Node;
if (node.Text != "文件列表")
{
textBox2.Text = node.Text;
if(node.Tag is GroupFileItem item)
{
StringBuilder info = new StringBuilder();
info.Append("文件名:");
info.Append(item.FileName);
info.Append(Environment.NewLine);
info.Append("文件大小:");
info.Append(item.FileSize);
info.Append(Environment.NewLine);
info.Append("文件描述:");
info.Append(item.Description);
info.Append(Environment.NewLine);
info.Append("上传人:");
info.Append(item.Owner);
info.Append(Environment.NewLine);
info.Append("上传时间:");
info.Append(item.UploadTime.ToString());
info.Append(Environment.NewLine);
info.Append("下载次数:");
info.Append(item.DownloadTimes);
info.Append(Environment.NewLine); textBox3.Text = info.ToString();
}
}
}

获取服务器的文件信息:

        private void userButton6_Click(object sender, EventArgs e)
{
// 获取服务器指定目录的所有文件
OperateResult result = integrationFileClient.DownloadPathFolders(out string[] folders, "Files", "Personal", "");
if (result.IsSuccess)
{
treeView1.Nodes[0].Nodes.Clear();
foreach (var fold in folders)
{
TreeNode node = new TreeNode(fold);
treeView1.Nodes[0].Nodes.Add(node);
}
treeView1.Nodes[0].Expand();
}
else
{
// 获取文件名失败
MessageBox.Show(result.ToMessageShowString());
}
}

这个用于获取到服务器在Files/Personal/目录下面的子文件名称,之前的存储就是有一个Admin

高级应用:

配置网络令牌

按上述搭建的服务器,可以轻松被客户端访问到,我们也没有输入过密码之类的东西,只要知道ip及端口,其他程序也可以访问你的数据,为了安全起见,允许在服务器端设置令牌,来加强安全,服务器端的代码如下:

        private void UltimateFileServerInitialization()
{
ultimateFileServer = new UltimateFileServer(); // 实例化对象
ultimateFileServer.KeyToken = new Guid("A8826745-84E1-4ED4-AE2E-D3D70A9725B5");
ultimateFileServer.FilesDirectoryPath = Application.StartupPath + @"\UltimateFile"; // 所有文件存储的基础路径
ultimateFileServer.ServerStart(34567); // 启动一个端口的引擎
}

然后客户端在实例化的时候,也需要指定一样的令牌,否则无法访问数据。

        private void IntegrationFileClientInitialization()
{
// 定义连接服务器的一些属性,超时时间,IP及端口信息
integrationFileClient = new IntegrationFileClient()
{
ConnectTimeout = 5000,
ServerIpEndPoint = new System.Net.IPEndPoint(System.Net.IPAddress.Parse("127.0.0.1"), 34567),
KeyToken = new Guid("A8826745-84E1-4ED4-AE2E-D3D70A9725B5") // 指定一个令牌
}; // 创建本地文件存储的路径
string path = Application.StartupPath + @"\Files";
if (!System.IO.Directory.Exists(path))
{
System.IO.Directory.CreateDirectory(path);
}
}

配置日志记录:

主要用于服务器端的日志记录:

        private void UltimateFileServerInitialization()
{
ultimateFileServer = new UltimateFileServer(); // 实例化对象
ultimateFileServer.KeyToken = new Guid("A8826745-84E1-4ED4-AE2E-D3D70A9725B5"); // 指定一个令牌
ultimateFileServer.LogNet = new HslCommunication.LogNet.LogNetSingle(Application.StartupPath + @"\Logs\123.txt");
ultimateFileServer.FilesDirectoryPath = Application.StartupPath + @"\UltimateFile"; // 所有文件存储的基础路径
ultimateFileServer.ServerStart(34567); // 启动一个端口的引擎
}

指定目录下的文件数量监视:

比如我们需要实现的功能,对这个目录下的("Files", "Personal", "Admin")文件数量进行监视,当有文件上传,删除时,进行触发消息,如下的示例演示,在服务器端界面新增一个数据,显示这个目录的文件总数量信息。

        private void UltimateFileServerInitialization()
{
ultimateFileServer = new UltimateFileServer(); // 实例化对象
ultimateFileServer.KeyToken = new Guid("A8826745-84E1-4ED4-AE2E-D3D70A9725B5"); // 指定一个令牌
ultimateFileServer.LogNet = new HslCommunication.LogNet.LogNetSingle(Application.StartupPath + @"\Logs\123.txt");
ultimateFileServer.FilesDirectoryPath = Application.StartupPath + @"\UltimateFile"; // 所有文件存储的基础路径
ultimateFileServer.ServerStart(34567); // 启动一个端口的引擎 // 订阅一个目录的信息,使用文件集容器实现
GroupFileContainer container = ultimateFileServer.GetGroupFromFilePath(Application.StartupPath + @"\UltimateFile\Files\Personal\Admin");
container.FileCountChanged += Container_FileCountChanged; // 当文件数量发生变化时触发
} private void Container_FileCountChanged(int obj)
{
if (InvokeRequired)
{
Invoke(new Action<int>(Container_FileCountChanged), obj);
return;
} label1.Text = "文件数量:" + obj.ToString();
}

示例界面:

该项目的源代码公开,请遵循MIT协议,地址为:https://github.com/dathlin/FileManagment

C# 文件上传下载功能实现 文件管理引擎开发的更多相关文章

  1. JavaWeb实现文件上传下载功能实例解析

    转:http://www.cnblogs.com/xdp-gacl/p/4200090.html JavaWeb实现文件上传下载功能实例解析 在Web应用系统开发中,文件上传和下载功能是非常常用的功能 ...

  2. JavaWeb实现文件上传下载功能实例解析 (好用)

    转: JavaWeb实现文件上传下载功能实例解析 转:http://www.cnblogs.com/xdp-gacl/p/4200090.html JavaWeb实现文件上传下载功能实例解析 在Web ...

  3. WEB文件上传下载功能

    WEB文件上传下载在日常工作中经常用到的功能 这里用到JS库 http://files.cnblogs.com/meilibao/ajaxupload.3.5.js 上传代码段(HTML) <% ...

  4. Struts2实现文件上传下载功能(批量上传)

    今天来发布一个使用Struts2上传下载的项目, struts2为文件上传下载提供了好的实现机制, 首先,可以先看一下我的项目截图 关于需要使用的jar包,需要用到commons-fileupload ...

  5. php实现文件上传下载功能小结

    文件的上传与下载是项目中必不可少的模块,也是php最基础的模块之一,大多数php框架中都封装了关于上传和下载的功能,不过对于原生的上传下载还是需要了解一下的.基本思路是通过form表单post方式实现 ...

  6. 文件一键上传、汉字转拼音、excel文件上传下载功能模块的实现

    ----------------------------------------------------------------------------------------------[版权申明: ...

  7. javaweb项目中的文件上传下载功能的实现

    框架是基于spring+myBatis的. 前台页面的部分代码: <form action="${ctx}/file/upLoadFile.do"method="p ...

  8. FasfDFS整合Java实现文件上传下载功能实例详解

    https://www.jb51.net/article/120675.htm 在上篇文章给大家介绍了FastDFS安装和配置整合Nginx-1.13.3的方法,大家可以点击查看下. 今天使用Java ...

  9. Nginx文件上传下载实现与文件管理

    1.Nginx 上传 Nginx 依赖包下载 # wget http://www.nginx.org/download/nginx-1.2.2.tar.gzinx # wget http://www. ...

随机推荐

  1. Flutter 控件之 Routes 和 Navigator. [PopupRoute]

    一个 App 通常会有多个界面,每个界面实现不同的功能,并在多个界面之间跳转.在 Flutter 中多个界面的跳转是通过 Navigator 来实现的. 在 Flutter 中定义了一个 Overla ...

  2. Bugku-CTF之变量1

    Day9 变量1 http://123.206.87.240:8004/index1.php      

  3. S-DES算法实现(C++版本)

    密码学实验二: /** :;LaEaHKEEGpPXU7;, .:75pKH11252U252XapZgRQgD6XJscLr;,. :LXpRgGaX521JLw1JswJJsJs22XHPPEZE ...

  4. 自动化测试系列:自动化测试KPI考评的一种方法

    更多原创测试技术文章同步更新到微信公众号 :三国测,敬请扫码关注个人的微信号,感谢! 原文链接:http://www.cnblogs.com/zishi/p/6856204.html 众所周知,在IT ...

  5. Progressive Scramble【模拟】

    问题 J: Progressive Scramble 时间限制: 1 Sec  内存限制: 128 MB 提交: 108  解决: 45 [提交] [状态] [命题人:admin] 题目描述 You ...

  6. Oracle 实例名/服务名 请问SID和Service_Name有什么区别啊?

    可以简单的这样理解:一个公司比喻成一台服务器,数据库是这个公司中的一个部门. 1.SID:一个数据库可以有多个实例(如RAC),SID是用来标识这个数据库内部每个实例的名字, 就好像一个部门里,每个人 ...

  7. Spring boot+mybatis+thymeleaf 实现登录注册,增删改查

    本文重在实现理解,过滤器,业务,逻辑需求,样式请无视.. 项目结构如下 1.idea新建Spring boot项目,在pom中加上thymeleaf和mybatis支持.pom.xml代码如下 < ...

  8. 一些有趣的 js 包

    https://github.com/octalmage/robotjs Node.js桌面自动化.控制鼠标,键盘和屏幕. http://robotjs.io

  9. CSS布局学习(三) - Normal Flow 正常布局流(官网直译)

    默认情况下,元素是如何布局? 首先,取得元素的内容,加上内边距(padding),边框(border),外边距(margin)放在一个盒子中 - 这就是我们之前看到的盒子模型 默认情况下,块级元素的内 ...

  10. Windows10家庭版用户无法在计算机管理更改权限的解决办法

    问题描述:今天因为动了注册表导致windows登陆界面只剩下一个管理员账号,而平常用的账号不知所踪~这个问题本来很好解决,但是由于在用的笔记本安装的是Windows家庭中文版,无法通过计算机管理在本地 ...