[教程] 文件上传 OSS 三部曲(二)

本文讲解如何讲解客户端文件上传aliOSS。
要点
签名在服务端(php)完成,然后直接通过表单上传到OSS 。
背景
每个用OSS的用户,都会用到上传。由于是网页上传,其中包括一些APP里面的html5页面,对上传的需求很强烈,很多人采用的做法是用户在浏览器/APP上传到应用服务器,然后应用服务器再把文件上传到OSS。

这种方法有三个缺点
第一:上传慢,先上传到应用服务器,再上传到OSS,网络传送多了一倍,而且OSS是采用BGP带宽,能保证各地各运营商的速度。
第二:扩展性不好,如果后续用户多了,应用服务器会成为瓶颈。
第三:费用高,因为OSS上传流量是免费的。如果数据直传到OSS,不走应用服务器。那么将能省下几台应用服务器。

相对于文件上传三部曲(一)的操作,我们提出改进方案

客户端用JS直接签名,然后上传到OSS

OSS的PostObject API细节可以参照(看不懂没有关系):
https://docs.aliyun.com/#/pub/oss/api-refe...
这里有一个很严重的安全隐患。就是OSS AccessId/AccessKey暴露在前端页面。可以随意拿到accessid/accesskey. 这是非常不安全的做法
将此例子进化,签名及上传policy从后端php代码取。
请求逻辑是:
1.客户端要上传图片时,到应用服务器取上传的policy及签名
2.客户端拿到签名直接上传到OSS

示例

直接用网页访问:http://oss-demo.aliyuncs.com/oss-h5-upload...

用手机测试该上传是否有效。二维码:可以用手机(微信,QQ,手机浏览器等)扫一扫试试(这个不是广告,只是上述网址的二维码。这为了让大家看一下这个实现能在手机端完美运行。)
文件上传是上传到一个测试的公共 bucket , 会定时清理,所以不要传一些敏感及重要数据

原理

设置plupload 上传参数如下:

multipart_params: {
    'key' : key + '${filename}' //后面会介绍到,key是应用服务器返回的,指定用户必须以这个前缀上传文件。
    'policy': policyBase64,
    'OSSAccessKeyId': accessid,
    'success_action_status' : '200', //让服务端返回200,不然,默认会返回204
    'signature': signature,
},

js最主要是从后端取到policyBase64, 及accessid,及signature这三个变量。 往后端取这三个变量核心代码如下

phpUrl = './php/get.php'
        xmlhttp.open( "GET", phpUrl, false );
        xmlhttp.send( null );
        var obj = eval ("(" + xmlhttp.responseText+ ")");
        host = obj['host']
        policyBase64 = obj['policy']
        accessid = obj['accessid']
        signature = obj['signature']
        expire = parseInt(obj['expire'])
        key = obj['dir']

现在咱们来一起解析一下xmlhttp.responseText(这个是我设计的范围,并不一定要求是以下的格式,但是必须有signature, accessid, policy这三个值

{"accessid":"6MKOqxGiGU4AUk44",
"host":"http://post-test.oss-cn-hangzhou.aliyuncs.com",
"policy":"eyJleHBpcmF0aW9uIjoiMjAxNS0xMS0wNVQyMDoyMzoyM1oiLCJjxb25kaXRpb25zIjpbWyJjcb250ZW50LWxlbmd0aC1yYW5nZSIsMCwxMDQ4NTc2MDAwXSxbInN0YXJ0cy13aXRoIiwiJGtleSIsInVzZXItZGlyXC8iXV19",
"signature":"I2u57FWjTKqX\/AE6doIdyff151E=",
"expire":1446726203,"dir":"user-dir/"}

第一个变量accessid: 指的用户请求的accessid,注意单知道accessid, 对数据不会有影响。
第二个变量host: 指的是用户要往哪个域名发往上传请求。
第三个变量policy:指的是用户表单上传的策略policy, 是经过base64编码过的字符串
第四个变更signature:是对上述第三个变量policy签名后的字符串
第五个变量expire:指的是当前上传策略失效时间,这个变量,并不是用来发送到OSS,因为这个已经指定在policy里面,这个变量的含义,后面讲。

现在咱们分析一下policy的内容,将其解码后的内容是:

{"expiration":"2015-11-05T20:23:23Z",
"conditions":[["content-length-range",0,1048576000],
["starts-with","$key","user-dir\/"]]

这里有一个关键的地方,PolicyText指定了该Policy 上传失效的最终时间。即在这个失效时间之前,都可以利用这个policy上传文件,所以没有必要每次上传,都去后端取签名。减少后端的压力。在这里我的设计是:初始化上传时,每上传一个文件后,取一次签名。然后再上传时,将当前时间跟签名时间对比,看是签名时间是否失效了。如果失效了,就重新取一次签名,如果没有失效就不取。这里就用到了第五个变量expire

now = timestamp = Date.parse(new Date()) / 1000;
[color=#000000]//可以判断当前expire是否超过了当前时间,如果超过了当前时间,就重新取一下.3s 做为缓冲[/color]
    if (expire < now + 3)
{  
     .....
     phpUrl = './php/get.php'
     xmlhttp.open( "GET", phpUrl, false );
     xmlhttp.send( null );
     ......
}
return .

再看一下上面policy 的内容比上面增加了starts-with, 这个指定此次上传的文件名,必须是user-dir开头(这个字符串,用户可以自己指定)
 为什么要增加这个的含义是:很多场景,一个应用一个bucket,不同用户的数据,为了防止数字覆盖,每个人上传到OSS,可以有特定的前缀。那么问题来了,那用户获取到这个policy后,是不是在失效期内,都能修改上传前缀,从而上传到别人的目录呢?所以,应用服务器可以在上传时就指定让用户传文件时,必须是某个前缀。如果用户拿到了policy他也没有办法上传别人的前缀上。保证了数据的安全性。

代码下载:Touch Me


注意,下面实战讲解:
注意,下面实战讲解:
注意,下面实战讲解:
最近在做一个创业公司网站的新闻模块,新闻视频采用了这种直传的方式。
在这贴上我的代码好了
控制器:

 /*
      * 视频上传
      * */

     public function gmt_iso8601($time) {
         $dStr = date('Y-m-d H:i:s',$time);
         $expiration = str_replace(" ","T",$dStr);
        return $expiration."Z";
     }

     public function ueditor(){
         return view('admin.news.ueditor');
   }

     public function upload()
     {
         $id= 'xxxxxxxxxxxxx;
         $key= 'CCCCCCCCCCCCCCCC';
         $host = 'http://youself.oss-cn-beijing.aliyuncs.com';

        $now = time();
         $expire = 30; //设置该policy超时时间是30s. 即这个policy过了这个有效时间,不能访问
         $end = $now + $expire;
         $expiration = $this->gmt_iso8601($end);
         $dir = 'news/video/';

         //最大文件大小.用户可以自己设置
         $condition = array(0=>'content-length-range', 1=>0, 2=>1048576000);
         $conditions[] = $condition;

         //表示用户上传的数据,必须是以$dir开始, 不然上传会失败,这一步不是必须项,只为了安全起见,防止用户通过policy上传到别人的目录
         $start = array(0=>'starts-with', 1=>'$key', 2=>$dir);
         $conditions[] = $start;

         $arr = array('expiration'=>$expiration,'conditions'=>$conditions);
         //echo json_encode($arr);
         //return;
         $policy = json_encode($arr);
         $base64_policy = base64_encode($policy);
         $string_to_sign = $base64_policy;
         $signature = base64_encode(hash_hmac('sha1', $string_to_sign, $key, true));

         $response = array();
         $response['accessid'] = $id;
         $response['host'] = $host;
         $response['policy'] = $base64_policy;
         $response['signature'] = $signature;
         $response['expire'] = $end;
         //这个参数是设置用户上传指定的前缀
         $response['dir'] = $dir;
         echo json_encode($response);
     }

blade模板

 @section('script')
 //引入js
    <script src='{{ asset('js/htmlToOSS/lib/plupload-2.1.2/js/plupload.full.min.js')}}'></script>" +
    "<script src='{{ asset('js/htmlToOSS/upload.js')}}'></script>
@endsection

form表单:

 <table  class="table table-bordered table-striped table-hover">
                  <tr>
                      <td>
                      <div style="display:none">
                          <form name=theform>
                              <input type="radio" name="myradio" value="local_name"  checked=true/> 上传文件名字保持本地文件名字
                              <input type="radio" name="myradio" value="random_name"  /> 上传文件名字是随机文件名, 后缀保留
                          </form>
                      </div>

                      <h4>如有新闻视频,在此飞快上传:</h4>
                      <div id="ossfile">你的浏览器不支持flash,Silverlight或者HTML5!因此不能使用该浏览器进行视频文件上传!</div>

                      <br/>

                      <div id="container" style="margin-bottom: 20px;">
                          <a id="selectfiles" href="javascript:void(0);" class='btn'>选择文件</a>
                          <a id="postfiles" href="javascript:void(0);" class='btn'>开始上传</a>
                      </div>

                      <pre id="console"></pre>
                      </td>
                  </tr>
                </table>

附录
OSS官链
OSS用户成长

Thanks

是非之外有一座花园,我们会在那里相遇
Martist
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 3
Destiny

666

7年前 评论
宇宙最厉害

内网传输也很快

7年前 评论
Ryan

OSS权限那一块实在繁琐

7年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!