今天分享一个自己手动搭建文件服务器,本来想上网找个现成的直接用的,结果发现太麻烦,级别太重,不如自己写的快,一天写测,第二天上线使用。
如下图,下面是调用的一个测试使用的界面。
测试上传和下载的功能。
基本原理说一下:
1.客户端上传file,转换成二进制流到服务器,服务器接收进行MD5加密,把文件及密码存入文件服务器库,文件根据MD5保存进本地文件夹,文件夹命名第一级取MD5前二位,第二级文件目录是MD5第3和4位,保存的文件重新命名,名称是当前加密的MD5。 当然,加密储存需要验证的,如果本地已经存了这个MD5就认为已经保存了相同的文件,就不需要再保存。
2.下载文件的时候 直接通过该MD5取文件即可。
上图是基本流程,逻辑上还是有漏洞,实际上又有改动。基本流程是这样了,可以大概看看,懒得再划一个图了。
服务端结构:
FileService.asmx 提供服务,核心代码在FileCoreService.cs. 本项目用的Dapper,简单方便、实用。
WebApplication1 就是测试用的,客户端调用的了。
WFBPMFile 可以忽略了,我的一个转换功能,原先文件是文件流存入数据库里的,大概100G,然后转换成文件,放入文件服务器了。
核心代码 放出来吧,喜欢的可以拿去.
1 using FZ.File.Dapper; 2 using System; 3 using System.Collections.Generic; 4 using System.Data; 5 using System.Linq; 6 using System.Diagnostics; 7 using System.IO; 8 using System.Security.Cryptography; 9 using System.Text; 10 11 namespace FZ.File.Logic 12 { 13 public class FileCoreService 14 { 15 /// <summary> 16 /// 根据文件名和MD5检查是否存在, 检查文件名和MD5都存在 17 /// </summary> 18 /// <param name="filename">文件名</param> 19 /// <param name="md5str">文件流加密的MD5</param> 20 /// <returns></returns> 21 public bool CheckFilNameMD5(string filename, string md5str) 22 { 23 using (IDbConnection conn = DBConfig.GetSqlConnection()) 24 { 25 try 26 { 27 string sql = "SELECT COUNT(*) FROM BPM_tb_UploadFile WHERE [FileName]=@FileName AND FileMD5=@FileMD5 "; 28 //sql = String.Format(sql,filename,md5str); 29 //var count = conn.ExecuteScalar(sql, null, null, null, CommandType.Text); 30 var param = new DynamicParameters(); 31 param.Add("@FileName", filename); 32 param.Add("@FileMD5", md5str); 33 var count = conn.ExecuteScalar(sql, param, null, 3600, CommandType.Text); 34 if ((int)count > 0) 35 { 36 return true; 37 } 38 } 39 catch (Exception ex) 40 { 41 throw ex; 42 } 43 } 44 return false; 45 } 46 47 /// <summary> 48 /// 验证数据的完整性(接收到的文件流MD5与接收到的MD5验证) 49 /// </summary> 50 /// <param name="md5str">接收的MD5</param> 51 /// <param name="sourceStream">文件流</param> 52 /// <returns></returns> 53 public bool CheckMD5(string md5str, System.Byte[] sourceStream) 54 { 55 var jmd5 = GetMD5HashByByte(sourceStream); 56 if (md5str == jmd5) 57 { 58 return true; 59 } 60 return false; 61 } 62 public bool InsertFile(System.Byte[] sourceStream,string md5str,string filename) 63 { 64 bool sf = SaveFileToDisk(sourceStream, "D:\UploadFile\", md5str); //先保存文件 65 if (sf) 66 { 67 //TO DO 插入数据库 68 using (IDbConnection conn = DBConfig.GetSqlConnection()) 69 { 70 try 71 { 72 string sql = "INSERT INTO BPM_tb_UploadFile([FileName],[FileMD5],[FileSize],[Description]) VALUES('{0}','{1}',{2},'{3}')"; 73 sql = String.Format(sql, filename, md5str, sourceStream.Length, ""); 74 var count = conn.Execute(sql, null, null, null, CommandType.Text); 75 //var param = new DynamicParameters(); 76 //param.Add("@FileName", filename); 77 //param.Add("@FileMD5", md5str); 78 //var count = conn.Execute(sql, param, null, 3600, CommandType.Text); 79 if (count > 0) 80 { 81 return true; 82 } 83 } 84 catch (Exception ex) 85 { 86 throw ex; 87 } 88 } 89 } 90 return false; 91 } 92 // 根据二进制流生成MD5 93 private string GetMD5HashByByte(System.Byte[] sourceStream) 94 { 95 MD5 md5 = new MD5CryptoServiceProvider(); 96 byte[] result = md5.ComputeHash(sourceStream); 97 String ret = ""; 98 for (int i = 0; i < result.Length; i++) 99 ret += result[i].ToString("x").PadLeft(2, '0'); 100 return ret; 101 } 102 103 // 根据文件流生成MD5(与上一方法生成结果相同) 104 private string GetMD5HashByFile(string fileName) 105 { 106 FileStream file = new FileStream(fileName, FileMode.Open); 107 MD5 md5 = new MD5CryptoServiceProvider(); 108 byte[] result = md5.ComputeHash(file); 109 file.Close(); 110 StringBuilder sb = new StringBuilder(); 111 for (int i = 0; i < result.Length; i++) 112 { 113 sb.Append(result[i].ToString("x2")); 114 } 115 return sb.ToString(); 116 } 117 118 // 保存文件流到服务器上指定位置 119 private bool SaveFileToDisk(System.Byte[] sourceStream, string fileFullName) 120 { 121 bool result = false; 122 try 123 { 124 //待保存的路径 125 string savePath = Path.GetDirectoryName(fileFullName); 126 if (!Directory.Exists(savePath)) 127 { 128 Directory.CreateDirectory(savePath); 129 } 130 131 using (FileStream fsTarget = new FileStream(fileFullName, FileMode.Create, FileAccess.Write, FileShare.None)) 132 { 133 fsTarget.Write(sourceStream, 0, sourceStream.Length); 134 fsTarget.Flush(); 135 fsTarget.Close(); 136 result = true; 137 } 138 } 139 finally 140 { 141 } 142 return result; 143 } 144 145 private bool SaveFileToDisk(System.Byte[] sourceStream, string filepath,string md5) 146 { 147 bool result = false; 148 string fileFullName = filepath + md5.Substring(0, 2) + "\" + md5.Substring(2, 2)+"\" + md5; 149 try 150 { 151 //待保存的路径 152 string savePath = Path.GetDirectoryName(fileFullName); 153 if (!Directory.Exists(savePath)) 154 { 155 Directory.CreateDirectory(savePath); 156 } 157 158 using (FileStream fsTarget = new FileStream(fileFullName, FileMode.Create, FileAccess.Write, FileShare.None)) 159 { 160 fsTarget.Write(sourceStream, 0, sourceStream.Length); 161 fsTarget.Flush(); 162 fsTarget.Close(); 163 result = true; 164 } 165 } 166 finally 167 { 168 } 169 return result; 170 } 171 172 public System.Byte[] ReadFileByte(string filename, string md5str) 173 { 174 var filepath = "D:\UploadFile\" + md5str.Substring(0, 2) + "\" + md5str.Substring(2, 2) + "\" + md5str; 175 FileStream fileStream = new FileStream(filepath, FileMode.Open); 176 byte[] bytes = new byte[fileStream.Length]; 177 fileStream.Read(bytes, 0, bytes.Length); 178 fileStream.Close(); 179 return bytes; 180 } 181 public FileStream ReadFileStream(string filename, string md5str) 182 { 183 var filepath = "D:\UploadFile\" + md5str.Substring(0, 2) + "\" + md5str.Substring(2, 2) + "\" + md5str; 184 FileStream fileStream = new FileStream(filepath, FileMode.Open); 185 fileStream.Close(); 186 return fileStream; 187 } 188 189 190 } 191 }
上面保存的文件路径自己写入配置文件吧,还有日志文件路径,自己到配置文件改一下。代码写的很烂,各位高人忽略即可。
提供的服务代码:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 using System.Web.Services; 6 using FZ.File.Logic; 7 using System.IO; 8 9 namespace BPMFileService 10 { 11 /// <summary> 12 /// FileService 的摘要说明 13 /// </summary> 14 [WebService(Namespace = "http://tempuri.org/")] 15 [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] 16 [System.ComponentModel.ToolboxItem(false)] 17 // 若要允许使用 ASP.NET AJAX 从脚本中调用此 Web 服务,请取消注释以下行。 18 // [System.Web.Script.Services.ScriptService] 19 public class FileService : System.Web.Services.WebService 20 { 21 22 [WebMethod] 23 public bool CheckMD5(string filename,string md5str) 24 { 25 FileCoreService fs = new FileCoreService(); 26 return fs.CheckFilNameMD5(filename, md5str); 27 } 28 [WebMethod] 29 public bool InsertFile(System.Byte[] FileStream,string filename, string md5str) 30 { 31 FileCoreService fs = new FileCoreService(); 32 bool b = fs.CheckMD5(md5str, FileStream); //验证MD5 33 if (b) 34 { 35 b = fs.InsertFile(FileStream, md5str,filename); //保存文件,并更新到数据库 36 if (b) { LocalLog.Write("插入文件成功,文件名:" + filename + " MD5:" + md5str); } 37 else { LocalLog.Write("插入文件失败,文件名:" + filename + " MD5:" + md5str); } 38 } 39 else 40 { 41 LocalLog.Write("接收的文件不完整,请检查!文件名:" + filename + " MD5:" + md5str); 42 } 43 return b; 44 } 45 [WebMethod] 46 public Byte[] ReadFile(string filename, string md5str) 47 { 48 FileCoreService fs = new FileCoreService(); 49 Byte[] bytes = fs.ReadFileByte(filename, md5str); 50 LocalLog.Write("读取文件 NAME:" + filename + " MD5:" + md5str); 51 return bytes; 52 } 53 } 54 }
客户端上传调用的代码:
1 protected void btnUp_Click(object sender, EventArgs e) 2 { 3 FileServiceSoapClient fsclient = new FileServiceSoapClient(); 4 byte[] fb = FileUpload1.FileBytes; 5 System.IO.Stream s = FileUpload1.PostedFile.InputStream; 6 var md5str = GetMD5HashByByte(fb); 7 var md5str2 = GetMD5HashByFile(s); 8 9 var filename = FileUpload1.FileName; 10 bool b = fsclient.CheckMD5(filename, md5str); 11 if (!b) 12 { 13 if (md5str == md5str2) { 14 b = fsclient.InsertFile(fb, filename, md5str); 15 } 16 } 17 }
客户端下载的代码:
protected void btndown_Click(object sender, EventArgs e) { FileServiceSoapClient fsclinent = new FileServiceSoapClient(); var Dbytes = fsclinent.ReadFile("新建文本文档.txt", "450ccb8dc556e010ff95b787084d2c51"); //byte[] bytes =byte.Parse(Dbytes.ToString()): Response.ContentType = "application/octet-stream;charset=gb2321"; //通知浏览器下载文件而不是打开;对中文名称进行编码 Response.AddHeader("Content-Disposition", "attachment; filename=" + HttpUtility.UrlEncode("新建文本文档.txt", System.Text.Encoding.UTF8)); Response.BinaryWrite(Dbytes); Response.Flush(); Response.End(); }
数据库也比较简单:
日志:
交流的企鹅群:14615476 ,群员免费提供源码。