参考原文地址:参考原文地址
阿里云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上传到阿里云,然后用分片上传的方法。
大体步骤:
- 阿里云添加RAM用户和相对应的角色,详见参考原文地址
- 后端java通过接口去获取sts凭证,并返回给前端
- 前端获取文件,通过阿里云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)。

然后当然是百度了,毕竟咱是基于百度开发的java程序员。
文章开头有STS临时授权访问OSS文档,通过java获取sts临时凭证,返回给前端,下面有java代码。
坑坑②
装个SDK都那么费劲,我很想哭的,开头有相关博客解决方法,"npm ERR! Error: EPERM: operation not permitted"问题解决。
我么,以管理员方式运行cmd,切换到项目目录,然后执行cnpm install ali-oss就可以了。
坑坑坑③
因为根据文首写的那个参考原文地址,是点击上传的时候再去获取STS Token,因为他用了异步async和await,反正我这是不行,创建OSSClient一直失败,因为没有获取到sts临时凭证,我这个半吊子到现在也没明白为什么异步和等待没起作用。
所以,我的方法是,点击上传的时候去获取sts临时授权,返回给前端,如果返回成功,再去创建OSSClient,如果创建成功,再去获取文件。详见下面indexBig.vue中的upload()方法。
坑坑坑坑④
这个上传的使用场景是这样的,页面有个新增按钮,新增弹框里面有上传方法。
页面是父页面,新增弹框是子页面,上传组件又是弹框中引入的,算是子页面的子组件。
因为上传成功会有提示,并且选择完文件会有文件名字,这些都需要在点开弹框的时候去清空。
详见tnews.vue中的addOrUpdateHandle()方法和tnews-add-or-update.vue的clearChildInfo()方法。
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>