你的位置:首页 > ASP.net教程

[ASP.net教程]Upload files to aliyunOSS with bootstrap


本文主要涉及两个概念:

  • 阿里云OSS:对象存储(Object Storage Service,简称OSS),是阿里云对外提供的海量、安全和高可靠的云存储服务。
  • bootstrap-fileinput:An enhanced HTML 5 file input for Bootstrap 3.x with file preview for various files, offers multiple selection, and more.

客服称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