暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

带进度条上传文件到 Azure Blob Storage

汪宇杰博客 2021-07-07
837

点击上方蓝字 / 关注“汪宇杰博客”


EDIWANG
前言

Azure Blob Storage

最近正在做一个小企业的办公系统,其中有个附件上传和下载的需求,非常适合使用 Azure Blob Storage 做存储。然而,附件的大小从几 KB 到数百 MB 不等,因此在用户上传文件时需要显示进度条。让我们看看如何套壳 Azure 轻松自主研发成功。

图 | 网络

EDIWANG

设计方案

Azure Blob Storage

OA 系统使用 ASP.NET Core 作为后端 API。所以基本上我可以想出两种将文件上传到 Azure Blob 存储的设计。

方案1

创建一个 API,该 API 接受来自 Web 前端的文件,然后上传到 Azure。

方案2

为 Azure 存储创建一个只返回 SAS 令牌的 API,Web 前端然后使用此令牌将文件直接上传到 Azure,而无需调用我们自己的 API。

方案1容易996,因为这需要修改API服务器以允许大型请求,而且我也必须编写很多复杂的代码才能向客户端报告进度,更不用说所有错误处理的场景了。做完一套,天都亮了,头发也没了。

图 | 网

而 Azure 存储已经拥有可以报告上传进度的 JavaScript API,因此我决定采用方案2。

EDIWANG

Azure上的准备工作

Azure Blob Storage

Container

创建一个容器来存储上传的文件。例如 “attachments”。

连接字符串

从存储帐户复制连接字符串以备后用。

CORS

在您的域的 Blob 服务上允许 CORS。对于演示,我允许所有来源和所有方法。请不要在生产环境中这样做,不然容易被程序员删库跑路报复社会。要上传文件,只需要 HTTP PUT 即可。

EDIWANG

创建API以返回SAS Token

Azure Blob Storage

以 ASP.NET Core 为例,安装最新版的 Azure.Storage.Blobs 包。

<PackageReference Include="Azure.Storage.Blobs" Version="12.9.1" />

将 Azure 存储账户设置写入 appsettings.json

"AppSettings": {

  "AzureStorage": {

    "ConnectionString": "<connection string>",

    "ContainerName": "attachments",

    "TokenExpirationMinutes": 20

  }

}

可以根据自己需要调整 TokenExpirationMinutes 的值,它代表token过期时间。

创建 Controller 及 Action。

[ApiController]

[Route("[controller]")]

public class AttachmentController : ControllerBase

{

    private readonly IConfiguration _configuration;


    public AttachmentController(IConfiguration configuration)

    {

        _configuration = configuration;

    }


    [HttpGet("token")]

    [ProducesResponseType(StatusCodes.Status409Conflict)]

    [ProducesResponseType(typeof(AzureStorageSASResult), StatusCodes.Status200OK)]

    public IActionResult SASToken()

    {

        var azureStorageConfig = _configuration.GetSection("AppSettings:AzureStorage").Get<AzureStorageConfig>();

        BlobContainerClient container = new(azureStorageConfig.ConnectionString, azureStorageConfig.ContainerName);


        if (!container.CanGenerateSasUri) return Conflict("The container can't generate SAS URI");


        var sasBuilder = new BlobSasBuilder

        {

            BlobContainerName = container.Name,

            Resource = "c",

            ExpiresOn = DateTimeOffset.UtcNow.AddMinutes(azureStorageConfig.TokenExpirationMinutes)

        };


        sasBuilder.SetPermissions(BlobContainerSasPermissions.All);


        var sasUri = container.GenerateSasUri(sasBuilder);


        var result = new AzureStorageSASResult

        {

            AccountName = container.AccountName,

            AccountUrl = $"{container.Uri.Scheme}://{container.Uri.Host}",

            ContainerName = container.Name,

            ContainerUri = container.Uri,

            SASUri = sasUri,

            SASToken = sasUri.Query.TrimStart('?'),

            SASPermission = sasBuilder.Permissions,

            SASExpire = sasBuilder.ExpiresOn

        };


        return Ok(result);

    }

}


public class AzureStorageConfig

{

    public string ConnectionString { get; set; }

    public string ContainerName { get; set; }

    public int TokenExpirationMinutes { get; set; }

}


public class AzureStorageSASResult

{

    public string AccountName { get; set; }

    public string AccountUrl { get; set; }

    public Uri ContainerUri { get; set; }

    public string ContainerName { get; set; }

    public Uri SASUri { get; set; }

    public string SASToken { get; set; }

    public string SASPermission { get; set; }

    public DateTimeOffset SASExpire { get; set; }

}

注意这里的 SAS 权限设置为All,用于演示,在生产中,出于安全原因,请仅设置您需要的权限。对于文件上传,只需要 Create,Add,Write即可。

sasBuilder.SetPermissions(BlobContainerSasPermissions.All);

API 返回的结果如下:

{

  "accountName": "cinderellastorage",

  "accountUrl": "https://cinderellastorage.blob.core.windows.net",

  "containerUri": "https://cinderellastorage.blob.core.windows.net/attachments",

  "containerName": "attachments",

  "sasUri": "https://cinderellastorage.blob.core.windows.net/attachments?sv=2020-08-04&se=2021-07-07T05%3A37%3A07Z&sr=c&sp=racwdxlt&sig=MxCEEtT%2FGVbjksDzgzVgvRqucQfjKd4JOp6zsId0h5c%3D",

  "sasToken": "sv=2020-08-04&se=2021-07-07T05%3A37%3A07Z&sr=c&sp=racwdxlt&sig=MxCEEtT%2FGVbjksDzgzVgvRqucQfjKd4JOp6zsId0h5c%3D",

  "sasPermission": "racwdxlt",

  "sasExpire": "2021-07-07T05:37:07.6443444+00:00"

}

EDIWANG

Web前端

Azure Blob Storage

我不是专业的前端开发人员。所以我自主研发了如下文章中的代码并稍作修改。azure-storage.blob.min.js 库可以从 https://aka.ms/downloadazurestoragejs 下载。

链接:https://blog.bitscry.com/2018/03/14/uploading-files-to-azure-blob-storage-from-the-browser/

<!DOCTYPE html>

<html>


<head>

    <meta charset="UTF-8">

    <meta http-equiv="X-UA-Compatible" content="IE=edge">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"

        integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">

    <title>Upload</title>

    <script src="azure-storage.blob.min.js"></script>

    <script src="https://oss-emcsprod-public.modb.pro/wechatSpider/modb_20211216_897eba5e-5e65-11ec-b838-fa163eb4f6be.png"></script>

    <script type="text/javascript">

        $(document).on('change', ':file', function () {

            var input = $(this)

            var label = $('#BrowseInput').val(input.val().replace(/\\/g, '/').replace(/.*\//, ''));

        });

    </script>

    <script type="text/javascript">

        function displayProcess(process) {

            document.getElementById("uploadProgressBar").style.width = process + '%';

            document.getElementById("uploadProgressBar").innerHTML = process + '%';

        }


        function uploadBlob() {

            displayProcess(0);

            document.getElementById("uploadProgressBarContainer").classList.remove('hidden');


            // TODO: Call API to get URL, SAS Token, Container name

            var blobUri = 'https://cinderellastorage.blob.core.windows.net';

            var containerName = 'attachments';

            var sasToken = 'sv=2020-08-04&se=2021-07-07T05%3A40%3A03Z&sr=c&sp=racwdxlt&sig=NKDKAdQESM03GxCfFs2FLHHYLWJzqRYy4LKdyxTcVx8%3D';


            var blobService = AzureStorage.Blob.createBlobServiceWithSas(blobUri, sasToken);


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


            var customBlockSize = file.size > 1024 * 1024 * 32 ? 1024 * 1024 * 4 : 1024 * 512;

            blobService.singleBlobPutThresholdInBytes = customBlockSize;


            var finishedOrError = false;

            var speedSummary = blobService.createBlockBlobFromBrowserFile(containerName, file.name, file, { blockSize: customBlockSize }, function (error, result, response) {

                finishedOrError = true;

                if (error) {

                    alert('Error');

                } else {

                    displayProcess(100);

                }

            });


            function refreshProgress() {

                setTimeout(function () {

                    if (!finishedOrError) {

                        var process = speedSummary.getCompletePercent();

                        displayProcess(process);

                        refreshProgress();

                    }

                }, 200);

            }


            refreshProgress();

        }

    </script>

</head>


<body>

    <div>

        <div>

            <form asp-controller="Home" asp-action="UploadSmallFile" enctype="multipart/form-data" id="BlobUploadForm"

                method="post" role="form">

                <div>

                    <div>

                        <div>

                            <label>

                                <span class="btn btn-primary">

                                    Browse… <input type="file" style="display: none;" name="file" id="FileInput">

                                </span>

                            </label>

                            <input type="text" readonly="" id="BrowseInput">

                        </div>

                    </div>

                    <div>

                        <div>

                            <button type="button" value="Upload to Blob" class="btn btn-success" id="UploadBlob"

                                onclick="uploadBlob()">Upload to Blob</button>

                        </div>

                    </div>

                    <div class="form-group hidden" id="uploadProgressBarContainer">

                        Uploading...

                        <div>

                            <div role="progressbar" id="uploadProgressBar" aria-valuenow="60"

                                aria-valuemin="0" aria-valuemax="100" style="width: 0%;">

                                0%

                            </div>

                        </div>

                    </div>

                </div>

            </form>

        </div>

    </div>

    <script src="https://oss-emcsprod-public.modb.pro/wechatSpider/modb_20211216_8af4c608-5e65-11ec-b838-fa163eb4f6be.png"

        integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"

        crossorigin="anonymous"></script>

</body>

</html>

这段代码的基本思想是将大文件拆分成一个个小块,逐个上传。Azure 存储 SDK 将处理上传并报告进度。

最后,我可以验证文件是否已上传到 Azure 存储。

就这么轻松搞定了!用了Azure,丝毫没有996的机会。

图 | 网

还是那句老话:贵的东西除了贵,没有别的缺点!

汪宇杰博客

Azure | .NET | 微软 MVP

无广告,不卖课,做纯粹的技术公众号

点击阅读原文查看博客原文
文章转载自汪宇杰博客,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论