《打造一个网站或者其他网络应用的文件管理接口(WebApi)第三章“多文件上传+数据库辅助存储”》
========================================================
作者:qiujuer
博客:blog.csdn.net/qiujuer
网站:www.qiujuer.net
开源库:Genius-Android
转载请注明出处: http://blog.csdn.net/qiujuer/article/details/41721165
========================================================
在上一章中我们讲解了怎么实现文件的上传;在文件上传到服务器后似乎不好管理;每次要下载一个文件也似乎不知从何处下手。
在这一章中将会讲解如何结合数据进行文件的上传存储,保证文件的存储足够安全以及足够简单;同时增加不重复保存同一个文件。
好了又到了我们的代码模块了。
先来看看这一次我们做了哪些文件的改动;当然是相对上一版本。
可以看出,共更改了6个文件;其中修改:Web、Upload、Resouce、ResouceApi;添加:HashUtils、WebResouceContext。
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属性暂时是虚的,没有投入实际使用;当然这个在后续版本将会详细讲解如何添加断点续传。针对文件的基本信息各位如有更好的方案还请提出来。
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个方法;前面两个只是一并写出来说不定以后要用上。
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",这个就是用来对应配置文件中的数据库连接字段的。下面就会讲解。
<?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>在这个文件中,有很多的配置;但是其中简单的来说,就是上面的那一条语句;在这条语句中现在指定的是本地的数据库;你也可以指定一个远程的数据连接。
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接口:
现在只有一个接口了。
说说流程:
@{
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"。
终于到了这里了,说实话写一个这个挺累的。
运行:localhost:60586/Home/Upload
提交后:
数据库
文件
可以看见文件是按照:年月/日/文件MD5 进行存储的。
与往常一样。
[WebApi] 捣鼓一个资源管理器--多文件上传+数据库辅助
在下一章中将会讲解;怎么在这一章的基础上进行文件的访问。
========================================================
作者:qiujuer
博客:blog.csdn.net/qiujuer
网站:www.qiujuer.net
开源库:Genius-Android
转载请注明出处:http://blog.csdn.net/qiujuer/article/details/41721165
========================================================
[WebApi] 捣鼓一个资源管理器--多文件上传+数据库辅助
原文地址:http://blog.csdn.net/qiujuer/article/details/41721165