前段时间参与了一个H5项目,里边有个需求是用户上传图片。当时的方案是前端先调用微信的JSSDK选择图片并上传,而后再从后端下载到服务器上。然而用的时候发现客户端给的图片有大有小,可是因为用了微信的接口,图片在下载以前是无法控制的。后来在想能不能调用HTML5原生的文件上传接口,另外还能够配合阿里云的OSS对图片作进一步处理,因此就有了这篇文章。php
其实以前也有想过用原生的,可手里的项目全是微信平台的H5,原生上传一直被告知有兼容性问题,因此这个方案一直是被搁置的;只是此次以为用微信接口实在不爽才从新翻出来的,没想到意外发现手里的米4竟然能够正经常使用。。好了闲话不说,上代码:css
<input id="img_input" type="file" accept="image/*" /> <div id="preview_box"></div>
HTML部分主要就是那个input,至于下边那个div,主要是留着放图片预览用的。html
<script src="http://cdn.bootcss.com/jquery/2.2.4/jquery.min.js"></script> <script> $("#img_input").on("change", function(e) { var file = e.target.files[0]; // 获取图片资源 var fd = new FormData(); // 用formdata上传文件 // 只选择图片文件 if (!file.type.match('image.*')) { return false; } fd.append('file', file, file.name); // 填入文件 $.ajax({ url: 'fileupload.php', data: fd, processData: false, contentType: false, type: 'POST', success: function () { // 成功后显示文件预览 var reader = new FileReader(); reader.readAsDataURL(file); // 读取文件 // 渲染文件 reader.onload = function(ev) { var img = '<img class="preview" src="' + ev.target.result + '" alt="preview"/>'; $("#preview_box").empty().append(img); } } }); }); </script>
文件填入FormData,而后POST上传,后端(用的PHP)简单写下接收就行。
(而后这里顺便想问下若是直接上传blob的话,PHP后端应该怎么写?有大神路过请不吝赐教~小弟这里先谢过了)前端
<?php if ($error == UPLOAD_ERR_OK) { $tmp_name = $_FILES["file"]["tmp_name"]; $name = $_FILES["file"]["name"]; move_uploaded_file($tmp_name, "$name"); }
而后处理下权限啥的,就能跑啦。jquery
localResizeIMG 是个好插件,用法也很简单,把 GitHub 里的 dist 文件夹拖下来改个名(我改了个“localRZ”),而后直接引用 lrz.bundle.js 文件就好了:git
<script src="http://cdn.bootcss.com/jquery/2.2.4/jquery.min.js"></script> <script src="localRZ/lrz.bundle.js"></script> <script> $("#img_input").on("change", function(e) { var file = e.target.files[0]; //获取图片资源 var filename = file.name; // 只选择图片文件 if (!file.type.match('image.*')) { return false; } // LocalResizeIMG处理: lrz(file, {width: 400}) .then(function (rst) { $.ajax({ url: 'fileupload.php', data: rst.formData, // LocalResizeIMG 直接封装好的 processData: false, contentType: false, type: 'POST' }).done(function(data, textStatus, jqXHR){ // 图片预览 var img = new Image(); img.src = rst.base64; img.onload = function () { $("#preview_box").empty().append(img); }; }); return rst; }) .catch(function (err) { // 万一出错了,这里能够捕捉到错误信息 // 并且以上的then都不会执行 alert('ERROR:' + err); }) .always(function () { // 无论是成功失败,这里都会执行 }); }); </script>
localResizeIMG 的 文档 写的挺清楚的,哪里不明白的话能够过去看看。github
原生的文件上传控件略丑,因此通常是要美化一下。
HTML:ajax
<div class="filePicker"> <input id="img_input" type="file" accept="image/*" /> <label for="img_input">上传图片</label> </div> <div class="preview_box"></div>
放一个 lable 上去,而后隐藏掉原有的 input:json
<style type="text/css"> .filePicker { margin: 200px; width: 200px; height: 50px; line-height: 50px; text-align: center; color: #fff; background: #00b7ee; } .filePicker label { display: block; width: 100%; height: 100%; } .filePicker input[type="file"] { display: none; } </style>
这样看起来就舒服多了。后端
关于直传,阿里官方给了三种方案:
客户端 JavaScript 签名后直传;
客户端申请服务端签名,而后打包上传;
客户端申请服务端签名,打包上传OSS后回调服务端。
这里主要用的是第二种。
根据官方给的案例代码,首先要搞个签名用的PHP:
<?php function gmt_iso8601($time) { $dtStr = date("c", $time); $mydatetime = new DateTime($dtStr); $expiration = $mydatetime->format(DateTime::ISO8601); $pos = strpos($expiration, '+'); $expiration = substr($expiration, 0, $pos); return $expiration."Z"; } //自行设置AccessKey和相应Bucket的外网域名 $id= 'xxxxxxxxxxx'; $key= 'yyyyyyyyyy'; $host = 'http://zzzzzzz.oss-cn-xxxxxxxxx.aliyuncs.com/'; $now = time(); $expire = 10; //设置该policy超时时间是10s. 即这个policy过了这个有效时间,将不能访问 $end = $now + $expire; $expiration = gmt_iso8601($end); //文件大小范围.用户能够本身设置 $condition = array(0=>'content-length-range', 1=>0, 2=>1048576000); //设置用户上传指定的前缀 $dir = 'test/'; //用户上传数据的位置匹配,这一步不是必须项,只是为了安全起见,防止用户经过policy上传到别人的目录 $start = array(0=>'starts-with', 1=>'$key', 2=>$dir); //设置bucket $bucket = array(0=>'eq', 1=>'$bucket', 2=>'gmei'); $conditions = array(0=>$bucket, 1=>$condition, 2=>$start); $arr = array('expiration'=>$expiration,'conditions'=>$conditions); //echo json_encode($arr); //return; $policy = json_encode($arr); $base64_policy = base64_encode($policy); $signature = base64_encode(hash_hmac('sha1', $base64_policy, $key, true)); $response = array( 'accessid' => $id, 'host' => $host, 'policy' => $base64_policy, 'signature' => $signature, 'expire' => $end, 'dir' => $dir.'${filename}' ); echo json_encode($response);
里边的东西填一下,而后保存在同目录下就行。而后改下HTML:
<script src="http://cdn.bootcss.com/jquery/2.2.4/jquery.min.js"></script> <script src="localRZ/lrz.bundle.js"></script> <script> $("#img_input").on("change", function(e) { var file = e.target.files[0]; //获取图片资源 var filename = file.name; // 只选择图片文件 if (!file.type.match('image.*')) { return false; } // LocalResizeIMG写法: lrz(file, {width: 200, fieldName: 'osstest'}) .then(function (rst) { // OSS要求把上传文件放到最后一项,可是用LocalResizeIMG输出的FormData,就只能放在 // 第一项,因此这里要本身new个出来 var ossData = new FormData(); // 先请求受权,而后回调 $.getJSON('ossget.php', function (json) { //签名用的PHP // 添加签名信息 ossData.append('OSSAccessKeyId', json.accessid); ossData.append('policy', json.policy); ossData.append('Signature', json.signature); ossData.append('key', json.dir); // 添加文件 ossData.append('file', rst.file, filename); $.ajax({ url: json.host, data: ossData, processData: false, contentType: false, type: 'POST' }).done(function(){ // 成功后显示图片预览 var img = new Image(); img.src = rst.base64; img.onload = function () { $(".preview_box").empty().append(img); }; }); }); return rst; }) .catch(function (err) { // 万一出错了,这里能够捕捉到错误信息 // 并且以上的then都不会执行 alert('ERROR:' + err); }) .always(function () { // 无论是成功失败,这里都会执行 }); }); </script>
OSS返回给客户端的XML无法正常解析
返回的XML是报错内容,可是不影响文件的正常上传(文件上传返回的是默认的204)。报错内容是(大意)“[AccessDenied]:The bucket you visit is not belong to you.”,查了下文档说缘由是“子用户没有Bucket管理的权限(如getBucketAcl CreateBucket、deleteBucket setBucketReferer、 getBucketReferer等)”,调了半天的 RAM(访问控制)也没弄好,不知道是什么缘由~
上次留下几个问题,已经解决了,因此过来填坑。
其实这两个问题算是一个问题,在 PostObject 文档里,表单域里有个参数“success_action_status”,描述是“未指定success_action_redirect表单域时,该表单域指定了上传成功后返回给客户端的状态码。 接受值为200, 201, 204(默认)。 若是该域的值为200或者204,OSS返回一个空文档和相应的状态码。 若是该域的值设置为201,OSS返回一个XML文件和201状态码。 若是其值未设置或者设置成一个非法值,OSS返回一个空文档和204状态码。”因此,以前返回不正常的这个问题,只要强行指定返回201状态码,就能够正常收到返回的XML了(而且也没有先前报错的问题了)。
上代码:
<script src="http://cdn.bootcss.com/jquery/2.2.4/jquery.min.js"></script> <script src="localRZ/lrz.bundle.js"></script> <script> $("#img_input").on("change", function(e) { var file = e.target.files[0]; //获取图片资源 var filename = file.name; // 只选择图片文件 if (!file.type.match('image.*')) { return false; } // LocalResizeIMG写法: lrz(file, {width: 200, fieldName: 'osstest'}) .then(function (rst) { var ossData = new FormData(); // 先请求受权,而后回调 $.getJSON('ossget.php', function (json) { // 添加配置参数 ossData.append('OSSAccessKeyId', json.accessid); ossData.append('policy', json.policy); ossData.append('Signature', json.signature); ossData.append('key', json.dir); ossData.append('success_action_status', 201); // 指定返回的状态码 ossData.append('file', rst.file, filename); $.ajax({ url: json.host, data: ossData, dataType: 'xml', // 这里加个对返回内容的类型指定 processData: false, contentType: false, type: 'POST' }).done(function(data){ // 返回的上传信息 if ($(data).find('PostResponse')) { var res = $(data).find('PostResponse'); console.info('Bucket:' + res.find('Bucket').text() ); console.info('Location:' + res.find('Location').text() ); console.info('Key:' + res.find('Key').text() ); console.info('ETag:' + res.find('ETag').text() ); } // 图片预览 var img = new Image(); img.src = rst.base64; img.onload = function () { $(".preview_box").empty().append(img); }; }); }); return rst; }) .catch(function (err) { // 万一出错了,这里能够捕捉到错误信息 // 并且以上的then都不会执行 alert('ERROR:'+err); }) .always(function () { // 无论是成功失败,这里都会执行 }); }); </script>
最后总结了下,HTTP 必定要学好啊!!(因而哭着滚去看书了……)
【参考资料】