laravel + webupload + Aliyun OSS Direct Sharing

Posted by webnick on Tue, 20 Aug 2019 05:43:18 +0200

Recently, in the day after management, we need to upload some app package files. These packages are within several hundred megabytes in size. Normally, they are uploaded. They are not only very stressful to the server, but also consume server resources. Later, it was found that Aliyun can do direct web transmission, so today I will record the process.

Attachment: Aliyun web Direct OSS Documents
Aliyun OSS Live Example (php version)

The sample document is clear. All you have to do is download the PHP sample package, inherit it to your project root directory, test whether the sample can upload files to oss, and then try to integrate it into the webupload plug-in. As for Aliyun's appkey and Appsecret, it's better to put them in the back-end code, not in the front-end, which is insecure, that is, back-end signature direct transmission.

Code:

Source package: aliyun-oss-appserver-php-master

  • configuration file
        'oss' => [
            'driver'        => 'oss',
            'access_id'     => 'xxxx',
            'access_key'    => 'xxxxxxxxxx',
            'bucket'        => 'xxxxxx',
            'endpoint'      => 'oss-cn-shenzhen.aliyuncs.com/', // OSS Extranet Node or Custom External Domain Name
            //'endpoint_internal'=>'< internal endpoint [OSS Intranet Node], such as: oss-cn-shenzhen-internal.aliyuncs.com>,//v2.0.4 new configuration properties, if empty, the default use of endpoint configuration (because the intranet upload problem is not solved, please do not use intranet node upload for the time being, is working with Ali Tech. In Technical Communication)
//            'cdnDomain'=>'http://xxxxx.xxxx.com', //if isCName is true, getUrl determines whether cdnDomain is set to determine the returned url. If cdnDomain is not set, endpoint is used to generate url, otherwise cdn is used.
            'ssl'           => false ,// true to use 'https://' and false to use 'http://'. default is false,
//            'isCName'=> true, // whether to use a custom domain name, true: Storage.url() generates a file url using a custom cdn or domain name, false: generates a url using an external node
            'debug'         => env("APP_DEBUG",false),
            'juta_apk' => env("JUTA_APK","upload"),//apk address
        ],

  • Getting Signature Parameters

Copy the content of get.php file to the project controller, or other directory files. I add an additional layer of Service layer to the Model layer here, so I put it in the / app/Models/Service/UploadService.php file and replace the corresponding configuration with my own Aliyun configuration.

    public function gmtIso8601($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 getParams($dir){
        $id= config("filesystems.disks.oss.access_id");          // Please fill in your Access Key Id.
        $key= config("filesystems.disks.oss.access_key");     // Please fill in your Access Key Secret.
        // The format of $host is bucketname.endpoint. Please replace it with your real information.
//        $host = 'http://bucket-name.oss-cn-hangzhou.aliyuncs.com';
        $host = "http://".config("filesystems.disks.oss.bucket").'.'.config("filesystems.disks.oss.endpoint");
        // callbackUrl is the URL of the upload and callback server. Configure the following IP and Port as your own real URL information.
        $callbackUrl = 'http://'.$_SERVER['HTTP_HOST'].'/upload/callBack';
//        $dir ='user-dir-prefix/'; // The prefix specified when a user uploads a file.
//        $dir = $dir; // The prefix specified when a user uploads a file.

        $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;  //Set the policy timeout to 10s. That is, the policy will not be accessible after this valid time.
        $end = $now + $expire;
        $expiration = $this->gmtIso8601($end);


        //Maximum file size. Users can set it by themselves
        $condition = array(0=>'content-length-range', 1=>0, 2=>1048576000);
        $conditions[] = $condition;

        // Data uploaded by users must start with $dir, otherwise upload will fail. This step is not necessary, just for security reasons, to prevent users from uploading to other people's directories through 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;  // This parameter is to set the prefix specified when the user uploads the file.
        echo json_encode($response);
    }

  • Callback

Callback code is placed in the / APP / Http / Controllers / Upload Controller. PHP file

<?php
namespace App\Http\Controllers;

use App\Models\Service\UploadService;
use Illuminate\Http\Request;
use DB;

class UploadController extends Controller
{
    public function getUploadSign(Request $request){
        $remoteDir = $request->get("dir");
        $uploadService = new UploadService();
        $res = $uploadService->getParams($remoteDir);
        return $res ;
    }
    public function callBack(){
        // 1. Get the signature header and public key url header of OSS
        $authorizationBase64 = "";
        $pubKeyUrlBase64 = "";
        /**
         * Note: If you want to use HTTP_AUTHORIZATION header, you need to set rewrite in apache or nginx. Take apache as an example, modify it
         * Configuration file / etc / httpd / conf / httpd. conf (whichever is your apache installation path), add the following two lines under DirectoryIndex. PHP
            RewriteEngine On
            RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization},last]
         * */
        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. Obtain the signature of OSS
        $authorization = base64_decode($authorizationBase64);

        // 3. Acquisition of public key
        $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. Get the callback body
        $body = file_get_contents('php://input');

        // 5. Stitching the string to be signed
        $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. Verify Signature
        $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();
        }

    }
}
  • web routing
//web-side direct oss
Route::post("upload/getUploadSign","UploadController@getUploadSign");
Route::post("upload/callBack","UploadController@callBack");
	Note: The callback address must be token validated in the app/Middleware/VerifyCsrfToken.php file
<?php

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;

class VerifyCsrfToken extends Middleware
{
    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     *
     */
    protected $except = [
        "upload/callBack"   //web Direct Signature Callback
    ];
}

  • Front-end code
    html file must be labeled as input with name='file', otherwise the error will be reported.
<div class="form-group">
    <label for="download_url" class="control-label col-sm-2"><span class="require_red">*&nbsp;&nbsp;</span>upload apk: </label>
    <div id="uploader" class="wu-example col-sm-10 col-md-4 col-lg-5">
        <input type="hidden" name="file"> <!--There must be an addition here. name='file' Reporting errors on the wrong side-->
        <!--Used to store file information-->
        <div id="preview" class="uploader-list" style="margin-bottom: 3px"></div>
        <div id="picker" class="col-sm-5  col-md-3">Select Files</div>
        <div id="ctlBtn" class="btn btn-primary" style="height: 40px">Start uploading</div>
          <span class="require_red font-size">file type xxx.apk</span>
    </div>
</div>
                        
    
   <script src="/plugins/select-input-autocomplete/js/input-autocomplete.js" type="text/javascript"></script>
    <script src="{{asset('plugins')}}/webuploader-0.1.5/webuploader.min.js"></script>
    <script type="text/javascript" src="{{asset('plugins')}}/aliyun-oss-appserver-php-master/upload.js"></script>
    <script>
        var objdata = {
            upfile_nametype:"local_name",
            cdn_url : 'https://staticwxappt.loke123.com/',
            host :  "{{"https://".config("filesystems.disks.oss.bucket").".".config("filesystems.disks.oss.endpoint")}}",
            dir : "{{config("filesystems.disks.oss.juta_apk")."/"}}",
            path : ''
        };
        var uploader = WebUploader.create({
            auto:false,

            // swf file path
            swf: "{{ asset("plugins") }}/webuploader-0.1.5/Uploader.swf",

            // File receiving server.
            server: objdata.host,//Aliyun oss address

            // Select the button for the file. Optional.
            // Internally, based on the current operation, it may be an input element or flash.
            pick: '#picker',
            // accept:{
            //     title:'Images',
            //     extentions: "png,jpeg,jpg",
            //     mimeTypes: 'image/jpg,image/jpeg,image/png'
            // },
           //{{--formData:{--}}
            //        {{--'_token':'{{csrf_token()}}'--}}
            //        {{--},--}}
            fileVal:"file",
            // Not compressed image, default is jpeg, file upload will be compressed before uploading a re-upload!
            resize: false,
            fileNumLimit: 1
        });
        uploader.on("uploadBeforeSend",function (obj, data, headers) {
            $.ajax({
                type:"post",
                url:"/upload/getUploadSign",
                timeout:10000,
                data:{
                    "_token": '{{csrf_token()}}',
                    'dir' : objdata.dir
                },
                success : function (str) {
                    console.log("str",str);
                    if  (str) {
                        try{
                            var res = JSON.parse(str);
                            console.log('res',res)
                            // console.log('ll',calculate_object_name(data.name,objdata.upfile_nametype));return;
                            $.extend(data,{
                                'key' : res.dir + calculate_object_name(data.name,objdata.upfile_nametype),
                                'policy': res.policy,
                                'OSSAccessKeyId': res.accessid,
                                'success_action_status' : '200', //Let the server return 200, otherwise, the default will return 204  
                                'callback' : res.callback,
                                'signature': res.signature
                            });
                        } catch (e) {
                            layer.alert("System error");
                        }

                    } else {
                        layer.alert("The result is empty.");
                    }
                },
                error : function(XMLHttpRequest, textStatus, errorThrown) {
                    alert("ajax error");
                },
                complete : function(XMLHttpRequest,status){ //Final execution parameters after completion of the request  
                    if(status == 'timeout'){
                        layer.alert('Request timeout, please try again later!');
                    }
                },
                async : false
            })
            obj.filepath = objdata.cdn_url + data.key;
            objdata.path = objdata.cdn_url + data.key;
            console.log("data",data)
            headers['Access-Control-Allow-Origin'] = "*";
        })
        // When files are added
        uploader.on( 'fileQueued', function( file ) {
            // Create Thumbnail
            // If it's a non-picture file, you don't need to call this method.
            // uploader.makeThumb( file, function( error, src ) {
            //     if ( error ) {
            //         layer.msg('can't preview');
            //         return;
            //     }
            //     $("#preview").html("<img src='"+src+"'>");
            // }, 100, 100 );
            //Delete the wrong first file
            $("#picker").on("click",function () {
                uploader.removeFile(file);
            })
        });
        //Click Upload
        $("#ctlBtn").on("click",function (obj) {
            layer.load();
            uploader.upload();
        })
        // File upload is successful, add successful class to item, and upload successfully with style tags.
        uploader.on( 'uploadSuccess', function( file , msg ) {
            layer.closeAll()
            if (msg.Status == 'Ok') {
                layer.msg('Upload Success',{icon:1,time:2000});
                $('input[name=download_url]').val(objdata.path);
            } else {
                layer.msg("Upload failure",{icon:2,time:3000});
            }
            console.log("file",file);
            console.log("msg",msg);
        });
        // File upload failed, showing upload error.
        uploader.on( 'uploadError', function( file ) {
            layer.closeAll()
            // console.log("error file",file);
            var $li = $( '#'+file.id ),
                $error = $li.find('div.error');

            // Avoid duplicate creation
            if ( !$error.length ) {
                $error = $('<div class="error"></div>').appendTo( $li );
            }

            $error.text('Upload failure');
        });
    </script>

  • Rewrite upload.js file in source package file
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;
}
//Get the filename
function calculate_object_name(filename,g_object_name_type) {
    var g_object_name = '' ;
    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
        g_object_name = random_string(16) + suffix
    }
    return g_object_name ;
}

Up to now, the direct transmission of laravel+ webupload+OSS has been completed. If you have any questions, please leave a message!

Topics: PHP Apache JSON Javascript