按照大型網(wǎng)站架構(gòu)對CDN(內(nèi)容分發(fā)網(wǎng)絡(luò))的要求,類似于圖片這樣的流量比較大的內(nèi)容就需要放在單獨(dú)的服務(wù)器中訪問,而云計(jì)算、云存儲能實(shí)現(xiàn)分布式存儲、提高響應(yīng)速度,以及按需使用、降低使用成本等諸多優(yōu)點(diǎn),已經(jīng)成為互聯(lián)網(wǎng)的基礎(chǔ)服務(wù)與設(shè)施。
現(xiàn)在的云平臺都要實(shí)名認(rèn)證,我試了下阿里云、又拍云、華為云、騰訊云等,最后發(fā)現(xiàn)還是微軟的Azure比較強(qiáng)大,它的使用文檔簡潔易讀,平臺可操作程度高。它有兩種試用方式:免費(fèi)試用、1元試用,申請立即生效,如果不想注冊,還可以使用 Azure 存儲模擬器在本地進(jìn)行開發(fā)和測試,幾步就搞定,對學(xué)習(xí)來說很方便。
我用Visual Studio開發(fā)ASP.Net程序,這次的學(xué)習(xí)任務(wù)是在前端的UEditor富文本編輯器上傳圖片到單獨(dú)的服務(wù)器并實(shí)時顯示在編輯框中,這就要用到圖片的云存儲,Azure官方有個快速上手的文檔:【通過 .NET 開始使用 Azure Blob 存儲】,我根據(jù)這個文檔上手,稍加研究以實(shí)現(xiàn)所需要的功能。
Azure 文件存儲有幾種不同的形式,包括:Blob 存儲、文件存儲、表存儲、隊(duì)列存儲。圖片用Blob 存儲,Blob 是一個可以存儲二進(jìn)制文件的容器,典型的Blob是一張圖片或一個聲音文件,由于它們的大尺寸,必須使用特殊的方式來處理(例如:上傳、下載或者存放到一個數(shù)據(jù)庫)。
首先得注冊賬號,創(chuàng)建個存儲空間。然后用Visual Studio打開解決方案,在項(xiàng)目中添加兩個NuGet 安裝包:
1.適用于 .NET 的 Microsoft Azure 存儲客戶端庫:在線搜索“WindowsAzure.Storage”,然后單擊“安裝”以安裝存儲客戶端庫和依賴項(xiàng)。
2.適用于 .NET 的 Microsoft Azure Configuration Manager 庫:在線搜索“ConfigurationManager”,然后單擊“安裝”以安裝 Azure 配置管理器。```
添加web.config配置(如果是桌面應(yīng)用程序則是app.config),在<configuration>的子節(jié)點(diǎn) <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;
修改它的無參構(gòu)造函數(shù),并聲明一個成員變量 ,使得該類每次被創(chuàng)建對象時,創(chuàng)建一個Blob 服務(wù)客戶端給該變量,方便其他方法調(diào)用:
CloudBlobClient BlobClient;//類的成員,用于創(chuàng)建Blob 服務(wù)客戶端
public AzureBlob()
{
//解析配置中的連接字符串
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("StorageConnectionString"));
//創(chuàng)建 Blob 服務(wù)客戶端
BlobClient = storageAccount.CreateCloudBlobClient();
}
Azure提供的Blob包含以下組件:

它們有一些命名規(guī)則,其中Blob命名可以無擴(kuò)展名,也可以加擴(kuò)展名表示不同格式的文件,如“***.jpg、***.txt”,blob名稱中還可以包含路徑信息,如2016/11/photo1.jpg,這將創(chuàng)建一個虛擬目錄結(jié)構(gòu),可以像傳統(tǒng)文件系統(tǒng)一樣組織和遍歷。所以創(chuàng)建了服務(wù)客戶端只是相當(dāng)于與云建立連接,還需要添加對容器和Blob的引用,將它們封裝進(jìn)一個方法中:
/// <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;
}
需要提到的一點(diǎn)就是Azure 存儲空間提供三種類型的 Blob:塊 Blob、頁 Blob 和追加 Blob。塊 Blob特別適用于存儲短的文本或二進(jìn)制文件,例如文檔和媒體文件。追加 Blob 類似于塊 Blob,因?yàn)樗鼈兪怯蓧K組成的,但針對追加操作對它們進(jìn)行了優(yōu)化,因此它們適用于日志記錄方案。頁 Blob 最大可達(dá) 1 TB 大小,并且對于頻繁的讀/寫操作更加高效,Azure 中的虛擬機(jī)就使用頁 Blob 作為 OS 和數(shù)據(jù)磁盤。圖片塊 Blob類型CloudBlockBlob。
然后寫上傳圖片的方法,因?yàn)閺木庉嬈鞯玫降膱D片經(jīng)控制器處理轉(zhuǎn)換成了byte[] 數(shù)組的形式,所以這里以UploadFromByteArrayAsync的方式上傳,Azure Blob還支持Stream文件流、文件路徑等上傳方式。Azure支持異步操作,UploadFromByteArrayAsync返回的Task類對象就是異步操作,可以拿它查看錯誤信息:
/// <param name="bytes">二進(jìn)制形式的文件</param>
/// <returns>異步信息</returns>
public Task UploadToBlob(string fileName, string mycontainer, byte[] bytes)
{
CloudBlobContainer container = BlobClient.GetContainerReference(mycontainer);//獲取容器的引用
//創(chuàng)建一個容器(如果該容器不存在)
container.CreateIfNotExists();
//設(shè)置該容器為公共容器,也就是說網(wǎng)絡(luò)上能訪問容器中的文件,但不能修改、刪除
container.SetPermissions(new BlobContainerPermissions { PublicAccess = BlobContainerPublicAccessType.Blob });
//將Blob(文件)上載到容器中,如果已存在同名Blob,則覆蓋它
CloudBlockBlob blockBlob = container.GetBlockBlobReference(fileName);//獲取塊 Blob 引用
Task result= blockBlob.UploadFromByteArrayAsync(bytes, 0, bytes.Length);//將二進(jìn)制文件上傳
return result;
}
上一步可以直接拿到blob的url地址,但有時候并不一定要上傳,為了方便根據(jù)文件名和容器名取到Blob地址,也寫一個方法:
public string GetBlobURI(string fileName, string mycontainer)
{
CloudBlockBlob blob = GetContainer(mycontainer,fileName);
return blob.Uri.ToString();
}
容器和Blob其實(shí)就相當(dāng)于文件夾、文件名,它們和存儲器地址三者合起來就是圖片上傳后的路徑,比如
存儲器地址:https://abcd.blob.core.chinacloudapi.cn
容器名:ueditor
Blob名:12/29/8ace5d.png
那么上傳后的地址是:https://abcd.blob.core.chinacloudapi.cn/ueditor/12/29/8ace5d.png
這個地址可以直接在瀏覽器上訪問(前提是Blob容器的訪問權(quán)限已設(shè)置為公共),瀏覽器默認(rèn)為行為是下載。將地址寫入html標(biāo)簽,由客戶端解析就可以在頁面上顯示。
再封裝下載和刪除的方法:
/// <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();
}
至此這個類封裝完成了,它可以實(shí)現(xiàn)簡單的圖片上傳、下載、查詢和刪除功能。
然后在后端處理前端請求的控制程序上調(diào)用它,我用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值+文件擴(kuò)展名,
string upFileName = CommonHelper.CalcMD5(uploadFileBytes) + Path.GetExtension(uploadFileName);
//文件上傳的虛擬路徑(作為Blob名)="當(dāng)前年份/當(dāng)前月份/當(dāng)前日期/文件名"
DateTime today = DateTime.Today;
string unVirtualPath = today.Year + "/" + today.Month + "/" + today.Day + "/" + upFileName;
ILog logger = LogManager.GetLogger(typeof(UploadHandler));//用log4net記錄系統(tǒng)日志
try {//上傳到Azure云存儲
AzureBlob azureBlob = new AzureBlob();
Task uploadResult = azureBlob.UploadToBlob(unVirtualPath, "ueditor", uploadFileBytes);
while (!uploadResult.IsCompleted)//IsCompleted:判斷上傳是否完成
{//while 等待上傳完成
if (uploadResult.Exception != null)
{//Exception:如果發(fā)生異常則返回異常信息,如果上傳完成且未發(fā)生異常則返回null
Result.State = UploadState.FileAccessError;//響應(yīng)編輯器表示上傳失敗
Result.ErrorMessage = "上傳文件失敗";//提示給編輯器的錯誤信息
logger.Error("上傳失敗,錯誤信息:" + uploadResult.Exception.GetBaseException());//日志中記錄錯誤
return;
}
}
Result.Url = azureBlob.GetBlobURI(unVirtualPath, "ueditor");//將上傳成功的文件地址返回給UEditor前端
Result.State = UploadState.Success;//響應(yīng)編輯器表示上傳成功
}catch (Exception ex){
Result.State = UploadState.FileAccessError;
Result.ErrorMessage = ex.Message;
logger.Error("上傳文件失敗,發(fā)生異常:", ex);
}finally{
WriteResult();//將客戶端與編輯器有關(guān)的內(nèi)容響應(yīng)給客戶端
}
UEditor前端上傳的時候默認(rèn)會在文件地址上加個前綴,這里不需要了,找到配置文件config.json,修改其中的imageUrlPrefix項(xiàng)為:"imageUrlPrefix": "", /* 圖片訪問路徑前綴 */。
測試結(jié)果:

成功了!可以在Azure門戶面板上監(jiān)視和編輯:
