Html5 js上传插件resumable.js 的使用

  最近做一个csv文件上传,网上找了几个,兼容性不好,年代也比较久远,就去github上看看找找,发现了一个resumable.js,支持分片上传,多文件上传,完全满足需求~项目地址:https://github.com/23/resumable.js。

  简单说下应用,顺便保存下代码,万一哪天再用了,忘了还得重新找.....这才是关键。

  后台代码,两个文件,一个model,一个webapi,基于C#的。

  model代码:

namespace Resumable.Models
{
	public class ResumableConfiguration
	{
		/// <summary>
		/// Gets or sets number of expected chunks in this upload.
		/// </summary>
		public int Chunks { get; set; }

		/// <summary>
		/// Gets or sets unique identifier for current upload.
		/// </summary>
		public string Identifier { get; set; }
		
		/// <summary>
		/// Gets or sets file name.
		/// </summary>
		public string FileName { get; set; }

		public ResumableConfiguration()
		{

		}

		/// <summary>
		/// Creates an object with file upload configuration.
		/// </summary>
		/// <param name="identifier">Upload unique identifier.</param>
		/// <param name="filename">File name.</param>
		/// <param name="chunks">Number of file chunks.</param>
		/// <returns>File upload configuration.</returns>
		public static ResumableConfiguration Create(string identifier, string filename, int chunks)
		{
			return new ResumableConfiguration { Identifier = identifier, FileName = filename, Chunks = chunks };
		}
	}
}

  API代码:

using Resumable.Models;
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;

namespace Resumable.Controllers
{
	[RoutePrefix("api/File")]
	public class FileUploadController : ApiController
	{
		private string root = System.Web.Hosting.HostingEnvironment.MapPath("~/upload"); 
		
		[Route("Upload"), HttpOptions]
		public object UploadFileOptions()
		{
			return Request.CreateResponse(HttpStatusCode.OK);
		}

		[Route("Upload"), HttpGet]
		public object Upload(int resumableChunkNumber, string resumableIdentifier)
		{
			return ChunkIsHere(resumableChunkNumber, resumableIdentifier) ? Request.CreateResponse(HttpStatusCode.OK) : Request.CreateResponse(HttpStatusCode.NoContent);
		}

		[Route("Upload"), HttpPost]
		public async Task<object> Upload()
		{
			// Check if the request contains multipart/form-data.
			if (!Request.Content.IsMimeMultipartContent())
			{
				throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
			}
			if (!Directory.Exists(root)) Directory.CreateDirectory(root);
			var provider = new MultipartFormDataStreamProvider(root);

			if (await readPart(provider))
			{
				// Success
				return Request.CreateResponse(HttpStatusCode.OK);
			}
			else
			{
				// Fail
				var message = DeleteInvalidChunkData(provider) ? "Cannot read multi part file data." : "Cannot delete temporary file chunk data.";
				return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, new System.Exception(message));
			}
		}

		private static bool DeleteInvalidChunkData(MultipartFormDataStreamProvider provider)
		{
			try
			{
				var localFileName = provider.FileData[0].LocalFileName;
				if (File.Exists(localFileName))
				{
					File.Delete(localFileName);
				}
				return true;
			}
			catch {
				return false;
			}
		}

		private async Task<bool> readPart(MultipartFormDataStreamProvider provider)
		{
			try
			{
				await Request.Content.ReadAsMultipartAsync(provider);
				ResumableConfiguration configuration = GetUploadConfiguration(provider);
				int chunkNumber = GetChunkNumber(provider);

				// Rename generated file
				MultipartFileData chunk = provider.FileData[0]; // Only one file in multipart message
				RenameChunk(chunk, chunkNumber, configuration.Identifier);

				// Assemble chunks into single file if they're all here
				TryAssembleFile(configuration);
				return true;
			}
			catch {
				return false;
			}
		}

		#region Get configuration

		[NonAction]
		private ResumableConfiguration GetUploadConfiguration(MultipartFormDataStreamProvider provider)
		{
			return ResumableConfiguration.Create(identifier: GetId(provider), filename: GetFileName(provider), chunks: GetTotalChunks(provider));
		}

		[NonAction]
		private string GetFileName(MultipartFormDataStreamProvider provider)
		{
			var filename = provider.FormData["resumableFilename"];
			return !String.IsNullOrEmpty(filename) ? filename : provider.FileData[0].Headers.ContentDisposition.FileName.Trim('\"');
		}

		[NonAction]
		private string GetId(MultipartFormDataStreamProvider provider)
		{
			var id = provider.FormData["resumableIdentifier"];
			return !String.IsNullOrEmpty(id) ? id : Guid.NewGuid().ToString();
		}

		[NonAction]
		private int GetTotalChunks(MultipartFormDataStreamProvider provider)
		{
			var total = provider.FormData["resumableTotalChunks"];
			return !String.IsNullOrEmpty(total) ? Convert.ToInt32(total) : 1;
		}

		[NonAction]
		private int GetChunkNumber(MultipartFormDataStreamProvider provider)
		{
			var chunk = provider.FormData["resumableChunkNumber"];
			return !String.IsNullOrEmpty(chunk) ? Convert.ToInt32(chunk) : 1;
		}

		#endregion

		#region Chunk methods

		[NonAction]
		private string GetChunkFileName(int chunkNumber, string identifier)
		{
			return Path.Combine(root, string.Format("{0}_{1}", identifier, chunkNumber.ToString()));
		}

		[NonAction]
		private void RenameChunk(MultipartFileData chunk, int chunkNumber, string identifier)
		{
			string generatedFileName = chunk.LocalFileName;
			string chunkFileName = GetChunkFileName(chunkNumber, identifier);
			if (File.Exists(chunkFileName)) File.Delete(chunkFileName);
			File.Move(generatedFileName, chunkFileName);

		}

		[NonAction]
		private string GetFilePath(ResumableConfiguration configuration)
		{
			return Path.Combine(root, configuration.Identifier);
		}

		[NonAction]
		private bool ChunkIsHere(int chunkNumber, string identifier)
		{
			string fileName = GetChunkFileName(chunkNumber, identifier);
			return File.Exists(fileName);
		}

		[NonAction]
		private bool AllChunksAreHere(ResumableConfiguration configuration)
		{
			for (int chunkNumber = 1; chunkNumber <= configuration.Chunks; chunkNumber++)
				if (!ChunkIsHere(chunkNumber, configuration.Identifier)) return false;
			return true;
		}
	
		[NonAction]
		private void TryAssembleFile(ResumableConfiguration configuration)
		{
			if (AllChunksAreHere(configuration))
			{
				// Create a single file
				var path = ConsolidateFile(configuration);

				// Rename consolidated with original name of upload
				RenameFile(path, Path.Combine(root, configuration.FileName));

				// Delete chunk files
				DeleteChunks(configuration);
			}
		}

		[NonAction]
		private void DeleteChunks(ResumableConfiguration configuration)
		{
			for (int chunkNumber = 1; chunkNumber <= configuration.Chunks; chunkNumber++)
			{
				var chunkFileName = GetChunkFileName(chunkNumber, configuration.Identifier);
				File.Delete(chunkFileName);
			}
		}
		
		[NonAction]
		private string ConsolidateFile(ResumableConfiguration configuration)
		{
			var path = GetFilePath(configuration);
			using (var destStream = File.Create(path, 15000))
			{
				for (int chunkNumber = 1; chunkNumber <= configuration.Chunks; chunkNumber++)
				{
					var chunkFileName = GetChunkFileName(chunkNumber, configuration.Identifier);
					using (var sourceStream = File.OpenRead(chunkFileName))
					{
						sourceStream.CopyTo(destStream);
					}
				}
				destStream.Close();
			}

			return path;
		}

		#endregion

		[NonAction]
		private string RenameFile(string sourceName, string targetName)
		{
			targetName = Path.GetFileName(targetName); // Strip to filename if directory is specified (avoid cross-directory attack)
			string realFileName = Path.Combine(root, targetName);
			if (File.Exists(realFileName)) File.Delete(realFileName);
			File.Move(sourceName, realFileName);
			return targetName;
		}
	}
}

 github上给的demo里边的代码~完全可以拿来用。然而并没有webapp的代码,我照着java的抄,发生了悲剧。不过稍微改改就好。

贴出来我的代码

<div id="frame">
    <link href="~/Content/resumablestyle.css" rel="stylesheet" />
    <script src="~/Scripts/jquery-1.10.2.min.js"></script>
    <script src="~/Scripts/resumable.js"></script>
    <br />
    <div class="resumable-drop" ondragenter="jQuery(this).addClass('resumable-dragover');" ondragend="jQuery(this).removeClass('resumable-dragover');" ondrop="jQuery(this).removeClass('resumable-dragover');">
        将文件拖拽到此处上传 <a class="resumable-browse"><u>点击选择要上传的文件</u></a>
    </div>
    <div class="resumable-progress">
        <table>
            <tr>
                <td width="100%"><div class="progress-container"><div class="progress-bar"></div></div></td>
                <td class="progress-text" nowrap="nowrap"></td>
                <td class="progress-pause" nowrap="nowrap">
                    <a href="#" onclick="r.upload(); return(false);" class="progress-resume-link"><img src="~/Img/resume.png" title="Resume upload" /></a>
                    <a href="#" onclick="r.pause(); return(false);" class="progress-pause-link"><img src="~/Img/pause.png" title="Pause upload" /></a>
                </td>
            </tr>
        </table>
    </div>

    <ul class="resumable-list"></ul>
    <script>
        var r = new Resumable({
            target: '@Url.Content("~/api/File/Upload")',
            chunkSize: 1 * 1024 * 1024,
            simultaneousUploads: 3,
            //testChunks: false,
            throttleProgressCallbacks: 1,
            fileType: ["csv"]
            //method: "octet"
        });
        // Resumable.js isn't supported, fall back on a different method
        if (!r.support) {
            $('.resumable-error').show();
        } else {
            // Show a place for dropping/selecting files
            $('.resumable-drop').show();
            r.assignDrop($('.resumable-drop')[0]);
            r.assignBrowse($('.resumable-browse')[0]);

            // Handle file add event
            r.on('fileAdded', function (file) {
                // Show progress pabr
                $('.resumable-progress, .resumable-list').show();
                // Show pause, hide resume
                $('.resumable-progress .progress-resume-link').hide();
                $('.resumable-progress .progress-pause-link').show();
                // Add the file to the list
                $('.resumable-list').append('<li class="resumable-file-' + file.uniqueIdentifier + '">Uploading <span class="resumable-file-name"></span> <span class="resumable-file-progress"></span>');
                $('.resumable-file-' + file.uniqueIdentifier + ' .resumable-file-name').html(file.fileName);
                // Actually start the upload
                r.upload();
            });
            r.on('pause', function () {
                // Show resume, hide pause
                $('.resumable-progress .progress-resume-link').show();
                $('.resumable-progress .progress-pause-link').hide();
            });
            r.on('complete', function () {
                // Hide pause/resume when the upload has completed
                $('.resumable-progress .progress-resume-link, .resumable-progress .progress-pause-link').hide();
            });
            r.on('fileSuccess', function (file, message) {
                // Reflect that the file upload has completed
                $('.resumable-file-' + file.uniqueIdentifier + ' .resumable-file-progress').html('(completed)');

            });
            r.on('fileError', function (file, message) {
                // Reflect that the file upload has resulted in error
                $('.resumable-file-' + file.uniqueIdentifier + ' .resumable-file-progress').html('(file could not be uploaded: ' + message + ')');
            });
            r.on('fileProgress', function (file) {
                // Handle progress for both the file and the overall upload
                $('.resumable-file-' + file.uniqueIdentifier + ' .resumable-file-progress').html(Math.floor(file.progress() * 100) + '%');
                $('.progress-bar').css({ width: Math.floor(r.progress() * 100) + '%' });
            });
        }
    </script>
</div>

  css样式,图标文件,都是用的demo里的。直接用就好。css中主要的就是:

/* Reset */
#frame {margin:0 auto; width:800px; text-align:left;}

/* Uploader: Drag & Drop */
/*.resumable-error {display:none; font-size:14px; font-style:italic;}
.resumable-drop {padding:15px; font-size:13px; text-align:center; color:#666; font-weight:bold;background-color:#eee; border:2px dashed #aaa; border-radius:10px; margin-top:40px; z-index:9999; display:none;}
.resumable-dragover {padding:30px; color:#555; background-color:#ddd; border:1px solid #999;}*/
.resumable-error {display:none; font-size:14px; font-style:italic;}
.resumable-drop { padding:15px;font-size:13px; text-align:center; color:#666; font-weight:bold;background-color:#eee; border:2px dashed #aaa; border-radius:10px;  z-index:9999; display:none;}
.resumable-dragover {padding:30px; color:#555; background-color:#ddd; border:1px solid #999;}
/* Uploader: Progress bar */
.resumable-progress {margin:30px 0 30px 0; width:100%; display:none;}
.progress-container {height:7px; background:#9CBD94; position:relative; }
.progress-bar {position:absolute; top:0; left:0; bottom:0; background:#45913A; width:0;}
.progress-text {font-size:11px; line-height:9px; padding-left:10px;}
.progress-pause {padding:0 0 0 7px;}
.progress-resume-link {display:none;}
.is-paused .progress-resume-link {display:inline;}
.is-paused .progress-pause-link {display:none;}
.is-complete .progress-pause {display:none;}

/* Uploader: List of items being uploaded */
.resumable-list {overflow:auto; margin-right:-20px; display:none;}
.uploader-item {width:148px; height:90px; background-color:#666; position:relative; border:2px solid black; float:left; margin:0 6px 6px 0;}
.uploader-item-thumbnail {width:100%; height:100%; position:absolute; top:0; left:0;}
.uploader-item img.uploader-item-thumbnail {opacity:0;}
.uploader-item-creating-thumbnail {padding:0 5px; font-size:9px; color:white;}
.uploader-item-title {position:absolute; font-size:9px; line-height:11px; padding:3px 50px 3px 5px; bottom:0; left:0; right:0; color:white; background-color:rgba(0,0,0,0.6); min-height:27px;}
.uploader-item-status {position:absolute; bottom:3px; right:3px;}

/* Uploader: Hover & Active status */
.uploader-item:hover, .is-active .uploader-item {border-color:#4a873c; cursor:pointer; }
.uploader-item:hover .uploader-item-title, .is-active .uploader-item .uploader-item-title {background-color:rgba(74,135,60,0.8);}

/* Uploader: Error status */
.is-error .uploader-item:hover, .is-active.is-error .uploader-item {border-color:#900;}
.is-error .uploader-item:hover .uploader-item-title, .is-active.is-error .uploader-item .uploader-item-title {background-color:rgba(153,0,0,0.6);}
.is-error .uploader-item-creating-thumbnail {display:none;}

  基本就这么多,简单粗暴.....效果还是很好的,能实现大文件分片上传,多文件上传处理,自带进度条提示,省了好多事,说下参数含义:target:后台API,chunkSize:分片大小,fileType:文件类型。

posted @ 2016-09-20 09:35  ttviva  阅读(935)  评论(0编辑  收藏  举报