【C#】.NET 7.0或.NET Core Web APi基于tus协议实现断点续传上传大文件

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

  1. 基于tus协议实现断点续传演示
    2913560-20221209143647546-1245254360.png
    2.基于tus协议前端脚本
<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>

3.接下来就是我们的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>

4.我们创建一个新项目,引用一个tus包 tusdotnet

2.png
5.接下来就是添加中间件,并且配置我们的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;
          });
评论区
头像