最近做一个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:文件类型。
时间: 2024-10-08 10:44:15