日常如果是上传一些小文件,在程序实现中,我们都是直接上传,一般都没什么问题。如果针对大文件上传的业务中,就会面临着:

1、网速问题,导致文件上传超时,而导致失败。

2、效率问题,上传大文件等待时间过长,如果是需要上传多个,就会更慢。

3、体验问题,用户无法预知上传还需花费的时间,系统没有及时反馈,用户无法判断文件是否还在上传,还是断开。

这时候就需要采用分布式文件上传系统。

项目简介

这是一个基于.Net Core构建的简单、跨平台分布式文件上传系统,支持分块上传、多个项目同时上传、接口权限控制采用JWT机制。

技术架构

1、跨平台:这是基于.Net Core开发的系统,可以部署在Docker, Windows, Linux, Mac。

2、.Net 2.1 + Jwt + simple-uploader

项目结构

项目分为分块上传与一般上传Demo,Web、控制台上传Demo。ufs项目是分布式文件上传的统一接口,ufs会根据配置把上传的文件发到ufs.node节点,ufs.node会把上传成功路径返回给ufs并存储,用户访问的时候,ufs会访问对应节点返回资源。

UploadServer为一般文件上传接口,UploadServer.FrontEndDemo为Web上传文件Demo。

**使用
**

1、配置

配置允许上传域名、服务接口地址、允许的文件格式、文件大小、存储路径等信息。

{  "AllowedHosts": "*",  "urls": "http://localhost:6001",  "uploadServer": {    "rootUrl": "http://localhost:6001",    "entryPoint1": "/upload",    "entryPoint2": "/chunkUpload",    "virtualPath": "",    "physicalPath": "/Users/loogn/Desktop/uploader",    "appendMimes": ".htm3:text/html;",    "responseCache": 604800,    "jwtSecret": "1234561234",    "limitSize": "20mb",    "allowExts": ".txt;.jpg;.jpeg;.png;.doc;.docx;.xls;.xlsx;.ppt;.pptx;.pdf",    "apps": {      "default": {        "allowOrigins": "",        "enableThumbnail": true,        "limitExts": ".exe;",        "thumbnailExts": ".jpg;.jpeg;.png;"      },      "app1": {        "allowOrigins": "*"      }    }  }}

2、前端

一般上传代码

$("#file1").change(function () {$.ajaxFileUpload({fileElementId: 'file1',url: 'http://localhost:6001/upload',dataType: 'text',//success: function (data) {console.log("上传成功:", data);},data: {"jwt": jwt}});});

分块上传

var uploader = new Uploader({target: 'http://localhost:6001/chunkupload',headers: {jwt: jwt}});uploader.assignBrowse(document.getElementById('browseButton'));//uploader.assignBrowse(document.getElementById('folderButton'), true);//// 文件添加 单个文件uploader.on('fileAdded', function (file, event) {console.log("fileAdded:", file, event)});// 单个文件上传成功uploader.on('fileSuccess', function (rootFile, file, message) {console.log("fileSuccess:", rootFile, file, message)});// 根下的单个文件(文件夹)上传完成uploader.on('fileComplete', function (rootFile) {console.log("fileComplete:", rootFile)});// 某个文件上传失败了uploader.on('fileError', function (rootFile, file, message) {console.log("fileError:", rootFile, file, message)});

3、后端

一般上传

 public async Task InvokeAsync(HttpContext context, RequestDelegate next){context.Response.Headers.Add("Access-Control-Allow-Origin", "*");context.Response.Headers.Add("Access-Control-Allow-Headers", "content-type,jwt,origin");if (context.Request.Method.Equals(HttpMethods.Options, StringComparison.OrdinalIgnoreCase)){context.Response.StatusCode = (int) HttpStatusCode.OK;}else if (context.Request.Method.Equals(HttpMethods.Post, StringComparison.OrdinalIgnoreCase)){//验证jwtstring token = null;if (context.Request.Headers.TryGetValue("jwt", out StringValues jwt)){token = jwt.ToString();}else if (context.Request.Form.TryGetValue("jwt", out jwt)){token = jwt.ToString();}else{await context.Response.WriteAsync(new UploadResult(){msg = "No JWT in the header and form"}.toJson());return;}try{var payload = new JwtBuilder().WithSecret(_config.JWTSecret).MustVerifySignature().Decode<JwtPayload>(token);var msg = payload.validate();if (msg != null){await context.Response.WriteAsync(new UploadResult(){msg = msg}.toJson());return;}//特定的配置var appConfig = _config.GetAppConfig(payload.app);//跨域context.Request.Headers.TryGetValue("Origin", out var origins);var origin = origins.ToString();if (!string.IsNullOrEmpty(origin) && appConfig.IsAllowOrigin(origin)){context.Response.Headers.Add("Access-Control-Allow-Origin", origin);}//获取上传的文件var file = context.Request.Form.Files.FirstOrDefault();if (file == null || file.Length == 0){await context.Response.WriteAsync(new UploadResult(){msg = "There is no file data"}.toJson());return;}//大小验证if (file.Length > (payload.GetByteSize() ?? _config.GetByteSize())){await context.Response.WriteAsync(new UploadResult(){msg = "The file is too big"}.toJson());return;}//后缀验证var ext = Path.GetExtension(file.FileName);if (!(payload.exts + _config.AllowExts).Contains(ext, StringComparison.OrdinalIgnoreCase)|| appConfig.LimitExts.Contains(ext, StringComparison.OrdinalIgnoreCase)){await context.Response.WriteAsync(new UploadResult(){msg = "File extension is not allowed"}.toJson());return;}//上传逻辑var now = DateTime.Now;var yy = now.ToString("yyyy");var mm = now.ToString("MM");var dd = now.ToString("dd");var fileName = Guid.NewGuid().ToString("n") + ext;var folder = Path.Combine(_config.PhysicalPath, payload.app, yy, mm, dd);if (!Directory.Exists(folder)){Directory.CreateDirectory(folder);}var filePath = Path.Combine(folder, fileName);using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write)){file.CopyTo(fileStream);fileStream.Flush(true);}var fileUrl = _config.RootUrl + "/" + payload.app + "/" + yy + "/" + mm +"/" +dd +"/" + fileName;await context.Response.WriteAsync(new UploadResult(){ok = true,url = fileUrl}.toJson());}catch (TokenExpiredException){await context.Response.WriteAsync(new UploadResult(){msg = "Token has expired"}.toJson());}catch (SignatureVerificationException){await context.Response.WriteAsync(new UploadResult(){msg = "Token has invalid signature"}.toJson());}}else{await context.Response.WriteAsync(new UploadResult(){msg = $"Request method '{context.Request.Method}' is not supported"}.toJson());}}

分块上传

public async Task InvokeAsync(HttpContext context, RequestDelegate next){context.Response.Headers.Add("Access-Control-Allow-Origin", "*");context.Response.Headers.Add("Access-Control-Allow-Headers", "content-type,jwt,origin");if (context.Request.Method.Equals(HttpMethods.Options, StringComparison.OrdinalIgnoreCase)){context.Response.StatusCode = (int)HttpStatusCode.OK;}else if (context.Request.Method.Equals(HttpMethods.Get, StringComparison.OrdinalIgnoreCase)){//简单实现context.Request.Query.TryGetValue("chunkNumber", out var chunkNumbers);int.TryParse(chunkNumbers.ToString(), out var chunkNumber);context.Request.Query.TryGetValue("identifier", out var identifiers);if (chunkNumber == 0 || string.IsNullOrEmpty(identifiers)){context.Response.StatusCode = 204;}else{var chunkFilename = getChunkFilename(_config.PhysicalPath, chunkNumber, identifiers);if (File.Exists(chunkFilename)){await context.Response.WriteAsync("found");}else{context.Response.StatusCode = 204;}}}else if (context.Request.Method.Equals(HttpMethods.Post, StringComparison.OrdinalIgnoreCase)){//验证jwtstring token = null;if (context.Request.Headers.TryGetValue("jwt", out StringValues jwt)){token = jwt.ToString();}else if (context.Request.Form.TryGetValue("jwt", out jwt)){token = jwt.ToString();}else{await context.Response.WriteAsync(new UploadResult(){msg = "No JWT in the header and form"}.toJson());return;}try{var payload = new JwtBuilder().WithSecret(_config.JWTSecret).MustVerifySignature().Decode<JwtPayload>(token);var msg = payload.validate();if (msg != null){await context.Response.WriteAsync(new UploadResult(){msg = msg}.toJson());return;}//特定的配置var appConfig = _config.GetAppConfig(payload.app);//跨域context.Request.Headers.TryGetValue("Origin", out var origins);var origin = origins.ToString();if (!string.IsNullOrEmpty(origin) && appConfig.IsAllowOrigin(origin)){context.Response.Headers.Add("Access-Control-Allow-Origin", origin);}//获取上传的文件分片var file = context.Request.Form.Files.FirstOrDefault();if (file == null || file.Length == 0){await context.Response.WriteAsync(new UploadResult(){msg = "There is no file data"}.toJson());return;}//后缀验证var ext = Path.GetExtension(file.FileName);if (!(payload.exts + _config.AllowExts).Contains(ext, StringComparison.OrdinalIgnoreCase)|| appConfig.LimitExts.Contains(ext, StringComparison.OrdinalIgnoreCase)){await context.Response.WriteAsync(new UploadResult(){msg = "File extension is not allowed"}.toJson());return;}//获取参数                    getParams(context, out var chunkNumber, out var chunkSize, out var totalSize, out string identifier,out string filename, out int totalChunks);//验证参数var validMsg = validateRequest(chunkNumber, chunkSize, totalSize, identifier, filename, file.Length, totalChunks, payload.GetByteSize() ?? _config.GetByteSize());if (validMsg != null){await context.Response.WriteAsync(new UploadResult(){msg = validMsg}.toJson());return;}else{var chunkFilename = getChunkFilename(_config.PhysicalPath, chunkNumber, identifier);try{using (var fileStream = File.OpenWrite(chunkFilename)){var stream = file.OpenReadStream();stream.CopyTo(fileStream);fileStream.Flush(true);countDict.AddOrUpdate(identifier, 1, (key, oldValue) => oldValue + 1);}if (chunkNumber == totalChunks){//验证块的完整性while (true){if (countDict.GetValueOrDefault(identifier) < totalChunks){await Task.Delay(TimeSpan.FromMilliseconds(500));}else{countDict.Remove(identifier, out _);break;}}//merge file;string[] chunkFiles = Directory.GetFiles(Path.Combine(_config.PhysicalPath, temporaryFolder),"uploader-" + identifier + ".*",SearchOption.TopDirectoryOnly);var fileUrl = await MergeChunkFiles(payload, ext, chunkFiles);await context.Response.WriteAsync(new UploadResult(){ok = true,url = fileUrl}.toJson());}else{await context.Response.WriteAsync("partly_done");return;}}catch (Exception exp){await context.Response.WriteAsync(new UploadResult(){msg = exp.Message}.toJson());return;}}}catch (TokenExpiredException){await context.Response.WriteAsync(new UploadResult(){msg = "Token has expired"}.toJson());}catch (SignatureVerificationException){await context.Response.WriteAsync(new UploadResult(){msg = "Token has invalid signature"}.toJson());}}else{context.Response.StatusCode = (int)HttpStatusCode.MethodNotAllowed;await context.Response.WriteAsync($"Request method '{context.Request.Method}' is not supported");}}

4、上传结果

上传成功

{"ok":true,"msg":null,"url":"http://localhost:6001/test/2019/06/17/abcd.jpg"}

上传失败

{"ok":false,"msg":"The file is too big","url":null}

Gitee:https://gitee.com/loogn/UploadServer

- End -

推荐阅读

  • 推荐一款基于 .NET Core开源的小程序商城系统

  • 推荐基于.Net6+Furion +iView开发的一套极简的进销存管理系统

  • Sampler:可视化数据库监控警报工具

  • 一个基于.Net Core+Vue+Element Ui开发的OA系统

  • **推荐一个.Net常用代码集合,助你高效完成业务
    **

专注分享编程知识、热门有用有趣的开源项目

NetCore开发的分布式文件上传系统相关推荐

  1. [转]仿163网盘无刷新文件上传系统

    原文链接:http://www.cnblogs.com/cloudgamer/archive/2008/10/20/1314766.html 这个仿163网盘无刷新文件上传系统,并没有用使用.net的 ...

  2. 仿163网盘无刷新多文件上传系统

    这个仿163网盘无刷新多文件上传系统,并没有用使用.net的控件,完全的手工制作.前台基本上是静态的,跟后台没有关系,所以后台用什么语言做都可以(后面有各个版本的实例下载). 本来觉得这个系统会很复杂 ...

  3. 【arduino】arudino开发ESP32 SPIFFS文件上传方法

    微信关注 "DLGG创客DIY" 设为"星标",重磅干货,第一时间送达. 之前发过ESP8266的SPIFFS文件及上传方法: [arduino]arudino ...

  4. 使用jspsmartupload完成简单的文件上传系统

    请不要妄想,一个html的file控件,再加上JavaScript与jQuery语句就可以完成文件上传, 文件上传系统从来是需要配合服务器来完成的 用户把自己的文件上传到服务器上 文件上传系统是很复杂 ...

  5. multer 文件上传系统在express中的使用

    multer 文件上传系统在express中的使用 参考: 技术栈 Multer 是一个node.js中间件,用于处理 multipart/form-data类型的表单数据,主要用于上传文件. 在fo ...

  6. Node开发文件上传系统及向七牛云存储和亚马逊AWS S3的文件上传

    背景起,有奏乐: 有伟人曰:学习技能的最好途径莫过于理论与实践相结合. 初学Node这货时,每每读教程必会Fall asleep. 当真要开发系统时,顿觉精神百倍,即便踩坑无数也不失斗志. 因为同团队 ...

  7. 在本地测试无组件上传类上传大文件可以,在服务器上就不行,仿163网盘无刷新文件上传系统...

    回复  引用  查看     2008-10-20 11:03 | fkeuem 真的很不错.谢谢. 回复  引用  查看     2008-10-20 11:20 | PuserChen 下载了,学 ...

  8. 安卓开发8-WebView支持文件上传

    安卓手机中采用webview访问OA系统,当OA中使用input=file的方式时,点选择文件没有反应,需要在WebChromeClient中增加openFileChooser方法:chrome浏览器 ...

  9. 简便无刷新文件上传系统

    兼容:ie6/7/8, firefox 3.5.5, opera 10.01, safari 4.0.3, chrome 3.0 效果预览 文件上传 选择文件 重命名 操作 状态    重置 选择文件 ...

最新文章

  1. 1048 Find Coins(二分法解法)
  2. 简单工厂模式(StaticFactoryMethod)
  3. oracle 树 向上查询,Oracle中显示树结构查询语句【子查父和父查子】
  4. Feign使用Hystrix无效原因及解决方法
  5. ALV输出无法指定STATUS
  6. Struts的MVC和Spring的MVC的区别
  7. 15件事造就有理想的程序员
  8. JVM 内存示意图(内存结构图/内存解析图)
  9. java中按钮的接口_Java接口基础
  10. linux 例行性工作,鳥哥的 Linux 私房菜
  11. 平面设计ai教程笔记
  12. DSP芯片TMS320C6678的spi挂载flash启动
  13. 《微积分:一元函数积分学》——基本积分表
  14. 基本数据类型的默认值
  15. 【1月英语—罗塞塔之爱】
  16. AutoHotKey进阶 --- 单击网页中的按钮(Acc库)
  17. 3.1 学习计算机,从编程入手 ——《逆袭大学》连载
  18. for循环的执行顺序(案例+详解)
  19. 可解释知识追踪(整理更新)
  20. CUDA Installer 前面的 X

热门文章

  1. 违约概率和违约损失率
  2. Unit 5: Windows Acquisition 5.1 Windows Acquisition Windows Forensic Imaging of Drives
  3. JAVA毕业设计数字家谱管理系统设计与实现计算机源码+lw文档+系统+调试部署+数据库
  4. PCB800661驱动宽屏(480x1280分辨率)LVDS液晶屏
  5. jQuery拖动滑块验证样式
  6. 【低代码开发】智慧水务解决方案
  7. 南海区行政审批管理系统接口规范v0.3(规划)
  8. onlyoffice document server实时文档协作的部署与开发细节
  9. Docker 安装 TensorFlow GPU 实战
  10. LTE RSRQ 报告值与 RSRQ 质量换算关系