提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、前端直传的优点
- 二、实现步骤
- 2.1、后端方面
- 2.1.1 添加依赖
- 2.1.2 增加接口
- 2.1.3 测试接口
- 2.2、前端方面
- 2.2.1 安装 cos-js-sdk-v5 依赖
- 2.2.2 新建组件
- 2.2.3 使用组件
- 总结
前言
嗨,大家好,我是希留,一个被迫致力于全栈开发的老菜鸟。
上一篇文章说到上传文件使用云服务商的对象存储,感兴趣的可以阅读该文章:传送门
发布后有不少伙伴反馈,前后端分离的项目更好的上传方式是使用前端直传的方式。于是我查阅相关文档,连夜把项目里的上传方式改成前端直传了(项目的技术栈是Springboot + Vue),发现上传速度明显提升了。所以这篇文章就来说说,前端直传的方式应该怎么弄呢?
一、前端直传的优点
前端数据直传的方式相比较后端上传的方式有不少的优点。
- 上传速度快。后端上传的方式是用户数据需先上传到应用服务器,之后再上传到COS。而前端直传的方式,用户数据不用通过应用服务器中转,直传到COS。减少了网络请求响应,速度将大大提升。而且COS采用BGP带宽,能保证各地各运营商之间的传输速度。
- 扩展性好。后端上传的方式会占用带宽,文件上传一多就会占用服务器大量的带宽,导致其它请求阻塞,甚至无法访问等情况。前端直传的方式节约了后端服务器的带宽和负载。
- 成本低。服务器真正的成本基本上都在带宽上,提升带宽的费用是很贵的。而使用前端直传的方式可以减少文件上传到服务器的带宽,从而降低带宽成本。
二、实现步骤
2.1、后端方面
后端需要提供一个生成临时密钥的接口,具体步骤如下,可参考文档,由于我的后端服务是Java语言,所以这里举例使用的是 Java SDK。
2.1.1 添加依赖
<!--腾讯云 COS 临时密钥-->
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos-sts_api</artifactId>
<version>3.1.1</version>
</dependency>
2.1.2 增加接口
代码如下(示例):
package com.xiliu.common.controller;
import com.tencent.cloud.CosStsClient;
import com.tencent.cloud.Response;
import com.xiliu.common.result.R;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.TreeMap;
/**
* @author xiliu
* @description cos存储前端控制器
* @date 2022/11/13 17:51
*/
@RestController
@RequestMapping("/cos")
public class CosController {
@Value("${cos.secretId}")
private String secretId;
@Value("${cos.secretKey}")
private String secretKey;
@Value("${cos.regionName}")
private String regionName;
@Value("${cos.bucketName}")
private String bucketName;
@ApiOperation(value = "获取cos临时密钥")
@GetMapping("/temp-key")
public R getTempKey() {
TreeMap<String, Object> config = new TreeMap<String, Object>();
try {
// 替换为您的云 api 密钥 SecretId
config.put("secretId", secretId);
// 替换为您的云 api 密钥 SecretKey
config.put("secretKey", secretKey);
// 临时密钥有效时长,单位是秒,默认 1800 秒,目前主账号最长 2 小时(即 7200 秒),子账号最长 36 小时(即 129600)秒
config.put("durationSeconds", 1800);
// 换成您的 bucket
config.put("bucket", bucketName);
// 换成 bucket 所在地区
config.put("region", regionName);
// 只允许用户访问 upload/house 目录下的资源
config.put("allowPrefixes", new String[] {"upload/house/*"});
// 密钥的权限列表。必须在这里指定本次临时密钥所需要的权限。
String[] allowActions = new String[] {
// 简单上传
"name/cos:PutObject",
// 表单上传、小程序上传
"name/cos:PostObject",
// 分块上传
"name/cos:InitiateMultipartUpload",
"name/cos:ListMultipartUploads",
"name/cos:ListParts",
"name/cos:UploadPart",
"name/cos:CompleteMultipartUpload"
};
config.put("allowActions", allowActions);
Response response = CosStsClient.getCredential(config);
return R.ok(response);
} catch (Exception e) {
e.printStackTrace();
throw new IllegalArgumentException("no valid secret !");
}
}
}
2.1.3 测试接口
由于是测试,接口的安全校验我就去掉了,方便测试是否能正常生成临时密钥。结果如下图所示,正常生成了。
2.2、前端方面
前端方面,可以参考文档,我使用的前端框架是Vue,具体步骤如下,上传功能可以封装成一个组件,提供给有需要的地方调用。
2.2.1 安装 cos-js-sdk-v5 依赖
npm install --save cos-js-sdk-v5
2.2.2 新建组件
在 components 目录下新建一个 upload 目录,在 upload 目录下新建 multiUpload.vue 组件。
代码示例如下:
<template>
<div>
<el-upload
action=""
list-type="picture-card"
:file-list="fileList"
:before-upload="beforeUpload"
:on-remove="handleRemove"
:http-request="handleUploadFile"
:on-preview="handlePreview"
:limit="maxCount"
:on-exceed="handleExceed"
>
<i class="el-icon-plus"></i>
</el-upload>
<!--进度条-->
<el-progress v-show="showProgress" :text-inside="true" :stroke-width="15"
:percentage="progress" status="success"></el-progress>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="dialogImageUrl" alt="">
</el-dialog>
</div>
</template>
<script>
import COS from 'cos-js-sdk-v5'
import { getCosTempKey } from '@/api/upload'
export default {
name: 'multiUpload',
props: {
//图片属性数组
value: Array,
//最大上传图片数量
maxCount:{
type:Number,
default:9
}
},
data() {
return {
// 图片预览
dialogVisible: false,
// 图片预览地址
dialogImageUrl:null,
// COS
cosData: {},
// 进度条的显示
showProgress: false,
// 进度条数据
progress: 0,
// 文件表单
fileParams: {
// 上传的文件目录
folder: '/upload/house/'
},
};
},
computed: {
fileList() {
let fileList=[];
for(let i=0;i<this.value.length;i++){
fileList.push({url:this.value[i]});
}
return fileList;
}
},
methods: {
emitInput(fileList) {
let value=[];
for(let i=0;i<fileList.length;i++){
value.push(fileList[i].url);
}
this.$emit('input', value)
},
// 移除图片
handleRemove(file, fileList) {
this.emitInput(fileList);
},
// 预览图片
handlePreview(file) {
this.dialogVisible = true;
this.dialogImageUrl = file.url;
},
// 上传预处理
beforeUpload(file) {
return new Promise((resolve, reject) => {
const isImage = file.type.indexOf("image/") != -1;
const isLt2M = file.size / 1024 / 1024 < 10;
if (!isImage) {
this.$modal.msgError("文件格式错误,请上传图片类型,如:JPG,PNG后缀的文件。");
reject(false)
}
if (!isLt2M) {
this.$message.error("上传头像图片大小不能超过 10MB!");
reject(false)
}
// 获取cos临时密钥
getCosTempKey().then(response => {
this.cosData = response.data;
resolve(true)
}).catch(error => {
this.$message.error('获取cos临时密钥失败!msg:' + error)
reject(false)
})
})
},
// 前端直传文件
handleUploadFile(file) {
let that = this;
// 获取COS实例
const cos = new COS({
// 必选参数
getAuthorization: (options, callback) => {
const obj = {
TmpSecretId: that.cosData.credentials.tmpSecretId,
TmpSecretKey: that.cosData.credentials.tmpSecretKey,
XCosSecurityToken: that.cosData.credentials.sessionToken,
// 时间戳,单位秒,如:1580000000
StartTime: that.cosData.startTime,
// 时间戳,单位秒,如:1580000900
ExpiredTime: that.cosData.expiredTime
}
callback(obj)
}
});
// 文件路径和文件名
let cloudFilePath = this.fileParams.folder + `${+new Date()}` + '_' + file.file.name
// 执行上传服务
cos.putObject({
// 你的存储桶名称
Bucket: 'xiliu-1259663924',
// 你的存储桶地址
Region: 'ap-guangzhou',
// key加上路径写法可以生成文件夹
Key: cloudFilePath,
StorageClass: 'STANDARD',
// 上传文件对象
Body: file.file,
onProgress: progressData => {
if (progressData) {
that.showProgress = true
that.progress = Math.floor(progressData.percent * 100)
}
}
},(err, data) => {
if (data && data.statusCode === 200) {
let uploadResult = `https://${data.Location}`
that.showProgress = false
that.$message({message: '上传成功', type: 'success'})
this.fileList.push({name: file.name,url:uploadResult});
this.emitInput(this.fileList);
} else {
that.$message.error("上传失败,请稍后重试!")
}
})
},
// 文件超出个数限制
handleExceed(files, fileList) {
this.$message({
message: '最多只能上传'+this.maxCount+'张图片',
type: 'warning',
duration:1000
});
},
}
}
</script>
<style>
</style>
2.2.3 使用组件
代码示例如下:
<el-form-item label="房屋图片/视频:">
<multi-upload v-model="selectHousePics"></multi-upload>
</el-form-item>
效果如下图所示:
总结
以上就是本文的全部内容了,感谢大家的阅读。
如果觉得文章对你有帮助,还不忘帮忙点赞、收藏、关注、评论哟,您的支持就是我创作最大的动力!