阿里云OSS文件上传几种方法(主要是前端)

零、准备

要是实现将文件上传到阿里云OSS,首先就要开通了OSS服务,然后创建bucket之类的。这些就不多说了。
稍微看了下文档就能看见有accessKeyId,accessKeySecret,endpoint,bucket等之类的。这些要哪里来呢。

  1. accessKeyId,accessKeySecret
    在控制台找到RAM访问控制它大概长下面这样,在这里创建用户,就能得到accessKeyId,accessKeySecret了。注意:accessKeySecret只会显示一次,你创建的时候就要保存起来,要不然就要重创了。
    image

  2. 然后就要给创建的用户权限,然后才能操作OSSAPI。
    image

  3. 然后就是endpoint,bucket这些了,要在'对象存储 OSS'页面中拿。
    Bucket列表能看到Bucket的名称,在每个bucket的概览里就能看到endpoint之类的了。
    image

结合PHP(或者纯前端)主要介绍三种不同的向OSS上传文件的方式,都是用过或者踩过坑的😂。

一、服务端签名后直传

优点:官方文档

  • 使用插件(如,element upload, web uploader等)上传,不用重写上传方法,通过地址提交,可以方便的使用插件提供的功能。
  • 前端值传不进过应用服务器中传,效率更高,而且前端也能方便获取上传进度。
  • 需要后端进行签名,前端获取签名后再去上传,也不会暴露accessKeySecret,accessKeyId等oss信息。

1. 阿里云控制台配置

创建跨越规则,否则web前端上传的时候会报CORS跨越错误。** 具体操作如下图:
image

2. 后端接口开发(PHP)

可以参考下官方示例代码PHP),然后我们按照自己需要进行修改就基本上能实现了。这里是修改后的代码,因为只需将视频上传到OSS中,而且上传后的地址也是知道的,所以就注释了上传回调相关的操作,代码如下:

use DateTime;

class ContentsController extends Controller {
    // 阿里云oss配置
    private $accessKeyId = 'xxxxxxxx'; // 请填写您的AccessKeyId
    private $accessKeySecret = 'xxxxxx'; // 请填写您的AccessKeySecret
    private $bucket= "your-bucketName";
    private $endpoint = "oss-cn-hangzhou.aliyuncs.com"; 

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

    // 阿里云签名接口
    // 调用该接口可传入要上传到的文件夹dir
    public function signature() {
        {
            
            $id = $this -> accessKeyId;          // 请填写您的AccessKeyId。
            $key = $this -> accessKeySecret;     // 请填写您的AccessKeySecret。
            $host = 'https://'.$this -> bucket.'.'.$this -> endpoint;
            // $host = 'https://xxxx.oss-cn-hangzhou.aliyuncs.com'; // $host的格式为 bucketname.endpoint,
            
            $dir = trim(I('dir', '', 'string')); // 用户上传文件时指定的前缀。

            // 若需要配置回调服务器URL 则此配置
            // private $callbackUrl = 'http://你的域名/callback.php';//上传回调的地址 还记得上图中callback.php文件吗,把这文件放在你的项目中,配个路由保证能访问到这个文件就行,这个值就是访问callback.php此文件的URL 例如:我放在项目根目录 那值就为 http://liutong.pro/callback.php
            // $callbackUrl = $this->callbackUrl;
            // $callbackUrl为上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实URL信息。
            // $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 => 2147483648);
            $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);
        }
    }
}

Java签名方法https://www.cnblogs.com/applesnt/p/14863057.html

3. 前端获取签名后上传

前端先调用签名接口(如上是'/syscenter/contents/signature'),返回签名信息:

{
    "accessid": "xxxxxtAq4LFttzpLZqvn2N5Y",
    "callback": null,
    "dir": "upload/",
    "expire": 1666918519,
    "host": "https://zzxxx.oss-cn-hangzhou.aliyuncs.com",
    "policy": "eyJxxxx0aW9uIjoiMjAyMi0xMC0yOFQwODo1NToxOVoiLCJjb25kaXRpb25zIjpbWyJjb250ZW50LWxlbmd0aC1yYW5nZSIsMCwyMTQ3NDgzNjQ4XSxbInN0YXJ0cy13aXRoIiwixxxxxxxxxx",
    "signature": "Q4xxWOjB3kjGaNRF097LyG0SQ="
}

然后就可以使用签名信息进行上传了,提交到host,其他参数也一起带上,主要代码如下:

<template>
  <el-upload
    v-model:file-list="fileList"
    :action="uploadUrl"
    :data="uploadData"
  >
    <el-button type="primary">上传</el-button>
  </el-upload>
</template>

<script setup>
const uploadUrl = ref();
const uploadData = reactive()

const getSignature = async () => {
  const data = await axios.get('/syscenter/contents/signature', param: {dir:'/upload'})
  uploadUrl.value = data.host
  uploadData.policy = data.policy
  uploadData.OSSAccessKeyId = data.accessid
  uploadData['success_action_status'] = 200
  uploadData.signature = data.signature
}
</script>

二、使用STS临时凭证进行上传

优点:官方文档

  1. 前端使用的是临时凭证访问的OSS,无需暴露长期密钥,更安全。
  2. 配合ali-oss能方便进行大文件的分片上传。

1. 后端接口开发(node)

const { STS } = require('ali-oss');
import express from "express";
const router = express.Router();

router.get("/getToken", function (req, res) {
  let sts = new STS({
    accessKeyId: "xxxxxxx",
    accessKeySecret: "xxxx",
  });
  // assumeRole配置RAM用户的ARN。
  // policy填写自定义权限策略,用于进一步限制STS临时访问凭证的权限。如果不指定Policy,则返回的STS临时访问凭证默认拥有指定角色的所有权限。
  // expiration用于设置临时访问凭证有效时间单位为秒,最小值为900,最大值以当前角色设定的最大会话时间为准。本示例指定有效时间为3000秒。
  // sessionName用于自定义角色会话名称,用来区分不同的令牌,例如填写为sessiontest。
  sts
    .assumeRole("acs:ram::175708322470****:role/ramtest", ``, "3000", "sessiontest")
    .then((result) => {
      console.log(result);
      res.set("Access-Control-Allow-Origin", "*");
      res.set("Access-Control-Allow-METHOD", "GET");
      res.json({
        AccessKeyId: result.credentials.AccessKeyId,
        AccessKeySecret: result.credentials.AccessKeySecret,
        SecurityToken: result.credentials.SecurityToken,
        Expiration: result.credentials.Expiration,
      });
    })
    .catch((err) => {
      console.log(err);
      res.status(400).json(err.message);
    });
});
export default router;

2. 前端获取临时token再通过sdk进行上传

{
    "accessKeyId": "STS.NSmigxxevTxbauZ5hMdM",
    "accessKeySecret": "5wLHcDpqjojqZaVXFgDnBej7WHrao5Xzy6eGXufWzkSC",
    "securityToken": "CAIS5QF1q6Ft5B2yfSjIr5DYIt3av7pX47i/Rx/homI0edUZh4jPrzz2IH5NfXhvBeAWtf83mmlY7/cdlqp0UIQd0O",
    "region": "cn-hangzhou",
    "bucket": "xxx-xx.oss-cn-hangzhou.aliyuncs.com",
    "endpoint": "sts.cn-hangzhou.aliyuncs.com"
}

引用https://gosspublic.alicdn.com/aliyun-oss-sdk-6.17.0.min.js ,用了构建工具则npm i ali-oss使用其sdk,其仓库及文档地址,其中要注意的是,浏览器环境并不是所有的api都能使用的,很多方法调用后undefined就是用不了了:

  1. bucket相关的方法(如:listBuckets, putBucketLogging等)是用不了的。
  2. policy相关,calculatePostSignature等方法也用不了(6.17.0),所以想通过签名去上传也是行的。
  3. 是用sdk上传,其put(name, file[, options]),multipartUpload(name, file[, options])等方法都是通过put上传的,没有在控制台跨域配置勾选put则还是会报cors错误。
  4. 若使用multipartUpload上传,则一定条件下会自动分片,所以需在跨域配置中配置暴露header:ETag,否则也会报错。

使用element upload上传示例,可以同步显示上传进度

<template>
    <el-upload
        ref="tookitUploader"
        action=""
        :http-request="uploadFunction"
        :before-upload="beforeUpload"
        :on-exceed="OnExceed"
        name="file"
        :limit="1"
        >
            <el-button size="small">点击上传</el-button>
        <div slot="tip" class="el-upload__tip">支持扩展名:.rar .zip .exe</div>
    </el-upload>
</template>

<script>
// 初始化OSS
initOSS() {
    getOSSToken().then(async (res) => {
        const data = res.data
        const client = new OSS({
            region: 'oss-cn-hangzhou',
            accessKeyId: data.accessKeyId,
            accessKeySecret: data.accessKeySecret,
            stsToken: data.securityToken,
            bucket: 'xxx',
            refreshSTSToken: async () => {
                const data = getOSSToken()
                return {
                    accessKeyId: data.accessKeyId,
                    accessKeySecret: data.accessKeySecret,
                    stsToken: data.securityToken
                }
            },
            refreshSTSTokenInterval: 300000
        })

        this.oss = client
    })
},

// 重新的上传方法
uploadFunction(item) {
    this.oss.multipartUpload('/xxxy/' + item.file.name, item.file, {
        // 同步上传进度
        // http-request返回的参数中有用onProgress可以同步进度
        progress: function (p, checkpoint) {
            item.onProgress({ percent: Math.floor(p * 100) });
        }
    }).then((res) => {
        let url = res.res.requestUrls[0].split('?').pop();
        this.uploadForm.filePath = url
        this.uploadForm.attachList.push(url)
    }).catch(() => {
        this.$message.error('上传出错')
    })
},
</script>

三、前端签名,使用PostObject直接上传(可配合使用STS)

1.应用场景

适合使用第三方的上传插件,可通过URL直接传入到OSS,同时能使用插件的自带的功能,而不用重写其内部方法。

生成签名的方法:

import '@/utils/crypto1/crypto/crypto'
import '@/utils/crypto1/hmac/hmac'
import '@/utils/crypto1/sha1/sha1'
import { Base64 } from 'js-base64'

/**
 * 生成policy和签名
 * 默认1小时后失效
 * @param {*} accessKeySecret oss accessKeySecret
 * @param {*} expiration 过期时间 单位秒
 * @returns 
 */
export const genSignature = (accessKeySecret, expir = 600) => {
    
    const expiration = (new Date(Date.now() +  expir * 1000)).toISOString()
    const policy = {
        expiration: expiration, // 失效时间
        conditions: [
            ['content-length-range', 0, 469824028] // 设置上传文件的大小限制 1G
        ]
    }
    const policyBase64 = Base64.encode(JSON.stringify(policy))

    const bytes = Crypto.HMAC(Crypto.SHA1, policyBase64, accessKeySecret, { asBytes: true });
    const signature = Crypto.util.bytesToBase64(bytes);
    
    return {
        policy:policyBase64,
        signature
    }
}

使用该方法可以在前端生成policy,signature,然后作为参数传递给url

如在el-upload中使用:

<el-upload action="http://xxxx.oss-cn-hangzhou.aliyuncs.com/xxxx/test/" :data="uploadData"></el-upload>


<script>
...
// 根据调用后端sts接口获取accessKeySecret,生成policy,signature
getOSSPOSTData() {
            getOSSToken().then(async (res) => {
                const data = res.data

                const signatureInfo = genSignature(data.accessKeySecret)
                this.uploadData.OSSAccessKeyId = data.accessKeyId
                this.uploadData.accessKeySecret = data.accessKeySecret
                this.uploadData.policy = signatureInfo.policy
                this.uploadData.Signature = signatureInfo.signature
                this.uploadData['success_action_status'] = 200
                this.uploadData['x-oss-security-token'] = data.securityToken
            })
        },
</script>

PostObject文档: PostObject
crypto1文件: crypto1
注:crypto1如用不是阿里云提供的可能会计算出来的hmac不一致导致,签名失败。

四、直接在前端使用sdk进行上传

当然我们还可以直接就在前端页面中,使用阿里云提供的js的sdk就能直接进行上传了,不过如此就会把OSS相关的AccessKeyId,AccessKeySecret之类的暴露出来就是。避免方法就是按照官方的建议搭建STS服务并使用其返回的临时访问凭证访问OSS。这里就没有进行尝试了,详细可参考官方文档

下面是没有使用STS,直接在前端操作的相关代码(使用的VUE和element-UI):

点击查看代码
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>文件上传</title>
    <link
      rel="stylesheet"
      href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"
    />
    <script src="https://gosspublic.alicdn.com/aliyun-oss-sdk-6.17.0.min.js"></script>
  </head>

  <body>
    <div id="app">
      <el-button @click="visible = true">Button</el-button>
      <el-dialog :visible.sync="visible" title="文件上传">
        <el-upload
          class="upload-demo"
          action=""
          :http-request="upload"
          name="file"
          :on-preview="handlePreview"
          :on-remove="handleRemove"
          :before-remove="beforeRemove"
          multiple
          :limit="3"
          :on-exceed="handleExceed"
          :file-list="fileList"
        >
          <el-button size="small" type="primary">点击上传</el-button>
          <div slot="tip" class="el-upload__tip">想传啥就传啥</div>
        </el-upload>
      </el-dialog>
    </div>
  </body>
  <script src="https://unpkg.com/vue@2/dist/vue.js"></script>
  <script src="https://unpkg.com/element-ui/lib/index.js"></script>

  <script>
    new Vue({
      el: "#app",
      data: function () {
        return {
          visible: false,
          oss: "",
          fileList: [],
        };
      },
      created() {
        this.initOSS();
      },
      methods: {
        initOSS() {
          this.oss = new OSS({
            // 阿里云oss相关信息
            region: "ss-cn-hangzhou",
            accessKeyId: "LTAIxxxxxwxxn7SM",
            accessKeySecret: "zxxxxxxx6xxx8V",
            bucket: "zflxx",
          });
        },
        upload(item) {
          this.oss.multipartUpload(item.filename, item.file).then((res) => {
            console.log(res);
          });
        },
      },
    });
  </script>
</html>

五、中转上传到应用服务器再上传到阿里云OSS

因为使用的是php,所以这里用的是OSS PHP SDK,可以参考官方文档安装好SDK后,然后就能可以调用其SDK开始操作了。

// 使用上传到OSS
public function uploadoss() {
    $accessKeyId = "xxxcxvxve232"; ;
    $accessKeySecret = "xxxxxxx12121212";
    $bucket= "xxx";
    $endpoint = "oss-cn-beijing.aliyuncs.com";

    $upname = trim(I('upname', '', 'string'));
    if (!$upname) {
        $upname = trim(I('get.upname', '', 'string'));
    }

    if (!empty($_FILES[$upname]['name'])) {
        try { // 开启OSSClient
            $ossClient = new OssClient($accessKeyId, $accessKeySecret, $endpoint);
        } catch (OssException $e) {
            print $e -> getMessage();
        }

        $content = $_FILES[$upname];
        $info = pathinfo($content["name"]);
        $ext = $info['extension'];
        $file_name = date("YmdHis").time().mt_rand(100000, 999999). ".{$ext}";
        $object = "upload/".$file_name;

        try {
            $result = $ossClient -> uploadFile($bucket, $object, $content['tmp_name']);
            $ossUrl = $result['oss-request-url'];

        } catch (OssException $e) {
            echo $e;
        }
        echo json_encode(array('flag' => 1, 'msg' => 'OK', 'picUrl' => $picUrl));
    } else {
        echo json_encode(array('flag' => -1, 'msg' => 'No', 'picUrl' => ''));
    }
}

六、错误

  1. Please set the etag of expose-headers in OSS:https://help.aliyun.com/document_detail/277773.html

  2. CORS 错误:https://help.aliyun.com/document_detail/216832.html
    注使用(ali-oss)包put文件使用的是put方法,光勾选post还会报错。

  3. 使用element upload 与 ali-oss sdk时进行度获取
    https://github.com/ElemeFE/element/issues/9759

七、小结

  1. 使用php-sdk的话,流程是将文件上传到了服务器一次了,然后php在服务器中调用了阿里的sdk再将文件传到了OSS,小文件就无所谓了。要是上传大文件就很麻烦,服务器,php都得对大文件进行配置。过大的话还需要进行分片,可能才会有更好的上传体验。
  2. 使用后端签名,或者使用sts服务这种的,相当于是浏览器直接与阿里云进传输了,对大文件上传时比较友好的。
posted @ 2022-11-15 20:36  会飞的一棵树  阅读(18023)  评论(0编辑  收藏  举报