最新要闻
- 北京白领通勤天花板,单手拎起10秒折叠,网友:老板我需要
- 今日讯!针刺成小儿科?埃安官宣弹匣电池2.0枪击试验发布会
- 环球观速讯丨《生化危机4:重制版》DLC正在开发中 艾达王是你的了
- 每日快播:深圳暴雨致航班取消 旅客情绪崩溃 跪求起飞 机场回应
- 环球通讯!全明星的三大遗憾,成就了这场本土篮球盛宴的缺憾之美
- 实时焦点:RTX 4070定了!还是你们讨厌的192位显存
- 每日看点!彻底没救了!《Forspoken》发售2个月后:RTX 4090依旧无法60帧
- 【播资讯】100亿捐款建大学 曹德旺:对标美国斯坦福 每个学生补贴5万
- 每日信息:做出莫斯利安的光明乳业:掉队了
- 女员工请假做试管婴儿被拒遭辞退 法院判了:恢复合同
- 热消息:4月10日上市 福特F-150猛禽烈焰风暴特别版官图发布 为中国市场而
- 能抄底了?一汽奥迪补贴经销商清库存 明星车型降价近10万
- 每日看点!马云回国首谈ChatGPT:机器只有“芯”而人有“心”
- 国产车赢麻了!报告:中国品牌占俄罗斯新车销量近40%
- 焦点信息:“高端家用车”?比亚迪汉DM-i试驾 极致的油耗和空间
- 天天视讯!国产两轮电动车龙头!雅迪2022年营收310亿创纪录:电动自行车销量大增
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
全球快看:基于.NET Core + Jquery实现文件断点分片上传
基于.NET Core + Jquery实现文件断点分片上传
前言
该项目是基于.NET Core 和 Jquery实现的文件分片上传,没有经过测试,因为博主没有那么大的文件去测试,目前上传2G左右的文件是没有问题的。
(资料图片仅供参考)
使用到的技术
- Redis缓存技术
- Jquery ajax请求技术
为什么要用到Redis,文章后面再说,先留个悬念。
页面截图
NuGet包
Microsoft.Extensions.Caching.StackExchangeRedis
Zack.ASPNETCore 杨中科封装的操作Redis包
分片上传是如何进行的?
在实现代码的时候,我们需要了解文件为什么要分片上传,我直接上传不行吗。大家在使用b站、快手等网站的视频上传的时候,可以发现文件中断的话,之前已经上传了的文件再次上传会很快。这就是分片上传的好处,如果发发生中断,我只要上传中断之后没有上传完成的文件即可,当一个大文件上传的时候,用户可能会断网,或者因为总总原因导致上传失败,但是几个G的文件,难不成又重新上传吗,那当然不行。
具体来说,分片上传文件的原理如下:
- 客户端将大文件切割成若干个小文件块,并为每个文件块生成一个唯一的标识符,以便后续的合并操作。
- 客户端将每个小文件块上传到服务器,并将其标识符和其他必要的信息发送给服务器。
- 服务器接收到每个小文件块后,将其保存在临时文件夹中,并返回一个标识符给客户端,以便客户端后续的合并操作。
- 客户端将所有小文件块的标识符发送给服务器,并请求服务器将这些小文件块合并成一个完整的文件。
- 服务器接收到客户端的请求后,将所有小文件块按照其标识符顺序进行合并,并将合并后的文件保存在指定的位置。
- 客户端接收到服务器的响应后,确认文件上传成功。
总的来说,分片上传文件的原理就是将一个大文件分成若干个小文件块,分别上传到服务器,最后再将这些小文件块合并成一个完整的文件。
在了解原理之后开始实现代码。
后端实现
注册reidis服务
首先在Program.cs
配置文件中注册reidis服务
builder.Services.AddScoped();//注册redis服务builder.Services.AddStackExchangeRedisCache(options =>{ string connStr = builder.Configuration.GetSection("Redis").Value; string password = builder.Configuration.GetSection("RedisPassword").Value; //redis服务器地址 options.Configuration = $"{connStr},password={password}";});
在appsettings.json中配置redis相关信息
"Redis": "redis地址", "RedisPassword": "密码"
保存文件的实现
在控制器中注入
private readonly IWebHostEnvironment _environment;private readonly IDistributedCacheHelper _distributedCache;public UpLoadController(IDistributedCacheHelper distributedCache, IWebHostEnvironment environment) { _distributedCache = distributedCache; _environment = environment; }
从redis中取文件名
string GetTmpChunkDir(string fileName) { var s = _distributedCache.GetOrCreate(fileName, ( e) => { //滑动过期时间 //e.SlidingExpiration = TimeSpan.FromSeconds(1800); //return Encoding.Default.GetBytes(Guid.NewGuid().ToString("N")); return fileName.Split(".")[0]; }, 1800); if (s != null) return fileName.Split(".")[0]; ; return "";}
实现保存文件方法
/// /// 保存文件 /// /// 文件 /// 文件名 /// 文件块 /// 分块数 /// public async Task SaveFile(IFormFile file, string fileName, int chunkIndex, int chunkCount) { try { //说明为空 if (file.Length == 0) { return Json(new { success = false, mas = "文件为空!!!" }); } if (chunkIndex == 0) { ////第一次上传时,生成一个随机id,做为保存块的临时文件夹 //将文件名保存到redis中,时间是s _distributedCache.GetOrCreate(fileName, (e) => { //滑动过期时间 //e.SlidingExpiration = TimeSpan.FromSeconds(1800); //return Encoding.Default.GetBytes(Guid.NewGuid().ToString("N")); return fileName.Split(".")[0]; ; }, 1800); } if(!Directory.Exists(GetFilePath())) Directory.CreateDirectory(GetFilePath()); var fullChunkDir = GetFilePath() + dirSeparator + GetTmpChunkDir(fileName); if(!Directory.Exists(fullChunkDir)) Directory.CreateDirectory(fullChunkDir); var blog = file.FileName; var newFileName = blog + chunkIndex + Path.GetExtension(fileName); var filePath = fullChunkDir + Path.DirectorySeparatorChar + newFileName; //如果文件块不存在则保存,否则可以直接跳过 if (!System.IO.File.Exists(filePath)) { //保存文件块 using (var stream = new FileStream(filePath, FileMode.Create)) { await file.CopyToAsync(stream); } } //所有块上传完成 if (chunkIndex == chunkCount - 1) { //也可以在这合并,在这合并就不用ajax调用CombineChunkFile合并 //CombineChunkFile(fileName); } var obj = new { success = true, date = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), newFileName, originalFileName = fileName, size = file.Length, nextIndex = chunkIndex + 1, }; return Json(obj); } catch (Exception ex) { return Json(new { success = false, msg = ex.Message, }); } }
讲解关键代码 Redis部分
当然也可以放到session里面,这里就不做演示了。
这是将文件名存入到redis中,作为唯一的key值,当然这里最好采用
Encoding.Default.GetBytes(Guid.NewGuid().ToString("N"));
去随机生成一个id保存,为什么我这里直接用文件名,一开始写这个是为了在学校上机课时和室友之间互相传文件,所以没有考虑那么多,根据自己的需求来。
在第一次上传文件的时候,redis会保存该文件名,如果reids中存在该文件名,那么后面分的文件块就可以直接放到该文件名下。
_distributedCache.GetOrCreate(fileName, (e) => { //滑动过期时间 //e.SlidingExpiration = TimeSpan.FromSeconds(1800); //return Encoding.Default.GetBytes(Guid.NewGuid().ToString("N")); return fileName.Split(".")[0]; ;}, 1800);
合并文件方法
//目录分隔符,兼容不同系统static readonly char dirSeparator = Path.DirectorySeparatorChar;
//获取文件的存储路径//用于保存的文件夹private string GetFilePath(){ return Path.Combine(_environment.WebRootPath, "UploadFolder");}
public async Task CombineChunkFile(string fileName) { try { return await Task.Run(() => { //获取文件唯一id值,这里是文件名 var tmpDir = GetTmpChunkDir(fileName); //找到文件块存放的目录 var fullChunkDir = GetFilePath() + dirSeparator + tmpDir;//开始时间 var beginTime = DateTime.Now; //新的文件名 var newFileName = tmpDir + Path.GetExtension(fileName); var destFile = GetFilePath() + dirSeparator + newFileName; //获取临时文件夹内的所有文件块,排好序 var files = Directory.GetFiles(fullChunkDir).OrderBy(x => x.Length).ThenBy(x => x).ToList(); //将文件块合成一个文件 using (var destStream = System.IO.File.OpenWrite(destFile)) { files.ForEach(chunk => { using (var chunkStream = System.IO.File.OpenRead(chunk)) { chunkStream.CopyTo(destStream); } System.IO.File.Delete(chunk); }); Directory.Delete(fullChunkDir); }//结束时间 var totalTime = DateTime.Now.Subtract(beginTime).TotalSeconds; return Json(new { success = true, destFile = destFile.Replace("\\", "/"), msg = $"合并完成 ! {totalTime} s", }); }); }catch (Exception ex) { return Json(new { success = false, msg = ex.Message, }); } finally { _distributedCache.Remove(fileName); }}
前端实现
原理
原理就是获取文件,然后切片,通过分片然后递归去请求后端保存文件的接口。
首先引入Jquery
<script src="~/lib/jquery/dist/jquery.min.js"></script>
然后随便写一个上传页面
将文件拖拽到这里上传
或者
0%
css实现
稍微让页面能够看得下去
Jqueuy代码实现
<script> $(function(){ var pause = false;//是否暂停 var $btnQuxiao = $("#btnQuxiao"); //暂停上传 var $file; //文件 var $completedChunks = $("#completedChunks");//上传完成块数 var $progress = $("#progress");//上传进度条 var $percent = $("#percent");//上传百分比 var MiB = 1024 * 1024; var chunkSize = 8.56 * MiB;//xx MiB var chunkIndex = 0;//上传到的块 var totalSize;//文件总大小 var totalSizeH;//文件总大小M var chunkCount;//分块数 var fileName;//文件名 var dropzone = $("#dropzone"); //拖拽 var $fileInput = $("#file1"); //file元素 var $btnfile = $("#btnfile"); //选择文件按钮 //通过自己的button按钮去打开选择文件的功能 $btnfile.click(function(){ $fileInput.click(); }) dropzone.on("dragover", function () { $(this).addClass("hover"); return false; }); dropzone.on("dragleave", function () { $(this).removeClass("hover"); return false; }); dropzone.on("drop", function (e) { setBtntrue(); e.preventDefault(); $(this).removeClass("hover"); var val = $("#btnfile").val() if (val == "Upload") { $file = e.originalEvent.dataTransfer.files[0]; if ($file === undefined) { $completedChunks.html("请选择文件 !"); return false; } totalSize = $file.size; chunkCount = Math.ceil(totalSize / chunkSize * 1.0); totalSizeH = (totalSize / MiB).toFixed(2); fileName = $file.name; $("#fName").html(fileName); $("#btnfile").val("Pause") pause = false; chunkIndex = 0; } postChunk(); }); $fileInput.change(function () { setBtntrue(); console.log("开始上传文件!") var val = $("#btnfile").val() if (val == "Upload") { $file = $fileInput[0].files[0]; if ($file === undefined) { $completedChunks.html("请选择文件 !"); return false; } totalSize = $file.size; chunkCount = Math.ceil(totalSize / chunkSize * 1.0); totalSizeH = (totalSize / MiB).toFixed(2); fileName = $file.name; $("#fName").html(fileName); $("#btnfile").val("Pause") pause = false; chunkIndex = 0; } postChunk(); }) function postChunk() { console.log(pause) if (pause) return false; var isLastChunk = chunkIndex === chunkCount - 1; var fromSize = chunkIndex * chunkSize; var chunk = !isLastChunk ? $file.slice(fromSize, fromSize + chunkSize) : $file.slice(fromSize, totalSize); var fd = new FormData(); fd.append("file", chunk); fd.append("chunkIndex", chunkIndex); fd.append("chunkCount", chunkCount); fd.append("fileName", fileName); $.ajax({ url: "/UpLoad/SaveFile", type: "POST", data: fd, cache: false, contentType: false, processData: false, success: function (d) { if (!d.success) { $completedChunks.html(d.msg); return false; } chunkIndex = d.nextIndex; //递归出口 if (isLastChunk) { $completedChunks.html("合并 .. "); $btnfile.val("Upload"); setBtntrue(); //合并文件 $.post("/UpLoad/CombineChunkFile", { fileName: fileName }, function (d) { $completedChunks.html(d.msg); $completedChunks.append("destFile: " + d.destFile); $btnfile.val("Upload"); setBtnfalse() $fileInput.val("");//清除文件 $("#fName").html(""); }); } else { postChunk();//递归上传文件块 //$completedChunks.html(chunkIndex + "/" + chunkCount ); $completedChunks.html((chunkIndex * chunkSize / MiB).toFixed(2) + "M/" + totalSizeH + "M"); } var completed = chunkIndex / chunkCount * 100; $percent.html(completed.toFixed(2) + "%").css("margin-left", parseInt(completed / 100 * $progress.width()) + "px"); $progress.css("background", "linear-gradient(to right, #ff0084 " + completed + "%, #e8c5d7 " + completed + "%)"); }, error: function (ex) { $completedChunks.html("ex:" + ex.responseText); } }); } $btnQuxiao.click(function(){ var val = $("#btnfile").val(); if (val == "Pause") { $btnQuxiao.css("background-color", "grey"); val = "Resume"; pause = true; } else if (val === "Resume") { $btnQuxiao.css("background-color", "greenyellow"); val = "Pause"; pause = false; } else { $("#btnfile").val("-"); } console.log(val + "" + pause) $("#btnfile").val(val) postChunk(); }) //设置按钮可用 function setBtntrue(){ $btnQuxiao.prop("disabled", false) $btnQuxiao.css("background-color", "greenyellow"); } //设置按钮不可用 function setBtnfalse() { $btnQuxiao.prop("disabled", true) $btnQuxiao.css("background-color", "grey"); } })</script>
合并文件请求
var isLastChunk = chunkIndex === chunkCount - 1;
当isLastChunk 为true时,执行合并文件,这里就不会再去请求保存文件了。
总结
分片上传文件原理很简单,根据原理去实现代码,慢慢的摸索很快就会熟练掌握,当然本文章有很多写的不好的地方可以指出来,毕竟博主还只是学生,需要不断的学习。
有问题评论,看到了会回复。
参考资料
- https://www.bilibili.com/video/BV1rB4y1Q7dF/?share_source=copy_web&vd_source=fce337a51d11a67781404c67ec0b5084
关键词:
-
全球快看:基于.NET Core + Jquery实现文件断点分片上传
基于 NETCore+Jquery实现文件断点分片上传前言该项目是基于 NETCore和Jquery实现的文件分片上传,没有...
来源: 前沿热点:美团面试:熟悉哪些JVM调优参数?
全球快看:基于.NET Core + Jquery实现文件断点分片上传
北京白领通勤天花板,单手拎起10秒折叠,网友:老板我需要
今日讯!针刺成小儿科?埃安官宣弹匣电池2.0枪击试验发布会
世界微资讯!手撕HashMap
每日资讯:一些面试高频题目
Synchronized详解
第134篇:解决浏览器的CORS跨域问题(CORS policy: Cross origin requests are only supported for p
环球观速讯丨《生化危机4:重制版》DLC正在开发中 艾达王是你的了
每日快播:深圳暴雨致航班取消 旅客情绪崩溃 跪求起飞 机场回应
环球通讯!全明星的三大遗憾,成就了这场本土篮球盛宴的缺憾之美
【Visual Leak Detector】配置项 AggregateDuplicates
Python毕业设计推荐
实时焦点:RTX 4070定了!还是你们讨厌的192位显存
每日看点!彻底没救了!《Forspoken》发售2个月后:RTX 4090依旧无法60帧
【播资讯】100亿捐款建大学 曹德旺:对标美国斯坦福 每个学生补贴5万
每日信息:做出莫斯利安的光明乳业:掉队了
女员工请假做试管婴儿被拒遭辞退 法院判了:恢复合同
环球观察:Vue 核心(二)
观焦点:HDFS Short-Circuit Local Reads
货币市场日报:3月27日
热消息:4月10日上市 福特F-150猛禽烈焰风暴特别版官图发布 为中国市场而
能抄底了?一汽奥迪补贴经销商清库存 明星车型降价近10万
每日看点!马云回国首谈ChatGPT:机器只有“芯”而人有“心”
国产车赢麻了!报告:中国品牌占俄罗斯新车销量近40%
焦点信息:“高端家用车”?比亚迪汉DM-i试驾 极致的油耗和空间
天天视讯!国产两轮电动车龙头!雅迪2022年营收310亿创纪录:电动自行车销量大增
焦点!通胀压力缓解避险情绪收敛 日债收益率周一全线回升
世界速读:商品日报(3月27日):沪锡领涨纯碱09合约增仓上行 苹果大幅下挫跌超4%
速读:前备箱门锁失灵?特斯拉扩大召回进口Model S
京东方iPhone 15灵动岛屏被曝漏光 苹果搬来三星救火:提前生产
天天热点评!女子赏花站梨树枝头甩衣狂摇 拍摄者:花瓣掉落一地
观热点:女子吃自助餐将店内甲鱼煮食 网友:是个狠人
乌鲁木齐周生生黄金价格多少钱一克(2023年3月27日)
新动态:CodeIgnitor 3.0.x 之 db 类实现机制
第一次博客
环球热点评!小皮Windows web面板漏洞详解
世界观速讯丨【原型设计工具】上海道宁为您提供Justinmind,助力您在几分钟内形成原型,并现场测试,无需编写任何代码
女生从俄罗斯花2分钟回国吃麻辣烫 挑战全网最短留学距离:网友感慨真近
环球观点:文心一言升级版!百度发布企业级大模型服务平台“文心千帆”
当前速讯:太危险!女子驾驶保时捷玩具车上机动车道吓坏路人:自称为了好玩
承认造假丑闻!百年日企川崎重工形象崩塌:鞠躬道歉
资讯推荐:男子跑滴滴1单挣了3148元:光等待费就两千多元
环球要闻:从GPT-4、文心一言再到Copilot,AIGC卷出新赛道?
即时:【必须收藏】别再乱找TiDB 集群部署教程了,这篇保姆级教程来帮你!!| 博学谷狂野架构师
当前关注:C#多态性学习,虚方法、抽象方法、接口等用法举例
python中index()、find()方法
每日视点!便宜香港主机推荐
环球速递!秒变大屏平板!三星将推新款折叠手机:双铰链、三折叠
天天报道:专家评中国为何没跟上ChatGPT浪潮:0-1的事情没人做
电动车价格还得降 宁德钠离子电池今年量产:高寒地区突出优势
广汽埃安新车慢充失灵 车主:4S店给补偿 但协议他不想签
天天热推荐:日本声优网络发文吐槽外送被打翻 网友:小心隐私
天天精选!批量上传iOS应用程序截图的实用技巧
天天热头条丨OPPO发布新机|无感验证护航“黄牛党”退退退!
环球滚动:易基因:肠道菌群:早产儿出生后不同时间点肠道微生物定植的动态变化|项目文章
快播:Qt 博客汇总
天天热推荐:图片识别文字(批量图片文字查找分类)
普京称嫉妒的人才说俄依赖中国 欧洲应该想想自己
科创板收盘播报:科创50指数探底回升跌0.33% 软件服务类个股表现强势
甲醛滤芯永不更换!米家全效空气净化器评测:不仅除尘、还能杀死H1N1病毒
顺丰开通贵州茅台首条全货机航线 10余吨酒、茶特产首飞成功
每日资讯:飞机航班为何不卖站票?大V科普:并非航空公司良心
罕见5风扇设计!铭瑄RTX 4070 Ti MGG OC12G显卡图赏
三电升级、寿命翻倍!绿源液冷2.0系统升级上线:能骑十年
Transformer详解
全球速递!解决google翻译出错问题
世界通讯!【高端访谈】“开放的中国大市场蕴藏更多新机遇”——访马士基首席执行官柯文胜
以铜之名尽显千年风流
全球信息:打造中国ChatGPT 搜狗输入法之父马占凯加入王慧文AI团队
独一无二木星纹理见过么?一加11推出木星岩限定版:100%手工挑选
环球快看:曾被宣布“野外灭绝” 长江鲟时隔23年首次自然产卵
当前速读:ubuntu 使用vsftpd搭建FTP服务器
全球热议:6.824-lab2-Raft简述
环球滚动:Template模板类
全球讯息:【Visual Leak Detector】配置项 VLD
【速看料】火山引擎 DataTester:构建增长闭环,3-5 人即可搭建企业增长团队
【新要闻】曾因配料“双标”遭网友痛批 好丽友漏税22万被罚12万
全球时讯:空姐为美容注射玻尿酸:打进血管 险丧命
三星W799哪年上市的?三星W799可以安装微信吗?
诺基亚920还能用吗?诺基亚920手机参数
英雄联盟手游大师多少颗星上宗师?英雄联盟几级可以组队匹配?
总线上的音频设备前是感叹号是什么原因?总线上的音频设备前是感叹号怎么处理?
华为合约机是什么意思?华为合约机和官方标配区别有哪些?
C#遍历指定文件夹中所有文件的3种方法
天天热议:Sed 备忘清单_开发速查表分享
史上最严奶粉新国标实施逾月 贝因美又一款二注奶粉上新 哪些信号值得关注?
热推荐:甘肃将为兰州牛肉拉面立法 网友:建议牛肉有重量标准
热点在线丨《原神》地位不保?开放世界游戏《鸣潮》实机画面公布
环球简讯:曹德旺:大学生去送外卖 和在学校没学到东西有关
任性钻“夹缝”被撞旋转1080° 司机甩锅:都怪后车开太快了
当前快播:判赔370万!中创新航回应不正当竞争案判决:未曾挖角宁德时代 将上诉
天天百事通!OpenIddict使用教程
流量分析入门
全球观速讯丨MySql随笔记基础
【高端访谈】强化城投债与量化领域主动管理 打造私募“固收+”独特策略——专访国恩资本董事总经理杨先哲
通讯!旃怎么读音(旃怎么读)
36年前电脑用上AI:IBM 5155运行ChatGPT是什么样?
环球看热讯:清晰度和功耗完美平衡!真我GT Neo5系列宣布告别1080P:普及1.5K屏
罕见!超级火流星划过北京夜空被拍下:彩色拖尾 爆裂闪光