.NET 7.0 基于tus协议实现断点续传上传文件

前言

由于看到网上的断点续传的文章,也不能说是同出一辙,那简直一模一样,于是有了此文章,不会的童鞋可以上GIthub上查看DEMO。

基于tus协议实现断点续传演示.

.NET 7.0 基于tus协议实现断点续传上传文件

基于tus协议前端脚本

1、关于此协议实现原理这里不做阐述,请参照上述github地址自行了解,本文只给出相关的代码,仅供参考!

<link  href="~/lib/layui/css/layui.css" rel="stylesheet"/>
<script src="~/lib/layui/layui.min.js"></script>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/tus-js-client/dist/tus.min.js"></script>

<div class="form-horizontal" style="margin:100px auto;text-align:center;">
 <div class="form-group" id="progress-group" style="display:block;">
  <div id="size"></div>
  <div class="progress">
   <div id="progress" class="progress-bar progress-bar-success progress-bar-animated progress-bar-striped" role="progressbar"
     aria-valuemin="0" aria-valuemax="100">
    <span id="percentage"></span>
   </div>
  </div>
 </div>
 <div class="form-group" style="margin-top: 15px;">
  <div class="row">
   <div class="input-group">
    <input name="file" id="file" type="file" class="form-control" aria-describedby="file" aria-label="Upload">
    <button class="btn btn-outline-secondary" type="button" id="upload">上传</button>
    <button class="btn btn-outline-danger" type="button" id="pause">暂停</button>
    <button class="btn btn btn-outline-info" type="button" id="continue">继续</button>
   </div>
  </div>
 </div>
</div>
.NET 7.0 基于tus协议实现断点续传上传文件

2、接下来就是我们的js代码,引入tus脚本包和相关的js文件就可以了。

<script>
 $(function () {
  
  layui.use(['layer', 'util'], function () {
   var layer = layui.layer,
   util = layui.util;

   var upload;

   //上传
   $('#upload').click(function () {

    $('#progress-group').show();

    
    var fileInput = $('#file').get(0).files[0];

    if (!fileInput) {
     layer.msg("请选择上传文件!");
     return;
    } 

    if (upload) {
     layer.msg("已经选择过上传文件啦!");
     return;
    }

    // 创建tus上传对象
    upload = new tus.Upload($('#file')[0].files[0], {
     // 文件服务器上传终结点地址设置
     endpoint: "uploadfile/",
     // 重试延迟设置
     retryDelays: [0, 3000, 5000, 10000, 20000],
     // 附件服务器所需的元数据
     metadata: {
      name: file.name,
      contentType: file.type || 'application/octet-stream',
      emptyMetaKey: ''
     },
     // 回调无法通过重试解决的错误
     onError: function (error) {
      console.log("Failed because: " + error)
     },
     // 上传进度回调
     onProgress: onProgress,
     // 上传完成后回调
     onSuccess: function () {
      layer.msg("上传成功,文件名:" + upload.file.name);
      console.log("Download %s from %s", upload.file.name, upload.url)
      upload=undefined;
     }
    })

    upload.findPreviousUploads().then((previousUploads) => {

     var chosenUpload = askToResumeUpload(previousUploads);

     if (chosenUpload) {
      upload.resumeFromPreviousUpload(chosenUpload);
     }

     upload.start();
    });


    // upload.start()
   });

   //暂停
   $('#pause').click(function () {
    if (!upload) {
     layer.msg("请先开始上传!")
     return;
    }
    if (upload._aborted) {
     layer.msg("已经暂停上传啦!")
     return;
    }
    upload.abort()
    console.log(upload._aborted)
   });

   //继续
   $('#continue').click(function () {
    if (!upload) {
     layer.msg("请先开始上传!")
     return;
    }
    if (!upload._aborted) {
     layer.msg("请先暂停上传!")
     return;
    }
    upload.start()
    console.log(upload._aborted)
   });

   function askToResumeUpload(previousUploads) {
    if (previousUploads.length === 0) return null;

    console.log(previousUploads);

    var text = "系统查询到您之前上传过此文件是否恢复上传?:\n\n";
    previousUploads.forEach((previousUpload, index) => {
     text += "[" + index + "] " + util.toDateString(previousUpload.creationTime) + "\n";
    });
    text += "\n输入相应的号码恢复上传或按“取消”键重新上传";

    var answer = prompt(text);
    var index = parseInt(answer, 10);

    if (!isNaN(index) && previousUploads[index]) {
     return previousUploads[index];
    }
   }

   //上传进度展示
   function onProgress(bytesUploaded, bytesTotal) {
    var percentage = (bytesUploaded / bytesTotal * 100).toFixed(2);
    $('#progress').attr('aria-valuenow', percentage);
    $('#progress').css('width', percentage + '%');

    $('#percentage').html(percentage + '%');

    var uploadBytes = byteToSize(bytesUploaded);
    var totalBytes = byteToSize(bytesTotal);

    $('#size').html(uploadBytes + '/' + totalBytes);
   }

   //将字节转换为Byte、KB、MB等
   function byteToSize(bytes, separator = '', postFix = '') {
    if (bytes) {
     const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
     const i = Math.min(parseInt(Math.floor(Math.log(bytes) / Math.log(1024)).toString(), 10), sizes.length - 1);
     return `${(bytes / (1024 ** i)).toFixed(i ? 1 : 0)}${separator}${sizes[i]}${postFix}`;
    }
    return 'n/a';
   }
  });
 });
</script>

创建WebAPI

1、我们创建一个新项目,引用一个tus包 tusdotnet

.NET 7.0 基于tus协议实现断点续传上传文件

2、接下来就是添加中间件,并且配置我们的tus,这里我只实现了相关文件上传后的处理,以及上传文件后垃圾文件回收处理。

private static DefaultTusConfiguration CreateTusConfiguration(WebApplicationBuilder builder)
        {
            var env = builder.Environment.WebRootPath;

            //文件上传路径
            var tusFiles = Path.Combine(env, "tusfiles");

            if (!Directory.Exists(tusFiles))
            {
                Directory.CreateDirectory(tusFiles);
            }

            return new DefaultTusConfiguration() {
                UrlPath= "/uploadfile",
                //文件存储路径
                Store = new TusDiskStore(tusFiles),
                //元数据是否允许空值
                MetadataParsingStrategy = MetadataParsingStrategy.AllowEmptyValues,
                //文件过期后不再更新
                Expiration = new AbsoluteExpiration(TimeSpan.FromMinutes(5)),
                //事件处理(各种事件,满足你所需)
                Events = new Events
                {
                    //上传完成事件回调
                    OnFileCompleteAsync = async ctx =>
                    {
                        //获取上传文件
                        var file = await ctx.GetFileAsync();

                        //获取上传文件元数据
                        var metadatas = await file.GetMetadataAsync(ctx.CancellationToken);

                        //获取上述文件元数据中的目标文件名称
                        var fileNameMetadata = metadatas["name"];

                        //目标文件名以base64编码,所以这里需要解码
                        var fileName = fileNameMetadata.GetString(Encoding.UTF8);

                        var extensionName = Path.GetExtension(fileName);

                        //将上传文件转换为实际目标文件
                        File.Move(Path.Combine(tusFiles, ctx.FileId), Path.Combine(tusFiles, $"{ctx.FileId}{extensionName}"));

                        var terminationStore = ctx.Store as ITusTerminationStore;
                        await terminationStore!.DeleteFileAsync(file.Id, ctx.CancellationToken);

                    }
                }
            };
        }

使用我们的tus服务

// 配置tus 服务
app.UseTus(context=> CreateTusConfiguration(builder));

配置上传大小

builder.WebHost.ConfigureKestrel((context, options) =>
 {
      options.Limits.MaxRequestBodySize = long.MaxValue;
 });

源码

https://github.com/yeuxuan/tusdemo