[学习记录]用Azure Blob作图片云存储

按照大型网站架构对CDN(内容分发网络)的要求,类似于图片这样的流量比较大的内容就需要放在单独的服务器中访问,而云计算、云存储能实现分布式存储、提高响应速度,以及按需使用、降低使用成本等诸多优点,已经成为互联网的基础服务与设施。

现在的云平台都要实名认证,我试了下阿里云、又拍云、华为云、腾讯云等,最后发现还是微软的Azure比较强大,它的使用文档简洁易读,平台可操作程度高。它有两种试用方式:免费试用、1元试用,申请立即生效,如果不想注册,还可以使用 Azure 存储模拟器在本地进行开发和测试,几步就搞定,对学习来说很方便。

我用Visual Studio开发ASP.Net程序,这次的学习任务是在前端的UEditor富文本编辑器上传图片到单独的服务器并实时显示在编辑框中,这就要用到图片的云存储,Azure官方有个快速上手的文档:【通过 .NET 开始使用 Azure Blob 存储】,我根据这个文档上手,稍加研究以实现所需要的功能。

Azure 文件存储有几种不同的形式,包括:Blob 存储、文件存储、表存储、队列存储。图片用Blob 存储,Blob 是一个可以存储二进制文件的容器,典型的Blob是一张图片或一个声音文件,由于它们的大尺寸,必须使用特殊的方式来处理(例如:上传、下载或者存放到一个数据库)。

首先得注册账号,创建个存储空间。然后用Visual Studio打开解决方案,在项目中添加两个NuGet 安装包:

1.适用于 .NET 的 Microsoft Azure 存储客户端库:在线搜索“WindowsAzure.Storage”,然后单击“安装”以安装存储客户端库和依赖项。
2.适用于 .NET 的 Microsoft Azure Configuration Manager 库:在线搜索“ConfigurationManager”,然后单击“安装”以安装 Azure 配置管理器。```

添加web.config配置(如果是桌面应用程序则是app.config),在<configuration>的子节点 <appSettings>下添加一个元素:

<add key="StorageConnectionString" value="DefaultEndpointsProtocol=https;AccountName=account-name;AccountKey=account-key;EndpointSuffix=core.chinacloudapi.cn" />```

account-name为存储帐户名称,account-key为存储帐户密钥,这些在Azure门户管理面板上可以看到。

封装一个类专门用于处理在Azure上的图片云存储:public class AzureBlob,需要添加三个using:

using Microsoft.Azure; 
using Microsoft.WindowsAzure.Storage; 
using Microsoft.WindowsAzure.Storage.Blob; 

修改它的无参构造函数,并声明一个成员变量 ,使得该类每次被创建对象时,创建一个Blob 服务客户端给该变量,方便其他方法调用:

CloudBlobClient BlobClient;//类的成员,用于创建Blob 服务客户端
public AzureBlob()
{
    //解析配置中的连接字符串
    CloudStorageAccount storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("StorageConnectionString"));
    //创建 Blob 服务客户端
    BlobClient = storageAccount.CreateCloudBlobClient();
}

Azure提供的Blob包含以下组件:

它们有一些命名规则,其中Blob命名可以无扩展名,也可以加扩展名表示不同格式的文件,如“***.jpg***.txt”,blob名称中还可以包含路径信息,如2016/11/photo1.jpg,这将创建一个虚拟目录结构,可以像传统文件系统一样组织和遍历。所以创建了服务客户端只是相当于与云建立连接,还需要添加对容器和Blob的引用,将它们封装进一个方法中:

/// <param name="mycontainer">容器名</param>
/// <param name="fileName">文件名</param>
public CloudBlockBlob GetContainer(string mycontainer,string fileName)
{
     //获取容器的引用
     CloudBlobContainer container = BlobClient.GetContainerReference(mycontainer);
     //获取块 Blob 引用
     CloudBlockBlob blob = container.GetBlockBlobReference(fileName);
     return blob;
}

需要提到的一点就是Azure 存储空间提供三种类型的 Blob:块 Blob、页 Blob 和追加 Blob。块 Blob特别适用于存储短的文本或二进制文件,例如文档和媒体文件。追加 Blob 类似于块 Blob,因为它们是由块组成的,但针对追加操作对它们进行了优化,因此它们适用于日志记录方案。页 Blob 最大可达 1 TB 大小,并且对于频繁的读/写操作更加高效,Azure 中的虚拟机就使用页 Blob 作为 OS 和数据磁盘。图片块 Blob类型CloudBlockBlob

然后写上传图片的方法,因为从编辑器得到的图片经控制器处理转换成了byte[] 数组的形式,所以这里以UploadFromByteArrayAsync的方式上传,Azure Blob还支持Stream文件流、文件路径等上传方式。Azure支持异步操作,UploadFromByteArrayAsync返回的Task类对象就是异步操作,可以拿它查看错误信息:

/// <param name="bytes">二进制形式的文件</param>
/// <returns>异步信息</returns>
public Task UploadToBlob(string fileName, string mycontainer, byte[] bytes)
{
    CloudBlobContainer container = BlobClient.GetContainerReference(mycontainer);//获取容器的引用
    //创建一个容器(如果该容器不存在)
    container.CreateIfNotExists();
    //设置该容器为公共容器,也就是说网络上能访问容器中的文件,但不能修改、删除
    container.SetPermissions(new BlobContainerPermissions { PublicAccess = BlobContainerPublicAccessType.Blob });
    //将Blob(文件)上载到容器中,如果已存在同名Blob,则覆盖它
    CloudBlockBlob blockBlob = container.GetBlockBlobReference(fileName);//获取块 Blob 引用
    Task result= blockBlob.UploadFromByteArrayAsync(bytes, 0, bytes.Length);//将二进制文件上传
    return result;
}

上一步可以直接拿到blob的url地址,但有时候并不一定要上传,为了方便根据文件名和容器名取到Blob地址,也写一个方法:

public string GetBlobURI(string fileName, string mycontainer)
{
    CloudBlockBlob blob = GetContainer(mycontainer,fileName);
    return blob.Uri.ToString();
}

容器和Blob其实就相当于文件夹、文件名,它们和存储器地址三者合起来就是图片上传后的路径,比如
存储器地址:https://abcd.blob.core.chinacloudapi.cn
容器名:ueditor
Blob名:12/29/8ace5d.png
那么上传后的地址是:https://abcd.blob.core.chinacloudapi.cn/ueditor/12/29/8ace5d.png
这个地址可以直接在浏览器上访问(前提是Blob容器的访问权限已设置为公共),浏览器默认为行为是下载。将地址写入html标签,由客户端解析就可以在页面上显示。
再封装下载和删除的方法:

/// <summary>
/// 下载Blob
/// </summary>
public void DownloadToFile(string fileName, string mycontainer, string fliePath)
{
    CloudBlockBlob blob = GetContainer(mycontainer, fileName);
    using (var fileStream = File.OpenWrite(fliePath))
    {
        blob.DownloadToStream(fileStream); //将blob保存在指定路径
    }
}
/// <summary>
/// 删除Blob
/// </summary>
public void DeleteBlob (string fileName, string mycontainer)
{
    CloudBlockBlob blob = GetContainer(mycontainer, fileName);
    blob.Delete();
}

至此这个类封装完成了,它可以实现简单的图片上传、下载、查询和删除功能。
然后在后端处理前端请求的控制程序上调用它,我用UEditor这个编辑器,找到 UploadHandler.cs,修改下面的一段代码:

/*
var savePath = PathFormatter.Format(uploadFileName, UploadConfig.PathFormat);
var localPath = Server.MapPath(savePath);
try{
    if (!Directory.Exists(Path.GetDirectoryName(localPath))){
        Directory.CreateDirectory(Path.GetDirectoryName(localPath));
    }
    File.WriteAllBytes(localPath, uploadFileBytes);
    Result.Url = savePath;
    Result.State = UploadState.Success;
}catch (Exception e){
    Result.State = UploadState.FileAccessError;
    Result.ErrorMessage = e.Message;
}finally{
    WriteResult();
}

改成这样:

//文件名=文件的MD5值+文件扩展名,
string upFileName = CommonHelper.CalcMD5(uploadFileBytes) + Path.GetExtension(uploadFileName);
//文件上传的虚拟路径(作为Blob名)="当前年份/当前月份/当前日期/文件名"
DateTime today = DateTime.Today;
string unVirtualPath = today.Year + "/" + today.Month + "/" + today.Day + "/" + upFileName;
ILog logger = LogManager.GetLogger(typeof(UploadHandler));//用log4net记录系统日志
try {//上传到Azure云存储  
    AzureBlob azureBlob = new AzureBlob();
    Task uploadResult = azureBlob.UploadToBlob(unVirtualPath, "ueditor", uploadFileBytes);
    while (!uploadResult.IsCompleted)//IsCompleted:判断上传是否完成
    {//while 等待上传完成
        if (uploadResult.Exception != null)
        {//Exception:如果发生异常则返回异常信息,如果上传完成且未发生异常则返回null
            Result.State = UploadState.FileAccessError;//响应编辑器表示上传失败
            Result.ErrorMessage = "上传文件失败";//提示给编辑器的错误信息
            logger.Error("上传失败,错误信息:" + uploadResult.Exception.GetBaseException());//日志中记录错误
            return;
        }
    }
    Result.Url = azureBlob.GetBlobURI(unVirtualPath, "ueditor");//将上传成功的文件地址返回给UEditor前端
    Result.State = UploadState.Success;//响应编辑器表示上传成功
}catch (Exception ex){
    Result.State = UploadState.FileAccessError;
    Result.ErrorMessage = ex.Message;
    logger.Error("上传文件失败,发生异常:", ex);
}finally{
    WriteResult();//将客户端与编辑器有关的内容响应给客户端
}

UEditor前端上传的时候默认会在文件地址上加个前缀,这里不需要了,找到配置文件config.json,修改其中的imageUrlPrefix项为:"imageUrlPrefix": "", /* 图片访问路径前缀 */
测试结果:

成功了!可以在Azure门户面板上监视和编辑:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,616评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,020评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,078评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,040评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,154评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,265评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,298评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,072评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,491评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,795评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,970评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,654评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,272评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,985评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,815评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,852评论 2 351

推荐阅读更多精彩内容