vue+element+sts临时授权上传大文件到阿里云OSS时踩过的坑。

参考原文地址:参考原文地址
阿里云js分片上传官方文档:阿里云js分片上传官方文档
STS临时授权访问OSS:STS临时授权访问OSS
STS接入地址:STS接入地址
报错Error: Please set the etag of expose-headers in OSS解决:报错Error: Please set the etag of expose-headers in OSS
"npm ERR! Error: EPERM: operation not permitted"问题解决:"npm ERR! Error: EPERM: operation not permitted"问题解决

因为公司需求,需要上传七八百兆的视频文件,原先是前端上传到后端,然后通过后端接口上传到阿里云的,但是已不满足现在需求。
就想直接通过前端js上传到阿里云,然后用分片上传的方法。

大体步骤:

  1. 阿里云添加RAM用户和相对应的角色,详见参考原文地址
  2. 后端java通过接口去获取sts凭证,并返回给前端
  3. 前端获取文件,通过阿里云sdk方法直接上传。

想想还是简单的,不注意的坑倒是掉了一次又一次。

1.需要通过sts临时授权访问
2.安装阿里云oss 的SDK要用cnpm install ali-oss,并且提示没权限
3.实例化oss客户端对象的时候,通过接口获取sts,但是无法实例化oss对象
4.页面引入弹框组件,弹框又引入上传组件,无法在父页面清空上传组件的内容(父清空子的子)
5.region没复制全!!!!!!!!!!!!!!!!!!
6.etag没设置
7.分片上传返回的地址需要自己拼接,而不是直接一个地址

坑①

首先当然是参考官方文档了,但是却报以下警告(图中地址是:https://help.aliyun.com/document_detail/32077.html)。

未设置etag
然后当然是百度了,毕竟咱是基于百度开发的java程序员。
文章开头有STS临时授权访问OSS文档,通过java获取sts临时凭证,返回给前端,下面有java代码。

坑坑②

装个SDK都那么费劲,我很想哭的,开头有相关博客解决方法,"npm ERR! Error: EPERM: operation not permitted"问题解决。
我么,以管理员方式运行cmd,切换到项目目录,然后执行cnpm install ali-oss就可以了。

坑坑坑③

因为根据文首写的那个参考原文地址,是点击上传的时候再去获取STS Token,因为他用了异步asyncawait,反正我这是不行,创建OSSClient一直失败,因为没有获取到sts临时凭证,我这个半吊子到现在也没明白为什么异步和等待没起作用。
所以,我的方法是,点击上传的时候去获取sts临时授权,返回给前端,如果返回成功,再去创建OSSClient,如果创建成功,再去获取文件。详见下面indexBig.vue中的upload()方法。

坑坑坑坑④

这个上传的使用场景是这样的,页面有个新增按钮,新增弹框里面有上传方法。
页面是父页面,新增弹框是子页面,上传组件又是弹框中引入的,算是子页面的子组件。
因为上传成功会有提示,并且选择完文件会有文件名字,这些都需要在点开弹框的时候去清空。
详见tnews.vue中的addOrUpdateHandle()方法和tnews-add-or-update.vueclearChildInfo()方法。
clearChildInfo()中有个延时调用上传组件的clearInfo()方法,因为打开弹框组件的时候,其中的上传子组件并没有同时导入,而是有个先后顺序,虽然只差了几毫秒。

坑坑坑坑坑⑤

这个错真的是最最最最最坑的,之前后端有上传阿里云相关,然后也有相关配置文件,我么直接复制了regionId后面的值,万万没想到啊,缺了个oss-,真的是死活上传不了。
凡是有配置值参数的接口,一定要检查好参数值对不对,我真的是一个字母一个字母对的,才发现地址栏少了oss-

在这里插入图片描述
在这里插入图片描述

坑坑坑坑坑坑⑥

文首有报错Error: Please set the etag of expose-headers in OSS解决文档。

坑坑坑坑坑坑坑⑦

下图是返回结果的打印,分为普通上传和分片上传。分片上传需要自己拼接文件地址。

普通上传

分片上传

========================我是分割线,我想静静 ========================

以下是完整实例,用于上传上G的视频文件,回显也是用的video,本例设置60兆以上才调用分片上传,并且会有上传进度条,可根据需求修改。
todo的地方都要根据实际情况改,本人前几天刚搞完(本人java,几年没搞前端的我差点被vue搞死了)。

页面一个新增按钮
新增弹框有个上传按钮

获取sts临时凭证的方法:

	@RequestMapping(value = "/getStsKey",method = RequestMethod.POST)
	public R list(){
	   String endpoint = "sts.cn-hangzhou.aliyuncs.com";//STS接入地址//todo
	   String AccessKeyId = "********************";//有上传文件权限的key//todo
	   String accessKeySecret = "********************";//有上传文件权限的Secret//todo
	   String roleArn = "acs:ram::*****************:role/ramoss";//todo
	   String roleSessionName = "stsUploadRoleSession";//自定义
	   /*String policy = "{\n" +
	           "    \"Version\": \"1\", \n" +
	           "    \"Statement\": [\n" +
	           "        {\n" +
	           "            \"Action\": [\n" +
	           "                \"oss:*\"\n" +
	           "            ], \n" +
	           "            \"Resource\": [\n" +
	           "                \"acs:oss:*:*:*\" \n" +
	           "            ], \n" +
	           "            \"Effect\": \"Allow\"\n" +
	           "        }\n" +
	           "    ]\n" +
	           "}";*/
	   StsSecurityTokenEntity stsSecurityTokenEntity=new StsSecurityTokenEntity();
	   try {
	       // 添加endpoint(直接使用STS endpoint,前两个参数留空,无需添加region ID)
	       DefaultProfile.addEndpoint("", "", "Sts", endpoint);
	       // 构造default profile(参数留空,无需添加region ID)
	       IClientProfile profile = DefaultProfile.getProfile("", AccessKeyId, accessKeySecret);
	       // 用profile构造client
	       DefaultAcsClient client = new DefaultAcsClient(profile);
	       final AssumeRoleRequest request = new AssumeRoleRequest();
	       request.setMethod(MethodType.POST);
	       request.setRoleArn(roleArn);
	       request.setRoleSessionName(roleSessionName);
	       request.setPolicy(null); // 若policy为空,则用户将获得该角色下所有权限
	       request.setDurationSeconds(60*30L); // 设置凭证有效时间,我设置了30分钟,单位是秒
	       final AssumeRoleResponse response = client.getAcsResponse(request);
	       System.out.println("Expiration: " + response.getCredentials().getExpiration());
	       stsSecurityTokenEntity.setExpiration(response.getCredentials().getExpiration());
	       System.out.println("Access Key Id: " + response.getCredentials().getAccessKeyId());
	       stsSecurityTokenEntity.setAccessKeyId(response.getCredentials().getAccessKeyId());
	       System.out.println("Access Key Secret: " + response.getCredentials().getAccessKeySecret());
	       stsSecurityTokenEntity.setAccessKeySecret(response.getCredentials().getAccessKeySecret());
	       System.out.println("Security Token: " + response.getCredentials().getSecurityToken());
	       stsSecurityTokenEntity.setSecurityToken(response.getCredentials().getSecurityToken());
	       System.out.println("RequestId: " + response.getRequestId());
	       stsSecurityTokenEntity.setRequestId(response.getRequestId());
	   } catch (ClientException e) {
	       System.out.println("Failed:");
	       System.out.println("Error code: " + e.getErrCode());
	       System.out.println("Error message: " + e.getErrMsg());
	       System.out.println("RequestId: " + e.getRequestId());
	   }
	   return R.ok().put("stsSecurityTokenEntity",stsSecurityTokenEntity);
	}

sts临时凭证实体类对象StsSecurityTokenEntity.java

	/**
	 * STS临时授权访问OSS
	 */
	@Data
	@Accessors(chain = true)
	public class StsSecurityTokenEntity implements Serializable {
		private static final long serialVersionUID = 1L;
	
		private String accessKeyId;
		private String accessKeySecret;
		private String securityToken;
	    private String expiration;
	    private String requestId;
	}

页面tnews.vue

<template>
  <div class="mod-config">
    <el-form :inline="true">
      <el-form-item>
        <el-button type="primary" @click="addOrUpdateHandle()">新增</el-button>
      </el-form-item>
    </el-form>
    <!-- 弹窗, 新增 -->
    <add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate"></add-or-update>
  </div>
</template>

<script>
    import AddOrUpdate from './tnews-add-or-update' //此导入路径根据你文件实际路径来//todo

    export default {
        data() {
            return {
                addOrUpdateVisible: false
            }
        },
        components: {
            AddOrUpdate
        },
        methods: {
            // 新增
            addOrUpdateHandle() {
                this.addOrUpdateVisible = true
                this.$nextTick(() => {
                    this.$refs.addOrUpdate.init()
                    this.$refs.addOrUpdate.clearChildInfo()
                })
            },
        }
    }
</script>

弹框组件tnews-add-or-update.vue

<template>
  <el-dialog
    v-loading.fullscreen.lock="fullscreenLoading"
    title="新增"
    :close-on-click-modal="false"
    :visible.sync="visible">
    <el-form label-width="80px">
      <el-form-item label="上传视频" prop="video" >
        <BigMmp4 ref="bigUpload" v-model="videoUrl"></BigMmp4>
      </el-form-item>
    </el-form>
    <span slot="footer" class="dialog-footer">
      <el-button @click="visible = false">取消</el-button>
      <el-button type="primary" @click="dataSubmit()">确定</el-button>
    </span>
  </el-dialog>
</template>

<script>
  import BigMmp4 from './indexBig'  //此导入路径根据你文件实际路径来//todo
  export default {
    data () {
      return {
        fullscreenLoading: null,
        visible: false,
        videoUrl:""
      }
    },
    components: {
      BigMmp4,
    },
    methods: {
    	//延时调用上传子组件的清空方法
        clearChildInfo(){
            setTimeout(()=>{
                this.$refs.bigUpload.clearInfo()
            },500)
        },
        //点击弹框调用
        init () {
          this.visible = true
        },
        // 表单提交
        dataSubmit () {
            console.log(this.videoUrl)
        }
    }
  }
</script>

上传组件indexBig.vue

<template>
  <div>
    <input ref="bigMap4UploadFile" type="file" id='fileInput' multiple='true'>
    <el-button id="uploadBtn" @click="upload()" style="background-color: seagreen;color: white">上传</el-button>
    <el-button id="stopBtn"  @click="stop()" style="background-color: indianred;color: white">大文件停止上传</el-button>
    <h2 id='status'>{{statusMsg}}</h2>
    <video :src="videoUrl" class="avatar"></video>
    <el-progress :percentage="progressValue" v-show="showValue==1"></el-progress>
  </div>
</template>

<script>
    const OSS = require('ali-oss');
    var credentials = null; // STS凭证
    var ossClient =null ; // oss客户端实例
    const partSize = 1024 * 1024 * 60; // 每个分片大小(byte)-我设置了60兆
    const parallel = 5; // 同时上传的分片数
    const checkpoints = {}; // 所有分片上传文件的检查点
    export default {
        data() {
            return{
                statusMsg:"",
                progressValue:0,
                showValue:0,
            }
        },
      model: {
          prop: 'videoUrl',
          event: 'change'
      },
      props: {
        videoUrl: String
      },
      methods: {
      	  //清空
          clearInfo(){
              this.statusMsg=""
              this.$refs.bigMap4UploadFile.value=""
              this.progressValue=0
              this.showValue=0
          },
          //上传
          upload() {
              this.statusMsg = '上传中';
              this.$http({
                  url: "",//此处是后台接口去获取sts临时授权凭证//todo
                  method: 'post',
              }).then(({data}) => {
                  if (data && data.code === 0) {
                      credentials = data.stsSecurityTokenEntity;
                      let {accessKeyId , accessKeySecret, securityToken } = credentials;
                      ossClient = new OSS({
                          accessKeyId:accessKeyId,
                          accessKeySecret:accessKeySecret,
                          stsToken: securityToken,
                          bucket:"********",//todo
                          secure:true,
                          region:"***********",//todo
                          folder: "test"
                      });
                      if(ossClient){
                          var fileInput = document.getElementById('fileInput'); // 文件选择器
                          var { files } = fileInput;
                          var fileList = Array.from(files);
                          var uploadTasks = fileList.forEach(file => {
                              // 如果文件大学小于分片大小,使用普通上传,否则使用分片上传
                              if (file.size < partSize) {
                                  this.showValue=0
                                  this.commonUpload(file);
                              } else {
                                  this.showValue=1
                                  this.multipartUpload(file);
                              }
                          });
                      }else {
                          this.statusMsg = 'initOSSClient异常空,请刷新重试或联系管理员'
                      }
                  }else {
                      this.statusMsg = 'sts临时凭证获取失败,请刷新重试或联系管理员'
                  }
              }).catch(err => {
                  this.statusMsg = 'initOSSClient异常,请刷新重试或联系管理员'
              })
          },
          //添加文件夹名字
          getDateFolder(){
              let t = new Date();
              let timeStr = "";
              timeStr = timeStr + t.getFullYear();//年
              timeStr = timeStr +(t.getMonth()+1);//月,因为从0开始,所以需要加1
              timeStr = timeStr +t.getDate();//日
              return timeStr
          },
          // 普通上传
          commonUpload(file) {
              var fileName = file.name;
              var dateFolder=this.getDateFolder();
              return ossClient.put(dateFolder+"/"+new Date().getTime()+"_"+fileName, file).then(result => {
                  console.log(`Common upload ${file.name} succeeded, result === `, result)
                  this.statusMsg = '上传成功';
                  this.$emit('change',result.url)
              }).catch(err => {
                  this.statusMsg = '上传失败';
                  this.$emit('change',"")
                  console.log(`Common upload ${file.name} failed === `, err);
              });
          },
          // 分片上传
          multipartUpload(file) {
              var fileName = file.name;
              var dateFolder =this.getDateFolder();
              var dateStr=new Date().getTime();
              return ossClient.multipartUpload(dateFolder+"/"+dateStr+"_"+fileName, file, {
                  parallel,
                  partSize,
                  progress: this.onMultipartUploadProgress
              }).then(result => {
                  this.statusMsg = '上传成功';
                  console.log(result)
                  var url = `http://aaa.bbb.aliyuncs.com/${dateFolder}/${dateStr}_${fileName}`;//a是bucket,b是region。//todo
                  this.$emit('change',url)
              }).catch(err => {
                  this.statusMsg = '上传失败';
                  this.$emit('change',"")
                  console.log(`Multipart upload ${file.name} failed === `, err);
                  this.progressValue=0
                  this.showValue=0
              });
          },
          // 分片上传进度改变回调
          onMultipartUploadProgress(progress, checkpoint) {
              console.log(`${checkpoint.file.name} 上传进度 ${progress}`);
              checkpoints[checkpoint.uploadId] = checkpoint;
              this.progressValue= Math.round(progress*100)
          },
          // 停止上传
          stop() {
            this.statusMsg = '停止上传';
            if (ossClient){
                ossClient.cancel();
            } else {
                this.statusMsg = '停止失败';
            }
        }
      }
    }
</script>

<style scoped lang="scss">
  .avatar {
    width: 178px;
    height: 178px;
    display: block;
  }
</style>


版权声明:本文为aiguo94原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。