# History
[[WebApi] 捣鼓一个资源管理器--文件下载](http://blog.csdn.net/qiujuer/article/details/41621781)
[[WebApi] 捣鼓一个资源管理器--多文件上传](http://blog.csdn.net/qiujuer/article/details/41675299)
# In This
##### 序言
在上一章中我们讲解了怎么实现文件的上传;在文件上传到服务器后似乎不好管理;每次要下载一个文件也似乎不知从何处下手。
在这一章中将会讲解如何结合数据进行文件的上传存储,保证文件的存储足够安全以及足够简单;同时增加不重复保存同一个文件。
##### 简要分析
1. 在我们想来,一个文件存储到数据库后如何才能保证其有一定的安全性呢?这个简单,最简单的做法就是去除掉后缀名。虽然和加密比起来相差甚远;但是如果有人得到了想要知道其中的东西似乎还是需要花些心思才行。
2. 在满足上面需求的情况下却增加了额外的负担,比如文件全部都没有后缀了那么我们需要下载文件时又怎么办?这时我们就需要记录下文件的基本信息了。
3. 我们知道在一般的上传文件控件上都是按照年月日进行文件夹分开存储的;但是你是否想过同一个文件重复提交的问题呢?针对这样的问题似乎在其他插件上都不行;不是不能做,而是没有这样做!我们这里既然要做那就要做好才行;所以我们需要具有这样的功能,那么怎么唯一标识一个文件呢?最简单的方法莫过于MD5.
4. 针对以上的问题;似乎我们都有思路了;所以现在我们应该知道数据库不是用来存储文件,而是用来存储文件的存储信息,用来标识管理文件。
# CodeTime
好了又到了我们的代码模块了。
##### 改动
先来看看这一次我们做了哪些文件的改动;当然是相对上一版本。
![](https://box.kancloud.cn/2016-01-11_5693565b7e43a.jpg)
可以看出,共更改了**6**个文件;其中修改:**Web、Upload、Resouce、ResouceApi**;添加:**HashUtils、WebResouceContext**。
##### Model
###### Resource.cs
~~~
using System;
using System.ComponentModel.DataAnnotations;
namespace WebResource.Models
{
/// <summary>
/// 文件存储信息表
/// </summary>
public class Resource
{
/// <summary>
/// Id 用于唯一标识
/// 存储时记录文件MD5
/// 在断点续传下存储文件名称+文件大小的MD5值
/// </summary>
[Key]
public string Id { get; set; }
/// <summary>
/// 文件名称
/// </summary>
[Required]
public string Name { get; set; }
/// <summary>
/// 文件大小
/// </summary>
[Required]
public long Size { get; set; }
/// <summary>
/// 当前存储位置
/// 断点续传下用于判断是否传输完成
/// </summary>
[Required]
public long Cursor { get; set; }
/// <summary>
/// 文件类型
/// </summary>
[Required]
public string Type { get; set; }
/// <summary>
/// 存储文件夹
/// </summary>
[Required]
public string Folder { get; set; }
/// <summary>
/// 点击数
/// </summary>
[Required]
public int Clicks { get; set; }
/// <summary>
/// 存储日期
/// </summary>
[Required]
public DateTime Published { get; set; }
}
}
~~~
这个是在上一个版本基础上进行修改后的数据库表结构;在我看来一个文件的存储信息应该包含上面的信息;当然其中用于断点续传的Cursor属性暂时是虚的,没有投入实际使用;当然这个在后续版本将会详细讲解如何添加断点续传。针对文件的基本信息各位如有更好的方案还请提出来。
###### HashUtils.cs
~~~
using System;
using System.IO;
using System.Security.Cryptography;
namespace WebResource.Models
{
public class HashUtils
{
/// <summary>
/// 获取文件的MD5-格式[大写]
/// </summary>
/// <param name="fileStream">文件FileStream</param>
/// <returns>MD5Hash</returns>
public static string GetMD5Hash(FileStream fileStream)
{
try
{
MD5 md5Provider = new MD5CryptoServiceProvider();
byte[] buffer = md5Provider.ComputeHash(fileStream);
md5Provider.Clear();
return BitConverter.ToString(buffer).Replace("-", "");
}
catch { return null; }
}
/// <summary>
/// 获取字符串的MD5-格式[大写]
/// </summary>
/// <param name="str">字符串</param>
/// <returns>MD5Hash</returns>
public static string GetMD5Hash(string str)
{
try
{
MD5 md5Provider = new MD5CryptoServiceProvider();
byte[] buffer = md5Provider.ComputeHash(System.Text.Encoding.UTF8.GetBytes(str));
md5Provider.Clear();
return BitConverter.ToString(buffer).Replace("-", "");
}
catch { return null; }
}
/// <summary>
/// 计算Byte数组的MD5-格式[大写]
/// </summary>
/// <param name="bytes">byte[]</param>
/// <returns>MD5Hash</returns>
public static string GetMD5Hash(byte[] bytes)
{
try
{
MD5 md5Provider = new MD5CryptoServiceProvider();
byte[] buffer = md5Provider.ComputeHash(bytes);
md5Provider.Clear();
return BitConverter.ToString(buffer).Replace("-", "");
}
catch { return null; }
}
}
}
~~~
顾名思义,这个类就是用来进行计算**MD5**值而准备的;当然实际使用中只会使用第3个方法;前面两个只是一并写出来说不定以后要用上。
###### WebResourceContext.cs
~~~
using System.Data.Entity;
namespace WebResource.Models
{
public class WebResourceContext : DbContext
{
public WebResourceContext() : base("name=WebResourceContext")
{
}
public System.Data.Entity.DbSet<WebResource.Models.Resource> Resources { get; set; }
}
}
~~~
这个类简单明了;用过EF Db的应该一看就懂了;这个类是用来进行数据表查询使用的类;当然使用的是EF。
其中指定了"name=WebResourceContext",这个就是用来对应配置文件中的数据库连接字段的。下面就会讲解。
##### Web.config
~~~
<?xml version="1.0" encoding="utf-8"?>
<configuration>
...
<connectionStrings>
<add name="WebResourceContext" connectionString="Data Source=(localdb)\v11.0; Initial Catalog=WebResourceContext-20141204104407; Integrated Security=True; MultipleActiveResultSets=True; AttachDbFilename=|DataDirectory|WebResourceContext-20141204104407.mdf"
providerName="System.Data.SqlClient" />
</connectionStrings>
...
</configuration>
~~~
在这个文件中,有很多的配置;但是其中简单的来说,就是上面的那一条语句;在这条语句中现在指定的是本地的数据库;你也可以指定一个远程的数据连接。
##### Controllers
###### ResourceApiController.cs
~~~
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
using System.Web.Http.Description;
using WebResource.Models;
namespace WebResource.Controllers
{
[RoutePrefix("Res")]
public class ResourceApiController : ApiController
{
private WebResourceContext db = new WebResourceContext();
private static readonly long MEMORY_SIZE = 64 * 1024 * 1024;
private static readonly string ROOT_PATH = HttpContext.Current.Server.MapPath("~/App_Data/");
/// <summary>
/// Post File
/// </summary>
/// <param name="Id">Md5</param>
/// <returns>Resource</returns>
[HttpPost]
[Route("Upload/{Id?}")]
[ResponseType(typeof(Resource))]
public async Task<IHttpActionResult> Post(string Id = null)
{
List<Resource> resources = new List<Resource>();
// multipart/form-data
var provider = new MultipartMemoryStreamProvider();
await Request.Content.ReadAsMultipartAsync(provider);
foreach (var item in provider.Contents)
{
if (item.Headers.ContentDisposition.FileName != null)
{
//Strem
var ms = item.ReadAsStreamAsync().Result;
using (var br = new BinaryReader(ms))
{
if (ms.Length <= 0)
break;
var data = br.ReadBytes((int)ms.Length);
//Md5
string id = HashUtils.GetMD5Hash(data);
Resource temp = await db.Resources.FindAsync(id);
if (temp == null)
{
//Create
Resource resource = new Resource();
resource.Id = id;
resource.Size = ms.Length;
resource.Cursor = resource.Size;
resource.Published = DateTime.Now;
//Info
FileInfo info = new FileInfo(item.Headers.ContentDisposition.FileName.Replace("\"", ""));
resource.Type = info.Extension.Substring(1).ToLower();
resource.Name = info.Name.Substring(0, info.Name.LastIndexOf("."));
//Relative
resource.Folder = DateTime.Now.ToString("yyyyMM/dd/", DateTimeFormatInfo.InvariantInfo);
//Write
try
{
string dirPath = Path.Combine(ROOT_PATH, resource.Folder);
if (!Directory.Exists(dirPath))
{
Directory.CreateDirectory(dirPath);
}
File.WriteAllBytes(Path.Combine(dirPath, resource.Id), data);
//Save To Datebase
db.Resources.Add(resource);
await db.SaveChangesAsync();
temp = await db.Resources.FindAsync(resource.Id);
}
catch { }
}
if (temp != null)
resources.Add(temp);
}
}
}
if (resources.Count == 0)
return BadRequest();
else if (resources.Count == 1)
return Ok(resources.FirstOrDefault());
else
return Ok(resources);
}
}
}
~~~
这个类才是本次讲解的核心部分。
在这个类中,首先看第一个修改的地方,我把API地址重定向为:[RoutePrefix("Res")],这个是为了方便以后使用,输入**Res**总是要比**Resource**要舒服些的。
另外我删除了**Get()**方法,因为这个版本中主要讲解上传;另外下载的话也将集合数据进行操作;所以在下一个版本将会添加新的**Get()**。
来看看现在的API接口:
![](https://box.kancloud.cn/2016-01-11_5693565b93937.jpg)
现在只有一个接口了。
说说流程:
1. 触发上传接口时;服务端会把上传的文件全部存储到内存中;而后遍历上传的每一个文件。
2. 计算每个文件的MD5,使用MD5去数据库查询是否有该记录;如果有就直接返回数据库中的信息,且不保存该文件。
3. 如果没有,提取文件的基本信息,并添加到类Resource Model中,而后保存文件且保存该文件的信息到数据库。
4. 循环完成返回保存结果。
##### View
###### Upload.cshtml
~~~
@{
ViewBag.Title = "Upload";
}
<h2>Upload</h2>
<div id="body">
<h1>多文件上传模式</h1>
<section class="main-content clear-fix">
<form name="form1" method="post" enctype="multipart/form-data" action="/Res/Upload">
<fieldset>
<legend>File Upload Example</legend>
<div>
<label for="caption">File1</label>
<input name="file1" type="file" />
</div>
<div>
<label for="image1">File2</label>
<input name="file2" type="file" />
</div>
<div>
<input type="submit" value="Submit" />
</div>
</fieldset>
</form>
</section>
</div>
~~~
这个文件中只改动了一个地方,就是 action="/Res/Upload"。
# RunTime
终于到了这里了,说实话写一个这个挺累的。
运行:localhost:60586/Home/Upload
![](https://box.kancloud.cn/2016-01-11_5693565bacc32.jpg)
提交后:
![](https://box.kancloud.cn/2016-01-11_5693565bc2875.jpg)
数据库
![](https://box.kancloud.cn/2016-01-11_5693565bd1c03.jpg)
文件
![](https://box.kancloud.cn/2016-01-11_5693565be2ceb.jpg)
![](https://box.kancloud.cn/2016-01-11_5693565c073a1.jpg)
可以看见文件是按照:年月/日/文件MD5 进行存储的。
# END
与往常一样。
##### 资源文件
[[WebApi] 捣鼓一个资源管理器--多文件上传+数据库辅助](http://download.csdn.net/detail/qiujuer/8223605)
##### 下一章
在下一章中将会讲解;怎么在这一章的基础上进行文件的访问。
已发布:[[WebApi] 捣鼓一个资源管理器--数据库辅助服务器文件访问](http://blog.csdn.net/qiujuer/article/details/41744733)