tp5实现阿里云oss的web直传及实时进度显示

本文介绍了如何使用TP5框架实现阿里云OSS的大文件Web直传功能,包括后端签名接口、前端HTML及JS文件的实现。在直传过程中,文章还分享了遇到的回调问题及解决办法,如回调失败和文件大小限制的配置。最后提醒回调路由定义必须使用POST方式,并建议前后端上传文件大小限制保持一致。

对于大文件上传时使用阿里云oss的web直传可谓是舒服至极,速度快,编写方便。

整体来看该功能我将其分为三个部分:后端签名和回调接口、前端html、文档内的js文件下载;话不多说直接帖代码,看完代码我将会指出期间本人遇到的坑及解决方法。

后端签名接口:

protected $accessKeyId = '';		// Access Key ID
    protected $accessKeySecret = '';	// Access Key Secret
    protected $endpoint = 'http://oss-cn-hangzhou.aliyuncs.com';	// 阿里云oss 固定的 外网地址endpoint
    protected $bucket = '';			// Bucket名称

    public function getWebToOssSignature()
    {
//        if (empty($name))
//            return json(['code'=>-1,'data'=>'','msg'=>'非法请求']);
        $id= $this->accessKeyId;          // 请填写您的AccessKeyId。
        $key= $this->accessKeySecret;     // 请填写您的AccessKeySecret。
        // $host的格式为 bucketname.endpoint,请替换为您的真实信息。
        $host = 'http://wanzufile.oss-cn-hangzhou.aliyuncs.com';
        // $callbackUrl为上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实URL信息。
        $callbackUrl = config('host').'/callback';
        $dir = '';          // 用户上传文件时指定的前缀。

        $callback_param = array('callbackUrl'=>$callbackUrl,
            'callbackBody'=>'filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}',
            'callbackBodyType'=>"application/x-www-form-urlencoded");
        $callback_string = json_encode($callback_param);

        $base64_callback_body = base64_encode($callback_string);
        $now = time();
        $expire = 30;  //设置该policy超时时间是10s. 即这个policy过了这个有效时间,将不能访问。
        $end = $now + $expire;
        $expiration = $this->gmt_iso8601($end);

        //最大文件大小.用户可以自己设置
        $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);
        $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['callback'] = $base64_callback_body;
        $response['dir'] = $dir;  // 这个参数是设置用户上传文件时指定的前缀。
        echo json_encode($response);
    }

    private 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";
    }

后端回调接口:

public function ossUploadCallback()
    {
        $authorizationBase64 = "";
        $pubKeyUrlBase64 = "";
        if (isset($_SERVER['HTTP_AUTHORIZATION']))
        {
            $authorizationBase64 = $_SERVER['HTTP_AUTHORIZATION'];
        }
        if (isset($_SERVER['HTTP_X_OSS_PUB_KEY_URL']))
        {
            $pubKeyUrlBase64 = $_SERVER['HTTP_X_OSS_PUB_KEY_URL'];
        }

        if ($authorizationBase64 == '' || $pubKeyUrlBase64 == '')
        {
            header("http/1.1 403 Forbidden");
            exit();
        }

        // 2.获取OSS的签名
        $authorization = base64_decode($authorizationBase64);

        // 3.获取公钥
        $pubKeyUrl = base64_decode($pubKeyUrlBase64);
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $pubKeyUrl);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
        $pubKey = curl_exec($ch);
        if ($pubKey == "")
        {
            //header("http/1.1 403 Forbidden");
            exit();
        }

        // 4.获取回调body
        $body = file_get_contents('php://input');

        // 5.拼接待签名字符串
        $authStr = '';
        $path = $_SERVER['REQUEST_URI'];
        $pos = strpos($path, '?');
        if ($pos === false)
        {
            $authStr = urldecode($path)."\n".$body;
        } else
        {
            $authStr = urldecode(substr($path, 0, $pos)).substr($path, $pos, strlen($path) - $pos)."\n".$body;
        }

        // 6.验证签名
        $ok = openssl_verify($authStr, $authorization, $pubKey, OPENSSL_ALGO_MD5);
        if ($ok == 1)
        {
            header("Content-Type: application/json");
            $data = array("Status"=>"Ok");
            echo json_encode($data);
        } else
        {
            //header("http/1.1 403 Forbidden");
            exit();
        }
        exit();
    }

本人前端的html(这段svg特效也是借鉴网上大神的仅做了小小的修改):

<style>
        .box {
            height: 50px;
            width: 50px;
            position: relative;
            top: 25px;
            left: 50%;
            -webkit-transform: translate(-50%, -50%);
            transform: translate(-50%, -50%);
            background: #020438;
            border-radius: 100%;
            overflow: hidden;
        }
        .box .percent {
            position: absolute;
            left: 0;
            top: 0;
            z-index: 3;
            width: 100%;
            height: 100%;
            display: flex;
            display: -webkit-flex;
            align-items: center;
            justify-content: center;
            color: #fff;
            font-size: 14px;
        }
        .box .water {
            position: absolute;
            left: 0;
            top: 0;
            z-index: 2;
            width: 100%;
            height: 100%;
            -webkit-transform: translate(0, 100%);
            transform: translate(0, 100%);
            background: #4D6DE3;
            transition: all .3s;
        }
        .box .water_wave {
            width: 200%;
            position: absolute;
            bottom: 100%;
        }
        .box .water_wave_back {
            right: 0;
            fill: #C7EEFF;
            -webkit-animation: wave-back 1.4s infinite linear;
            animation: wave-back 1.4s infinite linear;
        }
        .box .water_wave_front {
            left: 0;
            fill: #4D6DE3;
            margin-bottom: -1px;
            -webkit-animation: wave-front .7s infinite linear;
            animation: wave-front .7s infinite linear;
        }

        @-webkit-keyframes wave-front {
            100% {
                -webkit-transform: translate(-50%, 0);
                transform: translate(-50%, 0);
            }
        }

        @keyframes wave-front {
            100% {
                -webkit-transform: translate(-50%, 0);
                transform: translate(-50%, 0);
            }
        }
        @-webkit-keyframes wave-back {
            100% {
                -webkit-transform: translate(50%, 0);
                transform: translate(50%, 0);
            }
        }
        @keyframes wave-back {
            100% {
                -webkit-transform: translate(50%, 0);
                transform: translate(50%, 0);
            }
        }
    </style>


<div class="ed-list-three" id="container">
            <span>相关视频</span>
            <button type="button" class="layui-btn layui-btn-normal" id="video"><i class="layui-icon"></i>选择视频</button>
            <div class="layui-inline">
                <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" style="display: none;">
                    <symbol id="wave">
                        <path d="M420,20c21.5-0.4,38.8-2.5,51.1-4.5c13.4-2.2,26.5-5.2,27.3-5.4C514,6.5,518,4.7,528.5,2.7c7.1-1.3,17.9-2.8,31.5-2.7c0,0,0,0,0,0v20H420z"></path>
                        <path d="M420,20c-21.5-0.4-38.8-2.5-51.1-4.5c-13.4-2.2-26.5-5.2-27.3-5.4C326,6.5,322,4.7,311.5,2.7C304.3,1.4,293.6-0.1,280,0c0,0,0,0,0,0v20H420z"></path>
                        <path d="M140,20c21.5-0.4,38.8-2.5,51.1-4.5c13.4-2.2,26.5-5.2,27.3-5.4C234,6.5,238,4.7,248.5,2.7c7.1-1.3,17.9-2.8,31.5-2.7c0,0,0,0,0,0v20H140z"></path>
                        <path d="M140,20c-21.5-0.4-38.8-2.5-51.1-4.5c-13.4-2.2-26.5-5.2-27.3-5.4C46,6.5,42,4.7,31.5,2.7C24.3,1.4,13.6-0.1,0,0c0,0,0,0,0,0l0,20H140z"></path>
                    </symbol>
                </svg>
                <div class="box">
                    <div class="percent">
                        <div class="percentNum" id="count">0</div>
                        <div class="percentB" id="">%</div>
                    </div>
                    <div id="water" class="water">
                        <svg viewBox="0 0 560 20" class="water_wave water_wave_back">
                            <use xlink:href="#wave"></use>
                        </svg>
                        <svg viewBox="0 0 560 20" class="water_wave water_wave_front">
                            <use xlink:href="#wave"></use>
                        </svg>
                    </div>
                </div>
            </div>
            <div class="layui-inline">
                <div style="width: 747px;">
                    <input type="text" id="url" name="video" lay-verify="url" placeholder="输入视频地址或点击上传视频" autocomplete="off" class="layui-input">
                </div>
            </div>
        </div>

文档内下载的js文件并更改相关配置秩序在html内引入该js即可:

var accessid = '';
var accesskey = '';
var host = '';
var policyBase64 = '';
var signature = '';
var callbackbody = '';
var filename = '';
var key = '';
var expire = 0;
var g_object_name = '';
var g_object_name_type = '';
var now = timestamp = Date.parse(new Date()) / 1000;
var status = false;

function send_request()
{
    var xmlhttp = null;
    if (window.XMLHttpRequest)
    {
        xmlhttp=new XMLHttpRequest();
    }
    else if (window.ActiveXObject)
    {
        xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
    }

    if (xmlhttp!=null)
    {
        // serverUrl是 用户获取 '签名和Policy' 等信息的应用服务器的URL,请将下面的IP和Port配置为您自己的真实信息。
        // serverUrl = 'http://88.88.88.88:8888/aliyun-oss-appserver-php/php/get.php'
        var serverUrl = '自己的回调地址';

        xmlhttp.open( "GET", serverUrl, false );
        xmlhttp.send( null );
        return xmlhttp.responseText
    }
    else
    {
        alert("Your browser does not support XMLHTTP.");
    }
}

function check_object_radio() {
    g_object_name_type = 'random_name';
}

function get_signature()
{
    // 可以判断当前expire是否超过了当前时间, 如果超过了当前时间, 就重新取一下,3s 作为缓冲。
    now = timestamp = Date.parse(new Date()) / 1000;
    if (expire < now + 3)
    {
        body = send_request();
        var obj = eval ("(" + body + ")");
        host = obj['host'];
        policyBase64 = obj['policy'];
        accessid = obj['accessid'];
        signature = obj['signature'];
        expire = parseInt(obj['expire']);
        callbackbody = obj['callback'];
        key = obj['dir'];
        return true;
    }
    return false;
};

function random_string(len) {
    len = len || 32;
    var chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';
    var maxPos = chars.length;
    var pwd = '';
    for (i = 0; i < len; i++) {
        pwd += chars.charAt(Math.floor(Math.random() * maxPos));
    }
    return pwd;
}

function get_suffix(filename) {
    pos = filename.lastIndexOf('.');
    suffix = '';
    if (pos != -1) {
        suffix = filename.substring(pos)
    }
    return suffix;
}

function calculate_object_name(filename)
{
    if (g_object_name_type == 'local_name')
    {
        g_object_name += "${filename}"
    }
    else if (g_object_name_type == 'random_name')
    {
        suffix = get_suffix(filename)
        g_object_name = key + random_string(10) + suffix
    }
    return ''
}

function get_uploaded_object_name(filename)
{
    if (g_object_name_type == 'local_name')
    {
        tmp_name = g_object_name;
        tmp_name = tmp_name.replace("${filename}", filename);
        return tmp_name
    }
    else if(g_object_name_type == 'random_name')
    {
        return g_object_name
    }
}

function set_upload_param(up, filename, ret)
{
    if (ret == false)
    {
        ret = get_signature()
    }
    g_object_name = key;
    if (filename != '') { suffix = get_suffix(filename)
        calculate_object_name(filename)
    }
    var new_multipart_params = {
        'key' : g_object_name,
        'policy': policyBase64,
        'OSSAccessKeyId': accessid,
        'success_action_status' : '200', //让服务端返回200,不然,默认会返回204
        'callback' : callbackbody,
        'signature': signature,
    };

    up.setOption({
        'url': host,
        'multipart_params': new_multipart_params
    });

    up.start();
}

var uploader = new plupload.Uploader({
    runtimes : 'html5,flash,silverlight,html4',
    browse_button : 'video',
    multi_selection: false,
    // container: document.getElementById('container'),
    flash_swf_url : '__JS__/lib/Moxie.swf',
    silverlight_xap_url : '__JS__/lib/Moxie.xap',
    url : 'http://oss.aliyuncs.com',

    filters: {
        mime_types : [ //允许上传文件格式
            { title : "Video files", extensions : "mp4" },
            { title : "Audio files", extensions : "mp3" }
        ],
        max_file_size : '2048mb', //最大只能上传10mb的文件
        prevent_duplicates : true //不允许选取重复文件
    },

    init: {
        PostInit: function() {
        },

        FilesAdded: function(up, files) {
            plupload.each(files, function(file) {
                set_upload_param(uploader, '', false);
                // layer.load(1);
                return false;
            });
        },

        BeforeUpload: function(up, file) {
            check_object_radio();
            set_upload_param(up, file.name, true);
        },

        UploadProgress: function(up, file) {
            // 实时的进度条效果再此处实现
            $('#count').text(file.percent);
            $('#water').css('transform','translate(0' + ',' + (100 - file.percent) + '%)');
            // if (file.percent == 100)
                // layer.closeAll();
        },

        FileUploaded: function(up, file, info) {
            if (info.status == 200)
            {
                layer.msg('上传成功');
                let name = '阿里云地址'+get_uploaded_object_name(file.name);
                // name = '<video src="' + name + '" controls="controls"></video>';
                $('#url').val(name);//赋值到input表单
                // document.getElementById(file.id).getElementsByTagName('b')[0].innerHTML = 'upload to oss success, object name:' + get_uploaded_object_name(file.name) + ' 回调服务器返回的内容是:' + info.response;
            } else if (info.status == 203) {
                layer.msg('文件上传成功啦');
                let name = '阿里云地址'+get_uploaded_object_name(file.name);
                $('#url').val(name);//赋值到input表单
                // document.getElementById(file.id).getElementsByTagName('b')[0].innerHTML = '上传到OSS成功,但是oss访问用户设置的上传回调服务器失败,失败原因是:' + info.response;
            } else {
                // document.getElementById(file.id).getElementsByTagName('b')[0].innerHTML = info.response;
            }
        },

        Error: function(up, err) {
            var txt = '';
            if (err.code == -600) {
                txt = '选择的文件太大了,可以根据应用情况,在upload.js 设置一下上传的最大大小';
            } else if (err.code == -601) {
                txt = '选择的文件后缀不对,可以根据应用情况,在upload.js进行设置可允许的上传文件类型';
            } else if (err.code == -602) {
                txt = '这个文件已经上传过一遍了';
            } else {
                txt = "Error xml:" + err.response;
            }
            layer.msg(txt);
        }
    }
});

uploader.init();

上述所有代码仅需复制即可实现如下图的效果:(当然html中还需要引入jquery和layer文件)

与此同时你可能会遇到的问题就是你的回调问题,文件上传成功了但是却一直提示回调设置失败如下图:

解决方法就是修改tp入口文件中的.htaccess文件新增以下几行代码即可:

 RewriteEngine on
  RewriteCond %{HTTP:Authorization} .
  RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

上传文件大小的限制这个需要注意的有两个地方: 第一个是官网给出最大上传文件大小限制为5G,第二个就是自己设置文件大小时要注意上面一点的同时要保证前后台设置的文件大小限制一致。设置位置截图如下:

(服务端)

 (客户端)

不一致的情况下不一定会报错。为什么是不一定呢?本人真实遇到的情况是客户端设置了4.5G的上传限制而客户端只设置了1G的上传限制导致前端上传进度显示100%但是最终无法获取上传成功后的文件地址,并在一段时间后提示错误(如下图)。结论就是上传文件限制根据客户端或服务的设置的值取最小的,因此本人建议设置一致:

最后一点注意就是回调地址的路由定义时一定要用post方式;

欢迎各位铁汁提出您的宝贵意见。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值