本文主要涉及兩個概念:
客服稱OSS不限制上下行流量,而且不占用ECS帶寬,所以將靜態文件存放在OSS是個不錯的選擇。除了放置站點引用的資源文件外,主要用於用戶文件的上傳。阿裡雲提供了Web端直傳方式,文件不經過站點服務器,不影響站點的性能和帶寬。真正撸代碼時,遇到了不少坑,而官方文檔秉承阿裡一貫的程序員風格,天馬行空毫不連貫,博主東拼西湊才理順各細節,關鍵點記錄如下,供需要的朋友參考。
阿裡官方沒有提供.NET的demo,找到Post Object,該文檔描述了提交的各參數。進行Post操作要求對bucket有寫權限,如果bucket為public-read-write,可以不上傳簽名信息,否則要求對該操作進行簽名驗證。與Put操作不同,Post操作使用AccessKeySecret對policy進行簽名計算出簽名字符串作為Signature表單域的值,OSS會驗證該值從而判斷簽名的合法性(不需要遵循用戶簽名驗證(Authentication)的規定,另外在header或url中加上簽名)。policy又是啥呢?Post請求的policy表單域用於驗證請求的合法性。 policy為一段經過UTF-8和base64編碼的JSON文本,聲明了Post請求必須滿足的條件(文件大小、一些http頭等,還有該條件的過期時間)。雖然對於public-read-write的bucket上傳時,post表單域為可選項(其它情況,因為需要簽名驗證,所以是必選項),也強烈建議使用該域來限制Post請求。
so,第一步,構造Post條件,對於不同bucket,可以有不同的條件,可以寫在web.config中,如果經常變動,也可以寫在數據庫中方便管理。
然後,給前端提供一個獲取policy的接口,代碼就不貼了,使用官方提供的SDK,很簡單。這裡假設接口名稱為GetPolicyForOSSUpload,返回結果是:
Data = new ModelForOSSUpload { AccessKeyId = aliyunconfig.AccessKeyId, PolicyBase64 = encodedPolicy, Signature = signature, Expiration = expiration }
注意,expiration已經被用於構造PolicyBase64了,這裡又單獨給個字段返回,是給前端用的,在有效時間內,不用再調該接口,能減輕服務器壓力一點是一點。
前端js是這樣的:
1 _getUploadPreSetting: function (bucketKey, policyName) { 2 var presetting = {}, error = {}, self = this; 3 var inner_getUploadPreSetting = function () { 4 var now = new Date(); 5 if (!presetting.Expiration || presetting.Expiration < now) { 6 presetting = {}; 7 error = {}; 8 $.ajax({ 9 url: self._ossOpts.uploadPreSettingUrl, 10 type: 'GET', 11 async: false, 12 data: { bucketKey: bucketKey, policyName: policyName }, 13 success: function (result) { 14 if (result.IsSucceed) { 15 presetting = result.Data; 16 //應付/Date(1460817323032)/這種奇葩格式 17 presetting.Expiration = eval('new ' + (presetting.Expiration.replace(/\//g, ''))); 18 } 19 else { 20 error = result; 21 } 22 }, 23 error: function (xhr, msg) { 24 error = { IsSucceed: false, Message: msg }; 25 } 26 }); 27 } 28 return presetting || error; 29 }; 30 return inner_getUploadPreSetting; 31 }
此處用了閉包,這是因為不同的policy,我們以bucketKey和policyName進行區分(看貼出來的web.config片段),如果一個頁面需要多個policy,每個policy都要對應一個變量,數量不定的情況下,我們也不能硬編碼,同時為了讓它們互不影響,就采用了閉包的方式。調用如下:
var getUploadPreSetting = this._getUploadPreSetting(this._ossOpts.bucketKey, this._ossOpts.policyName); var presetting = getUploadPreSetting();
policy的部分到此為止,下面我們來看下bootstrap-fileinput這個組件,並對它進行一點點改造,以便於符合OSS的上傳規則。
除了文件數據,提交時還要附加OSS要求的一些表單域,比如剛才講的policy、Signature,還可以給每個文件加上cache-Control、callback(上傳成功後,OSS回調我們的接口地址)等。我們在初始化bootstrap-fileinput(下稱fileinput)時可以傳入一個uploadExtraData配置項(函數或對象),在上傳每個文件時,fileinput會調用uploadExtraData,將獲取到的數據附加到文件數據後一起上傳。然而這就存在一個問題了,OSS文檔中說“文件或文本內容,必須是表單中的最後一個域。”,否則會返回“The bucket POST must contain the specified 'key'. If it is specified, please check the order of the fields”的錯誤信息。
題外話:關於表單域順序的說明,以前文檔中並沒有,還是博主在一次工單提交和差點投訴後才加上去的,當時很奇怪難道沒人遇到過,還是說這樣用OSS的人不多?另外在和客服扯皮的同時,博主也上網搜了一把,發現AWS的用戶竟然也遇到類似問題,請看 InvalidArgumentBucket POST must contain a field named 'key'. If it is specified, please check the order of the fields.keyB,連返回的錯誤消息都大同小異,博主大膽猜測,阿裡雲和AWS的數據存儲用的是同一套組件,還是開源的,博主知識淺陋,有知道的朋友說一聲。
於是只能去看fileinput的源碼了,幸好它是開源的,我們只需要改動一下_uploadSingle函數的末尾:
self._uploadExtra(previewId, i); //新加 formdata.append(self.uploadFileAttr, files[i], self.filenames[i]); //文件數據 formdata.append('file_id', i); self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError, previewId, i, true); //也要改造下_ajaxSubmit,傳入true表示額外數據不再附加
_ajaxSubmit也改造了下,圓圈內為新加:
fileinput改造完畢,so easy!那麼當上傳成功後,我們一般都是要對應用數據做一些處理,比如更新數據庫中的url地址什麼的,我們可以獲取響應信息,然後再調用相應的後台接口,OSS也提供了直接回調的方式,後者能傳遞更多關於文件的信息,並且在構造callback時可自定義參數,因此更靈活,方便後台回調接口的數據處理,具體可看Callback。
下例,前端js:
callback:{ "callbackUrl":GlobalConfig.siteurl+'Merchandise/SetMerchandisePicUrl', "callbackBody":"merchandiseid="+vm.merchandise.BasicInfo.ID+"&picname=${object}" }
後端回調接口:
1 [AllowAnonymous] 2 public async Task<ActionResult> SetMerchandisePicUrl(int merchandiseid, string picname) 3 { 4 var aliyunconfig = MvcApplication.AliyunConfig; 5 var bucket = aliyunconfig.OSS.Buckets["merchandise_pictures"]; 6 var picUri = $"http://{ bucket.Value}.{aliyunconfig.OSS.ImgEndpoint}/{picname}"; 7 await MerchandiseContext.AppendPicUri(merchandiseid, picUri); 8 return this.Json(new 9 { 10 initialPreview = new string[] { picUri }, 11 initialPreviewConfig = new List<object> { 12 new { 13 caption = picname, 14 //width ="160px", //並沒有什麼用 15 url =Url.Action("RemoveMerchandisePicUrl"), 16 extra = new {picUri= picUri,merchandiseid = merchandiseid} 17 } 18 } 19 }); 20 }
接口允許匿名,便於OSS調用,為了防止惡意調用,最好進行簽名校驗。fileinput根據返回結果判斷是否上傳成功(沒有error屬性表示上傳成功),若成功且結果裡有initialPreview[和initialPreviewConfig]等屬性,則將這些屬性應用到對應文件顯示。特別注意initialPreviewConfig中的url和extra屬性,表示刪除該文件時調用的接口和傳遞的參數。
其它參考資料:
理解DOMString、Document、FormData、Blob、File、ArrayBuffer數據類型
Web 前沿——HTML5 Form Data 對象的使用
轉載請注明本文出處:http://www.cnblogs.com/newton/p/6066020.html