关于使用阿里云oss作大文件分片上传的做法

关于使用阿里云oss作大文件分片上传的做法

关于大文件上传,很简单,但是做起来也会碰到各种各样的问题,刚好做了一个,现在记录下过程

需求

公司新项目要做一个大文件上传功能,将一些工程项目模型之类的东西保存在线上,动辄上G的文件肯定不能保存在自己的服务器上面,使用阿里云的oss 服务。期间经历了以下几个方法
方法一:文件从客户端分片上传到服务器。服务器再进行分片上传到oss,已实现,
缺点:
1:文件在上传过程中占用服务器带宽。
2:用户上传的大文件可能删除不及时,在服务器成为垃圾文件,占用系统磁盘。需要另外写定时任务脚本清除。
优点:敏感数据不会暴露。

方法二:web 端直接上传 ,已实现。
缺点:临时 key, secret 会暴露给客户端,有一定风险。
优点:不会占用服务器资源,直接用户端跟oss 交互。

方法一 :后端保存上传

使用webuploader插件 进行分片上传到oss
先引入插件内容

<script src="/public/others/jquery.js"></script>
<script src="/public/webuploader/webuploader.js"></script>
<link rel ="slylesheet" type="text/css" href="/public/webuploader/webuploader.css">

html代码部分

<div class="demo">
    <div id="uploadfile">
        <!--用来存放文件信息-->
        <div id="the_2655" class="uploader-list"></div>
        <div class="form-group form-inline">
            <div id="pick_2655" style="float:left">选择文件</div>  
            <button id="Btn_2655" class="btn btn-default" style="padding: 5px 10px;border-radius: 3px;">开始上传</button>
        </div>
    </div>
</div>

js 部分

//上传文件函数
//ids唯一ID
//folder文件保存目录
function uploadfiles(ids,folder) {
    $(function(){
        var $list = $("#the_"+ids);
            $btn = $("#Btn_"+ids);
        var uploader = WebUploader.create({
          resize: false, // 不压缩image
          swf: '__PUBLIC__/ueditor/third-party/webuploader/uploader.swf', // swf文件路径
          server: '{:url("admin/upload/uploadFile")}', // 文件接收服务端。
          pick: "#pick_"+ids, // 选择文件的按钮。可选
          chunked: true, //是否要分片处理大文件上传
          chunkSize:5*1024*1024, //分片上传,每片2M,默认是5M
          //fileSizeLimit: 6*1024* 1024 * 1024,    // 所有文件总大小限制 6G
          fileSingleSizeLimit: 10*1024* 1024 * 1024,    // 单个文件大小限制 5 G
          formData: {
                folder:folder  //自定义参数
          }
          //auto: false //选择文件后是否自动上传
         // chunkRetry : 2, //如果某个分片由于网络问题出错,允许自动重传次数
          //runtimeOrder: 'html5,flash',
          // accept: {
          //   title: 'Images',
          //   extensions: 'gif,jpg,jpeg,bmp,png',
          //   mimeTypes: 'image/*'
          // }
        });
        // 当有文件被添加进队列的时候
        uploader.on( 'fileQueued', function( file ) {
            $list.append( '
+ file.id + '" class="item">' + '

' + file.name + '

'
+ '

等待上传...

'
+ '
'
); }); // 文件上传过程中创建进度条实时显示。 uploader.on( 'uploadProgress', function( file, percentage ) { var $li = $( '#'+file.id ), $percent = $li.find('.progress .progress-bar'); // 避免重复创建 if ( !$percent.length ) { $percent = $('
' + '
' + '
'
+ '
'
).appendTo( $li ).find('.progress-bar'); } $li.find('p.state').text('上传中'); $percent.css( 'width', percentage * 100 + '%' ); }); // 文件上传成功 uploader.on( 'uploadSuccess', function( file,response) { $( '#'+file.id ).find('p.state').text('已上传'); $list.append('+ids+'" value="'+response.filePath+'" />'); //alert(response.filePath); }); // 文件上传失败,显示上传出错 uploader.on( 'uploadError', function( file ) { $( '#'+file.id ).find('p.state').text('上传出错'); }); // 完成上传完 uploader.on( 'uploadComplete', function( file ) { $( '#'+file.id ).find('.progress').fadeOut(); }); $btn.on('click', function () { if ($(this).hasClass('disabled')) { return false; } uploader.upload(); // if (state === 'ready') { // uploader.upload(); // } else if (state === 'paused') { // uploader.upload(); // } else if (state === 'uploading') { // uploader.stop(); // } }); }); }

注意demo 里边分片的每片文件大小是5M,php 的ini 文件里边最大限制的参数要大于5M,才能上传成功,具体怎么修改的话去百度。

php 部分

/**
     * 上传文件函数,如过上传不成功打印$_FILES数组,查看error报错信息
     * 值:0; 没有错误发生,文件上传成功。
     * 值:1; 上传的文件超过了 php.ini 中 upload_max_filesize 选项限制的值。
     * 值:2; 上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值。
     * 值:3; 文件只有部分被上传。
     * 值:4; 没有文件被上传。
     * date:2018.4.18    from:zhix.net
     */
    public function uploadFile(){
        header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
        header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
        header("Content-type: text/html; charset=gbk32");
        header("Cache-Control: no-store, no-cache, must-revalidate");
        header("Cache-Control: post-check=0, pre-check=0", false);
        header("Pragma: no-cache");
        $folder = input('folder');
        if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
            exit; // finish preflight CORS requests here
        }
        if ( !empty($_REQUEST[ 'debug' ]) ) {
            $random = rand(0, intval($_REQUEST[ 'debug' ]) );
            if ( $random === 0 ) {
                header("HTTP/1.0 500 Internal Server Error");
                exit;
            }
        }
        // header("HTTP/1.0 500 Internal Server Error");
        // exit;
        // 5 minutes execution time
        set_time_limit(5 * 60);
        // Uncomment this one to fake upload time
         usleep(5000);
        // Settings
          $targetDir = Env::get('root_path').'/upload/test/file_material_tmp'; //存放分片临时目录        
      if($folder){
          $uploadDir =Env::get('root_path').'/upload/test/file_material/';
      }else{
          $uploadDir = Env::get('root_path').'/upload/test/file_material/';    //分片合并存放目录
      }

 
        $cleanupTargetDir = true; // Remove old files
        $maxFileAge = 5 * 3600; // Temp file age in seconds
 
        // Create target dir
        if (!file_exists($targetDir)) {
            mkdir($targetDir,0777,true);
        }
        // Create target dir
        if (!file_exists($uploadDir)) {
            mkdir($uploadDir,0777,true);
        }
        // Get a file name
        if (isset($_REQUEST["name"])) {
            $fileName = $_REQUEST["name"];
        } elseif (!empty($_FILES)) {
            $fileName = $_FILES["file"]["name"];
        } else {
            $fileName = uniqid("file_");
        }
        $oldName = $fileName;
 
        $fileName = iconv('UTF-8','gb2312',$fileName);
        $filePath = $targetDir . DIRECTORY_SEPARATOR . $fileName;
        // $uploadPath = $uploadDir . DIRECTORY_SEPARATOR . $fileName;
        // Chunking might be enabled
        $chunk = isset($_REQUEST["chunk"]) ? intval($_REQUEST["chunk"]) : 0;
        $chunks = isset($_REQUEST["chunks"]) ? intval($_REQUEST["chunks"]) : 1;
        // Remove old temp files
        if ($cleanupTargetDir) {
            if (!is_dir($targetDir) || !$dir = opendir($targetDir)) {
                die('{"jsonrpc" : "2.0", "error" : {"code": 100, "message": "Failed to open temp directory111."}, "id" : "id"}');
            }
            while (($file = readdir($dir)) !== false) {
                $tmpfilePath = $targetDir . DIRECTORY_SEPARATOR . $file;
                // If temp file is current file proceed to the next
                if ($tmpfilePath == "{$filePath}_{$chunk}.part" || $tmpfilePath == "{$filePath}_{$chunk}.parttmp") {
                    continue;
                }
                // Remove temp file if it is older than the max age and is not the current file
                if (preg_match('/\.(part|parttmp)$/', $file) && (filemtime($tmpfilePath) < time() - $maxFileAge)) {
                    unlink($tmpfilePath);
                }
            }
            closedir($dir);
        }
        // Open temp file
        if (!$out = fopen("{$filePath}_{$chunk}.parttmp", "wb")) {
            die('{"jsonrpc" : "2.0", "error" : {"code": 102, "message": "Failed to open output stream222."}, "id" : "id"}');
        }
        if (!empty($_FILES)) {
            if ($_FILES["file"]["error"] || !is_uploaded_file($_FILES["file"]["tmp_name"])) {
                die('{"jsonrpc" : "2.0", "error" : {"code": 103, "message": "Failed to move uploaded file333."}, "id" : "id"}');
            }
            // Read binary input stream and append it to temp file
            if (!$in = fopen($_FILES["file"]["tmp_name"], "rb")) {
                die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "Failed to open input stream444."}, "id" : "id"}');
            }
        } else {
            if (!$in = fopen("php://input", "rb")) {
                die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "Failed to open input stream555."}, "id" : "id"}');
            }
        }
        while ($buff = fread($in, 4096)) {
            fwrite($out, $buff);
        }
        fclose($out);
        fclose($in);
        rename("{$filePath}_{$chunk}.parttmp", "{$filePath}_{$chunk}.part");
        $index = 0;
        $done = true;
        for( $index = 0; $index < $chunks; $index++ ) {
            if ( !file_exists("{$filePath}_{$index}.part") ) {
                $done = false;
                break;
            }
        }
 
        if ($done) {
            $pathInfo = pathinfo($fileName);
            $hashStr = substr(md5($pathInfo['basename']),8,16);
            $hashName = time() . $hashStr . '.' .$pathInfo['extension'];
            $uploadPath = $uploadDir . DIRECTORY_SEPARATOR .$hashName;
            if (!$out = fopen($uploadPath, "wb")) {
                die('{"jsonrpc" : "2.0", "error" : {"code": 102, "message": "Failed to open output stream666."}, "id" : "id"}');
            }
            //flock($hander,LOCK_EX)文件锁
            if ( flock($out, LOCK_EX) ) {
                for( $index = 0; $index < $chunks; $index++ ) {
                    if (!$in = fopen("{$filePath}_{$index}.part", "rb")) {
                        break;
                    }
                    while ($buff = fread($in, 4096)) {
                        fwrite($out, $buff);
                    }
                    fclose($in);
                    unlink("{$filePath}_{$index}.part");
                }
                flock($out, LOCK_UN);
            }
            fclose($out);
            $response = [
                'success'=>true,
                'oldName'=>$oldName,
                'filePath'=>$uploadPath,
//                'fileSize'=>$data['size'],
                'fileSuffixes'=>$pathInfo['extension'],          //文件后缀名
//                'file_id'=>$data['id'],
            ];
            return json($response);
        }
 
        // Return Success JSON-RPC response
        die('{"jsonrpc" : "2.0", "result" : null, "id" : "id"}');
    }   

后端接收分片,并且组合文件的方法。注意几点
1:方法的header 头里边要加上 utf8的限制,如下,不然文件名称为中文会乱码
2:返回的路径是绝对路径,自己根据项目需求自己截取。

    header("Content-type: text/html; charset=utf-8");

文件上传部分:


if (is_file(__DIR__ . '/../autoload.php')) {
    require_once __DIR__ . '/../autoload.php';
}
if (is_file(__DIR__ . '/../vendor/autoload.php')) {
    require_once __DIR__ . '/../vendor/autoload.php';
}

use OSS\OssClient;
use OSS\Core\OssException;

// 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建RAM账号。
$accessKeyId = "";
$accessKeySecret = "";
// Endpoint以杭州为例,其它Region请按实际情况填写。
$endpoint = "http://oss-cn-hangzhou.aliyuncs.com";
$bucket= "";
$object = "";
$file = "";

$options = array(
    OssClient::OSS_CHECK_MD5 => true,
    OssClient::OSS_PART_SIZE => 1,
);
try{
    $ossClient = new OssClient($accessKeyId, $accessKeySecret, $endpoint);

    $ossClient->multiuploadFile($bucket, $object, $file, $options);
} catch(OssException $e) {
    printf(__FUNCTION__ . ": FAILED\n");
    printf($e->getMessage() . "\n");
    return;
}
print(__FUNCTION__ . ":  OK" . "\n");

注意 multiuploadFile 方法,该方法就是分片上传的具体封装的方法,阿里云分片上传是上传一个具体的文件,你自己的每一个分片阿里云是不承认的,所以不要自己尝试上传分片文件。
具体使用方法参考链接:
阿里云php oss分片上传描述文档

上传结果参考文档。

方法二 ,web前端直传

web 直传有个问题就是需要 STS临时授权访问
你需要了解这个玩意
具体就是阿里云总帐号创建一个子账号,子账号被赋予了oss 的权限,然后拥有oss 权限的子账号相当于一个新的RAM角色,通过这个子账号跟角色创建时候生成的 id和 secret 可以获取一个临时token,通过这个token,前端在不获取总账号权限的情况下可以进行一些操作。具体创建子账号的流程可以看上边链接,赋予的权限选择前端要使用的,根据自己实际情况。

走的弯路

阿里云有给到一个获取STS临时授权访问 的接口文档,
阿里云获取sts
关于使用阿里云oss作大文件分片上传的做法_第1张图片
注意这里的version 2015-04-01, 各种尝试不通,报错如下:

sts.aliyuncs.comInvalidVersionSpecified parameter Version is not valid.

提工单,回复如下:
在这里插入图片描述
我dnmd,不能用你挂这里干嘛?浪费谁的时间?

附:请求接口的方法:

class STS
{
    protected $url = 'https://sts.aliyuncs.com';
    protected $accessKeySecret = '1234567890qwertyuioasdfghj';
    protected $accessKeyId = 'LT11234567898';
    protected $roleArn = 'acs:ram::$accountID:role/$roleName';//指定角色的 ARN ,角色策略权限
    protected $roleSessionName = 'client1';//用户自定义参数。此参数用来区分不同的 token,可用于用户级别的访问审计。格式:^[a-zA-Z0-9\.@\-_]+$
    protected $durationSeconds = '1800';//指定的过期时间
    protected $type = 'xxx';//方便调用时获取不同的权限

    public function __construct($type)
    {
        $this->type = $type;
        $this->setRoleArn();
    }

    public function sts()
    {
        $action = 'AssumeRole';//通过扮演角色接口获取令牌
        date_default_timezone_set('UTC');
        $param = array(
            'Format'           => 'JSON',
            'Version'          => '2015-04-01',
            'AccessKeyId'      => $this->accessKeyId,
            'SignatureMethod'  => 'HMAC-SHA1',
            'SignatureVersion' => '1.0',
            'SignatureNonce'   => $this->getRandChar(8),
            'Action'           => $action,
            'RoleArn'          => $this->roleArn,
            'RoleSessionName'  => $this->roleSessionName,
            'DurationSeconds'  => $this->durationSeconds,
            'Timestamp'        => date('Y-m-d') . 'T' . date('H:i:s') . 'Z'
            //'Policy'=>'' //此参数可以限制生成的 STS token 的权限,若不指定则返回的 token 拥有指定角色的所有权限。
        );
        $param['Signature'] = $this->computeSignature($param, 'POST');
        $res = CurlHandle::httpPost($this->url, $param);//curl post请求
        if ($res) {
            return self::_render($res);
        } else {
            return [];
        }
    }

    private static function _render($res)
    {
        $res = json_decode($res, true);
        if (empty($res['Credentials'])) {
            return [];
        } else {
            return [
                'accessKeySecret' => $res['Credentials']['AccessKeySecret'] ?? '',
                'accessKeyId'     => $res['Credentials']['AccessKeyId'] ?? '',
                'expiration'      => $res['Credentials']['Expiration'] ?? '',
                'securityToken'   => $res['Credentials']['SecurityToken'] ?? '',
            ];
        }
    }

    protected function computeSignature($parameters, $setMethod)
    {
        ksort($parameters);
        $canonicalizedQueryString = '';
        foreach ($parameters as $key => $value) {
            $canonicalizedQueryString .= '&' . $this->percentEncode($key) . '=' . $this->percentEncode($value);
        }
        $stringToSign = $setMethod . '&%2F&' . $this->percentencode(substr($canonicalizedQueryString, 1));
        $signature = $this->getSignature($stringToSign, $this->accessKeySecret . '&');

        return $signature;
    }

    public function getSignature($source, $accessSecret)
    {
        return base64_encode(hash_hmac('sha1', $source, $accessSecret, true));
    }

    protected function percentEncode($str)
    {
        $res = urlencode($str);
        $res = preg_replace('/\+/', '%20', $res);
        $res = preg_replace('/\*/', '%2A', $res);
        $res = preg_replace('/%7E/', '~', $res);
        return $res;
    }

    public function getRandChar($length)
    {
        $str = null;
        $strPol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
        $max = strlen($strPol) - 1;

        for ($i = 0; $i < $length; $i++) {
            $str .= $strPol[rand(0, $max)];//rand($min,$max)生成介于min和max两个数之间的一个随机整数
        }

        return $str;
    }

    protected function setRoleArn()
    {
        if ($this->type == '123') {//根据入参使用不同的策略,当然这里还可以有其他写法兼容更多的策略的情况
            $this->roleArn = 'acs:ram::123456789098:role/=$roleName';
        }
    }
}

正道的光

还是要使用sdk ,不过这个sdk 是阿里云已经给封装好的该模块的sdk ,比自己从github里边下载的好使一万倍。tp 的话就放到vendor 下边就行,laravel 的话自己看,能正常引入就行。
STS的SDK下载链接
关于使用阿里云oss作大文件分片上传的做法_第2张图片
下载完成之后引入项目

简单的请求token 的方法,
1:注意过期时间,默认是3600s,
2:accessKeySecret,accessKeyId,这两个参数是你创建的子账号的参数,不是总账号的。


namespace app\services;
 
class StsService
{
    protected $url = 'https://sts.aliyuncs.com';
    protected $accessKeySecret;
    protected $accessKeyId;
    protected $roleArn;//指定角色的 ARN ,角色策略权限
    protected $roleSessionName = 'client';//用户自定义参数。此参数用来区分不同的 token,可用于用户级别的访问审计。格式:^[a-zA-Z0-9\.@\-_]+$
    protected $durationSeconds = '3600';//指定的过期时间
 
 
    public function __construct()
    {
        $this->accessKeySecret = config('oss_sts_accessKeySecret');
        $this->accessKeyId = config('oss_sts_accessKeyId');
        $this->roleArn = config('oss_sts_roleArn');
    }
 
 
    public function getStsOuah()
    {
 
        require_once VENDOR_PATH.'aliyuncs/sts-server/aliyun-php-sdk-core/Config.php';
 
        $iClientProfile = \DefaultProfile::getProfile("cn-hangzhou", $this->accessKeyId, $this->accessKeySecret);
 
        $client = new \DefaultAcsClient($iClientProfile);
 
        $request = new \Sts\Request\V20150401\AssumeRoleRequest();
 
        $request->setRoleSessionName("client_name");
 
        $request->setRoleArn($this->roleArn);
 
//        $request->setPolicy(VENDOR_PATH.'aliyuncs/sts-server/policy/bucket_write_policy.txt');
 
        $request->setDurationSeconds($this->durationSeconds);
 
        $response = $client->doAction($request);
 
        $rows = array();
 
        $body = $response->getBody();
 
        $content = json_decode($body);
 
        if ($response->getStatus() == 200){
            $rows['statusCode'] = 200;
            $rows['accessKeyId'] = $content->Credentials->AccessKeyId;
            $rows['accessKeySecret'] = $content->Credentials->AccessKeySecret;
            $rows['expiration'] = $content->Credentials->Expiration;
            $rows['securityToken'] = $content->Credentials->SecurityToken;
        }else{
            $rows['statusCode'] = 500;
            $rows['errorCode'] = $content->Code;
            $rows['errorMessage'] = $content->Message;
        }
 
        return $rows;
    }
 
}

返回的数据,需要把这些数据传给前端

   Array
(
    [statusCode] => 200
    [accessKeyId] => STS.NT3aC8UXWJydcuuoyreGfvWxJ
    [accessKeySecret] => 59vT1iGHaJtEiyDwpEN9125ZgBJ4eShfUbtnXiGvQirb
    [expiration] => 2020-06-06T06:11:20Z
    [securityToken] => CAIS9QF1q6Ft5B2yfSjIr5eGKvmMuId2/buzYVPEi3k2achKmZLTqDz2IHlPdHlrBOwesv8/lGFY7fwblqJ4T55IQ1Dza8J148z5Be4Lo8yT1fau5Jko1beHewHleTOZsebWZ+LmNqC/Ht6md1HDkAJq3LL+bk/Mdle5MJqP+/UFB5ZtKWveVzddA8pMLQZPsdITMWCrVcygKRn3mGHdfiEK00he8TonsPXhn5fEskSF3QOhlbAvyt6vcsT+Xa5FJ4xiVtq55utye5fa3TRYgxowr/4t3PwfpGuf4orNUwgBuE/fKYnd/thuKh5kYLM6Gr71cwRkHn730xqAARfcIG9I3u0E7TJzfmTvcIIIpogdqzjed27c1JvZAoAxHu+6XgIBgrbcLds0q2InwBPKWF4S0nfKnI9kXPn1CXtKdjQmgLW0cRz0w9/OfbxTIX9lb2HmtWABxvLVAgQtHJRb4o6xxgkQy5pBs3BeHYJ1yY9NZCDu5zW2OGC03MqZ
)

前端代码:
本人 不是专业前端,样式凑合着看吧。

html

<div id="up_wrap"></div>
<div class="form-group">
    <input type="file" id="file" multiple="multiple" />
</div>
<div class="form-group">
    <input type="button" class="btn btn-primary" id="file-button" value="Upload" />
    <input type="button" class="btn btn-primary" id="Continue-button" value="Continue" />
</div>

js

<script src="http://gosspublic.alicdn.com/aliyun-oss-sdk-6.0.2.min.js"></script>
<script type="text/javascript">
    var appServer = 'http://www.tele.bh/med/home/sts';//获取ststoken的接口,这边这个地址是我本地的。你们的接口地址自己应该清楚
    var bucket = '你新建的bucket名字';
    var region = 'oss-cn-hangzhou';//前面新建bucket时选择过的。
    var uid = 'x';//用户标识。这个根据自己情况自己定
    var Buffer = OSS.Buffer;
    console.log(OSS);
    //获取授权STSToken,并初始化client
    var applyTokenDo = function (func) {
        var url = appServer;
        return $.get(url).then(function (result) {
            var res = JSON.parse(result);
            var creds = res.data.Credentials;
            console.log(creds);
            var client = new OSS({
                region: region,
                accessKeyId: creds.AccessKeyId,
                accessKeySecret: creds.AccessKeySecret,
                stsToken: creds.SecurityToken,
                bucket: bucket
            });
            return func(client);
        });
    };
 
    //上传文件
    var uploadFile = function (client) {
        if (upfiles.length < 1)
            return;
        upfile = upfiles[0];
        var file = upfile.file;
        //key可以自定义为文件名(例如file.txt)或目录(例如abc/test/file.txt)的形式,实现将文件上传至当前Bucket或Bucket下的指定目录。
        var key = upfile.name;
        var objkey = key + "_" + uid + ".json";
        return client.multipartUpload(key, file, {
            progress: function (p, cpt, res) {
                console.log("p:", p);
                console.log("cpt:", cpt);
                if (cpt != undefined) {
                    var content = JSON.stringify(cpt);
                    client.put(objkey, new Buffer(content));
                }
 
                console.log(Math.floor(p * 100) + '%');
                var bar = document.getElementById('progress-bar_' + upfile.num);
                bar.style.width = Math.floor(p * 100) + '%';
                bar.innerHTML = Math.floor(p * 100) + '%';
 
            }
        }).then(function (res) {
            console.log('upload success: ', res);
            upfiles.shift();
            client.delete(objkey);
            applyTokenDo(uploadFile);
        }).catch(function(err) {
            console.log(err);
            error(err);
        });
    };
 
    //断点续传文件
    var reUploadFile = function (client) {
        if (upfiles.length < 1)
            return;
        upfile = upfiles[0];
        var file = upfile.file;
        //key可以自定义为文件名(例如file.txt)或目录(例如abc/test/file.txt)的形式,实现将文件上传至当前Bucket或Bucket下的指定目录。
        var key = upfile.name;
        var objkey = key + "_" + uid + ".json";
        return client.get(objkey).then(function (res) {
            var data = JSON.parse(res.content);
            data.file = file;
            return client.multipartUpload(key, file, {
                checkpoint: data,
                progress: function (p, cpt, res) {
                    console.log("p:", p);
                    console.log("cpt:", cpt);
                    if (cpt != undefined) {
                        var content = JSON.stringify(cpt);
                        client.put(objkey, new Buffer(content));
                    }
                    var bar = document.getElementById('progress-bar_' + upfile.num);
                    bar.style.width = Math.floor(p * 100) + '%';
                    bar.innerHTML = Math.floor(p * 100) + '%';
                }
            }).then(function (ret) {
                console.log('upload success:', ret);
                upfiles.shift();
                client.delete(objkey);
                applyTokenDo(uploadFile);
            }).catch(function(err) {
                console.log(err);
                error(err);
            });
        });
    };
 
    function error(err){
        switch (err.status) {
            case 0:
                if (err.name == "cancel") { //手动点击暂停上传
                    return;
                }
                break;
            case -1: //请求错误,自动重新上传
                // 重新上传;
                return;
            case 203: //回调失败
                // 前端自己给后台回调;
                return;
            case 400:
                switch (err.code) {
                    case 'FilePartInterity': //文件Part已改变
                    case 'FilePartNotExist': //文件Part不存在
                    case 'FilePartState': //文件Part过时
                    case 'InvalidPart': //无效的Part
                    case 'InvalidPartOrder': //无效的part顺序
                    case 'InvalidArgument': //参数格式错误
                        // 清空断点;
                        // 重新上传;
                        return;
                    case 'InvalidBucketName': //无效的Bucket名字
                    case 'InvalidDigest': //无效的摘要
                    case 'InvalidEncryptionAlgorithmError': //指定的熵编码加密算法错误
                    case 'InvalidObjectName': //无效的Object名字
                    case 'InvalidPolicyDocument': //无效的Policy文档
                    case 'InvalidTargetBucketForLogging': //Logging操作中有无效的目标bucket
                    case 'MalformedXML': //XML格式非法
                    case 'RequestIsNotMultiPartContent': //Post请求content-type非法
                        // 重新授权;
                        // 继续上传;
                        return;
                    case 'RequestTimeout'://请求超时
                        // 重新上传;
                        return;
                }
                break;
            case 403: //授权无效,重新授权
            case 411: //缺少参数
            case 404: //Bucket/Object/Multipart Upload ID 不存在
                // 重新授权;
                // 继续上传;
                return;
            case 500: //OSS内部发生错误
                // 重新上传;
                return;
            default:
                break;
        }
    }
    //文件上传队列
    var upfiles = [];
 
    $(function () {
        //初始化文件上传队列
        $("#file").change(function (e) {
            var ufiles = $(this).prop('files');
            var htm = "";
            for (var i = 0; i < ufiles.length; i++) {
                htm += "
" + ufiles[i].name + "
+ i + "\" class=\"progress-bar\" role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\" style=\"min-width: 2em;\">0%
"
; upfiles.push({ num: i, name: ufiles[i].name, file: ufiles[i] }) } console.log('upfiles:', upfiles); $("#up_wrap").html(htm); }); //上传 $("#file-button").click(function () { applyTokenDo(uploadFile); }); //续传 $("#Continue-button").click(function () { applyTokenDo(reUploadFile); }) }) </script>

分片上传完成之后会返回带uploadId的文件夹链接,上传给后端就行了。
注意几点,
1:角色的权限,RAM 设置的时候要给put, delete 请求权限,不然js会报错。
2:跨域访问:角色设置的时候要有 Access-Control-Allow-Origin header头,允许跨域访问。

综合所述

功能没那么难,问题出在要么阿里云给的给的文档不好使,要么sdk 难用。本人也是综合了好几个百度出来的文档才搞出来的,分享出来希望以后大家少走弯路吧。

你可能感兴趣的:(大文件上传)